diff --git a/DEPS b/DEPS
index 7f4a758c..f04844e 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': 'd46697ac36d5cb3b58571c6129cb5b26fe9d25d7',
+  'skia_revision': 'b2cd1d7b442b689ff74409029defbf505c044b2c',
   # 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': '5ad29d77f4d1fcb589f1e332e1eb0fff13abe16c',
+  'v8_revision': 'd23043ad2a77a25575409fb5758a906a37aa2970',
   # 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.
@@ -363,7 +363,7 @@
       Var('chromium_git') + '/external/github.com/swisspol/GCDWebServer.git' + '@' + '43555c66627f6ed44817855a0f6d465f559d30e0',
 
     'src/ios/third_party/material_components_ios/src':
-      Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'd2e5a2a6564f086c906484e618f5883d1f42dee7',
+      Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '6b368e0c8ddadad4dadab58087fd83f6b7cf1389',
 
     'src/ios/third_party/material_font_disk_loader_ios/src':
       Var('chromium_git') + '/external/github.com/material-foundation/material-font-disk-loader-ios.git' + '@' + '93acc021e3034898716028822cb802a3a816be7e',
@@ -465,7 +465,7 @@
       Var('chromium_git') + '/external/android_protobuf.git' + '@' + '999188d0dc72e97f7fe08bb756958a2cf090f4e7',
 
     'src/third_party/android_tools':
-      Var('chromium_git') + '/android_tools.git' + '@' + 'e429db7f48cd615b0b408cda259ffbc17d3945bb',
+      Var('chromium_git') + '/android_tools.git' + '@' + 'b43a6a289a7588b1769814f04dd6c7d7176974cc',
 
     'src/third_party/apache-portable-runtime/src':
       Var('chromium_git') + '/external/apache-portable-runtime.git' + '@' + 'c76a8c4277e09a82eaa229e35246edea1ee0a6a1',
diff --git a/android_webview/test/shell/AndroidManifest.xml b/android_webview/test/shell/AndroidManifest.xml
index 3482a27..9d8e17c 100644
--- a/android_webview/test/shell/AndroidManifest.xml
+++ b/android_webview/test/shell/AndroidManifest.xml
@@ -11,6 +11,7 @@
   <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
   <uses-permission android:name="android.permission.INTERNET"/>
+  <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
   <uses-permission android:name="android.permission.WAKE_LOCK"/>
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
diff --git a/ash/common/DEPS b/ash/common/DEPS
index f127b5e0b..dbf6b2b 100644
--- a/ash/common/DEPS
+++ b/ash/common/DEPS
@@ -1,9 +1,4 @@
 include_rules = [
-  "+ash/ash_export.h",
-  "+ash/common",
-  "+ash/public",
-  "+ash/resources",
-  "+ash/shared",
   "+components/prefs",
   "+components/ui_devtools",
   "+mojo/public/cpp",
diff --git a/build/secondary/third_party/android_tools/BUILD.gn b/build/secondary/third_party/android_tools/BUILD.gn
index 7612541..a28fad5d 100644
--- a/build/secondary/third_party/android_tools/BUILD.gn
+++ b/build/secondary/third_party/android_tools/BUILD.gn
@@ -30,7 +30,7 @@
   ]
 }
 
-lib_version = "25.1.0"
+lib_version = "25.0.1"
 lib_path = "$android_sdk_root/extras/android/m2repository/com/android/support"
 
 android_java_prebuilt("android_gcm_java") {
diff --git a/cc/layers/texture_layer_unittest.cc b/cc/layers/texture_layer_unittest.cc
index f0a6802..0d98d5f 100644
--- a/cc/layers/texture_layer_unittest.cc
+++ b/cc/layers/texture_layer_unittest.cc
@@ -654,6 +654,69 @@
         force_disable_reclaim_resources);
   }
 
+  void AdvanceTestCase() {
+    ++test_case_;
+    switch (test_case_) {
+      case 1:
+        // Case #1: change mailbox before the commit. The old mailbox should be
+        // released immediately.
+        SetMailbox('2');
+        EXPECT_EQ(1, callback_count_);
+        PostSetNeedsCommitToMainThread();
+
+        // Case 2 does not rely on callbacks to advance.
+        pending_callback_ = false;
+        break;
+      case 2:
+        // Case #2: change mailbox after the commit (and draw), where the
+        // layer draws. The old mailbox should be released during the next
+        // commit.
+        SetMailbox('3');
+        EXPECT_EQ(1, callback_count_);
+
+        // Cases 3-5 rely on a callback to advance.
+        pending_callback_ = true;
+        break;
+      case 3:
+        EXPECT_EQ(2, callback_count_);
+        // Case #3: change mailbox when the layer doesn't draw. The old
+        // mailbox should be released during the next commit.
+        layer_->SetBounds(gfx::Size());
+        SetMailbox('4');
+        break;
+      case 4:
+        EXPECT_EQ(3, callback_count_);
+        // Case #4: release mailbox that was committed but never drawn. The
+        // old mailbox should be released during the next commit.
+        layer_->SetTextureMailbox(TextureMailbox(), nullptr);
+        break;
+      case 5:
+        EXPECT_EQ(4, callback_count_);
+        // Restore a mailbox for the next step.
+        SetMailbox('5');
+
+        // Cases 6 and 7 do not rely on callbacks to advance.
+        pending_callback_ = false;
+        break;
+      case 6:
+        // Case #5: remove layer from tree. Callback should *not* be called, the
+        // mailbox is returned to the main thread.
+        EXPECT_EQ(4, callback_count_);
+        layer_->RemoveFromParent();
+        break;
+      case 7:
+        EXPECT_EQ(4, callback_count_);
+        // Resetting the mailbox will call the callback now.
+        layer_->SetTextureMailbox(TextureMailbox(), nullptr);
+        EXPECT_EQ(5, callback_count_);
+        EndTest();
+        break;
+      default:
+        NOTREACHED();
+        break;
+    }
+  }
+
   // Make sure callback is received on main and doesn't block the impl thread.
   void ReleaseCallback(char mailbox_char,
                        const gpu::SyncToken& sync_token,
@@ -661,6 +724,10 @@
     EXPECT_EQ(true, main_thread_.CalledOnValidThread());
     EXPECT_FALSE(lost_resource);
     ++callback_count_;
+
+    // If we are waiting on a callback, advance now.
+    if (pending_callback_)
+      AdvanceTestCase();
   }
 
   void SetMailbox(char mailbox_char) {
@@ -696,58 +763,14 @@
     SetMailbox('1');
     EXPECT_EQ(0, callback_count_);
 
-    // Case #1: change mailbox before the commit. The old mailbox should be
-    // released immediately.
-    SetMailbox('2');
-    EXPECT_EQ(1, callback_count_);
-    PostSetNeedsCommitToMainThread();
+    // Setup is complete - advance to test case 1.
+    AdvanceTestCase();
   }
 
   void DidCommit() override {
-    ++commit_count_;
-    switch (commit_count_) {
-      case 1:
-        // Case #2: change mailbox after the commit (and draw), where the
-        // layer draws. The old mailbox should be released during the next
-        // commit.
-        SetMailbox('3');
-        EXPECT_EQ(1, callback_count_);
-        break;
-      case 2:
-        EXPECT_EQ(2, callback_count_);
-        // Case #3: change mailbox when the layer doesn't draw. The old
-        // mailbox should be released during the next commit.
-        layer_->SetBounds(gfx::Size());
-        SetMailbox('4');
-        break;
-      case 3:
-        EXPECT_EQ(3, callback_count_);
-        // Case #4: release mailbox that was committed but never drawn. The
-        // old mailbox should be released during the next commit.
-        layer_->SetTextureMailbox(TextureMailbox(), nullptr);
-        break;
-      case 4:
-        EXPECT_EQ(4, callback_count_);
-        // Restore a mailbox for the next step.
-        SetMailbox('5');
-        break;
-      case 5:
-        // Case #5: remove layer from tree. Callback should *not* be called, the
-        // mailbox is returned to the main thread.
-        EXPECT_EQ(4, callback_count_);
-        layer_->RemoveFromParent();
-        break;
-      case 6:
-        EXPECT_EQ(4, callback_count_);
-        // Resetting the mailbox will call the callback now.
-        layer_->SetTextureMailbox(TextureMailbox(), nullptr);
-        EXPECT_EQ(5, callback_count_);
-        EndTest();
-        break;
-      default:
-        NOTREACHED();
-        break;
-    }
+    // If we are not waiting on a callback, advance now.
+    if (!pending_callback_)
+      AdvanceTestCase();
   }
 
   void AfterTest() override {}
@@ -755,7 +778,9 @@
  private:
   base::ThreadChecker main_thread_;
   int callback_count_ = 0;
-  int commit_count_ = 0;
+  int test_case_ = 0;
+  // Whether we are waiting on a callback to advance the test case.
+  bool pending_callback_ = false;
   scoped_refptr<Layer> root_;
   scoped_refptr<TextureLayer> layer_;
 };
diff --git a/cc/output/context_cache_controller.cc b/cc/output/context_cache_controller.cc
index 79e85b3..aec9232 100644
--- a/cc/output/context_cache_controller.cc
+++ b/cc/output/context_cache_controller.cc
@@ -139,6 +139,12 @@
 }
 
 void ContextCacheController::OnIdle(uint32_t idle_generation) {
+// TODO(ericrk): Temporarily disabling this to investigate whether this
+// code regressed scroll latency on Android. crbug.com/664181
+#if defined(OS_ANDROID)
+  return;
+#endif
+
   // First check if we should run our idle callback at all. If we have become
   // busy since scheduling, just schedule another idle callback and return.
   {
diff --git a/cc/output/context_cache_controller_unittest.cc b/cc/output/context_cache_controller_unittest.cc
index 35e1e25a..5a404a2 100644
--- a/cc/output/context_cache_controller_unittest.cc
+++ b/cc/output/context_cache_controller_unittest.cc
@@ -54,6 +54,11 @@
 }
 
 TEST(ContextCacheControllerTest, ScopedBusyWhileVisible) {
+// TODO(ericrk): Temporarily disabling this to investigate whether this
+// code regressed scroll latency on Android. crbug.com/664181
+#if defined(OS_ANDROID)
+  return;
+#endif
   StrictMock<MockContextSupport> context_support;
   auto task_runner = make_scoped_refptr(new base::TestMockTimeTaskRunner);
   ContextCacheController cache_controller(&context_support, task_runner);
@@ -76,6 +81,11 @@
 }
 
 TEST(ContextCacheControllerTest, ScopedBusyWhileNotVisible) {
+// TODO(ericrk): Temporarily disabling this to investigate whether this
+// code regressed scroll latency on Android. crbug.com/664181
+#if defined(OS_ANDROID)
+  return;
+#endif
   StrictMock<MockContextSupport> context_support;
   auto task_runner = make_scoped_refptr(new base::TestMockTimeTaskRunner);
   ContextCacheController cache_controller(&context_support, task_runner);
@@ -88,6 +98,11 @@
 }
 
 TEST(ContextCacheControllerTest, ScopedBusyMulitpleWhileVisible) {
+// TODO(ericrk): Temporarily disabling this to investigate whether this
+// code regressed scroll latency on Android. crbug.com/664181
+#if defined(OS_ANDROID)
+  return;
+#endif
   StrictMock<MockContextSupport> context_support;
   auto task_runner = make_scoped_refptr(new base::TestMockTimeTaskRunner);
   ContextCacheController cache_controller(&context_support, task_runner);
diff --git a/chrome/android/java/res/layout/bottom_control_container.xml b/chrome/android/java/res/layout/bottom_control_container.xml
index 1153e0a..b5ebc54a 100644
--- a/chrome/android/java/res/layout/bottom_control_container.xml
+++ b/chrome/android/java/res/layout/bottom_control_container.xml
@@ -43,10 +43,9 @@
         </view>
     </org.chromium.chrome.browser.toolbar.ToolbarControlContainer>
 
-    <View
+    <FrameLayout
         android:id="@+id/bottom_sheet_content"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="@android:color/white" />
+        android:layout_height="match_parent" />
 
 </org.chromium.chrome.browser.widget.BottomSheet>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index 8a40c13..6d8f1ea 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -336,6 +336,10 @@
                     getTabModelSelector(),
                     getControlContainerHeightResource());
         }
+
+        if (mBottomSheet != null) {
+            mBottomSheet.setTabModelSelector(mTabModelSelector);
+        }
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 83b9b37..94f36e93 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -12,6 +12,7 @@
 import android.app.ActivityManager.RecentTaskInfo;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.graphics.Color;
 import android.os.Build;
 import android.os.Bundle;
@@ -35,6 +36,7 @@
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.BuildInfo;
 import org.chromium.base.CommandLine;
+import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.MemoryPressureListener;
 import org.chromium.base.TraceEvent;
@@ -90,6 +92,7 @@
 import org.chromium.chrome.browser.preferences.ChromePreferenceManager;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
 import org.chromium.chrome.browser.preferences.datareduction.DataReductionPromoScreen;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.signin.SigninPromoUtil;
 import org.chromium.chrome.browser.snackbar.undo.UndoBarController;
 import org.chromium.chrome.browser.suggestions.ContentSuggestionsActivity;
@@ -128,7 +131,9 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Method;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * This is the main activity for ChromeMobile when not running in document mode.  All the tabs
@@ -414,11 +419,67 @@
         }
     }
 
+    /**
+     * Determine whether the incognito profile needs to be destroyed as part of startup.  This is
+     * only needed on L+ when it is possible to swipe away tasks from Android recents without
+     * killing the process.  When this occurs, the normal incognito profile shutdown does not
+     * happen, which can leave behind incognito cookies from an existing session.
+     */
+    @SuppressLint("NewApi")
+    private boolean shouldDestroyIncognitoProfile() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return false;
+
+        Context context = ContextUtils.getApplicationContext();
+        ActivityManager manager =
+                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        PackageManager pm = context.getPackageManager();
+
+        Set<Integer> tabbedModeTaskIds = new HashSet<>();
+        for (AppTask task : manager.getAppTasks()) {
+            RecentTaskInfo info = DocumentUtils.getTaskInfoFromTask(task);
+            if (info == null) continue;
+            String className = DocumentUtils.getTaskClassName(task, pm);
+
+            if (TextUtils.equals(className, ChromeTabbedActivity.class.getName())) {
+                tabbedModeTaskIds.add(info.id);
+            }
+        }
+
+        if (tabbedModeTaskIds.size() == 0) {
+            return Profile.getLastUsedProfile().hasOffTheRecordProfile();
+        }
+
+        List<WeakReference<Activity>> activities = ApplicationStatus.getRunningActivities();
+        for (int i = 0; i < activities.size(); i++) {
+            Activity activity = activities.get(i).get();
+            if (activity == null) continue;
+            tabbedModeTaskIds.remove(activity.getTaskId());
+        }
+
+        // If all tabbed mode tasks listed in Android recents are alive, check to see if
+        // any have incognito tabs exist.  If all are alive and no tabs exist, we should ensure that
+        // we delete the incognito profile if one is around still.
+        if (tabbedModeTaskIds.size() == 0) {
+            return TabWindowManager.getInstance().getIncognitoTabCount() == 0
+                    && Profile.getLastUsedProfile().hasOffTheRecordProfile();
+        }
+
+        // In this case, we have tabbed mode activities listed in recents that do not have an
+        // active running activity associated with them.  We can not accurately get an incognito
+        // tab count as we do not know if any incognito tabs are associated with the yet unrestored
+        // tabbed mode.  Thus we do not proactivitely destroy the incognito profile.
+        return false;
+    }
+
     @Override
     public void onResumeWithNative() {
         super.onResumeWithNative();
 
-        CookiesFetcher.restoreCookies(this);
+        if (shouldDestroyIncognitoProfile()) {
+            Profile.getLastUsedProfile().getOffTheRecordProfile().destroyWhenAppropriate();
+        } else {
+            CookiesFetcher.restoreCookies(this);
+        }
         StartupMetrics.getInstance().recordHistogram(false);
 
         if (FeatureUtilities.isTabModelMergingEnabled()) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationService.java b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationService.java
index fe76018..6135898 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationService.java
@@ -23,12 +23,15 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.TabState;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.document.DocumentUtils;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tabmodel.TabWindowManager;
 import org.chromium.chrome.browser.tabmodel.TabbedModeTabPersistencePolicy;
+import org.chromium.content.browser.BrowserStartupController;
 
 import java.io.File;
 import java.lang.ref.WeakReference;
@@ -72,10 +75,18 @@
             @Override
             public void run() {
                 int incognitoCount = TabWindowManager.getInstance().getIncognitoTabCount();
-                assert incognitoCount == 0;
+                if (incognitoCount != 0) {
+                    assert false : "Not all incognito tabs closed as expected";
+                    return;
+                }
+                IncognitoNotificationManager.dismissIncognitoNotification();
 
-                if (incognitoCount == 0) {
-                    IncognitoNotificationManager.dismissIncognitoNotification();
+                if (BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
+                        .isStartupSuccessfullyCompleted()) {
+                    if (Profile.getLastUsedProfile().hasOffTheRecordProfile()) {
+                        Profile.getLastUsedProfile().getOffTheRecordProfile()
+                                .destroyWhenAppropriate();
+                    }
                 }
             }
         });
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/MarketURLGetter.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/MarketURLGetter.java
index fb5338a..aba516c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/MarketURLGetter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/MarketURLGetter.java
@@ -8,12 +8,39 @@
 import android.content.SharedPreferences;
 import android.os.Looper;
 
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
+
 /**
  * Grabs the URL that points to the Android Market page for Chrome.
  * This incurs I/O, so don't use it from the main thread.
  */
 public class MarketURLGetter {
-    public String getMarketURL(
+
+    private static final class LazyHolder {
+        private static final MarketURLGetter INSTANCE = new MarketURLGetter();
+    }
+
+    /** See {@link #getMarketUrl(Context, String, String)} */
+    static String getMarketUrl(Context context) {
+        assert !ThreadUtils.runningOnUiThread();
+        MarketURLGetter instance =
+                sInstanceForTests == null ? LazyHolder.INSTANCE : sInstanceForTests;
+        return instance.getMarketUrl(
+                context, OmahaClient.PREF_PACKAGE, OmahaClient.PREF_MARKET_URL);
+    }
+
+    @VisibleForTesting
+    static void setInstanceForTests(MarketURLGetter getter) {
+        sInstanceForTests = getter;
+    }
+
+    private static MarketURLGetter sInstanceForTests;
+
+    protected MarketURLGetter() { }
+
+    /** Returns the Play Store URL that points to Chrome. */
+    public String getMarketUrl(
             Context applicationContext, String prefPackage, String prefMarketUrl) {
         assert Looper.myLooper() != Looper.getMainLooper();
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClient.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClient.java
index 3fc80889..84a07b6c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClient.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaClient.java
@@ -17,8 +17,8 @@
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Log;
+import org.chromium.base.StreamUtil;
 import org.chromium.base.VisibleForTesting;
-import org.chromium.base.annotations.SuppressFBWarnings;
 import org.chromium.chrome.browser.ChromeApplication;
 import org.chromium.chrome.browser.ChromeVersionInfo;
 
@@ -103,7 +103,7 @@
     static final String INSTALL_SOURCE_ORGANIC = "organic";
 
     // Lock object used to synchronize all calls that modify or read sIsFreshInstallOrDataCleared.
-    private static final Object sIsFreshInstallLock = new Object();
+    private static final Object IS_FRESH_INSTALL_LOCK = new Object();
 
     @VisibleForTesting
     static final String PREF_LATEST_VERSION = "latestVersion";
@@ -116,9 +116,6 @@
 
     // Static fields
     private static boolean sEnableCommunication = true;
-    private static boolean sEnableUpdateDetection = true;
-    private static VersionNumberGetter sVersionNumberGetter;
-    private static MarketURLGetter sMarketURLGetter;
     private static Boolean sIsFreshInstallOrDataCleared;
 
     // Member fields not persisted to disk.
@@ -152,14 +149,6 @@
         sEnableCommunication = state;
     }
 
-    /**
-     * If false, OmahaClient will never report that a newer version is available.
-     */
-    @VisibleForTesting
-    public static void setEnableUpdateDetection(boolean state) {
-        sEnableUpdateDetection = state;
-    }
-
     @VisibleForTesting
     long getTimestampForNextPostAttempt() {
         return mTimestampForNextPostAttempt;
@@ -243,7 +232,7 @@
      * Start a recurring alarm to fire request generation intents.
      */
     private void handleInitialize() {
-        scheduleRepeatingAlarm();
+        scheduleActiveUserCheck();
 
         // If a request exists, kick off POSTing it to the server immediately.
         if (hasRequest()) handlePostRequest();
@@ -264,7 +253,7 @@
      */
     private void handleRegisterRequest() {
         if (!isChromeBeingUsed()) {
-            cancelRepeatingAlarm();
+            unscheduleActiveUserCheck();
             return;
         }
 
@@ -319,9 +308,7 @@
 
             result = succeeded ? POST_RESULT_SENT : POST_RESULT_FAILED;
         } else {
-            // Set an alarm to POST at the proper time.  Previous alarms are destroyed.
-            Intent postIntent = createPostRequestIntent(this);
-            getBackoffScheduler().createAlarm(postIntent, mTimestampForNextPostAttempt);
+            schedulePost();
             result = POST_RESULT_SCHEDULED;
         }
 
@@ -330,22 +317,40 @@
         return result;
     }
 
+    /** Set an alarm to POST at the proper time.  Previous alarms are destroyed. */
+    private void schedulePost() {
+        Intent postIntent = createPostRequestIntent(this);
+        getBackoffScheduler().createAlarm(postIntent, mTimestampForNextPostAttempt);
+    }
+
     private boolean generateAndPostRequest(long currentTimestamp, String sessionID) {
         ExponentialBackoffScheduler scheduler = getBackoffScheduler();
         try {
             // Generate the XML for the current request.
             long installAgeInDays = RequestGenerator.installAge(currentTimestamp,
                     mTimestampOfInstall, mCurrentRequest.isSendInstallEvent());
-            String version = getVersionNumberGetter().getCurrentlyUsedVersion(this);
+            String version = VersionNumberGetter.getInstance().getCurrentlyUsedVersion(this);
             String xml = getRequestGenerator().generateXML(
                     sessionID, version, installAgeInDays, mCurrentRequest);
 
             // Send the request to the server & wait for a response.
             String response = postRequest(currentTimestamp, xml);
-            parseServerResponse(response);
+
+            // Parse out the response.
+            String appId = getRequestGenerator().getAppId();
+            boolean sentPingAndUpdate = !mSendInstallEvent;
+            ResponseParser parser = new ResponseParser(
+                    appId, mSendInstallEvent, sentPingAndUpdate, sentPingAndUpdate);
+            parser.parseResponse(response);
+            mLatestVersion = parser.getNewVersion();
+            mMarketURL = parser.getURL();
 
             // If we've gotten this far, we've successfully sent a request.
             mCurrentRequest = null;
+
+            mTimestampForNewRequest = getBackoffScheduler().getCurrentTime() + MS_BETWEEN_REQUESTS;
+            scheduleActiveUserCheck();
+
             mTimestampForNextPostAttempt = currentTimestamp + MS_POST_BASE_DELAY;
             scheduler.resetFailedAttempts();
             Log.i(TAG, "Request to Server Successful. Timestamp for next request:"
@@ -367,11 +372,11 @@
      * Setting the alarm overwrites whatever alarm is already there, and rebooting
      * clears whatever alarms are currently set.
      */
-    private void scheduleRepeatingAlarm() {
+    private void scheduleActiveUserCheck() {
         Intent registerIntent = createRegisterRequestIntent(this);
         PendingIntent pIntent = PendingIntent.getService(this, 0, registerIntent, 0);
         AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
-        setAlarm(am, pIntent, AlarmManager.RTC, mTimestampForNewRequest);
+        setAlarm(am, pIntent, mTimestampForNewRequest);
     }
 
     /**
@@ -379,8 +384,7 @@
      * Override to prevent a real alarm from being set.
      */
     @VisibleForTesting
-    protected void setAlarm(AlarmManager am, PendingIntent operation, int alarmType,
-            long triggerAtTime) {
+    protected void setAlarm(AlarmManager am, PendingIntent operation, long triggerAtTime) {
         try {
             am.setRepeating(AlarmManager.RTC, triggerAtTime, MS_BETWEEN_REQUESTS, operation);
         } catch (SecurityException e) {
@@ -391,7 +395,7 @@
     /**
      * Cancels the alarm that launches this service.  It will be replaced when Chrome next resumes.
      */
-    private void cancelRepeatingAlarm() {
+    private void unscheduleActiveUserCheck() {
         Intent requestIntent = createRegisterRequestIntent(this);
         PendingIntent pendingIntent =
                 PendingIntent.getService(this, 0, requestIntent, PendingIntent.FLAG_NO_CREATE);
@@ -429,7 +433,7 @@
         // Tentatively set the timestamp for a new request.  This will be updated when the server
         // is successfully contacted.
         mTimestampForNewRequest = currentTimestamp + MS_BETWEEN_REQUESTS;
-        scheduleRepeatingAlarm();
+        scheduleActiveUserCheck();
 
         saveState();
     }
@@ -462,9 +466,23 @@
         HttpURLConnection urlConnection = null;
         try {
             urlConnection = createConnection();
-            setUpPostRequest(timestamp, urlConnection, xml);
+
+            // Prepare the HTTP header.
+            urlConnection.setDoOutput(true);
+            urlConnection.setFixedLengthStreamingMode(xml.getBytes().length);
+            if (mSendInstallEvent && getCumulativeFailedAttempts() > 0) {
+                String age = Long.toString(mCurrentRequest.getAgeInSeconds(timestamp));
+                urlConnection.addRequestProperty("X-RequestAge", age);
+            }
+
             sendRequestToServer(urlConnection, xml);
             response = readResponseFromServer(urlConnection);
+        } catch (IllegalAccessError e) {
+            throw new RequestFailureException("Caught an IllegalAccessError:", e);
+        } catch (IllegalArgumentException e) {
+            throw new RequestFailureException("Caught an IllegalArgumentException:", e);
+        } catch (IllegalStateException e) {
+            throw new RequestFailureException("Caught an IllegalStateException:", e);
         } finally {
             if (urlConnection != null) {
                 urlConnection.disconnect();
@@ -475,21 +493,6 @@
     }
 
     /**
-     * Parse the server's response and confirm that we received an OK response.
-     */
-    private void parseServerResponse(String response) throws RequestFailureException {
-        String appId = getRequestGenerator().getAppId();
-        boolean sentPingAndUpdate = !mSendInstallEvent;
-        ResponseParser parser =
-                new ResponseParser(appId, mSendInstallEvent, sentPingAndUpdate, sentPingAndUpdate);
-        parser.parseResponse(response);
-        mTimestampForNewRequest = getBackoffScheduler().getCurrentTime() + MS_BETWEEN_REQUESTS;
-        mLatestVersion = parser.getNewVersion();
-        mMarketURL = parser.getURL();
-        scheduleRepeatingAlarm();
-    }
-
-    /**
      * Returns a HttpURLConnection to the server.
      */
     @VisibleForTesting
@@ -508,36 +511,15 @@
     }
 
     /**
-     * Prepares the HTTP header.
-     */
-    private void setUpPostRequest(long timestamp, HttpURLConnection urlConnection, String xml)
-            throws RequestFailureException {
-        try {
-            urlConnection.setDoOutput(true);
-            urlConnection.setFixedLengthStreamingMode(xml.getBytes().length);
-            if (mSendInstallEvent && getCumulativeFailedAttempts() > 0) {
-                String age = Long.toString(mCurrentRequest.getAgeInSeconds(timestamp));
-                urlConnection.addRequestProperty("X-RequestAge", age);
-            }
-        } catch (IllegalAccessError e) {
-            throw new RequestFailureException("Caught an IllegalAccessError:", e);
-        } catch (IllegalArgumentException e) {
-            throw new RequestFailureException("Caught an IllegalArgumentException:", e);
-        } catch (IllegalStateException e) {
-            throw new RequestFailureException("Caught an IllegalStateException:", e);
-        }
-    }
-
-    /**
      * Sends the request to the server.
      */
-    private void sendRequestToServer(HttpURLConnection urlConnection, String xml)
+    private static void sendRequestToServer(HttpURLConnection urlConnection, String xml)
             throws RequestFailureException {
         try {
             OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
             OutputStreamWriter writer = new OutputStreamWriter(out);
             writer.write(xml, 0, xml.length());
-            writer.close();
+            StreamUtil.closeQuietly(writer);
             checkServerResponseCode(urlConnection);
         } catch (IOException e) {
             throw new RequestFailureException("Failed to write request to server: ", e);
@@ -547,7 +529,7 @@
     /**
      * Reads the response from the Omaha Server.
      */
-    private String readResponseFromServer(HttpURLConnection urlConnection)
+    private static String readResponseFromServer(HttpURLConnection urlConnection)
             throws RequestFailureException {
         try {
             InputStreamReader reader = new InputStreamReader(urlConnection.getInputStream());
@@ -560,7 +542,7 @@
                 checkServerResponseCode(urlConnection);
                 return response.toString();
             } finally {
-                in.close();
+                StreamUtil.closeQuietly(in);
             }
         } catch (IOException e) {
             throw new RequestFailureException("Failed when reading response from server: ", e);
@@ -570,7 +552,7 @@
     /**
      * Confirms that the Omaha server sent back an "OK" code.
      */
-    private void checkServerResponseCode(HttpURLConnection urlConnection)
+    private static void checkServerResponseCode(HttpURLConnection urlConnection)
             throws RequestFailureException {
         try {
             if (urlConnection.getResponseCode() != 200) {
@@ -584,54 +566,6 @@
     }
 
     /**
-     * Checks if we know about a newer version available than the one we're using.  This does not
-     * actually fire any requests over to the server; it just checks the version we stored the last
-     * time we talked to the Omaha server.
-     *
-     * NOTE: This function incurs I/O, so don't use it on the main thread.
-     */
-    static boolean isNewerVersionAvailable(Context context) {
-        assert Looper.myLooper() != Looper.getMainLooper();
-
-        // This may be explicitly enabled for some channels and for unit tests.
-        if (!sEnableUpdateDetection) {
-            return false;
-        }
-
-        // If the market link is bad, don't show an update to avoid frustrating users trying to
-        // hit the "Update" button.
-        if ("".equals(getMarketURL(context))) {
-            return false;
-        }
-
-        // Compare version numbers.
-        VersionNumberGetter getter = getVersionNumberGetter();
-        String currentStr = getter.getCurrentlyUsedVersion(context);
-        String latestStr = getter.getLatestKnownVersion(context, PREF_PACKAGE, PREF_LATEST_VERSION);
-
-        VersionNumber currentVersionNumber = VersionNumber.fromString(currentStr);
-        VersionNumber latestVersionNumber = VersionNumber.fromString(latestStr);
-
-        if (currentVersionNumber == null || latestVersionNumber == null) {
-            return false;
-        }
-
-        return currentVersionNumber.isSmallerThan(latestVersionNumber);
-    }
-
-    /**
-     * Retrieves the latest version we know about from disk.
-     * This function incurs I/O, so make sure you don't use it from the main thread.
-     *
-     * @return A string representing the latest version.
-     */
-    static String getLatestVersionNumberString(Context context) {
-        assert Looper.myLooper() != Looper.getMainLooper();
-        VersionNumberGetter getter = getVersionNumberGetter();
-        return getter.getLatestKnownVersion(context, PREF_PACKAGE, PREF_LATEST_VERSION);
-    }
-
-    /**
      * Determine how the Chrome APK arrived on the device.
      * @param context Context to pull resources from.
      * @return A String indicating the install source.
@@ -750,44 +684,6 @@
     }
 
     /**
-     * Sets the VersionNumberGetter used to get version numbers.  Set a new one to override what
-     * version numbers are returned.
-     */
-    @VisibleForTesting
-    static void setVersionNumberGetterForTests(VersionNumberGetter getter) {
-        sVersionNumberGetter = getter;
-    }
-
-    @SuppressFBWarnings("LI_LAZY_INIT_STATIC")
-    @VisibleForTesting
-    static VersionNumberGetter getVersionNumberGetter() {
-        if (sVersionNumberGetter == null) {
-            sVersionNumberGetter = new VersionNumberGetter();
-        }
-        return sVersionNumberGetter;
-    }
-
-    /**
-     * Sets the MarketURLGetter used to get version numbers.  Set a new one to override what
-     * URL is returned.
-     */
-    @VisibleForTesting
-    static void setMarketURLGetterForTests(MarketURLGetter getter) {
-        sMarketURLGetter = getter;
-    }
-
-    /**
-     * Returns the stub used to grab the market URL for Chrome.
-     */
-    @SuppressFBWarnings("LI_LAZY_INIT_STATIC")
-    public static String getMarketURL(Context context) {
-        if (sMarketURLGetter == null) {
-            sMarketURLGetter = new MarketURLGetter();
-        }
-        return sMarketURLGetter.getMarketURL(context, PREF_PACKAGE, PREF_MARKET_URL);
-    }
-
-    /**
      * Pulls a long from the shared preferences map.
      */
     private static long getLongFromMap(final Map<String, ?> items, String key, long defaultValue) {
@@ -826,7 +722,7 @@
     }
 
     private static boolean setIsFreshInstallOrDataHasBeenCleared(Context context) {
-        synchronized (sIsFreshInstallLock) {
+        synchronized (IS_FRESH_INSTALL_LOCK) {
             if (sIsFreshInstallOrDataCleared == null) {
                 SharedPreferences prefs = context.getSharedPreferences(
                         PREF_PACKAGE, Context.MODE_PRIVATE);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java
index b8fab1c..fbccf8637 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelper.java
@@ -120,9 +120,10 @@
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
-                if (OmahaClient.isNewerVersionAvailable(activity)) {
-                    mUpdateUrl = OmahaClient.getMarketURL(activity);
-                    mLatestVersion = OmahaClient.getLatestVersionNumberString(activity);
+                if (VersionNumberGetter.isNewerVersionAvailable(activity)) {
+                    mUpdateUrl = MarketURLGetter.getMarketUrl(activity);
+                    mLatestVersion =
+                            VersionNumberGetter.getInstance().getLatestKnownVersion(activity);
                     mUpdateAvailable = true;
                     recordInternalStorageSize();
                 } else {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/VersionNumberGetter.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/VersionNumberGetter.java
index 44789204..a4edac5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/VersionNumberGetter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/VersionNumberGetter.java
@@ -6,25 +6,54 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.os.Looper;
 
 import org.chromium.base.BuildInfo;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
 
 /**
  * Stubbed class for getting version numbers from the rest of Chrome.  Override the functions for
  * unit tests.
  */
 public class VersionNumberGetter {
+
+    private static final class LazyHolder {
+        private static final VersionNumberGetter INSTANCE = new VersionNumberGetter();
+    }
+
+    @VisibleForTesting
+    static VersionNumberGetter getInstance() {
+        assert !ThreadUtils.runningOnUiThread();
+        return sInstanceForTests == null ? LazyHolder.INSTANCE : sInstanceForTests;
+    }
+
+    @VisibleForTesting
+    static void setInstanceForTests(VersionNumberGetter getter) {
+        sInstanceForTests = getter;
+    }
+
+    @VisibleForTesting
+    public static void setEnableUpdateDetection(boolean state) {
+        sEnableUpdateDetection = state;
+    }
+
+    private static VersionNumberGetter sInstanceForTests;
+
+    /** If false, OmahaClient will never report that a newer version is available. */
+    private static boolean sEnableUpdateDetection = true;
+
+    protected VersionNumberGetter() { }
+
     /**
      * Retrieve the latest version we know about from disk.
      * This function incurs I/O, so make sure you don't use it from the main thread.
      * @return The latest version if we retrieved one from the Omaha server, or "" if we haven't.
      */
-    public String getLatestKnownVersion(
-            Context context, String prefPackage, String prefLatestVersion) {
-        assert Looper.myLooper() != Looper.getMainLooper();
-        SharedPreferences prefs = context.getSharedPreferences(prefPackage, Context.MODE_PRIVATE);
-        return prefs.getString(prefLatestVersion, "");
+    public String getLatestKnownVersion(Context context) {
+        assert !ThreadUtils.runningOnUiThread();
+        SharedPreferences prefs =
+                context.getSharedPreferences(OmahaClient.PREF_PACKAGE, Context.MODE_PRIVATE);
+        return prefs.getString(OmahaClient.PREF_LATEST_VERSION, "");
     }
 
     /**
@@ -61,4 +90,40 @@
             throw new IllegalArgumentException("Application version incorrectly formatted");
         }
     }
+
+    /**
+     * Checks if we know about a newer version available than the one we're using.  This does not
+     * actually fire any requests over to the server: it just checks the version we stored the last
+     * time we talked to the Omaha server.
+     *
+     * NOTE: This function incurs I/O, so don't use it on the main thread.
+     */
+    static boolean isNewerVersionAvailable(Context context) {
+        assert !ThreadUtils.runningOnUiThread();
+
+        // This may be explicitly enabled for some channels and for unit tests.
+        if (!sEnableUpdateDetection) {
+            return false;
+        }
+
+        // If the market link is bad, don't show an update to avoid frustrating users trying to
+        // hit the "Update" button.
+        if ("".equals(MarketURLGetter.getMarketUrl(context))) {
+            return false;
+        }
+
+        // Compare version numbers.
+        VersionNumberGetter getter = getInstance();
+        String currentStr = getter.getCurrentlyUsedVersion(context);
+        String latestStr = getter.getLatestKnownVersion(context);
+
+        VersionNumber currentVersionNumber = VersionNumber.fromString(currentStr);
+        VersionNumber latestVersionNumber = VersionNumber.fromString(latestStr);
+
+        if (currentVersionNumber == null || latestVersionNumber == null) {
+            return false;
+        }
+
+        return currentVersionNumber.isSmallerThan(latestVersionNumber);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/util/MathUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/util/MathUtils.java
index 07ac76e..77f90a7b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/util/MathUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/util/MathUtils.java
@@ -9,6 +9,9 @@
  */
 public class MathUtils {
 
+    /** A minimum difference to use when comparing floats for equality. */
+    public static final float EPSILON = 0.001f;
+
     private MathUtils() {}
 
     /**
@@ -158,4 +161,14 @@
     public static int compareLongs(long lhs, long rhs) {
         return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1);
     }
+
+    /**
+     * Determine if two floats are equal.
+     * @param f1 The first float to compare.
+     * @param f2 The second float to compare.
+     * @return True if the floats are equal.
+     */
+    public static boolean areFloatsEqual(float f1, float f2) {
+        return Math.abs(f1 - f2) < MathUtils.EPSILON;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/BottomSheet.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/BottomSheet.java
index ba833810..0d9f110 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/BottomSheet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/BottomSheet.java
@@ -10,15 +10,23 @@
 import android.content.Context;
 import android.graphics.Region;
 import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import android.support.v4.view.ScrollingView;
 import android.util.AttributeSet;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.NativePage;
+import org.chromium.chrome.browser.ntp.NewTabPage;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.util.MathUtils;
 
 import java.lang.annotation.Retention;
@@ -69,6 +77,9 @@
     /** The interpolator that the height animator uses. */
     private final Interpolator mInterpolator = new DecelerateInterpolator(1.0f);
 
+    /** This is a cached array for getting the window location of different views. */
+    private final int[] mLocationArray = new int[2];
+
     /** For detecting scroll and fling events on the bottom sheet. */
     private GestureDetector mGestureDetector;
 
@@ -84,12 +95,30 @@
     /** The height of the toolbar. */
     private float mToolbarHeight;
 
+    /** The width of the view that contains the bottom sheet. */
+    private float mContainerWidth;
+
     /** The height of the view that contains the bottom sheet. */
     private float mContainerHeight;
 
     /** The current sheet state. If the sheet is moving, this will be the target state. */
     private int mCurrentState;
 
+    /** Used for getting the current tab. */
+    private TabModelSelector mTabModelSelector;
+
+    /** A handle to the native page being shown by the sheet. */
+    private NativePage mNativePage;
+
+    /** A handle to the toolbar control container. */
+    private View mControlContainer;
+
+    /** A handle to the FrameLayout that holds the content of the bottom sheet. */
+    private FrameLayout mBottomSheetContent;
+
+    /** A handle to the main scrolling view in the bottom sheet's content. */
+    private ScrollingView mScrollingContentView;
+
     /**
      * This class is responsible for detecting swipe and scroll events on the bottom sheet or
      * ignoring them when appropriate.
@@ -113,16 +142,27 @@
 
             // Cancel the settling animation if it is running so it doesn't conflict with where the
             // user wants to move the sheet.
+            boolean wasSettleAnimatorRunning = mSettleAnimator != null;
             cancelAnimation();
 
             mVelocityTracker.addMovement(e2);
 
             float currentShownRatio =
                     mContainerHeight > 0 ? getSheetOffsetFromBottom() / mContainerHeight : 0;
+            boolean isSheetInMaxPosition =
+                    MathUtils.areFloatsEqual(currentShownRatio,
+                            mStateRatios[mStateRatios.length - 1]);
 
-            // If the sheet is in the max position, don't move if the scroll is upward.
-            if (currentShownRatio >= mStateRatios[mStateRatios.length - 1]
-                    && distanceY > 0) {
+            // Allow the bottom sheet's content to be scrolled up without dragging the sheet down.
+            if (!isTouchEventInToolbar(e2) && isSheetInMaxPosition && mScrollingContentView != null
+                    && mScrollingContentView.computeVerticalScrollOffset() > 0) {
+                mIsScrolling = false;
+                return false;
+            }
+
+            // If the sheet is in the max position, don't move the sheet if the scroll is upward.
+            // Instead, allow the sheet's content to handle it if it needs to.
+            if (isSheetInMaxPosition && distanceY > 0) {
                 mIsScrolling = false;
                 return false;
             }
@@ -133,6 +173,12 @@
                 return false;
             }
 
+            // Send a notification that the sheet is exiting the peeking state into something that
+            // will show content.
+            if (!mIsScrolling && mCurrentState == SHEET_STATE_PEEK && !wasSettleAnimatorRunning) {
+                onExitPeekState();
+            }
+
             float newOffset = getSheetOffsetFromBottom() + distanceY;
             setSheetOffsetFromBottom(MathUtils.clamp(newOffset, getMinOffset(), getMaxOffset()));
 
@@ -166,7 +212,6 @@
         super(context, atts);
 
         setOrientation(LinearLayout.VERTICAL);
-
         mVelocityTracker = VelocityTracker.obtain();
 
         mGestureDetector = new GestureDetector(context, new BottomSheetSwipeDetector());
@@ -220,6 +265,13 @@
     }
 
     /**
+     * @param tabModelSelector A TabModelSelector for getting the current tab and activity.
+     */
+    public void setTabModelSelector(TabModelSelector tabModelSelector) {
+        mTabModelSelector = tabModelSelector;
+    }
+
+    /**
      * Adds layout change listeners to the views that the bottom sheet depends on. Namely the
      * heights of the root view and control container are important as they are used in many of the
      * calculations in this class.
@@ -227,7 +279,11 @@
      * @param controlContainer The container for the toolbar.
      */
     public void init(View root, View controlContainer) {
-        mToolbarHeight = controlContainer.getHeight();
+        mControlContainer = controlContainer;
+        mToolbarHeight = mControlContainer.getHeight();
+
+        mBottomSheetContent = (FrameLayout) findViewById(R.id.bottom_sheet_content);
+
         mCurrentState = SHEET_STATE_PEEK;
 
         // Listen to height changes on the root.
@@ -239,8 +295,9 @@
                     return;
                 }
 
+                mContainerWidth = right - left;
                 mContainerHeight = bottom - top;
-                updateSheetPeekHeight();
+                updateSheetDimensions();
 
                 cancelAnimation();
                 setSheetState(mCurrentState, false);
@@ -257,7 +314,7 @@
                 }
 
                 mToolbarHeight = bottom - top;
-                updateSheetPeekHeight();
+                updateSheetDimensions();
 
                 cancelAnimation();
                 setSheetState(mCurrentState, false);
@@ -266,6 +323,44 @@
     }
 
     /**
+     * Determines if a touch event is inside the toolbar. This assumes the toolbar is the full
+     * width of the screen and that the toolbar is at the top of the bottom sheet.
+     * @param e The motion event to test.
+     * @return True if the event occured in the toolbar region.
+     */
+    private boolean isTouchEventInToolbar(MotionEvent e) {
+        if (mControlContainer == null) return false;
+
+        mControlContainer.getLocationInWindow(mLocationArray);
+
+        return e.getRawY() < mLocationArray[1] + mToolbarHeight;
+    }
+
+    /**
+     * A notification that the sheet is exiting the peek state into one that shows content.
+     */
+    private void onExitPeekState() {
+        if (mNativePage == null) {
+            showNativePage(new NewTabPage(mTabModelSelector.getCurrentTab().getActivity(),
+                    mTabModelSelector.getCurrentTab(), mTabModelSelector));
+        }
+    }
+
+    /**
+     * Show a native page in the bottom sheet's content area.
+     * @param page The NativePage to show.
+     */
+    private void showNativePage(NativePage page) {
+        if (mNativePage != null) mBottomSheetContent.removeView(mNativePage.getView());
+
+        mNativePage = page;
+        mBottomSheetContent.addView(mNativePage.getView());
+        mScrollingContentView = findScrollingChild(mNativePage.getView());
+
+        mNativePage.updateForUrl("");
+    }
+
+    /**
      * Creates an unadjusted version of a MotionEvent.
      * @param e The original event.
      * @return The unadjusted version of the event.
@@ -277,12 +372,44 @@
     }
 
     /**
-     * Updates the bottom sheet's peeking height.
+     * Updates the bottom sheet's peeking and content height.
      */
-    private void updateSheetPeekHeight() {
+    private void updateSheetDimensions() {
         if (mContainerHeight <= 0) return;
 
+        // Though mStateRatios is a static constant, the peeking ratio is computed here because
+        // the correct toolbar height and container height are not know until those views are
+        // inflated.
         mStateRatios[0] = mToolbarHeight / mContainerHeight;
+
+        // Compute the height that the content section of the bottom sheet.
+        float contentHeight =
+                (mContainerHeight * mStateRatios[mStateRatios.length - 1]) - mToolbarHeight;
+        mBottomSheetContent.setLayoutParams(
+                new LinearLayout.LayoutParams((int) mContainerWidth, (int) contentHeight));
+    }
+
+    /**
+     * Find the first ScrollingView in a view hierarchy.
+     * TODO(mdjones): The root of native pages should be a ScrollingView so this logic is not
+     * necessary.
+     * @param view The root of the tree or subtree.
+     * @return The first scrolling view or null.
+     */
+    private ScrollingView findScrollingChild(@Nullable View view) {
+        if (view instanceof ScrollingView) {
+            return (ScrollingView) view;
+        }
+        if (view instanceof ViewGroup) {
+            ViewGroup group = (ViewGroup) view;
+            for (int i = 0, count = group.getChildCount(); i < count; i++) {
+                ScrollingView scrollingChild = findScrollingChild(group.getChildAt(i));
+                if (scrollingChild != null) {
+                    return scrollingChild;
+                }
+            }
+        }
+        return null;
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/OmahaClientTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/OmahaClientTest.java
index 15f2ae2..69f2db0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/OmahaClientTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/OmahaClientTest.java
@@ -319,9 +319,9 @@
 
         // Make sure we properly parsed out the server's response.
         assertEquals("Latest version numbers didn't match", expectedVersion,
-                OmahaClient.getVersionNumberGetter().getLatestKnownVersion(
-                        mContext, OmahaClient.PREF_PACKAGE, OmahaClient.PREF_LATEST_VERSION));
-        assertEquals("Market URL didn't match", expectedURL, OmahaClient.getMarketURL(mContext));
+                VersionNumberGetter.getInstance().getLatestKnownVersion(mContext));
+        assertEquals(
+                "Market URL didn't match", expectedURL, MarketURLGetter.getMarketUrl(mContext));
 
         // Check that the install event was sent properly.
         if (sentInstallEvent) {
@@ -587,8 +587,7 @@
         }
 
         @Override
-        protected void setAlarm(AlarmManager am, PendingIntent operation, int alarmType,
-                long triggerAtTime) {
+        protected void setAlarm(AlarmManager am, PendingIntent operation, long triggerAtTime) {
             mRequestAlarmWasSet = true;
         }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java
index 9c9d3a4..ed4fa276 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/UpdateMenuItemHelperTest.java
@@ -53,8 +53,7 @@
         }
 
         @Override
-        public String getLatestKnownVersion(
-                Context applicationContext, String prefPackage, String prefLatestVersion) {
+        public String getLatestKnownVersion(Context applicationContext) {
             assertNotNull("Never set the latest version", mLatestVersion);
             mAskedForLatestVersion = true;
             return mLatestVersion;
@@ -78,7 +77,7 @@
         }
 
         @Override
-        public String getMarketURL(
+        public String getMarketUrl(
                 Context applicationContext, String prefPackage, String prefMarketUrl) {
             return mURL;
         }
@@ -92,7 +91,7 @@
         super.setUp();
 
         // This test explicitly tests for the menu item, so turn it on.
-        OmahaClient.setEnableUpdateDetection(true);
+        VersionNumberGetter.setEnableUpdateDetection(true);
     }
 
     @Override
@@ -110,12 +109,12 @@
             throws Exception {
         // Report fake versions back to Main when it asks.
         mMockVersionNumberGetter = new MockVersionNumberGetter(currentVersion, latestVersion);
-        OmahaClient.setVersionNumberGetterForTests(mMockVersionNumberGetter);
+        VersionNumberGetter.setInstanceForTests(mMockVersionNumberGetter);
 
         // Report a dummy URL to Omaha.
         mMockMarketURLGetter = new MockMarketURLGetter(
                 "https://play.google.com/store/apps/details?id=com.android.chrome");
-        OmahaClient.setMarketURLGetterForTests(mMockMarketURLGetter);
+        MarketURLGetter.setInstanceForTests(mMockMarketURLGetter);
 
         // Start up main.
         startMainActivityWithURL(UrlConstants.NTP_URL);
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 1670b4f..ea88d61 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5634,13 +5634,6 @@
       <message name="IDS_FLAGS_SHOW_AUTOFILL_TYPE_PREDICTIONS_DESCRIPTION" desc="Description for the flag to show Autofill field type predictions for all forms">
         Annotates web forms with Autofill field type predictions as placeholder text.
       </message>
-      <!-- Autofill credit card signin promo -->
-      <message name="IDS_FLAGS_ENABLE_AUTOFILL_CREDIT_CARD_SIGNIN_PROMO_NAME" desc="Title for the flag to enable autofill credit card signin promo">
-        Enable Autofill Credit Card Signin Promo
-      </message>
-      <message name="IDS_FLAGS_ENABLE_AUTOFILL_CREDIT_CARD_SIGNIN_PROMO_DESCRIPTION" desc="Description for the flag to enable autofill credit card signin promo">
-        Enable showing the credit card signin promo inside the Autofill dropdown.
-      </message>
       <message name="IDS_FLAGS_TCP_FAST_OPEN_NAME" desc="Name of the flag that enables TCP Fast Open.">
         TCP Fast Open
       </message>
@@ -15542,6 +15535,15 @@
             Enabled (Flash lowers volume when interrupted by other sound, experimental)
         </message>
     </if>
+    
+    <if expr="is_win">
+      <message name="IDS_FLAGS_GDI_TEXT_PRINTING" desc="Name for the flag that enables using GDI to print text">
+        GDI Text Printing
+      </message>
+      <message name="IDS_FLAGS_GDI_TEXT_PRINTING_DESCRIPTION" desc="Description of the flag that enables using GDI to print text.">
+        Use GDI to print text as simply text
+      </message>
+    </if>
 
     <if expr="is_android">
       <message name="IDS_FLAGS_MODAL_PERMISSION_PROMPTS_NAME" desc="Name for the flag to enable modal permission prompts on Android" translateable="false">
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 1b7d34b..f4006f4 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3061,8 +3061,6 @@
       "chrome_browser_main_posix.h",
       "chrome_process_singleton.cc",
       "chrome_process_singleton.h",
-      "component_updater/widevine_cdm_component_installer.cc",
-      "component_updater/widevine_cdm_component_installer.h",
       "custom_handlers/register_protocol_handler_permission_request.cc",
       "custom_handlers/register_protocol_handler_permission_request.h",
       "custom_home_pages_table_model.cc",
@@ -3452,6 +3450,13 @@
     ]
   }
 
+  if (enable_pepper_cdms) {
+    sources += [
+      "component_updater/widevine_cdm_component_installer.cc",
+      "component_updater/widevine_cdm_component_installer.h",
+    ]
+  }
+
   if (!is_chrome_branded) {
     sources += [
       "search/local_files_ntp_source.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 4fa9ea5f..e25e527 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -825,10 +825,6 @@
      IDS_FLAGS_SHOW_AUTOFILL_TYPE_PREDICTIONS_NAME,
      IDS_FLAGS_SHOW_AUTOFILL_TYPE_PREDICTIONS_DESCRIPTION, kOsAll,
      SINGLE_VALUE_TYPE(autofill::switches::kShowAutofillTypePredictions)},
-    {"enable-credit-card-signin-promo",
-     IDS_FLAGS_ENABLE_AUTOFILL_CREDIT_CARD_SIGNIN_PROMO_NAME,
-     IDS_FLAGS_ENABLE_AUTOFILL_CREDIT_CARD_SIGNIN_PROMO_DESCRIPTION, kOsAll,
-     FEATURE_VALUE_TYPE(autofill::kAutofillCreditCardSigninPromo)},
     {"smooth-scrolling", IDS_FLAGS_SMOOTH_SCROLLING_NAME,
      IDS_FLAGS_SMOOTH_SCROLLING_DESCRIPTION,
      // Mac has a separate implementation with its own setting to disable.
@@ -2218,6 +2214,11 @@
      SINGLE_VALUE_TYPE(chromeos::switches::kEnableTouchCalibrationSetting)},
 #endif // defined(OS_CHROMEOS)
 
+#if defined(OS_WIN)
+     {"gdi-text-printing", IDS_FLAGS_GDI_TEXT_PRINTING,
+       IDS_FLAGS_GDI_TEXT_PRINTING_DESCRIPTION, kOsWin,
+       FEATURE_VALUE_TYPE(features::kGdiTextPrinting)}
+#endif
     // NOTE: Adding new command-line switches requires adding corresponding
     // entries to enum "LoginCustomFlags" in histograms.xml. See note in
     // histograms.xml and don't forget to run AboutFlagsHistogramTest unit test.
diff --git a/chrome/browser/apps/guest_view/web_view_interactive_browsertest.cc b/chrome/browser/apps/guest_view/web_view_interactive_browsertest.cc
index a0ecbd9..5d5eb78 100644
--- a/chrome/browser/apps/guest_view/web_view_interactive_browsertest.cc
+++ b/chrome/browser/apps/guest_view/web_view_interactive_browsertest.cc
@@ -1356,47 +1356,40 @@
 }
 #endif
 
-// Flaky on MacOS builders. https://crbug.com/670008
-#if defined(OS_MACOSX)
-#define MAYBE_FocusAndVisibility DISABLED_FocusAndVisibility
-#else
-#define MAYBE_FocusAndVisibility FocusAndVisibility
-#endif
-
-IN_PROC_BROWSER_TEST_P(WebViewFocusInteractiveTest, MAYBE_FocusAndVisibility) {
+IN_PROC_BROWSER_TEST_P(WebViewFocusInteractiveTest, FocusAndVisibility) {
   ASSERT_TRUE(StartEmbeddedTestServer());
   LoadAndLaunchPlatformApp("web_view/focus_visibility",
                            "WebViewInteractiveTest.LOADED");
   ExtensionTestMessageListener test_init_listener(
       "WebViewInteractiveTest.WebViewInitialized", false);
-  SendMessageToEmbedder("init");
+  SendMessageToEmbedder(GetParam() ? "init-oopif" : "init");
   test_init_listener.WaitUntilSatisfied();
 
-  // In oopif-webview, wait until the tab key triggers a focus change.
-  std::unique_ptr<content::FrameFocusedObserver> frame_focus_observer =
-      GetParam() ? base::MakeUnique<content::FrameFocusedObserver>(
-                       GetGuestViewManager()
-                           ->WaitForSingleGuestCreated()
-                           ->GetMainFrame())
-                 : nullptr;
-
   // Send several tab-keys. The button inside webview should receive focus at
   // least once.
-  for (size_t i = 0; i < 2; ++i)
+  ExtensionTestMessageListener key_processed_listener(
+      "WebViewInteractiveTest.KeyUp", false);
+#if defined(OS_MACOSX)
+  // On mac, the event listener seems one key event behind and deadlocks. Send
+  // an extra tab to get things unblocked. See http://crbug.com/685281 when
+  // fixed, this can be removed.
+  SendKeyPressToPlatformApp(ui::VKEY_TAB);
+#endif
+  for (size_t i = 0; i < 4; ++i) {
+    key_processed_listener.Reset();
     SendKeyPressToPlatformApp(ui::VKEY_TAB);
-  if (frame_focus_observer) {
-    frame_focus_observer->Wait();
-    frame_focus_observer.reset();
+    EXPECT_TRUE(key_processed_listener.WaitUntilSatisfied());
   }
-  for (size_t i = 0; i < 2; ++i)
-    SendKeyPressToPlatformApp(ui::VKEY_TAB);
+
+  // Verify that the button in the guest receives focus.
   ExtensionTestMessageListener webview_button_focused_listener(
       "WebViewInteractiveTest.WebViewButtonWasFocused", false);
   webview_button_focused_listener.set_failure_message(
       "WebViewInteractiveTest.WebViewButtonWasNotFocused");
   SendMessageToEmbedder("verify");
   EXPECT_TRUE(webview_button_focused_listener.WaitUntilSatisfied());
-  // Now make the <webview> invisible.
+
+  // Reset the test and now make the <webview> invisible.
   ExtensionTestMessageListener reset_listener("WebViewInteractiveTest.DidReset",
                                               false);
   SendMessageToEmbedder("reset");
@@ -1405,10 +1398,15 @@
       "WebViewInteractiveTest.DidHideWebView", false);
   SendMessageToEmbedder("hide-webview");
   did_hide_webview_listener.WaitUntilSatisfied();
+
+
   // Send the same number of keys and verify that the webview button was not
   // this time.
-  for (size_t i = 0; i < 4; ++i)
+  for (size_t i = 0; i < 4; ++i) {
+    key_processed_listener.Reset();
     SendKeyPressToPlatformApp(ui::VKEY_TAB);
+    EXPECT_TRUE(key_processed_listener.WaitUntilSatisfied());
+  }
   ExtensionTestMessageListener webview_button_not_focused_listener(
       "WebViewInteractiveTest.WebViewButtonWasNotFocused", false);
   webview_button_not_focused_listener.set_failure_message(
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 5436b5e..a6196c2b 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -800,8 +800,6 @@
     "login/screens/model_view_channel.h",
     "login/screens/network_error.cc",
     "login/screens/network_error.h",
-    "login/screens/network_error_model.cc",
-    "login/screens/network_error_model.h",
     "login/screens/network_error_view.h",
     "login/screens/network_model.cc",
     "login/screens/network_model.h",
diff --git a/chrome/browser/chromeos/app_mode/kiosk_app_manager.cc b/chrome/browser/chromeos/app_mode/kiosk_app_manager.cc
index 44b3116..054f8fe 100644
--- a/chrome/browser/chromeos/app_mode/kiosk_app_manager.cc
+++ b/chrome/browser/chromeos/app_mode/kiosk_app_manager.cc
@@ -40,6 +40,7 @@
 #include "chromeos/cryptohome/async_method_caller.h"
 #include "chromeos/cryptohome/cryptohome_parameters.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/session_manager_client.h"
 #include "chromeos/settings/cros_settings_names.h"
 #include "components/ownership/owner_key_util.h"
 #include "components/prefs/pref_registry_simple.h"
@@ -51,6 +52,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "extensions/common/extension_urls.h"
 #include "extensions/common/manifest_handlers/kiosk_mode_info.h"
+#include "third_party/cros_system_api/switches/chrome_switches.h"
 
 namespace chromeos {
 
@@ -147,6 +149,14 @@
                                           minor_version, bugfix_version));
 }
 
+// Converts a flag constant to actual command line switch value.
+std::string GetSwitchString(const std::string& flag_name) {
+  base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM);
+  cmd_line.AppendSwitch(flag_name);
+  DCHECK_EQ(2U, cmd_line.argv().size());
+  return cmd_line.argv()[1];
+}
+
 }  // namespace
 
 // static
@@ -241,10 +251,70 @@
                                    const std::string& app_id) {
   LOG_IF(FATAL, app_session_) << "Kiosk session is already initialized.";
 
+  base::CommandLine session_flags(base::CommandLine::NO_PROGRAM);
+  if (GetSwitchesForSessionRestore(app_id, &session_flags)) {
+    base::CommandLine::StringVector flags;
+    // argv[0] is the program name |base::CommandLine::NO_PROGRAM|.
+    flags.assign(session_flags.argv().begin() + 1, session_flags.argv().end());
+
+    // Update user flags, but do not restart Chrome - the purpose of the flags
+    // set here is to be able to properly restore session if the session is
+    // restarted - e.g. due to crash. For example, this will ensure restarted
+    // app session restores auto-launched state.
+    DBusThreadManager::Get()->GetSessionManagerClient()->SetFlagsForUser(
+        cryptohome::Identification(
+            user_manager::UserManager::Get()->GetActiveUser()->GetAccountId()),
+        flags);
+  }
+
   app_session_.reset(new AppSession);
   app_session_->Init(profile, app_id);
 }
 
+bool KioskAppManager::GetSwitchesForSessionRestore(
+    const std::string& app_id,
+    base::CommandLine* switches) {
+  bool auto_launched = app_id == currently_auto_launched_with_zero_delay_app_;
+  const base::CommandLine* current_command_line =
+      base::CommandLine::ForCurrentProcess();
+  bool has_auto_launched_flag =
+      current_command_line->HasSwitch(switches::kAppAutoLaunched);
+  if (auto_launched == has_auto_launched_flag)
+    return false;
+
+  // Collect current policy defined switches, so they can be passed on to the
+  // session manager as well - otherwise they would get lost on restart.
+  // This ignores 'flag-switches-begin' - 'flag-switches-end' flags, but those
+  // should not be present for kiosk sessions.
+  bool in_policy_switches_block = false;
+  const std::string policy_switches_begin =
+      GetSwitchString(switches::kPolicySwitchesBegin);
+  const std::string policy_switches_end =
+      GetSwitchString(switches::kPolicySwitchesEnd);
+
+  for (const auto& it : current_command_line->argv()) {
+    if (it == policy_switches_begin) {
+      DCHECK(!in_policy_switches_block);
+      in_policy_switches_block = true;
+    }
+
+    if (in_policy_switches_block)
+      switches->AppendSwitch(it);
+
+    if (it == policy_switches_end) {
+      DCHECK(in_policy_switches_block);
+      in_policy_switches_block = false;
+    }
+  }
+
+  DCHECK(!in_policy_switches_block);
+
+  if (auto_launched)
+    switches->AppendSwitch(switches::kAppAutoLaunched);
+
+  return true;
+}
+
 void KioskAppManager::AddAppForTest(
     const std::string& app_id,
     const AccountId& account_id,
diff --git a/chrome/browser/chromeos/app_mode/kiosk_app_manager.h b/chrome/browser/chromeos/app_mode/kiosk_app_manager.h
index 7aac2e7..f057d11 100644
--- a/chrome/browser/chromeos/app_mode/kiosk_app_manager.h
+++ b/chrome/browser/chromeos/app_mode/kiosk_app_manager.h
@@ -26,6 +26,10 @@
 class PrefRegistrySimple;
 class Profile;
 
+namespace base {
+class CommandLine;
+}
+
 namespace extensions {
 class Extension;
 class ExternalLoader;
@@ -317,6 +321,15 @@
   // Returns the auto launch delay.
   base::TimeDelta GetAutoLaunchDelay() const;
 
+  // Gets list of user switches that should be passed to Chrome in case current
+  // session has to be restored, e.g. in case of a crash. The switches will be
+  // returned as |switches| command line arguments.
+  // Returns whether the set of switches would have to be changed in respect to
+  // the current set of switches - if that is not the case |switches| might not
+  // get populated.
+  bool GetSwitchesForSessionRestore(const std::string& app_id,
+                                    base::CommandLine* switches);
+
   // True if machine ownership is already established.
   bool ownership_established_;
   std::vector<std::unique_ptr<KioskAppData>> apps_;
diff --git a/chrome/browser/chromeos/app_mode/kiosk_crash_restore_browsertest.cc b/chrome/browser/chromeos/app_mode/kiosk_crash_restore_browsertest.cc
deleted file mode 100644
index 703a763..0000000
--- a/chrome/browser/chromeos/app_mode/kiosk_crash_restore_browsertest.cc
+++ /dev/null
@@ -1,194 +0,0 @@
-// 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.
-
-#include <memory>
-#include <string>
-
-#include "apps/test/app_window_waiter.h"
-#include "base/base64.h"
-#include "base/command_line.h"
-#include "base/files/file_util.h"
-#include "base/path_service.h"
-#include "base/run_loop.h"
-#include "chrome/browser/chromeos/app_mode/fake_cws.h"
-#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
-#include "chrome/browser/chromeos/net/network_portal_detector_test_impl.h"
-#include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos_factory.h"
-#include "chrome/browser/chromeos/policy/device_local_account.h"
-#include "chrome/browser/chromeos/policy/device_policy_builder.h"
-#include "chrome/browser/extensions/browsertest_util.h"
-#include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/common/chrome_constants.h"
-#include "chrome/common/chrome_paths.h"
-#include "chrome/common/pref_names.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "chromeos/chromeos_switches.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
-#include "chromeos/dbus/fake_session_manager_client.h"
-#include "chromeos/dbus/fake_shill_manager_client.h"
-#include "components/ownership/mock_owner_key_util.h"
-#include "extensions/browser/app_window/app_window.h"
-#include "extensions/browser/app_window/app_window_registry.h"
-#include "extensions/browser/app_window/native_app_window.h"
-#include "extensions/common/value_builder.h"
-#include "extensions/test/extension_test_message_listener.h"
-#include "net/dns/mock_host_resolver.h"
-
-namespace em = enterprise_management;
-
-namespace chromeos {
-
-namespace {
-
-const char kTestKioskApp[] = "ggbflgnkafappblpkiflbgpmkfdpnhhe";
-
-}  // namespace
-
-class KioskCrashRestoreTest : public InProcessBrowserTest {
- public:
-  KioskCrashRestoreTest()
-      : owner_key_util_(new ownership::MockOwnerKeyUtil()),
-        fake_cws_(new FakeCWS) {}
-
-  // InProcessBrowserTest
-  void SetUp() override {
-    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
-    InProcessBrowserTest::SetUp();
-  }
-
-  bool SetUpUserDataDirectory() override {
-    SetUpExistingKioskApp();
-    return true;
-  }
-
-  void SetUpInProcessBrowserTestFixture() override {
-    host_resolver()->AddRule("*", "127.0.0.1");
-    SimulateNetworkOnline();
-
-    OverrideDevicePolicy();
-  }
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    const AccountId account_id = AccountId::FromUserEmail(GetTestAppUserId());
-    const cryptohome::Identification cryptohome_id(account_id);
-
-    command_line->AppendSwitchASCII(switches::kLoginUser, cryptohome_id.id());
-    command_line->AppendSwitchASCII(
-        switches::kLoginProfile,
-        CryptohomeClient::GetStubSanitizedUsername(cryptohome_id));
-
-    fake_cws_->Init(embedded_test_server());
-    fake_cws_->SetUpdateCrx(test_app_id_, test_app_id_ + ".crx", "1.0.0");
-  }
-
-  void SetUpOnMainThread() override {
-    extensions::browsertest_util::CreateAndInitializeLocalCache();
-
-    embedded_test_server()->StartAcceptingConnections();
-  }
-
-  const std::string GetTestAppUserId() const {
-    return policy::GenerateDeviceLocalAccountUserId(
-        test_app_id_, policy::DeviceLocalAccount::TYPE_KIOSK_APP);
-  }
-
-  const std::string& test_app_id() const { return test_app_id_; }
-
- private:
-  void SetUpExistingKioskApp() {
-    // Create policy data that contains the test app as an existing kiosk app.
-    em::DeviceLocalAccountsProto* const device_local_accounts =
-        device_policy_.payload().mutable_device_local_accounts();
-
-    em::DeviceLocalAccountInfoProto* const account =
-        device_local_accounts->add_account();
-    account->set_account_id(test_app_id_);
-    account->set_type(
-        em::DeviceLocalAccountInfoProto_AccountType_ACCOUNT_TYPE_KIOSK_APP);
-    account->mutable_kiosk_app()->set_app_id(test_app_id_);
-    device_policy_.Build();
-
-    // Prepare the policy data to store in device policy cache.
-    em::PolicyData policy_data;
-    CHECK(device_policy_.payload().SerializeToString(
-        policy_data.mutable_policy_value()));
-    const std::string policy_data_string = policy_data.SerializeAsString();
-    std::string encoded;
-    base::Base64Encode(policy_data_string, &encoded);
-
-    // Store policy data and existing device local accounts in local state.
-    const std::string local_state_json =
-        extensions::DictionaryBuilder()
-            .Set(prefs::kDeviceSettingsCache, encoded)
-            .Set("PublicAccounts",
-                 extensions::ListBuilder().Append(GetTestAppUserId()).Build())
-            .ToJSON();
-
-    base::FilePath local_state_file;
-    CHECK(PathService::Get(chrome::DIR_USER_DATA, &local_state_file));
-    local_state_file = local_state_file.Append(chrome::kLocalStateFilename);
-    base::WriteFile(local_state_file, local_state_json.data(),
-                    local_state_json.size());
-  }
-
-  void SimulateNetworkOnline() {
-    NetworkPortalDetectorTestImpl* const network_portal_detector =
-        new NetworkPortalDetectorTestImpl();
-    // Takes ownership of |network_portal_detector|.
-    network_portal_detector::InitializeForTesting(network_portal_detector);
-    network_portal_detector->SetDefaultNetworkForTesting(
-        FakeShillManagerClient::kFakeEthernetNetworkGuid);
-
-    NetworkPortalDetector::CaptivePortalState online_state;
-    online_state.status = NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE;
-    online_state.response_code = 204;
-    network_portal_detector->SetDetectionResultsForTesting(
-        FakeShillManagerClient::kFakeEthernetNetworkGuid, online_state);
-  }
-
-  void OverrideDevicePolicy() {
-    OwnerSettingsServiceChromeOSFactory::GetInstance()
-        ->SetOwnerKeyUtilForTesting(owner_key_util_);
-    owner_key_util_->SetPublicKeyFromPrivateKey(
-        *device_policy_.GetSigningKey());
-
-    session_manager_client_ = new FakeSessionManagerClient;
-    session_manager_client_->set_device_policy(device_policy_.GetBlob());
-
-    DBusThreadManager::GetSetterForTesting()->SetSessionManagerClient(
-        std::unique_ptr<SessionManagerClient>(session_manager_client_));
-  }
-
-  std::string test_app_id_ = kTestKioskApp;
-
-  policy::DevicePolicyBuilder device_policy_;
-  scoped_refptr<ownership::MockOwnerKeyUtil> owner_key_util_;
-  FakeSessionManagerClient* session_manager_client_;
-  std::unique_ptr<FakeCWS> fake_cws_;
-
-  DISALLOW_COPY_AND_ASSIGN(KioskCrashRestoreTest);
-};
-
-IN_PROC_BROWSER_TEST_F(KioskCrashRestoreTest, Basic) {
-  ExtensionTestMessageListener launch_data_check_listener(
-      "launchData.isKioskSession = true", false);
-
-  Profile* const app_profile = ProfileManager::GetPrimaryUserProfile();
-  ASSERT_TRUE(app_profile);
-  extensions::AppWindowRegistry* const app_window_registry =
-      extensions::AppWindowRegistry::Get(app_profile);
-  extensions::AppWindow* const window =
-      apps::AppWindowWaiter(app_window_registry, test_app_id()).Wait();
-  ASSERT_TRUE(window);
-
-  window->GetBaseWindow()->Close();
-
-  // Wait until the app terminates if it is still running.
-  if (!app_window_registry->GetAppWindowsForApp(test_app_id()).empty())
-    base::RunLoop().Run();
-
-  EXPECT_TRUE(launch_data_check_listener.was_satisfied());
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/auto_launched_kiosk_browsertest.cc b/chrome/browser/chromeos/login/auto_launched_kiosk_browsertest.cc
new file mode 100644
index 0000000..9b5f8c6
--- /dev/null
+++ b/chrome/browser/chromeos/login/auto_launched_kiosk_browsertest.cc
@@ -0,0 +1,470 @@
+// 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.
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "apps/test/app_window_waiter.h"
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/chromeos/app_mode/fake_cws.h"
+#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
+#include "chrome/browser/chromeos/login/app_launch_controller.h"
+#include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos_factory.h"
+#include "chrome/browser/chromeos/policy/device_local_account.h"
+#include "chrome/browser/chromeos/policy/device_policy_builder.h"
+#include "chrome/browser/chromeos/settings/stub_install_attributes.h"
+#include "chrome/browser/extensions/browsertest_util.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/pref_names.h"
+#include "chromeos/dbus/cryptohome_client.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/fake_session_manager_client.h"
+#include "chromeos/dbus/shill_manager_client.h"
+#include "components/ownership/mock_owner_key_util.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/notification_service.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/app_window/native_app_window.h"
+#include "extensions/common/value_builder.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "net/dns/mock_host_resolver.h"
+#include "third_party/cros_system_api/switches/chrome_switches.h"
+
+namespace em = enterprise_management;
+
+namespace chromeos {
+
+namespace {
+
+// This is a simple test app that creates an app window and immediately closes
+// it again. Webstore data json is in
+//   chrome/test/data/chromeos/app_mode/webstore/inlineinstall/
+//       detail/ggbflgnkafappblpkiflbgpmkfdpnhhe
+const char kTestKioskApp[] = "ggbflgnkafappblpkiflbgpmkfdpnhhe";
+
+const char kTestAccountId[] = "enterprise-kiosk-app@localhost";
+
+const char kSessionManagerStateCache[] = "test_session_manager_state.json";
+
+// Keys for values in dictionary used to preserve session manager state.
+const char kLoginArgsKey[] = "login_args";
+const char kExtraArgsKey[] = "extra_args";
+const char kArgNameKey[] = "name";
+const char kArgValueKey[] = "value";
+
+// Default set policy switches.
+const struct {
+  const char* name;
+  const char* value;
+} kDefaultPolicySwitches[] = {{"test_switch_1", ""},
+                              {"test_switch_2", "test_switch_2_value"}};
+
+// Fake session manager implementation that persists its state in local file.
+// It can be used to preserve session state in PRE_ browser tests.
+// Primarily used for testing user/login switches.
+class PersistentSessionManagerClient : public FakeSessionManagerClient {
+ public:
+  PersistentSessionManagerClient() {}
+
+  ~PersistentSessionManagerClient() override {
+    PersistFlagsToFile(backing_file_);
+  }
+
+  // Initializes session state (primarily session flags)- if |backing_file|
+  // exists, the session state is restored from the file value. Otherwise it's
+  // set to the default session state.
+  void Initialize(const base::FilePath& backing_file) {
+    backing_file_ = backing_file;
+
+    if (ExtractFlagsFromFile(backing_file_))
+      return;
+
+    // Failed to extract ached flags - set the default values.
+    login_args_ = {{"login-manager", ""}};
+
+    extra_args_ = {{switches::kPolicySwitchesBegin, ""}};
+    for (size_t i = 0; i < arraysize(kDefaultPolicySwitches); ++i) {
+      extra_args_.push_back(
+          {kDefaultPolicySwitches[i].name, kDefaultPolicySwitches[i].value});
+    }
+    extra_args_.push_back({switches::kPolicySwitchesEnd, ""});
+  }
+
+  void AppendSwitchesToCommandLine(base::CommandLine* command_line) {
+    for (const auto& flag : login_args_)
+      command_line->AppendSwitchASCII(flag.name, flag.value);
+    for (const auto& flag : extra_args_)
+      command_line->AppendSwitchASCII(flag.name, flag.value);
+  }
+
+  void StartSession(const cryptohome::Identification& cryptohome_id) override {
+    FakeSessionManagerClient::StartSession(cryptohome_id);
+
+    std::string user_id_hash =
+        CryptohomeClient::GetStubSanitizedUsername(cryptohome_id);
+    login_args_ = {{"login-user", cryptohome_id.id()},
+                   {"login-profile", user_id_hash}};
+  }
+
+  void StopSession() override {
+    FakeSessionManagerClient::StopSession();
+
+    login_args_ = {{"login-manager", ""}};
+  }
+
+  bool SupportsRestartToApplyUserFlags() const override { return true; }
+
+  void SetFlagsForUser(const cryptohome::Identification& identification,
+                       const std::vector<std::string>& flags) override {
+    extra_args_.clear();
+    FakeSessionManagerClient::SetFlagsForUser(identification, flags);
+
+    std::vector<std::string> argv = {"" /* Empty program */};
+    argv.insert(argv.end(), flags.begin(), flags.end());
+
+    // Parse flag name-value pairs using command line initialization.
+    base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM);
+    cmd_line.InitFromArgv(argv);
+
+    for (const auto& flag : cmd_line.GetSwitches())
+      extra_args_.push_back({flag.first, flag.second});
+  }
+
+ private:
+  // Keeps information about a switch - its name and value.
+  struct Switch {
+    std::string name;
+    std::string value;
+  };
+
+  bool ExtractFlagsFromFile(const base::FilePath& backing_file) {
+    JSONFileValueDeserializer deserializer(backing_file);
+
+    int error_code = 0;
+    std::unique_ptr<base::Value> value =
+        deserializer.Deserialize(&error_code, nullptr);
+    if (error_code != JSONFileValueDeserializer::JSON_NO_ERROR)
+      return false;
+
+    std::unique_ptr<base::DictionaryValue> value_dict =
+        base::DictionaryValue::From(std::move(value));
+    DCHECK(value_dict);
+
+    CHECK(InitArgListFromCachedValue(*value_dict, kLoginArgsKey, &login_args_));
+    CHECK(InitArgListFromCachedValue(*value_dict, kExtraArgsKey, &extra_args_));
+    return true;
+  }
+
+  bool PersistFlagsToFile(const base::FilePath& backing_file) {
+    base::DictionaryValue cached_state;
+    cached_state.Set(kLoginArgsKey, GetArgListValue(login_args_));
+    cached_state.Set(kExtraArgsKey, GetArgListValue(extra_args_));
+
+    JSONFileValueSerializer serializer(backing_file);
+    return serializer.Serialize(cached_state);
+  }
+
+  std::unique_ptr<base::ListValue> GetArgListValue(
+      const std::vector<Switch>& args) {
+    std::unique_ptr<base::ListValue> result(new base::ListValue());
+    for (const auto& arg : args) {
+      result->Append(extensions::DictionaryBuilder()
+                         .Set(kArgNameKey, arg.name)
+                         .Set(kArgValueKey, arg.value)
+                         .Build());
+    }
+    return result;
+  }
+
+  bool InitArgListFromCachedValue(const base::DictionaryValue& cache_value,
+                                  const std::string& list_key,
+                                  std::vector<Switch>* arg_list_out) {
+    arg_list_out->clear();
+    const base::ListValue* arg_list_value;
+    if (!cache_value.GetList(list_key, &arg_list_value))
+      return false;
+    for (size_t i = 0; i < arg_list_value->GetSize(); ++i) {
+      const base::DictionaryValue* arg_value;
+      if (!arg_list_value->GetDictionary(i, &arg_value))
+        return false;
+      Switch arg;
+      if (!arg_value->GetStringASCII(kArgNameKey, &arg.name) ||
+          !arg_value->GetStringASCII(kArgValueKey, &arg.value)) {
+        return false;
+      }
+      arg_list_out->push_back(arg);
+    }
+    return true;
+  }
+
+  std::vector<Switch> login_args_;
+  std::vector<Switch> extra_args_;
+
+  base::FilePath backing_file_;
+
+  DISALLOW_COPY_AND_ASSIGN(PersistentSessionManagerClient);
+};
+
+// Used to listen for app termination notification.
+class TerminationObserver : public content::NotificationObserver {
+ public:
+  TerminationObserver() {
+    registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
+                   content::NotificationService::AllSources());
+  }
+  ~TerminationObserver() override = default;
+
+  // Whether app has been terminated - i.e. whether app termination notification
+  // has been observed.
+  bool terminated() const { return notification_seen_; }
+
+ private:
+  void Observe(int type,
+               const content::NotificationSource& source,
+               const content::NotificationDetails& details) override {
+    ASSERT_EQ(chrome::NOTIFICATION_APP_TERMINATING, type);
+    notification_seen_ = true;
+  }
+
+  bool notification_seen_ = false;
+  content::NotificationRegistrar registrar_;
+
+  DISALLOW_COPY_AND_ASSIGN(TerminationObserver);
+};
+
+}  // namespace
+
+class AutoLaunchedKioskTest : public ExtensionApiTest {
+ public:
+  AutoLaunchedKioskTest()
+      : install_attributes_(
+            chromeos::ScopedStubInstallAttributes::CreateEnterprise(
+                "domain.com",
+                "device_id")),
+        owner_key_util_(new ownership::MockOwnerKeyUtil()),
+        fake_session_manager_(new PersistentSessionManagerClient()),
+        fake_cws_(new FakeCWS) {
+    set_chromeos_user_ = false;
+  }
+
+  ~AutoLaunchedKioskTest() override = default;
+
+  void SetUp() override {
+    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
+    AppLaunchController::SkipSplashWaitForTesting();
+
+    ExtensionApiTest::SetUp();
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    fake_cws_->Init(embedded_test_server());
+    fake_cws_->SetUpdateCrx(kTestKioskApp, std::string(kTestKioskApp) + ".crx",
+                            "1.0.0");
+    ExtensionApiTest::SetUpCommandLine(command_line);
+  }
+
+  bool SetUpUserDataDirectory() override {
+    InitDevicePolicy();
+
+    base::FilePath user_data_path;
+    CHECK(PathService::Get(chrome::DIR_USER_DATA, &user_data_path));
+    CacheDevicePolicyToLocalState(user_data_path);
+
+    // Restore session_manager state and ensure session manager flags are
+    // applied.
+    fake_session_manager_->Initialize(
+        user_data_path.Append(kSessionManagerStateCache));
+    fake_session_manager_->AppendSwitchesToCommandLine(
+        base::CommandLine::ForCurrentProcess());
+
+    return true;
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+
+    OwnerSettingsServiceChromeOSFactory::GetInstance()
+        ->SetOwnerKeyUtilForTesting(owner_key_util_);
+    owner_key_util_->SetPublicKeyFromPrivateKey(
+        *device_policy_.GetSigningKey());
+
+    fake_session_manager_->set_device_policy(device_policy_.GetBlob());
+    DBusThreadManager::GetSetterForTesting()->SetSessionManagerClient(
+        std::move(fake_session_manager_));
+
+    ExtensionApiTest::SetUpInProcessBrowserTestFixture();
+  }
+
+  void SetUpOnMainThread() override {
+    extensions::browsertest_util::CreateAndInitializeLocalCache();
+
+    embedded_test_server()->StartAcceptingConnections();
+
+    ExtensionApiTest::SetUpOnMainThread();
+  }
+
+  void RunTestOnMainThreadLoop() override {
+    termination_observer_.reset(new TerminationObserver());
+
+    ExtensionApiTest::RunTestOnMainThreadLoop();
+  }
+
+  void TearDownOnMainThread() override {
+    termination_observer_.reset();
+
+    ExtensionApiTest::TearDownOnMainThread();
+  }
+
+  void InitDevicePolicy() {
+    // Create device policy, and cache it to local state.
+    em::DeviceLocalAccountsProto* const device_local_accounts =
+        device_policy_.payload().mutable_device_local_accounts();
+
+    em::DeviceLocalAccountInfoProto* const account =
+        device_local_accounts->add_account();
+    account->set_account_id(kTestAccountId);
+    account->set_type(em::DeviceLocalAccountInfoProto::ACCOUNT_TYPE_KIOSK_APP);
+    account->mutable_kiosk_app()->set_app_id(kTestKioskApp);
+
+    device_local_accounts->set_auto_login_id(kTestAccountId);
+
+    device_policy_.Build();
+  }
+
+  void CacheDevicePolicyToLocalState(const base::FilePath& user_data_path) {
+    em::PolicyData policy_data;
+    DCHECK(device_policy_.payload().SerializeToString(
+        policy_data.mutable_policy_value()));
+    const std::string policy_data_str = policy_data.SerializeAsString();
+    std::string policy_data_encoded;
+    base::Base64Encode(policy_data_str, &policy_data_encoded);
+
+    std::unique_ptr<base::DictionaryValue> local_state =
+        extensions::DictionaryBuilder()
+            .Set(prefs::kDeviceSettingsCache, policy_data_encoded)
+            .Set("PublicAccounts",
+                 extensions::ListBuilder().Append(GetTestAppUserId()).Build())
+            .Build();
+
+    JSONFileValueSerializer serializer(
+        user_data_path.Append(chrome::kLocalStateFilename));
+    CHECK(serializer.Serialize(*local_state));
+  }
+
+  const std::string GetTestAppUserId() const {
+    return policy::GenerateDeviceLocalAccountUserId(
+        kTestAccountId, policy::DeviceLocalAccount::TYPE_KIOSK_APP);
+  }
+
+  bool CloseAppWindow(const std::string& app_id) {
+    Profile* const app_profile = ProfileManager::GetPrimaryUserProfile();
+    if (!app_profile) {
+      ADD_FAILURE() << "No primary (app) profile.";
+      return false;
+    }
+
+    extensions::AppWindowRegistry* const app_window_registry =
+        extensions::AppWindowRegistry::Get(app_profile);
+    extensions::AppWindow* const window =
+        apps::AppWindowWaiter(app_window_registry, app_id).Wait();
+    if (!window) {
+      ADD_FAILURE() << "No app window found for " << app_id << ".";
+      return false;
+    }
+
+    window->GetBaseWindow()->Close();
+
+    // Wait until the app terminates if it is still running.
+    if (!app_window_registry->GetAppWindowsForApp(app_id).empty())
+      base::RunLoop().Run();
+    return true;
+  }
+
+  bool IsKioskAppAutoLaunched(const std::string& app_id) {
+    KioskAppManager::App app;
+    if (!KioskAppManager::Get()->GetApp(app_id, &app)) {
+      ADD_FAILURE() << "App " << app_id << " not found.";
+      return false;
+    }
+    return app.was_auto_launched_with_zero_delay;
+  }
+
+  void ExpectCommandLineHasDefaultPolicySwitches(
+      const base::CommandLine& cmd_line) {
+    for (size_t i = 0u; i < arraysize(kDefaultPolicySwitches); ++i) {
+      EXPECT_TRUE(cmd_line.HasSwitch(kDefaultPolicySwitches[i].name))
+          << "Missing flag " << kDefaultPolicySwitches[i].name;
+      EXPECT_EQ(kDefaultPolicySwitches[i].value,
+                cmd_line.GetSwitchValueASCII(kDefaultPolicySwitches[i].name))
+          << "Invalid value for switch " << kDefaultPolicySwitches[i].name;
+    }
+  }
+
+ protected:
+  std::unique_ptr<TerminationObserver> termination_observer_;
+
+ private:
+  chromeos::ScopedStubInstallAttributes install_attributes_;
+  policy::DevicePolicyBuilder device_policy_;
+  scoped_refptr<ownership::MockOwnerKeyUtil> owner_key_util_;
+  std::unique_ptr<PersistentSessionManagerClient> fake_session_manager_;
+  std::unique_ptr<FakeCWS> fake_cws_;
+
+  DISALLOW_COPY_AND_ASSIGN(AutoLaunchedKioskTest);
+};
+
+IN_PROC_BROWSER_TEST_F(AutoLaunchedKioskTest, PRE_CrashRestore) {
+  // Verify that Chrome hasn't already exited, e.g. in order to apply user
+  // session flags.
+  ASSERT_FALSE(termination_observer_->terminated());
+
+  // Set up default network connections, so tests think the device is online.
+  DBusThreadManager::Get()
+      ->GetShillManagerClient()
+      ->GetTestInterface()
+      ->SetupDefaultEnvironment();
+
+  // Check that policy flags have not been lost.
+  ExpectCommandLineHasDefaultPolicySwitches(
+      *base::CommandLine::ForCurrentProcess());
+
+  ExtensionTestMessageListener listener("appWindowLoaded", false);
+  EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+  EXPECT_TRUE(IsKioskAppAutoLaunched(kTestKioskApp));
+
+  ASSERT_TRUE(CloseAppWindow(kTestKioskApp));
+}
+
+IN_PROC_BROWSER_TEST_F(AutoLaunchedKioskTest, CrashRestore) {
+  // Verify that Chrome hasn't already exited, e.g. in order to apply user
+  // session flags.
+  ASSERT_FALSE(termination_observer_->terminated());
+
+  ExpectCommandLineHasDefaultPolicySwitches(
+      *base::CommandLine::ForCurrentProcess());
+
+  ExtensionTestMessageListener listener("appWindowLoaded", false);
+  EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+  EXPECT_TRUE(IsKioskAppAutoLaunched(kTestKioskApp));
+
+  ASSERT_TRUE(CloseAppWindow(kTestKioskApp));
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/error_screen.cc b/chrome/browser/chromeos/login/screens/error_screen.cc
index cd1561b0..a0b46cb 100644
--- a/chrome/browser/chromeos/login/screens/error_screen.cc
+++ b/chrome/browser/chromeos/login/screens/error_screen.cc
@@ -45,6 +45,15 @@
 
 namespace {
 
+constexpr const char kContextKeyErrorStateCode[] = "error-state-code";
+constexpr const char kContextKeyErrorStateNetwork[] = "error-state-network";
+constexpr const char kContextKeyGuestSigninAllowed[] = "guest-signin-allowed";
+constexpr const char kContextKeyOfflineSigninAllowed[] =
+    "offline-signin-allowed";
+constexpr const char kContextKeyShowConnectingIndicator[] =
+    "show-connecting-indicator";
+constexpr const char kContextKeyUIState[] = "ui-state";
+
 // Returns the current running kiosk app profile in a kiosk session. Otherwise,
 // returns nullptr.
 Profile* GetAppProfile() {
@@ -55,15 +64,30 @@
 
 }  // namespace
 
+constexpr const char ErrorScreen::kUserActionConfigureCertsButtonClicked[] =
+    "configure-certs";
+constexpr const char ErrorScreen::kUserActionDiagnoseButtonClicked[] =
+    "diagnose";
+constexpr const char ErrorScreen::kUserActionLaunchOobeGuestSessionClicked[] =
+    "launch-oobe-guest";
+constexpr const char
+    ErrorScreen::kUserActionLocalStateErrorPowerwashButtonClicked[] =
+        "local-state-error-powerwash";
+constexpr const char ErrorScreen::kUserActionRebootButtonClicked[] = "reboot";
+constexpr const char ErrorScreen::kUserActionShowCaptivePortalClicked[] =
+    "show-captive-portal";
+constexpr const char ErrorScreen::kUserActionConnectRequested[] =
+    "connect-requested";
+
 ErrorScreen::ErrorScreen(BaseScreenDelegate* base_screen_delegate,
                          NetworkErrorView* view)
-    : NetworkErrorModel(base_screen_delegate),
+    : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_ERROR_MESSAGE),
       view_(view),
       weak_factory_(this) {
   network_state_informer_ = new NetworkStateInformer();
   network_state_informer_->Init();
   if (view_)
-    view_->Bind(*this);
+    view_->Bind(this);
 }
 
 ErrorScreen::~ErrorScreen() {
@@ -71,64 +95,6 @@
     view_->Unbind();
 }
 
-void ErrorScreen::Show() {
-  if (!on_hide_callback_) {
-    SetHideCallback(base::Bind(&ErrorScreen::DefaultHideCallback,
-                               weak_factory_.GetWeakPtr()));
-  }
-  if (view_)
-    view_->Show();
-}
-
-void ErrorScreen::Hide() {
-  if (view_)
-    view_->Hide();
-}
-
-void ErrorScreen::OnShow() {
-  LOG(WARNING) << "Network error screen message is shown";
-  content::NotificationService::current()->Notify(
-      chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN,
-      content::NotificationService::AllSources(),
-      content::NotificationService::NoDetails());
-  network_portal_detector::GetInstance()->SetStrategy(
-      PortalDetectorStrategy::STRATEGY_ID_ERROR_SCREEN);
-}
-
-void ErrorScreen::OnHide() {
-  LOG(WARNING) << "Network error screen message is hidden";
-  if (on_hide_callback_) {
-    on_hide_callback_->Run();
-    on_hide_callback_.reset();
-  }
-  network_portal_detector::GetInstance()->SetStrategy(
-      PortalDetectorStrategy::STRATEGY_ID_LOGIN_SCREEN);
-}
-
-void ErrorScreen::OnUserAction(const std::string& action_id) {
-  if (action_id == kUserActionShowCaptivePortalClicked)
-    ShowCaptivePortal();
-  else if (action_id == kUserActionConfigureCertsButtonClicked)
-    OnConfigureCerts();
-  else if (action_id == kUserActionDiagnoseButtonClicked)
-    OnDiagnoseButtonClicked();
-  else if (action_id == kUserActionLaunchOobeGuestSessionClicked)
-    OnLaunchOobeGuestSession();
-  else if (action_id == kUserActionLocalStateErrorPowerwashButtonClicked)
-    OnLocalStateErrorPowerwashButtonClicked();
-  else if (action_id == kUserActionRebootButtonClicked)
-    OnRebootButtonClicked();
-  else if (action_id == kUserActionConnectRequested)
-    OnConnectRequested();
-  else
-    BaseScreen::OnUserAction(action_id);
-}
-
-void ErrorScreen::OnContextKeyUpdated(
-    const ::login::ScreenContext::KeyType& key) {
-  BaseScreen::OnContextKeyUpdated(key);
-}
-
 void ErrorScreen::AllowGuestSignin(bool allowed) {
   GetContextEditor().SetBoolean(kContextKeyGuestSigninAllowed, allowed);
 }
@@ -203,6 +169,64 @@
   GetContextEditor().SetBoolean(kContextKeyShowConnectingIndicator, show);
 }
 
+ErrorScreen::ConnectRequestCallbackSubscription
+ErrorScreen::RegisterConnectRequestCallback(const base::Closure& callback) {
+  return connect_request_callbacks_.Add(callback);
+}
+
+void ErrorScreen::Show() {
+  if (!on_hide_callback_) {
+    SetHideCallback(base::Bind(&ErrorScreen::DefaultHideCallback,
+                               weak_factory_.GetWeakPtr()));
+  }
+  if (view_)
+    view_->Show();
+}
+
+void ErrorScreen::Hide() {
+  if (view_)
+    view_->Hide();
+}
+
+void ErrorScreen::OnShow() {
+  LOG(WARNING) << "Network error screen message is shown";
+  content::NotificationService::current()->Notify(
+      chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN,
+      content::NotificationService::AllSources(),
+      content::NotificationService::NoDetails());
+  network_portal_detector::GetInstance()->SetStrategy(
+      PortalDetectorStrategy::STRATEGY_ID_ERROR_SCREEN);
+}
+
+void ErrorScreen::OnHide() {
+  LOG(WARNING) << "Network error screen message is hidden";
+  if (on_hide_callback_) {
+    on_hide_callback_->Run();
+    on_hide_callback_.reset();
+  }
+  network_portal_detector::GetInstance()->SetStrategy(
+      PortalDetectorStrategy::STRATEGY_ID_LOGIN_SCREEN);
+}
+
+void ErrorScreen::OnUserAction(const std::string& action_id) {
+  if (action_id == kUserActionShowCaptivePortalClicked)
+    ShowCaptivePortal();
+  else if (action_id == kUserActionConfigureCertsButtonClicked)
+    OnConfigureCerts();
+  else if (action_id == kUserActionDiagnoseButtonClicked)
+    OnDiagnoseButtonClicked();
+  else if (action_id == kUserActionLaunchOobeGuestSessionClicked)
+    OnLaunchOobeGuestSession();
+  else if (action_id == kUserActionLocalStateErrorPowerwashButtonClicked)
+    OnLocalStateErrorPowerwashButtonClicked();
+  else if (action_id == kUserActionRebootButtonClicked)
+    OnRebootButtonClicked();
+  else if (action_id == kUserActionConnectRequested)
+    OnConnectRequested();
+  else
+    BaseScreen::OnUserAction(action_id);
+}
+
 void ErrorScreen::OnAuthFailure(const AuthFailure& error) {
   // The only condition leading here is guest mount failure, which should not
   // happen in practice. For now, just log an error so this situation is visible
@@ -241,11 +265,6 @@
   LOG(FATAL);
 }
 
-ErrorScreen::ConnectRequestCallbackSubscription
-ErrorScreen::RegisterConnectRequestCallback(const base::Closure& callback) {
-  return connect_request_callbacks_.Add(callback);
-}
-
 void ErrorScreen::DefaultHideCallback() {
   if (parent_screen_ != OobeScreen::SCREEN_UNKNOWN && view_)
     view_->ShowOobeScreen(parent_screen_);
diff --git a/chrome/browser/chromeos/login/screens/error_screen.h b/chrome/browser/chromeos/login/screens/error_screen.h
index 70f36f08..42cdd93 100644
--- a/chrome/browser/chromeos/login/screens/error_screen.h
+++ b/chrome/browser/chromeos/login/screens/error_screen.h
@@ -7,11 +7,12 @@
 
 #include <memory>
 
+#include "base/callback_list.h"
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/chromeos/login/screens/base_screen.h"
 #include "chrome/browser/chromeos/login/screens/network_error.h"
-#include "chrome/browser/chromeos/login/screens/network_error_model.h"
 #include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "chrome/browser/ui/webui/chromeos/login/network_state_informer.h"
 #include "chromeos/login/auth/login_performer.h"
@@ -23,38 +24,83 @@
 class NetworkErrorView;
 
 // Controller for the error screen.
-class ErrorScreen : public NetworkErrorModel, public LoginPerformer::Delegate {
+class ErrorScreen : public BaseScreen, public LoginPerformer::Delegate {
  public:
-  typedef std::unique_ptr<base::CallbackList<void()>::Subscription>
-      ConnectRequestCallbackSubscription;
+  using ConnectRequestCallbackSubscription =
+      std::unique_ptr<base::CallbackList<void()>::Subscription>;
+
+  // TODO(jdufault): Some of these are no longer used and can be removed. See
+  // crbug.com/672142.
+  static const char kUserActionConfigureCertsButtonClicked[];
+  static const char kUserActionDiagnoseButtonClicked[];
+  static const char kUserActionLaunchOobeGuestSessionClicked[];
+  static const char kUserActionLocalStateErrorPowerwashButtonClicked[];
+  static const char kUserActionRebootButtonClicked[];
+  static const char kUserActionShowCaptivePortalClicked[];
+  static const char kUserActionConnectRequested[];
 
   ErrorScreen(BaseScreenDelegate* base_screen_delegate, NetworkErrorView* view);
   ~ErrorScreen() override;
 
-  // NetworkErrorModel:
+  // Toggles the guest sign-in prompt.
+  void AllowGuestSignin(bool allowed);
+
+  // Toggles the offline sign-in.
+  void AllowOfflineLogin(bool allowed);
+
+  // Initializes captive portal dialog and shows that if needed.
+  virtual void FixCaptivePortal();
+
+  NetworkError::UIState GetUIState() const;
+  NetworkError::ErrorState GetErrorState() const;
+
+  // Returns id of the screen behind error screen ("caller" screen).
+  // Returns OobeScreen::SCREEN_UNKNOWN if error screen isn't the current
+  // screen.
+  OobeScreen GetParentScreen() const;
+
+  // Called when we're asked to hide captive portal dialog.
+  void HideCaptivePortal();
+
+  // This method is called, when view is being destroyed. Note, if model
+  // is destroyed earlier then it has to call Unbind().
+  void OnViewDestroyed(NetworkErrorView* view);
+
+  // Sets current UI state.
+  virtual void SetUIState(NetworkError::UIState ui_state);
+
+  // Sets current error screen content according to current UI state,
+  // |error_state|, and |network|.
+  virtual void SetErrorState(NetworkError::ErrorState error_state,
+                             const std::string& network);
+
+  // Sets "parent screen" i.e. one that has initiated this network error screen
+  // instance.
+  void SetParentScreen(OobeScreen parent_screen);
+
+  // Sets callback that is called on hide.
+  void SetHideCallback(const base::Closure& on_hide);
+
+  // Shows captive portal dialog.
+  void ShowCaptivePortal();
+
+  // Toggles the connection pending indicator.
+  void ShowConnectingIndicator(bool show);
+
+  // Register a callback to be invoked when the user indicates that an attempt
+  // to connect to the network should be made.
+  ConnectRequestCallbackSubscription RegisterConnectRequestCallback(
+      const base::Closure& callback);
+
+  // BaseScreen overrides:
   void Show() override;
   void Hide() override;
   void OnShow() override;
   void OnHide() override;
   void OnUserAction(const std::string& action_id) override;
-  void OnContextKeyUpdated(const ::login::ScreenContext::KeyType& key) override;
-  void AllowGuestSignin(bool allowed) override;
-  void AllowOfflineLogin(bool allowed) override;
-  void FixCaptivePortal() override;
-  NetworkError::UIState GetUIState() const override;
-  NetworkError::ErrorState GetErrorState() const override;
-  OobeScreen GetParentScreen() const override;
-  void HideCaptivePortal() override;
-  void OnViewDestroyed(NetworkErrorView* view) override;
-  void SetUIState(NetworkError::UIState ui_state) override;
-  void SetErrorState(NetworkError::ErrorState error_state,
-                     const std::string& network) override;
-  void SetParentScreen(OobeScreen parent_screen) override;
-  void SetHideCallback(const base::Closure& on_hide) override;
-  void ShowCaptivePortal() override;
-  void ShowConnectingIndicator(bool show) override;
 
-  // LoginPerformer::Delegate implementation:
+ private:
+  // LoginPerformer::Delegate overrides:
   void OnAuthFailure(const AuthFailure& error) override;
   void OnAuthSuccess(const UserContext& user_context) override;
   void OnOffTheRecordAuthSuccess() override;
@@ -63,12 +109,6 @@
   void PolicyLoadFailed() override;
   void SetAuthFlowOffline(bool offline) override;
 
-  // Register a callback to be invoked when the user indicates that an attempt
-  // to connect to the network should be made.
-  ConnectRequestCallbackSubscription RegisterConnectRequestCallback(
-      const base::Closure& callback);
-
- private:
   // Default hide_closure for Hide().
   void DefaultHideCallback();
 
diff --git a/chrome/browser/chromeos/login/screens/mock_error_screen.cc b/chrome/browser/chromeos/login/screens/mock_error_screen.cc
index 9bb300e..30bf748 100644
--- a/chrome/browser/chromeos/login/screens/mock_error_screen.cc
+++ b/chrome/browser/chromeos/login/screens/mock_error_screen.cc
@@ -33,23 +33,23 @@
   MockSetErrorState(error_state, network);
 }
 
-MockNetworkErrorView::MockNetworkErrorView() : model_(nullptr) {
+MockNetworkErrorView::MockNetworkErrorView() {
   EXPECT_CALL(*this, MockBind(_)).Times(AtLeast(1));
   EXPECT_CALL(*this, MockUnbind()).Times(AtLeast(1));
 }
 
 MockNetworkErrorView::~MockNetworkErrorView() {
-  if (model_)
-    model_->OnViewDestroyed(this);
+  if (screen_)
+    screen_->OnViewDestroyed(this);
 }
 
-void MockNetworkErrorView::Bind(NetworkErrorModel& model) {
-  model_ = &model;
-  MockBind(model);
+void MockNetworkErrorView::Bind(ErrorScreen* screen) {
+  screen_ = screen;
+  MockBind(screen);
 }
 
 void MockNetworkErrorView::Unbind() {
-  model_ = nullptr;
+  screen_ = nullptr;
   MockUnbind();
 }
 
diff --git a/chrome/browser/chromeos/login/screens/mock_error_screen.h b/chrome/browser/chromeos/login/screens/mock_error_screen.h
index 0a073a0..e7c3b12 100644
--- a/chrome/browser/chromeos/login/screens/mock_error_screen.h
+++ b/chrome/browser/chromeos/login/screens/mock_error_screen.h
@@ -7,7 +7,6 @@
 
 #include "chrome/browser/chromeos/login/screens/error_screen.h"
 #include "chrome/browser/chromeos/login/screens/network_error.h"
-#include "chrome/browser/chromeos/login/screens/network_error_model.h"
 #include "chrome/browser/chromeos/login/screens/network_error_view.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
@@ -36,17 +35,17 @@
   MockNetworkErrorView();
   virtual ~MockNetworkErrorView();
 
-  void Bind(NetworkErrorModel& model) override;
+  void Bind(ErrorScreen* screen) override;
   void Unbind() override;
 
   MOCK_METHOD0(Show, void());
   MOCK_METHOD0(Hide, void());
-  MOCK_METHOD1(MockBind, void(NetworkErrorModel& model));
+  MOCK_METHOD1(MockBind, void(ErrorScreen* screen));
   MOCK_METHOD0(MockUnbind, void());
   MOCK_METHOD1(ShowOobeScreen, void(OobeScreen screen));
 
  private:
-  NetworkErrorModel* model_;
+  ErrorScreen* screen_ = nullptr;
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/network_error.h b/chrome/browser/chromeos/login/screens/network_error.h
index 8e9bb01..cc85b42 100644
--- a/chrome/browser/chromeos/login/screens/network_error.h
+++ b/chrome/browser/chromeos/login/screens/network_error.h
@@ -7,6 +7,8 @@
 
 namespace chromeos {
 
+// TODO(jdufault): Remove Network prefix from NetworkError associated classes.
+// See crbug.com/672142
 class NetworkError {
  public:
   enum UIState {
diff --git a/chrome/browser/chromeos/login/screens/network_error_model.cc b/chrome/browser/chromeos/login/screens/network_error_model.cc
deleted file mode 100644
index 2b7cfa2..0000000
--- a/chrome/browser/chromeos/login/screens/network_error_model.cc
+++ /dev/null
@@ -1,40 +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 "chrome/browser/chromeos/login/screens/network_error_model.h"
-
-#include "chrome/browser/chromeos/login/wizard_controller.h"
-
-namespace chromeos {
-
-const char NetworkErrorModel::kContextKeyErrorStateCode[] = "error-state-code";
-const char NetworkErrorModel::kContextKeyErrorStateNetwork[] =
-    "error-state-network";
-const char NetworkErrorModel::kContextKeyGuestSigninAllowed[] =
-    "guest-signin-allowed";
-const char NetworkErrorModel::kContextKeyOfflineSigninAllowed[] =
-    "offline-signin-allowed";
-const char NetworkErrorModel::kContextKeyShowConnectingIndicator[] =
-    "show-connecting-indicator";
-const char NetworkErrorModel::kContextKeyUIState[] = "ui-state";
-const char NetworkErrorModel::kUserActionConfigureCertsButtonClicked[] =
-    "configure-certs";
-const char NetworkErrorModel::kUserActionDiagnoseButtonClicked[] = "diagnose";
-const char NetworkErrorModel::kUserActionLaunchOobeGuestSessionClicked[] =
-    "launch-oobe-guest";
-const char
-    NetworkErrorModel::kUserActionLocalStateErrorPowerwashButtonClicked[] =
-        "local-state-error-powerwash";
-const char NetworkErrorModel::kUserActionRebootButtonClicked[] = "reboot";
-const char NetworkErrorModel::kUserActionShowCaptivePortalClicked[] =
-    "show-captive-portal";
-const char NetworkErrorModel::kUserActionConnectRequested[] =
-    "connect-requested";
-
-NetworkErrorModel::NetworkErrorModel(BaseScreenDelegate* base_screen_delegate)
-    : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_ERROR_MESSAGE) {}
-
-NetworkErrorModel::~NetworkErrorModel() {}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/network_error_model.h b/chrome/browser/chromeos/login/screens/network_error_model.h
deleted file mode 100644
index 28ab1cd..0000000
--- a/chrome/browser/chromeos/login/screens/network_error_model.h
+++ /dev/null
@@ -1,86 +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 CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_NETWORK_ERROR_MODEL_H_
-#define CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_NETWORK_ERROR_MODEL_H_
-
-#include "base/callback_list.h"
-#include "base/memory/ref_counted.h"
-#include "chrome/browser/chromeos/login/oobe_screen.h"
-#include "chrome/browser/chromeos/login/screens/base_screen.h"
-#include "chrome/browser/chromeos/login/screens/network_error.h"
-
-namespace chromeos {
-
-class BaseScreenDelegate;
-class NetworkErrorView;
-
-class NetworkErrorModel : public BaseScreen {
- public:
-  static const char kContextKeyErrorStateCode[];
-  static const char kContextKeyErrorStateNetwork[];
-  static const char kContextKeyGuestSigninAllowed[];
-  static const char kContextKeyOfflineSigninAllowed[];
-  static const char kContextKeyShowConnectingIndicator[];
-  static const char kContextKeyUIState[];
-  static const char kUserActionConfigureCertsButtonClicked[];
-  static const char kUserActionDiagnoseButtonClicked[];
-  static const char kUserActionLaunchOobeGuestSessionClicked[];
-  static const char kUserActionLocalStateErrorPowerwashButtonClicked[];
-  static const char kUserActionRebootButtonClicked[];
-  static const char kUserActionShowCaptivePortalClicked[];
-  static const char kUserActionConnectRequested[];
-
-  explicit NetworkErrorModel(BaseScreenDelegate* base_screen_delegate);
-  ~NetworkErrorModel() override;
-
-  // Toggles the guest sign-in prompt.
-  virtual void AllowGuestSignin(bool allowed) = 0;
-
-  // Toggles the offline sign-in.
-  virtual void AllowOfflineLogin(bool allowed) = 0;
-
-  // Initializes captive portal dialog and shows that if needed.
-  virtual void FixCaptivePortal() = 0;
-
-  virtual NetworkError::UIState GetUIState() const = 0;
-  virtual NetworkError::ErrorState GetErrorState() const = 0;
-
-  // Returns id of the screen behind error screen ("caller" screen).
-  // Returns OobeScreen::SCREEN_UNKNOWN if error screen isn't the current
-  // screen.
-  virtual OobeScreen GetParentScreen() const = 0;
-
-  // Called when we're asked to hide captive portal dialog.
-  virtual void HideCaptivePortal() = 0;
-
-  // This method is called, when view is being destroyed. Note, if model
-  // is destroyed earlier then it has to call Unbind().
-  virtual void OnViewDestroyed(NetworkErrorView* view) = 0;
-
-  // Sets current UI state.
-  virtual void SetUIState(NetworkError::UIState ui_state) = 0;
-
-  // Sets current error screen content according to current UI state,
-  // |error_state|, and |network|.
-  virtual void SetErrorState(NetworkError::ErrorState error_state,
-                             const std::string& network) = 0;
-
-  // Sets "parent screen" i.e. one that has initiated this network error screen
-  // instance.
-  virtual void SetParentScreen(OobeScreen parent_screen) = 0;
-
-  // Sets callback that is called on hide.
-  virtual void SetHideCallback(const base::Closure& on_hide) = 0;
-
-  // Shows captive portal dialog.
-  virtual void ShowCaptivePortal() = 0;
-
-  // Toggles the connection pending indicator.
-  virtual void ShowConnectingIndicator(bool show) = 0;
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_NETWORK_ERROR_MODEL_H_
diff --git a/chrome/browser/chromeos/login/screens/network_error_view.h b/chrome/browser/chromeos/login/screens/network_error_view.h
index 51d28cf2..b58e3e3 100644
--- a/chrome/browser/chromeos/login/screens/network_error_view.h
+++ b/chrome/browser/chromeos/login/screens/network_error_view.h
@@ -10,7 +10,7 @@
 
 namespace chromeos {
 
-class NetworkErrorModel;
+class ErrorScreen;
 
 // Interface for dependency injection between ErrorScreen and its actual
 // representation. Owned by ErrorScreen.
@@ -24,10 +24,10 @@
   // Hides the contents of the screen.
   virtual void Hide() = 0;
 
-  // Binds |model| to the view.
-  virtual void Bind(NetworkErrorModel& model) = 0;
+  // Binds |screen| to the view.
+  virtual void Bind(ErrorScreen* screen) = 0;
 
-  // Unbinds model from the view.
+  // Unbinds the screen from the view.
   virtual void Unbind() = 0;
 
   // Switches to |screen|.
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.cc b/chrome/browser/chromeos/login/session/user_session_manager.cc
index fe16fe3..145a9ee 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager.cc
+++ b/chrome/browser/chromeos/login/session/user_session_manager.cc
@@ -581,14 +581,18 @@
   // type has be set before kiosk app controller takes over, as at that point
   // kiosk app profile would already be initialized - feature session type
   // should be set before that.
-  // TODO(tbarzic): Note that this does not work well for auto-launched
-  //     sessions, as information about whether session was auto-launched is not
-  //     persisted over session restart - http://crbug.com/677340.
   if (user->GetType() == user_manager::USER_TYPE_KIOSK_APP) {
     if (base::CommandLine::ForCurrentProcess()->HasSwitch(
             switches::kLoginUser)) {
+      // For kiosk session crash recovery, feature session type has be set
+      // before kiosk app controller takes over, as at that point iosk app
+      // profile would already be initialized - feature session type
+      // should be set before that.
+      bool auto_launched = base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kAppAutoLaunched);
       extensions::SetCurrentFeatureSessionType(
-          extensions::FeatureSessionType::KIOSK);
+          auto_launched ? extensions::FeatureSessionType::AUTOLAUNCHED_KIOSK
+                        : extensions::FeatureSessionType::KIOSK);
     }
     return;
   }
diff --git a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
index a3f7408..f785b60 100644
--- a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
+++ b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
@@ -30,6 +30,7 @@
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_app_launcher.h"
 #include "chrome/browser/chromeos/login/session/user_session_manager.h"
 #include "chrome/browser/chromeos/login/signin/auth_sync_observer.h"
@@ -859,6 +860,13 @@
   // Disable window animation since kiosk app runs in a single full screen
   // window and window animation causes start-up janks.
   command_line->AppendSwitch(wm::switches::kWindowAnimationsDisabled);
+
+  // If restoring auto-launched kiosk session, make sure the app is marked
+  // as auto-launched.
+  if (command_line->HasSwitch(switches::kLoginUser) &&
+      command_line->HasSwitch(switches::kAppAutoLaunched)) {
+    KioskAppManager::Get()->SetAppWasAutoLaunchedWithZeroDelay(kiosk_app_id);
+  }
 }
 
 void ChromeUserManagerImpl::ArcKioskAppLoggedIn(user_manager::User* user) {
diff --git a/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc b/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc
index 9b4e4a18..369beee 100644
--- a/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc
+++ b/chrome/browser/extensions/api/browsing_data/browsing_data_api.cc
@@ -269,9 +269,7 @@
       base::Time::UnixEpoch() :
       base::Time::FromDoubleT(ms_since_epoch / 1000.0);
 
-  removal_mask_ = GetRemovalMask();
-  if (bad_message())
-    return false;
+  EXTENSION_FUNCTION_VALIDATE(GetRemovalMask(&removal_mask_));
 
   // Check for prohibited data types.
   if (!IsRemovalPermitted(removal_mask_, GetProfile()->GetPrefs())) {
@@ -365,87 +363,96 @@
   return mask;
 }
 
-// Parses the |dataToRemove| argument to generate the removal mask. Sets
-// |bad_message_| (like EXTENSION_FUNCTION_VALIDATE would if this were a bool
-// method) if 'dataToRemove' is not present or any data-type keys don't have
-// supported (boolean) values.
-int BrowsingDataRemoveFunction::GetRemovalMask() {
+// Parses the |dataToRemove| argument to generate the removal mask.
+// Returns false if parse was not successful, i.e. if 'dataToRemove' is not
+// present or any data-type keys don't have supported (boolean) values.
+bool BrowsingDataRemoveFunction::GetRemovalMask(int* removal_mask) {
   base::DictionaryValue* data_to_remove;
-  if (!args_->GetDictionary(1, &data_to_remove)) {
-    set_bad_message(true);
-    return 0;
-  }
+  if (!args_->GetDictionary(1, &data_to_remove))
+    return false;
 
-  int removal_mask = 0;
-
+  *removal_mask = 0;
   for (base::DictionaryValue::Iterator i(*data_to_remove);
        !i.IsAtEnd();
        i.Advance()) {
     bool selected = false;
-    if (!i.value().GetAsBoolean(&selected)) {
-      set_bad_message(true);
-      return 0;
-    }
+    if (!i.value().GetAsBoolean(&selected))
+      return false;
     if (selected)
-      removal_mask |= MaskForKey(i.key().c_str());
+      *removal_mask |= MaskForKey(i.key().c_str());
   }
 
-  return removal_mask;
+  return true;
 }
 
-int BrowsingDataRemoveAppcacheFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_APPCACHE;
+bool BrowsingDataRemoveAppcacheFunction::GetRemovalMask(int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_APPCACHE;
+  return true;
 }
 
-int BrowsingDataRemoveCacheFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_CACHE;
+bool BrowsingDataRemoveCacheFunction::GetRemovalMask(int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_CACHE;
+  return true;
 }
 
-int BrowsingDataRemoveCookiesFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_COOKIES |
-         BrowsingDataRemover::REMOVE_CHANNEL_IDS;
+bool BrowsingDataRemoveCookiesFunction::GetRemovalMask(int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_COOKIES |
+                  BrowsingDataRemover::REMOVE_CHANNEL_IDS;
+  return true;
 }
 
-int BrowsingDataRemoveDownloadsFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_DOWNLOADS;
+bool BrowsingDataRemoveDownloadsFunction::GetRemovalMask(int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_DOWNLOADS;
+  return true;
 }
 
-int BrowsingDataRemoveFileSystemsFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_FILE_SYSTEMS;
+bool BrowsingDataRemoveFileSystemsFunction::GetRemovalMask(int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_FILE_SYSTEMS;
+  return true;
 }
 
-int BrowsingDataRemoveFormDataFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_FORM_DATA;
+bool BrowsingDataRemoveFormDataFunction::GetRemovalMask(int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_FORM_DATA;
+  return true;
 }
 
-int BrowsingDataRemoveHistoryFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_HISTORY;
+bool BrowsingDataRemoveHistoryFunction::GetRemovalMask(int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_HISTORY;
+  return true;
 }
 
-int BrowsingDataRemoveIndexedDBFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_INDEXEDDB;
+bool BrowsingDataRemoveIndexedDBFunction::GetRemovalMask(int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_INDEXEDDB;
+  return true;
 }
 
-int BrowsingDataRemoveLocalStorageFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_LOCAL_STORAGE;
+bool BrowsingDataRemoveLocalStorageFunction::GetRemovalMask(int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_LOCAL_STORAGE;
+  return true;
 }
 
-int BrowsingDataRemovePluginDataFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_PLUGIN_DATA;
+bool BrowsingDataRemovePluginDataFunction::GetRemovalMask(int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_PLUGIN_DATA;
+  return true;
 }
 
-int BrowsingDataRemovePasswordsFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_PASSWORDS;
+bool BrowsingDataRemovePasswordsFunction::GetRemovalMask(int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_PASSWORDS;
+  return true;
 }
 
-int BrowsingDataRemoveServiceWorkersFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_SERVICE_WORKERS;
+bool BrowsingDataRemoveServiceWorkersFunction::GetRemovalMask(
+    int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_SERVICE_WORKERS;
+  return true;
 }
 
-int BrowsingDataRemoveCacheStorageFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_CACHE_STORAGE;
+bool BrowsingDataRemoveCacheStorageFunction::GetRemovalMask(int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_CACHE_STORAGE;
+  return true;
 }
 
-int BrowsingDataRemoveWebSQLFunction::GetRemovalMask() {
-  return BrowsingDataRemover::REMOVE_WEBSQL;
+bool BrowsingDataRemoveWebSQLFunction::GetRemovalMask(int* removal_mask) {
+  *removal_mask = BrowsingDataRemover::REMOVE_WEBSQL;
+  return true;
 }
diff --git a/chrome/browser/extensions/api/browsing_data/browsing_data_api.h b/chrome/browser/extensions/api/browsing_data/browsing_data_api.h
index 4005717..0b6f231 100644
--- a/chrome/browser/extensions/api/browsing_data/browsing_data_api.h
+++ b/chrome/browser/extensions/api/browsing_data/browsing_data_api.h
@@ -100,7 +100,9 @@
 
   // Children should override this method to provide the proper removal mask
   // based on the API call they represent.
-  virtual int GetRemovalMask() = 0;
+  // Returns whether or not removal mask retrieval was successful.
+  // |removal_mask| is populated with the result, if successful.
+  virtual bool GetRemovalMask(int* removal_mask) = 0;
 
  private:
   // Updates the removal bitmask according to whether removing plugin data is
@@ -130,7 +132,7 @@
   ~BrowsingDataRemoveAppcacheFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemoveFunction : public BrowsingDataRemoverFunction {
@@ -141,7 +143,7 @@
   ~BrowsingDataRemoveFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemoveCacheFunction : public BrowsingDataRemoverFunction {
@@ -153,7 +155,7 @@
   ~BrowsingDataRemoveCacheFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemoveCookiesFunction : public BrowsingDataRemoverFunction {
@@ -165,7 +167,7 @@
   ~BrowsingDataRemoveCookiesFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemoveDownloadsFunction : public BrowsingDataRemoverFunction {
@@ -177,7 +179,7 @@
   ~BrowsingDataRemoveDownloadsFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemoveFileSystemsFunction
@@ -190,7 +192,7 @@
   ~BrowsingDataRemoveFileSystemsFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemoveFormDataFunction : public BrowsingDataRemoverFunction {
@@ -202,7 +204,7 @@
   ~BrowsingDataRemoveFormDataFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemoveHistoryFunction : public BrowsingDataRemoverFunction {
@@ -214,7 +216,7 @@
   ~BrowsingDataRemoveHistoryFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemoveIndexedDBFunction : public BrowsingDataRemoverFunction {
@@ -226,7 +228,7 @@
   ~BrowsingDataRemoveIndexedDBFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemoveLocalStorageFunction
@@ -239,7 +241,7 @@
   ~BrowsingDataRemoveLocalStorageFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemovePluginDataFunction
@@ -252,7 +254,7 @@
   ~BrowsingDataRemovePluginDataFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemovePasswordsFunction : public BrowsingDataRemoverFunction {
@@ -264,7 +266,7 @@
   ~BrowsingDataRemovePasswordsFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemoveServiceWorkersFunction
@@ -277,7 +279,7 @@
   ~BrowsingDataRemoveServiceWorkersFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemoveCacheStorageFunction
@@ -290,7 +292,7 @@
   ~BrowsingDataRemoveCacheStorageFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 class BrowsingDataRemoveWebSQLFunction : public BrowsingDataRemoverFunction {
@@ -302,7 +304,7 @@
   ~BrowsingDataRemoveWebSQLFunction() override {}
 
   // BrowsingDataRemoverFunction:
-  int GetRemovalMask() override;
+  bool GetRemovalMask(int* removal_mask) override;
 };
 
 #endif  // CHROME_BROWSER_EXTENSIONS_API_BROWSING_DATA_BROWSING_DATA_API_H_
diff --git a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.cc
index 016e3bc..d627499 100644
--- a/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/data_reduction_proxy_metrics_observer.cc
@@ -326,7 +326,8 @@
       timing.navigation_start, response_start, load_event_start,
       first_image_paint, first_contentful_paint,
       experimental_first_meaningful_paint,
-      parse_blocked_on_script_load_duration, parse_stop);
+      parse_blocked_on_script_load_duration, parse_stop, network_bytes_,
+      original_network_bytes_);
   GetPingbackClient()->SendPingback(*data_, data_reduction_proxy_timing);
 }
 
diff --git a/chrome/browser/printing/print_view_manager_base.cc b/chrome/browser/printing/print_view_manager_base.cc
index 33ada8c..964aef9d 100644
--- a/chrome/browser/printing/print_view_manager_base.cc
+++ b/chrome/browser/printing/print_view_manager_base.cc
@@ -46,7 +46,7 @@
 
 #if defined(OS_WIN)
 #include "base/command_line.h"
-#include "chrome/common/chrome_switches.h"
+#include "chrome/common/chrome_features.h"
 #endif
 
 using base::TimeDelta;
@@ -187,7 +187,7 @@
     bool print_text_with_gdi =
         document->settings().print_text_with_gdi() &&
         !document->settings().printer_is_xps() &&
-        switches::GDITextPrintingEnabled();
+        base::FeatureList::IsEnabled(features::kGdiTextPrinting);
     scoped_refptr<base::RefCountedBytes> bytes = new base::RefCountedBytes(
         reinterpret_cast<const unsigned char*>(shared_buf->memory()),
         params.data_size);
diff --git a/chrome/browser/resources/chromeos/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/chromevox/BUILD.gn
index c0b4895..da1a4dd 100644
--- a/chrome/browser/resources/chromeos/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/chromevox/BUILD.gn
@@ -130,6 +130,7 @@
   "cvox2/background/command_handler.js",
   "cvox2/background/constants.js",
   "cvox2/background/cursors.js",
+  "cvox2/background/custom_automation_event.js",
   "cvox2/background/desktop_automation_handler.js",
   "cvox2/background/earcon_engine.js",
   "cvox2/background/editing.js",
diff --git a/chrome/browser/resources/chromeos/chromevox/common/chrome_extension_externs.js b/chrome/browser/resources/chromeos/chromevox/common/chrome_extension_externs.js
index 90ef185..66d44a5 100644
--- a/chrome/browser/resources/chromeos/chromevox/common/chrome_extension_externs.js
+++ b/chrome/browser/resources/chromeos/chromevox/common/chrome_extension_externs.js
@@ -12,13 +12,5 @@
 /** @type {function() : !Object} */
 chrome.app.getDetails;
 
-// Media related automation actions and events.
-chrome.automation.AutomationNode.prototype.resumeMedia = function() {};
-chrome.automation.AutomationNode.prototype.startDuckingMedia = function() {};
-chrome.automation.AutomationNode.prototype.stopDuckingMedia = function() {};
-chrome.automation.AutomationNode.prototype.suspendMedia = function() {};
-chrome.automation.EventType.mediaStartedPlaying;
-chrome.automation.EventType.mediaStoppedPlaying;
-
 /** @type {string|undefined} */
 chrome.automation.AutomationNode.prototype.chromeChannel;
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_object_constructor_installer.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_object_constructor_installer.js
index cdb5e93e..de18ba9 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_object_constructor_installer.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_object_constructor_installer.js
@@ -22,13 +22,13 @@
   chrome.automation.AutomationNode =
       /** @type {function (new:chrome.automation.AutomationNode)} */(
           node.constructor);
-  node.addEventListener(chrome.automation.EventType.childrenChanged,
+  node.addEventListener(chrome.automation.EventType.CHILDREN_CHANGED,
       function installAutomationEvent(e) {
         chrome.automation.AutomationEvent =
             /** @type {function (new:chrome.automation.AutomationEvent)} */(
                 e.constructor);
         node.removeEventListener(
-            chrome.automation.EventType.childrenChanged,
+            chrome.automation.EventType.CHILDREN_CHANGED,
             installAutomationEvent,
             true);
         callback();
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_predicate.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_predicate.js
index bb028e0..04f3bd5 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_predicate.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_predicate.js
@@ -16,6 +16,7 @@
 var AutomationNode = chrome.automation.AutomationNode;
 var Dir = constants.Dir;
 var Role = chrome.automation.RoleType;
+var State = chrome.automation.StateType;
 
 /**
  * @constructor
@@ -58,21 +59,21 @@
 
 /** @type {AutomationPredicate.Unary} */
 AutomationPredicate.checkBox =
-    AutomationPredicate.roles([Role.checkBox, Role.switch]);
+    AutomationPredicate.roles([Role.CHECK_BOX, Role.SWITCH]);
 /** @type {AutomationPredicate.Unary} */
 AutomationPredicate.comboBox = AutomationPredicate.roles(
-    [Role.comboBox, Role.popUpButton, Role.menuListPopup]);
+    [Role.COMBO_BOX, Role.POP_UP_BUTTON, Role.MENU_LIST_POPUP]);
 /** @type {AutomationPredicate.Unary} */
-AutomationPredicate.heading = AutomationPredicate.roles([Role.heading]);
+AutomationPredicate.heading = AutomationPredicate.roles([Role.HEADING]);
 /** @type {AutomationPredicate.Unary} */
 AutomationPredicate.inlineTextBox =
-    AutomationPredicate.roles([Role.inlineTextBox]);
+    AutomationPredicate.roles([Role.INLINE_TEXT_BOX]);
 /** @type {AutomationPredicate.Unary} */
-AutomationPredicate.link = AutomationPredicate.roles([Role.link]);
+AutomationPredicate.link = AutomationPredicate.roles([Role.LINK]);
 /** @type {AutomationPredicate.Unary} */
-AutomationPredicate.row = AutomationPredicate.roles([Role.row]);
+AutomationPredicate.row = AutomationPredicate.roles([Role.ROW]);
 /** @type {AutomationPredicate.Unary} */
-AutomationPredicate.table = AutomationPredicate.roles([Role.grid, Role.table]);
+AutomationPredicate.table = AutomationPredicate.roles([Role.GRID, Role.TABLE]);
 
 /**
  * @param {!AutomationNode} node
@@ -100,13 +101,13 @@
     AutomationPredicate.editText
   ],
   anyRole: [
-    Role.checkBox,
-    Role.colorWell,
-    Role.listBox,
-    Role.slider,
-    Role.switch,
-    Role.tab,
-    Role.tree
+    Role.CHECK_BOX,
+    Role.COLOR_WELL,
+    Role.LIST_BOX,
+    Role.SLIDER,
+    Role.SWITCH,
+    Role.TAB,
+    Role.TREE
   ]
 });
 
@@ -116,12 +117,12 @@
     AutomationPredicate.formField,
   ],
   anyRole: [
-    Role.disclosureTriangle,
-    Role.menuItem,
-    Role.menuItemCheckBox,
-    Role.menuItemRadio,
-    Role.menuListOption,
-    Role.scrollBar
+    Role.DISCLOSURE_TRIANGLE,
+    Role.MENU_ITEM,
+    Role.MENU_ITEM_CHECK_BOX,
+    Role.MENU_ITEM_RADIO,
+    Role.MENU_LIST_OPTION,
+    Role.SCROLL_BAR
   ]
 });
 
@@ -130,7 +131,7 @@
  * @return {boolean}
  */
 AutomationPredicate.image = function(node) {
-  return node.role == Role.image && !!(node.name || node.url);
+  return node.role == Role.IMAGE && !!(node.name || node.url);
 };
 
 /** @type {AutomationPredicate.Unary} */
@@ -139,21 +140,21 @@
     AutomationPredicate.control
   ],
   anyRole: [
-    Role.link
+    Role.LINK
   ]
 });
 
 /** @type {AutomationPredicate.Unary} */
 AutomationPredicate.landmark = AutomationPredicate.roles([
-    Role.application,
-    Role.banner,
-    Role.complementary,
-    Role.contentInfo,
-    Role.form,
-    Role.main,
-    Role.navigation,
-    Role.region,
-    Role.search]);
+    Role.APPLICATION,
+    Role.BANNER,
+    Role.COMPLEMENTARY,
+    Role.CONTENT_INFO,
+    Role.FORM,
+    Role.MAIN,
+    Role.NAVIGATION,
+    Role.REGION,
+    Role.SEARCH]);
 
 /**
  * @param {!AutomationNode} node
@@ -177,14 +178,14 @@
  */
 AutomationPredicate.leaf = function(node) {
   return !node.firstChild ||
-      node.role == Role.button ||
-      node.role == Role.buttonDropDown ||
-      node.role == Role.popUpButton ||
-      node.role == Role.slider ||
-      node.role == Role.textField ||
-      node.state.invisible ||
+      node.role == Role.BUTTON ||
+      node.role == Role.BUTTON_DROP_DOWN ||
+      node.role == Role.POP_UP_BUTTON ||
+      node.role == Role.SLIDER ||
+      node.role == Role.TEXT_FIELD ||
+      node.state[State.INVISIBLE] ||
       node.children.every(function(n) {
-        return n.state.invisible;
+        return n.state[State.INVISIBLE];
       });
 };
 
@@ -206,7 +207,7 @@
  */
 AutomationPredicate.leafOrStaticText = function(node) {
   return AutomationPredicate.leaf(node) ||
-      node.role == Role.staticText;
+      node.role == Role.STATIC_TEXT;
 };
 
 /**
@@ -231,9 +232,9 @@
   return node.state.focusable ||
       (AutomationPredicate.leafOrStaticText(node) &&
        (/\S+/.test(node.name) ||
-        (node.role != Role.lineBreak &&
-         node.role != Role.staticText &&
-         node.role != Role.inlineTextBox)));
+        (node.role != Role.LINE_BREAK &&
+         node.role != Role.STATIC_TEXT &&
+         node.role != Role.INLINE_TEXT_BOX)));
 };
 
 /**
@@ -243,9 +244,9 @@
  */
 AutomationPredicate.group = AutomationPredicate.match({
   anyRole: [
-    Role.heading,
-    Role.list,
-    Role.paragraph
+    Role.HEADING,
+    Role.LIST,
+    Role.PARAGRAPH
   ],
   anyPredicate: [
     AutomationPredicate.editText,
@@ -277,18 +278,18 @@
 AutomationPredicate.container = function(node) {
   return AutomationPredicate.match({
     anyRole: [
-      Role.div,
-      Role.document,
-      Role.group,
-      Role.listItem,
-      Role.toolbar,
-      Role.window],
+      Role.DIV,
+      Role.DOCUMENT,
+      Role.GROUP,
+      Role.LIST_ITEM,
+      Role.TOOLBAR,
+      Role.WINDOW],
     anyPredicate: [
       AutomationPredicate.landmark,
       AutomationPredicate.structuralContainer,
       function(node) {
         // For example, crosh.
-        return (node.role == Role.textField && node.state.readOnly);
+        return (node.role == Role.TEXT_FIELD && node.state.readOnly);
       },
       function(node) {
         return (node.state.editable &&
@@ -305,14 +306,14 @@
  * @return {boolean}
  */
 AutomationPredicate.structuralContainer = AutomationPredicate.roles([
-    Role.alertDialog,
-    Role.dialog,
-    Role.rootWebArea,
-    Role.webView,
-    Role.window,
-    Role.embeddedObject,
-    Role.iframe,
-    Role.iframePresentational]);
+    Role.ALERT_DIALOG,
+    Role.DIALOG,
+    Role.ROOT_WEB_AREA,
+    Role.WEB_VIEW,
+    Role.WINDOW,
+    Role.EMBEDDED_OBJECT,
+    Role.IFRAME,
+    Role.IFRAME_PRESENTATIONAL]);
 
 /**
  * Returns whether the given node should not be crossed when performing
@@ -322,21 +323,21 @@
  */
 AutomationPredicate.root = function(node) {
   switch (node.role) {
-    case Role.window:
+    case Role.WINDOW:
       return true;
-    case Role.dialog:
+    case Role.DIALOG:
       // The below logic handles nested dialogs properly in the desktop tree
       // like that found in a bubble view.
-      return node.root.role != Role.desktop ||
+      return node.root.role != Role.DESKTOP ||
           (!!node.parent &&
-           node.parent.role == Role.window &&
+           node.parent.role == Role.WINDOW &&
            node.parent.children.every(function(child) {
-             return node.role == Role.window || node.role == Role.dialog;
+             return node.role == Role.WINDOW || node.role == Role.DIALOG;
            }));
-    case Role.toolbar:
-      return node.root.role == Role.desktop;
-    case Role.rootWebArea:
-      return !node.parent || node.parent.root.role == Role.desktop;
+    case Role.TOOLBAR:
+      return node.root.role == Role.DESKTOP;
+    case Role.ROOT_WEB_AREA:
+      return !node.parent || node.parent.root.role == Role.DESKTOP;
     default:
       return false;
   }
@@ -359,7 +360,7 @@
     return true;
 
   // Ignore list markers since we already announce listitem role.
-  if (node.role == Role.listMarker)
+  if (node.role == Role.LIST_MARKER)
     return true;
 
   // Don't ignore nodes with names or name-like attribute.
@@ -368,15 +369,15 @@
 
   // Ignore some roles.
   return AutomationPredicate.leaf(node) &&
-      (AutomationPredicate.roles([Role.client,
-                                  Role.column,
-                                  Role.div,
-                                  Role.group,
-                                  Role.image,
-                                  Role.staticText,
-                                  Role.svgRoot,
-                                  Role.tableHeaderContainer,
-                                  Role.unknown
+      (AutomationPredicate.roles([Role.CLIENT,
+                                  Role.COLUMN,
+                                  Role.DIV,
+                                  Role.GROUP,
+                                  Role.IMAGE,
+                                  Role.STATIC_TEXT,
+                                  Role.SVG_ROOT,
+                                  Role.TABLE_HEADER_CONTAINER,
+                                  Role.UNKNOWN
                                  ])(node));
 };
 
@@ -386,11 +387,11 @@
  * @return {boolean}
  */
 AutomationPredicate.checkable = AutomationPredicate.roles([
-  Role.checkBox,
-  Role.radioButton,
-  Role.menuItemCheckBox,
-  Role.menuItemRadio,
-  Role.treeItem]);
+  Role.CHECK_BOX,
+  Role.RADIO_BUTTON,
+  Role.MENU_ITEM_CHECK_BOX,
+  Role.MENU_ITEM_RADIO,
+  Role.TREE_ITEM]);
 
 // Table related predicates.
 /**
@@ -399,9 +400,9 @@
  * @return {boolean}
  */
 AutomationPredicate.cellLike = AutomationPredicate.roles([
-  Role.cell,
-  Role.rowHeader,
-  Role.columnHeader]);
+  Role.CELL,
+  Role.ROW_HEADER,
+  Role.COLUMN_HEADER]);
 
 /**
  * Returns a predicate that will match against the directed next cell taking
@@ -481,7 +482,7 @@
  */
 AutomationPredicate.makeHeadingPredicate = function(level) {
   return function(node) {
-    return node.role == Role.heading && node.hierarchicalLevel == level;
+    return node.role == Role.HEADING && node.hierarchicalLevel == level;
   };
 };
 
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util.js
index 83d0b6b3..77e323a 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util.js
@@ -237,7 +237,7 @@
 
   // Given two non-desktop roots, consider them in the "same" tree.
   return a.root === b.root ||
-      (a.root.role == b.root.role && a.root.role == RoleType.rootWebArea);
+      (a.root.role == b.root.role && a.root.role == RoleType.ROOT_WEB_AREA);
 };
 
 /**
@@ -285,13 +285,13 @@
  */
 AutomationUtil.getTopLevelRoot = function(node) {
   var root = node.root;
-  if (!root || root.role == RoleType.desktop)
+  if (!root || root.role == RoleType.DESKTOP)
     return null;
 
   while (root &&
       root.parent &&
       root.parent.root &&
-      root.parent.root.role != RoleType.desktop) {
+      root.parent.root.role != RoleType.DESKTOP) {
     root = root.parent.root;
   }
   return root;
@@ -322,8 +322,8 @@
   if (!node)
     return '';
 
-  if (node.role === RoleType.textField)
-    return node.value;
+  if (node.role === RoleType.TEXT_FIELD)
+    return node.value || '';
   return node.name || '';
 };
 
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util_test.extjs
index e5bf761..0b23cf5 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/automation_util_test.extjs
@@ -26,7 +26,7 @@
 
     /** Filters nodes not rooted by desktop. */
     function filterNonDesktopRoot(node) {
-      return node.root.role != RoleType.desktop;
+      return node.root.role != RoleType.DESKTOP;
     }
 
     window.getNonDesktopAncestors = function(node) {
@@ -83,8 +83,8 @@
 
     var leftAncestors = getNonDesktopAncestors(leftmost);
     var rightAncestors = getNonDesktopAncestors(rightmost);
-    assertEquals(RoleType.link, leftmost.role);
-    assertEquals(RoleType.button, rightmost.role);
+    assertEquals(RoleType.LINK, leftmost.role);
+    assertEquals(RoleType.BUTTON, rightmost.role);
     assertEquals(
         1, AutomationUtil.getDivergence(leftAncestors, rightAncestors));
 
@@ -97,17 +97,17 @@
         getNonDesktopUniqueAncestors(leftmost, rightmost);
 
     assertEquals(2, uniqueAncestorsLeft.length);
-    assertEquals(RoleType.paragraph,
+    assertEquals(RoleType.PARAGRAPH,
         uniqueAncestorsLeft[0].role);
-    assertEquals(RoleType.link,
+    assertEquals(RoleType.LINK,
         uniqueAncestorsLeft[1].role);
 
     assertEquals(3, uniqueAncestorsRight.length);
-    assertEquals(RoleType.heading,
+    assertEquals(RoleType.HEADING,
         uniqueAncestorsRight[0].role);
-    assertEquals(RoleType.group,
+    assertEquals(RoleType.GROUP,
         uniqueAncestorsRight[1].role);
-    assertEquals(RoleType.button,
+    assertEquals(RoleType.BUTTON,
         uniqueAncestorsRight[2].role);
 
     assertEquals(
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
index da646594..9003210 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
@@ -38,6 +38,7 @@
 var Dir = constants.Dir;
 var EventType = chrome.automation.EventType;
 var RoleType = chrome.automation.RoleType;
+var StateType = chrome.automation.StateType;
 
 /**
  * ChromeVox2 background page.
@@ -252,11 +253,12 @@
       return useNext ? ChromeVoxMode.FORCE_NEXT :
           ChromeVoxMode.CLASSIC_COMPAT;
 
-    var nextSite = this.isWhitelistedForNext_(topLevelRoot.docUrl);
-    var nextCompat = this.nextCompatRegExp_.test(topLevelRoot.docUrl) &&
+    var docUrl = topLevelRoot.docUrl || '';
+    var nextSite = this.isWhitelistedForNext_(docUrl);
+    var nextCompat = this.nextCompatRegExp_.test(docUrl) &&
         this.chromeChannel_ != 'dev';
     var classicCompat =
-        this.isWhitelistedForClassicCompat_(topLevelRoot.docUrl);
+        this.isWhitelistedForClassicCompat_(docUrl);
     if (nextCompat && useNext)
       return ChromeVoxMode.NEXT_COMPAT;
     else if (classicCompat && !useNext)
@@ -411,7 +413,7 @@
       start.makeVisible();
 
       var root = start.root;
-      if (!root || root.role == RoleType.desktop)
+      if (!root || root.role == RoleType.DESKTOP)
         return;
 
       var position = {};
@@ -494,7 +496,8 @@
         lca = AutomationUtil.getLeastCommonAncestor(prevRange.start.node,
                                                     range.start.node);
       }
-      if (!lca || lca.state.editable || !range.start.node.state.editable)
+      if (!lca || lca.state[StateType.EDITABLE] ||
+          !range.start.node.state[StateType.EDITABLE])
         range.select();
     }
 
@@ -588,9 +591,9 @@
    * @return {boolean}
    */
   isWhitelistedForClassicCompat_: function(url) {
-    return this.isBlacklistedForClassic_(url) || (this.getCurrentRange() &&
+    return (this.isBlacklistedForClassic_(url) || (this.getCurrentRange() &&
         !this.getCurrentRange().isWebRange() &&
-        this.getCurrentRange().start.node.state.focused);
+        this.getCurrentRange().start.node.state[StateType.FOCUSED])) || false;
   },
 
   /**
@@ -667,7 +670,7 @@
     if (!actionNodeSpan)
       return;
     var actionNode = actionNodeSpan.node;
-    if (actionNode.role === RoleType.inlineTextBox)
+    if (actionNode.role === RoleType.INLINE_TEXT_BOX)
       actionNode = actionNode.parent;
     actionNode.doDefault();
     if (selectionSpan) {
@@ -787,28 +790,28 @@
       var entered = AutomationUtil.getUniqueAncestors(
           prevRange.start.node, start);
       var embeddedObject = entered.find(function(f) {
-        return f.role == RoleType.embeddedObject; });
-      if (embeddedObject && !embeddedObject.state.focused)
+        return f.role == RoleType.EMBEDDED_OBJECT; });
+      if (embeddedObject && !embeddedObject.state[StateType.FOCUSED])
         embeddedObject.focus();
     }
 
-    if (start.state.focused || end.state.focused)
+    if (start.state[StateType.FOCUSED] || end.state[StateType.FOCUSED])
       return;
 
     var isFocusableLinkOrControl = function(node) {
-      return node.state.focusable &&
+      return node.state[StateType.FOCUSABLE] &&
           AutomationPredicate.linkOrControl(node);
     };
 
     // Next, try to focus the start or end node.
     if (!AutomationPredicate.structuralContainer(start) &&
-        start.state.focusable) {
-      if (!start.state.focused)
+        start.state[StateType.FOCUSABLE]) {
+      if (!start.state[StateType.FOCUSED])
         start.focus();
       return;
     } else if (!AutomationPredicate.structuralContainer(end) &&
-        end.state.focusable) {
-      if (!end.state.focused)
+        end.state[StateType.FOCUSABLE]) {
+      if (!end.state[StateType.FOCUSED])
         end.focus();
       return;
     }
@@ -817,7 +820,7 @@
     var ancestor = AutomationUtil.getLeastCommonAncestor(start, end);
     while (ancestor && ancestor.root == start.root) {
       if (isFocusableLinkOrControl(ancestor)) {
-        if (!ancestor.state.focused)
+        if (!ancestor.state[StateType.FOCUSED])
           ancestor.focus();
         return;
       }
@@ -827,7 +830,7 @@
     // If nothing is focusable, set the sequential focus navigation starting
     // point, which ensures that the next time you press Tab, you'll reach
     // the next or previous focusable node from |start|.
-    if (!start.state.offscreen)
+    if (!start.state[StateType.OFFSCREEN])
       start.setSequentialFocusNavigationStartingPoint();
   }
 };
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
index 61f51a6..4407167 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background_test.extjs
@@ -263,7 +263,7 @@
   var mockFeedback = this.createMockFeedback();
   this.runWithLoadedTree('<a aria-label="foo" href="a">a</a>',
     function(rootNode) {
-      rootNode.find({role: RoleType.link}).focus();
+      rootNode.find({role: RoleType.LINK}).focus();
       mockFeedback.expectSpeech('foo')
           .expectSpeech('Link')
           .expectBraille('foo lnk');
@@ -276,7 +276,7 @@
   var mockFeedback = this.createMockFeedback();
   this.runWithLoadedTree('<p>before</p><a href="a">a</a>',
     function(rootNode) {
-      var go = rootNode.find({ role: RoleType.link });
+      var go = rootNode.find({ role: RoleType.LINK });
       // Menus no longer nest a message loop, so we can launch menu and confirm
       // expected speech. The menu will not block test shutdown.
       mockFeedback.call(go.focus.bind(go))
@@ -310,10 +310,10 @@
         </script>
       */},
       function(rootNode) {
-        var button1 = rootNode.find({role: RoleType.button,
+        var button1 = rootNode.find({role: RoleType.BUTTON,
                                      attributes: { name: 'Click me' }});
         var textField = rootNode.find(
-            {role: RoleType.textField});
+            {role: RoleType.TEXT_FIELD});
         mockFeedback.expectBraille('start')
             .call(button1.focus.bind(button1))
             .expectBraille(/^Click me btn/)
@@ -376,8 +376,8 @@
         editable.focus();
       });
 
-      var editable = rootNode.find({ role: RoleType.textField });
-      var nonEditable = rootNode.find({ role: RoleType.paragraph });
+      var editable = rootNode.find({ role: RoleType.TEXT_FIELD });
+      var nonEditable = rootNode.find({ role: RoleType.PARAGRAPH });
 
       this.listenOnce(nonEditable, 'focus', assertDoesntExist);
       this.listenOnce(editable, 'focus', assertExists);
@@ -472,7 +472,7 @@
     */},
     function(rootNode) {
       var focusButton = function() {
-        rootNode.find({role: RoleType.button}).focus();
+        rootNode.find({role: RoleType.BUTTON}).focus();
       };
       var on = function() { cvox.ChromeVox.isActive = true; };
       var off = function() { cvox.ChromeVox.isActive = false; };
@@ -491,9 +491,9 @@
           .expectSpeech('b').expectSpeech('Button')
           .call(off)
           .call(focusThen.bind(this, rootNode.find(
-              { role: RoleType.link }), on))
+              { role: RoleType.LINK }), on))
           .call(focusThen.bind(this, rootNode.find(
-              { role: RoleType.textField })))
+              { role: RoleType.TEXT_FIELD })))
           .expectNextSpeechUtteranceIsNot('a')
           .expectSpeech('Edit text');
 
@@ -510,13 +510,13 @@
     var fakeWebRoot = {};
     fakeWebRoot.root = fakeWebRoot;
     fakeWebRoot.parent = fakeDesktop;
-    fakeWebRoot.role = RoleType.rootWebArea;
+    fakeWebRoot.role = RoleType.ROOT_WEB_AREA;
     fakeWebRoot.makeVisible = function() {};
     fakeWebRoot.location = {left: 1, top: 1, width: 1, height: 1};
     var fakeSubRoot = {};
     fakeSubRoot.root = fakeSubRoot;
     fakeSubRoot.parent = fakeWebRoot;
-    fakeSubRoot.role = RoleType.rootWebArea;
+    fakeSubRoot.role = RoleType.ROOT_WEB_AREA;
     fakeSubRoot.makeVisible = function() {};
     fakeSubRoot.location = {left: 1, top: 1, width: 1, height: 1};
     var bk = ChromeVoxState.instance;
@@ -578,8 +578,8 @@
     <iframe tabindex=0 src="data:text/html,<p>Inside</p>"></iframe>
     <button>outside</button>
   */}, function(root) {
-    var iframe = root.find({role: RoleType.iframe});
-    var button = root.find({role: RoleType.button});
+    var iframe = root.find({role: RoleType.IFRAME});
+    var button = root.find({role: RoleType.BUTTON});
 
     assertEquals('iframe', iframe.role);
     assertEquals('button', button.role);
@@ -600,8 +600,8 @@
     <div><a href="#">mylink</a></div>
     <button>after</button>
   */}, function(root) {
-    var link = root.find({role: RoleType.link});
-    var button = root.find({role: RoleType.button});
+    var link = root.find({role: RoleType.LINK});
+    var button = root.find({role: RoleType.BUTTON});
 
     assertEquals('link', link.role);
     assertEquals('button', button.role);
@@ -630,8 +630,8 @@
       update();
     </script>
   */}, function(root) {
-    var go = root.find({role: RoleType.button});
-    var slider = root.find({role: RoleType.slider});
+    var go = root.find({role: RoleType.BUTTON});
+    var slider = root.find({role: RoleType.SLIDER});
     var focusButton = go.focus.bind(go);
     var focusSlider = slider.focus.bind(slider);
     mockFeedback.call(focusButton)
@@ -659,7 +659,7 @@
       });
     </script>
   */}, function(root) {
-    var cbx = root.find({role: RoleType.checkBox});
+    var cbx = root.find({role: RoleType.CHECK_BOX});
     var click = cbx.doDefault.bind(cbx);
     var focus = cbx.focus.bind(cbx);
     mockFeedback.call(focus)
@@ -697,13 +697,13 @@
       return;
 
     // Return if the iframe hasn't loaded yet.
-    var iframe = rootNode.find({role: RoleType.iframe});
+    var iframe = rootNode.find({role: RoleType.IFRAME});
     var childDoc = iframe.firstChild;
     if (!childDoc || childDoc.children.length == 0)
       return;
 
     running = true;
-    var beforeButton = rootNode.find({role: RoleType.button,
+    var beforeButton = rootNode.find({role: RoleType.BUTTON,
                                       name: 'Before'});
     beforeButton.focus();
     mockFeedback.expectSpeech('Before', 'Button');
@@ -745,7 +745,7 @@
       return;
 
     running = true;
-    var beforeButton = rootNode.find({role: RoleType.button,
+    var beforeButton = rootNode.find({role: RoleType.BUTTON,
                                       name: 'Before'});
     beforeButton.focus();
     mockFeedback.expectSpeech('Before', 'Button');
@@ -784,7 +784,7 @@
       <option>grapefruit
     </select>
   */}, function(root) {
-    var select = root.find({role: RoleType.popUpButton});
+    var select = root.find({role: RoleType.POP_UP_BUTTON});
     var clickSelect = select.doDefault.bind(select);
     var lastOption = select.lastChild.lastChild;
     var selectLastOption = lastOption.doDefault.bind(lastOption);
@@ -854,7 +854,7 @@
       </li>
     </ul>
   */}, function(root) {
-    var listItem = root.find({role: RoleType.listItem});
+    var listItem = root.find({role: RoleType.LIST_ITEM});
 
     mockFeedback.call(listItem.focus.bind(listItem))
         .expectSpeech('List item')
@@ -874,7 +874,7 @@
 TEST_F('BackgroundTest', 'DisappearingObject', function() {
   var mockFeedback = this.createMockFeedback();
   this.runWithLoadedTree(this.disappearingObjectDoc, function(rootNode) {
-    var deleteButton = rootNode.find({role: RoleType.button,
+    var deleteButton = rootNode.find({role: RoleType.BUTTON,
                                       attributes: { name: 'Delete' }});
     var pressDelete = deleteButton.doDefault.bind(deleteButton);
     mockFeedback.expectSpeech('start').expectBraille('start');
@@ -907,7 +907,7 @@
   this.runWithLoadedTree(function(root) {/*!
     <input type="submit" aria-label="foo" value="foo"></input>
   */}, function(root) {
-    var btn = root.find({role: RoleType.button});
+    var btn = root.find({role: RoleType.BUTTON});
     mockFeedback.call(btn.focus.bind(btn))
         .expectSpeech('foo')
         .expectSpeech('Button')
@@ -921,7 +921,7 @@
     <p>before</p>
     <h1><a href="google.com">go</a><p>here</p></h1>
   */}, function(root) {
-    var link = root.find({role: RoleType.link});
+    var link = root.find({role: RoleType.LINK});
     mockFeedback.call(link.focus.bind(link))
         .expectSpeech('go')
         .expectSpeech('Link')
@@ -1200,7 +1200,7 @@
   */}, function(root) {
     this.listenOnce(root, 'focus', function(e) {
       var focus = ChromeVoxState.instance.currentRange.start.node;
-      assertEquals(RoleType.textField, focus.role);
+      assertEquals(RoleType.TEXT_FIELD, focus.role);
       assertTrue(focus.state.focused);
     });
     doCmd('nextEditText')();
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
index 9f70869..e483f40 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/command_handler.js
@@ -42,8 +42,8 @@
     case 'speakTimeAndDate':
       chrome.automation.getDesktop(function(d) {
         // First, try speaking the on-screen time.
-        var allTime = d.findAll({role: RoleType.time});
-        allTime.filter(function(t) { return t.root.role == RoleType.desktop; });
+        var allTime = d.findAll({role: RoleType.TIME});
+        allTime.filter(function(t) { return t.root.role == RoleType.DESKTOP; });
 
         var timeString = '';
         allTime.forEach(function(t) {
@@ -445,7 +445,7 @@
     case 'forceClickOnCurrentItem':
       if (ChromeVoxState.instance.currentRange_) {
         var actionNode = ChromeVoxState.instance.currentRange_.start.node;
-        if (actionNode.role == RoleType.inlineTextBox)
+        if (actionNode.role == RoleType.INLINE_TEXT_BOX)
           actionNode = actionNode.parent;
         actionNode.doDefault();
       }
@@ -466,8 +466,8 @@
 
         // Stop if we've wrapped back to the document.
         var maybeDoc = newRange.start.node;
-        if (maybeDoc.role == RoleType.rootWebArea &&
-            maybeDoc.parent.root.role == RoleType.desktop) {
+        if (maybeDoc.role == RoleType.ROOT_WEB_AREA &&
+            maybeDoc.parent.root.role == RoleType.DESKTOP) {
           ChromeVoxState.isReadingContinuously = false;
           return;
         }
@@ -493,7 +493,7 @@
     case 'contextMenu':
       if (ChromeVoxState.instance.currentRange_) {
         var actionNode = ChromeVoxState.instance.currentRange_.start.node;
-        if (actionNode.role == RoleType.inlineTextBox)
+        if (actionNode.role == RoleType.INLINE_TEXT_BOX)
           actionNode = actionNode.parent;
         actionNode.showContextMenu();
         return false;
@@ -524,13 +524,13 @@
       var target = ChromeVoxState.instance.currentRange_.start.node;
       var output = new Output();
 
-      if (target.root.role == RoleType.rootWebArea) {
+      if (target.root.role == RoleType.ROOT_WEB_AREA) {
         // Web.
         target = target.root;
         output.withString(target.name || target.docUrl);
       } else {
         // Views.
-        while (target.role != RoleType.window) target = target.parent;
+        while (target.role != RoleType.WINDOW) target = target.parent;
         if (target)
           output.withString(target.name || '');
       }
@@ -615,7 +615,7 @@
     case 'goToRowFirstCell':
     case 'goToRowLastCell':
       var node = current.start.node;
-      while (node && node.role != RoleType.row)
+      while (node && node.role != RoleType.ROW)
         node = node.parent;
       if (!node)
         break;
@@ -628,7 +628,7 @@
     case 'goToColFirstCell':
       dir = Dir.FORWARD;
       var node = current.start.node;
-      while (node && node.role != RoleType.table)
+      while (node && node.role != RoleType.TABLE)
         node = node.parent;
       if (!node || !node.firstChild)
         return false;
@@ -643,7 +643,7 @@
     case 'goToColLastCell':
       dir = Dir.BACKWARD;
       var node = current.start.node;
-      while (node && node.role != RoleType.table)
+      while (node && node.role != RoleType.TABLE)
         node = node.parent;
       if (!node || !node.lastChild)
         return false;
@@ -658,7 +658,7 @@
     case 'goToFirstCell':
     case 'goToLastCell':
       node = current.start.node;
-      while (node && node.role != RoleType.table)
+      while (node && node.role != RoleType.TABLE)
         node = node.parent;
       if (!node)
         break;
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js
index f69ff35..31c63b3 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors.js
@@ -121,11 +121,11 @@
     // node.
     var lNode = this.node;
     var rNode = rhs.node;
-    while (lNode && (lNode.role == RoleType.inlineTextBox ||
-        lNode.role == RoleType.staticText))
+    while (lNode && (lNode.role == RoleType.INLINE_TEXT_BOX ||
+        lNode.role == RoleType.STATIC_TEXT))
       lNode = lNode.parent;
-    while (rNode && (rNode.role == RoleType.inlineTextBox ||
-        rNode.role == RoleType.staticText))
+    while (rNode && (rNode.role == RoleType.INLINE_TEXT_BOX ||
+        rNode.role == RoleType.STATIC_TEXT))
       rNode = rNode.parent;
 
     // Ignore indicies for now.
@@ -176,22 +176,22 @@
     // Selections over line break nodes are broken.
     var parent = adjustedNode.parent;
     var grandparent = parent && parent.parent;
-    if (parent.role == RoleType.lineBreak) {
+    if (parent.role == RoleType.LINE_BREAK) {
       adjustedNode = grandparent;
-    } else if (grandparent.role == RoleType.lineBreak) {
+    } else if (grandparent.role == RoleType.LINE_BREAK) {
       adjustedNode = grandparent.parent;
     } else if (this.index_ == cursors.NODE_INDEX ||
-        adjustedNode.role == RoleType.inlineTextBox ||
-        chrome.automation.NameFromType[adjustedNode.nameFrom] != 'contents') {
+        adjustedNode.role == RoleType.INLINE_TEXT_BOX ||
+        adjustedNode.nameFrom != chrome.automation.NameFromType.CONTENTS) {
       // A node offset or unselectable character offset.
       adjustedNode = parent;
     } else {
       // A character offset into content.
       adjustedNode =
-          adjustedNode.find({role: RoleType.staticText}) || adjustedNode;
+          adjustedNode.find({role: RoleType.STATIC_TEXT}) || adjustedNode;
     }
 
-    return adjustedNode;
+    return adjustedNode || null;
   },
 
   /**
@@ -209,9 +209,9 @@
 
     if (this.node.state.editable) {
       return this.index_ == cursors.NODE_INDEX ? 0 : this.index_;
-    } else if (this.node.role == RoleType.inlineTextBox &&
+    } else if (this.node.role == RoleType.INLINE_TEXT_BOX &&
     // Selections under a line break are broken.
-        this.node.parent && this.node.parent.role != RoleType.lineBreak) {
+        this.node.parent && this.node.parent.role != RoleType.LINE_BREAK) {
       if (adjustedIndex == cursors.NODE_INDEX)
         adjustedIndex = 0;
 
@@ -221,13 +221,13 @@
         sibling = sibling.previousSibling;
       }
     } else if (this.index_ == cursors.NODE_INDEX ||
-        chrome.automation.NameFromType[this.node.nameFrom] != 'contents') {
+        this.node.nameFrom != chrome.automation.NameFromType.CONTENTS) {
       // A node offset or unselectable character offset.
 
       // The selected node could have been adjusted upwards in the tree.
       var childOfSelection = this.node;
       do {
-        adjustedIndex = childOfSelection.indexInParent;
+        adjustedIndex = childOfSelection.indexInParent || 0;
         childOfSelection = childOfSelection.parent;
       } while (childOfSelection && childOfSelection != this.selectionNode_);
     }
@@ -286,7 +286,7 @@
         }
         break;
       case Unit.WORD:
-        if (newNode.role != RoleType.inlineTextBox) {
+        if (newNode.role != RoleType.INLINE_TEXT_BOX) {
           newNode = AutomationUtil.findNextNode(newNode,
               Dir.FORWARD,
               AutomationPredicate.inlineTextBox,
@@ -294,7 +294,7 @@
         }
         switch (movement) {
           case Movement.BOUND:
-            if (newNode.role == RoleType.inlineTextBox) {
+            if (newNode.role == RoleType.INLINE_TEXT_BOX) {
               var start, end;
               for (var i = 0; i < newNode.wordStarts.length; i++) {
                 if (newIndex >= newNode.wordStarts[i] &&
@@ -311,7 +311,7 @@
             }
             break;
           case Movement.DIRECTIONAL:
-            if (newNode.role == RoleType.inlineTextBox) {
+            if (newNode.role == RoleType.INLINE_TEXT_BOX) {
               var start, end;
               for (var i = 0; i < newNode.wordStarts.length; i++) {
                 if (newIndex >= newNode.wordStarts[i] &&
@@ -334,7 +334,7 @@
                   if (newNode) {
                     newIndex = 0;
                     if (dir == Dir.BACKWARD &&
-                        newNode.role == RoleType.inlineTextBox) {
+                        newNode.role == RoleType.INLINE_TEXT_BOX) {
                       var starts = newNode.wordStarts;
                       newIndex = starts[starts.length - 1] || 0;
                     } else {
@@ -605,7 +605,7 @@
     return this.start.node &&
         this.end.node &&
         this.start.node.role == this.end.node.role &&
-        this.start.node.role == RoleType.inlineTextBox;
+        this.start.node.role == RoleType.INLINE_TEXT_BOX;
   },
 
   /**
@@ -657,7 +657,7 @@
 
     // Only allow selections within the same web tree.
     if (startNode.root &&
-        startNode.root.role == RoleType.rootWebArea &&
+        startNode.root.role == RoleType.ROOT_WEB_AREA &&
         startNode.root == endNode.root) {
       // We want to adjust to select the entire node for node offsets;
       // otherwise, use the plain character offset.
@@ -680,8 +680,8 @@
   */
   isWebRange: function() {
     return this.isValid() &&
-        (this.start.node.root.role != RoleType.desktop ||
-        this.end.node.root.role != RoleType.desktop);
+        (this.start.node.root.role != RoleType.DESKTOP ||
+        this.end.node.root.role != RoleType.DESKTOP);
   },
 
   /**
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors_test.extjs
index f83ea09d..fd8243ce 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/cursors_test.extjs
@@ -331,8 +331,8 @@
     <p>end of text</p>
   */},
   function(root) {
-    var link = root.find({role: RoleType.link});
-    var p1 = root.find({role: RoleType.paragraph});
+    var link = root.find({role: RoleType.LINK});
+    var p1 = root.find({role: RoleType.PARAGRAPH});
     var p2 = p1.nextSibling;
 
     var singleSel = new cursors.Range(
@@ -412,18 +412,18 @@
 
     // This is the link's static text.
     var testNode = root.lastChild.lastChild.previousSibling.firstChild;
-    assertEquals(RoleType.staticText, testNode.role);
+    assertEquals(RoleType.STATIC_TEXT, testNode.role);
     assertEquals('test', testNode.name);
 
     var ofSelectionNode = root.lastChild.lastChild;
     var cur = new cursors.Cursor(ofSelectionNode, 0);
     assertEquals('of selection', cur.selectionNode_.name);
-    assertEquals(RoleType.staticText, cur.selectionNode_.role);
+    assertEquals(RoleType.STATIC_TEXT, cur.selectionNode_.role);
     assertEquals(0, cur.selectionIndex_);
 
     var curIntoO = new cursors.Cursor(ofSelectionNode, 1);
     assertEquals('of selection', curIntoO.selectionNode_.name);
-    assertEquals(RoleType.staticText, curIntoO.selectionNode_.role);
+    assertEquals(RoleType.STATIC_TEXT, curIntoO.selectionNode_.role);
     assertEquals(1, curIntoO.selectionIndex_);
 
     var oRange = new cursors.Range(cur, curIntoO);
@@ -436,11 +436,11 @@
     <div role="region">this is a test</button>
   */}, function(root) {
     var region = root.firstChild;
-    assertEquals(RoleType.region, region.role);
+    assertEquals(RoleType.REGION, region.role);
     var staticText = region.firstChild;
-    assertEquals(RoleType.staticText, staticText.role);
+    assertEquals(RoleType.STATIC_TEXT, staticText.role);
     var inlineTextBox = staticText.firstChild;
-    assertEquals(RoleType.inlineTextBox, inlineTextBox.role);
+    assertEquals(RoleType.INLINE_TEXT_BOX, inlineTextBox.role);
 
     var rootRange = cursors.Range.fromNode(root);
     var regionRange = cursors.Range.fromNode(region);
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/custom_automation_event.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/custom_automation_event.js
new file mode 100644
index 0000000..3d6cf73
--- /dev/null
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/custom_automation_event.js
@@ -0,0 +1,33 @@
+// 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.
+
+/**
+ * @fileoverview Custom Automation Event.
+ *
+ * An object similar to a chrome.automation.AutomationEvent that we can
+ * construct, unlike the object from the extension system.
+ */
+
+goog.provide('CustomAutomationEvent');
+
+/**
+ * An object we can use instead of a chrome.automation.AutomationEvent.
+ * @constructor
+ * @extends {chrome.automation.AutomationEvent}
+ * @param {chrome.automation.EventType} type The event type.
+ * @param {!chrome.automation.AutomationNode} target The event target.
+ * @param {string} eventFrom The source of this event.
+ */
+var CustomAutomationEvent = function(type, target, eventFrom) {
+  this.type = type;
+  this.target = target;
+  this.eventFrom = eventFrom;
+};
+
+/**
+ * @override
+ */
+CustomAutomationEvent.prototype.stopPropagation = function() {
+  throw Error('Can\'t call stopPropagation on a CustomAutomationEvent');
+};
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
index 2b56429..2920129 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/desktop_automation_handler.js
@@ -11,6 +11,7 @@
 goog.require('AutomationObjectConstructorInstaller');
 goog.require('BaseAutomationHandler');
 goog.require('ChromeVoxState');
+goog.require('CustomAutomationEvent');
 goog.require('Stubs');
 goog.require('editing.TextEditHandler');
 
@@ -43,28 +44,48 @@
    */
   this.lastValueChanged_ = new Date(0);
 
-  var e = EventType;
-  this.addListener_(e.activedescendantchanged, this.onActiveDescendantChanged);
-  this.addListener_(e.alert, this.onAlert);
-  this.addListener_(e.ariaAttributeChanged, this.onAriaAttributeChanged);
-  this.addListener_(e.autocorrectionOccured, this.onEventIfInRange);
-  this.addListener_(e.checkedStateChanged, this.onCheckedStateChanged);
-  this.addListener_(e.childrenChanged, this.onActiveDescendantChanged);
-  this.addListener_(e.expandedChanged, this.onEventIfInRange);
-  this.addListener_(e.focus, this.onFocus);
-  this.addListener_(e.hover, this.onHover);
-  this.addListener_(e.invalidStatusChanged, this.onEventIfInRange);
-  this.addListener_(e.loadComplete, this.onLoadComplete);
-  this.addListener_(e.menuEnd, this.onMenuEnd);
-  this.addListener_(e.menuListItemSelected, this.onEventIfSelected);
-  this.addListener_(e.menuStart, this.onMenuStart);
-  this.addListener_(e.rowCollapsed, this.onEventIfInRange);
-  this.addListener_(e.rowExpanded, this.onEventIfInRange);
-  this.addListener_(e.scrollPositionChanged, this.onScrollPositionChanged);
-  this.addListener_(e.selection, this.onSelection);
-  this.addListener_(e.textChanged, this.onTextChanged);
-  this.addListener_(e.textSelectionChanged, this.onTextSelectionChanged);
-  this.addListener_(e.valueChanged, this.onValueChanged);
+  this.addListener_(EventType.ACTIVEDESCENDANTCHANGED,
+                    this.onActiveDescendantChanged);
+  this.addListener_(EventType.ALERT,
+                    this.onAlert);
+  this.addListener_(EventType.ARIA_ATTRIBUTE_CHANGED,
+                    this.onAriaAttributeChanged);
+  this.addListener_(EventType.AUTOCORRECTION_OCCURED,
+                    this.onEventIfInRange);
+  this.addListener_(EventType.CHECKED_STATE_CHANGED,
+                    this.onCheckedStateChanged);
+  this.addListener_(EventType.CHILDREN_CHANGED,
+                    this.onActiveDescendantChanged);
+  this.addListener_(EventType.EXPANDED_CHANGED,
+                    this.onEventIfInRange);
+  this.addListener_(EventType.FOCUS,
+                    this.onFocus);
+  this.addListener_(EventType.HOVER,
+                    this.onHover);
+  this.addListener_(EventType.INVALID_STATUS_CHANGED,
+                    this.onEventIfInRange);
+  this.addListener_(EventType.LOAD_COMPLETE,
+                    this.onLoadComplete);
+  this.addListener_(EventType.MENU_END,
+                    this.onMenuEnd);
+  this.addListener_(EventType.MENU_LIST_ITEM_SELECTED,
+                    this.onEventIfSelected);
+  this.addListener_(EventType.MENU_START,
+                    this.onMenuStart);
+  this.addListener_(EventType.ROW_COLLAPSED,
+                    this.onEventIfInRange);
+  this.addListener_(EventType.ROW_EXPANDED,
+                    this.onEventIfInRange);
+  this.addListener_(EventType.SCROLL_POSITION_CHANGED,
+                    this.onScrollPositionChanged);
+  this.addListener_(EventType.SELECTION,
+                    this.onSelection);
+  this.addListener_(EventType.TEXT_CHANGED,
+                    this.onTextChanged);
+  this.addListener_(EventType.TEXT_SELECTION_CHANGED,
+                    this.onTextSelectionChanged);
+  this.addListener_(EventType.VALUE_CHANGED,
+                    this.onValueChanged);
 
   AutomationObjectConstructorInstaller.init(node, function() {
     chrome.automation.getFocus((function(focus) {
@@ -72,9 +93,8 @@
         return;
 
       if (focus) {
-        this.onFocus(
-            new chrome.automation.AutomationEvent(
-                EventType.focus, focus, 'page'));
+        var event = new CustomAutomationEvent(EventType.FOCUS, focus, 'page');
+        this.onFocus(event);
       }
     }).bind(this));
   }.bind(this));
@@ -205,8 +225,9 @@
   onActiveDescendantChanged: function(evt) {
     if (!evt.target.activeDescendant || !evt.target.state.focused)
       return;
-    this.onEventDefault(new chrome.automation.AutomationEvent(
-        EventType.focus, evt.target.activeDescendant, evt.eventFrom));
+    var event = new CustomAutomationEvent(
+        EventType.FOCUS, evt.target.activeDescendant, evt.eventFrom);
+    this.onEventDefault(event);
   },
 
   /**
@@ -232,9 +253,9 @@
       return;
 
     Output.forceModeForNextSpeechUtterance(cvox.QueueMode.CATEGORY_FLUSH);
-    this.onEventIfInRange(
-        new chrome.automation.AutomationEvent(
-            EventType.checkedStateChanged, evt.target, evt.eventFrom));
+    var event = new CustomAutomationEvent(
+        EventType.CHECKED_STATE_CHANGED, evt.target, evt.eventFrom);
+    this.onEventIfInRange(event);
   },
 
   /**
@@ -248,7 +269,7 @@
     var node = evt.target;
 
     // Discard focus events on embeddedObject.
-    if (node.role == RoleType.embeddedObject)
+    if (node.role == RoleType.EMBEDDED_OBJECT)
       return;
 
     this.createTextEditHandlerIfNeeded_(evt.target);
@@ -261,7 +282,6 @@
       return;
 
     var root = AutomationUtil.getTopLevelRoot(node.root);
-
     // If we're crossing out of a root, save it in case it needs recovering.
     var prevRange = ChromeVoxState.instance.currentRange;
     var prevNode = prevRange ? prevRange.start.node : null;
@@ -270,7 +290,6 @@
       if (prevRoot && prevRoot !== root)
       ChromeVoxState.instance.focusRecoveryMap.set(prevRoot, prevRange);
     }
-
     // If a previous node was saved for this focus, restore it.
     var savedRange = ChromeVoxState.instance.focusRecoveryMap.get(root);
     ChromeVoxState.instance.focusRecoveryMap.delete(root);
@@ -278,9 +297,8 @@
       ChromeVoxState.instance.navigateToRange(savedRange, false);
       return;
     }
-
-    this.onEventDefault(new chrome.automation.AutomationEvent(
-        EventType.focus, node, evt.eventFrom));
+    var event = new CustomAutomationEvent(EventType.FOCUS, node, evt.eventFrom);
+    this.onEventDefault(event);
   },
 
   /**
@@ -314,7 +332,7 @@
         return;
 
       var o = new Output();
-      if (focus.role == RoleType.rootWebArea) {
+      if (focus.role == RoleType.ROOT_WEB_AREA) {
         // Restore to previous position.
         var url = focus.docUrl;
         url = url.substring(0, url.indexOf('#')) || url;
@@ -376,9 +394,9 @@
 
     // Sync the ChromeVox range to the editable, if a selection exists.
     var anchorObject = evt.target.root.anchorObject;
-    var anchorOffset = evt.target.root.anchorOffset;
+    var anchorOffset = evt.target.root.anchorOffset || 0;
     var focusObject = evt.target.root.focusObject;
-    var focusOffset = evt.target.root.focusOffset;
+    var focusOffset = evt.target.root.focusOffset || 0;
     if (anchorObject && focusObject) {
       var selectedRange = new cursors.Range(
           new cursors.WrappingCursor(anchorObject, anchorOffset),
@@ -410,7 +428,7 @@
 
     var t = evt.target;
     if (t.state.focused ||
-        t.root.role == RoleType.desktop ||
+        t.root.role == RoleType.DESKTOP ||
         AutomationUtil.isDescendantOf(
             ChromeVoxState.instance.currentRange.start.node, t)) {
       if (new Date() - this.lastValueChanged_ <=
@@ -421,7 +439,7 @@
 
       var output = new Output();
 
-      if (t.root.role == RoleType.desktop)
+      if (t.root.role == RoleType.DESKTOP)
         output.withQueueMode(cvox.QueueMode.FLUSH);
 
       output.format('$value', evt.target).go();
@@ -450,7 +468,7 @@
     chrome.automation.getFocus(function(focus) {
       // Desktop tabs get "selection" when there's a focused webview during tab
       // switching.
-      if (focus.role == RoleType.webView && evt.target.role == RoleType.tab) {
+      if (focus.role == RoleType.WEB_VIEW && evt.target.role == RoleType.TAB) {
         ChromeVoxState.instance.setCurrentRange(
             cursors.Range.fromNode(focus.firstChild));
         return;
@@ -458,9 +476,9 @@
 
       // Some cases (e.g. in overview mode), require overriding the assumption
       // that focus is an ancestor of a selection target.
-      var override = evt.target.role == RoleType.menuItem ||
+      var override = evt.target.role == RoleType.MENU_ITEM ||
           (evt.target.root == focus.root &&
-              focus.root.role == RoleType.desktop);
+              focus.root.role == RoleType.DESKTOP);
       Output.forceModeForNextSpeechUtterance(cvox.QueueMode.FLUSH);
       if (override || AutomationUtil.isDescendantOf(evt.target, focus))
         this.onEventDefault(evt);
@@ -487,9 +505,8 @@
     // after you close them.
     chrome.automation.getFocus(function(focus) {
       if (focus) {
-        this.onFocus(
-            new chrome.automation.AutomationEvent(
-                EventType.focus, focus, 'page'));
+        var event = new CustomAutomationEvent(EventType.FOCUS, focus, 'page');
+        this.onFocus(event);
       }
     }.bind(this));
   },
@@ -513,7 +530,7 @@
   shouldOutput_: function(evt) {
     var mode = ChromeVoxState.instance.mode;
     // Only output desktop rooted nodes or web nodes for next engine modes.
-    return evt.target.root.role == RoleType.desktop ||
+    return evt.target.root.role == RoleType.DESKTOP ||
         (mode == ChromeVoxMode.NEXT ||
          mode == ChromeVoxMode.FORCE_NEXT ||
          mode == ChromeVoxMode.CLASSIC_COMPAT);
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js
index ee6e1d2..e4bd5831 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing.js
@@ -25,6 +25,7 @@
 var EventType = chrome.automation.EventType;
 var Range = cursors.Range;
 var RoleType = chrome.automation.RoleType;
+var StateType = chrome.automation.StateType;
 var Movement = cursors.Movement;
 var Unit = cursors.Unit;
 
@@ -51,7 +52,7 @@
    * |valueChanged|.
    * An implementation of this method should emit the appropritate braille and
    * spoken feedback for the event.
-   * @param {!AutomationEvent} evt
+   * @param {!(AutomationEvent|CustomAutomationEvent)} evt
    */
   onEvent: goog.abstractMethod,
 };
@@ -73,10 +74,10 @@
 
   /** @override */
   onEvent: function(evt) {
-    if (evt.type !== EventType.textChanged &&
-        evt.type !== EventType.textSelectionChanged &&
-        evt.type !== EventType.valueChanged &&
-        evt.type !== EventType.focus)
+    if (evt.type !== EventType.TEXT_CHANGED &&
+        evt.type !== EventType.TEXT_SELECTION_CHANGED &&
+        evt.type !== EventType.VALUE_CHANGED &&
+        evt.type !== EventType.FOCUS)
       return;
     if (!evt.target.state.focused ||
         !evt.target.state.editable ||
@@ -101,13 +102,13 @@
   var end = node.textSelEnd;
   cvox.ChromeVoxEditableTextBase.call(
       this,
-      node.value,
+      node.value || '',
       Math.min(start, end),
       Math.max(start, end),
-      node.state.protected /**password*/,
+      node.state[StateType.PROTECTED] /**password*/,
       cvox.ChromeVox.tts);
   /** @override */
-  this.multiline = node.state.multiline || false;
+  this.multiline = node.state[StateType.MULTILINE] || false;
   /** @type {!AutomationNode} @private */
   this.node_ = node;
   /** @type {Array<number>} @private */
@@ -121,15 +122,15 @@
    * Called when the text field has been updated.
    */
   onUpdate: function() {
-    var newValue = this.node_.value;
+    var newValue = this.node_.value || '';
 
     if (this.value != newValue)
       this.lineBreaks_ = [];
 
     var textChangeEvent = new cvox.TextChangeEvent(
         newValue,
-        this.node_.textSelStart,
-        this.node_.textSelEnd,
+        this.node_.textSelStart || 0,
+        this.node_.textSelEnd || 0,
         true /* triggered by user */);
     this.changed(textChangeEvent);
     this.outputBraille_();
@@ -207,7 +208,7 @@
   var testNode = node;
 
   do {
-    if (testNode.state.focused && testNode.state.editable)
+    if (testNode.state[StateType.FOCUSED] && testNode.state[StateType.EDITABLE])
       rootFocusedEditable = testNode;
     testNode = testNode.parent;
   } while (testNode);
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing_test.extjs
index 763fbe7..2b1293f0 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/editing_test.extjs
@@ -33,7 +33,7 @@
 
   assertLineData: function(doc, data) {
     this.runWithLoadedTree(doc, function(root) {
-      var input = root.find({role: RoleType.textField});
+      var input = root.find({role: RoleType.TEXT_FIELD});
       input.addEventListener('focus', this.newCallback(function() {
         var textHandler = ChromeVoxState.desktopAutomationHandler
             .textEditHandler_.editableText_;
@@ -75,9 +75,9 @@
 TEST_F('EditingTest', 'Focus', function() {
   var mockFeedback = this.createMockFeedback();
   this.runWithLoadedTree(doc, function(root) {
-    var singleLine = root.find({role: RoleType.textField,
+    var singleLine = root.find({role: RoleType.TEXT_FIELD,
                                 attributes: {name: 'singleLine'}});
-    var textarea = root.find({role: RoleType.textField,
+    var textarea = root.find({role: RoleType.TEXT_FIELD,
                               attributes: {name: 'textArea'}});
     singleLine.focus();
     mockFeedback
@@ -98,7 +98,7 @@
 TEST_F('EditingTest', 'Multiline', function() {
   var mockFeedback = this.createMockFeedback();
   this.runWithLoadedTree(doc, function(root) {
-    var textarea = root.find({role: RoleType.textField,
+    var textarea = root.find({role: RoleType.TEXT_FIELD,
                               attributes: {name: 'textArea'}});
     textarea.focus();
     mockFeedback
@@ -147,7 +147,7 @@
       </script>
     */},
     function(root) {
-      var input = root.find({role: RoleType.textField});
+      var input = root.find({role: RoleType.TEXT_FIELD});
       input.focus();
       mockFeedback
           .expectSpeech('text1', 'Edit text')
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/find_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/find_handler.js
index 5aa0a7c..af0aa2a9 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/find_handler.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/find_handler.js
@@ -8,6 +8,9 @@
 
 goog.require('Output');
 
+goog.scope(function() {
+var TreeChangeObserverFilter = chrome.automation.TreeChangeObserverFilter;
+
 /**
  * Responds to mode changes.
  * @param {ChromeVoxMode} newMode
@@ -27,7 +30,7 @@
  */
 FindHandler.init_ = function() {
   chrome.automation.addTreeChangeObserver(
-      'textMarkerChanges', FindHandler.onTextMatch_);
+      TreeChangeObserverFilter.NO_TREE_CHANGES, FindHandler.onTextMatch_);
 };
 
 /**
@@ -54,3 +57,5 @@
       .withRichSpeechAndBraille(range, null, Output.EventType.NAVIGATE)
       .go();
 };
+
+});  // goog.scope
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/live_regions.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/live_regions.js
index 95c43aa..57197e2 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/live_regions.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/live_regions.js
@@ -14,6 +14,7 @@
 var AutomationNode = chrome.automation.AutomationNode;
 var RoleType = chrome.automation.RoleType;
 var TreeChange = chrome.automation.TreeChange;
+var TreeChangeObserverFilter = chrome.automation.TreeChangeObserverFilter;
 
 /**
  * ChromeVox2 live region handler.
@@ -43,7 +44,8 @@
    */
   this.liveRegionNodeSet_ = new WeakSet();
   chrome.automation.addTreeChangeObserver(
-      'liveRegionTreeChanges', this.onTreeChange.bind(this));
+      TreeChangeObserverFilter.LIVE_REGION_TREE_CHANGES,
+      this.onTreeChange.bind(this));
 };
 
 /**
@@ -91,7 +93,7 @@
     var webView = AutomationUtil.getTopLevelRoot(node);
     webView = webView ? webView.parent : null;
     if (!LiveRegions.announceLiveRegionsFromBackgroundTabs_ &&
-        currentRange.start.node.role != RoleType.desktop &&
+        currentRange.start.node.role != RoleType.DESKTOP &&
         (!webView || !webView.state.focused)) {
       return;
     }
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/live_regions_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/live_regions_test.extjs
index 88d7d53a..8673a2fe 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/live_regions_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/live_regions_test.extjs
@@ -61,7 +61,7 @@
       </script>
     */},
     function(rootNode) {
-      var go = rootNode.find({ role: RoleType.button });
+      var go = rootNode.find({ role: RoleType.BUTTON });
       mockFeedback.call(go.doDefault.bind(go))
           .expectCategoryFlushSpeech('Hello, world');
       mockFeedback.replay();
@@ -82,7 +82,7 @@
       </script>
     */},
     function(rootNode) {
-      var go = rootNode.find({ role: RoleType.button });
+      var go = rootNode.find({ role: RoleType.BUTTON });
       go.doDefault();
       mockFeedback.expectCategoryFlushSpeech('removed:')
           .expectQueuedSpeech('Hello, world');
@@ -107,7 +107,7 @@
       </script>
     */},
     function(rootNode) {
-      var go = rootNode.find({ role: RoleType.button });
+      var go = rootNode.find({ role: RoleType.BUTTON });
       mockFeedback.call(go.doDefault.bind(go))
           .expectCategoryFlushSpeech('Alpha Bravo Charlie')
       mockFeedback.replay();
@@ -135,7 +135,7 @@
       </script>
     */},
     function(rootNode) {
-      var go = rootNode.find({ role: RoleType.button });
+      var go = rootNode.find({ role: RoleType.BUTTON });
       mockFeedback.call(go.doDefault.bind(go))
           .expectCategoryFlushSpeech('After')
           .expectQueuedSpeech('Image');
@@ -160,7 +160,7 @@
       </script>
     */},
     function(rootNode) {
-      var go = rootNode.find({ role: RoleType.button });
+      var go = rootNode.find({ role: RoleType.BUTTON });
       mockFeedback.call(go.doDefault.bind(go))
           .expectCategoryFlushSpeech('Live')
           .expectQueuedSpeech('Focus');
@@ -185,7 +185,7 @@
       </script>
     */},
     function(rootNode) {
-      var go = rootNode.find({ role: RoleType.button });
+      var go = rootNode.find({ role: RoleType.BUTTON });
       mockFeedback.call(go.doDefault.bind(go))
           .expectQueuedSpeech('Focus')
           .expectCategoryFlushSpeech('Live');
@@ -214,7 +214,7 @@
       </script>
     */},
     function(rootNode) {
-      var go = rootNode.find({ role: RoleType.button });
+      var go = rootNode.find({ role: RoleType.BUTTON });
       mockFeedback.call(go.doDefault.bind(go))
           .expectCategoryFlushSpeech('Live1')
           .expectCategoryFlushSpeech('Live2');
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/media_automation_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/media_automation_handler.js
index f969fe7..72b3054a 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/media_automation_handler.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/media_automation_handler.js
@@ -36,9 +36,10 @@
   chrome.automation.getDesktop(function(node) {
     BaseAutomationHandler.call(this, node);
 
-    var e = EventType;
-    this.addListener_(e.mediaStartedPlaying, this.onMediaStartedPlaying);
-    this.addListener_(e.mediaStoppedPlaying, this.onMediaStoppedPlaying);
+    this.addListener_(EventType.MEDIA_STARTED_PLAYING,
+                      this.onMediaStartedPlaying);
+    this.addListener_(EventType.MEDIA_STOPPED_PLAYING,
+                      this.onMediaStoppedPlaying);
   }.bind(this));
 };
 
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
index 63f4ae5..4572068 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
@@ -30,6 +30,7 @@
 var Dir = constants.Dir;
 var EventType = chrome.automation.EventType;
 var RoleType = chrome.automation.RoleType;
+var StateType = chrome.automation.StateType;
 
 /**
  * An Output object formats a cursors.Range into speech, braille, or both
@@ -1003,7 +1004,7 @@
       return;
     var uniqueAncestors = AutomationUtil.getUniqueAncestors(prevParent, parent);
     for (var i = 0; parent = uniqueAncestors[i]; i++) {
-      if (parent.role == RoleType.window)
+      if (parent.role == RoleType.WINDOW)
         break;
       if (Output.ROLE_INFO_[parent.role] &&
           Output.ROLE_INFO_[parent.role].outputContextFirst) {
@@ -1069,20 +1070,19 @@
       // All possible tokens based on prefix.
       if (prefix == '$') {
         if (token == 'value') {
-          var text = node.value;
-          if (!node.state.editable && node.name == text)
+          var text = node.value || '';
+          if (!node.state[StateType.EDITABLE] && node.name == text)
             return;
 
           var selectedText = '';
-          if (text !== undefined) {
-            if (node.textSelStart !== undefined) {
-              options.annotation.push(new Output.SelectionSpan(
-                  node.textSelStart,
-                  node.textSelEnd));
+          if (node.textSelStart !== undefined) {
+            options.annotation.push(new Output.SelectionSpan(
+                node.textSelStart || 0,
+                node.textSelEnd || 0));
 
-              selectedText =
-                  node.value.substring(node.textSelStart, node.textSelEnd);
-            }
+            selectedText =
+                node.value.substring(node.textSelStart || 0,
+                                     node.textSelEnd || 0);
           }
           options.annotation.push(token);
           if (selectedText && !this.formatOptions_.braille) {
@@ -1105,7 +1105,7 @@
           this.append_(buff, node.description || '', options);
         } else if (token == 'urlFilename') {
           options.annotation.push('name');
-          var url = node.url;
+          var url = node.url || '';
           var filename = '';
           if (url.substring(0, 4) != 'data') {
             filename =
@@ -1117,23 +1117,22 @@
           }
           this.append_(buff, filename, options);
         } else if (token == 'nameFromNode') {
-          if (chrome.automation.NameFromType[node.nameFrom] ==
-              'contents')
+          if (node.nameFrom == chrome.automation.NameFromType.CONTENTS)
             return;
 
           options.annotation.push('name');
-          this.append_(buff, node.name, options);
+          this.append_(buff, node.name || '', options);
         } else if (token == 'nameOrDescendants') {
           options.annotation.push(token);
           if (node.name)
-            this.append_(buff, node.name, options);
+            this.append_(buff, node.name || '', options);
           else
             this.format_(node, '$descendants', buff);
         } else if (token == 'description') {
           if (node.name == node.description || node.value == node.description)
             return;
           options.annotation.push(token);
-          this.append_(buff, node.description, options);
+          this.append_(buff, node.description || '', options);
         } else if (token == 'indexInParent') {
           if (node.parent) {
             options.annotation.push(token);
@@ -1168,22 +1167,25 @@
               msg = 'aria_checked_false';
               break;
             default:
-            msg =
-                node.state.checked ? 'aria_checked_true' : 'aria_checked_false';
+              msg = node.state[StateType.CHECKED] ?
+                  'aria_checked_true' : 'aria_checked_false';
           }
           this.format_(node, '@' + msg, buff);
         } else if (token == 'state') {
-          Object.getOwnPropertyNames(node.state).forEach(function(s) {
-            var stateInfo = Output.STATE_INFO_[s];
-            if (stateInfo && !stateInfo.isRoleSpecific && stateInfo.on)
+          if (node.state) {
+            Object.getOwnPropertyNames(node.state).forEach(function(s) {
+              var stateInfo = Output.STATE_INFO_[s];
+              if (stateInfo && !stateInfo.isRoleSpecific && stateInfo.on)
               this.format_(node, '@' + stateInfo.on.msgId, buff);
-          }.bind(this));
+            }.bind(this));
+          }
         } else if (token == 'find') {
           // Find takes two arguments: JSON query string and format string.
           if (tree.firstChild) {
             var jsonQuery = tree.firstChild.value;
             node = node.find(
-                /** @type {Object}*/(JSON.parse(jsonQuery)));
+                /** @type {chrome.automation.FindParams}*/(
+                    JSON.parse(jsonQuery)));
             var formatString = tree.firstChild.nextSibling;
             if (node)
               this.format_(node, formatString, buff);
@@ -1230,7 +1232,7 @@
           } else {
             console.error('Missing role info for ' + node.role);
           }
-          this.append_(buff, msg, options);
+          this.append_(buff, msg || '', options);
         } else if (token == 'inputType') {
           if (!node.inputType)
             return;
@@ -1285,7 +1287,8 @@
             return;
           if (this.formatOptions_.speech && resolvedInfo.earconId) {
             options.annotation.push(
-                new Output.EarconAction(resolvedInfo.earconId), node.location);
+                new Output.EarconAction(resolvedInfo.earconId),
+                                        node.location || undefined);
           }
           var msgId =
               this.formatOptions_.braille ? resolvedInfo.msgId + '_brl' :
@@ -1307,7 +1310,8 @@
               return;
 
             options.annotation.push(
-                new Output.EarconAction(tree.firstChild.value, node.location));
+                new Output.EarconAction(tree.firstChild.value,
+                                        node.location || undefined));
             this.append_(buff, '', options);
           } else if (token == 'countChildren') {
             var role = tree.firstChild.value;
@@ -1474,7 +1478,7 @@
       for (i = 0; i < ancestors.length - 1; i++) {
         var node = ancestors[i];
         // Discard ancestors of deepest window.
-        if (node.role == RoleType.window) {
+        if (node.role == RoleType.WINDOW) {
           contextFirst = [];
           rest = [];
         }
@@ -1638,7 +1642,7 @@
       options.annotation.push(earcon);
     var text = '';
 
-    if (this.formatOptions_.braille && !node.state.editable) {
+    if (this.formatOptions_.braille && !node.state[StateType.EDITABLE]) {
       // In braille, we almost always want to show the entire contents and
       // simply place the cursor under the SelectionSpan we set above.
       text = range.start.getText();
@@ -1781,7 +1785,7 @@
             if (!s.node)
               return false;
             return s.node.display == 'inline' ||
-                s.node.role == RoleType.inlineTextBox;
+                s.node.role == RoleType.INLINE_TEXT_BOX;
           });
 
       var isName = cur.hasSpan('name');
@@ -1832,7 +1836,8 @@
       while (earconFinder = ancestors.pop()) {
         var info = Output.ROLE_INFO_[earconFinder.role];
         if (info && info.earconId) {
-          return new Output.EarconAction(info.earconId, node.location);
+          return new Output.EarconAction(info.earconId,
+                                         node.location || undefined);
           break;
         }
         earconFinder = earconFinder.parent;
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output_test.extjs b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output_test.extjs
index 8f40e62..7a98411 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output_test.extjs
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output_test.extjs
@@ -727,7 +727,7 @@
                            aria-valuenow="2" aria-label="volume"></div>
   */}, function(root) {
 
-    var obj = root.find({role: RoleType.slider});
+    var obj = root.find({role: RoleType.SLIDER});
     var o = new Output().withSpeech(cursors.Range.fromNode(obj));
       checkSpeechOutput('2|Min 1|Max 10|volume|Slider',
           [
@@ -738,7 +738,7 @@
           ],
           o);
 
-    obj = root.find({role: RoleType.progressIndicator});
+    obj = root.find({role: RoleType.PROGRESS_INDICATOR});
     o = new Output().withSpeech(cursors.Range.fromNode(obj));
       checkSpeechOutput('2|Min 1|Max 10|volume|Progress indicator',
           [
@@ -748,7 +748,7 @@
           ],
           o);
 
-    obj = root.find({role: RoleType.meter});
+    obj = root.find({role: RoleType.METER});
     o = new Output().withSpeech(cursors.Range.fromNode(obj));
       checkSpeechOutput('2|Min 1|Max 10|volume|Meter',
           [
@@ -758,7 +758,7 @@
           ],
           o);
 
-    obj = root.find({role: RoleType.spinButton});
+    obj = root.find({role: RoleType.SPIN_BUTTON});
     o = new Output().withSpeech(cursors.Range.fromNode(obj));
       checkSpeechOutput('2|Min 1|Max 10|volume|Spin button',
           [
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/panel.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/panel.js
index 4e67530a..acc3b48 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/panel.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/panel.js
@@ -785,7 +785,7 @@
 Panel.closeMenusAndRestoreFocus = function() {
   // Watch for the next focus event.
   var onFocus = function(desktop, evt) {
-    desktop.removeEventListener(chrome.automation.EventType.focus, onFocus);
+    desktop.removeEventListener(chrome.automation.EventType.FOCUS, onFocus);
     if (Panel.pendingCallback_) {
       // Clear it before calling it, in case the callback itself triggers
       // another pending callback.
@@ -798,7 +798,7 @@
   chrome.automation.getDesktop(function(desktop) {
     onFocus = /** @type {function(chrome.automation.AutomationEvent)} */(
         onFocus.bind(this, desktop));
-    desktop.addEventListener(chrome.automation.EventType.focus,
+    desktop.addEventListener(chrome.automation.EventType.FOCUS,
                              onFocus,
                              true);
 
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/tabs_automation_handler.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/tabs_automation_handler.js
index 836c23c..0e23656 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/tabs_automation_handler.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/tabs_automation_handler.js
@@ -8,11 +8,13 @@
 
 goog.provide('TabsAutomationHandler');
 
+goog.require('CustomAutomationEvent');
 goog.require('DesktopAutomationHandler');
 
 goog.scope(function() {
 var EventType = chrome.automation.EventType;
 var RoleType = chrome.automation.RoleType;
+var StateType = chrome.automation.StateType;
 
 /**
  * @param {!chrome.automation.AutomationNode} tabRoot
@@ -22,14 +24,14 @@
 TabsAutomationHandler = function(tabRoot) {
   DesktopAutomationHandler.call(this, tabRoot);
 
-  if (tabRoot.role != RoleType.rootWebArea)
+  if (tabRoot.role != RoleType.ROOT_WEB_AREA)
     throw new Error('Expected rootWebArea node but got ' + tabRoot.role);
 
   // When the root is focused, simulate what happens on a load complete.
-  if (tabRoot.state.focused) {
-    this.onLoadComplete(
-        new chrome.automation.AutomationEvent(EventType.loadComplete, tabRoot,
-                                              'page'));
+  if (tabRoot.state[StateType.FOCUSED]) {
+    var event = new CustomAutomationEvent(
+        EventType.LOAD_COMPLETE, tabRoot, 'page');
+    this.onLoadComplete(event);
   }
 };
 
@@ -44,8 +46,9 @@
   /** @override */
   onLoadComplete: function(evt) {
     var focused = evt.target.find({state: {focused: true}}) || evt.target;
-    this.onFocus(new chrome.automation.AutomationEvent(
-        EventType.focus, focused, evt.eventFrom));
+    var event = new CustomAutomationEvent(
+        EventType.FOCUS, focused, evt.eventFrom);
+    this.onFocus(event);
   }
 };
 
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/tree_walker.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/tree_walker.js
index f947a06..268ce13e 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/tree_walker.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/tree_walker.js
@@ -88,7 +88,7 @@
    * backward.
    * @type {chrome.automation.AutomationNode} @private
    */
-  this.backwardAncestor_ = node.parent;
+  this.backwardAncestor_ = node.parent || null;
   var restrictions = opt_restrictions || {};
 
   this.visitPred_ = function(node) {
@@ -221,8 +221,8 @@
     }
     if (node.parent && this.backwardAncestor_ == node.parent) {
       this.phase_ = AutomationTreeWalkerPhase.ANCESTOR;
-      this.backwardAncestor_ = node.parent.parent;
+      this.backwardAncestor_ = node.parent.parent || null;
     }
-    this.node_ = node.parent;
+    this.node_ = node.parent || null;
   }
 };
diff --git a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
index 330f03f..fbc377b 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
+++ b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
@@ -60,13 +60,13 @@
 
   chrome.automation.getDesktop(function(desktop) {
     desktop.addEventListener(
-        EventType.mousePressed, this.onMousePressed_.bind(this), true);
+        EventType.MOUSE_PRESSED, this.onMousePressed_.bind(this), true);
     desktop.addEventListener(
-        EventType.mouseDragged, this.onMouseDragged_.bind(this), true);
+        EventType.MOUSE_DRAGGED, this.onMouseDragged_.bind(this), true);
     desktop.addEventListener(
-        EventType.mouseReleased, this.onMouseReleased_.bind(this), true);
+        EventType.MOUSE_RELEASED, this.onMouseReleased_.bind(this), true);
     desktop.addEventListener(
-        EventType.mouseCanceled, this.onMouseCanceled_.bind(this), true);
+        EventType.MOUSE_CANCELED, this.onMouseCanceled_.bind(this), true);
   }.bind(this));
 };
 
@@ -122,10 +122,10 @@
     // roles here.
     var root = this.startNode_;
     while (root.parent &&
-        root.role != RoleType.window &&
-        root.role != RoleType.rootWebArea &&
-        root.role != RoleType.desktop &&
-        root.role != RoleType.dialog) {
+        root.role != RoleType.WINDOW &&
+        root.role != RoleType.ROOT_WEB_AREA &&
+        root.role != RoleType.DESKTOP &&
+        root.role != RoleType.DIALOG) {
       root = root.parent;
     }
 
@@ -189,7 +189,7 @@
     for (var i = 0; i < nodes.length; i++) {
       var node = nodes[i];
       var isLast = (i == nodes.length - 1);
-      chrome.tts.speak(node.name, {
+      chrome.tts.speak(node.name || '', {
         lang: 'en-US',
         'enqueue': true,
         onEvent: (function(node, isLast, event) {
diff --git a/chrome/browser/resources/settings/settings_menu/settings_menu.html b/chrome/browser/resources/settings/settings_menu/settings_menu.html
index ab0864f8..d9458b9 100644
--- a/chrome/browser/resources/settings/settings_menu/settings_menu.html
+++ b/chrome/browser/resources/settings/settings_menu/settings_menu.html
@@ -147,7 +147,7 @@
           </div>
         </paper-menu>
       </div>
-      <paper-submenu class="page-menu" id="advancedPage" actionable
+      <paper-submenu class="page-menu" id="advancedSubmenu" actionable
           opened="{{advancedOpened}}"
           hidden="[[!pageVisibility.advancedSettings]]">
         <div class="menu-trigger">
diff --git a/chrome/browser/resources/settings/settings_menu/settings_menu.js b/chrome/browser/resources/settings/settings_menu/settings_menu.js
index 04ff5513..590b331 100644
--- a/chrome/browser/resources/settings/settings_menu/settings_menu.js
+++ b/chrome/browser/resources/settings/settings_menu/settings_menu.js
@@ -43,7 +43,7 @@
     // https://codereview.chromium.org/2412343004) or a Polymer PR (ex:
     // https://github.com/PolymerElements/paper-menu/pull/107).
     if (this.advancedOpened)
-      this.$.advancedPage.open();
+      this.$.advancedSubmenu.open();
   },
 
   /**
diff --git a/chrome/browser/resources/vulcanize.py b/chrome/browser/resources/vulcanize.py
index 03aeb8f..2c8600e 100755
--- a/chrome/browser/resources/vulcanize.py
+++ b/chrome/browser/resources/vulcanize.py
@@ -109,10 +109,6 @@
 
 
 def main():
-  _vulcanize(directory='md_downloads', host='downloads',
-             html_in_file='downloads.html')
-  _css_build(directory='md_downloads', files=['vulcanized.html'])
-
   # Already loaded by history.html:
   history_extra_args = ['--exclude', 'chrome://resources/html/util.html',
                         '--exclude', 'chrome://history/constants.html']
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index fcba340..3a31875 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1043,8 +1043,6 @@
       "webui/options/options_ui.h",
       "webui/options/password_manager_handler.cc",
       "webui/options/password_manager_handler.h",
-      "webui/options/pepper_flash_content_settings_utils.cc",
-      "webui/options/pepper_flash_content_settings_utils.h",
       "webui/options/reset_profile_settings_handler.cc",
       "webui/options/reset_profile_settings_handler.h",
       "webui/options/search_engine_manager_handler.cc",
@@ -1206,6 +1204,8 @@
       "hung_plugin_tab_helper.h",
       "webui/flash_ui.cc",
       "webui/flash_ui.h",
+      "webui/options/pepper_flash_content_settings_utils.cc",
+      "webui/options/pepper_flash_content_settings_utils.h",
     ]
     deps += [ "//ppapi/proxy:ipc" ]
   }
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index 6b85038..f34d661 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -26,7 +26,6 @@
 #include "chrome/browser/ui/autofill/credit_card_scanner_controller.h"
 #include "chrome/browser/ui/autofill/save_card_bubble_controller_impl.h"
 #include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/web_data_service_factory.h"
@@ -369,14 +368,11 @@
 
 bool ChromeAutofillClient::ShouldShowSigninPromo() {
 #if !defined(OS_ANDROID)
-  // Determine if we are in a valid context (on desktop platforms, we could be
-  // in an app window with no Browser).
-  if (!chrome::FindBrowserWithWebContents(web_contents()))
-    return false;
-#endif
-
+  return false;
+#else
   return signin::ShouldShowPromo(
       Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
+#endif
 }
 
 void ChromeAutofillClient::StartSigninFlow() {
@@ -384,13 +380,6 @@
   chrome::android::SigninPromoUtilAndroid::StartAccountSigninActivityForPromo(
       content::ContentViewCore::FromWebContents(web_contents()),
       signin_metrics::AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN);
-#else
-  chrome::FindBrowserWithWebContents(web_contents())
-      ->window()
-      ->ShowAvatarBubbleFromAvatarButton(
-          BrowserWindow::AVATAR_BUBBLE_MODE_SIGNIN,
-          signin::ManageAccountsParams(),
-          signin_metrics::AccessPoint::ACCESS_POINT_AUTOFILL_DROPDOWN);
 #endif
 }
 
diff --git a/chrome/browser/ui/views/session_crashed_bubble_view.cc b/chrome/browser/ui/views/session_crashed_bubble_view.cc
index 0954fca..459ef599 100644
--- a/chrome/browser/ui/views/session_crashed_bubble_view.cc
+++ b/chrome/browser/ui/views/session_crashed_bubble_view.cc
@@ -14,8 +14,6 @@
 #include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
 #include "build/build_config.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/metrics/metrics_reporting_state.h"
 #include "chrome/browser/sessions/session_restore.h"
 #include "chrome/browser/ui/browser_list.h"
@@ -24,7 +22,6 @@
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/toolbar/app_menu_button.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
-#include "chrome/common/url_constants.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/installer/util/google_update_settings.h"
@@ -32,11 +29,7 @@
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/browser/notification_source.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/web_contents.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/views/bubble/bubble_frame_view.h"
 #include "ui/views/controls/button/checkbox.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/separator.h"
@@ -164,7 +157,7 @@
 
   Browser* browser = browser_observer->browser();
 
-  if (!browser) {
+  if (!browser || !browser->tab_strip_model()->GetActiveWebContents()) {
     RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_ERROR);
     return;
   }
@@ -172,17 +165,8 @@
   views::View* anchor_view = BrowserView::GetBrowserViewForBrowser(browser)
                                  ->toolbar()
                                  ->app_menu_button();
-  content::WebContents* web_contents =
-      browser->tab_strip_model()->GetActiveWebContents();
-
-  if (!web_contents) {
-    RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_ERROR);
-    return;
-  }
-
   SessionCrashedBubbleView* crash_bubble =
-      new SessionCrashedBubbleView(anchor_view, browser, web_contents,
-                                   offer_uma_optin);
+      new SessionCrashedBubbleView(anchor_view, browser, offer_uma_optin);
   views::BubbleDialogDelegateView::CreateBubble(crash_bubble)->Show();
 
   RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_SHOWN);
@@ -193,27 +177,16 @@
 SessionCrashedBubbleView::SessionCrashedBubbleView(
     views::View* anchor_view,
     Browser* browser,
-    content::WebContents* web_contents,
     bool offer_uma_optin)
     : BubbleDialogDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
-      content::WebContentsObserver(web_contents),
       browser_(browser),
-      web_contents_(web_contents),
       uma_option_(NULL),
       offer_uma_optin_(offer_uma_optin),
-      first_navigation_ignored_(false),
       restored_(false) {
   set_close_on_deactivate(false);
-  registrar_.Add(
-      this,
-      chrome::NOTIFICATION_TAB_CLOSING,
-      content::Source<content::NavigationController>(&(
-          web_contents->GetController())));
-  browser->tab_strip_model()->AddObserver(this);
 }
 
 SessionCrashedBubbleView::~SessionCrashedBubbleView() {
-  browser_->tab_strip_model()->RemoveObserver(this);
 }
 
 base::string16 SessionCrashedBubbleView::GetWindowTitle() const {
@@ -335,45 +308,6 @@
   RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_HELP);
 }
 
-void SessionCrashedBubbleView::DidFinishLoad(
-    content::RenderFrameHost* render_frame_host,
-    const GURL& validated_url) {
-  // We want to close the bubble if the user navigates the first tab away after
-  // a crash. Ignore subframe navigations since they're noise and don't affect
-  // the desired behavior.
-  if (render_frame_host->GetParent())
-    return;
-
-  if (!first_navigation_ignored_) {
-    first_navigation_ignored_ = true;
-    return;
-  }
-
-  CloseBubble();
-}
-
-void SessionCrashedBubbleView::WasShown() {
-  GetWidget()->Show();
-}
-
-void SessionCrashedBubbleView::WasHidden() {
-  GetWidget()->Hide();
-}
-
-void SessionCrashedBubbleView::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  DCHECK_EQ(chrome::NOTIFICATION_TAB_CLOSING, type);
-  CloseBubble();
-}
-
-void SessionCrashedBubbleView::TabDetachedAt(content::WebContents* contents,
-                                             int index) {
-  if (web_contents_ == contents)
-    CloseBubble();
-}
-
 void SessionCrashedBubbleView::RestorePreviousSession() {
   SessionRestore::RestoreSessionAfterCrash(browser_);
   RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_RESTORED);
@@ -385,9 +319,5 @@
     ChangeMetricsReportingState(true);
     RecordBubbleHistogramValue(SESSION_CRASHED_BUBBLE_UMA_OPTIN);
   }
-  CloseBubble();
-}
-
-void SessionCrashedBubbleView::CloseBubble() {
   GetWidget()->Close();
 }
diff --git a/chrome/browser/ui/views/session_crashed_bubble_view.h b/chrome/browser/ui/views/session_crashed_bubble_view.h
index 9779ce2..c8838de 100644
--- a/chrome/browser/ui/views/session_crashed_bubble_view.h
+++ b/chrome/browser/ui/views/session_crashed_bubble_view.h
@@ -9,11 +9,6 @@
 
 #include "base/macros.h"
 #include "chrome/browser/ui/session_crashed_bubble.h"
-#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
-#include "content/public/browser/reload_type.h"
-#include "content/public/browser/web_contents_observer.h"
 #include "ui/views/bubble/bubble_dialog_delegate.h"
 #include "ui/views/controls/styled_label_listener.h"
 
@@ -22,21 +17,14 @@
 class Widget;
 }
 
-namespace content {
-class WebContents;
-}
-
 class Browser;
 
-// It creates a session restore request bubble when the previous session has
-// crashed. It also presents an option to enable metrics reporting, if it not
-// enabled already.
+// SessionCrashedBubbleView shows a bubble allowing the user to restore the
+// previous session. If metrics reporting is not enabled a checkbox is presented
+// allowing the user to turn it on.
 class SessionCrashedBubbleView : public SessionCrashedBubble,
                                  public views::BubbleDialogDelegateView,
-                                 public views::StyledLabelListener,
-                                 public content::WebContentsObserver,
-                                 public content::NotificationObserver,
-                                 public TabStripModelObserver {
+                                 public views::StyledLabelListener {
  public:
   // A helper class that listens to browser removal event.
   class BrowserRemovalObserver;
@@ -51,7 +39,6 @@
  private:
   SessionCrashedBubbleView(views::View* anchor_view,
                            Browser* browser,
-                           content::WebContents* web_contents,
                            bool offer_uma_optin);
   ~SessionCrashedBubbleView() override;
 
@@ -74,47 +61,18 @@
                               const gfx::Range& range,
                               int event_flags) override;
 
-  // content::WebContentsObserver methods.
-  void DidFinishLoad(content::RenderFrameHost* render_frame_host,
-                     const GURL& validated_url) override;
-  void WasShown() override;
-  void WasHidden() override;
-
-  // content::NotificationObserver methods.
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
-
-  // TabStripModelObserver methods.
-  // When the tab with current bubble is being dragged and dropped to a new
-  // window or to another window, the bubble will be dismissed as if the user
-  // chose not to restore the previous session.
-  void TabDetachedAt(content::WebContents* contents, int index) override;
-
   // Restore previous session after user selects so.
   void RestorePreviousSession();
 
-  // Close and destroy the bubble.
-  void CloseBubble();
-
-  content::NotificationRegistrar registrar_;
-
   // Used for opening the question mark link as well as access the tab strip.
   Browser* browser_;
 
-  // The web content associated with current bubble.
-  content::WebContents* web_contents_;
-
   // Checkbox for the user to opt-in to UMA reporting.
   views::Checkbox* uma_option_;
 
   // Whether or not the UMA opt-in option should be shown.
   bool offer_uma_optin_;
 
-  // Whether or not the first navigation was ignored. This is needed because the
-  // bubble shouldn't go away when the new tab page loads after a crash.
-  bool first_navigation_ignored_;
-
   // Whether or not the user chose to restore previous session. It is used to
   // collect bubble usage stats.
   bool restored_;
diff --git a/chrome/browser/ui/webui/chromeos/login/app_launch_splash_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/app_launch_splash_screen_handler.cc
index 70d0ddd..bf074962 100644
--- a/chrome/browser/ui/webui/chromeos/login/app_launch_splash_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/app_launch_splash_screen_handler.cc
@@ -38,16 +38,10 @@
 
 AppLaunchSplashScreenHandler::AppLaunchSplashScreenHandler(
     const scoped_refptr<NetworkStateInformer>& network_state_informer,
-    NetworkErrorModel* network_error_model)
+    ErrorScreen* error_screen)
     : BaseScreenHandler(kJsScreenPath),
-      delegate_(NULL),
-      show_on_init_(false),
-      state_(APP_LAUNCH_STATE_LOADING_AUTH_FILE),
       network_state_informer_(network_state_informer),
-      network_error_model_(network_error_model),
-      online_state_(false),
-      network_config_done_(false),
-      network_config_requested_(false) {
+      error_screen_(error_screen) {
   network_state_informer_->AddObserver(this);
 }
 
@@ -147,43 +141,43 @@
   const std::string network_path = network_state_informer_->network_path();
   const std::string network_name = GetNetworkName(network_path);
 
-  network_error_model_->SetUIState(NetworkError::UI_STATE_KIOSK_MODE);
-  network_error_model_->AllowGuestSignin(false);
-  network_error_model_->AllowOfflineLogin(false);
+  error_screen_->SetUIState(NetworkError::UI_STATE_KIOSK_MODE);
+  error_screen_->AllowGuestSignin(false);
+  error_screen_->AllowOfflineLogin(false);
 
   switch (state) {
     case NetworkStateInformer::CAPTIVE_PORTAL: {
-      network_error_model_->SetErrorState(NetworkError::ERROR_STATE_PORTAL,
-                                          network_name);
-      network_error_model_->FixCaptivePortal();
+      error_screen_->SetErrorState(NetworkError::ERROR_STATE_PORTAL,
+                                   network_name);
+      error_screen_->FixCaptivePortal();
 
       break;
     }
     case NetworkStateInformer::PROXY_AUTH_REQUIRED: {
-      network_error_model_->SetErrorState(NetworkError::ERROR_STATE_PROXY,
-                                          network_name);
+      error_screen_->SetErrorState(NetworkError::ERROR_STATE_PROXY,
+                                   network_name);
       break;
     }
     case NetworkStateInformer::OFFLINE: {
-      network_error_model_->SetErrorState(NetworkError::ERROR_STATE_OFFLINE,
-                                          network_name);
+      error_screen_->SetErrorState(NetworkError::ERROR_STATE_OFFLINE,
+                                   network_name);
       break;
     }
     case NetworkStateInformer::ONLINE: {
-      network_error_model_->SetErrorState(
-          NetworkError::ERROR_STATE_KIOSK_ONLINE, network_name);
+      error_screen_->SetErrorState(NetworkError::ERROR_STATE_KIOSK_ONLINE,
+                                   network_name);
       break;
     }
     default:
-      network_error_model_->SetErrorState(NetworkError::ERROR_STATE_OFFLINE,
-                                          network_name);
+      error_screen_->SetErrorState(NetworkError::ERROR_STATE_OFFLINE,
+                                   network_name);
       NOTREACHED();
       break;
   }
 
   if (GetCurrentScreen() != OobeScreen::SCREEN_ERROR_MESSAGE)
-    network_error_model_->SetParentScreen(OobeScreen::SCREEN_APP_LAUNCH_SPLASH);
-  network_error_model_->Show();
+    error_screen_->SetParentScreen(OobeScreen::SCREEN_APP_LAUNCH_SPLASH);
+  error_screen_->Show();
 }
 
 bool AppLaunchSplashScreenHandler::IsNetworkReady() {
diff --git a/chrome/browser/ui/webui/chromeos/login/app_launch_splash_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/app_launch_splash_screen_handler.h
index 40f68b7..8cabf78 100644
--- a/chrome/browser/ui/webui/chromeos/login/app_launch_splash_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/app_launch_splash_screen_handler.h
@@ -10,7 +10,7 @@
 
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/app_launch_splash_screen_actor.h"
-#include "chrome/browser/chromeos/login/screens/network_error_model.h"
+#include "chrome/browser/chromeos/login/screens/error_screen.h"
 #include "chrome/browser/ui/webui/chromeos/login/base_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/network_state_informer.h"
 
@@ -24,7 +24,7 @@
  public:
   AppLaunchSplashScreenHandler(
       const scoped_refptr<NetworkStateInformer>& network_state_informer,
-      NetworkErrorModel* network_error_model);
+      ErrorScreen* error_screen);
   ~AppLaunchSplashScreenHandler() override;
 
   // BaseScreenHandler implementation:
@@ -57,22 +57,22 @@
   void HandleContinueAppLaunch();
   void HandleNetworkConfigRequested();
 
-  AppLaunchSplashScreenHandler::Delegate* delegate_;
-  bool show_on_init_;
+  AppLaunchSplashScreenHandler::Delegate* delegate_ = nullptr;
+  bool show_on_init_ = false;
   std::string app_id_;
-  AppLaunchState state_;
+  AppLaunchState state_ = APP_LAUNCH_STATE_LOADING_AUTH_FILE;
 
   scoped_refptr<NetworkStateInformer> network_state_informer_;
-  NetworkErrorModel* network_error_model_;
+  ErrorScreen* error_screen_;
 
   // True if we are online.
-  bool online_state_;
+  bool online_state_ = false;
 
   // True if we have network config screen was already shown before.
-  bool network_config_done_;
+  bool network_config_done_ = false;
 
   // True if we have manually requested network config screen.
-  bool network_config_requested_;
+  bool network_config_requested_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(AppLaunchSplashScreenHandler);
 };
diff --git a/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.cc
index f50dc75f..2b4a49d 100644
--- a/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.cc
@@ -122,20 +122,16 @@
 
 EnrollmentScreenHandler::EnrollmentScreenHandler(
     const scoped_refptr<NetworkStateInformer>& network_state_informer,
-    NetworkErrorModel* network_error_model)
+    ErrorScreen* error_screen)
     : BaseScreenHandler(kJsScreenPath),
-      controller_(NULL),
-      show_on_init_(false),
-      first_show_(true),
-      observe_network_failure_(false),
       network_state_informer_(network_state_informer),
-      network_error_model_(network_error_model),
+      error_screen_(error_screen),
       histogram_helper_(new ErrorScreensHistogramHelper("Enrollment")),
       weak_ptr_factory_(this) {
   set_async_assets_load_id(
       GetOobeScreenName(OobeScreen::SCREEN_OOBE_ENROLLMENT));
   DCHECK(network_state_informer_.get());
-  DCHECK(network_error_model_);
+  DCHECK(error_screen_);
   network_state_informer_->AddObserver(this);
 }
 
@@ -422,7 +418,7 @@
 
 bool EnrollmentScreenHandler::IsEnrollmentScreenHiddenByError() const {
   return (GetCurrentScreen() == OobeScreen::SCREEN_ERROR_MESSAGE &&
-          network_error_model_->GetParentScreen() ==
+          error_screen_->GetParentScreen() ==
               OobeScreen::SCREEN_OOBE_ENROLLMENT);
 }
 
@@ -455,7 +451,7 @@
                << "reason=" << NetworkError::ErrorReasonString(reason);
 
   if (is_online || !is_behind_captive_portal)
-    network_error_model_->HideCaptivePortal();
+    error_screen_->HideCaptivePortal();
 
   if (is_frame_error) {
     LOG(WARNING) << "Retry page load";
@@ -478,36 +474,36 @@
   const bool is_frame_error = reason == NetworkError::ERROR_REASON_FRAME_ERROR;
 
   if (is_proxy_error) {
-    network_error_model_->SetErrorState(NetworkError::ERROR_STATE_PROXY,
-                                        std::string());
+    error_screen_->SetErrorState(NetworkError::ERROR_STATE_PROXY,
+                                 std::string());
   } else if (is_behind_captive_portal) {
     // Do not bother a user with obsessive captive portal showing. This
     // check makes captive portal being shown only once: either when error
     // screen is shown for the first time or when switching from another
     // error screen (offline, proxy).
-    if (IsOnEnrollmentScreen() || (network_error_model_->GetErrorState() !=
-                                   NetworkError::ERROR_STATE_PORTAL)) {
-      network_error_model_->FixCaptivePortal();
+    if (IsOnEnrollmentScreen() ||
+        (error_screen_->GetErrorState() != NetworkError::ERROR_STATE_PORTAL)) {
+      error_screen_->FixCaptivePortal();
     }
     const std::string network_name = GetNetworkName(network_path);
-    network_error_model_->SetErrorState(NetworkError::ERROR_STATE_PORTAL,
-                                        network_name);
+    error_screen_->SetErrorState(NetworkError::ERROR_STATE_PORTAL,
+                                 network_name);
   } else if (is_frame_error) {
-    network_error_model_->SetErrorState(
-        NetworkError::ERROR_STATE_AUTH_EXT_TIMEOUT, std::string());
+    error_screen_->SetErrorState(NetworkError::ERROR_STATE_AUTH_EXT_TIMEOUT,
+                                 std::string());
   } else {
-    network_error_model_->SetErrorState(NetworkError::ERROR_STATE_OFFLINE,
-                                        std::string());
+    error_screen_->SetErrorState(NetworkError::ERROR_STATE_OFFLINE,
+                                 std::string());
   }
 
   if (GetCurrentScreen() != OobeScreen::SCREEN_ERROR_MESSAGE) {
     const std::string network_type = network_state_informer_->network_type();
-    network_error_model_->SetUIState(NetworkError::UI_STATE_SIGNIN);
-    network_error_model_->SetParentScreen(OobeScreen::SCREEN_OOBE_ENROLLMENT);
-    network_error_model_->SetHideCallback(base::Bind(
-        &EnrollmentScreenHandler::DoShow, weak_ptr_factory_.GetWeakPtr()));
-    network_error_model_->Show();
-    histogram_helper_->OnErrorShow(network_error_model_->GetErrorState());
+    error_screen_->SetUIState(NetworkError::UI_STATE_SIGNIN);
+    error_screen_->SetParentScreen(OobeScreen::SCREEN_OOBE_ENROLLMENT);
+    error_screen_->SetHideCallback(base::Bind(&EnrollmentScreenHandler::DoShow,
+                                              weak_ptr_factory_.GetWeakPtr()));
+    error_screen_->Show();
+    histogram_helper_->OnErrorShow(error_screen_->GetErrorState());
   }
 }
 
@@ -515,7 +511,7 @@
     NetworkStateInformer::State state,
     NetworkError::ErrorReason reason) {
   if (IsEnrollmentScreenHiddenByError())
-    network_error_model_->Hide();
+    error_screen_->Hide();
   histogram_helper_->OnErrorHide();
 }
 
diff --git a/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h
index bbedd3d..a329407 100644
--- a/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/enrollment_screen_handler.h
@@ -11,7 +11,7 @@
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/enrollment/enrollment_screen_actor.h"
 #include "chrome/browser/chromeos/login/enrollment/enterprise_enrollment_helper.h"
-#include "chrome/browser/chromeos/login/screens/network_error_model.h"
+#include "chrome/browser/chromeos/login/screens/error_screen.h"
 #include "chrome/browser/chromeos/policy/enrollment_config.h"
 #include "chrome/browser/ui/webui/chromeos/login/base_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/network_state_informer.h"
@@ -32,7 +32,7 @@
  public:
   EnrollmentScreenHandler(
       const scoped_refptr<NetworkStateInformer>& network_state_informer,
-      NetworkErrorModel* network_error_model);
+      ErrorScreen* error_screen);
   ~EnrollmentScreenHandler() override;
 
   // Implements WebUIMessageHandler:
@@ -120,24 +120,24 @@
                           authpolicy::ErrorType code);
 
   // Keeps the controller for this actor.
-  Controller* controller_;
+  Controller* controller_ = nullptr;
 
-  bool show_on_init_;
+  bool show_on_init_ = false;
 
   // The enrollment configuration.
   policy::EnrollmentConfig config_;
 
   // True if screen was not shown yet.
-  bool first_show_;
+  bool first_show_ = true;
 
   // Whether we should handle network errors on enrollment screen.
   // True when signin screen step is shown.
-  bool observe_network_failure_;
+  bool observe_network_failure_ = false;
 
   // Network state informer used to keep signin screen up.
   scoped_refptr<NetworkStateInformer> network_state_informer_;
 
-  NetworkErrorModel* network_error_model_;
+  ErrorScreen* error_screen_ = nullptr;
 
   std::unique_ptr<ErrorScreensHistogramHelper> histogram_helper_;
 
diff --git a/chrome/browser/ui/webui/chromeos/login/error_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/error_screen_handler.cc
index b3092a57..f8b7e674 100644
--- a/chrome/browser/ui/webui/chromeos/login/error_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/error_screen_handler.cc
@@ -7,7 +7,7 @@
 #include "ash/common/system/chromeos/devicetype_utils.h"
 #include "base/message_loop/message_loop.h"
 #include "base/time/time.h"
-#include "chrome/browser/chromeos/login/screens/network_error_model.h"
+#include "chrome/browser/chromeos/login/screens/error_screen.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/login/localized_values_builder.h"
@@ -23,15 +23,12 @@
 
 ErrorScreenHandler::ErrorScreenHandler()
     : BaseScreenHandler(kJsScreenPath),
-      model_(nullptr),
-      show_on_init_(false),
-      showing_(false),
       weak_ptr_factory_(this) {
 }
 
 ErrorScreenHandler::~ErrorScreenHandler() {
-  if (model_)
-    model_->OnViewDestroyed(this);
+  if (screen_)
+    screen_->OnViewDestroyed(this);
 }
 
 void ErrorScreenHandler::Show() {
@@ -40,24 +37,24 @@
     return;
   }
   BaseScreenHandler::ShowScreen(OobeScreen::SCREEN_ERROR_MESSAGE);
-  if (model_)
-    model_->OnShow();
+  if (screen_)
+    screen_->OnShow();
   showing_ = true;
 }
 
 void ErrorScreenHandler::Hide() {
   showing_ = false;
-  if (model_)
-   model_->OnHide();
+  if (screen_)
+    screen_->OnHide();
 }
 
-void ErrorScreenHandler::Bind(NetworkErrorModel& model) {
-  model_ = &model;
-  BaseScreenHandler::SetBaseScreen(model_);
+void ErrorScreenHandler::Bind(ErrorScreen* screen) {
+  screen_ = screen;
+  BaseScreenHandler::SetBaseScreen(screen_);
 }
 
 void ErrorScreenHandler::Unbind() {
-  model_ = nullptr;
+  screen_ = nullptr;
   BaseScreenHandler::SetBaseScreen(nullptr);
 }
 
@@ -65,11 +62,6 @@
   ShowScreen(screen);
 }
 
-void ErrorScreenHandler::HandleHideCaptivePortal() {
-  if (model_)
-    model_->HideCaptivePortal();
-}
-
 void ErrorScreenHandler::RegisterMessages() {
   AddCallback("hideCaptivePortal",
               &ErrorScreenHandler::HandleHideCaptivePortal);
@@ -127,8 +119,13 @@
 }
 
 void ErrorScreenHandler::OnConnectToNetworkRequested() {
-  if (showing_ && model_)
-    model_->OnUserAction(NetworkErrorModel::kUserActionConnectRequested);
+  if (showing_ && screen_)
+    screen_->OnUserAction(ErrorScreen::kUserActionConnectRequested);
+}
+
+void ErrorScreenHandler::HandleHideCaptivePortal() {
+  if (screen_)
+    screen_->HideCaptivePortal();
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/login/error_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/error_screen_handler.h
index e63fc19..f29d0da 100644
--- a/chrome/browser/ui/webui/chromeos/login/error_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/error_screen_handler.h
@@ -6,14 +6,13 @@
 #define CHROME_BROWSER_UI_WEBUI_CHROMEOS_LOGIN_ERROR_SCREEN_HANDLER_H_
 
 #include "base/macros.h"
+#include "chrome/browser/chromeos/login/screens/error_screen.h"
 #include "chrome/browser/chromeos/login/screens/network_error_view.h"
 #include "chrome/browser/ui/webui/chromeos/login/base_screen_handler.h"
 #include "chrome/browser/ui/webui/chromeos/login/network_dropdown_handler.h"
 
 namespace chromeos {
 
-class NetworkErrorModel;
-
 // A class that handles the WebUI hooks in error screen.
 class ErrorScreenHandler : public BaseScreenHandler,
                            public NetworkErrorView,
@@ -22,21 +21,18 @@
   ErrorScreenHandler();
   ~ErrorScreenHandler() override;
 
-  // ErrorView:
+ private:
+  // NetworkErrorView:
   void Show() override;
   void Hide() override;
-  void Bind(NetworkErrorModel& model) override;
+  void Bind(ErrorScreen* screen) override;
   void Unbind() override;
   void ShowOobeScreen(OobeScreen screen) override;
 
- private:
-  // WebUI message handlers.
-  void HandleHideCaptivePortal();
-
-  // WebUIMessageHandler implementation:
+  // WebUIMessageHandler:
   void RegisterMessages() override;
 
-  // BaseScreenHandler implementation:
+  // BaseScreenHandler:
   void DeclareLocalizedValues(
       ::login::LocalizedValuesBuilder* builder) override;
   void Initialize() override;
@@ -44,14 +40,17 @@
   // NetworkDropdownHandler:
   void OnConnectToNetworkRequested() override;
 
-  // Non-owning ptr.
-  NetworkErrorModel* model_;
+  // WebUI message handlers.
+  void HandleHideCaptivePortal();
 
-  // Keeps whether screen should be shown right after initialization.
-  bool show_on_init_;
+  // Non-owning ptr.
+  ErrorScreen* screen_ = nullptr;
+
+  // Should the screen be shown right after initialization?
+  bool show_on_init_ = false;
 
   // Whether the error screen is currently shown.
-  bool showing_;
+  bool showing_ = false;
 
   base::WeakPtrFactory<ErrorScreenHandler> weak_ptr_factory_;
 
diff --git a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
index 7dc1f89..128faa2 100644
--- a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
@@ -279,10 +279,10 @@
   network_dropdown_handler_->AddObserver(error_screen_handler_);
 
   error_screen_.reset(new ErrorScreen(nullptr, error_screen_handler_));
-  NetworkErrorModel* network_error_model = error_screen_.get();
+  ErrorScreen* error_screen = error_screen_.get();
 
   auto enrollment_screen_handler = base::MakeUnique<EnrollmentScreenHandler>(
-      network_state_informer_, network_error_model);
+      network_state_informer_, error_screen);
   enrollment_screen_actor_ = enrollment_screen_handler.get();
   AddScreenHandler(std::move(enrollment_screen_handler));
 
@@ -311,14 +311,14 @@
   AddScreenHandler(std::move(gaia_screen_handler));
 
   auto signin_screen_handler = base::MakeUnique<SigninScreenHandler>(
-      network_state_informer_, network_error_model, core_handler_,
+      network_state_informer_, error_screen, core_handler_,
       gaia_screen_handler_);
   signin_screen_handler_ = signin_screen_handler.get();
   AddScreenHandler(std::move(signin_screen_handler));
 
   auto app_launch_splash_screen_handler =
       base::MakeUnique<AppLaunchSplashScreenHandler>(network_state_informer_,
-                                                     network_error_model);
+                                                     error_screen);
   app_launch_splash_screen_actor_ = app_launch_splash_screen_handler.get();
   AddScreenHandler(std::move(app_launch_splash_screen_handler));
 
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
index 3b893b2..6fc2616 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
@@ -257,11 +257,11 @@
 
 SigninScreenHandler::SigninScreenHandler(
     const scoped_refptr<NetworkStateInformer>& network_state_informer,
-    NetworkErrorModel* network_error_model,
+    ErrorScreen* error_screen,
     CoreOobeActor* core_oobe_actor,
     GaiaScreenHandler* gaia_screen_handler)
     : network_state_informer_(network_state_informer),
-      network_error_model_(network_error_model),
+      error_screen_(error_screen),
       core_oobe_actor_(core_oobe_actor),
       caps_lock_enabled_(chromeos::input_method::InputMethodManager::Get()
                              ->GetImeKeyboard()
@@ -272,7 +272,7 @@
       histogram_helper_(new ErrorScreensHistogramHelper("Signin")),
       weak_factory_(this) {
   DCHECK(network_state_informer_.get());
-  DCHECK(network_error_model_);
+  DCHECK(error_screen_);
   DCHECK(core_oobe_actor_);
   gaia_screen_handler_->set_signin_screen_handler(this);
   network_state_informer_->AddObserver(this);
@@ -767,7 +767,7 @@
       &SigninScreenHandler::ReloadGaia, weak_factory_.GetWeakPtr(), true));
 
   if (is_online || !is_behind_captive_portal)
-    network_error_model_->HideCaptivePortal();
+    error_screen_->HideCaptivePortal();
 
   // Hide offline message (if needed) and return if current screen is
   // not a Gaia frame.
@@ -840,44 +840,44 @@
       (reason == NetworkError::ERROR_REASON_LOADING_TIMEOUT);
 
   if (is_proxy_error) {
-    network_error_model_->SetErrorState(NetworkError::ERROR_STATE_PROXY,
-                                        std::string());
+    error_screen_->SetErrorState(NetworkError::ERROR_STATE_PROXY,
+                                 std::string());
   } else if (is_behind_captive_portal) {
     // Do not bother a user with obsessive captive portal showing. This
     // check makes captive portal being shown only once: either when error
     // screen is shown for the first time or when switching from another
     // error screen (offline, proxy).
-    if (IsGaiaVisible() || (network_error_model_->GetErrorState() !=
-                            NetworkError::ERROR_STATE_PORTAL)) {
-      network_error_model_->FixCaptivePortal();
+    if (IsGaiaVisible() ||
+        (error_screen_->GetErrorState() != NetworkError::ERROR_STATE_PORTAL)) {
+      error_screen_->FixCaptivePortal();
     }
     const std::string network_name = GetNetworkName(network_path);
-    network_error_model_->SetErrorState(NetworkError::ERROR_STATE_PORTAL,
-                                        network_name);
+    error_screen_->SetErrorState(NetworkError::ERROR_STATE_PORTAL,
+                                 network_name);
   } else if (is_gaia_loading_timeout) {
-    network_error_model_->SetErrorState(
-        NetworkError::ERROR_STATE_AUTH_EXT_TIMEOUT, std::string());
+    error_screen_->SetErrorState(NetworkError::ERROR_STATE_AUTH_EXT_TIMEOUT,
+                                 std::string());
   } else {
-    network_error_model_->SetErrorState(NetworkError::ERROR_STATE_OFFLINE,
-                                        std::string());
+    error_screen_->SetErrorState(NetworkError::ERROR_STATE_OFFLINE,
+                                 std::string());
   }
 
   const bool guest_signin_allowed =
       IsGuestSigninAllowed() &&
-      IsSigninScreenError(network_error_model_->GetErrorState());
-  network_error_model_->AllowGuestSignin(guest_signin_allowed);
+      IsSigninScreenError(error_screen_->GetErrorState());
+  error_screen_->AllowGuestSignin(guest_signin_allowed);
 
   const bool offline_login_allowed =
-      IsSigninScreenError(network_error_model_->GetErrorState()) &&
-      network_error_model_->GetErrorState() !=
+      IsSigninScreenError(error_screen_->GetErrorState()) &&
+      error_screen_->GetErrorState() !=
           NetworkError::ERROR_STATE_AUTH_EXT_TIMEOUT;
-  network_error_model_->AllowOfflineLogin(offline_login_allowed);
+  error_screen_->AllowOfflineLogin(offline_login_allowed);
 
   if (GetCurrentScreen() != OobeScreen::SCREEN_ERROR_MESSAGE) {
-    network_error_model_->SetUIState(NetworkError::UI_STATE_SIGNIN);
-    network_error_model_->SetParentScreen(OobeScreen::SCREEN_GAIA_SIGNIN);
-    network_error_model_->Show();
-    histogram_helper_->OnErrorShow(network_error_model_->GetErrorState());
+    error_screen_->SetUIState(NetworkError::UI_STATE_SIGNIN);
+    error_screen_->SetParentScreen(OobeScreen::SCREEN_GAIA_SIGNIN);
+    error_screen_->Show();
+    histogram_helper_->OnErrorShow(error_screen_->GetErrorState());
   }
 }
 
@@ -888,7 +888,7 @@
 
   gaia_reload_reason_ = NetworkError::ERROR_REASON_NONE;
 
-  network_error_model_->Hide();
+  error_screen_->Hide();
   histogram_helper_->OnErrorHide();
 
   // Forces a reload for Gaia screen on hiding error message.
@@ -1521,7 +1521,7 @@
 
 bool SigninScreenHandler::IsSigninScreenHiddenByError() const {
   return (GetCurrentScreen() == OobeScreen::SCREEN_ERROR_MESSAGE) &&
-         (IsSigninScreen(network_error_model_->GetParentScreen()));
+         (IsSigninScreen(error_screen_->GetParentScreen()));
 }
 
 bool SigninScreenHandler::IsGuestSigninAllowed() const {
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
index 43f43a79..60809af 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h
@@ -17,7 +17,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/chromeos/login/screens/network_error_model.h"
+#include "chrome/browser/chromeos/login/screens/error_screen.h"
 #include "chrome/browser/chromeos/login/signin_specifics.h"
 #include "chrome/browser/chromeos/login/ui/login_display.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
@@ -223,7 +223,7 @@
  public:
   SigninScreenHandler(
       const scoped_refptr<NetworkStateInformer>& network_state_informer,
-      NetworkErrorModel* network_error_model,
+      ErrorScreen* error_screen,
       CoreOobeActor* core_oobe_actor,
       GaiaScreenHandler* gaia_screen_handler);
   ~SigninScreenHandler() override;
@@ -456,8 +456,8 @@
   bool webui_visible_ = false;
   bool preferences_changed_delayed_ = false;
 
-  NetworkErrorModel* network_error_model_;
-  CoreOobeActor* core_oobe_actor_;
+  ErrorScreen* error_screen_ = nullptr;
+  CoreOobeActor* core_oobe_actor_ = nullptr;
 
   NetworkStateInformer::State last_network_state_ =
       NetworkStateInformer::UNKNOWN;
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index b49e24c..acc2361 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -150,8 +150,6 @@
     "url_constants.h",
     "web_application_info.cc",
     "web_application_info.h",
-    "widevine_cdm_constants.cc",
-    "widevine_cdm_constants.h",
   ]
   defines = []
 
@@ -479,6 +477,12 @@
       "pepper_permission_util.h",
     ]
   }
+  if (enable_pepper_cdms) {
+    sources += [
+      "widevine_cdm_constants.cc",
+      "widevine_cdm_constants.h",
+    ]
+  }
   if (!enable_webrtc) {
     sources -= [ "media/webrtc_logging_messages.h" ]
   }
diff --git a/chrome/common/chrome_content_client.cc b/chrome/common/chrome_content_client.cc
index f8915a47..2e67b3b 100644
--- a/chrome/common/chrome_content_client.cc
+++ b/chrome/common/chrome_content_client.cc
@@ -77,7 +77,7 @@
 #if BUILDFLAG(ENABLE_PLUGINS)
 #include "content/public/common/pepper_plugin_info.h"
 #include "flapper_version.h"  // nogncheck  In SHARED_INTERMEDIATE_DIR.
-#include "ppapi/shared_impl/ppapi_permissions.h"
+#include "ppapi/shared_impl/ppapi_permissions.h"  // nogncheck
 #endif
 
 #if defined(WIDEVINE_CDM_AVAILABLE) && BUILDFLAG(ENABLE_PEPPER_CDMS) && \
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index fe299e5..c168f848 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -93,6 +93,12 @@
 const base::Feature kExperimentalKeyboardLockUI{
     "ExperimentalKeyboardLockUI", base::FEATURE_DISABLED_BY_DEFAULT};
 
+#if defined(OS_WIN)
+// Enables using GDI to print text as simply text.
+const base::Feature kGdiTextPrinting {"GdiTextPrinting",
+                                      base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
 #if defined (OS_CHROMEOS)
 // Enables or disables the Happiness Tracking System for the device.
 const base::Feature kHappinessTrackingSystem {
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 4a07739..340aa7a 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -60,6 +60,10 @@
 
 extern const base::Feature kExperimentalKeyboardLockUI;
 
+#if defined(OS_WIN)
+extern const base::Feature kGdiTextPrinting;
+#endif
+
 #if defined(OS_CHROMEOS)
 extern const base::Feature kHappinessTrackingSystem;
 #endif
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 68ab97c..6ff0a95 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -1081,10 +1081,6 @@
 #endif  // defined(OS_MACOSX)
 
 #if defined(OS_WIN)
-// Disables using GDI to print text as simply text. Fallback to printing text
-// as paths. Overrides --enable-gdi-text-printing.
-const char kDisableGDITextPrinting[] = "disable-gdi-text-printing";
-
 // Disables per monitor DPI for supported Windows versions.
 // This flag overrides kEnablePerMonitorDpi.
 const char kDisablePerMonitorDpi[]          = "disable-per-monitor-dpi";
@@ -1092,9 +1088,6 @@
 // Fallback to XPS. By default connector uses CDD.
 const char kEnableCloudPrintXps[]           = "enable-cloud-print-xps";
 
-// Enables using GDI to print text as simply text.
-const char kEnableGDITextPrinting[] = "enable-gdi-text-printing";
-
 // Enables per monitor DPI for supported Windows versions.
 const char kEnablePerMonitorDpi[]           = "enable-per-monitor-dpi";
 
@@ -1209,15 +1202,6 @@
 }
 #endif
 
-#if defined(OS_WIN)
-bool GDITextPrintingEnabled() {
-  const auto& command_line = *base::CommandLine::ForCurrentProcess();
-  if (command_line.HasSwitch(kDisableGDITextPrinting))
-    return false;
-  return command_line.HasSwitch(kEnableGDITextPrinting);
-}
-#endif
-
 // -----------------------------------------------------------------------------
 // DO NOT ADD YOUR VERY NICE FLAGS TO THE BOTTOM OF THIS FILE.
 //
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index c6312bb..14480fa5 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -325,10 +325,8 @@
 #endif  // defined(OS_MACOSX)
 
 #if defined(OS_WIN)
-extern const char kDisableGDITextPrinting[];
 extern const char kDisablePerMonitorDpi[];
 extern const char kEnableCloudPrintXps[];
-extern const char kEnableGDITextPrinting[];
 extern const char kEnablePerMonitorDpi[];
 extern const char kEnableProfileShortcutManager[];
 extern const char kHideIcons[];
diff --git a/chrome/common/extensions/api/automation.idl b/chrome/common/extensions/api/automation.idl
index f0fba7fd..ff4ecdbb 100644
--- a/chrome/common/extensions/api/automation.idl
+++ b/chrome/common/extensions/api/automation.idl
@@ -263,6 +263,16 @@
     nodeRemoved
   };
 
+  // Where the node's name is from.
+  enum NameFromType {
+    uninitialized,
+    attribute,
+    contents,
+    placeholder,
+    related_element,
+    value
+  };
+
   dictionary Rect {
     long left;
     long top;
@@ -358,108 +368,113 @@
   // A single node in an Automation tree.
   [nocompile, noinline_doc] dictionary AutomationNode {
     // The root node of the tree containing this AutomationNode.
-    AutomationNode root;
+    AutomationNode? root;
 
     // Whether this AutomationNode is a root node.
     boolean isRootNode;
 
     // The role of this node.
-    automation.RoleType role;
+    RoleType? role;
 
     // The $(ref:automation.StateType)s describing this node.
-    object state;
+    // <jsexterns>@type {Object<chrome.automation.StateType, boolean>}
+    // </jsexterns>
+    object? state;
 
     // The rendered location (as a bounding box) of this node in global
     // screen coordinates.
-    automation.Rect location;
+    Rect? location;
 
     // Computes the bounding box of a subrange of this node in global screen
     // coordinates. Returns the same as |location| if range information
     // is not available. The start and end indices are zero-based offsets
     // into the node's "name" string attribute.
-    static automation.Rect boundsForRange(long startIndex, long endIndex);
+    static Rect boundsForRange(long startIndex, long endIndex);
 
     // The purpose of the node, other than the role, if any.
-    DOMString description;
+    DOMString? description;
 
-    // The help text for the node, if any.
-    DOMString help;
+    // The placeholder for this text field, if any.
+    DOMString? placeholder;
 
     // The accessible name for this node, via the
     // <a href="http://www.w3.org/TR/wai-aria/roles#namecalculation">
     // Accessible Name Calculation</a> process.
-    DOMString name;
+    DOMString? name;
+
+    // The source of the name.
+    NameFromType? nameFrom;
 
     // The value for this node: for example the <code>value</code> attribute of
     // an <code>&lt;input&gt; element.
-    DOMString value;
+    DOMString? value;
 
     // The HTML tag for this element, if this node is an HTML element.
-    DOMString htmlTag;
+    DOMString? htmlTag;
 
     // The level of a heading or tree item.
-    long hierarchicalLevel;
+    long? hierarchicalLevel;
 
     // The start and end index of each word in an inline text box.
-    long[] wordStarts;
-    long[] wordEnds;
+    long[]? wordStarts;
+    long[]? wordEnds;
 
     // The nodes, if any, which this node is specified to control via
     // <a href="http://www.w3.org/TR/wai-aria/states_and_properties#aria-controls">
     // <code>aria-controls</code></a>.
-    AutomationNode[] controls;
+    AutomationNode[]? controls;
 
     // The nodes, if any, which form a description for this node.
-    AutomationNode[] describedBy;
+    AutomationNode[]? describedBy;
 
     // The nodes, if any, which may optionally be navigated to after this
     // one. See
     // <a href="http://www.w3.org/TR/wai-aria/states_and_properties#aria-flowto">
     // <code>aria-flowto</code></a>.
-    AutomationNode[] flowTo;
+    AutomationNode[]? flowTo;
 
     // The nodes, if any, which form a label for this element. Generally, the
     // text from these elements will also be exposed as the element's accessible
     // name, via the $(ref:automation.AutomationNode.name) attribute.
-    AutomationNode[] labelledBy;
+    AutomationNode[]? labelledBy;
 
     // The node referred to by <code>aria-activedescendant</code>, where
     // applicable
-    AutomationNode activedescendant;
+    AutomationNode? activeDescendant;
 
     //
     // Link attributes.
     //
 
     // The URL that this link will navigate to.
-    DOMString url;
+    DOMString? url;
 
     //
     // Document attributes.
     //
 
     // The URL of this document.
-    DOMString docUrl;
+    DOMString? docUrl;
 
     // The title of this document.
-    DOMString docTitle;
+    DOMString? docTitle;
 
     // Whether this document has finished loading.
-    boolean docLoaded;
+    boolean? docLoaded;
 
     // The proportion (out of 1.0) that this doc has completed loading.
-    double docLoadingProgress;
+    double? docLoadingProgress;
 
     //
     // Scrollable container attributes.
     //
 
-    long scrollX;
-    long scrollXMin;
-    long scrollXMax;
-    long scrollY;
-    long scrollYMin;
-    long scrollYMax;
+    long? scrollX;
+    long? scrollXMin;
+    long? scrollXMax;
+    long? scrollY;
+    long? scrollYMin;
+    long? scrollYMax;
 
     //
     // Editable text field attributes.
@@ -467,14 +482,27 @@
 
     // The character index of the start of the selection within this editable
     // text element; -1 if no selection.
-    long textSelStart;
+    long? textSelStart;
 
     // The character index of the end of the selection within this editable
     // text element; -1 if no selection.
-    long textSelEnd;
+    long? textSelEnd;
 
     // The input type, like email or number.
-    DOMString textInputType;
+    DOMString? textInputType;
+
+    // An array of indexes of the break between lines in editable text.
+    long[] lineBreaks;
+
+    // An array of indexes of the start position of each text marker.
+    long[] markerStarts;
+
+    // An array of indexes of the end position of each text marker.
+    long[] markerEnds;
+
+    // An array of numerical types indicating the type of each text marker,
+    // such as a spelling error.
+    long[] markerTypes;
 
     //
     // Tree selection attributes (available on root nodes only)
@@ -484,49 +512,69 @@
     AutomationNode? anchorObject;
     // The anchor offset of the tree selection, if any.
     long? anchorOffset;
+    // The affinity of the tree selection anchor, if any.
+    DOMString? anchorAffinity;
     // The focus node of the tree selection, if any.
     AutomationNode? focusObject;
     // The focus offset of the tree selection, if any.
     long? focusOffset;
+    // The affinity of the tree selection focus, if any.
+    DOMString? focusAffinity;
 
     //
     // Range attributes.
     //
 
     // The current value for this range.
-    double valueForRange;
+    double? valueForRange;
 
     // The minimum possible value for this range.
-    double minValueForRange;
+    double? minValueForRange;
 
     // The maximum possible value for this range.
-    double maxValueForRange;
+    double? maxValueForRange;
+
+    //
+    // List attributes.
+    //
+
+    // The 1-based index of an item in a set.
+    long? posInSet;
+
+    // The number of items in a set;
+    long? setSize;
 
     //
     // Table attributes.
     //
 
     // The number of rows in this table.
-    long tableRowCount;
+    long? tableRowCount;
 
     // The number of columns in this table.
-    long tableColumnCount;
+    long? tableColumnCount;
 
     //
     // Table cell attributes.
     //
 
     // The zero-based index of the column that this cell is in.
-    long tableCellColumnIndex;
+    long? tableCellColumnIndex;
 
     // The number of columns that this cell spans (default is 1).
-    long tableCellColumnSpan;
+    long? tableCellColumnSpan;
 
     // The zero-based index of the row that this cell is in.
-    long tableCellRowIndex;
+    long? tableCellRowIndex;
 
     // The number of rows that this cell spans (default is 1).
-    long tableCellRowSpan;
+    long? tableCellRowSpan;
+
+    // The corresponding column header for this cell.
+    AutomationNode? tableColumnHeader;
+
+    // The corresponding row header for this cell.
+    AutomationNode? tableRowHeader;
 
     //
     // Live region attributes.
@@ -534,39 +582,82 @@
 
     // The type of region if this is the root of a live region.
     // Possible values are 'polite' and 'assertive'.
-    DOMString liveStatus;
+    DOMString? liveStatus;
 
     // The value of aria-relevant for a live region.
-    DOMString liveRelevant;
+    DOMString? liveRelevant;
 
     // The value of aria-atomic for a live region.
-    boolean liveAtomic;
+    boolean? liveAtomic;
 
     // The value of aria-busy for a live region.
-    boolean liveBusy;
+    boolean? liveBusy;
 
     // The type of live region if this node is inside a live region.
-    DOMString containerLiveStatus;
+    DOMString? containerLiveStatus;
 
     // The value of aria-relevant if this node is inside a live region.
-    DOMString containerLiveRelevant;
+    DOMString? containerLiveRelevant;
 
     // The value of aria-atomic if this node is inside a live region.
-    boolean containerLiveAtomic;
+    boolean? containerLiveAtomic;
 
     // The value of aria-busy if this node is inside a live region.
-    boolean containerLiveBusy;
+    boolean? containerLiveBusy;
+
+    //
+    // Miscellaneous attributes.
+    //
+
+    // A map containing all HTML attributes and their values
+    // <jsexterns>@type {Object<string>}</jsexterns>
+    object? htmlAttributes;
+
+    // The input type of a text field, such as "text" or "email".
+    DOMString? inputType;
+
+    // The key that activates this widget.
+    DOMString? accessKey;
+
+    // The value of the aria-invalid attribute, indicating the error type.
+    DOMString? ariaInvalidValue;
+
+    // The value of the aria-readonly attribute, if applicable.
+    boolean? ariaReadonly;
+
+    // The CSS display attribute for this node, if applicable.
+    DOMString? display;
+
+    // A data url with the contents of this object's image or thumbnail.
+    DOMString? imageDataUrl;
+
+    // The language code for this subtree.
+    DOMString? language;
+
+    // If a checkbox or toggle button is in the mixed state.
+    boolean? buttonMixed;
+
+    // The RGBA foreground color of this subtree, as an integer.
+    long? color;
+
+    // The RGBA background color of this subtree, as an integer.
+    long? backgroundColor;
+
+    // The RGBA color of an input element whose value is a color.
+    long? colorValue;
 
     //
     // Walking the tree.
     //
 
     AutomationNode[] children;
-    AutomationNode parent;
-    AutomationNode firstChild;
-    AutomationNode lastChild;
-    AutomationNode previousSibling;
-    AutomationNode nextSibling;
+    AutomationNode? parent;
+    AutomationNode? firstChild;
+    AutomationNode? lastChild;
+    AutomationNode? previousSibling;
+    AutomationNode? nextSibling;
+    AutomationNode? nextOnLine;
+    AutomationNode? previousOnLine;
 
     // The index of this node in its parent node's list of children. If this is
     // the root node, this will be undefined.
@@ -600,6 +691,21 @@
     // time the user presses Tab or Shift+Tab.
     static void setSequentialFocusNavigationStartingPoint();
 
+    // Show the context menu for this element, as if the user right-clicked.
+    static void showContextMenu();
+
+    // Resume playing any media within this tree.
+    static void resumeMedia();
+
+    // Start ducking any media within this tree.
+    static void startDuckingMedia();
+
+    // Stop ducking any media within this tree.
+    static void stopDuckingMedia();
+
+    // Suspend any media playing within this tree.
+    static void suspendMedia();
+
     // Adds a listener for the given event type and event phase.
     static void addEventListener(
         EventType eventType, AutomationListener listener, boolean capture);
diff --git a/chrome/renderer/extensions/automation_internal_custom_bindings.cc b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
index 1cc6e97f..e56944b1 100644
--- a/chrome/renderer/extensions/automation_internal_custom_bindings.cc
+++ b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
@@ -31,20 +31,6 @@
 
 namespace {
 
-// Helper to convert an enum to a V8 object.
-template <typename EnumType>
-v8::Local<v8::Object> ToEnumObject(v8::Isolate* isolate,
-                                   EnumType start_after,
-                                   EnumType end_at) {
-  v8::Local<v8::Object> object = v8::Object::New(isolate);
-  for (int i = start_after + 1; i <= end_at; ++i) {
-    v8::Local<v8::String> value = v8::String::NewFromUtf8(
-        isolate, ui::ToString(static_cast<EnumType>(i)).c_str());
-    object->Set(value, value);
-  }
-  return object;
-}
-
 void ThrowInvalidArgumentsException(
     AutomationInternalCustomBindings* automation_bindings) {
   v8::Isolate* isolate = automation_bindings->GetIsolate();
@@ -717,6 +703,14 @@
 
         result.Set(v8::String::NewFromUtf8(isolate, attr_value.c_str()));
       });
+  RouteNodeIDFunction(
+      "GetNameFrom", [](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
+                        TreeCache* cache, ui::AXNode* node) {
+        ui::AXNameFrom name_from = static_cast<ui::AXNameFrom>(
+            node->data().GetIntAttribute(ui::AX_ATTR_NAME_FROM));
+        std::string name_from_str = ui::ToString(name_from);
+        result.Set(v8::String::NewFromUtf8(isolate, name_from_str.c_str()));
+      });
 }
 
 AutomationInternalCustomBindings::~AutomationInternalCustomBindings() {}
@@ -781,22 +775,6 @@
     const v8::FunctionCallbackInfo<v8::Value>& args) {
   v8::Local<v8::Object> additions = v8::Object::New(GetIsolate());
 
-  additions->Set(
-      v8::String::NewFromUtf8(GetIsolate(), "EventType"),
-      ToEnumObject(GetIsolate(), ui::AX_EVENT_NONE, ui::AX_EVENT_LAST));
-
-  additions->Set(
-      v8::String::NewFromUtf8(GetIsolate(), "RoleType"),
-      ToEnumObject(GetIsolate(), ui::AX_ROLE_NONE, ui::AX_ROLE_LAST));
-
-  additions->Set(
-      v8::String::NewFromUtf8(GetIsolate(), "StateType"),
-      ToEnumObject(GetIsolate(), ui::AX_STATE_NONE, ui::AX_STATE_LAST));
-
-  additions->Set(
-      v8::String::NewFromUtf8(GetIsolate(), "TreeChangeType"),
-      ToEnumObject(GetIsolate(), ui::AX_MUTATION_NONE, ui::AX_MUTATION_LAST));
-
   v8::Local<v8::Object> name_from_type(v8::Object::New(GetIsolate()));
   for (int i = ui::AX_NAME_FROM_NONE; i <= ui::AX_NAME_FROM_LAST; ++i) {
     name_from_type->Set(
diff --git a/chrome/renderer/resources/extensions/automation/automation_node.js b/chrome/renderer/resources/extensions/automation/automation_node.js
index ae62b333..15b72257 100644
--- a/chrome/renderer/resources/extensions/automation/automation_node.js
+++ b/chrome/renderer/resources/extensions/automation/automation_node.js
@@ -224,9 +224,15 @@
  */
 var GetHtmlAttribute = requireNative('automationInternal').GetHtmlAttribute;
 
+/**
+ * @param {number} axTreeID The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {automation.NameFromType} The source of the node's name.
+ */
+var GetNameFrom = requireNative('automationInternal').GetNameFrom;
+
 var lastError = require('lastError');
 var logging = requireNative('logging');
-var schema = requireNative('automationInternal').GetSchemaAdditions();
 var utils = require('utils');
 
 /**
@@ -359,6 +365,10 @@
         GetChildIDAtIndex(parent.treeID, parent.id, indexInParent + 1));
   },
 
+  get nameFrom() {
+    return GetNameFrom(this.treeID, this.id);
+  },
+
   doDefault: function() {
     this.performAction_('doDefault');
   },
@@ -669,23 +679,17 @@
 
 var stringAttributes = [
     'accessKey',
-    'action',
     'ariaInvalidValue',
-    'autoComplete',
     'containerLiveRelevant',
     'containerLiveStatus',
     'description',
     'display',
-    'dropeffect',
-    'help',
-    'htmlTag',
     'imageDataUrl',
     'language',
     'liveRelevant',
     'liveStatus',
     'name',
     'placeholder',
-    'shortcut',
     'textInputType',
     'url',
     'value'];
@@ -693,24 +697,16 @@
 var boolAttributes = [
     'ariaReadonly',
     'buttonMixed',
-    'canSetValue',
-    'canvasHasFallback',
     'containerLiveAtomic',
     'containerLiveBusy',
-    'grabbed',
-    'isAxTreeHost',
     'liveAtomic',
-    'liveBusy',
-    'updateLocationOnly'];
+    'liveBusy'];
 
 var intAttributes = [
     'backgroundColor',
     'color',
     'colorValue',
-    'descriptionFrom',
     'hierarchicalLevel',
-    'invalidState',
-    'nameFrom',
     'posInSet',
     'scrollX',
     'scrollXMax',
@@ -719,7 +715,6 @@
     'scrollYMax',
     'scrollYMin',
     'setSize',
-    'sortDirection',
     'tableCellColumnIndex',
     'tableCellColumnSpan',
     'tableCellRowIndex',
@@ -728,10 +723,8 @@
     'tableColumnIndex',
     'tableRowCount',
     'tableRowIndex',
-    'textDirection',
     'textSelEnd',
-    'textSelStart',
-    'textStyle'];
+    'textSelStart'];
 
 var nodeRefAttributes = [
     ['activedescendantId', 'activeDescendant'],
@@ -739,11 +732,9 @@
     ['previousOnLineId', 'previousOnLine'],
     ['tableColumnHeaderId', 'tableColumnHeader'],
     ['tableHeaderId', 'tableHeader'],
-    ['tableRowHeaderId', 'tableRowHeader'],
-    ['titleUiElement', 'titleUIElement']];
+    ['tableRowHeaderId', 'tableRowHeader']];
 
 var intListAttributes = [
-    'characterOffsets',
     'lineBreaks',
     'markerEnds',
     'markerStarts',
@@ -752,18 +743,15 @@
     'wordStarts'];
 
 var nodeRefListAttributes = [
-    ['cellIds', 'cells'],
     ['controlsIds', 'controls'],
     ['describedbyIds', 'describedBy'],
     ['flowtoIds', 'flowTo'],
-    ['labelledbyIds', 'labelledBy'],
-    ['uniqueCellIds', 'uniqueCells']];
+    ['labelledbyIds', 'labelledBy']];
 
 var floatAttributes = [
     'valueForRange',
     'minValueForRange',
-    'maxValueForRange',
-    'fontSize'];
+    'maxValueForRange'];
 
 var htmlAttributes = [
     ['type', 'inputType']];
@@ -1036,7 +1024,7 @@
   },
 
   destroy: function() {
-    this.dispatchEvent(schema.EventType.destroyed, 'none');
+    this.dispatchEvent('destroyed', 'none');
     for (var id in this.axNodeDataCache_)
       this.remove(id);
     this.detach();
@@ -1120,6 +1108,7 @@
       'lineStartOffsets',
       'root',
       'htmlAttributes',
+      'nameFrom',
   ]),
 });
 
diff --git a/chrome/renderer/resources/extensions/automation_custom_bindings.js b/chrome/renderer/resources/extensions/automation_custom_bindings.js
index 92ef4cb..1038fd3 100644
--- a/chrome/renderer/resources/extensions/automation_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/automation_custom_bindings.js
@@ -16,7 +16,6 @@
 var logging = requireNative('logging');
 var nativeAutomationInternal = requireNative('automationInternal');
 var GetRoutingID = nativeAutomationInternal.GetRoutingID;
-var GetSchemaAdditions = nativeAutomationInternal.GetSchemaAdditions;
 var DestroyAccessibilityTree =
     nativeAutomationInternal.DestroyAccessibilityTree;
 var GetIntAttribute = nativeAutomationInternal.GetIntAttribute;
@@ -26,7 +25,6 @@
 var RemoveTreeChangeObserver =
     nativeAutomationInternal.RemoveTreeChangeObserver;
 var GetFocusNative = nativeAutomationInternal.GetFocus;
-var schema = GetSchemaAdditions();
 
 /**
  * A namespace to export utility functions to other files in automation.
@@ -220,7 +218,7 @@
     return;
 
   var subroot = AutomationRootNode.get(childTreeID);
-  if (!subroot || subroot.role == schema.EventType.unknown) {
+  if (!subroot || subroot.role == 'unknown') {
     automationUtil.storeTreeCallback(childTreeID, function(root) {
       // Return early if the root has already been attached.
       if (root.parent)
@@ -229,12 +227,10 @@
       privates(root).impl.setHostNode(node);
 
       if (root.docLoaded) {
-        privates(root).impl.dispatchEvent(
-            schema.EventType.loadComplete, 'page');
+        privates(root).impl.dispatchEvent('loadComplete', 'page');
       }
 
-      privates(node).impl.dispatchEvent(
-          schema.EventType.childrenChanged, 'none');
+      privates(node).impl.dispatchEvent('childrenChanged', 'none');
     });
 
     automationInternal.enableFrame(childTreeID);
@@ -287,17 +283,17 @@
   var targetTree = AutomationRootNode.getOrCreate(id);
 
   var isFocusEvent = false;
-  if (eventParams.eventType == schema.EventType.focus) {
+  if (eventParams.eventType == 'focus') {
     isFocusEvent = true;
-  } else if (eventParams.eventType == schema.EventType.blur) {
+  } else if (eventParams.eventType == 'blur') {
     // Work around an issue where Chrome sends us 'blur' events on the
     // root node when nothing has focus, we need to treat those as focus
     // events but otherwise not handle blur events specially.
     var node = privates(targetTree).impl.get(eventParams.targetID);
     if (node == node.root)
       automationUtil.updateFocusedNodeOnBlur();
-  } else if (eventParams.eventType == schema.EventType.mediaStartedPlaying ||
-      eventParams.eventType == schema.EventType.mediaStoppedPlaying) {
+  } else if (eventParams.eventType == 'mediaStartedPlaying' ||
+      eventParams.eventType == 'mediaStoppedPlaying') {
     // These events are global to the tree.
     eventParams.targetID = privates(targetTree).impl.id;
   }
@@ -365,11 +361,4 @@
 });
 
 var binding = automation.generate();
-// Add additional accessibility bindings not specified in the automation IDL.
-// Accessibility and automation share some APIs (see
-// ui/accessibility/ax_enums.idl).
-forEach(schema, function(k, v) {
-  binding[k] = v;
-});
-
 exports.$set('binding', binding);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 7179c43..e16115f 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2186,7 +2186,6 @@
         "../browser/chromeos/app_mode/arc/arc_kiosk_app_manager_browsertest.cc",
         "../browser/chromeos/app_mode/kiosk_app_manager_browsertest.cc",
         "../browser/chromeos/app_mode/kiosk_app_update_service_browsertest.cc",
-        "../browser/chromeos/app_mode/kiosk_crash_restore_browsertest.cc",
         "../browser/chromeos/arc/arc_session_manager_browsertest.cc",
         "../browser/chromeos/arc/auth/arc_robot_auth_code_fetcher_browsertest.cc",
         "../browser/chromeos/arc/intent_helper/arc_settings_service_browsertest.cc",
@@ -2231,6 +2230,7 @@
         "../browser/chromeos/first_run/goodies_displayer_browsertest.cc",
         "../browser/chromeos/input_method/input_method_engine_browsertests.cc",
         "../browser/chromeos/input_method/mode_indicator_browsertest.cc",
+        "../browser/chromeos/login/auto_launched_kiosk_browsertest.cc",
         "../browser/chromeos/login/bluetooth_host_pairing_browsertest.cc",
         "../browser/chromeos/login/crash_restore_browsertest.cc",
         "../browser/chromeos/login/demo_mode/demo_app_launcher_browsertest.cc",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationTestUtils.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationTestUtils.java
index e931a39..90e3634 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationTestUtils.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationTestUtils.java
@@ -22,6 +22,7 @@
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.omaha.OmahaClient;
+import org.chromium.chrome.browser.omaha.VersionNumberGetter;
 import org.chromium.content.browser.test.util.Criteria;
 import org.chromium.content.browser.test.util.CriteriaHelper;
 
@@ -56,7 +57,7 @@
 
         // Disable Omaha related activities.
         OmahaClient.setEnableCommunication(false);
-        OmahaClient.setEnableUpdateDetection(false);
+        VersionNumberGetter.setEnableUpdateDetection(false);
     }
 
     public static void tearDown(Context context) throws Exception {
diff --git a/chrome/test/data/extensions/api_test/active_tab/background.js b/chrome/test/data/extensions/api_test/active_tab/background.js
index dbf83fc..7ccd118 100644
--- a/chrome/test/data/extensions/api_test/active_tab/background.js
+++ b/chrome/test/data/extensions/api_test/active_tab/background.js
@@ -50,7 +50,7 @@
 
   chrome.automation.getTree(callbackPass(function(rootNode) {
     assertFalse(rootNode == undefined);
-    assertEq(RoleType.rootWebArea, rootNode.role);
+    assertEq(RoleType.ROOT_WEB_AREA, rootNode.role);
   }));
 });
 
diff --git a/chrome/test/data/extensions/api_test/automation/tests/desktop/actions.js b/chrome/test/data/extensions/api_test/automation/tests/desktop/actions.js
index 8b2f8df..3a928eba 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/desktop/actions.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/desktop/actions.js
@@ -9,7 +9,7 @@
           return node.role == 'textField';
         });
     assertTrue(!!firstTextField);
-    listenOnce(firstTextField, EventType.focus, function(e) {
+    listenOnce(firstTextField, EventType.FOCUS, function(e) {
       chrome.test.succeed();
     }, true);
     firstTextField.doDefault();
@@ -26,7 +26,7 @@
 
   function testContextMenu() {
     var addressBar = rootNode.find({role: 'textField'});
-    listenOnce(rootNode, EventType.menuStart, function(e) {
+    listenOnce(rootNode, EventType.MENU_START, function(e) {
       addressBar.showContextMenu();
       chrome.test.succeed();
     }, true);
diff --git a/chrome/test/data/extensions/api_test/automation/tests/desktop/desktop.js b/chrome/test/data/extensions/api_test/automation/tests/desktop/desktop.js
index f3ebf8d9..eeb0241 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/desktop/desktop.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/desktop/desktop.js
@@ -5,7 +5,7 @@
 var allTests = [
   function testGetDesktop() {
     chrome.automation.getDesktop(function(rootNode) {
-      assertEq(RoleType.desktop, rootNode.role);
+      assertEq(RoleType.DESKTOP, rootNode.role);
       assertEq(undefined, rootNode.firstChild);
       chrome.test.succeed();
     });
@@ -35,7 +35,7 @@
 
   function testAutomationNodeToString() {
     chrome.automation.getDesktop(function(rootNode) {
-      assertEq(RoleType.desktop, rootNode.role);
+      assertEq(RoleType.DESKTOP, rootNode.role);
       var prefix = 'tree id=0';
       assertEq(prefix, rootNode.toString().substring(0, prefix.length));
       chrome.test.succeed();
diff --git a/chrome/test/data/extensions/api_test/automation/tests/desktop/focus_views.js b/chrome/test/data/extensions/api_test/automation/tests/desktop/focus_views.js
index c6855d0..38f0f65 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/desktop/focus_views.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/desktop/focus_views.js
@@ -10,7 +10,7 @@
         });
 
     assertTrue(!!firstFocusableNode);
-    listenOnce(firstFocusableNode, EventType.focus, function(e) {
+    listenOnce(firstFocusableNode, EventType.FOCUS, function(e) {
       chrome.test.succeed();
     }, true);
     firstFocusableNode.focus();
diff --git a/chrome/test/data/extensions/api_test/automation/tests/desktop/load_tabs.js b/chrome/test/data/extensions/api_test/automation/tests/desktop/load_tabs.js
index 6986a7a..bd306e57 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/desktop/load_tabs.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/desktop/load_tabs.js
@@ -6,7 +6,7 @@
 
 function getAllWebViews() {
   function findAllWebViews(node, nodes) {
-    if (node.role == chrome.automation.RoleType.webView)
+    if (node.role == chrome.automation.RoleType.WEB_VIEW)
       nodes.push(node);
 
     var children = node.children;
@@ -29,9 +29,9 @@
       assertEq(webViews[1], subroot.parent);
       assertEq(subroot, subroot.parent.children[0]);
       var button = subroot.firstChild.firstChild;
-      assertEq(chrome.automation.RoleType.button, button.role);
+      assertEq(chrome.automation.RoleType.BUTTON, button.role);
       var input = subroot.firstChild.lastChild.previousSibling;
-      assertEq(chrome.automation.RoleType.textField, input.role);
+      assertEq(chrome.automation.RoleType.TEXT_FIELD, input.role);
       chrome.test.succeed();
     });
   },
@@ -40,7 +40,7 @@
     runWithDocument(html, function(subroot) {
       var button = null;
 
-      rootNode.addEventListener(chrome.automation.EventType.focus,
+      rootNode.addEventListener(chrome.automation.EventType.FOCUS,
           function(evt) {
             assertEq(button, evt.target);
             chrome.test.succeed();
diff --git a/chrome/test/data/extensions/api_test/automation/tests/generated/generated_trees.js b/chrome/test/data/extensions/api_test/automation/tests/generated/generated_trees.js
index 8b285f82..d66f9b1 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/generated/generated_trees.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/generated/generated_trees.js
@@ -8,16 +8,16 @@
 var deepEq = chrome.test.checkDeepEq;
 
 var EventType = chrome.automation.EventType;
-var assertEqualEvent = EventType.loadComplete;
-var assertNotEqualEvent = EventType.activedescendantchanged;
-var testCompleteEvent = EventType.blur;
+var assertEqualEvent = EventType.LOAD_COMPLETE;
+var assertNotEqualEvent = EventType.ACTIVEDESCENDANTCHANGED;
+var testCompleteEvent = EventType.BLUR;
 
 var allTests = [
   function testDeserializeGeneratedTrees() {
     var tree0, tree1;
     function onTree1Retrieved(rootNode) {
       tree1 = rootNode;
-      tree1.addEventListener(EventType.destroyed, function() {
+      tree1.addEventListener('destroyed', function() {
         chrome.automation.getTree(1, onTree1Retrieved);
       });
     }
@@ -34,7 +34,7 @@
         assertFalse(tree0.toString() == tree1.toString(),
                     'tree0 should not be equal to tree1');
       });
-      tree0.addEventListener(EventType.destroyed, function() {
+      tree0.addEventListener('destroyed', function() {
         chrome.automation.getTree(0, onTree0Retrieved);
       });
       tree0.addEventListener(testCompleteEvent, function() {
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/actions.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/actions.js
index e6be325..10e2e09 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/actions.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/actions.js
@@ -5,7 +5,7 @@
 var allTests = [
   function testSimpleAction() {
     var okButton = rootNode.firstChild.firstChild;
-    okButton.addEventListener(EventType.focus, function() {
+    okButton.addEventListener(EventType.FOCUS, function() {
       chrome.test.succeed();
     }, true);
     okButton.focus();
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.js
index 3f4e538c..5e464a5 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/attributes.js
@@ -249,7 +249,15 @@
     assertTrue(editable !== undefined);
     assertEq('text', editable.htmlAttributes.type);
     chrome.test.succeed();
-  }
+  },
+
+  function testNameFrom() {
+    var link = rootNode.find({ role: 'link' });
+    assertEq(chrome.automation.NameFromType.CONTENTS, link.nameFrom);
+    var textarea = rootNode.find({ attributes: { name: 'textarea' } });
+    assertEq(chrome.automation.NameFromType.ATTRIBUTE, textarea.nameFrom);
+    chrome.test.succeed();
+  },
 ];
 
 setUpAndRunTests(allTests, 'attributes.html');
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/bounds_for_range.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/bounds_for_range.js
index 73bfc35c..22bffb2 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/bounds_for_range.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/bounds_for_range.js
@@ -7,13 +7,13 @@
 var allTests = [
   function boundsForRange() {
     function getNthListItemInlineTextBox(index) {
-      var list = rootNode.find({role: RoleType.list});
+      var list = rootNode.find({role: RoleType.LIST});
       var listItem = list.children[index];
-      assertEq(RoleType.listItem, listItem.role);
+      assertEq(RoleType.LIST_ITEM, listItem.role);
       var staticText = listItem.children[1];
-      assertEq(RoleType.staticText, staticText.role);
+      assertEq(RoleType.STATIC_TEXT, staticText.role);
       var inlineTextBox = staticText.firstChild;
-      assertEq(RoleType.inlineTextBox, inlineTextBox.role);
+      assertEq(RoleType.INLINE_TEXT_BOX, inlineTextBox.role);
       return inlineTextBox;
     }
 
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/close_tab.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/close_tab.js
index 6190449..f63c00b 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/close_tab.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/close_tab.js
@@ -10,7 +10,7 @@
           var button = rootNode.find({role: 'button'});
           assertEq(rootNode, button.root);
 
-          rootNode.addEventListener(EventType.destroyed, function() {
+          rootNode.addEventListener('destroyed', function() {
             // Poll until the root node doesn't have a role anymore
             // indicating that it really did get cleaned up.
             function checkSuccess() {
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/document_selection.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/document_selection.js
index ef13f25..9bbb477 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/document_selection.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/document_selection.js
@@ -17,13 +17,13 @@
   },
 
   function selectOutsideTextField() {
-    var textNode = rootNode.find({role: RoleType.paragraph}).firstChild;
+    var textNode = rootNode.find({role: RoleType.PARAGRAPH}).firstChild;
     assertTrue(!!textNode);
     chrome.automation.setDocumentSelection({anchorObject: textNode,
                                             anchorOffset: 0,
                                             focusObject: textNode,
                                             focusOffset: 3});
-    listenOnce(rootNode, EventType.documentSelectionChanged, function(evt) {
+    listenOnce(rootNode, EventType.DOCUMENT_SELECTION_CHANGED, function(evt) {
       assertEq(textNode, rootNode.anchorObject);
       assertEq(0, rootNode.anchorOffset);
       assertEq(textNode, rootNode.focusObject);
@@ -33,11 +33,11 @@
   },
 
   function selectInTextField() {
-    var textField = rootNode.find({role: RoleType.textField});
+    var textField = rootNode.find({role: RoleType.TEXT_FIELD});
     assertTrue(!!textField);
     textField.focus();
-    listenOnce(textField, EventType.textSelectionChanged, function(evt) {
-      listenOnce(rootNode, EventType.documentSelectionChanged, function(evt) {
+    listenOnce(textField, EventType.TEXT_SELECTION_CHANGED, function(evt) {
+      listenOnce(rootNode, EventType.DOCUMENT_SELECTION_CHANGED, function(evt) {
         assertTrue(evt.target === rootNode);
         assertEq(textField, rootNode.anchorObject);
         assertEq(0, rootNode.anchorOffset);
@@ -47,7 +47,7 @@
                                                 anchorOffset: 1,
                                                 focusObject: textField,
                                                 focusOffset: 3});
-        listenOnce(rootNode, EventType.documentSelectionChanged,
+        listenOnce(rootNode, EventType.DOCUMENT_SELECTION_CHANGED,
                    function(evt) {
           assertEq(textField, rootNode.anchorObject);
           assertEq(1, rootNode.anchorOffset);
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/events.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/events.js
index 1d6a92e..a951d1d 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/events.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/events.js
@@ -6,10 +6,10 @@
   function testEventListenerTarget() {
     var cancelButton = rootNode.firstChild.children[2];
     assertEq('Cancel', cancelButton.name);
-    cancelButton.addEventListener(EventType.focus,
+    cancelButton.addEventListener(EventType.FOCUS,
                                   function onFocusTarget(event) {
       window.setTimeout(function() {
-        cancelButton.removeEventListener(EventType.focus, onFocusTarget);
+        cancelButton.removeEventListener(EventType.FOCUS, onFocusTarget);
         chrome.test.succeed();
       }, 0);
     });
@@ -19,17 +19,17 @@
     var cancelButton = rootNode.firstChild.children[2];
     assertEq('Cancel', cancelButton.name);
     var cancelButtonGotEvent = false;
-    cancelButton.addEventListener(EventType.focus,
+    cancelButton.addEventListener(EventType.FOCUS,
                                   function onFocusBubble(event) {
       cancelButtonGotEvent = true;
-      cancelButton.removeEventListener(EventType.focus, onFocusBubble);
+      cancelButton.removeEventListener(EventType.FOCUS, onFocusBubble);
     });
-    rootNode.addEventListener(EventType.focus,
+    rootNode.addEventListener(EventType.FOCUS,
                                function onFocusBubbleRoot(event) {
       assertEq('focus', event.type);
       assertEq(cancelButton, event.target);
       assertTrue(cancelButtonGotEvent);
-      rootNode.removeEventListener(EventType.focus, onFocusBubbleRoot);
+      rootNode.removeEventListener(EventType.FOCUS, onFocusBubbleRoot);
       chrome.test.succeed();
     });
     cancelButton.focus();
@@ -38,19 +38,19 @@
     var cancelButton = rootNode.firstChild.children[2];
     assertEq('Cancel', cancelButton.name);
     function onFocusStopPropRoot(event) {
-      rootNode.removeEventListener(EventType.focus, onFocusStopPropRoot);
+      rootNode.removeEventListener(EventType.FOCUS, onFocusStopPropRoot);
       chrome.test.fail("Focus event was propagated to root");
     };
-    cancelButton.addEventListener(EventType.focus,
+    cancelButton.addEventListener(EventType.FOCUS,
                                   function onFocusStopProp(event) {
-      cancelButton.removeEventListener(EventType.focus, onFocusStopProp);
+      cancelButton.removeEventListener(EventType.FOCUS, onFocusStopProp);
       event.stopPropagation();
       window.setTimeout((function() {
-        rootNode.removeEventListener(EventType.focus, onFocusStopPropRoot);
+        rootNode.removeEventListener(EventType.FOCUS, onFocusStopPropRoot);
         chrome.test.succeed();
       }).bind(this), 0);
     });
-    rootNode.addEventListener(EventType.focus, onFocusStopPropRoot);
+    rootNode.addEventListener(EventType.FOCUS, onFocusStopPropRoot);
     cancelButton.focus();
   },
   function testEventListenerCapture() {
@@ -59,18 +59,18 @@
     var cancelButtonGotEvent = false;
     function onFocusCapture(event) {
       cancelButtonGotEvent = true;
-      cancelButton.removeEventListener(EventType.focus, onFocusCapture);
+      cancelButton.removeEventListener(EventType.FOCUS, onFocusCapture);
       chrome.test.fail("Focus event was not captured by root");
     };
-    cancelButton.addEventListener(EventType.focus, onFocusCapture);
-    rootNode.addEventListener(EventType.focus,
+    cancelButton.addEventListener(EventType.FOCUS, onFocusCapture);
+    rootNode.addEventListener(EventType.FOCUS,
                                function onFocusCaptureRoot(event) {
       assertEq('focus', event.type);
       assertEq(cancelButton, event.target);
       assertFalse(cancelButtonGotEvent);
       event.stopPropagation();
-      rootNode.removeEventListener(EventType.focus, onFocusCaptureRoot);
-      rootNode.removeEventListener(EventType.focus, onFocusCapture);
+      rootNode.removeEventListener(EventType.FOCUS, onFocusCaptureRoot);
+      rootNode.removeEventListener(EventType.FOCUS, onFocusCapture);
       window.setTimeout(chrome.test.succeed.bind(this), 0);
     }, true);
     cancelButton.focus();
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/find.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/find.js
index 4f0d98e..39b1d2a8 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/find.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/find.js
@@ -14,37 +14,37 @@
 
 function initializeNodes(rootNode) {
   group = rootNode.firstChild;
-  assertEq(RoleType.group, group.role);
+  assertEq(RoleType.GROUP, group.role);
 
   h1 = group.firstChild;
-  assertEq(RoleType.heading, h1.role);
+  assertEq(RoleType.HEADING, h1.role);
   assertEq(1, h1.hierarchicalLevel);
 
   p1 = group.lastChild;
-  assertEq(RoleType.paragraph, p1.role);
+  assertEq(RoleType.PARAGRAPH, p1.role);
 
   link = p1.children[1];
-  assertEq(RoleType.link, link.role);
+  assertEq(RoleType.LINK, link.role);
 
   main = rootNode.children[1];
-  assertEq(RoleType.main, main.role);
+  assertEq(RoleType.MAIN, main.role);
 
   p2 = main.firstChild;
-  assertEq(RoleType.paragraph, p2.role);
+  assertEq(RoleType.PARAGRAPH, p2.role);
 
   p3 = main.lastChild;
-  assertEq(RoleType.paragraph, p3.role);
+  assertEq(RoleType.PARAGRAPH, p3.role);
 
   okButton = rootNode.children[2];
-  assertEq(RoleType.button, okButton.role);
+  assertEq(RoleType.BUTTON, okButton.role);
   assertEq('Ok', okButton.name);
-  assertTrue(StateType.disabled in okButton.state);
+  assertTrue(StateType.DISABLED in okButton.state);
   assertTrue(okButton.state.disabled);
 
   cancelButton = rootNode.children[3];
-  assertEq(RoleType.button, cancelButton.role);
+  assertEq(RoleType.BUTTON, cancelButton.role);
   assertEq('Cancel', cancelButton.name);
-  assertFalse(StateType.disabled in cancelButton.state);
+  assertFalse(StateType.DISABLED in cancelButton.state);
 }
 
 var allTests = [
@@ -52,26 +52,26 @@
     initializeNodes(rootNode);
 
     // Should find the only instance of this role.
-    assertEq(h1, rootNode.find({ role: RoleType.heading}));
-    assertEq([h1], rootNode.findAll({ role: RoleType.heading}));
+    assertEq(h1, rootNode.find({role: RoleType.HEADING}));
+    assertEq([h1], rootNode.findAll({role: RoleType.HEADING}));
 
     // find should find first instance only.
-    assertEq(okButton, rootNode.find({ role: RoleType.button }));
-    assertEq(p1, rootNode.find({ role: RoleType.paragraph }));
+    assertEq(okButton, rootNode.find({role: RoleType.BUTTON}));
+    assertEq(p1, rootNode.find({role: RoleType.PARAGRAPH}));
 
     // findAll should find all instances.
-    assertEq([okButton, cancelButton],
-             rootNode.findAll({ role: RoleType.button }));
-    assertEq([p1, p2, p3], rootNode.findAll({ role: RoleType.paragraph }));
+    assertEq(
+        [okButton, cancelButton], rootNode.findAll({role: RoleType.BUTTON}));
+    assertEq([p1, p2, p3], rootNode.findAll({role: RoleType.PARAGRAPH}));
 
     // No instances: find should return null; findAll should return empty array.
-    assertEq(null, rootNode.find({ role: RoleType.checkbox }));
-    assertEq([], rootNode.findAll({ role: RoleType.checkbox }));
+    assertEq(null, rootNode.find({role: RoleType.CHECKBOX}));
+    assertEq([], rootNode.findAll({role: RoleType.CHECKBOX}));
 
     // Calling from node should search only its subtree.
-    assertEq(p1, group.find({ role: RoleType.paragraph }));
-    assertEq(p2, main.find({ role: RoleType.paragraph }));
-    assertEq([p2, p3], main.findAll({ role: RoleType.paragraph }));
+    assertEq(p1, group.find({role: RoleType.PARAGRAPH}));
+    assertEq(p2, main.find({role: RoleType.PARAGRAPH}));
+    assertEq([p2, p3], main.findAll({role: RoleType.PARAGRAPH}));
 
     chrome.test.succeed();
   },
@@ -80,21 +80,25 @@
     initializeNodes(rootNode);
 
     // Find all focusable elements (disabled button is not focusable).
-    assertEq(link, rootNode.find({ state: { focusable: true }}));
-    assertEq([link, cancelButton],
-             rootNode.findAll({ state: { focusable: true }}));
+    assertEq(link, rootNode.find({state: {focusable: true}}));
+    assertEq(
+        [link, cancelButton], rootNode.findAll({state: {focusable: true}}));
 
     // Find disabled buttons.
-    assertEq(okButton, rootNode.find({ role: RoleType.button,
-                                       state: { disabled: true }}));
-    assertEq([okButton], rootNode.findAll({ role: RoleType.button,
-                                            state: { disabled: true }}));
+    assertEq(
+        okButton,
+        rootNode.find({role: RoleType.BUTTON, state: {disabled: true}}));
+    assertEq(
+        [okButton],
+        rootNode.findAll({role: RoleType.BUTTON, state: {disabled: true}}));
 
     // Find enabled buttons.
-    assertEq(cancelButton, rootNode.find({ role: RoleType.button,
-                                           state: { disabled: false }}));
-    assertEq([cancelButton], rootNode.findAll({ role: RoleType.button,
-                                                state: { disabled: false }}));
+    assertEq(
+        cancelButton,
+        rootNode.find({role: RoleType.BUTTON, state: {disabled: false}}));
+    assertEq(
+        [cancelButton],
+        rootNode.findAll({role: RoleType.BUTTON, state: {disabled: false}}));
     chrome.test.succeed();
   },
 
@@ -102,59 +106,71 @@
     initializeNodes(rootNode);
 
     // Find by name attribute.
-    assertEq(okButton, rootNode.find({ attributes: { name: 'Ok' }}));
-    assertEq(cancelButton, rootNode.find({ attributes: { name: 'Cancel' }}));
+    assertEq(okButton, rootNode.find({attributes: {name: 'Ok'}}));
+    assertEq(cancelButton, rootNode.find({attributes: {name: 'Cancel'}}));
 
     // String attributes must be exact match unless a regex is used.
-    assertEq(null, rootNode.find({ attributes: { name: 'Canc' }}));
-    assertEq(null, rootNode.find({ attributes: { name: 'ok' }}));
+    assertEq(null, rootNode.find({attributes: {name: 'Canc'}}));
+    assertEq(null, rootNode.find({attributes: {name: 'ok'}}));
 
     // Find by value attribute - regexp.
-    var query = { attributes: { name: /relationship/ }};
+    var query = {attributes: {name: /relationship/}};
     assertEq(p2, rootNode.find(query).parent);
 
     // Find by role and hierarchicalLevel attribute.
-    assertEq(h1, rootNode.find({ role: RoleType.heading,
-                                 attributes: { hierarchicalLevel: 1 }}));
-    assertEq([], rootNode.findAll({ role: RoleType.heading,
-                                    attributes: { hierarchicalLevel: 2 }}));
+    assertEq(
+        h1, rootNode.find(
+                {role: RoleType.HEADING, attributes: {hierarchicalLevel: 1}}));
+    assertEq(
+        [], rootNode.findAll(
+                {role: RoleType.HEADING, attributes: {hierarchicalLevel: 2}}));
 
     // Searching for an attribute which no element has fails.
-    assertEq(null, rootNode.find({ attributes: { charisma: 12 } }));
+    assertEq(null, rootNode.find({attributes: {charisma: 12}}));
 
     // Searching for an attribute value of the wrong type fails (even if the
     // value is equivalent).
-    assertEq(null, rootNode.find({ role: RoleType.heading,
-                                   attributes: { hierarchicalLevel: true }} ));
+    assertEq(
+        null,
+        rootNode.find(
+            {role: RoleType.HEADING, attributes: {hierarchicalLevel: true}}));
 
     chrome.test.succeed();
   },
 
   function testMatches() {
     initializeNodes(rootNode);
-    assertTrue(h1.matches({ role: RoleType.heading }),
-               'h1 should match RoleType.heading');
-    assertTrue(h1.matches({ role: RoleType.heading,
-                            attributes: { hierarchicalLevel: 1 } }),
-               'h1 should match RoleType.heading and hierarchicalLevel: 1');
-    assertFalse(h1.matches({ role: RoleType.heading,
-                             state: { focusable: true },
-                             attributes: { hierarchicalLevel: 1 } }),
-               'h1 should not match focusable: true');
-    assertTrue(h1.matches({ role: RoleType.heading,
-                            state: { focusable: false },
-                            attributes: { hierarchicalLevel: 1 } }),
-               'h1 should match focusable: false');
+    assertTrue(
+        h1.matches({role: RoleType.HEADING}),
+        'h1 should match RoleType.HEADING');
+    assertTrue(
+        h1.matches(
+            {role: RoleType.HEADING, attributes: {hierarchicalLevel: 1}}),
+        'h1 should match RoleType.HEADING and hierarchicalLevel: 1');
+    assertFalse(
+        h1.matches({
+          role: RoleType.HEADING,
+          state: {focusable: true},
+          attributes: {hierarchicalLevel: 1}
+        }),
+        'h1 should not match focusable: true');
+    assertTrue(
+        h1.matches({
+          role: RoleType.HEADING,
+          state: {focusable: false},
+          attributes: {hierarchicalLevel: 1}
+        }),
+        'h1 should match focusable: false');
 
     var p2StaticText = p2.firstChild;
     assertTrue(
-        p2StaticText.matches({ role: RoleType.staticText,
-                               attributes: { name: /relationship/ } }),
+        p2StaticText.matches(
+            {role: RoleType.STATIC_TEXT, attributes: {name: /relationship/}}),
         'p2StaticText should match name: /relationship/ (regex match)');
     assertFalse(
-        p2StaticText.matches({ role: RoleType.staticText,
-                               attributes: { name: 'relationship' } }),
-      'p2 should not match name: \'relationship');
+        p2StaticText.matches(
+            {role: RoleType.STATIC_TEXT, attributes: {name: 'relationship'}}),
+        'p2 should not match name: \'relationship');
 
     chrome.test.succeed();
   }
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/image_data.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/image_data.js
index d98296e..cdc94c2 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/image_data.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/image_data.js
@@ -4,8 +4,8 @@
 
 var allTests = [
   function testGetImageData() {
-    var image = rootNode.find({ role: RoleType.image });
-    image.addEventListener(EventType.imageFrameUpdated, function() {
+    var image = rootNode.find({ role: RoleType.IMAGE });
+    image.addEventListener(EventType.IMAGE_FRAME_UPDATED, function() {
       assertEq(image.imageDataUrl.substr(0, 22), 'data:image/png;base64,');
       var imgElement = document.createElement('img');
       imgElement.src = image.imageDataUrl;
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/line_start_offsets.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/line_start_offsets.js
index cf82c2b..8fe7b7f 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/line_start_offsets.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/line_start_offsets.js
@@ -4,7 +4,7 @@
 
 var allTests = [
   function testInput() {
-    var textFields = rootNode.findAll({ role: RoleType.textField });
+    var textFields = rootNode.findAll({ role: RoleType.TEXT_FIELD });
     assertEq(2, textFields.length);
     var input = textFields[0];
     assertTrue(!!input);
@@ -15,7 +15,7 @@
   },
 
   function testTextarea() {
-    var textFields = rootNode.findAll({ role: RoleType.textField });
+    var textFields = rootNode.findAll({ role: RoleType.TEXT_FIELD });
     assertEq(2, textFields.length);
     var textarea = textFields[1];
     assertTrue(!!textarea);
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/location.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/location.js
index a4e48a5..628d2eef 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/location.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/location.js
@@ -5,7 +5,7 @@
 var allTests = [
   function testLocation() {
     function assertOkButtonLocation(event) {
-      var okButton = rootNode.find({ role: RoleType.button,
+      var okButton = rootNode.find({ role: RoleType.BUTTON,
                                      attributes: { name: 'Ok' }});
       assertTrue('location' in okButton);
 
@@ -25,7 +25,7 @@
     assertTrue('width' in okButton.location, 'no width in location');
 
     rootNode.addEventListener(
-        EventType.childrenChanged, assertOkButtonLocation);
+        EventType.CHILDREN_CHANGED, assertOkButtonLocation);
     chrome.tabs.executeScript({ 'code':
           'document.querySelector("button")' +
           '.setAttribute("style", "position: absolute; left: 100; top: 200; ' +
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/location2.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/location2.js
index 3ccd4e51..3812d398 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/location2.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/location2.js
@@ -7,7 +7,7 @@
   // automation API was returning the wrong location for an element.
   // See ../../sites/location2.html for the layout.
   function testLocation2() {
-    var textbox = rootNode.find({ role: RoleType.textField });
+    var textbox = rootNode.find({ role: RoleType.TEXT_FIELD });
     assertEq(0, textbox.location.left - rootNode.location.left);
     assertEq(100, textbox.location.top - rootNode.location.top);
     chrome.test.succeed();
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/sanity_check.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/sanity_check.js
index 3ff91b0..e3e9861 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/sanity_check.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/sanity_check.js
@@ -6,9 +6,9 @@
 // accessibility), since they can be inconsistent depending on the environment.
 var RemoveUntestedStates = function(state) {
   var result = JSON.parse(JSON.stringify(state));
-  delete result[StateType.horizontal];
-  delete result[StateType.hovered];
-  delete result[StateType.vertical];
+  delete result[StateType.HORIZONTAL];
+  delete result[StateType.HOVERED];
+  delete result[StateType.VERTICAL];
   return result;
 };
 
@@ -23,10 +23,9 @@
         state);
 
     var children = rootNode.children;
-    assertEq(RoleType.rootWebArea, rootNode.role);
+    assertEq(RoleType.ROOT_WEB_AREA, rootNode.role);
     assertEq(1, children.length);
     var body = children[0];
-    assertEq('body', body.htmlTag);
     state = RemoveUntestedStates(body.state);
     assertEq({readOnly: true}, state);
 
diff --git a/chrome/test/data/extensions/platform_apps/web_view/focus_visibility/guest.js b/chrome/test/data/extensions/platform_apps/web_view/focus_visibility/guest.js
index aff7298..1334066 100644
--- a/chrome/test/data/extensions/platform_apps/web_view/focus_visibility/guest.js
+++ b/chrome/test/data/extensions/platform_apps/web_view/focus_visibility/guest.js
@@ -1,27 +1,22 @@
 // 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.
+'use strict';
 
-var buttonWasFocused = false;
-
-
-function reset() {
-  buttonWasFocused = false;
-}
+var embedder = null;
 
 function onButtonReceivedFocus(e) {
-  buttonWasFocused = true;
+  embedder.postMessage('focus-event', '*');
 }
 
 function onWindowMessage(e) {
   switch (e.data) {
-    case 'verify':
-      e.source.postMessage(
-          buttonWasFocused ? 'was-focused' : 'was-not-focused', '*');
+    case 'connect':
+      embedder = e.source;
+      embedder.postMessage('connected', '*');
       break;
     case 'reset':
-      reset();
-      e.source.postMessage('', '*');
+      embedder.postMessage('reset-complete', '*');
       break;
   }
 }
@@ -32,3 +27,6 @@
 
 window.addEventListener('message', onWindowMessage);
 window.addEventListener('load', onLoad);
+window.addEventListener('keyup', function() {
+  embedder.postMessage('guest-keyup', '*');
+});
diff --git a/chrome/test/data/extensions/platform_apps/web_view/focus_visibility/window.html b/chrome/test/data/extensions/platform_apps/web_view/focus_visibility/window.html
index af60a61..4c7d229 100644
--- a/chrome/test/data/extensions/platform_apps/web_view/focus_visibility/window.html
+++ b/chrome/test/data/extensions/platform_apps/web_view/focus_visibility/window.html
@@ -6,9 +6,9 @@
 -->
 <html>
   <body>
-    <button> Embedder Button </button>
+    <button id="before"> Embedder Button </button>
     <div></div>
-    <button> Embedder Button </button>
+    <button id="after"> Embedder Button </button>
     <script src="window.js"></script>
   </body>
 </html>
diff --git a/chrome/test/data/extensions/platform_apps/web_view/focus_visibility/window.js b/chrome/test/data/extensions/platform_apps/web_view/focus_visibility/window.js
index 3fbeb49..485ecc82 100644
--- a/chrome/test/data/extensions/platform_apps/web_view/focus_visibility/window.js
+++ b/chrome/test/data/extensions/platform_apps/web_view/focus_visibility/window.js
@@ -1,6 +1,41 @@
 // 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.
+//
+'use strict';
+
+var isOopif = false;
+var webViewButtonFocused = false;
+
+var webviewReplyCallback = null;
+
+function listenForForKeyupAndButtonFocus() {
+  window.addEventListener('keyup', function() {
+    // With BrowserPlugin <webview> this renderer might see all events targetted
+    // to the <webview> before ultimately forwarding to the <webview>. Suppress
+    // the events once the webview with active.
+    // OTOH in OOPIF-<webiew>, keyup is only ever dispatched to one renderer.
+    // (The keyup that changes focus to the <webview> is sent to the embedder
+    // but activeElement is the <webview> by that point.)
+    if (isOopif || document.activeElement !== getWebView()) {
+      chrome.test.sendMessage('WebViewInteractiveTest.KeyUp');
+    }
+  });
+  window.addEventListener('message', function(e) {
+    if (e.data === 'guest-keyup') {
+      chrome.test.sendMessage('WebViewInteractiveTest.KeyUp');
+    }
+    else if (e.data === 'focus-event') {
+      webViewButtonFocused = true;
+    } else {
+      if (webviewReplyCallback) {
+        var temp = webviewReplyCallback;
+        webviewReplyCallback = null;
+        temp(e.data);
+      }
+    }
+  });
+}
 
 function setUpWebView(embedder) {
   var webview = document.createElement('webview');
@@ -9,7 +44,15 @@
     var url = 'http://localhost:' + config.testServer.port
         + '/extensions/platform_apps/web_view/focus_visibility/guest.html';
     webview.onloadstop = function() {
-      chrome.test.sendMessage('WebViewInteractiveTest.WebViewInitialized');
+      function callback(e) {
+        if (e.data === 'connected') {
+          e.stopImmediatePropagation();
+          window.removeEventListener('message', callback);
+          chrome.test.sendMessage('WebViewInteractiveTest.WebViewInitialized');
+        }
+      };
+      window.addEventListener('message', callback);
+      getWebView().contentWindow.postMessage('connect', '*');
     };
     webview.src = url;
     console.log('Setting URL to "' + url + '".');
@@ -18,17 +61,12 @@
 
 function reset() {
   getWebView().style.visibility = 'visible';
-  document.querySelector('button').focus();
-  webViewButtonReceivedFocus = false;
+  document.querySelector('#before').focus();
 }
 
 function sendMessageToWebViewAndReceiveReply(message, replyCallback) {
-  function callback(e) {
-    window.removeEventListener('message', callback);
-    replyCallback(e.data);
-  }
   if (replyCallback) {
-    window.addEventListener('message', callback);
+    webviewReplyCallback = replyCallback;
   }
   getWebView().contentWindow.postMessage(message, '*');
 }
@@ -39,21 +77,27 @@
 
 window.onAppMessage = function(command) {
   switch (command) {
+    case 'init-oopif':
+      isOopif = true;
+      // fallthrough
     case 'init':
+      listenForForKeyupAndButtonFocus();
+      document.querySelector('#before').focus();
       setUpWebView(document.querySelector('div'));
       break;
     case 'reset':
       reset();
-      sendMessageToWebViewAndReceiveReply("reset", function() {
-        chrome.test.sendMessage('WebViewInteractiveTest.DidReset');
+      sendMessageToWebViewAndReceiveReply("reset", function(reply) {
+        if (reply === 'reset-complete') {
+          webViewButtonFocused = false;
+          chrome.test.sendMessage('WebViewInteractiveTest.DidReset');
+        }
       });
       break;
     case 'verify':
-      sendMessageToWebViewAndReceiveReply('verify', function(result) {
-        chrome.test.sendMessage(result === 'was-focused' ?
-            'WebViewInteractiveTest.WebViewButtonWasFocused' :
-            'WebViewInteractiveTest.WebViewButtonWasNotFocused');
-      });
+      chrome.test.sendMessage(webViewButtonFocused ?
+          'WebViewInteractiveTest.WebViewButtonWasFocused' :
+          'WebViewInteractiveTest.WebViewButtonWasNotFocused');
       break;
     case 'hide-webview':
       getWebView().style.visibility = 'hidden';
diff --git a/chrome/test/data/webui/settings/settings_menu_test.js b/chrome/test/data/webui/settings/settings_menu_test.js
index 6381eb5..ed9d2a03 100644
--- a/chrome/test/data/webui/settings/settings_menu_test.js
+++ b/chrome/test/data/webui/settings/settings_menu_test.js
@@ -22,32 +22,32 @@
         assertFalse(settingsMenu.advancedOpened);
         settingsMenu.advancedOpened = true;
         Polymer.dom.flush();
-        assertTrue(settingsMenu.$.advancedPage.opened);
+        assertTrue(settingsMenu.$.advancedSubmenu.opened);
 
         settingsMenu.advancedOpened = false;
         Polymer.dom.flush();
-        assertFalse(settingsMenu.$.advancedPage.opened);
+        assertFalse(settingsMenu.$.advancedSubmenu.opened);
       });
 
       test('tapAdvanced', function() {
         assertFalse(settingsMenu.advancedOpened);
 
-        var advancedTrigger = settingsMenu.$$('#advancedPage .menu-trigger');
+        var advancedTrigger = settingsMenu.$$('#advancedSubmenu .menu-trigger');
         assertTrue(!!advancedTrigger);
 
         MockInteractions.tap(advancedTrigger);
         Polymer.dom.flush();
-        assertTrue(settingsMenu.$.advancedPage.opened);
+        assertTrue(settingsMenu.$.advancedSubmenu.opened);
 
         MockInteractions.tap(advancedTrigger);
         Polymer.dom.flush();
-        assertFalse(settingsMenu.$.advancedPage.opened);
+        assertFalse(settingsMenu.$.advancedSubmenu.opened);
       });
 
       test('upAndDownIcons', function() {
         // There should be different icons for a top level menu being open
         // vs. being closed. E.g. arrow-drop-up and arrow-drop-down.
-        var ironIconElement = settingsMenu.$.advancedPage.querySelector(
+        var ironIconElement = settingsMenu.$.advancedSubmenu.querySelector(
             '.menu-trigger iron-icon');
         assertTrue(!!ironIconElement);
 
@@ -63,9 +63,9 @@
 
       test('openResetSection', function() {
         settingsMenu.currentRoute = settings.Route.RESET;
-        var advancedPage = settingsMenu.$.advancedPage;
+        var advancedSubmenu = settingsMenu.$.advancedSubmenu;
         assertEquals('/reset',
-            advancedPage.querySelector('paper-menu').selected);
+            advancedSubmenu.querySelector('paper-menu').selected);
       });
 
       // Test that navigating via the paper menu always clears the current
diff --git a/chrome/test/data/webui/settings/settings_ui_browsertest.js b/chrome/test/data/webui/settings/settings_ui_browsertest.js
index b86224b..bde9ac9 100644
--- a/chrome/test/data/webui/settings/settings_ui_browsertest.js
+++ b/chrome/test/data/webui/settings/settings_ui_browsertest.js
@@ -103,7 +103,7 @@
       assertTrue(!!menu);
       assertTrue(menu.advancedOpened);
 
-      MockInteractions.tap(menu.$$('#advancedPage .menu-trigger'));
+      MockInteractions.tap(menu.$$('#advancedSubmenu .menu-trigger'));
       Polymer.dom.flush();
 
       // Check that all values are updated in unison.
diff --git a/chromeos/chromeos_switches.cc b/chromeos/chromeos_switches.cc
index 9e04b6d..48caceec 100644
--- a/chromeos/chromeos_switches.cc
+++ b/chromeos/chromeos_switches.cc
@@ -55,6 +55,11 @@
 // mode. This can be enabled by this flag.
 const char kAllowRAInDevMode[] = "allow-ra-in-dev-mode";
 
+// Specifies whether an app launched in kiosk mode was auto launched with zero
+// delay. Used in order to properly restore auto-launched state during session
+// restore flow.
+const char kAppAutoLaunched[] = "app-auto-launched";
+
 // Path for app's OEM manifest file.
 const char kAppOemManifestFile[] = "app-mode-oem-manifest";
 
diff --git a/chromeos/chromeos_switches.h b/chromeos/chromeos_switches.h
index 90e0850..86d216a 100644
--- a/chromeos/chromeos_switches.h
+++ b/chromeos/chromeos_switches.h
@@ -27,6 +27,7 @@
 CHROMEOS_EXPORT extern const char kAllowDataRoamingByDefault[];
 CHROMEOS_EXPORT extern const char kAllowFailedPolicyFetchForTest[];
 CHROMEOS_EXPORT extern const char kAllowRAInDevMode[];
+CHROMEOS_EXPORT extern const char kAppAutoLaunched[];
 CHROMEOS_EXPORT extern const char kAppOemManifestFile[];
 CHROMEOS_EXPORT extern const char kArcAvailable[];
 CHROMEOS_EXPORT extern const char kArtifactsDir[];
diff --git a/components/autofill/core/browser/autofill_experiments.cc b/components/autofill/core/browser/autofill_experiments.cc
index 92dfff2..ccee8c5 100644
--- a/components/autofill/core/browser/autofill_experiments.cc
+++ b/components/autofill/core/browser/autofill_experiments.cc
@@ -26,13 +26,10 @@
 
 const base::Feature kAutofillCreditCardAssist{
     "AutofillCreditCardAssist", base::FEATURE_DISABLED_BY_DEFAULT};
-const base::Feature kAutofillCreditCardSigninPromo{
-    "AutofillCreditCardSigninPromo", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kAutofillScanCardholderName{
     "AutofillScanCardholderName", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kAutofillCreditCardPopupLayout{
     "AutofillCreditCardPopupLayout", base::FEATURE_DISABLED_BY_DEFAULT};
-const char kCreditCardSigninPromoImpressionLimitParamKey[] = "impression_limit";
 const char kAutofillCreditCardPopupBackgroundColorKey[] = "background_color";
 const char kAutofillCreditCardPopupDividerColorKey[] = "dropdown_divider_color";
 const char kAutofillCreditCardPopupValueBoldKey[] = "is_value_bold";
@@ -69,10 +66,6 @@
   return group_name == "Disabled";
 }
 
-bool IsAutofillCreditCardSigninPromoEnabled() {
-  return base::FeatureList::IsEnabled(kAutofillCreditCardSigninPromo);
-}
-
 bool IsAutofillCreditCardAssistEnabled() {
 #if !defined(OS_ANDROID) && !defined(OS_IOS)
   return false;
@@ -81,17 +74,6 @@
 #endif
 }
 
-int GetCreditCardSigninPromoImpressionLimit() {
-  int impression_limit;
-  std::string param_value = variations::GetVariationParamValueByFeature(
-      kAutofillCreditCardSigninPromo,
-      kCreditCardSigninPromoImpressionLimitParamKey);
-  if (!param_value.empty() && base::StringToInt(param_value, &impression_limit))
-    return impression_limit;
-
-  return 0;
-}
-
 bool IsAutofillCreditCardPopupLayoutExperimentEnabled() {
   return base::FeatureList::IsEnabled(kAutofillCreditCardPopupLayout);
 }
diff --git a/components/autofill/core/browser/autofill_experiments.h b/components/autofill/core/browser/autofill_experiments.h
index 828c1890..db086876 100644
--- a/components/autofill/core/browser/autofill_experiments.h
+++ b/components/autofill/core/browser/autofill_experiments.h
@@ -25,7 +25,6 @@
 struct Suggestion;
 
 extern const base::Feature kAutofillCreditCardAssist;
-extern const base::Feature kAutofillCreditCardSigninPromo;
 extern const base::Feature kAutofillScanCardholderName;
 extern const base::Feature kAutofillCreditCardPopupLayout;
 extern const char kCreditCardSigninPromoImpressionLimitParamKey[];
@@ -41,16 +40,9 @@
 // disables providing suggestions.
 bool IsInAutofillSuggestionsDisabledExperiment();
 
-// Returns whether the Autofill credit card signin promo should be shown.
-bool IsAutofillCreditCardSigninPromoEnabled();
-
 // Returns whether the Autofill credit card assist infobar should be shown.
 bool IsAutofillCreditCardAssistEnabled();
 
-// Returns the maximum number of impressions of the credit card signin promo, or
-// 0 if there are no limits.
-int GetCreditCardSigninPromoImpressionLimit();
-
 // Returns true if the user should be offered to locally store unmasked cards.
 // This controls whether the option is presented at all rather than the default
 // response of the option.
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index 2569c41..ffcb8133 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -79,6 +79,8 @@
 using base::StartsWith;
 using base::TimeTicks;
 
+const int kCreditCardSigninPromoImpressionLimit = 3;
+
 namespace {
 
 const size_t kMaxRecentFormSignaturesToRemember = 3;
@@ -319,9 +321,6 @@
 bool AutofillManager::ShouldShowCreditCardSigninPromo(
     const FormData& form,
     const FormFieldData& field) {
-  if (!IsAutofillCreditCardSigninPromoEnabled())
-    return false;
-
   // Check whether we are dealing with a credit card field and whether it's
   // appropriate to show the promo (e.g. the platform is supported).
   AutofillField* autofill_field = GetAutofillField(form, field);
@@ -329,12 +328,10 @@
       !client_->ShouldShowSigninPromo())
     return false;
 
-  // The last step is checking if we are under the limit of impressions (a limit
-  // of 0 means there is no limit);
-  int impression_limit = GetCreditCardSigninPromoImpressionLimit();
+  // The last step is checking if we are under the limit of impressions.
   int impression_count = client_->GetPrefs()->GetInteger(
       prefs::kAutofillCreditCardSigninPromoImpressionCount);
-  if (impression_limit == 0 || impression_count < impression_limit) {
+  if (impression_count < kCreditCardSigninPromoImpressionLimit) {
     // The promo will be shown. Increment the impression count.
     client_->GetPrefs()->SetInteger(
         prefs::kAutofillCreditCardSigninPromoImpressionCount,
diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h
index ce8608f..5fe91c8 100644
--- a/components/autofill/core/browser/autofill_manager.h
+++ b/components/autofill/core/browser/autofill_manager.h
@@ -67,6 +67,9 @@
 struct FormData;
 struct FormFieldData;
 
+// We show the credit card signin promo only a certain number of times.
+extern const int kCreditCardSigninPromoImpressionLimit;
+
 // Manages saving and restoring the user's personal information entered into web
 // forms. One per frame; owned by the AutofillDriver.
 class AutofillManager : public AutofillDownloadManager::Observer,
diff --git a/components/autofill/core/browser/autofill_manager_unittest.cc b/components/autofill/core/browser/autofill_manager_unittest.cc
index d59d33268..834e8a2 100644
--- a/components/autofill/core/browser/autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/autofill_manager_unittest.cc
@@ -817,32 +817,6 @@
     request_context_ = nullptr;
   }
 
-  void EnableCreditCardSigninPromoFeatureWithLimit(int impression_limit) {
-    const std::string kTrialName = "MyTrial";
-    const std::string kGroupName = "Group1";
-
-    scoped_refptr<base::FieldTrial> trial(
-        base::FieldTrialList::CreateFieldTrial(kTrialName, kGroupName));
-    std::map<std::string, std::string> params;
-    params[kCreditCardSigninPromoImpressionLimitParamKey] =
-        base::IntToString(impression_limit);
-    ASSERT_TRUE(
-        variations::AssociateVariationParams(kTrialName, kGroupName, params));
-
-    // Enable the feature.
-    std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
-    feature_list->RegisterFieldTrialOverride(
-        kAutofillCreditCardSigninPromo.name,
-        base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial.get());
-    scoped_feature_list_.InitWithFeatureList(std::move(feature_list));
-
-    // Double-checking our params made it.
-    std::map<std::string, std::string> actualParams;
-    EXPECT_TRUE(variations::GetVariationParamsByFeature(
-        kAutofillCreditCardSigninPromo, &actualParams));
-    EXPECT_EQ(params, actualParams);
-  }
-
   void GetAutofillSuggestions(int query_id,
                               const FormData& form,
                               const FormFieldData& field) {
@@ -1672,9 +1646,6 @@
 // are no credit card suggestions and the promo is active. See the tests in
 // AutofillExternalDelegateTest that test whether the promo is added.
 TEST_F(AutofillManagerTest, GetCreditCardSuggestions_OnlySigninPromo) {
-  // Enable the signin promo feature with no impression limit.
-  EnableCreditCardSigninPromoFeatureWithLimit(0);
-
   // Make sure there are no credit cards.
   personal_data_.ClearCreditCards();
 
@@ -5253,56 +5224,9 @@
 }
 
 // Test that ShouldShowCreditCardSigninPromo behaves as expected for a credit
-// card form, with no impression limit and the feature enabled.
-TEST_F(AutofillManagerTest,
-       ShouldShowCreditCardSigninPromo_CreditCardField_NoLimit) {
-  // Enable the feature with no impression limit.
-  EnableCreditCardSigninPromoFeatureWithLimit(0);
-
-  // Set up our form data.
-  FormData form;
-  CreateTestCreditCardFormData(&form, true, false);
-  std::vector<FormData> forms(1, form);
-  FormsSeen(forms);
-
-  FormFieldData field;
-  test::CreateTestFormField("Name on Card", "nameoncard", "", "text", &field);
-
-  // The result will depend on what ShouldShowSigninPromo(). We control its
-  // output and verify that both cases behave as expected.
-  EXPECT_CALL(autofill_client_, ShouldShowSigninPromo())
-      .WillOnce(testing::Return(true));
-  EXPECT_TRUE(autofill_manager_->ShouldShowCreditCardSigninPromo(form, field));
-
-  EXPECT_CALL(autofill_client_, ShouldShowSigninPromo())
-      .WillOnce(testing::Return(false));
-  EXPECT_FALSE(autofill_manager_->ShouldShowCreditCardSigninPromo(form, field));
-}
-
-// Test that ShouldShowCreditCardSigninPromo doesn't show for a credit card form
-// when the feature is off.
-TEST_F(AutofillManagerTest,
-       ShouldShowCreditCardSigninPromo_CreditCardField_FeatureOff) {
-  // Set up our form data.
-  FormData form;
-  CreateTestCreditCardFormData(&form, true, false);
-  std::vector<FormData> forms(1, form);
-  FormsSeen(forms);
-
-  FormFieldData field;
-  test::CreateTestFormField("Name on Card", "nameoncard", "", "text", &field);
-
-  // Returns false without calling ShouldShowSigninPromo().
-  EXPECT_CALL(autofill_client_, ShouldShowSigninPromo()).Times(0);
-  EXPECT_FALSE(autofill_manager_->ShouldShowCreditCardSigninPromo(form, field));
-}
-
-// Test that ShouldShowCreditCardSigninPromo behaves as expected for a credit
-// card form with an impression limit and no impressions yet.
+// card form with an impression limit of three and no impressions yet.
 TEST_F(AutofillManagerTest,
        ShouldShowCreditCardSigninPromo_CreditCardField_UnmetLimit) {
-  // Enable the feature with an impression limit.
-  EnableCreditCardSigninPromoFeatureWithLimit(10);
   // No impressions yet.
   ASSERT_EQ(0, autofill_client_.GetPrefs()->GetInteger(
                    prefs::kAutofillCreditCardSigninPromoImpressionCount));
@@ -5339,9 +5263,6 @@
 // card form with an impression limit that has been attained already.
 TEST_F(AutofillManagerTest,
        ShouldShowCreditCardSigninPromo_CreditCardField_WithAttainedLimit) {
-  // Enable the feature with an impression limit.
-  EnableCreditCardSigninPromoFeatureWithLimit(10);
-
   // Set up our form data.
   FormData form;
   CreateTestCreditCardFormData(&form, true, false);
@@ -5353,7 +5274,8 @@
 
   // Set the impression count to the same value as the limit.
   autofill_client_.GetPrefs()->SetInteger(
-      prefs::kAutofillCreditCardSigninPromoImpressionCount, 10);
+      prefs::kAutofillCreditCardSigninPromoImpressionCount,
+      kCreditCardSigninPromoImpressionLimit);
 
   // Both calls will now return false, regardless of the mock implementation of
   // ShouldShowSigninPromo().
@@ -5366,8 +5288,9 @@
   EXPECT_FALSE(autofill_manager_->ShouldShowCreditCardSigninPromo(form, field));
 
   // Number of impressions stay the same.
-  EXPECT_EQ(10, autofill_client_.GetPrefs()->GetInteger(
-                    prefs::kAutofillCreditCardSigninPromoImpressionCount));
+  EXPECT_EQ(kCreditCardSigninPromoImpressionLimit,
+            autofill_client_.GetPrefs()->GetInteger(
+                prefs::kAutofillCreditCardSigninPromoImpressionCount));
 }
 
 // Test that ShouldShowCreditCardSigninPromo behaves as expected for an address
diff --git a/components/browser_sync/profile_sync_service.cc b/components/browser_sync/profile_sync_service.cc
index d95fccd..b17acf44 100644
--- a/components/browser_sync/profile_sync_service.cc
+++ b/components/browser_sync/profile_sync_service.cc
@@ -1448,6 +1448,7 @@
 void ProfileSyncService::OnConfigureStart() {
   DCHECK(thread_checker_.CalledOnValidThread());
   sync_configure_start_time_ = base::Time::Now();
+  engine_->StartConfiguration();
   NotifyObservers();
 }
 
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client.cc
index 8971799..7b0d644 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client.cc
@@ -4,6 +4,8 @@
 
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client.h"
 
+#include <stdint.h>
+
 #include "base/metrics/histogram_macros.h"
 #include "base/optional.h"
 #include "base/rand_util.h"
@@ -88,6 +90,8 @@
   request->set_effective_connection_type(
       protobuf_parser::ProtoEffectiveConnectionTypeFromEffectiveConnectionType(
           request_data.effective_connection_type()));
+  request->set_compressed_page_size_bytes(timing.network_bytes);
+  request->set_original_page_size_bytes(timing.original_network_bytes);
 }
 
 // Adds |current_time| as the metrics sent time to |request_data|, and returns
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client_unittest.cc
index 4c6f3395..70d0992 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_pingback_client.h"
 
+#include <stdint.h>
+
 #include <memory>
 #include <string>
 
@@ -37,6 +39,8 @@
     "DataReductionProxy.Pingback.Attempted";
 static const char kSessionKey[] = "fake-session";
 static const char kFakeURL[] = "http://www.google.com/";
+static const int64_t kBytes = 10000;
+static const int64_t kBytesOriginal = 1000000;
 
 }  // namespace
 
@@ -98,7 +102,9 @@
             base::Optional<base::TimeDelta>(base::TimeDelta::FromMilliseconds(
                 100)) /* parse_blocked_on_script_load_duration */,
             base::Optional<base::TimeDelta>(
-                base::TimeDelta::FromMilliseconds(2000)) /* parse_stop */) {}
+                base::TimeDelta::FromMilliseconds(2000)) /* parse_stop */,
+            kBytes /* network_bytes */,
+            kBytesOriginal /* original_network_bytes */) {}
 
   TestDataReductionProxyPingbackClient* pingback_client() const {
     return pingback_client_.get();
@@ -181,6 +187,9 @@
 
   EXPECT_EQ(kSessionKey, pageload_metrics.session_key());
   EXPECT_EQ(kFakeURL, pageload_metrics.first_request_url());
+  EXPECT_EQ(kBytes, pageload_metrics.compressed_page_size_bytes());
+  EXPECT_EQ(kBytesOriginal, pageload_metrics.original_page_size_bytes());
+
   EXPECT_EQ(
       PageloadMetrics_EffectiveConnectionType_EFFECTIVE_CONNECTION_TYPE_OFFLINE,
       pageload_metrics.effective_connection_type());
@@ -250,6 +259,8 @@
 
     EXPECT_EQ(kSessionKey, pageload_metrics.session_key());
     EXPECT_EQ(kFakeURL, pageload_metrics.first_request_url());
+    EXPECT_EQ(kBytes, pageload_metrics.compressed_page_size_bytes());
+    EXPECT_EQ(kBytesOriginal, pageload_metrics.original_page_size_bytes());
     EXPECT_EQ(
         PageloadMetrics_EffectiveConnectionType_EFFECTIVE_CONNECTION_TYPE_OFFLINE,
         pageload_metrics.effective_connection_type());
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.cc b/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.cc
index a5ed2e4..22a9b10 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.cc
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.cc
@@ -15,7 +15,9 @@
     const base::Optional<base::TimeDelta>& experimental_first_meaningful_paint,
     const base::Optional<base::TimeDelta>&
         parse_blocked_on_script_load_duration,
-    const base::Optional<base::TimeDelta>& parse_stop)
+    const base::Optional<base::TimeDelta>& parse_stop,
+    int64_t network_bytes,
+    int64_t original_network_bytes)
     : navigation_start(navigation_start),
       response_start(response_start),
       load_event_start(load_event_start),
@@ -24,7 +26,9 @@
       experimental_first_meaningful_paint(experimental_first_meaningful_paint),
       parse_blocked_on_script_load_duration(
           parse_blocked_on_script_load_duration),
-      parse_stop(parse_stop) {}
+      parse_stop(parse_stop),
+      network_bytes(network_bytes),
+      original_network_bytes(original_network_bytes) {}
 
 DataReductionProxyPageLoadTiming::DataReductionProxyPageLoadTiming(
     const DataReductionProxyPageLoadTiming& other) = default;
diff --git a/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h b/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h
index e9b0234..a1224225 100644
--- a/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h
+++ b/components/data_reduction_proxy/core/common/data_reduction_proxy_page_load_timing.h
@@ -5,6 +5,8 @@
 #ifndef COMPONENTS_DATA_REDUCTION_PROXY_CORE_COMMON_DATA_REDUCTION_PROXY_PAGE_LOAD_TIMING_H
 #define COMPONENTS_DATA_REDUCTION_PROXY_CORE_COMMON_DATA_REDUCTION_PROXY_PAGE_LOAD_TIMING_H
 
+#include <stdint.h>
+
 #include "base/optional.h"
 #include "base/time/time.h"
 
@@ -22,7 +24,9 @@
           experimental_first_meaningful_paint,
       const base::Optional<base::TimeDelta>&
           parse_blocked_on_script_load_duration,
-      const base::Optional<base::TimeDelta>& parse_stop);
+      const base::Optional<base::TimeDelta>& parse_stop,
+      int64_t network_bytes,
+      int64_t original_network_bytes);
 
   DataReductionProxyPageLoadTiming(
       const DataReductionProxyPageLoadTiming& other);
@@ -48,6 +52,11 @@
   const base::Optional<base::TimeDelta> parse_blocked_on_script_load_duration;
   // Time when parsing completed.
   const base::Optional<base::TimeDelta> parse_stop;
+  // The number of bytes served over the network, not including headers.
+  const int64_t network_bytes;
+  // The number of bytes that would have been served over the network if the
+  // user were not using data reduction proxy, not including headers.
+  const int64_t original_network_bytes;
 };
 
 }  // namespace data_reduction_proxy
diff --git a/components/data_reduction_proxy/proto/pageload_metrics.proto b/components/data_reduction_proxy/proto/pageload_metrics.proto
index cd9d142..ab567c1 100644
--- a/components/data_reduction_proxy/proto/pageload_metrics.proto
+++ b/components/data_reduction_proxy/proto/pageload_metrics.proto
@@ -55,10 +55,10 @@
 
   // The sum of original-content-length values, over resources that were not
   // loaded from browser cache.
-  optional int32 original_page_size_bytes = 10;
+  optional int64 original_page_size_bytes = 10;
   // The sum of (compressed) content-length, over resources that were not loaded
   // from browser cache.
-  optional int32 compressed_page_size_bytes = 11;
+  optional int64 compressed_page_size_bytes = 11;
 
   // The effective connection type at the start of the navigation.
   optional EffectiveConnectionType effective_connection_type = 12;
diff --git a/components/search_engines/template_url.cc b/components/search_engines/template_url.cc
index 8e300bd..dcaafd378 100644
--- a/components/search_engines/template_url.cc
+++ b/components/search_engines/template_url.cc
@@ -68,8 +68,6 @@
 const char kOutputEncodingType[] = "UTF-8";
 
 constexpr char kGoogleInstantExtendedEnabledKey[] =
-    "google:instantExtendedEnabledKey";
-constexpr char kGoogleInstantExtendedEnabledKeyFull[] =
     "{google:instantExtendedEnabledKey}";
 
 // Attempts to encode |terms| and |original_query| in |encoding| and escape
@@ -173,10 +171,10 @@
   if (search_terms_replacement_key1 == search_terms_replacement_key2)
     return true;
   if (search_terms_replacement_key1 == google_util::kInstantExtendedAPIParam &&
-      search_terms_replacement_key2 == kGoogleInstantExtendedEnabledKeyFull)
+      search_terms_replacement_key2 == kGoogleInstantExtendedEnabledKey)
     return true;
   if (search_terms_replacement_key2 == google_util::kInstantExtendedAPIParam &&
-      search_terms_replacement_key1 == kGoogleInstantExtendedEnabledKeyFull)
+      search_terms_replacement_key1 == kGoogleInstantExtendedEnabledKey)
     return true;
   return false;
 }
@@ -1179,8 +1177,7 @@
   ResizeURLRefVector();
   SetPrepopulateId(data_.prepopulate_id);
 
-  if (data_.search_terms_replacement_key ==
-      kGoogleInstantExtendedEnabledKeyFull)
+  if (data_.search_terms_replacement_key == kGoogleInstantExtendedEnabledKey)
     data_.search_terms_replacement_key = google_util::kInstantExtendedAPIParam;
 }
 
diff --git a/components/search_engines/template_url_unittest.cc b/components/search_engines/template_url_unittest.cc
index f6309f5..123e78b 100644
--- a/components/search_engines/template_url_unittest.cc
+++ b/components/search_engines/template_url_unittest.cc
@@ -1890,25 +1890,3 @@
   TemplateURL url(data);
   EXPECT_TRUE(TemplateURL::MatchesData(&url, &data, search_terms_data_));
 }
-
-// Test for correct replacement of GoogleInstantExtendedEnabledKey param.
-TEST_F(TemplateURLTest, GoogleInstantExtendedEnabledReplacement) {
-  TemplateURLData data;
-  data.SetURL(
-      "{google:baseURL}search/"
-      "?{google:instantExtendedEnabledKey}&q={searchTerms}");
-  data.SetShortName(ASCIIToUTF16("Google"));
-  data.SetKeyword(ASCIIToUTF16("google.com"));
-  data.search_terms_replacement_key = "{google:instantExtendedEnabledKey}";
-  TemplateURL turl(data);
-  EXPECT_TRUE(TemplateURL::MatchesData(&turl, &data, search_terms_data_));
-  // Expect that replacement of search_terms_replacement_key in TemplateURL
-  // constructor is correct.
-  EXPECT_EQ("espv", turl.search_terms_replacement_key());
-  // Expect that replacement of {google:instantExtendedEnabledKey} in search url
-  // is correct.
-  GURL search_generated = turl.GenerateSearchURL(search_terms_data_);
-  EXPECT_EQ("http://www.google.com/search/?espv&q=blah.blah.blah.blah.blah",
-            search_generated.spec());
-  EXPECT_TRUE(turl.HasSearchTermsReplacementKey(search_generated));
-}
diff --git a/components/sync/driver/data_type_controller.h b/components/sync/driver/data_type_controller.h
index 812a190..511e889e 100644
--- a/components/sync/driver/data_type_controller.h
+++ b/components/sync/driver/data_type_controller.h
@@ -91,6 +91,12 @@
   // return false while USS datatypes should return true.
   virtual bool ShouldLoadModelBeforeConfigure() const = 0;
 
+  // Called right before LoadModels. This method allows controller to register
+  // the type with sync engine. Directory datatypes download initial data in
+  // parallel with LoadModels and thus should be ready to receive updates with
+  // initial data before LoadModels finishes.
+  virtual void BeforeLoadModels(ModelTypeConfigurer* configurer) = 0;
+
   // Begins asynchronous operation of loading the model to get it ready for
   // model association. Once the models are loaded the callback will be invoked
   // with the result. If the models are already loaded it is safe to call the
diff --git a/components/sync/driver/data_type_manager_impl.cc b/components/sync/driver/data_type_manager_impl.cc
index 071d4c42..afb73b0 100644
--- a/components/sync/driver/data_type_manager_impl.cc
+++ b/components/sync/driver/data_type_manager_impl.cc
@@ -642,6 +642,12 @@
   model_association_manager_.StartAssociationAsync(types_to_associate);
 }
 
+void DataTypeManagerImpl::OnSingleDataTypeWillStart(ModelType type) {
+  DCHECK(controllers_->find(type) != controllers_->end());
+  DataTypeController* dtc = controllers_->find(type)->second.get();
+  dtc->BeforeLoadModels(configurer_);
+}
+
 void DataTypeManagerImpl::OnSingleDataTypeWillStop(ModelType type,
                                                    const SyncError& error) {
   DataTypeController::TypeMap::const_iterator c_it = controllers_->find(type);
diff --git a/components/sync/driver/data_type_manager_impl.h b/components/sync/driver/data_type_manager_impl.h
index 2f64a457..cf28cdf 100644
--- a/components/sync/driver/data_type_manager_impl.h
+++ b/components/sync/driver/data_type_manager_impl.h
@@ -59,6 +59,7 @@
   State state() const override;
 
   // |ModelAssociationManagerDelegate| implementation.
+  void OnSingleDataTypeWillStart(ModelType type) override;
   void OnAllDataTypesReadyForConfigure() override;
   void OnSingleDataTypeAssociationDone(
       ModelType type,
diff --git a/components/sync/driver/data_type_manager_impl_unittest.cc b/components/sync/driver/data_type_manager_impl_unittest.cc
index af1bf7e..52ecd58 100644
--- a/components/sync/driver/data_type_manager_impl_unittest.cc
+++ b/components/sync/driver/data_type_manager_impl_unittest.cc
@@ -5,6 +5,7 @@
 #include "components/sync/driver/data_type_manager_impl.h"
 
 #include <memory>
+#include <utility>
 
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
@@ -80,6 +81,15 @@
     last_params_ = std::move(params);
   }
 
+  void RegisterDirectoryDataType(ModelType type,
+                                 ModelSafeGroup group) override {
+    registered_directory_types_.Put(type);
+  }
+
+  void UnregisterDirectoryDataType(ModelType type) override {
+    registered_directory_types_.Remove(type);
+  }
+
   void ActivateDirectoryDataType(ModelType type,
                                  ModelSafeGroup group,
                                  ChangeProcessor* change_processor) override {
@@ -100,6 +110,10 @@
     // TODO(stanisc): crbug.com/515962: Add test coverage.
   }
 
+  const ModelTypeSet registered_directory_types() {
+    return registered_directory_types_;
+  }
+
   const ModelTypeSet activated_types() { return activated_types_; }
 
   int configure_call_count() const { return configure_call_count_; }
@@ -107,6 +121,7 @@
   const ConfigureParams& last_params() const { return last_params_; }
 
  private:
+  ModelTypeSet registered_directory_types_;
   ModelTypeSet activated_types_;
   int configure_call_count_ = 0;
   ConfigureParams last_params_;
@@ -342,6 +357,7 @@
 
   Configure(ModelTypeSet(BOOKMARKS));
   EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS), configurer_.registered_directory_types());
 
   FinishDownload(ModelTypeSet(), ModelTypeSet());
   FinishDownload(ModelTypeSet(BOOKMARKS), ModelTypeSet());
@@ -354,6 +370,7 @@
   dtm_->Stop();
   EXPECT_EQ(DataTypeManager::STOPPED, dtm_->state());
   EXPECT_TRUE(configurer_.activated_types().Empty());
+  EXPECT_TRUE(configurer_.registered_directory_types().Empty());
 }
 
 // Set up a DTM with a single controller, configure it, but stop it
@@ -369,9 +386,12 @@
 
     Configure(ModelTypeSet(BOOKMARKS));
     EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
+    EXPECT_EQ(ModelTypeSet(BOOKMARKS),
+              configurer_.registered_directory_types());
 
     dtm_->Stop();
     EXPECT_EQ(DataTypeManager::STOPPED, dtm_->state());
+    EXPECT_TRUE(configurer_.registered_directory_types().Empty());
   }
 
   last_configure_params().ready_task.Run(ModelTypeSet(BOOKMARKS),
@@ -393,13 +413,15 @@
 
     Configure(ModelTypeSet(BOOKMARKS));
     EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
-
+    EXPECT_EQ(ModelTypeSet(BOOKMARKS),
+              configurer_.registered_directory_types());
     FinishDownload(ModelTypeSet(), ModelTypeSet());
     FinishDownload(ModelTypeSet(BOOKMARKS), ModelTypeSet());
     EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
 
     dtm_->Stop();
     EXPECT_EQ(DataTypeManager::STOPPED, dtm_->state());
+    EXPECT_TRUE(configurer_.registered_directory_types().Empty());
     dtm_.reset();
   }
 
@@ -422,6 +444,8 @@
 
     Configure(ModelTypeSet(BOOKMARKS));
     EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
+    EXPECT_EQ(ModelTypeSet(BOOKMARKS),
+              configurer_.registered_directory_types());
 
     FinishDownload(ModelTypeSet(), ModelTypeSet());
     FinishDownload(ModelTypeSet(BOOKMARKS), ModelTypeSet());
@@ -430,6 +454,7 @@
 
     dtm_->Stop();
     EXPECT_EQ(DataTypeManager::STOPPED, dtm_->state());
+    EXPECT_TRUE(configurer_.registered_directory_types().Empty());
     dtm_.reset();
   }
 
@@ -497,6 +522,7 @@
   // Step 1.
   Configure(ModelTypeSet(BOOKMARKS));
   EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS), configurer_.registered_directory_types());
 
   // Step 2.
   FinishDownload(ModelTypeSet(), ModelTypeSet());
@@ -514,6 +540,8 @@
   // Step 4.
   Configure(ModelTypeSet(BOOKMARKS, PREFERENCES));
   EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS, PREFERENCES),
+            configurer_.registered_directory_types());
 
   // Step 5.
   FinishDownload(ModelTypeSet(), ModelTypeSet());
@@ -529,6 +557,7 @@
   dtm_->Stop();
   EXPECT_EQ(DataTypeManager::STOPPED, dtm_->state());
   EXPECT_TRUE(configurer_.activated_types().Empty());
+  EXPECT_TRUE(configurer_.registered_directory_types().Empty());
 }
 
 // Set up a DTM with two controllers.  Then:
@@ -550,6 +579,7 @@
   // Step 1.
   Configure(ModelTypeSet(BOOKMARKS));
   EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS), configurer_.registered_directory_types());
 
   // Step 2.
   FinishDownload(ModelTypeSet(), ModelTypeSet());
@@ -567,6 +597,8 @@
   // Step 4.
   Configure(ModelTypeSet(PREFERENCES));
   EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
+  EXPECT_EQ(ModelTypeSet(PREFERENCES),
+            configurer_.registered_directory_types());
 
   // Step 5.
   FinishDownload(ModelTypeSet(), ModelTypeSet());
@@ -582,6 +614,7 @@
   dtm_->Stop();
   EXPECT_EQ(DataTypeManager::STOPPED, dtm_->state());
   EXPECT_TRUE(configurer_.activated_types().Empty());
+  EXPECT_TRUE(configurer_.registered_directory_types().Empty());
 }
 
 // Set up a DTM with two controllers.  Then:
@@ -603,6 +636,7 @@
   // Step 1.
   Configure(ModelTypeSet(BOOKMARKS));
   EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS), configurer_.registered_directory_types());
 
   // Step 2.
   FinishDownload(ModelTypeSet(), ModelTypeSet());
@@ -626,11 +660,14 @@
   GetController(PREFERENCES)->FinishStart(DataTypeController::OK);
   EXPECT_EQ(DataTypeManager::CONFIGURED, dtm_->state());
   EXPECT_EQ(2U, configurer_.activated_types().Size());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS, PREFERENCES),
+            configurer_.registered_directory_types());
 
   // Step 7.
   dtm_->Stop();
   EXPECT_EQ(DataTypeManager::STOPPED, dtm_->state());
   EXPECT_TRUE(configurer_.activated_types().Empty());
+  EXPECT_TRUE(configurer_.registered_directory_types().Empty());
 }
 
 // Set up a DTM with one controller.  Then configure, finish
@@ -647,6 +684,7 @@
 
   Configure(ModelTypeSet(BOOKMARKS));
   EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS), configurer_.registered_directory_types());
 
   FinishDownload(ModelTypeSet(), ModelTypeSet());
   FinishDownload(ModelTypeSet(BOOKMARKS), ModelTypeSet());
@@ -657,6 +695,7 @@
       DataTypeController::UNRECOVERABLE_ERROR);
   EXPECT_EQ(DataTypeManager::STOPPED, dtm_->state());
   EXPECT_TRUE(configurer_.activated_types().Empty());
+  EXPECT_TRUE(configurer_.registered_directory_types().Empty());
 }
 
 // Set up a DTM with two controllers.  Then:
@@ -680,6 +719,8 @@
   // Step 1.
   Configure(ModelTypeSet(BOOKMARKS, PREFERENCES));
   EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS, PREFERENCES),
+            configurer_.registered_directory_types());
 
   // Step 2.
   FinishDownload(ModelTypeSet(), ModelTypeSet());
@@ -694,6 +735,7 @@
   GetController(PREFERENCES)
       ->FinishStart(DataTypeController::UNRECOVERABLE_ERROR);
   EXPECT_EQ(DataTypeManager::STOPPED, dtm_->state());
+  EXPECT_TRUE(configurer_.registered_directory_types().Empty());
 }
 
 // Set up a DTM with two controllers.  Then:
@@ -722,6 +764,8 @@
   // Step 1.
   Configure(ModelTypeSet(BOOKMARKS, PREFERENCES));
   EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS, PREFERENCES),
+            configurer_.registered_directory_types());
 
   // Step 2.
   FinishDownload(ModelTypeSet(), ModelTypeSet());
@@ -742,11 +786,13 @@
   FinishDownload(ModelTypeSet(BOOKMARKS), ModelTypeSet());
   EXPECT_EQ(DataTypeManager::CONFIGURED, dtm_->state());
   EXPECT_EQ(1U, configurer_.activated_types().Size());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS), configurer_.registered_directory_types());
 
   // Step 6.
   dtm_->Stop();
   EXPECT_EQ(DataTypeManager::STOPPED, dtm_->state());
   EXPECT_TRUE(configurer_.activated_types().Empty());
+  EXPECT_TRUE(configurer_.registered_directory_types().Empty());
 }
 
 // Set up a DTM with two controllers.  Then:
@@ -767,6 +813,7 @@
   // Step 1.
   Configure(ModelTypeSet(BOOKMARKS));
   EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS), configurer_.registered_directory_types());
 
   // Step 2.
   Configure(ModelTypeSet(BOOKMARKS, PREFERENCES));
@@ -786,10 +833,13 @@
   EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
   GetController(PREFERENCES)->FinishStart(DataTypeController::OK);
   EXPECT_EQ(DataTypeManager::CONFIGURED, dtm_->state());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS, PREFERENCES),
+            configurer_.registered_directory_types());
 
   // Step 6.
   dtm_->Stop();
   EXPECT_EQ(DataTypeManager::STOPPED, dtm_->state());
+  EXPECT_TRUE(configurer_.registered_directory_types().Empty());
 }
 
 // Set up a DTM with two controllers.  Then:
@@ -814,6 +864,7 @@
   Configure(ModelTypeSet(BOOKMARKS));
   FinishDownload(ModelTypeSet(), ModelTypeSet());
   EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS), configurer_.registered_directory_types());
 
   // Step 2.
   Configure(ModelTypeSet(BOOKMARKS, PREFERENCES));
@@ -833,10 +884,13 @@
   EXPECT_EQ(DataTypeManager::CONFIGURING, dtm_->state());
   GetController(PREFERENCES)->FinishStart(DataTypeController::OK);
   EXPECT_EQ(DataTypeManager::CONFIGURED, dtm_->state());
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS, PREFERENCES),
+            configurer_.registered_directory_types());
 
   // Step 6.
   dtm_->Stop();
   EXPECT_EQ(DataTypeManager::STOPPED, dtm_->state());
+  EXPECT_TRUE(configurer_.registered_directory_types().Empty());
 }
 
 // Tests a Purge then Configure.  This is similar to the sequence of
diff --git a/components/sync/driver/directory_data_type_controller.cc b/components/sync/driver/directory_data_type_controller.cc
index fc519dd..a03ee3d 100644
--- a/components/sync/driver/directory_data_type_controller.cc
+++ b/components/sync/driver/directory_data_type_controller.cc
@@ -35,6 +35,31 @@
   return false;
 }
 
+void DirectoryDataTypeController::BeforeLoadModels(
+    ModelTypeConfigurer* configurer) {
+  configurer->RegisterDirectoryDataType(type(), model_safe_group_);
+}
+
+void DirectoryDataTypeController::RegisterWithBackend(
+    base::Callback<void(bool)> set_downloaded,
+    ModelTypeConfigurer* configurer) {}
+
+void DirectoryDataTypeController::ActivateDataType(
+    ModelTypeConfigurer* configurer) {
+  DCHECK(CalledOnValidThread());
+  // Tell the backend about the change processor for this type so it can
+  // begin routing changes to it.
+  configurer->ActivateDirectoryDataType(type(), model_safe_group_,
+                                        GetChangeProcessor());
+}
+
+void DirectoryDataTypeController::DeactivateDataType(
+    ModelTypeConfigurer* configurer) {
+  DCHECK(CalledOnValidThread());
+  configurer->DeactivateDirectoryDataType(type());
+  configurer->UnregisterDirectoryDataType(type());
+}
+
 void DirectoryDataTypeController::GetAllNodes(
     const AllNodesCallback& callback) {
   std::unique_ptr<base::ListValue> node_list = GetAllNodesForTypeFromDirectory(
@@ -58,25 +83,6 @@
   callback.Run(type(), counters);
 }
 
-void DirectoryDataTypeController::RegisterWithBackend(
-    base::Callback<void(bool)> set_downloaded,
-    ModelTypeConfigurer* configurer) {}
-
-void DirectoryDataTypeController::ActivateDataType(
-    ModelTypeConfigurer* configurer) {
-  DCHECK(CalledOnValidThread());
-  // Tell the backend about the change processor for this type so it can
-  // begin routing changes to it.
-  configurer->ActivateDirectoryDataType(type(), model_safe_group_,
-                                        GetChangeProcessor());
-}
-
-void DirectoryDataTypeController::DeactivateDataType(
-    ModelTypeConfigurer* configurer) {
-  DCHECK(CalledOnValidThread());
-  configurer->DeactivateDirectoryDataType(type());
-}
-
 // static
 std::unique_ptr<base::ListValue>
 DirectoryDataTypeController::GetAllNodesForTypeFromDirectory(
diff --git a/components/sync/driver/directory_data_type_controller.h b/components/sync/driver/directory_data_type_controller.h
index 857055c..a521be1d 100644
--- a/components/sync/driver/directory_data_type_controller.h
+++ b/components/sync/driver/directory_data_type_controller.h
@@ -23,12 +23,13 @@
 
   // DataTypeController implementation.
   bool ShouldLoadModelBeforeConfigure() const override;
-  void GetAllNodes(const AllNodesCallback& callback) override;
-  void GetStatusCounters(const StatusCountersCallback& callback) override;
 
-  // Directory based data types don't need to register with backend.
-  // ModelTypeRegistry will create all necessary objects in
-  // SetEnabledDirectoryTypes based on routing info.
+  // Directory types need to register with sync engine before LoadModels because
+  // downloading initial data happens in parallel with LoadModels.
+  void BeforeLoadModels(ModelTypeConfigurer* configurer) override;
+
+  // Directory based data types register with backend before LoadModels in
+  // BeforeLoadModels. No need to do anything in RegisterWithBackend.
   void RegisterWithBackend(base::Callback<void(bool)> set_downloaded,
                            ModelTypeConfigurer* configurer) override;
 
@@ -46,6 +47,9 @@
   // See ModelTypeConfigurer::DeactivateDataType for more details.
   void DeactivateDataType(ModelTypeConfigurer* configurer) override;
 
+  void GetAllNodes(const AllNodesCallback& callback) override;
+  void GetStatusCounters(const StatusCountersCallback& callback) override;
+
   // Returns a ListValue representing all nodes for a specified type by querying
   // the directory.
   static std::unique_ptr<base::ListValue> GetAllNodesForTypeFromDirectory(
diff --git a/components/sync/driver/glue/sync_backend_host_core.cc b/components/sync/driver/glue/sync_backend_host_core.cc
index b934544..0751ba2 100644
--- a/components/sync/driver/glue/sync_backend_host_core.cc
+++ b/components/sync/driver/glue/sync_backend_host_core.cc
@@ -141,6 +141,16 @@
 
   ModelTypeSet new_control_types =
       registrar_->ConfigureDataTypes(ControlTypes(), ModelTypeSet());
+
+  // Control types don't have DataTypeControllers, but they need to have
+  // update handlers registered in ModelTypeRegistry. Register them here.
+  ModelTypeConnector* model_type_connector =
+      sync_manager_->GetModelTypeConnector();
+  ModelTypeSet control_types = ControlTypes();
+  for (auto it = control_types.First(); it.Good(); it.Inc()) {
+    model_type_connector->RegisterDirectoryType(it.Get(), GROUP_PASSIVE);
+  }
+
   ModelSafeRoutingInfo routing_info;
   registrar_->GetModelSafeRoutingInfo(&routing_info);
   SDVLOG(1) << "Control Types " << ModelTypeSetToString(new_control_types)
@@ -152,7 +162,7 @@
   sync_manager_->PurgeDisabledTypes(types_to_purge, ModelTypeSet(),
                                     ModelTypeSet());
   sync_manager_->ConfigureSyncer(
-      reason, new_control_types, routing_info,
+      reason, new_control_types,
       base::Bind(&SyncBackendHostCore::DoInitialProcessControlTypes,
                  weak_ptr_factory_.GetWeakPtr()),
       base::Closure());
@@ -389,11 +399,13 @@
   }
 }
 
-void SyncBackendHostCore::DoStartSyncing(
-    const ModelSafeRoutingInfo& routing_info,
-    base::Time last_poll_time) {
+void SyncBackendHostCore::DoStartConfiguration() {
+  sync_manager_->StartConfiguration();
+}
+
+void SyncBackendHostCore::DoStartSyncing(base::Time last_poll_time) {
   DCHECK(thread_checker_.CalledOnValidThread());
-  sync_manager_->StartSyncingNormally(routing_info, last_poll_time);
+  sync_manager_->StartSyncingNormally(last_poll_time);
 }
 
 void SyncBackendHostCore::DoSetEncryptionPassphrase(
@@ -515,9 +527,6 @@
 
   registrar_->ConfigureDataTypes(params.enabled_types, params.disabled_types);
 
-  ModelSafeRoutingInfo routing_info;
-  registrar_->GetModelSafeRoutingInfo(&routing_info);
-
   base::Closure chained_ready_task(base::Bind(
       &SyncBackendHostCore::DoFinishConfigureDataTypes,
       weak_ptr_factory_.GetWeakPtr(), params.to_download, params.ready_task));
@@ -526,8 +535,7 @@
                  weak_ptr_factory_.GetWeakPtr(), params.retry_callback));
 
   sync_manager_->ConfigureSyncer(params.reason, params.to_download,
-                                 routing_info, chained_ready_task,
-                                 chained_retry_task);
+                                 chained_ready_task, chained_retry_task);
 }
 
 void SyncBackendHostCore::DoFinishConfigureDataTypes(
diff --git a/components/sync/driver/glue/sync_backend_host_core.h b/components/sync/driver/glue/sync_backend_host_core.h
index 787a1eb..c46814b 100644
--- a/components/sync/driver/glue/sync_backend_host_core.h
+++ b/components/sync/driver/glue/sync_backend_host_core.h
@@ -106,10 +106,14 @@
   // SyncEngine::UpdateCredentials.
   void DoUpdateCredentials(const SyncCredentials& credentials);
 
+  // Switches sync engine into configuration mode. In this mode only initial
+  // data for newly enabled types is downloaded from server. No local changes
+  // are committed to server.
+  void DoStartConfiguration();
+
   // Called to tell the syncapi to start syncing (generally after
   // initialization and authentication).
-  void DoStartSyncing(const ModelSafeRoutingInfo& routing_info,
-                      base::Time last_poll_time);
+  void DoStartSyncing(base::Time last_poll_time);
 
   // Called to set the passphrase for encryption.
   void DoSetEncryptionPassphrase(const std::string& passphrase,
diff --git a/components/sync/driver/glue/sync_backend_host_impl.cc b/components/sync/driver/glue/sync_backend_host_impl.cc
index ff1a70e..0106db4c 100644
--- a/components/sync/driver/glue/sync_backend_host_impl.cc
+++ b/components/sync/driver/glue/sync_backend_host_impl.cc
@@ -90,15 +90,17 @@
                             credentials));
 }
 
+void SyncBackendHostImpl::StartConfiguration() {
+  sync_task_runner_->PostTask(
+      FROM_HERE, base::Bind(&SyncBackendHostCore::DoStartConfiguration, core_));
+}
+
 void SyncBackendHostImpl::StartSyncingWithServer() {
   SDVLOG(1) << "SyncBackendHostImpl::StartSyncingWithServer called.";
 
-  ModelSafeRoutingInfo routing_info;
-  registrar_->GetModelSafeRoutingInfo(&routing_info);
-
   sync_task_runner_->PostTask(
       FROM_HERE, base::Bind(&SyncBackendHostCore::DoStartSyncing, core_,
-                            routing_info, sync_prefs_->GetLastPollTime()));
+                            sync_prefs_->GetLastPollTime()));
 }
 
 void SyncBackendHostImpl::SetEncryptionPassphrase(const std::string& passphrase,
@@ -211,6 +213,15 @@
                             base::Passed(&params)));
 }
 
+void SyncBackendHostImpl::RegisterDirectoryDataType(ModelType type,
+                                                    ModelSafeGroup group) {
+  model_type_connector_->RegisterDirectoryType(type, group);
+}
+
+void SyncBackendHostImpl::UnregisterDirectoryDataType(ModelType type) {
+  model_type_connector_->UnregisterDirectoryType(type);
+}
+
 void SyncBackendHostImpl::EnableEncryptEverything() {
   sync_task_runner_->PostTask(
       FROM_HERE,
@@ -234,11 +245,12 @@
   registrar_->RegisterNonBlockingType(type);
   if (activation_context->model_type_state.initial_sync_done())
     registrar_->AddRestoredNonBlockingType(type);
-  model_type_connector_->ConnectType(type, std::move(activation_context));
+  model_type_connector_->ConnectNonBlockingType(type,
+                                                std::move(activation_context));
 }
 
 void SyncBackendHostImpl::DeactivateNonBlockingDataType(ModelType type) {
-  model_type_connector_->DisconnectType(type);
+  model_type_connector_->DisconnectNonBlockingType(type);
 }
 
 UserShare* SyncBackendHostImpl::GetUserShare() const {
diff --git a/components/sync/driver/glue/sync_backend_host_impl.h b/components/sync/driver/glue/sync_backend_host_impl.h
index 4d06288..e7f93eb 100644
--- a/components/sync/driver/glue/sync_backend_host_impl.h
+++ b/components/sync/driver/glue/sync_backend_host_impl.h
@@ -60,6 +60,7 @@
   void Initialize(InitParams params) override;
   void TriggerRefresh(const ModelTypeSet& types) override;
   void UpdateCredentials(const SyncCredentials& credentials) override;
+  void StartConfiguration() override;
   void StartSyncingWithServer() override;
   void SetEncryptionPassphrase(const std::string& passphrase,
                                bool is_explicit) override;
@@ -68,6 +69,8 @@
   void StopSyncingForShutdown() override;
   void Shutdown(ShutdownReason reason) override;
   void ConfigureDataTypes(ConfigureParams params) override;
+  void RegisterDirectoryDataType(ModelType type, ModelSafeGroup group) override;
+  void UnregisterDirectoryDataType(ModelType type) override;
   void ActivateDirectoryDataType(ModelType type,
                                  ModelSafeGroup group,
                                  ChangeProcessor* change_processor) override;
diff --git a/components/sync/driver/glue/sync_backend_host_impl_unittest.cc b/components/sync/driver/glue/sync_backend_host_impl_unittest.cc
index 21d8d42..a96b4b2 100644
--- a/components/sync/driver/glue/sync_backend_host_impl_unittest.cc
+++ b/components/sync/driver/glue/sync_backend_host_impl_unittest.cc
@@ -323,7 +323,6 @@
   EXPECT_TRUE(fake_manager_->GetAndResetDownloadedTypes().HasAll(
       Difference(enabled_types_, ControlTypes())));
   EXPECT_EQ(enabled_types_, fake_manager_->InitialSyncEndedTypes());
-  EXPECT_EQ(enabled_types_, fake_manager_->GetAndResetEnabledTypes());
   EXPECT_TRUE(
       fake_manager_->GetTypesWithEmptyProgressMarkerToken(enabled_types_)
           .Empty());
@@ -353,7 +352,6 @@
       Intersection(fake_manager_->GetAndResetPurgedTypes(), enabled_types_)
           .Empty());
   EXPECT_EQ(enabled_types_, fake_manager_->InitialSyncEndedTypes());
-  EXPECT_EQ(enabled_types_, fake_manager_->GetAndResetEnabledTypes());
   EXPECT_TRUE(
       fake_manager_->GetTypesWithEmptyProgressMarkerToken(enabled_types_)
           .Empty());
@@ -389,7 +387,6 @@
           .Empty());
   EXPECT_EQ(partial_types, fake_manager_->GetAndResetDownloadedTypes());
   EXPECT_EQ(enabled_types_, fake_manager_->InitialSyncEndedTypes());
-  EXPECT_EQ(enabled_types_, fake_manager_->GetAndResetEnabledTypes());
   EXPECT_TRUE(
       fake_manager_->GetTypesWithEmptyProgressMarkerToken(enabled_types_)
           .Empty());
@@ -424,7 +421,6 @@
       Intersection(fake_manager_->GetAndResetPurgedTypes(), enabled_types_)
           .Empty());
   EXPECT_EQ(enabled_types_, fake_manager_->InitialSyncEndedTypes());
-  EXPECT_EQ(enabled_types_, fake_manager_->GetAndResetEnabledTypes());
   EXPECT_TRUE(
       fake_manager_->GetTypesWithEmptyProgressMarkerToken(enabled_types_)
           .Empty());
@@ -459,7 +455,6 @@
   EXPECT_EQ(disabled_types,
             Intersection(fake_manager_->GetAndResetPurgedTypes(), old_types));
   EXPECT_EQ(enabled_types_, fake_manager_->InitialSyncEndedTypes());
-  EXPECT_EQ(enabled_types_, fake_manager_->GetAndResetEnabledTypes());
   EXPECT_TRUE(
       fake_manager_->GetTypesWithEmptyProgressMarkerToken(enabled_types_)
           .Empty());
@@ -495,7 +490,6 @@
       Intersection(fake_manager_->GetAndResetPurgedTypes(), enabled_types_)
           .Empty());
   EXPECT_EQ(enabled_types_, fake_manager_->InitialSyncEndedTypes());
-  EXPECT_EQ(enabled_types_, fake_manager_->GetAndResetEnabledTypes());
   EXPECT_TRUE(
       fake_manager_->GetTypesWithEmptyProgressMarkerToken(enabled_types_)
           .Empty());
@@ -534,7 +528,6 @@
   EXPECT_EQ(disabled_types,
             Intersection(fake_manager_->GetAndResetPurgedTypes(), old_types));
   EXPECT_EQ(enabled_types_, fake_manager_->InitialSyncEndedTypes());
-  EXPECT_EQ(enabled_types_, fake_manager_->GetAndResetEnabledTypes());
   EXPECT_EQ(disabled_types,
             fake_manager_->GetTypesWithEmptyProgressMarkerToken(old_types));
 }
@@ -570,7 +563,6 @@
       Intersection(fake_manager_->GetAndResetPurgedTypes(), enabled_types_)
           .Empty());
   EXPECT_EQ(enabled_types_, fake_manager_->InitialSyncEndedTypes());
-  EXPECT_EQ(enabled_types_, fake_manager_->GetAndResetEnabledTypes());
   EXPECT_TRUE(
       fake_manager_->GetTypesWithEmptyProgressMarkerToken(enabled_types_)
           .Empty());
@@ -612,7 +604,6 @@
       Intersection(fake_manager_->GetAndResetPurgedTypes(), enabled_types_)
           .Empty());
   EXPECT_EQ(enabled_types_, fake_manager_->InitialSyncEndedTypes());
-  EXPECT_EQ(enabled_types_, fake_manager_->GetAndResetEnabledTypes());
   EXPECT_TRUE(
       fake_manager_->GetTypesWithEmptyProgressMarkerToken(enabled_types_)
           .Empty());
diff --git a/components/sync/driver/model_association_manager.cc b/components/sync/driver/model_association_manager.cc
index 382c4f1..41d5331 100644
--- a/components/sync/driver/model_association_manager.cc
+++ b/components/sync/driver/model_association_manager.cc
@@ -158,14 +158,25 @@
 }
 
 void ModelAssociationManager::LoadEnabledTypes() {
+  for (auto it = desired_types_.First(); it.Good(); it.Inc()) {
+    auto dtc_iter = controllers_->find(it.Get());
+    DCHECK(dtc_iter != controllers_->end());
+    DataTypeController* dtc = dtc_iter->second.get();
+    if (dtc->state() == DataTypeController::NOT_RUNNING) {
+      DCHECK(!loaded_types_.Has(dtc->type()));
+      DCHECK(!associated_types_.Has(dtc->type()));
+      delegate_->OnSingleDataTypeWillStart(dtc->type());
+    }
+  }
   // Load in kStartOrder.
   for (size_t i = 0; i < arraysize(kStartOrder); i++) {
     ModelType type = kStartOrder[i];
     if (!desired_types_.Has(type))
       continue;
 
-    DCHECK(controllers_->find(type) != controllers_->end());
-    DataTypeController* dtc = controllers_->find(type)->second.get();
+    auto dtc_iter = controllers_->find(type);
+    DCHECK(dtc_iter != controllers_->end());
+    DataTypeController* dtc = dtc_iter->second.get();
     if (dtc->state() == DataTypeController::NOT_RUNNING) {
       DCHECK(!loaded_types_.Has(dtc->type()));
       DCHECK(!associated_types_.Has(dtc->type()));
diff --git a/components/sync/driver/model_association_manager.h b/components/sync/driver/model_association_manager.h
index 2f8bd9d..c083d47 100644
--- a/components/sync/driver/model_association_manager.h
+++ b/components/sync/driver/model_association_manager.h
@@ -28,6 +28,10 @@
 // those operations are passed back via this interface.
 class ModelAssociationManagerDelegate {
  public:
+  // Called right before ModelAssociationManager calls LoadModels. Allows
+  // directory types to register with sync engine before download starts.
+  virtual void OnSingleDataTypeWillStart(ModelType type) = 0;
+
   // Called when all desired types are ready to be configured with
   // ModelTypeConfigurer. Data type is ready when its progress marker is
   // available to configurer. Directory data types are always ready, their
diff --git a/components/sync/driver/model_association_manager_unittest.cc b/components/sync/driver/model_association_manager_unittest.cc
index ebb576b5..65b0da7d 100644
--- a/components/sync/driver/model_association_manager_unittest.cc
+++ b/components/sync/driver/model_association_manager_unittest.cc
@@ -25,6 +25,7 @@
   MOCK_METHOD2(OnSingleDataTypeAssociationDone,
                void(ModelType type,
                     const DataTypeAssociationStats& association_stats));
+  MOCK_METHOD1(OnSingleDataTypeWillStart, void(ModelType type));
   MOCK_METHOD2(OnSingleDataTypeWillStop,
                void(ModelType, const SyncError& error));
   MOCK_METHOD1(OnModelAssociationDone,
@@ -64,6 +65,8 @@
   ModelAssociationManager model_association_manager(&controllers_, &delegate_);
   ModelTypeSet types(BOOKMARKS, APPS);
   DataTypeManager::ConfigureResult expected_result(DataTypeManager::OK, types);
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(BOOKMARKS));
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(APPS));
   EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
   EXPECT_CALL(delegate_, OnModelAssociationDone(_))
       .WillOnce(VerifyResult(expected_result));
@@ -99,9 +102,9 @@
   ModelTypeSet types;
   types.Put(BOOKMARKS);
 
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(BOOKMARKS));
   DataTypeManager::ConfigureResult expected_result(DataTypeManager::ABORTED,
                                                    types);
-
   EXPECT_CALL(delegate_, OnModelAssociationDone(_))
       .WillOnce(VerifyResult(expected_result));
   EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(BOOKMARKS, _));
@@ -122,6 +125,7 @@
   ModelAssociationManager model_association_manager(&controllers_, &delegate_);
   ModelTypeSet types;
   types.Put(BOOKMARKS);
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(BOOKMARKS));
   DataTypeManager::ConfigureResult expected_result(DataTypeManager::OK, types);
   EXPECT_CALL(delegate_, OnModelAssociationDone(_))
       .WillOnce(VerifyResult(expected_result));
@@ -145,6 +149,7 @@
   ModelAssociationManager model_association_manager(&controllers_, &delegate_);
   ModelTypeSet types;
   types.Put(BOOKMARKS);
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(BOOKMARKS));
   DataTypeManager::ConfigureResult expected_result(DataTypeManager::OK, types);
   EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(BOOKMARKS, _));
   EXPECT_CALL(delegate_, OnModelAssociationDone(_))
@@ -167,6 +172,7 @@
   ModelAssociationManager model_association_manager(&controllers_, &delegate_);
   ModelTypeSet types;
   types.Put(BOOKMARKS);
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(BOOKMARKS));
   DataTypeManager::ConfigureResult expected_result(
       DataTypeManager::UNRECOVERABLE_ERROR, types);
   EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(BOOKMARKS, _));
@@ -195,6 +201,8 @@
   DataTypeManager::ConfigureResult expected_result_partially_done(
       DataTypeManager::OK, types);
 
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(BOOKMARKS));
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(APPS));
   EXPECT_CALL(delegate_, OnModelAssociationDone(_))
       .WillOnce(VerifyResult(expected_result_partially_done));
 
@@ -262,6 +270,7 @@
   ModelTypeSet types;
   types.Put(BOOKMARKS);
   DataTypeManager::ConfigureResult expected_result(DataTypeManager::OK, types);
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(BOOKMARKS));
   EXPECT_CALL(delegate_, OnSingleDataTypeWillStop(BOOKMARKS, _));
   EXPECT_CALL(delegate_, OnModelAssociationDone(_))
       .WillOnce(VerifyResult(expected_result));
@@ -281,6 +290,7 @@
   ModelTypeSet types;
   types.Put(BOOKMARKS);
   DataTypeManager::ConfigureResult expected_result(DataTypeManager::OK, types);
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(BOOKMARKS));
   EXPECT_CALL(delegate_, OnModelAssociationDone(_))
       .WillOnce(VerifyResult(expected_result));
 
@@ -315,6 +325,8 @@
   DataTypeManager::ConfigureResult expected_result_partially_done(
       DataTypeManager::OK, types);
 
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(BOOKMARKS));
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(APPS));
   EXPECT_CALL(delegate_, OnModelAssociationDone(_))
       .WillOnce(VerifyResult(expected_result_partially_done));
 
@@ -350,6 +362,8 @@
   DataTypeManager::ConfigureResult expected_result(DataTypeManager::OK, types);
   // OnAllDataTypesReadyForConfigure shouldn't be called, APPS data type is not
   // loaded yet.
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(BOOKMARKS));
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(APPS));
   EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure()).Times(0);
 
   model_association_manager.Initialize(types);
@@ -395,6 +409,7 @@
   DataTypeManager::ConfigureResult expected_result(DataTypeManager::OK, types);
   // OnAllDataTypesReadyForConfigure shouldn't be called, APPS data type is not
   // loaded yet.
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(APPS));
   EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure()).Times(0);
 
   model_association_manager.Initialize(types);
@@ -436,6 +451,8 @@
 
   // Apps will finish loading but bookmarks won't.
   // OnAllDataTypesReadyForConfigure shouldn't be called.
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(BOOKMARKS));
+  EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(APPS));
   EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure()).Times(0);
 
   model_association_manager.Initialize(types);
@@ -469,4 +486,31 @@
             DataTypeController::MODEL_LOADED);
 }
 
+// Tests that OnAllDataTypesReadyForConfigure is only called after
+// OnSingleDataTypeWillStart is called for all enabled types.
+TEST_F(SyncModelAssociationManagerTest, TypeRegistrationCallSequence) {
+  // Create two controllers and allow them to complete LoadModels synchronously.
+  controllers_[BOOKMARKS] = base::MakeUnique<FakeDataTypeController>(BOOKMARKS);
+  controllers_[APPS] = base::MakeUnique<FakeDataTypeController>(APPS);
+
+  ModelAssociationManager model_association_manager(&controllers_, &delegate_);
+  ModelTypeSet types(BOOKMARKS, APPS);
+  DataTypeManager::ConfigureResult expected_result(DataTypeManager::OK, types);
+  // OnAllDataTypesReadyForConfigure should only be called after calls to
+  // OnSingleDataTypeWillStart for both enabled types.
+  {
+    ::testing::InSequence call_sequence;
+    EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(BOOKMARKS));
+    EXPECT_CALL(delegate_, OnSingleDataTypeWillStart(APPS));
+    EXPECT_CALL(delegate_, OnAllDataTypesReadyForConfigure());
+  }
+
+  model_association_manager.Initialize(types);
+
+  EXPECT_EQ(DataTypeController::MODEL_LOADED,
+            GetController(controllers_, BOOKMARKS)->state());
+  EXPECT_EQ(DataTypeController::MODEL_LOADED,
+            GetController(controllers_, APPS)->state());
+}
+
 }  // namespace syncer
diff --git a/components/sync/driver/model_type_controller.cc b/components/sync/driver/model_type_controller.cc
index 639d723..7bced2e 100644
--- a/components/sync/driver/model_type_controller.cc
+++ b/components/sync/driver/model_type_controller.cc
@@ -123,18 +123,7 @@
                             std::move(error_handler), std::move(callback)));
 }
 
-void ModelTypeController::GetAllNodes(const AllNodesCallback& callback) {
-  model_thread_->PostTask(
-      FROM_HERE, base::Bind(&CallGetAllNodesHelper, sync_client_, type(),
-                            BindToCurrentThread(callback)));
-}
-
-void ModelTypeController::GetStatusCounters(
-    const StatusCountersCallback& callback) {
-  model_thread_->PostTask(
-      FROM_HERE,
-      base::Bind(&CallGetStatusCountersHelper, sync_client_, type(), callback));
-}
+void ModelTypeController::BeforeLoadModels(ModelTypeConfigurer* configurer) {}
 
 void ModelTypeController::LoadModelsDone(ConfigureResult result,
                                          const SyncError& error) {
@@ -249,6 +238,19 @@
   return state_;
 }
 
+void ModelTypeController::GetAllNodes(const AllNodesCallback& callback) {
+  model_thread_->PostTask(
+      FROM_HERE, base::Bind(&CallGetAllNodesHelper, sync_client_, type(),
+                            BindToCurrentThread(callback)));
+}
+
+void ModelTypeController::GetStatusCounters(
+    const StatusCountersCallback& callback) {
+  model_thread_->PostTask(
+      FROM_HERE,
+      base::Bind(&CallGetStatusCountersHelper, sync_client_, type(), callback));
+}
+
 void ModelTypeController::ReportModelError(const ModelError& error) {
   DCHECK(CalledOnValidThread());
   LoadModelsDone(UNRECOVERABLE_ERROR,
diff --git a/components/sync/driver/model_type_controller.h b/components/sync/driver/model_type_controller.h
index 658bf07..cbc315f 100644
--- a/components/sync/driver/model_type_controller.h
+++ b/components/sync/driver/model_type_controller.h
@@ -33,9 +33,8 @@
 
   // DataTypeController implementation.
   bool ShouldLoadModelBeforeConfigure() const override;
+  void BeforeLoadModels(ModelTypeConfigurer* configurer) override;
   void LoadModels(const ModelLoadCallback& model_load_callback) override;
-  void GetAllNodes(const AllNodesCallback& callback) override;
-  void GetStatusCounters(const StatusCountersCallback& callback) override;
   void RegisterWithBackend(base::Callback<void(bool)> set_downloaded,
                            ModelTypeConfigurer* configurer) override;
   void StartAssociating(const StartCallback& start_callback) override;
@@ -44,6 +43,8 @@
   void Stop() override;
   std::string name() const override;
   State state() const override;
+  void GetAllNodes(const AllNodesCallback& callback) override;
+  void GetStatusCounters(const StatusCountersCallback& callback) override;
 
  private:
   void RecordStartFailure(ConfigureResult result) const;
diff --git a/components/sync/driver/model_type_controller_unittest.cc b/components/sync/driver/model_type_controller_unittest.cc
index be623dd..ba3610c 100644
--- a/components/sync/driver/model_type_controller_unittest.cc
+++ b/components/sync/driver/model_type_controller_unittest.cc
@@ -92,6 +92,15 @@
     NOTREACHED() << "Not implemented.";
   }
 
+  void RegisterDirectoryDataType(ModelType type,
+                                 ModelSafeGroup group) override {
+    NOTREACHED() << "Not implemented.";
+  }
+
+  void UnregisterDirectoryDataType(ModelType type) override {
+    NOTREACHED() << "Not implemented.";
+  }
+
   void ActivateDirectoryDataType(ModelType type,
                                  ModelSafeGroup group,
                                  ChangeProcessor* change_processor) override {
diff --git a/components/sync/driver/proxy_data_type_controller.cc b/components/sync/driver/proxy_data_type_controller.cc
index 2b17298..0bc9a75 100644
--- a/components/sync/driver/proxy_data_type_controller.cc
+++ b/components/sync/driver/proxy_data_type_controller.cc
@@ -21,6 +21,9 @@
   return false;
 }
 
+void ProxyDataTypeController::BeforeLoadModels(
+    ModelTypeConfigurer* configurer) {}
+
 void ProxyDataTypeController::LoadModels(
     const ModelLoadCallback& model_load_callback) {
   DCHECK(CalledOnValidThread());
diff --git a/components/sync/driver/proxy_data_type_controller.h b/components/sync/driver/proxy_data_type_controller.h
index 635e656..2036f2a 100644
--- a/components/sync/driver/proxy_data_type_controller.h
+++ b/components/sync/driver/proxy_data_type_controller.h
@@ -23,6 +23,7 @@
 
   // DataTypeController interface.
   bool ShouldLoadModelBeforeConfigure() const override;
+  void BeforeLoadModels(ModelTypeConfigurer* configurer) override;
   void LoadModels(const ModelLoadCallback& model_load_callback) override;
   void RegisterWithBackend(base::Callback<void(bool)> set_downloaded,
                            ModelTypeConfigurer* configurer) override;
diff --git a/components/sync/engine/fake_model_type_connector.cc b/components/sync/engine/fake_model_type_connector.cc
index 59b80266..5142c12 100644
--- a/components/sync/engine/fake_model_type_connector.cc
+++ b/components/sync/engine/fake_model_type_connector.cc
@@ -12,10 +12,15 @@
 
 FakeModelTypeConnector::~FakeModelTypeConnector() {}
 
-void FakeModelTypeConnector::ConnectType(
+void FakeModelTypeConnector::ConnectNonBlockingType(
     ModelType type,
     std::unique_ptr<ActivationContext> activation_context) {}
 
-void FakeModelTypeConnector::DisconnectType(ModelType type) {}
+void FakeModelTypeConnector::DisconnectNonBlockingType(ModelType type) {}
+
+void FakeModelTypeConnector::RegisterDirectoryType(ModelType type,
+                                                   ModelSafeGroup group) {}
+
+void FakeModelTypeConnector::UnregisterDirectoryType(ModelType type) {}
 
 }  // namespace syncer
diff --git a/components/sync/engine/fake_model_type_connector.h b/components/sync/engine/fake_model_type_connector.h
index 7c47378..978e24aa 100644
--- a/components/sync/engine/fake_model_type_connector.h
+++ b/components/sync/engine/fake_model_type_connector.h
@@ -17,10 +17,12 @@
   FakeModelTypeConnector();
   ~FakeModelTypeConnector() override;
 
-  void ConnectType(
+  void ConnectNonBlockingType(
       ModelType type,
       std::unique_ptr<ActivationContext> activation_context) override;
-  void DisconnectType(ModelType type) override;
+  void DisconnectNonBlockingType(ModelType type) override;
+  void RegisterDirectoryType(ModelType type, ModelSafeGroup group) override;
+  void UnregisterDirectoryType(ModelType type) override;
 };
 
 }  // namespace syncer
diff --git a/components/sync/engine/fake_sync_engine.cc b/components/sync/engine/fake_sync_engine.cc
index e40e6739..8b35a10e 100644
--- a/components/sync/engine/fake_sync_engine.cc
+++ b/components/sync/engine/fake_sync_engine.cc
@@ -24,6 +24,8 @@
 
 void FakeSyncEngine::UpdateCredentials(const SyncCredentials& credentials) {}
 
+void FakeSyncEngine::StartConfiguration() {}
+
 void FakeSyncEngine::StartSyncingWithServer() {}
 
 void FakeSyncEngine::SetEncryptionPassphrase(const std::string& passphrase,
@@ -39,6 +41,11 @@
 
 void FakeSyncEngine::ConfigureDataTypes(ConfigureParams params) {}
 
+void FakeSyncEngine::RegisterDirectoryDataType(ModelType type,
+                                               ModelSafeGroup group) {}
+
+void FakeSyncEngine::UnregisterDirectoryDataType(ModelType type) {}
+
 void FakeSyncEngine::EnableEncryptEverything() {}
 
 void FakeSyncEngine::ActivateDirectoryDataType(
diff --git a/components/sync/engine/fake_sync_engine.h b/components/sync/engine/fake_sync_engine.h
index 70f3627..b71bec9 100644
--- a/components/sync/engine/fake_sync_engine.h
+++ b/components/sync/engine/fake_sync_engine.h
@@ -32,6 +32,8 @@
 
   void UpdateCredentials(const SyncCredentials& credentials) override;
 
+  void StartConfiguration() override;
+
   void StartSyncingWithServer() override;
 
   void SetEncryptionPassphrase(const std::string& passphrase,
@@ -45,6 +47,10 @@
 
   void ConfigureDataTypes(ConfigureParams params) override;
 
+  void RegisterDirectoryDataType(ModelType type, ModelSafeGroup group) override;
+
+  void UnregisterDirectoryDataType(ModelType type) override;
+
   void EnableEncryptEverything() override;
 
   void ActivateDirectoryDataType(ModelType type,
diff --git a/components/sync/engine/fake_sync_manager.cc b/components/sync/engine/fake_sync_manager.cc
index b97e933..e9f9ce8 100644
--- a/components/sync/engine/fake_sync_manager.cc
+++ b/components/sync/engine/fake_sync_manager.cc
@@ -17,10 +17,8 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/sync/base/weak_handle.h"
 #include "components/sync/engine/engine_components_factory.h"
-#include "components/sync/engine/fake_model_type_connector.h"
 #include "components/sync/engine/net/http_post_provider_factory.h"
 #include "components/sync/syncable/directory.h"
-#include "components/sync/test/fake_sync_encryption_handler.h"
 
 class GURL;
 
@@ -33,9 +31,7 @@
       progress_marker_types_(progress_marker_types),
       configure_fail_types_(configure_fail_types),
       last_configure_reason_(CONFIGURE_REASON_UNKNOWN),
-      num_invalidations_received_(0) {
-  fake_encryption_handler_ = base::MakeUnique<FakeSyncEncryptionHandler>();
-}
+      num_invalidations_received_(0) {}
 
 FakeSyncManager::~FakeSyncManager() {}
 
@@ -57,12 +53,6 @@
   return downloaded_types;
 }
 
-ModelTypeSet FakeSyncManager::GetAndResetEnabledTypes() {
-  ModelTypeSet enabled_types = enabled_types_;
-  enabled_types_.Clear();
-  return enabled_types;
-}
-
 ConfigureReason FakeSyncManager::GetAndResetConfigureReason() {
   ConfigureReason reason = last_configure_reason_;
   last_configure_reason_ = CONFIGURE_REASON_UNKNOWN;
@@ -144,20 +134,20 @@
   NOTIMPLEMENTED();
 }
 
-void FakeSyncManager::StartSyncingNormally(
-    const ModelSafeRoutingInfo& routing_info,
-    base::Time last_poll_time) {
+void FakeSyncManager::StartSyncingNormally(base::Time last_poll_time) {
+  // Do nothing.
+}
+
+void FakeSyncManager::StartConfiguration() {
   // Do nothing.
 }
 
 void FakeSyncManager::ConfigureSyncer(
     ConfigureReason reason,
     ModelTypeSet to_download,
-    const ModelSafeRoutingInfo& new_routing_info,
     const base::Closure& ready_task,
     const base::Closure& retry_task) {
   last_configure_reason_ = reason;
-  enabled_types_ = GetRoutingInfoTypes(new_routing_info);
   ModelTypeSet success_types = to_download;
   success_types.RemoveAll(configure_fail_types_);
 
@@ -208,6 +198,10 @@
   return test_user_share_.user_share();
 }
 
+ModelTypeConnector* FakeSyncManager::GetModelTypeConnector() {
+  return &fake_model_type_connector_;
+}
+
 std::unique_ptr<ModelTypeConnector>
 FakeSyncManager::GetModelTypeConnectorProxy() {
   return base::MakeUnique<FakeModelTypeConnector>();
@@ -227,7 +221,7 @@
 }
 
 SyncEncryptionHandler* FakeSyncManager::GetEncryptionHandler() {
-  return fake_encryption_handler_.get();
+  return &fake_encryption_handler_;
 }
 
 std::vector<std::unique_ptr<ProtocolEvent>>
diff --git a/components/sync/engine/fake_sync_manager.h b/components/sync/engine/fake_sync_manager.h
index 31f47440..8098e4dc 100644
--- a/components/sync/engine/fake_sync_manager.h
+++ b/components/sync/engine/fake_sync_manager.h
@@ -12,8 +12,10 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
+#include "components/sync/engine/fake_model_type_connector.h"
 #include "components/sync/engine/sync_manager.h"
 #include "components/sync/syncable/test_user_share.h"
+#include "components/sync/test/fake_sync_encryption_handler.h"
 
 namespace base {
 class SequencedTaskRunner;
@@ -55,11 +57,6 @@
   // GetAndResetDownloadedTypes(), or since startup if never called.
   ModelTypeSet GetAndResetDownloadedTypes();
 
-  // Returns those types that have been marked as enabled since the
-  // last call to GetAndResetEnabledTypes(), or since startup if never
-  // called.
-  ModelTypeSet GetAndResetEnabledTypes();
-
   // Returns the types that have most recently received a refresh request.
   ModelTypeSet GetLastRefreshRequestTypes();
 
@@ -85,11 +82,10 @@
                           ModelTypeSet to_journal,
                           ModelTypeSet to_unapply) override;
   void UpdateCredentials(const SyncCredentials& credentials) override;
-  void StartSyncingNormally(const ModelSafeRoutingInfo& routing_info,
-                            base::Time last_poll_time) override;
+  void StartSyncingNormally(base::Time last_poll_time) override;
+  void StartConfiguration() override;
   void ConfigureSyncer(ConfigureReason reason,
                        ModelTypeSet to_download,
-                       const ModelSafeRoutingInfo& new_routing_info,
                        const base::Closure& ready_task,
                        const base::Closure& retry_task) override;
   void OnIncomingInvalidation(
@@ -102,6 +98,7 @@
   void SaveChanges() override;
   void ShutdownOnSyncThread(ShutdownReason reason) override;
   UserShare* GetUserShare() override;
+  ModelTypeConnector* GetModelTypeConnector() override;
   std::unique_ptr<ModelTypeConnector> GetModelTypeConnectorProxy() override;
   const std::string cache_guid() override;
   bool ReceivedExperiment(Experiments* experiments) override;
@@ -140,8 +137,6 @@
   ModelTypeSet unapplied_types_;
   // The set of types that have been downloaded.
   ModelTypeSet downloaded_types_;
-  // The set of types that have been enabled.
-  ModelTypeSet enabled_types_;
 
   // The types for which a refresh was most recently requested.
   ModelTypeSet last_refresh_request_types_;
@@ -149,7 +144,9 @@
   // The most recent configure reason.
   ConfigureReason last_configure_reason_;
 
-  std::unique_ptr<FakeSyncEncryptionHandler> fake_encryption_handler_;
+  FakeModelTypeConnector fake_model_type_connector_;
+
+  FakeSyncEncryptionHandler fake_encryption_handler_;
 
   TestUserShare test_user_share_;
 
diff --git a/components/sync/engine/model_type_configurer.h b/components/sync/engine/model_type_configurer.h
index a913a35..72d0951 100644
--- a/components/sync/engine/model_type_configurer.h
+++ b/components/sync/engine/model_type_configurer.h
@@ -56,6 +56,16 @@
   // Changes the set of data types that are currently being synced.
   virtual void ConfigureDataTypes(ConfigureParams params) = 0;
 
+  // Registers directory type with sync engine. This function creates update
+  // handler for the type and thus needs to be called before ConfigureDataType
+  // that includes the type in |to_download| type set.
+  virtual void RegisterDirectoryDataType(ModelType type,
+                                         ModelSafeGroup group) = 0;
+
+  // Unregisters directory type from sync engine. After this call updates and
+  // local change will not be synced with server.
+  virtual void UnregisterDirectoryDataType(ModelType type) = 0;
+
   // Activates change processing for the given directory data type.  This must
   // be called synchronously with the data type's model association so
   // no changes are dropped between model association and change
diff --git a/components/sync/engine/model_type_connector.h b/components/sync/engine/model_type_connector.h
index 1917100..d95d8de 100644
--- a/components/sync/engine/model_type_connector.h
+++ b/components/sync/engine/model_type_connector.h
@@ -8,13 +8,17 @@
 #include <memory>
 
 #include "components/sync/base/model_type.h"
+#include "components/sync/engine/model_safe_worker.h"
 
 namespace syncer {
 struct ActivationContext;
 
-// An interface into the core parts of sync for USS model types. Handles
-// creating the connection between the ModelTypeWorker (CommitQueue) on the sync
-// side and the (Shared)ModelTypeProcessor on the model type side.
+// An interface into the core parts of sync for model types. By adding/removing
+// types through methods of this interface consumer controls which types will be
+// syncing (receiving updates and committing local changes).
+// In addition it handles creating the connection between the ModelTypeWorker
+// (CommitQueue) on the sync side and the (Shared)ModelTypeProcessor on the
+// model type side for non-blocking types.
 class ModelTypeConnector {
  public:
   ModelTypeConnector();
@@ -24,7 +28,7 @@
   // thread. Note that in production |activation_context| actually owns a
   // processor proxy that forwards calls to the model thread and is safe to call
   // from the sync thread.
-  virtual void ConnectType(
+  virtual void ConnectNonBlockingType(
       ModelType type,
       std::unique_ptr<ActivationContext> activation_context) = 0;
 
@@ -33,7 +37,16 @@
   // This is the sync thread's chance to clear state associated with the type.
   // It also causes the syncer to stop requesting updates for this type, and to
   // abort any in-progress commit requests.
-  virtual void DisconnectType(ModelType type) = 0;
+  virtual void DisconnectNonBlockingType(ModelType type) = 0;
+
+  // Registers directory based type with sync engine. Sync engine will create
+  // update handler and commit contributor objects for this type. It will start
+  // including the type in GetUpdates and commit requests.
+  virtual void RegisterDirectoryType(ModelType type, ModelSafeGroup group) = 0;
+
+  // Unregisters directory based type from sync engine. Type will no longer be
+  // included in communications with server.
+  virtual void UnregisterDirectoryType(ModelType type) = 0;
 };
 
 }  // namespace syncer
diff --git a/components/sync/engine/sync_engine.h b/components/sync/engine/sync_engine.h
index af4e53d0..55cd760c 100644
--- a/components/sync/engine/sync_engine.h
+++ b/components/sync/engine/sync_engine.h
@@ -95,6 +95,11 @@
   // Updates the engine's SyncCredentials.
   virtual void UpdateCredentials(const SyncCredentials& credentials) = 0;
 
+  // Switches sync engine into configuration mode. In this mode only initial
+  // data for newly enabled types is downloaded from server. No local changes
+  // are committed to server.
+  virtual void StartConfiguration() = 0;
+
   // This starts the sync engine running a Syncer object to communicate with
   // sync servers. Until this is called, no changes will leave or enter this
   // browser from the cloud / sync servers.
diff --git a/components/sync/engine/sync_manager.h b/components/sync/engine/sync_manager.h
index 37e7de63..b853b54 100644
--- a/components/sync/engine/sync_manager.h
+++ b/components/sync/engine/sync_manager.h
@@ -304,8 +304,11 @@
   virtual void UpdateCredentials(const SyncCredentials& credentials) = 0;
 
   // Put the syncer in normal mode ready to perform nudges and polls.
-  virtual void StartSyncingNormally(const ModelSafeRoutingInfo& routing_info,
-                                    base::Time last_poll_time) = 0;
+  virtual void StartSyncingNormally(base::Time last_poll_time) = 0;
+
+  // Put syncer in configuration mode. Only configuration sync cycles are
+  // performed. No local changes are committed to the server.
+  virtual void StartConfiguration() = 0;
 
   // Switches the mode of operation to CONFIGURATION_MODE and performs
   // any configuration tasks needed as determined by the params. Once complete,
@@ -317,7 +320,6 @@
   //              does finish.
   virtual void ConfigureSyncer(ConfigureReason reason,
                                ModelTypeSet to_download,
-                               const ModelSafeRoutingInfo& new_routing_info,
                                const base::Closure& ready_task,
                                const base::Closure& retry_task) = 0;
 
@@ -352,7 +354,13 @@
   // May be called from any thread.
   virtual UserShare* GetUserShare() = 0;
 
-  // Returns an instance of the main interface for non-blocking sync types.
+  // Returns non-owning pointer to ModelTypeConnector. In contrast with
+  // ModelTypeConnectorProxy all calls are executed synchronously, thus the
+  // pointer should be used on sync thread.
+  virtual ModelTypeConnector* GetModelTypeConnector() = 0;
+
+  // Returns an instance of the main interface for registering sync types with
+  // sync engine.
   virtual std::unique_ptr<ModelTypeConnector> GetModelTypeConnectorProxy() = 0;
 
   // Returns the cache_guid of the currently open database.
diff --git a/components/sync/engine_impl/cycle/sync_cycle.h b/components/sync/engine_impl/cycle/sync_cycle.h
index e25fad03..72c0789 100644
--- a/components/sync/engine_impl/cycle/sync_cycle.h
+++ b/components/sync/engine_impl/cycle/sync_cycle.h
@@ -16,7 +16,6 @@
 #include "base/time/time.h"
 #include "components/sync/base/model_type.h"
 #include "components/sync/engine/cycle/sync_cycle_snapshot.h"
-#include "components/sync/engine/model_safe_worker.h"
 #include "components/sync/engine_impl/cycle/status_controller.h"
 #include "components/sync/engine_impl/cycle/sync_cycle_context.h"
 #include "components/sync/engine_impl/sync_cycle_event.h"
diff --git a/components/sync/engine_impl/cycle/sync_cycle_context.cc b/components/sync/engine_impl/cycle/sync_cycle_context.cc
index 744fa4bc..c50ea25 100644
--- a/components/sync/engine_impl/cycle/sync_cycle_context.cc
+++ b/components/sync/engine_impl/cycle/sync_cycle_context.cc
@@ -43,9 +43,4 @@
   return model_type_registry_->GetEnabledTypes();
 }
 
-void SyncCycleContext::SetRoutingInfo(
-    const ModelSafeRoutingInfo& routing_info) {
-  model_type_registry_->SetEnabledDirectoryTypes(routing_info);
-}
-
 }  // namespace syncer
diff --git a/components/sync/engine_impl/cycle/sync_cycle_context.h b/components/sync/engine_impl/cycle/sync_cycle_context.h
index b7d50de..1e7e0aef 100644
--- a/components/sync/engine_impl/cycle/sync_cycle_context.h
+++ b/components/sync/engine_impl/cycle/sync_cycle_context.h
@@ -56,8 +56,6 @@
 
   ModelTypeSet GetEnabledTypes() const;
 
-  void SetRoutingInfo(const ModelSafeRoutingInfo& routing_info);
-
   ExtensionsActivity* extensions_activity() {
     return extensions_activity_.get();
   }
diff --git a/components/sync/engine_impl/model_type_connector_proxy.cc b/components/sync/engine_impl/model_type_connector_proxy.cc
index c863117..86adea02 100644
--- a/components/sync/engine_impl/model_type_connector_proxy.cc
+++ b/components/sync/engine_impl/model_type_connector_proxy.cc
@@ -18,19 +18,32 @@
 
 ModelTypeConnectorProxy::~ModelTypeConnectorProxy() {}
 
-void ModelTypeConnectorProxy::ConnectType(
+void ModelTypeConnectorProxy::ConnectNonBlockingType(
     ModelType type,
     std::unique_ptr<ActivationContext> activation_context) {
-  task_runner_->PostTask(
-      FROM_HERE,
-      base::Bind(&ModelTypeConnector::ConnectType, model_type_connector_, type,
-                 base::Passed(&activation_context)));
+  task_runner_->PostTask(FROM_HERE,
+                         base::Bind(&ModelTypeConnector::ConnectNonBlockingType,
+                                    model_type_connector_, type,
+                                    base::Passed(&activation_context)));
 }
 
-void ModelTypeConnectorProxy::DisconnectType(ModelType type) {
+void ModelTypeConnectorProxy::DisconnectNonBlockingType(ModelType type) {
+  task_runner_->PostTask(
+      FROM_HERE, base::Bind(&ModelTypeConnector::DisconnectNonBlockingType,
+                            model_type_connector_, type));
+}
+
+void ModelTypeConnectorProxy::RegisterDirectoryType(ModelType type,
+                                                    ModelSafeGroup group) {
   task_runner_->PostTask(FROM_HERE,
-                         base::Bind(&ModelTypeConnector::DisconnectType,
-                                    model_type_connector_, type));
+                         base::Bind(&ModelTypeConnector::RegisterDirectoryType,
+                                    model_type_connector_, type, group));
+}
+
+void ModelTypeConnectorProxy::UnregisterDirectoryType(ModelType type) {
+  task_runner_->PostTask(
+      FROM_HERE, base::Bind(&ModelTypeConnector::UnregisterDirectoryType,
+                            model_type_connector_, type));
 }
 
 }  // namespace syncer
diff --git a/components/sync/engine_impl/model_type_connector_proxy.h b/components/sync/engine_impl/model_type_connector_proxy.h
index ddf751b..6c2e4a9 100644
--- a/components/sync/engine_impl/model_type_connector_proxy.h
+++ b/components/sync/engine_impl/model_type_connector_proxy.h
@@ -10,6 +10,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/sequenced_task_runner.h"
 #include "components/sync/base/model_type.h"
+#include "components/sync/engine/model_safe_worker.h"
 #include "components/sync/engine/model_type_connector.h"
 
 namespace syncer {
@@ -25,10 +26,12 @@
   ~ModelTypeConnectorProxy() override;
 
   // ModelTypeConnector implementation
-  void ConnectType(
+  void ConnectNonBlockingType(
       ModelType type,
       std::unique_ptr<ActivationContext> activation_context) override;
-  void DisconnectType(ModelType type) override;
+  void DisconnectNonBlockingType(ModelType type) override;
+  void RegisterDirectoryType(ModelType type, ModelSafeGroup group) override;
+  void UnregisterDirectoryType(ModelType type) override;
 
  private:
   // A SequencedTaskRunner representing the thread where the ModelTypeConnector
diff --git a/components/sync/engine_impl/model_type_registry.cc b/components/sync/engine_impl/model_type_registry.cc
index 72450f4..7d15dd6 100644
--- a/components/sync/engine_impl/model_type_registry.cc
+++ b/components/sync/engine_impl/model_type_registry.cc
@@ -71,72 +71,7 @@
 
 ModelTypeRegistry::~ModelTypeRegistry() {}
 
-void ModelTypeRegistry::SetEnabledDirectoryTypes(
-    const ModelSafeRoutingInfo& routing_info) {
-  // Remove all existing directory processors and delete them.  The
-  // DebugInfoEmitters are not deleted here, since we want to preserve their
-  // counters.
-  for (ModelTypeSet::Iterator it = enabled_directory_types_.First(); it.Good();
-       it.Inc()) {
-    size_t result1 = update_handler_map_.erase(it.Get());
-    size_t result2 = commit_contributor_map_.erase(it.Get());
-    DCHECK_EQ(1U, result1);
-    DCHECK_EQ(1U, result2);
-  }
-
-  // Clear the old instances of directory update handlers and commit
-  // contributors, deleting their contents in the processs.
-  directory_update_handlers_.clear();
-  directory_commit_contributors_.clear();
-
-  enabled_directory_types_.Clear();
-
-  // Create new ones and add them to the appropriate containers.
-  for (const auto& routing_kv : routing_info) {
-    ModelType type = routing_kv.first;
-    ModelSafeGroup group = routing_kv.second;
-    if (group == GROUP_NON_BLOCKING)
-      continue;
-
-    std::map<ModelSafeGroup, scoped_refptr<ModelSafeWorker>>::iterator
-        worker_it = workers_map_.find(group);
-    DCHECK(worker_it != workers_map_.end());
-    scoped_refptr<ModelSafeWorker> worker = worker_it->second;
-
-    DataTypeDebugInfoEmitter* emitter = GetEmitter(type);
-    if (emitter == nullptr) {
-      auto new_emitter = base::MakeUnique<DirectoryTypeDebugInfoEmitter>(
-          directory(), type, &type_debug_info_observers_);
-      emitter = new_emitter.get();
-      data_type_debug_info_emitter_map_.insert(
-          std::make_pair(type, std::move(new_emitter)));
-    }
-
-    auto updater = base::MakeUnique<DirectoryUpdateHandler>(directory(), type,
-                                                            worker, emitter);
-    bool updater_inserted =
-        update_handler_map_.insert(std::make_pair(type, updater.get())).second;
-    DCHECK(updater_inserted)
-        << "Attempt to override existing type handler in map";
-    directory_update_handlers_.push_back(std::move(updater));
-
-    auto committer = base::MakeUnique<DirectoryCommitContributor>(
-        directory(), type, emitter);
-    bool committer_inserted =
-        commit_contributor_map_.insert(std::make_pair(type, committer.get()))
-            .second;
-    DCHECK(committer_inserted)
-        << "Attempt to override existing type handler in map";
-    directory_commit_contributors_.push_back(std::move(committer));
-
-    enabled_directory_types_.Put(type);
-  }
-
-  DCHECK(Intersection(GetEnabledDirectoryTypes(), GetEnabledNonBlockingTypes())
-             .Empty());
-}
-
-void ModelTypeRegistry::ConnectType(
+void ModelTypeRegistry::ConnectNonBlockingType(
     ModelType type,
     std::unique_ptr<ActivationContext> activation_context) {
   DCHECK(update_handler_map_.find(type) == update_handler_map_.end());
@@ -204,7 +139,7 @@
              .Empty());
 }
 
-void ModelTypeRegistry::DisconnectType(ModelType type) {
+void ModelTypeRegistry::DisconnectNonBlockingType(ModelType type) {
   DVLOG(1) << "Disabling an off-thread sync type: " << ModelTypeToString(type);
   DCHECK(update_handler_map_.find(type) != update_handler_map_.end());
   DCHECK(commit_contributor_map_.find(type) != commit_contributor_map_.end());
@@ -225,6 +160,58 @@
   }
 }
 
+void ModelTypeRegistry::RegisterDirectoryType(ModelType type,
+                                              ModelSafeGroup group) {
+  DCHECK(update_handler_map_.find(type) == update_handler_map_.end());
+  DCHECK(commit_contributor_map_.find(type) == commit_contributor_map_.end());
+  DCHECK(directory_update_handlers_.find(type) ==
+         directory_update_handlers_.end());
+  DCHECK(directory_commit_contributors_.find(type) ==
+         directory_commit_contributors_.end());
+  DCHECK(data_type_debug_info_emitter_map_.find(type) ==
+         data_type_debug_info_emitter_map_.end());
+  DCHECK_NE(GROUP_NON_BLOCKING, group);
+  DCHECK(workers_map_.find(group) != workers_map_.end());
+
+  auto worker = workers_map_.find(group)->second;
+  DCHECK(GetEmitter(type) == nullptr);
+  auto owned_emitter = base::MakeUnique<DirectoryTypeDebugInfoEmitter>(
+      directory(), type, &type_debug_info_observers_);
+  DataTypeDebugInfoEmitter* emitter_ptr = owned_emitter.get();
+  data_type_debug_info_emitter_map_[type] = std::move(owned_emitter);
+
+  auto updater = base::MakeUnique<DirectoryUpdateHandler>(directory(), type,
+                                                          worker, emitter_ptr);
+  auto committer = base::MakeUnique<DirectoryCommitContributor>(
+      directory(), type, emitter_ptr);
+
+  update_handler_map_[type] = updater.get();
+  commit_contributor_map_[type] = committer.get();
+
+  directory_update_handlers_[type] = std::move(updater);
+  directory_commit_contributors_[type] = std::move(committer);
+
+  DCHECK(Intersection(GetEnabledDirectoryTypes(), GetEnabledNonBlockingTypes())
+             .Empty());
+}
+
+void ModelTypeRegistry::UnregisterDirectoryType(ModelType type) {
+  DCHECK(update_handler_map_.find(type) != update_handler_map_.end());
+  DCHECK(commit_contributor_map_.find(type) != commit_contributor_map_.end());
+  DCHECK(directory_update_handlers_.find(type) !=
+         directory_update_handlers_.end());
+  DCHECK(directory_commit_contributors_.find(type) !=
+         directory_commit_contributors_.end());
+  DCHECK(data_type_debug_info_emitter_map_.find(type) !=
+         data_type_debug_info_emitter_map_.end());
+
+  update_handler_map_.erase(type);
+  commit_contributor_map_.erase(type);
+  directory_update_handlers_.erase(type);
+  directory_commit_contributors_.erase(type);
+  data_type_debug_info_emitter_map_.erase(type);
+}
+
 ModelTypeSet ModelTypeRegistry::GetEnabledTypes() const {
   return Union(GetEnabledDirectoryTypes(), GetEnabledNonBlockingTypes());
 }
@@ -351,7 +338,10 @@
 }
 
 ModelTypeSet ModelTypeRegistry::GetEnabledDirectoryTypes() const {
-  return enabled_directory_types_;
+  ModelTypeSet enabled_directory_types;
+  for (const auto& kv : directory_update_handlers_)
+    enabled_directory_types.Put(kv.first);
+  return enabled_directory_types;
 }
 
 ModelTypeSet ModelTypeRegistry::GetEnabledNonBlockingTypes() const {
diff --git a/components/sync/engine_impl/model_type_registry.h b/components/sync/engine_impl/model_type_registry.h
index 5229f0d6..3e25257 100644
--- a/components/sync/engine_impl/model_type_registry.h
+++ b/components/sync/engine_impl/model_type_registry.h
@@ -38,21 +38,17 @@
 class ModelTypeRegistry : public ModelTypeConnector,
                           public SyncEncryptionHandler::Observer {
  public:
-  // Constructs a ModelTypeRegistry that supports directory types.
   ModelTypeRegistry(const std::vector<scoped_refptr<ModelSafeWorker>>& workers,
                     UserShare* user_share,
                     NudgeHandler* nudge_handler,
                     const UssMigrator& uss_migrator);
   ~ModelTypeRegistry() override;
 
-  // Sets the set of enabled types.
-  void SetEnabledDirectoryTypes(const ModelSafeRoutingInfo& routing_info);
-
   // Enables an off-thread type for syncing.  Connects the given proxy
   // and its task_runner to the newly created worker.
   //
   // Expects that the proxy's ModelType is not currently enabled.
-  void ConnectType(
+  void ConnectNonBlockingType(
       ModelType type,
       std::unique_ptr<ActivationContext> activation_context) override;
 
@@ -60,7 +56,15 @@
   //
   // Expects that the type is currently enabled.
   // Deletes the worker associated with the type.
-  void DisconnectType(ModelType type) override;
+  void DisconnectNonBlockingType(ModelType type) override;
+
+  // Creates update handler and commit contributor objects for directory type.
+  // Expects that the type is not yet registered.
+  void RegisterDirectoryType(ModelType type, ModelSafeGroup group) override;
+
+  // Deletes objects related to directory type. Expects that the type is
+  // registered.
+  void UnregisterDirectoryType(ModelType type) override;
 
   // Implementation of SyncEncryptionHandler::Observer.
   void OnPassphraseRequired(
@@ -118,9 +122,9 @@
   }
 
   // Sets of handlers and contributors.
-  std::vector<std::unique_ptr<DirectoryCommitContributor>>
+  std::map<ModelType, std::unique_ptr<DirectoryCommitContributor>>
       directory_commit_contributors_;
-  std::vector<std::unique_ptr<DirectoryUpdateHandler>>
+  std::map<ModelType, std::unique_ptr<DirectoryUpdateHandler>>
       directory_update_handlers_;
 
   std::vector<std::unique_ptr<ModelTypeWorker>> model_type_workers_;
@@ -149,9 +153,6 @@
   // The NudgeHandler.  Not owned.
   NudgeHandler* nudge_handler_;
 
-  // The set of enabled directory types.
-  ModelTypeSet enabled_directory_types_;
-
   // Function to call to migrate data from the directory to USS.
   UssMigrator uss_migrator_;
 
diff --git a/components/sync/engine_impl/model_type_registry_unittest.cc b/components/sync/engine_impl/model_type_registry_unittest.cc
index 39d6548e..0b72864 100644
--- a/components/sync/engine_impl/model_type_registry_unittest.cc
+++ b/components/sync/engine_impl/model_type_registry_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/deferred_sequenced_task_runner.h"
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
+#include "base/test/gtest_util.h"
 #include "components/sync/engine/activation_context.h"
 #include "components/sync/engine/fake_model_type_processor.h"
 #include "components/sync/protocol/model_type_state.pb.h"
@@ -24,11 +25,29 @@
 
 class ModelTypeRegistryTest : public ::testing::Test {
  public:
-  ModelTypeRegistryTest();
-  void SetUp() override;
-  void TearDown() override;
+  void SetUp() override {
+    test_user_share_.SetUp();
+    scoped_refptr<ModelSafeWorker> passive_worker(
+        new FakeModelWorker(GROUP_PASSIVE));
+    scoped_refptr<ModelSafeWorker> ui_worker(new FakeModelWorker(GROUP_UI));
+    scoped_refptr<ModelSafeWorker> db_worker(new FakeModelWorker(GROUP_DB));
+    workers_.push_back(passive_worker);
+    workers_.push_back(ui_worker);
+    workers_.push_back(db_worker);
 
-  ModelTypeRegistry* registry();
+    registry_ = base::MakeUnique<ModelTypeRegistry>(
+        workers_, test_user_share_.user_share(), &mock_nudge_handler_,
+        base::Bind(&ModelTypeRegistryTest::MigrateDirectory,
+                   base::Unretained(this)));
+  }
+
+  void TearDown() override {
+    registry_.reset();
+    workers_.clear();
+    test_user_share_.TearDown();
+  }
+
+  ModelTypeRegistry* registry() { return registry_.get(); }
 
   static sync_pb::ModelTypeState MakeInitialModelTypeState(ModelType type) {
     sync_pb::ModelTypeState state;
@@ -66,7 +85,9 @@
     return true;
   }
 
-  syncable::Directory* directory();
+  syncable::Directory* directory() {
+    return test_user_share_.user_share()->directory.get();
+  }
 
   base::MessageLoop message_loop_;
 
@@ -77,127 +98,51 @@
   bool migration_attempted_ = false;
 };
 
-ModelTypeRegistryTest::ModelTypeRegistryTest() {}
-
-void ModelTypeRegistryTest::SetUp() {
-  test_user_share_.SetUp();
-  scoped_refptr<ModelSafeWorker> passive_worker(
-      new FakeModelWorker(GROUP_PASSIVE));
-  scoped_refptr<ModelSafeWorker> ui_worker(new FakeModelWorker(GROUP_UI));
-  scoped_refptr<ModelSafeWorker> db_worker(new FakeModelWorker(GROUP_DB));
-  workers_.push_back(passive_worker);
-  workers_.push_back(ui_worker);
-  workers_.push_back(db_worker);
-
-  registry_ = base::MakeUnique<ModelTypeRegistry>(
-      workers_, test_user_share_.user_share(), &mock_nudge_handler_,
-      base::Bind(&ModelTypeRegistryTest::MigrateDirectory,
-                 base::Unretained(this)));
-}
-
-void ModelTypeRegistryTest::TearDown() {
-  registry_.reset();
-  workers_.clear();
-  test_user_share_.TearDown();
-}
-
-ModelTypeRegistry* ModelTypeRegistryTest::registry() {
-  return registry_.get();
-}
-
-syncable::Directory* ModelTypeRegistryTest::directory() {
-  return test_user_share_.user_share()->directory.get();
-}
-
-// Create some directory update handlers and commit contributors.
-//
-// We don't get to inspect any of the state we're modifying.  This test is
-// useful only for detecting crashes or memory leaks.
-TEST_F(ModelTypeRegistryTest, SetEnabledDirectoryTypes_Once) {
-  ModelSafeRoutingInfo routing_info;
-  routing_info.insert(std::make_pair(NIGORI, GROUP_PASSIVE));
-  routing_info.insert(std::make_pair(BOOKMARKS, GROUP_UI));
-  routing_info.insert(std::make_pair(AUTOFILL, GROUP_DB));
-  routing_info.insert(std::make_pair(APPS, GROUP_NON_BLOCKING));
-
-  registry()->SetEnabledDirectoryTypes(routing_info);
-
+// Tests operations with directory types.
+// Registering/unregistering type should affect enabled types and handlers map.
+// Registering/unregistering type twice should trigger DCHECK.
+// Registering type with unknown ModelSafeGroup should trigger DCHECK.
+TEST_F(ModelTypeRegistryTest, DirectoryTypes) {
   UpdateHandlerMap* update_handler_map = registry()->update_handler_map();
-  // Apps is non-blocking type, SetEnabledDirectoryTypes shouldn't instantiate
-  // update_handler for it.
-  EXPECT_TRUE(update_handler_map->find(APPS) == update_handler_map->end());
-}
+  EXPECT_TRUE(registry()->GetEnabledTypes().Empty());
 
-// Try two different routing info settings.
-//
-// We don't get to inspect any of the state we're modifying.  This test is
-// useful only for detecting crashes or memory leaks.
-TEST_F(ModelTypeRegistryTest, SetEnabledDirectoryTypes_Repeatedly) {
-  ModelSafeRoutingInfo routing_info1;
-  routing_info1.insert(std::make_pair(NIGORI, GROUP_PASSIVE));
-  routing_info1.insert(std::make_pair(BOOKMARKS, GROUP_PASSIVE));
-  routing_info1.insert(std::make_pair(AUTOFILL, GROUP_PASSIVE));
-  routing_info1.insert(std::make_pair(APPS, GROUP_NON_BLOCKING));
+  registry()->RegisterDirectoryType(AUTOFILL, GROUP_DB);
+  EXPECT_EQ(ModelTypeSet(AUTOFILL), registry()->GetEnabledTypes());
 
-  registry()->SetEnabledDirectoryTypes(routing_info1);
+  registry()->RegisterDirectoryType(BOOKMARKS, GROUP_UI);
+  EXPECT_EQ(ModelTypeSet(AUTOFILL, BOOKMARKS), registry()->GetEnabledTypes());
 
-  ModelSafeRoutingInfo routing_info2;
-  routing_info2.insert(std::make_pair(NIGORI, GROUP_PASSIVE));
-  routing_info2.insert(std::make_pair(BOOKMARKS, GROUP_UI));
-  routing_info2.insert(std::make_pair(AUTOFILL, GROUP_DB));
-  routing_info2.insert(std::make_pair(APPS, GROUP_NON_BLOCKING));
+  // Try registering already registered type.
+  EXPECT_DCHECK_DEATH(registry()->RegisterDirectoryType(BOOKMARKS, GROUP_UI));
 
-  registry()->SetEnabledDirectoryTypes(routing_info2);
-}
+  EXPECT_TRUE(update_handler_map->find(AUTOFILL) != update_handler_map->end());
+  EXPECT_TRUE(update_handler_map->find(BOOKMARKS) != update_handler_map->end());
 
-// Test removing all types from the list.
-//
-// We don't get to inspect any of the state we're modifying.  This test is
-// useful only for detecting crashes or memory leaks.
-TEST_F(ModelTypeRegistryTest, SetEnabledDirectoryTypes_Clear) {
-  ModelSafeRoutingInfo routing_info1;
-  routing_info1.insert(std::make_pair(NIGORI, GROUP_PASSIVE));
-  routing_info1.insert(std::make_pair(BOOKMARKS, GROUP_UI));
-  routing_info1.insert(std::make_pair(AUTOFILL, GROUP_DB));
-  routing_info1.insert(std::make_pair(APPS, GROUP_NON_BLOCKING));
+  registry()->UnregisterDirectoryType(AUTOFILL);
+  EXPECT_EQ(ModelTypeSet(BOOKMARKS), registry()->GetEnabledTypes());
+  EXPECT_TRUE(update_handler_map->find(AUTOFILL) == update_handler_map->end());
+  EXPECT_TRUE(update_handler_map->find(BOOKMARKS) != update_handler_map->end());
 
-  registry()->SetEnabledDirectoryTypes(routing_info1);
+  // Try unregistering already unregistered type.
+  EXPECT_DCHECK_DEATH(registry()->UnregisterDirectoryType(AUTOFILL));
 
-  ModelSafeRoutingInfo routing_info2;
-  registry()->SetEnabledDirectoryTypes(routing_info2);
-}
-
-// Test disabling then re-enabling some directory types.
-//
-// We don't get to inspect any of the state we're modifying.  This test is
-// useful only for detecting crashes or memory leaks.
-TEST_F(ModelTypeRegistryTest, SetEnabledDirectoryTypes_OffAndOn) {
-  ModelSafeRoutingInfo routing_info1;
-  routing_info1.insert(std::make_pair(NIGORI, GROUP_PASSIVE));
-  routing_info1.insert(std::make_pair(BOOKMARKS, GROUP_UI));
-  routing_info1.insert(std::make_pair(AUTOFILL, GROUP_DB));
-  routing_info1.insert(std::make_pair(APPS, GROUP_NON_BLOCKING));
-
-  registry()->SetEnabledDirectoryTypes(routing_info1);
-
-  ModelSafeRoutingInfo routing_info2;
-  registry()->SetEnabledDirectoryTypes(routing_info2);
-
-  registry()->SetEnabledDirectoryTypes(routing_info1);
+  // Try registering type with unknown worker.
+  EXPECT_DCHECK_DEATH(
+      registry()->RegisterDirectoryType(SESSIONS, GROUP_HISTORY));
 }
 
 TEST_F(ModelTypeRegistryTest, NonBlockingTypes) {
   EXPECT_TRUE(registry()->GetEnabledTypes().Empty());
 
-  registry()->ConnectType(
+  registry()->ConnectNonBlockingType(
       THEMES, MakeActivationContext(MakeInitialModelTypeState(THEMES)));
   EXPECT_EQ(ModelTypeSet(THEMES), registry()->GetEnabledTypes());
 
-  registry()->ConnectType(
+  registry()->ConnectNonBlockingType(
       SESSIONS, MakeActivationContext(MakeInitialModelTypeState(SESSIONS)));
   EXPECT_EQ(ModelTypeSet(THEMES, SESSIONS), registry()->GetEnabledTypes());
 
-  registry()->DisconnectType(THEMES);
+  registry()->DisconnectNonBlockingType(THEMES);
   EXPECT_EQ(ModelTypeSet(SESSIONS), registry()->GetEnabledTypes());
 
   // Allow ModelTypeRegistry destruction to delete the
@@ -205,73 +150,70 @@
 }
 
 TEST_F(ModelTypeRegistryTest, NonBlockingTypesWithDirectoryTypes) {
-  ModelSafeRoutingInfo routing_info1;
-  routing_info1.insert(std::make_pair(NIGORI, GROUP_PASSIVE));
-  routing_info1.insert(std::make_pair(BOOKMARKS, GROUP_UI));
-  routing_info1.insert(std::make_pair(AUTOFILL, GROUP_DB));
-  routing_info1.insert(std::make_pair(THEMES, GROUP_NON_BLOCKING));
-  routing_info1.insert(std::make_pair(SESSIONS, GROUP_NON_BLOCKING));
-
   ModelTypeSet directory_types(NIGORI, BOOKMARKS, AUTOFILL);
 
   ModelTypeSet current_types;
   EXPECT_TRUE(registry()->GetEnabledTypes().Empty());
 
   // Add the themes non-blocking type.
-  registry()->ConnectType(
+  registry()->ConnectNonBlockingType(
       THEMES, MakeActivationContext(MakeInitialModelTypeState(THEMES)));
   current_types.Put(THEMES);
   EXPECT_EQ(current_types, registry()->GetEnabledTypes());
 
   // Add some directory types.
-  registry()->SetEnabledDirectoryTypes(routing_info1);
+  for (auto it = directory_types.First(); it.Good(); it.Inc())
+    registry()->RegisterDirectoryType(it.Get(), GROUP_PASSIVE);
   current_types.PutAll(directory_types);
   EXPECT_EQ(current_types, registry()->GetEnabledTypes());
 
   // Add sessions non-blocking type.
-  registry()->ConnectType(
+  registry()->ConnectNonBlockingType(
       SESSIONS, MakeActivationContext(MakeInitialModelTypeState(SESSIONS)));
   current_types.Put(SESSIONS);
   EXPECT_EQ(current_types, registry()->GetEnabledTypes());
 
   // Remove themes non-blocking type.
-  registry()->DisconnectType(THEMES);
+  registry()->DisconnectNonBlockingType(THEMES);
   current_types.Remove(THEMES);
   EXPECT_EQ(current_types, registry()->GetEnabledTypes());
 
   // Clear all directory types.
-  ModelSafeRoutingInfo routing_info2;
-  registry()->SetEnabledDirectoryTypes(routing_info2);
+  for (auto it = directory_types.First(); it.Good(); it.Inc())
+    registry()->UnregisterDirectoryType(it.Get());
   current_types.RemoveAll(directory_types);
   EXPECT_EQ(current_types, registry()->GetEnabledTypes());
 }
 
 // Tests correct result returned from GetInitialSyncEndedTypes.
 TEST_F(ModelTypeRegistryTest, GetInitialSyncEndedTypes) {
-  ModelSafeRoutingInfo routing_info;
-  // Add two directory and two non-blocking types.
-  routing_info.insert(std::make_pair(AUTOFILL, GROUP_PASSIVE));
-  routing_info.insert(std::make_pair(BOOKMARKS, GROUP_PASSIVE));
-  routing_info.insert(std::make_pair(THEMES, GROUP_NON_BLOCKING));
-  routing_info.insert(std::make_pair(SESSIONS, GROUP_NON_BLOCKING));
-  registry()->SetEnabledDirectoryTypes(routing_info);
+  // Add two directory types.
+  registry()->RegisterDirectoryType(AUTOFILL, GROUP_PASSIVE);
+  registry()->RegisterDirectoryType(BOOKMARKS, GROUP_PASSIVE);
 
   // Only Autofill and Themes types finished initial sync.
   MarkInitialSyncEndedForDirectoryType(AUTOFILL);
 
+  // Add two non-blocking type.
   sync_pb::ModelTypeState model_type_state = MakeInitialModelTypeState(THEMES);
   model_type_state.set_initial_sync_done(true);
-  registry()->ConnectType(THEMES, MakeActivationContext(model_type_state));
+  registry()->ConnectNonBlockingType(THEMES,
+                                     MakeActivationContext(model_type_state));
+
+  registry()->ConnectNonBlockingType(
+      SESSIONS, MakeActivationContext(MakeInitialModelTypeState(SESSIONS)));
 
   EXPECT_EQ(ModelTypeSet(AUTOFILL, THEMES),
             registry()->GetInitialSyncEndedTypes());
 }
 
+// Tests that when directory data is present for type ConnectNonBlockingType
+// triggers USS migration.
 TEST_F(ModelTypeRegistryTest, UssMigrationAttempted) {
   EXPECT_FALSE(migration_attempted());
 
   MarkInitialSyncEndedForDirectoryType(THEMES);
-  registry()->ConnectType(
+  registry()->ConnectNonBlockingType(
       THEMES, MakeActivationContext(MakeInitialModelTypeState(THEMES)));
 
   EXPECT_TRUE(migration_attempted());
diff --git a/components/sync/engine_impl/sync_manager_impl.cc b/components/sync/engine_impl/sync_manager_impl.cc
index e52090e..b4f52a50 100644
--- a/components/sync/engine_impl/sync_manager_impl.cc
+++ b/components/sync/engine_impl/sync_manager_impl.cc
@@ -182,7 +182,6 @@
 void SyncManagerImpl::ConfigureSyncer(
     ConfigureReason reason,
     ModelTypeSet to_download,
-    const ModelSafeRoutingInfo& new_routing_info,
     const base::Closure& ready_task,
     const base::Closure& retry_task) {
   DCHECK(thread_checker_.CalledOnValidThread());
@@ -195,12 +194,9 @@
 
   DVLOG(1) << "Configuring -"
            << "\n\t"
-           << "current types: "
-           << ModelTypeSetToString(GetRoutingInfoTypes(new_routing_info))
-           << "\n\t"
            << "types to download: " << ModelTypeSetToString(to_download);
   ConfigurationParams params(GetSourceFromReason(reason), to_download,
-                             new_routing_info, ready_task, retry_task);
+                             ready_task, retry_task);
 
   scheduler_->Start(SyncScheduler::CONFIGURATION_MODE, base::Time());
   scheduler_->ScheduleConfiguration(params);
@@ -396,18 +392,17 @@
     const SyncEncryptionHandler::NigoriState& nigori_state) {}
 
 void SyncManagerImpl::StartSyncingNormally(
-    const ModelSafeRoutingInfo& routing_info,
     base::Time last_poll_time) {
   // Start the sync scheduler.
-  // TODO(sync): We always want the newest set of routes when we switch back
-  // to normal mode. Figure out how to enforce set_routing_info is always
-  // appropriately set and that it's only modified when switching to normal
-  // mode.
   DCHECK(thread_checker_.CalledOnValidThread());
-  cycle_context_->SetRoutingInfo(routing_info);
   scheduler_->Start(SyncScheduler::NORMAL_MODE, last_poll_time);
 }
 
+void SyncManagerImpl::StartConfiguration() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  scheduler_->Start(SyncScheduler::CONFIGURATION_MODE, base::Time());
+}
+
 syncable::Directory* SyncManagerImpl::directory() {
   return share_.directory.get();
 }
@@ -906,6 +901,11 @@
   return &share_;
 }
 
+ModelTypeConnector* SyncManagerImpl::GetModelTypeConnector() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  return model_type_registry_.get();
+}
+
 std::unique_ptr<ModelTypeConnector>
 SyncManagerImpl::GetModelTypeConnectorProxy() {
   DCHECK(initialized_);
diff --git a/components/sync/engine_impl/sync_manager_impl.h b/components/sync/engine_impl/sync_manager_impl.h
index 4b396e9..6dbc42a 100644
--- a/components/sync/engine_impl/sync_manager_impl.h
+++ b/components/sync/engine_impl/sync_manager_impl.h
@@ -72,11 +72,10 @@
                           ModelTypeSet to_journal,
                           ModelTypeSet to_unapply) override;
   void UpdateCredentials(const SyncCredentials& credentials) override;
-  void StartSyncingNormally(const ModelSafeRoutingInfo& routing_info,
-                            base::Time last_poll_time) override;
+  void StartSyncingNormally(base::Time last_poll_time) override;
+  void StartConfiguration() override;
   void ConfigureSyncer(ConfigureReason reason,
                        ModelTypeSet to_download,
-                       const ModelSafeRoutingInfo& new_routing_info,
                        const base::Closure& ready_task,
                        const base::Closure& retry_task) override;
   void SetInvalidatorEnabled(bool invalidator_enabled) override;
@@ -89,6 +88,7 @@
   void SaveChanges() override;
   void ShutdownOnSyncThread(ShutdownReason reason) override;
   UserShare* GetUserShare() override;
+  ModelTypeConnector* GetModelTypeConnector() override;
   std::unique_ptr<ModelTypeConnector> GetModelTypeConnectorProxy() override;
   const std::string cache_guid() override;
   bool ReceivedExperiment(Experiments* experiments) override;
diff --git a/components/sync/engine_impl/sync_manager_impl_unittest.cc b/components/sync/engine_impl/sync_manager_impl_unittest.cc
index afaab93..8bf15f9 100644
--- a/components/sync/engine_impl/sync_manager_impl_unittest.cc
+++ b/components/sync/engine_impl/sync_manager_impl_unittest.cc
@@ -1017,8 +1017,6 @@
     EXPECT_FALSE(js_backend_.IsInitialized());
 
     std::vector<scoped_refptr<ModelSafeWorker>> workers;
-    ModelSafeRoutingInfo routing_info;
-    GetModelSafeRoutingInfo(&routing_info);
 
     // This works only because all routing info types are GROUP_PASSIVE.
     // If we had types in other groups, we would need additional workers
@@ -1052,10 +1050,10 @@
     EXPECT_EQ(EngineComponentsFactory::STORAGE_ON_DISK, storage_used_);
 
     if (initialization_succeeded_) {
-      for (ModelSafeRoutingInfo::iterator i = routing_info.begin();
-           i != routing_info.end(); ++i) {
-        type_roots_[i->first] =
-            MakeTypeRoot(sync_manager_.GetUserShare(), i->first);
+      ModelTypeSet enabled_types = GetEnabledTypes();
+      for (auto it = enabled_types.First(); it.Good(); it.Inc()) {
+        type_roots_[it.Get()] =
+            MakeTypeRoot(sync_manager_.GetUserShare(), it.Get());
       }
     }
 
@@ -1071,23 +1069,20 @@
     PumpLoop();
   }
 
-  void GetModelSafeRoutingInfo(ModelSafeRoutingInfo* out) {
-    (*out)[NIGORI] = GROUP_PASSIVE;
-    (*out)[DEVICE_INFO] = GROUP_PASSIVE;
-    (*out)[EXPERIMENTS] = GROUP_PASSIVE;
-    (*out)[BOOKMARKS] = GROUP_PASSIVE;
-    (*out)[THEMES] = GROUP_PASSIVE;
-    (*out)[SESSIONS] = GROUP_PASSIVE;
-    (*out)[PASSWORDS] = GROUP_PASSIVE;
-    (*out)[PREFERENCES] = GROUP_PASSIVE;
-    (*out)[PRIORITY_PREFERENCES] = GROUP_PASSIVE;
-    (*out)[ARTICLES] = GROUP_PASSIVE;
-  }
-
   ModelTypeSet GetEnabledTypes() {
-    ModelSafeRoutingInfo routing_info;
-    GetModelSafeRoutingInfo(&routing_info);
-    return GetRoutingInfoTypes(routing_info);
+    ModelTypeSet enabled_types;
+    enabled_types.Put(NIGORI);
+    enabled_types.Put(DEVICE_INFO);
+    enabled_types.Put(EXPERIMENTS);
+    enabled_types.Put(BOOKMARKS);
+    enabled_types.Put(THEMES);
+    enabled_types.Put(SESSIONS);
+    enabled_types.Put(PASSWORDS);
+    enabled_types.Put(PREFERENCES);
+    enabled_types.Put(PRIORITY_PREFERENCES);
+    enabled_types.Put(ARTICLES);
+
+    return enabled_types;
   }
 
   void OnChangesApplied(ModelType model_type,
@@ -2663,18 +2658,15 @@
 // Verify transaction version of a model type is incremented when node of
 // that type is updated.
 TEST_F(SyncManagerTest, IncrementTransactionVersion) {
-  ModelSafeRoutingInfo routing_info;
-  GetModelSafeRoutingInfo(&routing_info);
-
   {
     ReadTransaction read_trans(FROM_HERE, sync_manager_.GetUserShare());
-    for (ModelSafeRoutingInfo::iterator i = routing_info.begin();
-         i != routing_info.end(); ++i) {
+    ModelTypeSet enabled_types = GetEnabledTypes();
+    for (auto it = enabled_types.First(); it.Good(); it.Inc()) {
       // Transaction version is incremented when SyncManagerTest::SetUp()
       // creates a node of each type.
       EXPECT_EQ(1,
                 sync_manager_.GetUserShare()->directory->GetTransactionVersion(
-                    i->first));
+                    it.Get()));
     }
   }
 
@@ -2688,11 +2680,11 @@
 
   {
     ReadTransaction read_trans(FROM_HERE, sync_manager_.GetUserShare());
-    for (ModelSafeRoutingInfo::iterator i = routing_info.begin();
-         i != routing_info.end(); ++i) {
-      EXPECT_EQ(i->first == BOOKMARKS ? 2 : 1,
+    ModelTypeSet enabled_types = GetEnabledTypes();
+    for (auto it = enabled_types.First(); it.Good(); it.Inc()) {
+      EXPECT_EQ(it.Get() == BOOKMARKS ? 2 : 1,
                 sync_manager_.GetUserShare()->directory->GetTransactionVersion(
-                    i->first));
+                    it.Get()));
     }
   }
 }
@@ -2783,21 +2775,34 @@
 };
 
 // Test that the configuration params are properly created and sent to
-// ScheduleConfigure. No callback should be invoked. Any disabled datatypes
-// should be purged.
+// ScheduleConfigure. No callback should be invoked.
 TEST_F(SyncManagerTestWithMockScheduler, BasicConfiguration) {
   ConfigureReason reason = CONFIGURE_REASON_RECONFIGURATION;
   ModelTypeSet types_to_download(BOOKMARKS, PREFERENCES);
-  ModelSafeRoutingInfo new_routing_info;
-  GetModelSafeRoutingInfo(&new_routing_info);
-  ModelTypeSet enabled_types = GetRoutingInfoTypes(new_routing_info);
-  ModelTypeSet disabled_types = Difference(ModelTypeSet::All(), enabled_types);
 
   ConfigurationParams params;
   EXPECT_CALL(*scheduler(), Start(SyncScheduler::CONFIGURATION_MODE, _));
   EXPECT_CALL(*scheduler(), ScheduleConfiguration(_))
       .WillOnce(SaveArg<0>(&params));
 
+  CallbackCounter ready_task_counter, retry_task_counter;
+  sync_manager_.ConfigureSyncer(
+      reason, types_to_download,
+      base::Bind(&CallbackCounter::Callback,
+                 base::Unretained(&ready_task_counter)),
+      base::Bind(&CallbackCounter::Callback,
+                 base::Unretained(&retry_task_counter)));
+  EXPECT_EQ(0, ready_task_counter.times_called());
+  EXPECT_EQ(0, retry_task_counter.times_called());
+  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::RECONFIGURATION, params.source);
+  EXPECT_EQ(types_to_download, params.types_to_download);
+}
+
+// Test that PurgeDisabledTypes only purges recently disabled types leaving
+// others intact.
+TEST_F(SyncManagerTestWithMockScheduler, PurgeDisabledTypes) {
+  ModelTypeSet enabled_types = GetEnabledTypes();
+  ModelTypeSet disabled_types = Difference(ModelTypeSet::All(), enabled_types);
   // Set data for all types.
   ModelTypeSet protocol_types = ProtocolTypes();
   for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good();
@@ -2807,19 +2812,6 @@
 
   sync_manager_.PurgeDisabledTypes(disabled_types, ModelTypeSet(),
                                    ModelTypeSet());
-  CallbackCounter ready_task_counter, retry_task_counter;
-  sync_manager_.ConfigureSyncer(
-      reason, types_to_download, new_routing_info,
-      base::Bind(&CallbackCounter::Callback,
-                 base::Unretained(&ready_task_counter)),
-      base::Bind(&CallbackCounter::Callback,
-                 base::Unretained(&retry_task_counter)));
-  EXPECT_EQ(0, ready_task_counter.times_called());
-  EXPECT_EQ(0, retry_task_counter.times_called());
-  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::RECONFIGURATION, params.source);
-  EXPECT_EQ(types_to_download, params.types_to_download);
-  EXPECT_EQ(new_routing_info, params.routing_info);
-
   // Verify all the disabled types were purged.
   EXPECT_EQ(enabled_types,
             sync_manager_.GetUserShare()->directory->InitialSyncEndedTypes());
@@ -2827,60 +2819,6 @@
                                 ModelTypeSet::All()));
 }
 
-// Test that on a reconfiguration (configuration where the session context
-// already has routing info), only those recently disabled types are purged.
-TEST_F(SyncManagerTestWithMockScheduler, ReConfiguration) {
-  ConfigureReason reason = CONFIGURE_REASON_RECONFIGURATION;
-  ModelTypeSet types_to_download(BOOKMARKS, PREFERENCES);
-  ModelTypeSet disabled_types = ModelTypeSet(THEMES, SESSIONS);
-  ModelSafeRoutingInfo old_routing_info;
-  ModelSafeRoutingInfo new_routing_info;
-  GetModelSafeRoutingInfo(&old_routing_info);
-  new_routing_info = old_routing_info;
-  new_routing_info.erase(THEMES);
-  new_routing_info.erase(SESSIONS);
-  ModelTypeSet enabled_types = GetRoutingInfoTypes(new_routing_info);
-
-  ConfigurationParams params;
-  EXPECT_CALL(*scheduler(), Start(SyncScheduler::CONFIGURATION_MODE, _));
-  EXPECT_CALL(*scheduler(), ScheduleConfiguration(_))
-      .WillOnce(SaveArg<0>(&params));
-
-  // Set data for all types except those recently disabled (so we can verify
-  // only those recently disabled are purged) .
-  ModelTypeSet protocol_types = ProtocolTypes();
-  for (ModelTypeSet::Iterator iter = protocol_types.First(); iter.Good();
-       iter.Inc()) {
-    if (!disabled_types.Has(iter.Get())) {
-      SetProgressMarkerForType(iter.Get(), true);
-    } else {
-      SetProgressMarkerForType(iter.Get(), false);
-    }
-  }
-
-  // Set the context to have the old routing info.
-  cycle_context()->SetRoutingInfo(old_routing_info);
-
-  CallbackCounter ready_task_counter, retry_task_counter;
-  sync_manager_.PurgeDisabledTypes(ModelTypeSet(), ModelTypeSet(),
-                                   ModelTypeSet());
-  sync_manager_.ConfigureSyncer(
-      reason, types_to_download, new_routing_info,
-      base::Bind(&CallbackCounter::Callback,
-                 base::Unretained(&ready_task_counter)),
-      base::Bind(&CallbackCounter::Callback,
-                 base::Unretained(&retry_task_counter)));
-  EXPECT_EQ(0, ready_task_counter.times_called());
-  EXPECT_EQ(0, retry_task_counter.times_called());
-  EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::RECONFIGURATION, params.source);
-  EXPECT_EQ(types_to_download, params.types_to_download);
-  EXPECT_EQ(new_routing_info, params.routing_info);
-
-  // Verify only the recently disabled types were purged.
-  EXPECT_EQ(disabled_types, sync_manager_.GetTypesWithEmptyProgressMarkerToken(
-                                ProtocolTypes()));
-}
-
 // Test that SyncManager::ClearServerData invokes the scheduler.
 TEST_F(SyncManagerTestWithMockScheduler, ClearServerData) {
   EXPECT_CALL(*scheduler(), Start(SyncScheduler::CLEAR_SERVER_DATA_MODE, _));
@@ -2894,9 +2832,7 @@
 // Test that PurgePartiallySyncedTypes purges only those types that have not
 // fully completed their initial download and apply.
 TEST_F(SyncManagerTest, PurgePartiallySyncedTypes) {
-  ModelSafeRoutingInfo routing_info;
-  GetModelSafeRoutingInfo(&routing_info);
-  ModelTypeSet enabled_types = GetRoutingInfoTypes(routing_info);
+  ModelTypeSet enabled_types = GetEnabledTypes();
 
   UserShare* share = sync_manager_.GetUserShare();
 
@@ -2971,9 +2907,7 @@
 // Test CleanupDisabledTypes properly purges all disabled types as specified
 // by the previous and current enabled params.
 TEST_F(SyncManagerTest, PurgeDisabledTypes) {
-  ModelSafeRoutingInfo routing_info;
-  GetModelSafeRoutingInfo(&routing_info);
-  ModelTypeSet enabled_types = GetRoutingInfoTypes(routing_info);
+  ModelTypeSet enabled_types = GetEnabledTypes();
   ModelTypeSet disabled_types = Difference(ModelTypeSet::All(), enabled_types);
 
   // The harness should have initialized the enabled_types for us.
@@ -3014,10 +2948,8 @@
 // Test PurgeDisabledTypes properly unapplies types by deleting their local data
 // and preserving their server data and progress marker.
 TEST_F(SyncManagerTest, PurgeUnappliedTypes) {
-  ModelSafeRoutingInfo routing_info;
-  GetModelSafeRoutingInfo(&routing_info);
   ModelTypeSet unapplied_types = ModelTypeSet(BOOKMARKS, PREFERENCES);
-  ModelTypeSet enabled_types = GetRoutingInfoTypes(routing_info);
+  ModelTypeSet enabled_types = GetEnabledTypes();
   ModelTypeSet disabled_types = Difference(ModelTypeSet::All(), enabled_types);
 
   // The harness should have initialized the enabled_types for us.
diff --git a/components/sync/engine_impl/sync_scheduler.h b/components/sync/engine_impl/sync_scheduler.h
index c1c18b2..160dc1c1 100644
--- a/components/sync/engine_impl/sync_scheduler.h
+++ b/components/sync/engine_impl/sync_scheduler.h
@@ -26,7 +26,6 @@
   ConfigurationParams(
       const sync_pb::GetUpdatesCallerInfo::GetUpdatesSource& source,
       ModelTypeSet types_to_download,
-      const ModelSafeRoutingInfo& routing_info,
       const base::Closure& ready_task,
       const base::Closure& retry_task);
   ConfigurationParams(const ConfigurationParams& other);
@@ -36,8 +35,6 @@
   sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source;
   // The types that should be downloaded.
   ModelTypeSet types_to_download;
-  // The new routing info (superset of types to be downloaded).
-  ModelSafeRoutingInfo routing_info;
   // Callback to invoke on configuration completion.
   base::Closure ready_task;
   // Callback to invoke on configuration failure.
diff --git a/components/sync/engine_impl/sync_scheduler_impl.cc b/components/sync/engine_impl/sync_scheduler_impl.cc
index 69559b51..09a873c 100644
--- a/components/sync/engine_impl/sync_scheduler_impl.cc
+++ b/components/sync/engine_impl/sync_scheduler_impl.cc
@@ -100,12 +100,10 @@
 ConfigurationParams::ConfigurationParams(
     const sync_pb::GetUpdatesCallerInfo::GetUpdatesSource& source,
     ModelTypeSet types_to_download,
-    const ModelSafeRoutingInfo& routing_info,
     const base::Closure& ready_task,
     const base::Closure& retry_task)
     : source(source),
       types_to_download(types_to_download),
-      routing_info(routing_info),
       ready_task(ready_task),
       retry_task(retry_task) {
   DCHECK(!ready_task.is_null());
@@ -270,25 +268,6 @@
     observer.OnSyncCycleEvent(event);
 }
 
-namespace {
-
-// Helper to extract the routing info corresponding to types in
-// |types_to_download| from |current_routes|.
-void BuildModelSafeParams(ModelTypeSet types_to_download,
-                          const ModelSafeRoutingInfo& current_routes,
-                          ModelSafeRoutingInfo* result_routes) {
-  for (ModelTypeSet::Iterator iter = types_to_download.First(); iter.Good();
-       iter.Inc()) {
-    ModelType type = iter.Get();
-    ModelSafeRoutingInfo::const_iterator route = current_routes.find(type);
-    DCHECK(route != current_routes.end());
-    ModelSafeGroup group = route->second;
-    (*result_routes)[type] = group;
-  }
-}
-
-}  // namespace.
-
 void SyncSchedulerImpl::ScheduleConfiguration(
     const ConfigurationParams& params) {
   DCHECK(CalledOnValidThread());
@@ -302,11 +281,6 @@
   // for a pending configure job.
   DCHECK(!pending_configure_params_);
 
-  ModelSafeRoutingInfo restricted_routes;
-  BuildModelSafeParams(params.types_to_download, params.routing_info,
-                       &restricted_routes);
-  cycle_context_->SetRoutingInfo(restricted_routes);
-
   // Only reconfigure if we have types to download.
   if (!params.types_to_download.Empty()) {
     pending_configure_params_ = base::MakeUnique<ConfigurationParams>(params);
diff --git a/components/sync/engine_impl/sync_scheduler_impl_unittest.cc b/components/sync/engine_impl/sync_scheduler_impl_unittest.cc
index d4d6734..93d8a00 100644
--- a/components/sync/engine_impl/sync_scheduler_impl_unittest.cc
+++ b/components/sync/engine_impl/sync_scheduler_impl_unittest.cc
@@ -89,14 +89,6 @@
   RunLoop();
 }
 
-ModelSafeRoutingInfo TypesToRoutingInfo(ModelTypeSet types) {
-  ModelSafeRoutingInfo routes;
-  for (ModelTypeSet::Iterator iter = types.First(); iter.Good(); iter.Inc()) {
-    routes[iter.Get()] = GROUP_PASSIVE;
-  }
-  return routes;
-}
-
 static const size_t kMinNumSamples = 5;
 
 // Test harness for the SyncScheduler.  Test the delays and backoff timers used
@@ -129,11 +121,6 @@
     delay_ = nullptr;
     extensions_activity_ = new ExtensionsActivity();
 
-    routing_info_[THEMES] = GROUP_UI;
-    routing_info_[TYPED_URLS] = GROUP_DB;
-    routing_info_[THEMES] = GROUP_UI;
-    routing_info_[NIGORI] = GROUP_PASSIVE;
-
     workers_.clear();
     workers_.push_back(make_scoped_refptr(new FakeModelWorker(GROUP_UI)));
     workers_.push_back(make_scoped_refptr(new FakeModelWorker(GROUP_DB)));
@@ -146,6 +133,9 @@
     model_type_registry_ = base::MakeUnique<ModelTypeRegistry>(
         workers_, test_user_share_.user_share(), &mock_nudge_handler_,
         UssMigrator());
+    model_type_registry_->RegisterDirectoryType(NIGORI, GROUP_PASSIVE);
+    model_type_registry_->RegisterDirectoryType(THEMES, GROUP_UI);
+    model_type_registry_->RegisterDirectoryType(TYPED_URLS, GROUP_DB);
 
     context_ = base::MakeUnique<SyncCycleContext>(
         connection_.get(), directory(), extensions_activity_.get(),
@@ -154,7 +144,6 @@
         true,   // enable keystore encryption
         false,  // force enable pre-commit GU avoidance
         "fake_invalidator_client_id");
-    context_->SetRoutingInfo(routing_info_);
     context_->set_notifications_enabled(true);
     context_->set_account_name("Test");
     scheduler_ = base::MakeUnique<SyncSchedulerImpl>(
@@ -164,7 +153,6 @@
   }
 
   SyncSchedulerImpl* scheduler() { return scheduler_.get(); }
-  const ModelSafeRoutingInfo& routing_info() { return routing_info_; }
   MockSyncer* syncer() { return syncer_; }
   MockDelayProvider* delay() { return delay_; }
   MockConnectionManager* connection() { return connection_.get(); }
@@ -309,7 +297,6 @@
   MockDelayProvider* delay_;
   std::vector<scoped_refptr<ModelSafeWorker>> workers_;
   scoped_refptr<ExtensionsActivity> extensions_activity_;
-  ModelSafeRoutingInfo routing_info_;
   base::WeakPtrFactory<SyncSchedulerImplTest> weak_ptr_factory_;
 };
 
@@ -393,7 +380,6 @@
   CallbackCounter retry_counter;
   ConfigurationParams params(
       GetUpdatesCallerInfo::RECONFIGURATION, model_types,
-      TypesToRoutingInfo(model_types),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter)));
   scheduler()->ScheduleConfiguration(params);
@@ -422,7 +408,6 @@
   CallbackCounter retry_counter;
   ConfigurationParams params(
       GetUpdatesCallerInfo::RECONFIGURATION, model_types,
-      TypesToRoutingInfo(model_types),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter)));
   scheduler()->ScheduleConfiguration(params);
@@ -468,7 +453,6 @@
   CallbackCounter retry_counter;
   ConfigurationParams params(
       GetUpdatesCallerInfo::RECONFIGURATION, model_types,
-      TypesToRoutingInfo(model_types),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter)));
   scheduler()->ScheduleConfiguration(params);
@@ -490,7 +474,6 @@
   CallbackCounter retry_counter;
   ConfigurationParams params(
       GetUpdatesCallerInfo::RECONFIGURATION, model_types,
-      TypesToRoutingInfo(model_types),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter)));
   scheduler()->ScheduleConfiguration(params);
@@ -518,7 +501,6 @@
   CallbackCounter retry_counter;
   ConfigurationParams params(
       GetUpdatesCallerInfo::RECONFIGURATION, model_types,
-      TypesToRoutingInfo(model_types),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter)));
   scheduler()->ScheduleConfiguration(params);
@@ -546,7 +528,6 @@
   CallbackCounter retry_counter;
   ConfigurationParams params(
       GetUpdatesCallerInfo::RECONFIGURATION, model_types,
-      TypesToRoutingInfo(model_types),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter)));
   scheduler()->ScheduleConfiguration(params);
@@ -807,7 +788,7 @@
   CallbackCounter ready_counter;
   CallbackCounter retry_counter;
   ConfigurationParams params(
-      GetUpdatesCallerInfo::RECONFIGURATION, types, TypesToRoutingInfo(types),
+      GetUpdatesCallerInfo::RECONFIGURATION, types,
       base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter)));
   scheduler()->ScheduleConfiguration(params);
@@ -891,7 +872,7 @@
   CallbackCounter ready_counter;
   CallbackCounter retry_counter;
   ConfigurationParams params(
-      GetUpdatesCallerInfo::RECONFIGURATION, types, TypesToRoutingInfo(types),
+      GetUpdatesCallerInfo::RECONFIGURATION, types,
       base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter)));
   scheduler()->ScheduleConfiguration(params);
@@ -1212,7 +1193,6 @@
   CallbackCounter retry_counter;
   ConfigurationParams params(
       GetUpdatesCallerInfo::RECONFIGURATION, config_types,
-      TypesToRoutingInfo(config_types),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter)));
   scheduler()->ScheduleConfiguration(params);
@@ -1229,9 +1209,6 @@
       .WillOnce(DoAll(Invoke(test_util::SimulateNormalSuccess),
                       RecordSyncShare(&times2, true)));
 
-  // TODO(tim): Figure out how to remove this dangerous need to reset
-  // routing info between mode switches.
-  context()->SetRoutingInfo(routing_info());
   StartSyncScheduler(base::Time());
 
   RunLoop();
@@ -1307,7 +1284,7 @@
   CallbackCounter ready_counter;
   CallbackCounter retry_counter;
   ConfigurationParams params(
-      GetUpdatesCallerInfo::RECONFIGURATION, types, TypesToRoutingInfo(types),
+      GetUpdatesCallerInfo::RECONFIGURATION, types,
       base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter)));
   scheduler()->ScheduleConfiguration(params);
@@ -1356,7 +1333,7 @@
   CallbackCounter ready_counter;
   CallbackCounter retry_counter;
   ConfigurationParams params(
-      GetUpdatesCallerInfo::RECONFIGURATION, types, TypesToRoutingInfo(types),
+      GetUpdatesCallerInfo::RECONFIGURATION, types,
       base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter)));
   scheduler()->ScheduleConfiguration(params);
@@ -1589,7 +1566,6 @@
   CallbackCounter retry_counter;
   ConfigurationParams params(
       GetUpdatesCallerInfo::RECONFIGURATION, model_types,
-      TypesToRoutingInfo(model_types),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&ready_counter)),
       base::Bind(&CallbackCounter::Callback, base::Unretained(&retry_counter)));
   scheduler()->ScheduleConfiguration(params);
diff --git a/components/sync/engine_impl/syncer_unittest.cc b/components/sync/engine_impl/syncer_unittest.cc
index c07f91a..a66b784 100644
--- a/components/sync/engine_impl/syncer_unittest.cc
+++ b/components/sync/engine_impl/syncer_unittest.cc
@@ -219,15 +219,6 @@
   void OnProtocolEvent(const ProtocolEvent& event) override {}
   void OnSyncProtocolError(const SyncProtocolError& error) override {}
 
-  void GetModelSafeRoutingInfo(ModelSafeRoutingInfo* out) {
-    // We're just testing the sync engine here, so we shunt everything to
-    // the SyncerThread.  Datatypes which aren't enabled aren't in the map.
-    for (ModelTypeSet::Iterator it = enabled_datatypes_.First(); it.Good();
-         it.Inc()) {
-      (*out)[it.Get()] = GROUP_PASSIVE;
-    }
-  }
-
   void OnSyncCycleEvent(const SyncCycleEvent& event) override {
     DVLOG(1) << "HandleSyncEngineEvent in unittest " << event.what_happened;
     // we only test for entry-specific events, not status changed ones.
@@ -271,32 +262,28 @@
     mock_server_ = base::MakeUnique<MockConnectionManager>(
         directory(), &cancelation_signal_);
     debug_info_getter_ = base::MakeUnique<MockDebugInfoGetter>();
-    EnableDatatype(BOOKMARKS);
-    EnableDatatype(EXTENSIONS);
-    EnableDatatype(NIGORI);
-    EnableDatatype(PREFERENCES);
-    EnableDatatype(NIGORI);
     workers_.push_back(
         scoped_refptr<ModelSafeWorker>(new FakeModelWorker(GROUP_PASSIVE)));
     std::vector<SyncEngineEventListener*> listeners;
     listeners.push_back(this);
 
-    ModelSafeRoutingInfo routing_info;
-    GetModelSafeRoutingInfo(&routing_info);
-
     model_type_registry_ = base::MakeUnique<ModelTypeRegistry>(
         workers_, test_user_share_.user_share(), &mock_nudge_handler_,
         UssMigrator());
     model_type_registry_->RegisterDirectoryTypeDebugInfoObserver(
         &debug_info_cache_);
 
+    EnableDatatype(BOOKMARKS);
+    EnableDatatype(EXTENSIONS);
+    EnableDatatype(NIGORI);
+    EnableDatatype(PREFERENCES);
+
     context_ = base::MakeUnique<SyncCycleContext>(
         mock_server_.get(), directory(), extensions_activity_.get(), listeners,
         debug_info_getter_.get(), model_type_registry_.get(),
         true,   // enable keystore encryption
         false,  // force enable pre-commit GU avoidance experiment
         "fake_invalidator_client_id");
-    context_->SetRoutingInfo(routing_info);
     syncer_ = new Syncer(&cancelation_signal_);
     scheduler_ = base::MakeUnique<SyncSchedulerImpl>(
         "TestSyncScheduler", BackoffDelayProvider::FromDefaults(),
@@ -510,27 +497,13 @@
 
   void EnableDatatype(ModelType model_type) {
     enabled_datatypes_.Put(model_type);
-
-    ModelSafeRoutingInfo routing_info;
-    GetModelSafeRoutingInfo(&routing_info);
-
-    if (context_) {
-      context_->SetRoutingInfo(routing_info);
-    }
-
+    model_type_registry_->RegisterDirectoryType(model_type, GROUP_PASSIVE);
     mock_server_->ExpectGetUpdatesRequestTypes(enabled_datatypes_);
   }
 
   void DisableDatatype(ModelType model_type) {
     enabled_datatypes_.Remove(model_type);
-
-    ModelSafeRoutingInfo routing_info;
-    GetModelSafeRoutingInfo(&routing_info);
-
-    if (context_) {
-      context_->SetRoutingInfo(routing_info);
-    }
-
+    model_type_registry_->UnregisterDirectoryType(model_type);
     mock_server_->ExpectGetUpdatesRequestTypes(enabled_datatypes_);
   }
 
@@ -4896,7 +4869,6 @@
   // The expectations of this test happen in the MockConnectionManager's
   // GetUpdates handler.  EnableDatatype sets the expectation value from our
   // set of enabled/disabled datatypes.
-  EnableDatatype(BOOKMARKS);
   EXPECT_TRUE(SyncShareNudge());
   EXPECT_EQ(1, mock_server_->GetAndClearNumGetUpdatesRequests());
 
@@ -4904,10 +4876,6 @@
   EXPECT_TRUE(SyncShareNudge());
   EXPECT_EQ(1, mock_server_->GetAndClearNumGetUpdatesRequests());
 
-  EnableDatatype(PREFERENCES);
-  EXPECT_TRUE(SyncShareNudge());
-  EXPECT_EQ(1, mock_server_->GetAndClearNumGetUpdatesRequests());
-
   DisableDatatype(BOOKMARKS);
   EXPECT_TRUE(SyncShareNudge());
   EXPECT_EQ(1, mock_server_->GetAndClearNumGetUpdatesRequests());
diff --git a/components/sync/tools/sync_client.cc b/components/sync/tools/sync_client.cc
index 69954bca..2056aea1 100644
--- a/components/sync/tools/sync_client.cc
+++ b/components/sync/tools/sync_client.cc
@@ -378,10 +378,6 @@
   model_types.Put(FAVICON_IMAGES);
   model_types.Put(FAVICON_TRACKING);
 
-  ModelSafeRoutingInfo routing_info;
-  for (ModelTypeSet::Iterator it = model_types.First(); it.Good(); it.Inc()) {
-    routing_info[it.Get()] = GROUP_PASSIVE;
-  }
   scoped_refptr<PassiveModelWorker> passive_model_safe_worker =
       new PassiveModelWorker();
   std::vector<scoped_refptr<ModelSafeWorker>> workers;
@@ -442,7 +438,13 @@
   invalidator->RegisterHandler(shim.get());
   CHECK(invalidator->UpdateRegisteredIds(
       shim.get(), ModelTypeSetToObjectIdSet(model_types)));
-  sync_manager->StartSyncingNormally(routing_info, base::Time());
+  ModelTypeConnector* model_type_connector =
+      sync_manager->GetModelTypeConnector();
+  for (ModelTypeSet::Iterator it = model_types.First(); it.Good(); it.Inc()) {
+    model_type_connector->RegisterDirectoryType(it.Get(), GROUP_PASSIVE);
+  }
+
+  sync_manager->StartSyncingNormally(base::Time());
 
   base::RunLoop().Run();
 
diff --git a/components/undo/undo_manager.cc b/components/undo/undo_manager.cc
index 6829e52..47e41bed 100644
--- a/components/undo/undo_manager.cc
+++ b/components/undo/undo_manager.cc
@@ -8,6 +8,7 @@
 
 #include "base/auto_reset.h"
 #include "base/logging.h"
+#include "base/memory/ptr_util.h"
 #include "components/undo/undo_manager_observer.h"
 #include "components/undo/undo_operation.h"
 #include "grit/components_strings.h"
@@ -35,14 +36,12 @@
     set_undo_label_id(operation->GetUndoLabelId());
     set_redo_label_id(operation->GetRedoLabelId());
   }
-  operations_.push_back(operation.release());
+  operations_.push_back(std::move(operation));
 }
 
 void UndoGroup::Undo() {
-  for (ScopedVector<UndoOperation>::reverse_iterator ri = operations_.rbegin();
-       ri != operations_.rend(); ++ri) {
+  for (auto ri = operations_.rbegin(); ri != operations_.rend(); ++ri)
     (*ri)->Undo();
-  }
 }
 
 // UndoManager ----------------------------------------------------------------
@@ -139,31 +138,6 @@
   return undo_suspended_count_ > 0;
 }
 
-std::vector<UndoOperation*> UndoManager::GetAllUndoOperations() const {
-  std::vector<UndoOperation*> result;
-  for (size_t i = 0; i < undo_actions_.size(); ++i) {
-    const std::vector<UndoOperation*>& operations =
-        undo_actions_[i]->undo_operations();
-    result.insert(result.end(), operations.begin(), operations.end());
-  }
-  for (size_t i = 0; i < redo_actions_.size(); ++i) {
-    const std::vector<UndoOperation*>& operations =
-        redo_actions_[i]->undo_operations();
-    result.insert(result.end(), operations.begin(), operations.end());
-  }
-  // Ensure that if an Undo is in progress the UndoOperations part of that
-  // UndoGroup are included in the returned set. This will ensure that any
-  // changes (such as renumbering) will be applied to any potentially
-  // unprocessed UndoOperations.
-  if (undo_in_progress_action_) {
-    const std::vector<UndoOperation*>& operations =
-        undo_in_progress_action_->undo_operations();
-    result.insert(result.end(), operations.begin(), operations.end());
-  }
-
-  return result;
-}
-
 void UndoManager::RemoveAllOperations() {
   DCHECK(!group_actions_count_);
   undo_actions_.clear();
@@ -180,8 +154,9 @@
   observers_.RemoveObserver(observer);
 }
 
-void UndoManager::Undo(bool* performing_indicator,
-                       ScopedVector<UndoGroup>* active_undo_group) {
+void UndoManager::Undo(
+    bool* performing_indicator,
+    std::vector<std::unique_ptr<UndoGroup>>* active_undo_group) {
   // Check that action grouping has been correctly ended.
   DCHECK(!group_actions_count_);
 
@@ -189,11 +164,11 @@
     return;
 
   base::AutoReset<bool> incoming_changes(performing_indicator, true);
-  std::unique_ptr<UndoGroup> action(active_undo_group->back());
+  std::unique_ptr<UndoGroup> action = std::move(active_undo_group->back());
   base::AutoReset<UndoGroup*> action_context(&undo_in_progress_action_,
       action.get());
-  active_undo_group->weak_erase(
-      active_undo_group->begin() + active_undo_group->size() - 1);
+  active_undo_group->erase(active_undo_group->begin() +
+                           active_undo_group->size() - 1);
 
   StartGroupingActions();
   action->Undo();
@@ -208,7 +183,7 @@
 }
 
 void UndoManager::AddUndoGroup(UndoGroup* new_undo_group) {
-  GetActiveUndoGroup()->push_back(new_undo_group);
+  GetActiveUndoGroup()->push_back(base::WrapUnique<UndoGroup>(new_undo_group));
 
   // User actions invalidate any available redo actions.
   if (is_user_action())
@@ -221,6 +196,6 @@
   NotifyOnUndoManagerStateChange();
 }
 
-ScopedVector<UndoGroup>* UndoManager::GetActiveUndoGroup() {
+std::vector<std::unique_ptr<UndoGroup>>* UndoManager::GetActiveUndoGroup() {
   return performing_undo_ ? &redo_actions_ : &undo_actions_;
 }
diff --git a/components/undo/undo_manager.h b/components/undo/undo_manager.h
index 3cb7fdf..95eb354 100644
--- a/components/undo/undo_manager.h
+++ b/components/undo/undo_manager.h
@@ -8,9 +8,9 @@
 #include <stddef.h>
 
 #include <memory>
+#include <vector>
 
 #include "base/macros.h"
-#include "base/memory/scoped_vector.h"
 #include "base/observer_list.h"
 #include "base/strings/string16.h"
 
@@ -27,8 +27,8 @@
   ~UndoGroup();
 
   void AddOperation(std::unique_ptr<UndoOperation> operation);
-  const std::vector<UndoOperation*>& undo_operations() {
-    return operations_.get();
+  const std::vector<std::unique_ptr<UndoOperation>>& undo_operations() {
+    return operations_;
   }
   void Undo();
 
@@ -40,7 +40,7 @@
   void set_redo_label_id(int label_id) { redo_label_id_ = label_id; }
 
  private:
-  ScopedVector<UndoOperation> operations_;
+  std::vector<std::unique_ptr<UndoOperation>> operations_;
 
   // The resource string id describing the undo and redo action.
   int undo_label_id_;
@@ -80,10 +80,6 @@
   void ResumeUndoTracking();
   bool IsUndoTrakingSuspended() const;
 
-  // Returns all UndoOperations that are awaiting Undo or Redo. Note that
-  // ownership of the UndoOperations is retained by UndoManager.
-  std::vector<UndoOperation*> GetAllUndoOperations() const;
-
   // Remove all undo and redo operations. Note that grouping of actions and
   // suspension of undo tracking states are left unchanged.
   void RemoveAllOperations();
@@ -93,8 +89,10 @@
   void RemoveObserver(UndoManagerObserver* observer);
 
  private:
+  friend class UndoManagerTestApi;
+
   void Undo(bool* performing_indicator,
-            ScopedVector<UndoGroup>* active_undo_group);
+            std::vector<std::unique_ptr<UndoGroup>>* active_undo_group);
   bool is_user_action() const { return !performing_undo_ && !performing_redo_; }
 
   // Notifies the observers that the undo manager's state has changed.
@@ -105,11 +103,11 @@
 
   // Returns the undo or redo UndoGroup container that should store the next
   // change taking into account if an undo or redo is being executed.
-  ScopedVector<UndoGroup>* GetActiveUndoGroup();
+  std::vector<std::unique_ptr<UndoGroup>>* GetActiveUndoGroup();
 
   // Containers of user actions ready for an undo or redo treated as a stack.
-  ScopedVector<UndoGroup> undo_actions_;
-  ScopedVector<UndoGroup> redo_actions_;
+  std::vector<std::unique_ptr<UndoGroup>> undo_actions_;
+  std::vector<std::unique_ptr<UndoGroup>> redo_actions_;
 
   // The observers to notify when internal state changes.
   base::ObserverList<UndoManagerObserver> observers_;
diff --git a/components/undo/undo_manager_test.cc b/components/undo/undo_manager_test.cc
index eea1598..70e50f08 100644
--- a/components/undo/undo_manager_test.cc
+++ b/components/undo/undo_manager_test.cc
@@ -13,6 +13,56 @@
 
 namespace {
 
+std::vector<UndoOperation*> ConvertToRawPtrVector(
+    const std::vector<std::unique_ptr<UndoOperation>>& args) {
+  std::vector<UndoOperation*> args_rawptrs;
+  for (auto i = args.begin(); i != args.end(); ++i)
+    args_rawptrs.push_back(i->get());
+  return args_rawptrs;
+}
+
+}  // namespace
+
+// UndoManagerTestApi ----------------------------------------------------------
+
+class UndoManagerTestApi {
+ public:
+  // Returns all UndoOperations that are awaiting Undo or Redo.
+  static std::vector<UndoOperation*> GetAllUndoOperations(
+      const UndoManager& undo_manager);
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(UndoManagerTestApi);
+};
+
+std::vector<UndoOperation*> UndoManagerTestApi::GetAllUndoOperations(
+    const UndoManager& undo_manager) {
+  std::vector<UndoOperation*> result;
+  for (size_t i = 0; i < undo_manager.undo_actions_.size(); ++i) {
+    const std::vector<UndoOperation*> operations =
+        ConvertToRawPtrVector(undo_manager.undo_actions_[i]->undo_operations());
+    result.insert(result.end(), operations.begin(), operations.end());
+  }
+  for (size_t i = 0; i < undo_manager.redo_actions_.size(); ++i) {
+    const std::vector<UndoOperation*> operations =
+        ConvertToRawPtrVector(undo_manager.redo_actions_[i]->undo_operations());
+    result.insert(result.end(), operations.begin(), operations.end());
+  }
+  // Ensure that if an Undo is in progress the UndoOperations part of that
+  // UndoGroup are included in the returned set. This will ensure that any
+  // changes (such as renumbering) will be applied to any potentially
+  // unprocessed UndoOperations.
+  if (undo_manager.undo_in_progress_action_) {
+    const std::vector<UndoOperation*> operations = ConvertToRawPtrVector(
+        undo_manager.undo_in_progress_action_->undo_operations());
+    result.insert(result.end(), operations.begin(), operations.end());
+  }
+
+  return result;
+}
+
+namespace {
+
 class TestUndoOperation;
 
 // TestUndoService -------------------------------------------------------------
@@ -231,7 +281,7 @@
   ASSERT_EQ(1U, undo_service.undo_manager_.redo_count());
 
   std::vector<UndoOperation*> all_operations =
-      undo_service.undo_manager_.GetAllUndoOperations();
+      UndoManagerTestApi::GetAllUndoOperations(undo_service.undo_manager_);
   EXPECT_EQ(4U, all_operations.size());
 }
 
diff --git a/content/browser/browser_plugin/browser_plugin_guest.cc b/content/browser/browser_plugin/browser_plugin_guest.cc
index f95dab6a5e..514cc36 100644
--- a/content/browser/browser_plugin/browser_plugin_guest.cc
+++ b/content/browser/browser_plugin/browser_plugin_guest.cc
@@ -339,10 +339,6 @@
   *renderer_prefs = *owner_web_contents_->GetMutableRendererPrefs();
   renderer_prefs->user_agent_override = guest_user_agent_override;
 
-  // We would like the guest to report changes to frame names so that we can
-  // update the BrowserPlugin's corresponding 'name' attribute.
-  // TODO(fsamuel): Remove this once http://crbug.com/169110 is addressed.
-  renderer_prefs->report_frame_name_changes = true;
   // Navigation is disabled in Chrome Apps. We want to make sure guest-initiated
   // navigations still continue to function inside the app.
   renderer_prefs->browser_handles_all_top_level_requests = false;
diff --git a/content/browser/frame_host/debug_urls.cc b/content/browser/frame_host/debug_urls.cc
index c404067..9aafa8aa 100644
--- a/content/browser/frame_host/debug_urls.cc
+++ b/content/browser/frame_host/debug_urls.cc
@@ -22,12 +22,12 @@
 #include "content/public/common/content_constants.h"
 #include "content/public/common/url_constants.h"
 #include "ppapi/features/features.h"
-#include "ppapi/proxy/ppapi_messages.h"
 #include "third_party/kasko/kasko_features.h"
 #include "url/gurl.h"
 
 #if BUILDFLAG(ENABLE_PLUGINS)
-#include "content/browser/ppapi_plugin_process_host.h"
+#include "content/browser/ppapi_plugin_process_host.h"  // nogncheck
+#include "ppapi/proxy/ppapi_messages.h"  // nogncheck
 #endif
 
 namespace content {
diff --git a/content/browser/frame_host/render_frame_host_manager.cc b/content/browser/frame_host/render_frame_host_manager.cc
index bbc35ce..ebba686 100644
--- a/content/browser/frame_host/render_frame_host_manager.cc
+++ b/content/browser/frame_host/render_frame_host_manager.cc
@@ -952,12 +952,6 @@
 
 void RenderFrameHostManager::OnDidUpdateName(const std::string& name,
                                              const std::string& unique_name) {
-  // The window.name message may be sent outside of --site-per-process when
-  // report_frame_name_changes renderer preference is set (used by
-  // WebView).  Don't send the update to proxies in those cases.
-  if (!SiteIsolationPolicy::AreCrossProcessFramesPossible())
-    return;
-
   for (const auto& pair : proxy_hosts_) {
     pair.second->Send(new FrameMsg_DidUpdateName(pair.second->GetRoutingID(),
                                                  name, unique_name));
diff --git a/content/browser/renderer_host/font_utils_linux.cc b/content/browser/renderer_host/font_utils_linux.cc
index 245475f6..f32f6a1b 100644
--- a/content/browser/renderer_host/font_utils_linux.cc
+++ b/content/browser/renderer_host/font_utils_linux.cc
@@ -12,8 +12,11 @@
 #include <string>
 
 #include "base/posix/eintr_wrapper.h"
-#include "ppapi/c/private/pp_private_font_charset.h"
-#include "ppapi/c/trusted/ppb_browser_font_trusted.h"
+
+// TODO(crbug/685022): Guard the inclusion of ppapi headers with
+// BUILDFLAG(ENABLE_PLUGINS).
+#include "ppapi/c/private/pp_private_font_charset.h"  // nogncheck
+#include "ppapi/c/trusted/ppb_browser_font_trusted.h"  // nogncheck
 
 namespace {
 
diff --git a/content/browser/renderer_host/render_message_filter.cc b/content/browser/renderer_host/render_message_filter.cc
index 6901145a..b56d17ef 100644
--- a/content/browser/renderer_host/render_message_filter.cc
+++ b/content/browser/renderer_host/render_message_filter.cc
@@ -66,7 +66,6 @@
 #include "net/http/http_cache.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
-#include "ppapi/shared_impl/file_type_conversion.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 6f3c7f8..b57a31f 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -181,7 +181,6 @@
 #include "mojo/public/cpp/bindings/associated_interface_ptr.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "ppapi/features/features.h"
-#include "ppapi/shared_impl/ppapi_switches.h"
 #include "services/service_manager/public/cpp/connection.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "services/service_manager/public/cpp/interface_registry.h"
@@ -235,6 +234,7 @@
 
 #if BUILDFLAG(ENABLE_PLUGINS)
 #include "content/browser/plugin_service_impl.h"
+#include "ppapi/shared_impl/ppapi_switches.h"  // nogncheck
 #endif
 
 #if BUILDFLAG(ENABLE_WEBRTC)
diff --git a/content/browser/renderer_host/sandbox_ipc_linux.cc b/content/browser/renderer_host/sandbox_ipc_linux.cc
index eac3a32..27250ec0 100644
--- a/content/browser/renderer_host/sandbox_ipc_linux.cc
+++ b/content/browser/renderer_host/sandbox_ipc_linux.cc
@@ -26,7 +26,6 @@
 #include "content/common/sandbox_linux/sandbox_linux.h"
 #include "content/common/set_process_title.h"
 #include "content/public/common/content_switches.h"
-#include "ppapi/c/trusted/ppb_browser_font_trusted.h"
 #include "skia/ext/skia_utils_base.h"
 #include "third_party/skia/include/ports/SkFontConfigInterface.h"
 #include "ui/gfx/font.h"
diff --git a/content/browser/webrtc/webrtc_getusermedia_browsertest.cc b/content/browser/webrtc/webrtc_getusermedia_browsertest.cc
index b7a055c..6440176 100644
--- a/content/browser/webrtc/webrtc_getusermedia_browsertest.cc
+++ b/content/browser/webrtc/webrtc_getusermedia_browsertest.cc
@@ -580,16 +580,8 @@
             ExecuteJavascriptAndReturnResult(call));
 }
 
-#if defined(OS_ANDROID)
-// Disabled until http://crbug.com/679302 is fixed.
-#define MAYBE_GetUserMediaFailToAccessAudioDevice \
-    DISABLED_GetUserMediaFailToAccessAudioDevice
-#else
-#define MAYBE_GetUserMediaFailToAccessAudioDevice \
-    GetUserMediaFailToAccessAudioDevice
-#endif
 IN_PROC_BROWSER_TEST_F(WebRtcGetUserMediaBrowserTest,
-                       MAYBE_GetUserMediaFailToAccessAudioDevice) {
+                       GetUserMediaFailToAccessAudioDevice) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
   GURL url(embedded_test_server()->GetURL("/media/getusermedia.html"));
diff --git a/content/common/all_messages.h b/content/common/all_messages.h
index b61a78c5..8f33d436d 100644
--- a/content/common/all_messages.h
+++ b/content/common/all_messages.h
@@ -12,5 +12,5 @@
 
 #include "content/common/content_message_generator.h"
 #if BUILDFLAG(ENABLE_PLUGINS)
-#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/proxy/ppapi_messages.h"  // nogncheck
 #endif
diff --git a/content/common/view_messages.h b/content/common/view_messages.h
index 0c0a4ad..8df18e9 100644
--- a/content/common/view_messages.h
+++ b/content/common/view_messages.h
@@ -240,7 +240,6 @@
   IPC_STRUCT_TRAITS_MEMBER(webrtc_udp_max_port)
   IPC_STRUCT_TRAITS_MEMBER(user_agent_override)
   IPC_STRUCT_TRAITS_MEMBER(accept_languages)
-  IPC_STRUCT_TRAITS_MEMBER(report_frame_name_changes)
   IPC_STRUCT_TRAITS_MEMBER(tap_multiple_targets_strategy)
   IPC_STRUCT_TRAITS_MEMBER(disable_client_blocked_error_page)
   IPC_STRUCT_TRAITS_MEMBER(plugin_fullscreen_allowed)
diff --git a/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java b/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java
index 8f06eb5..3836fda 100644
--- a/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java
+++ b/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java
@@ -226,6 +226,14 @@
         return ContentMain.start();
     }
 
+    /**
+     * @return Whether the browser process completed successfully.
+     */
+    public boolean isStartupSuccessfullyCompleted() {
+        ThreadUtils.assertOnUiThread();
+        return mStartupDone && mStartupSuccess;
+    }
+
     public void addStartupCompletedObserver(StartupCallback callback) {
         ThreadUtils.assertOnUiThread();
         if (mStartupDone) {
diff --git a/content/public/common/renderer_preferences.cc b/content/public/common/renderer_preferences.cc
index f37d54f6..46341c4 100644
--- a/content/public/common/renderer_preferences.cc
+++ b/content/public/common/renderer_preferences.cc
@@ -33,7 +33,6 @@
       enable_do_not_track(false),
       webrtc_udp_min_port(0),
       webrtc_udp_max_port(0),
-      report_frame_name_changes(false),
       tap_multiple_targets_strategy(TAP_MULTIPLE_TARGETS_STRATEGY_POPUP),
       disable_client_blocked_error_page(false),
       plugin_fullscreen_allowed(true),
diff --git a/content/public/common/renderer_preferences.h b/content/public/common/renderer_preferences.h
index d32e93c5..15d589ab 100644
--- a/content/public/common/renderer_preferences.h
+++ b/content/public/common/renderer_preferences.h
@@ -114,14 +114,6 @@
   // The accept-languages of the browser, comma-separated.
   std::string accept_languages;
 
-  // Specifies whether the renderer reports frame name changes to the browser
-  // process.
-  // TODO(fsamuel): This is a short-term workaround to avoid regressing
-  // Sunspider. We need to find an efficient way to report changes to frame
-  // names to the browser process. See http://crbug.com/169110 for more
-  // information.
-  bool report_frame_name_changes;
-
   // How to handle a tap gesture touching multiple targets
   TapMultipleTargetsStrategy tap_multiple_targets_strategy;
 
diff --git a/content/renderer/media/user_media_client_impl.cc b/content/renderer/media/user_media_client_impl.cc
index 533ee29..c490a1c3 100644
--- a/content/renderer/media/user_media_client_impl.cc
+++ b/content/renderer/media/user_media_client_impl.cc
@@ -1089,14 +1089,6 @@
     OnTrackStarted(
         native_source,
         connected ? MEDIA_DEVICE_OK : MEDIA_DEVICE_TRACK_START_FAILURE, "");
-#if defined(OS_ANDROID)
-  } else if (connected) {
-    CHECK(native_source->is_local_source());
-    // On Android, we won't get the callback indicating the device readyness.
-    // TODO(tommi): Update the android implementation to support the
-    // OnAudioSourceStarted notification.  http://crbug.com/679302
-    OnTrackStarted(native_source, MEDIA_DEVICE_OK, "");
-#endif
   }
 }
 
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index e0013d70..a53ed81 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -3163,7 +3163,6 @@
 
 void RenderFrameImpl::didChangeName(const blink::WebString& name,
                                     const blink::WebString& unique_name) {
-  // TODO(creis): Remove report_frame_name_changes from RendererPreferences.
   Send(new FrameHostMsg_DidChangeName(
       routing_id_, base::UTF16ToUTF8(base::StringPiece16(name)),
       base::UTF16ToUTF8(base::StringPiece16(unique_name))));
diff --git a/content/renderer/renderer_blink_platform_impl.cc b/content/renderer/renderer_blink_platform_impl.cc
index 462b144..1da9e4d 100644
--- a/content/renderer/renderer_blink_platform_impl.cc
+++ b/content/renderer/renderer_blink_platform_impl.cc
@@ -1085,7 +1085,8 @@
 
 gpu::GpuMemoryBufferManager*
 RendererBlinkPlatformImpl::getGpuMemoryBufferManager() {
-  return RenderThreadImpl::current()->GetGpuMemoryBufferManager();
+  RenderThreadImpl* thread = RenderThreadImpl::current();
+  return thread ? thread->GetGpuMemoryBufferManager() : nullptr;
 }
 
 //------------------------------------------------------------------------------
diff --git a/content/shell/renderer/layout_test/layout_test_content_renderer_client.cc b/content/shell/renderer/layout_test/layout_test_content_renderer_client.cc
index cfbce8e7..0867714 100644
--- a/content/shell/renderer/layout_test/layout_test_content_renderer_client.cc
+++ b/content/shell/renderer/layout_test/layout_test_content_renderer_client.cc
@@ -32,7 +32,6 @@
 #include "content/test/mock_webclipboard_impl.h"
 #include "gin/modules/module_registry.h"
 #include "media/media_features.h"
-#include "ppapi/shared_impl/ppapi_switches.h"
 #include "third_party/WebKit/public/platform/WebMediaStreamCenter.h"
 #include "third_party/WebKit/public/web/WebFrameWidget.h"
 #include "third_party/WebKit/public/web/WebKit.h"
diff --git a/content/shell/renderer/shell_content_renderer_client.cc b/content/shell/renderer/shell_content_renderer_client.cc
index 2fd5d5c2..4545d6e 100644
--- a/content/shell/renderer/shell_content_renderer_client.cc
+++ b/content/shell/renderer/shell_content_renderer_client.cc
@@ -24,7 +24,7 @@
 #include "v8/include/v8.h"
 
 #if BUILDFLAG(ENABLE_PLUGINS)
-#include "ppapi/shared_impl/ppapi_switches.h"
+#include "ppapi/shared_impl/ppapi_switches.h"  // nogncheck
 #endif
 
 #if defined(ENABLE_MOJO_CDM)
diff --git a/extensions/browser/extension_function.h b/extensions/browser/extension_function.h
index 415391a..048a0fe 100644
--- a/extensions/browser/extension_function.h
+++ b/extensions/browser/extension_function.h
@@ -241,7 +241,6 @@
   // Retrieves any error string from the function.
   virtual const std::string& GetError() const;
 
-  bool bad_message() const { return bad_message_; }
   void set_bad_message(bool bad_message) { bad_message_ = bad_message; }
 
   // Specifies the name of the function. A long-lived string (such as a string
diff --git a/ios/third_party/material_components_ios/README.chromium b/ios/third_party/material_components_ios/README.chromium
index 23cdb8e3..2aeb7a7 100644
--- a/ios/third_party/material_components_ios/README.chromium
+++ b/ios/third_party/material_components_ios/README.chromium
@@ -1,7 +1,7 @@
 Name: Material Components for iOS
 URL: https://github.com/material-components/material-components-ios
 Version: 0
-Revision: b1eb638d70792b7aa06118b128e3518c20e2aaae
+Revision: 6b368e0c8ddadad4dadab58087fd83f6b7cf1389
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 429bc8a..8c572b7 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -4717,6 +4717,21 @@
           isMIMETypePassKitType:[_pendingNavigationInfo MIMEType]]) {
     GURL URL = net::GURLWithNSURL(navigationResponse.response.URL);
     [self.passKitDownloader downloadPassKitFileWithURL:URL];
+    allowNavigation = NO;
+
+    // Discard the pending PassKit entry to ensure that the current URL is not
+    // different from what is displayed on the view. If there is no previous
+    // committed URL, which can happen when a link is opened in a new tab via a
+    // context menu or window.open, the pending entry should not be
+    // discarded so that the NavigationManager is never empty. Also, URLs loaded
+    // in a native view should be excluded to avoid an ugly animation where the
+    // web view is inserted and quickly removed.
+    GURL lastCommittedURL = self.webState->GetLastCommittedURL();
+    BOOL isFirstLoad = lastCommittedURL.is_empty();
+    BOOL previousItemWasLoadedInNativeView =
+        [self shouldLoadURLInNativeView:lastCommittedURL];
+    if (!isFirstLoad && !previousItemWasLoadedInNativeView)
+      [self.sessionController discardNonCommittedEntries];
   }
 
   handler(allowNavigation ? WKNavigationResponsePolicyAllow
diff --git a/ios/web_view/internal/DEPS b/ios/web_view/internal/DEPS
index 58dfe5d..1068058 100644
--- a/ios/web_view/internal/DEPS
+++ b/ios/web_view/internal/DEPS
@@ -12,9 +12,4 @@
   "+ios/web_view",
   "+net",
   "+ui/base",
-
-  # TODO(crbug.com/622967): Remove these exceptions.
-  "+ios/web/navigation/crw_session_controller.h",
-  "+ios/web/web_state/ui/crw_web_controller.h",
-  "+ios/web/web_state/web_state_impl.h",
 ]
diff --git a/ios/web_view/internal/criwv_web_view_impl.mm b/ios/web_view/internal/criwv_web_view_impl.mm
index 76709d3..7c85e56 100644
--- a/ios/web_view/internal/criwv_web_view_impl.mm
+++ b/ios/web_view/internal/criwv_web_view_impl.mm
@@ -10,14 +10,12 @@
 #import "base/ios/weak_nsobject.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/sys_string_conversions.h"
-#import "ios/web/navigation/crw_session_controller.h"
 #include "ios/web/public/referrer.h"
 #import "ios/web/public/navigation_manager.h"
 #import "ios/web/public/web_state/ui/crw_web_delegate.h"
 #import "ios/web/public/web_state/web_state.h"
 #import "ios/web/public/web_state/web_state_delegate_bridge.h"
-#import "ios/web/web_state/ui/crw_web_controller.h"
-#import "ios/web/web_state/web_state_impl.h"
+#import "ios/web/public/web_state/web_state_observer_bridge.h"
 #include "ios/web_view/internal/criwv_browser_state.h"
 #import "ios/web_view/internal/translate/criwv_translate_client.h"
 #import "ios/web_view/public/criwv_web_view_delegate.h"
@@ -25,13 +23,12 @@
 #include "ui/base/page_transition_types.h"
 #include "url/gurl.h"
 
-@interface CRIWVWebViewImpl ()<CRWWebDelegate, CRWWebStateDelegate> {
+@interface CRIWVWebViewImpl ()<CRWWebStateDelegate, CRWWebStateObserver> {
   id<CRIWVWebViewDelegate> _delegate;
   ios_web_view::CRIWVBrowserState* _browserState;
-  std::unique_ptr<web::WebStateImpl> _webStateImpl;
-  base::WeakNSObject<CRWWebController> _webController;
+  std::unique_ptr<web::WebState> _webState;
   std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
-
+  std::unique_ptr<web::WebStateObserverBridge> _webStateObserver;
   CGFloat _loadProgress;
 }
 
@@ -47,73 +44,74 @@
   self = [super init];
   if (self) {
     _browserState = browserState;
-    _webStateImpl = base::MakeUnique<web::WebStateImpl>(_browserState);
-    _webStateImpl->GetNavigationManagerImpl().InitializeSession(nil, nil, NO,
-                                                                0);
+
+    web::WebState::CreateParams webStateCreateParams(_browserState);
+    _webState = web::WebState::Create(webStateCreateParams);
+    _webState->SetWebUsageEnabled(true);
+
+    _webStateObserver =
+        base::MakeUnique<web::WebStateObserverBridge>(_webState.get(), self);
     _webStateDelegate = base::MakeUnique<web::WebStateDelegateBridge>(self);
-    _webStateImpl->SetDelegate(_webStateDelegate.get());
-    _webController.reset(_webStateImpl->GetWebController());
-    [_webController setDelegate:self];
-    [_webController setWebUsageEnabled:YES];
+    _webState->SetDelegate(_webStateDelegate.get());
 
     // Initialize Translate.
-    ios_web_view::CRIWVTranslateClient::CreateForWebState(_webStateImpl.get());
+    ios_web_view::CRIWVTranslateClient::CreateForWebState(_webState.get());
   }
   return self;
 }
 
 - (UIView*)view {
-  return [_webController view];
+  return _webState->GetView();
 }
 
 - (BOOL)canGoBack {
-  return _webStateImpl && _webStateImpl->GetNavigationManager()->CanGoBack();
+  return _webState && _webState->GetNavigationManager()->CanGoBack();
 }
 
 - (BOOL)canGoForward {
-  return _webStateImpl && _webStateImpl->GetNavigationManager()->CanGoForward();
+  return _webState && _webState->GetNavigationManager()->CanGoForward();
 }
 
 - (BOOL)isLoading {
-  return _webStateImpl->IsLoading();
+  return _webState->IsLoading();
 }
 
 - (NSURL*)visibleURL {
-  return net::NSURLWithGURL(_webStateImpl->GetVisibleURL());
+  return net::NSURLWithGURL(_webState->GetVisibleURL());
 }
 
 - (NSString*)pageTitle {
-  return base::SysUTF16ToNSString(_webStateImpl->GetTitle());
+  return base::SysUTF16ToNSString(_webState->GetTitle());
 }
 
 - (void)goBack {
-  if (_webStateImpl->GetNavigationManager())
-    _webStateImpl->GetNavigationManager()->GoBack();
+  if (_webState->GetNavigationManager())
+    _webState->GetNavigationManager()->GoBack();
 }
 
 - (void)goForward {
-  if (_webStateImpl->GetNavigationManager())
-    _webStateImpl->GetNavigationManager()->GoForward();
+  if (_webState->GetNavigationManager())
+    _webState->GetNavigationManager()->GoForward();
 }
 
 - (void)reload {
-  [_webController reload];
+  _webState->GetNavigationManager()->Reload(true);
 }
 
 - (void)stopLoading {
-  [_webController stopLoading];
+  _webState->Stop();
 }
 
 - (void)loadURL:(NSURL*)URL {
   web::NavigationManager::WebLoadParams params(net::GURLWithNSURL(URL));
   params.transition_type = ui::PAGE_TRANSITION_TYPED;
-  [_webController loadWithParams:params];
+  _webState->GetNavigationManager()->LoadURLWithParams(params);
 }
 
 - (void)evaluateJavaScript:(NSString*)javaScriptString
          completionHandler:(void (^)(id, NSError*))completionHandler {
-  [_webStateImpl->GetJSInjectionReceiver() executeJavaScript:javaScriptString
-                                           completionHandler:completionHandler];
+  [_webState->GetJSInjectionReceiver() executeJavaScript:javaScriptString
+                                       completionHandler:completionHandler];
 }
 
 - (void)setDelegate:(id<CRIWVWebViewDelegate>)delegate {
@@ -121,16 +119,13 @@
 
   // Set up the translate delegate.
   ios_web_view::CRIWVTranslateClient* translateClient =
-      ios_web_view::CRIWVTranslateClient::FromWebState(_webStateImpl.get());
+      ios_web_view::CRIWVTranslateClient::FromWebState(_webState.get());
   id<CRIWVTranslateDelegate> translateDelegate = nil;
   if ([_delegate respondsToSelector:@selector(translateDelegate)])
     translateDelegate = [_delegate translateDelegate];
   translateClient->set_translate_delegate(translateDelegate);
 }
 
-// -----------------------------------------------------------------------
-// WebDelegate implementation.
-
 - (void)notifyDidUpdateWithChanges:(CRIWVWebViewUpdateType)changes {
   SEL selector = @selector(webView:didUpdateWithChanges:);
   if ([_delegate respondsToSelector:selector]) {
@@ -138,99 +133,28 @@
   }
 }
 
-- (void)webController:(CRWWebController*)webController
-       titleDidChange:(NSString*)title {
-  [self notifyDidUpdateWithChanges:CRIWVWebViewUpdateTypeTitle];
-}
+// -----------------------------------------------------------------------
+// WebStateObserver implementation.
 
-- (void)webDidUpdateSessionForLoadWithParams:
-            (const web::NavigationManager::WebLoadParams&)params
-                        wasInitialNavigation:(BOOL)initialNavigation {
+- (void)didStartProvisionalNavigationForURL:(const GURL&)URL {
   [self notifyDidUpdateWithChanges:CRIWVWebViewUpdateTypeURL];
 }
 
-- (void)webWillFinishHistoryNavigationFromEntry:(CRWSessionEntry*)fromEntry {
+- (void)didCommitNavigationWithDetails:
+    (const web::LoadCommittedDetails&)details {
   [self notifyDidUpdateWithChanges:CRIWVWebViewUpdateTypeURL];
 }
 
-- (void)webDidAddPendingURL {
-  [self notifyDidUpdateWithChanges:CRIWVWebViewUpdateTypeURL];
-}
-
-- (void)webDidUpdateHistoryStateWithPageURL:(const GURL&)pageUrl {
-  [self notifyDidUpdateWithChanges:CRIWVWebViewUpdateTypeURL];
-}
-
-- (void)webCancelStartLoadingRequest {
-  [self notifyDidUpdateWithChanges:CRIWVWebViewUpdateTypeURL];
-}
-
-- (void)webDidStartLoadingURL:(const GURL&)currentUrl
-          shouldUpdateHistory:(BOOL)updateHistory {
-  [self notifyDidUpdateWithChanges:CRIWVWebViewUpdateTypeURL];
-}
-
-- (void)webDidFinishWithURL:(const GURL&)url loadSuccess:(BOOL)loadSuccess {
+- (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success {
+  DCHECK_EQ(_webState.get(), webState);
   SEL selector = @selector(webView:didFinishLoadingWithURL:loadSuccess:);
   if ([_delegate respondsToSelector:selector]) {
     [_delegate webView:self
-        didFinishLoadingWithURL:net::NSURLWithGURL(url)
-                    loadSuccess:loadSuccess];
+        didFinishLoadingWithURL:[self visibleURL]
+                    loadSuccess:success];
   }
 }
 
-- (void)webLoadCancelled:(const GURL&)url {
-  [self notifyDidUpdateWithChanges:CRIWVWebViewUpdateTypeURL];
-}
-
-- (void)webWillAddPendingURL:(const GURL&)url
-                  transition:(ui::PageTransition)transition {
-}
-- (CRWWebController*)webPageOrderedOpen:(const GURL&)url
-                               referrer:(const web::Referrer&)referrer
-                             windowName:(NSString*)windowName
-                           inBackground:(BOOL)inBackground {
-  return nil;
-}
-
-- (CRWWebController*)webPageOrderedOpen {
-  return nil;
-}
-
-- (void)webPageOrderedClose {
-}
-- (void)openURLWithParams:(const web::WebState::OpenURLParams&)params {
-}
-- (BOOL)openExternalURL:(const GURL&)url linkClicked:(BOOL)linkClicked {
-  return NO;
-}
-- (void)webController:(CRWWebController*)webController
-    retrievePlaceholderOverlayImage:(void (^)(UIImage*))block {
-}
-- (void)webController:(CRWWebController*)webController
-    onFormResubmissionForRequest:(NSURLRequest*)request
-                   continueBlock:(ProceduralBlock)continueBlock
-                     cancelBlock:(ProceduralBlock)cancelBlock {
-}
-- (void)webWillReload {
-}
-- (void)webWillInitiateLoadWithParams:
-    (web::NavigationManager::WebLoadParams&)params {
-}
-- (BOOL)webController:(CRWWebController*)webController
-        shouldOpenURL:(const GURL&)url
-      mainDocumentURL:(const GURL&)mainDocumentURL
-          linkClicked:(BOOL)linkClicked {
-  SEL selector = @selector(webView:shouldOpenURL:mainDocumentURL:linkClicked:);
-  if ([_delegate respondsToSelector:selector]) {
-    return [_delegate webView:self
-                shouldOpenURL:net::NSURLWithGURL(url)
-              mainDocumentURL:net::NSURLWithGURL(mainDocumentURL)
-                  linkClicked:linkClicked];
-  }
-  return YES;
-}
-
 // -----------------------------------------------------------------------
 // CRWWebStateDelegate implementation.
 
diff --git a/ios/web_view/public/criwv_web_view_delegate.h b/ios/web_view/public/criwv_web_view_delegate.h
index 6c00c03..640f847e 100644
--- a/ios/web_view/public/criwv_web_view_delegate.h
+++ b/ios/web_view/public/criwv_web_view_delegate.h
@@ -31,12 +31,6 @@
 
 - (id<CRIWVTranslateDelegate>)translateDelegate;
 
-// Returns YES if the web view should load |url| or NO for a custom handling.
-- (BOOL)webView:(id<CRIWVWebView>)webView
-      shouldOpenURL:(NSURL*)url
-    mainDocumentURL:(NSURL*)mainDocumentURL
-        linkClicked:(BOOL)linkClicked;
-
 @end
 
 #endif  // IOS_WEB_VIEW_PUBLIC_CRIWV_WEB_VIEW_DELEGATE_H_
diff --git a/net/BUILD.gn b/net/BUILD.gn
index b9b63798..15f0453d 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -5624,29 +5624,29 @@
 }
 
 if (host_toolchain == current_toolchain && !is_proto_quic) {
-  executable("domain_security_preload_generator") {
+  executable("transport_security_state_generator") {
     sources = [
-      "tools/domain_security_preload_generator/bit_writer.cc",
-      "tools/domain_security_preload_generator/bit_writer.h",
-      "tools/domain_security_preload_generator/cert_util.cc",
-      "tools/domain_security_preload_generator/cert_util.h",
-      "tools/domain_security_preload_generator/domain_security_entry.cc",
-      "tools/domain_security_preload_generator/domain_security_entry.h",
-      "tools/domain_security_preload_generator/domain_security_preload_generator.cc",
-      "tools/domain_security_preload_generator/huffman/huffman_frequency_tracker.cc",
-      "tools/domain_security_preload_generator/huffman/huffman_frequency_tracker.h",
-      "tools/domain_security_preload_generator/pinset.cc",
-      "tools/domain_security_preload_generator/pinset.h",
-      "tools/domain_security_preload_generator/pinsets.cc",
-      "tools/domain_security_preload_generator/pinsets.h",
-      "tools/domain_security_preload_generator/preloaded_state_generator.cc",
-      "tools/domain_security_preload_generator/preloaded_state_generator.h",
-      "tools/domain_security_preload_generator/spki_hash.cc",
-      "tools/domain_security_preload_generator/spki_hash.h",
-      "tools/domain_security_preload_generator/trie/trie_bit_buffer.cc",
-      "tools/domain_security_preload_generator/trie/trie_bit_buffer.h",
-      "tools/domain_security_preload_generator/trie/trie_writer.cc",
-      "tools/domain_security_preload_generator/trie/trie_writer.h",
+      "tools/transport_security_state_generator/bit_writer.cc",
+      "tools/transport_security_state_generator/bit_writer.h",
+      "tools/transport_security_state_generator/cert_util.cc",
+      "tools/transport_security_state_generator/cert_util.h",
+      "tools/transport_security_state_generator/huffman/huffman_builder.cc",
+      "tools/transport_security_state_generator/huffman/huffman_builder.h",
+      "tools/transport_security_state_generator/pinset.cc",
+      "tools/transport_security_state_generator/pinset.h",
+      "tools/transport_security_state_generator/pinsets.cc",
+      "tools/transport_security_state_generator/pinsets.h",
+      "tools/transport_security_state_generator/preloaded_state_generator.cc",
+      "tools/transport_security_state_generator/preloaded_state_generator.h",
+      "tools/transport_security_state_generator/spki_hash.cc",
+      "tools/transport_security_state_generator/spki_hash.h",
+      "tools/transport_security_state_generator/transport_security_state_entry.cc",
+      "tools/transport_security_state_generator/transport_security_state_entry.h",
+      "tools/transport_security_state_generator/transport_security_state_generator.cc",
+      "tools/transport_security_state_generator/trie/trie_bit_buffer.cc",
+      "tools/transport_security_state_generator/trie/trie_bit_buffer.h",
+      "tools/transport_security_state_generator/trie/trie_writer.cc",
+      "tools/transport_security_state_generator/trie/trie_writer.h",
     ]
     deps = [
       "//base",
diff --git a/net/tools/domain_security_preload_generator/domain_security_entry.cc b/net/tools/domain_security_preload_generator/domain_security_entry.cc
deleted file mode 100644
index cbc8a86..0000000
--- a/net/tools/domain_security_preload_generator/domain_security_entry.cc
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 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.
-#include "net/tools/domain_security_preload_generator/domain_security_entry.h"
-
-namespace net {
-
-namespace transport_security_state {
-
-DomainSecurityEntry::DomainSecurityEntry() {}
-DomainSecurityEntry::~DomainSecurityEntry() {}
-
-}  // namespace transport_security_state
-
-}  // namespace
\ No newline at end of file
diff --git a/net/tools/domain_security_preload_generator/domain_security_entry.h b/net/tools/domain_security_preload_generator/domain_security_entry.h
deleted file mode 100644
index e9d01f3..0000000
--- a/net/tools/domain_security_preload_generator/domain_security_entry.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) 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.
-
-#ifndef NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_DOMAIN_SECURITY_ENTRY_H_
-#define NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_DOMAIN_SECURITY_ENTRY_H_
-
-#include <memory>
-#include <string>
-#include <vector>
-
-namespace net {
-
-namespace transport_security_state {
-
-// DomainSecurityEntry represents a preloaded entry.
-struct DomainSecurityEntry {
-  DomainSecurityEntry();
-  ~DomainSecurityEntry();
-
-  std::string hostname;
-
-  bool include_subdomains = false;
-  bool force_https = false;
-
-  bool hpkp_include_subdomains = false;
-  std::string pinset;
-
-  bool expect_ct = false;
-  std::string expect_ct_report_uri;
-
-  bool expect_staple = false;
-  bool expect_staple_include_subdomains = false;
-  std::string expect_staple_report_uri;
-};
-
-using DomainSecurityEntries = std::vector<std::unique_ptr<DomainSecurityEntry>>;
-
-// TODO(Martijnc): Remove the domain IDs from the preload format.
-// https://crbug.com/661206.
-using DomainIDList = std::vector<std::string>;
-
-// ReversedEntry points to a DomainSecurityEntry and contains the reversed
-// hostname for that entry. This is used to construct the trie.
-struct ReversedEntry {
-  ReversedEntry(std::vector<uint8_t> reversed_name,
-                const DomainSecurityEntry* entry);
-  ~ReversedEntry();
-
-  std::vector<uint8_t> reversed_name;
-  const DomainSecurityEntry* entry;
-};
-
-using ReversedEntries = std::vector<std::unique_ptr<ReversedEntry>>;
-
-}  // namespace transport_security_state
-
-}  // namespace net
-
-#endif  // NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_DOMAIN_SECURITY_ENTRY_H_
diff --git a/net/tools/domain_security_preload_generator/bit_writer.cc b/net/tools/transport_security_state_generator/bit_writer.cc
similarity index 93%
rename from net/tools/domain_security_preload_generator/bit_writer.cc
rename to net/tools/transport_security_state_generator/bit_writer.cc
index fa438423..b10316d 100644
--- a/net/tools/domain_security_preload_generator/bit_writer.cc
+++ b/net/tools/transport_security_state_generator/bit_writer.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/tools/domain_security_preload_generator/bit_writer.h"
+#include "net/tools/transport_security_state_generator/bit_writer.h"
 
 #include "base/logging.h"
 
diff --git a/net/tools/domain_security_preload_generator/bit_writer.h b/net/tools/transport_security_state_generator/bit_writer.h
similarity index 88%
rename from net/tools/domain_security_preload_generator/bit_writer.h
rename to net/tools/transport_security_state_generator/bit_writer.h
index 9aad7ae..e45ff42 100644
--- a/net/tools/domain_security_preload_generator/bit_writer.h
+++ b/net/tools/transport_security_state_generator/bit_writer.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_BIT_WRITER_H_
-#define NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_BIT_WRITER_H_
+#ifndef NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_BIT_WRITER_H_
+#define NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_BIT_WRITER_H_
 
 #include <stdint.h>
 
@@ -58,4 +58,4 @@
 
 }  // namespace net
 
-#endif  // NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_BIT_WRITER_H_
+#endif  // NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_BIT_WRITER_H_
diff --git a/net/tools/domain_security_preload_generator/cert_util.cc b/net/tools/transport_security_state_generator/cert_util.cc
similarity index 96%
rename from net/tools/domain_security_preload_generator/cert_util.cc
rename to net/tools/transport_security_state_generator/cert_util.cc
index 6ab0035..45954a4 100644
--- a/net/tools/domain_security_preload_generator/cert_util.cc
+++ b/net/tools/transport_security_state_generator/cert_util.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/tools/domain_security_preload_generator/cert_util.h"
+#include "net/tools/transport_security_state_generator/cert_util.h"
 
 #include <string>
 
@@ -10,7 +10,7 @@
 #include "base/files/file_util.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
-#include "net/tools/domain_security_preload_generator/spki_hash.h"
+#include "net/tools/transport_security_state_generator/spki_hash.h"
 #include "third_party/boringssl/src/include/openssl/crypto.h"
 
 using net::transport_security_state::SPKIHash;
diff --git a/net/tools/domain_security_preload_generator/cert_util.h b/net/tools/transport_security_state_generator/cert_util.h
similarity index 88%
rename from net/tools/domain_security_preload_generator/cert_util.h
rename to net/tools/transport_security_state_generator/cert_util.h
index 6d0a3f4..e857cada 100644
--- a/net/tools/domain_security_preload_generator/cert_util.h
+++ b/net/tools/transport_security_state_generator/cert_util.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_CERT_UTIL_H_
-#define NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_CERT_UTIL_H_
+#ifndef NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_CERT_UTIL_H_
+#define NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_CERT_UTIL_H_
 
 #include <stdint.h>
 
@@ -43,4 +43,4 @@
     base::StringPiece pem_key,
     net::transport_security_state::SPKIHash* out_hash);
 
-#endif  // NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_CERT_UTIL_H_
+#endif  // NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_CERT_UTIL_H_
diff --git a/net/tools/domain_security_preload_generator/huffman/huffman_frequency_tracker.cc b/net/tools/transport_security_state_generator/huffman/huffman_builder.cc
similarity index 80%
rename from net/tools/domain_security_preload_generator/huffman/huffman_frequency_tracker.cc
rename to net/tools/transport_security_state_generator/huffman/huffman_builder.cc
index f2e68b7..9e715a7e 100644
--- a/net/tools/domain_security_preload_generator/huffman/huffman_frequency_tracker.cc
+++ b/net/tools/transport_security_state_generator/huffman/huffman_builder.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/tools/domain_security_preload_generator/huffman/huffman_frequency_tracker.h"
+#include "net/tools/transport_security_state_generator/huffman/huffman_builder.h"
 
 #include <algorithm>
 
@@ -49,15 +49,15 @@
 
 }  // namespace
 
-HuffmanFrequencyTracker::HuffmanFrequencyTracker() {}
+HuffmanBuilder::HuffmanBuilder() {}
 
-HuffmanFrequencyTracker::~HuffmanFrequencyTracker() {}
+HuffmanBuilder::~HuffmanBuilder() {}
 
-void HuffmanFrequencyTracker::RecordUsage(uint8_t character) {
+void HuffmanBuilder::RecordUsage(uint8_t character) {
   counts_[character] += 1;
 }
 
-HuffmanRepresentationTable HuffmanFrequencyTracker::ToTable() {
+HuffmanRepresentationTable HuffmanBuilder::ToTable() {
   HuffmanRepresentationTable table;
   std::unique_ptr<HuffmanNode> node(BuildTree());
 
@@ -65,10 +65,10 @@
   return table;
 }
 
-void HuffmanFrequencyTracker::TreeToTable(HuffmanNode* node,
-                                          uint32_t bits,
-                                          uint32_t number_of_bits,
-                                          HuffmanRepresentationTable* table) {
+void HuffmanBuilder::TreeToTable(HuffmanNode* node,
+                                 uint32_t bits,
+                                 uint32_t number_of_bits,
+                                 HuffmanRepresentationTable* table) {
   if (node->IsLeaf()) {
     HuffmanRepresentation item;
     item.bits = bits;
@@ -82,15 +82,15 @@
   }
 }
 
-std::vector<uint8_t> HuffmanFrequencyTracker::ToVector() {
+std::vector<uint8_t> HuffmanBuilder::ToVector() {
   std::vector<uint8_t> bytes;
   std::unique_ptr<HuffmanNode> node(BuildTree());
   WriteToVector(node.get(), &bytes);
   return bytes;
 }
 
-uint32_t HuffmanFrequencyTracker::WriteToVector(HuffmanNode* node,
-                                                std::vector<uint8_t>* vector) {
+uint32_t HuffmanBuilder::WriteToVector(HuffmanNode* node,
+                                       std::vector<uint8_t>* vector) {
   uint8_t left_value;
   uint8_t right_value;
   uint32_t child_position;
@@ -117,7 +117,7 @@
   return position;
 }
 
-std::unique_ptr<HuffmanNode> HuffmanFrequencyTracker::BuildTree() {
+std::unique_ptr<HuffmanNode> HuffmanBuilder::BuildTree() {
   std::vector<std::unique_ptr<HuffmanNode>> nodes;
   nodes.reserve(counts_.size());
 
diff --git a/net/tools/domain_security_preload_generator/huffman/huffman_frequency_tracker.h b/net/tools/transport_security_state_generator/huffman/huffman_builder.h
similarity index 88%
rename from net/tools/domain_security_preload_generator/huffman/huffman_frequency_tracker.h
rename to net/tools/transport_security_state_generator/huffman/huffman_builder.h
index e8c78eb..5368970 100644
--- a/net/tools/domain_security_preload_generator/huffman/huffman_frequency_tracker.h
+++ b/net/tools/transport_security_state_generator/huffman/huffman_builder.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_HUFFMAN_FREQUENCY_TRACKER_H_
-#define NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_HUFFMAN_FREQUENCY_TRACKER_H_
+#ifndef NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_HUFFMAN_BUILDER_H_
+#define NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_HUFFMAN_BUILDER_H_
 
 #include <stdint.h>
 
@@ -33,10 +33,10 @@
 // This class tracks the number of times each character is used and calculates
 // a space efficient way to represent all tracked characters by constructing a
 // Huffman tree based on the number of times each character is seen.
-class HuffmanFrequencyTracker {
+class HuffmanBuilder {
  public:
-  HuffmanFrequencyTracker();
-  ~HuffmanFrequencyTracker();
+  HuffmanBuilder();
+  ~HuffmanBuilder();
 
   // Will increase the count for |character| by one, indicating it has been
   // used.
@@ -81,4 +81,4 @@
 
 }  // namespace net
 
-#endif  // NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_HUFFMAN_FREQUENCY_TRACKER_H_
+#endif  // NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_HUFFMAN_BUILDER_H_
\ No newline at end of file
diff --git a/net/tools/domain_security_preload_generator/pinset.cc b/net/tools/transport_security_state_generator/pinset.cc
similarity index 90%
rename from net/tools/domain_security_preload_generator/pinset.cc
rename to net/tools/transport_security_state_generator/pinset.cc
index 1b35123..03c2d9e4 100644
--- a/net/tools/domain_security_preload_generator/pinset.cc
+++ b/net/tools/transport_security_state_generator/pinset.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/tools/domain_security_preload_generator/pinset.h"
+#include "net/tools/transport_security_state_generator/pinset.h"
 
 namespace net {
 
diff --git a/net/tools/domain_security_preload_generator/pinset.h b/net/tools/transport_security_state_generator/pinset.h
similarity index 88%
rename from net/tools/domain_security_preload_generator/pinset.h
rename to net/tools/transport_security_state_generator/pinset.h
index f74ed3a..b8efca66 100644
--- a/net/tools/domain_security_preload_generator/pinset.h
+++ b/net/tools/transport_security_state_generator/pinset.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_PINSET_H_
-#define NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_PINSET_H_
+#ifndef NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_PINSET_H_
+#define NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_PINSET_H_
 
 #include <string>
 #include <vector>
@@ -55,4 +55,4 @@
 
 }  // namespace net
 
-#endif  // NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_PINSET_H_
+#endif  // NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_PINSET_H_
diff --git a/net/tools/domain_security_preload_generator/pinsets.cc b/net/tools/transport_security_state_generator/pinsets.cc
similarity index 83%
rename from net/tools/domain_security_preload_generator/pinsets.cc
rename to net/tools/transport_security_state_generator/pinsets.cc
index a22dd62..a9e7446a 100644
--- a/net/tools/domain_security_preload_generator/pinsets.cc
+++ b/net/tools/transport_security_state_generator/pinsets.cc
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/tools/domain_security_preload_generator/pinsets.h"
+#include "net/tools/transport_security_state_generator/pinsets.h"
 
-#include "net/tools/domain_security_preload_generator/spki_hash.h"
+#include "net/tools/transport_security_state_generator/spki_hash.h"
 
 namespace net {
 
diff --git a/net/tools/domain_security_preload_generator/pinsets.h b/net/tools/transport_security_state_generator/pinsets.h
similarity index 72%
rename from net/tools/domain_security_preload_generator/pinsets.h
rename to net/tools/transport_security_state_generator/pinsets.h
index aa48592..b22f8c9 100644
--- a/net/tools/domain_security_preload_generator/pinsets.h
+++ b/net/tools/transport_security_state_generator/pinsets.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_PINSETS_H_
-#define NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_PINSETS_H_
+#ifndef NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_PINSETS_H_
+#define NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_PINSETS_H_
 
 #include <map>
 #include <memory>
@@ -11,10 +11,10 @@
 
 #include "base/macros.h"
 #include "base/strings/string_piece.h"
-#include "net/tools/domain_security_preload_generator/cert_util.h"
-#include "net/tools/domain_security_preload_generator/pinset.h"
-#include "net/tools/domain_security_preload_generator/pinsets.h"
-#include "net/tools/domain_security_preload_generator/spki_hash.h"
+#include "net/tools/transport_security_state_generator/cert_util.h"
+#include "net/tools/transport_security_state_generator/pinset.h"
+#include "net/tools/transport_security_state_generator/pinsets.h"
+#include "net/tools/transport_security_state_generator/spki_hash.h"
 
 namespace net {
 
@@ -53,4 +53,4 @@
 
 }  // namespace net
 
-#endif  // NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_PINSETS_H_
+#endif  // NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_PINSETS_H_
diff --git a/net/tools/domain_security_preload_generator/preloaded_state_generator.cc b/net/tools/transport_security_state_generator/preloaded_state_generator.cc
similarity index 90%
rename from net/tools/domain_security_preload_generator/preloaded_state_generator.cc
rename to net/tools/transport_security_state_generator/preloaded_state_generator.cc
index e5e08a0d..64e89890 100644
--- a/net/tools/domain_security_preload_generator/preloaded_state_generator.cc
+++ b/net/tools/transport_security_state_generator/preloaded_state_generator.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/tools/domain_security_preload_generator/preloaded_state_generator.h"
+#include "net/tools/transport_security_state_generator/preloaded_state_generator.h"
 
 #include <iostream>
 
@@ -10,9 +10,9 @@
 
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
-#include "net/tools/domain_security_preload_generator/cert_util.h"
-#include "net/tools/domain_security_preload_generator/huffman/huffman_frequency_tracker.h"
-#include "net/tools/domain_security_preload_generator/spki_hash.h"
+#include "net/tools/transport_security_state_generator/cert_util.h"
+#include "net/tools/transport_security_state_generator/huffman/huffman_builder.h"
+#include "net/tools/transport_security_state_generator/spki_hash.h"
 
 namespace net {
 
@@ -109,18 +109,18 @@
 }
 
 HuffmanRepresentationTable ApproximateHuffman(
-    const DomainSecurityEntries& entries) {
-  HuffmanFrequencyTracker tracker;
+    const TransportSecurityStateEntries& entries) {
+  HuffmanBuilder huffman_builder;
   for (const auto& entry : entries) {
     for (const auto& c : entry->hostname) {
-      tracker.RecordUsage(c);
+      huffman_builder.RecordUsage(c);
     }
 
-    tracker.RecordUsage(TrieWriter::kTerminalValue);
-    tracker.RecordUsage(TrieWriter::kEndOfTableValue);
+    huffman_builder.RecordUsage(TrieWriter::kTerminalValue);
+    huffman_builder.RecordUsage(TrieWriter::kEndOfTableValue);
   }
 
-  return tracker.ToTable();
+  return huffman_builder.ToTable();
 }
 
 }  // namespace
@@ -131,7 +131,7 @@
 
 std::string PreloadedStateGenerator::Generate(
     const std::string& preload_template,
-    const DomainSecurityEntries& entries,
+    const TransportSecurityStateEntries& entries,
     const DomainIDList& domain_ids,
     const Pinsets& pinsets,
     bool verbose) {
@@ -157,20 +157,21 @@
   // efficient Huffman table for the given inputs. This table is used for the
   // second run.
   HuffmanRepresentationTable table = ApproximateHuffman(entries);
-  HuffmanFrequencyTracker tracker;
+  HuffmanBuilder huffman_builder;
   TrieWriter writer(table, domain_ids_map, expect_ct_report_uri_map,
-                    expect_staple_report_uri_map, pinsets_map, &tracker);
+                    expect_staple_report_uri_map, pinsets_map,
+                    &huffman_builder);
   writer.WriteEntries(entries);
   uint32_t initial_length = writer.position();
 
-  HuffmanRepresentationTable optimal_table = tracker.ToTable();
+  HuffmanRepresentationTable optimal_table = huffman_builder.ToTable();
   TrieWriter new_writer(optimal_table, domain_ids_map, expect_ct_report_uri_map,
                         expect_staple_report_uri_map, pinsets_map, nullptr);
 
   uint32_t root_position = new_writer.WriteEntries(entries);
   uint32_t new_length = new_writer.position();
 
-  std::vector<uint8_t> huffman_tree = tracker.ToVector();
+  std::vector<uint8_t> huffman_tree = huffman_builder.ToVector();
 
   new_writer.Flush();
 
@@ -252,7 +253,7 @@
 }
 
 void PreloadedStateGenerator::ProcessExpectCTURIs(
-    const DomainSecurityEntries& entries,
+    const TransportSecurityStateEntries& entries,
     NameIDMap* expect_ct_report_uri_map,
     std::string* tpl) {
   std::string output = "{";
@@ -279,7 +280,7 @@
 }
 
 void PreloadedStateGenerator::ProcessExpectStapleURIs(
-    const DomainSecurityEntries& entries,
+    const TransportSecurityStateEntries& entries,
     NameIDMap* expect_staple_report_uri_map,
     std::string* tpl) {
   std::string output = "{";
diff --git a/net/tools/domain_security_preload_generator/preloaded_state_generator.h b/net/tools/transport_security_state_generator/preloaded_state_generator.h
similarity index 67%
rename from net/tools/domain_security_preload_generator/preloaded_state_generator.h
rename to net/tools/transport_security_state_generator/preloaded_state_generator.h
index 037a595..562c4f38 100644
--- a/net/tools/domain_security_preload_generator/preloaded_state_generator.h
+++ b/net/tools/transport_security_state_generator/preloaded_state_generator.h
@@ -2,18 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_PRELOADED_STATE_GENERATOR_H_
-#define NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_PRELOADED_STATE_GENERATOR_H_
+#ifndef NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_PRELOADED_STATE_GENERATOR_H_
+#define NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_PRELOADED_STATE_GENERATOR_H_
 
 #include <stdint.h>
 
 #include <memory>
 #include <string>
 
-#include "net/tools/domain_security_preload_generator/domain_security_entry.h"
-#include "net/tools/domain_security_preload_generator/pinset.h"
-#include "net/tools/domain_security_preload_generator/pinsets.h"
-#include "net/tools/domain_security_preload_generator/trie/trie_writer.h"
+#include "net/tools/transport_security_state_generator/pinset.h"
+#include "net/tools/transport_security_state_generator/pinsets.h"
+#include "net/tools/transport_security_state_generator/transport_security_state_entry.h"
+#include "net/tools/transport_security_state_generator/trie/trie_writer.h"
 
 namespace net {
 
@@ -29,7 +29,7 @@
   ~PreloadedStateGenerator();
 
   std::string Generate(const std::string& preload_template,
-                       const DomainSecurityEntries& entries,
+                       const TransportSecurityStateEntries& entries,
                        const DomainIDList& domain_ids,
                        const Pinsets& pinsets,
                        bool verbose);
@@ -41,10 +41,10 @@
                         NameIDMap* map,
                         std::string* tpl);
   void ProcessSPKIHashes(const Pinsets& pinset, std::string* tpl);
-  void ProcessExpectCTURIs(const DomainSecurityEntries& entries,
+  void ProcessExpectCTURIs(const TransportSecurityStateEntries& entries,
                            NameIDMap* expect_ct_report_uri_map,
                            std::string* tpl);
-  void ProcessExpectStapleURIs(const DomainSecurityEntries& entries,
+  void ProcessExpectStapleURIs(const TransportSecurityStateEntries& entries,
                                NameIDMap* expect_staple_report_uri_map,
                                std::string* tpl);
   void ProcessPinsets(const Pinsets& pinset,
@@ -56,4 +56,4 @@
 
 }  // namespace net
 
-#endif  // NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_PRELOADED_STATE_GENERATOR_H_
+#endif  // NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_PRELOADED_STATE_GENERATOR_H_
diff --git a/net/tools/domain_security_preload_generator/resources/transport_security_state_static.template b/net/tools/transport_security_state_generator/resources/transport_security_state_static.template
similarity index 95%
rename from net/tools/domain_security_preload_generator/resources/transport_security_state_static.template
rename to net/tools/transport_security_state_generator/resources/transport_security_state_static.template
index 1f76c1d..be15c425 100644
--- a/net/tools/domain_security_preload_generator/resources/transport_security_state_static.template
+++ b/net/tools/transport_security_state_generator/resources/transport_security_state_static.template
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// This file is generated by net/tools/transport_security_state_generator/.
+
 #ifndef NET_HTTP_TRANSPORT_SECURITY_STATE_STATIC_H_
 #define NET_HTTP_TRANSPORT_SECURITY_STATE_STATIC_H_
 
diff --git a/net/tools/domain_security_preload_generator/spki_hash.cc b/net/tools/transport_security_state_generator/spki_hash.cc
similarity index 93%
rename from net/tools/domain_security_preload_generator/spki_hash.cc
rename to net/tools/transport_security_state_generator/spki_hash.cc
index d95de69..a6986fc 100644
--- a/net/tools/domain_security_preload_generator/spki_hash.cc
+++ b/net/tools/transport_security_state_generator/spki_hash.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/tools/domain_security_preload_generator/spki_hash.h"
+#include "net/tools/transport_security_state_generator/spki_hash.h"
 
 #include <string>
 
diff --git a/net/tools/domain_security_preload_generator/spki_hash.h b/net/tools/transport_security_state_generator/spki_hash.h
similarity index 85%
rename from net/tools/domain_security_preload_generator/spki_hash.h
rename to net/tools/transport_security_state_generator/spki_hash.h
index ca07d0a..c65313c 100644
--- a/net/tools/domain_security_preload_generator/spki_hash.h
+++ b/net/tools/transport_security_state_generator/spki_hash.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_SPKI_HASH_H_
-#define NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_SPKI_HASH_H_
+#ifndef NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_SPKI_HASH_H_
+#define NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_SPKI_HASH_H_
 
 #include <stdint.h>
 #include <string>
@@ -46,4 +46,4 @@
 
 }  // namespace net
 
-#endif  // NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_SPKI_HASH_H_
+#endif  // NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_SPKI_HASH_H_
diff --git a/net/tools/transport_security_state_generator/transport_security_state_entry.cc b/net/tools/transport_security_state_generator/transport_security_state_entry.cc
new file mode 100644
index 0000000..189dc7c
--- /dev/null
+++ b/net/tools/transport_security_state_generator/transport_security_state_entry.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 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.
+#include "net/tools/transport_security_state_generator/transport_security_state_entry.h"
+
+namespace net {
+
+namespace transport_security_state {
+
+TransportSecurityStateEntry::TransportSecurityStateEntry() {}
+TransportSecurityStateEntry::~TransportSecurityStateEntry() {}
+
+}  // namespace transport_security_state
+
+}  // namespace
\ No newline at end of file
diff --git a/net/tools/transport_security_state_generator/transport_security_state_entry.h b/net/tools/transport_security_state_generator/transport_security_state_entry.h
new file mode 100644
index 0000000..9a3cff4
--- /dev/null
+++ b/net/tools/transport_security_state_generator/transport_security_state_entry.h
@@ -0,0 +1,61 @@
+// Copyright (c) 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.
+
+#ifndef NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_DOMAIN_SECURITY_ENTRY_H_
+#define NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_DOMAIN_SECURITY_ENTRY_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace net {
+
+namespace transport_security_state {
+
+// TransportSecurityStateEntry represents a preloaded entry.
+struct TransportSecurityStateEntry {
+  TransportSecurityStateEntry();
+  ~TransportSecurityStateEntry();
+
+  std::string hostname;
+
+  bool include_subdomains = false;
+  bool force_https = false;
+
+  bool hpkp_include_subdomains = false;
+  std::string pinset;
+
+  bool expect_ct = false;
+  std::string expect_ct_report_uri;
+
+  bool expect_staple = false;
+  bool expect_staple_include_subdomains = false;
+  std::string expect_staple_report_uri;
+};
+
+using TransportSecurityStateEntries =
+    std::vector<std::unique_ptr<TransportSecurityStateEntry>>;
+
+// TODO(Martijnc): Remove the domain IDs from the preload format.
+// https://crbug.com/661206.
+using DomainIDList = std::vector<std::string>;
+
+// ReversedEntry points to a TransportSecurityStateEntry and contains the
+// reversed hostname for that entry. This is used to construct the trie.
+struct ReversedEntry {
+  ReversedEntry(std::vector<uint8_t> reversed_name,
+                const TransportSecurityStateEntry* entry);
+  ~ReversedEntry();
+
+  std::vector<uint8_t> reversed_name;
+  const TransportSecurityStateEntry* entry;
+};
+
+using ReversedEntries = std::vector<std::unique_ptr<ReversedEntry>>;
+
+}  // namespace transport_security_state
+
+}  // namespace net
+
+#endif  // NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_DOMAIN_SECURITY_ENTRY_H_
diff --git a/net/tools/domain_security_preload_generator/domain_security_preload_generator.cc b/net/tools/transport_security_state_generator/transport_security_state_generator.cc
similarity index 94%
rename from net/tools/domain_security_preload_generator/domain_security_preload_generator.cc
rename to net/tools/transport_security_state_generator/transport_security_state_generator.cc
index 9ad333e..8473414 100644
--- a/net/tools/domain_security_preload_generator/domain_security_preload_generator.cc
+++ b/net/tools/transport_security_state_generator/transport_security_state_generator.cc
@@ -19,16 +19,16 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "crypto/openssl_util.h"
-#include "net/tools/domain_security_preload_generator/cert_util.h"
-#include "net/tools/domain_security_preload_generator/domain_security_entry.h"
-#include "net/tools/domain_security_preload_generator/pinset.h"
-#include "net/tools/domain_security_preload_generator/pinsets.h"
-#include "net/tools/domain_security_preload_generator/preloaded_state_generator.h"
-#include "net/tools/domain_security_preload_generator/spki_hash.h"
+#include "net/tools/transport_security_state_generator/cert_util.h"
+#include "net/tools/transport_security_state_generator/pinset.h"
+#include "net/tools/transport_security_state_generator/pinsets.h"
+#include "net/tools/transport_security_state_generator/preloaded_state_generator.h"
+#include "net/tools/transport_security_state_generator/spki_hash.h"
+#include "net/tools/transport_security_state_generator/transport_security_state_entry.h"
 #include "third_party/boringssl/src/include/openssl/x509v3.h"
 
-using net::transport_security_state::DomainSecurityEntry;
-using net::transport_security_state::DomainSecurityEntries;
+using net::transport_security_state::TransportSecurityStateEntry;
+using net::transport_security_state::TransportSecurityStateEntries;
 using net::transport_security_state::Pinset;
 using net::transport_security_state::Pinsets;
 using net::transport_security_state::PreloadedStateGenerator;
@@ -39,7 +39,7 @@
 
 // Print the command line help.
 void PrintHelp() {
-  std::cout << "domain_security_preload_generator <json-file> <pins-file>"
+  std::cout << "transport_security_state_generator <json-file> <pins-file>"
             << " <template-file> <output-file> [-v]" << std::endl;
 }
 
@@ -50,7 +50,7 @@
 // More info on the format can be found in
 // net/http/transport_security_state_static.json
 bool ParseJSON(const std::string& json,
-               DomainSecurityEntries* entries,
+               TransportSecurityStateEntries* entries,
                Pinsets* pinsets,
                DomainIDList* domain_ids) {
   std::unique_ptr<base::Value> value = base::JSONReader::Read(json);
@@ -73,7 +73,8 @@
       return false;
     }
 
-    std::unique_ptr<DomainSecurityEntry> entry(new DomainSecurityEntry());
+    std::unique_ptr<TransportSecurityStateEntry> entry(
+        new TransportSecurityStateEntry());
 
     if (!parsed->GetString("name", &entry->hostname)) {
       std::cerr << "Could not extract the name for entry " << i << std::endl;
@@ -484,7 +485,7 @@
 }
 
 // Checks if there are two or more entries for the same hostname.
-bool CheckDuplicateEntries(const DomainSecurityEntries& entries) {
+bool CheckDuplicateEntries(const TransportSecurityStateEntries& entries) {
   std::set<std::string> seen_entries;
   for (const auto& entry : entries) {
     if (seen_entries.find(entry->hostname) != seen_entries.cend()) {
@@ -497,7 +498,7 @@
 }
 
 // Checks for entries which have no effect.
-bool CheckNoopEntries(const DomainSecurityEntries& entries) {
+bool CheckNoopEntries(const TransportSecurityStateEntries& entries) {
   for (const auto& entry : entries) {
     if (!entry->force_https && entry->pinset.empty() && !entry->expect_ct &&
         !entry->expect_staple) {
@@ -517,7 +518,7 @@
 }
 
 // Checks all entries for incorrect usage of the includeSubdomains flags.
-bool CheckSubdomainsFlags(const DomainSecurityEntries& entries) {
+bool CheckSubdomainsFlags(const TransportSecurityStateEntries& entries) {
   for (const auto& entry : entries) {
     if (entry->include_subdomains && entry->hpkp_include_subdomains) {
       std::cerr << "Entry for \"" << entry->hostname
@@ -581,7 +582,7 @@
     return 1;
   }
 
-  DomainSecurityEntries entries;
+  TransportSecurityStateEntries entries;
   Pinsets pinsets;
   DomainIDList domain_ids;
 
diff --git a/net/tools/domain_security_preload_generator/trie/trie_bit_buffer.cc b/net/tools/transport_security_state_generator/trie/trie_bit_buffer.cc
similarity index 91%
rename from net/tools/domain_security_preload_generator/trie/trie_bit_buffer.cc
rename to net/tools/transport_security_state_generator/trie/trie_bit_buffer.cc
index 93af94b..5add4ea 100644
--- a/net/tools/domain_security_preload_generator/trie/trie_bit_buffer.cc
+++ b/net/tools/transport_security_state_generator/trie/trie_bit_buffer.cc
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/tools/domain_security_preload_generator/trie/trie_bit_buffer.h"
+#include "net/tools/transport_security_state_generator/trie/trie_bit_buffer.h"
 
 #include "base/logging.h"
-#include "net/tools/domain_security_preload_generator/bit_writer.h"
+#include "net/tools/transport_security_state_generator/bit_writer.h"
 
 namespace net {
 
@@ -73,12 +73,12 @@
 
 void TrieBitBuffer::WriteChar(uint8_t byte,
                               const HuffmanRepresentationTable& table,
-                              HuffmanFrequencyTracker* tracker) {
+                              HuffmanBuilder* huffman_builder) {
   HuffmanRepresentationTable::const_iterator item;
   item = table.find(byte);
   DCHECK(item != table.end());
-  if (tracker) {
-    tracker->RecordUsage(byte);
+  if (huffman_builder) {
+    huffman_builder->RecordUsage(byte);
   }
   WriteBits(item->second.bits, item->second.number_of_bits);
 }
diff --git a/net/tools/domain_security_preload_generator/trie/trie_bit_buffer.h b/net/tools/transport_security_state_generator/trie/trie_bit_buffer.h
similarity index 82%
rename from net/tools/domain_security_preload_generator/trie/trie_bit_buffer.h
rename to net/tools/transport_security_state_generator/trie/trie_bit_buffer.h
index f767fbc7..f49ffb1 100644
--- a/net/tools/domain_security_preload_generator/trie/trie_bit_buffer.h
+++ b/net/tools/transport_security_state_generator/trie/trie_bit_buffer.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_TRIE_TRIE_BIT_BUFFER_H_
-#define NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_TRIE_TRIE_BIT_BUFFER_H_
+#ifndef NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_TRIE_TRIE_BIT_BUFFER_H_
+#define NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_TRIE_TRIE_BIT_BUFFER_H_
 
 #include <stdint.h>
 
 #include <vector>
 
-#include "net/tools/domain_security_preload_generator/huffman/huffman_frequency_tracker.h"
+#include "net/tools/transport_security_state_generator/huffman/huffman_builder.h"
 
 namespace net {
 
@@ -34,16 +34,16 @@
   void WriteBits(uint32_t bits, uint8_t number_of_bits);
 
   // Write a position to the buffer. Actually writes the difference between
-  // |position| and |last_position|. |*last_position| will be updated to equal
+  // |position| and |*last_position|. |*last_position| will be updated to equal
   // the input |position|.
   void WritePosition(uint32_t position, int32_t* last_position);
 
   // Writes the character in |byte| to the buffer using its Huffman
   // representation in |table|. Optionally tracks usage of the character in
-  // |*tracker|.
+  // |*huffman_builder|.
   void WriteChar(uint8_t byte,
                  const HuffmanRepresentationTable& table,
-                 HuffmanFrequencyTracker* tracker);
+                 HuffmanBuilder* huffman_builder);
 
   // Writes the entire buffer to |*writer|. Returns the position |*writer| was
   // at before the buffer was written to it.
@@ -82,4 +82,4 @@
 
 }  // namespace net
 
-#endif  // NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_TRIE_TRIE_BIT_BUFFER_H_
+#endif  // NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_TRIE_TRIE_BIT_BUFFER_H_
diff --git a/net/tools/domain_security_preload_generator/trie/trie_writer.cc b/net/tools/transport_security_state_generator/trie/trie_writer.cc
similarity index 90%
rename from net/tools/domain_security_preload_generator/trie/trie_writer.cc
rename to net/tools/transport_security_state_generator/trie/trie_writer.cc
index 3e0da3c..68dc1da 100644
--- a/net/tools/domain_security_preload_generator/trie/trie_writer.cc
+++ b/net/tools/transport_security_state_generator/trie/trie_writer.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/tools/domain_security_preload_generator/trie/trie_writer.h"
+#include "net/tools/transport_security_state_generator/trie/trie_writer.h"
 
 #include <algorithm>
 
@@ -10,7 +10,7 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
-#include "net/tools/domain_security_preload_generator/trie/trie_bit_buffer.h"
+#include "net/tools/transport_security_state_generator/trie/trie_bit_buffer.h"
 
 namespace net {
 
@@ -44,7 +44,7 @@
 }  // namespace
 
 ReversedEntry::ReversedEntry(std::vector<uint8_t> reversed_name,
-                             const DomainSecurityEntry* entry)
+                             const TransportSecurityStateEntry* entry)
     : reversed_name(reversed_name), entry(entry) {}
 
 ReversedEntry::~ReversedEntry() {}
@@ -54,17 +54,18 @@
                        const NameIDMap& expect_ct_report_uri_map,
                        const NameIDMap& expect_staple_report_uri_map,
                        const NameIDMap& pinsets_map,
-                       HuffmanFrequencyTracker* frequency_tracker)
+                       HuffmanBuilder* huffman_builder)
     : huffman_table_(huffman_table),
       domain_ids_map_(domain_ids_map),
       expect_ct_report_uri_map_(expect_ct_report_uri_map),
       expect_staple_report_uri_map_(expect_staple_report_uri_map),
       pinsets_map_(pinsets_map),
-      frequency_tracker_(frequency_tracker) {}
+      huffman_builder_(huffman_builder) {}
 
 TrieWriter::~TrieWriter() {}
 
-uint32_t TrieWriter::WriteEntries(const DomainSecurityEntries& entries) {
+uint32_t TrieWriter::WriteEntries(
+    const TransportSecurityStateEntries& entries) {
   ReversedEntries reversed_entries;
 
   for (auto const& entry : entries) {
@@ -93,7 +94,7 @@
 
   if (prefix.size()) {
     for (size_t i = 0; i < prefix.size(); ++i) {
-      writer.WriteChar(prefix.at(i), huffman_table_, frequency_tracker_);
+      writer.WriteChar(prefix.at(i), huffman_table_, huffman_builder_);
     }
   }
 
@@ -110,12 +111,12 @@
       }
     }
 
-    writer.WriteChar(candidate, huffman_table_, frequency_tracker_);
+    writer.WriteChar(candidate, huffman_table_, huffman_builder_);
 
     if (candidate == kTerminalValue) {
       DCHECK((sub_entries_end - start) == 1)
           << "Multiple values with the same name";
-      WriteSecurityEntry((*start)->entry, &writer);
+      WriteEntry((*start)->entry, &writer);
     } else {
       RemovePrefix(1, start, sub_entries_end);
       uint32_t position = WriteDispatchTables(start, sub_entries_end);
@@ -125,7 +126,7 @@
     start = sub_entries_end;
   }
 
-  writer.WriteChar(kEndOfTableValue, huffman_table_, frequency_tracker_);
+  writer.WriteChar(kEndOfTableValue, huffman_table_, huffman_builder_);
 
   uint32_t position = buffer_.position();
   writer.Flush();
@@ -133,8 +134,8 @@
   return position;
 }
 
-void TrieWriter::WriteSecurityEntry(const DomainSecurityEntry* entry,
-                                    TrieBitBuffer* writer) {
+void TrieWriter::WriteEntry(const TransportSecurityStateEntry* entry,
+                            TrieBitBuffer* writer) {
   uint8_t include_subdomains = 0;
   if (entry->include_subdomains) {
     include_subdomains = 1;
diff --git a/net/tools/domain_security_preload_generator/trie/trie_writer.h b/net/tools/transport_security_state_generator/trie/trie_writer.h
similarity index 77%
rename from net/tools/domain_security_preload_generator/trie/trie_writer.h
rename to net/tools/transport_security_state_generator/trie/trie_writer.h
index a28aad2..93fce36 100644
--- a/net/tools/domain_security_preload_generator/trie/trie_writer.h
+++ b/net/tools/transport_security_state_generator/trie/trie_writer.h
@@ -2,21 +2,21 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_TRIE_TRIE_WRITER_H_
-#define NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_TRIE_TRIE_WRITER_H_
+#ifndef NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_TRIE_TRIE_WRITER_H_
+#define NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_TRIE_TRIE_WRITER_H_
 
 #include <string>
 #include <vector>
 
-#include "net/tools/domain_security_preload_generator/bit_writer.h"
-#include "net/tools/domain_security_preload_generator/domain_security_entry.h"
-#include "net/tools/domain_security_preload_generator/huffman/huffman_frequency_tracker.h"
+#include "net/tools/transport_security_state_generator/bit_writer.h"
+#include "net/tools/transport_security_state_generator/huffman/huffman_builder.h"
+#include "net/tools/transport_security_state_generator/transport_security_state_entry.h"
 
 namespace net {
 
 namespace transport_security_state {
 
-struct DomainSecurityEntry;
+struct TransportSecurityStateEntry;
 class TrieBitBuffer;
 
 // Maps a name to an index. This is used to track the index of several values
@@ -35,12 +35,12 @@
              const NameIDMap& expect_ct_report_uri_map,
              const NameIDMap& expect_staple_report_uri_map,
              const NameIDMap& pinsets_map,
-             HuffmanFrequencyTracker* frequency_tracker);
+             HuffmanBuilder* huffman_builder);
   ~TrieWriter();
 
   // Constructs a trie containing all |entries|. The output is written to
   // |buffer_|. Returns the position of the trie root.
-  uint32_t WriteEntries(const DomainSecurityEntries& entries);
+  uint32_t WriteEntries(const TransportSecurityStateEntries& entries);
 
   // Returns the position |buffer_| is currently at. The returned value
   // represents the number of bits.
@@ -58,8 +58,8 @@
                                ReversedEntries::iterator end);
 
   // Serializes |*entry| and writes it to |*writer|.
-  void WriteSecurityEntry(const DomainSecurityEntry* entry,
-                          TrieBitBuffer* writer);
+  void WriteEntry(const TransportSecurityStateEntry* entry,
+                  TrieBitBuffer* writer);
 
   // Removes the first |length| characters from all entries between |start| and
   // |end|.
@@ -82,11 +82,11 @@
   const NameIDMap& expect_ct_report_uri_map_;
   const NameIDMap& expect_staple_report_uri_map_;
   const NameIDMap& pinsets_map_;
-  HuffmanFrequencyTracker* frequency_tracker_;
+  HuffmanBuilder* huffman_builder_;
 };
 
 }  // namespace transport_security_state
 
 }  // namespace net
 
-#endif  // NET_TOOLS_DOMAIN_SECURITY_PRELOAD_GENERATOR_TRIE_TRIE_WRITER_H_
+#endif  // NET_TOOLS_TRANSPORT_SECURITY_STATE_GENERATOR_TRIE_TRIE_WRITER_H_
diff --git a/remoting/host/token_validator_base.cc b/remoting/host/token_validator_base.cc
index d461b57..42cafe1 100644
--- a/remoting/host/token_validator_base.cc
+++ b/remoting/host/token_validator_base.cc
@@ -36,6 +36,7 @@
 #include "net/url_request/url_request.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_status.h"
+#include "remoting/base/logging.h"
 #include "url/gurl.h"
 
 namespace {
@@ -236,6 +237,13 @@
     net::X509Certificate* client_cert,
     net::SSLPrivateKey* client_private_key) {
   if (request_) {
+    if (client_cert) {
+      HOST_LOG << "Using certificate issued by: '"
+               << client_cert->issuer().common_name << "' with start date: '"
+               << client_cert->valid_start() << "' and expiry date: '"
+               << client_cert->valid_expiry() << "'";
+    }
+
     request_->ContinueWithCertificate(client_cert, client_private_key);
   }
 }
diff --git a/third_party/WebKit/LayoutTests/W3CImportExpectations b/third_party/WebKit/LayoutTests/W3CImportExpectations
index c4a51c0..64b8391 100644
--- a/third_party/WebKit/LayoutTests/W3CImportExpectations
+++ b/third_party/WebKit/LayoutTests/W3CImportExpectations
@@ -2,7 +2,9 @@
 #
 # This file acts as a blacklist; directories and files not listed here will automatically
 # be found and imported.
-
+#
+# This file is read by webkitpy/w3c/directory_owners_extractor.py to decide who
+# to contact for new failures; to allow it to read
 # When removing blacklist entries:
 # * Comment out the line rather than deleting it, to show it is intentional
 # * Change [ Skip ] to [ Pass ]
@@ -162,7 +164,7 @@
 external/csswg-test/vendor-imports/mozilla/mozilla-central-reftests/filters [ Skip ]
 external/csswg-test/vendor-imports/mozilla/mozilla-central-reftests/background [ Skip ]
 ## Owners: cbiesinger@chromium.org
-# external/csswg-test/vendor-imports/mozilla/mozilla-central-reftests/flexbox [ Skip ]
+# external/csswg-test/vendor-imports/mozilla/mozilla-central-reftests/flexbox [ Pass ]
 external/csswg-test/vendor-imports/mozilla/mozilla-central-reftests/flexbox/reftest.list [ Skip ]
 external/csswg-test/vendor-imports/mozilla/mozilla-central-reftests/fonts3 [ Skip ]
 external/csswg-test/vendor-imports/mozilla/mozilla-central-reftests/images3 [ Skip ]
@@ -326,14 +328,14 @@
 ## Owners: jww@chromium.org
 external/wpt/subresource-integrity [ Skip ]
 ## Owners: foolip@chromium.org
-external/wpt/svg [ Pass ]
+# external/wpt/svg [ Pass ]
 external/wpt/svg/import [ Skip ]
 external/wpt/tools [ Skip ]
 ## Owners: chongz@chromium.org
 # external/wpt/touch-events [ Pass ]
 external/wpt/typedarrays [ Skip ]
 ## Owners: dtapuska@chromium.org
-external/wpt/uievents [ Pass ]
+# external/wpt/uievents [ Pass ]
 external/wpt/url [ Skip ]
 ## Owners: jsbell@chromium.org
 # external/wpt/user-timing [ Pass ]
diff --git a/third_party/WebKit/LayoutTests/inspector/profiler/cpu-profiler-profile-removal.html b/third_party/WebKit/LayoutTests/inspector/profiler/cpu-profiler-profile-removal.html
index 0bd0b122..9868123 100644
--- a/third_party/WebKit/LayoutTests/inspector/profiler/cpu-profiler-profile-removal.html
+++ b/third_party/WebKit/LayoutTests/inspector/profiler/cpu-profiler-profile-removal.html
@@ -17,7 +17,7 @@
 {
     InspectorTest.startProfilerTest(function() {
         function viewLoaded() {
-            var profiles = UI.panels.js_profiler;
+            var profiles = UI.panels.heap_profiler;
             var type = Profiler.ProfileTypeRegistry.instance.cpuProfileType;
             while (type.getProfiles().length !== 0)
                 type.removeProfile(type.getProfiles()[0]);
diff --git a/third_party/WebKit/LayoutTests/inspector/profiler/cpu-profiler-save-load.html b/third_party/WebKit/LayoutTests/inspector/profiler/cpu-profiler-save-load.html
index 6b4aa54..5774543 100644
--- a/third_party/WebKit/LayoutTests/inspector/profiler/cpu-profiler-save-load.html
+++ b/third_party/WebKit/LayoutTests/inspector/profiler/cpu-profiler-save-load.html
@@ -102,7 +102,7 @@
                 }
                 next();
             }
-            var profilesPanel = UI.panels.js_profiler;
+            var profilesPanel = UI.panels.heap_profiler;
             var profileName = file.name.substr(0, file.name.length - ".cpuprofile".length);
             InspectorTest.waitUntilProfileViewIsShown(profileName, checkLoadedContent);
             profilesPanel._loadFromFile(file);
@@ -111,7 +111,7 @@
             {
                 loadedProfileData = this._jsonifiedProfile;
                 onTransferFinished.call(this);
-                UI.panels.js_profiler.showProfile(this);
+                UI.panels.heap_profiler.showProfile(this);
             }
         }
 
diff --git a/third_party/WebKit/LayoutTests/inspector/profiler/cpu-profiler-stopped-removed-race.html b/third_party/WebKit/LayoutTests/inspector/profiler/cpu-profiler-stopped-removed-race.html
index ee5935c1..028ba78 100644
--- a/third_party/WebKit/LayoutTests/inspector/profiler/cpu-profiler-stopped-removed-race.html
+++ b/third_party/WebKit/LayoutTests/inspector/profiler/cpu-profiler-stopped-removed-race.html
@@ -12,7 +12,7 @@
             var cpuProfiler = SDK.targetManager.mainTarget().cpuProfilerModel;
             var targetManager = SDK.targetManager;
             targetManager.addEventListener(SDK.TargetManager.Events.SuspendStateChanged, onSuspendStateChanged);
-            var profilesPanel = UI.panels.js_profiler;
+            var profilesPanel = UI.panels.heap_profiler;
             InspectorTest.addSniffer(cpuProfiler, "stopRecording", stopRecording);
             InspectorTest.addSniffer(profilesPanel, "_addProfileHeader", onAddProfileHeader);
             profilesPanel.toggleRecord();  // Start profiling.
diff --git a/third_party/WebKit/LayoutTests/inspector/profiler/profiler-test.js b/third_party/WebKit/LayoutTests/inspector/profiler/profiler-test.js
index 79e0808..4de21aa3 100644
--- a/third_party/WebKit/LayoutTests/inspector/profiler/profiler-test.js
+++ b/third_party/WebKit/LayoutTests/inspector/profiler/profiler-test.js
@@ -1,12 +1,12 @@
 var initialize_ProfilerTest = function() {
 
-InspectorTest.preloadPanel("js_profiler");
+InspectorTest.preloadPanel("heap_profiler");
 Bindings.TempFile = InspectorTest.TempFileMock;
 
 InspectorTest.startProfilerTest = function(callback)
 {
     InspectorTest.addResult("Profiler was enabled.");
-    InspectorTest.addSniffer(UI.panels.js_profiler, "_addProfileHeader", InspectorTest._profileHeaderAdded, true);
+    InspectorTest.addSniffer(UI.panels.heap_profiler, "_addProfileHeader", InspectorTest._profileHeaderAdded, true);
     InspectorTest.addSniffer(Profiler.ProfileView.prototype, "refresh", InspectorTest._profileViewRefresh, true);
     InspectorTest.safeWrap(callback)();
 };
@@ -46,14 +46,14 @@
 InspectorTest._profileHeaderAdded = function(profile)
 {
     if (InspectorTest._showProfileWhenAdded === profile.title)
-        UI.panels.js_profiler.showProfile(profile);
+        UI.panels.heap_profiler.showProfile(profile);
 };
 
 InspectorTest.waitUntilProfileViewIsShown = function(title, callback)
 {
     callback = InspectorTest.safeWrap(callback);
 
-    var profilesPanel = UI.panels.js_profiler;
+    var profilesPanel = UI.panels.heap_profiler;
     if (profilesPanel.visibleView && profilesPanel.visibleView.profile && profilesPanel.visibleView._profileHeader.title === title)
         callback(profilesPanel.visibleView);
     else
diff --git a/third_party/WebKit/LayoutTests/inspector/user-metrics-expected.txt b/third_party/WebKit/LayoutTests/inspector/user-metrics-expected.txt
index 6ab094d7..bf3430f4 100644
--- a/third_party/WebKit/LayoutTests/inspector/user-metrics-expected.txt
+++ b/third_party/WebKit/LayoutTests/inspector/user-metrics-expected.txt
@@ -44,7 +44,7 @@
     sources : 4
     timeline : 5
 }
-Panel shown: js_profiler
+Panel shown: heap_profiler
 Panel shown: timeline
 Panel shown: audits
 
diff --git a/third_party/WebKit/LayoutTests/inspector/user-metrics.html b/third_party/WebKit/LayoutTests/inspector/user-metrics.html
index ca156a6..453d77d 100644
--- a/third_party/WebKit/LayoutTests/inspector/user-metrics.html
+++ b/third_party/WebKit/LayoutTests/inspector/user-metrics.html
@@ -32,7 +32,7 @@
 
     InspectorTest.addResult("\nrecordPanelShown:");
     InspectorTest.dump(Host.UserMetrics._PanelCodes);
-    UI.viewManager.showView("js_profiler");
+    UI.viewManager.showView("heap_profiler");
     UI.viewManager.showView("timeline");
     UI.viewManager.showView("audits");
 
diff --git a/third_party/WebKit/Source/core/frame/FrameView.cpp b/third_party/WebKit/Source/core/frame/FrameView.cpp
index 9fca55f..e50c31d 100644
--- a/third_party/WebKit/Source/core/frame/FrameView.cpp
+++ b/third_party/WebKit/Source/core/frame/FrameView.cpp
@@ -3044,6 +3044,13 @@
 
   forAllNonThrottledFrameViews([](FrameView& frameView) {
     frameView.lifecycle().advanceTo(DocumentLifecycle::InPrePaint);
+    if (frameView.canThrottleRendering()) {
+      // This frame can be throttled but not throttled, meaning we are not in an
+      // AllowThrottlingScope. Now this frame may contain dirty paint flags, and
+      // we need to propagate the flags into the ancestor chain so that
+      // PrePaintTreeWalk can reach this frame.
+      frameView.setNeedsPaintPropertyUpdate();
+    }
   });
 
   if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled())
diff --git a/third_party/WebKit/Source/core/inspector/browser_protocol.json b/third_party/WebKit/Source/core/inspector/browser_protocol.json
index e3cfff5..ad416b6d 100644
--- a/third_party/WebKit/Source/core/inspector/browser_protocol.json
+++ b/third_party/WebKit/Source/core/inspector/browser_protocol.json
@@ -1704,6 +1704,16 @@
                 "returns": [
                 ],
                 "description": "Clears all entries from an object store."
+            },
+            {
+                "name": "deleteDatabase",
+                "parameters": [
+                    { "name": "securityOrigin", "type": "string", "description": "Security origin." },
+                    { "name": "databaseName", "type": "string", "description": "Database name." }
+                ],
+                "returns": [
+                ],
+                "description": "Deletes a database."
             }
         ]
     },
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 37e7db9..c14b682 100644
--- a/third_party/WebKit/Source/core/inspector/inspector_protocol_config.json
+++ b/third_party/WebKit/Source/core/inspector/inspector_protocol_config.json
@@ -42,7 +42,7 @@
             },
             {
                 "domain": "IndexedDB",
-                "async": ["requestDatabaseNames", "requestDatabase", "requestData", "clearObjectStore"]
+                "async": ["requestDatabaseNames", "requestDatabase", "requestData", "clearObjectStore", "deleteDatabase"]
             },
             {
                 "domain": "LayerTree"
diff --git a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp
index cba284b..3c99a4f3 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp
@@ -779,12 +779,21 @@
   StickyPositionScrollingConstraints constraints;
   FloatSize skippedContainersOffset;
   LayoutBlock* containingBlock = this->containingBlock();
+  // The location container for boxes is not always the containing block.
+  LayoutBox* locationContainer = isLayoutInline()
+                                     ? containingBlock
+                                     : toLayoutBox(this)->locationContainer();
   // Skip anonymous containing blocks.
   while (containingBlock->isAnonymous()) {
-    skippedContainersOffset +=
-        toFloatSize(FloatPoint(containingBlock->frameRect().location()));
     containingBlock = containingBlock->containingBlock();
   }
+  MapCoordinatesFlags flags = 0;
+  skippedContainersOffset =
+      toFloatSize(locationContainer
+                      ->localToAncestorQuadWithoutTransforms(
+                          FloatQuad(), containingBlock, flags)
+                      .boundingBox()
+                      .location());
   LayoutBox* scrollAncestor =
       layer()->ancestorOverflowLayer()->isRootLayer()
           ? nullptr
@@ -812,13 +821,11 @@
   }
   if (containingBlock != scrollAncestor) {
     FloatQuad localQuad(FloatRect(containingBlock->paddingBoxRect()));
-    TransformState transformState(TransformState::ApplyTransformDirection,
-                                  localQuad.boundingBox().center(), localQuad);
-    containingBlock->mapLocalToAncestor(scrollAncestor, transformState,
-                                        ApplyContainerFlip);
-    transformState.flatten();
     scrollContainerRelativePaddingBoxRect =
-        transformState.lastPlanarQuad().boundingBox();
+        containingBlock
+            ->localToAncestorQuadWithoutTransforms(localQuad, scrollAncestor,
+                                                   flags)
+            .boundingBox();
 
     // The sticky position constraint rects should be independent of the current
     // scroll position, so after mapping we add in the scroll position to get
diff --git a/third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp b/third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp
index 9fbdbbdc..919cb04 100644
--- a/third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutBoxModelObjectTest.cpp
@@ -249,4 +249,33 @@
       IntRect(15, 165, 100, 100),
       enclosingIntRect(getScrollContainerRelativeStickyBoxRect(constraints)));
 }
+
+TEST_F(LayoutBoxModelObjectTest, StickyPositionTableContainers) {
+  setBodyInnerHTML(
+      "<style> td, th { height: 50px; width: 50px; } "
+      "#sticky { position: sticky; left: 0; will-change: transform; }"
+      "table {border: none; }"
+      "#scroller { overflow: auto; }"
+      "</style>"
+      "<div id='scroller'>"
+      "<table cellspacing='0' cellpadding='0'>"
+      "    <thead><tr><td></td></tr></thead>"
+      "    <tr><td id='sticky'></td></tr>"
+      "</table></div>");
+  LayoutBoxModelObject* scroller =
+      toLayoutBoxModelObject(getLayoutObjectByElementId("scroller"));
+  PaintLayerScrollableArea* scrollableArea = scroller->getScrollableArea();
+  LayoutBoxModelObject* sticky =
+      toLayoutBoxModelObject(getLayoutObjectByElementId("sticky"));
+  sticky->updateStickyPositionConstraints();
+  const StickyPositionScrollingConstraints& constraints =
+      scrollableArea->stickyConstraintsMap().get(sticky->layer());
+  EXPECT_EQ(IntRect(0, 0, 50, 100),
+            enclosingIntRect(
+                getScrollContainerRelativeContainingBlockRect(constraints)));
+  EXPECT_EQ(
+      IntRect(0, 50, 50, 50),
+      enclosingIntRect(getScrollContainerRelativeStickyBoxRect(constraints)));
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/layout/LayoutObject.cpp b/third_party/WebKit/Source/core/layout/LayoutObject.cpp
index fa4d1c1e..1bb5c34b 100644
--- a/third_party/WebKit/Source/core/layout/LayoutObject.cpp
+++ b/third_party/WebKit/Source/core/layout/LayoutObject.cpp
@@ -2238,14 +2238,28 @@
     const FloatQuad& localQuad,
     const LayoutBoxModelObject* ancestor,
     MapCoordinatesFlags mode) const {
+  return localToAncestorQuadInternal(localQuad, ancestor, mode | UseTransforms);
+}
+
+FloatQuad LayoutObject::localToAncestorQuadWithoutTransforms(
+    const FloatQuad& localQuad,
+    const LayoutBoxModelObject* ancestor,
+    MapCoordinatesFlags mode) const {
+  DCHECK(!(mode & UseTransforms));
+  return localToAncestorQuadInternal(localQuad, ancestor, mode);
+}
+
+FloatQuad LayoutObject::localToAncestorQuadInternal(
+    const FloatQuad& localQuad,
+    const LayoutBoxModelObject* ancestor,
+    MapCoordinatesFlags mode) const {
   // Track the point at the center of the quad's bounding box. As
   // mapLocalToAncestor() calls offsetFromContainer(), it will use that point
   // as the reference point to decide which column's transform to apply in
   // multiple-column blocks.
   TransformState transformState(TransformState::ApplyTransformDirection,
                                 localQuad.boundingBox().center(), localQuad);
-  mapLocalToAncestor(ancestor, transformState,
-                     mode | ApplyContainerFlip | UseTransforms);
+  mapLocalToAncestor(ancestor, transformState, mode | ApplyContainerFlip);
   transformState.flatten();
 
   return transformState.lastPlanarQuad();
diff --git a/third_party/WebKit/Source/core/layout/LayoutObject.h b/third_party/WebKit/Source/core/layout/LayoutObject.h
index 785c2dfe..ae8d7805 100644
--- a/third_party/WebKit/Source/core/layout/LayoutObject.h
+++ b/third_party/WebKit/Source/core/layout/LayoutObject.h
@@ -1226,6 +1226,13 @@
                             const LayoutPoint& preOffset,
                             const LayoutPoint& postOffset) const;
 
+  // Convert a local quad into the coordinate system of container, not
+  // include transforms. See localToAncestorQuad for details.
+  FloatQuad localToAncestorQuadWithoutTransforms(
+      const FloatQuad&,
+      const LayoutBoxModelObject* ancestor,
+      MapCoordinatesFlags = 0) const;
+
   // Return the transformation matrix to map points from local to the coordinate
   // system of a container, taking transforms into account.
   // Passing null for |ancestor| behaves the same as localToAncestorQuad.
@@ -2038,6 +2045,10 @@
       LayoutRect&,
       const LayoutBoxModelObject& paintInvalidationContainer) const;
 
+  FloatQuad localToAncestorQuadInternal(const FloatQuad&,
+                                        const LayoutBoxModelObject* ancestor,
+                                        MapCoordinatesFlags = 0) const;
+
   void clearLayoutRootIfNeeded() const;
 
   bool isInert() const;
diff --git a/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMappingTest.cpp b/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMappingTest.cpp
index c01a0df3..72ad343 100644
--- a/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMappingTest.cpp
+++ b/third_party/WebKit/Source/core/layout/compositing/CompositedLayerMappingTest.cpp
@@ -1299,4 +1299,33 @@
             IntPoint(constraint2.parentRelativeStickyBoxOffset));
 }
 
+TEST_P(CompositedLayerMappingTest, StickyPositionTableCellContentOffset) {
+  setBodyInnerHTML(
+      "<style>body {height: 2000px; width: 2000px;} "
+      "td, th { height: 50px; width: 50px; } "
+      "table {border: none; }"
+      "#scroller { overflow: auto; will-change: transform; height: 50px; }"
+      "#sticky { position: sticky; left: 0; will-change: transform; }"
+      "</style>"
+      "<div id='scroller'><table cellspacing='0' cellpadding='0'>"
+      "    <thead><tr><td></td></tr></thead>"
+      "    <tr><td id='sticky'></td></tr>"
+      "</table></div>");
+  document().view()->updateLifecycleToCompositingCleanPlusScrolling();
+
+  CompositedLayerMapping* sticky =
+      toLayoutBlock(getLayoutObjectByElementId("sticky"))
+          ->layer()
+          ->compositedLayerMapping();
+
+  ASSERT_TRUE(sticky);
+  WebLayerStickyPositionConstraint constraint =
+      sticky->mainGraphicsLayer()
+          ->contentLayer()
+          ->layer()
+          ->stickyPositionConstraint();
+  EXPECT_EQ(IntPoint(0, 50),
+            IntPoint(constraint.parentRelativeStickyBoxOffset));
+}
+
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/paint/PaintPropertyTreeUpdateTests.cpp b/third_party/WebKit/Source/core/paint/PaintPropertyTreeUpdateTests.cpp
index 5aaeb80..fb26bff 100644
--- a/third_party/WebKit/Source/core/paint/PaintPropertyTreeUpdateTests.cpp
+++ b/third_party/WebKit/Source/core/paint/PaintPropertyTreeUpdateTests.cpp
@@ -328,6 +328,9 @@
   EXPECT_EQ(FloatRoundedRect(0, 0, 5, 5), frameContentClip()->clipRect());
 }
 
+// There is also FrameThrottlingTest.UpdatePaintPropertiesOnUnthrottling
+// testing with real frame viewport intersection observer. This one tests
+// paint property update with or without AllowThrottlingScope.
 TEST_P(PaintPropertyTreeUpdateTest, BuildingStopsAtThrottledFrames) {
   setBodyInnerHTML(
       "<style>body { margin: 0; }</style>"
@@ -378,7 +381,7 @@
     // actively throttled descendants.
     document().view()->updateAllLifecyclePhases();
     EXPECT_FALSE(document().layoutView()->needsPaintPropertyUpdate());
-    EXPECT_TRUE(document().layoutView()->descendantNeedsPaintPropertyUpdate());
+    EXPECT_FALSE(document().layoutView()->descendantNeedsPaintPropertyUpdate());
     EXPECT_FALSE(transform->needsPaintPropertyUpdate());
     EXPECT_FALSE(transform->descendantNeedsPaintPropertyUpdate());
     EXPECT_FALSE(iframeLayoutView->needsPaintPropertyUpdate());
diff --git a/third_party/WebKit/Source/core/paint/PrePaintTreeWalk.cpp b/third_party/WebKit/Source/core/paint/PrePaintTreeWalk.cpp
index 426de330..847539ad 100644
--- a/third_party/WebKit/Source/core/paint/PrePaintTreeWalk.cpp
+++ b/third_party/WebKit/Source/core/paint/PrePaintTreeWalk.cpp
@@ -44,12 +44,11 @@
   m_paintInvalidator.processPendingDelayedPaintInvalidations();
 }
 
-bool PrePaintTreeWalk::walk(FrameView& frameView,
+void PrePaintTreeWalk::walk(FrameView& frameView,
                             const PrePaintTreeWalkContext& parentContext) {
   if (frameView.shouldThrottleRendering()) {
-    // The walk was interrupted by throttled rendering so this subtree was not
-    // fully updated.
-    return false;
+    // Skip the throttled frame. Will update it when it becomes unthrottled.
+    return;
   }
 
   PrePaintTreeWalkContext context(parentContext);
@@ -59,17 +58,13 @@
   m_paintInvalidator.invalidatePaintIfNeeded(frameView,
                                              context.paintInvalidatorContext);
 
-  LayoutView* view = frameView.layoutView();
-  bool descendantsFullyUpdated = view ? walk(*view, context) : true;
-  if (descendantsFullyUpdated) {
+  if (LayoutView* view = frameView.layoutView()) {
+    walk(*view, context);
 #if DCHECK_IS_ON()
-    frameView.layoutView()->assertSubtreeClearedPaintInvalidationFlags();
+    view->assertSubtreeClearedPaintInvalidationFlags();
 #endif
-    // If descendants were not fully updated, do not clear flags. During the
-    // next PrePaintTreeWalk, these flags will be used again.
-    frameView.clearNeedsPaintPropertyUpdate();
   }
-  return descendantsFullyUpdated;
+  frameView.clearNeedsPaintPropertyUpdate();
 }
 
 static void updateAuxiliaryObjectProperties(const LayoutObject& object,
@@ -94,7 +89,7 @@
     context.ancestorOverflowPaintLayer = paintLayer;
 }
 
-bool PrePaintTreeWalk::walk(const LayoutObject& object,
+void PrePaintTreeWalk::walk(const LayoutObject& object,
                             const PrePaintTreeWalkContext& parentContext) {
   PrePaintTreeWalkContext context(parentContext);
 
@@ -115,9 +110,7 @@
       !context.paintInvalidatorContext.forcedSubtreeInvalidationFlags &&
       !object
            .shouldCheckForPaintInvalidationRegardlessOfPaintInvalidationState()) {
-    // Even though the subtree was not walked, we know that a walk will not
-    // change anything and can return true as if the subtree was fully updated.
-    return true;
+    return;
   }
 
   m_propertyTreeBuilder.updatePropertiesForSelf(object,
@@ -127,16 +120,13 @@
   m_propertyTreeBuilder.updatePropertiesForChildren(object,
                                                     context.treeBuilderContext);
 
-  bool descendantsFullyUpdated = true;
   for (const LayoutObject* child = object.slowFirstChild(); child;
        child = child->nextSibling()) {
     if (child->isLayoutMultiColumnSpannerPlaceholder()) {
       child->getMutableForPainting().clearPaintFlags();
       continue;
     }
-    bool childFullyUpdated = walk(*child, context);
-    if (!childFullyUpdated)
-      descendantsFullyUpdated = false;
+    walk(*child, context);
   }
 
   if (object.isLayoutPart()) {
@@ -148,19 +138,12 @@
           widget->frameRect().location();
       context.treeBuilderContext.current.paintOffset =
           roundedIntPoint(context.treeBuilderContext.current.paintOffset);
-      bool frameFullyUpdated = walk(*toFrameView(widget), context);
-      if (!frameFullyUpdated)
-        descendantsFullyUpdated = false;
+      walk(*toFrameView(widget), context);
     }
     // TODO(pdr): Investigate RemoteFrameView (crbug.com/579281).
   }
 
-  if (descendantsFullyUpdated) {
-    // If descendants were not updated, do not clear flags. During the next
-    // PrePaintTreeWalk, these flags will be used again.
-    object.getMutableForPainting().clearPaintFlags();
-  }
-  return descendantsFullyUpdated;
+  object.getMutableForPainting().clearPaintFlags();
 }
 
 }  // namespace blink
diff --git a/third_party/WebKit/Source/core/paint/PrePaintTreeWalk.h b/third_party/WebKit/Source/core/paint/PrePaintTreeWalk.h
index 8f0f917..424af92 100644
--- a/third_party/WebKit/Source/core/paint/PrePaintTreeWalk.h
+++ b/third_party/WebKit/Source/core/paint/PrePaintTreeWalk.h
@@ -25,11 +25,8 @@
   void walk(FrameView& rootFrame);
 
  private:
-  // Throttled rendering (see: FrameView::shouldThrottleRendering()) can prevent
-  // updating a subtree. We return true if the subtree was fully walked/updated,
-  // and false if the walk was interrupted by throttling.
-  bool walk(FrameView&, const PrePaintTreeWalkContext&);
-  bool walk(const LayoutObject&, const PrePaintTreeWalkContext&);
+  void walk(FrameView&, const PrePaintTreeWalkContext&);
+  void walk(const LayoutObject&, const PrePaintTreeWalkContext&);
 
   PaintPropertyTreeBuilder m_propertyTreeBuilder;
   PaintInvalidator m_paintInvalidator;
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 35a788f..95ca2a2 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,4 +1,4 @@
-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/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/aria-allowed-attr":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -18,10 +18,14 @@
 
 'use strict';
 
-const Audit = require('./audit');
-const Formatter = require('../formatters/formatter');
+/**
+ * @fileoverview Ensures ARIA attributes are allowed for an element's role.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class ARIAAllowedAttr extends Audit {
+const AxeAudit = require('./axe-audit');
+
+class ARIAAllowedAttr extends AxeAudit {
   /**
    * @return {!AuditMeta}
    */
@@ -29,42 +33,15 @@
     return {
       category: 'Accessibility',
       name: 'aria-allowed-attr',
-      description: 'Element aria-* roles are valid',
+      description: 'Element aria-* attributes are allowed for this role',
       requiredArtifacts: ['Accessibility']
     };
   }
-
-  /**
-   * @param {!Artifacts} artifacts
-   * @return {!AuditResult}
-   */
-  static audit(artifacts) {
-    const rule =
-        artifacts.Accessibility.violations.find(result => result.id === 'aria-allowed-attr');
-
-    return ARIAAllowedAttr.generateAuditResult({
-      rawValue: typeof rule === 'undefined',
-      debugString: this.createDebugString(rule),
-      extendedInfo: {
-        formatter: Formatter.SUPPORTED_FORMATS.ACCESSIBILITY,
-        value: rule
-      }
-    });
-  }
-
-  static createDebugString(rule) {
-    if (typeof rule === 'undefined') {
-      return '';
-    }
-
-    const elementsStr = rule.nodes.length === 1 ? 'element' : 'elements';
-    return `${rule.help} (Failed on ${rule.nodes.length} ${elementsStr})`;
-  }
 }
 
 module.exports = ARIAAllowedAttr;
 
-},{"../formatters/formatter":7,"./audit":"../audits/audit"}],"../audits/aria-valid-attr":[function(require,module,exports){
+},{"./axe-audit":2}],"../audits/accessibility/aria-required-attr":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -84,10 +61,100 @@
 
 'use strict';
 
-const Audit = require('./audit');
-const Formatter = require('../formatters/formatter');
+/**
+ * @fileoverview Ensures elements with ARIA roles have all required ARIA attributes.
+ * See base class in axe-audit.js for audit() implementation.
+ */
 
-class ARIAValidAttr extends Audit {
+const AxeAudit = require('./axe-audit');
+
+class ARIARequiredAttr extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'aria-required-attr',
+      description: 'Elements with ARIA roles have the required aria-* attributes',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
+}
+
+module.exports = ARIARequiredAttr;
+
+},{"./axe-audit":2}],"../audits/accessibility/aria-valid-attr-value":[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';
+
+/**
+ * @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: 'Element aria-* attributes have valid values',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
+}
+
+module.exports = ARIAValidAttr;
+
+},{"./axe-audit":2}],"../audits/accessibility/aria-valid-attr":[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';
+
+/**
+ * @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}
    */
@@ -95,42 +162,15 @@
     return {
       category: 'Accessibility',
       name: 'aria-valid-attr',
-      description: 'Element aria-* attributes are valid ARIA attributes',
+      description: 'Element aria-* attributes are valid and not misspelled or non-existent.',
       requiredArtifacts: ['Accessibility']
     };
   }
-
-  /**
-   * @param {!Artifacts} artifacts
-   * @return {!AuditResult}
-   */
-  static audit(artifacts) {
-    const rule =
-        artifacts.Accessibility.violations.find(result => result.id === 'aria-valid-attr');
-
-    return ARIAValidAttr.generateAuditResult({
-      rawValue: typeof rule === 'undefined',
-      debugString: this.createDebugString(rule),
-      extendedInfo: {
-        formatter: Formatter.SUPPORTED_FORMATS.ACCESSIBILITY,
-        value: rule
-      }
-    });
-  }
-
-  static createDebugString(rule) {
-    if (typeof rule === 'undefined') {
-      return '';
-    }
-
-    const elementsStr = rule.nodes.length === 1 ? 'element' : 'elements';
-    return `${rule.help} (Failed on ${rule.nodes.length} ${elementsStr})`;
-  }
 }
 
 module.exports = ARIAValidAttr;
 
-},{"../formatters/formatter":7,"./audit":"../audits/audit"}],"../audits/audit":[function(require,module,exports){
+},{"./axe-audit":2}],"../audits/accessibility/color-contrast":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -147,63 +187,163 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 'use strict';
 
-const DEFAULT_PASS = 'defaultPass';
+/**
+ * @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.
+ */
 
-class Audit {
-  /**
-   * @return {!String}
-   */
-  static get DEFAULT_PASS() {
-    return DEFAULT_PASS;
-  }
+const AxeAudit = require('./axe-audit');
 
+class ColorContrast extends AxeAudit {
   /**
    * @return {!AuditMeta}
    */
   static get meta() {
-    throw new Error('Audit meta information must be overridden.');
-  }
-
-  /**
-   * @param {!AuditResultInput} result
-   * @return {!AuditResult}
-   */
-  static generateAuditResult(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,
-      debugString: result.debugString,
-      optimalValue: result.optimalValue,
-      extendedInfo: result.extendedInfo,
-      name: this.meta.name,
-      category: this.meta.category,
-      description: this.meta.description,
-      helpText: this.meta.helpText
+      category: 'Accessibility',
+      name: 'color-contrast',
+      description: 'Background and foreground colors have a sufficient contrast ratio',
+      requiredArtifacts: ['Accessibility']
     };
   }
 }
 
-module.exports = Audit;
+module.exports = ColorContrast;
 
-},{}],"../audits/cache-start-url":[function(require,module,exports){
+},{"./axe-audit":2}],"../audits/accessibility/image-alt":[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';
+
+/**
+ * @fileoverview Ensures <img> elements have alternate text or a role of none or presentation.
+ * See base class in axe-audit.js for audit() implementation.
+ */
+
+const AxeAudit = require('./axe-audit');
+
+class ImageAlt extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'image-alt',
+      description: 'Every image element has an alt attribute',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
+}
+
+module.exports = ImageAlt;
+
+},{"./axe-audit":2}],"../audits/accessibility/label":[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';
+
+/**
+ * @fileoverview Ensures every form element has a label.
+ * See base class in axe-audit.js for audit() implementation.
+ */
+
+const AxeAudit = require('./axe-audit');
+
+class Label extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'label',
+      description: 'Every form element has a label',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
+}
+
+module.exports = Label;
+
+},{"./axe-audit":2}],"../audits/accessibility/tabindex":[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';
+
+/**
+ * @fileoverview Ensures tabindex attribute values are not greater than 0.
+ * See base class in axe-audit.js for audit() implementation.
+ */
+
+const AxeAudit = require('./axe-audit');
+
+class TabIndex extends AxeAudit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Accessibility',
+      name: 'tabindex',
+      description: 'No element has a `tabindex` attribute greater than 0',
+      requiredArtifacts: ['Accessibility']
+    };
+  }
+}
+
+module.exports = TabIndex;
+
+},{"./axe-audit":2}],"../audits/cache-start-url":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -244,7 +384,7 @@
    */
   static audit(artifacts) {
     let cacheHasStartUrl = false;
-    const manifest = artifacts.Manifest && artifacts.Manifest.value;
+    const manifest = artifacts.Manifest.value;
     const cacheContents = artifacts.CacheContents;
 
     if (!(manifest && manifest.start_url && manifest.start_url.value)) {
@@ -254,16 +394,16 @@
       });
     }
 
-    if (!(Array.isArray(cacheContents) && artifacts.URL &&
-          artifacts.URL.finalUrl)) {
+    if (!Array.isArray(cacheContents)) {
       return CacheStartUrl.generateAuditResult({
         rawValue: false,
-        debugString: 'No cache or URL detected'
+        debugString: cacheContents.debugString || 'No cache detected'
       });
     }
 
     // Remove any UTM strings.
     const startURL = manifest.start_url.value;
+    /** @const {string} */
     const altStartURL = startURL
         .replace(/\?utm_([^=]*)=([^&]|$)*/, '')
         .replace(/\?$/, '');
@@ -285,73 +425,7 @@
 
 module.exports = CacheStartUrl;
 
-},{"./audit":"../audits/audit"}],"../audits/color-contrast":[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('../formatters/formatter');
-
-class ColorContrast extends Audit {
-  /**
-   * @return {!AuditMeta}
-   */
-  static get meta() {
-    return {
-      category: 'Accessibility',
-      name: 'color-contrast',
-      description: 'Background and foreground colors have a sufficient contrast ratio',
-      requiredArtifacts: ['Accessibility']
-    };
-  }
-
-  /**
-   * @param {!Artifacts} artifacts
-   * @return {!AuditResult}
-   */
-  static audit(artifacts) {
-    const rule =
-        artifacts.Accessibility.violations.find(result => result.id === 'color-contrast');
-
-    return ColorContrast.generateAuditResult({
-      rawValue: typeof rule === 'undefined',
-      debugString: this.createDebugString(rule),
-      extendedInfo: {
-        formatter: Formatter.SUPPORTED_FORMATS.ACCESSIBILITY,
-        value: rule
-      }
-    });
-  }
-
-  static createDebugString(rule) {
-    if (typeof rule === 'undefined') {
-      return '';
-    }
-
-    const elementsStr = rule.nodes.length === 1 ? 'element' : 'elements';
-    return `${rule.help} (Failed on ${rule.nodes.length} ${elementsStr})`;
-  }
-}
-
-module.exports = ColorContrast;
-
-},{"../formatters/formatter":7,"./audit":"../audits/audit"}],"../audits/content-width":[function(require,module,exports){
+},{"./audit":3}],"../audits/content-width":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -382,6 +456,9 @@
       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']
     };
   }
@@ -391,17 +468,9 @@
    * @return {!AuditResult}
    */
   static audit(artifacts) {
-    if (typeof artifacts.ContentWidth === 'undefined' ||
-        typeof artifacts.ContentWidth.scrollWidth === 'undefined' ||
-        typeof artifacts.ContentWidth.viewportWidth === 'undefined') {
-      return ContentWidth.generateAuditResult({
-        rawValue: false,
-        debugString: 'Unable to find scroll and viewport widths.'
-      });
-    }
-
-    const widthsMatch =
-        artifacts.ContentWidth.scrollWidth === artifacts.ContentWidth.viewportWidth;
+    const scrollWidth = artifacts.ContentWidth.scrollWidth;
+    const viewportWidth = artifacts.ContentWidth.viewportWidth;
+    const widthsMatch = scrollWidth === viewportWidth && scrollWidth !== -1;
 
     return ContentWidth.generateAuditResult({
       rawValue: widthsMatch,
@@ -421,7 +490,7 @@
 
 module.exports = ContentWidth;
 
-},{"./audit":"../audits/audit"}],"../audits/critical-request-chains":[function(require,module,exports){
+},{"./audit":3}],"../audits/critical-request-chains":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -454,6 +523,11 @@
       name: 'critical-request-chains',
       description: 'Critical Request Chains',
       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']
     };
   }
@@ -482,10 +556,16 @@
         }, '');
       }
 
-      walk(chains, 0);
+      // Account for initial navigation
+      const initialNavigationKey = Object.keys(chains)[0];
+      const initialNavChildren = chains[initialNavigationKey].children;
+      if (initialNavChildren && Object.keys(initialNavChildren).length > 0) {
+        walk(initialNavChildren, 0);
+      }
 
       return CriticalRequestChains.generateAuditResult({
-        rawValue: chainCount,
+        rawValue: chainCount <= this.meta.optimalValue,
+        displayValue: chainCount,
         optimalValue: this.meta.optimalValue,
         extendedInfo: {
           formatter: Formatter.SUPPORTED_FORMATS.CRITICAL_REQUEST_CHAINS,
@@ -498,7 +578,1186 @@
 
 module.exports = CriticalRequestChains;
 
-},{"../formatters/formatter":7,"./audit":"../audits/audit"}],"../audits/estimated-input-latency":[function(require,module,exports){
+},{"../formatters/formatter":8,"./audit":3}],"../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.
+ */
+
+/**
+ * @fileoverview Audit a page to ensure it is not using the Application Cache API.
+ */
+
+'use strict';
+
+const Audit = require('../audit');
+
+class AppCacheManifestAttr extends Audit {
+
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Offline',
+      name: 'appcache-manifest',
+      description: 'Site does not use 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) {
+    if (artifacts.AppCacheManifest === -1) {
+      return AppCacheManifestAttr.generateAuditResult({
+        rawValue: false,
+        debugString: 'Unable to determine if you\'re using AppCache.'
+      });
+    }
+
+    const usingAppcache = artifacts.AppCacheManifest !== null;
+    const displayValue = usingAppcache ? `<html manifest="${artifacts.AppCacheManifest}">` : '';
+
+    return AppCacheManifestAttr.generateAuditResult({
+      rawValue: !usingAppcache,
+      displayValue: displayValue
+    });
+  }
+}
+
+module.exports = AppCacheManifestAttr;
+
+},{"../audit":3}],"../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 URL = require('../../lib/url-shim');
+const Audit = require('../audit');
+const Formatter = require('../../formatters/formatter');
+
+class ExternalAnchorsUseRelNoopenerAudit extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'external-anchors-use-rel-noopener',
+      description: 'Site 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) {
+    if (artifacts.AnchorsWithNoRelNoopener === -1) {
+      return ExternalAnchorsUseRelNoopenerAudit.generateAuditResult({
+        rawValue: -1,
+        debugString: 'Unknown error with the AnchorsWithNoRelNoopener gatherer.'
+      });
+    }
+
+    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.usages
+      .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.URLLIST,
+        value: failingAnchors
+      },
+      debugString
+    });
+  }
+}
+
+module.exports = ExternalAnchorsUseRelNoopenerAudit;
+
+},{"../../formatters/formatter":8,"../../lib/url-shim":30,"../audit":3}],"../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.
+ */
+
+/**
+ * @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 Formatter = require('../../formatters/formatter');
+
+class GeolocationOnStart extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'UX',
+      name: 'geolocation-on-start',
+      description: 'Page does not automatically request geolocation 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) {
+    if (artifacts.GeolocationOnStart.value === -1) {
+      let debugString = 'Unknown error with the GeolocationOnStart gatherer';
+      if (artifacts.GeolocationOnStart.debugString) {
+        debugString = artifacts.GeolocationOnStart.debugString;
+      }
+
+      return GeolocationOnStart.generateAuditResult({
+        rawValue: -1,
+        debugString
+      });
+    }
+
+    const results = artifacts.GeolocationOnStart.usage.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.URLLIST,
+        value: results
+      }
+    });
+  }
+
+}
+
+module.exports = GeolocationOnStart;
+
+},{"../../formatters/formatter":8,"../audit":3}],"../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.
+ */
+
+/**
+ * @fileoverview Audit a page to see if it does not use <link> that block first paint.
+ */
+
+'use strict';
+
+const Audit = require('../audit');
+const Formatter = require('../../formatters/formatter');
+
+class LinkBlockingFirstPaintAudit extends Audit {
+
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'link-blocking-first-paint',
+      description: 'Site does not use <link> that delay first paint',
+      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']
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @param {string} tagFilter The tagName to filter on
+   * @return {!Object} The object to pass to `generateAuditResult`
+   */
+  static computeAuditResultForTags(artifacts, tagFilter) {
+    const artifact = artifacts.TagsBlockingFirstPaint;
+    if (artifact.value === -1) {
+      return {
+        rawValue: -1,
+        debugString: artifact.debugString
+      };
+    }
+
+    let totalSpendTime = 0;
+    const results = artifact.items
+      .filter(item => item.tag.tagName === tagFilter)
+      .map(item => {
+        totalSpendTime += item.spendTime;
+
+        return {
+          url: item.tag.url,
+          label: `delayed first paint by ${item.spendTime}ms`
+        };
+      });
+
+    let displayValue = '';
+    if (results.length > 1) {
+      displayValue = `${results.length} resources delayed first paint by ${totalSpendTime}ms`;
+    } else if (results.length === 1) {
+      displayValue = `${results.length} resource delayed first paint by ${totalSpendTime}ms`;
+    }
+
+    return {
+      displayValue,
+      rawValue: results.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.URLLIST,
+        value: results
+      }
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const result = LinkBlockingFirstPaintAudit.computeAuditResultForTags(artifacts, 'LINK');
+    return LinkBlockingFirstPaintAudit.generateAuditResult(result);
+  }
+}
+
+module.exports = LinkBlockingFirstPaintAudit;
+
+},{"../../formatters/formatter":8,"../audit":3}],"../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.
+ */
+
+/**
+ * @fileoverview Audit a page to see if it's using console.time()/console.timeEnd.
+ */
+
+'use strict';
+
+const URL = require('../../lib/url-shim');
+const Audit = require('../audit');
+const Formatter = require('../../formatters/formatter');
+
+class NoConsoleTimeAudit extends Audit {
+
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'JavaScript',
+      name: 'no-console-time',
+      description: 'Site does not use `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) {
+    if (artifacts.ConsoleTimeUsage.value === -1) {
+      let debugString = 'Unknown error with the ConsoleTimeUsage gatherer';
+      if (artifacts.ConsoleTimeUsage.debugString) {
+        debugString = artifacts.ConsoleTimeUsage.debugString;
+      }
+
+      return NoConsoleTimeAudit.generateAuditResult({
+        rawValue: -1,
+        debugString
+      });
+    }
+
+    let debugString;
+    // Filter usage from other hosts and keep eval'd code.
+    const results = artifacts.ConsoleTimeUsage.usage.filter(err => {
+      if (err.isEval) {
+        return !!err.url;
+      }
+
+      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;
+    }).map(err => {
+      return Object.assign({
+        label: `line: ${err.line}, col: ${err.col}`
+      }, err);
+    });
+
+    return NoConsoleTimeAudit.generateAuditResult({
+      rawValue: results.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.URLLIST,
+        value: results
+      },
+      debugString
+    });
+  }
+}
+
+module.exports = NoConsoleTimeAudit;
+
+},{"../../formatters/formatter":8,"../../lib/url-shim":30,"../audit":3}],"../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.
+ */
+
+/**
+ * @fileoverview Audit a page to see if it's using Date.now() (instead of a
+ * newer API like performance.now()).
+ */
+
+'use strict';
+
+const URL = require('../../lib/url-shim');
+const Audit = require('../audit');
+const Formatter = require('../../formatters/formatter');
+
+class NoDateNowAudit extends Audit {
+
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'JavaScript',
+      name: 'no-datenow',
+      description: 'Site does not use `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) {
+    if (artifacts.DateNowUse.value === -1) {
+      let debugString = 'Unknown error with the DateNowUse gatherer';
+      if (artifacts.DateNowUse.debugString) {
+        debugString = artifacts.DateNowUse.debugString;
+      }
+
+      return NoDateNowAudit.generateAuditResult({
+        rawValue: -1,
+        debugString
+      });
+    }
+
+    let debugString;
+    // Filter usage from other hosts and keep eval'd code.
+    const results = artifacts.DateNowUse.usage.filter(err => {
+      if (err.isEval) {
+        return !!err.url;
+      }
+
+      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;
+    }).map(err => {
+      return Object.assign({
+        label: `line: ${err.line}, col: ${err.col}`,
+        url: err.url
+      }, err);
+    });
+
+    return NoDateNowAudit.generateAuditResult({
+      rawValue: results.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.URLLIST,
+        value: results
+      },
+      debugString
+    });
+  }
+}
+
+module.exports = NoDateNowAudit;
+
+},{"../../formatters/formatter":8,"../../lib/url-shim":30,"../audit":3}],"../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.
+ */
+
+/**
+ * @fileoverview Audit a page to see if it's using document.write()
+ */
+
+'use strict';
+
+const Audit = require('../audit');
+const Formatter = require('../../formatters/formatter');
+
+class NoDocWriteAudit extends Audit {
+
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'no-document-write',
+      description: 'Site does not use 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) {
+    if (artifacts.DocWriteUse.value === -1) {
+      let debugString = 'Unknown error with the DocWriteUse gatherer';
+      if (artifacts.DocWriteUse.debugString) {
+        debugString = artifacts.DocWriteUse.debugString;
+      }
+
+      return NoDocWriteAudit.generateAuditResult({
+        rawValue: -1,
+        debugString
+      });
+    }
+
+    const results = artifacts.DocWriteUse.usage.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.URLLIST,
+        value: results
+      }
+    });
+  }
+}
+
+module.exports = NoDocWriteAudit;
+
+},{"../../formatters/formatter":8,"../audit":3}],"../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.
+ */
+
+/**
+ * @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 EventHelpers = require('../../lib/event-helpers');
+const Formatter = require('../../formatters/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: 'Site does not use 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) {
+    if (artifacts.EventListeners.rawValue === -1) {
+      return NoMutationEventsAudit.generateAuditResult(artifacts.EventListeners);
+    }
+
+    let debugString;
+    const listeners = artifacts.EventListeners;
+
+    const results = listeners.filter(loc => {
+      const isMutationEvent = this.MUTATION_EVENTS.indexOf(loc.type) !== -1;
+      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.URLLIST,
+        value: groupedResults
+      },
+      debugString
+    });
+  }
+}
+
+module.exports = NoMutationEventsAudit;
+
+},{"../../formatters/formatter":8,"../../lib/event-helpers":22,"../../lib/url-shim":30,"../audit":3}],"../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.
+ */
+
+/**
+ * @fileoverview Audit a page to see if it is using the obsolete
+ *     `display: box` flexbox.
+ */
+
+'use strict';
+
+const Audit = require('../audit');
+const StyleHelpers = require('../../lib/styles-helpers');
+const Formatter = require('../../formatters/formatter');
+
+class NoOldFlexboxAudit extends Audit {
+
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'CSS',
+      name: 'no-old-flexbox',
+      description: 'Site does not use the 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']
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    if (artifacts.Styles.rawValue === -1) {
+      return NoOldFlexboxAudit.generateAuditResult(artifacts.Styles);
+    }
+
+    // https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/
+    // (e.g. box-flex, box-orient, box-flex-group, display: flexbox (2011 version))
+    const propsNames = ['box-flex', 'box-orient', 'box-flex-group', 'display'];
+    const propsNamesWithPrefixes = StyleHelpers.addVendorPrefixes(propsNames);
+    const propsValues = ['box', 'flexbox'];
+    const sheetsUsingDisplayBox = StyleHelpers.filterStylesheetsByUsage(
+        artifacts.Styles, propsNamesWithPrefixes, propsValues);
+    const urlList = [];
+    sheetsUsingDisplayBox.forEach(sheet => {
+      sheet.parsedContent.forEach(props => {
+        const formattedStyleRule = StyleHelpers.getFormattedStyleRule(sheet.content, props);
+        urlList.push({
+          url: sheet.header.sourceURL,
+          label: formattedStyleRule.location,
+          code: formattedStyleRule.styleRule
+        });
+      });
+    });
+
+    return NoOldFlexboxAudit.generateAuditResult({
+      rawValue: sheetsUsingDisplayBox.length === 0,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.URLLIST,
+        value: urlList
+      }
+    });
+  }
+}
+
+module.exports = NoOldFlexboxAudit;
+
+},{"../../formatters/formatter":8,"../../lib/styles-helpers":27,"../audit":3}],"../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.
+ */
+
+/**
+ * @fileoverview Audit a page to ensure that it does not open a database using
+ * the WebSQL API.
+ */
+
+'use strict';
+
+const Audit = require('../audit');
+
+class NoWebSQLAudit extends Audit {
+
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Offline',
+      name: 'no-websql',
+      description: 'Site does not use 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) {
+    if (artifacts.WebSQL.database === -1) {
+      return NoWebSQLAudit.generateAuditResult({
+        rawValue: -1,
+        debugString: artifacts.WebSQL.debugString
+      });
+    }
+
+    const db = artifacts.WebSQL.database;
+    const displayValue = (db && db.database ?
+        `db name: ${db.database.name}, version: ${db.database.version}` : '');
+
+    return NoWebSQLAudit.generateAuditResult({
+      rawValue: !db,
+      displayValue: displayValue
+    });
+  }
+}
+
+module.exports = NoWebSQLAudit;
+
+},{"../audit":3}],"../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.
+ */
+
+/**
+ * @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 Formatter = require('../../formatters/formatter');
+
+class NotificationOnStart extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'UX',
+      name: 'notification-on-start',
+      description: 'Page does not automatically request notification permissions 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) {
+    if (artifacts.NotificationOnStart.value === -1) {
+      let debugString = 'Unknown error with the NotificationOnStart gatherer';
+      if (artifacts.NotificationOnStart.debugString) {
+        debugString = artifacts.NotificationOnStart.debugString;
+      }
+
+      return NotificationOnStart.generateAuditResult({
+        rawValue: -1,
+        debugString
+      });
+    }
+
+    const results = artifacts.NotificationOnStart.usage.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.URLLIST,
+        value: results
+      }
+    });
+  }
+
+}
+
+module.exports = NotificationOnStart;
+
+},{"../../formatters/formatter":8,"../audit":3}],"../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.
+ */
+
+/**
+ * @fileoverview Audit a page to see if it does not use sync <script> in <head>.
+ */
+
+'use strict';
+
+const Audit = require('../audit');
+const LinkBlockingFirstPaintAudit = require('./link-blocking-first-paint');
+
+class ScriptBlockingFirstPaint extends Audit {
+
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'script-blocking-first-paint',
+      description: 'Site does not use <script> in head that delays first paint',
+      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']
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  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){
+/**
+ * @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 Audit a page to ensure that resource loaded over its own
+ * origin are over the http/2 protocol.
+ */
+
+'use strict';
+
+const URL = require('../../lib/url-shim');
+const Audit = require('../audit');
+const Formatter = require('../../formatters/formatter');
+
+class UsesHTTP2Audit extends Audit {
+
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Performance',
+      name: 'uses-http2',
+      description: 'Site 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;
+
+    // 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 {
+        label: 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 UsesHTTP2Audit.generateAuditResult({
+      rawValue: resources.length === 0,
+      displayValue: displayValue,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.URLLIST,
+        value: resources
+      }
+    });
+  }
+}
+
+module.exports = UsesHTTP2Audit;
+
+},{"../../formatters/formatter":8,"../../lib/url-shim":30,"../audit":3}],"../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.
+ */
+
+/**
+ * @fileoverview Audit a page to see if it is using passive event listeners on
+ * scroll-blocking touch and wheel event listeners.
+ */
+
+'use strict';
+
+const URL = require('../../lib/url-shim');
+const Audit = require('../audit');
+const EventHelpers = require('../../lib/event-helpers');
+const Formatter = require('../../formatters/formatter');
+
+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: 'Site 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']
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    if (artifacts.EventListeners.rawValue === -1) {
+      return PassiveEventsAudit.generateAuditResult(artifacts.EventListeners);
+    }
+
+    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.indexOf(loc.type) !== -1;
+      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.URLLIST,
+        value: groupedResults
+      },
+      debugString
+    });
+  }
+}
+
+module.exports = PassiveEventsAudit;
+
+},{"../../formatters/formatter":8,"../../lib/event-helpers":22,"../../lib/url-shim":30,"../audit":3}],"../audits/estimated-input-latency":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -536,7 +1795,12 @@
       name: 'estimated-input-latency',
       description: 'Estimated Input Latency',
       optimalValue: SCORING_POINT_OF_DIMINISHING_RETURNS.toLocaleString() + 'ms',
-      requiredArtifacts: ['traceContents']
+      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']
     };
   }
 
@@ -568,7 +1832,7 @@
       displayValue: `${rawValue}ms`,
       extendedInfo: {
         value: latencyPercentiles,
-        formatter: Formatter.SUPPORTED_FORMATS.ESTIMATED_INPUT_LATENCY
+        formatter: Formatter.SUPPORTED_FORMATS.NULL
       }
     });
   }
@@ -595,7 +1859,7 @@
 
 module.exports = EstimatedInputLatency;
 
-},{"../formatters/formatter":7,"../lib/traces/tracing-processor":25,"./audit":"../audits/audit"}],"../audits/first-meaningful-paint":[function(require,module,exports){
+},{"../formatters/formatter":8,"../lib/traces/tracing-processor":29,"./audit":3}],"../audits/first-meaningful-paint":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -619,14 +1883,16 @@
 const TracingProcessor = require('../lib/traces/tracing-processor');
 const Formatter = require('../formatters/formatter');
 
-const FAILURE_MESSAGE = 'Navigation and first paint timings not found.';
-
 // 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;
 
-const BLOCK_FIRST_MEANINGFUL_PAINT_IF_BLANK_CHARACTERS_MORE_THAN = 200;
+// We want an fMP at or after our fCP, however we see traces with the sole fMP
+// being up to 1ms BEFORE the fCP. We're okay if this happens, however if we see
+// a gap of more than 2 frames (32,000 microseconds), then it's a bug that should
+// be addressed in FirstMeaningfulPaintDetector.cpp
+const FCPFMP_TOLERANCE = 32 * 1000;
 
 class FirstMeaningfulPaint extends Audit {
   /**
@@ -638,7 +1904,9 @@
       name: 'first-meaningful-paint',
       description: 'First meaningful paint',
       optimalValue: SCORING_POINT_OF_DIMINISHING_RETURNS.toLocaleString() + 'ms',
-      requiredArtifacts: ['traceContents']
+      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']
     };
   }
 
@@ -652,30 +1920,8 @@
   static audit(artifacts) {
     return new Promise((resolve, reject) => {
       const traceContents = artifacts.traces[this.DEFAULT_PASS].traceEvents;
-      if (!traceContents || !Array.isArray(traceContents)) {
-        throw new Error(FAILURE_MESSAGE);
-      }
       const evts = this.collectEvents(traceContents);
-
-      const navStart = evts.navigationStart;
-      const fCP = evts.firstContentfulPaint;
-      const fMPbasic = this.findFirstMeaningfulPaint(evts, {});
-      const fMPpageheight = this.findFirstMeaningfulPaint(evts, {pageHeight: true});
-      const fMPwebfont = this.findFirstMeaningfulPaint(evts, {webFont: true});
-      const fMPfull = this.findFirstMeaningfulPaint(evts, {pageHeight: true, webFont: true});
-
-      var data = {
-        navStart,
-        fmpCandidates: {
-          fCP,
-          fMPbasic,
-          fMPpageheight,
-          fMPwebfont,
-          fMPfull
-        }
-      };
-
-      const result = this.calculateScore(data);
+      const result = this.calculateScore(evts);
 
       resolve(FirstMeaningfulPaint.generateAuditResult({
         score: result.score,
@@ -697,25 +1943,23 @@
     });
   }
 
-  static calculateScore(data) {
-    // there are a few candidates for fMP:
-    // * firstContentfulPaint: the first time that text or image content was painted.
-    // * fMP basic: paint after most significant layout
-    // * fMP page height: basic + scaling sigificance to page height
-    // * fMP webfont: basic + waiting for in-flight webfonts to paint
-    // * fMP full: considering both page height + webfont heuristics
+  static calculateScore(evts) {
+    const firstMeaningfulPaint = (evts.firstMeaningfulPaint.ts - evts.navigationStart.ts) / 1000;
+    const firstContentfulPaint = (evts.firstContentfulPaint.ts - evts.navigationStart.ts) / 1000;
 
-    // Calculate the difference from navigation and save all candidates
-    const timings = {};
-    const timingsArr = [];
-    Object.keys(data.fmpCandidates).forEach(name => {
-      const evt = data.fmpCandidates[name];
-      timings[name] = evt && ((evt.ts - data.navStart.ts) / 1000);
-      timingsArr.push(timings[name]);
-    });
-
-    // First meaningful paint is the last timestamp observed from the candidates
-    const firstMeaningfulPaint = timingsArr.reduce((maxTimestamp, curr) => max(maxTimestamp, curr));
+    // Expose the raw, unchanged monotonic timestamps from the trace, along with timing durations
+    const extendedInfo = {
+      timestamps: {
+        navStart: evts.navigationStart.ts,
+        fCP: evts.firstContentfulPaint.ts,
+        fMP: evts.firstMeaningfulPaint.ts
+      },
+      timings: {
+        navStart: 0,
+        fCP: parseFloat(firstContentfulPaint.toFixed(3)),
+        fMP: parseFloat(firstMeaningfulPaint.toFixed(3))
+      }
+    };
 
     // Use the CDF of a log-normal distribution for scoring.
     //   < 1100ms: score≈100
@@ -729,13 +1973,11 @@
     score = Math.min(100, score);
     score = Math.max(0, score);
 
-    timings.navStart = data.navStart.ts / 1000;
-
     return {
       duration: `${firstMeaningfulPaint.toFixed(1)}`,
       score: Math.round(score),
-      rawValue: firstMeaningfulPaint.toFixed(1),
-      extendedInfo: {timings}
+      rawValue: parseFloat(firstMeaningfulPaint.toFixed(1)),
+      extendedInfo
     };
   }
 
@@ -743,257 +1985,48 @@
    * @param {!Array<!Object>} traceData
    */
   static collectEvents(traceData) {
-    let mainFrameID;
-    let navigationStart;
-    let firstContentfulPaint;
-    const layouts = new Map();
-    const paints = [];
-
-    // const model = new DevtoolsTimelineModel(traceData);
-    // const events = model.timelineModel().mainThreadEvents();
-    const events = traceData;
-
     // Parse the trace for our key events and sort them by timestamp.
-    events.filter(e => {
-      return e.cat.includes('blink.user_timing') ||
-        e.name === 'FrameView::performLayout' ||
-        e.name === 'Paint' ||
-        e.name === 'TracingStartedInPage';
-    }).sort((event0, event1) => {
-      return event0.ts - event1.ts;
-    }).forEach(event => {
-      // Grab the page's ID from the first TracingStartedInPage in the trace
-      if (event.name === 'TracingStartedInPage' && !mainFrameID) {
-        mainFrameID = event.args.data.page;
-      }
+    const events = traceData.filter(e => {
+      return e.cat.includes('blink.user_timing') || e.name === 'TracingStartedInPage';
+    }).sort((event0, event1) => event0.ts - event1.ts);
 
-      // Record the navigationStart, but only once TracingStartedInPage has started
-      // which is when mainFrameID exists
-      if (event.name === 'navigationStart' && !!mainFrameID && !navigationStart) {
-        navigationStart = event;
-      }
-      // firstContentfulPaint == the first time that text or image content was
-      // painted. See src/third_party/WebKit/Source/core/paint/PaintTiming.h
-      // COMPAT: firstContentfulPaint trace event first introduced in Chrome 49 (r370921)
-      if (event.name === 'firstContentfulPaint' && event.args.frame === mainFrameID &&
-          !!navigationStart && event.ts >= navigationStart.ts) {
-        firstContentfulPaint = event;
-      }
-      // COMPAT: frame property requires Chrome 52 (r390306)
-      // https://codereview.chromium.org/1922823003
-      if (event.name === 'FrameView::performLayout' &&
-          event.args.counters && event.args.counters.frame === mainFrameID &&
-          !!navigationStart && event.ts >= navigationStart.ts) {
-        layouts.set(event, event.args.counters);
-      }
+    // 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 = events.find(e => e.name === 'TracingStartedInPage');
+    // Filter to just events matching the frame ID for sanity
+    const frameEvents = events.filter(e => e.args.frame === startedInPageEvt.args.data.page);
 
-      if (event.name === 'Paint' && event.args.data.frame === mainFrameID &&
-        !!navigationStart && event.ts >= navigationStart.ts) {
-        paints.push(event);
-      }
-    });
+    // Find our first FCP
+    const firstFCP = frameEvents.find(e => e.name === 'firstContentfulPaint');
+    // Our navStart will be the latest one before fCP.
+    const navigationStart = frameEvents.filter(e =>
+        e.name === 'navigationStart' && e.ts < firstFCP.ts).pop();
+    // fMP will follow at/after the FCP, though we allow some timestamp tolerance
+    const firstMeaningfulPaint = frameEvents.find(e =>
+        e.name === 'firstMeaningfulPaint' && e.ts >= (firstFCP.ts - FCPFMP_TOLERANCE));
+
+    // Sometimes fMP is triggered before fCP
+    if (!firstMeaningfulPaint) {
+      throw new Error('No usable `firstMeaningfulPaint` event found in trace');
+    }
 
     // navigationStart is currently essential to FMP calculation.
     // see: https://github.com/GoogleChrome/lighthouse/issues/753
     if (!navigationStart) {
-      throw new Error('No `navigationStart` event found after `TracingStartedInPage` in trace');
+      throw new Error('No `navigationStart` event found in trace');
     }
 
     return {
       navigationStart,
-      firstContentfulPaint,
-      layouts,
-      paints
+      firstMeaningfulPaint,
+      firstContentfulPaint: firstFCP
     };
   }
-
-  static findFirstMeaningfulPaint(evts, heuristics) {
-    let mostSignificantLayout;
-    let significance = 0;
-    let maxSignificanceSoFar = 0;
-    let pending = 0;
-
-    evts.layouts.forEach((countersObj, layoutEvent) => {
-      const counter = val => countersObj[val];
-
-      function heightRatio() {
-        const ratioBefore = counter('contentsHeightBeforeLayout') / counter('visibleHeight');
-        const ratioAfter = counter('contentsHeightAfterLayout') / counter('visibleHeight');
-        return (max(1, ratioBefore) + max(1, ratioAfter)) / 2;
-      }
-
-      // If there are loading fonts when layout happened, the layout change accounting is postponed
-      // until the font is displayed. However, icon fonts shouldn't block first meaningful paint.
-      // We use a threshold that only web fonts that laid out more than 200 characters
-      // should block first meaningful paint.
-      //   https://docs.google.com/document/d/1BR94tJdZLsin5poeet0XoTW60M0SjvOJQttKT-JK8HI/edit#heading=h.wjx8tsc9m27r
-      function hasTooManyBlankCharactersToBeMeaningful() {
-        return counter('approximateBlankCharacterCount') >
-            BLOCK_FIRST_MEANINGFUL_PAINT_IF_BLANK_CHARACTERS_MORE_THAN;
-      }
-
-      if (!counter('host') || counter('visibleHeight') === 0) {
-        return;
-      }
-
-      const layoutCount = counter('LayoutObjectsThatHadNeverHadLayout') || 0;
-      // layout significance = number of layout objects added / max(1, page height / screen height)
-      significance = (heuristics.pageHeight) ? (layoutCount / heightRatio()) : layoutCount;
-
-      if (heuristics.webFont && hasTooManyBlankCharactersToBeMeaningful()) {
-        pending += significance;
-      } else {
-        significance += pending;
-        pending = 0;
-        if (significance > maxSignificanceSoFar) {
-          maxSignificanceSoFar = significance;
-          mostSignificantLayout = layoutEvent;
-        }
-      }
-    });
-
-    let paintAfterMSLayout;
-    if (mostSignificantLayout) {
-      paintAfterMSLayout = evts.paints.find(e => e.ts > mostSignificantLayout.ts);
-    }
-    return paintAfterMSLayout;
-  }
 }
 
 module.exports = FirstMeaningfulPaint;
 
-/**
- * Math.max, but with NaN values removed
- * @param {...number} _
- */
-function max(_) {
-  const args = [...arguments].filter(val => !isNaN(val));
-  return Math.max.apply(Math, args);
-}
-
-},{"../formatters/formatter":7,"../lib/traces/tracing-processor":25,"./audit":"../audits/audit"}],"../audits/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.
- */
-
-'use strict';
-
-const Audit = require('./audit');
-
-class GeolocationOnStart extends Audit {
-  /**
-   * @return {!AuditMeta}
-   */
-  static get meta() {
-    return {
-      category: 'UX',
-      name: 'geolocation',
-      description: 'Page does not automatically request geolocation',
-      requiredArtifacts: ['GeolocationOnStart']
-    };
-  }
-
-  /**
-   * @param {!Artifacts} artifacts
-   * @return {!AuditResult}
-   */
-  static audit(artifacts) {
-    if (typeof artifacts.GeolocationOnStart === 'undefined' ||
-        artifacts.GeolocationOnStart === -1) {
-      return GeolocationOnStart.generateAuditResult({
-        rawValue: false,
-        debugString: 'Unable to get geolocation values.'
-      });
-    }
-
-    return GeolocationOnStart.generateAuditResult({
-      rawValue: artifacts.GeolocationOnStart
-    });
-  }
-}
-
-module.exports = GeolocationOnStart;
-
-},{"./audit":"../audits/audit"}],"../audits/image-alt":[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('../formatters/formatter');
-
-class ImageAlt extends Audit {
-  /**
-   * @return {!AuditMeta}
-   */
-  static get meta() {
-    return {
-      category: 'Accessibility',
-      name: 'image-alt',
-      description: 'Every image element has an alt attribute',
-      requiredArtifacts: ['Accessibility']
-    };
-  }
-
-  /**
-   * @param {!Artifacts} artifacts
-   * @return {!AuditResult}
-   */
-  static audit(artifacts) {
-    const rule =
-        artifacts.Accessibility.violations.find(result => result.id === 'image-alt');
-
-    return ImageAlt.generateAuditResult({
-      rawValue: typeof rule === 'undefined',
-      debugString: this.createDebugString(rule),
-      extendedInfo: {
-        formatter: Formatter.SUPPORTED_FORMATS.ACCESSIBILITY,
-        value: rule
-      }
-    });
-  }
-
-  static createDebugString(rule) {
-    if (typeof rule === 'undefined') {
-      return '';
-    }
-
-    const elementsStr = rule.nodes.length === 1 ? 'element' : 'elements';
-    return `${rule.help} (Failed on ${rule.nodes.length} ${elementsStr})`;
-  }
-}
-
-module.exports = ImageAlt;
-
-},{"../formatters/formatter":7,"./audit":"../audits/audit"}],"../audits/is-on-https":[function(require,module,exports){
+},{"../formatters/formatter":8,"../lib/traces/tracing-processor":29,"./audit":3}],"../audits/is-on-https":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1023,6 +2056,11 @@
       category: 'Security',
       name: 'is-on-https',
       description: 'Site is on 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']
     };
   }
@@ -1041,73 +2079,7 @@
 
 module.exports = HTTPS;
 
-},{"./audit":"../audits/audit"}],"../audits/label":[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('../formatters/formatter');
-
-class Label extends Audit {
-  /**
-   * @return {!AuditMeta}
-   */
-  static get meta() {
-    return {
-      category: 'Accessibility',
-      name: 'label',
-      description: 'Every form element has a label',
-      requiredArtifacts: ['Accessibility']
-    };
-  }
-
-  /**
-   * @param {!Artifacts} artifacts
-   * @return {!AuditResult}
-   */
-  static audit(artifacts) {
-    const rule =
-        artifacts.Accessibility.violations.find(result => result.id === 'label');
-
-    return Label.generateAuditResult({
-      rawValue: typeof rule === 'undefined',
-      debugString: this.createDebugString(rule),
-      extendedInfo: {
-        formatter: Formatter.SUPPORTED_FORMATS.ACCESSIBILITY,
-        value: rule
-      }
-    });
-  }
-
-  static createDebugString(rule) {
-    if (typeof rule === 'undefined') {
-      return '';
-    }
-
-    const elementsStr = rule.nodes.length === 1 ? 'element' : 'elements';
-    return `${rule.help} (Failed on ${rule.nodes.length} ${elementsStr})`;
-  }
-}
-
-module.exports = Label;
-
-},{"../formatters/formatter":7,"./audit":"../audits/audit"}],"../audits/manifest-background-color":[function(require,module,exports){
+},{"./audit":3}],"../audits/manifest-background-color":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1138,7 +2110,7 @@
     return {
       category: 'Manifest',
       name: 'manifest-background-color',
-      description: 'Manifest contains background_color',
+      description: 'Manifest contains `background_color`',
       requiredArtifacts: ['Manifest']
     };
   }
@@ -1171,7 +2143,7 @@
 
 module.exports = ManifestBackgroundColor;
 
-},{"../formatters/formatter":7,"./audit":"../audits/audit"}],"../audits/manifest-display":[function(require,module,exports){
+},{"../formatters/formatter":8,"./audit":3}],"../audits/manifest-display":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1201,7 +2173,7 @@
     return {
       category: 'Manifest',
       name: 'manifest-display',
-      description: 'Manifest\'s display property is set',
+      description: 'Manifest\'s `display` property is set',
       requiredArtifacts: ['Manifest']
     };
   }
@@ -1211,7 +2183,7 @@
    * @return {boolean}
    */
   static hasRecommendedValue(val) {
-    return (val === 'fullscreen' || val === 'standalone' || val === 'browser');
+    return ['browser', 'fullscreen', 'minimal-ui', 'standalone'].indexOf(val) !== -1;
   }
 
   /**
@@ -1224,17 +2196,20 @@
 
     const hasRecommendedValue = ManifestDisplay.hasRecommendedValue(displayValue);
 
-    return ManifestDisplay.generateAuditResult({
+    const auditResult = {
       rawValue: hasRecommendedValue,
-      displayValue,
-      debugString: 'Manifest display property should be set.'
-    });
+      displayValue
+    };
+    if (!hasRecommendedValue) {
+      auditResult.debugString = 'Manifest display property should be set.';
+    }
+    return ManifestDisplay.generateAuditResult(auditResult);
   }
 }
 
 module.exports = ManifestDisplay;
 
-},{"./audit":"../audits/audit"}],"../audits/manifest-exists":[function(require,module,exports){
+},{"./audit":3}],"../audits/manifest-exists":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1283,7 +2258,7 @@
 
 module.exports = ManifestExists;
 
-},{"./audit":"../audits/audit"}],"../audits/manifest-icons-min-144":[function(require,module,exports){
+},{"./audit":3}],"../audits/manifest-icons-min-144":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1334,11 +2309,19 @@
     }
 
     const matchingIcons = icons.sizeAtLeast(144, /** @type {!Manifest} */ (manifest));
-    const foundSizesDebug = matchingIcons.length ?
-        `Found icons of sizes: ${matchingIcons}` : undefined;
+
+    let displayValue;
+    let debugString;
+    if (matchingIcons.length) {
+      displayValue = `found sizes: ${matchingIcons.join(', ')}`;
+    } else {
+      debugString = 'WARNING: No icons are at least 144px';
+    }
+
     return ManifestIconsMin144.generateAuditResult({
       rawValue: !!matchingIcons.length,
-      debugString: foundSizesDebug
+      displayValue,
+      debugString
     });
   }
 }
@@ -1346,7 +2329,7 @@
 module.exports = ManifestIconsMin144;
 
 
-},{"../lib/icons":20,"./audit":"../audits/audit"}],"../audits/manifest-icons-min-192":[function(require,module,exports){
+},{"../lib/icons":23,"./audit":3}],"../audits/manifest-icons-min-192":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1398,18 +2381,25 @@
 
     const matchingIcons = icons.sizeAtLeast(192, /** @type {!Manifest} */ (manifest));
 
-    const foundSizesDebug = matchingIcons.length ?
-        `Found icons of sizes: ${matchingIcons}` : undefined;
+    let displayValue;
+    let debugString;
+    if (matchingIcons.length) {
+      displayValue = `found sizes: ${matchingIcons.join(', ')}`;
+    } else {
+      debugString = 'WARNING: No icons are at least 192px';
+    }
+
     return ManifestIconsMin192.generateAuditResult({
       rawValue: !!matchingIcons.length,
-      debugString: foundSizesDebug
+      displayValue,
+      debugString
     });
   }
 }
 
 module.exports = ManifestIconsMin192;
 
-},{"../lib/icons":20,"./audit":"../audits/audit"}],"../audits/manifest-name":[function(require,module,exports){
+},{"../lib/icons":23,"./audit":3}],"../audits/manifest-name":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1439,7 +2429,7 @@
     return {
       category: 'Manifest',
       name: 'manifest-name',
-      description: 'Manifest contains name',
+      description: 'Manifest contains `name`',
       requiredArtifacts: ['Manifest']
     };
   }
@@ -1464,7 +2454,7 @@
 
 module.exports = ManifestName;
 
-},{"./audit":"../audits/audit"}],"../audits/manifest-short-name-length":[function(require,module,exports){
+},{"./audit":3}],"../audits/manifest-short-name-length":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1494,7 +2484,7 @@
     return {
       category: 'Manifest',
       name: 'manifest-short-name-length',
-      description: 'Manifest\'s short_name won\'t be truncated when displayed on homescreen',
+      description: 'Manifest\'s `short_name` won\'t be truncated when displayed on homescreen',
       requiredArtifacts: ['Manifest']
     };
   }
@@ -1532,7 +2522,7 @@
 
 module.exports = ManifestShortNameLength;
 
-},{"./audit":"../audits/audit"}],"../audits/manifest-short-name":[function(require,module,exports){
+},{"./audit":3}],"../audits/manifest-short-name":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1562,7 +2552,7 @@
     return {
       category: 'Manifest',
       name: 'manifest-short-name',
-      description: 'Manifest contains short_name',
+      description: 'Manifest contains `short_name`',
       requiredArtifacts: ['Manifest']
     };
   }
@@ -1587,7 +2577,7 @@
 
 module.exports = ManifestShortName;
 
-},{"./audit":"../audits/audit"}],"../audits/manifest-start-url":[function(require,module,exports){
+},{"./audit":3}],"../audits/manifest-start-url":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1617,7 +2607,7 @@
     return {
       category: 'Manifest',
       name: 'manifest-start-url',
-      description: 'Manifest contains start_url',
+      description: 'Manifest contains `start_url`',
       requiredArtifacts: ['Manifest']
     };
   }
@@ -1642,7 +2632,7 @@
 
 module.exports = ManifestStartUrl;
 
-},{"./audit":"../audits/audit"}],"../audits/manifest-theme-color":[function(require,module,exports){
+},{"./audit":3}],"../audits/manifest-theme-color":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1672,7 +2662,7 @@
     return {
       category: 'Manifest',
       name: 'manifest-theme-color',
-      description: 'Manifest contains theme_color',
+      description: 'Manifest contains `theme_color`',
       requiredArtifacts: ['Manifest']
     };
   }
@@ -1697,72 +2687,7 @@
 
 module.exports = ManifestThemeColor;
 
-},{"./audit":"../audits/audit"}],"../audits/meta-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 validColor = require('../lib/web-inspector').Color.parse;
-const Audit = require('./audit');
-
-class ThemeColor extends Audit {
-  /**
-   * @return {!AuditMeta}
-   */
-  static get meta() {
-    return {
-      category: 'HTML',
-      name: 'theme-color-meta',
-      description: 'HTML has a theme-color <meta>',
-      requiredArtifacts: ['ThemeColor']
-    };
-  }
-
-  /**
-   * @param {!Artifacts} artifacts
-   * @return {!AuditResult}
-   */
-  static audit(artifacts) {
-    const themeColorMeta = artifacts.ThemeColor;
-    if (!themeColorMeta) {
-      return ThemeColor.generateAuditResult({
-        rawValue: false,
-        debugString: 'No valid theme-color meta tag found.'
-      });
-    }
-
-    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":"../audits/audit"}],"../audits/redirects-http":[function(require,module,exports){
+},{"./audit":3}],"../audits/redirects-http":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1792,6 +2717,8 @@
       category: 'Security',
       name: 'redirects-http',
       description: 'Site 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']
     };
   }
@@ -1801,13 +2728,6 @@
    * @return {!AuditResult}
    */
   static audit(artifacts) {
-    if (!artifacts.HTTPRedirect ||
-        !artifacts.HTTPRedirect.value) {
-      return RedirectsHTTP.generateAuditResult({
-        rawValue: false
-      });
-    }
-
     return RedirectsHTTP.generateAuditResult({
       rawValue: artifacts.HTTPRedirect.value,
       debugString: artifacts.HTTPRedirect.debugString
@@ -1817,7 +2737,7 @@
 
 module.exports = RedirectsHTTP;
 
-},{"./audit":"../audits/audit"}],"../audits/screenshots":[function(require,module,exports){
+},{"./audit":3}],"../audits/screenshots":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1849,7 +2769,7 @@
       category: 'Performance',
       name: 'screenshots',
       description: 'Screenshots of all captured frames',
-      requiredArtifacts: ['traceContents']
+      requiredArtifacts: ['traces']
     };
   }
 
@@ -1859,12 +2779,6 @@
    */
   static audit(artifacts) {
     const trace = artifacts.traces[this.DEFAULT_PASS];
-    if (typeof trace === 'undefined') {
-      return Promise.resolve(Screenshots.generateAuditResult({
-        rawValue: -1,
-        debugString: 'No trace found to generate screenshots'
-      }));
-    }
 
     return artifacts.requestScreenshots(trace).then(screenshots => {
       if (typeof screenshots === 'undefined') {
@@ -1887,7 +2801,7 @@
 
 module.exports = Screenshots;
 
-},{"../formatters/formatter":7,"./audit":"../audits/audit"}],"../audits/service-worker":[function(require,module,exports){
+},{"../formatters/formatter":8,"./audit":3}],"../audits/service-worker":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -1907,27 +2821,17 @@
 
 'use strict';
 
-const url = require('url');
+const URL = require('../lib/url-shim');
 const Audit = require('./audit');
 
 /**
- * @param {string} targetURL
- * @return {string}
- */
-function getOrigin(targetURL) {
-  const parsedURL = url.parse(targetURL);
-  return `${parsedURL.protocol}//${parsedURL.hostname}` +
-      (parsedURL.port ? `:${parsedURL.port}` : '');
-}
-
-/**
  * @param {!Array<!ServiceWorkerVersion>} versions
  * @param {string} url
  * @return {(!ServiceWorkerVersion|undefined)}
  */
 function getActivatedServiceWorker(versions, url) {
-  const origin = getOrigin(url);
-  return versions.find(v => v.status === 'activated' && getOrigin(v.scriptURL) === origin);
+  const origin = new URL(url).origin;
+  return versions.find(v => v.status === 'activated' && new URL(v.scriptURL).origin === origin);
 }
 
 class ServiceWorker extends Audit {
@@ -1939,6 +2843,9 @@
       category: 'Offline',
       name: 'service-worker',
       description: 'Has a registered 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']
     };
   }
@@ -1971,7 +2878,7 @@
 
 module.exports = ServiceWorker;
 
-},{"./audit":"../audits/audit","url":204}],"../audits/speed-index-metric":[function(require,module,exports){
+},{"../lib/url-shim":30,"./audit":3}],"../audits/speed-index-metric":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -2009,8 +2916,10 @@
       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: ['traceContents']
+      requiredArtifacts: ['traces']
     };
   }
 
@@ -2022,12 +2931,6 @@
    */
   static audit(artifacts) {
     const trace = artifacts.traces[this.DEFAULT_PASS];
-    if (typeof trace === 'undefined') {
-      return SpeedIndexMetric.generateAuditResult({
-        rawValue: -1,
-        debugString: 'No trace found to generate screenshots'
-      });
-    }
 
     // run speedline
     return artifacts.requestSpeedline(trace).then(speedline => {
@@ -2060,9 +2963,18 @@
       score = Math.max(0, score);
 
       const extendedInfo = {
-        first: speedline.first,
-        complete: speedline.complete,
-        duration: speedline.duration,
+        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(),
@@ -2091,7 +3003,7 @@
 
 module.exports = SpeedIndexMetric;
 
-},{"../formatters/formatter":7,"../lib/traces/tracing-processor":25,"./audit":"../audits/audit"}],"../audits/tabindex":[function(require,module,exports){
+},{"../formatters/formatter":8,"../lib/traces/tracing-processor":29,"./audit":3}],"../audits/theme-color-meta":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -2108,22 +3020,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 'use strict';
 
+const validColor = require('../lib/web-inspector').Color.parse;
 const Audit = require('./audit');
-const Formatter = require('../formatters/formatter');
 
-class TabIndex extends Audit {
+class ThemeColor extends Audit {
   /**
    * @return {!AuditMeta}
    */
   static get meta() {
     return {
-      category: 'Accessibility',
-      name: 'tab-index',
-      description: 'No element has a tabindex attribute greater than 0',
-      requiredArtifacts: ['Accessibility']
+      category: 'HTML',
+      name: 'theme-color-meta',
+      description: 'HTML has a `<meta name="theme-color">` tag',
+      requiredArtifacts: ['ThemeColor']
     };
   }
 
@@ -2132,32 +3043,32 @@
    * @return {!AuditResult}
    */
   static audit(artifacts) {
-    const rule =
-        artifacts.Accessibility.violations.find(result => result.id === 'tabindex');
-
-    return TabIndex.generateAuditResult({
-      rawValue: typeof rule === 'undefined',
-      debugString: this.createDebugString(rule),
-      extendedInfo: {
-        formatter: Formatter.SUPPORTED_FORMATS.ACCESSIBILITY,
-        value: rule
-      }
-    });
-  }
-
-  static createDebugString(rule) {
-    if (typeof rule === 'undefined') {
-      return '';
+    const themeColorMeta = artifacts.ThemeColor;
+    if (themeColorMeta === null || themeColorMeta === -1) {
+      return ThemeColor.generateAuditResult({
+        rawValue: false,
+        debugString: 'No valid theme-color meta tag found.'
+      });
     }
 
-    const elementsStr = rule.nodes.length === 1 ? 'element' : 'elements';
-    return `${rule.help} (Failed on ${rule.nodes.length} ${elementsStr})`;
+    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 = TabIndex;
+module.exports = ThemeColor;
 
-},{"../formatters/formatter":7,"./audit":"../audits/audit"}],"../audits/time-to-interactive":[function(require,module,exports){
+},{"../lib/web-inspector":31,"./audit":3}],"../audits/time-to-interactive":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -2177,6 +3088,8 @@
 //   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 {
   /**
@@ -2187,8 +3100,11 @@
       category: 'Performance',
       name: 'time-to-interactive',
       description: 'Time To Interactive (alpha)',
-      optimalValue: SCORING_POINT_OF_DIMINISHING_RETURNS.toLocaleString(),
-      requiredArtifacts: ['traceContents']
+      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']
     };
   }
 
@@ -2228,9 +3144,6 @@
       if (fmpResult.rawValue === -1) {
         return generateError(fmpResult.debugString);
       }
-      const fmpTiming = parseFloat(fmpResult.rawValue);
-      const timings = fmpResult.extendedInfo && fmpResult.extendedInfo.value &&
-          fmpResult.extendedInfo.value.timings;
 
       // Process the trace
       const tracingProcessor = new TracingProcessor();
@@ -2238,23 +3151,22 @@
       const model = tracingProcessor.init(trace);
       const endOfTraceTime = model.bounds.max;
 
-      // TODO: Wait for DOMContentLoadedEndEvent
-      // TODO: Wait for UA loading indicator to be done
+      const fmpTiming = fmpResult.rawValue;
+      const fmpResultExt = fmpResult.extendedInfo.value;
 
-      // TODO CHECK these units are the same
-      const fMPts = timings.fMPfull + timings.navStart;
+      // 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 = fmpResultExt.timestamps.fMP / 1000;
+      const navStartTsInMS = fmpResultExt.timestamps.navStart / 1000;
 
       // look at speedline results for 85% starting at FMP
       let visuallyReadyTiming = 0;
-
       if (speedline.frames) {
         const eightyFivePctVC = speedline.frames.find(frame => {
-          return frame.getTimeStamp() >= fMPts && frame.getProgress() >= 85;
+          return frame.getTimeStamp() >= fMPtsInMS && frame.getProgress() >= 85;
         });
-
         if (eightyFivePctVC) {
-          // TODO CHECK these units are the same
-          visuallyReadyTiming = eightyFivePctVC.getTimeStamp() - timings.navStart;
+          visuallyReadyTiming = eightyFivePctVC.getTimeStamp() - navStartTsInMS;
         }
       }
 
@@ -2278,7 +3190,7 @@
         // Get our expected latency for the time window
         const latencies = TracingProcessor.getRiskToResponsiveness(
           model, trace, startTime, endTime, percentiles);
-        const estLatency = latencies[0].time.toFixed(2);
+        const estLatency = latencies[0].time;
         foundLatencies.push({
           estLatency: estLatency,
           startTime: startTime.toFixed(1)
@@ -2287,7 +3199,8 @@
         // Grab this latency and try the threshold again
         currentLatency = estLatency;
       }
-      const timeToInteractive = parseFloat(startTime.toFixed(1));
+      // The start of our window is our TTI
+      const timeToInteractive = startTime;
 
       // Use the CDF of a log-normal distribution for scoring.
       //   < 1200ms: score≈100
@@ -2304,18 +3217,23 @@
 
       const extendedInfo = {
         timings: {
-          fMP: fmpTiming.toFixed(1),
-          visuallyReady: visuallyReadyTiming.toFixed(1),
-          mainThreadAvail: startTime.toFixed(1)
+          fMP: parseFloat(fmpTiming.toFixed(3)),
+          visuallyReady: parseFloat(visuallyReadyTiming.toFixed(3)),
+          timeToInteractive: parseFloat(startTime.toFixed(3))
         },
-        expectedLatencyAtTTI: currentLatency,
+        timestamps: {
+          fMP: fMPtsInMS * 1000,
+          visuallyReady: (visuallyReadyTiming + navStartTsInMS) * 1000,
+          timeToInteractive: (timeToInteractive + navStartTsInMS) * 1000
+        },
+        expectedLatencyAtTTI: parseFloat(currentLatency.toFixed(3)),
         foundLatencies
       };
 
       return TTIMetric.generateAuditResult({
         score,
-        rawValue: timeToInteractive,
-        displayValue: `${timeToInteractive}ms`,
+        rawValue: parseFloat(timeToInteractive.toFixed(1)),
+        displayValue: `${parseFloat(timeToInteractive.toFixed(1))}ms`,
         optimalValue: this.meta.optimalValue,
         debugString: speedline.debugString,
         extendedInfo: {
@@ -2340,7 +3258,160 @@
   });
 }
 
-},{"../formatters/formatter":7,"../lib/traces/tracing-processor":25,"./audit":"../audits/audit","./first-meaningful-paint":"../audits/first-meaningful-paint"}],"../audits/user-timings":[function(require,module,exports){
+},{"../formatters/formatter":8,"../lib/traces/tracing-processor":29,"./audit":3,"./first-meaningful-paint":"../audits/first-meaningful-paint"}],"../audits/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('./audit');
+const Formatter = require('../formatters/formatter');
+
+const PREVIEW_LENGTH = 100;
+const ALLOWABLE_UNUSED_RULES_RATIO = 0.10;
+
+class UnusedCSSRules extends Audit {
+  /**
+   * @return {!AuditMeta}
+   */
+  static get meta() {
+    return {
+      category: 'Best Practices',
+      name: 'unused-css-rules',
+      description: 'Site does not have more than 10% unused CSS',
+      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']
+    };
+  }
+
+  /**
+   * @param {!Array.<{header: {styleSheetId: string}}>} styles The output of the Styles gatherer.
+   * @return {!Object} A map of styleSheetId to stylesheet information.
+   */
+  static indexStylesheetsById(styles) {
+    return styles.reduce((indexed, stylesheet) => {
+      indexed[stylesheet.header.styleSheetId] = Object.assign({
+        used: [],
+        unused: [],
+      }, 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 (rule.used) {
+        stylesheetInfo.used.push(rule);
+      } else {
+        unused++;
+        stylesheetInfo.unused.push(rule);
+      }
+    });
+
+    return unused;
+  }
+
+  /**
+   * @param {!Object} stylesheetInfo The stylesheetInfo object.
+   * @return {!{url: string, label: string, code: string}} The result for the URLLIST formatter.
+   */
+  static mapSheetToResult(stylesheetInfo) {
+    const numUsed = stylesheetInfo.used.length;
+    const numUnused = stylesheetInfo.unused.length;
+    const percentUsed = Math.round(100 * numUsed / (numUsed + numUnused)) || 0;
+
+    let contentPreview = stylesheetInfo.content;
+    if (contentPreview.length > PREVIEW_LENGTH) {
+      const firstRuleStart = contentPreview.indexOf('{');
+      const firstRuleEnd = contentPreview.indexOf('}');
+      if (firstRuleStart === -1 || firstRuleEnd === -1
+          || firstRuleStart > firstRuleEnd
+          || firstRuleStart > PREVIEW_LENGTH) {
+        contentPreview = contentPreview.slice(0, PREVIEW_LENGTH) + '...';
+      } else if (firstRuleEnd < PREVIEW_LENGTH) {
+        contentPreview = contentPreview.slice(0, firstRuleEnd + 1) + ' ...';
+      } else {
+        const lastSemicolonIndex = contentPreview.slice(0, PREVIEW_LENGTH).lastIndexOf(';');
+        contentPreview = lastSemicolonIndex < firstRuleStart ?
+            contentPreview.slice(0, PREVIEW_LENGTH) + '... } ...' :
+            contentPreview.slice(0, lastSemicolonIndex + 1) + ' ... } ...';
+      }
+    }
+
+    return {
+      url: stylesheetInfo.header.sourceURL || 'inline',
+      label: `${percentUsed}% rules used`,
+      code: contentPreview.trim(),
+    };
+  }
+
+  /**
+   * @param {!Artifacts} artifacts
+   * @return {!AuditResult}
+   */
+  static audit(artifacts) {
+    const styles = artifacts.Styles;
+    const usage = artifacts.CSSUsage;
+
+    if (styles.rawValue === -1) {
+      return UnusedCSSRules.generateAuditResult(styles);
+    } else if (usage.rawValue === -1) {
+      return UnusedCSSRules.generateAuditResult(usage);
+    }
+
+    const indexedSheets = UnusedCSSRules.indexStylesheetsById(styles);
+    const unused = UnusedCSSRules.countUnusedRules(usage, indexedSheets);
+    const unusedRatio = (unused / usage.length) || 0;
+    const results = Object.keys(indexedSheets).map(sheetId => {
+      return UnusedCSSRules.mapSheetToResult(indexedSheets[sheetId]);
+    });
+
+
+    let displayValue = '';
+    if (unused > 1) {
+      displayValue = `${unused} CSS rules were unused`;
+    } else if (unused === 1) {
+      displayValue = `${unused} CSS rule was unused`;
+    }
+
+    return UnusedCSSRules.generateAuditResult({
+      displayValue,
+      rawValue: unusedRatio < ALLOWABLE_UNUSED_RULES_RATIO,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.URLLIST,
+        value: results
+      }
+    });
+  }
+}
+
+module.exports = UnusedCSSRules;
+
+},{"../formatters/formatter":8,"./audit":3}],"../audits/user-timings":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -2364,8 +3435,6 @@
 const Formatter = require('../formatters/formatter');
 const TimelineModel = require('../lib/traces/devtools-timeline-model');
 
-const FAILURE_MESSAGE = 'Trace data not found.';
-
 /**
  * @param {!Array<!Object>} traceData
  * @return {!Array<!UserTimingsExtendedInfo>}
@@ -2393,7 +3462,9 @@
     if (ut.hasCategory('blink.user_timing')) {
       // 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 !(ut.name === 'requestStart' || ut.args.frame !== undefined);
+      return ut.name !== 'requestStart' &&
+          ut.name !== 'paintNonDefaultBackgroundColor' &&
+          ut.args.frame === undefined;
     }
 
     return false;
@@ -2461,7 +3532,10 @@
       category: 'Performance',
       name: 'user-timings',
       description: 'User Timing marks and measures',
-      requiredArtifacts: ['traceContents']
+      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']
     };
   }
 
@@ -2470,34 +3544,23 @@
    * @return {!AuditResult}
    */
   static audit(artifacts) {
-    return new Promise((resolve, reject) => {
-      const traceContents =
-        artifacts.traces[this.DEFAULT_PASS] &&
-        artifacts.traces[this.DEFAULT_PASS].traceEvents;
-      if (!traceContents || !Array.isArray(traceContents)) {
-        throw new Error(FAILURE_MESSAGE);
-      }
+    const traceContents = artifacts.traces[Audit.DEFAULT_PASS].traceEvents;
+    const userTimings = filterTrace(traceContents);
 
-      const userTimings = filterTrace(traceContents);
-      resolve(UserTimings.generateAuditResult({
-        rawValue: userTimings.length,
-        extendedInfo: {
-          formatter: Formatter.SUPPORTED_FORMATS.USER_TIMINGS,
-          value: userTimings
-        }
-      }));
-    }).catch(err => {
-      return UserTimings.generateAuditResult({
-        rawValue: -1,
-        debugString: err.message
-      });
+    return UserTimings.generateAuditResult({
+      rawValue: true,
+      displayValue: userTimings.length,
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.USER_TIMINGS,
+        value: userTimings
+      }
     });
   }
 }
 
 module.exports = UserTimings;
 
-},{"../formatters/formatter":7,"../lib/traces/devtools-timeline-model":24,"./audit":"../audits/audit"}],"../audits/viewport":[function(require,module,exports){
+},{"../formatters/formatter":8,"../lib/traces/devtools-timeline-model":28,"./audit":3}],"../audits/viewport":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -2526,7 +3589,9 @@
     return {
       category: 'Mobile Friendly',
       name: 'viewport',
-      description: 'HTML has a viewport <meta>',
+      description: 'HTML has a `<meta name="viewport">` tag',
+      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']
     };
   }
@@ -2546,7 +3611,7 @@
 
 module.exports = Viewport;
 
-},{"./audit":"../audits/audit"}],"../audits/without-javascript":[function(require,module,exports){
+},{"./audit":3}],"../audits/without-javascript":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -2577,6 +3642,9 @@
       category: 'JavaScript',
       name: 'without-javascript',
       description: 'Page contains some content when its scripts are 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']
     };
   }
@@ -2586,23 +3654,31 @@
    * @return {!AuditResult}
    */
   static audit(artifacts) {
-    let bodyHasContent = true;
-    let debugString;
-    if (artifacts.HTMLWithoutJavaScript.trim() === '') {
-      bodyHasContent = false;
-      debugString = 'The page body should render some content if its scripts are not available.';
+    const artifact = artifacts.HTMLWithoutJavaScript;
+    if (typeof artifact.value !== 'string') {
+      return WithoutJavaScript.generateAuditResult({
+        rawValue: -1,
+        debugString: artifact.debugString ||
+            'HTMLWithoutJavaScript gatherer did not complete successfully'
+      });
+    }
+
+    if (artifact.value.trim() === '') {
+      return WithoutJavaScript.generateAuditResult({
+        rawValue: false,
+        debugString: 'The page body should render some content if its scripts are not available.'
+      });
     }
 
     return WithoutJavaScript.generateAuditResult({
-      rawValue: bodyHasContent,
-      debugString
+      rawValue: true
     });
   }
 }
 
 module.exports = WithoutJavaScript;
 
-},{"./audit":"../audits/audit"}],"../audits/works-offline":[function(require,module,exports){
+},{"./audit":3}],"../audits/works-offline":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -2632,6 +3708,9 @@
       category: 'Offline',
       name: 'works-offline',
       description: 'URL 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']
     };
   }
@@ -2649,7 +3728,7 @@
 
 module.exports = WorksOffline;
 
-},{"./audit":"../audits/audit"}],"./computed/computed-artifact":[function(require,module,exports){
+},{"./audit":3}],"./computed/computed-artifact":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -2680,7 +3759,7 @@
    * Override to implement a computed artifact. Can return a Promise or the
    * computed artifact itself.
    * @param {!Object} artifact Input to computation.
-   * @return {!Promise|!Object|!Array}
+   * @throws {Error}
    */
   compute_(artifact) {
     throw new Error('compute_() not implemented for computed artifact ' + this.name);
@@ -2848,7 +3927,7 @@
 
 module.exports = CriticalRequestChains;
 
-},{"../../lib/web-inspector":26,"./computed-artifact":"./computed/computed-artifact"}],"./computed/pushed-requests":[function(require,module,exports){
+},{"../../lib/web-inspector":31,"./computed-artifact":"./computed/computed-artifact"}],"./computed/pushed-requests":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -2945,7 +4024,7 @@
 
 module.exports = ScreenshotFilmstrip;
 
-},{"../../lib/traces/devtools-timeline-model":24,"./computed-artifact":"./computed/computed-artifact"}],"./computed/speedline":[function(require,module,exports){
+},{"../../lib/traces/devtools-timeline-model":28,"./computed-artifact":"./computed/computed-artifact"}],"./computed/speedline":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -2989,7 +4068,7 @@
 
 module.exports = Speedline;
 
-},{"./computed-artifact":"./computed/computed-artifact","speedline":254}],"./gatherers/accessibility":[function(require,module,exports){
+},{"./computed-artifact":"./computed/computed-artifact","speedline":284}],"./gatherers/accessibility":[function(require,module,exports){
 (function (Buffer){
 /**
  * @license
@@ -3013,13 +4092,27 @@
 
 const Gatherer = require('./gatherer');
 
-const axe = Buffer("LyohIGFYZSB2MS4xLjEKICogQ29weXJpZ2h0IChjKSAyMDE1IERlcXVlIFN5c3RlbXMsIEluYy4KICoKICogWW91ciB1c2Ugb2YgdGhpcyBTb3VyY2UgQ29kZSBGb3JtIGlzIHN1YmplY3QgdG8gdGhlIHRlcm1zIG9mIHRoZSBNb3ppbGxhIFB1YmxpYwogKiBMaWNlbnNlLCB2LiAyLjAuIElmIGEgY29weSBvZiB0aGUgTVBMIHdhcyBub3QgZGlzdHJpYnV0ZWQgd2l0aCB0aGlzCiAqIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uCiAqCiAqIFRoaXMgZW50aXJlIGNvcHlyaWdodCBub3RpY2UgbXVzdCBhcHBlYXIgaW4gZXZlcnkgY29weSBvZiB0aGlzIGZpbGUgeW91CiAqIGRpc3RyaWJ1dGUgb3IgaW4gYW55IGZpbGUgdGhhdCBjb250YWlucyBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGlzIHNvdXJjZQogKiBjb2RlLgogKi8KIWZ1bmN0aW9uKGEsYil7ZnVuY3Rpb24gYyhhKXsidXNlIHN0cmljdCI7dmFyIGIsZCxlPWE7aWYobnVsbCE9PWEmJiJvYmplY3QiPT10eXBlb2YgYSlpZihBcnJheS5pc0FycmF5KGEpKWZvcihlPVtdLGI9MCxkPWEubGVuZ3RoO2Q+YjtiKyspZVtiXT1jKGFbYl0pO2Vsc2V7ZT17fTtmb3IoYiBpbiBhKWVbYl09YyhhW2JdKX1yZXR1cm4gZX1mdW5jdGlvbiBkKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1hfHx7fTtyZXR1cm4gYi5ydWxlcz1iLnJ1bGVzfHxbXSxiLnRvb2xzPWIudG9vbHN8fFtdLGIuY2hlY2tzPWIuY2hlY2tzfHxbXSxiLmRhdGE9Yi5kYXRhfHx7Y2hlY2tzOnt9LHJ1bGVzOnt9fSxifWZ1bmN0aW9uIGUoYSxiLGMpeyJ1c2Ugc3RyaWN0Ijt2YXIgZCxlO2ZvcihkPTAsZT1hLmxlbmd0aDtlPmQ7ZCsrKWJbY10oYVtkXSl9ZnVuY3Rpb24gZihhKXsidXNlIHN0cmljdCI7YT1kKGEpLFMuY29tbW9ucz1SPWEuY29tbW9ucyx0aGlzLnJlcG9ydGVyPWEucmVwb3J0ZXIsdGhpcy5ydWxlcz1bXSx0aGlzLnRvb2xzPXt9LHRoaXMuY2hlY2tzPXt9LGUoYS5ydWxlcyx0aGlzLCJhZGRSdWxlIiksZShhLnRvb2xzLHRoaXMsImFkZFRvb2wiKSxlKGEuY2hlY2tzLHRoaXMsImFkZENoZWNrIiksdGhpcy5kYXRhPWEuZGF0YXx8e2NoZWNrczp7fSxydWxlczp7fX0sSChhLnN0eWxlKX1mdW5jdGlvbiBnKGEpeyJ1c2Ugc3RyaWN0Ijt0aGlzLmlkPWEuaWQsdGhpcy5kYXRhPW51bGwsdGhpcy5yZWxhdGVkTm9kZXM9W10sdGhpcy5yZXN1bHQ9bnVsbH1mdW5jdGlvbiBoKGEpeyJ1c2Ugc3RyaWN0Ijt0aGlzLmlkPWEuaWQsdGhpcy5vcHRpb25zPWEub3B0aW9ucyx0aGlzLnNlbGVjdG9yPWEuc2VsZWN0b3IsdGhpcy5ldmFsdWF0ZT1hLmV2YWx1YXRlLGEuYWZ0ZXImJih0aGlzLmFmdGVyPWEuYWZ0ZXIpLGEubWF0Y2hlcyYmKHRoaXMubWF0Y2hlcz1hLm1hdGNoZXMpLHRoaXMuZW5hYmxlZD1hLmhhc093blByb3BlcnR5KCJlbmFibGVkIik/YS5lbmFibGVkOiEwfWZ1bmN0aW9uIGkoYSxiKXsidXNlIHN0cmljdCI7aWYoIVQuaXNIaWRkZW4oYikpe3ZhciBjPVQuZmluZEJ5KGEsIm5vZGUiLGIpO2N8fGEucHVzaCh7bm9kZTpiLGluY2x1ZGU6W10sZXhjbHVkZTpbXX0pfX1mdW5jdGlvbiBqKGEsYyxkKXsidXNlIHN0cmljdCI7YS5mcmFtZXM9YS5mcmFtZXN8fFtdO3ZhciBlLGYsZz1iLnF1ZXJ5U2VsZWN0b3JBbGwoZC5zaGlmdCgpKTthOmZvcih2YXIgaD0wLGk9Zy5sZW5ndGg7aT5oO2grKyl7Zj1nW2hdO2Zvcih2YXIgaj0wLGs9YS5mcmFtZXMubGVuZ3RoO2s+ajtqKyspaWYoYS5mcmFtZXNbal0ubm9kZT09PWYpe2EuZnJhbWVzW2pdW2NdLnB1c2goZCk7YnJlYWsgYX1lPXtub2RlOmYsaW5jbHVkZTpbXSxleGNsdWRlOltdfSxkJiZlW2NdLnB1c2goZCksYS5mcmFtZXMucHVzaChlKX19ZnVuY3Rpb24gayhhKXsidXNlIHN0cmljdCI7aWYoYSYmIm9iamVjdCI9PXR5cGVvZiBhfHxhIGluc3RhbmNlb2YgTm9kZUxpc3Qpe2lmKGEgaW5zdGFuY2VvZiBOb2RlKXJldHVybntpbmNsdWRlOlthXSxleGNsdWRlOltdfTtpZihhLmhhc093blByb3BlcnR5KCJpbmNsdWRlIil8fGEuaGFzT3duUHJvcGVydHkoImV4Y2x1ZGUiKSlyZXR1cm57aW5jbHVkZTphLmluY2x1ZGV8fFtiXSxleGNsdWRlOmEuZXhjbHVkZXx8W119O2lmKGEubGVuZ3RoPT09K2EubGVuZ3RoKXJldHVybntpbmNsdWRlOmEsZXhjbHVkZTpbXX19cmV0dXJuInN0cmluZyI9PXR5cGVvZiBhP3tpbmNsdWRlOlthXSxleGNsdWRlOltdfTp7aW5jbHVkZTpbYl0sZXhjbHVkZTpbXX19ZnVuY3Rpb24gbChhLGMpeyJ1c2Ugc3RyaWN0Ijtmb3IodmFyIGQsZT1bXSxmPTAsZz1hW2NdLmxlbmd0aDtnPmY7ZisrKXtpZihkPWFbY11bZl0sInN0cmluZyI9PXR5cGVvZiBkKXtlPWUuY29uY2F0KFQudG9BcnJheShiLnF1ZXJ5U2VsZWN0b3JBbGwoZCkpKTticmVha31kJiZkLmxlbmd0aD9kLmxlbmd0aD4xP2ooYSxjLGQpOmU9ZS5jb25jYXQoVC50b0FycmF5KGIucXVlcnlTZWxlY3RvckFsbChkWzBdKSkpOmUucHVzaChkKX1yZXR1cm4gZS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIGF9KX1mdW5jdGlvbiBtKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz10aGlzO3RoaXMuZnJhbWVzPVtdLHRoaXMuaW5pdGlhdG9yPWEmJiJib29sZWFuIj09dHlwZW9mIGEuaW5pdGlhdG9yP2EuaW5pdGlhdG9yOiEwLHRoaXMucGFnZT0hMSxhPWsoYSksdGhpcy5leGNsdWRlPWEuZXhjbHVkZSx0aGlzLmluY2x1ZGU9YS5pbmNsdWRlLHRoaXMuaW5jbHVkZT1sKHRoaXMsImluY2x1ZGUiKSx0aGlzLmV4Y2x1ZGU9bCh0aGlzLCJleGNsdWRlIiksVC5zZWxlY3QoImZyYW1lLCBpZnJhbWUiLHRoaXMpLmZvckVhY2goZnVuY3Rpb24oYSl7TShhLGMpJiZpKGMuZnJhbWVzLGEpfSksMT09PXRoaXMuaW5jbHVkZS5sZW5ndGgmJnRoaXMuaW5jbHVkZVswXT09PWImJih0aGlzLnBhZ2U9ITApfWZ1bmN0aW9uIG4oYSl7InVzZSBzdHJpY3QiO3RoaXMuaWQ9YS5pZCx0aGlzLnJlc3VsdD1TLmNvbnN0YW50cy5yZXN1bHQuTkEsdGhpcy5wYWdlTGV2ZWw9YS5wYWdlTGV2ZWwsdGhpcy5pbXBhY3Q9bnVsbCx0aGlzLm5vZGVzPVtdfWZ1bmN0aW9uIG8oYSxiKXsidXNlIHN0cmljdCI7dGhpcy5fYXVkaXQ9Yix0aGlzLmlkPWEuaWQsdGhpcy5zZWxlY3Rvcj1hLnNlbGVjdG9yfHwiKiIsdGhpcy5leGNsdWRlSGlkZGVuPSJib29sZWFuIj09dHlwZW9mIGEuZXhjbHVkZUhpZGRlbj9hLmV4Y2x1ZGVIaWRkZW46ITAsdGhpcy5lbmFibGVkPSJib29sZWFuIj09dHlwZW9mIGEuZW5hYmxlZD9hLmVuYWJsZWQ6ITAsdGhpcy5wYWdlTGV2ZWw9ImJvb2xlYW4iPT10eXBlb2YgYS5wYWdlTGV2ZWw/YS5wYWdlTGV2ZWw6ITEsdGhpcy5hbnk9YS5hbnl8fFtdLHRoaXMuYWxsPWEuYWxsfHxbXSx0aGlzLm5vbmU9YS5ub25lfHxbXSx0aGlzLnRhZ3M9YS50YWdzfHxbXSxhLm1hdGNoZXMmJih0aGlzLm1hdGNoZXM9YS5tYXRjaGVzKX1mdW5jdGlvbiBwKGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4gVC5nZXRBbGxDaGVja3MoYSkubWFwKGZ1bmN0aW9uKGIpe3ZhciBjPWEuX2F1ZGl0LmNoZWNrc1tiLmlkfHxiXTtyZXR1cm4iZnVuY3Rpb24iPT10eXBlb2YgYy5hZnRlcj9jOm51bGx9KS5maWx0ZXIoQm9vbGVhbil9ZnVuY3Rpb24gcShhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1bXTtyZXR1cm4gYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe3ZhciBkPVQuZ2V0QWxsQ2hlY2tzKGEpO2QuZm9yRWFjaChmdW5jdGlvbihhKXthLmlkPT09YiYmYy5wdXNoKGEpfSl9KSxjfWZ1bmN0aW9uIHIoYSl7InVzZSBzdHJpY3QiO3JldHVybiBhLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYS5maWx0ZXJlZCE9PSEwfSl9ZnVuY3Rpb24gcyhhKXsidXNlIHN0cmljdCI7dmFyIGI9WyJhbnkiLCJhbGwiLCJub25lIl0sYz1hLm5vZGVzLmZpbHRlcihmdW5jdGlvbihhKXt2YXIgYz0wO3JldHVybiBiLmZvckVhY2goZnVuY3Rpb24oYil7YVtiXT1yKGFbYl0pLGMrPWFbYl0ubGVuZ3RofSksYz4wfSk7cmV0dXJuIGEucGFnZUxldmVsJiZjLmxlbmd0aCYmKGM9W2MucmVkdWNlKGZ1bmN0aW9uKGEsYyl7cmV0dXJuIGE/KGIuZm9yRWFjaChmdW5jdGlvbihiKXthW2JdLnB1c2guYXBwbHkoYVtiXSxjW2JdKX0pLGEpOnZvaWQgMH0pXSksY31mdW5jdGlvbiB0KGEpeyJ1c2Ugc3RyaWN0IjthLnNvdXJjZT1hLnNvdXJjZXx8e30sdGhpcy5pZD1hLmlkLHRoaXMub3B0aW9ucz1hLm9wdGlvbnMsdGhpcy5fcnVuPWEuc291cmNlLnJ1bix0aGlzLl9jbGVhbnVwPWEuc291cmNlLmNsZWFudXAsdGhpcy5hY3RpdmU9ITF9ZnVuY3Rpb24gdShhKXsidXNlIHN0cmljdCI7aWYoIVMuX2F1ZGl0KXRocm93IG5ldyBFcnJvcigiTm8gYXVkaXQgY29uZmlndXJlZCIpO3ZhciBjPVQucXVldWUoKTtPYmplY3Qua2V5cyhTLl9hdWRpdC50b29scykuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgYj1TLl9hdWRpdC50b29sc1thXTtiLmFjdGl2ZSYmYy5kZWZlcihmdW5jdGlvbihhKXtiLmNsZWFudXAoYSl9KX0pLFQudG9BcnJheShiLnF1ZXJ5U2VsZWN0b3JBbGwoImZyYW1lLCBpZnJhbWUiKSkuZm9yRWFjaChmdW5jdGlvbihhKXtjLmRlZmVyKGZ1bmN0aW9uKGIpe3JldHVybiBULnNlbmRDb21tYW5kVG9GcmFtZShhLHtjb21tYW5kOiJjbGVhbnVwLXRvb2wifSxiKX0pfSksYy50aGVuKGEpfWZ1bmN0aW9uIHYoYSxjKXsidXNlIHN0cmljdCI7dmFyIGQ9YSYmYS5jb250ZXh0fHx7fTtkLmluY2x1ZGUmJiFkLmluY2x1ZGUubGVuZ3RoJiYoZC5pbmNsdWRlPVtiXSk7dmFyIGU9YSYmYS5vcHRpb25zfHx7fTtzd2l0Y2goYS5jb21tYW5kKXtjYXNlInJ1bGVzIjpyZXR1cm4geChkLGUsYyk7Y2FzZSJydW4tdG9vbCI6cmV0dXJuIHkoYS5wYXJhbWV0ZXIsYS5zZWxlY3RvckFycmF5LGUsYyk7Y2FzZSJjbGVhbnVwLXRvb2wiOnJldHVybiB1KGMpfX1mdW5jdGlvbiB3KGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4ic3RyaW5nIj09dHlwZW9mIGEmJldbYV0/V1thXToiZnVuY3Rpb24iPT10eXBlb2YgYT9hOlZ9ZnVuY3Rpb24geChhLGIsYyl7InVzZSBzdHJpY3QiO2E9bmV3IG0oYSk7dmFyIGQ9VC5xdWV1ZSgpLGU9Uy5fYXVkaXQ7YS5mcmFtZXMubGVuZ3RoJiZkLmRlZmVyKGZ1bmN0aW9uKGMpe1QuY29sbGVjdFJlc3VsdHNGcm9tRnJhbWVzKGEsYiwicnVsZXMiLG51bGwsYyl9KSxkLmRlZmVyKGZ1bmN0aW9uKGMpe2UucnVuKGEsYixjKX0pLGQudGhlbihmdW5jdGlvbihkKXt2YXIgZj1ULm1lcmdlUmVzdWx0cyhkLm1hcChmdW5jdGlvbihhKXtyZXR1cm57cmVzdWx0czphfX0pKTthLmluaXRpYXRvciYmKGY9ZS5hZnRlcihmLGIpLGY9Zi5tYXAoVC5maW5hbGl6ZVJ1bGVSZXN1bHQpKSxjKGYpfSl9ZnVuY3Rpb24geShhLGMsZCxlKXsidXNlIHN0cmljdCI7aWYoIVMuX2F1ZGl0KXRocm93IG5ldyBFcnJvcigiTm8gYXVkaXQgY29uZmlndXJlZCIpO2lmKGMubGVuZ3RoPjEpe3ZhciBmPWIucXVlcnlTZWxlY3RvcihjLnNoaWZ0KCkpO3JldHVybiBULnNlbmRDb21tYW5kVG9GcmFtZShmLHtvcHRpb25zOmQsY29tbWFuZDoicnVuLXRvb2wiLHBhcmFtZXRlcjphLHNlbGVjdG9yQXJyYXk6Y30sZSl9dmFyIGc9Yi5xdWVyeVNlbGVjdG9yKGMuc2hpZnQoKSk7Uy5fYXVkaXQudG9vbHNbYV0ucnVuKGcsZCxlKX1mdW5jdGlvbiB6KGEsYil7InVzZSBzdHJpY3QiO2lmKGI9Ynx8MzAwLGEubGVuZ3RoPmIpe3ZhciBjPWEuaW5kZXhPZigiPiIpO2E9YS5zdWJzdHJpbmcoMCxjKzEpfXJldHVybiBhfWZ1bmN0aW9uIEEoYSl7InVzZSBzdHJpY3QiO3ZhciBiPWEub3V0ZXJIVE1MO3JldHVybiBifHwiZnVuY3Rpb24iIT10eXBlb2YgWE1MU2VyaWFsaXplcnx8KGI9KG5ldyBYTUxTZXJpYWxpemVyKS5zZXJpYWxpemVUb1N0cmluZyhhKSkseihifHwiIil9ZnVuY3Rpb24gQihhLGIpeyJ1c2Ugc3RyaWN0IjtiPWJ8fHt9LHRoaXMuc2VsZWN0b3I9Yi5zZWxlY3Rvcnx8W1QuZ2V0U2VsZWN0b3IoYSldLHRoaXMuc291cmNlPXZvaWQgMCE9PWIuc291cmNlP2Iuc291cmNlOkEoYSksdGhpcy5lbGVtZW50PWF9ZnVuY3Rpb24gQyhhLGIpeyJ1c2Ugc3RyaWN0IjtPYmplY3Qua2V5cyhTLmNvbnN0YW50cy5yYWlzZWRNZXRhZGF0YSkuZm9yRWFjaChmdW5jdGlvbihjKXt2YXIgZD1TLmNvbnN0YW50cy5yYWlzZWRNZXRhZGF0YVtjXSxlPWIucmVkdWNlKGZ1bmN0aW9uKGEsYil7dmFyIGU9ZC5pbmRleE9mKGJbY10pO3JldHVybiBlPmE/ZTphfSwtMSk7ZFtlXSYmKGFbY109ZFtlXSl9KX1mdW5jdGlvbiBEKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1hLmFueS5sZW5ndGh8fGEuYWxsLmxlbmd0aHx8YS5ub25lLmxlbmd0aDtyZXR1cm4gYj9TLmNvbnN0YW50cy5yZXN1bHQuRkFJTDpTLmNvbnN0YW50cy5yZXN1bHQuUEFTU31mdW5jdGlvbiBFKGEpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBiKGEpe3JldHVybiBULmV4dGVuZEJsYWNrbGlzdCh7fSxhLFsicmVzdWx0Il0pfXZhciBjPVQuZXh0ZW5kQmxhY2tsaXN0KHt2aW9sYXRpb25zOltdLHBhc3NlczpbXX0sYSxbIm5vZGVzIl0pO3JldHVybiBhLm5vZGVzLmZvckVhY2goZnVuY3Rpb24oYSl7dmFyIGQ9VC5nZXRGYWlsaW5nQ2hlY2tzKGEpLGU9RChkKTtyZXR1cm4gZT09PVMuY29uc3RhbnRzLnJlc3VsdC5GQUlMPyhDKGEsVC5nZXRBbGxDaGVja3MoZCkpLGEuYW55PWQuYW55Lm1hcChiKSxhLmFsbD1kLmFsbC5tYXAoYiksYS5ub25lPWQubm9uZS5tYXAoYiksdm9pZCBjLnZpb2xhdGlvbnMucHVzaChhKSk6KGEuYW55PWEuYW55LmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYS5yZXN1bHR9KS5tYXAoYiksYS5hbGw9YS5hbGwubWFwKGIpLGEubm9uZT1hLm5vbmUubWFwKGIpLHZvaWQgYy5wYXNzZXMucHVzaChhKSl9KSxDKGMsYy52aW9sYXRpb25zKSxjLnJlc3VsdD1jLnZpb2xhdGlvbnMubGVuZ3RoP1MuY29uc3RhbnRzLnJlc3VsdC5GQUlMOmMucGFzc2VzLmxlbmd0aD9TLmNvbnN0YW50cy5yZXN1bHQuUEFTUzpjLnJlc3VsdCxjfWZ1bmN0aW9uIEYoYSl7InVzZSBzdHJpY3QiO2Zvcih2YXIgYj0xLGM9YS5ub2RlTmFtZTthPWEucHJldmlvdXNFbGVtZW50U2libGluZzspYS5ub2RlTmFtZT09PWMmJmIrKztyZXR1cm4gYn1mdW5jdGlvbiBHKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjLGQsZT1hLnBhcmVudE5vZGUuY2hpbGRyZW47aWYoIWUpcmV0dXJuITE7dmFyIGY9ZS5sZW5ndGg7Zm9yKGM9MDtmPmM7YysrKWlmKGQ9ZVtjXSxkIT09YSYmVC5tYXRjaGVzU2VsZWN0b3IoZCxiKSlyZXR1cm4hMDtyZXR1cm4hMX1mdW5jdGlvbiBIKGEpeyJ1c2Ugc3RyaWN0IjtpZihYJiZYLnBhcmVudE5vZGUmJihYLnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQoWCksWD1udWxsKSxhKXt2YXIgYz1iLmhlYWR8fGIuZ2V0RWxlbWVudHNCeVRhZ05hbWUoImhlYWQiKVswXTtyZXR1cm4gWD1iLmNyZWF0ZUVsZW1lbnQoInN0eWxlIiksWC50eXBlPSJ0ZXh0L2NzcyIsdm9pZCAwPT09WC5zdHlsZVNoZWV0P1guYXBwZW5kQ2hpbGQoYi5jcmVhdGVUZXh0Tm9kZShhKSk6WC5zdHlsZVNoZWV0LmNzc1RleHQ9YSxjLmFwcGVuZENoaWxkKFgpLFh9fWZ1bmN0aW9uIEkoYSxiLGMpeyJ1c2Ugc3RyaWN0IjthLmZvckVhY2goZnVuY3Rpb24oYSl7YS5ub2RlLnNlbGVjdG9yLnVuc2hpZnQoYyksYS5ub2RlPW5ldyBULkRxRWxlbWVudChiLGEubm9kZSk7dmFyIGQ9VC5nZXRBbGxDaGVja3MoYSk7ZC5sZW5ndGgmJmQuZm9yRWFjaChmdW5jdGlvbihhKXthLnJlbGF0ZWROb2Rlcy5mb3JFYWNoKGZ1bmN0aW9uKGEpe2Euc2VsZWN0b3IudW5zaGlmdChjKSxhPW5ldyBULkRxRWxlbWVudChiLGEpfSl9KX0pfWZ1bmN0aW9uIEooYSxiKXsidXNlIHN0cmljdCI7Zm9yKHZhciBjLGQsZT1iWzBdLm5vZGUsZj0wLGc9YS5sZW5ndGg7Zz5mO2YrKylpZihkPWFbZl0ubm9kZSxjPVQubm9kZVNvcnRlcihkLmVsZW1lbnQsZS5lbGVtZW50KSxjPjB8fDA9PT1jJiZlLnNlbGVjdG9yLmxlbmd0aDxkLnNlbGVjdG9yLmxlbmd0aClyZXR1cm4gdm9pZCBhLnNwbGljZS5hcHBseShhLFtmLDBdLmNvbmNhdChiKSk7YS5wdXNoLmFwcGx5KGEsYil9ZnVuY3Rpb24gSyhhKXsidXNlIHN0cmljdCI7cmV0dXJuIGEmJmEucmVzdWx0cz9BcnJheS5pc0FycmF5KGEucmVzdWx0cyk/YS5yZXN1bHRzLmxlbmd0aD9hLnJlc3VsdHM6bnVsbDpbYS5yZXN1bHRzXTpudWxsfWZ1bmN0aW9uIEwoYSl7InVzZSBzdHJpY3QiO3JldHVybiBhLnNvcnQoZnVuY3Rpb24oYSxiKXtyZXR1cm4gVC5jb250YWlucyhhLGIpPzE6LTF9KVswXX1mdW5jdGlvbiBNKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjPWIuaW5jbHVkZSYmTChiLmluY2x1ZGUuZmlsdGVyKGZ1bmN0aW9uKGIpe3JldHVybiBULmNvbnRhaW5zKGIsYSl9KSksZD1iLmV4Y2x1ZGUmJkwoYi5leGNsdWRlLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4gVC5jb250YWlucyhiLGEpfSkpO3JldHVybiFkJiZjfHxkJiZULmNvbnRhaW5zKGQsYyk/ITA6ITF9ZnVuY3Rpb24gTihhLGIsYyl7InVzZSBzdHJpY3QiO2Zvcih2YXIgZD0wLGU9Yi5sZW5ndGg7ZT5kO2QrKyktMT09PWEuaW5kZXhPZihiW2RdKSYmTShiW2RdLGMpJiZhLnB1c2goYltkXSl9dmFyIE8sUD1mdW5jdGlvbigpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBhKGEpe3ZhciBiLGMsZD1hLkVsZW1lbnQucHJvdG90eXBlLGU9WyJtYXRjaGVzIiwibWF0Y2hlc1NlbGVjdG9yIiwibW96TWF0Y2hlc1NlbGVjdG9yIiwid2Via2l0TWF0Y2hlc1NlbGVjdG9yIiwibXNNYXRjaGVzU2VsZWN0b3IiXSxmPWUubGVuZ3RoO2ZvcihiPTA7Zj5iO2IrKylpZihjPWVbYl0sZFtjXSlyZXR1cm4gY312YXIgYjtyZXR1cm4gZnVuY3Rpb24oYyxkKXtyZXR1cm4gYiYmY1tiXXx8KGI9YShjLm93bmVyRG9jdW1lbnQuZGVmYXVsdFZpZXcpKSxjW2JdKGQpfX0oKSxRPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijtmb3IodmFyIGIsYz1TdHJpbmcoYSksZD1jLmxlbmd0aCxlPS0xLGY9IiIsZz1jLmNoYXJDb2RlQXQoMCk7KytlPGQ7KXtpZihiPWMuY2hhckNvZGVBdChlKSwwPT1iKXRocm93IG5ldyBFcnJvcigiSU5WQUxJRF9DSEFSQUNURVJfRVJSIik7Zis9Yj49MSYmMzE+PWJ8fGI+PTEyNyYmMTU5Pj1ifHwwPT1lJiZiPj00OCYmNTc+PWJ8fDE9PWUmJmI+PTQ4JiY1Nz49YiYmNDU9PWc/IlxcIitiLnRvU3RyaW5nKDE2KSsiICI6KDEhPWV8fDQ1IT1ifHw0NSE9ZykmJihiPj0xMjh8fDQ1PT1ifHw5NT09Ynx8Yj49NDgmJjU3Pj1ifHxiPj02NSYmOTA+PWJ8fGI+PTk3JiYxMjI+PWIpP2MuY2hhckF0KGUpOiJcXCIrYy5jaGFyQXQoZSl9cmV0dXJuIGZ9OyFmdW5jdGlvbihhKXtmdW5jdGlvbiBiKGEsYixjKXt2YXIgZD1iJiZjfHwwLGU9MDtmb3IoYj1ifHxbXSxhLnRvTG93ZXJDYXNlKCkucmVwbGFjZSgvWzAtOWEtZl17Mn0vZyxmdW5jdGlvbihhKXsxNj5lJiYoYltkK2UrK109bFthXSl9KTsxNj5lOyliW2QrZSsrXT0wO3JldHVybiBifWZ1bmN0aW9uIGMoYSxiKXt2YXIgYz1ifHwwLGQ9aztyZXR1cm4gZFthW2MrK11dK2RbYVtjKytdXStkW2FbYysrXV0rZFthW2MrK11dKyItIitkW2FbYysrXV0rZFthW2MrK11dKyItIitkW2FbYysrXV0rZFthW2MrK11dKyItIitkW2FbYysrXV0rZFthW2MrK11dKyItIitkW2FbYysrXV0rZFthW2MrK11dK2RbYVtjKytdXStkW2FbYysrXV0rZFthW2MrK11dK2RbYVtjKytdXX1mdW5jdGlvbiBkKGEsYixkKXt2YXIgZT1iJiZkfHwwLGY9Ynx8W107YT1hfHx7fTt2YXIgZz1udWxsIT1hLmNsb2Nrc2VxP2EuY2xvY2tzZXE6cCxoPW51bGwhPWEubXNlY3M/YS5tc2VjczoobmV3IERhdGUpLmdldFRpbWUoKSxpPW51bGwhPWEubnNlY3M/YS5uc2VjczpyKzEsaj1oLXErKGktcikvMWU0O2lmKDA+aiYmbnVsbD09YS5jbG9ja3NlcSYmKGc9ZysxJjE2MzgzKSwoMD5qfHxoPnEpJiZudWxsPT1hLm5zZWNzJiYoaT0wKSxpPj0xZTQpdGhyb3cgbmV3IEVycm9yKCJ1dWlkLnYxKCk6IENhbid0IGNyZWF0ZSBtb3JlIHRoYW4gMTBNIHV1aWRzL3NlYyIpO3E9aCxyPWkscD1nLGgrPTEyMjE5MjkyOGU1O3ZhciBrPSgxZTQqKDI2ODQzNTQ1NSZoKStpKSU0Mjk0OTY3Mjk2O2ZbZSsrXT1rPj4+MjQmMjU1LGZbZSsrXT1rPj4+MTYmMjU1LGZbZSsrXT1rPj4+OCYyNTUsZltlKytdPTI1NSZrO3ZhciBsPWgvNDI5NDk2NzI5NioxZTQmMjY4NDM1NDU1O2ZbZSsrXT1sPj4+OCYyNTUsZltlKytdPTI1NSZsLGZbZSsrXT1sPj4+MjQmMTV8MTYsZltlKytdPWw+Pj4xNiYyNTUsZltlKytdPWc+Pj44fDEyOCxmW2UrK109MjU1Jmc7Zm9yKHZhciBtPWEubm9kZXx8byxuPTA7Nj5uO24rKylmW2Urbl09bVtuXTtyZXR1cm4gYj9iOmMoZil9ZnVuY3Rpb24gZShhLGIsZCl7dmFyIGU9YiYmZHx8MDsic3RyaW5nIj09dHlwZW9mIGEmJihiPSJiaW5hcnkiPT1hP25ldyBqKDE2KTpudWxsLGE9bnVsbCksYT1hfHx7fTt2YXIgZz1hLnJhbmRvbXx8KGEucm5nfHxmKSgpO2lmKGdbNl09MTUmZ1s2XXw2NCxnWzhdPTYzJmdbOF18MTI4LGIpZm9yKHZhciBoPTA7MTY+aDtoKyspYltlK2hdPWdbaF07cmV0dXJuIGJ8fGMoZyl9dmFyIGYsZz1hLmNyeXB0b3x8YS5tc0NyeXB0bztpZighZiYmZyYmZy5nZXRSYW5kb21WYWx1ZXMpe3ZhciBoPW5ldyBVaW50OEFycmF5KDE2KTtmPWZ1bmN0aW9uKCl7cmV0dXJuIGcuZ2V0UmFuZG9tVmFsdWVzKGgpLGh9fWlmKCFmKXt2YXIgaT1uZXcgQXJyYXkoMTYpO2Y9ZnVuY3Rpb24oKXtmb3IodmFyIGEsYj0wOzE2PmI7YisrKTA9PT0oMyZiKSYmKGE9NDI5NDk2NzI5NipNYXRoLnJhbmRvbSgpKSxpW2JdPWE+Pj4oKDMmYik8PDMpJjI1NTtyZXR1cm4gaX19Zm9yKHZhciBqPSJmdW5jdGlvbiI9PXR5cGVvZiBhLkJ1ZmZlcj9hLkJ1ZmZlcjpBcnJheSxrPVtdLGw9e30sbT0wOzI1Nj5tO20rKylrW21dPShtKzI1NikudG9TdHJpbmcoMTYpLnN1YnN0cigxKSxsW2tbbV1dPW07dmFyIG49ZigpLG89WzF8blswXSxuWzFdLG5bMl0sblszXSxuWzRdLG5bNV1dLHA9MTYzODMmKG5bNl08PDh8bls3XSkscT0wLHI9MDtPPWUsTy52MT1kLE8udjQ9ZSxPLnBhcnNlPWIsTy51bnBhcnNlPWMsTy5CdWZmZXJDbGFzcz1qfShhKTt2YXIgUixTPXt9LFQ9Uy51dGlscz17fTtULm1hdGNoZXNTZWxlY3Rvcj1QLFQuZXNjYXBlU2VsZWN0b3I9USxULmNsb25lPWM7dmFyIFU9e307Zi5wcm90b3R5cGUuYWRkUnVsZT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7YS5tZXRhZGF0YSYmKHRoaXMuZGF0YS5ydWxlc1thLmlkXT1hLm1ldGFkYXRhKTtmb3IodmFyIGIsYz0wLGQ9dGhpcy5ydWxlcy5sZW5ndGg7ZD5jO2MrKylpZihiPXRoaXMucnVsZXNbY10sYi5pZD09PWEuaWQpcmV0dXJuIHZvaWQodGhpcy5ydWxlc1tjXT1uZXcgbyhhLHRoaXMpKTt0aGlzLnJ1bGVzLnB1c2gobmV3IG8oYSx0aGlzKSl9LGYucHJvdG90eXBlLmFkZFRvb2w9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3RoaXMudG9vbHNbYS5pZF09bmV3IHQoYSl9LGYucHJvdG90eXBlLmFkZENoZWNrPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjthLm1ldGFkYXRhJiYodGhpcy5kYXRhLmNoZWNrc1thLmlkXT1hLm1ldGFkYXRhKSx0aGlzLmNoZWNrc1thLmlkXT1uZXcgaChhKX0sZi5wcm90b3R5cGUucnVuPWZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7dmFyIGQ9VC5xdWV1ZSgpO3RoaXMucnVsZXMuZm9yRWFjaChmdW5jdGlvbihjKXtULnJ1bGVTaG91bGRSdW4oYyxhLGIpJiZkLmRlZmVyKGZ1bmN0aW9uKGQpe2MucnVuKGEsYixkKX0pfSksZC50aGVuKGMpfSxmLnByb3RvdHlwZS5hZnRlcj1mdW5jdGlvbihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz10aGlzLnJ1bGVzO3JldHVybiBhLm1hcChmdW5jdGlvbihhKXt2YXIgZD1ULmZpbmRCeShjLCJpZCIsYS5pZCk7cmV0dXJuIGQuYWZ0ZXIoYSxiKX0pfSxoLnByb3RvdHlwZS5tYXRjaGVzPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4hdGhpcy5zZWxlY3Rvcnx8VC5tYXRjaGVzU2VsZWN0b3IoYSx0aGlzLnNlbGVjdG9yKT8hMDohMX0saC5wcm90b3R5cGUucnVuPWZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7Yj1ifHx7fTt2YXIgZD1iLmhhc093blByb3BlcnR5KCJlbmFibGVkIik/Yi5lbmFibGVkOnRoaXMuZW5hYmxlZCxlPWIub3B0aW9uc3x8dGhpcy5vcHRpb25zO2lmKGQmJnRoaXMubWF0Y2hlcyhhKSl7dmFyIGYsaD1uZXcgZyh0aGlzKSxpPVQuY2hlY2tIZWxwZXIoaCxjKTt0cnl7Zj10aGlzLmV2YWx1YXRlLmNhbGwoaSxhLGUpfWNhdGNoKGope3JldHVybiBTLmxvZyhqLm1lc3NhZ2Usai5zdGFjayksdm9pZCBjKG51bGwpfWkuaXNBc3luY3x8KGgucmVzdWx0PWYsc2V0VGltZW91dChmdW5jdGlvbigpe2MoaCl9LDApKX1lbHNlIGMobnVsbCl9LG8ucHJvdG90eXBlLm1hdGNoZXM9ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7cmV0dXJuITB9LG8ucHJvdG90eXBlLmdhdGhlcj1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7dmFyIGI9VC5zZWxlY3QodGhpcy5zZWxlY3RvcixhKTtyZXR1cm4gdGhpcy5leGNsdWRlSGlkZGVuP2IuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiFULmlzSGlkZGVuKGEpfSk6Yn0sby5wcm90b3R5cGUucnVuQ2hlY2tzPWZ1bmN0aW9uKGEsYixjLGQpeyJ1c2Ugc3RyaWN0Ijt2YXIgZT10aGlzLGY9VC5xdWV1ZSgpO3RoaXNbYV0uZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgZD1lLl9hdWRpdC5jaGVja3NbYS5pZHx8YV0sZz1ULmdldENoZWNrT3B0aW9uKGQsZS5pZCxjKTtmLmRlZmVyKGZ1bmN0aW9uKGEpe2QucnVuKGIsZyxhKX0pfSksZi50aGVuKGZ1bmN0aW9uKGIpe2I9Yi5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIGF9KSxkKHt0eXBlOmEscmVzdWx0czpifSl9KX0sby5wcm90b3R5cGUucnVuPWZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7dmFyIGQsZT10aGlzLmdhdGhlcihhKSxmPVQucXVldWUoKSxnPXRoaXM7ZD1uZXcgbih0aGlzKSxlLmZvckVhY2goZnVuY3Rpb24oYSl7Zy5tYXRjaGVzKGEpJiZmLmRlZmVyKGZ1bmN0aW9uKGMpe3ZhciBlPVQucXVldWUoKTtlLmRlZmVyKGZ1bmN0aW9uKGMpe2cucnVuQ2hlY2tzKCJhbnkiLGEsYixjKX0pLGUuZGVmZXIoZnVuY3Rpb24oYyl7Zy5ydW5DaGVja3MoImFsbCIsYSxiLGMpfSksZS5kZWZlcihmdW5jdGlvbihjKXtnLnJ1bkNoZWNrcygibm9uZSIsYSxiLGMpfSksZS50aGVuKGZ1bmN0aW9uKGIpe2lmKGIubGVuZ3RoKXt2YXIgZT0hMSxmPXtub2RlOm5ldyBULkRxRWxlbWVudChhKX07Yi5mb3JFYWNoKGZ1bmN0aW9uKGEpe3ZhciBiPWEucmVzdWx0cy5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIGF9KTtmW2EudHlwZV09YixiLmxlbmd0aCYmKGU9ITApfSksZSYmZC5ub2Rlcy5wdXNoKGYpfWMoKX0pfSl9KSxmLnRoZW4oZnVuY3Rpb24oKXtjKGQpfSl9LG8ucHJvdG90eXBlLmFmdGVyPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjPXAodGhpcyksZD10aGlzLmlkO3JldHVybiBjLmZvckVhY2goZnVuY3Rpb24oYyl7dmFyIGU9cShhLm5vZGVzLGMuaWQpLGY9VC5nZXRDaGVja09wdGlvbihjLGQsYiksZz1jLmFmdGVyKGUsZik7ZS5mb3JFYWNoKGZ1bmN0aW9uKGEpey0xPT09Zy5pbmRleE9mKGEpJiYoYS5maWx0ZXJlZD0hMCl9KX0pLGEubm9kZXM9cyhhKSxhfSx0LnByb3RvdHlwZS5ydW49ZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0IjtiPSJ1bmRlZmluZWQiPT10eXBlb2YgYj90aGlzLm9wdGlvbnM6Yix0aGlzLmFjdGl2ZT0hMCx0aGlzLl9ydW4oYSxiLGMpfSx0LnByb3RvdHlwZS5jbGVhbnVwPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt0aGlzLmFjdGl2ZT0hMSx0aGlzLl9jbGVhbnVwKGEpfSxTLmNvbnN0YW50cz17fSxTLmNvbnN0YW50cy5yZXN1bHQ9e1BBU1M6IlBBU1MiLEZBSUw6IkZBSUwiLE5BOiJOQSJ9LFMuY29uc3RhbnRzLnJhaXNlZE1ldGFkYXRhPXtpbXBhY3Q6WyJtaW5vciIsIm1vZGVyYXRlIiwic2VyaW91cyIsImNyaXRpY2FsIl19LFMudmVyc2lvbj0iZGV2IixhLmF4ZT1TLFMubG9nPWZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiOyJvYmplY3QiPT10eXBlb2YgY29uc29sZSYmY29uc29sZS5sb2cmJkZ1bmN0aW9uLnByb3RvdHlwZS5hcHBseS5jYWxsKGNvbnNvbGUubG9nLGNvbnNvbGUsYXJndW1lbnRzKX0sUy5jbGVhbnVwPXUsUy5jb25maWd1cmU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPVMuX2F1ZGl0O2lmKCFiKXRocm93IG5ldyBFcnJvcigiTm8gYXVkaXQgY29uZmlndXJlZCIpO2EucmVwb3J0ZXImJigiZnVuY3Rpb24iPT10eXBlb2YgYS5yZXBvcnRlcnx8V1thLnJlcG9ydGVyXSkmJihiLnJlcG9ydGVyPWEucmVwb3J0ZXIpLGEuY2hlY2tzJiZhLmNoZWNrcy5mb3JFYWNoKGZ1bmN0aW9uKGEpe2IuYWRkQ2hlY2soYSl9KSxhLnJ1bGVzJiZhLnJ1bGVzLmZvckVhY2goZnVuY3Rpb24oYSl7Yi5hZGRSdWxlKGEpfSksYS50b29scyYmYS50b29scy5mb3JFYWNoKGZ1bmN0aW9uKGEpe2IuYWRkVG9vbChhKX0pfSxTLmdldFJ1bGVzPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjthPWF8fFtdO3ZhciBiPWEubGVuZ3RoP1MuX2F1ZGl0LnJ1bGVzLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4hIWEuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybi0xIT09Yi50YWdzLmluZGV4T2YoYSl9KS5sZW5ndGh9KTpTLl9hdWRpdC5ydWxlcyxjPVMuX2F1ZGl0LmRhdGEucnVsZXN8fHt9O3JldHVybiBiLm1hcChmdW5jdGlvbihhKXt2YXIgYj1jW2EuaWRdfHx7fTtyZXR1cm57cnVsZUlkOmEuaWQsZGVzY3JpcHRpb246Yi5kZXNjcmlwdGlvbixoZWxwOmIuaGVscCxoZWxwVXJsOmIuaGVscFVybCx0YWdzOmEudGFnc319KX0sUy5fbG9hZD1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7VC5yZXNwb25kYWJsZS5zdWJzY3JpYmUoImF4ZS5waW5nIixmdW5jdGlvbihhLGIpe2Ioe2F4ZTohMH0pfSksVC5yZXNwb25kYWJsZS5zdWJzY3JpYmUoImF4ZS5zdGFydCIsdiksUy5fYXVkaXQ9bmV3IGYoYSl9O3ZhciBWLFc9e307Uy5yZXBvcnRlcj1mdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiO1dbYV09YixjJiYoVj1iKX0sUy5hMTF5Q2hlY2s9ZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0IjsiZnVuY3Rpb24iPT10eXBlb2YgYiYmKGM9YixiPXt9KSxiJiYib2JqZWN0Ij09dHlwZW9mIGJ8fChiPXt9KTt2YXIgZD1TLl9hdWRpdDtpZighZCl0aHJvdyBuZXcgRXJyb3IoIk5vIGF1ZGl0IGNvbmZpZ3VyZWQiKTt2YXIgZT13KGIucmVwb3J0ZXJ8fGQucmVwb3J0ZXIpO3goYSxiLGZ1bmN0aW9uKGEpe2UoYSxjKX0pfSxTLnRvb2w9eSxVLmZhaWx1cmVTdW1tYXJ5PWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj17fTtyZXR1cm4gYi5ub25lPWEubm9uZS5jb25jYXQoYS5hbGwpLGIuYW55PWEuYW55LE9iamVjdC5rZXlzKGIpLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gYlthXS5sZW5ndGg/Uy5fYXVkaXQuZGF0YS5mYWlsdXJlU3VtbWFyaWVzW2FdLmZhaWx1cmVNZXNzYWdlKGJbYV0ubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhLm1lc3NhZ2V8fCIifSkpOnZvaWQgMH0pLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gdm9pZCAwIT09YX0pLmpvaW4oIlxuXG4iKX0sVS5mb3JtYXRDaGVjaz1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJue2lkOmEuaWQsaW1wYWN0OmEuaW1wYWN0LG1lc3NhZ2U6YS5tZXNzYWdlLGRhdGE6YS5kYXRhLHJlbGF0ZWROb2RlczphLnJlbGF0ZWROb2Rlcy5tYXAoVS5mb3JtYXROb2RlKX19LFUuZm9ybWF0Q2hlY2tzPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3JldHVybiBhLmFueT1iLmFueS5tYXAoVS5mb3JtYXRDaGVjayksYS5hbGw9Yi5hbGwubWFwKFUuZm9ybWF0Q2hlY2spLGEubm9uZT1iLm5vbmUubWFwKFUuZm9ybWF0Q2hlY2spLGF9LFUuZm9ybWF0Tm9kZT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJue3RhcmdldDphP2Euc2VsZWN0b3I6bnVsbCxodG1sOmE/YS5zb3VyY2U6bnVsbH19LFUuZm9ybWF0UnVsZVJlc3VsdD1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJue2lkOmEuaWQsZGVzY3JpcHRpb246YS5kZXNjcmlwdGlvbixoZWxwOmEuaGVscCxoZWxwVXJsOmEuaGVscFVybHx8bnVsbCxpbXBhY3Q6bnVsbCx0YWdzOmEudGFncyxub2RlczpbXX19LFUuc3BsaXRSZXN1bHRzV2l0aENoZWNrcz1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJuIFUuc3BsaXRSZXN1bHRzKGEsVS5mb3JtYXRDaGVja3MpfSxVLnNwbGl0UmVzdWx0cz1mdW5jdGlvbihiLGMpeyJ1c2Ugc3RyaWN0Ijt2YXIgZD1bXSxlPVtdO3JldHVybiBiLmZvckVhY2goZnVuY3Rpb24oYSl7ZnVuY3Rpb24gYihiKXt2YXIgZD1iLnJlc3VsdHx8YS5yZXN1bHQsZT1VLmZvcm1hdE5vZGUoYi5ub2RlKTtyZXR1cm4gZS5pbXBhY3Q9Yi5pbXBhY3R8fG51bGwsYyhlLGIsZCl9dmFyIGYsZz1VLmZvcm1hdFJ1bGVSZXN1bHQoYSk7Zj1ULmNsb25lKGcpLGYuaW1wYWN0PWEuaW1wYWN0fHxudWxsLGYubm9kZXM9YS52aW9sYXRpb25zLm1hcChiKSxnLm5vZGVzPWEucGFzc2VzLm1hcChiKSxmLm5vZGVzLmxlbmd0aCYmZC5wdXNoKGYpLGcubm9kZXMubGVuZ3RoJiZlLnB1c2goZyl9KSx7dmlvbGF0aW9uczpkLHBhc3NlczplLHVybDphLmxvY2F0aW9uLmhyZWYsdGltZXN0YW1wOm5ldyBEYXRlfX0sUy5yZXBvcnRlcigibmEiLGZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjPWEuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiAwPT09YS52aW9sYXRpb25zLmxlbmd0aCYmMD09PWEucGFzc2VzLmxlbmd0aH0pLm1hcChVLmZvcm1hdFJ1bGVSZXN1bHQpLGQ9VS5zcGxpdFJlc3VsdHNXaXRoQ2hlY2tzKGEpO2Ioe3Zpb2xhdGlvbnM6ZC52aW9sYXRpb25zLHBhc3NlczpkLnBhc3Nlcyxub3RBcHBsaWNhYmxlOmMsdGltZXN0YW1wOmQudGltZXN0YW1wLHVybDpkLnVybH0pfSksUy5yZXBvcnRlcigibm8tcGFzc2VzIixmdW5jdGlvbihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1VLnNwbGl0UmVzdWx0c1dpdGhDaGVja3MoYSk7Yih7dmlvbGF0aW9uczpjLnZpb2xhdGlvbnMsdGltZXN0YW1wOmMudGltZXN0YW1wLHVybDpjLnVybH0pfSksUy5yZXBvcnRlcigicmF3IixmdW5jdGlvbihhLGIpeyJ1c2Ugc3RyaWN0IjtiKGEpfSksUy5yZXBvcnRlcigidjEiLGZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjPVUuc3BsaXRSZXN1bHRzKGEsZnVuY3Rpb24oYSxiLGMpe3JldHVybiBjPT09Uy5jb25zdGFudHMucmVzdWx0LkZBSUwmJihhLmZhaWx1cmVTdW1tYXJ5PVUuZmFpbHVyZVN1bW1hcnkoYikpLGF9KTtiKHt2aW9sYXRpb25zOmMudmlvbGF0aW9ucyxwYXNzZXM6Yy5wYXNzZXMsdGltZXN0YW1wOmMudGltZXN0YW1wLHVybDpjLnVybH0pfSksUy5yZXBvcnRlcigidjIiLGZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjPVUuc3BsaXRSZXN1bHRzV2l0aENoZWNrcyhhKTtiKHt2aW9sYXRpb25zOmMudmlvbGF0aW9ucyxwYXNzZXM6Yy5wYXNzZXMsdGltZXN0YW1wOmMudGltZXN0YW1wLHVybDpjLnVybH0pfSwhMCksVC5jaGVja0hlbHBlcj1mdW5jdGlvbihhLGIpeyJ1c2Ugc3RyaWN0IjtyZXR1cm57aXNBc3luYzohMSxhc3luYzpmdW5jdGlvbigpe3JldHVybiB0aGlzLmlzQXN5bmM9ITAsZnVuY3Rpb24oYyl7YS52YWx1ZT1jLGIoYSl9fSxkYXRhOmZ1bmN0aW9uKGIpe2EuZGF0YT1ifSxyZWxhdGVkTm9kZXM6ZnVuY3Rpb24oYil7Yj1iIGluc3RhbmNlb2YgTm9kZT9bYl06VC50b0FycmF5KGIpLGEucmVsYXRlZE5vZGVzPWIubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBuZXcgVC5EcUVsZW1lbnQoYSl9KX19fSxULnNlbmRDb21tYW5kVG9GcmFtZT1mdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiO3ZhciBkPWEuY29udGVudFdpbmRvdztpZighZClyZXR1cm4gUy5sb2coIkZyYW1lIGRvZXMgbm90IGhhdmUgYSBjb250ZW50IHdpbmRvdyIsYSksYyh7fSk7dmFyIGU9c2V0VGltZW91dChmdW5jdGlvbigpe2U9c2V0VGltZW91dChmdW5jdGlvbigpe1MubG9nKCJObyByZXNwb25zZSBmcm9tIGZyYW1lOiAiLGEpLGMobnVsbCl9LDApfSw1MDApO1QucmVzcG9uZGFibGUoZCwiYXhlLnBpbmciLG51bGwsZnVuY3Rpb24oKXtjbGVhclRpbWVvdXQoZSksZT1zZXRUaW1lb3V0KGZ1bmN0aW9uKCl7Uy5sb2coIkVycm9yIHJldHVybmluZyByZXN1bHRzIGZyb20gZnJhbWU6ICIsYSksYyh7fSksYz1udWxsfSwzZTQpLFQucmVzcG9uZGFibGUoZCwiYXhlLnN0YXJ0IixiLGZ1bmN0aW9uKGEpe2MmJihjbGVhclRpbWVvdXQoZSksYyhhKSl9KX0pfSxULmNvbGxlY3RSZXN1bHRzRnJvbUZyYW1lcz1mdW5jdGlvbihhLGIsYyxkLGUpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBmKGUpe3ZhciBmPXtvcHRpb25zOmIsY29tbWFuZDpjLHBhcmFtZXRlcjpkLGNvbnRleHQ6e2luaXRpYXRvcjohMSxwYWdlOmEucGFnZSxpbmNsdWRlOmUuaW5jbHVkZXx8W10sZXhjbHVkZTplLmV4Y2x1ZGV8fFtdfX07Zy5kZWZlcihmdW5jdGlvbihhKXt2YXIgYj1lLm5vZGU7VC5zZW5kQ29tbWFuZFRvRnJhbWUoYixmLGZ1bmN0aW9uKGMpe3JldHVybiBjP2Eoe3Jlc3VsdHM6YyxmcmFtZUVsZW1lbnQ6YixmcmFtZTpULmdldFNlbGVjdG9yKGIpfSk6dm9pZCBhKG51bGwpfSl9KX1mb3IodmFyIGc9VC5xdWV1ZSgpLGg9YS5mcmFtZXMsaT0wLGo9aC5sZW5ndGg7aj5pO2krKylmKGhbaV0pO2cudGhlbihmdW5jdGlvbihhKXtlKFQubWVyZ2VSZXN1bHRzKGEpKX0pfSxULmNvbnRhaW5zPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3JldHVybiJmdW5jdGlvbiI9PXR5cGVvZiBhLmNvbnRhaW5zP2EuY29udGFpbnMoYik6ISEoMTYmYS5jb21wYXJlRG9jdW1lbnRQb3NpdGlvbihiKSl9LEIucHJvdG90eXBlLnRvSlNPTj1mdW5jdGlvbigpeyJ1c2Ugc3RyaWN0IjtyZXR1cm57c2VsZWN0b3I6dGhpcy5zZWxlY3Rvcixzb3VyY2U6dGhpcy5zb3VyY2V9fSxULkRxRWxlbWVudD1CLFQuZXh0ZW5kQmxhY2tsaXN0PWZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7Yz1jfHxbXTtmb3IodmFyIGQgaW4gYiliLmhhc093blByb3BlcnR5KGQpJiYtMT09PWMuaW5kZXhPZihkKSYmKGFbZF09YltkXSk7cmV0dXJuIGF9LFQuZXh0ZW5kTWV0YURhdGE9ZnVuY3Rpb24oYSxiKXsidXNlIHN0cmljdCI7Zm9yKHZhciBjIGluIGIpaWYoYi5oYXNPd25Qcm9wZXJ0eShjKSlpZigiZnVuY3Rpb24iPT10eXBlb2YgYltjXSl0cnl7YVtjXT1iW2NdKGEpfWNhdGNoKGQpe2FbY109bnVsbH1lbHNlIGFbY109YltjXX0sVC5nZXRGYWlsaW5nQ2hlY2tzPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1hLmFueS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIWEucmVzdWx0fSk7cmV0dXJue2FsbDphLmFsbC5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIWEucmVzdWx0fSksYW55OmIubGVuZ3RoPT09YS5hbnkubGVuZ3RoP2I6W10sbm9uZTphLm5vbmUuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiEhYS5yZXN1bHR9KX19LFQuZmluYWxpemVSdWxlUmVzdWx0PWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4gVC5wdWJsaXNoTWV0YURhdGEoYSksRShhKX0sVC5maW5kQnk9ZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0IjthPWF8fFtdO3ZhciBkLGU7Zm9yKGQ9MCxlPWEubGVuZ3RoO2U+ZDtkKyspaWYoYVtkXVtiXT09PWMpcmV0dXJuIGFbZF19LFQuZ2V0QWxsQ2hlY2tzPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1bXTtyZXR1cm4gYi5jb25jYXQoYS5hbnl8fFtdKS5jb25jYXQoYS5hbGx8fFtdKS5jb25jYXQoYS5ub25lfHxbXSl9LFQuZ2V0Q2hlY2tPcHRpb249ZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0Ijt2YXIgZD0oKGMucnVsZXMmJmMucnVsZXNbYl18fHt9KS5jaGVja3N8fHt9KVthLmlkXSxlPShjLmNoZWNrc3x8e30pW2EuaWRdLGY9YS5lbmFibGVkLGc9YS5vcHRpb25zO3JldHVybiBlJiYoZS5oYXNPd25Qcm9wZXJ0eSgiZW5hYmxlZCIpJiYoZj1lLmVuYWJsZWQpLGUuaGFzT3duUHJvcGVydHkoIm9wdGlvbnMiKSYmKGc9ZS5vcHRpb25zKSksZCYmKGQuaGFzT3duUHJvcGVydHkoImVuYWJsZWQiKSYmKGY9ZC5lbmFibGVkKSxkLmhhc093blByb3BlcnR5KCJvcHRpb25zIikmJihnPWQub3B0aW9ucykpLHtlbmFibGVkOmYsb3B0aW9uczpnfX0sVC5nZXRTZWxlY3Rvcj1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gYyhhKXtyZXR1cm4gVC5lc2NhcGVTZWxlY3RvcihhKX1mb3IodmFyIGQsZT1bXTthLnBhcmVudE5vZGU7KXtpZihkPSIiLGEuaWQmJjE9PT1iLnF1ZXJ5U2VsZWN0b3JBbGwoIiMiK1QuZXNjYXBlU2VsZWN0b3IoYS5pZCkpLmxlbmd0aCl7ZS51bnNoaWZ0KCIjIitULmVzY2FwZVNlbGVjdG9yKGEuaWQpKTticmVha31pZihhLmNsYXNzTmFtZSYmInN0cmluZyI9PXR5cGVvZiBhLmNsYXNzTmFtZSYmKGQ9Ii4iK2EuY2xhc3NOYW1lLnRyaW0oKS5zcGxpdCgvXHMrLykubWFwKGMpLmpvaW4oIi4iKSwoIi4iPT09ZHx8RyhhLGQpKSYmKGQ9IiIpKSwhZCl7aWYoZD1ULmVzY2FwZVNlbGVjdG9yKGEubm9kZU5hbWUpLnRvTG93ZXJDYXNlKCksImh0bWwiPT09ZHx8ImJvZHkiPT09ZCl7ZS51bnNoaWZ0KGQpO2JyZWFrfUcoYSxkKSYmKGQrPSI6bnRoLW9mLXR5cGUoIitGKGEpKyIpIil9ZS51bnNoaWZ0KGQpLGE9YS5wYXJlbnROb2RlfXJldHVybiBlLmpvaW4oIiA+ICIpfTt2YXIgWDtULmlzSGlkZGVuPWZ1bmN0aW9uKGIsYyl7InVzZSBzdHJpY3QiO2lmKDk9PT1iLm5vZGVUeXBlKXJldHVybiExO3ZhciBkPWEuZ2V0Q29tcHV0ZWRTdHlsZShiLG51bGwpO3JldHVybiBkJiZiLnBhcmVudE5vZGUmJiJub25lIiE9PWQuZ2V0UHJvcGVydHlWYWx1ZSgiZGlzcGxheSIpJiYoY3x8ImhpZGRlbiIhPT1kLmdldFByb3BlcnR5VmFsdWUoInZpc2liaWxpdHkiKSkmJiJ0cnVlIiE9PWIuZ2V0QXR0cmlidXRlKCJhcmlhLWhpZGRlbiIpP1QuaXNIaWRkZW4oYi5wYXJlbnROb2RlLCEwKTohMH0sVC5tZXJnZVJlc3VsdHM9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPVtdO3JldHVybiBhLmZvckVhY2goZnVuY3Rpb24oYSl7dmFyIGM9SyhhKTtjJiZjLmxlbmd0aCYmYy5mb3JFYWNoKGZ1bmN0aW9uKGMpe2Mubm9kZXMmJmEuZnJhbWUmJkkoYy5ub2RlcyxhLmZyYW1lRWxlbWVudCxhLmZyYW1lKTt2YXIgZD1ULmZpbmRCeShiLCJpZCIsYy5pZCk7ZD9jLm5vZGVzLmxlbmd0aCYmSihkLm5vZGVzLGMubm9kZXMpOmIucHVzaChjKX0pfSksYn0sVC5ub2RlU29ydGVyPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3JldHVybiBhPT09Yj8wOjQmYS5jb21wYXJlRG9jdW1lbnRQb3NpdGlvbihiKT8tMToxfSxULnB1Ymxpc2hNZXRhRGF0YT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gYihhKXtyZXR1cm4gZnVuY3Rpb24oYil7dmFyIGQ9Y1tiLmlkXXx8e30sZT1kLm1lc3NhZ2VzfHx7fSxmPVQuZXh0ZW5kQmxhY2tsaXN0KHt9LGQsWyJtZXNzYWdlcyJdKTtmLm1lc3NhZ2U9Yi5yZXN1bHQ9PT1hP2UucGFzczplLmZhaWwsVC5leHRlbmRNZXRhRGF0YShiLGYpfX12YXIgYz1TLl9hdWRpdC5kYXRhLmNoZWNrc3x8e30sZD1TLl9hdWRpdC5kYXRhLnJ1bGVzfHx7fSxlPVQuZmluZEJ5KFMuX2F1ZGl0LnJ1bGVzLCJpZCIsYS5pZCl8fHt9O2EudGFncz1ULmNsb25lKGUudGFnc3x8W10pO3ZhciBmPWIoITApLGc9YighMSk7YS5ub2Rlcy5mb3JFYWNoKGZ1bmN0aW9uKGEpe2EuYW55LmZvckVhY2goZiksYS5hbGwuZm9yRWFjaChmKSxhLm5vbmUuZm9yRWFjaChnKX0pLFQuZXh0ZW5kTWV0YURhdGEoYSxULmNsb25lKGRbYS5pZF18fHt9KSl9LGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2Z1bmN0aW9uIGEoKXt9ZnVuY3Rpb24gYigpe2Z1bmN0aW9uIGIoKXtmb3IodmFyIGE9ZS5sZW5ndGg7YT5mO2YrKyl7dmFyIGI9ZVtmXSxkPWIuc2hpZnQoKTtiLnB1c2goYyhmKSksZC5hcHBseShudWxsLGIpfX1mdW5jdGlvbiBjKGEpe3JldHVybiBmdW5jdGlvbihiKXtlW2FdPWIsLS1nfHxkKCl9fWZ1bmN0aW9uIGQoKXtoKGUpfXZhciBlPVtdLGY9MCxnPTAsaD1hO3JldHVybntkZWZlcjpmdW5jdGlvbihhKXtlLnB1c2goW2FdKSwrK2csYigpfSx0aGVuOmZ1bmN0aW9uKGEpe2g9YSxnfHxkKCl9LGFib3J0OmZ1bmN0aW9uKGIpe2g9YSxiKGUpfX19VC5xdWV1ZT1ifSgpLGZ1bmN0aW9uKGIpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBjKGEpe3JldHVybiJvYmplY3QiPT10eXBlb2YgYSYmInN0cmluZyI9PXR5cGVvZiBhLnV1aWQmJmEuX3Jlc3BvbmRhYmxlPT09ITB9ZnVuY3Rpb24gZChhLGIsYyxkLGUpe3ZhciBmPXt1dWlkOmQsdG9waWM6YixtZXNzYWdlOmMsX3Jlc3BvbmRhYmxlOiEwfTtoW2RdPWUsYS5wb3N0TWVzc2FnZShKU09OLnN0cmluZ2lmeShmKSwiKiIpfWZ1bmN0aW9uIGUoYSxiLGMsZSl7dmFyIGY9Ty52MSgpO2QoYSxiLGMsZixlKX1mdW5jdGlvbiBmKGEsYil7dmFyIGM9Yi50b3BpYyxkPWIubWVzc2FnZSxlPWlbY107ZSYmZShkLGcoYS5zb3VyY2UsbnVsbCxiLnV1aWQpKX1mdW5jdGlvbiBnKGEsYixjKXtyZXR1cm4gZnVuY3Rpb24oZSxmKXtkKGEsYixlLGMsZil9fXZhciBoPXt9LGk9e307ZS5zdWJzY3JpYmU9ZnVuY3Rpb24oYSxiKXtpW2FdPWJ9LGEuYWRkRXZlbnRMaXN0ZW5lcigibWVzc2FnZSIsZnVuY3Rpb24oYSl7aWYoInN0cmluZyI9PXR5cGVvZiBhLmRhdGEpe3ZhciBiO3RyeXtiPUpTT04ucGFyc2UoYS5kYXRhKX1jYXRjaChkKXt9aWYoYyhiKSl7dmFyIGU9Yi51dWlkO2hbZV0mJihoW2VdKGIubWVzc2FnZSxnKGEuc291cmNlLGIudG9waWMsZSkpLGhbZV09bnVsbCksZihhLGIpfX19LCExKSxiLnJlc3BvbmRhYmxlPWV9KFQpLFQucnVsZVNob3VsZFJ1bj1mdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiO2lmKGEucGFnZUxldmVsJiYhYi5wYWdlKXJldHVybiExO3ZhciBkPWMucnVuT25seSxlPShjLnJ1bGVzfHx7fSlbYS5pZF07cmV0dXJuIGQ/InJ1bGUiPT09ZC50eXBlPy0xIT09ZC52YWx1ZXMuaW5kZXhPZihhLmlkKTohIShkLnZhbHVlc3x8W10pLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4tMSE9PWEudGFncy5pbmRleE9mKGIpfSkubGVuZ3RoOihlJiZlLmhhc093blByb3BlcnR5KCJlbmFibGVkIik/ZS5lbmFibGVkOmEuZW5hYmxlZCk/ITA6ITF9LFQuc2VsZWN0PWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO2Zvcih2YXIgYyxkPVtdLGU9MCxmPWIuaW5jbHVkZS5sZW5ndGg7Zj5lO2UrKyljPWIuaW5jbHVkZVtlXSxjLm5vZGVUeXBlPT09Yy5FTEVNRU5UX05PREUmJlQubWF0Y2hlc1NlbGVjdG9yKGMsYSkmJk4oZCxbY10sYiksTihkLGMucXVlcnlTZWxlY3RvckFsbChhKSxiKTtyZXR1cm4gZC5zb3J0KFQubm9kZVNvcnRlcil9LFQudG9BcnJheT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJuIEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGEpfSxTLl9sb2FkKHtkYXRhOntydWxlczp7YWNjZXNza2V5czp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZXZlcnkgYWNjZXNza2V5IGF0dHJpYnV0ZSB2YWx1ZSBpcyB1bmlxdWUiLGhlbHA6ImFjY2Vzc2tleSBhdHRyaWJ1dGUgdmFsdWUgbXVzdCBiZSB1bmlxdWUiLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL2FjY2Vzc2tleXMifSwiYXJlYS1hbHQiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyA8YXJlYT4gZWxlbWVudHMgb2YgaW1hZ2UgbWFwcyBoYXZlIGFsdGVybmF0ZSB0ZXh0IixoZWxwOiJBY3RpdmUgPGFyZWE+IGVsZW1lbnRzIG11c3QgaGF2ZSBhbHRlcm5hdGUgdGV4dCIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvYXJlYS1hbHQifSwiYXJpYS1hbGxvd2VkLWF0dHIiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBBUklBIGF0dHJpYnV0ZXMgYXJlIGFsbG93ZWQgZm9yIGFuIGVsZW1lbnQncyByb2xlIixoZWxwOiJFbGVtZW50cyBtdXN0IG9ubHkgdXNlIGFsbG93ZWQgQVJJQSBhdHRyaWJ1dGVzIixoZWxwVXJsOiJodHRwczovL2RlcXVldW5pdmVyc2l0eS5jb20vcnVsZXMvYXhlLzEuMS9hcmlhLWFsbG93ZWQtYXR0ciJ9LCJhcmlhLXJlcXVpcmVkLWF0dHIiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBlbGVtZW50cyB3aXRoIEFSSUEgcm9sZXMgaGF2ZSBhbGwgcmVxdWlyZWQgQVJJQSBhdHRyaWJ1dGVzIixoZWxwOiJSZXF1aXJlZCBBUklBIGF0dHJpYnV0ZXMgbXVzdCBiZSBwcm92aWRlZCIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvYXJpYS1yZXF1aXJlZC1hdHRyIn0sImFyaWEtcmVxdWlyZWQtY2hpbGRyZW4iOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBlbGVtZW50cyB3aXRoIGFuIEFSSUEgcm9sZSB0aGF0IHJlcXVpcmUgY2hpbGQgcm9sZXMgY29udGFpbiB0aGVtIixoZWxwOiJDZXJ0YWluIEFSSUEgcm9sZXMgbXVzdCBjb250YWluIHBhcnRpY3VsYXIgY2hpbGRyZW4iLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL2FyaWEtcmVxdWlyZWQtY2hpbGRyZW4ifSwiYXJpYS1yZXF1aXJlZC1wYXJlbnQiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBlbGVtZW50cyB3aXRoIGFuIEFSSUEgcm9sZSB0aGF0IHJlcXVpcmUgcGFyZW50IHJvbGVzIGFyZSBjb250YWluZWQgYnkgdGhlbSIsaGVscDoiQ2VydGFpbiBBUklBIHJvbGVzIG11c3QgYmUgY29udGFpbmVkIGJ5IHBhcnRpY3VsYXIgcGFyZW50cyIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvYXJpYS1yZXF1aXJlZC1wYXJlbnQifSwiYXJpYS1yb2xlcyI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGFsbCBlbGVtZW50cyB3aXRoIGEgcm9sZSBhdHRyaWJ1dGUgdXNlIGEgdmFsaWQgdmFsdWUiLGhlbHA6IkFSSUEgcm9sZXMgdXNlZCBtdXN0IGNvbmZvcm0gdG8gdmFsaWQgdmFsdWVzIixoZWxwVXJsOiJodHRwczovL2RlcXVldW5pdmVyc2l0eS5jb20vcnVsZXMvYXhlLzEuMS9hcmlhLXJvbGVzIn0sImFyaWEtdmFsaWQtYXR0ci12YWx1ZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGFsbCBBUklBIGF0dHJpYnV0ZXMgaGF2ZSB2YWxpZCB2YWx1ZXMiLGhlbHA6IkFSSUEgYXR0cmlidXRlcyBtdXN0IGNvbmZvcm0gdG8gdmFsaWQgdmFsdWVzIixoZWxwVXJsOiJodHRwczovL2RlcXVldW5pdmVyc2l0eS5jb20vcnVsZXMvYXhlLzEuMS9hcmlhLXZhbGlkLWF0dHItdmFsdWUifSwiYXJpYS12YWxpZC1hdHRyIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgYXR0cmlidXRlcyB0aGF0IGJlZ2luIHdpdGggYXJpYS0gYXJlIHZhbGlkIEFSSUEgYXR0cmlidXRlcyIsaGVscDoiQVJJQSBhdHRyaWJ1dGVzIG11c3QgY29uZm9ybSB0byB2YWxpZCBuYW1lcyIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvYXJpYS12YWxpZC1hdHRyIn0sImF1ZGlvLWNhcHRpb24iOntkZXNjcmlwdGlvbjoiRW5zdXJlcyA8YXVkaW8+IGVsZW1lbnRzIGhhdmUgY2FwdGlvbnMiLGhlbHA6IjxhdWRpbz4gZWxlbWVudHMgbXVzdCBoYXZlIGEgY2FwdGlvbnMgdHJhY2siLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL2F1ZGlvLWNhcHRpb24ifSxibGluazp7ZGVzY3JpcHRpb246IkVuc3VyZXMgPGJsaW5rPiBlbGVtZW50cyBhcmUgbm90IHVzZWQiLGhlbHA6IjxibGluaz4gZWxlbWVudHMgYXJlIGRlcHJlY2F0ZWQgYW5kIG11c3Qgbm90IGJlIHVzZWQiLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL2JsaW5rIn0sImJ1dHRvbi1uYW1lIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgYnV0dG9ucyBoYXZlIGRpc2Nlcm5pYmxlIHRleHQiLGhlbHA6IkJ1dHRvbnMgbXVzdCBoYXZlIGRpc2Nlcm5pYmxlIHRleHQiLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL2J1dHRvbi1uYW1lIn0sYnlwYXNzOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBlYWNoIHBhZ2UgaGFzIGF0IGxlYXN0IG9uZSBtZWNoYW5pc20gZm9yIGEgdXNlciB0byBieXBhc3MgbmF2aWdhdGlvbiBhbmQganVtcCBzdHJhaWdodCB0byB0aGUgY29udGVudCIsaGVscDoiUGFnZSBtdXN0IGhhdmUgbWVhbnMgdG8gYnlwYXNzIHJlcGVhdGVkIGJsb2NrcyIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvYnlwYXNzIn0sY2hlY2tib3hncm91cDp7ZGVzY3JpcHRpb246J0Vuc3VyZXMgcmVsYXRlZCA8aW5wdXQgdHlwZT0iY2hlY2tib3giPiBlbGVtZW50cyBoYXZlIGEgZ3JvdXAgYW5kIHRoYXQgdGhhdCBncm91cCBkZXNpZ25hdGlvbiBpcyBjb25zaXN0ZW50JyxoZWxwOiJDaGVja2JveCBpbnB1dHMgd2l0aCB0aGUgc2FtZSBuYW1lIGF0dHJpYnV0ZSB2YWx1ZSBtdXN0IGJlIHBhcnQgb2YgYSBncm91cCIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvY2hlY2tib3hncm91cCJ9LCJjb2xvci1jb250cmFzdCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIHRoZSBjb250cmFzdCBiZXR3ZWVuIGZvcmVncm91bmQgYW5kIGJhY2tncm91bmQgY29sb3JzIG1lZXRzIFdDQUcgMiBBQSBjb250cmFzdCByYXRpbyB0aHJlc2hvbGRzIixoZWxwOiJFbGVtZW50cyBtdXN0IGhhdmUgc3VmZmljaWVudCBjb2xvciBjb250cmFzdCIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvY29sb3ItY29udHJhc3QifSwiZGF0YS10YWJsZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGRhdGEgdGFibGVzIGFyZSBtYXJrZWQgdXAgc2VtYW50aWNhbGx5IGFuZCBoYXZlIHRoZSBjb3JyZWN0IGhlYWRlciBzdHJ1Y3R1cmUiLGhlbHA6IkRhdGEgdGFibGVzIHNob3VsZCBiZSBtYXJrZWQgdXAgcHJvcGVybHkiLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL2RhdGEtdGFibGUifSwiZGVmaW5pdGlvbi1saXN0Ijp7ZGVzY3JpcHRpb246IkVuc3VyZXMgPGRsPiBlbGVtZW50cyBhcmUgc3RydWN0dXJlZCBjb3JyZWN0bHkiLGhlbHA6IjxkbD4gZWxlbWVudHMgbXVzdCBvbmx5IGRpcmVjdGx5IGNvbnRhaW4gcHJvcGVybHktb3JkZXJlZCA8ZHQ+IGFuZCA8ZGQ+IGdyb3VwcywgPHNjcmlwdD4gb3IgPHRlbXBsYXRlPiBlbGVtZW50cyIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvZGVmaW5pdGlvbi1saXN0In0sZGxpdGVtOntkZXNjcmlwdGlvbjoiRW5zdXJlcyA8ZHQ+IGFuZCA8ZGQ+IGVsZW1lbnRzIGFyZSBjb250YWluZWQgYnkgYSA8ZGw+IixoZWxwOiI8ZHQ+IGFuZCA8ZGQ+IGVsZW1lbnRzIG11c3QgYmUgY29udGFpbmVkIGJ5IGEgPGRsPiIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvZGxpdGVtIn0sImRvY3VtZW50LXRpdGxlIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZWFjaCBIVE1MIGRvY3VtZW50IGNvbnRhaW5zIGEgbm9uLWVtcHR5IDx0aXRsZT4gZWxlbWVudCIsaGVscDoiRG9jdW1lbnRzIG11c3QgaGF2ZSA8dGl0bGU+IGVsZW1lbnQgdG8gYWlkIGluIG5hdmlnYXRpb24iLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL2RvY3VtZW50LXRpdGxlIn0sImR1cGxpY2F0ZS1pZCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGV2ZXJ5IGlkIGF0dHJpYnV0ZSB2YWx1ZSBpcyB1bmlxdWUiLGhlbHA6ImlkIGF0dHJpYnV0ZSB2YWx1ZSBtdXN0IGJlIHVuaXF1ZSIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvZHVwbGljYXRlLWlkIn0sImVtcHR5LWhlYWRpbmciOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBoZWFkaW5ncyBoYXZlIGRpc2Nlcm5pYmxlIHRleHQiLGhlbHA6IkhlYWRpbmdzIG11c3Qgbm90IGJlIGVtcHR5IixoZWxwVXJsOiJodHRwczovL2RlcXVldW5pdmVyc2l0eS5jb20vcnVsZXMvYXhlLzEuMS9lbXB0eS1oZWFkaW5nIn0sImZyYW1lLXRpdGxlIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgPGlmcmFtZT4gYW5kIDxmcmFtZT4gZWxlbWVudHMgY29udGFpbiBhIHVuaXF1ZSBhbmQgbm9uLWVtcHR5IHRpdGxlIGF0dHJpYnV0ZSIsaGVscDoiRnJhbWVzIG11c3QgaGF2ZSB1bmlxdWUgdGl0bGUgYXR0cmlidXRlIixoZWxwVXJsOiJodHRwczovL2RlcXVldW5pdmVyc2l0eS5jb20vcnVsZXMvYXhlLzEuMS9mcmFtZS10aXRsZSJ9LCJoZWFkaW5nLW9yZGVyIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhlIG9yZGVyIG9mIGhlYWRpbmdzIGlzIHNlbWFudGljYWxseSBjb3JyZWN0IixoZWxwOiJIZWFkaW5nIGxldmVscyBzaG91bGQgb25seSBpbmNyZWFzZSBieSBvbmUiLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL2hlYWRpbmctb3JkZXIifSwiaHRtbC1sYW5nIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZXZlcnkgSFRNTCBkb2N1bWVudCBoYXMgYSBsYW5nIGF0dHJpYnV0ZSBhbmQgaXRzIHZhbHVlIGlzIHZhbGlkIixoZWxwOiI8aHRtbD4gZWxlbWVudCBtdXN0IGhhdmUgYSB2YWxpZCBsYW5nIGF0dHJpYnV0ZSIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvaHRtbC1sYW5nIn0sImltYWdlLWFsdCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxpbWc+IGVsZW1lbnRzIGhhdmUgYWx0ZXJuYXRlIHRleHQgb3IgYSByb2xlIG9mIG5vbmUgb3IgcHJlc2VudGF0aW9uIixoZWxwOiJJbWFnZXMgbXVzdCBoYXZlIGFsdGVybmF0ZSB0ZXh0IixoZWxwVXJsOiJodHRwczovL2RlcXVldW5pdmVyc2l0eS5jb20vcnVsZXMvYXhlLzEuMS9pbWFnZS1hbHQifSwiaW5wdXQtaW1hZ2UtYWx0Ijp7ZGVzY3JpcHRpb246J0Vuc3VyZXMgPGlucHV0IHR5cGU9ImltYWdlIj4gZWxlbWVudHMgaGF2ZSBhbHRlcm5hdGUgdGV4dCcsaGVscDoiSW1hZ2UgYnV0dG9ucyBtdXN0IGhhdmUgYWx0ZXJuYXRlIHRleHQiLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL2lucHV0LWltYWdlLWFsdCJ9LCJsYWJlbC10aXRsZS1vbmx5Ijp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhhdCBldmVyeSBmb3JtIGVsZW1lbnQgaXMgbm90IHNvbGVseSBsYWJlbGVkIHVzaW5nIHRoZSB0aXRsZSBvciBhcmlhLWRlc2NyaWJlZGJ5IGF0dHJpYnV0ZXMiLGhlbHA6IkZvcm0gZWxlbWVudHMgc2hvdWxkIGhhdmUgYSB2aXNpYmxlIGxhYmVsIixoZWxwVXJsOiJodHRwczovL2RlcXVldW5pdmVyc2l0eS5jb20vcnVsZXMvYXhlLzEuMS9sYWJlbC10aXRsZS1vbmx5In0sbGFiZWw6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGV2ZXJ5IGZvcm0gZWxlbWVudCBoYXMgYSBsYWJlbCIsaGVscDoiRm9ybSBlbGVtZW50cyBtdXN0IGhhdmUgbGFiZWxzIixoZWxwVXJsOiJodHRwczovL2RlcXVldW5pdmVyc2l0eS5jb20vcnVsZXMvYXhlLzEuMS9sYWJlbCJ9LCJsYXlvdXQtdGFibGUiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBwcmVzZW50YXRpb25hbCA8dGFibGU+IGVsZW1lbnRzIGRvIG5vdCB1c2UgPHRoPiwgPGNhcHRpb24+IGVsZW1lbnRzIG9yIHRoZSBzdW1tYXJ5IGF0dHJpYnV0ZSIsaGVscDoiTGF5b3V0IHRhYmxlcyBtdXN0IG5vdCB1c2UgZGF0YSB0YWJsZSBlbGVtZW50cyIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvbGF5b3V0LXRhYmxlIn0sImxpbmstbmFtZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGxpbmtzIGhhdmUgZGlzY2VybmlibGUgdGV4dCIsaGVscDoiTGlua3MgbXVzdCBoYXZlIGRpc2Nlcm5pYmxlIHRleHQiLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL2xpbmstbmFtZSJ9LGxpc3Q6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIHRoYXQgbGlzdHMgYXJlIHN0cnVjdHVyZWQgY29ycmVjdGx5IixoZWxwOiI8dWw+IGFuZCA8b2w+IG11c3Qgb25seSBkaXJlY3RseSBjb250YWluIDxsaT4sIDxzY3JpcHQ+IG9yIDx0ZW1wbGF0ZT4gZWxlbWVudHMiLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL2xpc3QifSxsaXN0aXRlbTp7ZGVzY3JpcHRpb246IkVuc3VyZXMgPGxpPiBlbGVtZW50cyBhcmUgdXNlZCBzZW1hbnRpY2FsbHkiLGhlbHA6IjxsaT4gZWxlbWVudHMgbXVzdCBiZSBjb250YWluZWQgaW4gYSA8dWw+IG9yIDxvbD4iLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL2xpc3RpdGVtIn0sbWFycXVlZTp7ZGVzY3JpcHRpb246IkVuc3VyZXMgPG1hcnF1ZWU+IGVsZW1lbnRzIGFyZSBub3QgdXNlZCIsaGVscDoiPG1hcnF1ZWU+IGVsZW1lbnRzIGFyZSBkZXByZWNhdGVkIGFuZCBtdXN0IG5vdCBiZSB1c2VkIixoZWxwVXJsOiJodHRwczovL2RlcXVldW5pdmVyc2l0eS5jb20vcnVsZXMvYXhlLzEuMS9tYXJxdWVlIn0sIm1ldGEtcmVmcmVzaCI6e2Rlc2NyaXB0aW9uOidFbnN1cmVzIDxtZXRhIGh0dHAtZXF1aXY9InJlZnJlc2giPiBpcyBub3QgdXNlZCcsaGVscDoiVGltZWQgcmVmcmVzaCBtdXN0IG5vdCBleGlzdCIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvbWV0YS1yZWZyZXNoIn0sIm1ldGEtdmlld3BvcnQiOntkZXNjcmlwdGlvbjonRW5zdXJlcyA8bWV0YSBuYW1lPSJ2aWV3cG9ydCI+IGRvZXMgbm90IGRpc2FibGUgdGV4dCBzY2FsaW5nIGFuZCB6b29taW5nJyxoZWxwOiJab29taW5nIGFuZCBzY2FsaW5nIG11c3Qgbm90IGJlIGRpc2FibGVkIixoZWxwVXJsOiJodHRwczovL2RlcXVldW5pdmVyc2l0eS5jb20vcnVsZXMvYXhlLzEuMS9tZXRhLXZpZXdwb3J0In0sIm9iamVjdC1hbHQiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyA8b2JqZWN0PiBlbGVtZW50cyBoYXZlIGFsdGVybmF0ZSB0ZXh0IixoZWxwOiI8b2JqZWN0PiBlbGVtZW50cyBtdXN0IGhhdmUgYWx0ZXJuYXRlIHRleHQiLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL29iamVjdC1hbHQifSxyYWRpb2dyb3VwOntkZXNjcmlwdGlvbjonRW5zdXJlcyByZWxhdGVkIDxpbnB1dCB0eXBlPSJyYWRpbyI+IGVsZW1lbnRzIGhhdmUgYSBncm91cCBhbmQgdGhhdCB0aGUgZ3JvdXAgZGVzaWduYXRpb24gaXMgY29uc2lzdGVudCcsaGVscDoiUmFkaW8gaW5wdXRzIHdpdGggdGhlIHNhbWUgbmFtZSBhdHRyaWJ1dGUgdmFsdWUgbXVzdCBiZSBwYXJ0IG9mIGEgZ3JvdXAiLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL3JhZGlvZ3JvdXAifSxyZWdpb246e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGFsbCBjb250ZW50IGlzIGNvbnRhaW5lZCB3aXRoaW4gYSBsYW5kbWFyayByZWdpb24iLGhlbHA6IkNvbnRlbnQgc2hvdWxkIGJlIGNvbnRhaW5lZCBpbiBhIGxhbmRtYXJrIHJlZ2lvbiIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvcmVnaW9uIn0sc2NvcGU6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIHRoZSBzY29wZSBhdHRyaWJ1dGUgaXMgdXNlZCBjb3JyZWN0bHkgb24gdGFibGVzIixoZWxwOiJzY29wZSBhdHRyaWJ1dGUgc2hvdWxkIGJlIHVzZWQgY29ycmVjdGx5IixoZWxwVXJsOiJodHRwczovL2RlcXVldW5pdmVyc2l0eS5jb20vcnVsZXMvYXhlLzEuMS9zY29wZSJ9LCJzZXJ2ZXItc2lkZS1pbWFnZS1tYXAiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGF0IHNlcnZlci1zaWRlIGltYWdlIG1hcHMgYXJlIG5vdCB1c2VkIixoZWxwOiJTZXJ2ZXItc2lkZSBpbWFnZSBtYXBzIG11c3Qgbm90IGJlIHVzZWQiLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL3NlcnZlci1zaWRlLWltYWdlLW1hcCJ9LCJza2lwLWxpbmsiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGUgZmlyc3QgbGluayBvbiB0aGUgcGFnZSBpcyBhIHNraXAgbGluayIsaGVscDoiVGhlIHBhZ2Ugc2hvdWxkIGhhdmUgYSBza2lwIGxpbmsgYXMgaXRzIGZpcnN0IGxpbmsiLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL3NraXAtbGluayJ9LHRhYmluZGV4OntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0YWJpbmRleCBhdHRyaWJ1dGUgdmFsdWVzIGFyZSBub3QgZ3JlYXRlciB0aGFuIDAiLGhlbHA6IkVsZW1lbnRzIHNob3VsZCBub3QgaGF2ZSB0YWJpbmRleCBncmVhdGVyIHRoYW4gemVybyIsaGVscFVybDoiaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzL2F4ZS8xLjEvdGFiaW5kZXgifSwidmFsaWQtbGFuZyI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGxhbmcgYXR0cmlidXRlcyBoYXZlIHZhbGlkIHZhbHVlcyIsaGVscDoibGFuZyBhdHRyaWJ1dGUgbXVzdCBoYXZlIGEgdmFsaWQgdmFsdWUiLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL3ZhbGlkLWxhbmcifSwidmlkZW8tY2FwdGlvbiI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDx2aWRlbz4gZWxlbWVudHMgaGF2ZSBjYXB0aW9ucyIsaGVscDoiPHZpZGVvPiBlbGVtZW50cyBtdXN0IGhhdmUgY2FwdGlvbnMiLApoZWxwVXJsOiJodHRwczovL2RlcXVldW5pdmVyc2l0eS5jb20vcnVsZXMvYXhlLzEuMS92aWRlby1jYXB0aW9uIn0sInZpZGVvLWRlc2NyaXB0aW9uIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgPHZpZGVvPiBlbGVtZW50cyBoYXZlIGF1ZGlvIGRlc2NyaXB0aW9ucyIsaGVscDoiPHZpZGVvPiBlbGVtZW50cyBtdXN0IGhhdmUgYW4gYXVkaW8gZGVzY3JpcHRpb24gdHJhY2siLGhlbHBVcmw6Imh0dHBzOi8vZGVxdWV1bml2ZXJzaXR5LmNvbS9ydWxlcy9heGUvMS4xL3ZpZGVvLWRlc2NyaXB0aW9uIn19LGNoZWNrczp7YWNjZXNza2V5czp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkFjY2Vzc2tleSBhdHRyaWJ1dGUgdmFsdWUgaXMgdW5pcXVlIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRG9jdW1lbnQgaGFzIG11bHRpcGxlIGVsZW1lbnRzIHdpdGggdGhlIHNhbWUgYWNjZXNza2V5IjtyZXR1cm4gYn19fSwibm9uLWVtcHR5LWFsdCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBhIG5vbi1lbXB0eSBhbHQgYXR0cmlidXRlIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBoYXMgbm8gYWx0IGF0dHJpYnV0ZSBvciB0aGUgYWx0IGF0dHJpYnV0ZSBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sImFyaWEtbGFiZWwiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iYXJpYS1sYWJlbCBhdHRyaWJ1dGUgZXhpc3RzIGFuZCBpcyBub3QgZW1wdHkiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJhcmlhLWxhYmVsIGF0dHJpYnV0ZSBkb2VzIG5vdCBleGlzdCBvciBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sImFyaWEtbGFiZWxsZWRieSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJhcmlhLWxhYmVsbGVkYnkgYXR0cmlidXRlIGV4aXN0cyBhbmQgcmVmZXJlbmNlcyBlbGVtZW50cyB0aGF0IGFyZSB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iYXJpYS1sYWJlbGxlZGJ5IGF0dHJpYnV0ZSBkb2VzIG5vdCBleGlzdCwgcmVmZXJlbmNlcyBlbGVtZW50cyB0aGF0IGRvIG5vdCBleGlzdCBvciByZWZlcmVuY2VzIGVsZW1lbnRzIHRoYXQgYXJlIGVtcHR5IG9yIG5vdCB2aXNpYmxlIjtyZXR1cm4gYn19fSwiYXJpYS1hbGxvd2VkLWF0dHIiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQVJJQSBhdHRyaWJ1dGVzIGFyZSB1c2VkIGNvcnJlY3RseSBmb3IgdGhlIGRlZmluZWQgcm9sZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkFSSUEgYXR0cmlidXRlIisoYS5kYXRhJiZhLmRhdGEubGVuZ3RoPjE/InMgYXJlIjoiIGlzIikrIiBub3QgYWxsb3dlZDoiLGM9YS5kYXRhO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2Y+ZTspZD1jW2UrPTFdLGIrPSIgIitkO3JldHVybiBifX19LCJhcmlhLXJlcXVpcmVkLWF0dHIiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQWxsIHJlcXVpcmVkIEFSSUEgYXR0cmlidXRlcyBhcmUgcHJlc2VudCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlJlcXVpcmVkIEFSSUEgYXR0cmlidXRlIisoYS5kYXRhJiZhLmRhdGEubGVuZ3RoPjE/InMiOiIiKSsiIG5vdCBwcmVzZW50OiIsYz1hLmRhdGE7aWYoYylmb3IodmFyIGQsZT0tMSxmPWMubGVuZ3RoLTE7Zj5lOylkPWNbZSs9MV0sYis9IiAiK2Q7cmV0dXJuIGJ9fX0sImFyaWEtcmVxdWlyZWQtY2hpbGRyZW4iOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iUmVxdWlyZWQgQVJJQSBjaGlsZHJlbiBhcmUgcHJlc2VudCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlJlcXVpcmVkIEFSSUEgIisoYS5kYXRhJiZhLmRhdGEubGVuZ3RoPjE/ImNoaWxkcmVuIjoiY2hpbGQiKSsiIHJvbGUgbm90IHByZXNlbnQ6IixjPWEuZGF0YTtpZihjKWZvcih2YXIgZCxlPS0xLGY9Yy5sZW5ndGgtMTtmPmU7KWQ9Y1tlKz0xXSxiKz0iICIrZDtyZXR1cm4gYn19fSwiYXJpYS1yZXF1aXJlZC1wYXJlbnQiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iUmVxdWlyZWQgQVJJQSBwYXJlbnQgcm9sZSBwcmVzZW50IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iUmVxdWlyZWQgQVJJQSBwYXJlbnQiKyhhLmRhdGEmJmEuZGF0YS5sZW5ndGg+MT8icyI6IiIpKyIgcm9sZSBub3QgcHJlc2VudDoiLGM9YS5kYXRhO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2Y+ZTspZD1jW2UrPTFdLGIrPSIgIitkO3JldHVybiBifX19LGludmFsaWRyb2xlOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQVJJQSByb2xlIGlzIHZhbGlkIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iUm9sZSBtdXN0IGJlIG9uZSBvZiB0aGUgdmFsaWQgQVJJQSByb2xlcyI7cmV0dXJuIGJ9fX0sYWJzdHJhY3Ryb2xlOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJBYnN0cmFjdCByb2xlcyBhcmUgbm90IHVzZWQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJBYnN0cmFjdCByb2xlcyBjYW5ub3QgYmUgZGlyZWN0bHkgdXNlZCI7cmV0dXJuIGJ9fX0sImFyaWEtdmFsaWQtYXR0ci12YWx1ZSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJBUklBIGF0dHJpYnV0ZSB2YWx1ZXMgYXJlIHZhbGlkIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iSW52YWxpZCBBUklBIGF0dHJpYnV0ZSB2YWx1ZSIrKGEuZGF0YSYmYS5kYXRhLmxlbmd0aD4xPyJzIjoiIikrIjoiLGM9YS5kYXRhO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2Y+ZTspZD1jW2UrPTFdLGIrPSIgIitkO3JldHVybiBifX19LCJhcmlhLXZhbGlkLWF0dHIiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQVJJQSBhdHRyaWJ1dGUgbmFtZSIrKGEuZGF0YSYmYS5kYXRhLmxlbmd0aD4xPyJzIjoiIikrIiBhcmUgdmFsaWQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJJbnZhbGlkIEFSSUEgYXR0cmlidXRlIG5hbWUiKyhhLmRhdGEmJmEuZGF0YS5sZW5ndGg+MT8icyI6IiIpKyI6IixjPWEuZGF0YTtpZihjKWZvcih2YXIgZCxlPS0xLGY9Yy5sZW5ndGgtMTtmPmU7KWQ9Y1tlKz0xXSxiKz0iICIrZDtyZXR1cm4gYn19fSxjYXB0aW9uOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIG11bHRpbWVkaWEgZWxlbWVudCBoYXMgYSBjYXB0aW9ucyB0cmFjayI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSBtdWx0aW1lZGlhIGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhIGNhcHRpb25zIHRyYWNrIjtyZXR1cm4gYn19fSxleGlzdHM6e2ltcGFjdDoibWlub3IiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGRvZXMgbm90IGV4aXN0IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBleGlzdHMiO3JldHVybiBifX19LCJub24tZW1wdHktaWYtcHJlc2VudCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50ICI7cmV0dXJuIGIrPWEuZGF0YT8iaGFzIGEgbm9uLWVtcHR5IHZhbHVlIGF0dHJpYnV0ZSI6ImRvZXMgbm90IGhhdmUgYSB2YWx1ZSBhdHRyaWJ1dGUifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBhIHZhbHVlIGF0dHJpYnV0ZSBhbmQgdGhlIHZhbHVlIGF0dHJpYnV0ZSBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sIm5vbi1lbXB0eS12YWx1ZSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBhIG5vbi1lbXB0eSB2YWx1ZSBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBubyB2YWx1ZSBhdHRyaWJ1dGUgb3IgdGhlIHZhbHVlIGF0dHJpYnV0ZSBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sImJ1dHRvbi1oYXMtdmlzaWJsZS10ZXh0Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIGlubmVyIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBkb2VzIG5vdCBoYXZlIGlubmVyIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn19fSwicm9sZS1wcmVzZW50YXRpb24iOntpbXBhY3Q6Im1vZGVyYXRlIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0nRWxlbWVudFwncyBkZWZhdWx0IHNlbWFudGljcyB3ZXJlIG92ZXJyaWRlbiB3aXRoIHJvbGU9InByZXNlbnRhdGlvbiInO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSdFbGVtZW50XCdzIGRlZmF1bHQgc2VtYW50aWNzIHdlcmUgbm90IG92ZXJyaWRkZW4gd2l0aCByb2xlPSJwcmVzZW50YXRpb24iJztyZXR1cm4gYn19fSwicm9sZS1ub25lIjp7aW1wYWN0OiJtb2RlcmF0ZSIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9J0VsZW1lbnRcJ3MgZGVmYXVsdCBzZW1hbnRpY3Mgd2VyZSBvdmVycmlkZW4gd2l0aCByb2xlPSJub25lIic7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9J0VsZW1lbnRcJ3MgZGVmYXVsdCBzZW1hbnRpY3Mgd2VyZSBub3Qgb3ZlcnJpZGRlbiB3aXRoIHJvbGU9Im5vbmUiJztyZXR1cm4gYn19fSwiZHVwbGljYXRlLWltZy1sYWJlbCI6e2ltcGFjdDoibWlub3IiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGRvZXMgbm90IGR1cGxpY2F0ZSBleGlzdGluZyB0ZXh0IGluIDxpbWc+IGFsdCB0ZXh0IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBjb250YWlucyA8aW1nPiBlbGVtZW50IHdpdGggYWx0IHRleHQgdGhhdCBkdXBsaWNhdGVzIGV4aXN0aW5nIHRleHQiO3JldHVybiBifX19LCJmb2N1c2FibGUtbm8tbmFtZSI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaXMgbm90IGluIHRhYiBvcmRlciBvciBoYXMgYWNjZXNzaWJsZSB0ZXh0IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBpcyBpbiB0YWIgb3JkZXIgYW5kIGRvZXMgbm90IGhhdmUgYWNjZXNzaWJsZSB0ZXh0IjtyZXR1cm4gYn19fSwiaW50ZXJuYWwtbGluay1wcmVzZW50Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlZhbGlkIHNraXAgbGluayBmb3VuZCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9Ik5vIHZhbGlkIHNraXAgbGluayBmb3VuZCI7cmV0dXJuIGJ9fX0sImhlYWRlci1wcmVzZW50Ijp7aW1wYWN0OiJtb2RlcmF0ZSIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlBhZ2UgaGFzIGEgaGVhZGVyIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iUGFnZSBkb2VzIG5vdCBoYXZlIGEgaGVhZGVyIjtyZXR1cm4gYn19fSxsYW5kbWFyazp7aW1wYWN0OiJzZXJpb3VzIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iUGFnZSBoYXMgYSBsYW5kbWFyayByZWdpb24iO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJQYWdlIGRvZXMgbm90IGhhdmUgYSBsYW5kbWFyayByZWdpb24iO3JldHVybiBifX19LCJncm91cC1sYWJlbGxlZGJ5Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9J0FsbCBlbGVtZW50cyB3aXRoIHRoZSBuYW1lICInK2EuZGF0YS5uYW1lKyciIHJlZmVyZW5jZSB0aGUgc2FtZSBlbGVtZW50IHdpdGggYXJpYS1sYWJlbGxlZGJ5JztyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0nQWxsIGVsZW1lbnRzIHdpdGggdGhlIG5hbWUgIicrYS5kYXRhLm5hbWUrJyIgZG8gbm90IHJlZmVyZW5jZSB0aGUgc2FtZSBlbGVtZW50IHdpdGggYXJpYS1sYWJlbGxlZGJ5JztyZXR1cm4gYn19fSxmaWVsZHNldDp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaXMgY29udGFpbmVkIGluIGEgZmllbGRzZXQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSIiLGM9YS5kYXRhJiZhLmRhdGEuZmFpbHVyZUNvZGU7cmV0dXJuIGIrPSJuby1sZWdlbmQiPT09Yz8iRmllbGRzZXQgZG9lcyBub3QgaGF2ZSBhIGxlZ2VuZCBhcyBpdHMgZmlyc3QgY2hpbGQiOiJlbXB0eS1sZWdlbmQiPT09Yz8iTGVnZW5kIGRvZXMgbm90IGhhdmUgdGV4dCB0aGF0IGlzIHZpc2libGUgdG8gc2NyZWVuIHJlYWRlcnMiOiJtaXhlZC1pbnB1dHMiPT09Yz8iRmllbGRzZXQgY29udGFpbnMgdW5yZWxhdGVkIGlucHV0cyI6Im5vLWdyb3VwLWxhYmVsIj09PWM/IkFSSUEgZ3JvdXAgZG9lcyBub3QgaGF2ZSBhcmlhLWxhYmVsIG9yIGFyaWEtbGFiZWxsZWRieSI6Imdyb3VwLW1peGVkLWlucHV0cyI9PT1jPyJBUklBIGdyb3VwIGNvbnRhaW5zIHVucmVsYXRlZCBpbnB1dHMiOiJFbGVtZW50IGRvZXMgbm90IGhhdmUgYSBjb250YWluaW5nIGZpZWxkc2V0IG9yIEFSSUEgZ3JvdXAifX19LCJjb2xvci1jb250cmFzdCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSIiO3JldHVybiBiKz1hLmRhdGEmJmEuZGF0YS5jb250cmFzdFJhdGlvPyJFbGVtZW50IGhhcyBzdWZmaWNpZW50IGNvbG9yIGNvbnRyYXN0IG9mICIrYS5kYXRhLmNvbnRyYXN0UmF0aW86IlVuYWJsZSB0byBkZXRlcm1pbmUgY29udHJhc3QgcmF0aW8ifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBpbnN1ZmZpY2llbnQgY29sb3IgY29udHJhc3Qgb2YgIithLmRhdGEuY29udHJhc3RSYXRpbysiIChmb3JlZ3JvdW5kIGNvbG9yOiAiK2EuZGF0YS5mZ0NvbG9yKyIsIGJhY2tncm91bmQgY29sb3I6ICIrYS5kYXRhLmJnQ29sb3IrIiwgZm9udCBzaXplOiAiK2EuZGF0YS5mb250U2l6ZSsiLCBmb250IHdlaWdodDogIithLmRhdGEuZm9udFdlaWdodCsiKSI7cmV0dXJuIGJ9fX0sImNvbnNpc3RlbnQtY29sdW1ucyI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJUYWJsZSBoYXMgY29uc2lzdGVudCBjb2x1bW4gd2lkdGhzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iVGFibGUgZG9lcyBub3QgaGF2ZSB0aGUgc2FtZSBudW1iZXIgb2YgY29sdW1ucyBpbiBldmVyeSByb3ciO3JldHVybiBifX19LCJjZWxsLW5vLWhlYWRlciI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJBbGwgZGF0YSBjZWxscyBoYXZlIHRhYmxlIGhlYWRlcnMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJTb21lIGRhdGEgY2VsbHMgZG8gbm90IGhhdmUgdGFibGUgaGVhZGVycyI7cmV0dXJuIGJ9fX0sImhlYWRlcnMtdmlzaWJsZS10ZXh0Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkhlYWRlciBjZWxsIGhhcyB2aXNpYmxlIHRleHQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJIZWFkZXIgY2VsbCBkb2VzIG5vdCBoYXZlIHZpc2libGUgdGV4dCI7cmV0dXJuIGJ9fX0sImhlYWRlcnMtYXR0ci1yZWZlcmVuY2UiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iaGVhZGVycyBhdHRyaWJ1dGUgcmVmZXJlbmNlcyBlbGVtZW50cyB0aGF0IGFyZSB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iaGVhZGVycyBhdHRyaWJ1dGUgcmVmZXJlbmNlcyBlbGVtZW50IHRoYXQgaXMgbm90IHZpc2libGUgdG8gc2NyZWVuIHJlYWRlcnMiO3JldHVybiBifX19LCJ0aC1zY29wZSI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9Ijx0aD4gZWxlbWVudHMgdXNlIHNjb3BlIGF0dHJpYnV0ZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9Ijx0aD4gZWxlbWVudHMgbXVzdCB1c2Ugc2NvcGUgYXR0cmlidXRlIjtyZXR1cm4gYn19fSwibm8tY2FwdGlvbiI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlRhYmxlIGhhcyBhIDxjYXB0aW9uPiI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlRhYmxlIGRvZXMgbm90IGhhdmUgYSA8Y2FwdGlvbj4iO3JldHVybiBifX19LCJ0aC1oZWFkZXJzLWF0dHIiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSI8dGg+IGVsZW1lbnRzIGRvIG5vdCB1c2UgaGVhZGVycyBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSI8dGg+IGVsZW1lbnRzIHNob3VsZCBub3QgdXNlIGhlYWRlcnMgYXR0cmlidXRlIjtyZXR1cm4gYn19fSwidGgtc2luZ2xlLXJvdy1jb2x1bW4iOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSI8dGg+IGVsZW1lbnRzIGFyZSB1c2VkIHdoZW4gdGhlcmUgaXMgb25seSBhIHNpbmdsZSByb3cgYW5kIHNpbmdsZSBjb2x1bW4gb2YgaGVhZGVycyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9Ijx0aD4gZWxlbWVudHMgc2hvdWxkIG9ubHkgYmUgdXNlZCB3aGVuIHRoZXJlIGlzIGEgc2luZ2xlIHJvdyBhbmQgc2luZ2xlIGNvbHVtbiBvZiBoZWFkZXJzIjtyZXR1cm4gYn19fSwic2FtZS1jYXB0aW9uLXN1bW1hcnkiOntpbXBhY3Q6Im1vZGVyYXRlIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQ29udGVudCBvZiBzdW1tYXJ5IGF0dHJpYnV0ZSBhbmQgPGNhcHRpb24+IGFyZSBub3QgZHVwbGljYXRlZCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkNvbnRlbnQgb2Ygc3VtbWFyeSBhdHRyaWJ1dGUgYW5kIDxjYXB0aW9uPiBlbGVtZW50IGFyZSBpbmRlbnRpY2FsIjtyZXR1cm4gYn19fSxyb3dzcGFuOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iVGFibGUgZG9lcyBub3QgaGF2ZSBjZWxscyB3aXRoIHJvd3NwYW4gYXR0cmlidXRlIGdyZWF0ZXIgdGhhbiAxIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iVGFibGUgaGFzIGNlbGxzIHdob3NlIHJvd3NwYW4gYXR0cmlidXRlIGlzIG5vdCBlcXVhbCB0byAxIjtyZXR1cm4gYn19fSwic3RydWN0dXJlZC1kbGl0ZW1zIjp7aW1wYWN0OiJzZXJpb3VzIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iV2hlbiBub3QgZW1wdHksIGVsZW1lbnQgaGFzIGJvdGggPGR0PiBhbmQgPGRkPiBlbGVtZW50cyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IldoZW4gbm90IGVtcHR5LCBlbGVtZW50IGRvZXMgbm90IGhhdmUgYXQgbGVhc3Qgb25lIDxkdD4gZWxlbWVudCBmb2xsb3dlZCBieSBhdCBsZWFzdCBvbmUgPGRkPiBlbGVtZW50IjtyZXR1cm4gYn19fSwib25seS1kbGl0ZW1zIjp7aW1wYWN0OiJzZXJpb3VzIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBvbmx5IGhhcyBjaGlsZHJlbiB0aGF0IGFyZSA8ZHQ+IG9yIDxkZD4gZWxlbWVudHMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBjaGlsZHJlbiB0aGF0IGFyZSBub3QgPGR0PiBvciA8ZGQ+IGVsZW1lbnRzIjtyZXR1cm4gYn19fSxkbGl0ZW06e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkRlc2NyaXB0aW9uIGxpc3QgaXRlbSBoYXMgYSA8ZGw+IHBhcmVudCBlbGVtZW50IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRGVzY3JpcHRpb24gbGlzdCBpdGVtIGRvZXMgbm90IGhhdmUgYSA8ZGw+IHBhcmVudCBlbGVtZW50IjtyZXR1cm4gYn19fSwiZG9jLWhhcy10aXRsZSI6e2ltcGFjdDoibW9kZXJhdGUiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJEb2N1bWVudCBoYXMgYSBub24tZW1wdHkgPHRpdGxlPiBlbGVtZW50IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRG9jdW1lbnQgZG9lcyBub3QgaGF2ZSBhIG5vbi1lbXB0eSA8dGl0bGU+IGVsZW1lbnQiO3JldHVybiBifX19LCJkdXBsaWNhdGUtaWQiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRG9jdW1lbnQgaGFzIG5vIGVsZW1lbnRzIHRoYXQgc2hhcmUgdGhlIHNhbWUgaWQgYXR0cmlidXRlIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRG9jdW1lbnQgaGFzIG11bHRpcGxlIGVsZW1lbnRzIHdpdGggdGhlIHNhbWUgaWQgYXR0cmlidXRlOiAiK2EuZGF0YTtyZXR1cm4gYn19fSwiaGFzLXZpc2libGUtdGV4dCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyB0ZXh0IHRoYXQgaXMgdmlzaWJsZSB0byBzY3JlZW4gcmVhZGVycyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgZG9lcyBub3QgaGF2ZSB0ZXh0IHRoYXQgaXMgdmlzaWJsZSB0byBzY3JlZW4gcmVhZGVycyI7cmV0dXJuIGJ9fX0sIm5vbi1lbXB0eS10aXRsZSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBhIHRpdGxlIGF0dHJpYnV0ZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIG5vIHRpdGxlIGF0dHJpYnV0ZSBvciB0aGUgdGl0bGUgYXR0cmlidXRlIGlzIGVtcHR5IjtyZXR1cm4gYn19fSwidW5pcXVlLWZyYW1lLXRpdGxlIjp7aW1wYWN0OiJzZXJpb3VzIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCdzIHRpdGxlIGF0dHJpYnV0ZSBpcyB1bmlxdWUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50J3MgdGl0bGUgYXR0cmlidXRlIGlzIG5vdCB1bmlxdWUiO3JldHVybiBifX19LCJoZWFkaW5nLW9yZGVyIjp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkhlYWRpbmcgb3JkZXIgdmFsaWQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJIZWFkaW5nIG9yZGVyIGludmFsaWQiO3JldHVybiBifX19LCJoYXMtbGFuZyI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSA8aHRtbD4gZWxlbWVudCBoYXMgYSBsYW5nIGF0dHJpYnV0ZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSA8aHRtbD4gZWxlbWVudCBkb2VzIG5vdCBoYXZlIGEgbGFuZyBhdHRyaWJ1dGUiO3JldHVybiBifX19LCJ2YWxpZC1sYW5nIjp7aW1wYWN0OiJzZXJpb3VzIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iVmFsdWUgb2YgbGFuZyBhdHRyaWJ1dGUgaXMgaW5jbHVkZWQgaW4gdGhlIGxpc3Qgb2YgdmFsaWQgbGFuZ3VhZ2VzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iVmFsdWUgb2YgbGFuZyBhdHRyaWJ1dGUgbm90IGluY2x1ZGVkIGluIHRoZSBsaXN0IG9mIHZhbGlkIGxhbmd1YWdlcyI7cmV0dXJuIGJ9fX0sImhhcy1hbHQiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBoYXMgYW4gYWx0IGF0dHJpYnV0ZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhbiBhbHQgYXR0cmlidXRlIjtyZXR1cm4gYn19fSwidGl0bGUtb25seSI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkZvcm0gZWxlbWVudCBkb2VzIG5vdCBzb2xlbHkgdXNlIHRpdGxlIGF0dHJpYnV0ZSBmb3IgaXRzIGxhYmVsIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iT25seSB0aXRsZSB1c2VkIHRvIGdlbmVyYXRlIGxhYmVsIGZvciBmb3JtIGVsZW1lbnQiO3JldHVybiBifX19LCJpbXBsaWNpdC1sYWJlbCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgaGFzIGFuIGltcGxpY2l0ICh3cmFwcGVkKSA8bGFiZWw+IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRm9ybSBlbGVtZW50IGRvZXMgbm90IGhhdmUgYW4gaW1wbGljaXQgKHdyYXBwZWQpIDxsYWJlbD4iO3JldHVybiBifX19LCJleHBsaWNpdC1sYWJlbCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgaGFzIGFuIGV4cGxpY2l0IDxsYWJlbD4iO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhbiBleHBsaWNpdCA8bGFiZWw+IjtyZXR1cm4gYn19fSwiaGVscC1zYW1lLWFzLWxhYmVsIjp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkhlbHAgdGV4dCAodGl0bGUgb3IgYXJpYS1kZXNjcmliZWRieSkgZG9lcyBub3QgZHVwbGljYXRlIGxhYmVsIHRleHQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJIZWxwIHRleHQgKHRpdGxlIG9yIGFyaWEtZGVzY3JpYmVkYnkpIHRleHQgaXMgdGhlIHNhbWUgYXMgdGhlIGxhYmVsIHRleHQiO3JldHVybiBifX19LCJtdWx0aXBsZS1sYWJlbCI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkZvcm0gZWxlbWVudCBkb2VzIG5vdCBoYXZlIG11bHRpcGxlIDxsYWJlbD4gZWxlbWVudHMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgaGFzIG11bHRpcGxlIDxsYWJlbD4gZWxlbWVudHMiO3JldHVybiBifX19LCJoYXMtdGgiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgZG9lcyBub3QgdXNlIDx0aD4gZWxlbWVudHMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgdXNlcyA8dGg+IGVsZW1lbnRzIjtyZXR1cm4gYn19fSwiaGFzLWNhcHRpb24iOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgZG9lcyBub3QgdXNlIDxjYXB0aW9uPiBlbGVtZW50IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iTGF5b3V0IHRhYmxlIHVzZXMgPGNhcHRpb24+IGVsZW1lbnQiO3JldHVybiBifX19LCJoYXMtc3VtbWFyeSI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkxheW91dCB0YWJsZSBkb2VzIG5vdCB1c2Ugc3VtbWFyeSBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgdXNlcyBzdW1tYXJ5IGF0dHJpYnV0ZSI7cmV0dXJuIGJ9fX0sIm9ubHktbGlzdGl0ZW1zIjp7aW1wYWN0OiJzZXJpb3VzIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iTGlzdCBlbGVtZW50IG9ubHkgaGFzIGNoaWxkcmVuIHRoYXQgYXJlIDxsaT4sIDxzY3JpcHQ+IG9yIDx0ZW1wbGF0ZT4gZWxlbWVudHMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJMaXN0IGVsZW1lbnQgaGFzIGNoaWxkcmVuIHRoYXQgYXJlIG5vdCA8bGk+LCA8c2NyaXB0PiBvciA8dGVtcGxhdGU+IGVsZW1lbnRzIjtyZXR1cm4gYn19fSxsaXN0aXRlbTp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9Ikxpc3QgaXRlbSBoYXMgYSA8dWw+IG9yIDxvbD4gcGFyZW50IGVsZW1lbnQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJMaXN0IGl0ZW0gZG9lcyBub3QgaGF2ZSBhIDx1bD4gb3IgPG9sPiBwYXJlbnQgZWxlbWVudCI7cmV0dXJuIGJ9fX0sIm1ldGEtcmVmcmVzaCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSI8bWV0YT4gdGFnIGRvZXMgbm90IGltbWVkaWF0ZWx5IHJlZnJlc2ggdGhlIHBhZ2UiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSI8bWV0YT4gdGFnIGZvcmNlcyB0aW1lZCByZWZyZXNoIG9mIHBhZ2UiO3JldHVybiBifX19LCJtZXRhLXZpZXdwb3J0Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IjxtZXRhPiB0YWcgZG9lcyBub3QgZGlzYWJsZSB6b29taW5nIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iPG1ldGE+IHRhZyBkaXNhYmxlcyB6b29taW5nIjtyZXR1cm4gYn19fSxyZWdpb246e2ltcGFjdDoibW9kZXJhdGUiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJDb250ZW50IGNvbnRhaW5lZCBieSBBUklBIGxhbmRtYXJrIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iQ29udGVudCBub3QgY29udGFpbmVkIGJ5IGFuIEFSSUEgbGFuZG1hcmsiO3JldHVybiBifX19LCJodG1sNS1zY29wZSI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlNjb3BlIGF0dHJpYnV0ZSBpcyBvbmx5IHVzZWQgb24gdGFibGUgaGVhZGVyIGVsZW1lbnRzICg8dGg+KSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkluIEhUTUwgNSwgc2NvcGUgYXR0cmlidXRlcyBtYXkgb25seSBiZSB1c2VkIG9uIHRhYmxlIGhlYWRlciBlbGVtZW50cyAoPHRoPikiO3JldHVybiBifX19LCJodG1sNC1zY29wZSI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlNjb3BlIGF0dHJpYnV0ZSBpcyBvbmx5IHVzZWQgb24gdGFibGUgY2VsbCBlbGVtZW50cyAoPHRoPiBhbmQgPHRkPikiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJJbiBIVE1MIDQsIHRoZSBzY29wZSBhdHRyaWJ1dGUgbWF5IG9ubHkgYmUgdXNlZCBvbiB0YWJsZSBjZWxsIGVsZW1lbnRzICg8dGg+IGFuZCA8dGQ+KSI7cmV0dXJuIGJ9fX0sInNjb3BlLXZhbHVlIjp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlNjb3BlIGF0dHJpYnV0ZSBpcyB1c2VkIGNvcnJlY3RseSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSB2YWx1ZSBvZiB0aGUgc2NvcGUgYXR0cmlidXRlIG1heSBvbmx5IGJlICdyb3cnIG9yICdjb2wnIjtyZXR1cm4gYn19fSwic2tpcC1saW5rIjp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlZhbGlkIHNraXAgbGluayBmb3VuZCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9Ik5vIHZhbGlkIHNraXAgbGluayBmb3VuZCI7cmV0dXJuIGJ9fX0sdGFiaW5kZXg6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhIHRhYmluZGV4IGdyZWF0ZXIgdGhhbiAwIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBoYXMgYSB0YWJpbmRleCBncmVhdGVyIHRoYW4gMCI7cmV0dXJuIGJ9fX0sZGVzY3JpcHRpb246e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSBtdWx0aW1lZGlhIGVsZW1lbnQgaGFzIGFuIGF1ZGlvIGRlc2NyaXB0aW9uIHRyYWNrIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIG11bHRpbWVkaWEgZWxlbWVudCBkb2VzIG5vdCBoYXZlIGFuIGF1ZGlvIGRlc2NyaXB0aW9uIHRyYWNrIjtyZXR1cm4gYn19fX0sZmFpbHVyZVN1bW1hcmllczp7YW55OntmYWlsdXJlTWVzc2FnZTpmdW5jdGlvbihhKXt2YXIgYj0iRml4IGFueSBvZiB0aGUgZm9sbG93aW5nOiIsYz1hO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2Y+ZTspZD1jW2UrPTFdLGIrPSJcbiAgIitkLnNwbGl0KCJcbiIpLmpvaW4oIlxuICAiKTtyZXR1cm4gYn19LG5vbmU6e2ZhaWx1cmVNZXNzYWdlOmZ1bmN0aW9uKGEpe3ZhciBiPSJGaXggYWxsIG9mIHRoZSBmb2xsb3dpbmc6IixjPWE7aWYoYylmb3IodmFyIGQsZT0tMSxmPWMubGVuZ3RoLTE7Zj5lOylkPWNbZSs9MV0sYis9IlxuICAiK2Quc3BsaXQoIlxuIikuam9pbigiXG4gICIpO3JldHVybiBifX19fSxydWxlczpbe2lkOiJhY2Nlc3NrZXlzIixzZWxlY3RvcjoiW2FjY2Vzc2tleV0iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMjExIl0sYWxsOltdLGFueTpbXSxub25lOlsiYWNjZXNza2V5cyJdfSx7aWQ6ImFyZWEtYWx0IixzZWxlY3RvcjoibWFwIGFyZWFbaHJlZl0iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcxMTEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOGEiXSxhbGw6W10sYW55Olsibm9uLWVtcHR5LWFsdCIsImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiXSxub25lOltdfSx7aWQ6ImFyaWEtYWxsb3dlZC1hdHRyIix0YWdzOlsid2NhZzJhIiwid2NhZzQxMSJdLGFsbDpbXSxhbnk6WyJhcmlhLWFsbG93ZWQtYXR0ciJdLG5vbmU6W119LHtpZDoiYXJpYS1yZXF1aXJlZC1hdHRyIixzZWxlY3RvcjoiW3JvbGVdIix0YWdzOlsid2NhZzJhIiwid2NhZzQxMSJdLGFsbDpbXSxhbnk6WyJhcmlhLXJlcXVpcmVkLWF0dHIiXSxub25lOltdfSx7aWQ6ImFyaWEtcmVxdWlyZWQtY2hpbGRyZW4iLHNlbGVjdG9yOiJbcm9sZV0iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnNDExIl0sYWxsOltdLGFueTpbImFyaWEtcmVxdWlyZWQtY2hpbGRyZW4iXSxub25lOltdfSx7aWQ6ImFyaWEtcmVxdWlyZWQtcGFyZW50IixzZWxlY3RvcjoiW3JvbGVdIix0YWdzOlsid2NhZzJhIiwid2NhZzQxMSJdLGFsbDpbXSxhbnk6WyJhcmlhLXJlcXVpcmVkLXBhcmVudCJdLG5vbmU6W119LHtpZDoiYXJpYS1yb2xlcyIsc2VsZWN0b3I6Iltyb2xlXSIsdGFnczpbIndjYWcyYSIsIndjYWc0MTEiXSxhbGw6W10sYW55OltdLG5vbmU6WyJpbnZhbGlkcm9sZSIsImFic3RyYWN0cm9sZSJdfSx7aWQ6ImFyaWEtdmFsaWQtYXR0ci12YWx1ZSIsdGFnczpbIndjYWcyYSIsIndjYWc0MTEiXSxhbGw6W10sYW55Olt7b3B0aW9uczpbXSxpZDoiYXJpYS12YWxpZC1hdHRyLXZhbHVlIn1dLG5vbmU6W119LHtpZDoiYXJpYS12YWxpZC1hdHRyIix0YWdzOlsid2NhZzJhIiwid2NhZzQxMSJdLGFsbDpbXSxhbnk6W3tvcHRpb25zOltdLGlkOiJhcmlhLXZhbGlkLWF0dHIifV0sbm9uZTpbXX0se2lkOiJhdWRpby1jYXB0aW9uIixzZWxlY3RvcjoiYXVkaW8iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcxMjIiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOGEiXSxhbGw6W10sYW55OltdLG5vbmU6WyJjYXB0aW9uIl19LHtpZDoiYmxpbmsiLHNlbGVjdG9yOiJibGluayIsdGFnczpbIndjYWcyYSIsIndjYWcyMjIiXSxhbGw6W10sYW55OltdLG5vbmU6WyJleGlzdHMiXX0se2lkOiJidXR0b24tbmFtZSIsc2VsZWN0b3I6J2J1dHRvbiwgW3JvbGU9ImJ1dHRvbiJdLCBpbnB1dFt0eXBlPSJidXR0b24iXSwgaW5wdXRbdHlwZT0ic3VibWl0Il0sIGlucHV0W3R5cGU9InJlc2V0Il0nLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnNDEyIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDhhIl0sYWxsOltdLGFueTpbIm5vbi1lbXB0eS1pZi1wcmVzZW50Iiwibm9uLWVtcHR5LXZhbHVlIiwiYnV0dG9uLWhhcy12aXNpYmxlLXRleHQiLCJhcmlhLWxhYmVsIiwiYXJpYS1sYWJlbGxlZGJ5Iiwicm9sZS1wcmVzZW50YXRpb24iLCJyb2xlLW5vbmUiXSxub25lOlsiZHVwbGljYXRlLWltZy1sYWJlbCIsImZvY3VzYWJsZS1uby1uYW1lIl19LHtpZDoiYnlwYXNzIixzZWxlY3RvcjoiaHRtbCIscGFnZUxldmVsOiEwLG1hdGNoZXM6ZnVuY3Rpb24oYSl7cmV0dXJuISFhLnF1ZXJ5U2VsZWN0b3IoImFbaHJlZl0iKX0sdGFnczpbIndjYWcyYSIsIndjYWcyNDEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOG8iXSxhbGw6W10sYW55OlsiaW50ZXJuYWwtbGluay1wcmVzZW50IiwiaGVhZGVyLXByZXNlbnQiLCJsYW5kbWFyayJdLG5vbmU6W119LHtpZDoiY2hlY2tib3hncm91cCIsc2VsZWN0b3I6ImlucHV0W3R5cGU9Y2hlY2tib3hdW25hbWVdIix0YWdzOlsid2NhZzJhIiwid2NhZzEzMSJdLGFsbDpbXSxhbnk6WyJncm91cC1sYWJlbGxlZGJ5IiwiZmllbGRzZXQiXSxub25lOltdfSx7aWQ6ImNvbG9yLWNvbnRyYXN0IixzZWxlY3RvcjoiKiIsdGFnczpbIndjYWcyYWEiLCJ3Y2FnMTQzIl0sYWxsOltdLGFueTpbImNvbG9yLWNvbnRyYXN0Il0sbm9uZTpbXX0se2lkOiJkYXRhLXRhYmxlIixzZWxlY3RvcjoidGFibGUiLG1hdGNoZXM6ZnVuY3Rpb24oYSl7cmV0dXJuIFIudGFibGUuaXNEYXRhVGFibGUoYSl9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbImNvbnNpc3RlbnQtY29sdW1ucyJdLG5vbmU6WyJjZWxsLW5vLWhlYWRlciIsImhlYWRlcnMtdmlzaWJsZS10ZXh0IiwiaGVhZGVycy1hdHRyLXJlZmVyZW5jZSIsInRoLXNjb3BlIiwibm8tY2FwdGlvbiIsInRoLWhlYWRlcnMtYXR0ciIsInRoLXNpbmdsZS1yb3ctY29sdW1uIiwic2FtZS1jYXB0aW9uLXN1bW1hcnkiLCJyb3dzcGFuIl19LHtpZDoiZGVmaW5pdGlvbi1saXN0IixzZWxlY3RvcjoiZGwiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbXSxub25lOlsic3RydWN0dXJlZC1kbGl0ZW1zIiwib25seS1kbGl0ZW1zIl19LHtpZDoiZGxpdGVtIixzZWxlY3RvcjoiZGQsIGR0Iix0YWdzOlsid2NhZzJhIiwid2NhZzEzMSJdLGFsbDpbXSxhbnk6WyJkbGl0ZW0iXSxub25lOltdfSx7aWQ6ImRvY3VtZW50LXRpdGxlIixzZWxlY3RvcjoiaHRtbCIsdGFnczpbIndjYWcyYSIsIndjYWcyNDIiXSxhbGw6W10sYW55OlsiZG9jLWhhcy10aXRsZSJdLG5vbmU6W119LHtpZDoiZHVwbGljYXRlLWlkIixzZWxlY3RvcjoiW2lkXSIsdGFnczpbIndjYWcyYSIsIndjYWc0MTEiXSxhbGw6W10sYW55OlsiZHVwbGljYXRlLWlkIl0sbm9uZTpbXX0se2lkOiJlbXB0eS1oZWFkaW5nIixzZWxlY3RvcjonaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgW3JvbGU9ImhlYWRpbmciXScsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiXSxhbGw6W10sYW55OlsiaGFzLXZpc2libGUtdGV4dCIsInJvbGUtcHJlc2VudGF0aW9uIiwicm9sZS1ub25lIl0sbm9uZTpbXX0se2lkOiJmcmFtZS10aXRsZSIsc2VsZWN0b3I6ImZyYW1lLCBpZnJhbWUiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMjQxIl0sYWxsOltdLGFueTpbIm5vbi1lbXB0eS10aXRsZSJdLG5vbmU6WyJ1bmlxdWUtZnJhbWUtdGl0bGUiXX0se2lkOiJoZWFkaW5nLW9yZGVyIixzZWxlY3RvcjoiaDEsaDIsaDMsaDQsaDUsaDYsW3JvbGU9aGVhZGluZ10iLGVuYWJsZWQ6ITEsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55OlsiaGVhZGluZy1vcmRlciJdLG5vbmU6W119LHtpZDoiaHRtbC1sYW5nIixzZWxlY3RvcjoiaHRtbCIsdGFnczpbIndjYWcyYSIsIndjYWczMTEiXSxhbGw6W10sYW55OlsiaGFzLWxhbmciXSxub25lOlt7b3B0aW9uczpbImFhIiwiYWIiLCJhZSIsImFmIiwiYWsiLCJhbSIsImFuIiwiYXIiLCJhcyIsImF2IiwiYXkiLCJheiIsImJhIiwiYmUiLCJiZyIsImJoIiwiYmkiLCJibSIsImJuIiwiYm8iLCJiciIsImJzIiwiY2EiLCJjZSIsImNoIiwiY28iLCJjciIsImNzIiwiY3UiLCJjdiIsImN5IiwiZGEiLCJkZSIsImR2IiwiZHoiLCJlZSIsImVsIiwiZW4iLCJlbyIsImVzIiwiZXQiLCJldSIsImZhIiwiZmYiLCJmaSIsImZqIiwiZm8iLCJmciIsImZ5IiwiZ2EiLCJnZCIsImdsIiwiZ24iLCJndSIsImd2IiwiaGEiLCJoZSIsImhpIiwiaG8iLCJociIsImh0IiwiaHUiLCJoeSIsImh6IiwiaWEiLCJpZCIsImllIiwiaWciLCJpaSIsImlrIiwiaW4iLCJpbyIsImlzIiwiaXQiLCJpdSIsIml3IiwiamEiLCJqaSIsImp2IiwianciLCJrYSIsImtnIiwia2kiLCJraiIsImtrIiwia2wiLCJrbSIsImtuIiwia28iLCJrciIsImtzIiwia3UiLCJrdiIsImt3Iiwia3kiLCJsYSIsImxiIiwibGciLCJsaSIsImxuIiwibG8iLCJsdCIsImx1IiwibHYiLCJtZyIsIm1oIiwibWkiLCJtayIsIm1sIiwibW4iLCJtbyIsIm1yIiwibXMiLCJtdCIsIm15IiwibmEiLCJuYiIsIm5kIiwibmUiLCJuZyIsIm5sIiwibm4iLCJubyIsIm5yIiwibnYiLCJueSIsIm9jIiwib2oiLCJvbSIsIm9yIiwib3MiLCJwYSIsInBpIiwicGwiLCJwcyIsInB0IiwicXUiLCJybSIsInJuIiwicm8iLCJydSIsInJ3Iiwic2EiLCJzYyIsInNkIiwic2UiLCJzZyIsInNoIiwic2kiLCJzayIsInNsIiwic20iLCJzbiIsInNvIiwic3EiLCJzciIsInNzIiwic3QiLCJzdSIsInN2Iiwic3ciLCJ0YSIsInRlIiwidGciLCJ0aCIsInRpIiwidGsiLCJ0bCIsInRuIiwidG8iLCJ0ciIsInRzIiwidHQiLCJ0dyIsInR5IiwidWciLCJ1ayIsInVyIiwidXoiLCJ2ZSIsInZpIiwidm8iLCJ3YSIsIndvIiwieGgiLCJ5aSIsInlvIiwiemEiLCJ6aCIsInp1Il0saWQ6InZhbGlkLWxhbmcifV19LHtpZDoiaW1hZ2UtYWx0IixzZWxlY3RvcjoiaW1nIix0YWdzOlsid2NhZzJhIiwid2NhZzExMSIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4YSJdLGFsbDpbXSxhbnk6WyJoYXMtYWx0IiwiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsIm5vbi1lbXB0eS10aXRsZSIsInJvbGUtcHJlc2VudGF0aW9uIiwicm9sZS1ub25lIl0sbm9uZTpbXX0se2lkOiJpbnB1dC1pbWFnZS1hbHQiLHNlbGVjdG9yOidpbnB1dFt0eXBlPSJpbWFnZSJdJyx0YWdzOlsid2NhZzJhIiwid2NhZzExMSIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4YSJdLGFsbDpbXSxhbnk6WyJub24tZW1wdHktYWx0IiwiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSJdLG5vbmU6W119LHtpZDoibGFiZWwtdGl0bGUtb25seSIsc2VsZWN0b3I6ImlucHV0Om5vdChbdHlwZT0naGlkZGVuJ10pOm5vdChbdHlwZT0naW1hZ2UnXSk6bm90KFt0eXBlPSdidXR0b24nXSk6bm90KFt0eXBlPSdzdWJtaXQnXSk6bm90KFt0eXBlPSdyZXNldCddKSwgc2VsZWN0LCB0ZXh0YXJlYSIsZW5hYmxlZDohMSx0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6W10sbm9uZTpbInRpdGxlLW9ubHkiXX0se2lkOiJsYWJlbCIsc2VsZWN0b3I6ImlucHV0Om5vdChbdHlwZT0naGlkZGVuJ10pOm5vdChbdHlwZT0naW1hZ2UnXSk6bm90KFt0eXBlPSdidXR0b24nXSk6bm90KFt0eXBlPSdzdWJtaXQnXSk6bm90KFt0eXBlPSdyZXNldCddKSwgc2VsZWN0LCB0ZXh0YXJlYSIsdGFnczpbIndjYWcyYSIsIndjYWczMzIiLCJ3Y2FnMTMxIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDhuIl0sYWxsOltdLGFueTpbImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJpbXBsaWNpdC1sYWJlbCIsImV4cGxpY2l0LWxhYmVsIiwibm9uLWVtcHR5LXRpdGxlIl0sbm9uZTpbImhlbHAtc2FtZS1hcy1sYWJlbCIsIm11bHRpcGxlLWxhYmVsIl19LHtpZDoibGF5b3V0LXRhYmxlIixzZWxlY3RvcjoidGFibGUiLG1hdGNoZXM6ZnVuY3Rpb24oYSl7cmV0dXJuIVIudGFibGUuaXNEYXRhVGFibGUoYSl9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbXSxub25lOlsiaGFzLXRoIiwiaGFzLWNhcHRpb24iLCJoYXMtc3VtbWFyeSJdfSx7aWQ6ImxpbmstbmFtZSIsc2VsZWN0b3I6J2FbaHJlZl06bm90KFtyb2xlPSJidXR0b24iXSksIFtyb2xlPWxpbmtdW2hyZWZdJyx0YWdzOlsid2NhZzJhIiwid2NhZzExMSIsIndjYWc0MTIiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOGEiXSxhbGw6W10sYW55OlsiaGFzLXZpc2libGUtdGV4dCIsImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJyb2xlLXByZXNlbnRhdGlvbiIsInJvbGUtbm9uZSJdLG5vbmU6WyJkdXBsaWNhdGUtaW1nLWxhYmVsIiwiZm9jdXNhYmxlLW5vLW5hbWUiXX0se2lkOiJsaXN0IixzZWxlY3RvcjoidWwsIG9sIix0YWdzOlsid2NhZzJhIiwid2NhZzEzMSJdLGFsbDpbXSxhbnk6W10sbm9uZTpbIm9ubHktbGlzdGl0ZW1zIl19LHtpZDoibGlzdGl0ZW0iLHNlbGVjdG9yOiJsaSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiXSxhbGw6W10sYW55OlsibGlzdGl0ZW0iXSxub25lOltdfSx7aWQ6Im1hcnF1ZWUiLHNlbGVjdG9yOiJtYXJxdWVlIix0YWdzOlsid2NhZzJhIiwid2NhZzIyMiIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4aiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImV4aXN0cyJdfSx7aWQ6Im1ldGEtcmVmcmVzaCIsc2VsZWN0b3I6J21ldGFbaHR0cC1lcXVpdj0icmVmcmVzaCJdJyxleGNsdWRlSGlkZGVuOiExLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMmFhYSIsIndjYWcyMjEiLCJ3Y2FnMjI0Iiwid2NhZzMyNSJdLGFsbDpbXSxhbnk6WyJtZXRhLXJlZnJlc2giXSxub25lOltdfSx7aWQ6Im1ldGEtdmlld3BvcnQiLHNlbGVjdG9yOidtZXRhW25hbWU9InZpZXdwb3J0Il0nLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYWEiLCJ3Y2FnMTQ0Il0sYWxsOltdLGFueTpbIm1ldGEtdmlld3BvcnQiXSxub25lOltdfSx7aWQ6Im9iamVjdC1hbHQiLHNlbGVjdG9yOiJvYmplY3QiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTExIl0sYWxsOltdLGFueTpbImhhcy12aXNpYmxlLXRleHQiXSxub25lOltdfSx7aWQ6InJhZGlvZ3JvdXAiLHNlbGVjdG9yOiJpbnB1dFt0eXBlPXJhZGlvXVtuYW1lXSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiXSxhbGw6W10sYW55OlsiZ3JvdXAtbGFiZWxsZWRieSIsImZpZWxkc2V0Il0sbm9uZTpbXX0se2lkOiJyZWdpb24iLHNlbGVjdG9yOiJodG1sIixwYWdlTGV2ZWw6ITAsZW5hYmxlZDohMSx0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6WyJyZWdpb24iXSxub25lOltdfSx7aWQ6InNjb3BlIixzZWxlY3RvcjoiW3Njb3BlXSIsZW5hYmxlZDohMSx0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6WyJodG1sNS1zY29wZSIsImh0bWw0LXNjb3BlIl0sbm9uZTpbInNjb3BlLXZhbHVlIl19LHtpZDoic2VydmVyLXNpZGUtaW1hZ2UtbWFwIixzZWxlY3RvcjoiaW1nW2lzbWFwXSIsdGFnczpbIndjYWcyYSIsIndjYWcyMTEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOGYiXSxhbGw6W10sYW55OltdLG5vbmU6WyJleGlzdHMiXX0se2lkOiJza2lwLWxpbmsiLHNlbGVjdG9yOiJhW2hyZWZdIixwYWdlTGV2ZWw6ITAsZW5hYmxlZDohMSx0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6WyJza2lwLWxpbmsiXSxub25lOltdfSx7aWQ6InRhYmluZGV4IixzZWxlY3RvcjoiW3RhYmluZGV4XSIsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55OlsidGFiaW5kZXgiXSxub25lOltdfSx7aWQ6InZhbGlkLWxhbmciLHNlbGVjdG9yOiJbbGFuZ106bm90KGh0bWwpLCBbeG1sXFw6bGFuZ106bm90KGh0bWwpIix0YWdzOlsid2NhZzJhYSIsIndjYWczMTIiXSxhbGw6W10sYW55OltdLG5vbmU6W3tvcHRpb25zOlsiYWEiLCJhYiIsImFlIiwiYWYiLCJhayIsImFtIiwiYW4iLCJhciIsImFzIiwiYXYiLCJheSIsImF6IiwiYmEiLCJiZSIsImJnIiwiYmgiLCJiaSIsImJtIiwiYm4iLCJibyIsImJyIiwiYnMiLCJjYSIsImNlIiwiY2giLCJjbyIsImNyIiwiY3MiLCJjdSIsImN2IiwiY3kiLCJkYSIsImRlIiwiZHYiLCJkeiIsImVlIiwiZWwiLCJlbiIsImVvIiwiZXMiLCJldCIsImV1IiwiZmEiLCJmZiIsImZpIiwiZmoiLCJmbyIsImZyIiwiZnkiLCJnYSIsImdkIiwiZ2wiLCJnbiIsImd1IiwiZ3YiLCJoYSIsImhlIiwiaGkiLCJobyIsImhyIiwiaHQiLCJodSIsImh5IiwiaHoiLCJpYSIsImlkIiwiaWUiLCJpZyIsImlpIiwiaWsiLCJpbiIsImlvIiwiaXMiLCJpdCIsIml1IiwiaXciLCJqYSIsImppIiwianYiLCJqdyIsImthIiwia2ciLCJraSIsImtqIiwia2siLCJrbCIsImttIiwia24iLCJrbyIsImtyIiwia3MiLCJrdSIsImt2Iiwia3ciLCJreSIsImxhIiwibGIiLCJsZyIsImxpIiwibG4iLCJsbyIsImx0IiwibHUiLCJsdiIsIm1nIiwibWgiLCJtaSIsIm1rIiwibWwiLCJtbiIsIm1vIiwibXIiLCJtcyIsIm10IiwibXkiLCJuYSIsIm5iIiwibmQiLCJuZSIsIm5nIiwibmwiLCJubiIsIm5vIiwibnIiLCJudiIsIm55Iiwib2MiLCJvaiIsIm9tIiwib3IiLCJvcyIsInBhIiwicGkiLCJwbCIsInBzIiwicHQiLCJxdSIsInJtIiwicm4iLCJybyIsInJ1IiwicnciLCJzYSIsInNjIiwic2QiLCJzZSIsInNnIiwic2giLCJzaSIsInNrIiwic2wiLCJzbSIsInNuIiwic28iLCJzcSIsInNyIiwic3MiLCJzdCIsInN1Iiwic3YiLCJzdyIsInRhIiwidGUiLCJ0ZyIsInRoIiwidGkiLCJ0ayIsInRsIiwidG4iLCJ0byIsInRyIiwidHMiLCJ0dCIsInR3IiwidHkiLCJ1ZyIsInVrIiwidXIiLCJ1eiIsInZlIiwidmkiLCJ2byIsIndhIiwid28iLCJ4aCIsInlpIiwieW8iLCJ6YSIsInpoIiwienUiXSxpZDoidmFsaWQtbGFuZyJ9XX0se2lkOiJ2aWRlby1jYXB0aW9uIixzZWxlY3RvcjoidmlkZW8iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTIyIiwid2NhZzEyMyIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4YSJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImNhcHRpb24iXX0se2lkOiJ2aWRlby1kZXNjcmlwdGlvbiIsc2VsZWN0b3I6InZpZGVvIix0YWdzOlsid2NhZzJhYSIsIndjYWcxMjUiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOGEiXSxhbGw6W10sYW55OltdLG5vbmU6WyJkZXNjcmlwdGlvbiJdfV0sY2hlY2tzOlt7aWQ6ImFic3RyYWN0cm9sZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4iYWJzdHJhY3QiPT09Ui5hcmlhLmdldFJvbGVUeXBlKGEuZ2V0QXR0cmlidXRlKCJyb2xlIikpfX0se2lkOiJhcmlhLWFsbG93ZWQtYXR0ciIsbWF0Y2hlczpmdW5jdGlvbihhKXt2YXIgYj1hLmdldEF0dHJpYnV0ZSgicm9sZSIpO2J8fChiPVIuYXJpYS5pbXBsaWNpdFJvbGUoYSkpO3ZhciBjPVIuYXJpYS5hbGxvd2VkQXR0cihiKTtpZihiJiZjKXt2YXIgZD0vXmFyaWEtLztpZihhLmhhc0F0dHJpYnV0ZXMoKSlmb3IodmFyIGU9YS5hdHRyaWJ1dGVzLGY9MCxnPWUubGVuZ3RoO2c+ZjtmKyspaWYoZC50ZXN0KGVbZl0ubm9kZU5hbWUpKXJldHVybiEwfXJldHVybiExfSxldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjLGQsZSxmPVtdLGc9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKSxoPWEuYXR0cmlidXRlcztpZihnfHwoZz1SLmFyaWEuaW1wbGljaXRSb2xlKGEpKSxlPVIuYXJpYS5hbGxvd2VkQXR0cihnKSxnJiZlKWZvcih2YXIgaT0wLGo9aC5sZW5ndGg7aj5pO2krKyljPWhbaV0sZD1jLm5vZGVOYW1lLFIuYXJpYS52YWxpZGF0ZUF0dHIoZCkmJi0xPT09ZS5pbmRleE9mKGQpJiZmLnB1c2goZCsnPSInK2Mubm9kZVZhbHVlKyciJyk7cmV0dXJuIGYubGVuZ3RoPyh0aGlzLmRhdGEoZiksITEpOiEwfX0se2lkOiJpbnZhbGlkcm9sZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4hUi5hcmlhLmlzVmFsaWRSb2xlKGEuZ2V0QXR0cmlidXRlKCJyb2xlIikpfX0se2lkOiJhcmlhLXJlcXVpcmVkLWF0dHIiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9W107aWYoYS5oYXNBdHRyaWJ1dGVzKCkpe3ZhciBkLGU9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKSxmPVIuYXJpYS5yZXF1aXJlZEF0dHIoZSk7aWYoZSYmZilmb3IodmFyIGc9MCxoPWYubGVuZ3RoO2g+ZztnKyspZD1mW2ddLGEuZ2V0QXR0cmlidXRlKGQpfHxjLnB1c2goZCl9cmV0dXJuIGMubGVuZ3RoPyh0aGlzLmRhdGEoYyksITEpOiEwfX0se2lkOiJhcmlhLXJlcXVpcmVkLWNoaWxkcmVuIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Z1bmN0aW9uIGMoYSxiLGMpe2lmKG51bGw9PT1hKXJldHVybiExO3ZhciBkPWcoYiksZT1bJ1tyb2xlPSInK2IrJyJdJ107cmV0dXJuIGQmJihlPWUuY29uY2F0KGQpKSxlPWUuam9pbigiLCIpLGM/aChhLGUpfHwhIWEucXVlcnlTZWxlY3RvcihlKTohIWEucXVlcnlTZWxlY3RvcihlKX1mdW5jdGlvbiBkKGEsYil7dmFyIGQsZTtmb3IoZD0wLGU9YS5sZW5ndGg7ZT5kO2QrKylpZihudWxsIT09YVtkXSYmYyhhW2RdLGIsITApKXJldHVybiEwO3JldHVybiExfWZ1bmN0aW9uIGUoYSxiLGUpe3ZhciBmLGc9Yi5sZW5ndGgsaD1bXSxqPWkoYSwiYXJpYS1vd25zIik7Zm9yKGY9MDtnPmY7ZisrKXt2YXIgaz1iW2ZdO2lmKGMoYSxrKXx8ZChqLGspKXtpZighZSlyZXR1cm4gbnVsbH1lbHNlIGUmJmgucHVzaChrKX1yZXR1cm4gaC5sZW5ndGg/aDohZSYmYi5sZW5ndGg/YjpudWxsfXZhciBmPVIuYXJpYS5yZXF1aXJlZE93bmVkLGc9Ui5hcmlhLmltcGxpY2l0Tm9kZXMsaD1SLnV0aWxzLm1hdGNoZXNTZWxlY3RvcixpPVIuZG9tLmlkcmVmcyxqPWEuZ2V0QXR0cmlidXRlKCJyb2xlIiksaz1mKGopO2lmKCFrKXJldHVybiEwO3ZhciBsPSExLG09ay5vbmU7aWYoIW0pe3ZhciBsPSEwO209ay5hbGx9dmFyIG49ZShhLG0sbCk7cmV0dXJuIG4/KHRoaXMuZGF0YShuKSwhMSk6ITB9fSx7aWQ6ImFyaWEtcmVxdWlyZWQtcGFyZW50IixldmFsdWF0ZTpmdW5jdGlvbihhLGMpe2Z1bmN0aW9uIGQoYSl7dmFyIGI9Ui5hcmlhLmltcGxpY2l0Tm9kZXMoYSl8fFtdO3JldHVybiBiLmNvbmNhdCgnW3JvbGU9IicrYSsnIl0nKS5qb2luKCIsIil9ZnVuY3Rpb24gZShhLGIsYyl7dmFyIGUsZixnPWEuZ2V0QXR0cmlidXRlKCJyb2xlIiksaD1bXTtpZihifHwoYj1SLmFyaWEucmVxdWlyZWRDb250ZXh0KGcpKSwhYilyZXR1cm4gbnVsbDtmb3IoZT0wLGY9Yi5sZW5ndGg7Zj5lO2UrKyl7aWYoYyYmUi51dGlscy5tYXRjaGVzU2VsZWN0b3IoYSxkKGJbZV0pKSlyZXR1cm4gbnVsbDtpZihSLmRvbS5maW5kVXAoYSxkKGJbZV0pKSlyZXR1cm4gbnVsbDtoLnB1c2goYltlXSl9cmV0dXJuIGh9ZnVuY3Rpb24gZihhKXtmb3IodmFyIGM9W10sZD1udWxsO2E7KWEuaWQmJihkPWIucXVlcnlTZWxlY3RvcigiW2FyaWEtb3duc349IitSLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKyJdIiksZCYmYy5wdXNoKGQpKSxhPWEucGFyZW50Tm9kZTtyZXR1cm4gYy5sZW5ndGg/YzpudWxsfXZhciBnPWUoYSk7aWYoIWcpcmV0dXJuITA7dmFyIGg9ZihhKTtpZihoKWZvcih2YXIgaT0wLGo9aC5sZW5ndGg7aj5pO2krKylpZihnPWUoaFtpXSxnLCEwKSwhZylyZXR1cm4hMDtyZXR1cm4gdGhpcy5kYXRhKGcpLCExfX0se2lkOiJhcmlhLXZhbGlkLWF0dHItdmFsdWUiLG1hdGNoZXM6ZnVuY3Rpb24oYSl7dmFyIGI9L15hcmlhLS87aWYoYS5oYXNBdHRyaWJ1dGVzKCkpZm9yKHZhciBjPWEuYXR0cmlidXRlcyxkPTAsZT1jLmxlbmd0aDtlPmQ7ZCsrKWlmKGIudGVzdChjW2RdLm5vZGVOYW1lKSlyZXR1cm4hMDtyZXR1cm4hMX0sZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtiPUFycmF5LmlzQXJyYXkoYik/YjpbXTtmb3IodmFyIGMsZCxlPVtdLGY9L15hcmlhLS8sZz1hLmF0dHJpYnV0ZXMsaD0wLGk9Zy5sZW5ndGg7aT5oO2grKyljPWdbaF0sZD1jLm5vZGVOYW1lLC0xPT09Yi5pbmRleE9mKGQpJiZmLnRlc3QoZCkmJiFSLmFyaWEudmFsaWRhdGVBdHRyVmFsdWUoYSxkKSYmZS5wdXNoKGQrJz0iJytjLm5vZGVWYWx1ZSsnIicpO3JldHVybiBlLmxlbmd0aD8odGhpcy5kYXRhKGUpLCExKTohMH0sb3B0aW9uczpbXX0se2lkOiJhcmlhLXZhbGlkLWF0dHIiLG1hdGNoZXM6ZnVuY3Rpb24oYSl7dmFyIGI9L15hcmlhLS87aWYoYS5oYXNBdHRyaWJ1dGVzKCkpZm9yKHZhciBjPWEuYXR0cmlidXRlcyxkPTAsZT1jLmxlbmd0aDtlPmQ7ZCsrKWlmKGIudGVzdChjW2RdLm5vZGVOYW1lKSlyZXR1cm4hMDtyZXR1cm4hMX0sZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtiPUFycmF5LmlzQXJyYXkoYik/YjpbXTtmb3IodmFyIGMsZD1bXSxlPS9eYXJpYS0vLGY9YS5hdHRyaWJ1dGVzLGc9MCxoPWYubGVuZ3RoO2g+ZztnKyspYz1mW2ddLm5vZGVOYW1lLC0xPT09Yi5pbmRleE9mKGMpJiZlLnRlc3QoYykmJiFSLmFyaWEudmFsaWRhdGVBdHRyKGMpJiZkLnB1c2goYyk7cmV0dXJuIGQubGVuZ3RoPyh0aGlzLmRhdGEoZCksITEpOiEwfSxvcHRpb25zOltdfSx7aWQ6ImNvbG9yLWNvbnRyYXN0IixtYXRjaGVzOmZ1bmN0aW9uKGEpe3ZhciBjPWEubm9kZU5hbWUsZD1hLnR5cGUsZT1iO2lmKCJJTlBVVCI9PT1jKXJldHVybi0xPT09WyJoaWRkZW4iLCJyYW5nZSIsImNvbG9yIiwiY2hlY2tib3giLCJyYWRpbyIsImltYWdlIl0uaW5kZXhPZihkKSYmIWEuZGlzYWJsZWQ7aWYoIlNFTEVDVCI9PT1jKXJldHVybiEhYS5vcHRpb25zLmxlbmd0aCYmIWEuZGlzYWJsZWQ7aWYoIlRFWFRBUkVBIj09PWMpcmV0dXJuIWEuZGlzYWJsZWQ7aWYoIk9QVElPTiI9PT1jKXJldHVybiExO2lmKCJCVVRUT04iPT09YyYmYS5kaXNhYmxlZClyZXR1cm4hMTtpZigiTEFCRUwiPT09Yyl7dmFyIGY9YS5odG1sRm9yJiZlLmdldEVsZW1lbnRCeUlkKGEuaHRtbEZvcik7aWYoZiYmZi5kaXNhYmxlZClyZXR1cm4hMTt2YXIgZj1hLnF1ZXJ5U2VsZWN0b3IoJ2lucHV0Om5vdChbdHlwZT0iaGlkZGVuIl0pOm5vdChbdHlwZT0iaW1hZ2UiXSk6bm90KFt0eXBlPSJidXR0b24iXSk6bm90KFt0eXBlPSJzdWJtaXQiXSk6bm90KFt0eXBlPSJyZXNldCJdKSwgc2VsZWN0LCB0ZXh0YXJlYScpO2lmKGYmJmYuZGlzYWJsZWQpcmV0dXJuITF9aWYoYS5pZCl7dmFyIGY9ZS5xdWVyeVNlbGVjdG9yKCJbYXJpYS1sYWJlbGxlZGJ5fj0iK1IudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS5pZCkrIl0iKTtpZihmJiZmLmRpc2FibGVkKXJldHVybiExfWlmKCIiPT09Ui50ZXh0LnZpc2libGUoYSwhMSwhMCkpcmV0dXJuITE7dmFyIGcsaCxpPWIuY3JlYXRlUmFuZ2UoKSxqPWEuY2hpbGROb2RlcyxrPWoubGVuZ3RoO2ZvcihoPTA7az5oO2grKylnPWpbaF0sMz09PWcubm9kZVR5cGUmJiIiIT09Ui50ZXh0LnNhbml0aXplKGcubm9kZVZhbHVlKSYmaS5zZWxlY3ROb2RlQ29udGVudHMoZyk7dmFyIGw9aS5nZXRDbGllbnRSZWN0cygpO2ZvcihrPWwubGVuZ3RoLGg9MDtrPmg7aCsrKWlmKFIuZG9tLnZpc3VhbGx5T3ZlcmxhcHMobFtoXSxhKSlyZXR1cm4hMDtyZXR1cm4hMX0sZXZhbHVhdGU6ZnVuY3Rpb24oYixjKXt2YXIgZD1bXSxlPVIuY29sb3IuZ2V0QmFja2dyb3VuZENvbG9yKGIsZCksZj1SLmNvbG9yLmdldEZvcmVncm91bmRDb2xvcihiKTtpZihudWxsPT09Znx8bnVsbD09PWUpcmV0dXJuITA7dmFyIGc9YS5nZXRDb21wdXRlZFN0eWxlKGIpLGg9cGFyc2VGbG9hdChnLmdldFByb3BlcnR5VmFsdWUoImZvbnQtc2l6ZSIpKSxpPWcuZ2V0UHJvcGVydHlWYWx1ZSgiZm9udC13ZWlnaHQiKSxqPS0xIT09WyJib2xkIiwiYm9sZGVyIiwiNjAwIiwiNzAwIiwiODAwIiwiOTAwIl0uaW5kZXhPZihpKSxrPVIuY29sb3IuaGFzVmFsaWRDb250cmFzdFJhdGlvKGUsZixoLGopO3JldHVybiB0aGlzLmRhdGEoe2ZnQ29sb3I6Zi50b0hleFN0cmluZygpLGJnQ29sb3I6ZS50b0hleFN0cmluZygpLGNvbnRyYXN0UmF0aW86ay5jb250cmFzdFJhdGlvLnRvRml4ZWQoMiksZm9udFNpemU6KDcyKmgvOTYpLnRvRml4ZWQoMSkrInB0Iixmb250V2VpZ2h0Omo/ImJvbGQiOiJub3JtYWwifSksay5pc1ZhbGlkfHx0aGlzLnJlbGF0ZWROb2RlcyhkKSxrLmlzVmFsaWR9fSx7aWQ6ImZpZWxkc2V0IixldmFsdWF0ZTpmdW5jdGlvbihhLGMpe2Z1bmN0aW9uIGQoYSxiKXtyZXR1cm4gUi51dGlscy50b0FycmF5KGEucXVlcnlTZWxlY3RvckFsbCgnc2VsZWN0LHRleHRhcmVhLGJ1dHRvbixpbnB1dDpub3QoW25hbWU9IicrYisnIl0pOm5vdChbdHlwZT0iaGlkZGVuIl0pJykpfWZ1bmN0aW9uIGUoYSxiKXt2YXIgYz1hLmZpcnN0RWxlbWVudENoaWxkO2lmKCFjfHwiTEVHRU5EIiE9PWMubm9kZU5hbWUpcmV0dXJuIGoucmVsYXRlZE5vZGVzKFthXSksaT0ibm8tbGVnZW5kIiwhMTtpZighUi50ZXh0LmFjY2Vzc2libGVUZXh0KGMpKXJldHVybiBqLnJlbGF0ZWROb2RlcyhbY10pLGk9ImVtcHR5LWxlZ2VuZCIsITE7dmFyIGU9ZChhLGIpO3JldHVybiBlLmxlbmd0aD8oai5yZWxhdGVkTm9kZXMoZSksaT0ibWl4ZWQtaW5wdXRzIiwhMSk6ITB9ZnVuY3Rpb24gZihhLGIpe3ZhciBjPVIuZG9tLmlkcmVmcyhhLCJhcmlhLWxhYmVsbGVkYnkiKS5zb21lKGZ1bmN0aW9uKGEpe3JldHVybiBhJiZSLnRleHQuYWNjZXNzaWJsZVRleHQoYSl9KSxlPWEuZ2V0QXR0cmlidXRlKCJhcmlhLWxhYmVsIik7aWYoIShjfHxlJiZSLnRleHQuc2FuaXRpemUoZSkpKXJldHVybiBqLnJlbGF0ZWROb2RlcyhhKSxpPSJuby1ncm91cC1sYWJlbCIsITE7dmFyIGY9ZChhLGIpO3JldHVybiBmLmxlbmd0aD8oai5yZWxhdGVkTm9kZXMoZiksaT0iZ3JvdXAtbWl4ZWQtaW5wdXRzIiwhMSk6ITB9ZnVuY3Rpb24gZyhhLGIpe3JldHVybiBSLnV0aWxzLnRvQXJyYXkoYSkuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBhIT09Yn0pfWZ1bmN0aW9uIGgoYyl7dmFyIGQ9Ui51dGlscy5lc2NhcGVTZWxlY3RvcihhLm5hbWUpLGg9Yi5xdWVyeVNlbGVjdG9yQWxsKCdpbnB1dFt0eXBlPSInK1IudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS50eXBlKSsnIl1bbmFtZT0iJytkKyciXScpO2lmKGgubGVuZ3RoPDIpcmV0dXJuITA7dmFyIGs9Ui5kb20uZmluZFVwKGMsImZpZWxkc2V0IiksbD1SLmRvbS5maW5kVXAoYywnW3JvbGU9Imdyb3VwIl0nKygicmFkaW8iPT09YS50eXBlPycsW3JvbGU9InJhZGlvZ3JvdXAiXSc6IiIpKTtyZXR1cm4gbHx8az9rP2UoayxkKTpmKGwsZCk6KGk9Im5vLWdyb3VwIixqLnJlbGF0ZWROb2RlcyhnKGgsYykpLCExKX12YXIgaSxqPXRoaXMsaz17bmFtZTphLmdldEF0dHJpYnV0ZSgibmFtZSIpLHR5cGU6YS5nZXRBdHRyaWJ1dGUoInR5cGUiKX0sbD1oKGEpO3JldHVybiBsfHwoay5mYWlsdXJlQ29kZT1pKSx0aGlzLmRhdGEoayksbH0sYWZ0ZXI6ZnVuY3Rpb24oYSxiKXt2YXIgYz17fTtyZXR1cm4gYS5maWx0ZXIoZnVuY3Rpb24oYSl7aWYoYS5yZXN1bHQpcmV0dXJuITA7dmFyIGI9YS5kYXRhO2lmKGIpe2lmKGNbYi50eXBlXT1jW2IudHlwZV18fHt9LCFjW2IudHlwZV1bYi5uYW1lXSlyZXR1cm4gY1tiLnR5cGVdW2IubmFtZV09W2JdLCEwO3ZhciBkPWNbYi50eXBlXVtiLm5hbWVdLnNvbWUoZnVuY3Rpb24oYSl7cmV0dXJuIGEuZmFpbHVyZUNvZGU9PT1iLmZhaWx1cmVDb2RlfSk7cmV0dXJuIGR8fGNbYi50eXBlXVtiLm5hbWVdLnB1c2goYiksIWR9cmV0dXJuITF9KX19LHtpZDoiZ3JvdXAtbGFiZWxsZWRieSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxjKXt0aGlzLmRhdGEoe25hbWU6YS5nZXRBdHRyaWJ1dGUoIm5hbWUiKSx0eXBlOmEuZ2V0QXR0cmlidXRlKCJ0eXBlIil9KTt2YXIgZD1iLnF1ZXJ5U2VsZWN0b3JBbGwoJ2lucHV0W3R5cGU9IicrUi51dGlscy5lc2NhcGVTZWxlY3RvcihhLnR5cGUpKyciXVtuYW1lPSInK1IudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS5uYW1lKSsnIl0nKTtyZXR1cm4gZC5sZW5ndGg8PTE/ITA6MCE9PVtdLm1hcC5jYWxsKGQsZnVuY3Rpb24oYSl7dmFyIGI9YS5nZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWxsZWRieSIpO3JldHVybiBiP2Iuc3BsaXQoL1xzKy8pOltdfSkucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybi0xIT09Yi5pbmRleE9mKGEpfSl9KS5maWx0ZXIoZnVuY3Rpb24oYSl7CnZhciBjPWIuZ2V0RWxlbWVudEJ5SWQoYSk7cmV0dXJuIGMmJlIudGV4dC5hY2Nlc3NpYmxlVGV4dChjKX0pLmxlbmd0aH0sYWZ0ZXI6ZnVuY3Rpb24oYSxiKXt2YXIgYz17fTtyZXR1cm4gYS5maWx0ZXIoZnVuY3Rpb24oYSl7dmFyIGI9YS5kYXRhO3JldHVybiBiJiYoY1tiLnR5cGVdPWNbYi50eXBlXXx8e30sIWNbYi50eXBlXVtiLm5hbWVdKT8oY1tiLnR5cGVdW2IubmFtZV09ITAsITApOiExfSl9fSx7aWQ6ImFjY2Vzc2tleXMiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIHRoaXMuZGF0YShhLmdldEF0dHJpYnV0ZSgiYWNjZXNza2V5IikpLHRoaXMucmVsYXRlZE5vZGVzKFthXSksITB9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9e307cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBjW2EuZGF0YV0/KGNbYS5kYXRhXS5yZWxhdGVkTm9kZXMucHVzaChhLnJlbGF0ZWROb2Rlc1swXSksITEpOihjW2EuZGF0YV09YSxhLnJlbGF0ZWROb2Rlcz1bXSwhMCl9KS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGEucmVzdWx0PSEhYS5yZWxhdGVkTm9kZXMubGVuZ3RoLGF9KX19LHtpZDoiZm9jdXNhYmxlLW5vLW5hbWUiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YS5nZXRBdHRyaWJ1dGUoInRhYmluZGV4IiksZD1SLmRvbS5pc0ZvY3VzYWJsZShhKSYmYz4tMTtyZXR1cm4gZD8hUi50ZXh0LmFjY2Vzc2libGVUZXh0KGEpOiExfX0se2lkOiJ0YWJpbmRleCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4gYS50YWJJbmRleDw9MH19LHtpZDoiZHVwbGljYXRlLWltZy1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmb3IodmFyIGM9YS5xdWVyeVNlbGVjdG9yQWxsKCJpbWciKSxkPVIudGV4dC52aXNpYmxlKGEsITApLGU9MCxmPWMubGVuZ3RoO2Y+ZTtlKyspe3ZhciBnPVIudGV4dC5hY2Nlc3NpYmxlVGV4dChjW2VdKTtpZihnPT09ZCYmIiIhPT1kKXJldHVybiEwfXJldHVybiExfSxlbmFibGVkOiExfSx7aWQ6ImV4cGxpY2l0LWxhYmVsIixldmFsdWF0ZTpmdW5jdGlvbihhLGMpe3ZhciBkPWIucXVlcnlTZWxlY3RvcignbGFiZWxbZm9yPSInK1IudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS5pZCkrJyJdJyk7cmV0dXJuIGQ/ISFSLnRleHQuYWNjZXNzaWJsZVRleHQoZCk6ITF9LHNlbGVjdG9yOiJbaWRdIn0se2lkOiJoZWxwLXNhbWUtYXMtbGFiZWwiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9Ui50ZXh0LmxhYmVsKGEpLGQ9YS5nZXRBdHRyaWJ1dGUoInRpdGxlIik7aWYoIWMpcmV0dXJuITE7aWYoIWQmJihkPSIiLGEuZ2V0QXR0cmlidXRlKCJhcmlhLWRlc2NyaWJlZGJ5IikpKXt2YXIgZT1SLmRvbS5pZHJlZnMoYSwiYXJpYS1kZXNjcmliZWRieSIpO2Q9ZS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGE/Ui50ZXh0LmFjY2Vzc2libGVUZXh0KGEpOiIifSkuam9pbigiIil9cmV0dXJuIFIudGV4dC5zYW5pdGl6ZShkKT09PVIudGV4dC5zYW5pdGl6ZShjKX0sZW5hYmxlZDohMX0se2lkOiJpbXBsaWNpdC1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1SLmRvbS5maW5kVXAoYSwibGFiZWwiKTtyZXR1cm4gYz8hIVIudGV4dC5hY2Nlc3NpYmxlVGV4dChjKTohMX19LHtpZDoibXVsdGlwbGUtbGFiZWwiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYyl7Zm9yKHZhciBkPVtdLnNsaWNlLmNhbGwoYi5xdWVyeVNlbGVjdG9yQWxsKCdsYWJlbFtmb3I9IicrUi51dGlscy5lc2NhcGVTZWxlY3RvcihhLmlkKSsnIl0nKSksZT1hLnBhcmVudE5vZGU7ZTspIkxBQkVMIj09PWUudGFnTmFtZSYmLTE9PT1kLmluZGV4T2YoZSkmJmQucHVzaChlKSxlPWUucGFyZW50Tm9kZTtyZXR1cm4gdGhpcy5yZWxhdGVkTm9kZXMoZCksZC5sZW5ndGg+MX19LHtpZDoidGl0bGUtb25seSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1SLnRleHQubGFiZWwoYSk7cmV0dXJuIShjfHwhYS5nZXRBdHRyaWJ1dGUoInRpdGxlIikmJiFhLmdldEF0dHJpYnV0ZSgiYXJpYS1kZXNjcmliZWRieSIpKX19LHtpZDoiaGFzLWxhbmciLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIGEuaGFzQXR0cmlidXRlKCJsYW5nIil8fGEuaGFzQXR0cmlidXRlKCJ4bWw6bGFuZyIpfX0se2lkOiJ2YWxpZC1sYW5nIixvcHRpb25zOlsiYWEiLCJhYiIsImFlIiwiYWYiLCJhayIsImFtIiwiYW4iLCJhciIsImFzIiwiYXYiLCJheSIsImF6IiwiYmEiLCJiZSIsImJnIiwiYmgiLCJiaSIsImJtIiwiYm4iLCJibyIsImJyIiwiYnMiLCJjYSIsImNlIiwiY2giLCJjbyIsImNyIiwiY3MiLCJjdSIsImN2IiwiY3kiLCJkYSIsImRlIiwiZHYiLCJkeiIsImVlIiwiZWwiLCJlbiIsImVvIiwiZXMiLCJldCIsImV1IiwiZmEiLCJmZiIsImZpIiwiZmoiLCJmbyIsImZyIiwiZnkiLCJnYSIsImdkIiwiZ2wiLCJnbiIsImd1IiwiZ3YiLCJoYSIsImhlIiwiaGkiLCJobyIsImhyIiwiaHQiLCJodSIsImh5IiwiaHoiLCJpYSIsImlkIiwiaWUiLCJpZyIsImlpIiwiaWsiLCJpbiIsImlvIiwiaXMiLCJpdCIsIml1IiwiaXciLCJqYSIsImppIiwianYiLCJqdyIsImthIiwia2ciLCJraSIsImtqIiwia2siLCJrbCIsImttIiwia24iLCJrbyIsImtyIiwia3MiLCJrdSIsImt2Iiwia3ciLCJreSIsImxhIiwibGIiLCJsZyIsImxpIiwibG4iLCJsbyIsImx0IiwibHUiLCJsdiIsIm1nIiwibWgiLCJtaSIsIm1rIiwibWwiLCJtbiIsIm1vIiwibXIiLCJtcyIsIm10IiwibXkiLCJuYSIsIm5iIiwibmQiLCJuZSIsIm5nIiwibmwiLCJubiIsIm5vIiwibnIiLCJudiIsIm55Iiwib2MiLCJvaiIsIm9tIiwib3IiLCJvcyIsInBhIiwicGkiLCJwbCIsInBzIiwicHQiLCJxdSIsInJtIiwicm4iLCJybyIsInJ1IiwicnciLCJzYSIsInNjIiwic2QiLCJzZSIsInNnIiwic2giLCJzaSIsInNrIiwic2wiLCJzbSIsInNuIiwic28iLCJzcSIsInNyIiwic3MiLCJzdCIsInN1Iiwic3YiLCJzdyIsInRhIiwidGUiLCJ0ZyIsInRoIiwidGkiLCJ0ayIsInRsIiwidG4iLCJ0byIsInRyIiwidHMiLCJ0dCIsInR3IiwidHkiLCJ1ZyIsInVrIiwidXIiLCJ1eiIsInZlIiwidmkiLCJ2byIsIndhIiwid28iLCJ4aCIsInlpIiwieW8iLCJ6YSIsInpoIiwienUiXSxldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPShhLmdldEF0dHJpYnV0ZSgibGFuZyIpfHwiIikudHJpbSgpLnRvTG93ZXJDYXNlKCksZD0oYS5nZXRBdHRyaWJ1dGUoInhtbDpsYW5nIil8fCIiKS50cmltKCkudG9Mb3dlckNhc2UoKSxlPVtdO3JldHVybihifHxbXSkuZm9yRWFjaChmdW5jdGlvbihhKXthPWEudG9Mb3dlckNhc2UoKSwhY3x8YyE9PWEmJjAhPT1jLmluZGV4T2YoYS50b0xvd2VyQ2FzZSgpKyItIil8fChjPW51bGwpLCFkfHxkIT09YSYmMCE9PWQuaW5kZXhPZihhLnRvTG93ZXJDYXNlKCkrIi0iKXx8KGQ9bnVsbCl9KSxkJiZlLnB1c2goJ3htbDpsYW5nPSInK2QrJyInKSxjJiZlLnB1c2goJ2xhbmc9IicrYysnIicpLGUubGVuZ3RoPyh0aGlzLmRhdGEoZSksITApOiExfX0se2lkOiJkbGl0ZW0iLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIkRMIj09PWEucGFyZW50Tm9kZS50YWdOYW1lfX0se2lkOiJoYXMtbGlzdGl0ZW0iLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YS5jaGlsZHJlbjtpZigwPT09Yy5sZW5ndGgpcmV0dXJuITA7Zm9yKHZhciBkPTA7ZDxjLmxlbmd0aDtkKyspaWYoIkxJIj09PWNbZF0ubm9kZU5hbWUpcmV0dXJuITE7cmV0dXJuITB9fSx7aWQ6Imxpc3RpdGVtIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybi0xIT09WyJVTCIsIk9MIl0uaW5kZXhPZihhLnBhcmVudE5vZGUudGFnTmFtZSl9fSx7aWQ6Im9ubHktZGxpdGVtcyIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmb3IodmFyIGMsZD1bXSxlPWEuY2hpbGROb2RlcyxmPSExLGc9MDtnPGUubGVuZ3RoO2crKyljPWVbZ10sMT09PWMubm9kZVR5cGUmJiJEVCIhPT1jLm5vZGVOYW1lJiYiREQiIT09Yy5ub2RlTmFtZSYmIlNDUklQVCIhPT1jLm5vZGVOYW1lJiYiVEVNUExBVEUiIT09Yy5ub2RlTmFtZT9kLnB1c2goYyk6Mz09PWMubm9kZVR5cGUmJiIiIT09Yy5ub2RlVmFsdWUudHJpbSgpJiYoZj0hMCk7ZC5sZW5ndGgmJnRoaXMucmVsYXRlZE5vZGVzKGQpO3ZhciBoPSEhZC5sZW5ndGh8fGY7cmV0dXJuIGh9fSx7aWQ6Im9ubHktbGlzdGl0ZW1zIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Zvcih2YXIgYyxkPVtdLGU9YS5jaGlsZE5vZGVzLGY9ITEsZz0wO2c8ZS5sZW5ndGg7ZysrKWM9ZVtnXSwxPT09Yy5ub2RlVHlwZSYmIkxJIiE9PWMubm9kZU5hbWUmJiJTQ1JJUFQiIT09Yy5ub2RlTmFtZSYmIlRFTVBMQVRFIiE9PWMubm9kZU5hbWU/ZC5wdXNoKGMpOjM9PT1jLm5vZGVUeXBlJiYiIiE9PWMubm9kZVZhbHVlLnRyaW0oKSYmKGY9ITApO3JldHVybiBkLmxlbmd0aCYmdGhpcy5yZWxhdGVkTm9kZXMoZCksISFkLmxlbmd0aHx8Zn19LHtpZDoic3RydWN0dXJlZC1kbGl0ZW1zIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuY2hpbGRyZW47aWYoIWN8fCFjLmxlbmd0aClyZXR1cm4hMTtmb3IodmFyIGQ9ITEsZT0hMSxmPTA7ZjxjLmxlbmd0aDtmKyspe2lmKCJEVCI9PT1jW2ZdLm5vZGVOYW1lJiYoZD0hMCksZCYmIkREIj09PWNbZl0ubm9kZU5hbWUpcmV0dXJuITE7IkREIj09PWNbZl0ubm9kZU5hbWUmJihlPSEwKX1yZXR1cm4gZHx8ZX19LHtpZDoiY2FwdGlvbiIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4hYS5xdWVyeVNlbGVjdG9yKCJ0cmFja1traW5kPWNhcHRpb25zXSIpfX0se2lkOiJkZXNjcmlwdGlvbiIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4hYS5xdWVyeVNlbGVjdG9yKCJ0cmFja1traW5kPWRlc2NyaXB0aW9uc10iKX19LHtpZDoibWV0YS12aWV3cG9ydCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmb3IodmFyIGMsZD1hLmdldEF0dHJpYnV0ZSgiY29udGVudCIpfHwiIixlPWQuc3BsaXQoL1s7LF0vKSxmPXt9LGc9MCxoPWUubGVuZ3RoO2g+ZztnKyspe2M9ZVtnXS5zcGxpdCgiPSIpO3ZhciBpPWMuc2hpZnQoKTtpJiZjLmxlbmd0aCYmKGZbaS50cmltKCldPWMuam9pbigiPSIpLnRyaW0oKSl9cmV0dXJuIGZbIm1heGltdW0tc2NhbGUiXSYmcGFyc2VGbG9hdChmWyJtYXhpbXVtLXNjYWxlIl0pPDU/ITE6Im5vIj09PWZbInVzZXItc2NhbGFibGUiXT8hMTohMH19LHtpZDoiaGVhZGVyLXByZXNlbnQiLHNlbGVjdG9yOiJodG1sIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiEhYS5xdWVyeVNlbGVjdG9yKCdoMSwgaDIsIGgzLCBoNCwgaDUsIGg2LCBbcm9sZT0iaGVhZGluZyJdJyl9fSx7aWQ6ImhlYWRpbmctb3JkZXIiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YS5nZXRBdHRyaWJ1dGUoImFyaWEtbGV2ZWwiKTtpZihudWxsIT09YylyZXR1cm4gdGhpcy5kYXRhKHBhcnNlSW50KGMsMTApKSwhMDt2YXIgZD1hLnRhZ05hbWUubWF0Y2goL0goXGQpLyk7cmV0dXJuIGQ/KHRoaXMuZGF0YShwYXJzZUludChkWzFdLDEwKSksITApOiEwfSxhZnRlcjpmdW5jdGlvbihhLGIpe2lmKGEubGVuZ3RoPDIpcmV0dXJuIGE7Zm9yKHZhciBjPWFbMF0uZGF0YSxkPTE7ZDxhLmxlbmd0aDtkKyspYVtkXS5yZXN1bHQmJmFbZF0uZGF0YT5jKzEmJihhW2RdLnJlc3VsdD0hMSksYz1hW2RdLmRhdGE7cmV0dXJuIGF9fSx7aWQ6ImludGVybmFsLWxpbmstcHJlc2VudCIsc2VsZWN0b3I6Imh0bWwiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuISFhLnF1ZXJ5U2VsZWN0b3IoJ2FbaHJlZl49IiMiXScpfX0se2lkOiJsYW5kbWFyayIsc2VsZWN0b3I6Imh0bWwiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuISFhLnF1ZXJ5U2VsZWN0b3IoJ1tyb2xlPSJtYWluIl0nKX19LHtpZDoibWV0YS1yZWZyZXNoIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJjb250ZW50Iil8fCIiLGQ9Yy5zcGxpdCgvWzssXS8pO3JldHVybiIiPT09Y3x8IjAiPT09ZFswXX19LHtpZDoicmVnaW9uIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Z1bmN0aW9uIGMoYSl7cmV0dXJuIGgmJlIuZG9tLmlzRm9jdXNhYmxlKFIuZG9tLmdldEVsZW1lbnRCeVJlZmVyZW5jZShoLCJocmVmIikpJiZoPT09YX1mdW5jdGlvbiBkKGEpe3ZhciBiPWEuZ2V0QXR0cmlidXRlKCJyb2xlIik7cmV0dXJuIGImJi0xIT09Zy5pbmRleE9mKGIpfWZ1bmN0aW9uIGUoYSl7cmV0dXJuIGQoYSk/bnVsbDpjKGEpP2YoYSk6Ui5kb20uaXNWaXNpYmxlKGEsITApJiYoUi50ZXh0LnZpc2libGUoYSwhMCwhMCl8fFIuZG9tLmlzVmlzdWFsQ29udGVudChhKSk/YTpmKGEpfWZ1bmN0aW9uIGYoYSl7dmFyIGI9Ui51dGlscy50b0FycmF5KGEuY2hpbGRyZW4pO3JldHVybiAwPT09Yi5sZW5ndGg/W106Yi5tYXAoZSkuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBudWxsIT09YX0pLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBhLmNvbmNhdChiKX0sW10pfXZhciBnPVIuYXJpYS5nZXRSb2xlc0J5VHlwZSgibGFuZG1hcmsiKSxoPWEucXVlcnlTZWxlY3RvcigiYVtocmVmXSIpLGk9ZihhKTtyZXR1cm4gdGhpcy5yZWxhdGVkTm9kZXMoaSksIWkubGVuZ3RofSxhZnRlcjpmdW5jdGlvbihhLGIpe3JldHVyblthWzBdXX19LHtpZDoic2tpcC1saW5rIixzZWxlY3RvcjoiYVtocmVmXSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4gUi5kb20uaXNGb2N1c2FibGUoUi5kb20uZ2V0RWxlbWVudEJ5UmVmZXJlbmNlKGEsImhyZWYiKSl9LGFmdGVyOmZ1bmN0aW9uKGEsYil7cmV0dXJuW2FbMF1dfX0se2lkOiJ1bmlxdWUtZnJhbWUtdGl0bGUiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIHRoaXMuZGF0YShhLnRpdGxlKSwhMH0sYWZ0ZXI6ZnVuY3Rpb24oYSxiKXt2YXIgYz17fTtyZXR1cm4gYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe2NbYS5kYXRhXT12b2lkIDAhPT1jW2EuZGF0YV0/KytjW2EuZGF0YV06MH0pLGEuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiEhY1thLmRhdGFdfSl9fSx7aWQ6ImFyaWEtbGFiZWwiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YS5nZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWwiKTtyZXR1cm4hIShjP1IudGV4dC5zYW5pdGl6ZShjKS50cmltKCk6IiIpfX0se2lkOiJhcmlhLWxhYmVsbGVkYnkiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGMsZCxlPVIuZG9tLmlkcmVmcyhhLCJhcmlhLWxhYmVsbGVkYnkiKSxmPWUubGVuZ3RoO2ZvcihkPTA7Zj5kO2QrKylpZihjPWVbZF0sYyYmUi50ZXh0LmFjY2Vzc2libGVUZXh0KGMpLnRyaW0oKSlyZXR1cm4hMDtyZXR1cm4hMX19LHtpZDoiYnV0dG9uLWhhcy12aXNpYmxlLXRleHQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIFIudGV4dC5hY2Nlc3NpYmxlVGV4dChhKS5sZW5ndGg+MH0sc2VsZWN0b3I6J2J1dHRvbiwgW3JvbGU9ImJ1dHRvbiJdOm5vdChpbnB1dCknfSx7aWQ6ImRvYy1oYXMtdGl0bGUiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYyl7dmFyIGQ9Yi50aXRsZTtyZXR1cm4hIShkP1IudGV4dC5zYW5pdGl6ZShkKS50cmltKCk6IiIpfX0se2lkOiJkdXBsaWNhdGUtaWQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYyl7Zm9yKHZhciBkPWIucXVlcnlTZWxlY3RvckFsbCgnW2lkPSInK1IudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS5pZCkrJyJdJyksZT1bXSxmPTA7ZjxkLmxlbmd0aDtmKyspZFtmXSE9PWEmJmUucHVzaChkW2ZdKTtyZXR1cm4gZS5sZW5ndGgmJnRoaXMucmVsYXRlZE5vZGVzKGUpLHRoaXMuZGF0YShhLmdldEF0dHJpYnV0ZSgiaWQiKSksZC5sZW5ndGg8PTF9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9W107cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybi0xPT09Yy5pbmRleE9mKGEuZGF0YSk/KGMucHVzaChhLmRhdGEpLCEwKTohMX0pfX0se2lkOiJleGlzdHMiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuITB9fSx7aWQ6Imhhcy1hbHQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIGEuaGFzQXR0cmlidXRlKCJhbHQiKX19LHtpZDoiaGFzLXZpc2libGUtdGV4dCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4gUi50ZXh0LmFjY2Vzc2libGVUZXh0KGEpLmxlbmd0aD4wfX0se2lkOiJub24tZW1wdHktYWx0IixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJhbHQiKTtyZXR1cm4hIShjP1IudGV4dC5zYW5pdGl6ZShjKS50cmltKCk6IiIpfX0se2lkOiJub24tZW1wdHktaWYtcHJlc2VudCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLmdldEF0dHJpYnV0ZSgidmFsdWUiKTtyZXR1cm4gdGhpcy5kYXRhKGMpLG51bGw9PT1jfHwiIiE9PVIudGV4dC5zYW5pdGl6ZShjKS50cmltKCl9LHNlbGVjdG9yOidbdHlwZT0ic3VibWl0Il0sIFt0eXBlPSJyZXNldCJdJ30se2lkOiJub24tZW1wdHktdGl0bGUiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YS5nZXRBdHRyaWJ1dGUoInRpdGxlIik7cmV0dXJuISEoYz9SLnRleHQuc2FuaXRpemUoYykudHJpbSgpOiIiKX19LHtpZDoibm9uLWVtcHR5LXZhbHVlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJ2YWx1ZSIpO3JldHVybiEhKGM/Ui50ZXh0LnNhbml0aXplKGMpLnRyaW0oKToiIil9LHNlbGVjdG9yOidbdHlwZT0iYnV0dG9uIl0nfSx7aWQ6InJvbGUtbm9uZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4ibm9uZSI9PT1hLmdldEF0dHJpYnV0ZSgicm9sZSIpfX0se2lkOiJyb2xlLXByZXNlbnRhdGlvbiIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4icHJlc2VudGF0aW9uIj09PWEuZ2V0QXR0cmlidXRlKCJyb2xlIil9fSx7aWQ6ImNlbGwtbm8taGVhZGVyIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Zvcih2YXIgYyxkLGU9W10sZj0wLGc9YS5yb3dzLmxlbmd0aDtnPmY7ZisrKXtjPWEucm93c1tmXTtmb3IodmFyIGg9MCxpPWMuY2VsbHMubGVuZ3RoO2k+aDtoKyspZD1jLmNlbGxzW2hdLCFSLnRhYmxlLmlzRGF0YUNlbGwoZCl8fFIuYXJpYS5sYWJlbChkKXx8Ui50YWJsZS5nZXRIZWFkZXJzKGQpLmxlbmd0aHx8ZS5wdXNoKGQpfXJldHVybiBlLmxlbmd0aD8odGhpcy5yZWxhdGVkTm9kZXMoZSksITApOiExfX0se2lkOiJjb25zaXN0ZW50LWNvbHVtbnMiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Zm9yKHZhciBjLGQ9Ui50YWJsZS50b0FycmF5KGEpLGU9W10sZj0wLGc9ZC5sZW5ndGg7Zz5mO2YrKykwPT09Zj9jPWRbZl0ubGVuZ3RoOmMhPT1kW2ZdLmxlbmd0aCYmZS5wdXNoKGEucm93c1tmXSk7cmV0dXJuIWUubGVuZ3RofX0se2lkOiJoYXMtY2FwdGlvbiIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4hIWEuY2FwdGlvbn19LHtpZDoiaGFzLXN1bW1hcnkiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuISFhLnN1bW1hcnl9fSx7aWQ6Imhhcy10aCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmb3IodmFyIGMsZCxlPVtdLGY9MCxnPWEucm93cy5sZW5ndGg7Zz5mO2YrKyl7Yz1hLnJvd3NbZl07Zm9yKHZhciBoPTAsaT1jLmNlbGxzLmxlbmd0aDtpPmg7aCsrKWQ9Yy5jZWxsc1toXSwiVEgiPT09ZC5ub2RlTmFtZSYmZS5wdXNoKGQpfXJldHVybiBlLmxlbmd0aD8odGhpcy5yZWxhdGVkTm9kZXMoZSksITApOiExfX0se2lkOiJoZWFkZXJzLWF0dHItcmVmZXJlbmNlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Z1bmN0aW9uIGMoYSl7YSYmUi50ZXh0LmFjY2Vzc2libGVUZXh0KGEpfHxnLnB1c2goZSl9Zm9yKHZhciBkLGUsZixnPVtdLGg9MCxpPWEucm93cy5sZW5ndGg7aT5oO2grKyl7ZD1hLnJvd3NbaF07Zm9yKHZhciBqPTAsaz1kLmNlbGxzLmxlbmd0aDtrPmo7aisrKWU9ZC5jZWxsc1tqXSxmPVIuZG9tLmlkcmVmcyhlLCJoZWFkZXJzIiksZi5sZW5ndGgmJmYuZm9yRWFjaChjKX1yZXR1cm4gZy5sZW5ndGg/KHRoaXMucmVsYXRlZE5vZGVzKGcpLCEwKTohMX19LHtpZDoiaGVhZGVycy12aXNpYmxlLXRleHQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Zm9yKHZhciBjLGQsZT1bXSxmPTAsZz1hLnJvd3MubGVuZ3RoO2c+ZjtmKyspe2M9YS5yb3dzW2ZdO2Zvcih2YXIgaD0wLGk9Yy5jZWxscy5sZW5ndGg7aT5oO2grKylkPWMuY2VsbHNbaF0sUi50YWJsZS5pc0hlYWRlcihkKSYmIVIudGV4dC5hY2Nlc3NpYmxlVGV4dChkKSYmZS5wdXNoKGQpfXJldHVybiBlLmxlbmd0aD8odGhpcy5yZWxhdGVkTm9kZXMoZSksITApOiExfX0se2lkOiJodG1sNC1zY29wZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxjKXtyZXR1cm4gUi5kb20uaXNIVE1MNShiKT8hMToiVEgiPT09YS5ub2RlTmFtZXx8IlREIj09PWEubm9kZU5hbWV9fSx7aWQ6Imh0bWw1LXNjb3BlIixldmFsdWF0ZTpmdW5jdGlvbihhLGMpe3JldHVybiBSLmRvbS5pc0hUTUw1KGIpPyJUSCI9PT1hLm5vZGVOYW1lOiExfX0se2lkOiJuby1jYXB0aW9uIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiEoYS5jYXB0aW9ufHx7fSkudGV4dENvbnRlbnR9LGVuYWJsZWQ6ITF9LHtpZDoicm93c3BhbiIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmb3IodmFyIGMsZCxlPVtdLGY9MCxnPWEucm93cy5sZW5ndGg7Zz5mO2YrKyl7Yz1hLnJvd3NbZl07Zm9yKHZhciBoPTAsaT1jLmNlbGxzLmxlbmd0aDtpPmg7aCsrKWQ9Yy5jZWxsc1toXSwxIT09ZC5yb3dTcGFuJiZlLnB1c2goZCl9cmV0dXJuIGUubGVuZ3RoPyh0aGlzLnJlbGF0ZWROb2RlcyhlKSwhMCk6ITF9fSx7aWQ6InNhbWUtY2FwdGlvbi1zdW1tYXJ5IixzZWxlY3RvcjoidGFibGUiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuISghYS5zdW1tYXJ5fHwhYS5jYXB0aW9uKSYmYS5zdW1tYXJ5PT09Ui50ZXh0LmFjY2Vzc2libGVUZXh0KGEuY2FwdGlvbil9fSx7aWQ6InNjb3BlLXZhbHVlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJzY29wZSIpO3JldHVybiJyb3ciIT09YyYmImNvbCIhPT1jfX0se2lkOiJ0aC1oZWFkZXJzLWF0dHIiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Zm9yKHZhciBjLGQsZT1bXSxmPTAsZz1hLnJvd3MubGVuZ3RoO2c+ZjtmKyspe2M9YS5yb3dzW2ZdO2Zvcih2YXIgaD0wLGk9Yy5jZWxscy5sZW5ndGg7aT5oO2grKylkPWMuY2VsbHNbaF0sIlRIIj09PWQubm9kZU5hbWUmJmQuZ2V0QXR0cmlidXRlKCJoZWFkZXJzIikmJmUucHVzaChkKX1yZXR1cm4gZS5sZW5ndGg/KHRoaXMucmVsYXRlZE5vZGVzKGUpLCEwKTohMX19LHtpZDoidGgtc2NvcGUiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Zm9yKHZhciBjLGQsZT1bXSxmPTAsZz1hLnJvd3MubGVuZ3RoO2c+ZjtmKyspe2M9YS5yb3dzW2ZdO2Zvcih2YXIgaD0wLGk9Yy5jZWxscy5sZW5ndGg7aT5oO2grKylkPWMuY2VsbHNbaF0sIlRIIiE9PWQubm9kZU5hbWV8fGQuZ2V0QXR0cmlidXRlKCJzY29wZSIpfHxlLnB1c2goZCl9cmV0dXJuIGUubGVuZ3RoPyh0aGlzLnJlbGF0ZWROb2RlcyhlKSwhMCk6ITF9fSx7aWQ6InRoLXNpbmdsZS1yb3ctY29sdW1uIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Zvcih2YXIgYyxkLGUsZj1bXSxnPVtdLGg9MCxpPWEucm93cy5sZW5ndGg7aT5oO2grKyl7Yz1hLnJvd3NbaF07Zm9yKHZhciBqPTAsaz1jLmNlbGxzLmxlbmd0aDtrPmo7aisrKWQ9Yy5jZWxsc1tqXSxkLm5vZGVOYW1lJiYoUi50YWJsZS5pc0NvbHVtbkhlYWRlcihkKSYmLTE9PT1nLmluZGV4T2YoaCk/Zy5wdXNoKGgpOlIudGFibGUuaXNSb3dIZWFkZXIoZCkmJihlPVIudGFibGUuZ2V0Q2VsbFBvc2l0aW9uKGQpLC0xPT09Zi5pbmRleE9mKGUueCkmJmYucHVzaChlLngpKSl9cmV0dXJuIGcubGVuZ3RoPjF8fGYubGVuZ3RoPjE/ITA6ITF9fV0sY29tbW9uczpmdW5jdGlvbigpe2Z1bmN0aW9uIGMoYil7dmFyIGMsZD1hLmdldENvbXB1dGVkU3R5bGUoYik7aWYoIm5vbmUiIT09ZC5nZXRQcm9wZXJ0eVZhbHVlKCJiYWNrZ3JvdW5kLWltYWdlIikpcmV0dXJuIG51bGw7dmFyIGU9ZC5nZXRQcm9wZXJ0eVZhbHVlKCJiYWNrZ3JvdW5kLWNvbG9yIik7InRyYW5zcGFyZW50Ij09PWU/Yz1uZXcgci5Db2xvcigwLDAsMCwwKTooYz1uZXcgci5Db2xvcixjLnBhcnNlUmdiU3RyaW5nKGUpKTt2YXIgZj1kLmdldFByb3BlcnR5VmFsdWUoIm9wYWNpdHkiKTtyZXR1cm4gYy5hbHBoYT1jLmFscGhhKmYsY31mdW5jdGlvbiBkKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1hLm1hdGNoKC9yZWN0XHMqXCgoWzAtOV0rKXB4LD9ccyooWzAtOV0rKXB4LD9ccyooWzAtOV0rKXB4LD9ccyooWzAtOV0rKXB4XHMqXCkvKTtyZXR1cm4gYiYmNT09PWIubGVuZ3RoP2JbM10tYlsxXTw9MCYmYlsyXS1iWzRdPD0wOiExfWZ1bmN0aW9uIGUoYSl7dmFyIGM9bnVsbDtyZXR1cm4gYS5pZCYmKGM9Yi5xdWVyeVNlbGVjdG9yKCdsYWJlbFtmb3I9Iicrdi5lc2NhcGVTZWxlY3RvcihhLmlkKSsnIl0nKSk/YzpjPXMuZmluZFVwKGEsImxhYmVsIil9ZnVuY3Rpb24gZihhKXtyZXR1cm4tMSE9PVsiYnV0dG9uIiwicmVzZXQiLCJzdWJtaXQiXS5pbmRleE9mKGEudHlwZSl9ZnVuY3Rpb24gZyhhKXtyZXR1cm4iVEVYVEFSRUEiPT09YS5ub2RlTmFtZXx8IlNFTEVDVCI9PT1hLm5vZGVOYW1lfHwiSU5QVVQiPT09YS5ub2RlTmFtZSYmImhpZGRlbiIhPT1hLnR5cGV9ZnVuY3Rpb24gaChhKXtyZXR1cm4tMSE9PVsiQlVUVE9OIiwiU1VNTUFSWSIsIkEiXS5pbmRleE9mKGEubm9kZU5hbWUpfWZ1bmN0aW9uIGkoYSl7cmV0dXJuLTEhPT1bIlRBQkxFIiwiRklHVVJFIl0uaW5kZXhPZihhLm5vZGVOYW1lKX1mdW5jdGlvbiBqKGEpe2lmKCJJTlBVVCI9PT1hLm5vZGVOYW1lKXJldHVybiFhLmhhc0F0dHJpYnV0ZSgidHlwZSIpfHwtMSE9PXkuaW5kZXhPZihhLmdldEF0dHJpYnV0ZSgidHlwZSIpKSYmYS52YWx1ZT9hLnZhbHVlOiIiO2lmKCJTRUxFQ1QiPT09YS5ub2RlTmFtZSl7dmFyIGI9YS5vcHRpb25zO2lmKGImJmIubGVuZ3RoKXtmb3IodmFyIGM9IiIsZD0wO2Q8Yi5sZW5ndGg7ZCsrKWJbZF0uc2VsZWN0ZWQmJihjKz0iICIrYltkXS50ZXh0KTtyZXR1cm4gdS5zYW5pdGl6ZShjKX1yZXR1cm4iIn1yZXR1cm4iVEVYVEFSRUEiPT09YS5ub2RlTmFtZSYmYS52YWx1ZT9hLnZhbHVlOiIifWZ1bmN0aW9uIGsoYSxiKXt2YXIgYz1hLnF1ZXJ5U2VsZWN0b3IoYik7cmV0dXJuIGM/dS5hY2Nlc3NpYmxlVGV4dChjKToiIn1mdW5jdGlvbiBsKGEpe2lmKCFhKXJldHVybiExO3N3aXRjaChhLm5vZGVOYW1lKXtjYXNlIlNFTEVDVCI6Y2FzZSJURVhUQVJFQSI6cmV0dXJuITA7Y2FzZSJJTlBVVCI6cmV0dXJuIWEuaGFzQXR0cmlidXRlKCJ0eXBlIil8fC0xIT09eS5pbmRleE9mKGEuZ2V0QXR0cmlidXRlKCJ0eXBlIikpO2RlZmF1bHQ6cmV0dXJuITF9fWZ1bmN0aW9uIG0oYSl7cmV0dXJuIklOUFVUIj09PWEubm9kZU5hbWUmJiJpbWFnZSI9PT1hLnR5cGV8fC0xIT09WyJJTUciLCJBUFBMRVQiLCJBUkVBIl0uaW5kZXhPZihhLm5vZGVOYW1lKX1mdW5jdGlvbiBuKGEpe3JldHVybiEhdS5zYW5pdGl6ZShhKX12YXIgbz17fSxwPW8uYXJpYT17fSxxPXAuX2x1dD17fTtxLmF0dHJpYnV0ZXM9eyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiOnt0eXBlOiJpZHJlZiJ9LCJhcmlhLWF0b21pYyI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1hdXRvY29tcGxldGUiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJpbmxpbmUiLCJsaXN0IiwiYm90aCIsIm5vbmUiXX0sImFyaWEtYnVzeSI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1jaGVja2VkIjp7dHlwZToibm10b2tlbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIiwibWl4ZWQiLCJ1bmRlZmluZWQiXX0sImFyaWEtY29sY291bnQiOnt0eXBlOiJpbnQifSwiYXJpYS1jb2xpbmRleCI6e3R5cGU6ImludCJ9LCJhcmlhLWNvbHNwYW4iOnt0eXBlOiJpbnQifSwiYXJpYS1jb250cm9scyI6e3R5cGU6ImlkcmVmcyJ9LCJhcmlhLWRlc2NyaWJlZGJ5Ijp7dHlwZToiaWRyZWZzIn0sImFyaWEtZGlzYWJsZWQiOnt0eXBlOiJib29sZWFuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiXX0sImFyaWEtZHJvcGVmZmVjdCI6e3R5cGU6Im5tdG9rZW5zIix2YWx1ZXM6WyJjb3B5IiwibW92ZSIsInJlZmVyZW5jZSIsImV4ZWN1dGUiLCJwb3B1cCIsIm5vbmUiXX0sImFyaWEtZXhwYW5kZWQiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiLCJ1bmRlZmluZWQiXX0sImFyaWEtZmxvd3RvIjp7dHlwZToiaWRyZWZzIn0sImFyaWEtZ3JhYmJlZCI6e3R5cGU6Im5tdG9rZW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSIsInVuZGVmaW5lZCJdfSwiYXJpYS1oYXNwb3B1cCI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1oaWRkZW4iOnt0eXBlOiJib29sZWFuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiXX0sImFyaWEtaW52YWxpZCI6e3R5cGU6Im5tdG9rZW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSIsInNwZWxsaW5nIiwiZ3JhbW1hciJdfSwiYXJpYS1sYWJlbCI6e3R5cGU6InN0cmluZyJ9LCJhcmlhLWxhYmVsbGVkYnkiOnt0eXBlOiJpZHJlZnMifSwiYXJpYS1sZXZlbCI6e3R5cGU6ImludCJ9LCJhcmlhLWxpdmUiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJvZmYiLCJwb2xpdGUiLCJhc3NlcnRpdmUiXX0sImFyaWEtbXVsdGlsaW5lIjp7dHlwZToiYm9vbGVhbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIl19LCJhcmlhLW11bHRpc2VsZWN0YWJsZSI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1vcmllbnRhdGlvbiI6e3R5cGU6Im5tdG9rZW4iLHZhbHVlczpbImhvcml6b250YWwiLCJ2ZXJ0aWNhbCJdfSwiYXJpYS1vd25zIjp7dHlwZToiaWRyZWZzIn0sImFyaWEtcG9zaW5zZXQiOnt0eXBlOiJpbnQifSwiYXJpYS1wcmVzc2VkIjp7dHlwZToibm10b2tlbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIiwibWl4ZWQiLCJ1bmRlZmluZWQiXX0sImFyaWEtcmVhZG9ubHkiOnt0eXBlOiJib29sZWFuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiXX0sImFyaWEtcmVsZXZhbnQiOnt0eXBlOiJubXRva2VucyIsdmFsdWVzOlsiYWRkaXRpb25zIiwicmVtb3ZhbHMiLCJ0ZXh0IiwiYWxsIl19LCJhcmlhLXJlcXVpcmVkIjp7dHlwZToiYm9vbGVhbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIl19LCJhcmlhLXJvd2NvdW50Ijp7dHlwZToiaW50In0sImFyaWEtcm93aW5kZXgiOnt0eXBlOiJpbnQifSwiYXJpYS1yb3dzcGFuIjp7dHlwZToiaW50In0sImFyaWEtc2VsZWN0ZWQiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiLCJ1bmRlZmluZWQiXX0sImFyaWEtc2V0c2l6ZSI6e3R5cGU6ImludCJ9LCJhcmlhLXNvcnQiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJhc2NlbmRpbmciLCJkZXNjZW5kaW5nIiwib3RoZXIiLCJub25lIl19LCJhcmlhLXZhbHVlbWF4Ijp7dHlwZToiZGVjaW1hbCJ9LCJhcmlhLXZhbHVlbWluIjp7dHlwZToiZGVjaW1hbCJ9LCJhcmlhLXZhbHVlbm93Ijp7dHlwZToiZGVjaW1hbCJ9LCJhcmlhLXZhbHVldGV4dCI6e3R5cGU6InN0cmluZyJ9fSxxLmdsb2JhbEF0dHJpYnV0ZXM9WyJhcmlhLWF0b21pYyIsImFyaWEtYnVzeSIsImFyaWEtY29udHJvbHMiLCJhcmlhLWRlc2NyaWJlZGJ5IiwiYXJpYS1kaXNhYmxlZCIsImFyaWEtZHJvcGVmZmVjdCIsImFyaWEtZmxvd3RvIiwiYXJpYS1ncmFiYmVkIiwiYXJpYS1oYXNwb3B1cCIsImFyaWEtaGlkZGVuIiwiYXJpYS1pbnZhbGlkIiwiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsImFyaWEtbGl2ZSIsImFyaWEtb3ducyIsImFyaWEtcmVsZXZhbnQiXSxxLnJvbGU9e2FsZXJ0Ont0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LGFsZXJ0ZGlhbG9nOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LGFwcGxpY2F0aW9uOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0sYXJ0aWNsZTp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiYXJ0aWNsZSJdfSxiYW5uZXI6e3R5cGU6ImxhbmRtYXJrIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxidXR0b246e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiLCJhcmlhLXByZXNzZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiYnV0dG9uIiwnaW5wdXRbdHlwZT0iYnV0dG9uIl0nLCdpbnB1dFt0eXBlPSJpbWFnZSJdJ119LGNlbGw6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtY29saW5kZXgiLCJhcmlhLWNvbHNwYW4iLCJhcmlhLXJvd2luZGV4IiwiYXJpYS1yb3dzcGFuIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJyb3ciXX0sY2hlY2tib3g6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7cmVxdWlyZWQ6WyJhcmlhLWNoZWNrZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0iY2hlY2tib3giXSddfSxjb2x1bW5oZWFkZXI6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiLCJhcmlhLXNvcnQiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1zZWxlY3RlZCIsImFyaWEtcmVxdWlyZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpbInJvdyJdfSxjb21ib2JveDp7dHlwZToiY29tcG9zaXRlIixhdHRyaWJ1dGVzOntyZXF1aXJlZDpbImFyaWEtZXhwYW5kZWQiXSxhbGxvd2VkOlsiYXJpYS1hdXRvY29tcGxldGUiLCJhcmlhLXJlcXVpcmVkIiwiYXJpYS1hY3RpdmVkZXNjZW5kYW50Il19LG93bmVkOnthbGw6WyJsaXN0Ym94IiwidGV4dGJveCJdfSxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0sY29tbWFuZDp7bmFtZUZyb206WyJhdXRob3IiXSx0eXBlOiJhYnN0cmFjdCJ9LGNvbXBsZW1lbnRhcnk6e3R5cGU6ImxhbmRtYXJrIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiYXNpZGUiXX0sY29tcG9zaXRlOntuYW1lRnJvbTpbImF1dGhvciJdLHR5cGU6ImFic3RyYWN0In0sY29udGVudGluZm86e3R5cGU6ImxhbmRtYXJrIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxkZWZpbml0aW9uOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LGRpYWxvZzp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiZGlhbG9nIl19LGRpcmVjdG9yeTp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0Om51bGx9LGRvY3VtZW50Ont0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJib2R5Il19LGZvcm06e3R5cGU6ImxhbmRtYXJrIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxncmlkOnt0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWxldmVsIiwiYXJpYS1tdWx0aXNlbGVjdGFibGUiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1leHBhbmRlZCJdfSxvd25lZDp7b25lOlsicm93Z3JvdXAiLCJyb3ciXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LGdyaWRjZWxsOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXNlbGVjdGVkIiwiYXJpYS1yZWFkb25seSIsImFyaWEtZXhwYW5kZWQiLCJhcmlhLXJlcXVpcmVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJyb3ciXX0sZ3JvdXA6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImRldGFpbHMiXX0saGVhZGluZzp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1sZXZlbCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiaDEiLCJoMiIsImgzIiwiaDQiLCJoNSIsImg2Il19LGltZzp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiaW1nIl19LGlucHV0OntuYW1lRnJvbTpbImF1dGhvciJdLHR5cGU6ImFic3RyYWN0In0sbGFuZG1hcms6e25hbWVGcm9tOlsiYXV0aG9yIl0sdHlwZToiYWJzdHJhY3QifSxsaW5rOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImFbaHJlZl0iXX0sbGlzdDp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDp7YWxsOlsibGlzdGl0ZW0iXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJvbCIsInVsIl19LGxpc3Rib3g6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtbXVsdGlzZWxlY3RhYmxlIiwiYXJpYS1yZXF1aXJlZCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e2FsbDpbIm9wdGlvbiJdfSxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbInNlbGVjdCJdfSxsaXN0aXRlbTp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1sZXZlbCIsImFyaWEtcG9zaW5zZXQiLCJhcmlhLXNldHNpemUiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJsaXN0Il0saW1wbGljaXQ6WyJsaSJdfSxsb2c6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0sbWFpbjp7dHlwZToibGFuZG1hcmsiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LG1hcnF1ZWU6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0sbWF0aDp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxtZW51Ont0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOntvbmU6WyJtZW51aXRlbSIsIm1lbnVpdGVtcmFkaW8iLCJtZW51aXRlbWNoZWNrYm94Il19LG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxtZW51YmFyOnt0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LG1lbnVpdGVtOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6bnVsbCxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0OlsibWVudSIsIm1lbnViYXIiXX0sbWVudWl0ZW1jaGVja2JveDp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOntyZXF1aXJlZDpbImFyaWEtY2hlY2tlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0OlsibWVudSIsIm1lbnViYXIiXX0sbWVudWl0ZW1yYWRpbzp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1zZWxlY3RlZCIsImFyaWEtcG9zaW5zZXQiLCJhcmlhLXNldHNpemUiXSxyZXF1aXJlZDpbImFyaWEtY2hlY2tlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0OlsibWVudSIsIm1lbnViYXIiXX0sbmF2aWdhdGlvbjp7dHlwZToibGFuZG1hcmsiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LG5vbmU6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczpudWxsLG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LG5vdGU6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0sb3B0aW9uOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXNlbGVjdGVkIiwiYXJpYS1wb3NpbnNldCIsImFyaWEtc2V0c2l6ZSIsImFyaWEtY2hlY2tlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0OlsibGlzdGJveCJdfSxwcmVzZW50YXRpb246e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczpudWxsLG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHByb2dyZXNzYmFyOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXZhbHVldGV4dCIsImFyaWEtdmFsdWVub3ciLCJhcmlhLXZhbHVlbWF4IiwiYXJpYS12YWx1ZW1pbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxyYWRpbzp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1zZWxlY3RlZCIsImFyaWEtcG9zaW5zZXQiLCJhcmlhLXNldHNpemUiXSxyZXF1aXJlZDpbImFyaWEtY2hlY2tlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WydpbnB1dFt0eXBlPSJyYWRpbyJdJ119LHJhZGlvZ3JvdXA6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtcmVxdWlyZWQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOnthbGw6WyJyYWRpbyJdfSxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0scmFuZ2U6e25hbWVGcm9tOlsiYXV0aG9yIl0sdHlwZToiYWJzdHJhY3QifSxyZWdpb246e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbInNlY3Rpb24iXX0scm9sZXR5cGU6e3R5cGU6ImFic3RyYWN0In0scm93Ont0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWxldmVsIiwiYXJpYS1zZWxlY3RlZCIsImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e29uZTpbImNlbGwiLCJjb2x1bW5oZWFkZXIiLCJyb3doZWFkZXIiLCJncmlkY2VsbCJdfSxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpbInJvd2dyb3VwIiwiZ3JpZCIsInRyZWVncmlkIiwidGFibGUiXX0scm93Z3JvdXA6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e2FsbDpbInJvdyJdfSxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpbImdyaWQiLCJ0YWJsZSJdfSxyb3doZWFkZXI6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtc29ydCIsImFyaWEtcmVxdWlyZWQiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1leHBhbmRlZCIsImFyaWEtc2VsZWN0ZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpbInJvdyJdfSxzY3JvbGxiYXI6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7cmVxdWlyZWQ6WyJhcmlhLWNvbnRyb2xzIiwiYXJpYS1vcmllbnRhdGlvbiIsImFyaWEtdmFsdWVub3ciLCJhcmlhLXZhbHVlbWF4IiwiYXJpYS12YWx1ZW1pbiJdLGFsbG93ZWQ6WyJhcmlhLXZhbHVldGV4dCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxzZWFyY2g6e3R5cGU6ImxhbmRtYXJrIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxzZWFyY2hib3g6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtYXV0b2NvbXBsZXRlIiwiYXJpYS1tdWx0aWxpbmUiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1yZXF1aXJlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0ic2VhcmNoIl0nXX0sc2VjdGlvbjp7bmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLHR5cGU6ImFic3RyYWN0In0sc2VjdGlvbmhlYWQ6e25hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSx0eXBlOiJhYnN0cmFjdCJ9LHNlbGVjdDp7bmFtZUZyb206WyJhdXRob3IiXSx0eXBlOiJhYnN0cmFjdCJ9LHNlcGFyYXRvcjp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCIsImFyaWEtb3JpZW50YXRpb24iXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0sc2xpZGVyOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXZhbHVldGV4dCIsImFyaWEtb3JpZW50YXRpb24iXSxyZXF1aXJlZDpbImFyaWEtdmFsdWVub3ciLCJhcmlhLXZhbHVlbWF4IiwiYXJpYS12YWx1ZW1pbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxzcGluYnV0dG9uOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXZhbHVldGV4dCIsImFyaWEtcmVxdWlyZWQiXSxyZXF1aXJlZDpbImFyaWEtdmFsdWVub3ciLCJhcmlhLXZhbHVlbWF4IiwiYXJpYS12YWx1ZW1pbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxzdGF0dXM6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbIm91dHB1dCJdfSxzdHJ1Y3R1cmU6e3R5cGU6ImFic3RyYWN0In0sInN3aXRjaCI6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7cmVxdWlyZWQ6WyJhcmlhLWNoZWNrZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsfSx0YWI6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtc2VsZWN0ZWQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJ0YWJsaXN0Il19LHRhYmxlOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWNvbGNvdW50IiwiYXJpYS1yb3djb3VudCJdfSxvd25lZDp7b25lOlsicm93Z3JvdXAiLCJyb3ciXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJ0YWJsZSJdfSx0YWJsaXN0Ont0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLWV4cGFuZGVkIiwiYXJpYS1sZXZlbCIsImFyaWEtbXVsdGlzZWxlY3RhYmxlIl19LG93bmVkOnthbGw6WyJ0YWIiXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHRhYnBhbmVsOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHRleHQ6e3R5cGU6InN0cnVjdHVyZSIsb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsfSx0ZXh0Ym94Ont0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLWF1dG9jb21wbGV0ZSIsImFyaWEtbXVsdGlsaW5lIiwiYXJpYS1yZWFkb25seSIsImFyaWEtcmVxdWlyZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbJ2lucHV0W3R5cGU9InRleHQiXScsImlucHV0Om5vdChbdHlwZV0pIl19LHRpbWVyOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHRvb2xiYXI6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbJ21lbnVbdHlwZT0idG9vbGJhciJdJ119LHRvb2x0aXA6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsfSx0cmVlOnt0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLW11bHRpc2VsZWN0YWJsZSIsImFyaWEtcmVxdWlyZWQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOnthbGw6WyJ0cmVlaXRlbSJdfSxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0sdHJlZWdyaWQ6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiLCJhcmlhLWxldmVsIiwiYXJpYS1tdWx0aXNlbGVjdGFibGUiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1yZXF1aXJlZCJdfSxvd25lZDp7YWxsOlsidHJlZWl0ZW0iXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHRyZWVpdGVtOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWNoZWNrZWQiLCJhcmlhLXNlbGVjdGVkIiwiYXJpYS1leHBhbmRlZCIsImFyaWEtbGV2ZWwiLCJhcmlhLXBvc2luc2V0IiwiYXJpYS1zZXRzaXplIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJ0cmVlZ3JpZCIsInRyZWUiXX0sd2lkZ2V0Ont0eXBlOiJhYnN0cmFjdCJ9LHdpbmRvdzp7bmFtZUZyb206WyJhdXRob3IiXSx0eXBlOiJhYnN0cmFjdCJ9fTt2YXIgcj17fTtvLmNvbG9yPXI7dmFyIHM9by5kb209e30sdD1vLnRhYmxlPXt9LHU9by50ZXh0PXt9LHY9by51dGlscz17fTt2LmVzY2FwZVNlbGVjdG9yPVMudXRpbHMuZXNjYXBlU2VsZWN0b3Isdi5tYXRjaGVzU2VsZWN0b3I9Uy51dGlscy5tYXRjaGVzU2VsZWN0b3Isdi5jbG9uZT1TLnV0aWxzLmNsb25lLHAucmVxdWlyZWRBdHRyPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1xLnJvbGVbYV0sYz1iJiZiLmF0dHJpYnV0ZXMmJmIuYXR0cmlidXRlcy5yZXF1aXJlZDtyZXR1cm4gY3x8W119LHAuYWxsb3dlZEF0dHI9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPXEucm9sZVthXSxjPWImJmIuYXR0cmlidXRlcyYmYi5hdHRyaWJ1dGVzLmFsbG93ZWR8fFtdLGQ9YiYmYi5hdHRyaWJ1dGVzJiZiLmF0dHJpYnV0ZXMucmVxdWlyZWR8fFtdO3JldHVybiBjLmNvbmNhdChxLmdsb2JhbEF0dHJpYnV0ZXMpLmNvbmNhdChkKX0scC52YWxpZGF0ZUF0dHI9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3JldHVybiEhcS5hdHRyaWJ1dGVzW2FdfSxwLnZhbGlkYXRlQXR0clZhbHVlPWZ1bmN0aW9uKGEsYyl7InVzZSBzdHJpY3QiO3ZhciBkLGUsZixnLGg9YixpPWEuZ2V0QXR0cmlidXRlKGMpLGo9cS5hdHRyaWJ1dGVzW2NdO2lmKCFqKXJldHVybiEwO2lmKGoudmFsdWVzKXJldHVybiJzdHJpbmciPT10eXBlb2YgaSYmLTEhPT1qLnZhbHVlcy5pbmRleE9mKGkudG9Mb3dlckNhc2UoKSk/ITA6ITE7c3dpdGNoKGoudHlwZSl7Y2FzZSJpZHJlZiI6cmV0dXJuISghaXx8IWguZ2V0RWxlbWVudEJ5SWQoaSkpO2Nhc2UiaWRyZWZzIjpmb3IoZD12LnRva2VuTGlzdChpKSxlPTAsZj1kLmxlbmd0aDtmPmU7ZSsrKWlmKGRbZV0mJiFoLmdldEVsZW1lbnRCeUlkKGRbZV0pKXJldHVybiExO3JldHVybiEhZC5sZW5ndGg7Y2FzZSJzdHJpbmciOnJldHVybiEwO2Nhc2UiZGVjaW1hbCI6cmV0dXJuIGc9aS5tYXRjaCgvXlstK10/KFswLTldKilcLj8oWzAtOV0qKSQvKSwhKCFnfHwhZ1sxXSYmIWdbMl0pO2Nhc2UiaW50IjpyZXR1cm4vXlstK10/WzAtOV0rJC8udGVzdChpKX19LHAubGFiZWw9ZnVuY3Rpb24oYSl7dmFyIGIsYztyZXR1cm4gYS5nZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWxsZWRieSIpJiYoYj1zLmlkcmVmcyhhLCJhcmlhLWxhYmVsbGVkYnkiKSxjPWIubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhP3UudmlzaWJsZShhLCEwKToiIn0pLmpvaW4oIiAiKS50cmltKCkpP2M6KGM9YS5nZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWwiKSxjJiYoYz11LnNhbml0aXplKGMpLnRyaW0oKSk/YzpudWxsKX0scC5pc1ZhbGlkUm9sZT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJuIHEucm9sZVthXT8hMDohMX0scC5nZXRSb2xlc1dpdGhOYW1lRnJvbUNvbnRlbnRzPWZ1bmN0aW9uKCl7cmV0dXJuIE9iamVjdC5rZXlzKHEucm9sZSkuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBxLnJvbGVbYV0ubmFtZUZyb20mJi0xIT09cS5yb2xlW2FdLm5hbWVGcm9tLmluZGV4T2YoImNvbnRlbnRzIil9KX0scC5nZXRSb2xlc0J5VHlwZT1mdW5jdGlvbihhKXtyZXR1cm4gT2JqZWN0LmtleXMocS5yb2xlKS5maWx0ZXIoZnVuY3Rpb24oYil7cmV0dXJuIHEucm9sZVtiXS50eXBlPT09YX0pfSxwLmdldFJvbGVUeXBlPWZ1bmN0aW9uKGEpe3ZhciBiPXEucm9sZVthXTtyZXR1cm4gYiYmYi50eXBlfHxudWxsfSxwLnJlcXVpcmVkT3duZWQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPW51bGwsYz1xLnJvbGVbYV07cmV0dXJuIGMmJihiPXYuY2xvbmUoYy5vd25lZCkpLGJ9LHAucmVxdWlyZWRDb250ZXh0PWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1udWxsLGM9cS5yb2xlW2FdO3JldHVybiBjJiYoYj12LmNsb25lKGMuY29udGV4dCkpLGJ9LHAuaW1wbGljaXROb2Rlcz1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7dmFyIGI9bnVsbCxjPXEucm9sZVthXTtyZXR1cm4gYyYmYy5pbXBsaWNpdCYmKGI9di5jbG9uZShjLmltcGxpY2l0KSksYn0scC5pbXBsaWNpdFJvbGU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiLGMsZCxlPXEucm9sZTtmb3IoYiBpbiBlKWlmKGUuaGFzT3duUHJvcGVydHkoYikmJihjPWVbYl0sYy5pbXBsaWNpdCkpZm9yKHZhciBmPTAsZz1jLmltcGxpY2l0Lmxlbmd0aDtnPmY7ZisrKWlmKGQ9Yy5pbXBsaWNpdFtmXSx2Lm1hdGNoZXNTZWxlY3RvcihhLGQpKXJldHVybiBiO3JldHVybiBudWxsfSxyLkNvbG9yPWZ1bmN0aW9uKGEsYixjLGQpe3RoaXMucmVkPWEsdGhpcy5ncmVlbj1iLHRoaXMuYmx1ZT1jLHRoaXMuYWxwaGE9ZCx0aGlzLnRvSGV4U3RyaW5nPWZ1bmN0aW9uKCl7dmFyIGE9TWF0aC5yb3VuZCh0aGlzLnJlZCkudG9TdHJpbmcoMTYpLGI9TWF0aC5yb3VuZCh0aGlzLmdyZWVuKS50b1N0cmluZygxNiksYz1NYXRoLnJvdW5kKHRoaXMuYmx1ZSkudG9TdHJpbmcoMTYpO3JldHVybiIjIisodGhpcy5yZWQ+MTUuNT9hOiIwIithKSsodGhpcy5ncmVlbj4xNS41P2I6IjAiK2IpKyh0aGlzLmJsdWU+MTUuNT9jOiIwIitjKX07dmFyIGU9L15yZ2JcKChcZCspLCAoXGQrKSwgKFxkKylcKSQvLGY9L15yZ2JhXCgoXGQrKSwgKFxkKyksIChcZCspLCAoXGQqKFwuXGQrKT8pXCkvO3RoaXMucGFyc2VSZ2JTdHJpbmc9ZnVuY3Rpb24oYSl7dmFyIGI9YS5tYXRjaChlKTtyZXR1cm4gYj8odGhpcy5yZWQ9cGFyc2VJbnQoYlsxXSwxMCksdGhpcy5ncmVlbj1wYXJzZUludChiWzJdLDEwKSx0aGlzLmJsdWU9cGFyc2VJbnQoYlszXSwxMCksdm9pZCh0aGlzLmFscGhhPTEpKTooYj1hLm1hdGNoKGYpLGI/KHRoaXMucmVkPXBhcnNlSW50KGJbMV0sMTApLHRoaXMuZ3JlZW49cGFyc2VJbnQoYlsyXSwxMCksdGhpcy5ibHVlPXBhcnNlSW50KGJbM10sMTApLHZvaWQodGhpcy5hbHBoYT1wYXJzZUZsb2F0KGJbNF0pKSk6dm9pZCAwKX0sdGhpcy5nZXRSZWxhdGl2ZUx1bWluYW5jZT1mdW5jdGlvbigpe3ZhciBhPXRoaXMucmVkLzI1NSxiPXRoaXMuZ3JlZW4vMjU1LGM9dGhpcy5ibHVlLzI1NSxkPS4wMzkyOD49YT9hLzEyLjkyOk1hdGgucG93KChhKy4wNTUpLzEuMDU1LDIuNCksZT0uMDM5Mjg+PWI/Yi8xMi45MjpNYXRoLnBvdygoYisuMDU1KS8xLjA1NSwyLjQpLGY9LjAzOTI4Pj1jP2MvMTIuOTI6TWF0aC5wb3coKGMrLjA1NSkvMS4wNTUsMi40KTtyZXR1cm4uMjEyNipkKy43MTUyKmUrLjA3MjIqZn19LHIuZmxhdHRlbkNvbG9ycz1mdW5jdGlvbihhLGIpe3ZhciBjPWEuYWxwaGEsZD0oMS1jKSpiLnJlZCtjKmEucmVkLGU9KDEtYykqYi5ncmVlbitjKmEuZ3JlZW4sZj0oMS1jKSpiLmJsdWUrYyphLmJsdWUsZz1hLmFscGhhK2IuYWxwaGEqKDEtYS5hbHBoYSk7cmV0dXJuIG5ldyByLkNvbG9yKGQsZSxmLGcpfSxyLmdldENvbnRyYXN0PWZ1bmN0aW9uKGEsYil7aWYoIWJ8fCFhKXJldHVybiBudWxsO2IuYWxwaGE8MSYmKGI9ci5mbGF0dGVuQ29sb3JzKGIsYSkpO3ZhciBjPWEuZ2V0UmVsYXRpdmVMdW1pbmFuY2UoKSxkPWIuZ2V0UmVsYXRpdmVMdW1pbmFuY2UoKTtyZXR1cm4oTWF0aC5tYXgoZCxjKSsuMDUpLyhNYXRoLm1pbihkLGMpKy4wNSl9LHIuaGFzVmFsaWRDb250cmFzdFJhdGlvPWZ1bmN0aW9uKGEsYixjLGQpe3ZhciBlPXIuZ2V0Q29udHJhc3QoYSxiKSxmPWQmJk1hdGguY2VpbCg3MipjKS85NjwxNHx8IWQmJk1hdGguY2VpbCg3MipjKS85NjwxODtyZXR1cm57aXNWYWxpZDpmJiZlPj00LjV8fCFmJiZlPj0zLGNvbnRyYXN0UmF0aW86ZX19LHMuaXNPcGFxdWU9ZnVuY3Rpb24oYSl7dmFyIGI9YyhhKTtyZXR1cm4gbnVsbD09PWJ8fDE9PT1iLmFscGhhPyEwOiExfTt2YXIgdz1mdW5jdGlvbihjLGQpe2Zvcih2YXIgZSxmLGcsaCxpLGosayxsPVtdLG09ITEsbj1jLG89YS5nZXRDb21wdXRlZFN0eWxlKG4pO251bGwhPT1uJiYoIXMuaXNPcGFxdWUobil8fDA9PT1wYXJzZUludChvLmdldFByb3BlcnR5VmFsdWUoImhlaWdodCIpLDEwKSk7KWc9by5nZXRQcm9wZXJ0eVZhbHVlKCJwb3NpdGlvbiIpLGg9by5nZXRQcm9wZXJ0eVZhbHVlKCJ0b3AiKSxpPW8uZ2V0UHJvcGVydHlWYWx1ZSgiYm90dG9tIiksaj1vLmdldFByb3BlcnR5VmFsdWUoImxlZnQiKSxrPW8uZ2V0UHJvcGVydHlWYWx1ZSgicmlnaHQiKSwoInN0YXRpYyIhPT1nJiYicmVsYXRpdmUiIT09Z3x8InJlbGF0aXZlIj09PWcmJigiYXV0byIhPT1qfHwiYXV0byIhPT1rfHwiYXV0byIhPT1ofHwiYXV0byIhPT1pKSkmJihtPSEwKSxuPW4ucGFyZW50RWxlbWVudCxudWxsIT09biYmKG89YS5nZXRDb21wdXRlZFN0eWxlKG4pLDAhPT1wYXJzZUludChvLmdldFByb3BlcnR5VmFsdWUoImhlaWdodCIpLDEwKSYmbC5wdXNoKG4pKTtpZihtJiZzLnN1cHBvcnRzRWxlbWVudHNGcm9tUG9pbnQoYikpe2lmKGU9cy5lbGVtZW50c0Zyb21Qb2ludChiLE1hdGguY2VpbChkLmxlZnQrMSksTWF0aC5jZWlsKGQudG9wKzEpKSxmPWUuaW5kZXhPZihjKSwtMT09PWYpcmV0dXJuIG51bGw7ZSYmZjxlLmxlbmd0aC0xJiYobD1lLnNsaWNlKGYrMSkpfXJldHVybiBsfTtyLmdldEJhY2tncm91bmRDb2xvcj1mdW5jdGlvbihhLGIpe3ZhciBkLGUsZj1jKGEpO2lmKCFifHxudWxsIT09ZiYmMD09PWYuYWxwaGF8fGIucHVzaChhKSxudWxsPT09Znx8MT09PWYuYWxwaGEpcmV0dXJuIGY7YS5zY3JvbGxJbnRvVmlldygpO3ZhciBnPWEuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksaD1hLGk9W3tjb2xvcjpmLG5vZGU6YX1dLGo9dyhoLGcpO2lmKCFqKXJldHVybiBudWxsO2Zvcig7MSE9PWYuYWxwaGE7KXtpZihkPWouc2hpZnQoKSwhZCYmIkhUTUwiIT09aC50YWdOYW1lKXJldHVybiBudWxsO2lmKGR8fCJIVE1MIiE9PWgudGFnTmFtZSl7aWYoIXMudmlzdWFsbHlDb250YWlucyhhLGQpKXJldHVybiBudWxsO2lmKGU9YyhkKSwhYnx8bnVsbCE9PWUmJjA9PT1lLmFscGhhfHxiLnB1c2goZCksbnVsbD09PWUpcmV0dXJuIG51bGx9ZWxzZSBlPW5ldyByLkNvbG9yKDI1NSwyNTUsMjU1LDEpO2g9ZCxmPWUsaS5wdXNoKHtjb2xvcjpmLG5vZGU6aH0pfWZvcih2YXIgaz1pLnBvcCgpLGw9ay5jb2xvcjt2b2lkIDAhPT0oaz1pLnBvcCgpKTspbD1yLmZsYXR0ZW5Db2xvcnMoay5jb2xvcixsKTtyZXR1cm4gbH0sci5nZXRGb3JlZ3JvdW5kQ29sb3I9ZnVuY3Rpb24oYil7dmFyIGM9YS5nZXRDb21wdXRlZFN0eWxlKGIpLGQ9bmV3IHIuQ29sb3I7ZC5wYXJzZVJnYlN0cmluZyhjLmdldFByb3BlcnR5VmFsdWUoImNvbG9yIikpO3ZhciBlPWMuZ2V0UHJvcGVydHlWYWx1ZSgib3BhY2l0eSIpO2lmKGQuYWxwaGE9ZC5hbHBoYSplLDE9PT1kLmFscGhhKXJldHVybiBkO3ZhciBmPXIuZ2V0QmFja2dyb3VuZENvbG9yKGIpO3JldHVybiBudWxsPT09Zj9udWxsOnIuZmxhdHRlbkNvbG9ycyhkLGYpfSxzLnN1cHBvcnRzRWxlbWVudHNGcm9tUG9pbnQ9ZnVuY3Rpb24oYSl7dmFyIGI9YS5jcmVhdGVFbGVtZW50KCJ4Iik7cmV0dXJuIGIuc3R5bGUuY3NzVGV4dD0icG9pbnRlci1ldmVudHM6YXV0byIsImF1dG8iPT09Yi5zdHlsZS5wb2ludGVyRXZlbnRzfHwhIWEubXNFbGVtZW50c0Zyb21Qb2ludH0scy5lbGVtZW50c0Zyb21Qb2ludD1mdW5jdGlvbihhLGIsYyl7dmFyIGQsZSxmLGc9W10saD1bXTtpZihhLm1zRWxlbWVudHNGcm9tUG9pbnQpe3ZhciBpPWEubXNFbGVtZW50c0Zyb21Qb2ludChiLGMpO3JldHVybiBpP0FycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGkpOm51bGw7Cn1mb3IoOyhkPWEuZWxlbWVudEZyb21Qb2ludChiLGMpKSYmLTE9PT1nLmluZGV4T2YoZCkmJm51bGwhPT1kJiYoZy5wdXNoKGQpLGgucHVzaCh7dmFsdWU6ZC5zdHlsZS5nZXRQcm9wZXJ0eVZhbHVlKCJwb2ludGVyLWV2ZW50cyIpLHByaW9yaXR5OmQuc3R5bGUuZ2V0UHJvcGVydHlQcmlvcml0eSgicG9pbnRlci1ldmVudHMiKX0pLGQuc3R5bGUuc2V0UHJvcGVydHkoInBvaW50ZXItZXZlbnRzIiwibm9uZSIsImltcG9ydGFudCIpLCFzLmlzT3BhcXVlKGQpKTspO2ZvcihlPWgubGVuZ3RoO2Y9aFstLWVdOylnW2VdLnN0eWxlLnNldFByb3BlcnR5KCJwb2ludGVyLWV2ZW50cyIsZi52YWx1ZT9mLnZhbHVlOiIiLGYucHJpb3JpdHkpO3JldHVybiBnfSxzLmZpbmRVcD1mdW5jdGlvbihhLGMpeyJ1c2Ugc3RyaWN0Ijt2YXIgZCxlPWIucXVlcnlTZWxlY3RvckFsbChjKSxmPWUubGVuZ3RoO2lmKCFmKXJldHVybiBudWxsO2ZvcihlPXYudG9BcnJheShlKSxkPWEucGFyZW50Tm9kZTtkJiYtMT09PWUuaW5kZXhPZihkKTspZD1kLnBhcmVudE5vZGU7cmV0dXJuIGR9LHMuZ2V0RWxlbWVudEJ5UmVmZXJlbmNlPWZ1bmN0aW9uKGEsYyl7InVzZSBzdHJpY3QiO3ZhciBkLGU9YS5nZXRBdHRyaWJ1dGUoYyksZj1iO2lmKGUmJiIjIj09PWUuY2hhckF0KDApKXtpZihlPWUuc3Vic3RyaW5nKDEpLGQ9Zi5nZXRFbGVtZW50QnlJZChlKSlyZXR1cm4gZDtpZihkPWYuZ2V0RWxlbWVudHNCeU5hbWUoZSksZC5sZW5ndGgpcmV0dXJuIGRbMF19cmV0dXJuIG51bGx9LHMuZ2V0RWxlbWVudENvb3JkaW5hdGVzPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1zLmdldFNjcm9sbE9mZnNldChiKSxkPWMubGVmdCxlPWMudG9wLGY9YS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtyZXR1cm57dG9wOmYudG9wK2UscmlnaHQ6Zi5yaWdodCtkLGJvdHRvbTpmLmJvdHRvbStlLGxlZnQ6Zi5sZWZ0K2Qsd2lkdGg6Zi5yaWdodC1mLmxlZnQsaGVpZ2h0OmYuYm90dG9tLWYudG9wfX0scy5nZXRTY3JvbGxPZmZzZXQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO2lmKCFhLm5vZGVUeXBlJiZhLmRvY3VtZW50JiYoYT1hLmRvY3VtZW50KSw5PT09YS5ub2RlVHlwZSl7dmFyIGI9YS5kb2N1bWVudEVsZW1lbnQsYz1hLmJvZHk7cmV0dXJue2xlZnQ6YiYmYi5zY3JvbGxMZWZ0fHxjJiZjLnNjcm9sbExlZnR8fDAsdG9wOmImJmIuc2Nyb2xsVG9wfHxjJiZjLnNjcm9sbFRvcHx8MH19cmV0dXJue2xlZnQ6YS5zY3JvbGxMZWZ0LHRvcDphLnNjcm9sbFRvcH19LHMuZ2V0Vmlld3BvcnRTaXplPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYixjPWEuZG9jdW1lbnQsZD1jLmRvY3VtZW50RWxlbWVudDtyZXR1cm4gYS5pbm5lcldpZHRoP3t3aWR0aDphLmlubmVyV2lkdGgsaGVpZ2h0OmEuaW5uZXJIZWlnaHR9OmQ/e3dpZHRoOmQuY2xpZW50V2lkdGgsaGVpZ2h0OmQuY2xpZW50SGVpZ2h0fTooYj1jLmJvZHkse3dpZHRoOmIuY2xpZW50V2lkdGgsaGVpZ2h0OmIuY2xpZW50SGVpZ2h0fSl9LHMuaWRyZWZzPWZ1bmN0aW9uKGEsYyl7InVzZSBzdHJpY3QiO3ZhciBkLGUsZj1iLGc9W10saD1hLmdldEF0dHJpYnV0ZShjKTtpZihoKWZvcihoPXYudG9rZW5MaXN0KGgpLGQ9MCxlPWgubGVuZ3RoO2U+ZDtkKyspZy5wdXNoKGYuZ2V0RWxlbWVudEJ5SWQoaFtkXSkpO3JldHVybiBnfSxzLmlzRm9jdXNhYmxlPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtpZighYXx8YS5kaXNhYmxlZHx8IXMuaXNWaXNpYmxlKGEpJiYiQVJFQSIhPT1hLm5vZGVOYW1lKXJldHVybiExO3N3aXRjaChhLm5vZGVOYW1lKXtjYXNlIkEiOmNhc2UiQVJFQSI6aWYoYS5ocmVmKXJldHVybiEwO2JyZWFrO2Nhc2UiSU5QVVQiOnJldHVybiJoaWRkZW4iIT09YS50eXBlO2Nhc2UiVEVYVEFSRUEiOmNhc2UiU0VMRUNUIjpjYXNlIkRFVEFJTFMiOmNhc2UiQlVUVE9OIjpyZXR1cm4hMH12YXIgYj1hLmdldEF0dHJpYnV0ZSgidGFiaW5kZXgiKTtyZXR1cm4gYiYmIWlzTmFOKHBhcnNlSW50KGIsMTApKT8hMDohMX0scy5pc0hUTUw1PWZ1bmN0aW9uKGEpe3ZhciBiPWEuZG9jdHlwZTtyZXR1cm4gbnVsbD09PWI/ITE6Imh0bWwiPT09Yi5uYW1lJiYhYi5wdWJsaWNJZCYmIWIuc3lzdGVtSWR9LHMuaXNOb2RlPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4gYSBpbnN0YW5jZW9mIE5vZGV9LHMuaXNPZmZzY3JlZW49ZnVuY3Rpb24oYyl7InVzZSBzdHJpY3QiO3ZhciBkLGU9Yi5kb2N1bWVudEVsZW1lbnQsZj1hLmdldENvbXB1dGVkU3R5bGUoYi5ib2R5fHxlKS5nZXRQcm9wZXJ0eVZhbHVlKCJkaXJlY3Rpb24iKSxnPXMuZ2V0RWxlbWVudENvb3JkaW5hdGVzKGMpO2lmKGcuYm90dG9tPDApcmV0dXJuITA7aWYoImx0ciI9PT1mKXtpZihnLnJpZ2h0PDApcmV0dXJuITB9ZWxzZSBpZihkPU1hdGgubWF4KGUuc2Nyb2xsV2lkdGgscy5nZXRWaWV3cG9ydFNpemUoYSkud2lkdGgpLGcubGVmdD5kKXJldHVybiEwO3JldHVybiExfSxzLmlzVmlzaWJsZT1mdW5jdGlvbihiLGMsZSl7InVzZSBzdHJpY3QiO3ZhciBmLGc9Yi5ub2RlTmFtZSxoPWIucGFyZW50Tm9kZTtyZXR1cm4gOT09PWIubm9kZVR5cGU/ITA6KGY9YS5nZXRDb21wdXRlZFN0eWxlKGIsbnVsbCksbnVsbD09PWY/ITE6Im5vbmUiPT09Zi5nZXRQcm9wZXJ0eVZhbHVlKCJkaXNwbGF5Iil8fCJTVFlMRSI9PT1nfHwiU0NSSVBUIj09PWd8fCFjJiZkKGYuZ2V0UHJvcGVydHlWYWx1ZSgiY2xpcCIpKXx8IWUmJigiaGlkZGVuIj09PWYuZ2V0UHJvcGVydHlWYWx1ZSgidmlzaWJpbGl0eSIpfHwhYyYmcy5pc09mZnNjcmVlbihiKSl8fGMmJiJ0cnVlIj09PWIuZ2V0QXR0cmlidXRlKCJhcmlhLWhpZGRlbiIpPyExOmg/cy5pc1Zpc2libGUoaCxjLCEwKTohMSl9LHMuaXNWaXN1YWxDb250ZW50PWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijtzd2l0Y2goYS50YWdOYW1lLnRvVXBwZXJDYXNlKCkpe2Nhc2UiSU1HIjpjYXNlIklGUkFNRSI6Y2FzZSJPQkpFQ1QiOmNhc2UiVklERU8iOmNhc2UiQVVESU8iOmNhc2UiQ0FOVkFTIjpjYXNlIlNWRyI6Y2FzZSJNQVRIIjpjYXNlIkJVVFRPTiI6Y2FzZSJTRUxFQ1QiOmNhc2UiVEVYVEFSRUEiOmNhc2UiS0VZR0VOIjpjYXNlIlBST0dSRVNTIjpjYXNlIk1FVEVSIjpyZXR1cm4hMDtjYXNlIklOUFVUIjpyZXR1cm4iaGlkZGVuIiE9PWEudHlwZTtkZWZhdWx0OnJldHVybiExfX0scy52aXN1YWxseUNvbnRhaW5zPWZ1bmN0aW9uKGIsYyl7dmFyIGQ9Yi5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSxlPWMuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksZj1lLnRvcCxnPWUubGVmdCxoPXt0b3A6Zi1jLnNjcm9sbFRvcCxib3R0b206Zi1jLnNjcm9sbFRvcCtjLnNjcm9sbEhlaWdodCxsZWZ0OmctYy5zY3JvbGxMZWZ0LHJpZ2h0OmctYy5zY3JvbGxMZWZ0K2Muc2Nyb2xsV2lkdGh9O2lmKGQubGVmdDxoLmxlZnQmJmQubGVmdDxlLmxlZnR8fGQudG9wPGgudG9wJiZkLnRvcDxlLnRvcHx8ZC5yaWdodD5oLnJpZ2h0JiZkLnJpZ2h0PmUucmlnaHR8fGQuYm90dG9tPmguYm90dG9tJiZkLmJvdHRvbT5lLmJvdHRvbSlyZXR1cm4hMTt2YXIgaT1hLmdldENvbXB1dGVkU3R5bGUoYyk7cmV0dXJuIGQucmlnaHQ+ZS5yaWdodHx8ZC5ib3R0b20+ZS5ib3R0b20/InNjcm9sbCI9PT1pLm92ZXJmbG93fHwiYXV0byI9PT1pLm92ZXJmbG93fHwiaGlkZGVuIj09PWkub3ZlcmZsb3d8fGMgaW5zdGFuY2VvZiBIVE1MQm9keUVsZW1lbnR8fGMgaW5zdGFuY2VvZiBIVE1MSHRtbEVsZW1lbnQ6ITB9LHMudmlzdWFsbHlPdmVybGFwcz1mdW5jdGlvbihiLGMpe3ZhciBkPWMuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksZT1kLnRvcCxmPWQubGVmdCxnPXt0b3A6ZS1jLnNjcm9sbFRvcCxib3R0b206ZS1jLnNjcm9sbFRvcCtjLnNjcm9sbEhlaWdodCxsZWZ0OmYtYy5zY3JvbGxMZWZ0LHJpZ2h0OmYtYy5zY3JvbGxMZWZ0K2Muc2Nyb2xsV2lkdGh9O2lmKGIubGVmdD5nLnJpZ2h0JiZiLmxlZnQ+ZC5yaWdodHx8Yi50b3A+Zy5ib3R0b20mJmIudG9wPmQuYm90dG9tfHxiLnJpZ2h0PGcubGVmdCYmYi5yaWdodDxkLmxlZnR8fGIuYm90dG9tPGcudG9wJiZiLmJvdHRvbTxkLnRvcClyZXR1cm4hMTt2YXIgaD1hLmdldENvbXB1dGVkU3R5bGUoYyk7cmV0dXJuIGIubGVmdD5kLnJpZ2h0fHxiLnRvcD5kLmJvdHRvbT8ic2Nyb2xsIj09PWgub3ZlcmZsb3d8fCJhdXRvIj09PWgub3ZlcmZsb3d8fGMgaW5zdGFuY2VvZiBIVE1MQm9keUVsZW1lbnR8fGMgaW5zdGFuY2VvZiBIVE1MSHRtbEVsZW1lbnQ6ITB9LHQuZ2V0Q2VsbFBvc2l0aW9uPWZ1bmN0aW9uKGEpe2Zvcih2YXIgYixjPXQudG9BcnJheShzLmZpbmRVcChhLCJ0YWJsZSIpKSxkPTA7ZDxjLmxlbmd0aDtkKyspaWYoY1tkXSYmKGI9Y1tkXS5pbmRleE9mKGEpLC0xIT09YikpcmV0dXJue3g6Yix5OmR9fSx0LmdldEhlYWRlcnM9ZnVuY3Rpb24oYSl7aWYoYS5nZXRBdHRyaWJ1dGUoImhlYWRlcnMiKSlyZXR1cm4gby5kb20uaWRyZWZzKGEsImhlYWRlcnMiKTtmb3IodmFyIGIsYz1bXSxkPW8udGFibGUudG9BcnJheShvLmRvbS5maW5kVXAoYSwidGFibGUiKSksZT1vLnRhYmxlLmdldENlbGxQb3NpdGlvbihhKSxmPWUueC0xO2Y+PTA7Zi0tKWI9ZFtlLnldW2ZdLG8udGFibGUuaXNSb3dIZWFkZXIoYikmJmMudW5zaGlmdChiKTtmb3IodmFyIGc9ZS55LTE7Zz49MDtnLS0pYj1kW2ddW2UueF0sYiYmby50YWJsZS5pc0NvbHVtbkhlYWRlcihiKSYmYy51bnNoaWZ0KGIpO3JldHVybiBjfSx0LmlzQ29sdW1uSGVhZGVyPWZ1bmN0aW9uKGEpe3ZhciBiPWEuZ2V0QXR0cmlidXRlKCJzY29wZSIpO2lmKCJjb2wiPT09YilyZXR1cm4hMDtpZihifHwiVEgiIT09YS5ub2RlTmFtZSlyZXR1cm4hMTtmb3IodmFyIGMsZD10LmdldENlbGxQb3NpdGlvbihhKSxlPXQudG9BcnJheShzLmZpbmRVcChhLCJ0YWJsZSIpKSxmPWVbZC55XSxnPTAsaD1mLmxlbmd0aDtoPmc7ZysrKWlmKGM9ZltnXSxjIT09YSYmdC5pc0RhdGFDZWxsKGMpKXJldHVybiExO3JldHVybiEwfSx0LmlzRGF0YUNlbGw9ZnVuY3Rpb24oYSl7cmV0dXJuIGEuY2hpbGRyZW4ubGVuZ3RofHxhLnRleHRDb250ZW50LnRyaW0oKT8iVEQiPT09YS5ub2RlTmFtZTohMX0sdC5pc0RhdGFUYWJsZT1mdW5jdGlvbihiKXt2YXIgYz1iLmdldEF0dHJpYnV0ZSgicm9sZSIpO2lmKCgicHJlc2VudGF0aW9uIj09PWN8fCJub25lIj09PWMpJiYhcy5pc0ZvY3VzYWJsZShiKSlyZXR1cm4hMTtpZigidHJ1ZSI9PT1iLmdldEF0dHJpYnV0ZSgiY29udGVudGVkaXRhYmxlIil8fHMuZmluZFVwKGIsJ1tjb250ZW50ZWRpdGFibGU9InRydWUiXScpKXJldHVybiEwO2lmKCJncmlkIj09PWN8fCJ0cmVlZ3JpZCI9PT1jfHwidGFibGUiPT09YylyZXR1cm4hMDtpZigibGFuZG1hcmsiPT09by5hcmlhLmdldFJvbGVUeXBlKGMpKXJldHVybiEwO2lmKCIwIj09PWIuZ2V0QXR0cmlidXRlKCJkYXRhdGFibGUiKSlyZXR1cm4hMTtpZihiLmdldEF0dHJpYnV0ZSgic3VtbWFyeSIpKXJldHVybiEwO2lmKGIudEhlYWR8fGIudEZvb3R8fGIuY2FwdGlvbilyZXR1cm4hMDtmb3IodmFyIGQ9MCxlPWIuY2hpbGRyZW4ubGVuZ3RoO2U+ZDtkKyspaWYoIkNPTEdST1VQIj09PWIuY2hpbGRyZW5bZF0ubm9kZU5hbWUpcmV0dXJuITA7Zm9yKHZhciBmLGcsaD0wLGk9Yi5yb3dzLmxlbmd0aCxqPSExLGs9MDtpPms7aysrKXtmPWIucm93c1trXTtmb3IodmFyIGw9MCxtPWYuY2VsbHMubGVuZ3RoO20+bDtsKyspe2lmKGc9Zi5jZWxsc1tsXSxqfHxnLm9mZnNldFdpZHRoPT09Zy5jbGllbnRXaWR0aCYmZy5vZmZzZXRIZWlnaHQ9PT1nLmNsaWVudEhlaWdodHx8KGo9ITApLGcuZ2V0QXR0cmlidXRlKCJzY29wZSIpfHxnLmdldEF0dHJpYnV0ZSgiaGVhZGVycyIpfHxnLmdldEF0dHJpYnV0ZSgiYWJiciIpKXJldHVybiEwO2lmKCJUSCI9PT1nLm5vZGVOYW1lKXJldHVybiEwO2lmKDE9PT1nLmNoaWxkcmVuLmxlbmd0aCYmIkFCQlIiPT09Zy5jaGlsZHJlblswXS5ub2RlTmFtZSlyZXR1cm4hMDtoKyt9fWlmKGIuZ2V0RWxlbWVudHNCeVRhZ05hbWUoInRhYmxlIikubGVuZ3RoKXJldHVybiExO2lmKDI+aSlyZXR1cm4hMTt2YXIgbj1iLnJvd3NbTWF0aC5jZWlsKGkvMildO2lmKDE9PT1uLmNlbGxzLmxlbmd0aCYmMT09PW4uY2VsbHNbMF0uY29sU3BhbilyZXR1cm4hMTtpZihuLmNlbGxzLmxlbmd0aD49NSlyZXR1cm4hMDtpZihqKXJldHVybiEwO3ZhciBwLHE7Zm9yKGs9MDtpPms7aysrKXtpZihmPWIucm93c1trXSxwJiZwIT09YS5nZXRDb21wdXRlZFN0eWxlKGYpLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtY29sb3IiKSlyZXR1cm4hMDtpZihwPWEuZ2V0Q29tcHV0ZWRTdHlsZShmKS5nZXRQcm9wZXJ0eVZhbHVlKCJiYWNrZ3JvdW5kLWNvbG9yIikscSYmcSE9PWEuZ2V0Q29tcHV0ZWRTdHlsZShmKS5nZXRQcm9wZXJ0eVZhbHVlKCJiYWNrZ3JvdW5kLWltYWdlIikpcmV0dXJuITA7cT1hLmdldENvbXB1dGVkU3R5bGUoZikuZ2V0UHJvcGVydHlWYWx1ZSgiYmFja2dyb3VuZC1pbWFnZSIpfXJldHVybiBpPj0yMD8hMDpzLmdldEVsZW1lbnRDb29yZGluYXRlcyhiKS53aWR0aD4uOTUqcy5nZXRWaWV3cG9ydFNpemUoYSkud2lkdGg/ITE6MTA+aD8hMTpiLnF1ZXJ5U2VsZWN0b3IoIm9iamVjdCwgZW1iZWQsIGlmcmFtZSwgYXBwbGV0Iik/ITE6ITB9LHQuaXNIZWFkZXI9ZnVuY3Rpb24oYSl7cmV0dXJuIHQuaXNDb2x1bW5IZWFkZXIoYSl8fHQuaXNSb3dIZWFkZXIoYSk/ITA6YS5pZD8hIWIucXVlcnlTZWxlY3RvcignW2hlYWRlcnN+PSInK3YuZXNjYXBlU2VsZWN0b3IoYS5pZCkrJyJdJyk6ITF9LHQuaXNSb3dIZWFkZXI9ZnVuY3Rpb24oYSl7dmFyIGI9YS5nZXRBdHRyaWJ1dGUoInNjb3BlIik7aWYoInJvdyI9PT1iKXJldHVybiEwO2lmKGJ8fCJUSCIhPT1hLm5vZGVOYW1lKXJldHVybiExO2lmKHQuaXNDb2x1bW5IZWFkZXIoYSkpcmV0dXJuITE7Zm9yKHZhciBjLGQ9dC5nZXRDZWxsUG9zaXRpb24oYSksZT10LnRvQXJyYXkocy5maW5kVXAoYSwidGFibGUiKSksZj0wLGc9ZS5sZW5ndGg7Zz5mO2YrKylpZihjPWVbZl1bZC54XSxjIT09YSYmdC5pc0RhdGFDZWxsKGMpKXJldHVybiExO3JldHVybiEwfSx0LnRvQXJyYXk9ZnVuY3Rpb24oYSl7Zm9yKHZhciBiPVtdLGM9YS5yb3dzLGQ9MCxlPWMubGVuZ3RoO2U+ZDtkKyspe3ZhciBmPWNbZF0uY2VsbHM7YltkXT1iW2RdfHxbXTtmb3IodmFyIGc9MCxoPTAsaT1mLmxlbmd0aDtpPmg7aCsrKWZvcih2YXIgaj0wO2o8ZltoXS5jb2xTcGFuO2orKyl7Zm9yKHZhciBrPTA7azxmW2hdLnJvd1NwYW47aysrKXtmb3IoYltkK2tdPWJbZCtrXXx8W107YltkK2tdW2ddOylnKys7YltkK2tdW2ddPWZbaF19ZysrfX1yZXR1cm4gYn07dmFyIHg9e3N1Ym1pdDoiU3VibWl0IixyZXNldDoiUmVzZXQifSx5PVsidGV4dCIsInNlYXJjaCIsInRlbCIsInVybCIsImVtYWlsIiwiZGF0ZSIsInRpbWUiLCJudW1iZXIiLCJyYW5nZSIsImNvbG9yIl0sej1bImEiLCJlbSIsInN0cm9uZyIsInNtYWxsIiwibWFyayIsImFiYnIiLCJkZm4iLCJpIiwiYiIsInMiLCJ1IiwiY29kZSIsInZhciIsInNhbXAiLCJrYmQiLCJzdXAiLCJzdWIiLCJxIiwiY2l0ZSIsInNwYW4iLCJiZG8iLCJiZGkiLCJiciIsIndiciIsImlucyIsImRlbCIsImltZyIsImVtYmVkIiwib2JqZWN0IiwiaWZyYW1lIiwibWFwIiwiYXJlYSIsInNjcmlwdCIsIm5vc2NyaXB0IiwicnVieSIsInZpZGVvIiwiYXVkaW8iLCJpbnB1dCIsInRleHRhcmVhIiwic2VsZWN0IiwiYnV0dG9uIiwibGFiZWwiLCJvdXRwdXQiLCJkYXRhbGlzdCIsImtleWdlbiIsInByb2dyZXNzIiwiY29tbWFuZCIsImNhbnZhcyIsInRpbWUiLCJtZXRlciJdO3JldHVybiB1LmFjY2Vzc2libGVUZXh0PWZ1bmN0aW9uKGEpe2Z1bmN0aW9uIGIoYSxiLGMpe3ZhciBpPSIiO2lmKGgoYSkmJihpPWQoYSwhMSwhMSl8fCIiLG4oaSkpKXJldHVybiBpO2lmKCJGSUdVUkUiPT09YS5ub2RlTmFtZSYmKGk9ayhhLCJmaWdjYXB0aW9uIiksbihpKSkpcmV0dXJuIGk7aWYoIlRBQkxFIj09PWEubm9kZU5hbWUpe2lmKGk9ayhhLCJjYXB0aW9uIiksbihpKSlyZXR1cm4gaTtpZihpPWEuZ2V0QXR0cmlidXRlKCJ0aXRsZSIpfHxhLmdldEF0dHJpYnV0ZSgic3VtbWFyeSIpfHwiIixuKGkpKXJldHVybiBpfWlmKG0oYSkpcmV0dXJuIGEuZ2V0QXR0cmlidXRlKCJhbHQiKXx8IiI7aWYoZyhhKSYmIWMpe2lmKGYoYSkpcmV0dXJuIGEudmFsdWV8fGEudGl0bGV8fHhbYS50eXBlXXx8IiI7dmFyIGo9ZShhKTtpZihqKXJldHVybiBvKGosYiwhMCl9cmV0dXJuIiJ9ZnVuY3Rpb24gYyhhLGIsYyl7cmV0dXJuIWImJmEuaGFzQXR0cmlidXRlKCJhcmlhLWxhYmVsbGVkYnkiKT91LnNhbml0aXplKHMuaWRyZWZzKGEsImFyaWEtbGFiZWxsZWRieSIpLm1hcChmdW5jdGlvbihiKXtyZXR1cm4gYT09PWImJnEucG9wKCksbyhiLCEwLGEhPT1iKX0pLmpvaW4oIiAiKSk6YyYmbChhKXx8IWEuaGFzQXR0cmlidXRlKCJhcmlhLWxhYmVsIik/IiI6dS5zYW5pdGl6ZShhLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbCIpKX1mdW5jdGlvbiBkKGEsYixjKXtmb3IodmFyIGQsZT1hLmNoaWxkTm9kZXMsZj0iIixnPTA7ZzxlLmxlbmd0aDtnKyspZD1lW2ddLDM9PT1kLm5vZGVUeXBlP2YrPWQudGV4dENvbnRlbnQ6MT09PWQubm9kZVR5cGUmJigtMT09PXouaW5kZXhPZihkLm5vZGVOYW1lLnRvTG93ZXJDYXNlKCkpJiYoZis9IiAiKSxmKz1vKGVbZ10sYixjKSk7cmV0dXJuIGZ9ZnVuY3Rpb24gbyhhLGUsZil7InVzZSBzdHJpY3QiO3ZhciBnPSIiO2lmKG51bGw9PT1hfHwhcy5pc1Zpc2libGUoYSwhMCl8fC0xIT09cS5pbmRleE9mKGEpKXJldHVybiIiO3EucHVzaChhKTt2YXIgaD1hLmdldEF0dHJpYnV0ZSgicm9sZSIpO3JldHVybiBnKz1jKGEsZSxmKSxuKGcpP2c6KGc9YihhLGUsZiksbihnKT9nOmYmJihnKz1qKGEpLG4oZykpP2c6aShhKXx8aCYmLTE9PT1wLmdldFJvbGVzV2l0aE5hbWVGcm9tQ29udGVudHMoKS5pbmRleE9mKGgpfHwoZz1kKGEsZSxmKSwhbihnKSk/YS5oYXNBdHRyaWJ1dGUoInRpdGxlIik/YS5nZXRBdHRyaWJ1dGUoInRpdGxlIik6IiI6Zyl9dmFyIHE9W107cmV0dXJuIHUuc2FuaXRpemUobyhhKSl9LHUubGFiZWw9ZnVuY3Rpb24oYSl7dmFyIGMsZDtyZXR1cm4oZD1wLmxhYmVsKGEpKT9kOmEuaWQmJihjPWIucXVlcnlTZWxlY3RvcignbGFiZWxbZm9yPSInK3YuZXNjYXBlU2VsZWN0b3IoYS5pZCkrJyJdJyksZD1jJiZ1LnZpc2libGUoYywhMCkpP2Q6KGM9cy5maW5kVXAoYSwibGFiZWwiKSxkPWMmJnUudmlzaWJsZShjLCEwKSxkP2Q6bnVsbCl9LHUuc2FuaXRpemU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3JldHVybiBhLnJlcGxhY2UoL1xyXG4vZywiXG4iKS5yZXBsYWNlKC9cdTAwQTAvZywiICIpLnJlcGxhY2UoL1tcc117Mix9L2csIiAiKS50cmltKCl9LHUudmlzaWJsZT1mdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiO3ZhciBkLGUsZixnPWEuY2hpbGROb2RlcyxoPWcubGVuZ3RoLGk9IiI7Zm9yKGQ9MDtoPmQ7ZCsrKWU9Z1tkXSwzPT09ZS5ub2RlVHlwZT8oZj1lLm5vZGVWYWx1ZSxmJiZzLmlzVmlzaWJsZShhLGIpJiYoaSs9ZS5ub2RlVmFsdWUpKTpjfHwoaSs9dS52aXNpYmxlKGUsYikpO3JldHVybiB1LnNhbml0aXplKGkpfSx2LnRvQXJyYXk9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3JldHVybiBBcnJheS5wcm90b3R5cGUuc2xpY2UuY2FsbChhKX0sdi50b2tlbkxpc3Q9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3JldHVybiBhLnRyaW0oKS5yZXBsYWNlKC9cc3syLH0vZywiICIpLnNwbGl0KCIgIil9LG99KCl9KSxTLnZlcnNpb249IjEuMS4xIn0od2luZG93LHdpbmRvdy5kb2N1bWVudCk7","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");
 
 // 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 new Promise((resolve, reject) => {
-    axe.a11yCheck(document, resolve);
+  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'
+      ]
+    }
   });
 }
 
@@ -3034,22 +4127,25 @@
 
   afterPass(options) {
     const driver = options.driver;
+    const expression = `(function () {
+      ${axe};
+      return (${runA11yChecks.toString()}());
+    })()`;
 
     return driver
-        .evaluateAsync(`${axe};(${runA11yChecks.toString()}())`)
+        .evaluateAsync(expression)
         .then(returnedValue => {
           if (!returnedValue) {
-            this.artifact = Accessibility._errorAccessibility('Unable to parse axe results');
-            return;
+            return Accessibility._errorAccessibility('Unable to parse axe results');
           }
 
           if (returnedValue.error) {
-            this.artifact = Accessibility._errorAccessibility(returnedValue.error);
-          } else {
-            this.artifact = returnedValue;
+            return Accessibility._errorAccessibility(returnedValue.error);
           }
+
+          return returnedValue;
         }, _ => {
-          this.artifact = Accessibility._errorAccessibility('Axe results timed out');
+          return Accessibility._errorAccessibility('Axe results timed out');
         });
   }
 }
@@ -3057,7 +4153,7 @@
 module.exports = Accessibility;
 
 }).call(this,require("buffer").Buffer)
-},{"./gatherer":"./gatherers/gatherer","buffer":194}],"./gatherers/cache-contents":[function(require,module,exports){
+},{"./gatherer":18,"buffer":200}],"./gatherers/cache-contents":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -3120,19 +4216,18 @@
         .evaluateAsync(`(${getCacheContents.toString()}())`)
         .then(returnedValue => {
           if (!returnedValue) {
-            this.artifact = CacheContents._error('Unable to retrieve cache contents');
-            return;
+            return CacheContents._error('Unable to retrieve cache contents');
           }
-          this.artifact = returnedValue;
+          return returnedValue;
         }, _ => {
-          this.artifact = CacheContents._error('Unable to retrieve cache contents');
+          return CacheContents._error('Unable to retrieve cache contents');
         });
   }
 }
 
 module.exports = CacheContents;
 
-},{"./gatherer":"./gatherers/gatherer"}],"./gatherers/content-width":[function(require,module,exports){
+},{"./gatherer":18}],"./gatherers/content-width":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -3173,88 +4268,27 @@
     return driver.evaluateAsync(`(${getContentWidth.toString()}())`)
 
     .then(returnedValue => {
-      this.artifact = returnedValue;
+      if (!Number.isFinite(returnedValue.scrollWidth) ||
+          !Number.isFinite(returnedValue.viewportWidth)) {
+        throw new Error(`ContentWidth results were not numeric: ${JSON.stringify(returnedValue)}`);
+      }
+
+      return returnedValue;
     }, _ => {
-      this.artifact = {
+      return {
         scrollWidth: -1,
         viewportWidth: -1
       };
-      return;
     });
   }
 }
 
 module.exports = ContentWidth;
 
-},{"./gatherer":"./gatherers/gatherer"}],"./gatherers/gatherer":[function(require,module,exports){
+},{"./gatherer":18}],"./gatherers/css-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.
- */
-'use strict';
-
-/**
- * Base class for all gatherers; defines pass lifecycle methods.
- */
-class Gatherer {
-
-  constructor() {
-    this.artifact = {};
-  }
-
-  /**
-   * @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
-   */
-  afterPass(options, loadData) { }
-
-  /* eslint-enable no-unused-vars */
-
-}
-
-module.exports = Gatherer;
-
-},{}],"./gatherers/geolocation-on-start":[function(require,module,exports){
-/**
- * @license
- * Copyright 2016 Google Inc. All rights reserved.
+ * 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.
@@ -3273,49 +4307,783 @@
 const Gatherer = require('./gatherer');
 
 /**
- * @fileoverview Tests whether the page attempts to request geolocation on page load. This often
- * represents a poor user experience, since it lacks context. As such, if the page requests
- * geolocation the gatherer will intercept the call and mark a boolean flag to true. The audit that
- * corresponds with this gatherer then checks for the flag.
- * @author Paul Lewis
+ * @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'));
+  }
+
+  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);
+    }).catch(err => {
+      return {
+        rawValue: -1,
+        debugString: err,
+      };
+    });
+  }
+}
+
+module.exports = CSSUsage;
+
+},{"./gatherer":18}],"./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.
  */
 
-/* global navigator, window */
+/**
+ * @fileoverview Tests whether the page is using passive event listeners.
+ */
 
-/* istanbul ignore next */
-function overrideGeo() {
-  window.__didNotCallGeo = true;
-  // Override the geo functions so that if they're called they're intercepted and we know about it.
-  navigator.geolocation.getCurrentPosition =
-  navigator.geolocation.watchPosition = function() {
-    window.__didNotCallGeo = false;
-  };
+'use strict';
+
+const Gatherer = require('../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);
+      });
+    });
+  }
+
+  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.<EventListener>>}
+   * @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) {
+    return nodes.reduce((chain, node) => {
+      return chain.then(prevArr => {
+        // Call getEventListeners once for each node in the list.
+        return this.getEventListeners(node.element ? node.element.nodeId : node)
+            .then(result => prevArr.concat(result));
+      });
+    }, Promise.resolve([]));
+  }
+
+  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/ *')) // drill into shadow trees
+        .then(nodes => {
+          nodes.push('document', 'window');
+          return this.collectListeners(nodes);
+        }).catch(_ => {
+          return {
+            rawValue: -1,
+            debugString: 'Unable to collect passive events listener usage.'
+          };
+        });
+  }
 }
 
-function collectGeoState() {
-  return Promise.resolve(window.__didNotCallGeo);
+module.exports = EventListeners;
+
+},{"../gatherer":18}],"./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');
+
+class AnchorsWithNoRelNoopener extends Gatherer {
+
+  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.getProperty('href'),
+            node.getAttribute('rel'),
+            node.getAttribute('target')
+          ]);
+        });
+        return Promise.all(failingNodes);
+      })
+      .then(failingNodes => {
+        return {
+          usages: failingNodes.map(node => {
+            return {
+              href: node[0],
+              rel: node[1],
+              target: node[2]
+            };
+          })
+        };
+      })
+      .catch(_ => {
+        return -1;
+      });
+  }
 }
 
+module.exports = AnchorsWithNoRelNoopener;
+
+},{"../gatherer":18}],"./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');
+
+class AppCacheManifest extends Gatherer {
+
+  afterPass(options) {
+    const driver = options.driver;
+
+    return driver.querySelector('html')
+      .then(node => node && node.getAttribute('manifest'))
+      .catch(_ => {
+        return -1;
+      });
+  }
+}
+
+module.exports = AppCacheManifest;
+
+},{"../gatherer":18}],"./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');
+
+class ConsoleTimeUsage extends Gatherer {
+
+  beforePass(options) {
+    this.collectUsage = options.driver.captureFunctionCallSites('console.time');
+  }
+
+  afterPass() {
+    return this.collectUsage().then(consoleTimeUsage => {
+      return {
+        usage: consoleTimeUsage
+      };
+    }, e => {
+      return {
+        value: -1,
+        debugString: e.message
+      };
+    });
+  }
+}
+
+module.exports = ConsoleTimeUsage;
+
+},{"../gatherer":18}],"./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');
+
+class DateNowUse extends Gatherer {
+
+  beforePass(options) {
+    this.collectUsage = options.driver.captureFunctionCallSites('Date.now');
+  }
+
+  afterPass() {
+    return this.collectUsage().then(dateNowUses => {
+      return {
+        usage: dateNowUses
+      };
+    }, e => {
+      return {
+        value: -1,
+        debugString: e.message
+      };
+    });
+  }
+}
+
+module.exports = DateNowUse;
+
+},{"../gatherer":18}],"./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');
+
+class DocWriteUse extends Gatherer {
+
+  beforePass(options) {
+    this.collectUsage = options.driver.captureFunctionCallSites('document.write');
+  }
+
+  afterPass() {
+    return this.collectUsage().then(DocWriteUses => {
+      return {
+        usage: DocWriteUses
+      };
+    }, e => {
+      return {
+        value: -1,
+        debugString: e.message
+      };
+    });
+  }
+}
+
+module.exports = DocWriteUse;
+
+},{"../gatherer":18}],"./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');
+
 class GeolocationOnStart extends Gatherer {
 
   beforePass(options) {
-    return options.driver.evaluateScriptOnLoad(`(${overrideGeo.toString()}())`);
+    this.collectCurrentPosUsage = options.driver.captureFunctionCallSites(
+        'navigator.geolocation.getCurrentPosition');
+    this.collectWatchPosUsage = options.driver.captureFunctionCallSites(
+        'navigator.geolocation.watchPosition');
   }
 
   afterPass(options) {
-    return options.driver.evaluateAsync(`(${collectGeoState.toString()}())`)
-        .then(returnedValue => {
-          this.artifact = returnedValue;
-        }, _ => {
-          this.artifact = -1;
-          return;
+    return options.driver.queryPermissionState('geolocation')
+        .then(state => {
+          if (state === 'granted' || state === 'denied') {
+            return {
+              value: -1,
+              debugString: 'Unable to determine if this permission was requested ' +
+                           'on page load because it had already been set. ' +
+                           'Try resetting the permission and run Lighthouse again.'
+            };
+          }
+
+          return this.collectCurrentPosUsage().then(results => {
+            return this.collectWatchPosUsage().then(results2 => results.concat(results2));
+          }).then(results => {
+            return {
+              usage: results
+            };
+          });
+        }).catch(e => {
+          return {
+            value: -1,
+            debugString: e && e.message
+          };
         });
   }
 }
 
 module.exports = GeolocationOnStart;
 
-},{"./gatherer":"./gatherers/gatherer"}],"./gatherers/html-without-javascript":[function(require,module,exports){
+},{"../gatherer":18}],"./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');
+
+class NotificationOnStart extends Gatherer {
+
+  beforePass(options) {
+    this.collectNotificationUsage = options.driver.captureFunctionCallSites(
+        'Notification.requestPermission');
+  }
+
+  afterPass(options) {
+    return options.driver.queryPermissionState('notifications')
+        .then(state => {
+          if (state === 'granted' || state === 'denied') {
+            return {
+              value: -1,
+              debugString: 'Unable to determine if this permission was requested ' +
+                           'on page load because it had already been set. ' +
+                           'Try resetting the permission and run Lighthouse again.'
+            };
+          }
+
+          return this.collectNotificationUsage().then(results => {
+            return {
+              usage: results
+            };
+          });
+        }).catch(e => {
+          return {
+            value: -1,
+            debugString: e && e.message
+          };
+        });
+  }
+}
+
+module.exports = NotificationOnStart;
+
+},{"../gatherer":18}],"./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');
+
+/* 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;
+      }
+    });
+  }
+
+  window.__asyncLinks = window.__asyncLinks || {};
+  setInterval(checkForLinks, 100);
+  checkForLinks();
+}
+
+/* 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);
+          }
+
+          // 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}`));
+    }
+  });
+}
+
+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.indexOf('html') > -1;
+
+    // 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
+      };
+    }
+
+    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);
+
+      let totalTransferSize = 0;
+      let totalSpendTime = 0;
+
+      const blockingTags = tags.reduce((prev, tag) => {
+        const request = requests[tag.url];
+        if (request) {
+          const data = {
+            tag,
+            transferSize: request.transferSize,
+            spendTime: Math.round((request.endTime - request.startTime) * 1000)
+          };
+          totalTransferSize += data.transferSize;
+          totalSpendTime += data.spendTime;
+          prev.push(data);
+        }
+        return prev;
+      }, []);
+
+      return {
+        items: blockingTags,
+        total: {
+          transferSize: totalTransferSize,
+          spendTime: totalSpendTime
+        }
+      };
+    });
+  }
+
+  beforePass(options) {
+    const scriptSrc = `(${saveAsyncLinks.toString()})()`;
+    return options.driver.evaluateScriptOnLoad(scriptSrc);
+  }
+
+  afterPass(options, tracingData) {
+    return TagsBlockingFirstPaint
+      .findBlockingTags(options.driver, tracingData.networkRecords)
+      .catch(err => {
+        return {
+          value: -1,
+          debugString: err.message
+        };
+      });
+  }
+}
+
+module.exports = TagsBlockingFirstPaint;
+
+},{"../gatherer":18}],"./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 MAX_WAIT_TIMEOUT = 500;
+
+class WebSQL extends Gatherer {
+
+  listenForDatabaseEvents(driver) {
+    let timeout;
+
+    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);
+
+      // 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);
+    });
+  }
+
+  afterPass(options) {
+    return this.listenForDatabaseEvents(options.driver)
+      .then(database => {
+        const artifact = {
+          database
+        };
+        if (!database) {
+          artifact.debugString = 'No WebSQL databases were opened';
+        }
+        return artifact;
+      }).catch(_ => {
+        return {
+          database: -1,
+          debugString: 'Unable to gather WebSQL database state'
+        };
+      });
+  }
+}
+
+module.exports = WebSQL;
+
+},{"../gatherer":18}],"./gatherers/html-without-javascript":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -3334,10 +5102,13 @@
  */
 'use strict';
 
-/* Note that this returns the innerText of the <body> element, not the HTML. */
-
 const Gatherer = require('./gatherer');
 
+/**
+ * @fileoverview Returns the innerText of the <body> element while JavaScript is
+ * disabled.
+ */
+
 /* global document */
 
 /* istanbul ignore next */
@@ -3348,7 +5119,6 @@
 }
 
 class HTMLWithoutJavaScript extends Gatherer {
-
   beforePass(options) {
     options.disableJavaScript = true;
   }
@@ -3357,17 +5127,20 @@
     // Reset the JS disable.
     options.disableJavaScript = false;
 
-    const driver = options.driver;
-
-    this.artifact = {};
-    return driver.evaluateAsync(`(${getBodyText.toString()}())`)
+    return options.driver.evaluateAsync(`(${getBodyText.toString()}())`)
       .then(result => {
-        this.artifact = result;
+        if (typeof result !== 'string') {
+          throw new Error('result was not a string');
+        }
+
+        return {
+          value: result
+        };
       })
-      .catch(_ => {
-        this.artifact = {
+      .catch(err => {
+        return {
           value: -1,
-          debugString: 'Unable to get document body innerText'
+          debugString: `Unable to get document body innerText: ${err.message}`
         };
       });
   }
@@ -3375,7 +5148,7 @@
 
 module.exports = HTMLWithoutJavaScript;
 
-},{"./gatherer":"./gatherers/gatherer"}],"./gatherers/html":[function(require,module,exports){
+},{"./gatherer":18}],"./gatherers/html":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -3407,9 +5180,9 @@
           nodeId: nodeId
         }))
         .then(nodeHTML => {
-          this.artifact = nodeHTML.outerHTML;
+          return nodeHTML.outerHTML;
         }).catch(_ => {
-          this.artifact = {
+          return {
             value: -1,
             debugString: 'Unable to get document HTML'
           };
@@ -3419,7 +5192,7 @@
 
 module.exports = HTML;
 
-},{"./gatherer":"./gatherers/gatherer"}],"./gatherers/http-redirect":[function(require,module,exports){
+},{"./gatherer":18}],"./gatherers/http-redirect":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -3494,14 +5267,14 @@
     ]).then(result => {
       // Clear timeout. No effect if it won, no need to wait if it lost.
       clearTimeout(noSecurityChangesTimeout);
-      this.artifact = result;
+      return result;
     });
   }
 }
 
 module.exports = HTTPRedirect;
 
-},{"./gatherer":"./gatherers/gatherer"}],"./gatherers/https":[function(require,module,exports){
+},{"./gatherer":18}],"./gatherers/https":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -3568,14 +5341,14 @@
     ]).then(result => {
       // Clear timeout. No effect if it won, no need to wait if it lost.
       clearTimeout(noSecurityChangesTimeout);
-      this.artifact = result;
+      return result;
     });
   }
 }
 
 module.exports = HTTPS;
 
-},{"./gatherer":"./gatherers/gatherer"}],"./gatherers/manifest":[function(require,module,exports){
+},{"./gatherer":18}],"./gatherers/manifest":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -3597,6 +5370,12 @@
 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 {
 
   static _errorManifest(errorString) {
@@ -3608,41 +5387,34 @@
   }
 
   afterPass(options) {
-    const driver = options.driver;
-    /**
-     * This re-fetches the manifest separately, which could
-     * potentially lead to a different asset. Using the original manifest
-     * resource is tracked in issue #83
-     */
-    return driver.sendCommand('Page.getAppManifest')
+    return options.driver.sendCommand('Page.getAppManifest')
       .then(response => {
-        if (response.errors.length) {
+        // 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) {
           let errorString;
           if (response.url) {
-            errorString = `Unable to retrieve manifest at ${response.url}: `;
+            errorString = `Unable to retrieve manifest at ${response.url}`;
+          } else {
+            // The driver will return an empty string for url and the data if
+            // the page has no manifest.
+            errorString = 'No manifest found.';
           }
-          this.artifact = Manifest._errorManifest(errorString + response.errors.join(', '));
-          return;
+
+          return Manifest._errorManifest(errorString);
         }
 
-        // The driver will return an empty string for url and the data if the
-        // page has no manifest.
-        if (!response.data.length && !response.data.url) {
-          this.artifact = Manifest._errorManifest('No manifest found.');
-          return;
-        }
-
-        this.artifact = manifestParser(response.data, response.url, options.url);
-      }, _ => {
-        this.artifact = Manifest._errorManifest('Unable to retrieve manifest');
-        return;
+        return manifestParser(response.data, response.url, options.url);
+      }, err => {
+        return Manifest._errorManifest('Unable to retrieve manifest: ' + err);
       });
   }
 }
 
 module.exports = Manifest;
 
-},{"../../lib/manifest-parser":22,"./gatherer":"./gatherers/gatherer"}],"./gatherers/offline":[function(require,module,exports){
+},{"../../lib/manifest-parser":25,"./gatherer":18}],"./gatherers/offline":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -3673,15 +5445,15 @@
       return record._url === options.url && record._fetchedViaServiceWorker;
     }).pop(); // Take the last record that matches.
 
-    this.artifact = navigationRecord ? navigationRecord.statusCode : -1;
-
-    return options.driver.goOnline(options);
+    return options.driver.goOnline(options).then(_ => {
+      return navigationRecord ? navigationRecord.statusCode : -1;
+    });
   }
 }
 
 module.exports = Offline;
 
-},{"./gatherer":"./gatherers/gatherer"}],"./gatherers/service-worker":[function(require,module,exports){
+},{"./gatherer":18}],"./gatherers/service-worker":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -3716,15 +5488,13 @@
         return {
           debugString: `Error in querying Service Worker status: ${err.message}`
         };
-      }).then(artifact => {
-        this.artifact = artifact;
       });
   }
 }
 
 module.exports = ServiceWorker;
 
-},{"./gatherer":"./gatherers/gatherer"}],"./gatherers/styles":[function(require,module,exports){
+},{"./gatherer":18}],"./gatherers/styles":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -3752,6 +5522,7 @@
 
 const WebInspector = require('../../lib/web-inspector');
 const Gatherer = require('./gatherer');
+const log = require('../../lib/log.js');
 
 /**
  * @param {!gonzales.AST} parseTree
@@ -3761,6 +5532,12 @@
   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]},
@@ -3812,7 +5589,7 @@
   endStylesCollect(driver) {
     return new Promise((resolve, reject) => {
       if (!this._activeStyleSheetIds.length) {
-        reject('No active stylesheets were collected.');
+        resolve([]);
         return;
       }
 
@@ -3825,8 +5602,15 @@
         }).then(content => {
           const styleHeader = this._activeStyleHeaders[sheetId];
           styleHeader.content = content.text;
-          styleHeader.parsedContent = getCSSPropsInStyleSheet(
-              parser.parse(styleHeader.content));
+
+          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;
         });
       });
@@ -3834,9 +5618,12 @@
       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));
+        resolve(styleHeaders);
+        // Currently both CSSUsage and Styles use these domains, so let it disable there.
+        // TODO: have a better way to specify used domains
+        // return driver.sendCommand('CSS.disable')
+        //   .then(_ => driver.sendCommand('DOM.disable'))
+        //   .then(_ => resolve(styleHeaders));
       }).catch(err => reject(err));
     });
   }
@@ -3848,15 +5635,19 @@
   afterPass(options) {
     return this.endStylesCollect(options.driver)
       .then(stylesheets => {
-        // Want unique stylesheets. Remove those with the same text content.
+        // 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]));
-        this.artifact = Array.from(map.values());
+        return stylesheets.map(stylesheet => {
+          const idInMap = map.get(stylesheet.content).header.styleSheetId;
+          stylesheet.isDuplicate = idInMap !== stylesheet.header.styleSheetId;
+          return stylesheet;
+        });
       }, err => {
-        this.artifact = {
+        return {
           rawValue: -1,
           debugString: err
         };
@@ -3866,7 +5657,7 @@
 
 module.exports = Styles;
 
-},{"../../lib/web-inspector":26,"./gatherer":"./gatherers/gatherer"}],"./gatherers/theme-color":[function(require,module,exports){
+},{"../../lib/log.js":24,"../../lib/web-inspector":31,"./gatherer":18}],"./gatherers/theme-color":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -3894,19 +5685,15 @@
 
     return driver.querySelector('head meta[name="theme-color"]')
       .then(node => node && node.getAttribute('content'))
-      .then(themeColorMeta => {
-        this.artifact = themeColorMeta;
-      })
       .catch(_ => {
-        // The audit should read this as a fail since -1 is not a valid color.
-        this.artifact = -1;
+        return -1;
       });
   }
 }
 
 module.exports = ThemeColor;
 
-},{"./gatherer":"./gatherers/gatherer"}],"./gatherers/url":[function(require,module,exports){
+},{"./gatherer":18}],"./gatherers/url":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -3934,7 +5721,7 @@
     // 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)
-    this.artifact = {
+    return {
       initialUrl: options.initialUrl,
       finalUrl: options.url
     };
@@ -3943,7 +5730,7 @@
 
 module.exports = URL;
 
-},{"./gatherer":"./gatherers/gatherer"}],"./gatherers/viewport":[function(require,module,exports){
+},{"./gatherer":18}],"./gatherers/viewport":[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -3975,18 +5762,15 @@
 
     return driver.querySelector('head meta[name="viewport"]')
       .then(node => node && node.getAttribute('content'))
-      .then(viewport => {
-        this.artifact = viewport;
-      })
       .catch(_ => {
-        this.artifact = -1;
+        return -1;
       });
   }
 }
 
 module.exports = Viewport;
 
-},{"./gatherer":"./gatherers/gatherer"}],1:[function(require,module,exports){
+},{"./gatherer":18}],1:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -4152,10 +5936,10 @@
       // to the overall score and add each to the subItems list.
       expectedNames.forEach(e => {
         /* istanbul ignore if */
-        // TODO(paullewis): Remove once coming soon audits have landed.
+        // TODO(paullewis): Remove once coming soon audits have landed
         if (item.audits[e].comingSoon) {
           subItems.push({
-            score: '¯\\_(ツ)_/¯', // TODO(samthor): Patch going to Closure, String.raw is badly typed
+            score: '¯\\_(ツ)_/¯',
             name: 'coming-soon',
             category: item.audits[e].category,
             description: item.audits[e].description,
@@ -4166,7 +5950,7 @@
         }
 
         if (!filteredAndRemappedResults[e]) {
-          return;
+          throw new Error(`aggregations: expected audit results not found under audit name ${e}`);
         }
 
         subItems.push(filteredAndRemappedResults[e].name);
@@ -4197,18 +5981,29 @@
   }
 
   /**
+   * Calculates total score of an aggregate.
+   * @param {!Array<!AggregationResultItem>} scores
+   * @return {number}
+   */
+  static getTotal(scores) {
+    return scores.reduce((total, s) => total + s.overall, 0) / scores.length;
+  }
+
+  /**
    * Aggregates all the results.
    * @param {!Aggregation} aggregation
-   * @param {!Array<!AuditResult>} results
+   * @param {!Array<!AuditResult>} auditResults
    * @return {!AggregationResult}
    */
   static aggregate(aggregation, auditResults) {
+    const score = Aggregate.compare(auditResults, aggregation.items, aggregation.scored);
     return {
       name: aggregation.name,
       description: aggregation.description,
       scored: aggregation.scored,
+      total: (aggregation.scored ? Aggregate.getTotal(score) : null),
       categorizable: aggregation.categorizable,
-      score: Aggregate.compare(auditResults, aggregation.items, aggregation.scored)
+      score: score
     };
   }
 }
@@ -4216,6 +6011,143 @@
 module.exports = Aggregate;
 
 },{}],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';
+
+/**
+ * @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('../../formatters/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);
+
+    return this.generateAuditResult({
+      rawValue: typeof rule === 'undefined',
+      debugString: this.createDebugString(rule),
+      extendedInfo: {
+        formatter: Formatter.SUPPORTED_FORMATS.ACCESSIBILITY,
+        value: rule
+      }
+    });
+  }
+
+  /**
+   * @param {!{nodes: Array, help: string}} rule
+   * @return {!string}
+   */
+  static createDebugString(rule) {
+    if (typeof rule === 'undefined') {
+      return '';
+    }
+
+    const elementsStr = rule.nodes.length === 1 ? 'element' : 'elements';
+    return `${rule.help} (Failed on ${rule.nodes.length} ${elementsStr})`;
+  }
+}
+
+module.exports = AxeAudit;
+
+},{"../../formatters/formatter":8,"../audit":3}],3:[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;
+  }
+
+  /**
+   * @throws {Error}
+   */
+  static get meta() {
+    throw new Error('Audit meta information must be overridden.');
+  }
+
+  /**
+   * @param {!AuditResultInput} result
+   * @return {!AuditResult}
+   */
+  static generateAuditResult(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,
+      debugString: result.debugString,
+      optimalValue: result.optimalValue,
+      extendedInfo: result.extendedInfo,
+      name: this.meta.name,
+      category: this.meta.category,
+      description: this.meta.description,
+      helpText: this.meta.helpText
+    };
+  }
+}
+
+module.exports = Audit;
+
+},{}],4:[function(require,module,exports){
 (function (__dirname){
 /**
  * @license
@@ -4243,6 +6175,10 @@
 const log = require('../lib/log');
 const path = require('path');
 const Audit = require('../audits/audit');
+const Runner = require('../runner');
+
+const _flatten = arr => [].concat(...arr);
+const _uniq = arr => Array.from(new Set(arr));
 
 // cleanTrace is run to remove duplicate TracingStartedInPage events,
 // and to change TracingStartedInBrowser events into TracingStartedInPage.
@@ -4331,7 +6267,7 @@
   if (!Array.isArray(passes)) {
     return;
   }
-  const requiredGatherers = getGatherersNeededByAudits(audits);
+  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 => {
@@ -4361,50 +6297,9 @@
   });
 }
 
-function getGatherersNeededByAudits(audits) {
-  // It's possible we didn't get given any audits (but existing audit results), in which case
-  // there is no need to do any work here.
-  if (!audits) {
-    return new Set();
-  }
+function assertValidAudit(auditDefinition, auditPath) {
+  const auditName = auditPath || auditDefinition.meta.name;
 
-  return audits.reduce((list, audit) => {
-    audit.meta.requiredArtifacts.forEach(artifact => list.add(artifact));
-    return list;
-  }, new Set());
-}
-
-function requireAudits(audits, configPath) {
-  if (!audits) {
-    return null;
-  }
-  const Runner = require('../runner');
-  const coreList = Runner.getAuditList();
-
-  return audits.map(nameOrAuditClass => {
-    let AuditClass;
-    if (typeof nameOrAuditClass === 'string') {
-      const name = nameOrAuditClass;
-      // See if the audit is a Lighthouse core audit.
-      const coreAudit = coreList.find(a => a === `${name}.js`);
-      let requirePath = `../audits/${name}`;
-      if (!coreAudit) {
-        // Otherwise, attempt to find it elsewhere. This throws if not found.
-        requirePath = Runner.resolvePlugin(name, configPath, 'audit');
-      }
-      AuditClass = require(requirePath);
-      assertValidAudit(AuditClass, name);
-    } else {
-      AuditClass = nameOrAuditClass;
-      assertValidAudit(AuditClass);
-    }
-
-    return AuditClass;
-  });
-}
-
-function assertValidAudit(auditDefinition, auditName) {
-  auditName = auditName || (auditDefinition.meta && auditDefinition.meta.name) || 'audit';
   if (typeof auditDefinition.audit !== 'function') {
     throw new Error(`${auditName} has no audit() method.`);
   }
@@ -4519,13 +6414,167 @@
 
     this._aggregations = configJSON.aggregations || null;
 
-    this._audits = requireAudits(configJSON.audits, this._configDir);
+    this._audits = Config.requireAudits(configJSON.audits, this._configDir);
     this._artifacts = expandArtifacts(configJSON.artifacts);
 
     // validatePasses must follow after audits are required
     validatePasses(configJSON.passes, this._audits, this._configDir);
   }
 
+  static getMapOfAuditPathToName(config) {
+    // The `audits` property in the config is a list of paths of audits to run.
+    // `requestedAuditNames` is a list of audit *names*. Map paths to names, then
+    // filter out any paths of audits with names that weren't requested.
+    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;
+  }
+
+  // Find audits required for remaining aggregations.
+  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 getGatherersNeededByAudits(audits) {
+    // It's possible we didn't get 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());
+  }
+
+
+  static selectPassesNeededByGatherers(passes, requiredGatherers) {
+    const filteredPasses = passes.map(pass => {
+      // remove any unncessary gatherers
+      pass.gatherers = pass.gatherers.filter(gathererName => {
+        gathererName = GatherRunner.getGathererClass(gathererName).name;
+        return requiredGatherers.has(gathererName);
+      });
+      return pass;
+    // remove any passes lacking concrete gatherers
+    }).filter(pass => pass.gatherers.length > 0);
+
+    // handle the perf-only case (no specific gatherers, just trace & network)
+    if (filteredPasses.length === 0) {
+      if (requiredGatherers.has('traces') || requiredGatherers.has('networkRecords')) {
+        filteredPasses.push({
+          recordNetwork: requiredGatherers.has('networkRecords'),
+          recordTrace: requiredGatherers.has('traces'),
+          gatherers: []
+        });
+      }
+    }
+    return filteredPasses;
+  }
+
+  static getAggregationsByTags(aggregations, chosenTags) {
+    // Provided a config aggregation, should it be included?
+    const isAggregationSelected = agg => agg.tags.some(itemTag => chosenTags.includes(itemTag));
+
+    const chosenAggregations = [];
+    aggregations.forEach(aggregation => {
+      // Case #1: Simple non-parent aggregation
+      if (aggregation.items.length === 1) {
+        if (isAggregationSelected(aggregation)) {
+          chosenAggregations.push(aggregation);
+        }
+        return;
+      }
+
+      // Reduce the child aggregations based on our tags
+      aggregation.items = aggregation.items.filter(isAggregationSelected);
+
+      // Case #2: Child aggregations are good, but parent isn't
+      //   We push the children to top-level
+      if (aggregation.items.length && !isAggregationSelected(aggregation)) {
+        aggregation.items.forEach(item => {
+          item.scored = false;
+          item.categorizable = false;
+          item.items = [{audits: item.audits}];
+          delete item.audits;
+          chosenAggregations.push(item);
+        });
+        return;
+      };
+
+      // Case #3: All items were removed earlier, so we're uninterested in the parent aggregation
+      if (aggregation.items.length === 0) {
+        return;
+      }
+
+      // Case #4: We have good child items, and the parent aggregation is good
+      chosenAggregations.push(aggregation);
+    });
+    return chosenAggregations;
+  }
+
+   /**
+   * Filter out any unrequested aggregations from the config. If any audits are
+   * no longer needed by any remaining aggregations, filter out those as well.
+   * @param {!Object} config Lighthouse config object.
+   * @param {!Array<string>} chosenTags Ids of aggregation tags to include.
+   */
+  static rebuildConfigFromTags(config, chosenTags) {
+    config.aggregations = Config.getAggregationsByTags(config.aggregations, chosenTags);
+    const requestedAuditNames = Config.getAuditsNeededByAggregations(config.aggregations);
+
+    const auditPathToName = Config.getMapOfAuditPathToName(config);
+    config.audits = config.audits.filter(auditPath =>
+        requestedAuditNames.has(auditPathToName.get(auditPath)));
+
+    const auditObjectsSelected = Config.requireAudits(config.audits);
+    const requiredGatherers = Config.getGatherersNeededByAudits(auditObjectsSelected);
+    config.passes = Config.selectPassesNeededByGatherers(config.passes, requiredGatherers);
+  }
+
+  /**
+   * 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;
@@ -4560,7 +6609,7 @@
 module.exports = Config;
 
 }).call(this,"/../lighthouse-core/config")
-},{"../audits/audit":"../audits/audit","../gather/gather-runner":16,"../lib/log":21,"../lib/network-recorder":23,"../runner":27,"./default.json":3,"path":198}],3:[function(require,module,exports){
+},{"../audits/audit":3,"../gather/gather-runner":17,"../lib/log":24,"../lib/network-recorder":26,"../runner":33,"./default.json":5,"path":204}],5:[function(require,module,exports){
 module.exports={
   "passes": [{
     "recordNetwork": true,
@@ -4572,9 +6621,7 @@
       "theme-color",
       "manifest",
       "accessibility",
-      "content-width",
-      "cache-contents",
-      "geolocation-on-start"
+      "content-width"
     ]
   },
   {
@@ -4590,6 +6637,23 @@
       "http-redirect",
       "html-without-javascript"
     ]
+  }, {
+    "recordNetwork": true,
+    "passName": "dbw",
+    "gatherers": [
+      "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/tags-blocking-first-paint",
+      "dobetterweb/websql"
+    ]
   }],
 
   "audits": [
@@ -4605,7 +6669,6 @@
     "estimated-input-latency",
     "time-to-interactive",
     "user-timings",
-    "screenshots",
     "critical-request-chains",
     "manifest-exists",
     "manifest-background-color",
@@ -4616,25 +6679,44 @@
     "manifest-short-name",
     "manifest-short-name-length",
     "manifest-start-url",
-    "meta-theme-color",
-    "aria-valid-attr",
-    "aria-allowed-attr",
-    "color-contrast",
-    "image-alt",
-    "label",
-    "tabindex",
+    "theme-color-meta",
+    "unused-css-rules",
     "content-width",
-    "geolocation-on-start"
+    "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",
+    "dobetterweb/external-anchors-use-rel-noopener",
+    "dobetterweb/appcache-manifest",
+    "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",
     "description": "These audits validate the aspects of a Progressive Web App.",
+    "id": "pwa_parent",
+    "tags": ["pwa"],
     "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 <a href=\"https://developers.google.com/web/fundamentals/primers/service-worker/\">Service Worker</a>.",
+      "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/).",
+      "tags": ["pwa"],
       "audits": {
         "service-worker": {
           "expectedValue": true,
@@ -4643,15 +6725,13 @@
         "works-offline": {
           "expectedValue": true,
           "weight": 1
-        },
-        "cache-start-url": {
-          "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.",
+      "id": "perf_metrics",
+      "tags": ["pwa", "perf"],
       "audits": {
         "first-meaningful-paint": {
           "expectedValue": 100,
@@ -4694,6 +6774,8 @@
     }, {
       "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.",
+      "id": "progressive_enhancement",
+      "tags": ["pwa"],
       "audits": {
         "without-javascript": {
           "expectedValue": true,
@@ -4703,6 +6785,8 @@
     }, {
       "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.",
+      "id": "network_security",
+      "tags": ["pwa"],
       "audits": {
         "is-on-https": {
           "expectedValue": true,
@@ -4715,7 +6799,9 @@
       }
     }, {
       "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 <a href=\"https://developers.google.com/web/updates/2015/03/increasing-engagement-with-app-install-banners-in-chrome-for-android?hl=en\">prompt (aka app install banner)</a> 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).",
+      "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).",
+      "tags": ["pwa"],
+      "id": "a2hs",
       "see": "https://github.com/GoogleChrome/lighthouse/issues/23",
       "audits": {
         "service-worker": {
@@ -4741,7 +6827,9 @@
       }
     }, {
       "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 <a href=\"https://developers.google.com/web/updates/2015/10/splashscreen?hl=en\">splash screen</a> the user sees between tapping the home screen icon and your app’s first paint.",
+      "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.",
+      "tags": ["pwa"],
+      "id": "splash_screen",
       "see": "https://github.com/GoogleChrome/lighthouse/issues/24",
       "audits": {
         "manifest-exists": {
@@ -4767,7 +6855,9 @@
       }
     }, {
       "name": "Address bar matches brand colors",
-      "description": "The browser address bar can be themed to match your site. A theme-color <a href=\"https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android\">meta tag</a> will upgrade the address bar when a user browses the site, and the <a href=\"https://developers.google.com/web/updates/2015/08/using-manifest-to-set-sitewide-theme-color\">manifest theme-color</a> will apply the same theme site-wide once it's been added to homescreen.",
+      "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.",
+      "tags": ["pwa"],
+      "id": "omnibox",
       "audits": {
         "manifest-exists": {
           "expectedValue": true,
@@ -4785,6 +6875,8 @@
     }, {
       "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.",
+      "id": "mobile_friendly",
+      "tags": ["pwa"],
       "audits": {
         "viewport": {
           "expectedValue": true,
@@ -4796,21 +6888,113 @@
         }
       }
     }]
-  },{
+  }, {
     "name": "Best Practices",
-    "description": "These audits do not affect your score but are worth a look.",
+    "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.",
+    "id": "best_practices_parent",
+    "tags": ["best_practices"],
     "scored": false,
-    "categorizable": false,
+    "categorizable": true,
     "items": [{
+      "name": "Using modern offline features",
+      "id": "modern_offline",
+      "tags": ["best_practices"],
+      "audits": {
+        "appcache-manifest": {
+          "expectedValue": false
+        },
+        "no-websql": {
+          "expectedValue": false
+        }
+      }
+    }, {
+      "name": "Using modern protocols",
+      "id": "modern_network",
+      "tags": ["best_practices"],
+      "audits": {
+        "is-on-https": {
+          "expectedValue": false
+        },
+        "uses-http2": {
+          "expectedValue": false,
+          "description": "Resources made by this application should be severed over HTTP/2 for improved performance."
+        }
+      }
+    }, {
+      "name": "Using bytes efficiently",
+      "id": "byte_efficiency",
+      "tags": ["best_practices"],
+      "audits": {
+        "unused-css-rules": {
+          "expectedValue": false
+        }
+      }
+    }, {
+      "name": "Using modern CSS features",
+      "id": "modern_css",
+      "tags": ["best_practices"],
+      "audits": {
+        "no-old-flexbox": {
+          "expectedValue": false
+        }
+      }
+    }, {
+      "name": "Using modern JavaScript features",
+      "id": "modern_js",
+      "tags": ["best_practices"],
+      "audits": {
+        "uses-passive-event-listeners": {
+          "expectedValue": true
+        },
+        "no-mutation-events": {
+          "expectedValue": false
+        }
+      }
+    }, {
+      "name": "Avoiding APIs that harm the user experience",
+      "id": "ux_harmful_apis",
+      "tags": ["best_practices"],
+      "audits": {
+        "no-document-write": {
+          "expectedValue": false
+        },
+        "link-blocking-first-paint": {
+          "expectedValue": false
+        },
+        "script-blocking-first-paint": {
+          "expectedValue": false
+        },
+        "external-anchors-use-rel-noopener": {
+          "expectedValue": true
+        },
+        "geolocation-on-start": {
+          "expectedValue": false
+        },
+        "notification-on-start": {
+          "expectedValue": false
+        }
+      }
+    }, {
+      "name": "Accessibility",
+      "id": "a11y",
+      "tags": ["best_practices"],
       "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
@@ -4826,7 +7010,13 @@
         "tabindex": {
           "expectedValue": true,
           "weight": 1
-        },
+        }
+      }
+    }, {
+      "name": "Other",
+      "id": "other_best_practices",
+      "tags": ["best_practices"],
+      "audits": {
         "manifest-short-name-length": {
           "expectedValue": true,
           "weight": 1
@@ -4835,10 +7025,6 @@
           "expectedValue": true,
           "weight": 1
         },
-        "geolocation-on-start": {
-          "expectedValue": true,
-          "weight": 1
-        },
         "serviceworker-push": {
           "expectedValue": true,
           "weight": 0,
@@ -4857,28 +7043,30 @@
           "expectedValue": true,
           "weight": 0,
           "comingSoon": true,
-          "description": "Payment forms marked up with [autocomplete] attributes",
+          "description": "Payment forms marked up with `autocomplete` attributes",
           "category": "UX"
         },
         "login-autocomplete": {
           "expectedValue": true,
           "weight": 0,
           "comingSoon": true,
-          "description": "Login forms marked up with [autocomplete] attributes",
+          "description": "Login forms marked up with `autocomplete` attributes",
           "category": "UX"
         },
         "input-type": {
           "expectedValue": true,
           "weight": 0,
           "comingSoon": true,
-          "description": "Input fields use appropriate [type] attributes for custom keyboards",
+          "description": "Input fields use appropriate `type` attributes for custom keyboards",
           "category": "UX"
         }
       }
     }]
-  },{
+  }, {
     "name": "Performance Metrics",
     "description": "These encapsulate your app's performance.",
+    "id": "perf_diagnostics",
+    "tags": ["perf"],
     "scored": false,
     "categorizable": false,
     "items": [{
@@ -4893,10 +7081,28 @@
         }
       }
     }]
+  }, {
+    "name": "Fancier stuff",
+    "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.",
+    "id": "fancy_best_practices",
+    "tags": ["best_practices"],
+    "scored": false,
+    "categorizable": true,
+    "items": [{
+      "name": "New JavaScript features",
+      "audits": {
+        "no-datenow": {
+          "expectedValue": false
+        },
+        "no-console-time": {
+          "expectedValue": false
+        }
+      }
+    }]
   }]
 }
 
-},{}],4:[function(require,module,exports){
+},{}],6:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -4919,7 +7125,7 @@
 const path = require('path');
 
 const Formatter = require('./formatter');
-const html = "<style>\n.axe-violation {\n  color: #D0021B;\n}\n\n.axe-violation__help {\n  color: #76B530;\n}\n\n.axe-violation__help-minor,\n.axe-violation__help-moderate {\n  color: #F5A623;\n}\n\n.axe-violation__help-serious,\n.axe-violation__help--critical {\n  color: #D0021B;\n}\n</style>\n<div class=\"axe-violation\">\n  <details>\n    <summary>\n      <a class=\"axe-violation__help--{{ this.impact }}\" href=\"{{ this.helpUrl }}\" target=\"_blank\">{{ this.help }}</a>\n    </summary>\n    <ul>\n    {{#each this.nodes}}\n      <li><code>{{ this.target }}</code></li>\n    {{/each}}\n    </ul>\n  </details>\n</div>\n";
+const html = "<details class=\"subitem__details\">\n  <summary class=\"subitem__detail\">\n    {{#if (gt this.nodes.length 1)}}\n      {{this.nodes.length}} elements fail this test\n    {{else}}\n      {{this.nodes.length}} element fails this test\n    {{/if}}\n    <a href=\"{{ this.helpUrl }}\" target=\"_blank\"><small>learn more</small></a>\n  </summary>\n  <ul class=\"subitem__details\">\n  {{#each this.nodes}}\n    <li class=\"subitem__detail\"><code>{{ this.target }}</code></li>\n  {{/each}}\n  </ul>\n</details>\n";
 
 class Accessibilty extends Formatter {
   static getFormatter(type) {
@@ -4949,11 +7155,20 @@
         throw new Error('Unknown formatter type');
     }
   }
+
+  static getHelpers() {
+    return {
+      gt(a, b) {
+        return a > b;
+      }
+    };
+  }
 }
 
 module.exports = Accessibilty;
 
-},{"./formatter":7,"path":198}],5:[function(require,module,exports){
+},{"./formatter":8,"path":204}],7:[function(require,module,exports){
+(function (process){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -4973,11 +7188,22 @@
 
 'use strict';
 
-const url = require('url');
+const URL = require('../lib/url-shim');
 const path = require('path');
 
 const Formatter = require('./formatter');
-const html = "<style>\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: bold;\n}\n\n.cnc-node__tree-hostname {\n  color: #999;\n}\n\n</style>\n\n{{#*inline \"writeNode\"}}\n  <div class=\"cnc-node\" title=\"{{ @key }}\">\n    <span class=\"cnc-node__tree-marker\">\n    {{#each treeMarkers as |separator| }}\n      {{#separator}}\n      <span class=\"tree-marker vert\"></span>\n      <span class=\"tree-marker space\"></span>\n      {{else}}\n      <span class=\"tree-marker space\"></span>\n      <span class=\"tree-marker space\"></span>\n      {{/separator}}\n    {{/each}}\n    {{#isLastChild}}\n      <span class=\"tree-marker up-right\"></span>\n      <span class=\"tree-marker right\"></span>\n    {{else}}\n      <span class=\"tree-marker vert-right\"></span>\n      <span class=\"tree-marker right\"></span>\n    {{/isLastChild}}\n\n    {{#hasChildren}}\n      <span class=\"tree-marker horiz-down\"></span>\n    {{else}}\n      <span class=\"tree-marker right\"></span>\n    {{/hasChildren}}\n    </span>\n\n    <span class=\"cnc-node__tree-value\">\n      {{#parseURL this.node.request.url }}\n        <span class=\"cnc-node__tree-file\">{{ this.file }}</span>\n        <span class=\"cnc-node__tree-hostname\">({{ this.hostname }})</span>\n      {{/parseURL}}\n      {{#unless hasChildren}}\n        - <span class=\"cnc-node__chain-duration\">{{chainDuration startTime this.node.request.endTime }}ms, {{formatTransferSize this.transferSize}}KB</span>\n      {{/unless}}\n    </span>\n  </div>\n\n  {{#each this.node.children as |child| }}\n    {{#createContextFor ../node.children @key ../treeMarkers ../isLastChild ../startTime ../transferSize }}\n      {{> writeNode this }}\n    {{/createContextFor }}\n  {{/each}}\n{{/inline}}\n\n<div class=\"cnc-tree\">\n  <div>Longest request chain (shorter is better): <strong>{{longestChain this}}</strong></div>\n  <div>Longest chain duration (shorter is better): <strong>{{formatTime (longestDuration this)}}ms</strong></div>\n  <div>Longest chain transfer size (smaller is better): <strong>{{formatTransferSize (longestChainTransferSize this)}}KB</strong></div>\n  <div>\n    <div>Initial navigation</div>\n    {{#createTreeRenderContext this}}\n      {{#each this.tree }}\n        {{#createContextFor ../tree @key undefined undefined ../startTime ../transferSize }}\n          {{> writeNode this }}\n        {{/createContextFor}}\n      {{/each}}\n    {{/createTreeRenderContext}}\n  </div>\n</div>\n";
+const html = "<style>\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: bold;\n}\n\n.cnc-node__tree-hostname {\n  color: #999;\n}\n\n</style>\n\n{{#*inline \"writeNode\"}}\n  <div class=\"cnc-node\" title=\"{{ this.node.request.url }}\">\n    <span class=\"cnc-node__tree-marker\">\n    {{#each treeMarkers as |separator| }}\n      {{#separator}}\n      <span class=\"tree-marker vert\"></span>\n      <span class=\"tree-marker space\"></span>\n      {{else}}\n      <span class=\"tree-marker space\"></span>\n      <span class=\"tree-marker space\"></span>\n      {{/separator}}\n    {{/each}}\n    {{#isLastChild}}\n      <span class=\"tree-marker up-right\"></span>\n      <span class=\"tree-marker right\"></span>\n    {{else}}\n      <span class=\"tree-marker vert-right\"></span>\n      <span class=\"tree-marker right\"></span>\n    {{/isLastChild}}\n\n    {{#hasChildren}}\n      <span class=\"tree-marker horiz-down\"></span>\n    {{else}}\n      <span class=\"tree-marker right\"></span>\n    {{/hasChildren}}\n    </span>\n\n    <span class=\"cnc-node__tree-value\">\n      {{#parseURL this.node.request.url }}\n        <span class=\"cnc-node__tree-file\">{{ this.file }}</span>\n        <span class=\"cnc-node__tree-hostname\">({{ this.hostname }})</span>\n      {{/parseURL}}\n      {{#unless hasChildren}}\n        - <span class=\"cnc-node__chain-duration\">{{chainDuration startTime this.node.request.endTime }}ms, {{formatTransferSize this.transferSize}}KB</span>\n      {{/unless}}\n    </span>\n  </div>\n\n  {{#each this.node.children as |child| }}\n    {{#createContextFor ../node.children @key ../treeMarkers ../isLastChild ../startTime ../transferSize }}\n      {{> writeNode this }}\n    {{/createContextFor }}\n  {{/each}}\n{{/inline}}\n\n<ul class=\"subitem__details\">\n  <li class=\"subitem__detail\">Longest request chain (shorter is better): <strong>{{longestChain this}}</strong></li>\n  <li class=\"subitem__detail\">Longest chain duration (shorter is better): <strong>{{formatTime (longestDuration this)}}ms</strong></li>\n  <li class=\"subitem__detail\">Longest chain transfer size (smaller is better): <strong>{{formatTransferSize (longestChainTransferSize this)}}KB</strong></li>\n  <li class=\"subitem__detail\">\n    <div>Initial navigation</div>\n    {{#createTreeRenderContext this}}\n      {{#each this.tree }}\n        {{#createContextFor ../tree @key undefined undefined ../startTime ../transferSize }}\n          {{> writeNode this }}\n        {{/createContextFor}}\n      {{/each}}\n    {{/createTreeRenderContext}}\n  </li>\n</ul>\n";
+
+const isWindows = process.platform === 'win32';
+
+// See https://github.com/GoogleChrome/lighthouse/issues/1228
+const heavyHorizontal = isWindows ? '\u2500' : '━';
+const heavyVertical = isWindows ? '\u2502 ' : '┃ ';
+const heavyUpAndRight = isWindows ? '\u2514' : '┗';
+const heavyUpAndRightLong = heavyUpAndRight + heavyHorizontal;
+const heavyVerticalAndRight = isWindows ? '\u251C' : '┣';
+const heavyVerticalAndRightLong = heavyVerticalAndRight + heavyHorizontal;
+const heavyDownAndHorizontal = isWindows ? '\u252C' : '┳';
 
 class CriticalRequestChains extends Formatter {
 
@@ -5104,7 +7330,7 @@
 
         // If the parent is the last child then don't drop the vertical bar.
         const ancestorTreeMarker = treeMarkers.reduce((markers, marker) => {
-          return markers + (marker ? '┃ ' : '  ');
+          return markers + (marker ? heavyVertical : '  ');
         }, '');
 
         // Copy the tree markers so that we don't change by reference.
@@ -5116,8 +7342,8 @@
         // Create the appropriate tree marker based on the depth of this
         // node as well as whether or not it has children and is itself the last child.
         const treeMarker = ancestorTreeMarker +
-            (isLastChild ? '┗━' : '┣━') +
-            (hasChildren ? '┳' : '━');
+            (isLastChild ? heavyUpAndRightLong : heavyVerticalAndRightLong) +
+            (hasChildren ? heavyDownAndHorizontal : heavyHorizontal);
 
         const parsedURL = CriticalRequestChains.parseURL(node[id].request.url);
 
@@ -5161,23 +7387,9 @@
   }
 
   static parseURL(resourceURL, opts) {
-    const MAX_FILENAME_LENGTH = 64;
-    const parsedResourceURL = url.parse(resourceURL);
-    const hostname = parsedResourceURL.hostname;
-    // Handle 'about:*' URLs specially since they have no path.
-    let file = parsedResourceURL.protocol === 'about:' ? parsedResourceURL.href :
-        // Otherwise, remove any query strings from the path.
-        parsedResourceURL.path.replace(/\?.*/, '')
-        // And grab the last two parts.
-        .split('/').slice(-2).join('/');
-
-    if (file.length > MAX_FILENAME_LENGTH) {
-      file = file.slice(0, MAX_FILENAME_LENGTH) + '...';
-    }
-
     const parsedURL = {
-      file,
-      hostname
+      file: URL.getDisplayName(resourceURL),
+      hostname: new URL(resourceURL).hostname
     };
 
     // If we get passed the opts parameter, this is Handlebars, so we
@@ -5264,85 +7476,8 @@
 
 module.exports = CriticalRequestChains;
 
-},{"./formatter":7,"path":198,"url":204}],6:[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 Formatter = require('./formatter');
-const path = require('path');
-
-const html = "<style>\n  .input-latency-measures {\n    font-size: 14px\n  }\n</style>\n\n<div>\n  <div class=\"input-latency-measures\">\n    <div>90% probability of input latency at <strong>{{ninetiethTime this}}ms</strong> or shorter.<div>\n    <div>\n      ({{#each this}}{{percentile this.percentile}}%: {{fixedTenths this.time}}ms{{#unless @last}}, {{/unless}}{{/each}})\n    </div>\n  </div>\n</div>\n";
-
-class EstimatedInputLatencyFormatter extends Formatter {
-  static getFormatter(type) {
-    switch (type) {
-      case 'pretty':
-        return function(percentiles) {
-          if (!percentiles || !Array.isArray(percentiles)) {
-            return '';
-          }
-
-          const ninetieth = percentiles.find(result => result.percentile === 0.9);
-          const time = ninetieth.time.toFixed(1);
-          const allResults = percentiles.map(result => {
-            const percentile = Math.round(result.percentile * 100);
-            const time = result.time.toFixed(1);
-            return `${percentile}%: ${time}ms`;
-          }).join(', ');
-
-          const output = `    - 90% probability of input latency at ${time}ms or shorter.\n` +
-              `      (${allResults})\n`;
-
-          return output;
-        };
-
-      case 'html':
-        // Returns a handlebars string to be used by the Report.
-        return html;
-
-      default:
-        throw new Error('Unknown formatter type');
-    }
-  }
-
-  static getHelpers() {
-    return {
-      ninetiethTime(percentiles) {
-        if (!Array.isArray(percentiles)) {
-          return;
-        }
-
-        const ninetieth = percentiles.find(result => result.percentile === 0.9);
-        return ninetieth.time.toFixed(1);
-      },
-      percentile(value) {
-        return Math.round(value * 100);
-      },
-      fixedTenths(value) {
-        return value.toFixed(1);
-      }
-    };
-  }
-}
-
-module.exports = EstimatedInputLatencyFormatter;
-
-},{"./formatter":7,"path":198}],7:[function(require,module,exports){
+}).call(this,require('_process'))
+},{"../lib/url-shim":30,"./formatter":8,"_process":205,"path":204}],8:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -5386,7 +7521,6 @@
     this._formatters = {
       accessibility: require('./accessibility'),
       criticalRequestChains: require('./critical-request-chains'),
-      estimatedInputLatency: require('./estimated-input-latency'),
       urllist: require('./url-list'),
       null: require('./null-formatter'),
       speedline: require('./speedline-formatter'),
@@ -5429,7 +7563,7 @@
 
 module.exports = Formatter;
 
-},{"./accessibility":4,"./critical-request-chains":5,"./estimated-input-latency":6,"./null-formatter":8,"./speedline-formatter":9,"./url-list":10,"./user-timings":11}],8:[function(require,module,exports){
+},{"./accessibility":6,"./critical-request-chains":7,"./null-formatter":9,"./speedline-formatter":10,"./url-list":11,"./user-timings":12}],9:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -5468,7 +7602,7 @@
 
 module.exports = NullFormatter;
 
-},{"./formatter":7}],9:[function(require,module,exports){
+},{"./formatter":8}],10:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -5490,7 +7624,7 @@
 const Formatter = require('./formatter');
 const path = require('path');
 
-const html = "<style>\n  .speedline-measures {\n    font-size: 14px\n  }\n</style>\n\n<div>\n  <div class=\"speedline-measures\">\n    <div>First Visual Change: <strong>{{this.first}}ms</strong></div>\n    <div>Last Visual Change: <strong>{{this.complete}}ms</strong></div>\n  </div>\n</div>\n";
+const html = "<style>\n  .speedline-measures {\n    font-size: 14px\n  }\n</style>\n\n<ul class=\"subitem__details\">\n  <li class=\"subitem__detail\">First Visual Change: <strong>{{this.first}}ms</strong></li>\n  <li class=\"subitem__detail\">Last Visual Change: <strong>{{this.complete}}ms</strong></li>\n</ul>\n";
 
 class SpeedlineFormatter extends Formatter {
   static getFormatter(type) {
@@ -5519,7 +7653,7 @@
 
 module.exports = SpeedlineFormatter;
 
-},{"./formatter":7,"path":198}],10:[function(require,module,exports){
+},{"./formatter":8,"path":204}],11:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -5542,7 +7676,7 @@
 const Formatter = require('./formatter');
 const path = require('path');
 
-const html = "<style>\n  .http-resources {\n    font-size: 14px;\n  }\n  .http-resource__url {\n    margin-right: 8px;\n  }\n  .http-resource__protocol,\n  .http-resource__code {\n    color: #999;\n  }\n</style>\n\n<div>\n  <details class=\"http-resources\">\n    <summary>URLs</summary>\n    {{#each this}}\n      <div class=\"http-resource\">\n        <span class=\"http-resource__url\">{{this.url}}</span>\n        {{#if this.label}}\n          <span class=\"http-resource__protocol\">({{this.label}})</span>\n        {{/if}}\n        {{#if this.code}}\n          <pre class=\"http-resource__code\">{{this.code}}</pre>\n        {{/if}}\n      </div>\n    {{/each}}\n  </details>\n</div>\n";
+const html = "<style>\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</style>\n\n<details class=\"subitem__details\">\n  <summary class=\"subitem__detail\">URLs</summary>\n  <ul class=\"subitem__details\">\n  {{#each this}}\n    <li class=\"subitem__detail http-resource\">\n      <span class=\"http-resource__url\">{{this.url}}</span>\n      {{#if this.label}}\n        <span class=\"http-resource__protocol\">({{this.label}})</span>\n      {{/if}}\n      {{#if this.code}}\n        <pre class=\"http-resource__code\">{{this.code}}</pre>\n      {{/if}}\n    </li>\n  {{/each}}\n  </ul>\n</details>\n";
 
 class UrlList extends Formatter {
   static getFormatter(type) {
@@ -5576,7 +7710,7 @@
 
 module.exports = UrlList;
 
-},{"./formatter":7,"path":198}],11:[function(require,module,exports){
+},{"./formatter":8,"path":204}],12:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -5599,7 +7733,7 @@
 const Formatter = require('./formatter');
 const path = require('path');
 
-const html = "<style>\n  .ut-measures {\n    font-size: 14px\n  }\n\n  .ut-measure_listing-duration {\n    font-weight: bold\n  }\n</style>\n\n<div>\n  <div class=\"ut-measures\">\n    {{#each this}}\n      <div>\n        {{#if this.isMark}}\n          <span class=\"ut-measure_listing-duration\">Mark: {{ decimal this.startTime }}ms</span> - {{ this.name }}\n        {{else}}\n          <span class=\"ut-measure_listing-duration\">Measure {{ decimal this.duration }}ms</span> - {{ this.name }}\n        {{/if}}\n      </div>\n    {{/each}}\n  </div>\n</div>\n";
+const html = "<style>\n  .ut-measure_listing-duration {\n    font-weight: bold\n  }\n</style>\n\n<ul class=\"subitem__details\">\n  {{#each this}}\n    <li class=\"subitem__detail\">\n      {{#if this.isMark}}\n        <strong class=\"ut-measure_listing-duration\">Mark: {{ decimal this.startTime }}ms</strong> - {{ this.name }}\n      {{else}}\n        <strong class=\"ut-measure_listing-duration\">Measure {{ decimal this.duration }}ms</strong> - {{ this.name }}\n      {{/if}}\n    </li>\n  {{/each}}\n</ul>\n";
 
 class UserTimings extends Formatter {
   static getFormatter(type) {
@@ -5634,7 +7768,7 @@
 
 module.exports = UserTimings;
 
-},{"./formatter":7,"path":198}],12:[function(require,module,exports){
+},{"./formatter":8,"path":204}],13:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All Rights Reserved.
@@ -5712,6 +7846,7 @@
 
   /**
    * @param {string} message
+   * @return {!Promise}
    * @protected
    */
   sendRawMessage(message) {
@@ -5722,6 +7857,7 @@
 
   /**
    * @param {string} message
+   * @return {!Promise}
    * @protected
    */
   handleRawMessage(message) {
@@ -5731,24 +7867,46 @@
     if (object.id) {
       const callback = this._callbacks.get(object.id);
       this._callbacks.delete(object.id);
-      if (object.error) {
-        log.formatProtocol('method <= browser ERR',
-            {method: callback.method, params: object.result}, 'error');
-        callback.reject(object.result);
-        return;
-      }
-      log.formatProtocol('method <= browser OK',
+
+      // handleRawError returns or throws synchronously; wrap to put into promise chain.
+      return callback.resolve(Promise.resolve().then(_ => {
+        if (object.error) {
+          return this.handleRawError(object.error, callback.method);
+        }
+
+        log.formatProtocol('method <= browser OK',
           {method: callback.method, params: object.result}, 'verbose');
-      callback.resolve(object.result);
-      return;
+        return object.result;
+      }));
     }
-    log.formatProtocol('method <= browser EVENT',
-        {method: object.method, params: object.result}, 'verbose');
+    log.formatProtocol('<= event',
+        {method: object.method, params: object.params}, 'verbose');
     this.emitNotification(object.method, object.params);
   }
 
   /**
-   * @param {!string} command
+   * Handles error responses from the protocol, absorbing errors we don't care
+   * about and throwing on the rest.
+   *
+   * Currently the only error ignored is from defensive calls of `DOM.disable`
+   * when already disabled.
+   * @param {{message: string}} error
+   * @param {string} method Protocol method that received the error response.
+   * @throws {Error}
+   * @protected
+   */
+  handleRawError(error, method) {
+    // We proactively disable the DOM domain. Ignore any errors.
+    if (error.message && error.message.includes('DOM agent hasn\'t been enabled')) {
+      return;
+    }
+
+    log.formatProtocol('method <= browser ERR', {method}, 'error');
+    throw new Error(`Protocol error (${method}): ${error.message}`);
+  }
+
+  /**
+   * @param {!string} method
    * @param {!Object} params
    * @protected
    */
@@ -5767,7 +7925,7 @@
 
 module.exports = Connection;
 
-},{"../../lib/log.js":21,"events":195}],13:[function(require,module,exports){
+},{"../../lib/log.js":24,"events":201}],14:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All Rights Reserved.
@@ -5786,14 +7944,262 @@
  */
 'use strict';
 
-const NetworkRecorder = require('../../lib/network-recorder');
-const emulation = require('../../lib/emulation');
-const Element = require('../../lib/element');
-const EventEmitter = require('events').EventEmitter;
-const parseURL = require('url').parse;
-
+const Connection = require('./connection.js');
 const log = require('../../lib/log.js');
 
+/* globals chrome */
+
+class ExtensionConnection extends Connection {
+
+  constructor() {
+    super();
+    this._tabId = null;
+
+    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 error;
+          try {
+            error = JSON.parse(message);
+          } catch (e) {}
+          error = error || {message: 'Unknown debugger protocol error.'};
+
+          // handleRawError returns or throws synchronously, so try/catch awkwardly.
+          try {
+            return resolve(this.handleRawError(error, command));
+          } catch (err) {
+            return reject(err);
+          }
+        }
+
+        log.formatProtocol('method <= browser OK', {method: command, params: result}, 'verbose');
+        resolve(result);
+      });
+    });
+  }
+
+  _queryCurrentTab() {
+    return new Promise((resolve, reject) => {
+      const queryOpts = {
+        active: true,
+        lastFocusedWindow: true,
+        windowType: 'normal'
+      };
+
+      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));
+        }
+        resolve(tabs[0]);
+      }));
+    });
+  }
+
+  /**
+   * Used by lighthouse-background to kick off the run on the current page
+   */
+  getCurrentTabURL() {
+    return this._queryCurrentTab().then(tab => tab.url);
+  }
+}
+
+module.exports = ExtensionConnection;
+
+},{"../../lib/log.js":24,"./connection.js":13}],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';
+
+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) { }
+
+  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();
+  }
+
+  /**
+   * @override
+   * @param {string} message
+   */
+  sendRawMessage(message) {
+    this._port.send(message);
+  }
+}
+
+module.exports = RawConnection;
+
+},{"./connection.js":13}],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 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 MAX_WAIT_FOR_FULLY_LOADED = 25 * 1000;
 const PAUSE_AFTER_LOAD = 500;
 
@@ -5807,6 +8213,7 @@
     this._traceCategories = Driver.traceCategories;
     this._eventEmitter = new EventEmitter();
     this._connection = connection;
+    this.online = true;
     connection.on('notification', event => this._eventEmitter.emit(event.method, event.params));
   }
 
@@ -5817,14 +8224,14 @@
       'blink.console',
       'blink.user_timing',
       'benchmark',
-      'netlog',
+      'latencyInfo',
       'devtools.timeline',
-      'disabled-by-default-blink.debug.layout',
       'disabled-by-default-devtools.timeline',
       'disabled-by-default-devtools.timeline.frame',
       'disabled-by-default-devtools.timeline.stack',
-      // 'disabled-by-default-v8.cpu_profile',  // these would include JS stack samples, but
-      // 'disabled-by-default-v8.cpu_profile.hires', // will take the trace from 5MB -> 100MB
+      // 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'
     ];
   }
@@ -5900,28 +8307,53 @@
   }
 
   /**
-   * Evaluate an expression in the context of the current page. Expression must
-   * evaluate to a Promise. Returns a promise that resolves on asyncExpression's
-   * resolved value.
-   * @param {string} asyncExpression
+   * 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(asyncExpression) {
+  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
       );
+
       this.sendCommand('Runtime.evaluate', {
-        expression: asyncExpression,
+        // 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()
+              .then(_ => ${expression})
+              .catch(${wrapRuntimeEvalErrorInBrowser.toString()})
+              .then(resolve);
+          });
+        }())`,
         includeCommandLineAPI: true,
         awaitPromise: true,
         returnByValue: true
       }).then(result => {
         clearTimeout(asyncTimeout);
-        resolve(result.result.value);
-      }).catch(reject);
+        const value = result.result.value;
+
+        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);
+      });
     });
   }
 
@@ -5947,10 +8379,61 @@
     });
   }
 
+  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);
+    });
+  }
+
+  /**
+   * 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 => {
@@ -6110,8 +8593,45 @@
       .then(_ => waitForLoad && this._waitForFullyLoaded(pauseAfterLoadMs));
   }
 
-  reloadForCleanStateIfNeeded() {
-    return Promise.resolve();
+  /**
+  * @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) {
+          resolve(propertyForName.value.value);
+        } else {
+          reject(null);
+        }
+      });
+    });
+  }
+
+  /**
+   * @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);
   }
 
   /**
@@ -6133,6 +8653,28 @@
       });
   }
 
+  /**
+   * @param {string} selector Selector to find in the DOM
+   * @return {!Promise<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;
+      });
+  }
+
   beginTrace() {
     const tracingOpts = {
       categories: this._traceCategories.join(','),
@@ -6140,7 +8682,12 @@
       options: 'sampling-frequency=10000'  // 1000 is default and too slow.
     };
 
-    return this.sendCommand('Page.enable')
+    // Disable any domains that could interfere or add overhead to the trace
+    return this.sendCommand('Debugger.disable')
+      .then(_ => this.sendCommand('CSS.disable'))
+      .then(_ => this.sendCommand('DOM.disable'))
+      // Enable Page domain to wait for Page.loadEventFired
+      .then(_ => this.sendCommand('Page.enable'))
       .then(_ => this.sendCommand('Tracing.start', tracingOpts));
   }
 
@@ -6162,8 +8709,8 @@
       // COMPAT: We've found `result` not retaining its value in this scenario when it's
       // declared with `let`. Observed in Chrome 50 and 52. While investigating the V8 bug
       // further, we'll use a plain `var` declaration.
-      var isEOF = false;
-      var result = '';
+      let isEOF = false;
+      let result = '';
 
       const readArguments = {
         handle: streamHandle.stream
@@ -6227,11 +8774,22 @@
     return this.sendCommand('Runtime.enable');
   }
 
-  beginEmulation() {
-    return Promise.all([
-      emulation.enableNexus5X(this),
-      emulation.enableNetworkThrottling(this)
-    ]);
+  beginEmulation(flags) {
+    const emulations = [];
+
+    if (!flags.disableDeviceEmulation) {
+      emulations.push(emulation.enableNexus5X(this));
+    }
+
+    if (!flags.disableNetworkThrottling) {
+      emulations.push(emulation.enableNetworkThrottling(this));
+    }
+
+    if (!flags.disableCpuThrottling) {
+      emulations.push(emulation.enableCPUThrottling(this));
+    }
+
+    return Promise.all(emulations);
   }
 
   /**
@@ -6239,22 +8797,28 @@
    * @return {!Promise}
    */
   goOffline() {
-    return this.sendCommand('Network.enable').then(_ => emulation.goOffline(this));
+    return this.sendCommand('Network.enable').then(_ => {
+      return emulation.goOffline(this);
+    }).then(_ => {
+      this.online = false;
+    });
   }
 
   /**
    * Enable internet connection, using emulated mobile settings if
-   * `options.flags.mobile` is true.
+   * `options.flags.disableNetworkThrottling` is false.
    * @param {!Object} options
    * @return {!Promise}
    */
   goOnline(options) {
     return this.sendCommand('Network.enable').then(_ => {
-      if (options.flags.mobile) {
+      if (!options.flags.disableNetworkThrottling) {
         return emulation.enableNetworkThrottling(this);
       }
 
       return emulation.disableNetworkThrottling(this);
+    }).then(_ => {
+      this.online = true;
     });
   }
 
@@ -6274,9 +8838,7 @@
   }
 
   clearDataForOrigin(url) {
-    const parsedURL = parseURL(url);
-    const origin = `${parsedURL.protocol}//${parsedURL.hostname}` +
-      (parsedURL.port ? `:${parsedURL.port}` : '');
+    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
@@ -6299,6 +8861,15 @@
   }
 
   /**
+   * 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').
@@ -6309,7 +8880,16 @@
     const globalVarToPopulate = `window['__${funcName}StackTraces']`;
     const collectUsage = () => {
       return this.evaluateAsync(
-          `__returnResults(Array.from(${globalVarToPopulate}).map(item => JSON.parse(item)))`);
+          `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();
@@ -6320,6 +8900,11 @@
 
     return collectUsage;
   }
+
+  blockUrlPatterns(urlPatterns) {
+    const promiseArr = urlPatterns.map(url => this.sendCommand('Network.addBlockedURL', {url}));
+    return Promise.all(promiseArr);
+  }
 }
 
 /**
@@ -6330,8 +8915,9 @@
  * @return {function(...*): *} A wrapper around the original function.
  */
 function captureJSCallUsage(funcRef, set) {
+  const __nativeError = window.__nativeError || Error;
   const originalFunc = funcRef;
-  const originalPrepareStackTrace = Error.prepareStackTrace;
+  const originalPrepareStackTrace = __nativeError.prepareStackTrace;
 
   return function() {
     // Note: this function runs in the context of the page that is being audited.
@@ -6339,276 +8925,75 @@
     const args = [...arguments]; // callee's arguments.
 
     // See v8's Stack Trace API https://github.com/v8/v8/wiki/Stack-Trace-API#customizing-stack-traces
-    Error.prepareStackTrace = function(error, structStackTrace) {
+    __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];
-      const file = callFrame.getFileName();
+      let url = callFrame.getFileName() || callFrame.getEvalOrigin();
       const line = callFrame.getLineNumber();
       const col = callFrame.getColumnNumber();
-      const stackTrace = structStackTrace.slice(1).map(
-          callsite => callsite.toString());
-      return {url: file, args, line, col, stackTrace}; // return value is e.stack
+      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 Error(`__called ${funcRef.name}__`);
+    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.
-    Error.prepareStackTrace = originalPrepareStackTrace;
+    __nativeError.prepareStackTrace = originalPrepareStackTrace;
 
+    // eslint-disable-next-line no-invalid-this
     return originalFunc.apply(this, arguments);
   };
 }
 
+/**
+ * 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';
+
+  return {
+    __failedInBrowser: true,
+    name: err.name || 'Error',
+    message: err.message || fallbackMessage,
+    stack: err.stack || (new Error()).stack,
+  };
+}
+
 module.exports = Driver;
 
-},{"../../lib/element":18,"../../lib/emulation":19,"../../lib/log.js":21,"../../lib/network-recorder":23,"events":195,"url":204}],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';
-
-const Connection = require('./connection.js');
-const log = require('../../lib/log.js');
-
-/* globals chrome */
-
-class ExtensionConnection extends Connection {
-
-  constructor() {
-    super();
-    this._tabId = null;
-
-    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(chrome.runtime.lastError);
-            }
-            resolve(tabId);
-          });
-        });
-      });
-  }
-
-  /**
-   * @override
-   * @return {!Promise}
-   */
-  disconnect() {
-    if (this._tabId === null) {
-      return Promise.resolve();
-    }
-
-    const tabId = this._tabId;
-    return new Promise((resolve, reject) => {
-      chrome.debugger.detach({tabId}, _ => {
-        if (chrome.runtime.lastError) {
-          return reject(chrome.runtime.lastError);
-        }
-        resolve();
-      });
-    }).then(_ => this._detachCleanup());
-  }
-
-  reloadForCleanStateIfNeeded(options) {
-    // Reload the page to remove any side-effects (like disabling JavaScript).
-    const status = 'Reloading page to reset state';
-    log.log('status', status);
-    return this.gotoURL(options.url).then(_ => {
-      log.log('statusEnd', status);
-    });
-  }
-
-  /**
-   * @override
-   * @param {!string} method
-   * @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('No tabId set for sendCommand');
-      }
-      chrome.debugger.sendCommand({tabId: this._tabId}, command, params, result => {
-        if (chrome.runtime.lastError) {
-          log.formatProtocol('method <= browser ERR', {method: command, params: result}, 'error');
-          return reject(chrome.runtime.lastError);
-        }
-
-        if (result.wasThrown) {
-          log.formatProtocol('method <= browser ERR', {method: command, params: result}, 'error');
-          return reject(result.exceptionDetails);
-        }
-
-        log.formatProtocol('method <= browser OK', {method: command, params: result}, 'verbose');
-        resolve(result);
-      });
-    });
-  }
-
-  _queryCurrentTab() {
-    return new Promise((resolve, reject) => {
-      const queryOpts = {
-        active: true,
-        lastFocusedWindow: true,
-        windowType: 'normal'
-      };
-
-      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));
-        }
-        resolve(tabs[0]);
-      }));
-    });
-  }
-
-  /**
-   * Used by lighthouse-background to kick off the run on the current page
-   */
-  getCurrentTabURL() {
-    return this._queryCurrentTab().then(tab => tab.url);
-  }
-}
-
-module.exports = ExtensionConnection;
-
-},{"../../lib/log.js":21,"./connection.js":12}],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';
-
-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) { }
-
-  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();
-  }
-
-  /**
-   * @override
-   * @param {string} message
-   */
-  sendRawMessage(message) {
-    this._port.send(message);
-  }
-}
-
-module.exports = RawConnection;
-
-},{"./connection.js":12}],16:[function(require,module,exports){
+},{"../lib/element":20,"../lib/emulation":21,"../lib/log.js":24,"../lib/network-recorder":26,"../lib/url-shim":30,"events":201}],17:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -6636,11 +9021,15 @@
  * Execution sequence when GatherRunner.run() is called:
  *
  * 1. Setup
- *   A. driver.connect()
- *   B. GatherRunner.setupDriver()
- *     i. beginEmulation
- *     ii. cleanAndDisableBrowserCaches
- *     iii. clearDataForOrigin
+ *   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
  *
  * 2. For each pass in the config:
  *   A. GatherRunner.beforePass()
@@ -6656,9 +9045,10 @@
  *     ii. all gatherer's afterPass()
  *
  * 3. Teardown
- *   A. reloadForCleanStateIfNeeded
- *   B. driver.disconnect()
- *   C. collect all artifacts and return them
+ *   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 {
   /**
@@ -6696,25 +9086,52 @@
 
   static setupDriver(driver, options) {
     log.log('status', 'Initializing…');
-    // Enable emulation if required.
-    return Promise.resolve(options.flags.mobile && driver.beginEmulation())
+    // Enable emulation based on flags
+    return driver.assertNoSameOriginServiceWorkerClients(options.url)
+      .then(_ => driver.beginEmulation(options.flags))
       .then(_ => driver.enableRuntimeEvents())
+      .then(_ => driver.cacheNatives())
       .then(_ => driver.cleanAndDisableBrowserCaches())
-      .then(_ => driver.clearDataForOrigin(options.url));
+      .then(_ => driver.clearDataForOrigin(options.url))
+      .then(_ => driver.blockUrlPatterns(options.flags.blockedUrlPatterns || []));
+  }
+
+  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();
+  }
+
+  /**
+   * Catches any `recoverable` errors from the supplied promise, rejecting on
+   * the rest.
+   * @param {!Promise<*>} promise
+   * @return {!Promise<*>}
+   */
+  static recoverOrThrow(promise) {
+    return promise.catch(err => {
+      if (!err.recoverable) {
+        throw err;
+      }
+    });
   }
 
   /**
    * Navigates to about:blank and calls beforePass() on gatherers before tracing
    * has started and before navigation to the target page.
    * @param {!Object} options
+   * @param {!Object<!Array<!Promise<*>>} gathererResults
    * @return {!Promise}
    */
-  static beforePass(options) {
+  static beforePass(options, gathererResults) {
     const pass = GatherRunner.loadBlank(options.driver);
 
     return options.config.gatherers.reduce((chain, gatherer) => {
       return chain.then(_ => {
-        return gatherer.beforePass(options);
+        const artifactPromise = Promise.resolve().then(_ => gatherer.beforePass(options));
+        gathererResults[gatherer.name] = [artifactPromise];
+        return GatherRunner.recoverOrThrow(artifactPromise);
       });
     }, pass);
   }
@@ -6723,9 +9140,10 @@
    * Navigates to requested URL and then runs pass() on gatherers while trace
    * (if requested) is still being recorded.
    * @param {!Object} options
+   * @param {!Object<!Array<!Promise<*>>} gathererResults
    * @return {!Promise}
    */
-  static pass(options) {
+  static pass(options, gathererResults) {
     const driver = options.driver;
     const config = options.config;
     const gatherers = config.gatherers;
@@ -6739,7 +9157,11 @@
     });
 
     return gatherers.reduce((chain, gatherer) => {
-      return chain.then(_ => gatherer.pass(options));
+      return chain.then(_ => {
+        const artifactPromise = Promise.resolve().then(_ => gatherer.pass(options));
+        gathererResults[gatherer.name].push(artifactPromise);
+        return GatherRunner.recoverOrThrow(artifactPromise);
+      });
     }, pass);
   }
 
@@ -6748,9 +9170,10 @@
    * afterPass() on gatherers with trace data passed in. Promise resolves with
    * object containing trace and network data.
    * @param {!Object} options
+   * @param {!Object<!Array<!Promise<*>>} gathererResults
    * @return {!Promise}
    */
-  static afterPass(options) {
+  static afterPass(options, gathererResults) {
     const driver = options.driver;
     const config = options.config;
     const gatherers = config.gatherers;
@@ -6777,6 +9200,13 @@
       log.log('status', status);
       return driver.endNetworkCollect();
     }).then(networkRecords => {
+      const mainRecord = networkRecords.find(record => record.url === options.url);
+      if (driver.online && mainRecord.failed) {
+        log.error('GatherRunner', mainRecord.localizedFailDescription);
+        const error = new Error(`Unable to load the page: ${mainRecord.localizedFailDescription}`);
+        error.code = 'PAGE_LOAD_ERROR';
+        return Promise.reject(error);
+      }
       // Network records only given to gatherers if requested by config.
       config.recordNetwork && (passData.networkRecords = networkRecords);
       log.verbose('statusEnd', status);
@@ -6786,10 +9216,11 @@
       const status = `Retrieving: ${gatherer.name}`;
       return chain.then(_ => {
         log.log('status', status);
-        return gatherer.afterPass(options, passData);
-      }).then(ret => {
+        const artifactPromise = Promise.resolve().then(_ => gatherer.afterPass(options, passData));
+        gathererResults[gatherer.name].push(artifactPromise);
+        return GatherRunner.recoverOrThrow(artifactPromise);
+      }).then(_ => {
         log.verbose('statusEnd', status);
-        return ret;
       });
     }, pass);
 
@@ -6797,6 +9228,39 @@
     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 recoverable error was rejected from a gatherer phase,
+   * uses that error as the artifact instead.
+   * @param {!Object<!Array<!Promise<*>>} 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 recoverable, 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 = {
@@ -6816,15 +9280,17 @@
       return Promise.reject(new Error('You must provide a config'));
     }
 
-    // Default mobile emulation and page loading to true.
-    // The extension will switch these off initially.
-    if (typeof options.flags.mobile === 'undefined') {
-      options.flags.mobile = true;
+    // CPU throttling is temporarily off by default
+    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))
 
       // Run each pass
@@ -6834,9 +9300,9 @@
         return passes.reduce((chain, config, passIndex) => {
           const runOptions = Object.assign({}, options, {config});
           return chain
-            .then(_ => GatherRunner.beforePass(runOptions))
-            .then(_ => GatherRunner.pass(runOptions))
-            .then(_ => GatherRunner.afterPass(runOptions))
+            .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.
@@ -6853,29 +9319,19 @@
           options.url = urlAfterRedirects;
         });
       })
-      .then(_ => {
-        // We dont need to hold up the reporting for the reload/disconnect,
-        // so we will not return a promise in here.
-        driver.reloadForCleanStateIfNeeded(options).then(_ => {
-          log.log('status', 'Disconnecting from browser...');
-          driver.disconnect();
-        });
-      })
-      .then(_ => {
-        // Collate all the gatherer results.
+      .then(_ => GatherRunner.disposeDriver(driver))
+      .then(_ => GatherRunner.collectArtifacts(gathererResults))
+      .then(artifacts => {
+        // Add tracing data and computed artifacts to artifacts object.
         const computedArtifacts = this.instantiateComputedArtifacts();
-        const artifacts = Object.assign({}, computedArtifacts, tracingData);
-
-        passes.forEach(pass => {
-          pass.gatherers.forEach(gatherer => {
-            if (typeof gatherer.artifact === 'undefined') {
-              throw new Error(`${gatherer.constructor.name} failed to provide an artifact.`);
-            }
-
-            artifacts[gatherer.name] = gatherer.artifact;
-          });
-        });
+        Object.assign(artifacts, computedArtifacts, tracingData);
         return artifacts;
+      })
+      // cleanup on error
+      .catch(err => {
+        GatherRunner.disposeDriver(driver);
+
+        throw err;
       });
   }
 
@@ -6921,10 +9377,6 @@
     if (typeof gathererInstance.afterPass !== 'function') {
       throw new Error(`${gathererName} has no afterPass() method.`);
     }
-
-    if (typeof gathererInstance.artifact !== 'object') {
-      throw new Error(`${gathererName} has no artifact property.`);
-    }
   }
 
   static instantiateComputedArtifacts() {
@@ -6959,7 +9411,76 @@
 
 module.exports = GatherRunner;
 
-},{"../audits/audit":"../audits/audit","../lib/log.js":21,"../runner":27,"path":198}],17:[function(require,module,exports){
+},{"../audits/audit":3,"../lib/log.js":24,"../runner":33,"path":204}],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';
+
+/**
+ * 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 `recoverable` property on the Error. If set to
+ * `true`, the runner will treat it as an expected 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 */
+
+}
+
+module.exports = Gatherer;
+
+},{}],19:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All Rights Reserved.
@@ -7015,7 +9536,7 @@
 
 module.exports = ConsoleQuieter;
 
-},{"./log.js":21}],18:[function(require,module,exports){
+},{"./log.js":24}],20:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All Rights Reserved.
@@ -7064,11 +9585,25 @@
         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);
+      });
+  }
 }
 
 module.exports = Element;
 
-},{}],19:[function(require,module,exports){
+},{}],21:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All Rights Reserved.
@@ -7114,8 +9649,8 @@
 
 const TYPICAL_MOBILE_THROTTLING_METRICS = {
   latency: 150, // 150ms
-  downloadThroughput: 1.6 * 1024 * 1024 / 8, // 1.6Mbps
-  uploadThroughput: 750 * 1024 / 8, // 750Kbps
+  downloadThroughput: Math.floor(1.6 * 1024 * 1024 / 8), // 1.6Mbps
+  uploadThroughput: Math.floor(750 * 1024 / 8), // 750Kbps
   offline: false
 };
 
@@ -7134,6 +9669,13 @@
   offline: false
 };
 
+const NO_CPU_THROTTLE_METRICS = {
+  rate: 1
+};
+const CPU_THROTTLE_METRICS = {
+  rate: 5
+};
+
 function enableNexus5X(driver) {
   /**
    * Finalizes touch emulation by enabling `"ontouchstart" in window` feature detect
@@ -7144,9 +9686,9 @@
   /* eslint-disable no-proto */ /* global window, document */ /* istanbul ignore next */
   const injectedTouchEventsFunction = function() {
     const touchEvents = ['ontouchstart', 'ontouchend', 'ontouchmove', 'ontouchcancel'];
-    var recepients = [window.__proto__, document.__proto__];
-    for (var i = 0; i < touchEvents.length; ++i) {
-      for (var j = 0; j < recepients.length; ++j) {
+    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
@@ -7184,14 +9726,110 @@
   return driver.sendCommand('Network.emulateNetworkConditions', OFFLINE_METRICS);
 }
 
+function enableCPUThrottling(driver) {
+  return driver.sendCommand('Emulation.setCPUThrottlingRate', CPU_THROTTLE_METRICS);
+}
+
+function disableCPUThrottling(driver) {
+  return driver.sendCommand('Emulation.setCPUThrottlingRate', NO_CPU_THROTTLE_METRICS);
+}
+
 module.exports = {
   enableNexus5X,
   enableNetworkThrottling,
   disableNetworkThrottling,
+  enableCPUThrottling,
+  disableCPUThrottling,
   goOffline
 };
 
-},{}],20:[function(require,module,exports){
+},{}],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';
+
+/**
+ * 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}`,
+    code: `${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, code: 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.code.trim() + '\n\n';
+    }, '');
+    lineColUrlObj.code = 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);
+  });
+
+  return results;
+}
+
+module.exports = {
+  addFormattedCodeSnippet,
+  groupCodeSnippetsByLocation
+};
+
+},{}],23:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All Rights Reserved.
@@ -7234,7 +9872,7 @@
   // 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.apply([], nestedSizes);
+  const flattenedSizes = [].concat(...nestedSizes);
 
   return flattenedSizes
       // First, filter out any undefined values, in case an icon was defined without a size
@@ -7259,7 +9897,7 @@
   sizeAtLeast
 };
 
-},{}],21:[function(require,module,exports){
+},{}],24:[function(require,module,exports){
 (function (process){
 /**
  * @license
@@ -7281,30 +9919,28 @@
 
 const debug = require('debug');
 const EventEmitter = require('events').EventEmitter;
+const isWindows = process.platform === 'win32';
 
-function setLevel(level) {
-  if (level === 'verbose') {
-    debug.enable('*');
-  } else if (level === 'error') {
-    debug.enable('*:error');
-  } else {
-    debug.enable('*, -*:verbose');
-  }
-}
+// process.browser is set when browserify'd via the `process` npm module
+const isBrowser = process.browser;
 
-const loggers = {};
-function _log(title, logargs) {
-  const args = [...logargs].slice(1);
-  if (!loggers[title]) {
-    loggers[title] = debug(title);
-  }
-  return loggers[title](...args);
-}
+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
    */
   issueStatus(title, args) {
     if (title === 'status' || title === 'statusEnd') {
@@ -7321,48 +9957,147 @@
   }
 }
 
-/**
- * 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'.
- */
-function formatProtocol(prefix, data, level) {
-  const columns = (!process || process.browser) ? Infinity : process.stdout.columns;
-  const maxLength = columns - data.method.length - prefix.length - 18;
-  // IO.read blacklisted here to avoid logging megabytes of trace data
-  const snippet = (data.params && data.method !== 'IO.read') ?
+const loggersByTitle = {};
+const loggingBufferColumns = 25;
+
+class Log {
+
+  static _logToStdErr(title, argsArray) {
+    const args = [...argsArray];
+    const log = Log.loggerfn(title);
+    log(...args);
+  }
+
+  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.disable();
+        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) : '';
-  level = level || 'log';
-  _log(`${prefix}:${level}`, prefix, data.method, snippet);
+    Log._logToStdErr(`${prefix}:${level || ''}`, [data.method, snippet]);
+  }
+
+  static log(title) {
+    Log.events.issueStatus(title, arguments);
+    return Log._logToStdErr(title, Array.from(arguments).slice(1));
+  }
+
+  static warn(title) {
+    Log.events.issueWarning(arguments);
+    return Log._logToStdErr(`${title}:warn`, Array.from(arguments).slice(1));
+  }
+
+  static error(title) {
+    return Log._logToStdErr(`${title}:error`, Array.from(arguments).slice(1));
+  }
+
+  static verbose(title) {
+    Log.events.issueStatus(title);
+    return Log._logToStdErr(`${title}:verbose`, Array.from(arguments).slice(1));
+  }
+
+  /**
+   * 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 doubleLightHorizontal() {
+    return '──';
+  }
 }
 
-module.exports = {
-  setLevel,
-  formatProtocol,
-  events: new Emitter(),
-  log(title) {
-    this.events.issueStatus(title, arguments);
-    return _log(title, arguments);
-  },
+Log.events = new Emitter();
 
-  warn(title) {
-    this.events.issueWarning(arguments);
-    return _log(`${title}:warn`, arguments);
-  },
-
-  error(title) {
-    return _log(`${title}:error`, arguments);
-  },
-
-  verbose(title) {
-    this.events.issueStatus(title, arguments);
-    return _log(`${title}:verbose`, arguments);
-  }
-};
+module.exports = Log;
 
 }).call(this,require('_process'))
-},{"_process":199,"debug":234,"events":195}],22:[function(require,module,exports){
+},{"_process":205,"debug":234,"events":201}],25:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All Rights Reserved.
@@ -7381,7 +10116,7 @@
  */
 'use strict';
 
-const url = require('url');
+const URL = require('./url-shim');
 const validateColor = require('./web-inspector').Color.parse;
 
 const ALLOWED_DISPLAY_VALUES = [
@@ -7460,12 +10195,10 @@
  * @return {boolean}
  */
 function checkSameOrigin(url1, url2) {
-  const parsed1 = url.parse(url1);
-  const parsed2 = url.parse(url2);
+  const parsed1 = new URL(url1);
+  const parsed2 = new URL(url2);
 
-  return parsed1.protocol === parsed2.protocol &&
-      parsed1.hostname === parsed2.hostname &&
-      parsed1.port === parsed2.port;
+  return parsed1.origin === parsed2.origin;
 }
 
 /**
@@ -7491,9 +10224,7 @@
   // 8.10(4) - construct URL with raw as input and manifestUrl as the base.
   let startUrl;
   try {
-    // TODO(bckenny): need better URL constructor to do this properly. See
-    // https://github.com/GoogleChrome/lighthouse/issues/602
-    startUrl = url.resolve(manifestUrl, raw);
+    startUrl = new URL(raw, manifestUrl).href;
   } catch (e) {
     // 8.10(5) - discard invalid URLs.
     return {
@@ -7556,10 +10287,8 @@
     src.value = undefined;
   }
   if (src.value) {
-    // TODO(bckenny): need better URL constructor to do this properly. See
-    // https://github.com/GoogleChrome/lighthouse/issues/602
     // 9.4(4) - construct URL with manifest URL as the base
-    src.value = url.resolve(manifestUrl, src.value);
+    src.value = new URL(src.value, manifestUrl).href;
   }
 
   const type = parseString(raw.type, true);
@@ -7640,10 +10369,8 @@
   const appUrl = parseString(raw.url, true);
   if (appUrl.value) {
     try {
-      // TODO(bckenny): need better URL constructor to do this properly. See
-      // https://github.com/GoogleChrome/lighthouse/issues/602
       // 10.2.(4) - attempt to construct URL.
-      appUrl.value = url.parse(appUrl.value).href;
+      appUrl.value = new URL(appUrl.value).href;
     } catch (e) {
       appUrl.value = undefined;
       appUrl.debugString = 'ERROR: invalid application URL ${raw.url}';
@@ -7771,7 +10498,7 @@
 
 module.exports = parse;
 
-},{"./web-inspector":26,"url":204}],23:[function(require,module,exports){
+},{"./url-shim":30,"./web-inspector":31}],26:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All Rights Reserved.
@@ -7944,7 +10671,141 @@
 
 module.exports = NetworkRecorder;
 
-},{"../lib/log.js":21,"./web-inspector":26,"events":195}],24:[function(require,module,exports){
+},{"../lib/log.js":24,"./web-inspector":31,"events":201}],27:[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 indexOf on null value
+      if (propName) {
+        propName = Array.isArray(propName) ? propName : [propName];
+        usedName = propName.indexOf(item.property.name) > -1;
+      }
+      if (propVal) {
+        propVal = Array.isArray(propVal) ? propVal : [propVal];
+        usedVal = propVal.indexOf(item.property.val) > -1;
+      }
+      // 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} {
+  ${rule}
+}`;
+
+  return {
+    styleRule: block.trim(),
+    location: `line: ${startLine}, row: ${start}, col: ${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
+};
+
+},{}],28:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All Rights Reserved.
@@ -8017,17 +10878,17 @@
   }
 
   topDown() {
-    var filters = [];
+    const filters = [];
     filters.push(WebInspector.TimelineUIUtils.visibleEventsFilter());
     filters.push(new WebInspector.ExcludeTopLevelFilter());
-    var nonessentialEvents = [
+    const nonessentialEvents = [
       WebInspector.TimelineModel.RecordType.EventDispatch,
       WebInspector.TimelineModel.RecordType.FunctionCall,
       WebInspector.TimelineModel.RecordType.TimerFire
     ];
     filters.push(new WebInspector.ExclusiveNameFilter(nonessentialEvents));
 
-    var topDown = WebInspector.TimelineProfileTree.buildTopDown(
+    const topDown = WebInspector.TimelineProfileTree.buildTopDown(
         this._timelineModel.mainThreadEvents(),
         filters, /* startTime */ 0, /* endTime */ Infinity,
         WebInspector.TimelineAggregator.eventId);
@@ -8035,9 +10896,9 @@
   }
 
   bottomUp() {
-    var topDown = this.topDown();
-    var noGrouping = WebInspector.TimelineAggregator.GroupBy.None;
-    var noGroupAggregator = this._createAggregator().groupFunction(noGrouping);
+    const topDown = this.topDown();
+    const noGrouping = WebInspector.TimelineAggregator.GroupBy.None;
+    const noGroupAggregator = this._createAggregator().groupFunction(noGrouping);
     return WebInspector.TimelineProfileTree.buildBottomUp(topDown, noGroupAggregator);
   }
 
@@ -8046,11 +10907,11 @@
   * @return {!WebInspector.TimelineProfileTree.Node} A grouped and sorted tree
   */
   bottomUpGroupBy(grouping) {
-    var topDown = this.topDown();
+    const topDown = this.topDown();
 
-    var groupSetting = WebInspector.TimelineAggregator.GroupBy[grouping];
-    var groupingAggregator = this._createAggregator().groupFunction(groupSetting);
-    var bottomUpGrouped =
+    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
@@ -8059,7 +10920,7 @@
   }
 
   frameModel() {
-    var frameModel = new WebInspector.TimelineFrameModel(event =>
+    const frameModel = new WebInspector.TimelineFrameModel(event =>
       WebInspector.TimelineUIUtils.eventStyle(event).category.name
     );
     frameModel.addTraceEvents({ /* target */ },
@@ -8072,7 +10933,7 @@
   }
 
   interactionModel() {
-    var irModel = new WebInspector.TimelineIRModel();
+    const irModel = new WebInspector.TimelineIRModel();
     irModel.populate(this._timelineModel);
     return irModel;
   }
@@ -8081,7 +10942,7 @@
 
 module.exports = TimelineModel;
 
-},{"../console-quieter":17,"../web-inspector":26,"devtools-timeline-model/lib/timeline-model-treeview.js":236}],25:[function(require,module,exports){
+},{"../console-quieter":19,"../web-inspector":31,"devtools-timeline-model/lib/timeline-model-treeview.js":236}],29:[function(require,module,exports){
 (function (global){
 /**
  * @license
@@ -8120,16 +10981,15 @@
   global[exportName] = glMatrixModule[exportName];
 });
 // from catapult/tracing/tracing/extras/importer/jszip.html
-global.JSZip = require('jszip/dist/jszip.min.js');
+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 'jszip/dist/jszip.min.js';
+    return '../../../lib/empty-stub.js';
   }
   if (path === '/mannwhitneyu.js') {
     return '../../../lib/empty-stub.js';
@@ -8195,7 +11055,7 @@
    * 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/18gvP-CBA2BiBpi3Rz1I1ISciKGhniTSZ9TY0XCnXS7E/preview
+   * @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.
@@ -8259,7 +11119,7 @@
   /**
    * 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/18gvP-CBA2BiBpi3Rz1I1ISciKGhniTSZ9TY0XCnXS7E/preview
+   * @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.
@@ -8278,6 +11138,21 @@
       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';
@@ -8312,8 +11187,10 @@
     });
     durations.sort((a, b) => a - b);
 
-    // Actual calculation of percentiles done in _riskPercentiles.
-    return TraceProcessor._riskPercentiles(durations, totalTime, percentiles, clippedLength);
+    return {
+      durations,
+      clippedLength
+    };
   }
 
   /**
@@ -8347,7 +11224,98 @@
 module.exports = TraceProcessor;
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"../../third_party/traceviewer-js/":79,"gl-matrix":237,"jszip/dist/jszip.min.js":251}],26:[function(require,module,exports){
+},{"../../third_party/traceviewer-js/":85,"gl-matrix":237}],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.
+ */
+
+/**
+ * URL shim so we keep our code DRY
+ */
+
+'use strict';
+
+/* global self */
+
+// 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} url
+ * @return {string}
+ */
+URL.getDisplayName = function getDisplayName(url) {
+  const parsed = new URL(url);
+
+  // Handle 'about:*' URLs specially since they have no path.
+  let name = parsed.protocol === 'about:' ? parsed.href :
+      // Otherwise, remove any query strings from the path.
+      parsed.pathname.replace(/\?.*/, '')
+      // And grab the last two parts.
+      .split('/').slice(-2).join('/');
+
+  const MAX_LENGTH = 64;
+  // Always elide hash
+  name = name.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, '$1\u2026');
+  // Elide too long names
+  if (name.length > MAX_LENGTH) {
+    const dotIndex = name.lastIndexOf('.');
+    name = name.slice(0, MAX_LENGTH - 1 - (name.length - dotIndex)) +
+        // Show file extension
+        `\u2026${name.slice(dotIndex)}`;
+  }
+
+  return name;
+};
+
+module.exports = URL;
+
+},{"whatwg-url":199}],31:[function(require,module,exports){
 (function (global){
 /**
  * @license
@@ -8575,7 +11543,7 @@
     },
 
     _onRequestStarted: function(event) {
-      var request = event.data;
+      const request = event.data;
       if (this._requests.has(request.url)) {
         return;
       }
@@ -8628,20 +11596,20 @@
 
   // Mostly taken from from chrome-devtools-frontend/front_end/gonzales/SCSSParser.js.
   WebInspector.SCSSParser.prototype.parse = function(content) {
-    var ast = null;
+    let ast = null;
     try {
       ast = gonzales.parse(content, {syntax: 'css'});
     } catch (e) {
-      return [];
+      return {error: e};
     }
 
     /** @type {!{properties: !Array<!Gonzales.Node>, node: !Gonzales.Node}} */
-    var rootBlock = {
+    const rootBlock = {
       properties: [],
       node: ast
     };
     /** @type {!Array<!{properties: !Array<!Gonzales.Node>, node: !Gonzales.Node}>} */
-    var blocks = [rootBlock];
+    const blocks = [rootBlock];
     ast.selectors = [];
     WebInspector.SCSSParser.extractNodes(ast, blocks, rootBlock);
 
@@ -8652,7 +11620,328 @@
 })();
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"chrome-devtools-frontend/front_end/bindings/TempFile.js":206,"chrome-devtools-frontend/front_end/common/Color.js":207,"chrome-devtools-frontend/front_end/common/Object.js":208,"chrome-devtools-frontend/front_end/common/ParsedURL.js":209,"chrome-devtools-frontend/front_end/common/ResourceType.js":210,"chrome-devtools-frontend/front_end/common/SegmentedRange.js":211,"chrome-devtools-frontend/front_end/common/TextRange.js":212,"chrome-devtools-frontend/front_end/common/UIString.js":213,"chrome-devtools-frontend/front_end/components_lazy/FilmStripModel.js":214,"chrome-devtools-frontend/front_end/gonzales/SCSSParser.js":215,"chrome-devtools-frontend/front_end/gonzales/gonzales-scss.js":216,"chrome-devtools-frontend/front_end/platform/utilities.js":217,"chrome-devtools-frontend/front_end/sdk/CPUProfileDataModel.js":218,"chrome-devtools-frontend/front_end/sdk/NetworkManager.js":219,"chrome-devtools-frontend/front_end/sdk/NetworkRequest.js":220,"chrome-devtools-frontend/front_end/sdk/ProfileTreeModel.js":221,"chrome-devtools-frontend/front_end/sdk/Target.js":222,"chrome-devtools-frontend/front_end/sdk/TargetManager.js":223,"chrome-devtools-frontend/front_end/sdk/TracingModel.js":224,"chrome-devtools-frontend/front_end/timeline/TimelineTreeView.js":225,"chrome-devtools-frontend/front_end/timeline/TimelineUIUtils.js":226,"chrome-devtools-frontend/front_end/timeline_model/LayerTreeModel.js":227,"chrome-devtools-frontend/front_end/timeline_model/TimelineFrameModel.js":228,"chrome-devtools-frontend/front_end/timeline_model/TimelineIRModel.js":229,"chrome-devtools-frontend/front_end/timeline_model/TimelineJSProfile.js":230,"chrome-devtools-frontend/front_end/timeline_model/TimelineModel.js":231,"chrome-devtools-frontend/front_end/timeline_model/TimelineProfileTree.js":232,"chrome-devtools-frontend/front_end/ui_lazy/SortableDataGrid.js":233}],27:[function(require,module,exports){
+},{"chrome-devtools-frontend/front_end/bindings/TempFile.js":206,"chrome-devtools-frontend/front_end/common/Color.js":207,"chrome-devtools-frontend/front_end/common/Object.js":208,"chrome-devtools-frontend/front_end/common/ParsedURL.js":209,"chrome-devtools-frontend/front_end/common/ResourceType.js":210,"chrome-devtools-frontend/front_end/common/SegmentedRange.js":211,"chrome-devtools-frontend/front_end/common/TextRange.js":212,"chrome-devtools-frontend/front_end/common/UIString.js":213,"chrome-devtools-frontend/front_end/components_lazy/FilmStripModel.js":214,"chrome-devtools-frontend/front_end/gonzales/SCSSParser.js":215,"chrome-devtools-frontend/front_end/gonzales/gonzales-scss.js":216,"chrome-devtools-frontend/front_end/platform/utilities.js":217,"chrome-devtools-frontend/front_end/sdk/CPUProfileDataModel.js":218,"chrome-devtools-frontend/front_end/sdk/NetworkManager.js":219,"chrome-devtools-frontend/front_end/sdk/NetworkRequest.js":220,"chrome-devtools-frontend/front_end/sdk/ProfileTreeModel.js":221,"chrome-devtools-frontend/front_end/sdk/Target.js":222,"chrome-devtools-frontend/front_end/sdk/TargetManager.js":223,"chrome-devtools-frontend/front_end/sdk/TracingModel.js":224,"chrome-devtools-frontend/front_end/timeline/TimelineTreeView.js":225,"chrome-devtools-frontend/front_end/timeline/TimelineUIUtils.js":226,"chrome-devtools-frontend/front_end/timeline_model/LayerTreeModel.js":227,"chrome-devtools-frontend/front_end/timeline_model/TimelineFrameModel.js":228,"chrome-devtools-frontend/front_end/timeline_model/TimelineIRModel.js":229,"chrome-devtools-frontend/front_end/timeline_model/TimelineJSProfile.js":230,"chrome-devtools-frontend/front_end/timeline_model/TimelineModel.js":231,"chrome-devtools-frontend/front_end/timeline_model/TimelineProfileTree.js":232,"chrome-devtools-frontend/front_end/ui_lazy/SortableDataGrid.js":233}],32:[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 Formatter = require('../formatters/formatter');
+const Handlebars = require('handlebars');
+
+const path = require('path');
+const marked = require('marked');
+
+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;
+}
+
+class ReportGenerator {
+
+  constructor() {
+    const getTotalScore = aggregation => {
+      return Math.round(aggregation.total * 100);
+    };
+
+    const getItemRating = value => {
+      if (typeof value === 'boolean') {
+        return value ? RATINGS.GOOD.label : RATINGS.POOR.label;
+      }
+      return calculateRating(value);
+    };
+
+    // Converts a name to a link.
+    Handlebars.registerHelper('nameToLink', name => {
+      return name.toLowerCase().replace(/\s/, '-');
+    });
+
+    // Figures out the total score for an aggregation
+    Handlebars.registerHelper('getTotalScore', getTotalScore);
+
+    // Converts the total score to a rating that can be used for styling.
+    Handlebars.registerHelper('getTotalScoreRating', aggregation => {
+      const totalScore = getTotalScore(aggregation);
+      return calculateRating(totalScore);
+    });
+
+    // Converts a value to a rating string, which can be used inside the report
+    // for color styling.
+    Handlebars.registerHelper('getItemRating', getItemRating);
+
+    Handlebars.registerHelper('shouldShowHelpText',
+      value => (getItemRating(value) !== RATINGS.GOOD.label));
+
+    // Convert numbers to fixed point decimals
+    Handlebars.registerHelper('decimal', number => {
+      if (number && number.toFixed) {
+        return number.toFixed(2);
+      }
+      return number;
+    });
+
+    // value is boolean?
+    Handlebars.registerHelper('is-bool', value => (typeof value === 'boolean'));
+
+    // !value
+    Handlebars.registerHelper('not', value => !value);
+
+    // value == value2?
+    Handlebars.registerHelper('if_not_eq', 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);
+      }
+    });
+
+    // arg1 && arg2 && ... && argn
+    Handlebars.registerHelper('and', function() {
+      let arg = false;
+      for (let i = 0, n = arguments.length - 1; i < n; i++) {
+        arg = arguments[i];
+        if (!arg) {
+          break;
+        }
+      }
+      return arg;
+    });
+
+    // eslint-disable-next-line no-unused-vars
+    Handlebars.registerHelper('sanitize', function(str, opts) {
+      // const isViewer = opts.data.root.reportContext === 'viewer';
+
+      // 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 links and code snippets.
+      // All other markdown ad HTML is ignored.
+      const renderer = new marked.Renderer();
+      renderer.link = (href, title, text) => {
+        title = title || text;
+        return `<a href="${href}" target="_blank" rel="noopener" title="${title}">${text}</a>`;
+      };
+      renderer.codespan = function(str) {
+        return `<code>${str}</code>`;
+      };
+      // 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 santized and transformed. Mark it as safe so
+      // handlebars renders the text as HTML.
+      return new Handlebars.SafeString(str);
+    });
+  }
+
+  /**
+   * Format time
+   * @param {string} date
+   * @return {string}
+   */
+  _formatTime(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));
+  }
+
+  /**
+   * Escape closing script tags.
+   * @param {string} jsonStr
+   * @return {string}
+   */
+  _escapeScriptTags(jsonStr) {
+    return jsonStr.replace(/<\/script>/g, '<\\/script>');
+  }
+
+  /**
+   * Gets the template for the report.
+   * @return {string}
+   */
+  getReportTemplate() {
+    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\n<html data-report-context=\"{{reportContext}}\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, minimum-scale=1.0\">\n  <link rel=\"shortcut icon\" href=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAEfElEQVR4Aa2WA5B0RxDH/z3zuDp/tq2Lbdu2XUoxKMW2bdu2nZxte/dpplO3hTjZ2s0z5zftpkVrFXJYGCBAggEoUPY2p0XkOLoJNoE+yH5II3vLOQJyHL0bsgpiC9G1meishuiBzJEhchm9HXIL0fu5e949TuX1cvVbxplbiu6O3BjiP19HEFMpuNI9Z4O8XpIlKb6SbrnQOG0WeQGkKATAgAVdz3Sw/G6BenwoXAnls/JH1IqZ0fN70DeN2Q84b4AA0jBWkHewfXsUh0ykKSZTcVGamoincKR7xyrKpGH8uxDGv0vgmOqXZuf1o87ccP6wCpIkgb5ePZw2Yvj0icN+eshdMUcFIRVgAwIyGHTKUGqLEluUOWJqkpISRRi2S+BDgAuyAYHhwYsIxCACM/wMSQEBNXkLAjhvFVnEP2WMYzdvO6/4muDZMVCavSD8ujHDQkmxe90VP8y+5s1oznSoCJSfkRkC07yBhd3XRz+m1bd9/tN14XMd3kutI4/5s1qfnmEPjWgIcL5GZoCQUcbA56Cpw4g8TguxZBYiloomfGRCE1SAigKIxZKfG1rCW7cddOhIFIEEEWutIeLmyy+nXni8bMkMDiORv5uaxHW+NRSbFhbL0AeBI9MOyZyRbkpO2D2WVQ7lgSg/L6IsA8xJ0zMiX5GMpFnc37L+/bs3uX/d8rrPkYJQXFAuIgJ8UNwMLaest7Hy3Vs2e/S4OZ9dB4YV09CgAnOR0IwkzR5p2+SNSzd+9Jhp394M6YbJBRCuISYAFJpN5SQAy2q/SP1wg3JnRIlFDAIRokwi6If4XyoaQzohYgmAASGiCWv4W6QqR5asQgBNVGjBAWDLcSAug1Fj9FOvaH7tztd+fOhDzZXbYoiVKBwgkAiGMdYzPnVN1V6PfXLI9dUb9vHtZBwZ6RAYlHegAVkNBAjnzKzf4om6WVso2zFD3/HGyYZpGimjsJJJgBKUmOCarfdpXLYdAbY/QVplwVLHHMMEc4FGZhg2HBFZfppYg6GF4bkJGXiLWz6aYfg+CyoEoDVcC4ahmaGk6Ttx2x9b9t3Lmz9+9tp3Li5yw0CD8sumTExi8ujGoFwzbRuxgZ45tR/M+OlpZ+ATuHP9otluvxfqJBGzBmn6bwALhmQApAT5knyoYcRKsKC3Y+U3rxZ/8xzGPkNsLcwNSIcp3ZfqFpkxUNIgB7CZDQUAikjTHwEENjSYaFxiEIiACuYZozx1yC8dSZZ31U9cXl9W1bfvosDYkXRoMMcjuyjggcq37cGl0UBK95RQV5LasqOVACnNQiMSxKCFlSFFEp0EG3p1v15Rr+b/oqdWcVETOx0whpXwe3k+yAKHwB+63hR1JDjSUYnwp9PoPNG3TDSvkFVLxA/TMQTMBDsRLZrGPDVUO34TrX9LzfyQ3ToiQBVBJ6BtsCRGjEJiZqLfezDAabY0AaRAAeQE5CgjJG+u6NnS+HFH451NqSZO8879PNjpLl3xFnEMYQWUAzBIAxrEyHUhsJjcQBABzEEWA2Jkc/Ojk38Fe4MpHMjZ+XoAAAAASUVORK5CYII=\">\n  <title>Lighthouse report: {{ url }}</title>\n  <style>{{{ css }}}</style>\n  {{#each scripts }}\n    <script>{{{ this }}}</script>\n  {{/each}}\n</head>\n<body>\n\n<div class=\"js-report report\">\n  <section class=\"report-body js-report-body\" status=\"stable\">\n    <div class=\"report-body__header\">\n      <div class=\"report-body__metadata\">\n        <div class=\"report-body__url\">Results for: <a href=\"{{ url }}\" target=\"_blank\">{{ url }}</a></div>\n        <div class=\"report-body__url\">Generated on: {{generatedTime}}</div>\n      </div>\n      <div class=\"report-body__buttons\">\n        <span class=\"export-section\">\n          <button class=\"export-button js-export\" title=\"Export report in different formats\">Export...</button>\n          <ul class=\"export-dropdown\">\n            <a href=\"#\" data-action=\"print\">Print...</a>\n            <a href=\"#\" data-action=\"copy\">Copy JSON...</a>\n            <a href=\"#\" data-action=\"save-html\">Save as HTML...</a>\n            <a href=\"#\" data-action=\"save-json\">Save as JSON...</a>\n          </ul>\n        </span>\n        <button class=\"report-body__icon rerun-button js-rerun-button\" title=\"Rerun this test\"></button>\n        <button class=\"report-body__icon share js-share\"></button>\n        {{#if_not_eq reportContext \"viewer\"}}\n          <button class=\"report-body__icon print js-print\"></button>\n        {{/if_not_eq}}\n      </div>\n    </div>\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: {{lighthouseVersion}}</div>\n          </div>\n          <ul class=\"menu__nav\">\n            {{#each aggregations}}\n            <li class=\"menu__nav-item\">\n              <a class=\"menu__link\" href=\"#{{nameToLink this.name}}\">\n                {{ this.name }}\n              </a>\n            </li>\n            {{/each}}\n          </ul>\n        </div>\n      </div>\n\n      <div class=\"report-body__aggregations-container\">\n      {{#each aggregations}}\n      <section class=\"js-breakdown aggregations\" id=\"{{nameToLink this.name}}\">\n        <header class=\"aggregations__header\">\n          <h1>{{ this.name }}</h1>\n          <p class=\"aggregations__desc\">{{ sanitize this.description }}</p>\n          {{#if this.scored}}\n          <div class=\"section-result\">\n            <span class=\"section-result__score score-{{ getTotalScoreRating this }}-bg\">\n              <span class=\"section-result__points\">{{ getTotalScore this }}</span>\n              <span class=\"section-result__divider\">/</span>\n              <span class=\"section-result__total\">100</span>\n            </span>\n          </div>\n          {{/if}}\n        </header>\n\n        <div class=\"js-report-by-user-feature\">\n          {{#each this.score as |aggregation|}}\n            <section class=\"aggregation\">\n\n            {{#if aggregation.name }}\n            <header class=\"aggregation__header\">\n              <h2>{{ aggregation.name }}</h2>\n              {{#if aggregation.description }}\n                <p class=\"aggregation__desc\">{{ sanitize aggregation.description }}</p>\n              {{/if}}\n            </header>\n            {{/if}}\n\n            <ul class=\"subitems\">\n              {{#each aggregation.subItems as |subItem| }}\n                <li class=\"subitem {{#if subItem.comingSoon}}--coming-soon{{/if}} {{#if (shouldShowHelpText subItem.score)}}--show-help{{/if}}\">\n\n                  <p class=\"subitem__desc\">\n                    {{#unless ../../scored }}\n                      <strong class=\"subitem__category\">{{ subItem.category }}:</strong>\n                    {{/unless}}\n\n                    {{ sanitize subItem.description }}\n\n                    {{~#if (and subItem.displayValue (not (is-bool subItem.displayValue))) ~}}\n                      <strong class=\"subitem__raw-value\">: {{ subItem.displayValue }}</strong>\n                    {{/if}}\n\n                    {{#if subItem.optimalValue }}\n                      <small>(target: {{ subItem.optimalValue }})</small>\n                    {{/if}}\n\n                    {{#if subItem.comingSoon}}\n                      <small class=\"subitem__tease\">(Coming soon)</small>\n                    {{/if}}\n\n                    {{#if subItem.helpText }}\n                      <input type=\"checkbox\" class=\"subitem__help-toggle\" title=\"Toggle help text\"  {{#if (shouldShowHelpText subItem.score)}}checked{{/if}}>\n                      <span class=\"subitem__help\">\n                        {{ sanitize subItem.helpText }}\n                      </span>\n                    {{/if}}\n                  </p>\n\n                  {{#if subItem.debugString }}\n                    <div class=\"subitem__debug\">\n                      {{ subItem.debugString }}\n                    </div>\n                  {{/if}}\n\n                  <div class=\"subitem-result\">\n                    {{#if subItem.comingSoon}}\n                          <span class=\"subitem-result__unknown score-unknown-bg\">N/A</span>\n                    {{else}}\n                      {{#if (is-bool subItem.score)}}\n                        {{#if subItem.score}}\n                          <span class=\"subitem-result__good score-good-bg\">Pass</span>\n                        {{else}}\n                          <span class=\"subitem-result__poor score-poor-bg\">Fail</span>\n                        {{/if}}\n                      {{else}}\n                        <span class=\"subitem-result__points score-{{ getItemRating subItem.score }}-bg\">\n                          {{ subItem.score }}\n                        </span>\n                      {{/if}}\n                    {{/if}}\n                  </div>\n\n                  {{#if subItem.extendedInfo.value}}\n                    {{> (lookup . 'name') subItem.extendedInfo.value }}\n                  {{/if}}\n                </li>\n              {{/each}}\n            </ul>\n          </section>\n          {{/each}}\n        </div>\n      </section>\n      {{/each}}\n      </div>\n    </div>\n\n    <footer class=\"footer\">\n      Generated by <b>Lighthouse</b> {{lighthouseVersion}} on {{generatedTime}} | <a href=\"https://github.com/GoogleChrome/Lighthouse/issues\" target=\"_blank\">File an issue</a>\n    </footer>\n  </section>\n</div>\n";
+  }
+
+  /**
+   * Gets the template for any exceptions.
+   * @return {string}
+   */
+  getExceptionTemplate() {
+    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>{{{ css }}}</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: {{{ errMessage }}}</h1>\n          <p class=\"error-stack\"> {{{ errStack }}}</p>\n          <big>\n            ➡ <a target=\"_blank\" href=\"https://github.com/GoogleChrome/lighthouse/issues\">Please report this bug</a>\n          </big>\n          <div class=\"error-results\"><pre>{{{ results }}}</pre></div>\n        </div>\n\n      </div>\n\n    </section>\n    <footer class=\"footer\">\n      Generated by <b>Lighthouse</b> on {{generatedTime}} | <a href=\"https://github.com/GoogleChrome/Lighthouse/issues\" target=\"_blank\">File an issue</a>\n    </footer>\n  </div>\n\n</body>\n</html>\n";
+  }
+
+  /**
+   * Gets the CSS for the report.
+   * @return {string}
+   */
+  getReportCSS() {
+    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: #212121;\n  --secondary-text-color: #757575;\n  --accent-color: #719EA8;\n  --poor-color: #eb211e;\n  --good-color: #1ac123;\n  --average-color: #ffae00;\n  --unknown-color: #b3b3b3;\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  --subheading-font-size: 16px;\n  --subheading-line-height: 24px;\n  --subheading-color: var(--accent-color);\n  --heading-font-size: 24px;\n  --heading-line-height: 32px;\n  --subitem-indent: 24px;\n  --max-line-length: none;\n\n  --report-width: 1280px;\n  --report-menu-width: 280px;\n  --report-header-height: 58px;\n}\n\n:root[data-report-context=\"devtools\"] {\n  --text-font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;\n  --text-color: #222;\n  --secondary-text-color: #606060;\n  --accent-color: #3879d9;\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  --subheading-color: inherit;\n  --report-header-height: 0;\n  --heading-font-size: 20px;\n  --heading-line-height: 24px;\n  --subitem-indent: 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: #f5f5f5;\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: #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: #FFF;\n  box-shadow: 0 0 6px 0 rgba(0,0,0,0.26);\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}\n\n.report-body__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-body__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}\n\n.report-body__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  display: none;\n}\n\n.report-body__icon.rerun-button {\n  background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24px\" height=\"24px\" viewBox=\"0 0 24 24\" fill=\"#000000\"><path d=\"M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z\"/><path d=\"M0 0h24v24H0z\" fill=\"none\"/></svg>');\n  display: none;\n}\n\n.rerun-button[status=running] {\n  animation: rotate 1000ms infinite;\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  padding-top: var(--report-header-height);\n  will-change: transform;\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: #FFFFFF;\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: #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}\n\n.menu__header::after {\n  content: '';\n  display: block;\n  width: 90px;\n  height: 90px;\n  position: absolute;\n  top: 0;\n  right: 0;\n  background: url('data:image/svg+xml;utf-8,<svg width=\"86\" height=\"86\" viewBox=\"0 0 86 86\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"><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\"/><filter x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\" filterUnits=\"objectBoundingBox\" id=\"c\"><feGaussianBlur stdDeviation=\"3.5\" in=\"SourceAlpha\" result=\"shadowBlurInner1\"/><feOffset in=\"shadowBlurInner1\" result=\"shadowOffsetInner1\"/><feComposite in=\"shadowOffsetInner1\" in2=\"SourceAlpha\" operator=\"arithmetic\" k2=\"-1\" k3=\"1\" result=\"shadowInnerInner1\"/><feColorMatrix values=\"0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.689509737 0\" in=\"shadowInnerInner1\"/></filter><text id=\"f\" font-family=\"Arial-BoldMT, Arial\" font-size=\"13\" font-weight=\"bold\" 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(45 55.44 24.523)\"><use fill=\"#000\" filter=\"url(#a)\" xlink:href=\"#b\"/><use fill=\"#CF3A3C\" xlink:href=\"#b\"/></g><use filter=\"url(#c)\" xlink:href=\"#d\" mask=\"url(#mask-2)\" transform=\"rotate(45 58.4 27.55)\" fill=\"#000\"/><g mask=\"url(#mask-2)\" transform=\"rotate(45 52.556 36.435)\" fill=\"#FFF\"><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)\"/></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  opacity: 0.4;\n  color: #fff;\n  font-family: var(--text-font-family);\n  font-size: 14px;\n  line-height: 1.5;\n}\n\n.menu__nav {\n  list-style: 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 #EBEBEB;\n}\n\n.menu__link {\n  padding: 0 20px;\n  text-decoration: none;\n  color: #777;\n  display: flex;\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}\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  border-bottom: 1px solid #EBEBEB;\n  background: #FAFAFA;\n  margin-left: var(--report-menu-width);\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  justify-content: flex-start;\n  padding: 0 var(--heading-line-height);\n  position: fixed;\n  will-change: transform;\n  z-index: 1;\n  max-width: calc( var(--report-width) - var(--report-menu-width));\n  width: calc(100vw - var(--report-menu-width));\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: 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: 0px 0px 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 0px 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  height: 130px;\n  line-height: 90px;\n  text-align: center;\n  font-size: 12px;\n  border-top: 1px solid #EBEBEB;\n  color: #999;\n}\n\n.coming-soon, .coming-soon * {\n  color: #AAA;\n}\n\n.coming-soon .report-section__item-value {\n  font-size: 70%;\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 > h1 {\n  font-size: var(--heading-font-size);\n  font-weight: normal;\n  line-height: var(--heading-line-height);\n}\n\n.aggregations {\n  padding: var(--heading-line-height);\n  padding-left: calc(var(--heading-line-height) + var(--gutter-width) + var(--gutter-gap));\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\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: #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  max-width: var(--max-line-length);\n}\n\n.aggregation__header > h2 {\n  font-size: var(--subheading-font-size);\n  font-weight: normal;\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}\n\n.aggregation__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.subitems {\n  list-style: 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) + var(--gutter-width) + var(--gutter-gap));\n  margin-top: calc(var(--subitem-line-height) / 2);\n}\n\n.subitem.--coming-soon {\n  color: var(--secondary-text-color);\n}\n\n.subitem strong {\n  font-weight: bold;\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: var(--subitem-indent);\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__poor, .subitem-result__unknown {\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__poor::after, .subitem-result__unknown::after {\n  content: '';\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0;\n  bottom: 0;\n  -webkit-mask: center center / 12px 12px no-repeat;\n  background-color: #fff;\n}\n\n.subitem-result__good::after { -webkit-mask-image: 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=\"#FFF\" fill-rule=\"evenodd\"/></svg>'); }\n.subitem-result__poor::after { -webkit-mask-image: 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=\"#FFF\" fill-rule=\"evenodd\"/></svg>'); }\n.subitem-result__unknown::after { -webkit-mask-image: 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=\"#FFF\" fill-rule=\"evenodd\"/></svg>'); }\n\n.subitem-result__points {\n  margin-top: calc((var(--subitem-line-height) - var(--subitem-font-size) - 4px) / 2);\n  background: #000;\n  padding: 2px 4px;\n  border-radius: 1px;\n  color: #fff;\n  border-radius: 2px;\n}\n\n.subitem__details {\n  list-style: 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  position: relative;\n  display: inline-block;\n  width: 16px;\n  height: 16px;\n  border-radius: 50%;\n  border: 1px solid #ccc;\n  vertical-align: middle;\n  margin-left: .5em;\n  outline: 0;\n}\n\n.subitem__help-toggle::after {\n  content: '';\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0;\n  bottom: 0;\n  -webkit-mask: 1px 1px / 12px 12px no-repeat;\n  -webkit-mask-image: 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=\"#000\" fill-rule=\"evenodd\"/></svg>');\n  background-color: var(--secondary-text-color);\n  cursor: pointer;\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::after {\n  background-color: #fff;\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-unknown-bg {\n  background-color: var(--unknown-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: 0;\n  font-weight: 500;\n  display: none;\n}\n\n.export-dropdown {\n  position: absolute;\n  background-color: #fafafa;\n  border: 1px solid #ccc;\n  border-radius: 3px;\n  margin: 0;\n  padding: 8px 0;\n  cursor: pointer;\n  top: 36px;\n  left: 0;\n  z-index: 1;\n  box-shadow: 1px 1px 3px #ccc;\n  min-width: 125px;\n  list-style: none;\n  line-height: 1.5em;\n  visibility: hidden;\n  clip: rect(0, 140px, 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  visibility: visible;\n  opacity: 1;\n  clip: rect(0, 140px, 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}\n\n.export-dropdown a:hover,\n.export-dropdown a:focus {\n  background-color: rgb(239,239,239);\n  outline: 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,\n  .report-body__menu-container {\n    display: none;\n  }\n\n  .report-body__content {\n    margin-left: 0;\n  }\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__header {\n    margin-left: 0;\n  }\n  .report-body__header {\n    width: 100%;\n    padding: 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 {\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:root[data-report-context=\"viewer\"] .export-button {\n  display: initial;\n}\n";
+  }
+
+  /**
+   * 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/* global window, document */\n\n'use strict';\n\nwindow.addEventListener('DOMContentLoaded', _ => {\n  const printButton = document.querySelector('.js-print');\n  printButton.addEventListener('click', _ => {\n    window.print();\n  });\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 = Handlebars.compile(this.getExceptionTemplate());
+    return template({
+      errMessage: err.message,
+      errStack: err.stack,
+      css: this.getReportCSS(),
+      results: JSON.stringify(results, null, 2)
+    });
+  }
+
+  /**
+   * Generates the Lighthouse report HTML.
+   * @param {!Object} results Lighthouse results.
+   * @param {!string} reportContext What app is requesting the report (eg. devtools, extension)
+   * @return {string} HTML of the report page.
+   */
+  generateHTML(results, reportContext) {
+    reportContext = reportContext || 'extension';
+
+    // Ensure the formatter for each extendedInfo is registered.
+    Object.keys(results.audits).forEach(audit => {
+      // Use value rather than key for audit.
+      audit = results.audits[audit];
+
+      if (!audit.extendedInfo) {
+        return;
+      }
+      if (!audit.extendedInfo.formatter) {
+        // HTML formatter not provided for this subItem
+        return;
+      }
+      const formatter = Formatter.getByName(audit.extendedInfo.formatter);
+      const helpers = formatter.getHelpers();
+      if (helpers) {
+        Handlebars.registerHelper(helpers);
+      }
+
+      Handlebars.registerPartial(audit.name, formatter.getFormatter('html'));
+    });
+
+    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.compile(this.getReportTemplate());
+
+    return template({
+      url: results.url,
+      lighthouseVersion: results.lighthouseVersion,
+      generatedTime: this._formatTime(results.generatedTime),
+      lhresults: this._escapeScriptTags(JSON.stringify(results, null, 2)),
+      css: this.getReportCSS(),
+      reportContext: reportContext,
+      scripts: this.getReportJS(reportContext),
+      aggregations: results.aggregations,
+      auditsByCategory: this._createPWAAuditsByCategory(results.aggregations)
+    });
+  }
+}
+
+module.exports = ReportGenerator;
+
+},{"../formatters/formatter":8,"handlebars":276,"marked":281,"path":204}],33:[function(require,module,exports){
 (function (process,__dirname){
 /**
  * @license
@@ -8672,26 +11961,20 @@
  */
 'use strict';
 
-const Driver = require('./gather/drivers/driver.js');
+const Driver = require('./gather/driver.js');
 const GatherRunner = require('./gather/gather-runner');
 const Aggregate = require('./aggregator/aggregate');
-const assetSaver = require('./lib/asset-saver');
+const Audit = require('./audits/audit');
 const log = require('./lib/log');
 
 const path = require('path');
-const url = require('url');
+const URL = require('./lib/url-shim');
 
 class Runner {
   static run(connection, opts) {
     // Clean opts input.
     opts.flags = opts.flags || {};
 
-    // Default mobile emulation and page loading to true.
-    // The extension will switch these off initially.
-    if (typeof opts.flags.mobile === 'undefined') {
-      opts.flags.mobile = true;
-    }
-
     const config = opts.config;
 
     // save the initialUrl provided by the user
@@ -8699,20 +11982,24 @@
     if (typeof opts.initialUrl !== 'string' || opts.initialUrl.length === 0) {
       return Promise.reject(new Error('You must provide a url to the driver'));
     }
-    const parsedURL = url.parse(opts.url);
-    // canonicalize URL with any trailing slashes neccessary
-    opts.url = url.format(parsedURL);
 
-    if (!parsedURL.protocol || !parsedURL.hostname) {
+    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.includes('https') && parsedURL.hostname !== 'localhost') {
+    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;
 
@@ -8735,37 +12022,41 @@
         });
       }
 
-      // Ignoring these two flags for coverage as this functionality is not exposed by the module.
-      /* istanbul ignore next */
-      if (opts.flags.saveArtifacts) {
+      // 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 => {
-          opts.flags.saveArtifacts && assetSaver.saveArtifacts(artifacts);
-          return artifacts;
-        });
-      }
-      if (opts.flags.saveAssets) {
-        run = run.then(artifacts => {
-          return assetSaver.saveAssets(opts, artifacts)
+          return Runner._runAudit(audit, artifacts)
+            .then(ret => auditResults.push(ret))
             .then(_ => artifacts);
         });
       }
-
-      // Now run the audits.
-      const auditResults = [];
-      run = run.then(artifacts => config.audits.reduce((chain, audit) => {
-        const status = `Evaluating: ${audit.meta.description}`;
-        // Run each audit sequentially, the auditResults array has all our fine work
-        return chain.then(_ => {
-          log.log('status', status);
-          return audit.audit(artifacts);
-        }).then(ret => {
-          log.verbose('statusEnd', status);
-          auditResults.push(ret);
-        });
-      }, Promise.resolve()).then(_ => auditResults));
+      run = run.then(artifacts => {
+        return {artifacts, auditResults};
+      });
     } else if (config.auditResults) {
       // If there are existing audit results, surface those here.
-      run = run.then(_ => config.auditResults);
+      // 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');
@@ -8774,8 +12065,8 @@
 
     // Format and aggregate results before returning.
     run = run
-      .then(auditResults => {
-        const formattedAudits = auditResults.reduce((formatted, audit) => {
+      .then(runResults => {
+        const formattedAudits = runResults.auditResults.reduce((formatted, audit) => {
           formatted[audit.name] = audit;
           return formatted;
         }, {});
@@ -8783,7 +12074,8 @@
         // Only run aggregations if needed.
         let aggregations = [];
         if (config.aggregations) {
-          aggregations = config.aggregations.map(a => Aggregate.aggregate(a, auditResults));
+          aggregations = config.aggregations.map(
+            a => Aggregate.aggregate(a, runResults.auditResults));
         }
 
         return {
@@ -8792,6 +12084,7 @@
           initialUrl: opts.initialUrl,
           url: opts.url,
           audits: formattedAudits,
+          artifacts: runResults.artifacts,
           aggregations
         };
       });
@@ -8800,12 +12093,58 @@
   }
 
   /**
+   * 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.
+      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.`);
+          return audit.generateAuditResult({
+            rawValue: -1,
+            debugString: `Required ${artifactName} gatherer did not run.`
+          });
+        }
+      }
+
+      return audit.audit(artifacts);
+    }).then(result => {
+      log.verbose('statusEnd', status);
+      return result;
+    });
+  }
+
+  /**
    * Returns list of audit names for external querying.
    * @return {!Array<string>}
    */
   static getAuditList() {
-    return ["aria-allowed-attr.js","aria-valid-attr.js","audit.js","cache-start-url.js","color-contrast.js","content-width.js","critical-request-chains.js","dobetterweb","estimated-input-latency.js","first-meaningful-paint.js","geolocation-on-start.js","image-alt.js","is-on-https.js","label.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","meta-theme-color.js","redirects-http.js","screenshots.js","service-worker.js","speed-index-metric.js","tabindex.js","time-to-interactive.js","user-timings.js","viewport.js","without-javascript.js","works-offline.js"]
-        .filter(f => /\.js$/.test(f));
+    const fileList = [
+      ...["accessibility","audit.js","cache-start-url.js","content-width.js","critical-request-chains.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","unused-css-rules.js","user-timings.js","viewport.js","without-javascript.js","works-offline.js"],
+      ...["appcache-manifest.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}`)
+    ];
+    return fileList.filter(f => {
+      return /\.js$/.test(f) && f !== 'audit.js' && f !== 'accessibility/axe-audit.js';
+    }).sort();
   }
 
   /**
@@ -8813,8 +12152,12 @@
    * @return {!Array<string>}
    */
   static getGathererList() {
-    return ["accessibility.js","cache-contents.js","content-width.js","dobetterweb","gatherer.js","geolocation-on-start.js","html-without-javascript.js","html.js","http-redirect.js","https.js","manifest.js","offline.js","service-worker.js","styles.js","theme-color.js","url.js","viewport.js"]
-        .filter(f => /\.js$/.test(f));
+    const fileList = [
+      ...["accessibility.js","cache-contents.js","content-width.js","css-usage.js","dobetterweb","gatherer.js","html-without-javascript.js","html.js","http-redirect.js","https.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","geolocation-on-start.js","notification-on-start.js","tags-blocking-first-paint.js","websql.js"]
+          .map(f => `dobetterweb/${f}`)
+    ];
+    return fileList.filter(f => /\.js$/.test(f) && f !== 'gatherer.js').sort();
   }
 
   /**
@@ -8867,26984 +12210,653 @@
 module.exports = Runner;
 
 }).call(this,require('_process'),"/../lighthouse-core")
-},{"../package":256,"./aggregator/aggregate":1,"./gather/drivers/driver.js":13,"./gather/gather-runner":16,"./lib/asset-saver":193,"./lib/log":21,"_process":199,"path":198,"url":204}],28:[function(require,module,exports){
+},{"../package":286,"./aggregator/aggregate":1,"./audits/audit":3,"./gather/driver.js":16,"./gather/gather-runner":17,"./lib/log":24,"./lib/url-shim":30,"_process":205,"path":204}],34:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-'use strict';
-
-/**
- * The global object.
- * @type {!Object}
- * @const
- */
-
-/** Platform, package, object property, and Event support. */
-
-global.tr = function () {
-  if (global.tr) {
-    console.warn('Base was multiply initialized. First init wins.');
-    return global.tr;
-  }
-
-  /**
-   * Builds an object structure for the provided namespace path,
-   * ensuring that names that already exist are not overwritten. For
-   * example:
-   * 'a.b.c' -> a = {};a.b={};a.b.c={};
-   * @param {string} name Name of the object that this file defines.
-   * @private
-   */
-  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) {
-      // Maybe we should check the prototype chain here? The current usage
-      // pattern is always using an object literal so we only care about own
-      // properties.
-      var propertyDescriptor = Object.getOwnPropertyDescriptor(exports, propertyName);
-      if (propertyDescriptor) Object.defineProperty(obj, propertyName, propertyDescriptor);
-    }
-  };
-
-  /**
-   * Initialization which must be deferred until run-time.
-   */
-  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();
+"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 : {})
-},{}],29:[function(require,module,exports){
+},{}],35:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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) {
-    // http://stackoverflow.com/questions/9267899/
-    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;
-  };
-
-  /*
-   * Wrapper of btoa
-   * The reason is that window object has a builtin btoa,
-   * but we also want to use btoa when it is headless.
-   * For example we want to use it in a mapper
-   */
-  Base64.btoa = function (input) {
-    return btoa(input);
-  };
-
-  /*
-   * Wrapper of atob
-   * The reason is that window object has a builtin atob,
-   * but we also want to use atob when it is headless.
-   * For example we want to use it in a mapper
-   */
-  Base64.atob = function (input) {
-    return atob(input);
-  };
-
-  return {
-    Base64: Base64
-  };
-});
+"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":28}],30:[function(require,module,exports){
+},{"./base.js":34}],36:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("./base.js");
-
-'use strict';
-
-/**
- * @fileoverview Helper code for working with tracing categories.
- *
- */
-global.tr.exportTo('tr.b', function () {
-
-  // Cached values for getCategoryParts.
-  var categoryPartsFor = {};
-
-  /**
-   * Categories are stored in comma-separated form, e.g: 'a,b' meaning
-   * that the event is part of the a and b category.
-   *
-   * This function returns the category split by string, caching the
-   * array for performance.
-   *
-   * Do not mutate the returned array!!!!
-   */
-  function getCategoryParts(category) {
-    var parts = categoryPartsFor[category];
-    if (parts !== undefined) return parts;
-    parts = category.split(',');
-    categoryPartsFor[category] = parts;
-    return parts;
-  }
-
-  return {
-    getCategoryParts: getCategoryParts
-  };
-});
+"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":28}],31:[function(require,module,exports){
+},{"./base.js":34}],37:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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);
-  };
-
-  /**
-   * Converts an HSL triplet with alpha to an RGB color.
-   * |h| Hue value in [0, 1].
-   * |s| Saturation value in [0, 1].
-   * |l| Lightness in [0, 1].
-   * |a| Alpha in [0, 1]
-   */
-  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 + ')';
-    },
-
-    /**
-     * Returns a dict {h, s, l, a} with:
-     * |h| Hue value in [0, 1].
-     * |s| Saturation value in [0, 1].
-     * |l| Lightness in [0, 1].
-     * |a| Alpha in [0, 1]
-     */
-    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
-  };
-});
+"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":28}],32:[function(require,module,exports){
+},{"./base.js":34}],38:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./base.js");
-require("./color.js");
-require("./iteration_helpers.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides color scheme related functions.
- */
-global.tr.exportTo('tr.b', function () {
-  // Basic constants...
-  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)
-  };
-
-  // Some constants we'll need for later lookups.
-  var numGeneralPurposeColorIds = generalPurposeColors.length;
-  var numReservedColorIds = tr.b.dictionaryLength(reservedColorsByName);
-  var numColorsPerVariant = numGeneralPurposeColorIds + numReservedColorIds;
-
-  function ColorScheme() {}
-
-  /*
-   * A flat array of tr.b.Color values of the palette, and their variants.
-   *
-   * This array is made up of a set of base colors, repeated N times to form
-   * a set of variants on that base color.
-   *
-   * Within the base colors, there are "general purpose" colors,
-   * which can be used for random color selection, and
-   * reserved colors, which are used when specific colors
-   * need to be used, e.g. where red is desired.
-   *
-   * The variants are automatically generated from the base colors. The 0th
-   * variant is the default apeparance of the color, and the varaiants are
-   * mutations of that color, e.g. several brightening levels and desaturations.
-   *
-   * For example, a very simple version of this array looks like the following:
-   *     0: Generic Color 0
-   *     1: Generic Color 1
-   *     2: Named Color 'foo'
-   *     3: Brightened Generic Color 0
-   *     4: Brightened Generic Color 1
-   *     5: Brightened Named Color 'foo'
-   */
-  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);
-  }
-
-  // Basic colors.
-  pushVariant(function (c) {
-    return c;
-  });
-
-  // Brightened variants.
-  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);
-  });
-
-  // Desaturated variants.
-  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);
-  });
-
-  /**
-   * A toString'd representation of ColorScheme.colors.
-   */
-  ColorScheme.colorsAsStrings = ColorScheme.colors.map(function (c) {
-    return c.toString();
-  });
-
-  // Build reservedColorNameToIdMap.
-  var reservedColorNameToIdMap = function () {
-    var m = new Map();
-    var i = generalPurposeColors.length;
-    tr.b.iterItems(reservedColorsByName, function (key, value) {
-      m.set(key, i++);
-    });
-    return m;
-  }();
-
-  /**
-   * @param {String} name The color name.
-   * @return {Number} The color ID for the given color name.
-   */
-  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];
-  };
-
-  /**
-   * Computes a simplistic hashcode of the provide name. Used to chose colors
-   * for slices.
-   * @param {string} name The string to hash.
-   */
-  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;
-  };
-
-  // Previously computed string color IDs. They are based on a stable hash, so
-  // it is safe to save them throughout the program time.
-  var stringColorIdCache = new Map();
-
-  /**
-   * @return {Number} A color ID that is stably associated to the provided via
-   * the getStringHash method. The color ID will be chosen from the general
-   * purpose ID space only, e.g. no reserved ID will be used.
-   */
-  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
-  };
-});
+"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":28,"./color.js":31,"./iteration_helpers.js":41}],33:[function(require,module,exports){
+},{"./base.js":34,"./color.js":37,"./iteration_helpers.js":47}],39:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./event_target.js");
-
-'use strict';
-
-global.tr.exportTo('tr.b', function () {
-  var Event;
-  if (tr.isHeadless) {
-    /**
-     * Creates a new event to be used with tr.b.EventTarget or DOM EventTarget
-     * objects.
-     * @param {string} type The name of the event.
-     * @param {boolean=} opt_bubbles Whether the event bubbles.
-     *     Default is false.
-     * @param {boolean=} opt_preventable Whether the default action of the event
-     *     can be prevented.
-     * @constructor
-     * @extends {Event}
-     */
-    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 {
-    /**
-     * Creates a new event to be used with tr.b.EventTarget or DOM EventTarget
-     * objects.
-     * @param {string} type The name of the event.
-     * @param {boolean=} opt_bubbles Whether the event bubbles.
-     *     Default is false.
-     * @param {boolean=} opt_preventable Whether the default action of the event
-     *     can be prevented.
-     * @constructor
-     * @extends {Event}
-     */
-    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;
-  }
-
-  /**
-   * Dispatches a simple event on an event target.
-   * @param {!EventTarget} target The event target to dispatch the event on.
-   * @param {string} type The type of the event.
-   * @param {boolean=} opt_bubbles Whether the event bubbles or not.
-   * @param {boolean=} opt_cancelable Whether the default action of the event
-   *     can be prevented.
-   * @param {!Object=} opt_fields
-   *
-   * @return {boolean} If any of the listeners called {@code preventDefault}
-   *     during the dispatch this will return false.
-   */
-  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
-  };
-});
+"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":34}],34:[function(require,module,exports){
+},{"./event_target.js":40}],40:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./base.js");
-
-'use strict';
-
-/**
- * @fileoverview This contains an implementation of the EventTarget interface
- * as defined by DOM Level 2 Events.
- */
-global.tr.exportTo('tr.b', function () {
-
-  /**
-   * Creates a new EventTarget. This class implements the DOM level 2
-   * EventTarget interface and can be used wherever those are used.
-   * @constructor
-   */
-  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 = {
-
-    /**
-     * Adds an event listener to the target.
-     * @param {string} type The name of the event.
-     * @param {!Function|{handleEvent:Function}} handler The handler for the
-     *     event. This is called when the event is dispatched.
-     */
-    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);
-      }
-    },
-
-    /**
-     * Removes an event listener from the target.
-     * @param {string} type The name of the event.
-     * @param {!Function|{handleEvent:Function}} handler The handler for the
-     *     event.
-     */
-    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) {
-          // Clean up if this was the last listener.
-          if (handlers.length == 1) delete this.listeners_[type];else handlers.splice(index, 1);
-        }
-      }
-    },
-
-    /**
-     * Dispatches an event and calls all the listeners that are listening to
-     * the type of the event.
-     * @param {!cr.event.Event} event The event to dispatch.
-     * @return {boolean} Whether the default action was prevented. If someone
-     *     calls preventDefault on the event object then this returns false.
-     */
-    dispatchEvent: function (event) {
-      if (!this.listeners_) return true;
-
-      // Since we are using DOM Event objects we need to override some of the
-      // properties and methods so that we can emulate this correctly.
-      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_) {
-        // Clone to prevent removal during dispatch
-        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;
-    }
-  };
-
-  // Export
-  return {
-    EventTarget: EventTarget,
-    EventTargetHelper: EventTargetHelper
-  };
-});
+"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":28}],35:[function(require,module,exports){
+},{"./base.js":34}],41:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-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';
-
-/**
- * @fileoverview Helper code for defining extension registries, which can be
- * used to make a part of trace-viewer extensible.
- *
- * This file provides two basic types of extension registries:
- * - Generic: register a type with metadata, query for those types based on
- *            a predicate
- *
- * - TypeName-based: register a type that handles some combination
- *                   of tracing categories or typeNames, then query
- *                   for it based on a category, typeName or both.
- *
- * When you register subtypes, you pass the constructor for the
- * subtype, and any metadata you want associated with the subtype. Use metadata
- * instead of stuffing fields onto the constructor. E.g.:
- *     registry.register(MySubclass, {titleWhenShownInTabStrip: 'MySub'})
- *
- * Some registries want a default object that is returned when a more precise
- * subtype has been registered. To provide one, set the defaultConstructor
- * option on the registry options.
- *
- * TODO: Extension registry used to make reference to mandatoryBaseType but it
- * was never enforced. We may want to add it back in the future in order to
- * enforce the types that can be put into a given registry.
- */
-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');
-    }
-
-    // Make it an event target.
-    if (registry.addEventListener === undefined) tr.b.EventTarget.decorate(registry);
-  }
-
-  return {
-    decorateExtensionRegistry: decorateExtensionRegistry
-  };
-});
+"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":34,"./extension_registry_base.js":36,"./extension_registry_basic.js":37,"./extension_registry_type_based.js":38,"./iteration_helpers.js":41}],36:[function(require,module,exports){
+},{"./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";
-/**
-Copyright (c) 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.
-**/
-
-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
-  };
-});
+"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":28}],37:[function(require,module,exports){
+},{"./base.js":34}],43:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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
-  };
-});
+"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":33,"./extension_registry_base.js":36}],38:[function(require,module,exports){
+},{"./event.js":39,"./extension_registry_base.js":42}],44:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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');
-
-      // Sanity checks...
-      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);
-
-      // Actual registration.
-      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;
-    };
-
-    // TODO(nduca): Remove or rename.
-    registry.getConstructor = function (category, typeName) {
-      var typeInfo = registry.getTypeInfo(category, typeName);
-      if (typeInfo) return typeInfo.constructor;
-      return undefined;
-    };
-  }
-
-  return {
-    _decorateTypeBasedExtensionRegistry: decorateTypeBasedExtensionRegistry
-  };
-});
+"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":30,"./event.js":33,"./extension_registry_base.js":36}],39:[function(require,module,exports){
+},{"./category_util.js":36,"./event.js":39,"./extension_registry_base.js":42}],45:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./base.js");
-
-'use strict';
-
-global.tr.exportTo('tr.b', function () {
-  var nextGUID = 1;
-
-  var UUID4_PATTERN = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
-
-  var GUID = {
-    /* Allocate an integer GUID.
-     *
-     * These GUIDs are not unique between loads, but are fast to generate, and
-     * consume very little memory.
-     *
-     * @return {number} globally unique id.
-     */
-    allocateSimple: function () {
-      return nextGUID++;
-    },
-
-    /* Return the last GUID allocated without allocating a new one.
-     *
-     * @return {number} last guid.
-     */
-    getLastSimpleGuid: function () {
-      return nextGUID - 1;
-    },
-
-    /* Generate a random string UUID.
-     *
-     * Version 4 random UUIDs are practically guaranteed to be unique between
-     * loads, so they can be serialized and compared with results from other
-     * loads. These are slower to generate and consume more memory than simple
-     * GUIDs.
-     *
-     * @return {string} universally unique id.
-     */
-    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
-  };
-});
+"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":28}],40:[function(require,module,exports){
+},{"./base.js":34}],46:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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);
-  }
-
-  /**
-   * This class implements an interval tree.
-   *    See: http://wikipedia.org/wiki/Interval_tree
-   *
-   * Internally the tree is a Red-Black tree. The insertion/colour is done using
-   * the Left-leaning Red-Black Trees algorithm as described in:
-   *       http://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf
-   *
-   * @param {function} beginPositionCb Callback to retrieve the begin position.
-   * @param {function} endPositionCb Callback to retrieve the end position.
-   *
-   * @constructor
-   */
-  function IntervalTree(beginPositionCb, endPositionCb) {
-    this.beginPositionCb_ = beginPositionCb;
-    this.endPositionCb_ = endPositionCb;
-
-    this.root_ = undefined;
-    this.size_ = 0;
-  }
-
-  IntervalTree.prototype = {
-    /**
-     * Insert events into the interval tree.
-     *
-     * @param {Object} datum The object to insert.
-     */
-    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;
-    },
-
-    /* The high values are used to find intersection. It should be called after
-     * all of the nodes are inserted. Doing it each insert is _slow_. */
-    updateHighValues: function () {
-      this.updateHighValues_(this.root_);
-    },
-
-    /* There is probably a smarter way to do this by starting from the inserted
-     * node, but need to handle the rotations correctly. Went the easy route
-     * for now. */
-    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');
-    },
-
-    /**
-     * Retrieve all overlapping intervals.
-     *
-     * @param {number} queryLow The low value for the intersection interval.
-     * @param {number} queryHigh The high value for the intersection interval.
-     * @return {Array} All [begin, end] pairs inside intersecting intervals.
-     */
-    findIntersection: function (queryLow, queryHigh) {
-      this.validateFindArguments_(queryLow, queryHigh);
-      if (this.root_ === undefined) return [];
-
-      var ret = [];
-      this.root_.appendIntersectionsInto_(ret, queryLow, queryHigh);
-      return ret;
-    },
-
-    /**
-     * Returns the number of nodes in the tree.
-     */
-    get size() {
-      return this.size_;
-    },
-
-    /**
-     * Returns the root node in the tree.
-     */
-    get root() {
-      return this.root_;
-    },
-
-    /**
-     * Dumps out the [lowValue, highValue] pairs for each node in depth-first
-     * order.
-     */
-    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) {
-      /* This node starts has a start point at or further right then queryHigh
-       * so we know this node is out and all right children are out. Just need
-       * to check left */
-      if (this.lowValue_ >= queryHigh) {
-        if (!this.leftNode_) return;
-        return this.leftNode_.appendIntersectionsInto_(ret, queryLow, queryHigh);
-      }
-
-      /* If we have a maximum left high value that is bigger then queryLow we
-       * need to check left for matches */
-      if (this.maxHighLeft_ > queryLow) {
-        this.leftNode_.appendIntersectionsInto_(ret, queryLow, queryHigh);
-      }
-
-      /* We know that this node starts before queryHigh, if any of it's data
-       * ends after queryLow we need to add those nodes */
-      if (this.highValue > queryLow) {
-        for (var i = this.data.length - 1; i >= 0; --i) {
-          /* data nodes are sorted by high value, so as soon as we see one
-           * before low value we're done. */
-          if (this.data[i].high < queryLow) break;
-
-          ret.push(this.data[i].datum);
-        }
-      }
-
-      /* check for matches in the right tree */
-      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
-  };
-});
+"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":28}],41:[function(require,module,exports){
+},{"./base.js":34}],47:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./base.js");
-
-'use strict';
-
-global.tr.exportTo('tr.b', function () {
-
-  /**
-   * Converts any object which is either (a) an iterable, or (b) an
-   * "array-ish" object (has length property and can be indexed into)
-   * into an array.
-   */
-  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;
-  }
-
-  /**
-   * Returns the only element in the iterable. If the iterable is empty or has
-   * more than one element, an error is thrown.
-   */
-  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;
-  }
-
-  /**
-   * Returns the first element in the iterable. If the iterable is empty, an
-   * error is thrown.
-   */
-  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;
-  }
-
-  /**
-   * Compares two values when one or both might be undefined. Undefined
-   * values are sorted after defined.
-   */
-  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;
-  }
-
-  /**
-   * Compares two numeric values when one or both might be undefined or NaNs.
-   * Undefined / NaN values are sorted after others.
-   */
-  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() /*arguments*/{
-    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() /*arguments*/{
-    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;
-  }
-
-  /**
-   * Returns true if all the elements of the iterable pass the predicate.
-   */
-  function every(iterable, predicate) {
-    for (var x of iterable) if (!predicate(x)) return false;
-    return true;
-  }
-
-  /**
-   * Returns a new dictionary with items grouped by the return value of the
-   * specified function being called on each item.
-   * @param {!Array.<!*>} ary The array being iterated through
-   * @param {!function(!*):!*} callback The mapping function between the array
-   * value and the map key.
-   * @param {*=} opt_this
-   */
-  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;
-  }
-
-  /**
-   * Returns a new Map with items grouped by the return value of the
-   * specified function being called on each item.
-   * @param {!Array.<!*>} ary The array being iterated through
-   * @param {!function(!*):!*} callback The mapping function between the array
-   * value and the map key.
-   * @param {*=} opt_this
-   */
-  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]);
-    }
-  }
-
-  /**
-   * Create a new dictionary with the same keys as the original dictionary
-   * mapped to the results of the provided function called on the corresponding
-   * entries in the original dictionary, i.e. result[key] = fn(key, dict[key])
-   * for all keys in dict (own enumerable properties only).
-   *
-   * Example:
-   *   var srcDict = {a: 10, b: 15};
-   *   var dstDict = mapItems(srcDict, function(k, v) { return 2 * v; });
-   *   // srcDict is unmodified and dstDict is now equal to {a: 20, b: 30}.
-   */
-  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);
-    }
-  }
-
-  /**
-   * Convert an array of dictionaries to a dictionary of arrays.
-   *
-   * The keys of the resulting dictionary are a union of the keys of all
-   * dictionaries in the provided array. Each array in the resulting dictionary
-   * has the same length as the provided array and contains the values of its
-   * key in the dictionaries in the provided array. Example:
-   *
-   *   INPUT:
-   *
-   *     [
-   *       {a: 6, b: 5      },
-   *       undefined,
-   *       {a: 4, b: 3, c: 2},
-   *       {      b: 1, c: 0}
-   *     ]
-   *
-   *   OUTPUT:
-   *
-   *     {
-   *       a: [6,         undefined, 4, undefined],
-   *       b: [5,         undefined, 3, 1        ],
-   *       c: [undefined, undefined, 2, 0        ]
-   *     }
-   *
-   * @param {!Array} array Array of items to be inverted. If opt_dictGetter
-   *     is not provided, all elements of the array must be either undefined,
-   *     or dictionaries.
-   * @param {?(function(*): (!Object|undefined))=} opt_dictGetter Optional
-   *     function mapping defined elements of array to dictionaries.
-   * @param {*=} opt_this Optional 'this' context for opt_dictGetter.
-   */
-  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;
-  }
-
-  /**
-   * Convert an array to a dictionary.
-   *
-   * Every element in the array is mapped in the dictionary to the key returned
-   * by the provided function:
-   *
-   *   dictionary[valueToKeyFn(element)] = element;
-   *
-   * @param {!Array} array Arbitrary array.
-   * @param {function(*): string} valueToKeyFn Function mapping array elements
-   *     to dictionary keys.
-   * @param {*=} opt_this Optional 'this' context for valueToKeyFn.
-   */
-  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;
-  }
-
-  /** Returns the value passed in. */
-  function identity(d) {
-    return d;
-  }
-
-  /**
-   * Returns the index of the first element in |ary| for which |opt_func|
-   * returns a truthy value.
-   *
-   * @param {!Array} ary The array being searched
-   * @param {function(*): *=} opt_func The test function which accepts an array
-   *     element and returns a value that is coerced to a boolean. Defaults to
-   *     the identity function.
-   * @param {*=} opt_this Optional 'this' context for opt_func.
-   */
-  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;
-  }
-
-  /**
-   * Returns the value of the first element in |ary| for which |opt_func|
-   * returns a truthy value.
-   *
-   * @param {!Array} ary The array being searched.
-   * @param {function(*): *=} opt_func The test function which accepts an array
-   *     element and returns a value that is coerced to a boolean. Defaults to
-   *     the identity function.
-   * @param {*=} opt_this Optional 'this' context for opt_func.
-   */
-  function findFirstInArray(ary, opt_func, opt_this) {
-    var i = findFirstIndexInArray(ary, opt_func, opt_func);
-    if (i === -1) return undefined;
-    return ary[i];
-  }
-
-  /**
-   * Returns the key of the first dictionary entry for which |opt_func| returns
-   * a truthy value.
-   *
-   * @param {!Object} dict The dictionary being searched.
-   * @param {function(*, *): *=} opt_func The test function which accepts a
-   *     dictionary key as its first parameter, the corresponding dictionary
-   *     value as its second parameter, and returns a value that is coerced to a
-   *     boolean. Defaults to the identity function (which returns the key).
-   * @param {*=} opt_this Optional 'this' context for opt_func.
-   */
-  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;
-  }
-
-  /** Returns the values in an ES6 Map object. */
-  function mapValues(map) {
-    var values = [];
-    for (var value of map.values()) values.push(value);
-    return values;
-  }
-
-  /**
-   * Calls |fn| on each entry in an ES6 Map.
-   *
-   * @param {!Map} The map whose entries are being processed.
-   * @param {function(*, *): *} The function which accepts a map key as its
-   *     first parameter and a map value as its second parameter. Any return
-   *     value is ignored.
-   * @param {*=} opt_this Optional 'this' context for fn.
-   */
-  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
-  };
-});
+"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":28}],42:[function(require,module,exports){
+},{"./base.js":34}],48:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./base.js");
-
-'use strict';
-
-// In node, the script-src for gl-matrix-min above brings in glmatrix into
-// a module, instead of into the global scope. Whereas, Tracing code
-// assumes that glMatrix is in the global scope. So, in Node only, we
-// require() it in, and then take all its exports and shove them into the
-// global scope by hand.
-(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 () {
-  /* Returns true when x and y are within delta of each other. */
-  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;
-  }
-
-  /* The Gauss error function gives the probability that a measurement (which is
-   * under the influence of normally distributed errors with standard deviation
-   * sigma = 1) is less than x from the mean value of the standard normal
-   * distribution.
-   * https://www.desmos.com/calculator/t1v4bdpske
-   *
-   * @param {number} x A tolerance for error.
-   * @return {number} The probability that a measurement is less than |x| from
-   * the mean value of the standard normal distribution.
-   */
-  function erf(x) {
-    // save the sign of x
-    // erf(-x) = -erf(x);
-    var sign = x >= 0 ? 1 : -1;
-    x = Math.abs(x);
-
-    // constants
-    var a1 = 0.254829592;
-    var a2 = -0.284496736;
-    var a3 = 1.421413741;
-    var a4 = -1.453152027;
-    var a5 = 1.061405429;
-    var p = 0.3275911;
-
-    // Abramowitz and Stegun formula 7.1.26
-    // maximum error: 1.5e-7
-    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) {
-    // 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
-  };
-});
+"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":28}],43:[function(require,module,exports){
+},{"./base.js":34}],49:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-require("./base.js");
-
-'use strict';
-
-/**
- * @fileoverview Multi-dimensional view data structure.
- *
- * A multi-dimensional view provides a hierarchical representation of a
- * collection of multi-dimensional paths with associated scalar values. Unlike
- * separate single-dimensional views (e.g. one tree for each dimension),
- * multi-dimensional views facilitate aggregation over combinations of
- * substrings of the path dimensions (rather than just substrings of a single
- * path dimension).
- *
- * Every view consists of multi-dimensional nodes (see MultiDimensionalViewNode
- * for more details). This file also provides a builder class for constructing
- * top-down and bottom-up representations of arbitrary collections of
- * multi-dimensional paths (see MultiDimensionalViewBuilder for more details).
- *
- * Example: Given the following collection of two dimensional paths:
- *
- *   <===================== Path =====================>   <== Total values ===>
- *    <------- dimension 0 ------->  <- dimension 1 ->    <- v 0 ->  <- v 1 ->
- *   [['Run()', 'Exec()', 'Call()'], ['Obj', 'View']  ]: [1        , 3
- *   [['Run()', 'Exec()', 'Call()'], ['Obj', 'Widget']]: [2        , 5
- *   [['Run()', 'Exec()', 'Load()'], ['Obj']          ]: [4        , 11
- *   [['Run()', 'Exec()']          , ['int']          ]: [8        , 7
- *   [['Run()']                    , ['Obj', 'Window']]: [16       , 0
- *   [['Stop()']                   , ['Obj']          ]: [32       , 13
- *
- * a multi-dimensional view provides a recursive breakdown of the aggregated
- * values, e.g. (total values shown in square brackets):
- *
- *   (root): [63, 39]
- *     |
- *     | break down by 0th dimension
- *     v
- *   Run():  [31, 26]
- *     |
- *     | break down by 0th dimension
- *     v
- *   Exec(): [15, 26]
- *     |
- *     | break down by 1st dimension
- *     v
- *   Obj:    [7, 19]
- *     |
- *     | break down by 0th dimension again
- *     v
- *   Call(): [3, 8]
- *     |
- *     | break down by 1st dimension again
- *     v
- *   View:   [1, 3]
- *
- * Observe that the recursive breakdown above is over both dimensions.
- * Furthermore, the underlying single-dimension paths (Run() -> Exec() -> Call()
- * and Obj -> View) can be arbitrarily interleaved in the breakdown.
- */
-global.tr.exportTo('tr.b', function () {
-
-  /**
-   * Node of a multi-dimensional view.
-   *
-   * The structure of a view is encoded in the nodes using links to their
-   * children wrt each dimension. The diagram below shows how the nodes
-   * corresponding to the following four two-dimensional view paths:
-   *
-   *   1. [['A', 'B'], ['1', '2']]
-   *   2. [['A', 'C'], ['1', '2']]
-   *   3. [['A', 'B'], ['1', '3']]
-   *   4. [['A', 'C'], ['1', '3']]
-   *
-   * can be reached from the root of a two-dimensional view using these links
-   * ('*' stands for undefined):
-   *
-   *                       +---------------------+
-   *                       | title: [*,*] (root) |
-   *                       +---------------------+
-   *                     children wrt    children wrt
-   *                    0th dimension    1st dimension
-   *                              |        :
-   *              _______A________|        :........1.........
-   *             |                                           :
-   *             v                                           v
-   *         +--------------+                     +--------------+
-   *         | title: [A,*] |                     | title: [*,1] |
-   *         +--------------+                     +--------------+
-   *    children wrt   children wrt         children wrt   children wrt
-   *   0th dimension   1st dimension       0th dimension   1st dimension
-   *           | |       :.....1......    _____A_____|       : :
-   *        _B_| |__C__              :   |             ...2..: :.3..
-   *       |           |             :   |             :           :
-   *       v           v             v   v             v           v
-   *   +-------+   +-------+       +-------+       +-------+   +-------+
-   *   | [B,*] |   | [C,*] |       | [A,1] |       | [*,2] |   | [*,3] |
-   *   +-------+   +-------+       +-------+       +-------+   +-------+
-   *       :        ___:_____B______| | : :......3.....|....       |
-   *       :.1..   |   :.1..    __C___| :...2...    _A_|   :    _A_|
-   *           :   |       :   |               :   |       :   |
-   *           v   v       v   v               v   v       v   v
-   *         +-------+   +-------+           +-------+   +-------+
-   *         | [B,1] |   | [C,1] |           | [A,2] |   | [A,3] |
-   *         +-------+   +-------+           +-------+   +-------+
-   *           :   :       :   :.......3.......||..........   ||
-   *           :   :..3....:................   BC         :   BC
-   *           :     ______:_______________:___||         :   ||
-   *           2    |      2        _______:____|   ______:___||
-   *           :    |      :       |       :       |      :    |
-   *           v    v      v       v       v       v      v    v
-   *       +----------+   +----------+   +----------+   +----------+
-   *       |  [B,2]   |   |  [C,2]   |   |  [B,3]   |   |  [C,3]   |
-   *       | (node 1) |   | (node 2) |   | (node 3) |   | (node 4) |
-   *       +----------+   +----------+   +----------+   +----------+
-   *
-   * The self/total values of a node represents the aggregated values of all
-   * paths (in the collection from which the view was built) matching the node
-   * excluding/including the node's descendants.
-   *
-   * Terminology examples:
-   *
-   *   - Children of [A,*] wrt 0th dimension: [B,*], [C,*]
-   *   - Children of [A,*] (wrt all dimensions): [B,*], [C,*], [A,1]
-   *   - Descendants of [A,*] wrt 1st dimension: [A,1], [A,2], [A,3]
-   *   - Single-dimensional descendants of [A,*]: [A,1], [A,2], [A,3], [B,*],
-   *     [C,*]
-   *   - Descendants of [A,*] (wrt all dimensions): [A,1], [A,2], [A,3], [B,*],
-   *     [C,*], [B,1], [C,1], [B,2], [C,2], [B,3], [C,3]
-   *
-   * @{constructor}
-   */
-  function MultiDimensionalViewNode(title, valueCount) {
-    // List of titles of this node wrt each dimension.
-    this.title = title;
-
-    // Map from child name to child node for each dimension.
-    var dimensions = title.length;
-    this.children = new Array(dimensions);
-    for (var i = 0; i < dimensions; i++) this.children[i] = new Map();
-
-    // For each value index (from 0 to |valueCount| - 1), we store the self and
-    // total values together with a Boolean flag whether the value is only a
-    // lower bound (i.e. aggregated from children rather than provided
-    // directly).
-    this.values = new Array(valueCount);
-    for (var v = 0; v < valueCount; v++) this.values[v] = { self: 0, total: 0, totalState: NOT_PROVIDED };
-  }
-
-  /**
-   * States of total values stored in multi-dimensional view nodes.
-   *
-   * @enum
-   */
-  MultiDimensionalViewNode.TotalState = {
-    // Neither total nor self value was provided for either the node or any of
-    // its descendants.
-    NOT_PROVIDED: 0,
-
-    // The total value was NOT provided for the node, but the self value was
-    // provided for the node or the total or self value was provided for at
-    // least one of its descendants.
-    LOWER_BOUND: 1,
-
-    // The total value was provided for the node.
-    EXACT: 2
-  };
-  // Cache the total value states to avoid repeated object field lookups.
-  var NOT_PROVIDED = MultiDimensionalViewNode.TotalState.NOT_PROVIDED;
-  var LOWER_BOUND = MultiDimensionalViewNode.TotalState.LOWER_BOUND;
-  var EXACT = MultiDimensionalViewNode.TotalState.EXACT;
-
-  MultiDimensionalViewNode.prototype = {
-    /** Duck type <tr-ui-b-table> rows. */
-    get subRows() {
-      return tr.b.mapValues(this.children[0]);
-    }
-  };
-
-  /**
-   * Builder for multi-dimensional views.
-   *
-   * Given a collection of multi-dimensional paths, a builder can be used to
-   * construct the following three representations of the paths:
-   *
-   *   1. Top-down tree view
-   *      A multi-dimensional path in the view corresponds to all paths in the
-   *      collection that have it as their prefix.
-   *
-   *   2. Top-down heavy view
-   *      A multi-dimensional path in the view corresponds to all paths in the
-   *      collection that have it as their substring
-   *
-   *   3. Bottom-up heavy view
-   *      A multi-dimensional path in the view corresponds to all paths in the
-   *      collection that have it as their substring reversed.
-   *
-   * For example, the following collection of 2-dimensional paths (with single
-   * values):
-   *
-   *                  2-dimensional path                | self
-   *    Time (0th dimension) | Activity (1st dimension) | value
-   *   ========================+========================+=======
-   *    Saturday             | Cooking                  |   1 h
-   *    Saturday             | Sports -> Football       |   2 h
-   *    Sunday               | Sports -> Basketball     |   3 h
-   *
-   * gives rise to the following top-down tree view, which aggregates the
-   * scalar values over prefixes of the given paths:
-   *
-   *                              +---------+
-   *                              |    *    |
-   *                              |    *    |
-   *                              | self=0  |
-   *                              | total=6 |
-   *                              +---------+
-   *                                | : | :
-   *         _________Cooking_______| : | :............Sunday............
-   *        |                         : |                               :
-   *        |            ...Saturday..: |_Sports_                       :
-   *        |            :                       |                      :
-   *        v            v                       v                      v
-   *   +---------+  +---------+            +---------+             +---------+
-   *   |    *    |  |   Sat   |            |    *    |             |   Sun   |
-   *   | Cooking |  |    *    |            | Sports  |             |    *    |
-   *   | self=0  |  | self=0  |            | self=0  |             | self=0  |
-   *   | total=1 |  | total=3 |            | total=5 |             | total=3 |
-   *   +---------+  +---------+            +---------+             +---------+
-   *      :          |   |                   : | | :                     |
-   *    Saturday     | Sports                : | | :                  Sports
-   *      :          |   |  .....Saturday....: | | :.....Sunday.......   |
-   *      :    _Cook_|   |  :            _Foot_| |_Bask_             :   |
-   *      :   |          |  :           |               |            :   |
-   *      v   v          v  v           v               v            v   v
-   *   +---------+  +---------+  +------------+  +--------------+  +---------+
-   *   |   Sat   |  |   Sat   |  |     *      |  |      *       |  |   Sun   |
-   *   | Cooking |  | Sports  |  | S/Football |  | S/Basketball |  | Sports  |
-   *   | self=1  |  | self=0  |  | self=0     |  | self=0       |  | self=0  |
-   *   | total=1 |  | total=2 |  | total=2    |  | total=3      |  | total=3 |
-   *   +---------+  +---------+  +------------+  +--------------+  +---------+
-   *                    |              :                 :               |
-   *                    |_Foot_  ..Sat.:                 :.Sun..   _Bask_|
-   *                           | :                             :  |
-   *                           v v                             v  v
-   *                     +------------+                   +--------------+
-   *                     |    Sat     |                   |     Sun      |
-   *                     | S/Football |                   | S/Basketball |
-   *                     | self=2     |                   | self=3       |
-   *                     | total=2    |                   | total=3      |
-   *                     +------------+                   +--------------+
-   *
-   * To build a multi-dimensional view of a collection of multi-dimensional
-   * paths, you create a builder, add the paths to it and then use it to
-   * construct the view. For example, the following code generates the
-   * 2-dimensional top-down tree view shown above:
-   *
-   *   var builder = new MultiDimensionalViewBuilder(2);
-   *   builder.addPath([['Saturday'], ['Cooking']], [1], SELF);
-   *   builder.addPath([['Saturday'], ['Sports', 'Football']], [2], SELF);
-   *   builder.addPath([['Sunday'], ['Sports', 'Basketball']], [3], SELF);
-   *   var treeViewRoot = builder.buildTopDownTreeView();
-   *
-   * The heavy views can be constructed analogously (by calling
-   * buildTopDownHeavyView() or buildBottomUpHeavyView() at the end instead).
-   *
-   * Note that the same builder can be used to construct both the tree and
-   * heavy views (for the same collection of paths). However, no more paths can
-   * be added once either view has been built.
-   *
-   * @{constructor}
-   */
-  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;
-  }
-
-  /** @{enum} */
-  MultiDimensionalViewBuilder.ValueKind = {
-    SELF: 0,
-    TOTAL: 1
-  };
-
-  /**
-   * Types of multi-dimensional views provided by MultiDimensionalViewBuilder.
-   *
-   * @enum
-   */
-  MultiDimensionalViewBuilder.ViewType = {
-    TOP_DOWN_TREE_VIEW: 0,
-    TOP_DOWN_HEAVY_VIEW: 1,
-    BOTTOM_UP_HEAVY_VIEW: 2
-  };
-
-  MultiDimensionalViewBuilder.prototype = {
-    /**
-     * Add values associated with a multi-dimensional path to the tree.
-     *
-     * The path must have the same number of dimensions as the builder. Its
-     * elements must be single-dimension paths (lists of strings) of arbitrary
-     * length (empty for the root of the given dimension). Starting from the
-     * root of the tree, each single-dimension path is traversed from left to
-     * right to reach the node corresponding to the whole path.
-     *
-     * The length of the provided list of values must be equal to the builder's
-     * value count. The builder supports adding both kinds of values
-     * (self/total) wrt all value indices for an arbitrary multi-dimensional
-     * path. The rationale for adding total values (in addition to/instead of
-     * self values) is to cater for missing sub-paths. Example: Consider the
-     * following collection of single-dimensional paths (with single values):
-     *
-     *   [['Loop::Run()', 'Execute()', 'FunctionBig']]:       self=99000
-     *   [['Loop::Run()', 'Execute()', 'FunctionSmall1']]:    self=1
-     *   [['Loop::Run()', 'Execute()', 'FunctionSmall2']]:    self=1
-     *   ...
-     *   [['Loop::Run()', 'Execute()', 'FunctionSmall1000']]: self=1
-     *
-     * If we required that only self values could be added to the builder, then
-     * all of the 1001 paths would need to be provided (most likely in a trace)
-     * to obtain the correct total of [['Loop::Run()', 'Execute()']]. However,
-     * since we allow adding total values as well, only the following 2 paths
-     * need to be provided to get the correct numbers explaining 99% of the
-     * aggregated total value:
-     *
-     *   [['Loop::Run()', 'Execute()']]:                total=100000
-     *   [['Loop::Run()', 'Execute()', 'FunctionBig']]: self=99000
-     *
-     * In other words, the long tail containing 1000 small paths need not be
-     * dumped (greatly reducing the size of a trace where applicable).
-     *
-     * Important: No paths can be added to a builder once either view has been
-     * built!
-     */
-    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);
-      }
-    },
-
-    /**
-     * Build the top-down tree view of the multi-dimensional view.
-     *
-     * Note that no more paths can be added to the builder once either view has
-     * been built.
-     */
-    buildTopDownTreeView: function () {
-      if (this.topDownTreeViewRoot_ === undefined) {
-        var treeViewRoot = this.buildRoot_;
-        this.buildRoot_ = undefined;
-
-        this.setUpMissingChildRelationships_(treeViewRoot, 0 /* firstDimensionToSetUp */);
-        this.finalizeTotalValues_(treeViewRoot, 0 /* firstDimensionToFinalize */
-        , new WeakMap() /* dimensionalSelfSumsMap */);
-
-        this.topDownTreeViewRoot_ = treeViewRoot;
-      }
-
-      return this.topDownTreeViewRoot_;
-    },
-
-    /**
-     * Build the top-down heavy view of the multi-dimensional view.
-     *
-     * Note that no more paths can be added to the builder once either view has
-     * been built.
-     */
-    buildTopDownHeavyView: function () {
-      if (this.topDownHeavyViewRoot_ === undefined) {
-        this.topDownHeavyViewRoot_ = this.buildGenericHeavyView_(this.addDimensionToTopDownHeavyViewNode_.bind(this));
-      }
-      return this.topDownHeavyViewRoot_;
-    },
-
-    /**
-     * Build the bottom-up heavy view of the multi-dimensional view.
-     *
-     * Note that no more paths can be added to the builder once either view has
-     * been built.
-     */
-    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_) /* title */, 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;
-    },
-
-    /**
-     * Set up missing child relationships.
-     *
-     * When an arbitrary multi-dimensional path [path1, path2, ..., pathN] is
-     * added to the build tree (see addPath), only the nodes on the path1 ->
-     * path2 -> ... -> pathN chain are created (i.e. no interleavings of the
-     * single-dimensional paths are added to the tree). This method recursively
-     * adds all the missing paths.
-     *
-     * Two-dimensional example:
-     *
-     *    Initial build tree   .       After path      .  After missing child
-     *        (root only)      .    [[A, B], [1, 2]]   .   relationships were
-     *                         .       was added       .        set up
-     *                         .                       .
-     *           +---+         .         +---+         .         +---+
-     *           |*,*|         .         |*,*|         .         |*,*|
-     *           +---+         .         +---+         .         +---+
-     *                         .         A             .         A   1
-     *                         .         |             .         |   :
-     *                         .         v             .         v   V
-     *                         .     +---+             .     +---+   +---+
-     *                         .     |A,*|             .     |A,*|   |*,1|
-     *                         .     +---+             .     +---+   +---+
-     *                         .     B                 .     B   1   A   2
-     *                         .     |                 .     |   :   |   :
-     *                         .     v                 .     v   v   v   v
-     *                         . +---+                 . +---+   +---+   +---+
-     *                         . |B,*|                 . |B,*|   |A,1|   |*,2|
-     *                         . +---+                 . +---+   +---+   +---+
-     *                         .     1                 .     1   B   2   A
-     *                         .     :                 .     :   |   :   |
-     *                         .     v                 .     v   v   v   v
-     *                         .     +---+             .     +---+   +---+
-     *                         .     |B,1|             .     |B,1|   |A,2|
-     *                         .     +---+             .     +---+   +---+
-     *                         .         2             .         2   B
-     *                         .         :             .         :   |
-     *                         .         v             .         v   V
-     *                         .         +---+         .         +---+
-     *                         .         |B,2|         .         |B,2|
-     *                         .         +---+         .         +---+
-     */
-    setUpMissingChildRelationships_: function (node, firstDimensionToSetUp) {
-      // Missing child relationships of this node wrt dimensions 0, ...,
-      // (firstDimensionToSetUp - 1) and all descendants of the associated
-      // children have already been set up. Now we do the same for dimensions
-      // firstDimensionToSetUp, ..., (this.dimensions_ - 1).
-      for (var d = firstDimensionToSetUp; d < this.dimensions_; d++) {
-        // Step 1. Gather the names of all children wrt the current dimension.
-        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);
-            }
-          }
-        }
-
-        // Step 2. Add missing children wrt the current dimension and
-        // recursively set up its missing child relationships.
-        for (var currentDimensionChildTitle of currentDimensionChildTitles) {
-          // Add a missing child (if it doesn't exist).
-          var currentDimensionChildNode = this.getOrCreateChildNode_(node, d, currentDimensionChildTitle);
-
-          // Set-up child relationships (of the child node) wrt dimensions 0,
-          // ..., d - 1.
-          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);
-              }
-            }
-          }
-
-          // Set-up child relationships (of the child node) wrt dimensions d,
-          // ..., (this.dimensions_ - 1).
-          this.setUpMissingChildRelationships_(currentDimensionChildNode, d);
-        }
-      }
-    },
-
-    /**
-     * Finalize the total values of a multi-dimensional tree.
-     *
-     * The intermediate builder tree, a node of which we want to finalize
-     * recursively, already has the right shape. The only thing that needs to
-     * be done is to propagate self and total values from subsumed child nodes
-     * in each dimension and update total value states appropriately.
-     *
-     * To derive the expression for the lower bound on the total value wrt
-     * value index V (from 1 to |this.valueCount_| - 1), we rely on the
-     * following assumptions:
-     *
-     *   1. Self/total values associated with different value indices are
-     *      independent. From this point onwards, "self/total value" refers to
-     *      self/total value wrt the fixed value index V.
-     *
-     *   2. Each node's self value does NOT overlap with the self or total value
-     *      of any other node.
-     *
-     *   3. The total values of a node's children wrt a single dimension (e.g.
-     *      [path1/A, path2] and [path1/B, path2]) do NOT overlap.
-     *
-     *   4. The total values of a node's children wrt different dimensions
-     *      (e.g. [path1/A, path2] and [path1, path2/1]) MIGHT overlap.
-     *
-     * As a consequence of assumptions 1 and 3, the total value of a node can
-     * be split into the part that cannot overlap (so-called "self-sum") and
-     * the part that can overlap (so-called "residual"):
-     *
-     *   total(N, V) = selfSum(N, V) + residual(N, V)                   (A)
-     *
-     * where the self-sum is calculated as the sum of the node's self value
-     * plus the sum of its descendants' self values (summed over all
-     * dimensions):
-     *
-     *   selfSum(N, V) = self(N, V) + sum over all descendants C of N {
-     *       self(C, V)                                                 (B)
-     *   }
-     *
-     * Observe that the residual of a node does not include any self value (of
-     * any node in the view). Furthermore, by assumption 2, we derive that the
-     * residuals of a node's children wrt a single dimension don't overlap. On
-     * the other hand, the residuals of a node's children wrt different
-     * dimensions might overlap. This gives us the following lower bound on the
-     * residual of a node:
-     *
-     *   residual(N, V) >= minResidual(N, V) = max over dimensions D {
-     *       sum over children C of N at dimension D {
-     *           residual(C, V)                                         (C)
-     *       }
-     *   })
-     *
-     * By combining equation (A) and inequality (C), we get a lower bound on
-     * the total value of a node:
-     *
-     *   total(N, V) >= selfSum(N, V) + minResidual(N, V)
-     *
-     * For example, given a two-dimensional node [path1, path2] with self value
-     * 10 and four children (2 wrt each dimension):
-     *
-     *    Child            | Self value | Total value
-     *   ==================+============+=============
-     *    [path1/A, path2] |         21 |          30
-     *    [path1/B, path2] |         25 |          32
-     *    [path1, path2/1] |         3  |          15
-     *    [path1, path2/2] |         40 |          41
-     *
-     * and assuming that the children have no further descendants (i.e. their
-     * residual values are equal to the differences between their total and
-     * self values), the lower bound on the total value of [path1, path2] is:
-     *
-     *   total([path1, path2], 0)
-     *       >= selfSum([path1, path2], 0) +
-     *          minResidual([path1, path2], 0)
-     *        = self([path1, path2], 0) +
-     *          sum over all descendants C of [path1, path2] {
-     *              self (C, 0)
-     *          } +
-     *          max over dimensions D {
-     *              sum over children C of [path1, path2] at dimension D {
-     *                  residual(C, 0)
-     *              }
-     *          }
-     *        = self([path1, path2], 0) +
-     *          ((self([path1/A, path2], 0) + self([path1/B, path2], 0)) +
-     *           (self([path1, path2/1], 0) + self([path1, path2/2], 0))) +
-     *          max(residual([path1/A, path2], 0) +
-     *              residual([path1/B, path2], 0),
-     *              residual([path1, path2/1], 0) +
-     *              residual([path1, path2/2], 0))
-     *        = 10 +
-     *          ((21 + 25) + (3 + 40)) +
-     *          max((30 - 21) + (32 - 25), (15 - 3) + (41 - 40))
-     *        = 115
-     *
-     * To reduce the complexity of the calculation, we keep a temporary list of
-     * dimensional self-sums for each node that we have already visited. For a
-     * given node, the Kth element in the list is equal to the self size of the
-     * node plus the sum of self sizes of all its descendants wrt dimensions 0
-     * to K (inclusive). The list has two important properties:
-     *
-     *   1. The last element in the list is equal to the self-sum of the
-     *      associated node (equation (B)).
-     *
-     *   2. The calculation of the list can be performed recursively using the
-     *      lists of the associated node's children (avoids square complexity
-     *      in the size of the graph):
-     *
-     *        dimensionalSelfSum(N, V)[D] =
-     *            self(N, V) +
-     *            sum I = 0 to D {
-     *                sum over children C of N at dimension I {
-     *                    dimensionalSelfSum(C, V)[I]
-     *                }
-     *            }
-     *
-     * This method also (recursively) ensures that, for each value index V, if
-     * at least one of the descendants C of node N has at least a LOWER_BOUND
-     * on total(C, V), then the N will also be marked as having a LOWER_BOUND
-     * on total(N, V) (unless N contains the EXACT value of total(N, V), in
-     * which case its relevant totalState won't be modified).
-     */
-    finalizeTotalValues_: function (node, firstDimensionToFinalize, dimensionalSelfSumsMap) {
-      // Dimension D -> Value index V -> dimensionalSelfSum(|node|, V)[D].
-      var dimensionalSelfSums = new Array(this.dimensions_);
-
-      // Value index V -> minResidual(|node|, V).
-      var minResidual = new Array(this.valueCount_);
-      for (var v = 0; v < this.valueCount_; v++) minResidual[v] = 0;
-
-      // Value index V -> |node| value V.
-      var nodeValues = node.values;
-
-      // Value index V -> dimensionalSelfSum(|node|, V)[|d|].
-      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++) {
-        // Value index V -> sum over children C of |node| at dimension |d| {
-        // residual(C, V) }.
-        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);
-          // Dimension D -> Value index V ->
-          // dimensionalSelfSum(|childNode|, V)[D].
-          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);
-    },
-
-    /**
-     * Build a generic heavy view of the multi-dimensional view.
-     */
-    buildGenericHeavyView_: function (treeViewNodeHandler) {
-      // 1. Clone the root node of the top-down tree view node (except
-      // children).
-      var treeViewRoot = this.buildTopDownTreeView();
-      var heavyViewRoot = this.createRootNode_();
-      heavyViewRoot.values = treeViewRoot.values;
-
-      // 2. Create recursion depth trackers (to avoid total value
-      // double-counting).
-      var recursionDepthTrackers = new Array(this.dimensions_);
-      for (var d = 0; d < this.dimensions_; d++) {
-        recursionDepthTrackers[d] = new RecursionDepthTracker(this.maxDimensionDepths_[d], d);
-      }
-
-      // 3. Add all paths associated with the single-dimensional descendants of
-      // the top-down tree view root node to the heavy view root node
-      // (depending on the type of the target heavy view).
-      this.addDimensionsToGenericHeavyViewNode_(treeViewRoot, heavyViewRoot, 0 /* startDimension */, recursionDepthTrackers, false /* previousDimensionsRecursive */, treeViewNodeHandler);
-
-      // 4. Set up missing child relationships.
-      this.setUpMissingChildRelationships_(heavyViewRoot, 0 /* firstDimensionToSetUp */);
-
-      return heavyViewRoot;
-    },
-
-    /**
-     * Add all paths associated with the single-dimensional descendants of a
-     * top-down tree-view node wrt multiple dimensions to a generic heavy-view
-     * node (depending on the type of the target heavy view).
-     */
-    addDimensionsToGenericHeavyViewNode_: function (treeViewParentNode, heavyViewParentNode, startDimension, recursionDepthTrackers, previousDimensionsRecursive, treeViewNodeHandler) {
-      for (var d = startDimension; d < this.dimensions_; d++) {
-        this.addDimensionDescendantsToGenericHeavyViewNode_(treeViewParentNode, heavyViewParentNode, d, recursionDepthTrackers, previousDimensionsRecursive, treeViewNodeHandler);
-      }
-    },
-
-    /**
-     * Add all paths associated with the descendants of a top-down tree-view
-     * node wrt a single dimension to a generic heavy-view node (depending on
-     * the type of the target heavy view).
-     */
-    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);
-
-        // Add all paths associated with the child node to the heavy view-node
-        // parent node.
-        treeViewNodeHandler(treeViewChildNode, heavyViewParentNode, currentDimension, recursionDepthTrackers, previousDimensionsRecursive);
-
-        // Recursively add all paths associated with the descendants of the
-        // tree view child node wrt the current dimension to the heavy-view
-        // parent node.
-        this.addDimensionDescendantsToGenericHeavyViewNode_(treeViewChildNode, heavyViewParentNode, currentDimension, recursionDepthTrackers, previousDimensionsRecursive, treeViewNodeHandler);
-
-        recursionDepthTracker.pop();
-      }
-    },
-
-    /**
-     * Add a top-down tree-view child node together with its single-dimensional
-     * subtree to a top-down heavy-view parent node (tree-view node handler for
-     * top-down heavy view).
-     *
-     * Sample resulting top-down heavy view:
-     *
-     *       +----------------+                    +-----------------+
-     *       |     source     |                    |   destination   |
-     *       | tree-view root |  ===============>  | heavy-view root |
-     *       |     self=0     |                    |     self=0      |
-     *       |    total=48    |                    |    total=48     |
-     *       +----------------+                    +-----------------+
-     *         |            |                  ______|      |      |______
-     *         v            v                 v             v             v
-     *    +----------+ +----------+      +----------+ +----------+ +----------+
-     *    |    A*    | |    B     |      |    A***  | |    B     | |    C     |
-     *    | self=10  | | self=12  |      | self=13  | | self=13  | | self=2   |
-     *    | total=30 | | total=18 |      | total=30 | | total=34 | | total=7  |
-     *    +----------+ +----------+      +----------+ +----------+ +----------+
-     *         |                              :            :   :.........
-     *         v                              v            v            v
-     *    +----------+                   ............ ............ ............
-     *    |    B     |                   :    B     : :    A     : :    C     :
-     *    | self=1   |                   : self=1   : : self=3   : : self=2   :
-     *    | total=16 |                   : total=16 : : total=8  : : total=7  :
-     *    +----------+                   ............ ............ ............
-     *         |   |________                  :   :.........
-     *         v            v                 v            v
-     *    +----------+ +----------+      ............ ............
-     *    |    A**   | |    C     |      :    A     : :    C     :
-     *    | self=3   | | self=2   |      : self=3   : : self=2   :
-     *    | total=8  | | total=7  |      : total=8  : : total=7  :
-     *    +----------+ +----------+      ............ ............
-     *
-     * Observe that care needs to be taken when dealing with recursion to avoid
-     * double-counting, e.g. the total value of A** (8) was not added to the
-     * total value of A*** (30) because it is already included in the total
-     * value of A* (30) (which was also added to A***). That is why we need to
-     * keep track of the path we traversed along the current dimension (to
-     * determine whether total value should be added or not).
-     */
-    addDimensionToTopDownHeavyViewNode_: function (treeViewChildNode, heavyViewParentNode, currentDimension, recursionDepthTrackers, previousDimensionsRecursive) {
-      this.addDimensionToTopDownHeavyViewNodeRecursively_(treeViewChildNode, heavyViewParentNode, currentDimension, recursionDepthTrackers, previousDimensionsRecursive, 1 /* subTreeDepth */);
-    },
-
-    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 /* addTotal */);
-
-      // Add the descendants of the tree-view child node wrt the next
-      // dimensions as children of the heavy-view child node.
-      this.addDimensionsToGenericHeavyViewNode_(treeViewChildNode, heavyViewChildNode, currentDimension + 1, recursionDepthTrackers, currentOrPreviousDimensionsRecursive, this.addDimensionToTopDownHeavyViewNode_.bind(this));
-
-      for (var treeViewGrandChildNode of treeViewChildNode.children[currentDimension].values()) {
-        recursionDepthTracker.push(treeViewGrandChildNode);
-
-        // Recursively add the tree-view grandchild node to the heavy-view
-        // child node.
-        this.addDimensionToTopDownHeavyViewNodeRecursively_(treeViewGrandChildNode, heavyViewChildNode, currentDimension, recursionDepthTrackers, previousDimensionsRecursive, subTreeDepth + 1);
-
-        recursionDepthTracker.pop();
-      }
-    },
-
-    /**
-     * Add a top-down tree-view child node together with all its ancestors wrt
-     * the given dimension as descendants of a bottom-up heavy-view parent node
-     * in the reverse order (tree-view node handler for bottom-up heavy view).
-     *
-     * Sample resulting bottom-up heavy view:
-     *
-     *       +----------------+                    +-----------------+
-     *       |     source     |                    |   destination   |
-     *       | tree-view root |  ===============>  | heavy-view root |
-     *       |     self=0     |                    |     self=0      |
-     *       |    total=48    |                    |    total=48     |
-     *       +----------------+                    +-----------------+
-     *         |            |                  ______|      |      |______
-     *         v            v                 v             v             v
-     *    +----------+ +----------+      +----------+ +----------+ +----------+
-     *    |    A*    | |    B     |      |    A***  | |    B     | |    C     |
-     *    | self=10  | | self=12  |      | self=13  | | self=13  | | self=2   |
-     *    | total=30 | | total=18 |      | total=30 | | total=34 | | total=7  |
-     *    +----------+ +----------+      +----------+ +----------+ +----------+
-     *         |                              :            :            :
-     *         v                              v            v            v
-     *    +----------+                   ............ ............ ............
-     *    |    B#    |                   :    B     : :    A     : :    B##   :
-     *    | self=1   |                   : self=3   : : self=1   : : self=2   :
-     *    | total=16 |                   : total=8  : : total=16 : : total=7  :
-     *    +----------+                   ............ ............ ............
-     *         |   |________                  :                         :
-     *         v            v                 v                         v
-     *    +----------+ +----------+      ............              ............
-     *    |    A**   | |    C     |      :    A     :              :    A     :
-     *    | self=3   | | self=2   |      : self=3   :              : self=2   :
-     *    | total=8  | | total=7  |      : total=8  :              : total=7  :
-     *    +----------+ +----------+      ............              ............
-     *
-     * Similarly to the construction of the top-down heavy view, care needs to
-     * be taken when dealing with recursion to avoid double-counting, e.g. the
-     * total value of A** (8) was not added to the total value of A*** (30)
-     * because it is already included in the total value of A* (30) (which was
-     * also added to A***). That is why we need to keep track of the path we
-     * traversed along the current dimension (to determine whether total value
-     * should be added or not).
-     *
-     * Note that when we add an ancestor (B#) of a top-down tree-view node (C)
-     * to the bottom-up heavy view, the values of the original tree-view node
-     * (C) (rather than the ancestor's values) are added to the corresponding
-     * heavy-view node (B##).
-     */
-    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;
-
-        // The self and total values are taken from the original top-down tree
-        // view child node (rather than the ancestor node).
-        this.addNodeValues_(treeViewChildNode, heavyViewDescendantNode, !currentOrPreviousDimensionsRecursive);
-
-        // Add the descendants of the tree-view child node wrt the next
-        // dimensions as children of the heavy-view child node.
-        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);
-          }
-        }
-      }
-    }
-  };
-
-  /**
-   * Recursion depth tracker.
-   *
-   * This class tracks the recursion depth of the current stack (updated via
-   * the push and pop methods). The recursion depth of a stack is the lengh of
-   * its longest leaf suffix that is repeated within the stack itself.
-   *
-   * For example, the recursion depth of the stack A -> B -> C -> A -> B -> B
-   * -> C (where C is the leaf node) is 2 because the suffix B -> C is repeated
-   * within it.
-   *
-   * @{constructor}
-   */
-  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 /* recursionDepth */, undefined /* parent */);
-  }
-
-  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) {
-        // Child node already exists, so we don't need to calculate anything.
-        this.currentTrackerNode_ = childTrackerNode;
-        return;
-      }
-
-      // Child node doesn't exist yet, so we need to calculate its recursion
-      // depth.
-      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()
-      };
-    }
-  };
-
-  /**
-   * Calculate the Z-function of (a suffix of) a list.
-   *
-   * Z-function: Given a list (or a string) of length n, for each index i from
-   * 1 to n - 1, find the length z[i] of the longest substring starting at
-   * position i which is also a prefix of the list. This function returns the
-   * list of maximum lengths z.
-   *
-   * Mathematically, for each i from 1 to n - 1, z[i] is the maximum value such
-   * that [list[0], ..., list[i - 1]] = [list[i], ..., list[i + z[i] - 1]].
-   * z[0] is defined to be zero for convenience.
-   *
-   * Example:
-   *
-   *   Input (list): ['A', 'B', 'A', 'C', 'A', 'B', 'A']
-   *   Output (z):   [ 0 ,  0 ,  1 ,  0 ,  3 ,  0 ,  1 ]
-   *
-   * Unlike the brute-force approach (which is O(n^2) in the worst case), the
-   * complexity of this implementation is linear in the size of the list, i.e.
-   * O(n).
-   *
-   * Source: http://e-maxx-eng.github.io/string/z-function.html
-   */
-  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,
-
-    // Exports below are for testing only.
-    RecursionDepthTracker: RecursionDepthTracker,
-    zFunction: zFunction
-  };
-});
+"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":28}],44:[function(require,module,exports){
+},{"./base.js":34}],50:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-require("./base.js");
-
-'use strict';
-
-global.tr.exportTo('tr.b', function () {
-  var PERCENTILE_PRECISION = 1e-7;
-  /**
-   * A function that consists of linear pieces.
-   * See https://en.wikipedia.org/wiki/Piecewise_linear_function.
-   * @constructor
-   */
-  function PiecewiseLinearFunction() {
-    this.pieces = [];
-  }
-
-  PiecewiseLinearFunction.prototype = {
-    /**
-     * Push a linear piece defined by linear interpolation between.
-     * (x1, y1) and (x2, y2).
-     * Pieces must be pushed in the order of increasing x coordinate.
-     */
-    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));
-    },
-
-    /**
-     *  Returns the size of the set A such that for all x in A: f(x) < y.
-     */
-    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;
-    },
-
-    /**
-    * Returns the minimum possible value y such that the percentage of x points
-    * that have f(x) <= y is approximately equal to the given |percent|.
-    */
-    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;
-    }
-  };
-
-  /**
-  * A linear segment from (x1, y1) to (x2, y2).
-  * @constructor
-  */
-  function Piece(x1, y1, x2, y2) {
-    this.x1 = x1;
-    this.y1 = y1;
-    this.x2 = x2;
-    this.y2 = y2;
-  }
-
-  Piece.prototype = {
-    /**
-    * The total length of all x points such that f(x) < y.
-    * More formally:
-    * max(x2 - x1) such that for all x in [x1 .. x2]: f(x) < y.
-    */
-    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
-  };
-});
+"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":28}],45:[function(require,module,exports){
+},{"./base.js":34}],51:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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;
-  }
-
-  /**
-   * @constructor
-   */
-  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 () {
-      // Simple rectangle check. Note: will not handle out-of-order components.
-      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
-  };
-});
+"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":28,"./math.js":42}],46:[function(require,module,exports){
+},{"./base.js":34,"./math.js":48}],52:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./utils.js");
-
-'use strict';
-
-global.tr.exportTo('tr.b', function () {
-  var ESTIMATED_IDLE_PERIOD_LENGTH_MILLISECONDS = 10;
-  // The maximum amount of time that we allow for a task to get scheduled
-  // in idle time before forcing the task to run.
-  var REQUEST_IDLE_CALLBACK_TIMEOUT_MILLISECONDS = 100;
-
-  // Setting this to true will cause stack traces to get dumped into the
-  // tasks. When an exception happens the original stack will be printed.
-  //
-  // NOTE: This should never be set committed as true.
-  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 /* forceAllTasksToRun */, 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) {
-      // We assume that we want to do a fixed maximum amount of optional work
-      // per frame. Hopefully rAF will eventually pass this in for us.
-      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());
-      // Check timer after running at least one idle task to avoid buggy
-      // window.performance.now() on some platforms from blocking the idle
-      // task queue.
-      if (!forceAllTasksToRun && (tr.isHeadless || deadline.timeRemaining() <= 0)) {
-        break;
-      }
-    }
-
-    if (pendingIdleCallbacks.length > 0) scheduleIdleWork();
-  }
-
-  function getStack_() {
-    if (!recordRAFStacks) return '';
-
-    var stackLines = tr.b.stackTrace();
-    // Strip off getStack_.
-    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
-  };
-});
+"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":59}],47:[function(require,module,exports){
+},{"./utils.js":65}],53:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./base.js");
-require("./iteration_helpers.js");
-require("./math.js");
-
-'use strict';
-
-/**
- * @fileoverview Quick range computations.
- */
-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
-      };
-    },
-
-    /**
-     * Returns a slice of the input array that intersects with this range
-     * inclusively.
-     * If the range does not have a min, it is treated as unbounded from below.
-     * Similarly, if max is undefined, the range is unbounded from above.
-     *
-     * @param {Array} array The array of elements to be filtered.
-     * @param {Funcation=} opt_keyFunc A function that extracts a numeric value,
-     *        to be used in comparisons, from an element of the array. If not
-     *        specified, array elements themselves will be used.
-     * @param {Object=} opt_this An optional this argument to be passed to
-     *        opt_keyFunc.
-     */
-    filterArray: function (array, opt_keyFunc, opt_this) {
-      if (this.isEmpty_) return [];
-      // Binary search. |test| is a function that should return true when we
-      // need to explore the left branch and false to explore the right branch.
-      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; // Explore the left branch.
-          else i0 = i + 1; // Explore the right branch.
-        }
-        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
-  };
-});
+"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":28,"./iteration_helpers.js":41,"./math.js":42}],48:[function(require,module,exports){
+},{"./base.js":34,"./iteration_helpers.js":47,"./math.js":48}],54:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./base.js");
-require("./iteration_helpers.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides event merging functionality for grouping/analysis.
- */
-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 = [];
-
-      // Refill merge buffer if needed.
-      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;
-      }
-
-      // Too big a gap.
-      flushCurrentMergeBuffer();
-    }
-    flushCurrentMergeBuffer();
-
-    return mergedEvents;
-  }
-
-  // Pass in |opt_totalRange| in order to find empty ranges before the first of
-  // |inRanges| and after the last of |inRanges|.
-  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) {
-          // |inRanges| is sorted, so |other| is the first range after |range|,
-          // and there is an empty range between them.
-          emptyRanges.push(tr.b.Range.fromExplicitRange(range.max, other.min));
-          return;
-        }
-        // Otherwise, |other| starts before |range| ends, so |other| might
-        // possibly contain the end of |range|.
-
-        if (other.max > range.max) {
-          // |other| does contain the end of |range|, so no empty range starts
-          // at the end of this |range|.
-          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
-  };
-});
+"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":28,"./iteration_helpers.js":41}],49:[function(require,module,exports){
+},{"./base.js":34,"./iteration_helpers.js":47}],55:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./base.js");
-require("./math.js");
-
-'use strict';
-
-global.tr.exportTo('tr.b', function () {
-
-  /**
-   * Tracks a 2D bounding box.
-   * @constructor
-   */
-  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
-  };
-});
+"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":28,"./math.js":42}],50:[function(require,module,exports){
+},{"./base.js":34,"./math.js":48}],56:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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 () {
-  /***
-  * An object of this class computes basic statistics online in O(1).
-  * Usage:
-  * 1. Create an instance.
-  * 2. Add numbers using the |add| method.
-  * 3. Query statistics.
-  * 4. Repeat from step 2.
-  */
-  class RunningStatistics {
-    constructor() {
-      this.mean_ = 0;
-      this.count_ = 0;
-      this.max_ = -Infinity;
-      this.min_ = Infinity;
-      this.sum_ = 0;
-      this.variance_ = 0;
-
-      // Mean of logarithms of absolute values of samples, or undefined if any
-      // samples were <= 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;
-
-      // The geometric mean is computed using the arithmetic mean of logarithms.
-      if (x <= 0) this.meanlogs_ = undefined;else if (this.meanlogs_ !== undefined) this.meanlogs_ += (Math.log(Math.abs(x)) - this.meanlogs_) / this.count;
-
-      // The following uses Welford's algorithm for computing running mean
-      // and variance. See http://www.johndcook.com/blog/standard_deviation.
-      if (this.count_ === 1) {
-        this.mean_ = x;
-        this.variance_ = 0;
-      } else {
-        var oldMean = this.mean_;
-        var oldVariance = this.variance_;
-        // Using the 2nd formula for updating the mean yields better precision
-        // but it doesn't work for the case oldMean is Infinity. Hence we handle
-        // that case separately.
-        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 {
-        // Combine the mean and the variance using the formulas from
-        // https://goo.gl/ddcAep.
-        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;
-
-        // Merge the arithmetic means of logarithms of absolute values of
-        // samples, weighted by counts.
-        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 [];
-      }
-      // It's more efficient to serialize these fields in an array. If you
-      // add any other fields, you should re-evaluate whether it would be more
-      // efficient to serialize as a dict.
-      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
-  };
-});
+"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":28}],51:[function(require,module,exports){
+},{"./base.js":34}],57:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-require("./color.js");
-require("./iteration_helpers.js");
-require("./math.js");
-
-'use strict';
-global.tr.exportTo('tr.b', function () {
-  /**
-   * Generate pretty colors!
-   * http://basecase.org/env/on-rainbows
-   * https://mycarta.wordpress.com/2012/10/06/the-rainbow-is-deadlong-live-the-rainbow-part-3/
-   *
-   * Set brightness = 0 to always generate black.
-   * Set brightness = 2 to always generate white.
-   * Set brightness = 1 to generate saturated colors.
-   *
-   * @constructor
-   * @param {number=} opt_a alpha opacity in [0,1]
-   * @param {number=} opt_brightness in [0,2]
-   */
-  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;
-    // Roughly correct for human perception.
-    // https://en.wikipedia.org/wiki/Luma_%28video%29
-    // Multiply by 2 to normalize all values to 0.5.
-    // (Halfway between black and white.)
-    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
-  };
-});
+"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":31,"./iteration_helpers.js":41,"./math.js":42}],52:[function(require,module,exports){
+},{"./color.js":37,"./iteration_helpers.js":47,"./math.js":48}],58:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./base.js");
-
-'use strict';
-
-/**
- * @fileoverview Helper functions for doing intersections and iteration
- * over sorted arrays and intervals.
- *
- */
-global.tr.exportTo('tr.b', function () {
-  /**
-   * Finds the first index in the array whose value is >= loVal.
-   *
-   * The key for the search is defined by the mapFn. This array must
-   * be prearranged such that ary.map(mapFn) would also be sorted in
-   * ascending order.
-   *
-   * @param {Array} ary An array of arbitrary objects.
-   * @param {function():*} mapFn Callback that produces a key value
-   *     from an element in ary.
-   * @param {number} loVal Value for which to search.
-   * @return {Number} Offset o into ary where all ary[i] for i <= o
-   *     are < loVal, or ary.length if loVal is greater than all elements in
-   *     the array.
-   */
-  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 where we hit, or failing that the low pos
-    return hitPos != -1 ? hitPos : low;
-  }
-
-  // From devtools/front_end/platform/utilities.js upperBound
-  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;
-  }
-
-  /**
-   * Finds an index in an array of intervals that either intersects
-   * the provided loVal, or if no intersection is found, -1 or ary.length.
-   *
-   * The array of intervals is defined implicitly via two mapping functions
-   * over the provided ary. mapLoFn determines the lower value of the interval,
-   * mapWidthFn the width. Intersection is lower-inclusive, e.g. [lo,lo+w).
-   *
-   * The array of intervals formed by this mapping must be non-overlapping and
-   * sorted in ascending order by loVal.
-   *
-   * @param {Array} ary An array of objects that can be converted into sorted
-   *     nonoverlapping ranges [x,y) using the mapLoFn and mapWidth.
-   * @param {function():*} mapLoFn Callback that produces the low value for the
-   *     interval represented by an  element in the array.
-   * @param {function():*} mapWidthFn Callback that produces the width for the
-   *     interval represented by an  element in the array.
-   * @param {number} loVal The low value for the search.
-   * @return {Number} An index in the array that intersects or is first-above
-   *     loVal, -1 if none found and loVal is below than all the intervals,
-   *     ary.length if loVal is greater than all the intervals.
-   */
-  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;
-    }
-  }
-
-  /**
-   * Finds an index in an array of sorted closed intervals that either
-   * intersects the provided val, or if no intersection is found, -1 or
-   *  ary.length.
-   *
-   * The array of intervals is defined implicitly via two mapping functions
-   * over the provided ary. mapLoFn determines the lower value of the interval,
-   * mapHiFn the high. Intersection is closed, e.g. [lo,hi], unlike with
-   * findIndexInSortedIntervals, which is right-open.
-   *
-   * The array of intervals formed by this mapping must be non-overlapping, and
-   * sorted in ascending order by val.
-   *
-   * @param {Array} ary An array of objects that can be converted into sorted
-   *     nonoverlapping ranges [x,y) using the mapLoFn and mapWidth.
-   * @param {function():*} mapLoFn Callback that produces the low value for the
-   *     interval represented by an  element in the array.
-   * @param {function():*} mapHiFn Callback that produces the high for the
-   *     interval represented by an  element in the array.
-   * @param {number} val The value for the search.
-   * @return {Number} An index in the array that intersects or is first-above
-   *     val, -1 if none found and val is below than all the intervals,
-   *     ary.length if val is greater than all the intervals.
-   */
-  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;
-    }
-  }
-
-  /**
-   * Calls cb for all intervals in the implicit array of intervals
-   * defnied by ary, mapLoFn and mapHiFn that intersect the range
-   * [loVal,hiVal)
-   *
-   * This function uses the same scheme as findLowIndexInSortedArray
-   * to define the intervals. The same restrictions on sortedness and
-   * non-overlappingness apply.
-   *
-   * @param {Array} ary An array of objects that can be converted into sorted
-   * nonoverlapping ranges [x,y) using the mapLoFn and mapWidth.
-   * @param {function():*} mapLoFn Callback that produces the low value for the
-   * interval represented by an element in the array.
-   * @param {function():*} mapWidthFn Callback that produces the width for the
-   * interval represented by an element in the array.
-   * @param {number} loVal The low value for the search, inclusive.
-   * @param {number} hiVal The high value for the search, non inclusive.
-   * @param {function():*} cb The function to run for intersecting intervals.
-   */
-  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);
-    }
-  }
-
-  /**
-   * Non iterative version of iterateOverIntersectingIntervals.
-   *
-   * @return {Array} Array of elements in ary that intersect loVal, hiVal.
-   */
-  function getIntersectingIntervals(ary, mapLoFn, mapWidthFn, loVal, hiVal) {
-    var tmp = [];
-    iterateOverIntersectingIntervals(ary, mapLoFn, mapWidthFn, loVal, hiVal, function (d) {
-      tmp.push(d);
-    });
-    return tmp;
-  }
-
-  /**
-   * Finds the element in the array whose value is closest to |val|.
-   *
-   * The same restrictions on sortedness as for findLowIndexInSortedArray apply.
-   *
-   * @param {Array} ary An array of arbitrary objects.
-   * @param {function():*} mapFn Callback that produces a key value
-   *     from an element in ary.
-   * @param {number} val Value for which to search.
-   * @param {number} maxDiff Maximum allowed difference in value between |val|
-   *     and an element's value.
-   * @return {object} Object in the array whose value is closest to |val|, or
-   *     null if no object is within range.
-   */
-  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];
-  }
-
-  /**
-   * Finds the closest interval in the implicit array of intervals
-   * defined by ary, mapLoFn and mapHiFn.
-   *
-   * This function uses the same scheme as findLowIndexInSortedArray
-   * to define the intervals. The same restrictions on sortedness and
-   * non-overlappingness apply.
-   *
-   * @param {Array} ary An array of objects that can be converted into sorted
-   *     nonoverlapping ranges [x,y) using the mapLoFn and mapHiFn.
-   * @param {function():*} mapLoFn Callback that produces the low value for the
-   *     interval represented by an element in the array.
-   * @param {function():*} mapHiFn Callback that produces the high for the
-   *     interval represented by an element in the array.
-   * @param {number} val The value for the search.
-   * @param {number} maxDiff Maximum allowed difference in value between |val|
-   *     and an interval's low or high value.
-   * @return {interval} Interval in the array whose high or low value is closest
-   *     to |val|, or null if no interval is within range.
-   */
-  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
-  };
-});
+"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":28}],53:[function(require,module,exports){
+},{"./base.js":34}],59:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./math.js");
-require("./range.js");
-
-'use strict';
-
-// In node, the script-src for mannwhitneyu above brings in mannwhitneyui
-// into a module, instead of into the global scope. Whereas this file
-// assumes that mannwhitneyu is in the global scope. So, in Node only, we
-// require() it in, and then take all its exports and shove them into the
-// global scope by hand.
-(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';
-
-// TODO(charliea): Remove:
-/* eslint-disable catapult-camelcase */
-
-global.tr.exportTo('tr.b', function () {
-  var identity = x => x;
-
-  var Statistics = {};
-
-  /* Returns the quotient, or zero if the denominator is zero.*/
-  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;
-
-    // The geometric mean is expressed as the arithmetic mean of logarithms
-    // in order to prevent overflow.
-    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);
-  };
-
-  // Returns undefined if the sum of the weights is zero.
-  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];
-  };
-
-  /**
-   * Sorts the samples, and map them linearly to the range [0,1].
-   *
-   * They're mapped such that for the N samples, the first sample is 0.5/N and
-   * the last sample is (N-0.5)/N.
-   *
-   * Background: The discrepancy of the sample set i/(N-1); i=0, ..., N-1 is
-   * 2/N, twice the discrepancy of the sample set (i+1/2)/N; i=0, ..., N-1. In
-   * our case we don't want to distinguish between these two cases, as our
-   * original domain is not bounded (it is for Monte Carlo integration, where
-   * discrepancy was first used).
-   **/
-  Statistics.normalizeSamples = function (samples) {
-    if (samples.length === 0) {
-      return {
-        normalized_samples: samples,
-        scale: 1.0
-      };
-    }
-    // Create a copy to make sure that we don't mutate original |samples| input.
-    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 is an array of 0.5 in this case.
-      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
-    };
-  };
-
-  /**
-   * Computes the discrepancy of a set of 1D samples from the interval [0,1].
-   *
-   * The samples must be sorted. We define the discrepancy of an empty set
-   * of samples to be zero.
-   *
-   * http://en.wikipedia.org/wiki/Low-discrepancy_sequence
-   * http://mathworld.wolfram.com/Discrepancy.html
-   */
-  Statistics.discrepancy = function (samples, opt_locationCount) {
-    if (samples.length === 0) return 0.0;
-
-    var maxLocalDiscrepancy = 0;
-    var invSampleCount = 1.0 / samples.length;
-    var locations = [];
-    // For each location, stores the number of samples less than that location.
-    var countLess = [];
-    // For each location, stores the number of samples less than or equal to
-    // that location.
-    var countLessEqual = [];
-
-    if (opt_locationCount !== undefined) {
-      // Generate list of equally spaced locations.
-      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 {
-      // Populate locations with sample positions. Append 0 and 1 if necessary.
-      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);
-      }
-    }
-
-    // Compute discrepancy as max(overshoot, -undershoot), where
-    // overshoot = max(countClosed(i, j)/N - length(i, j)) for all i < j,
-    // undershoot = min(countOpen(i, j)/N - length(i, j)) for all i < j,
-    // N = len(samples),
-    // countClosed(i, j) is the number of points between i and j
-    // including ends,
-    // countOpen(i, j) is the number of points between i and j excluding ends,
-    // length(i, j) is locations[i] - locations[j].
-
-    // The following algorithm is modification of Kadane's algorithm,
-    // see https://en.wikipedia.org/wiki/Maximum_subarray_problem.
-
-    // The maximum of (countClosed(k, i-1)/N - length(k, i-1)) for any k < i-1.
-    var maxDiff = 0;
-    // The minimum of (countOpen(k, i-1)/N - length(k, i-1)) for any k < i-1.
-    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];
-      // Number of points that are added if we extend a closed range that
-      // ends at location (i-1).
-      var countClosedIncrement = countLessEqual[i] - countLessEqual[i - 1];
-      // Number of points that are added if we extend an open range that
-      // ends at location (i-1).
-      var countOpenIncrement = countLess[i] - countLess[i - 1];
-
-      // Either extend the previous optimal range or start a new one.
-      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;
-  };
-
-  /**
-   * A discrepancy based metric for measuring timestamp jank.
-   *
-   * timestampsDiscrepancy quantifies the largest area of jank observed in a
-   * series of timestamps.  Note that this is different from metrics based on
-   * the max_time_interval. For example, the time stamp series A = [0,1,2,3,5,6]
-   *  and B = [0,1,2,3,5,7] have the same max_time_interval = 2, but
-   * Discrepancy(B) > Discrepancy(A).
-   *
-   * Two variants of discrepancy can be computed:
-   *
-   * Relative discrepancy is following the original definition of
-   * discrepancy. It characterized the largest area of jank, relative to the
-   * duration of the entire time stamp series.  We normalize the raw results,
-   * because the best case discrepancy for a set of N samples is 1/N (for
-   * equally spaced samples), and we want our metric to report 0.0 in that
-   * case.
-   *
-   * Absolute discrepancy also characterizes the largest area of jank, but its
-   * value wouldn't change (except for imprecisions due to a low
-   * |interval_multiplier|) if additional 'good' intervals were added to an
-   * exisiting list of time stamps.  Its range is [0,inf] and the unit is
-   * milliseconds.
-   *
-   * The time stamp series C = [0,2,3,4] and D = [0,2,3,4,5] have the same
-   * absolute discrepancy, but D has lower relative discrepancy than C.
-   *
-   * |timestamps| may be a list of lists S = [S_1, S_2, ..., S_N], where each
-   * S_i is a time stamp series. In that case, the discrepancy D(S) is:
-   * D(S) = max(D(S_1), D(S_2), ..., D(S_N))
-   **/
-  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) {
-      // Compute absolute discrepancy
-      discrepancy /= sampleScale;
-    } else {
-      // Compute relative discrepancy
-      discrepancy = tr.b.clamp((discrepancy - invSampleCount) / (1.0 - invSampleCount), 0.0, 1.0);
-    }
-    return discrepancy;
-  };
-
-  /**
-   * A discrepancy based metric for measuring duration jank.
-   *
-   * DurationsDiscrepancy computes a jank metric which measures how irregular a
-   * given sequence of intervals is. In order to minimize jank, each duration
-   * should be equally long. This is similar to how timestamp jank works,
-   * and we therefore reuse the timestamp discrepancy function above to compute
-   * a similar duration discrepancy number.
-   *
-   * Because timestamp discrepancy is defined in terms of timestamps, we first
-   * convert the list of durations to monotonically increasing timestamps.
-   *
-   * Args:
-   *  durations: List of interval lengths in milliseconds.
-   *  absolute: See TimestampsDiscrepancy.
-   *  opt_locationCount: See TimestampsDiscrepancy.
-   **/
-  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);
-  };
-
-  /**
-   * Modifies |samples| in-place to reduce its length down to |count|.
-   *
-   * @param {!Array} samples
-   * @param {number} count
-   * @return {!Array}
-   */
-  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;
-  };
-
-  /**
-   * A mechanism to uniformly sample elements from an arbitrary long stream.
-   *
-   * Call this method every time a new element is obtained from the stream,
-   * passing always the same |samples| array and the |numSamples| you desire.
-   * Also pass in the current |streamLength|, which is the same as the index of
-   * |newElement| within that stream.
-   *
-   * The |samples| array will possibly be updated, replacing one of its element
-   * with |newElements|. The length of |samples| will not be more than
-   * |numSamples|.
-   *
-   * This method guarantees that after |streamLength| elements have been
-   * processed each one has equal probability of being in |samples|. The order
-   * of samples is not preserved though.
-   *
-   * Args:
-   *  samples: Array of elements that have already been selected. Start with [].
-   *  streamLength: The current length of the stream, up to |newElement|.
-   *  newElement: The element that was just extracted from the stream.
-   *  numSamples: The total number of samples desired.
-   **/
-  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; // New sample was rejected.
-
-    // Keeping it, replace an alement randomly.
-    var index = Math.floor(Math.random() * numSamples);
-    samples[index] = newElement;
-  };
-
-  /**
-   * A mechanism to merge two arrays of uniformly sampled elements in a way that
-   * ensures elements in the final array are still sampled uniformly.
-   *
-   * This works similarly to sampleStreamUniform. The |samplesA| array will be
-   * updated, some of its elements replaced by elements from |samplesB| in a
-   * way that ensure that elements will be sampled uniformly.
-   *
-   * Args:
-   *  samplesA: Array of uniformly sampled elements, will be updated.
-   *  streamLengthA: The length of the stream from which |samplesA| was sampled.
-   *  samplesB: Other array of uniformly sampled elements, will NOT be updated.
-   *  streamLengthB: The length of the stream from which |samplesB| was sampled.
-   *  numSamples: The total number of samples desired, both in |samplesA| and
-   *      |samplesB|.
-   **/
-  Statistics.mergeSampledStreams = function (samplesA, streamLengthA, samplesB, streamLengthB, numSamples) {
-    if (streamLengthB < numSamples) {
-      // samplesB has not reached max capacity so every sample of stream B were
-      // chosen with certainty. Add them one by one into samplesA.
-      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) {
-      // samplesA has not reached max capacity so every sample of stream A were
-      // chosen with certainty. Add them one by one into samplesB.
-      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);
-      }
-      // Copy that back into the first vector.
-      for (var i = 0; i < tempSamples.length; ++i) {
-        samplesA[i] = tempSamples[i];
-      }
-      return;
-    }
-
-    // Both sample arrays are at max capacity, use the power of maths!
-    // Elements in samplesA have been selected with probability
-    // numSamples / streamLengthA. Same for samplesB. For each index of the
-    // array we keep samplesA[i] with probability
-    //   P = streamLengthA / (streamLengthA + streamLengthB)
-    // and replace it with samplesB[i] with probability 1-P.
-    // The total probability of keeping it is therefore
-    //   numSamples / streamLengthA *
-    //                      streamLengthA / (streamLengthA + streamLengthB)
-    //   = numSamples / (streamLengthA + streamLengthB)
-    // A similar computation shows we have the same probability of keeping any
-    // element in samplesB. Magic!
-    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];
-      }
-    }
-  };
-
-  /* Continuous distributions are defined by probability density functions.
-   *
-   * Random variables are referred to by capital letters: X, Y, Z.
-   * Particular values from these distributions are referred to by lowercase
-   * letters like |x|.
-   * The probability that |X| ever exactly equals |x| is P(X==x) = 0.
-   *
-   * For a discrete probability distribution, see tr.v.Histogram.
-   */
-  function Distribution() {}
-
-  Distribution.prototype = {
-    /* The probability density of the random variable at value |x| is the
-     * relative likelihood for this random variable to take on the given value
-     * |x|.
-     *
-     * @param {number} x A value from the random distribution.
-     * @return {number} probability density at x.
-     */
-    computeDensity: function (x) {
-      throw Error('Not implemented');
-    },
-
-    /* A percentile is the probability that a sample from the distribution is
-     * less than the given value |x|. This function is monotonically increasing.
-     *
-     * @param {number} x A value from the random distribution.
-     * @return {number} P(X<x).
-     */
-    computePercentile: function (x) {
-      throw Error('Not implemented');
-    },
-
-    /* A complementary percentile is the probability that a sample from the
-     * distribution is greater than the given value |x|. This function is
-     * monotonically decreasing.
-     *
-     * @param {number} x A value from the random distribution.
-     * @return {number} P(X>x).
-     */
-    computeComplementaryPercentile: function (x) {
-      return 1 - this.computePercentile(x);
-    },
-
-    /* Compute the mean of the probability distribution.
-     *
-     * @return {number} mean.
-     */
-    get mean() {
-      throw Error('Not implemented');
-    },
-
-    /* The mode of a distribution is the most likely value.
-     * The maximum of the computeDensity() function is at this mode.
-     * @return {number} mode.
-     */
-    get mode() {
-      throw Error('Not implemented');
-    },
-
-    /* The median is the center value of the distribution.
-     * computePercentile(median) = computeComplementaryPercentile(median) = 0.5
-     *
-     * @return {number} median.
-     */
-    get median() {
-      throw Error('Not implemented');
-    },
-
-    /* The standard deviation is a measure of how dispersed or spread out the
-     * distribution is (this statistic has the same units as the values).
-     *
-     * @return {number} standard deviation.
-     */
-    get standardDeviation() {
-      throw Error('Not implemented');
-    },
-
-    /* An alternative measure of how spread out the distribution is,
-     * the variance is the square of the standard deviation.
-     * @return {number} variance.
-     */
-    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;
-    }
-  };
-
-  /* The Normal or Gaussian distribution, or bell curve, is common in complex
-   * processes such as are found in many of the natural sciences.  If Z is the
-   * standard normal distribution with mean = 0 and variance = 1, then the
-   * general normal distribution is Y = mean + Z*sqrt(variance).
-   * https://www.desmos.com/calculator/tqtbjm4s3z
-   */
-  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_;
-    }
-  };
-
-  /* The log-normal distribution is a continuous probability distribution of a
-   * random variable whose logarithm is normally distributed.
-   * If Y is the general normal distribution, then X = exp(Y) is the general
-   * log-normal distribution.
-   * X will have different parameters from Y,
-   * so the mean of Y is called the "location" of X,
-   * and the standard deviation of Y is called the "shape" of X.
-   * The standard lognormal distribution exp(Z) has location = 0 and shape = 1.
-   * https://www.desmos.com/calculator/tqtbjm4s3z
-   */
-  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);
-    }
-  };
-
-  /**
-   * Instead of describing a LogNormalDistribution in terms of its "location"
-   * and "shape", it can also be described in terms of its median
-   * and the point at which its complementary cumulative distribution
-   * function bends between the linear-ish region in the middle and the
-   * exponential-ish region. When the distribution is used to compute
-   * percentiles for log-normal random processes such as latency, as the latency
-   * improves, it hits a point of diminishing returns, when it becomes
-   * relatively difficult to improve the score further. This point of
-   * diminishing returns is the first x-intercept of the third derivative of the
-   * CDF, which is the second derivative of the PDF.
-   *
-   * https://www.desmos.com/calculator/cg5rnftabn
-   *
-   * @param {number} median The median of the distribution.
-   * @param {number} diminishingReturns The point of diminishing returns.
-   * @return {LogNormalDistribution}
-   */
-  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);
-  };
-
-  // p-values less than this indicate statistical significance.
-  Statistics.DEFAULT_ALPHA = 0.05;
-
-  /** @enum */
-  Statistics.Significance = {
-    INSIGNIFICANT: -1,
-    DONT_CARE: 0,
-    SIGNIFICANT: 1
-  };
-
-  /**
-   * @typedef {Object} HypothesisTestResult
-   * @property {number} p
-   * @property {number} U
-   * @property {!tr.b.Statistics.Significance} significance
-   */
-
-  /**
-   * @param {!Array.<number>} a
-   * @param {!Array.<number>} b
-   * @param {number=} opt_alpha
-   * @return {!HypothesisTestResult}
-   */
-  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
-  };
-});
+"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":42,"./range.js":47}],54:[function(require,module,exports){
+},{"./math.js":48,"./range.js":53}],60:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("./raf.js");
-require("./timing.js");
-
-'use strict';
-
-global.tr.exportTo('tr.b', function () {
-  var Timing = tr.b.Timing;
-  /**
-   * A task is a combination of a run callback, a set of subtasks, and an after
-   * task.
-   *
-   * When executed, a task does the following things:
-   * 1. Runs its callback
-   * 2. Runs its subtasks
-   * 3. Runs its after callback.
-   *
-   * The list of subtasks and after task can be mutated inside step #1 but as
-   * soon as the task's callback returns, the subtask list and after task is
-   * fixed and cannot be changed again.
-   *
-   * Use task.after().after().after() to describe the toplevel passes that make
-   * up your computation. Then, use subTasks to add detail to each subtask as it
-   * runs. For example:
-   *    var pieces = [];
-   *    taskA = new Task(function() { pieces = getPieces(); });
-   *    taskA.after(function(taskA) {
-   *      pieces.forEach(function(piece) {
-   *        taskA.subTask(function(taskB) { piece.process(); }, this);
-   *      });
-   *    });
-   *
-   * @constructor
-   */
-  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;
-    },
-
-    /*
-     * See constructor documentation on semantics of subtasks.
-     */
-    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];
-    },
-
-    /**
-     * Runs the current task and returns the task that should be executed next.
-     */
-    run: function () {
-      if (this.runCb_ !== undefined) this.runCb_.call(this.thisArg_, this);
-      var subTasks = this.subTasks_;
-      this.subTasks_ = undefined; // Prevent more subTasks from being posted.
-
-      if (!subTasks.length) return this.afterTask_;
-
-      // If there are subtasks, then we want to execute all the subtasks and
-      // then this task's afterTask. To make this happen, we update the
-      // afterTask of all the subtasks so the point upward to each other, e.g.
-      // subTask[0].afterTask to subTask[1] and so on. Then, the last subTask's
-      // afterTask points at this task's 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];
-    },
-
-    /*
-     * See constructor documentation on semantics of after tasks.
-     */
-    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_;
-    },
-
-    /*
-     * See constructor documentation on semantics of after tasks.
-     * Note: timedAfter doesn't work when a task throws an exception.
-     * This is because task system doesn't support catching currently.
-     * At the time of writing, this is considered to be an acceptable tradeoff.
-     */
-    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);
-    },
-
-    /*
-     * See constructor documentation on semantics of after tasks.
-     * Note: namedTimedAfter doesn't work when a task throws an exception.
-     * This is because task system doesn't support catching currently.
-     * At the time of writing, this is considered to be an acceptable tradeoff.
-     */
-    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_;
-    },
-
-    /*
-     * Adds a task after the chain of tasks.
-     */
-    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();
-  };
-
-  /**
-   * Runs a task using raf.requestIdleCallback, returning
-   * a promise for its completion.
-   */
-  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
-  };
-});
+"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":46,"./timing.js":56}],55:[function(require,module,exports){
+},{"./raf.js":52,"./timing.js":62}],61:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-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.
-**/
-
-require("./unit_scale.js");
-
-'use strict';
-
-/**
- * @fileoverview Time currentDisplayUnit
- */
-global.tr.exportTo('tr.b', function () {
-  var msDisplayMode = {
-    scale: 1e-3,
-    suffix: 'ms',
-    // Compares a < b with adjustments to precision errors.
-    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',
-    // Compares a < b with adjustments to precision errors.
-    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
-  };
-});
+"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":58}],56:[function(require,module,exports){
+},{"./unit_scale.js":64}],62:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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, // export for testing
-    TimedNamedPromise: TimedNamedPromise,
-    Timing: Timing
-  };
-});
+"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":28,"./base64.js":29}],57:[function(require,module,exports){
+},{"./base.js":34,"./base64.js":35}],63:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-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.
-**/
-
-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;
-  }
-
-  /** @enum */
-  var ImprovementDirection = {
-    DONT_CARE: 0,
-    BIGGER_IS_BETTER: 1,
-    SMALLER_IS_BETTER: 2
-  };
-
-  /** @constructor */
-  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;
-
-    // Example: powerInWattsDelta_biggerIsBetter -> powerInWatts.
-    this.baseUnit = undefined;
-
-    // Example: energyInJoules_smallerIsBetter ->
-    // energyInJoulesDelta_smallerIsBetter.
-    this.correspondingDeltaUnit = undefined;
-  }
-
-  Unit.prototype = {
-    asJSON: function () {
-      return this.jsonName;
-    },
-
-    get unitString() {
-      // TODO(benjhayden): Refactor with format() and test.
-      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; // Treat positive and negative values symmetrically.
-      } 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 the context overrides only one of the two |*FractionDigits|
-      // properties and the other one is provided by the unit, we might need to
-      // shift the other property so that
-      // |minimumFractionDigits| <= |maximumFractionDigits|.
-      if (minimumFractionDigits > maximumFractionDigits) {
-        if ('minimumFractionDigits' in context && !('maximumFractionDigits' in context)) {
-          // Only minimumFractionDigits was overriden by context.
-          maximumFractionDigits = minimumFractionDigits;
-        } else if ('maximumFractionDigits' in context && !('minimumFractionDigits' in context)) {
-          // Only maximumFractionDigits was overriden by 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_;
-    },
-    // Use tr-v-ui-preferred-display-unit element instead of directly setting.
-    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');
-  };
-
-  /**
-   * Define all combinations of a unit with isDelta and improvementDirection
-   * flags. For example, the following code:
-   *
-   *   Unit.define({
-   *     baseUnitName: 'powerInWatts'
-   *     baseJsonName: 'W'
-   *     formatSpec: {
-   *       // Specification of how the unit should be formatted (unit symbol,
-   *       // unit prefix, fraction digits, etc), or a function returning such
-   *       // a specification.
-   *     }
-   *   });
-   *
-   * generates the following six units (JSON names shown in parentheses):
-   *
-   *   Unit.byName.powerInWatts (W)
-   *   Unit.byName.powerInWatts_smallerIsBetter (W_smallerIsBetter)
-   *   Unit.byName.powerInWatts_biggerIsBetter (W_biggerIsBetter)
-   *   Unit.byName.powerInWattsDelta (WDelta)
-   *   Unit.byName.powerInWattsDelta_smallerIsBetter (WDelta_smallerIsBetter)
-   *   Unit.byName.powerInWattsDelta_biggerIsBetter (WDelta_biggerIsBetter)
-   *
-   * with the appropriate flags and formatting code (including +/- prefixes
-   * for deltas).
-   */
-  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();
-
-  // Known display units follow.
-  //////////////////////////////////////////////////////////////////////////////
-
-  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
-  };
-});
+"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":33,"./event_target.js":34,"./iteration_helpers.js":41,"./time_display_modes.js":55,"./unit_scale.js":58}],58:[function(require,module,exports){
+},{"./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";
-/**
-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.
-**/
-
-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 + '\'');
-    }
-
-    // If the 'AUTO' unit prefix is used, the prefix that results in
-    // the absolute formatted value being as close to the [1, 1024) interval as
-    // possible is used. Example: 1023 and 1024 bytes are displayed as
-    // "1,023.0 B" and "1.0 KiB", respectively.
-    prefixes.AUTO = tr.b.dictionaryValues(prefixes);
-    prefixes.AUTO.sort((a, b) => a.value - b.value);
-
-    UnitScale[name] = prefixes;
-  }
-
-  /**
-   * Converts |value| from |fromPrefix| (e.g. kilo) to |toPrefix| (e.g. mega).
-   *
-   * Returns undefined if |value| is undefined.
-   * |fromPrefix| and |toPrefix| need not come from the same UnitScale.
-   *
-   * @param {(undefined|number)} value
-   * @param {!object} fromPrefix
-   * @param {!object} toPrefix
-   * @return {(undefined|number)}
-   */
-  function convertUnit(value, fromPrefix, toPrefix) {
-    if (value === undefined) return undefined;
-    return value * (fromPrefix.value / toPrefix.value);
-  }
-
-  // See https://en.wikipedia.org/wiki/Binary_prefix.
-  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' }
-  });
-
-  // See https://en.wikipedia.org/wiki/Metric_prefix.
-  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
-  };
-});
+"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":41}],59:[function(require,module,exports){
+},{"./iteration_helpers.js":47}],65:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./base.js");
-
-'use strict';
-
-global.tr.exportTo('tr.b', function () {
-  /**
-   * Adds a {@code getInstance} static method that always return the same
-   * instance object.
-   * @param {!Function} ctor The constructor for the class to add the static
-   *     method to.
-   */
-  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;
-  }
-
-  /**
-   * Format date as a string "YYYY-MM-DD HH:mm:ss". The timezone is implicitly
-   * UTC. This format is based on the ISO format, but without milliseconds and
-   * the 'T' is replaced with a space for legibility.
-   *
-   * @param {!Date} date
-   * @return {string}
-   */
-  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
-  };
-});
+"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":28}],60:[function(require,module,exports){
+},{"./base.js":34}],66:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/base.js");
-require("../base/extension_registry.js");
-
-'use strict';
-
-/**
- * @fileoverview Base class for auditors.
- */
-global.tr.exportTo('tr.c', function () {
-  function Auditor(model) {
-    this.model_ = model;
-  }
-
-  Auditor.prototype = {
-    __proto__: Object.prototype,
-
-    get model() {
-      return this.model_;
-    },
-
-    /**
-     * Called by the Model after baking slices. May modify model.
-     */
-    runAnnotate: function () {},
-
-    /**
-     * Called by import to install userFriendlyCategoryDriver.
-     */
-    installUserFriendlyCategoryDriverIfNeeded: function () {},
-
-    /**
-     * Called by the Model after importing. Should not modify model, except
-     * for adding interaction ranges and audits.
-     */
-    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
-  };
-});
+"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":28,"../base/extension_registry.js":35}],61:[function(require,module,exports){
+},{"../base/base.js":34,"../base/extension_registry.js":41}],67:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2012 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.
-**/
-
-require("../base/base.js");
-
-'use strict';
-
-global.tr.exportTo('tr.c', function () {
-  function makeCaseInsensitiveRegex(pattern) {
-    // See https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/
-    // Regular_Expressions.
-    pattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
-    return new RegExp(pattern, 'i');
-  }
-
-  /**
-   * @constructor The generic base class for filtering a Model based on
-   * various rules. The base class returns true for everything.
-   */
-  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;
-    }
-  };
-
-  /**
-   * @constructor A filter that matches objects by their name or category
-   * case insensitive.
-   * .findAllObjectsMatchingFilter
-   */
-  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);
-    }
-  };
-
-  /**
-   * @constructor A filter that matches objects with the exact given title.
-   */
-  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_;
-    }
-  };
-
-  /**
-   * @constructor A filter that matches objects by their full text contents
-   * (title, category, args). Note that for performance this filter applies a
-   * regex against all the keys of the slice arguments instead of recursing
-   * through any embedded sub-objects.
-   */
-  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
-  };
-});
+"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":28}],62:[function(require,module,exports){
+},{"../base/base.js":34}],68:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-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,
-
-    // Legacy InputLatencyAsyncSlices involve a top-level slice titled
-    // "InputLatency" containing a subSlice whose title starts with
-    // "InputLatency:". Modern InputLatencyAsyncSlices involve a single
-    // top-level slice whose title starts with "InputLatency::".
-    // Legacy subSlices are not available at construction time, so
-    // determineLegacyTypeName_() must be called at get time.
-    // So this returns false for the legacy subSlice events titled like
-    // "InputLatency:Foo" even though they are technically legacy events.
-    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 () {
-      // This method works both on modern events titled like
-      // "InputLatency::Foo" and also on the legacy subSlices titled like
-      // "InputLatency:Foo". Modern events' titles contain 2 colons, whereas the
-      // legacy subSlices events contain 1 colon.
-
-      var lastColonIndex = this.title.lastIndexOf(':');
-      if (lastColonIndex < 0) return;
-
-      var characterAfterLastColonIndex = lastColonIndex + 1;
-      this.typeName_ = this.title.slice(characterAfterLastColonIndex);
-
-      // Check that the determined typeName is known.
-      this.checkTypeName_();
-    },
-
-    determineLegacyTypeName_: function () {
-      // Iterate over all descendent subSlices.
-      for (var subSlice of this.enumerateAllDescendents()) {
-
-        // If |subSlice| is not an InputLatencyAsyncSlice, then ignore it.
-        var subSliceIsAInputLatencyAsyncSlice = subSlice instanceof InputLatencyAsyncSlice;
-        if (!subSliceIsAInputLatencyAsyncSlice) continue;
-
-        // If |subSlice| does not have a typeName, then ignore it.
-        if (!subSlice.typeName) continue;
-
-        // If |this| already has a typeName and |subSlice| has a different
-        // typeName, then explode!
-        if (this.typeName_ && subSlice.typeName_) {
-          var subSliceHasDifferentTypeName = this.typeName_ !== subSlice.typeName_;
-          if (subSliceHasDifferentTypeName) {
-            throw 'InputLatencyAsyncSlice.determineLegacyTypeName_() ' + ' found multiple typeNames';
-          }
-        }
-
-        // The typeName of |this| top-level event is whatever the typeName of
-        // |subSlice| is. Set |this.typeName_| to the subSlice's typeName.
-        this.typeName_ = subSlice.typeName_;
-      }
-
-      // If typeName could not be determined, then explode!
-      if (!this.typeName_) throw 'InputLatencyAsyncSlice.determineLegacyTypeName_() failed';
-
-      // Check that the determined typeName is known.
-      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;
-    },
-
-    // Find the Latency::ScrollUpdate slice that corresponds to the
-    // InputLatency::GestureScrollUpdate slice.
-    // The C++ CL that makes this connection is at:
-    // https://codereview.chromium.org/1178963003
-    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) {
-          // Old trace, we can only rely on the timestamp to find the slice
-          if (slice.start < gestureScrollUpdateStart || slice.start >= gestureScrollUpdateEnd) continue;
-        } else {
-          // New trace, we can definitively find the latency slice by comparing
-          // its sequence number with gesture id
-          if (parseInt(parentId) !== parseInt(this.id)) continue;
-        }
-
-        slice.associatedEvents.forEach(function (event) {
-          this.associatedEvents_.push(event);
-        }, this);
-        break;
-      }
-    },
-
-    // Return true if the slice hierarchy is tracked by LatencyInfo of other
-    // input latency events. If the slice hierarchy is tracked by both, this
-    // function still returns true.
-    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;
-    },
-
-    // Return true if |event| triggers slices of other inputs.
-    triggerOtherInputs: function (event, flowEvents) {
-      if (event.outFlowEvents === undefined || event.outFlowEvents.length === 0) return false;
-
-      // Once we fix the bug of flow event binding, there should exist one and
-      // only one outgoing flow (PostTask) from ScheduleBeginImplFrameDeadline
-      // and PostComposite.
-      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;
-    },
-
-    // Follow outgoing flow of subsequentSlices in the current hierarchy.
-    // We also handle cases where different inputs interfere with each other.
-    followSubsequentSlices: function (event, queue, visited, flowEvents) {
-      var stopFollowing = false;
-      var inputAck = false;
-
-      event.iterateAllSubsequentSlices(function (slice) {
-        if (stopFollowing) return;
-
-        // Do not follow TaskQueueManager::RunTask because it causes
-        // many false events to be included.
-        if (slice.title === 'TaskQueueManager::RunTask') return;
-
-        // Do not follow ScheduledActionSendBeginMainFrame because the real
-        // main thread BeginMainFrame is already traced by LatencyInfo flow.
-        if (slice.title === 'ThreadProxy::ScheduledActionSendBeginMainFrame') return;
-
-        // Do not follow ScheduleBeginImplFrameDeadline that triggers an
-        // OnBeginImplFrameDeadline that is tracked by another LatencyInfo.
-        if (slice.title === 'Scheduler::ScheduleBeginImplFrameDeadline') {
-          if (this.triggerOtherInputs(slice, flowEvents)) return;
-        }
-
-        // Do not follow PostComposite that triggers CompositeImmediately
-        // that is tracked by another LatencyInfo.
-        if (slice.title === 'CompositorImpl::PostComposite') {
-          if (this.triggerOtherInputs(slice, flowEvents)) return;
-        }
-
-        // Stop following the rest of the current slice hierarchy if
-        // FilterAndSendWebInputEvent occurs after ProcessInputEventAck.
-        if (slice.title === 'InputRouterImpl::ProcessInputEventAck') inputAck = true;
-        if (inputAck && slice.title === 'InputRouterImpl::FilterAndSendWebInputEvent') stopFollowing = true;
-
-        this.followCurrentSlice(slice, queue, visited);
-      }, this);
-    },
-
-    // Follow outgoing flow events of the current slice.
-    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);
-
-        // TODO(yuhao): For now, we backtrace all the way to the source input.
-        // But is this really needed? I will have an entry in the design
-        // doc to discuss this.
-        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;
-      });
-    },
-
-    // Find rasterization slices that have the source_prepare_tiles_id
-    // same as the prepare_tiles_id of TileManager::PrepareTiles
-    // The C++ CL that makes this connection is at:
-    // https://codereview.chromium.org/1208683002/
-    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 = [];
-
-      // Collect all the rasterizer tasks. Return the cached copy if possible.
-      if (sortedRasterizerSlices.length === 0) this.sortRasterizerSlices(rasterWorkerThreads, sortedRasterizerSlices);
-
-      // TODO(yuhao): Once TaskSetFinishedTaskImpl also get the prepareTileId
-      // we can simply track by checking id rather than counting.
-      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;
-          }
-        }
-      }
-
-      // Trace PostTask from rasterizer tasks.
-      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 = [];
-      // Keep track of visited nodes when traversing a DAG
-      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();
-
-        // Push the current event chunk into associatedEvents.
-        this.addEntireSliceHierarchy(event);
-
-        this.followCurrentSlice(event, pendingEventQueue, visitedEvents);
-
-        this.followSubsequentSlices(event, pendingEventQueue, visitedEvents, flowEvents);
-
-        // The rasterization work (CompositorTileWorker thread) and the
-        // Compositor tile manager are connect by the prepare_tiles_id
-        // instead of flow events.
-        var COMPOSITOR_PREPARE_TILES = 'TileManager::PrepareTiles';
-        prepareTiles = event.findDescendentSlice(COMPOSITOR_PREPARE_TILES);
-        if (prepareTiles) this.addRasterizationEvents(prepareTiles, rendererHelper, visitedEvents, flowEvents, sortedRasterizerSlices);
-
-        // OnBeginImplFrameDeadline could be triggered by other inputs.
-        // For now, we backtrace from it.
-        // TODO(yuhao): There are more such slices that we need to backtrace
-        var COMPOSITOR_ON_BIFD = 'Scheduler::OnBeginImplFrameDeadline';
-        beginImplFrame = event.findDescendentSlice(COMPOSITOR_ON_BIFD);
-        if (beginImplFrame) this.backtraceFromDraw(beginImplFrame, visitedEvents);
-      }
-
-      // A separate pass on GestureScrollUpdate.
-      // Scroll update doesn't go through the main thread, but the compositor
-      // may go back to the main thread if there is an onscroll event handler.
-      // This is captured by a different flow event, which does not have the
-      // same ID as the Input Latency Event, but it is technically causally
-      // related to the GestureScrollUpdate input. Add them manually for now.
-      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_;
-
-      // Step 1: Get events that are directly connected by the LatencyInfo
-      // flow events. This gives us a small set of events that are guaranteed
-      // to be associated with the input, but are almost certain incomplete.
-      // We call this set "source" event set.
-      // This step returns the "source" event set (sourceSlices), which is then
-      // used in the second step.
-      var sourceSlices = this.addDirectlyAssociatedEvents(flowEvents);
-
-      // Step 2: Start from the previously constructed "source" event set, we
-      // follow the toplevel (i.e., PostTask) and IPC flow events. Any slices
-      // that are reachable from the "source" event set via PostTasks or IPCs
-      // are conservatively considered associated with the input event.
-      // We then deal with specific cases where flow events either over include
-      // or miss capturing slices.
-      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) {
-    // Old style.
-    allTypeNames.push('InputLatency:' + eventTypeName);
-
-    // New style.
-    allTypeNames.push('InputLatency::' + eventTypeName);
-  });
-
-  AsyncSlice.subTypes.register(InputLatencyAsyncSlice, {
-    typeNames: allTypeNames,
-    categoryParts: ['latencyInfo']
-  });
-
-  return {
-    InputLatencyAsyncSlice: InputLatencyAsyncSlice,
-    INPUT_EVENT_TYPE_NAMES: INPUT_EVENT_TYPE_NAMES
-  };
-});
+"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":103,"../../../model/event_set.js":120,"../../../model/helpers/chrome_model_helper.js":127}],63:[function(require,module,exports){
+},{"../../../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";
-/**
-Copyright (c) 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.
-**/
-
-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'],
-
-    // Where do these go?
-    renderer_misc: ['DecodeFont', 'ThreadState::completeSweep' // blink_gc
-    ],
-
-    // TODO(fmeawad): https://github.com/catapult-project/catapult/issues/2572
-    v8_runtime: [
-      // Dynamically populated.
-    ],
-
-    [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);
-    });
-  }
-
-  // keys: event.category part
-  // values: user friendly 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();
-
-  // Prime the color generator by iterating through all UFCs in alphabetical
-  // order.
-  for (var category of ChromeUserFriendlyCategoryDriver.ALL_TITLES) ChromeUserFriendlyCategoryDriver.getColor(category);
-
-  return {
-    ChromeUserFriendlyCategoryDriver: ChromeUserFriendlyCategoryDriver
-  };
-});
+"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":33,"../../base/iteration_helpers.js":41,"../../base/sinebow_color_generator.js":51}],64:[function(require,module,exports){
+},{"../../base/event.js":39,"../../base/iteration_helpers.js":47,"../../base/sinebow_color_generator.js":57}],70:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-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.
-**/
-
-require("../../model/source_info/js_source_info.js");
-
-'use strict';
-
-/**
- * @fileoverview TraceCodeEntry is a wrapper around the V8 CodeEntry that
- * extracts extra context information for each item. This includes things like
- * the source file, line and if the function is a native method or not.
- */
-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;
-
-    // Stolen from DevTools TimelineJSProfileProcessor._buildCallFrame
-    // Code states:
-    // (empty) -> compiled
-    //    ~    -> optimizable
-    //    *    -> optimized
-    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);
-        // 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;
-      }
-
-      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
-  };
-});
+"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":153}],65:[function(require,module,exports){
+},{"../../model/source_info/js_source_info.js":159}],71:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-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.
-**/
-
-require("./trace_code_entry.js");
-
-'use strict';
-
-global.tr.exportTo('tr.e.importer', function () {
-  // This code is a tracification of:
-  // devtools/front_end/timeline/TimelineJSProfile.js
-  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) {
-      // FIXME: Handle bank spanning addresses ...
-      this.getBank_(addressHex).addEntry(entry);
-    },
-
-    getAddress_: function (addressHex) {
-      // 13 hex digits == 52 bits, double mantissa fits 53 bits.
-      var bankSizeHexDigits = 13;
-      addressHex = addressHex.slice(2); // cut 0x prefix.
-      return parseInt(addressHex.slice(-bankSizeHexDigits), 16);
-    },
-
-    getBank_: function (addressHex) {
-      addressHex = addressHex.slice(2); // cut 0x prefix.
-
-      // 13 hex digits == 52 bits, double mantissa fits 53 bits.
-      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) {
-      // findLowIndexInSortedArray returns 1 for empty. Just handle the
-      // empty list and bail early.
-      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) {
-      // findLowIndexInSortedArray returns 1 for empty list. Just push the
-      // new address as it's the only item.
-      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
-  };
-});
+"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":64}],66:[function(require,module,exports){
+},{"./trace_code_entry.js":70}],72:[function(require,module,exports){
 (function (global){
-"use strict";/**
-Copyright (c) 2012 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.
-**/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';/**
- * @fileoverview TraceEventImporter imports TraceEvent-formatted data
- * into the provided model.
- */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.';// Map from raw memory dump byte stat names to model byte stat names. See
-// //base/trace_event/process_memory_maps.cc in Chromium.
-var BYTE_STAT_NAME_MAP={'pc':'privateCleanResident','pd':'privateDirtyResident','sc':'sharedCleanResident','sd':'sharedDirtyResident','pss':'proportionalResident','sw':'swapped'};// See tr.model.MemoryAllocatorDump 'weak' field and
-// base::trace_event::MemoryAllocatorDump::Flags::WEAK in the Chromium
-// codebase.
-var WEAK_MEMORY_ALLOCATOR_DUMP_FLAG=1<<0;// Object type name patterns for various compilers.
-var OBJECT_TYPE_NAME_PATTERNS=[{// Clang.
-prefix:'const char *WTF::getStringWithTypeName() [T = ',suffix:']'},{// GCC.
-prefix:'const char* WTF::getStringWithTypeName() [with T = ',suffix:']'},{// Microsoft Visual C++
-prefix:'const char *__cdecl WTF::getStringWithTypeName<',suffix:'>(void)'}];// The list of fields on the trace that are known to contain subtraces.
-var SUBTRACE_FIELDS=new Set(['powerTraceAsString','systemTraceEvents']);// The complete list of fields on the trace that should not be treated as
-// trace metadata.
-var NON_METADATA_FIELDS=new Set(['samples','stackFrames','traceAnnotations','traceEvents']);// TODO(charliea): Replace this with the spread (...) operator in literal
-// above once v8 is updated to a sufficiently recent version (>M45).
-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_=[];// For tracking async events that is used to create back-compat clock sync
-// event.
-this.asyncClockSyncStart_=undefined;this.asyncClockSyncFinish_=undefined;// Dump ID -> PID -> [process memory dump events].
-this.allMemoryDumpEvents_={};// PID -> Object type ID -> Object type name.
-this.objectTypeNameMap_={};// For old Chrome traces with no clock domain metadata, just use a
-// placeholder clock domain.
-this.clockDomainId_=tr.model.ClockDomainId.UNKNOWN_CHROME_LEGACY;// A function able to transform timestamps in |clockDomainId| to timestamps
-// in the model clock domain.
-this.toModelTime_=undefined;if(typeof eventData==='string'||eventData instanceof String){eventData=eventData.trim();// If the event data begins with a [, then we know it should end with a ].
-// The reason we check for this is because some tracing implementations
-// cannot guarantee that a ']' gets written to the trace file. So, we are
-// forgiving and if this is obviously the case, we fix it up before
-// throwing the string at JSON.parse.
-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;// Some trace_event implementations put the actual trace events
-// inside a container. E.g { ... , traceEvents: [ ] }
-// If we see that, just pull out the trace events.
-if(this.events_.traceEvents){var container=this.events_;this.events_=this.events_.traceEvents;// Some trace authors store subtraces as specific properties of the trace.
-for(var subtraceField of SUBTRACE_FIELDS)if(container[subtraceField])this.subtraces_.push(container[subtraceField]);// Sampling data.
-this.sampleEvents_=container.samples;this.stackFrameEvents_=container.stackFrames;// Some implementations specify displayTimeUnit
-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;}// Any other fields in the container should be treated as metadata.
-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'];}}}}/**
-   * @return {boolean} Whether obj is a TraceEvent array.
-   */TraceEventImporter.canImport=function(eventData){// May be encoded JSON. But we dont want to parse it fully yet.
-// Use a simple heuristic:
-//   - eventData that starts with [ are probably trace_event
-//   - eventData that starts with { are probably trace_event
-// May be encoded JSON. Treat files that start with { as importable by us.
-if(typeof eventData==='string'||eventData instanceof String){eventData=eventData.trim();return eventData[0]==='{'||eventData[0]==='[';}// Might just be an array of events
-if(eventData instanceof Array&&eventData.length&&eventData[0].ph)return true;// Might be an object with a traceEvents field in it.
-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(){// Because subtraces can be quite large, we need to make sure that we
-// don't hold a reference to the memory.
-var subtraces=this.subtraces_;this.subtraces_=[];return subtraces;},/**
-     * Deep copying is only needed if the trace was given to us as events.
-     */deepCopyIfNeeded_:function(obj){if(obj===undefined)obj={};if(this.eventsWereFromString_)return obj;return deepCopy(obj);},/**
-     * Always perform deep copying.
-     */deepCopyAlways_:function(obj){if(obj===undefined)obj={};return deepCopy(obj);},/**
-     * Helper to process an async event.
-     */processAsyncEvent:function(event){var thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);this.allAsyncEvents_.push({sequenceNumber:this.allAsyncEvents_.length,event:event,thread:thread});},/**
-     * Helper to process a flow event.
-     */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,// slice for events that have flow info
-thread:thread});},/**
-     * Helper that creates and adds samples to a Counter object based on
-     * 'C' phase events.
-     */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;// Initialize the counter's series fields if needed.
-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.'});// Drop the counter.
-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);// The context processor maintains a cache of unique context objects and
-// active context sets to reduce memory usage. If an object is modified,
-// we should invalidate this cache, because otherwise context sets from
-// before and after the modification may erroneously point to the same
-// context snapshot (as both are the same set/object instances).
-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){// Preventing the overhead slices from making it into the model. This
-// only applies to legacy traces, as the overhead traces have been
-// removed from the chromium code.
-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;// TODO(dsinclair): There are _a lot_ of JitCode events so I'm skipping
-// the display for now. Can revisit later if we want to show them.
-// Handle JitCodeMoved and JitCodeAdded event.
-if(event.name==='JitCodeMoved')map.moveEntry(data.code_start,data.new_code_start,data.code_len);else// event.name === 'JitCodeAdded'
-map.addEntry(data.code_start,data.code_len,data.name,data.script_id);},processMetadataEvent:function(event){// V8 JIT events are currently logged as phase 'M' so we need to
-// separate them out and handle specially.
-if(event.name==='JitCodeAdded'||event.name==='JitCodeMoved'){this.v8SamplingData_.push(event);return;}// The metadata events aren't useful without args.
-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;// Not all render processes agree on the cpu count in trace_event. Some
-// processes will report 1, while others will report the actual cpu
-// count. To deal with this, take the max of what is reported.
-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){// V8 JIT events were logged as phase 'I' in the old format,
-// so we need to separate them out and handle specially.
-if(event.name==='JitCodeAdded'||event.name==='JitCodeMoved'){this.v8SamplingData_.push(event);return;}// Thread-level instant events are treated as zero-duration slices.
-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;// As-per DevTools, the backend sometimes creates bogus samples. Skip it.
-if(data.vm_state==='js'&&!data.stack.length)return;var rootStackFrame=this.v8ProcessRootStackFrame_[event.pid];if(!rootStackFrame){rootStackFrame=new tr.model.StackFrame(undefined/* parent */,'v8-root-stack-frame'/* id */,'v8-root-stack-frame'/* title */,0/* colorId */);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;// There are several types of v8 sample events, gc, native, compiler, etc.
-// Some of these types have stacks and some don't, we handle those two
-// cases differently. For types that don't have any stack frames attached
-// we synthesize one based on the type of thing that's happening so when
-// we view all the samples we'll see something like 'external' or 'gc'
-// as a fraction of the time spent.
-if(data.stack.length>0&&this.v8ProcessCodeMaps_[event.pid]){var map=this.v8ProcessCodeMaps_[event.pid];// Stacks have the leaf node first, flip them around so the root
-// comes first.
-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/* cpu */,thread,'V8 Sample',this.toModelTimeFromUs_(event.ts),lastStackFrame,1/* weight */,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;}// Dump ID -> PID -> [process memory dump events].
-var allEvents=this.allMemoryDumpEvents_;// PID -> [process memory dump events].
-var dumpIdEvents=allEvents[dumpId];if(dumpIdEvents===undefined)allEvents[dumpId]=dumpIdEvents={};// [process memory dump events].
-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){// When Chrome is the tracing controller and is the requester of the
-// clock sync, the clock sync event looks like:
-//
-//   {
-//     "args": {
-//       "sync_id": "abc123",
-//       "issue_ts": 12340
-//     }
-//     "ph": "c"
-//     "ts": 12345
-//     ...
-//   }
-this.model_.clockSyncManager.addClockSyncMarker(this.clockDomainId_,syncId,tr.b.Unit.timestampFromUs(event.args.issue_ts),tr.b.Unit.timestampFromUs(event.ts));}else{// When Chrome is a tracing agent and is the recipient of the clock
-// sync request, the clock sync event looks like:
-//
-//   {
-//     "args": { "sync_id": "abc123" }
-//     "ph": "c"
-//     "ts": 12345
-//     ...
-//   }
-this.model_.clockSyncManager.addClockSyncMarker(this.clockDomainId_,syncId,tr.b.Unit.timestampFromUs(event.ts));}},// Because the order of Jit code events and V8 samples are not guaranteed,
-// We store them in an array, sort by timestamp, and then process them.
-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;// Older version of Chrome doesn't support clock sync API, hence
-// telemetry get around it by marking the clock sync events with
-// console.time & console.timeEnd. When we encounter async events
-// with named started with 'ClockSyncEvent.' prefix, create a
-// synthetic clock sync events based on their timestamps.
-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);}},/**
-     * Walks through the events_ list and outputs the structures discovered to
-     * model_.
-     */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);// TODO(yuhaoz): If Chrome supports creating other events with flow,
-// we will need to call processFlowEvent for them also.
-// https://github.com/catapult-project/catapult/issues/1259
-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);// Note, I is historic. The instant event marker got changed, but we
-// want to support loading old trace files so we have both I and i.
-}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'){// No-op. Clock sync events have already been processed in
-// importClockSyncMarkers().
-}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();// Remove all the root stack frame children as they should
-// already be added.
-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/* parentFrame */,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 we haven't figured out yet which compiler the object type names
-// come from, we try to do it now.
-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)){// With compiler-specific prefix and suffix (automatically annotated
-// object types).
-objectTypeNameMap[objectTypeId]=rawObjectTypeName.substring(objectTypeNamePrefix.length,rawObjectTypeName.length-objectTypeNameSuffix.length);}else{// Without compiler-specific prefix and suffix (manually annotated
-// object types and '[unknown]').
-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);}},/**
-     * Called by the Model after all other importers have imported their
-     * events.
-     */finalizeImport:function(){if(this.softwareMeasuredCpuCount_!==undefined){this.model_.kernel.softwareMeasuredCpuCount=this.softwareMeasuredCpuCount_;}this.createAsyncSlices_();this.createFlowSlices_();this.createExplicitObjects_();this.createImplicitObjects_();this.createMemoryDumps_();},/* Events can have one or more stack frames associated with them, but
-     * that frame might be encoded either as a stack trace of program counters,
-     * or as a direct stack frame reference. This handles either case and
-     * if found, returns the stackframe.
-     */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){// TODO(alph,fmeawad): Add codemap resolution code here.
-return undefined;},importSampleData:function(){if(!this.sampleEvents_)return;var m=this.model_;// If this is the only importer, then fake-create the threads.
-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=[];// Group nestable async events by ID. Events with the same ID should
-// belong to the same parent async event.
-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);}// Handle legacy async events.
-this.createLegacyAsyncSlices_(legacyEvents);// Parse nestable measure async events into AsyncSlices.
-this.createNestableAsyncSlices_(nestableMeasureAsyncEventsByKey);// Parse nestable async events into AsyncSlices.
-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;}// TODO(simonjam): Add a synchronous tick on the appropriate thread.
-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'){// Create a slice from start to end.
-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;// Create subSlices for each step. Skip the start and finish events,
-// which are always first and last respectively.
-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){// Add |slice| to the start-thread's asyncSlices.
-slice.startThread.asyncSliceGroup.push(slice);}delete asyncEventStatesByNameThenID[name][id];}}}},createNestableAsyncSlices_:function(nestableEventsByKey){for(var key in nestableEventsByKey){var eventStateEntries=nestableEventsByKey[key];// Stack of enclosing BEGIN events.
-var parentStack=[];for(var i=0;i<eventStateEntries.length;++i){var eventStateEntry=eventStateEntries[i];// If this is the end of an event, match it to the start.
-if(eventStateEntry.event.ph==='e'){// Walk up the parent stack to find the corresponding BEGIN for
-// this END.
-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){// Unmatched end.
-eventStateEntry.finished=false;}else{parentStack[parentIndex].end=eventStateEntry;// Pop off all enclosing unmatched BEGINs util parentIndex.
-while(parentIndex<parentStack.length){parentStack.pop();}}}// Inherit the current parent.
-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];// Skip matched END, as its slice will be created when we
-// encounter its corresponding BEGIN.
-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){// Unmatched BEGIN. End it when last event with this ID ends.
-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{// Include args for both END and BEGIN for a matched pair.
-function concatenateArguments(args1,args2){if(args1.params===undefined||args2.params===undefined)return tr.b.concatenateObjects(args1,args2);// Make an argument object to hold the combined params.
-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{// Unmatched END. Start it at the first event with this ID starts.
-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;// Add the slice to the topLevelSlices array if there is no parent.
-// Otherwise, add the slice to the subSlices of its parent.
-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;}// Support Flow API v1.
-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;}// Support Flow API v2.
-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){// Support Flow API v2.
-startSlice=opt_slice;flowId=event.bind_id;flowStartTs=this.toModelTimeFromUs_(event.ts+event.dur);}else{// Support Flow API v1.
-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){// Support Flow API v2.
-endSlice=opt_slice;}else{// Support Flow API v1.
-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){// One flow producer can have more than one flow consumers.
-// In this case, create a new flow event using the flow producer.
-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]){// Can't open the same flow again while it's still open.
-// This is essentially the multi-producer case which we don't support
-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;}// Actual import.
-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={};// true: open; false: closed.
-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;// Support for Flow API v2.
-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;// open the flow.
-}else{if(!processFlowConsumer(flowIdToEvent,sliceGuidToEvent,event,slice))continue;flowStatus[event.bind_id]=false;// close the flow.
-if(event.flowPhase===STEP){if(!processFlowProducer(flowIdToEvent,flowStatus,event,slice))continue;flowStatus[event.bind_id]=true;// open the flow again.
-}}continue;}// Support for Flow API v1.
-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){// TODO(yuhaoz): In flow V2, there is no notion of binding point.
-// Removal of binding point is tracked in
-// https://github.com/google/trace-viewer/issues/991.
-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 this is a step, then create another flow event.
-if(ok&&event.ph==='t'){flowEvent=createFlowEvent(thread,event);flowIdToEvent[event.id]=flowEvent;}}}},/**
-     * This function creates objects described via the N, D, and O phase
-     * events.
-     */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);},// Here, we collect all the snapshots that internally contain a
-// Javascript-level object inside their args list that has an "id" field,
-// and turn that into a snapshot of the instance referred to by id.
-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;}/**
-       * Iterates over the fields in the object, calling func for every
-       * field/value found.
-       *
-       * @return {object} If the function does not want the field's value to be
-       * iterated, return null. If iteration of the field value is desired, then
-       * return either undefined (if the field value did not change) or the new
-       * field value if it was changed.
-       */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);}}// TODO(nduca): We may need to iterate the instances in sorted order by
-// creationTs.
-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){// 1. Create a GlobalMemoryDump for the provided process memory dump
-// the events, all of which have the same dump ID.
-// Calculate the range of the global memory dump.
-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');// Create the global memory dump.
-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={};// 2. Create a ProcessMemoryDump for each PID in the provided process
-// memory dump events. Everything except for edges between memory
-// allocator dumps is parsed from the process memory dump trace events at
-// this step.
-for(var pid in dumpIdEvents){this.createProcessMemoryDump_(globalMemoryDump,globalMemoryAllocatorDumpsByFullName,levelsOfDetail,allMemoryAllocatorDumpsByGuid,dumpIdEvents[pid],pid,dumpId);}// 3. Set the level of detail and memory allocator dumps of the
-// GlobalMemoryDump, which come from the process memory dump trace
-// events parsed in the prebvious step.
-globalMemoryDump.levelOfDetail=levelsOfDetail.global;// Find the root allocator dumps and establish the parent links of
-// the global memory dump.
-globalMemoryDump.memoryAllocatorDumps=this.inferMemoryAllocatorDumpTree_(globalMemoryAllocatorDumpsByFullName);// 4. Finally, parse the edges between all memory allocator dumps within
-// the GlobalMemoryDump. This can only be done once all memory allocator
-// dumps have been parsed (i.e. it is necessary to iterate over the
-// process memory dump trace events once more).
-this.parseMemoryDumpAllocatorEdges_(allMemoryAllocatorDumpsByGuid,dumpIdEvents,dumpId);},createProcessMemoryDump_:function(globalMemoryDump,globalMemoryAllocatorDumpsByFullName,levelsOfDetail,allMemoryAllocatorDumpsByGuid,processEvents,pid,dumpId){// Calculate the range of the process memory dump.
-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');// Create the process memory dump.
-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={};// Parse all process memory dump trace events for the newly created
-// ProcessMemoryDump.
-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;}// Totals, VM regions, and heap dumps for the newly created
-// ProcessMemoryDump should be present in at most one event, so they
-// can be added to the ProcessMemoryDump immediately.
-this.parseMemoryDumpTotals_(processMemoryDump,dumps,pid,dumpId);this.parseMemoryDumpVmRegions_(processMemoryDump,dumps,pid,dumpId);this.parseMemoryDumpHeapDumps_(processMemoryDump,dumps,pid,dumpId);// All process memory dump trace events for the newly created
-// ProcessMemoryDump must be processed before level of detail and
-// allocator dumps can be added to it.
-this.parseMemoryDumpLevelOfDetail_(levelsOfDetail,dumps,pid,dumpId);this.parseMemoryDumpAllocatorDumps_(processMemoryDump,globalMemoryDump,processMemoryAllocatorDumpsByFullName,globalMemoryAllocatorDumpsByFullName,allMemoryAllocatorDumpsByGuid,dumps,pid,dumpId);}if(levelsOfDetail.process===undefined){// Infer level of detail from the presence of VM regions in legacy
-// traces (where raw process memory dump events don't contain the
-// level_of_detail field). These traces will not have BACKGROUND mode.
-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;// Reused for all process dumps.
-// Find the root allocator dumps and establish the parent links of
-// the process memory dump.
-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;// Total resident bytes.
-if(rawTotalName==='resident_set_bytes'){totals.residentBytes=parseInt(rawTotalValue,16);continue;}// Peak resident bytes.
-if(rawTotalName==='peak_resident_set_bytes'){totals.peakResidentBytes=parseInt(rawTotalValue,16);continue;}if(rawTotalName==='is_peak_rss_resetable'){totals.arePeakResidentBytesResettable=!!rawTotalValue;continue;}// OS-specific totals (e.g. private resident on Mac).
-if(platformSpecificTotals===undefined){platformSpecificTotals={};totals.platformSpecific=platformSpecificTotals;}platformSpecificTotals[rawTotalName]=parseInt(rawTotalValue,16);}// Either both peak_resident_set_bytes and is_peak_rss_resetable should
-// be present in the trace, or neither.
-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;}// See //base/trace_event/process_memory_maps.cc in Chromium.
-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),// startAddress
-parseInt(rawVmRegion.sz,16),// sizeInBytes
-rawVmRegion.pf,// protectionFlags
-rawVmRegion.mf,// mappedFile
-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;}// The old format always starts with a {size: <total>} entry.
-// See https://goo.gl/WYStil
-// TODO(petrcermak): Remove support for the old format once the new
-// format has been around long enough.
-var isOldFormat=entries[0].bt===undefined;if(!isOldFormat&&objectTypeNameMap===undefined){// Mapping from object type IDs to names must be provided in the new
-// format.
-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;// There are two possible mappings from leaf stack frame indices
-// (provided in the trace) to the corresponding stack frames
-// depending on the format.
-if(isOldFormat){// Old format:
-//   Undefined index        -> / (root)
-//   Defined index for /A/B -> /A/B/<self>
-if(leafStackFrameIndex===undefined){leafStackFrame=undefined/* root */;}else{// Get the leaf stack frame corresponding to the provided index.
-var leafStackFrameId=idPrefix+leafStackFrameIndex;if(leafStackFrameIndex===''){leafStackFrame=undefined/* root */;}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;}}// Inject an artificial <self> leaf stack frame.
-leafStackFrameId+=':self';if(model.stackFrames[leafStackFrameId]!==undefined){// The frame might already exist if there are multiple process
-// memory dumps (for the same process) in the trace.
-leafStackFrame=model.stackFrames[leafStackFrameId];}else{leafStackFrame=new tr.model.StackFrame(leafStackFrame,leafStackFrameId,'<self>',undefined/* colorId */);model.addStackFrame(leafStackFrame);}}}else{// New format:
-//   Undefined index        -> (invalid value)
-//   Defined index for /A/B -> /A/B
-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;}// Get the leaf stack frame corresponding to the provided index.
-var leafStackFrameId=idPrefix+leafStackFrameIndex;if(leafStackFrameIndex===''){leafStackFrame=undefined/* root */;}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/* total over all types */;}else if(objectTypeNameMap===undefined){// This can only happen when the old format is used.
-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);}// Throw away heap dumps with no entries. This can happen if all raw
-// entries in the trace are skipped for some reason (e.g. invalid leaf
-// stack frame ID).
-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 all process memory dump events have the same level of detail (for
-// the particular 'process' or 'global' scope), return true.
-if(!(scope in levelsOfDetail)||level===levelsOfDetail[scope]){levelsOfDetail[scope]=level;return true;}// If the process memory dump events have different levels of detail (for
-// the particular 'process' or 'global' scope), use the highest level and
-// return false.
-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;// Construct the MemoryAllocatorDump objects without parent links
-// and add them to the processMemoryAllocatorDumpsByName and
-// globalMemoryAllocatorDumpsByName indices appropriately.
-for(var fullName in rawAllocatorDumps){var rawAllocatorDump=rawAllocatorDumps[fullName];// Every memory allocator dump should have a GUID. If not, then
-// it cannot be associated with any edges.
-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.'});}// A memory allocator dump can have optional flags.
-var flags=rawAllocatorDump.flags||0;var isWeakDump=!!(flags&WEAK_MEMORY_ALLOCATOR_DUMP_FLAG);// Determine if this is a global memory allocator dump (check if
-// it's prefixed with 'global/').
-var containerMemoryDump;var dstIndex;if(fullName.startsWith(GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX)){// Global memory allocator dump.
-fullName=fullName.substring(GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX.length);containerMemoryDump=globalMemoryDump;dstIndex=globalMemoryAllocatorDumpsByFullName;}else{// Process memory allocator dump.
-containerMemoryDump=processMemoryDump;dstIndex=processMemoryAllocatorDumpsByFullName;}// Construct or retrieve a memory allocator dump with the provided
-// GUID.
-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{// A memory allocator dump with this GUID has already been
-// dumped (so we will only add new attributes). Check that it
-// belonged to the same process or was also global.
-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;}// Check that the names of the memory allocator dumps match.
-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){// A MemoryAllocatorDump is non-weak if at least one process dumped
-// it without WEAK_MEMORY_ALLOCATOR_DUMP_FLAG.
-allocatorDump.weak=false;}}// Add all new attributes to the memory allocator dump.
-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];// This is a loop because we might need to build implicit
-// ancestors in case they were not present in the trace.
-while(true){var lastSlashIndex=fullName.lastIndexOf('/');if(lastSlashIndex===-1){// If the dump is a root, add it to the top-level
-// rootAllocatorDumps list.
-rootAllocatorDumps.push(allocatorDump);break;}// If the dump is not a root, find its parent.
-var parentFullName=fullName.substring(0,lastSlashIndex);var parentAllocatorDump=memoryAllocatorDumpsByFullName[parentFullName];// If the parent dump does not exist yet, we build an implicit
-// one and continue up the ancestor chain.
-var parentAlreadyExisted=true;if(parentAllocatorDump===undefined){parentAlreadyExisted=false;parentAllocatorDump=new tr.model.MemoryAllocatorDump(allocatorDump.containerMemoryDump,parentFullName);if(allocatorDump.weak!==false){// If we are inferring a parent dump (e.g. 'root/parent') of a
-// current dump (e.g. 'root/parent/current') which is weak (or
-// was also inferred and we don't know yet whether it's weak or
-// not), then we clear the weak flag on the parent dump because
-// we don't know yet whether it should be weak or non-weak:
-//
-//   * We can't mark the parent as non-weak straightaway because
-//     the parent might have no non-weak descendants (in which
-//     case we want the inferred parent to be weak, so that it
-//     would be later removed like the current dump).
-//   * We can't mark the parent as weak immediately either. If we
-//     did and later encounter a non-weak child of the parent
-//     (e.g. 'root/parent/another_child'), then we couldn't
-//     retroactively mark the inferred parent dump as non-weak
-//     because we couldn't tell whether the parent dump was
-//     dumped in the trace as weak (in which case it should stay
-//     weak and be subsequently removed) or whether it was
-//     inferred as weak (in which case it should be changed to
-//     non-weak).
-//
-// Therefore, we defer marking the inferred parent as
-// weak/non-weak. If an inferred parent dump does not have any
-// non-weak child, it will be marked as weak at the end of this
-// method.
-//
-// Note that this should not be confused with the recursive
-// propagation of the weak flag from parent dumps to their
-// children and from owned dumps to their owners, which is
-// performed in GlobalMemoryDump.prototype.removeWeakDumps().
-parentAllocatorDump.weak=undefined;}memoryAllocatorDumpsByFullName[parentFullName]=parentAllocatorDump;}// Setup the parent <-> children relationships
-allocatorDump.parent=parentAllocatorDump;parentAllocatorDump.children.push(allocatorDump);// If the parent already existed, then its ancestors were/will be
-// constructed in another iteration of the forEach loop.
-if(parentAlreadyExisted){if(!allocatorDump.weak){// If the current dump is non-weak, then we must ensure that all
-// its inferred ancestors are also non-weak.
-while(parentAllocatorDump!==undefined&&parentAllocatorDump.weak===undefined){parentAllocatorDump.weak=false;parentAllocatorDump=parentAllocatorDump.parent;}}break;}fullName=parentFullName;allocatorDump=parentAllocatorDump;}}// All inferred ancestor dumps that have a non-weak child have already
-// been marked as non-weak. We now mark the rest as weak.
-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+').'});}}}}},/**
-     * Converts |ts| (in microseconds) to a timestamp in the model clock domain
-     * (in milliseconds).
-     */toModelTimeFromUs_:function(ts){if(!this.toModelTime_){this.toModelTime_=this.model_.clockSyncManager.getModelTimeTransformer(this.clockDomainId_);}return this.toModelTime_(tr.b.Unit.timestampFromUs(ts));},/**
-     * Converts |ts| (in microseconds) to a timestamp in the model clock domain
-     * (in milliseconds). If |ts| is undefined, undefined is returned.
-     */maybeToModelTimeFromUs_:function(ts){if(ts===undefined)return undefined;return this.toModelTimeFromUs_(ts);}};tr.importer.Importer.register(TraceEventImporter);return{TraceEventImporter:TraceEventImporter};});
+"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":29,"../../base/color_scheme.js":32,"../../base/range.js":47,"../../base/unit.js":57,"../../base/utils.js":59,"../../importer/context_processor.js":70,"../../importer/importer.js":76,"../../model/comment_box_annotation.js":106,"../../model/constants.js":108,"../../model/container_memory_dump.js":109,"../../model/counter_series.js":112,"../../model/flow_event.js":121,"../../model/global_memory_dump.js":123,"../../model/heap_dump.js":124,"../../model/instant_event.js":130,"../../model/memory_allocator_dump.js":134,"../../model/model.js":135,"../../model/process_memory_dump.js":145,"../../model/rect_annotation.js":146,"../../model/scoped_id.js":148,"../../model/slice_group.js":152,"../../model/vm_region.js":168,"../../model/x_marker_annotation.js":169,"../../value/numeric.js":190,"./trace_code_entry.js":64,"./trace_code_map.js":65,"./v8/codemap.js":67}],67:[function(require,module,exports){
+},{"../../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";
-/**
-Copyright (c) 2012 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.
-**/
-
-require("./splaytree.js");
-
-'use strict';
-
-/**
- * @fileoverview Map addresses to dynamically created functions.
- */
-global.tr.exportTo('tr.e.importer.v8', function () {
-  /**
-   * Constructs a mapper that maps addresses into code entries.
-   *
-   * @constructor
-   */
-  function CodeMap() {
-    /**
-     * Dynamic code entries. Used for JIT compiled code.
-     */
-    this.dynamics_ = new tr.e.importer.v8.SplayTree();
-
-    /**
-     * Name generator for entries having duplicate names.
-     */
-    this.dynamicsNameGen_ = new tr.e.importer.v8.CodeMap.NameGenerator();
-
-    /**
-     * Static code entries. Used for statically compiled code.
-     */
-    this.statics_ = new tr.e.importer.v8.SplayTree();
-
-    /**
-     * Libraries entries. Used for the whole static code libraries.
-     */
-    this.libraries_ = new tr.e.importer.v8.SplayTree();
-
-    /**
-     * Map of memory pages occupied with static code.
-     */
-    this.pages_ = [];
-  }
-
-  /**
-   * The number of alignment bits in a page address.
-   */
-  CodeMap.PAGE_ALIGNMENT = 12;
-
-  /**
-   * Page size in bytes.
-   */
-  CodeMap.PAGE_SIZE = 1 << CodeMap.PAGE_ALIGNMENT;
-
-  /**
-   * Adds a dynamic (i.e. moveable and discardable) code entry.
-   *
-   * @param {number} start The starting address.
-   * @param {CodeMap.CodeEntry} codeEntry Code entry object.
-   */
-  CodeMap.prototype.addCode = function (start, codeEntry) {
-    this.deleteAllCoveredNodes_(this.dynamics_, start, start + codeEntry.size);
-    this.dynamics_.insert(start, codeEntry);
-  };
-
-  /**
-   * Moves a dynamic code entry. Throws an exception if there is no dynamic
-   * code entry with the specified starting address.
-   *
-   * @param {number} from The starting address of the entry being moved.
-   * @param {number} to The destination address.
-   */
-  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);
-  };
-
-  /**
-   * Discards a dynamic code entry. Throws an exception if there is no dynamic
-   * code entry with the specified starting address.
-   *
-   * @param {number} start The starting address of the entry being deleted.
-   */
-  CodeMap.prototype.deleteCode = function (start) {
-    var removedNode = this.dynamics_.remove(start);
-  };
-
-  /**
-   * Adds a library entry.
-   *
-   * @param {number} start The starting address.
-   * @param {CodeMap.CodeEntry} codeEntry Code entry object.
-   */
-  CodeMap.prototype.addLibrary = function (start, codeEntry) {
-    this.markPages_(start, start + codeEntry.size);
-    this.libraries_.insert(start, codeEntry);
-  };
-
-  /**
-   * Adds a static code entry.
-   *
-   * @param {number} start The starting address.
-   * @param {CodeMap.CodeEntry} codeEntry Code entry object.
-   */
-  CodeMap.prototype.addStaticCode = function (start, codeEntry) {
-    this.statics_.insert(start, codeEntry);
-  };
-
-  /**
-   * @private
-   */
-  CodeMap.prototype.markPages_ = function (start, end) {
-    for (var addr = start; addr <= end; addr += CodeMap.PAGE_SIZE) {
-      this.pages_[addr >>> CodeMap.PAGE_ALIGNMENT] = 1;
-    }
-  };
-
-  /**
-   * @private
-   */
-  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]);
-  };
-
-  /**
-   * @private
-   */
-  CodeMap.prototype.isAddressBelongsTo_ = function (addr, node) {
-    return addr >= node.key && addr < node.key + node.value.size;
-  };
-
-  /**
-   * @private
-   */
-  CodeMap.prototype.findInTree_ = function (tree, addr) {
-    var node = tree.findGreatestLessThan(addr);
-    return node && this.isAddressBelongsTo_(addr, node) ? node.value : null;
-  };
-
-  /**
-   * Finds a code entry that contains the specified address in static libraries.
-   *
-   * @param {number} addr Address.
-   */
-  CodeMap.prototype.findEntryInLibraries = function (addr) {
-    var pageAddr = addr >>> CodeMap.PAGE_ALIGNMENT;
-    if (pageAddr in this.pages_) return this.findInTree_(this.libraries_, addr);
-    return undefined;
-  };
-
-  /**
-   * Finds a code entry that contains the specified address. Both static and
-   * dynamic code entries are considered.
-   *
-   * @param {number} addr Address.
-   */
-  CodeMap.prototype.findEntry = function (addr) {
-    var pageAddr = addr >>> CodeMap.PAGE_ALIGNMENT;
-    if (pageAddr in this.pages_) {
-      // Static code entries can contain "holes" of unnamed code.
-      // In this case, the whole library is assigned to this address.
-      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;
-      // Dedupe entry name.
-      if (!dynaEntry.nameUpdated_) {
-        dynaEntry.name = this.dynamicsNameGen_.getName(dynaEntry.name);
-        dynaEntry.nameUpdated_ = true;
-      }
-      return dynaEntry;
-    }
-    return null;
-  };
-
-  /**
-   * Returns a dynamic code entry using its starting address.
-   *
-   * @param {number} addr Address.
-   */
-  CodeMap.prototype.findDynamicEntryByStartAddress = function (addr) {
-    var node = this.dynamics_.find(addr);
-    return node ? node.value : null;
-  };
-
-  /**
-   * Returns an array of all dynamic code entries.
-   */
-  CodeMap.prototype.getAllDynamicEntries = function () {
-    return this.dynamics_.exportValues();
-  };
-
-  /**
-   * Returns an array of pairs of all dynamic code entries and their addresses.
-   */
-  CodeMap.prototype.getAllDynamicEntriesWithAddresses = function () {
-    return this.dynamics_.exportKeysAndValues();
-  };
-
-  /**
-   * Returns an array of all static code entries.
-   */
-  CodeMap.prototype.getAllStaticEntries = function () {
-    return this.statics_.exportValues();
-  };
-
-  /**
-   * Returns an array of all libraries entries.
-   */
-  CodeMap.prototype.getAllLibrariesEntries = function () {
-    return this.libraries_.exportValues();
-  };
-
-  /**
-   * Enum for code state regarding its dynamic optimization.
-   *
-   * @enum {number}
-   */
-  CodeMap.CodeState = {
-    COMPILED: 0,
-    OPTIMIZABLE: 1,
-    OPTIMIZED: 2
-  };
-
-  /**
-   * Creates a code entry object.
-   *
-   * @param {number} size Code entry size in bytes.
-   * @param {string=} opt_name Code entry name.
-   * @constructor
-   */
-  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'
-  };
-
-  /**
-   * Creates a dynamic code entry.
-   *
-   * @param {number} size Code size.
-   * @param {string} type Code type.
-   * @param {CodeMap.FunctionEntry} func Shared function entry.
-   * @param {CodeMap.CodeState} state Code optimization state.
-   * @constructor
-   */
-  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,
-
-    /**
-     * Returns node name.
-     */
-    get name() {
-      return CodeMap.DynamicFuncCodeEntry.STATE_PREFIX[this.state] + this.func.name;
-    },
-
-    set name(value) {
-      this.name_ = value;
-    },
-
-    /**
-     * Returns raw node name (without type decoration).
-     */
-    getRawName: function () {
-      return this.func.getName();
-    },
-
-    isJSFunction: function () {
-      return true;
-    },
-
-    toString: function () {
-      return this.type + ': ' + this.name + ': ' + this.size.toString(16);
-    }
-  };
-
-  /**
-   * Creates a shared function object entry.
-   *
-   * @param {string} name Function name.
-   * @constructor
-   */
-  CodeMap.FunctionEntry = function (name) {
-    CodeMap.CodeEntry.call(this, 0, name);
-  };
-
-  CodeMap.FunctionEntry.prototype = {
-    __proto__: CodeMap.CodeEntry.prototype,
-
-    /**
-     * Returns node name.
-     */
-    get name() {
-      var name = this.name_;
-      if (name.length == 0) {
-        name = '<anonymous>';
-      } else if (name.charAt(0) == ' ') {
-        // An anonymous function with location: " aaa.js:10".
-        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
-  };
-});
+"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":68}],68:[function(require,module,exports){
+},{"./splaytree.js":74}],74:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2012 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.
-**/
-
-require("../../../base/base.js");
-
-'use strict';
-
-/**
- * @fileoverview Splay tree used by CodeMap.
- */
-global.tr.exportTo('tr.e.importer.v8', function () {
-  /**
-   * Constructs a Splay tree.  A splay tree is a self-balancing binary
-   * search tree with the additional property that recently accessed
-   * elements are quick to access again. It performs basic operations
-   * such as insertion, look-up and removal in O(log(n)) amortized time.
-   *
-   * @constructor
-   */
-  function SplayTree() {};
-
-  /**
-   * Pointer to the root node of the tree.
-   *
-   * @type {SplayTree.Node}
-   * @private
-   */
-  SplayTree.prototype.root_ = null;
-
-  /**
-   * @return {boolean} Whether the tree is empty.
-   */
-  SplayTree.prototype.isEmpty = function () {
-    return !this.root_;
-  };
-
-  /**
-   * Inserts a node into the tree with the specified key and value if
-   * the tree does not already contain a node with the specified key. If
-   * the value is inserted, it becomes the root of the tree.
-   *
-   * @param {number} key Key to insert into the tree.
-   * @param {*} value Value to insert into the tree.
-   */
-  SplayTree.prototype.insert = function (key, value) {
-    if (this.isEmpty()) {
-      this.root_ = new SplayTree.Node(key, value);
-      return;
-    }
-    // Splay on the key to move the last node on the search path for
-    // the key to the root of the tree.
-    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;
-  };
-
-  /**
-   * Removes a node with the specified key from the tree if the tree
-   * contains a node with this key. The removed node is returned. If the
-   * key is not found, an exception is thrown.
-   *
-   * @param {number} key Key to find and remove from the tree.
-   * @return {SplayTree.Node} The removed 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;
-      // Splay to make sure that the new root has an empty right child.
-      this.splay_(key);
-      // Insert the original right child as the right child of the new
-      // root.
-      this.root_.right = right;
-    }
-    return removed;
-  };
-
-  /**
-   * Returns the node having the specified key or null if the tree doesn't
-   * contain a node with the specified key.
-   *
-   *
-   * @param {number} key Key to find in the tree.
-   * @return {SplayTree.Node} Node having the specified key.
-   */
-  SplayTree.prototype.find = function (key) {
-    if (this.isEmpty()) {
-      return null;
-    }
-    this.splay_(key);
-    return this.root_.key == key ? this.root_ : null;
-  };
-
-  /**
-   * @return {SplayTree.Node} Node having the minimum key value.
-   */
-  SplayTree.prototype.findMin = function () {
-    if (this.isEmpty()) {
-      return null;
-    }
-    var current = this.root_;
-    while (current.left) {
-      current = current.left;
-    }
-    return current;
-  };
-
-  /**
-   * @return {SplayTree.Node} Node having the maximum key value.
-   */
-  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;
-  };
-
-  /**
-   * @return {SplayTree.Node} Node having the maximum key value that
-   *     is less or equal to the specified key value.
-   */
-  SplayTree.prototype.findGreatestLessThan = function (key) {
-    if (this.isEmpty()) {
-      return null;
-    }
-    // Splay on the key to move the node with the given key or the last
-    // node on the search path to the top of the tree.
-    this.splay_(key);
-    // Now the result is either the root node or the greatest node in
-    // the left subtree.
-    if (this.root_.key <= key) {
-      return this.root_;
-    } else if (this.root_.left) {
-      return this.findMax(this.root_.left);
-    } else {
-      return null;
-    }
-  };
-
-  /**
-   * @return {Array<*>} An array containing all the values of tree's nodes
-   * paired with keys.
-   *
-   */
-  SplayTree.prototype.exportKeysAndValues = function () {
-    var result = [];
-    this.traverse_(function (node) {
-      result.push([node.key, node.value]);
-    });
-    return result;
-  };
-
-  /**
-   * @return {Array<*>} An array containing all the values of tree's nodes.
-   */
-  SplayTree.prototype.exportValues = function () {
-    var result = [];
-    this.traverse_(function (node) {
-      result.push(node.value);
-    });
-    return result;
-  };
-
-  /**
-   * Perform the splay operation for the given key. Moves the node with
-   * the given key to the top of the tree.  If no node has the given
-   * key, the last node on the search path is moved to the top of the
-   * tree. This is the simplified top-down splaying algorithm from:
-   * "Self-adjusting Binary Search Trees" by Sleator and Tarjan
-   *
-   * @param {number} key Key to splay the tree on.
-   * @private
-   */
-  SplayTree.prototype.splay_ = function (key) {
-    if (this.isEmpty()) {
-      return;
-    }
-    // Create a dummy node.  The use of the dummy node is a bit
-    // counter-intuitive: The right child of the dummy node will hold
-    // the L tree of the algorithm.  The left child of the dummy node
-    // will hold the R tree of the algorithm.  Using a dummy node, left
-    // and right will always be nodes and we avoid special cases.
-    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) {
-          // Rotate right.
-          var tmp = current.left;
-          current.left = tmp.right;
-          tmp.right = current;
-          current = tmp;
-          if (!current.left) {
-            break;
-          }
-        }
-        // Link right.
-        right.left = current;
-        right = current;
-        current = current.left;
-      } else if (key > current.key) {
-        if (!current.right) {
-          break;
-        }
-        if (key > current.right.key) {
-          // Rotate left.
-          var tmp = current.right;
-          current.right = tmp.left;
-          tmp.left = current;
-          current = tmp;
-          if (!current.right) {
-            break;
-          }
-        }
-        // Link left.
-        left.right = current;
-        left = current;
-        current = current.right;
-      } else {
-        break;
-      }
-    }
-    // Assemble.
-    left.right = current.left;
-    right.left = current.right;
-    current.left = dummy.right;
-    current.right = dummy.left;
-    this.root_ = current;
-  };
-
-  /**
-   * Performs a preorder traversal of the tree.
-   *
-   * @param {function(SplayTree.Node)} f Visitor function.
-   * @private
-   */
-  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);
-    }
-  };
-
-  /**
-   * Constructs a Splay tree node.
-   *
-   * @param {number} key Key.
-   * @param {*} value Value.
-   */
-  SplayTree.Node = function (key, value) {
-    this.key = key;
-    this.value = value;
-  };
-
-  /**
-   * @type {SplayTree.Node}
-   */
-  SplayTree.Node.prototype.left = null;
-
-  /**
-   * @type {SplayTree.Node}
-   */
-  SplayTree.Node.prototype.right = null;
-
-  return {
-    SplayTree: SplayTree
-  };
-});
+"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":28}],69:[function(require,module,exports){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-/**
-The lean config is just enough to import uncompressed, trace-event-formatted
-json blobs.
-**/
-
-require("./importer/trace_event_importer.js");
-require("../model/model.js");
-},{"../model/model.js":135,"./importer/trace_event_importer.js":66}],70:[function(require,module,exports){
+},{"../../../base/base.js":34}],75:[function(require,module,exports){
+"use strict";require("./importer/trace_event_importer.js");require("../model/model.js");
+},{"../model/model.js":141,"./importer/trace_event_importer.js":72}],76:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-require("../base/base.js");
-
-'use strict';
-
-global.tr.exportTo('tr.importer', function () {
-  /**
-   * The context processor consumes context events and maintains a set of
-   * active contexts for a single thread.
-   *
-   * @constructor
-   */
-  function ContextProcessor(model) {
-    this.model_ = model;
-    this.activeContexts_ = [];
-    this.stackPerType_ = {};
-    // Cache of unique context objects.
-    this.contextCache_ = {};
-    // Cache of unique context object sets.
-    this.contextSetCache_ = {};
-    this.cachedEntryForActiveContexts_ = undefined;
-    // All seen context object snapshots.
-    this.seenSnapshots_ = {};
-  };
-
-  ContextProcessor.prototype = {
-    enterContext: function (contextType, scopedId) {
-      var newActiveContexts = [this.getOrCreateContext_(contextType, scopedId)];
-      for (var oldContext of this.activeContexts_) {
-        if (oldContext.type === contextType) {
-          // If a previous context of the same type is active, it is removed
-          // and pushed onto the stack for this type.
-          this.pushContext_(oldContext);
-        } else {
-          // Otherwise the old context is it is still active.
-          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) {
-      // Remove all matching contexts from stacks.
-      tr.b.iterItems(this.stackPerType_, function (contextType, stack) {
-        // Perform in-place filtering instead of Array.prototype.filter to
-        // prevent creating a new array.
-        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;
-      });
-
-      // Remove all matching contexts from active context set.
-      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)) {
-          // If we left this context, remove it from the active set and
-          // restore any previous context of the same type.
-          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() {
-      // Keep a single instance for each unique set of active contexts to
-      // reduce memory usage.
-      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) {
-        // Do not alter unrelated contexts.
-        if (context.snapshot.scope !== scopedId.scope || context.snapshot.idRef !== scopedId.id) return context;
-        // Replace the invalidated context by a deep copy.
-        return {
-          type: context.type,
-          snapshot: {
-            scope: context.snapshot.scope,
-            idRef: context.snapshot.idRef
-          }
-        };
-      });
-      this.seenSnapshots_ = {};
-    }
-  };
-
-  return {
-    ContextProcessor: ContextProcessor
-  };
-});
+"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":28}],71:[function(require,module,exports){
+},{"../base/base.js":34}],77:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/base.js");
-require("./importer.js");
-
-'use strict';
-
-/**
- * @fileoverview Base class for trace data importers.
- */
-global.tr.exportTo('tr.importer', function () {
-  /**
-   * Importer for empty strings and arrays.
-   * @constructor
-   */
-  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
-  };
-});
+"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":28,"./importer.js":76}],72:[function(require,module,exports){
+},{"../base/base.js":34,"./importer.js":82}],78:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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';
-
-  /**
-   * If there's less than this much time between the end of one event and the
-   * start of the next, then they might be merged.
-   * There was not enough thought given to this value, so if you have any slight
-   * reason to change it, then please do so. It might also be good to split this
-   * into multiple values.
-   */
-  var INPUT_MERGE_THRESHOLD_MS = 200;
-  var ANIMATION_MERGE_THRESHOLD_MS = 32; // 2x 60FPS frames
-
-  /**
-   * If two MouseWheel events begin this close together, then they're an
-   * Animation, not two responses.
-   */
-  var MOUSE_WHEEL_THRESHOLD_MS = 40;
-
-  /**
-   * If two MouseMoves are more than this far apart, then they're two Responses,
-   * not Animation.
-   */
-  var MOUSE_MOVE_THRESHOLD_MS = 40;
-
-  // Strings used to name IRs.
-  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';
-
-  // TODO(benjhayden) Find a better home for this.
-  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;
-
-      // TODO(beaudoin): This should never happen but it does. Investigate
-      // the trace linked at in #1567 and remove that when it's fixed.
-      if (isNaN(slice.start) || isNaN(slice.duration) || isNaN(slice.end)) continue;
-
-      inputEvents.push(slice);
-    }
-
-    return inputEvents.sort(compareEvents);
-  }
-
-  function findProtoExpectations(modelHelper, sortedInputEvents) {
-    var protoExpectations = [];
-    // This order is not important. Handlers are independent.
-    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;
-  }
-
-  /**
-   * Every keyboard event is a Response.
-   */
-  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;
-  }
-
-  /**
-   * Some mouse events can be translated directly into Responses.
-   */
-  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;
-  }
-  /**
-   * MouseWheel events are caused either by a physical wheel on a physical
-   * mouse, or by a touch-drag gesture on a track-pad. The physical wheel
-   * causes MouseWheel events that are much more spaced out, and have no
-   * chance of hitting 60fps, so they are each turned into separate Response
-   * IRs. The track-pad causes MouseWheel events that are much closer
-   * together, and are expected to be 60fps, so the first event in a sequence
-   * is turned into a Response, and the rest are merged into an Animation.
-   * NB this threshold uses the two events' start times, unlike
-   * ProtoExpectation.isNear, which compares the end time of the previous event
-   * with the start time of the next.
-   */
-  function handleMouseWheelEvents(modelHelper, sortedInputEvents) {
-    var protoExpectations = [];
-    var currentPE = undefined;
-    var prevEvent_ = undefined;
-    forEventTypesIn(sortedInputEvents, MOUSE_WHEEL_TYPE_NAMES, function (event) {
-      // Switch prevEvent in one place so that we can early-return later.
-      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;
-  }
-
-  /**
-   * Down events followed closely by Up events are click Responses, but the
-   * Response doesn't start until the Up event.
-   *
-   *     RRR
-   * DDD UUU
-   *
-   * If there are any Move events in between a Down and an Up, then the Down
-   * and the first Move are a Response, then the rest of the Moves are an
-   * Animation:
-   *
-   * RRRRRRRAAAAAAAAAAAAAAAAAAAA
-   * DDD MMM MMM MMM MMM MMM UUU
-   */
-  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 {
-            // Responses typically don't start until the mouse up event.
-            // Add this MouseDown to the Response that starts at the MouseUp.
-            mouseDownEvent = event;
-          }
-          break;
-
-        // There may be more than 100ms between the start of the mouse down
-        // and the start of the mouse up. Chrome and the web don't start to
-        // respond until the mouse up. ResponseIRs start deducting comfort
-        // at 100ms duration. If more than that 100ms duration is burned
-        // through while waiting for the user to release the
-        // mouse button, then ResponseIR will unfairly start deducting
-        // comfort before Chrome even has a mouse up to respond to.
-        // It is technically possible for a site to afford one response on
-        // mouse down and another on mouse up, but that is an edge case. The
-        // vast majority of mouse downs are not responses.
-
-        case INPUT_TYPE.MOUSE_MOVE:
-          if (!causedFrame(event)) {
-            // Ignore MouseMoves that do not affect the screen. They are not
-            // part of an interaction record by definition.
-            var pe = new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);
-            pe.pushEvent(event);
-            protoExpectations.push(pe);
-          } else if (!currentPE || !currentPE.isNear(event, MOUSE_MOVE_THRESHOLD_MS)) {
-            // The first MouseMove after a MouseDown or after a while is a
-            // Response.
-            currentPE = new ProtoExpectation(ProtoExpectation.RESPONSE_TYPE, MOUSE_IR_NAME);
-            currentPE.pushEvent(event);
-            if (mouseDownEvent) {
-              currentPE.associatedEvents.push(mouseDownEvent);
-              mouseDownEvent = undefined;
-            }
-            protoExpectations.push(currentPE);
-          } else {
-            // Merge this event into an Animation.
-            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;
-  }
-
-  /**
-   * Solitary Tap events are simple Responses:
-   *
-   * RRR
-   * TTT
-   *
-   * TapDowns are part of Responses.
-   *
-   * RRRRRRR
-   * DDD TTT
-   *
-   * TapCancels are part of Responses, which seems strange. They always go
-   * with scrolls, so they'll probably be merged with scroll Responses.
-   * TapCancels can take a significant amount of time and account for a
-   * significant amount of work, which should be grouped with the scroll IRs
-   * if possible.
-   *
-   * RRRRRRR
-   * DDD CCC
-   **/
-  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 {
-            // Sometimes we get Tap events with no TapDown, sometimes we get
-            // TapDown events. Handle both.
-            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;
-  }
-
-  /**
-   * The PinchBegin and the first PinchUpdate comprise a Response, then the
-   * rest of the PinchUpdates comprise an Animation.
-   *
-   * RRRRRRRAAAAAAAAAAAAAAAAAAAA
-   * BBB UUU UUU UUU UUU UUU EEE
-   */
-  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:
-          // Like ScrollUpdates, the Begin and the first Update constitute a
-          // Response, then the rest of the Updates constitute an Animation
-          // that begins when the Response ends. If the user pauses in the
-          // middle of an extended pinch gesture, then multiple Animations
-          // will be created.
-          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;
-  }
-
-  /**
-   * Flings are defined by 3 types of events: FlingStart, FlingCancel, and the
-   * renderer fling event. Flings do not begin with a Response. Flings end
-   * either at the beginning of a FlingCancel, or at the end of the renderer
-   * fling event.
-   *
-   * AAAAAAAAAAAAAAAAAAAAAAAAAA
-   * SSS
-   *     RRRRRRRRRRRRRRRRRRRRRR
-   *
-   *
-   * AAAAAAAAAAA
-   * SSS        CCC
-   */
-  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);
-            // Set end to an invalid value so that it can be noticed and fixed
-            // later.
-            currentPE.end = 0;
-            protoExpectations.push(currentPE);
-          }
-          break;
-
-        case INPUT_TYPE.FLING_CANCEL:
-          if (currentPE) {
-            currentPE.pushEvent(event);
-            // FlingCancel events start when TouchStart events start, which is
-            // typically when a Response starts. FlingCancel events end when
-            // chrome acknowledges them, not when they update the screen. So
-            // there might be one more frame during the FlingCancel, after
-            // this Animation ends. That won't affect the scoring algorithms,
-            // and it will make the IRs look more correct if they don't
-            // overlap unnecessarily.
-            currentPE.end = event.start;
-            currentPE = undefined;
-          } else {
-            var pe = new ProtoExpectation(ProtoExpectation.IGNORED_TYPE);
-            pe.pushEvent(event);
-            protoExpectations.push(pe);
-          }
-          break;
-      }
-    });
-    // If there was neither a FLING_CANCEL nor a renderer fling after the
-    // FLING_START, then assume that it ends at the end of the model, so set
-    // the end of currentPE to the end of the model.
-    if (currentPE && !currentPE.end) currentPE.end = modelHelper.model.bounds.max;
-    return protoExpectations;
-  }
-
-  /**
-   * The TouchStart and the first TouchMove comprise a Response, then the
-   * rest of the TouchMoves comprise an Animation.
-   *
-   * RRRRRRRAAAAAAAAAAAAAAAAAAAA
-   * SSS MMM MMM MMM MMM MMM EEE
-   *
-   * If there are no TouchMove events in between a TouchStart and a TouchEnd,
-   * then it's just a Response.
-   *
-   * RRRRRRR
-   * SSS EEE
-   */
-  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) {
-            // NB: currentPE will probably be merged with something from
-            // handlePinchEvents(). Multiple TouchStart events without an
-            // intervening TouchEnd logically implies that multiple fingers
-            // are on the screen, so this is probably a pinch gesture.
-            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;
-          }
-
-          // Like Scrolls and Pinches, the Response is defined to be the
-          // TouchStart plus the first TouchMove, then the rest of the
-          // TouchMoves constitute an Animation.
-          if (sawFirstMove && currentPE.irType === ProtoExpectation.RESPONSE_TYPE || !currentPE.isNear(event, INPUT_MERGE_THRESHOLD_MS)) {
-            // If there's already a touchmove in the currentPE or it's not
-            // near event, then finish it and start a new animation.
-            var prevEnd = currentPE.end;
-            currentPE = new ProtoExpectation(ProtoExpectation.ANIMATION_TYPE, TOUCH_IR_NAME);
-            currentPE.pushEvent(event);
-            // It's possible for there to be a gap between TouchMoves, but
-            // that doesn't mean that there should be an Idle IR there.
-            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;
-  }
-
-  /**
-   * The first ScrollBegin and the first ScrollUpdate comprise a Response,
-   * then the rest comprise an Animation.
-   *
-   * RRRRRRRAAAAAAAAAAAAAAAAAAAA
-   * BBB UUU UUU UUU UUU UUU EEE
-   */
-  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:
-          // Always begin a new PE even if there already is one, unlike
-          // PinchBegin.
-          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 {
-            // ScrollUpdate without ScrollBegin.
-            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;
-  }
-
-  /**
-   * Returns proto expectations for video animation events.
-   *
-   * Video animations represent video playback, and are based on
-   * VideoPlayback async events (going from the VideoFrameCompositor::Start
-   * to VideoFrameCompositor::Stop calls)
-   */
-  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;
-  }
-
-  /**
-   * CSS Animations are merged into AnimationExpectations when they intersect.
-   */
-  function handleCSSAnimations(modelHelper, sortedInputEvents) {
-    // First find all the top-level CSS Animation async events.
-    var animationEvents = modelHelper.browserHelper.getAllAsyncSlicesMatching(function (event) {
-      return event.title === CSS_ANIMATION_TITLE && event.isTopLevel && event.duration > 0;
-    });
-
-    // Time ranges where animations are actually running will be collected here.
-    // Each element will contain {min, max, animation}.
-    var animationRanges = [];
-
-    // This helper function will be called when a time range is found
-    // during which the animation is actually running.
-    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 {
-        // Now run a state machine over the animation's subSlices, which
-        // indicate the animations running/paused/finished states, in order to
-        // find ranges where the animation was actually running.
-        var start = undefined;
-        animation.subSlices.forEach(function (sub) {
-          if (sub.args.data.state === 'running' && start === undefined) {
-            // It's possible for the state to alternate between running and
-            // pending, but the animation is still running in that case,
-            // so only set start if the state is changing from one of the halted
-            // states.
-            start = sub.start;
-          } else if (sub.args.data.state === 'paused' || sub.args.data.state === 'idle' || sub.args.data.state === 'finished') {
-            if (start === undefined) {
-              // An animation was already running when the trace started.
-              // (Actually, it's possible that the animation was in the 'idle'
-              // state when tracing started, but that should be rare, and will
-              // be fixed when async events are buffered.)
-              // http: //crbug.com/565627
-              start = modelHelper.model.bounds.min;
-            }
-
-            pushAnimationRange(start, sub.start, animation);
-            start = undefined;
-          }
-        });
-
-        // An animation was still running when the
-        // top-level animation event ended.
-        if (start !== undefined) pushAnimationRange(start, animation.end, animation);
-      }
-    });
-
-    // Now we have a set of time ranges when css animations were actually
-    // running.
-    // Leave merging intersecting animations to mergeIntersectingAnimations(),
-    // after findFrameEventsForAnimations removes frame-less animations.
-
-    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;
-    });
-  }
-
-  /**
-   * Get all the events (prepareMailbox and serviceScriptedAnimations)
-   * relevant to WebGL. Note that modelHelper is the helper object containing
-   * the model, and mailboxEvents and animationEvents are arrays where the
-   * events are being pushed into (DrawingBuffer::prepareMailbox events go
-   * into mailboxEvents; PageAnimator::serviceScriptedAnimations events go
-   * into animationEvents). The function does not return anything but
-   * modifies mailboxEvents and animationEvents.
-   */
-  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);
-    }
-  }
-
-  /**
-   * Returns a list of events in mailboxEvents that have an event in
-   * animationEvents close by (within ANIMATION_MERGE_THRESHOLD_MS).
-   */
-  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 = [];
-
-    // We iterate through the mailboxEvents. With each event, we check if
-    // there is a animationEvent near it, and if so, add it to the result.
-    for (var event of mailboxEvents) {
-      // If the current animationEvent is too far before the mailboxEvent,
-      // we advance until we get to the next animationEvent that is not too
-      // far before the animationEvent.
-      while (animationEvent && animationEvent.start < event.start - ANIMATION_MERGE_THRESHOLD_MS) animationEvent = animationIterator.next().value;
-
-      // If there aren't any more animationEvents, then that means all the
-      // remaining mailboxEvents are too far after the animationEvents, so
-      // we can quit now.
-      if (!animationEvent) break;
-
-      // If there's a animationEvent close to the mailboxEvent, then we push
-      // the current mailboxEvent onto the stack.
-      if (animationEvent.start < event.start + ANIMATION_MERGE_THRESHOLD_MS) filteredEvents.push(event);
-    }
-    return filteredEvents;
-  }
-
-  /**
-   * Merge consecutive mailbox events into a ProtoExpectation. Note: Only
-   * the drawingBuffer::prepareMailbox events will end up in the
-   * associatedEvents. The PageAnimator::serviceScriptedAnimations events
-   * will not end up in the associatedEvents.
-   */
-  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;
-  }
-
-  // WebGL animations are identified by the DrawingBuffer::prepareMailbox
-  // and PageAnimator::serviceScriptedAnimations events (one of each per frame)
-  // and consecutive frames are merged into the same animation.
-  function handleWebGLAnimations(modelHelper, sortedInputEvents) {
-    // Get the prepareMailbox and scriptedAnimation events.
-    var prepareMailboxEvents = [];
-    var scriptedAnimationEvents = [];
-
-    findWebGLEvents(modelHelper, prepareMailboxEvents, scriptedAnimationEvents);
-    var webGLMailboxEvents = findMailboxEventsNearAnimationEvents(prepareMailboxEvents, scriptedAnimationEvents);
-
-    return createProtoExpectationsFromMailboxEvents(webGLMailboxEvents);
-  }
-
-  function postProcessProtoExpectations(modelHelper, protoExpectations) {
-    // protoExpectations is input only. Returns a modified set of
-    // ProtoExpectations.  The order is important.
-    protoExpectations = findFrameEventsForAnimations(modelHelper, protoExpectations);
-    protoExpectations = mergeIntersectingResponses(protoExpectations);
-    protoExpectations = mergeIntersectingAnimations(protoExpectations);
-    protoExpectations = fixResponseAnimationStarts(protoExpectations);
-    protoExpectations = fixTapResponseTouchAnimations(protoExpectations);
-    return protoExpectations;
-  }
-
-  /**
-   * TouchStarts happen at the same time as ScrollBegins.
-   * It's easier to let multiple handlers create multiple overlapping
-   * Responses and then merge them, rather than make the handlers aware of the
-   * other handlers' PEs.
-   *
-   * For example:
-   * RR
-   *  RRR  -> RRRRR
-   *    RR
-   *
-   * protoExpectations is input only.
-   * Returns a modified set of ProtoExpectations.
-   */
-  function mergeIntersectingResponses(protoExpectations) {
-    var newPEs = [];
-    while (protoExpectations.length) {
-      var pe = protoExpectations.shift();
-      newPEs.push(pe);
-
-      // Only consider Responses for now.
-      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;
-
-        // Don't merge together Responses of the same type.
-        // If handleTouchEvents wanted two of its Responses to be merged, then
-        // it would have made them that way to begin with.
-        var typeNames = pe.associatedEvents.map(function (event) {
-          return event.typeName;
-        });
-        if (otherPE.containsTypeNames(typeNames)) continue;
-
-        pe.merge(otherPE);
-        protoExpectations.splice(i, 1);
-
-        // Don't skip the next otherPE!
-        --i;
-      }
-    }
-    return newPEs;
-  }
-
-  /**
-   * An animation is simply an expectation of 60fps between start and end.
-   * If two animations overlap, then merge them.
-   *
-   * For example:
-   * AA
-   *  AAA  -> AAAAA
-   *    AA
-   *
-   * protoExpectations is input only.
-   * Returns a modified set of ProtoExpectations.
-   */
-  function mergeIntersectingAnimations(protoExpectations) {
-    var newPEs = [];
-    while (protoExpectations.length) {
-      var pe = protoExpectations.shift();
-      newPEs.push(pe);
-
-      // Only consider Animations for now.
-      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;
-
-        // Don't merge CSS Animations with any other types.
-        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;
-        }
-
-        // Don't merge Fling Animations with any other types.
-        if (isFling !== otherPE.containsTypeNames([INPUT_TYPE.FLING_START])) continue;
-
-        // Don't merge Video Animations with any other types.
-        if (isVideo !== otherPE.containsTypeNames([VIDEO_IR_NAME])) continue;
-
-        pe.merge(otherPE);
-        protoExpectations.splice(i, 1);
-        // Don't skip the next otherPE!
-        --i;
-      }
-    }
-    return newPEs;
-  }
-
-  /**
-   * The ends of responses frequently overlap the starts of animations.
-   * Fix the animations to reflect the fact that the user can only start to
-   * expect 60fps after the response.
-   *
-   * For example:
-   * RRR   -> RRRAA
-   *  AAAA
-   *
-   * protoExpectations is input only.
-   * Returns a modified set of ProtoExpectations.
-   */
-  function fixResponseAnimationStarts(protoExpectations) {
-    protoExpectations.forEach(function (ape) {
-      // Only consider animations for now.
-      if (ape.irType !== ProtoExpectation.ANIMATION_TYPE) return;
-
-      protoExpectations.forEach(function (rpe) {
-        // Only consider responses for now.
-        if (rpe.irType !== ProtoExpectation.RESPONSE_TYPE) return;
-
-        // Only consider responses that end during the animation.
-        if (!ape.containsTimestampInclusive(rpe.end)) return;
-
-        // Ignore Responses that are entirely contained by the animation.
-        if (ape.containsTimestampInclusive(rpe.start)) return;
-
-        // Move the animation start to the response end.
-        ape.start = rpe.end;
-      });
-    });
-    return protoExpectations;
-  }
-
-  /**
-   * Merge Tap Responses that overlap Touch-only Animations.
-   * https: *github.com/catapult-project/catapult/issues/1431
-   */
-  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);
-
-      // protoExpectations are sorted by start time, and we don't know whether
-      // the Tap Response or the Touch Animation will be first
-      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 might be the Touch Animation, but the merged ProtoExpectation
-        // should be a Response.
-        pe.irType = ProtoExpectation.RESPONSE_TYPE;
-
-        pe.merge(otherPE);
-        protoExpectations.splice(i, 1);
-        // Don't skip the next otherPE!
-        --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 = [];
-      // TODO(benjhayden): Use frame blame contexts here.
-      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 a tree falls in a forest...
-      // If there were not actually any frames while the animation was
-      // running, then it wasn't really an animation, now, was it?
-      // Philosophy aside, the system_health Animation metrics fail hard if
-      // there are no frames in an AnimationExpectation.
-      // Since WebGL animations don't generate this type of frame event,
-      // don't remove them if it's a WebGL animation.
-      // TODO(alexandermont): Identify what the correct frame event to
-      // use here is.
-      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;
-  }
-
-  /**
-   * Check that none of the handlers accidentally ignored an input event.
-   */
-  function checkAllInputEventsHandled(sortedInputEvents, protoExpectations) {
-    var handledEvents = [];
-    protoExpectations.forEach(function (protoExpectation) {
-      protoExpectation.associatedEvents.forEach(function (event) {
-        // Ignore CSS Animations that might have multiple active ranges.
-        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));
-      }
-    });
-  }
-
-  /**
-   * Find ProtoExpectations, post-process them, convert them to real IRs.
-   */
-  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
-  };
-});
+"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":48,"../extras/chrome/cc/input_latency_async_slice.js":62,"./proto_expectation.js":77}],73:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 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.
-**/
-
-require("../model/user_model/load_expectation.js");
-
-'use strict';
-
-global.tr.exportTo('tr.importer', function () {
-  // This global instant event marks the start of a navigation.
-  var NAVIGATION_START = 'NavigationTiming navigationStart';
-
-  // This render-process instant event marks the first contentful paint in a
-  // main frame.
-  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;
-    // TODO(alexandermont): What's supposed to happen if there are two
-    // NAVIGATION_STARTs with no FIRST_CONTENTFUL_PAINT_TITLE between
-    // them? Are you supposed to just "lose" the first NAVIGATION_START,
-    // like what's happening now?
-    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 the trace ended between navigation start and first contentful paint,
-    // then make a LoadExpectation that ends at the end of the trace.
-    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
-  };
-});
+"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":163}],74:[function(require,module,exports){
+},{"../model/user_model/load_expectation.js":169}],80:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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);
-  }
-
-  // If a thread contains a typical initialization slice, then the first event
-  // on that thread is a startup event.
-  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();
-  }
-
-  // Match every event in |openingEvents| to the first following event from
-  // |closingEvents| and return an array containing a load interaction record
-  // for each pair.
-  function findStartupExpectations(modelHelper) {
-    var openingEvents = getStartupEvents(modelHelper);
-    var closingEvents = getAllFrameEvents(modelHelper);
-    var startups = [];
-    openingEvents.forEach(function (openingEvent) {
-      closingEvents.forEach(function (closingEvent) {
-        // Ignore opening event that already have a closing event.
-        if (openingEvent.closingEvent) return;
-
-        // Ignore closing events that already belong to an opening event.
-        if (closingEvent.openingEvent) return;
-
-        // Ignore closing events before |openingEvent|.
-        if (closingEvent.start <= openingEvent.start) return;
-
-        // Ignore events from different threads.
-        if (openingEvent.parentContainer.parent.pid !== closingEvent.parentContainer.parent.pid) return;
-
-        // This is the first closing event for this opening event, record it.
-        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
-  };
-});
+"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":165}],75:[function(require,module,exports){
+},{"../model/user_model/startup_expectation.js":171}],81:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-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.
-**/
-
-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;
-
-    // Callback called after
-    // importers run in which more data can be added to the model, before it is
-    // finalized.
-    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.');
-
-    // TODO(dsinclair): Check the model is empty.
-
-    this.importing_ = false;
-    this.importOptions_ = opt_options || new ImportOptions();
-
-    this.model_ = model;
-    this.model_.importOptions = this.importOptions_;
-  }
-
-  Import.prototype = {
-    __proto__: Object.prototype,
-
-    /**
-     * Imports the provided traces into the model. The eventData type
-     * is undefined and will be passed to all the importers registered
-     * via Importer.register. The first importer that returns true
-     * for canImport(events) will be used to import the events.
-     *
-     * The primary trace is provided via the eventData variable. If multiple
-     * traces are to be imported, specify the first one as events, and the
-     * remainder in the opt_additionalEventData array.
-     *
-     * @param {Array} traces An array of eventData to be imported. Each
-     * eventData should correspond to a single trace file and will be handled by
-     * a separate importer.
-     */
-    importTraces: function (traces) {
-      var progressMeter = {
-        update: function (msg) {}
-      };
-
-      tr.b.Task.RunSynchronously(this.createImportTracesTask(progressMeter, traces));
-    },
-
-    /**
-     * Imports a trace with the usual options from importTraces, but
-     * does so using idle callbacks, putting up an import dialog
-     * during the import process.
-     */
-    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;
-    },
-
-    /**
-     * Creates a task that will import the provided traces into the model,
-     * updating the progressMeter as it goes. Parameters are as defined in
-     * importTraces.
-     */
-    createImportTracesTask: function (progressMeter, traces) {
-      if (this.importing_) throw new Error('Already importing.');
-      this.importing_ = true;
-
-      // Just some simple setup. It is useful to have a no-op first
-      // task so that we can set up the lastTask = lastTask.after()
-      // pattern that follows.
-      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() {
-        // Copy the traces array, we may mutate it.
-        traces = traces.slice(0);
-        progressMeter.update('Creating importers...');
-        // Figure out which importers to use.
-        for (var i = 0; i < traces.length; ++i) importers.push(this.createImporter_(traces[i]));
-
-        // Some traces have other traces inside them. Before doing the full
-        // import, ask the importer if it has any subtraces, and if so, create
-        // importers for them, also.
-        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) {
-              // TODO(kphanee): Log the subtrace file which has failed.
-              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.');
-        }
-
-        // Sort them on priority. This ensures importing happens in a
-        // predictable order, e.g. ftrace_importer before
-        // trace_event_importer.
-        importers.sort(function (x, y) {
-          return x.importPriority - y.importPriority;
-        });
-      }, this);
-
-      // We import clock sync markers before all other events. This is necessary
-      // because we need the clock sync markers in order to know by how much we
-      // need to shift the timestamps of other events.
-      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);
-
-      // Run the import.
-      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);
-
-      // Run the cusomizeModelCallback if needed.
-      if (this.importOptions_.customizeModelCallback) {
-        lastTask = lastTask.timedAfter('TraceImport', function runCustomizeCallbacks(task) {
-          this.importOptions_.customizeModelCallback(this.model_);
-        }, this);
-      }
-
-      // Import sample data.
-      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);
-
-      // Autoclose open slices and create subSlices.
-      lastTask = lastTask.timedAfter('TraceImport', function runAutoclosers() {
-        progressMeter.update('Autoclosing open slices...');
-        this.model_.autoCloseOpenSlices();
-        this.model_.createSubSlices();
-      }, this);
-
-      // Finalize import.
-      lastTask = lastTask.timedAfter('TraceImport', function finalizeImport(task) {
-        importers.forEach(function (importer, index) {
-          progressMeter.update('Finalizing import ' + (index + 1) + '/' + importers.length);
-          importer.finalizeImport();
-        }, this);
-      }, this);
-
-      // Run preinit.
-      lastTask = lastTask.timedAfter('TraceImport', function runPreinits() {
-        progressMeter.update('Initializing objects (step 1/2)...');
-        this.model_.preInitializeObjects();
-      }, this);
-
-      // Prune empty containers.
-      if (this.importOptions_.pruneEmptyContainers) {
-        lastTask = lastTask.timedAfter('TraceImport', function runPruneEmptyContainers() {
-          progressMeter.update('Pruning empty containers...');
-          this.model_.pruneEmptyContainers();
-        }, this);
-      }
-
-      // Merge kernel and userland slices on each thread.
-      lastTask = lastTask.timedAfter('TraceImport', function runMergeKernelWithuserland() {
-        progressMeter.update('Merging kernel with userland...');
-        this.model_.mergeKernelWithUserland();
-      }, this);
-
-      // Create auditors
-      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);
-
-      // Build the flow event interval tree.
-      lastTask = lastTask.timedAfter('TraceImport', function buildFlowEventIntervalTree() {
-        progressMeter.update('Building flow event map...');
-        this.model_.buildFlowEventIntervalTree();
-      }, this);
-
-      // Join refs.
-      lastTask = lastTask.timedAfter('TraceImport', function joinRefs() {
-        progressMeter.update('Joining object refs...');
-        this.model_.joinRefs();
-      }, this);
-
-      // Delete any undeleted objects.
-      lastTask = lastTask.timedAfter('TraceImport', function cleanupUndeletedObjects() {
-        progressMeter.update('Cleaning up undeleted objects...');
-        this.model_.cleanupUndeletedObjects();
-      }, this);
-
-      // Sort global and process memory dumps.
-      lastTask = lastTask.timedAfter('TraceImport', function sortMemoryDumps() {
-        progressMeter.update('Sorting memory dumps...');
-        this.model_.sortMemoryDumps();
-      }, this);
-
-      // Finalize memory dump graphs.
-      lastTask = lastTask.timedAfter('TraceImport', function finalizeMemoryGraphs() {
-        progressMeter.update('Finalizing memory dump graphs...');
-        this.model_.finalizeMemoryGraphs();
-      }, this);
-
-      // Run initializers.
-      lastTask = lastTask.timedAfter('TraceImport', function initializeObjects() {
-        progressMeter.update('Initializing objects (step 2/2)...');
-        this.model_.initializeObjects();
-      }, this);
-
-      // Build event indices mapping from an event id to all flow events.
-      lastTask = lastTask.timedAfter('TraceImport', function buildEventIndices() {
-        progressMeter.update('Building event indices...');
-        this.model_.buildEventIndices();
-      }, this);
-
-      // Build the UserModel.
-      lastTask = lastTask.timedAfter('TraceImport', function buildUserModel() {
-        progressMeter.update('Building UserModel...');
-        var userModelBuilder = new tr.importer.UserModelBuilder(this.model_);
-        userModelBuilder.buildUserModel();
-      }, this);
-
-      // Sort Expectations.
-      lastTask = lastTask.timedAfter('TraceImport', function sortExpectations() {
-        progressMeter.update('Sorting user expectations...');
-        this.model_.userModel.sortExpectations();
-      }, this);
-
-      // Run audits.
-      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...');
-        // Log an import warning if the clock is low resolution.
-        if (!this.model_.isTimeHighResolution) {
-          this.model_.importWarning({
-            type: 'low_resolution_timer',
-            message: 'Trace time is low resolution, trace may be unusable.',
-            showToUser: true
-          });
-        }
-      }, this);
-
-      // Cleanup.
-      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
-  };
-});
+"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":28,"../base/timing.js":56,"../ui/base/overlay.js":174,"./empty_importer.js":71,"./importer.js":76,"./user_model_builder.js":78}],76:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/base.js");
-require("../base/extension_registry.js");
-
-'use strict';
-
-/**
- * @fileoverview Base class for trace data importers.
- */
-global.tr.exportTo('tr.importer', function () {
-  function Importer() {}
-
-  Importer.prototype = {
-    __proto__: Object.prototype,
-
-    get importerName() {
-      return 'Importer';
-    },
-
-    /**
-     * Called by the Model to check whether the importer type stores the actual
-     * trace data or just holds it as container for further extraction.
-     */
-    isTraceDataContainer: function () {
-      return false;
-    },
-
-    /**
-     * Called by the Model to extract one or more subtraces from the event data.
-     */
-    extractSubtraces: function () {
-      return [];
-    },
-
-    /**
-     * Called to import clock sync markers into the Model.
-     */
-    importClockSyncMarkers: function () {},
-
-    /**
-     * Called to import events into the Model.
-     */
-    importEvents: function () {},
-
-    /**
-     * Called to import sample data into the Model.
-     */
-    importSampleData: function () {},
-
-    /**
-     * Called by the Model after all other importers have imported their
-     * events.
-     */
-    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
-  };
-});
+"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":28,"../base/extension_registry.js":35}],77:[function(require,module,exports){
+},{"../base/base.js":34,"../base/extension_registry.js":41}],83:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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 () {
-  // This is an intermediate data format between InputLatencyAsyncSlices and
-  // Response and Animation IRs.
-  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';
-
-  // Explicitly ignore some input events to allow
-  // UserModelBuilder.checkAllInputEventsHandled() to determine which events
-  // were unintentionally ignored due to a bug.
-  ProtoExpectation.IGNORED_TYPE = 'ignored';
-
-  ProtoExpectation.prototype = {
-    get isValid() {
-      return this.end > this.start;
-    },
-
-    // Return true if any associatedEvent's typeName is in typeNames.
-    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);
-
-        // |event| is either an InputLatencyAsyncSlice (which collects all of
-        // its associated events transitively) or a CSS Animation (which doesn't
-        // have any associated events). So this does not need to recurse.
-        if (event.associatedEvents) ir.associatedEvents.addEventSet(event.associatedEvents);
-      }
-
-      this.associatedEvents.forEach(function (event) {
-        pushAssociatedEvents(event);
-
-        // Old-style InputLatencyAsyncSlices have subSlices.
-        if (event.subSlices) event.subSlices.forEach(pushAssociatedEvents);
-      });
-
-      return ir;
-    },
-
-    // Merge the other ProtoExpectation into this one.
-    // The irTypes need not match: ignored ProtoExpectations might be merged
-    // into overlapping ProtoExpectations, and Touch-only Animations are merged
-    // into Tap Responses.
-    merge: function (other) {
-      other.names.forEach(function (name) {
-        this.names.add(name);
-      }.bind(this));
-
-      // Don't use pushEvent(), which would lose special start, end.
-      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;
-    },
-
-    // Include |event| in this ProtoExpectation, expanding start/end to include
-    // it.
-    pushEvent: function (event) {
-      // Usually, this method will be called while iterating over a list of
-      // events sorted by start time, so this method won't usually change
-      // this.start. However, this will sometimes be called for
-      // ProtoExpectations created by previous handlers, in which case
-      // event.start could possibly be before this.start.
-      this.start = Math.min(this.start, event.start);
-      this.end = Math.max(this.end, event.end);
-      this.associatedEvents.push(event);
-    },
-
-    // Returns true if timestamp is contained in this ProtoExpectation.
-    containsTimestampInclusive: function (timestamp) {
-      return this.start <= timestamp && timestamp <= this.end;
-    },
-
-    // Return true if the other event intersects this ProtoExpectation.
-    intersects: function (other) {
-      // http://stackoverflow.com/questions/325933
-      return other.start < this.end && other.end > this.start;
-    },
-
-    isNear: function (event, threshold) {
-      return this.end + threshold > event.start;
-    },
-
-    // Return a string describing this ProtoExpectation for debugging.
-    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
-  };
-});
+"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":28,"../base/range_utils.js":48,"../core/auditor.js":60,"../model/event_info.js":118,"../model/user_model/animation_expectation.js":161,"../model/user_model/response_expectation.js":164}],78:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 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.
-**/
-
-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();
-        // There are not currently any known cases when this could throw.
-      } catch (error) {
-        this.model.importWarning({
-          type: 'UserModelBuilder',
-          message: error,
-          showToUser: true
-        });
-        return;
-      }
-      expectations.forEach(function (expectation) {
-        this.model.userModel.expectations.push(expectation);
-      }, this);
-
-      // TODO(benjhayden) Find Gestures here.
-    },
-
-    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));
-      // findIdleExpectations must be called last!
-      expectations.push.apply(expectations, this.findIdleExpectations(expectations));
-      this.collectUnassociatedEvents_(expectations);
-      return expectations;
-    },
-
-    // Find all unassociated top-level ThreadSlices. If they start during an
-    // Idle or Load IR, then add their entire hierarchy to that IR.
-    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;
-          }
-        }
-      });
-    },
-
-    // Fill in the empty space between IRs with IdleIRs.
-    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) {
-        // Ignore insignificantly tiny idle ranges.
-        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('.'));
-
-    // createCustomizeModelLinesFromModel() throws an error if there's an
-    // unsupported event.
-    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
-  };
-});
+"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":28,"../base/range_utils.js":48,"../core/auditor.js":60,"../extras/chrome/cc/input_latency_async_slice.js":62,"../model/event_info.js":118,"../model/ir_coverage.js":131,"../model/user_model/idle_expectation.js":162,"./find_input_expectations.js":72,"./find_load_expectations.js":73,"./find_startup_expectations.js":74}],79:[function(require,module,exports){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./importer/import.js");
-require("./model/model.js");
-require("./extras/lean_config.js");
-require("./metrics/all_metrics.js");
-},{"./extras/lean_config.js":69,"./importer/import.js":75,"./metrics/all_metrics.js":80,"./model/model.js":135}],80:[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.
-**/
-
-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":81,"./cpu_process_metric.js":82,"./sample_metric.js":84,"./system_health/clock_sync_latency_metric.js":85,"./system_health/hazard_metric.js":87,"./system_health/loading_metric.js":88,"./system_health/memory_metric.js":90,"./system_health/power_metric.js":91,"./system_health/responsiveness_metric.js":92,"./system_health/system_health_metrics.js":93,"./system_health/webview_startup_metric.js":95,"./tracing_metric.js":96,"./v8/execution_metric.js":97,"./v8/gc_metric.js":98,"./v8/v8_metrics.js":100}],81:[function(require,module,exports){
+},{"../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":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":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";
-/**
-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.
-**/
-
-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 () {
-  // Maps the Blink GC events in timeline to telemetry friendly names.
-  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;
-
-  // 0.1 steps from 0 to 20 since it is the most common range.
-  // Exponentially increasing steps from 20 to 200.
-  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;
-  }
-
-  /**
-   * Example output:
-   * - blink-gc-marking.
-   */
-  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);
-    });
-  }
-
-  /**
-   * Example output:
-   * - blink-gc-total
-   */
-  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);
-    });
-  }
-
-  /**
-   * Example output:
-   * - blink-gc-marking_idle_deadline_overrun,
-   * - blink-gc-marking_outside_idle,
-   * - blink-gc-marking_percentage_idle.
-   */
-  function addIdleTimesOfTopEvents(values, model) {
-    tr.metrics.v8.utils.groupAndProcessEvents(model, isBlinkGarbageCollectionEvent, blinkGarbageCollectionEventName, function (name, events) {
-      addIdleTimes(values, model, name, events);
-    });
-  }
-
-  /**
-   * Example output:
-   * - blink-gc-total_idle_deadline_overrun,
-   * - blink-gc-total_outside_idle,
-   * - blink-gc-total_percentage_idle.
-   */
-  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;
-          // Don't count time over the deadline as being inside idle time.
-          // Since the deadline should be relative to wall clock we
-          // compare allotted_time_ms with wall duration instead of thread
-          // duration, and then assume the thread duration was inside idle
-          // for the same percentage of time.
-          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
-  };
-});
+"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":47,"../../base/unit.js":57,"../../value/histogram.js":189,"../metric_registry.js":83,"../v8/utils.js":99}],82:[function(require,module,exports){
+},{"../../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";
-/**
-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.
-**/
-
-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);
-      // The process path may be missing on Windows because of AccessDenied
-      // error thrown by psutil package used by CPU tracing agent.
-      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;
-      // Treat missing snapshots as zeros. A process is missing from a snapshots
-      // when its CPU usage was below minimum threshold when the snapshot was
-      // taken.
-      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
-  };
-});
+"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":189,"./metric_registry.js":83}],83:[function(require,module,exports){
+},{"../value/histogram.js":195,"./metric_registry.js":89}],89:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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
-  };
-});
+"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":28,"../base/extension_registry.js":35,"../base/iteration_helpers.js":41}],84:[function(require,module,exports){
+},{"../base/base.js":34,"../base/extension_registry.js":41,"../base/iteration_helpers.js":47}],90:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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
-  };
-});
+"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":47,"../value/histogram.js":189,"./metric_registry.js":83}],85:[function(require,module,exports){
+},{"../base/range.js":53,"../value/histogram.js":195,"./metric_registry.js":89}],91:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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
-  };
-});
+"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":135,"../../value/histogram.js":189,"../metric_registry.js":83,"./utils.js":94}],86:[function(require,module,exports){
+},{"../../model/model.js":141,"../../value/histogram.js":195,"../metric_registry.js":89,"./utils.js":100}],92:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-require("../metric_registry.js");
-require("../../value/histogram.js");
-
-'use strict';
-
-global.tr.exportTo('tr.metrics.sh', function () {
-  // Use a lower bound of 0.01 for the metric boundaries (when no CPU time
-  // is consumed) and an upper bound of 50 (fifty cores are all active
-  // for the entire time). We can't use zero exactly for the lower bound with an
-  // exponential histogram.
-  var CPU_TIME_PERCENTAGE_BOUNDARIES = tr.v.HistogramBinBoundaries.createExponential(0.01, 50, 200);
-
-  /**
-   * This metric measures total CPU time for Chrome processes, per second of
-   *   clock time.
-   * This metric requires only the 'toplevel' tracing category.
-   *
-   * @param {!tr.v.ValueSet} values
-   * @param {!tr.model.Model} model
-   * @param {!Object=} opt_options
-   */
-  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;
-
-          // We assume that if a slice doesn't lie entirely inside the range of
-          // interest, then the CPU time is evenly distributed inside of the
-          // slice.
-          threadCpuTime += slice.cpuDuration * fractionOfSliceInsideRangeOfInterest;
-        });
-        processCpuTime += threadCpuTime;
-      }
-      allProcessCpuTime += processCpuTime;
-    }
-
-    // Normalize cpu time by clock time.
-    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
-  };
-});
+"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":189,"../metric_registry.js":83}],87:[function(require,module,exports){
+},{"../../value/histogram.js":195,"../metric_registry.js":89}],93:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../metric_registry.js");
-require("./long_tasks_metric.js");
-require("../../value/numeric.js");
-
-'use strict';
-
-global.tr.exportTo('tr.metrics.sh', function () {
-  // The following math is easier if the units are seconds rather than ms,
-  // so durations will be converted from ms to s.
-  var MS_PER_S = 1000;
-
-  // https://www.desmos.com/calculator/ysabhcc42g
-  var RESPONSE_RISK = tr.b.Statistics.LogNormalDistribution.fromMedianAndDiminishingReturns(100 / MS_PER_S, 50 / MS_PER_S);
-
-  /**
-   * This helper function computes the risk that a task of the given duration
-   * would impact the responsiveness of a speculative input.
-   *
-   * @param {number} durationMs
-   * @return {number} task hazard
-   */
-  function computeResponsivenessRisk(durationMs) {
-    // Returns 0 when the risk of impacting responsiveness is minimal.
-    // Returns 1 when it is maximal.
-    // durationMs is the duration of a long task.
-    // It is at least LONG_TASK_MS.
-    // The FAST_RESPONSE_HISTOGRAM was designed to permit both a 50ms task
-    // when a Scroll Response begins, plus 16ms latency between the task
-    // and the first frame of the scroll, without impacting the responsiveness
-    // score.
-    // Add 16ms to durationMs to simulate the standard (maximum ideal) scroll
-    // response latency, and use the FAST_RESPONSE_HISTOGRAM to punish every ms
-    // that the long task exceeds LONG_TASK_MS.
-
-    durationMs += 16;
-
-    // This returns a normalized percentage that
-    // represents the fraction of users that would be satisfied with a
-    // Scroll Response that takes durationMs to respond.
-    // The risk of impacting responsiveness is approximated as the long task's
-    // impact on a hypothetical Scroll Response that starts when the long task
-    // starts, and then takes the standard 16ms to respond after the long task
-    // finishes.
-    // We imagine a Scroll Response instead of a Load or another type of
-    // Response because the Scroll Response carries the strictest expectation.
-    // The risk of impacting responsiveness is framed as the fraction of users
-    // that would be *un*satisifed with the responsiveness of that hypothetical
-    // Scroll Response. The fraction of users who are unsatisfied with something
-    // is equal to 1 - the fraction of users who are satisfied with it.
-    return RESPONSE_RISK.computePercentile(durationMs / MS_PER_S);
-  }
-
-  /**
-   * This weighting function is similar to tr.metrics.sh.perceptualBlend,
-   * but this version is appropriate for SmallerIsBetter metrics, whereas
-   * that version is for BiggerIsBetter metrics.
-   * (This would not be necessary if hazard were reframed as a BiggerIsBetter
-   * metric such as "input readiness".)
-   * Also, that version assumes that the 'ary' will be UserExpectations, whereas
-   * this version assumes that the 'ary' will be scores.
-   *
-   * @param {number} hazardScore
-   * @return {number} weight
-   */
-  function perceptualBlendSmallerIsBetter(hazardScore) {
-    return Math.exp(hazardScore);
-  }
-
-  /**
-   * Compute and return the normalized score for the risk that a speculative
-   * input's responsiveness would have been impacted by long tasks on the given
-   * thread in the given range.
-   *
-   * @param {tr.model.Thread} thread
-   * @param {tr.b.Range=} opt_range
-   * @return {number} hazard
-   */
-  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);
-  }
-
-  /**
-   * Compute and return the normalized score for the risk that a speculative
-   * input's responsiveness would have been impacted by long tasks.
-   *
-   * @param {tr.model.Model} model The model.
-   * @return {number} hazard
-   */
-  function computeHazardForLongTasks(model) {
-    var threadHazardScores = [];
-    tr.metrics.sh.iterateRendererMainThreads(model, function (thread) {
-      threadHazardScores.push(computeHazardForLongTasksInRangeOnThread(thread));
-    });
-    return tr.b.Statistics.weightedMean(threadHazardScores, perceptualBlendSmallerIsBetter);
-  }
-
-  /**
-   * This EXPERIMENTAL metric computes a scalar normalized score that
-   * represents the risk that a speculative input's responsiveness would have
-   * been impacted by long tasks.
-   * This metric requires only the 'toplevel' tracing category.
-   */
-  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
-  };
-});
+"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":190,"../metric_registry.js":83,"./long_tasks_metric.js":89}],88:[function(require,module,exports){
+},{"../../value/numeric.js":196,"../metric_registry.js":89,"./long_tasks_metric.js":95}],94:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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;
-
-  // TODO(ksakamoto): This should be a method of tr.model.Event or one of its
-  // subclasses.
-  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;
-  }
-
-  /**
-   * A utility class for finding navigationStart event for given frame and
-   * timestamp.
-   * @constructor
-   */
-  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) // 50ms step to 1s
-  .addLinearBins(3e3, 20) // 100ms step to 3s
-  .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',
-  // Chrome on Android creates main frames with the below URL for plugins.
-  '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);
-        // Ignore layout w/o preceding navigationStart, as they are not
-        // attributed to any time-to-X metric.
-        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;
-    // Find the first interactive point X after firstMeaningfulPaint so that
-    // range [X, X + INTERACTIVE_WINDOW_SIZE] contains no
-    // 'TaskQueueManager::ProcessTaskFromWorkQueues' slice which takes more than
-    // RESPONSIVENESS_THRESHOLD.
-    // For more details on why TaskQueueManager::ProcessTaskFromWorkQueue is
-    // chosen as a proxy for all un-interruptable task on renderer thread, see
-    // https://github.com/GoogleChrome/lighthouse/issues/489
-    // TODO(nedn): replace this with just "var ev of rendererHelper..." once
-    // canary binary is updated.
-    // (https://github.com/catapult-project/catapult/issues/2586)
-    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
-    });
-  }
-
-  /**
-   * Computes Time to first meaningful paint (TTFMP) & time to interactive (TTI)
-   * from |model| and add it to |value|.
-   *
-   * First meaningful paint is the paint following the layout with the highest
-   * "Layout Significance". The Layout Significance is computed inside Blink,
-   * by FirstMeaningfulPaintDetector class. It logs
-   * "firstMeaningfulPaintCandidate" event every time the Layout Significance
-   * marks a record. TTFMP is the time between NavigationStart and the last
-   * firstMeaningfulPaintCandidate event.
-   *
-   * Design doc: https://goo.gl/vpaxv6
-   *
-   * TTI is computed as the starting time of the timed window with size
-   * INTERACTIVE_WINDOW_SIZE that happens after FMP in which there is no
-   * uninterruptable task on the main thread with size more than
-   * RESPONSIVENESS_THRESHOLD.
-   *
-   * Design doc: https://goo.gl/ISWndc
-   */
-  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;
-
-      // Iterate over the FMP candidates, remembering the last one.
-      for (var ev of candidatesForFrameId[frameIdRef]) {
-        var navigationStartForThisCandidate = navigationStartFinder.findNavigationStartEventForFrameBeforeTimestamp(frameIdRef, ev.start);
-        // Ignore candidate w/o preceding navigationStart, as they are not
-        // attributed to any TTFMP.
-        if (navigationStartForThisCandidate === undefined) continue;
-
-        if (navigationStart !== navigationStartForThisCandidate) {
-          // New navigation is found. Compute TTFMP for current navigation, and
-          // reset the state variables.
-          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;
-      }
-
-      // Emit TTFMP for the last navigation.
-      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
-  };
-});
+"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":30,"../../base/statistics.js":53,"../../model/helpers/chrome_model_helper.js":127,"../../model/timed_event.js":160,"../../value/histogram.js":189,"../../value/numeric.js":190,"../metric_registry.js":83,"./utils.js":94}],89:[function(require,module,exports){
+},{"../../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";
-/**
-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.
-**/
-
-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;
-
-  // Anything longer than this should be so rare that its length beyond this is
-  // uninteresting.
-  var LONGEST_TASK_MS = 1000;
-
-  /**
-   * This helper function calls |cb| for each of the top-level tasks on the
-   * given thread in the given range whose duration is longer than LONG_TASK_MS.
-   *
-   * @param {tr.model.Thread} thread
-   * @param {tr.b.Range=} opt_range
-   * @param {function()} cb
-   * @param {Object=} opt_this
-   */
-  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);
-    });
-  }
-
-  /**
-   * This helper function calls |cb| for each of the main renderer threads in
-   * the model.
-   *
-   * @param {tr.model.Model} model The model.
-   * @param {function()} cb Callback.
-   * @param {Object=} opt_this Context object.
-   */
-  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);
-    });
-  }
-
-  /**
-   * This metric directly measures long tasks on renderer main threads.
-   * This metric requires only the 'toplevel' tracing category.
-   *
-   * @param {!tr.v.ValueSet} values
-   * @param {!tr.model.Model} model
-   * @param {!Object=} opt_options
-   */
-  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
-  };
-});
+"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":63,"../../model/helpers/chrome_model_helper.js":127,"../../value/histogram.js":189,"../metric_registry.js":83}],90:[function(require,module,exports){
+},{"../../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";
-/**
-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.
-**/
-
-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();
-  // For unitless numerics (process counts), we use 20 linearly scaled bins
-  // from 0 to 20.
-  BOUNDARIES_FOR_UNIT_MAP.set(count_smallerIsBetter, tr.v.HistogramBinBoundaries.createLinear(0, 20, 20));
-  // For size numerics (subsystem and vm stats), we use 1 bin from 0 B to
-  // 1 KiB and 4*24 exponentially scaled bins from 1 KiB to 16 GiB (=2^24 KiB).
-  BOUNDARIES_FOR_UNIT_MAP.set(sizeInBytes_smallerIsBetter, new tr.v.HistogramBinBoundaries(0).addBinBoundary(1024 /* 1 KiB */).addExponentialBins(16 * 1024 * 1024 * 1024 /* 16 GiB */, 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);
-  }
-
-  /**
-   * Splits the global memory dumps in |model| by browser name.
-   *
-   * @param {!tr.Model} model The trace model from which the global dumps
-   *     should be extracted.
-   * @param {!tr.b.Range=} opt_rangeOfInterest If proided, global memory dumps
-   *     that do not inclusively intersect the range will be skipped.
-   * @return {!Map<string, !Array<!tr.model.GlobalMemoryDump>} A map from
-   *     browser names to the associated global memory dumps.
-   */
-  function splitGlobalDumpsByBrowserName(model, opt_rangeOfInterest) {
-    var chromeModelHelper = model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
-    var browserNameToGlobalDumps = new Map();
-    var globalDumpToBrowserHelper = new WeakMap();
-
-    // 1. For each browser process in the model, add its global memory dumps to
-    // |browserNameToGlobalDumps|. |chromeModelHelper| can be undefined if
-    // it fails to find any browser, renderer or GPU process (see
-    // tr.model.helpers.ChromeModelHelper.supportsModel).
-    if (chromeModelHelper) {
-      chromeModelHelper.browserHelpers.forEach(function (helper) {
-        // Retrieve the associated global memory dumps and check that they
-        // haven't been classified as belonging to another browser process.
-        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);
-      });
-    }
-
-    // 2. If any global memory dump does not have any associated browser
-    // process for some reason, associate it with an 'unknown_browser' browser
-    // so that we don't lose the data.
-    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'
-  };
-
-  /**
-   * Convert a canonical browser name used in value names to a user-friendly
-   * name used in value descriptions.
-   *
-   * Examples:
-   *
-   *   CANONICAL BROWSER NAME -> USER-FRIENDLY NAME
-   *   chrome                 -> Chrome
-   *   unknown_browser        -> an unknown browser
-   *   webview2               -> WebView(2)
-   *   unexpected             -> 'unexpected' 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'; // Intentionally plural.
-      case 'browser':
-        return 'browser_process';
-      default:
-        return baseCanonicalName;
-    }
-  }
-
-  /**
-   * Convert a canonical process name used in value names to a user-friendly
-   * name used in value descriptions.
-   */
-  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 for adding entries with duplicate keys to a map without
-   * overriding existing entries.
-   *
-   * This is achieved by appending numeric indices (2, 3, 4, ...) to duplicate
-   * keys. Example:
-   *
-   *   var map = new Map();
-   *   // map = Map {}.
-   *
-   *   makeKeyUniqueAndSet(map, 'key', 'a');
-   *   // map = Map {"key" => "a"}.
-   *
-   *   makeKeyUniqueAndSet(map, 'key', 'b');
-   *   // map = Map {"key" => "a", "key2" => "b"}.
-   *                                ^^^^
-   *   makeKeyUniqueAndSet(map, 'key', 'c');
-   *   // map = Map {"key" => "a", "key2" => "b", "key3" => "c"}.
-   *                                ^^^^           ^^^^
-   */
-  function makeKeyUniqueAndSet(map, key, value) {
-    var uniqueKey = key;
-    var nextIndex = 2;
-    while (map.has(uniqueKey)) {
-      uniqueKey = key + nextIndex;
-      nextIndex++;
-    }
-    map.set(uniqueKey, value);
-  }
-
-  /**
-   * Add general memory dump values calculated from all global memory dumps to
-   * |values|. In particular, this function adds the following values:
-   *
-   *   * PROCESS COUNTS
-   *     memory:{chrome, webview}:
-   *         {browser_process, renderer_processes, ..., all_processes}:
-   *         process_count
-   *     type: tr.v.Histogram (over all matching global memory dumps)
-   *     unit: count_smallerIsBetter
-   *
-   *   * MEMORY USAGE REPORTED BY CHROME
-   *     memory:{chrome, webview}:
-   *         {browser_process, renderer_processes, ..., all_processes}:
-   *         reported_by_chrome[:{v8, malloc, ...}]:
-   *         {effective_size, allocated_objects_size, locked_size}
-   *     type: tr.v.Histogram (over all matching global memory dumps)
-   *     unit: sizeInBytes_smallerIsBetter
-   */
-  function addGeneralMemoryDumpValues(browserNameToGlobalDumps, values) {
-    addMemoryDumpValues(browserNameToGlobalDumps, gmd => true /* process all global memory dumps */
-    , function (processDump, addProcessScalar) {
-      // Increment memory:<browser-name>:<process-name>:process_count value.
-      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
-          });
-        });
-      }
-
-      // Add memory:<browser-name>:<process-name>:reported_by_chrome:...
-      // values.
-      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
-          });
-        });
-        // Some dump providers add allocated objects size as
-        // "allocated_objects" child dump.
-        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']
-            });
-          }
-        }
-      });
-
-      // Add memory:<browser-name>:<process-name>:reported_by_chrome:v8:
-      //     {heap, allocated_by_malloc}:...
-      addV8MemoryDumpValues(processDump, addProcessScalar);
-    }, function (componentTree) {
-      // Subtract memory:<browser-name>:<process-name>:reported_by_chrome:
-      // tracing:<size-property> from memory:<browser-name>:<process-name>:
-      // reported_by_chrome:<size-property> if applicable.
-      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);
-  }
-
-  /**
-   * Add memory dump values calculated from V8 components excluding
-   * 'heap_spaces/other_spaces'.
-   *
-   * @param {!tr.model.ProcessMemoryDump} processDump The process memory dump.
-   * @param {!function} addProcessScalar The callback for adding a scalar value.
-   */
-  function addV8MemoryDumpValues(processDump, addProcessScalar) {
-    var v8Dump = processDump.getMemoryAllocatorDumpByFullName('v8');
-    if (v8Dump === undefined) return;
-    v8Dump.children.forEach(function (isolateDump) {
-      // v8:allocated_by_malloc:...
-      var mallocDump = isolateDump.getDescendantDumpByFullName('malloc');
-      if (mallocDump !== undefined) {
-        addV8ComponentValues(mallocDump, ['v8', 'allocated_by_malloc'], addProcessScalar);
-      }
-      // v8:heap:...
-      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);
-        });
-      }
-    });
-
-    // V8 generates bytecode when interpreting and code objects when
-    // compiling the javascript. Total code size includes the size
-    // of code and bytecode objects.
-    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
-    });
-  }
-
-  /**
-   * Add memory dump values calculated from the specified V8 component.
-   *
-   * @param {!tr.model.MemoryAllocatorDump} v8Dump The V8 memory dump.
-   * @param {!Array<string>} componentPath The component path for reporting.
-   * @param {!function} addProcessScalar The callback for adding a scalar value.
-   */
-  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
-      });
-    });
-  }
-
-  /**
-   * Build a description prefix for a memory:<browser-name>:<process-name>:
-   * process_count value.
-   *
-   * @param {!Array<string>} componentPath The underlying component path (must
-   *     be empty).
-   * @param {string} processName The canonical name of the process.
-   * @return {string} Prefix for the value's description (always
-   *     'total number of renderer processes').
-   */
-  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 /* opt_requirePlural */);
-  }
-
-  /**
-   * Build a description prefix for a memory:<browser-name>:<process-name>:
-   * reported_by_chrome:... value.
-   *
-   * @param {{
-   *     userFriendlyPropertyName: string,
-   *     userFriendlyPropertyNamePrefix: (string|undefined),
-   *     totalUserFriendlyPropertyName: (string|undefined),
-   *     componentPreposition: (string|undefined) }}
-   *     formatSpec Specification of how the property should be formatted.
-   * @param {!Array<string>} componentPath The underlying component path (e.g.
-   *     ['malloc']).
-   * @param {string} processName The canonical name of the process.
-   * @return {string} Prefix for the value's description (e.g.
-   *     'effective size of malloc in the browser process').
-   */
-  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) {
-        // Use component name as an adjective
-        // (e.g. 'size of V8 code and metadata').
-        if (formatSpec.userFriendlyPropertyNamePrefix) nameParts.push(formatSpec.userFriendlyPropertyNamePrefix);
-        nameParts.push(componentPath.join(':'));
-        nameParts.push(formatSpec.userFriendlyPropertyName);
-      } else {
-        // Use component name as a noun with a preposition
-        // (e.g. 'size of all objects allocated BY MALLOC').
-        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(' ');
-  }
-
-  // Specifications of properties reported by Chrome.
-  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')
-    }
-  };
-
-  /**
-   * Add heavy memory dump values calculated from heavy global memory dumps to
-   * |values|. In particular, this function adds the following values:
-   *
-   *   * MEMORY USAGE REPORTED BY THE OS
-   *     memory:{chrome, webview}:
-   *         {browser_process, renderer_processes, ..., all_processes}:
-   *         reported_by_os:system_memory:[{ashmem, native_heap, java_heap}:]
-   *         {proportional_resident_size, private_dirty_size}
-   *     memory:{chrome, webview}:
-   *         {browser_process, renderer_processes, ..., all_processes}:
-   *         reported_by_os:gpu_memory:[{gl, graphics, ...}:]
-   *         proportional_resident_size
-   *     type: tr.v.Histogram (over matching heavy global memory dumps)
-   *     unit: sizeInBytes_smallerIsBetter
-   *
-   *   * MEMORY USAGE REPORTED BY CHROME
-   *     memory:{chrome, webview}:
-   *         {browser_process, renderer_processes, ..., all_processes}:
-   *         reported_by_chrome:v8:code_and_metadata_size
-   *     type: tr.v.Histogram (over matching heavy global memory dumps)
-   *     unit: sizeInBytes_smallerIsBetter
-   */
-  function addDetailedMemoryDumpValues(browserNameToGlobalDumps, values) {
-    addMemoryDumpValues(browserNameToGlobalDumps, g => g.levelOfDetail === DETAILED, function (processDump, addProcessScalar) {
-      // Add memory:<browser-name>:<process-name>:reported_by_os:
-      // system_memory:... values.
-      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
-          });
-        });
-      });
-
-      // Add memory:<browser-name>:<process-name>:reported_by_os:
-      // gpu_memory:... values.
-      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);
-  }
-
-  // Specifications of components reported by the system.
-  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'
-    }
-  };
-
-  // Specifications of properties reported by the system.
-  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')
-    }
-  };
-
-  /**
-   * Build a description prefix for a memory:<browser-name>:<process-name>:
-   * reported_by_os:... value.
-   *
-   * @param {string} userFriendlyPropertyName User-friendly name of the
-   *     underlying property (e.g. 'private dirty size').
-   * @param {!Array<string>} componentPath The underlying component path (e.g.
-   *     ['system', 'java_heap']).
-   * @param {string} processName The canonical name of the process.
-   * @return {string} Prefix for the value's description (e.g.
-   *     'total private dirty size of the Java heal in the GPU process').
-   */
-  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(' ');
-  }
-
-  /**
-   * Build a description prefix for a memory:<browser-name>:<process-name>:
-   * reported_by_chrome:...:code_and_metadata_size value.
-   *
-   * @param {!Array<string>} componentPath The underlying component path (e.g.
-   *     ['v8']).
-   * @param {string} processName The canonical name of the process.
-   * @return {string} Prefix for the value's description (e.g.
-   *     'size of v8 code and metadata in').
-   */
-  function buildCodeAndMetadataSizeValueDescriptionPrefix(componentPath, processName) {
-    return buildChromeValueDescriptionPrefix({
-      userFriendlyPropertyNamePrefix: 'size of',
-      userFriendlyPropertyName: 'code and metadata'
-    }, componentPath, processName);
-  }
-
-  /**
-   * Get the descendant of a VM region classification |node| specified by the
-   * given |path| of child node titles. If |node| is undefined or such a
-   * descendant does not exist, this function returns undefined.
-   */
-  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;
-  }
-
-  /**
-   * Add global memory dump counts to |values|. In particular, this function
-   * adds the following values:
-   *
-   *   * DUMP COUNTS
-   *     memory:{chrome, webview}:all_processes:dump_count[:{light, detailed}]
-   *     type: tr.v.Histogram
-   *     unit: count_smallerIsBetter
-   *
-   * Note that unlike all other values generated by the memory metric, the
-   * global memory dump counts are NOT instances of tr.v.Histogram
-   * because it doesn't make sense to aggregate them (they are already counts
-   * over all global dumps associated with the relevant browser).
-   */
-  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++;
-
-        // Increment the level-of-detail-specific dump count (if possible).
-        var levelOfDetailName = LEVEL_OF_DETAIL_NAMES.get(globalDump.levelOfDetail);
-        if (!(levelOfDetailName in levelOfDetailNameToDumpCount)) return; // Unknown level of detail.
-        levelOfDetailNameToDumpCount[levelOfDetailName]++;
-      });
-
-      // Add memory:<browser-name>:all_processes:dump_count[:<level>] values.
-      reportMemoryDumpCountAsValue(browserName, undefined /* total */
-      , totalDumpCount, values);
-      tr.b.iterItems(levelOfDetailNameToDumpCount, function (levelOfDetailName, levelOfDetailDumpCount) {
-        reportMemoryDumpCountAsValue(browserName, levelOfDetailName, levelOfDetailDumpCount, values);
-      });
-    });
-  }
-
-  /**
-   * Add a tr.v.Histogram value to |values| reporting that the number of
-   * |levelOfDetailName| memory dumps added by |browserName| was
-   * |levelOfDetailCount|.
-   */
-  function reportMemoryDumpCountAsValue(browserName, levelOfDetailName, levelOfDetailDumpCount, values) {
-    // Construct the name of the memory value.
-    var nameParts = ['memory', browserName, 'all_processes', 'dump_count'];
-    if (levelOfDetailName !== undefined) nameParts.push(levelOfDetailName);
-    var name = nameParts.join(':');
-
-    // Build the underlying histogram for the memory value.
-    var histogram = new tr.v.Histogram(name, count_smallerIsBetter, BOUNDARIES_FOR_UNIT_MAP.get(count_smallerIsBetter));
-    histogram.addSample(levelOfDetailDumpCount);
-
-    // Build the options for the memory value.
-    histogram.description = ['total number of', levelOfDetailName || 'all', 'memory dumps added by', convertBrowserNameToUserFriendlyName(browserName), 'to the trace'].join(' ');
-
-    // Report the memory value.
-    values.addHistogram(histogram);
-  }
-
-  /**
-   * Add generic values extracted from process memory dumps and aggregated by
-   * process name and component path into |values|.
-   *
-   * For each browser and set of global dumps in |browserNameToGlobalDumps|,
-   * |customProcessDumpValueExtractor| is applied to every process memory dump
-   * associated with the global memory dump. The second argument provided to the
-   * callback is a function for adding extracted values:
-   *
-   *   function sampleProcessDumpCallback(processDump, addProcessValue) {
-   *     ...
-   *     addProcessScalar({
-   *       source: 'reported_by_chrome',
-   *       component: ['system', 'native_heap'],
-   *       property: 'proportional_resident_size',
-   *       value: pssExtractedFromProcessDump2,
-   *       descriptionPrefixBuilder: function(componentPath) {
-   *         return 'PSS of ' + componentPath.join('/') + ' in';
-   *       }
-   *     });
-   *     ...
-   *   }
-   *
-   * For each global memory dump, the extracted values are summed by process
-   * name (browser_process, renderer_processes, ..., all_processes) and
-   * component path (e.g. gpu is a sum of gpu:gl, gpu:graphics, ...). The sums
-   * are then aggregated over all global memory dumps associated with the given
-   * browser. For example, assuming that |customProcessDumpValueExtractor|
-   * extracts 'proportional_resident_size' values for component paths
-   * ['X', 'A'], ['X', 'B'] and ['Y'] under the same 'source' from each process
-   * memory dump, the following values will be reported (for Chrome):
-   *
-   *    memory:chrome:browser_process:source:X:A:proportional_resident_size :
-   *        Histogram aggregated over [
-   *          sum of X:A in all 'browser' process dumps in global dump 1,
-   *          ...
-   *          sum of X:A in all 'browser' process dumps in global dump N
-   *        ]
-   *
-   *    memory:chrome:browser_process:source:X:B:proportional_resident_size :
-   *        Histogram aggregated over [
-   *          sum of X:B in all 'browser' process dumps in global dump 1,
-   *          ...
-   *          sum of X:B in all 'browser' process dumps in global dump N
-   *        ]
-   *
-   *    memory:chrome:browser_process:source:X:proportional_resident_size :
-   *        Histogram aggregated over [
-   *          sum of X:A+X:B in all 'browser' process dumps in global dump 1,
-   *          ...
-   *          sum of X:A+X:B in all 'browser' process dumps in global dump N
-   *        ]
-   *
-   *    memory:chrome:browser_process:source:Y:proportional_resident_size :
-   *        Histogram aggregated over [
-   *          sum of Y in all 'browser' process dumps in global dump 1,
-   *          ...
-   *          sum of Y in all 'browser' process dumps in global dump N
-   *        ]
-   *
-   *    memory:chrome:browser_process:source:proportional_resident_size :
-   *        Histogram aggregated over [
-   *          sum of X:A+X:B+Y in all 'browser' process dumps in global dump 1,
-   *          ...
-   *          sum of X:A+X:B+Y in all 'browser' process dumps in global dump N
-   *        ]
-   *
-   *    ...
-   *
-   *    memory:chrome:all_processes:source:X:A:proportional_resident_size :
-   *        Histogram aggregated over [
-   *          sum of X:A in all process dumps in global dump 1,
-   *          ...
-   *          sum of X:A in all process dumps in global dump N,
-   *    ]
-   *
-   *    memory:chrome:all_processes:source:X:B:proportional_resident_size :
-   *        Histogram aggregated over [
-   *          sum of X:B in all process dumps in global dump 1,
-   *          ...
-   *          sum of X:B in all process dumps in global dump N,
-   *    ]
-   *
-   *    memory:chrome:all_processes:source:X:proportional_resident_size :
-   *        Histogram aggregated over [
-   *          sum of X:A+X:B in all process dumps in global dump 1,
-   *          ...
-   *          sum of X:A+X:B in all process dumps in global dump N,
-   *    ]
-   *
-   *    memory:chrome:all_processes:source:Y:proportional_resident_size :
-   *        Histogram aggregated over [
-   *          sum of Y in all process dumps in global dump 1,
-   *          ...
-   *          sum of Y in all process dumps in global dump N
-   *    ]
-   *
-   *    memory:chrome:all_processes:source:proportional_resident_size :
-   *        Histogram aggregated over [
-   *          sum of X:A+X:B+Y in all process dumps in global dump 1,
-   *          ...
-   *          sum of X:A+X:B+Y in all process dumps in global dump N
-   *        ]
-   *
-   * where global dumps 1 to N are the global dumps associated with the given
-   * browser.
-   *
-   * @param {!Map<string, !Array<!tr.model.GlobalMemoryDump>}
-   *     browserNameToGlobalDumps Map from browser names to arrays of global
-   *     memory dumps. The generic values will be extracted from the associated
-   *     process memory dumps.
-   * @param {!function(!tr.model.GlobalMemoryDump): boolean}
-   *     customGlobalDumpFilter Predicate for filtering global memory dumps.
-   * @param {!function(
-   *     !tr.model.ProcessMemoryDump,
-   *     !function(!{
-   *         source: string,
-   *         componentPath: (!Array<string>|undefined),
-   *         propertyName: (string|undefined),
-   *         value: (!tr.v.Histogram|number|undefined),
-   *         unit: (!tr.b.Unit|undefined),
-   *         descriptionPrefixBuilder: (!function(!Array<string>): string)
-   *     }))}
-   *     customProcessDumpValueExtractor Callback for extracting values from a
-   *     process memory dump.
-   * @param {!function(!tr.b.MultiDimensionalViewNode)}
-   *     customComponentTreeModifier Callback applied to every component tree
-   *     wrt each process name.
-   * @param {!tr.v.ValueSet} values List of values to which the
-   *     resulting aggregated values are added.
-   */
-  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);
-    });
-  }
-
-  /**
-   * For each global memory dump in |globalDumps|, calculate per-process-name
-   * sums of values extracted by |customProcessDumpValueExtractor| from the
-   * associated process memory dumps.
-   *
-   * This function returns the following nested map structure:
-   *
-   *  Source name (Map key, e.g. 'reported_by_os')
-   *    -> Property name (Map key, e.g. 'proportional_resident_size')
-   *      -> {unit, descriptionPrefixBuilder, processAndComponentTreeBuilder}
-   *
-   *  where |processAndComponentTreeBuilder| is a
-   *  tr.b.MultiDimensionalViewBuilder:
-   *
-   *  Browser name (0th dimension key, e.g. 'webview') x
-   *    -> Component path (1st dimension keys, e.g. ['system', 'native_heap'])
-   *      -> Sum of value over the processes (number).
-   *
-   * See addMemoryDumpValues for more details.
-   */
-  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) {
-    // Process name is typically 'browser', 'renderer', etc.
-    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 /* dimensions (process name and component path) */
-          , dumpCount /* valueCount */),
-          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] /* path */, values, tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL /* valueKind */);
-    });
-  }
-
-  function reportDataAsValues(sourceToPropertyToData, browserName, customComponentTreeModifier, values) {
-    // For each source name (e.g. 'reported_by_os')...
-    sourceToPropertyToData.forEach(function (propertyToData, sourceName) {
-      // For each property name (e.g. 'effective_size')...
-      propertyToData.forEach(function (data, propertyName) {
-        var tree = data.processAndComponentTreeBuilder.buildTopDownTreeView();
-        var unit = data.unit;
-        var descriptionPrefixBuilder = data.descriptionPrefixBuilder;
-
-        // Total over 'all' processes...
-        customComponentTreeModifier(tree);
-        reportComponentDataAsValues(browserName, sourceName, propertyName, 'all_processes', [] /* componentPath */, tree, unit, descriptionPrefixBuilder, values);
-
-        // For each process name (e.g. 'renderer')...
-        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, [] /* componentPath */, processTree, unit, descriptionPrefixBuilder, values);
-        });
-      });
-    });
-  }
-
-  /**
-   * For the given |browserName| (e.g. 'chrome'), |processName|
-   * (e.g. 'gpu_process'), |propertyName| (e.g. 'effective_size'),
-   * |componentPath| (e.g. ['v8']), add a tr.v.Histogram with |unit| aggregating
-   * the total values of the associated |componentNode| across all timestamps
-   * (corresponding to global memory dumps associated with the given browser)
-   * to |values|.
-   *
-   * See addMemoryDumpValues for more details.
-   */
-  function reportComponentDataAsValues(browserName, sourceName, propertyName, processName, componentPath, componentNode, unit, descriptionPrefixBuilder, values) {
-    // Construct the name of the memory value.
-    var nameParts = ['memory', browserName, processName, sourceName].concat(componentPath);
-    if (propertyName !== undefined) nameParts.push(propertyName);
-    var name = nameParts.join(':');
-
-    // Build the underlying numeric for the memory value.
-    var numeric = buildMemoryNumericFromNode(name, componentNode, unit);
-
-    // Build the options for the memory value.
-    numeric.description = [descriptionPrefixBuilder(componentPath, processName), 'in', convertBrowserNameToUserFriendlyName(browserName)].join(' ');
-
-    // Report the memory value.
-    values.addHistogram(numeric);
-
-    // Recursively report memory values for sub-components.
-    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();
-  }
-
-  /**
-   * Create a memory tr.v.Histogram with |unit| and add all total values in
-   * |node| to it.
-   */
-  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
-  };
-});
+"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":41,"../../base/multi_dimensional_view.js":43,"../../base/range.js":47,"../../base/unit.js":57,"../../model/container_memory_dump.js":109,"../../model/helpers/chrome_model_helper.js":127,"../../model/memory_allocator_dump.js":134,"../../value/histogram.js":189,"../metric_registry.js":83}],91:[function(require,module,exports){
+},{"../../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";
-/**
-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.
-**/
-
-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 () {
-
-  // TODO(alexandermont): Per-frame power metric will be deprecated once
-  // newer metrics come online.
-  // Frame rate, used to divide power sample interval into frames
-  // for purposes of per-frame power metric.
-  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);
-
-  /**
-   * Returns power data for the specified interval in the form:
-   * {
-   *   duration: durationInMs,
-   *   energy: energyInJ,
-   *   power: powerInW
-   * }
-   */
-  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 };
-  }
-
-  // TODO(alexandermont): When LoadExpectation v1.0 is released,
-  // update this function to use the new LoadExpectation rather
-  // than calling loading_metric.html. If we set the end of the loading
-  // RAIL stage to be the TTI, then we may not even need to treat the loading
-  // events separately; we can just treat them like any other RAIL stage
-  // (and the RAIL stage boundaries will be the intervals that we want.)
-  /**
-   * Returns the intervals of time between navigation event and time to
-   * interactive.
-   */
-  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);
-  }
-
-  /**
-   * Creates a histogram suitable for time data.
-   */
-  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;
-  }
-
-  /**
-   * Creates a histogram suitable for energy data.
-   */
-  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;
-  }
-
-  /**
-   * Creates a histogram suitable for power data.
-   */
-  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;
-  }
-
-  /**
-   * Stores the power data in data into the given histograms for time, energy,
-   * and power. If a histogram is undefined then the corresponding type of
-   * data is not stored.
-   *
-   * @param {!Object} data - Power data (obtained from getPowerData_)
-   * @param {tr.v.Histogram} timeHist - Histogram to store time data.
-   * @param {tr.v.Histogram} energyHist - Histogram to store energy data.
-   * @param {tr.v.Histogram} powerHist - Histogram to store power data.
-   */
-  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 = {};
-
-    // "Generic" RAIL stage metrics. These give time, energy, and power
-    // for each RAIL stage, indexed by name. For instance, "Tap Animation"
-    // is different from "Tap, Touch Animation". There is one histogram
-    // for each RAIL stage name; if there are multiple RAIL stages with
-    // the same name, these are different samples in the histogram.
-    hists.railStageToTimeHist = new Map();
-    hists.railStageToEnergyHist = new Map();
-    hists.railStageToPowerHist = new Map();
-
-    // Metrics for scrolling. A scroll stage is any stage with the
-    // string "Scroll" in its name. For instance, "Scroll Response",
-    // "Scroll Animation", and "Scroll, Touch Animation" are all
-    // scroll stages. Histograms for scroll metrics contain one
-    // sample for each scroll stage.
-    hists.scrollTimeHist = makeTimeHistogram_(values, 'scroll', 'scrolling');
-    hists.scrollEnergyHist = makeEnergyHistogram_(values, 'scroll', 'scrolling');
-    hists.scrollPowerHist = makePowerHistogram_(values, 'scroll', 'scrolling');
-
-    // Metrics for loading. Loading intervals are defined by the intervals
-    // between navigation and TTI (time-to-interactive) given by
-    // getNavigationTTIIntervals_. We also have a metric for the energy
-    // consumed after load.
-    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');
-
-    // Metrics for video. A video stage is any stage with the string "Video"
-    // in its name. Histograms for video metrics contain one sample for each
-    // video stage. Only power metrics are available for video stages.
-    hists.videoPowerHist = makePowerHistogram_(values, 'video', 'video playback');
-
-    // Frame based power metric.
-    hists.frameEnergyHist = makeEnergyHistogram_(values, 'per_frame', 'each frame');
-
-    for (var exp of model.userModel.expectations) {
-      var currTitle = exp.title.toLowerCase().replace(' ', '_');
-      // If we haven't seen a RAIL stage with this title before,
-      // we have to create a new set of histograms for the "generic"
-      // RAIL stage metrics.
-      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;
-  }
-
-  /**
-   * Process a single interaction record (RAIL stage) for power metric
-   * purposes. This function only keeps track of metrics that are based
-   * on the start and end time of the RAIL stages.
-   */
-  function processInteractionRecord_(exp, model, hists) {
-    var currTitle = exp.title.toLowerCase().replace(' ', '_');
-    var data = getPowerData_(model, exp.start, exp.end);
-
-    // Add the samples for the "generic" RAIL stage metrics.
-    storePowerData_(data, hists.railStageToTimeHist.get(currTitle), hists.railStageToEnergyHist.get(currTitle), hists.railStageToPowerHist.get(currTitle));
-
-    // If this is a scroll stage, add the sample for the scroll metrics.
-    if (exp.title.indexOf("Scroll") !== -1) {
-      storePowerData_(data, hists.scrollTimeHist, hists.scrollEnergyHist, hists.scrollPowerHist);
-    }
-
-    // If this is a video stage, add the sample for the video metrics.
-    if (exp.title.indexOf("Video") !== -1) storePowerData_(data, undefined, undefined, hists.videoPowerHist);
-  }
-
-  /**
-   * Compute the loading power metric from the model and put the results
-   * in |hists|. Note that this is not in processInteractionRecord_ because
-   * the loading metric intervals don't correspond exactly to the RAIL stages.
-   */
-  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);
-    }
-  }
-
-  /**
-   * Compute the per-frame power metrics and put the results in |hists|.
-   */
-  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);
-
-    // The following two metrics aren't based directly on the IR intervals,
-    // and so need to be computed outside the processInteractionRecord_ loop.
-    computeLoadingMetric_(model, hists);
-    computeFrameBasedPowerMetric_(model, hists);
-  }
-
-  tr.metrics.MetricRegistry.register(powerMetric);
-
-  return {
-    powerMetric: powerMetric
-  };
-});
+"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":53,"../../value/histogram.js":189,"../metric_registry.js":83,"./loading_metric.js":88}],92:[function(require,module,exports){
+},{"../../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";
-/**
-Copyright (c) 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.
-**/
-
-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 () {
-  // In the case of Response, Load, and DiscreteAnimation IRs, Responsiveness is
-  // derived from the time between when the user thinks they begin an interation
-  // (expectedStart) and the time when the screen first changes to reflect the
-  // interaction (actualEnd).  There may be a delay between expectedStart and
-  // when chrome first starts processing the interaction (actualStart) if the
-  // main thread is busy.  The user doesn't know when actualStart is, they only
-  // know when expectedStart is. User responsiveness, by definition, considers
-  // only what the user experiences, so "duration" is defined as actualEnd -
-  // expectedStart.
-
-  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);
-  }
-
-  /**
-   * @param {!tr.v.ValueSet} values
-   * @param {!tr.model.Model} model
-   * @param {!Object=} opt_options
-   */
-  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]) });
-
-      // Responsiveness is not defined for Idle or Startup expectations.
-      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) {
-        // This is already covered by loadingMetric.
-      } 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) {
-          // Ignore animation stages that do not have associated frames:
-          // https://github.com/catapult-project/catapult/issues/2446
-          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
-  };
-});
+"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":53,"../../model/user_model/animation_expectation.js":161,"../../model/user_model/load_expectation.js":163,"../../model/user_model/response_expectation.js":164,"../../value/histogram.js":189,"../metric_registry.js":83,"./utils.js":94}],93:[function(require,module,exports){
+},{"../../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";
-/**
-Copyright (c) 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.
-**/
-
-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
-  };
-});
+"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":86,"./hazard_metric.js":87,"./long_tasks_metric.js":89,"./power_metric.js":91}],94:[function(require,module,exports){
+},{"./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";
-/**
-Copyright (c) 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.
-**/
-
-require("../../model/user_model/user_expectation.js");
-
-'use strict';
-
-global.tr.exportTo('tr.metrics.sh', function () {
-  // Returns a weight for this score.
-  // score should be a number between 0 and 1 inclusive.
-  // This function is expected to be passed to tr.b.Statistics.weightedMean as
-  // its weightCallback.
-  function perceptualBlend(ir, index, score) {
-    // Lower scores are exponentially more important than higher scores
-    // due to the Peak-end rule.
-    // Other than that general rule, there is no specific reasoning behind this
-    // specific formula -- it is fairly arbitrary.
-    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
-  };
-});
+"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":166}],95:[function(require,module,exports){
+},{"../../model/user_model/user_expectation.js":172}],101:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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';
-
-    // TODO(alexandermont): Only iterate over the processes and threads that
-    // could contain these events.
-    for (var slice of model.getDescendantEvents()) {
-      if (!(slice instanceof tr.model.ThreadSlice)) continue;
-
-      // WebViewStartupInterval is the title of the section of code that is
-      // entered (via android.os.Trace.beginSection) when WebView is started
-      // up. This value is defined in TelemetryActivity.java.
-      if (slice.title === 'WebViewStartupInterval') {
-        startupWallHist.addSample(slice.duration);
-        startupCPUHist.addSample(slice.cpuDuration);
-      }
-
-      // WebViewBlankUrlLoadInterval is the title of the section of code
-      // that is entered (via android.os.Trace.beginSection) when WebView
-      // is started up. This value is defined in TelemetryActivity.java.
-      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
-  };
-});
+"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":189,"../metric_registry.js":83,"./utils.js":94}],96:[function(require,module,exports){
+},{"../../value/histogram.js":195,"../metric_registry.js":89,"./utils.js":100}],102:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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);
-    }
-
-    // Adds values specific to memory-infra dumps.
-    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);
-
-        // We maintain a sliding window of records [start ... end-1] where end
-        // increments each time through the loop, and we move start just far enough
-        // to keep the window less than 1 second wide. Note that we need to compute
-        // the number of time intervals (i.e. units that timeInterval is given in)
-        // in one second to know how wide the sliding window should be.
-        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) {
-            // Slide the end marker forward. Moving the end marker from N
-            // to N+1 adds eventStats[N] to the sliding window.
-            runningEventNumPerSec += eventStats[end].numEvents;
-            runningEventBytesPerSec += eventStats[end].totalEventSizeinBytes;
-            end++;
-
-            // Slide the start marker forward so that the time interval covered
-            // by the window is less than 1 second wide.
-            while (eventStats[end - 1].timeInterval - eventStats[start].timeInterval >= INTERVALS_PER_SEC) {
-                runningEventNumPerSec -= eventStats[start].numEvents;
-                runningEventBytesPerSec -= eventStats[start].totalEventSizeinBytes;
-                start++;
-            }
-
-            // Update maximum values.
-            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());
-
-        // Determine the category with the highest total event size.
-        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,
-        // For testing only:
-        MEMORY_INFRA_TRACING_CATEGORY: MEMORY_INFRA_TRACING_CATEGORY
-    };
-});
+"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":41,"../value/diagnostics/diagnostic_map.js":179,"../value/histogram.js":189,"./metric_registry.js":83}],97:[function(require,module,exports){
+},{"../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";
-/**
-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.
-**/
-
-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';
-    // TODO(eakuefner): Stop computing overall values once dash v2 is ready.
-    // https://github.com/catapult-project/catapult/issues/2180
-    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
-  };
-});
+"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":47,"../../base/unit.js":57,"../../value/histogram.js":189,"../metric_registry.js":83}],98:[function(require,module,exports){
+},{"../../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";
-/**
-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.
-**/
-
-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 () {
-  // The time window size for mutator utilization computation.
-  // It is equal to the duration of one frame corresponding to 60 FPS rendering.
-  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;
-
-  // 0.1 steps from 0 to 20 since it is the most common range.
-  // Exponentially increasing steps from 20 to 200.
-  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) {
-    // We exclude garbage collection events forced by benchmark runner,
-    // because they cannot happen in real world.
-    return tr.metrics.v8.utils.isTopGarbageCollectionEvent(event) && !tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event);
-  }
-
-  function isNotForcedSubGarbageCollectionEvent(event) {
-    // We exclude garbage collection events forced by benchmark runner,
-    // because they cannot happen in real world.
-    return tr.metrics.v8.utils.isSubGarbageCollectionEvent(event) && !tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event);
-  }
-
-  /**
-   * Example output:
-   * - v8-gc-full-mark-compactor.
-   */
-  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);
-    });
-  }
-
-  /**
-   * Example output:
-   * - v8-gc-total
-   */
-  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);
-    });
-  }
-
-  /**
-   * Example output:
-   * - v8-gc-full-mark-compactor-evacuate.
-   */
-  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);
-    });
-  }
-
-  /**
-   * Example output:
-   * - v8-gc-full-mark-compactor_idle_deadline_overrun,
-   * - v8-gc-full-mark-compactor_outside_idle,
-   * - v8-gc-full-mark-compactor_percentage_idle.
-   */
-  function addIdleTimesOfTopEvents(values, model) {
-    tr.metrics.v8.utils.groupAndProcessEvents(model, isNotForcedTopGarbageCollectionEvent, tr.metrics.v8.utils.topGarbageCollectionEventName, function (name, events) {
-      addIdleTimes(values, model, name, events);
-    });
-  }
-
-  /**
-   * Example output:
-   * - v8-gc-total_idle_deadline_overrun,
-   * - v8-gc-total_outside_idle,
-   * - v8-gc-total_percentage_idle.
-   */
-  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;
-          // Don't count time over the deadline as being inside idle time.
-          // Since the deadline should be relative to wall clock we
-          // compare allotted_time_ms with wall duration instead of thread
-          // duration, and then assume the thread duration was inside idle
-          // for the same percentage of time.
-          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);
-  }
-
-  /**
-   * Example output:
-   * - v8-gc-full-mark-compactor_percentage_in_v8_execute.
-   */
-  function addPercentageInV8ExecuteOfTopEvents(values, model) {
-    tr.metrics.v8.utils.groupAndProcessEvents(model, isNotForcedTopGarbageCollectionEvent, tr.metrics.v8.utils.topGarbageCollectionEventName, function (name, events) {
-      addPercentageInV8Execute(values, model, name, events);
-    });
-  }
-
-  /**
-   * Example output:
-   * - v8-gc-total_percentage_in_v8_execute.
-   */
-  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 = [];
-      // Glue together the v8.execute events and adjust the GC pause
-      // times accordingly.
-      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;
-      }
-      // Now we have one big v8.execute interval from 0 to |time| and
-      // a list of GC pauses.
-      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 // For testing purposes only.
-  };
-});
+"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":47,"../../base/unit.js":57,"../../value/histogram.js":189,"../metric_registry.js":83,"./utils.js":99}],99:[function(require,module,exports){
+},{"../../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";
-/**
-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.
-**/
-
-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 () {
-  // The title of the idle task event.
-  var IDLE_TASK_EVENT = 'SingleThreadIdleTaskRunner::RunTask';
-
-  // V8 execution event.
-  var V8_EXECUTE = 'V8.Execute';
-
-  // GC events start with this prefix.
-  var GC_EVENT_PREFIX = 'V8.GC';
-
-  // Special handling is required for full GCs inside low memory notification.
-  var FULL_GC_EVENT = 'V8.GCCompactor';
-
-  var LOW_MEMORY_EVENT = 'V8.GCLowMemoryNotification';
-
-  var MAJOR_GC_EVENT = 'MajorGC';
-  var MINOR_GC_EVENT = 'MinorGC';
-
-  // Maps the top-level GC events in timeline to telemetry friendly names.
-  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';
-
-  /**
-   * Finds the first parent of the |event| for which the |predicate| holds.
-   */
-  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) {
-    // Low memory notification is handled specially because it contains
-    // several full mark compact events.
-    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) {
-    // To reduce number of results, we return only the first level of GC
-    // subevents. Some subevents are nested in MajorGC or MinorGC events, so
-    // we have to check for it explicitly.
-    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) {
-      // Full mark compact events inside a low memory notification
-      // are counted as low memory mark compacts.
-      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';
-    // Remove redundant prefixes and convert to lower case.
-    var name = event.title.replace('V8.GC_MC_', '').replace('V8.GC_SCAVENGER_', '').replace('V8.GC_', '').replace(/_/g, '-').toLowerCase();
-    return prefix + '-' + name;
-  }
-
-  /**
-   * Filters events using the |filterCallback|, then groups events by the user
-   * the name computed using the |nameCallback|, and then invokes
-   * the |processCallback| with the grouped events.
-   * @param {Function} filterCallback Takes an event and returns a boolean.
-   * @param {Function} nameCallback Takes event and returns a string.
-   * @param {Function} processCallback Takes a name, and an array of events.
-   */
-  function groupAndProcessEvents(model, filterCallback, nameCallback, processCallback) {
-    // Map: name -> [events].
-    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);
-    });
-  }
-
-  /**
-  * Given a list of intervals, returns a new list with all overalapping
-  * intervals merged into a single interval.
-  */
-  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)
-      };
-    });
-  }
-
-  /**
-   * An end-point of a window that is sliding from left to right
-   * over |points| starting from time |start|.
-   * It is intended to be used only by the |mutatorUtilization| function.
-   * @constructor
-   */
-  function WindowEndpoint(start, points) {
-    this.points = points;
-    // The index of the last passed point.
-    this.lastIndex = -1;
-    // The position of the end-point in the time line.
-    this.position = start;
-    // The distance until the next point.
-    this.distanceUntilNextPoint = points[0].position - start;
-    // The cumulative duration of GC pauses until this position.
-    this.cummulativePause = 0;
-    // The number of entered GC intervals.
-    this.stackDepth = 0;
-  }
-
-  WindowEndpoint.prototype = {
-    // Advance the end-point by the given |delta|.
-    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;
-        }
-      }
-    }
-  };
-
-  /**
-   * Returns mutator utilization as a piecewise linear function.
-   * Mutator utilization for a window size w is a function of time mu_w(t)
-   * that shows how much time in [t, t+w] is left for the mutator relative
-   * to the time window size.
-   * More formally:
-   * mu_w(t) = (w - total_time_spent_in_gc_in(t, t + w)) / w.
-   * The range of mu_w(t) is [0..1].
-   * See "A Parallel, Real-Time Garbage Collector" by Cheng et. al. for
-   * more info [https://www.cs.cmu.edu/~guyb/papers/gc2001.pdf].
-   *
-   * All parameters must use the same time unit.
-   * @param {number} start The start time of execution.
-   * @param {number} end The end time of execution.
-   * @param {number} timeWindow The size of the time window.
-   * @param {!Array<!{start: number, end: number}>} intervals The list of
-   *     GC pauses.
-   */
-  function mutatorUtilization(start, end, timeWindow, intervals) {
-    var mu = new tr.b.PiecewiseLinearFunction();
-    // If the interval is smaller than the time window, then the function is
-    // empty.
-    if (end - start <= timeWindow) return mu;
-    // If there are GC pauses then the mutator utilization is 1.0.
-    if (intervals.length === 0) {
-      mu.push(start, 1.0, end - timeWindow, 1.0);
-      return mu;
-    }
-    intervals = unionOfIntervals(intervals);
-    // Create a point for the start and the end of each interval.
-    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 });
-    // The left and the right limit of the sliding window.
-    var left = new WindowEndpoint(start, points);
-    var right = new WindowEndpoint(start, points);
-    // Advance the right end-point until we get the correct window size.
-    while (right.position - left.position < timeWindow) right.advance(timeWindow - (right.position - left.position));
-    while (right.lastIndex < points.length) {
-      // Advance the window end-points by the largest possible amount
-      // without jumping over a point.
-      var distanceUntilNextPoint = Math.min(left.distanceUntilNextPoint, right.distanceUntilNextPoint);
-      var position1 = left.position;
-      var value1 = right.cummulativePause - left.cummulativePause;
-      left.advance(distanceUntilNextPoint);
-      right.advance(distanceUntilNextPoint);
-      // Add a new mutator utilization segment only if it is non-trivial.
-      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(); // Empty 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
-  };
-});
+"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":44,"../../base/range.js":47,"../../base/range_utils.js":48,"../../base/unit.js":57,"../../value/histogram.js":189,"../metric_registry.js":83}],100:[function(require,module,exports){
+},{"../../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";
-/**
-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.
-**/
-
-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
-  };
-});
+"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":83,"../system_health/memory_metric.js":90,"./execution_metric.js":97,"./gc_metric.js":98}],101:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 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.
-**/
-
-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
-  };
-});
+"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":57,"./event_info.js":118,"./event_set.js":120,"./timed_event.js":160}],102:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/extension_registry.js");
-require("../base/guid.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-  /**
-   * Annotation is a base class that represents all annotation objects that
-   * can be drawn on the timeline.
-   *
-   * @constructor
-   */
-  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_;
-    },
-
-    // Invoked by trace model when this annotation is removed.
-    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
-  };
-});
+"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":35,"../base/guid.js":39}],103:[function(require,module,exports){
+},{"../base/extension_registry.js":41,"../base/guid.js":45}],109:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/unit.js");
-require("./timed_event.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the AsyncSlice class.
- */
-global.tr.exportTo('tr.model', function () {
-  /**
-   * A AsyncSlice represents an interval of time during which an
-   * asynchronous operation is in progress. An AsyncSlice consumes no CPU time
-   * itself and so is only associated with Threads at its start and end point.
-   *
-   * @constructor
-   */
-  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 || '';
-    // We keep the original title from the trace file in originalTitle since
-    // some sub-classes, e.g. NetAsyncSlice, change the title field.
-    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;
-
-    // isTopLevel is set at import because only NESTABLE_ASYNC events might not
-    // be topLevel. All legacy async events are toplevel by definition.
-    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
-  };
-});
+"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":57,"./timed_event.js":160}],104:[function(require,module,exports){
+},{"../base/unit.js":63,"./timed_event.js":166}],110:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/guid.js");
-require("../base/range.js");
-require("./async_slice.js");
-require("./event_container.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the AsyncSliceGroup class.
- */
-global.tr.exportTo('tr.model', function () {
-  /**
-   * A group of AsyncSlices associated with a thread.
-   * @constructor
-   * @extends {tr.model.EventContainer}
-   */
-  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_;
-    },
-
-    /**
-     * Helper function that pushes the provided slice onto the slices array.
-     */
-    push: function (slice) {
-      slice.parentContainer = this.parentContainer;
-      this.slices.push(slice);
-      return slice;
-    },
-
-    /**
-     * @return {Number} The number of slices in this group.
-     */
-    get length() {
-      return this.slices.length;
-    },
-
-    /**
-     * Shifts all the timestamps inside this group forward by the amount
-     * specified, including all nested subSlices if there are any.
-     */
-    shiftTimestampsForward: function (amount) {
-      for (var sI = 0; sI < this.slices.length; sI++) {
-        var slice = this.slices[sI];
-        slice.start = slice.start + amount;
-        // Shift all nested subSlices recursively.
-        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);
-      }
-    },
-
-    /**
-     * Updates the bounds for this group based on the slices it contains.
-     */
-    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);
-      }
-    },
-
-    /**
-     * Gets the sub-groups in this A-S-G defined by the group titles.
-     *
-     * @return {Array} An array of AsyncSliceGroups where each group has
-     * slices that started on the same thread.
-     */
-    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* () {
-      // Async slices normally don't have sub-slices, and when they do,
-      // the sub-slice is specific to the type of async slice. Thus,
-      // it is not expected for sub-slices to themselves have sub-sub-slices,
-      // which is why we don't recurse into the sub-slices here.
-      for (var slice of this.slices) {
-        yield slice;
-        if (slice.subSlices) yield* slice.subSlices;
-      }
-    },
-
-    childEventContainers: function* () {}
-  };
-
-  return {
-    AsyncSliceGroup: AsyncSliceGroup
-  };
-});
+"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":39,"../base/range.js":47,"./async_slice.js":103,"./event_container.js":117}],105:[function(require,module,exports){
+},{"../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";
-/**
-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.
-**/
-
-require("../base/iteration_helpers.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-  var ClockDomainId = {
-    BATTOR: 'BATTOR',
-
-    // NOTE: Exists for backwards compatibility with old Chrome traces which
-    // didn't explicitly specify the clock being used.
-    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" isn't really a clock domain because Telemetry actually
-    // can use one of several clock domains, just like Chrome. However,
-    // because there's a chance that Telemetry is running off of the same
-    // clock as Chrome (e.g. LINUX_CLOCK_MONOTONIC) but on a separate device
-    // (i.e. on a host computer with Chrome running on an attached phone),
-    // there's a chance that Chrome and Telemetry will erroneously get put into
-    // the same clock domain. The solution for this is that clock domains should
-    // actually be some (unique_device_id, clock_id) tuple. For now, though,
-    // we'll hack around this by putting Telemetry into its own clock domain.
-    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]);
-
-  // The number of milliseconds above which the BattOr sync is no longer
-  // considered "fast", and it's more accurate to use the sync start timestamp
-  // instead of the normal sync timestamp due to a bug in the Chrome serial code
-  // making serial reads too slow.
-  var BATTOR_FAST_SYNC_THRESHOLD_MS = 3;
-
-  /**
-   * A ClockSyncManager holds clock sync markers and uses them to shift
-   * timestamps from agents' clock domains onto the model's clock domain.
-   *
-   * In this context, a "clock domain" is a single perspective on the passage
-   * of time. A single computer can have multiple clock domains because it
-   * can have multiple methods of retrieving a timestamp (e.g.
-   * clock_gettime(CLOCK_MONOTONIC) and clock_gettime(CLOCK_REALTIME) on Linux).
-   * Another common reason for multiple clock domains within a single trace
-   * is that traces can span devices (e.g. a laptop collecting a Chrome trace
-   * can have its power consumption recorded by a second device and the two
-   * traces can be viewed alongside each other).
-   *
-   * For more information on how to synchronize multiple time domains using this
-   * method, see: http://bit.ly/1OVkqju.
-   *
-   * @constructor
-   */
-  function ClockSyncManager() {
-    // A set of all domains seen by the ClockSyncManager.
-    this.domainsSeen_ = new Set();
-    this.markersBySyncId_ = new Map();
-    // transformerMapByDomainId_[fromDomainId][toDomainId] returns the function
-    // that converts timestamps in the "from" domain to timestamps in the "to"
-    // domain.
-    this.transformerMapByDomainId_ = {};
-  }
-
-  ClockSyncManager.prototype = {
-    /**
-     * Adds a clock sync marker to the list of known markers.
-     *
-     * @param {string} domainId The clock domain that the marker is in.
-     * @param {string} syncId The identifier shared by both sides of the clock
-     *                 sync marker.
-     * @param {number} startTs The time (in ms) at which the sync started.
-     * @param {number=} opt_endTs The time (in ms) at which the sync ended. If
-     *                  unspecified, it's assumed to be the same as the start,
-     *                  indicating an instantaneous sync.
-     */
-    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);
-    },
-
-    // TODO(charliea): Remove this once the clockSyncMetric is no longer using
-    // it.
-    get markersBySyncId() {
-      return this.markersBySyncId_;
-    },
-
-    /** @return {Set<String>} The string IDs of the domains seen so far. */
-    get domainsSeen() {
-      return this.domainsSeen_;
-    },
-
-    /**
-     * Returns a function that, given a timestamp in the domain with |domainId|,
-     * returns a timestamp in the model's clock domain.
-     *
-     * NOTE: All clock sync markers should be added before calling this function
-     * for the first time. This is because the first time that this function is
-     * called, a model clock domain is selected. This clock domain must have
-     * syncs connecting it with all other clock domains. If multiple clock
-     * domains are viable candidates, the one with the clock domain ID that is
-     * the first alphabetically is selected.
-     */
-    getModelTimeTransformer: function (domainId) {
-      return this.getModelTimeTransformerRaw_(domainId).fn;
-    },
-
-    /**
-     * Returns the error associated with the transformation given by
-     * |getModelTimeTransformer(domainId)|.
-     */
-    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;
-    },
-
-    /**
-     * Returns a function that, given a timestamp in the "from" domain, returns
-     * a timestamp in the "to" domain.
-     */
-    getTransformerBetween_: function (fromDomainId, toDomainId) {
-      // Do a breadth-first search from the "from" domain until we reach the
-      // "to" domain.
-      var visitedDomainIds = new Set();
-      // Keep a queue of nodes to visit, starting with the "from" domain.
-      var queue = [{
-        domainId: fromDomainId,
-        transformer: Transformer.IDENTITY
-      }];
-
-      while (queue.length > 0) {
-        // NOTE: Using a priority queue here would theoretically be much more
-        // efficient, but the actual performance difference is negligible given
-        // how few clock domains we have in a trace.
-        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;
-
-        // Add all nodes that are directly connected to this one to the queue.
-        for (var outgoingDomainId in outgoingTransformers) {
-          // We have two transformers: one to get us from the "from" domain to
-          // the current domain, and another to get us from the current domain
-          // to the next domain. By composing those two transformers, we can
-          // create one that gets us from the "from" domain to the next domain.
-          var toNextDomainTransformer = outgoingTransformers[outgoingDomainId];
-          var toCurrentDomainTransformer = current.transformer;
-
-          queue.push({
-            domainId: outgoingDomainId,
-            transformer: Transformer.compose(toNextDomainTransformer, toCurrentDomainTransformer)
-          });
-        }
-      }
-
-      return undefined;
-    },
-
-    /**
-     * Selects the domain to use as the model domain from among the domains
-     * with registered markers.
-     *
-     * This is necessary because some common domain must be chosen before all
-     * timestamps can be shifted onto the same domain.
-     *
-     * For the time being, preference is given to Chrome clock domains. If no
-     * Chrome clock domain is present, the first clock domain alphabetically
-     * is selected.
-     */
-    selectModelDomainId_: function () {
-      this.ensureAllDomainsAreConnected_();
-
-      // While we're migrating to the new clock sync system, we have to make
-      // sure to prefer the Chrome clock domain because legacy clock sync
-      // mechanisms assume that's the case.
-      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];
-    },
-
-    /** Throws an error if all domains are not connected. */
-    ensureAllDomainsAreConnected_: function () {
-      // NOTE: this is a ridiculously inefficient way to do this. Given how few
-      // clock domains we're likely to have, this shouldn't be a problem.
-      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;
-    },
-
-    /** Observer called each time that a clock domain is seen. */
-    onDomainSeen_: function (domainId) {
-      if (domainId === ClockDomainId.UNKNOWN_CHROME_LEGACY && !this.domainsSeen_.has(ClockDomainId.UNKNOWN_CHROME_LEGACY)) {
-        // UNKNOWN_CHROME_LEGACY was just seen for the first time: collapse it
-        // and the other Chrome clock domains into one.
-        //
-        // This makes sure that we don't have two separate clock sync graphs:
-        // one attached to UNKNOWN_CHROME_LEGACY and the other attached to the
-        // real Chrome clock domain.
-        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);
-    },
-
-    /**
-     * Observer called when a complete sync is made involving |marker1| and
-     * |marker2|.
-     */
-    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;
-      }
-    },
-
-    /** Makes timestamps in the two clock domains interchangeable. */
-    collapseDomains_: function (domain1Id, domain2Id) {
-      this.getOrCreateTransformerMap_(domain1Id)[domain2Id] = this.getOrCreateTransformerMap_(domain2Id)[domain1Id] = Transformer.IDENTITY;
-    },
-
-    /**
-     * Returns (and creates if it doesn't exist) the transformer map describing
-     * how to transform timestamps between directly connected clock domains.
-     */
-    getOrCreateTransformerMap_: function (domainId) {
-      if (!this.transformerMapByDomainId_[domainId]) this.transformerMapByDomainId_[domainId] = {};
-
-      return this.transformerMapByDomainId_[domainId];
-    }
-  };
-
-  /**
-   * A ClockSyncMarker is an internal entity that represents a marker in a
-   * trace log indicating that a clock sync happened at a specified time.
-   *
-   * If no end timestamp argument is specified in the constructor, it's assumed
-   * that the end timestamp is the same as the start (i.e. the clock sync
-   * was instantaneous).
-   */
-  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;
-    }
-  };
-
-  /**
-   * A Transformer encapsulates information about how to turn timestamps in one
-   * clock domain into timestamps in another. It also stores additional data
-   * about the maximum error involved in doing so.
-   */
-  function Transformer(fn, error) {
-    this.fn = fn;
-    this.error = error;
-  }
-
-  Transformer.IDENTITY = new Transformer(tr.b.identity, 0);
-
-  /**
-   * Given two transformers, creates a third that's a composition of the two.
-   *
-   * @param {function(Number): Number} aToB A function capable of converting a
-   *     timestamp from domain A to domain B.
-   * @param {function(Number): Number} bToC A function capable of converting a
-   *     timestamp from domain B to domain C.
-   *
-   * @return {function(Number): Number} A function capable of converting a
-   *     timestamp from domain A to domain C.
-   */
-  Transformer.compose = function (aToB, bToC) {
-    return new Transformer(ts => bToC.fn(aToB.fn(ts)), aToB.error + bToC.error);
-  };
-
-  /**
-   * Returns a function that, given a timestamp in |fromMarker|'s domain,
-   * returns a timestamp in |toMarker|'s domain.
-   */
-  Transformer.fromMarkers = function (fromMarker, toMarker) {
-    var fromTs = fromMarker.ts,
-        toTs = toMarker.ts;
-
-    // TODO(charliea): Usually, we estimate that the clock sync marker is
-    // issued by the agent exactly in the middle of the controller's start and
-    // end timestamps. However, there's currently a bug in the Chrome serial
-    // code that's making the clock sync ack for BattOr take much longer to
-    // read than it should (by about 8ms). This is causing the above estimate
-    // of the controller's sync timestamp to be off by a substantial enough
-    // amount that it makes traces hard to read. For now, make an exception
-    // for BattOr and just use the controller's start timestamp as the sync
-    // time. In the medium term, we should fix the Chrome serial code in order
-    // to remove this special logic and get an even more accurate estimate.
-    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
-  };
-});
+"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":41}],106:[function(require,module,exports){
+},{"../base/iteration_helpers.js":47}],112:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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
-  };
-});
+"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":171,"./annotation.js":102,"./location.js":133,"./rect_annotation.js":146}],107:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/base.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-
-  /**
-   * Indicates how much of a compound-event is selected [if any].
-   *
-   * The CompoundEventSelectionState enum is used with events that are
-   * directly selectable, but also have associated events, too, that can be
-   * selected. In this situation, there are a variety of different
-   * selected states other than just "yes, no". This enum encodes those
-   * various possible states.
-   */
-  var CompoundEventSelectionState = {
-    // Basic bit states.
-    NOT_SELECTED: 0,
-    EVENT_SELECTED: 0x1,
-    SOME_ASSOCIATED_EVENTS_SELECTED: 0x2,
-    ALL_ASSOCIATED_EVENTS_SELECTED: 0x4,
-
-    // Common combinations.
-    EVENT_AND_SOME_ASSOCIATED_SELECTED: 0x1 | 0x2,
-    EVENT_AND_ALL_ASSOCIATED_SELECTED: 0x1 | 0x4
-  };
-
-  return {
-    CompoundEventSelectionState: CompoundEventSelectionState
-  };
-});
+"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":28}],108:[function(require,module,exports){
+},{"../base/base.js":34}],114:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2012 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.
-**/
-
-require("../base/base.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-  return {
-    // Since the PID of the browser process is not known to the child processes,
-    // we let them use "pid_ref = -1" to reference an object created in the
-    // browser process.
-    BROWSER_PROCESS_PID_REF: -1,
-
-    // The default scope of object events, when not explicitly specified.
-    OBJECT_DEFAULT_SCOPE: 'ptr'
-  };
-});
+"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":28}],109:[function(require,module,exports){
+},{"../base/base.js":34}],115:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./timed_event.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the ContainerMemoryDump class.
- */
-global.tr.exportTo('tr.model', function () {
-  /**
-   * The ContainerMemoryDump represents an abstract container memory dump.
-   * @constructor
-   */
-  function ContainerMemoryDump(start) {
-    tr.model.TimedEvent.call(this, start);
-
-    this.levelOfDetail = undefined;
-
-    this.memoryAllocatorDumps_ = undefined;
-    this.memoryAllocatorDumpsByFullName_ = undefined;
-  };
-
-  /**
-   * Memory dump level of detail. See base::trace_event::MemoryDumpLevelOfDetail
-   * in the Chromium repository.
-   *
-   * @enum
-   */
-  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;
-
-      // Lazily generate the index if necessary.
-      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 () {
-      // Clear the index and generate it lazily.
-      this.memoryAllocatorDumpsByFullName_ = undefined;
-    },
-
-    iterateRootAllocatorDumps: function (fn, opt_this) {
-      if (this.memoryAllocatorDumps === undefined) return;
-      this.memoryAllocatorDumps.forEach(fn, opt_this || this);
-    }
-  };
-
-  return {
-    ContainerMemoryDump: ContainerMemoryDump
-  };
-});
+"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":160}],110:[function(require,module,exports){
+},{"./timed_event.js":166}],116:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/guid.js");
-require("../base/range.js");
-require("./counter_series.js");
-require("./event_container.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-
-  /**
-   * A container holding all series of a given type of measurement.
-   *
-   * As an example, if we're measuring the throughput of data sent over several
-   * USB connections, the throughput of each cable might be added as a separate
-   * series to a single counter.
-   *
-   * @constructor
-   * @extends {EventContainer}
-   */
-  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;
-    },
-
-    /**
-     * Obtains min, max, avg, values, start, and end for different series for
-     * a given counter
-     *     getSampleStatistics([0,1])
-     * The statistics objects that this returns are an array of objects, one
-     * object for each series for the counter in the form:
-     * {min: minVal, max: maxVal, avg: avgVal, start: startVal, end: endVal}
-     *
-     * @param {Array.<Number>} Indices to summarize.
-     * @return {Object} An array of statistics. Each element in the array
-     * has data for one of the series in the selected counter.
-     */
-    getSampleStatistics: function (sampleIndices) {
-      sampleIndices.sort();
-
-      var ret = [];
-      this.series_.forEach(function (series) {
-        ret.push(series.getStatistics(sampleIndices));
-      });
-      return ret;
-    },
-
-    /**
-     * Shifts all the timestamps inside this counter forward by the amount
-     * specified.
-     */
-    shiftTimestampsForward: function (amount) {
-      for (var i = 0; i < this.series_.length; ++i) this.series_[i].shiftTimestampsForward(amount);
-    },
-
-    /**
-     * Updates the bounds for this counter based on the samples it contains.
-     */
-    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;
-
-      // Sum the samples at each timestamp.
-      // Note, this assumes that all series have all timestamps.
-      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);
-      }
-    }
-  };
-
-  /**
-   * Comparison between counters that orders by parent.compareTo, then name.
-   */
-  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
-  };
-});
+"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":39,"../base/range.js":47,"./counter_series.js":112,"./event_container.js":117}],111:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 2013 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.
-**/
-
-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 () {
-
-  /**
-   * The value of a given measurement at a given time.
-   *
-   * As an example, if we're measuring the throughput of data sent over a USB
-   * connection, each counter sample might represent the instantaneous
-   * throughput of the connection at a given time.
-   *
-   * @constructor
-   * @extends {Event}
-   */
-  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
-  };
-});
+"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":41,"../base/sorted_array_utils.js":52,"../base/unit.js":57,"./event.js":116,"./event_registry.js":119}],112:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("./counter_sample.js");
-require("./event_container.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-  var CounterSample = tr.model.CounterSample;
-
-  /**
-   * A container holding all samples of a given measurement over time.
-   *
-   * As an example, a counter series might measure the throughput of data sent
-   * over a USB connection, with each sample representing the instantaneous
-   * throughput of the connection.
-   *
-   * @constructor
-   * @extends {EventContainer}
-   */
-  function CounterSeries(name, color) {
-    tr.model.EventContainer.call(this);
-
-    this.name_ = name;
-    this.color_ = color;
-
-    this.timestamps_ = [];
-    this.samples_ = [];
-
-    // Set by counter.addSeries
-    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
-  };
-});
+"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":111,"./event_container.js":117}],113:[function(require,module,exports){
+},{"./counter_sample.js":117,"./event_container.js":123}],119:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/range.js");
-require("./counter.js");
-require("./cpu_slice.js");
-require("./process_base.js");
-require("./thread_time_slice.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the Cpu class.
- */
-global.tr.exportTo('tr.model', function () {
-
-  var ColorScheme = tr.b.ColorScheme;
-  var Counter = tr.model.Counter;
-  var CpuSlice = tr.model.CpuSlice;
-
-  /**
-   * The Cpu represents a Cpu from the kernel's point of view.
-   * @constructor
-   */
-  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; // Set during createSubSlices
-
-    // Start timestamp of the last active thread.
-    this.lastActiveTimestamp_ = undefined;
-
-    // Identifier of the last active thread. On Linux, it's a pid while on
-    // Windows it's a thread id.
-    this.lastActiveThread_ = undefined;
-
-    // Name and arguments of the last active thread.
-    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) {
-      // All CpuSlices are toplevel since CpuSlices do not nest.
-      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);
-    },
-
-    /**
-     * @return {Counter} The counter on this CPU with the given category/name
-     * combination, creating it if it doesn't exist.
-     */
-    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];
-    },
-
-    /**
-     * @return {Counter} the counter on this CPU with the given category/name
-     * combination, or undefined if it doesn't exist.
-     */
-    getCounter: function (cat, name) {
-      var id = cat + '.' + name;
-      if (!this.counters[id]) return undefined;
-      return this.counters[id];
-    },
-
-    /**
-     * Shifts all the timestamps inside this CPU forward by the amount
-     * specified.
-     */
-    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);
-    },
-
-    /**
-     * Updates the range based on the current slices attached to the cpu.
-     */
-    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;
-    },
-
-    /*
-     * Returns the index of the slice in the CPU's slices, or undefined.
-     */
-    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;
-    },
-
-    /**
-     * Closes the thread running on the CPU. |endTimestamp| is the timestamp
-     * at which the thread was unscheduled. |args| is merged with the arguments
-     * specified when the thread was initially scheduled.
-     */
-    closeActiveThread: function (endTimestamp, args) {
-      // Don't generate a slice if the last active thread is the idle task.
-      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.');
-      }
-
-      // Merge |args| with |this.lastActiveArgs_|. If a key is in both
-      // dictionaries, the value from |args| is used.
-      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);
-
-      // Clear the last state.
-      this.lastActiveTimestamp_ = undefined;
-      this.lastActiveThread_ = undefined;
-      this.lastActiveName_ = undefined;
-      this.lastActiveArgs_ = undefined;
-    },
-
-    switchActiveThread: function (timestamp, oldThreadArgs, newThreadId, newThreadName, newThreadArgs) {
-      // Close the previous active thread and generate a slice.
-      this.closeActiveThread(timestamp, oldThreadArgs);
-
-      // Keep track of the new thread.
-      this.lastActiveTimestamp_ = timestamp;
-      this.lastActiveThread_ = newThreadId;
-      this.lastActiveName_ = newThreadName;
-      this.lastActiveArgs_ = newThreadArgs;
-    },
-
-    /**
-     * Returns the frequency statistics for this CPU;
-     * the returned object contains the frequencies as keys,
-     * and the duration at this frequency in milliseconds as the value,
-     * for the range that was specified.
-     */
-    getFreqStatsForRange: function (range) {
-      var stats = {};
-
-      function addStatsForFreq(freqSample, index) {
-        // Counters don't have an explicit end or duration;
-        // calculate the end by looking at the starting point
-        // of the next value in the series, or if that doesn't
-        // exist, assume this frequency is held until the end.
-        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;
-    }
-  };
-
-  /**
-   * Comparison between processes that orders by cpuNumber.
-   */
-  Cpu.compare = function (x, y) {
-    return x.cpuNumber - y.cpuNumber;
-  };
-
-  return {
-    Cpu: Cpu
-  };
-});
+"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":47,"./counter.js":110,"./cpu_slice.js":114,"./process_base.js":144,"./thread_time_slice.js":158}],114:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/range.js");
-require("./thread_time_slice.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the CpuSlice class.
- */
-global.tr.exportTo('tr.model', function () {
-
-  var Slice = tr.model.Slice;
-
-  /**
-   * A CpuSlice represents a slice of time on a CPU.
-   *
-   * @constructor
-   */
-  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
-  };
-});
+"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":47,"./thread_time_slice.js":158}],115:[function(require,module,exports){
+},{"../base/range.js":53,"./thread_time_slice.js":164}],121:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/guid.js");
-require("../base/range.js");
-require("./event_container.js");
-require("./power_series.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the Device class.
- */
-global.tr.exportTo('tr.model', function () {
-
-  /**
-   * Device represents the device-level objects in the model.
-   * @constructor
-   * @extends {tr.model.EventContainer}
-   */
-  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
-  };
-});
+"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":39,"../base/range.js":47,"./event_container.js":117,"./power_series.js":142}],116:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/guid.js");
-require("../base/range.js");
-require("./event_set.js");
-require("./selectable_item.js");
-require("./selection_state.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the Event class.
- */
-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;
-
-  /**
-   * An Event is the base type for any non-container, selectable piece
-   * of data in the trace model.
-   *
-   * @constructor
-   * @extends {SelectableItem}
-   */
-  function Event() {
-    SelectableItem.call(this, this /* modelItem */);
-    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;
-    },
-
-    // Empty by default. Lazily initialized on an instance in
-    // addAssociatedAlert(). See #1930.
-    associatedAlerts: IMMUTABLE_EMPTY_SET,
-
-    addAssociatedAlert: function (alert) {
-      if (this.associatedAlerts === IMMUTABLE_EMPTY_SET) this.associatedAlerts = new tr.model.EventSet();
-      this.associatedAlerts.push(alert);
-    },
-
-    // Adds the range of timestamps for this event to the specified range.
-    // If this is not overridden in subclass, it means that type of event
-    // doesn't have timestamps.
-    addBoundsToRange: function (range) {}
-  };
-
-  return {
-    Event: Event
-  };
-});
+"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":39,"../base/range.js":47,"./event_set.js":120,"./selectable_item.js":149,"./selection_state.js":150}],117:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/base.js");
-require("../base/guid.js");
-require("../base/range.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-
-  /**
-   * EventContainer is a base class for any class in the trace model that
-   * contains child events or child EventContainers.
-   *
-   * For all EventContainers, updateBounds() must be called after modifying the
-   * container's events if an up-to-date bounds is expected.
-   *
-   * @constructor
-   */
-  function EventContainer() {
-    this.guid_ = tr.b.GUID.allocateSimple();
-    this.important = true;
-    this.bounds_ = new tr.b.Range();
-  }
-
-  EventContainer.prototype = {
-    get guid() {
-      return this.guid_;
-    },
-
-    /**
-     * @return {String} A stable and unique identifier that describes this
-     * container's position in the event tree relative to the root. If an event
-     * container 'B' is a child to another event container 'A', then container
-     * B's stable ID would be 'A.B'.
-     */
-    get stableId() {
-      throw new Error('Not implemented');
-    },
-
-    /**
-     * Returns the bounds of the event container, which describe the range
-     * of timestamps for all ancestor events.
-     */
-    get bounds() {
-      return this.bounds_;
-    },
-
-    // TODO(charliea): A default implementation of this method could likely be
-    // provided that iterates throuch getDescendantEvents.
-    /**
-     * Updates the bounds of the event container. After updating, this.bounds
-     * will describe the range of timestamps of all ancestor events.
-     */
-    updateBounds: function () {
-      throw new Error('Not implemented');
-    },
-
-    // TODO(charliea): A default implementation of this method could likely be
-    // provided that iterates through getDescendantEvents.
-    /**
-     * Shifts the timestamps for ancestor events by 'amount' milliseconds.
-     */
-    shiftTimestampsForward: function (amount) {
-      throw new Error('Not implemented');
-    },
-
-    /**
-    * Returns an iterable of all child events.
-    */
-    childEvents: function* () {},
-
-    /**
-     * Returns an iterable of all events in this and descendant
-     * event containers.
-     */
-    getDescendantEvents: function* () {
-      yield* this.childEvents();
-      for (var container of this.childEventContainers()) yield* container.getDescendantEvents();
-    },
-
-    /**
-     * Returns an iterable of all child event containers.
-     */
-    childEventContainers: function* () {},
-
-    /**
-    * Returns an iterable containing this and all descendant event containers.
-    */
-    getDescendantEventContainers: function* () {
-      yield this;
-      for (var container of this.childEventContainers()) yield* container.getDescendantEventContainers();
-    },
-
-    /**
-     * Finds topmost slices in this container (see docstring for
-     * findTopmostSlices).
-     */
-    findTopmostSlicesInThisContainer: function* (eventPredicate, opt_this) {},
-
-    /**
-     * The findTopmostSlices* series of helpers find all topmost slices
-     * satisfying the given predicates.
-     *
-     * As an example, suppose we are trying to find slices named 'C', with the
-     * following thread:
-     *
-     *  -> |---C---| |-----D-----|
-     *       |-C-|      |---C---| <-
-     *
-     * findTopmostSlices would locate the pointed-to Cs, because the bottom C on
-     * the  left is not the topmost C, and the right one is, even though it is
-     * not itself a top-level slice.
-     */
-    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
-  };
-});
+"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":28,"../base/guid.js":39,"../base/range.js":47}],118:[function(require,module,exports){
+},{"../base/base.js":34,"../base/guid.js":45,"../base/range.js":53}],124:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/color_scheme.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-  var ColorScheme = tr.b.ColorScheme;
-
-  /**
-   * EventInfo is an annotation added to Events in order to document
-   * what they represent, and override their title/colorId values.
-   *
-   * TODO(ccraik): eventually support more complex structure/paragraphs.
-   *
-   * @param {string} title A user-visible title for the event.
-   * @param {string} description A user-visible description of the event.
-   * @param {Array} docLinks A list of Objects, each of the form
-   * {label: str, textContent: str, href: str}
-   *
-   * @constructor
-   */
-  function EventInfo(title, description, docLinks) {
-    this.title = title;
-    this.description = description;
-    this.docLinks = docLinks;
-    this.colorId = ColorScheme.getColorIdForGeneralPurposeString(title);
-  }
-
-  return {
-    EventInfo: EventInfo
-  };
-});
+"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":32}],119:[function(require,module,exports){
+},{"../base/color_scheme.js":38}],125:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/extension_registry.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the EventRegistry class.
- */
-global.tr.exportTo('tr.model', function () {
-  // Create the type registry.
-  function EventRegistry() {}
-
-  var options = new tr.b.ExtensionRegistryOptions(tr.b.BASIC_REGISTRY_MODE);
-  tr.b.decorateExtensionRegistry(EventRegistry, options);
-
-  // Enforce all options objects have the right fields.
-  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');
-
-    // Add a subtype registry to every event so that all events can be
-    // extended
-    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;
-  });
-
-  // Helper: lookup Events indexed by type name.
-  var eventsByTypeName = undefined;
-  EventRegistry.getEventTypeInfoByTypeName = function (typeName) {
-    if (eventsByTypeName === undefined) {
-      eventsByTypeName = {};
-      EventRegistry.getAllRegisteredTypeInfos().forEach(function (typeInfo) {
-        eventsByTypeName[typeInfo.metadata.name] = typeInfo;
-      });
-    }
-    return eventsByTypeName[typeName];
-  };
-
-  // Ensure eventsByTypeName stays current.
-  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
-  };
-});
+"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":35}],120:[function(require,module,exports){
+},{"../base/extension_registry.js":41}],126:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2012 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.
-**/
-
-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);
-
-  /**
-   * Represents a event set within a  and its associated set of tracks.
-   * @constructor
-   */
-  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 pushes only unique events.
-    // If an event has been already pushed, do nothing.
-    push: function (event) {
-      if (event.guid == undefined) throw new Error('Event must have a GUID');
-
-      if (!this.events_.has(event)) {
-        this.events_.add(event);
-        // Some uses of eventSet, particularly in tests, have Events as objects
-        // that don't have addBoundsToRange as a function. Thus we need to
-        // handle this case.
-        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) {
-      // Convert to array, then sort, then convert back
-      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;
-      });
-    },
-
-    /**
-     * @param {!function(!tr.model.Event):string} cb
-     * @param {*=} opt_this
-     * @return {!Object}
-     */
-    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
-  };
-});
+"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":33,"../base/guid.js":39,"../base/iteration_helpers.js":41,"../base/range.js":47,"./event_registry.js":119}],121:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/unit.js");
-require("./timed_event.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the Flow class.
- */
-global.tr.exportTo('tr.model', function () {
-  /**
-   * A Flow represents an interval of time plus parameters associated
-   * with that interval.
-   *
-   * @constructor
-   */
-  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
-  };
-});
+"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":57,"./timed_event.js":160}],122:[function(require,module,exports){
+},{"../base/unit.js":63,"./timed_event.js":166}],128:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/color_scheme.js");
-require("../base/statistics.js");
-require("./event.js");
-require("./event_set.js");
-
-'use strict';
-
-/**
- * @fileoverview Class describing rendered frames.
- *
- * Because a frame is produced by multiple threads, it does not inherit from
- * TimedEvent, and has no duration.
- */
-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'
-  };
-
-  /**
-   * @constructor
-   * @param {Array} associatedEvents Selection of events composing the frame.
-   * @param {Array} threadTimeRanges Array of {thread, start, end}
-   * for each thread, describing the critical path of the frame.
-   */
-  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
-  };
-});
+"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":32,"../base/statistics.js":53,"./event.js":116,"./event_set.js":120}],123:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 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.
-**/
-
-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';
-
-/**
- * @fileoverview Provides the GlobalMemoryDump class.
- */
-global.tr.exportTo('tr.model', function () {
-  /**
-   * The GlobalMemoryDump represents a simultaneous memory dump of all
-   * processes.
-   * @constructor
-   */
-  function GlobalMemoryDump(model, start) {
-    tr.model.ContainerMemoryDump.call(this, start);
-    this.model = model;
-    this.processMemoryDumps = {};
-  }
-
-  // Size numeric names.
-  var SIZE_NUMERIC_NAME = tr.model.MemoryAllocatorDump.SIZE_NUMERIC_NAME;
-  var EFFECTIVE_SIZE_NUMERIC_NAME = tr.model.MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME;
-
-  // Size numeric info types.
-  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;
-
-  // TODO(petrcermak): Move this to tracing/base/iteration_helpers.html.
-  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]; // Move elements only if necessary.
-      nextPosition++;
-    }
-
-    if (nextPosition < array.length) array.length = nextPosition; // Truncate the array only if necessary.
-  }
-
-  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 () {
-      // 1. Transitively remove weak memory allocator dumps and all their
-      // owners and descendants from the model. This must be performed before
-      // any other steps.
-      this.removeWeakDumps();
-
-      // 2. Add ownership links from tracing MADs to descendants of malloc or
-      // winheap MADs so that tracing would be automatically discounted from
-      // them later (step 3).
-      this.setUpTracingOverheadOwnership();
-
-      // 3. Aggregate all other numerics of all MADs (*excluding* sizes and
-      // effective sizes) and propagate numerics from global MADs to their
-      // owners (*including* sizes and effective sizes). This step must be
-      // carried out before the sizes of all MADs are calculated (step 3).
-      // Otherwise, the propagated sizes of all MADs would not be aggregated.
-      this.aggregateNumerics();
-
-      // 4. Calculate the sizes of all memory allocator dumps (MADs). This step
-      // requires that the memory allocator dump graph has been finalized (step
-      // 1) and numerics were propagated from global MADs (step 2). Subsequent
-      // modifications of the graph will most likely break the calculation
-      // invariants.
-      this.calculateSizes();
-
-      // 5. Calculate the effective sizes of all MADs. This step requires that
-      // the sizes of all MADs have already been calculated (step 3).
-      this.calculateEffectiveSizes();
-
-      // 6. Discount tracing from VM regions stats. This steps requires that
-      // resident sizes (step 2) and sizes (step 3) of the tracing MADs have
-      // already been calculated.
-      this.discountTracingOverheadFromVmRegions();
-
-      // 7. The above steps (especially steps 1 and 3) can create new memory
-      // allocator dumps, so we force rebuilding the memory allocator dump
-      // indices of all container memory dumps.
-      this.forceRebuildingMemoryAllocatorDumpByFullNameIndices();
-    },
-
-    removeWeakDumps: function () {
-      // Mark all transitive owners and children of weak memory allocator dumps
-      // as weak.
-      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) {
-            // The dump is weak, so remove it. This will implicitly remove all
-            // its descendants, which are also weak due to the initial marking
-            // step.
-            return false;
-          }
-
-          // This dump is non-weak, so keep it. Recursively remove its weak
-          // descendants and ownership links from weak dumps instead.
-          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);
-      });
-    },
-
-    /**
-     * Calculate the size of all memory allocator dumps in the dump graph.
-     *
-     * The size refers to the allocated size of a (sub)component. It is a
-     * natural extension of the optional size numeric provided by
-     * MemoryAllocatorDump(s):
-     *
-     *   - If a MAD provides a size numeric, then its size is assumed to be
-     *     equal to it.
-     *   - If a MAD does not provide a size numeric, then its size is assumed
-     *     to be the maximum of (1) the size of the largest owner of the MAD
-     *     and (2) the aggregated size of the MAD's children.
-     *
-     * Metric motivation: "How big is a (sub)system?"
-     *
-     * Please refer to the Memory Dump Graph Metric Calculation design document
-     * for more details (https://goo.gl/fKg0dt).
-     */
-    calculateSizes: function () {
-      this.traverseAllocatorDumpsInDepthFirstPostOrder(this.calculateMemoryAllocatorDumpSize_.bind(this));
-    },
-
-    /**
-     * Calculate the size of the given MemoryAllocatorDump. This method assumes
-     * that the size of both the children and owners of the dump has already
-     * been calculated.
-     */
-    calculateMemoryAllocatorDumpSize_: function (dump) {
-      // This flag becomes true if the size numeric of the current dump should
-      // be defined, i.e. if (1) the current dump's size numeric is defined,
-      // (2) the size of at least one of its children is defined or (3) the
-      // size of at least one of its owners is defined.
-      var shouldDefineSize = false;
-
-      // This helper function returns the value of the size numeric of the
-      // given dependent memory allocator dump. If the numeric is defined, the
-      // shouldDefineSize flag above is also set to true (because condition
-      // (2) or (3) is satisfied). Otherwise, zero is returned (and the flag is
-      // left unchanged).
-      function getDependencySize(dependencyDump) {
-        var numeric = dependencyDump.numerics[SIZE_NUMERIC_NAME];
-        if (numeric === undefined) return 0;
-        shouldDefineSize = true;
-        return numeric.value;
-      }
-
-      // 1. Get the size provided by the dump. If present, define a function
-      // for checking dependent size consistency (a dump must always be bigger
-      // than all its children aggregated together and/or its largest owner).
-      var sizeNumeric = dump.numerics[SIZE_NUMERIC_NAME];
-      var size = 0;
-      var checkDependencySizeIsConsistent = function () {/* no-op */};
-      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);
-      }
-
-      // 2. Aggregate size of children. The recursive function traverses all
-      // descendants and ensures that double-counting due to ownership within a
-      // subsystem is avoided.
-      var aggregatedChildrenSize = 0;
-      // Owned child dump name -> (Owner child dump name -> overlapping size).
-      var allOverlaps = {};
-      dump.children.forEach(function (childDump) {
-        function aggregateDescendantDump(descendantDump) {
-          // Don't count this descendant dump if it owns another descendant of
-          // the current dump (would cause double-counting).
-          var ownedDumpLink = descendantDump.owns;
-          if (ownedDumpLink !== undefined && ownedDumpLink.target.isDescendantOf(dump)) {
-            // If the target owned dump is a descendant of a *different* child
-            // of the the current dump (i.e. not childDump), then we remember
-            // the ownership so that we could explain why the size of the
-            // current dump is not equal to the sum of its children.
-            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 this descendant dump is a leaf node, add its size to the
-          // aggregated size.
-          if (descendantDump.children.length === 0) {
-            aggregatedChildrenSize += getDependencySize(descendantDump);
-            return;
-          }
-
-          // If this descendant dump is an intermediate node, recurse down into
-          // its children. Note that the dump's size is NOT added because it is
-          // an aggregate of its children (would cause double-counting).
-          descendantDump.children.forEach(aggregateDescendantDump);
-        }
-        aggregateDescendantDump(childDump);
-      });
-      checkDependencySizeIsConsistent(aggregatedChildrenSize, PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN, 'the aggregated size of its children');
-
-      // 3. Calculate the largest owner size.
-      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 neither the dump nor any of its dependencies (children and owners)
-      // provide a size, do NOT add a zero size numeric.
-      if (!shouldDefineSize) {
-        // The rest of the pipeline relies on size being either a valid
-        // ScalarNumeric, or undefined.
-        delete dump.numerics[SIZE_NUMERIC_NAME];
-        return;
-      }
-
-      // A dump must always be bigger than all its children aggregated
-      // together and/or its largest owner.
-      size = Math.max(size, aggregatedChildrenSize, largestOwnerSize);
-
-      dump.numerics[SIZE_NUMERIC_NAME] = new tr.v.ScalarNumeric(tr.b.Unit.byName.sizeInBytes_smallerIsBetter, size);
-
-      // Add a virtual child to make up for extra size of the dump with
-      // respect to its children (if applicable).
-      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);
-      }
-    },
-
-    /**
-     * Calculate the effective size of all memory allocator dumps in the dump
-     * graph.
-     *
-     * The effective size refers to the amount of memory a particular component
-     * is using/consuming. In other words, every (reported) byte of used memory
-     * is uniquely attributed to exactly one component. Consequently, unlike
-     * size, effective size is cumulative, i.e. the sum of the effective sizes
-     * of (top-level) components is equal to the total amount of (reported)
-     * used memory.
-     *
-     * Metric motivation: "How much memory does a (sub)system use?" or "For how
-     * much memory should a (sub)system be 'charged'?"
-     *
-     * Please refer to the Memory Dump Graph Metric Calculation design document
-     * for more details (https://goo.gl/fKg0dt).
-     *
-     * This method assumes that the size of all contained memory allocator
-     * dumps has already been calculated [see calculateSizes()].
-     */
-    calculateEffectiveSizes: function () {
-      // 1. Calculate not-owned and not-owning sub-sizes of all MADs
-      // (depth-first post-order traversal).
-      this.traverseAllocatorDumpsInDepthFirstPostOrder(this.calculateDumpSubSizes_.bind(this));
-
-      // 2. Calculate owned and owning coefficients of owned and owner MADs
-      // respectively (arbitrary traversal).
-      this.traverseAllocatorDumpsInDepthFirstPostOrder(this.calculateDumpOwnershipCoefficient_.bind(this));
-
-      // 3. Calculate cumulative owned and owning coefficients of all MADs
-      // (depth-first pre-order traversal).
-      this.traverseAllocatorDumpsInDepthFirstPreOrder(this.calculateDumpCumulativeOwnershipCoefficient_.bind(this));
-
-      // 4. Calculate the effective sizes of all MADs (depth-first post-order
-      // traversal).
-      this.traverseAllocatorDumpsInDepthFirstPostOrder(this.calculateDumpEffectiveSize_.bind(this));
-    },
-
-    /**
-     * Calculate not-owned and not-owning sub-sizes of a memory allocator dump
-     * from its children's (sub-)sizes.
-     *
-     * Not-owned sub-size refers to the aggregated memory of all children which
-     * is not owned by other MADs. Conversely, not-owning sub-size is the
-     * aggregated memory of all children which do not own another MAD. The
-     * diagram below illustrates these two concepts:
-     *
-     *     ROOT 1                         ROOT 2
-     *     size: 4                        size: 5
-     *     not-owned sub-size: 4          not-owned sub-size: 1 (!)
-     *     not-owning sub-size: 0 (!)     not-owning sub-size: 5
-     *
-     *      ^                              ^
-     *      |                              |
-     *
-     *     PARENT 1   ===== owns =====>   PARENT 2
-     *     size: 4                        size: 5
-     *     not-owned sub-size: 4          not-owned sub-size: 5
-     *     not-owning sub-size: 4         not-owning sub-size: 5
-     *
-     *      ^                              ^
-     *      |                              |
-     *
-     *     CHILD 1                        CHILD 2
-     *     size [given]: 4                size [given]: 5
-     *     not-owned sub-size: 4          not-owned sub-size: 5
-     *     not-owning sub-size: 4         not-owning sub-size: 5
-     *
-     * This method assumes that (1) the size of the dump, its children, and its
-     * owners [see calculateSizes()] and (2) the not-owned and not-owning
-     * sub-sizes of both the children and owners of the dump have already been
-     * calculated [depth-first post-order traversal].
-     */
-    calculateDumpSubSizes_: function (dump) {
-      // Completely skip dumps with undefined size.
-      if (!hasSize(dump)) return;
-
-      // If the dump is a leaf node, then both sub-sizes are equal to the size.
-      if (dump.children === undefined || dump.children.length === 0) {
-        var size = getSize(dump);
-        dump.notOwningSubSize_ = size;
-        dump.notOwnedSubSize_ = size;
-        return;
-      }
-
-      // Calculate this dump's not-owning sub-size by summing up the not-owning
-      // sub-sizes of children MADs which do not own another MAD.
-      var notOwningSubSize = 0;
-      dump.children.forEach(function (childDump) {
-        if (childDump.owns !== undefined) return;
-        notOwningSubSize += optional(childDump.notOwningSubSize_, 0);
-      });
-      dump.notOwningSubSize_ = notOwningSubSize;
-
-      // Calculate this dump's not-owned sub-size.
-      var notOwnedSubSize = 0;
-      dump.children.forEach(function (childDump) {
-        // If the child dump is not owned, then add its not-owned sub-size.
-        if (childDump.ownedBy.length === 0) {
-          notOwnedSubSize += optional(childDump.notOwnedSubSize_, 0);
-          return;
-        }
-        // If the child dump is owned, then add the difference between its size
-        // and the largest owner.
-        var largestChildOwnerSize = 0;
-        childDump.ownedBy.forEach(function (ownershipLink) {
-          largestChildOwnerSize = Math.max(largestChildOwnerSize, getSize(ownershipLink.source));
-        });
-        notOwnedSubSize += getSize(childDump) - largestChildOwnerSize;
-      });
-      dump.notOwnedSubSize_ = notOwnedSubSize;
-    },
-
-    /**
-     * Calculate owned and owning coefficients of a memory allocator dump and
-     * its owners.
-     *
-     * The owning coefficient refers to the proportion of a dump's not-owning
-     * sub-size which is attributed to the dump (only relevant to owning MADs).
-     * Conversely, the owned coefficient is the proportion of a dump's
-     * not-owned sub-size, which is attributed to it (only relevant to owned
-     * MADs).
-     *
-     * The not-owned size of the owned dump is split among its owners in the
-     * order of the ownership importance as demonstrated by the following
-     * example:
-     *
-     *                                          memory allocator dumps
-     *                                   OWNED  OWNER1  OWNER2  OWNER3  OWNER4
-     *       not-owned sub-size [given]     10       -       -       -       -
-     *      not-owning sub-size [given]      -       6       7       5       8
-     *               importance [given]      -       2       2       1       0
-     *    attributed not-owned sub-size      2       -       -       -       -
-     *   attributed not-owning sub-size      -       3       4       0       1
-     *                owned coefficient   2/10       -       -       -       -
-     *               owning coefficient      -     3/6     4/7     0/5     1/8
-     *
-     * Explanation: Firstly, 6 bytes are split equally among OWNER1 and OWNER2
-     * (highest importance). OWNER2 owns one more byte, so its attributed
-     * not-owning sub-size is 6/2 + 1 = 4 bytes. OWNER3 is attributed no size
-     * because it is smaller than the owners with higher priority. However,
-     * OWNER4 is larger, so it's attributed the difference 8 - 7 = 1 byte.
-     * Finally, 2 bytes remain unattributed and are hence kept in the OWNED
-     * dump as attributed not-owned sub-size. The coefficients are then
-     * directly calculated as fractions of the sub-sizes and corresponding
-     * attributed sub-sizes.
-     *
-     * Note that we always assume that all ownerships of a dump overlap (e.g.
-     * OWNER3 is subsumed by both OWNER1 and OWNER2). Hence, the table could
-     * be alternatively represented as follows:
-     *
-     *                                 owned memory range
-     *              0   1   2    3    4    5    6        7        8   9  10
-     *   Priority 2 |  OWNER1 + OWNER2 (split)  | OWNER2 |
-     *   Priority 1 | (already attributed) |
-     *   Priority 0 | - - -  (already attributed)  - - - | OWNER4 |
-     *    Remainder | - - - - - (already attributed) - - - - - -  | OWNED |
-     *
-     * This method assumes that (1) the size of the dump [see calculateSizes()]
-     * and (2) the not-owned size of the dump and not-owning sub-sizes of its
-     * owners [see the first step of calculateEffectiveSizes()] have already
-     * been calculated. Note that the method doesn't make any assumptions about
-     * the order in which dumps are visited.
-     */
-    calculateDumpOwnershipCoefficient_: function (dump) {
-      // Completely skip dumps with undefined size.
-      if (!hasSize(dump)) return;
-
-      // We only need to consider owned dumps.
-      if (dump.ownedBy.length === 0) return;
-
-      // Sort the owners in decreasing order of ownership importance and
-      // increasing order of not-owning sub-size (in case of equal importance).
-      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;
-      });
-
-      // Loop over the list of owners and distribute the owned dump's not-owned
-      // sub-size among them according to their ownership importance and
-      // not-owning sub-size.
-      var currentImportanceStartPos = 0;
-      var alreadyAttributedSubSize = 0;
-      while (currentImportanceStartPos < owners.length) {
-        var currentImportance = owners[currentImportanceStartPos].importance;
-
-        // Find the position of the first owner with lower priority.
-        var nextImportanceStartPos = currentImportanceStartPos + 1;
-        while (nextImportanceStartPos < owners.length && owners[nextImportanceStartPos].importance === currentImportance) {
-          nextImportanceStartPos++;
-        }
-
-        // Visit the owners with the same importance in increasing order of
-        // not-owned sub-size, split the owned memory among them appropriately,
-        // and calculate their owning coefficients.
-        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;
-      }
-
-      // Attribute the remainder of the owned dump's not-owned sub-size to
-      // the dump itself and calculate its owned coefficient.
-      var notOwnedSubSize = optional(dump.notOwnedSubSize_, 0);
-      var remainderSubSize = notOwnedSubSize - alreadyAttributedSubSize;
-      var ownedCoefficient = 0;
-      if (notOwnedSubSize !== 0) ownedCoefficient = remainderSubSize / notOwnedSubSize;
-      dump.ownedCoefficient_ = ownedCoefficient;
-    },
-
-    /**
-     * Calculate cumulative owned and owning coefficients of a memory allocator
-     * dump from its (non-cumulative) owned and owning coefficients and the
-     * cumulative coefficients of its parent and/or owned dump.
-     *
-     * The cumulative coefficients represent the total effect of all
-     * (non-strict) ancestor ownerships on a memory allocator dump. The
-     * cumulative owned coefficient of a MAD can be calculated simply as:
-     *
-     *   cumulativeOwnedC(M) = ownedC(M) * cumulativeOwnedC(parent(M))
-     *
-     * This reflects the assumption that if a parent of a child MAD is
-     * (partially) owned, then the parent's owner also indirectly owns (a part
-     * of) the child MAD.
-     *
-     * The cumulative owning coefficient of a MAD depends on whether the MAD
-     * owns another dump:
-     *
-     *                           [if M doesn't own another MAD]
-     *                         / cumulativeOwningC(parent(M))
-     *   cumulativeOwningC(M) =
-     *                         \ [if M owns another MAD]
-     *                           owningC(M) * cumulativeOwningC(owned(M))
-     *
-     * The reasoning behind the first case is similar to the one for cumulative
-     * owned coefficient above. The only difference is that we don't need to
-     * include the dump's (non-cumulative) owning coefficient because it is
-     * implicitly 1.
-     *
-     * The formula for the second case is derived as follows: Since the MAD
-     * owns another dump, its memory is not included in its parent's not-owning
-     * sub-size and hence shouldn't be affected by the parent's corresponding
-     * cumulative coefficient. Instead, the MAD indirectly owns everything
-     * owned by its owned dump (and so it should be affected by the
-     * corresponding coefficient).
-     *
-     * Note that undefined coefficients (and coefficients of non-existent
-     * dumps) are implicitly assumed to be 1.
-     *
-     * This method assumes that (1) the size of the dump [see calculateSizes()],
-     * (2) the (non-cumulative) owned and owning coefficients of the dump [see
-     * the second step of calculateEffectiveSizes()], and (3) the cumulative
-     * coefficients of the dump's parent and owned MADs (if present)
-     * [depth-first pre-order traversal] have already been calculated.
-     */
-    calculateDumpCumulativeOwnershipCoefficient_: function (dump) {
-      // Completely skip dumps with undefined size.
-      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;
-    },
-
-    /**
-     * Calculate the effective size of a memory allocator dump.
-     *
-     * In order to simplify the (already complex) calculation, we use the fact
-     * that effective size is cumulative (unlike regular size), i.e. the
-     * effective size of a non-leaf node is equal to the sum of effective sizes
-     * of its children. The effective size of a leaf MAD is calculated as:
-     *
-     *   effectiveSize(M) = size(M) * cumulativeOwningC(M) * cumulativeOwnedC(M)
-     *
-     * This method assumes that (1) the size of the dump and its children [see
-     * calculateSizes()] and (2) the cumulative owning and owned coefficients
-     * of the dump (if it's a leaf node) [see the third step of
-     * calculateEffectiveSizes()] or the effective sizes of its children (if
-     * it's a non-leaf node) [depth-first post-order traversal] have already
-     * been calculated.
-     */
-    calculateDumpEffectiveSize_: function (dump) {
-      // Completely skip dumps with undefined size. As a result, each dump will
-      // have defined effective size if and only if it has defined size.
-      if (!hasSize(dump)) {
-        // The rest of the pipeline relies on effective size being either a
-        // valid ScalarNumeric, or undefined.
-        delete dump.numerics[EFFECTIVE_SIZE_NUMERIC_NAME];
-        return;
-      }
-
-      var effectiveSize;
-      if (dump.children === undefined || dump.children.length === 0) {
-        // Leaf dump.
-        effectiveSize = getSize(dump) * dump.cumulativeOwningCoefficient_ * dump.cumulativeOwnedCoefficient_;
-      } else {
-        // Non-leaf dump.
-        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 () {
-      // 1. Aggregate numerics in this global memory dump.
-      this.iterateRootAllocatorDumps(function (dump) {
-        dump.aggregateNumericsRecursively(this.model);
-      });
-
-      // 2. Propagate numerics and diagnostics from global memory allocator
-      // dumps to their owners.
-      this.iterateRootAllocatorDumps(this.propagateNumericsAndDiagnosticsRecursively);
-
-      // 3. Aggregate numerics in the associated process memory dumps.
-      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) {
-              // Numerics and diagnostics provided by process memory allocator
-              // dumps themselves have precedence over numerics and diagnostics
-              // propagated from global memory allocator dumps.
-              return;
-            }
-            processAllocatorDump[field][name] = value;
-          });
-        });
-      });
-
-      // Recursively propagate numerics from all child memory allocator dumps.
-      globalAllocatorDump.children.forEach(this.propagateNumericsAndDiagnosticsRecursively, this);
-    },
-
-    setUpTracingOverheadOwnership: function () {
-      tr.b.iterItems(this.processMemoryDumps, function (pid, dump) {
-        dump.setUpTracingOverheadOwnership(this.model);
-      }, this);
-    },
-
-    discountTracingOverheadFromVmRegions: function () {
-      // TODO(petrcermak): Consider factoring out all the finalization code and
-      // constants to a single file.
-      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);
-      });
-    },
-
-    /**
-     * Traverse the memory dump graph in a depth first post-order, i.e.
-     * children and owners of a memory allocator dump are visited before the
-     * dump itself. This method will throw an exception if the graph contains
-     * a cycle.
-     */
-    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);
-
-        // Visit owners before the dumps they own.
-        dump.ownedBy.forEach(function (ownershipLink) {
-          visit.call(this, ownershipLink.source);
-        }, this);
-
-        // Visit children before parents.
-        dump.children.forEach(visit, this);
-
-        // Actually visit the current memory allocator dump.
-        fn.call(this, dump);
-        visitedDumps.add(dump);
-
-        openDumps.delete(dump);
-      }
-
-      this.iterateAllRootAllocatorDumps(visit);
-    },
-
-    /**
-     * Traverse the memory dump graph in a depth first pre-order, i.e.
-     * children and owners of a memory allocator dump are visited after the
-     * dump itself. This method will not visit some dumps if the graph contains
-     * a cycle.
-     */
-    traverseAllocatorDumpsInDepthFirstPreOrder: function (fn) {
-      var visitedDumps = new WeakSet();
-
-      function visit(dump) {
-        if (visitedDumps.has(dump)) return;
-
-        // If this dumps owns another dump which hasn't been visited yet, then
-        // wait for this dump to be visited later.
-        if (dump.owns !== undefined && !visitedDumps.has(dump.owns.target)) return;
-
-        // If this dump's parent hasn't been visited yet, then wait for this
-        // dump to be visited later.
-        if (dump.parent !== undefined && !visitedDumps.has(dump.parent)) return;
-
-        // Actually visit the current memory allocator dump.
-        fn.call(this, dump);
-        visitedDumps.add(dump);
-
-        // Visit owners after the dumps they own.
-        dump.ownedBy.forEach(function (ownershipLink) {
-          visit.call(this, ownershipLink.source);
-        }, this);
-
-        // Visit children after parents.
-        dump.children.forEach(visit, this);
-      }
-
-      this.iterateAllRootAllocatorDumps(visit);
-    }
-  };
-
-  tr.model.EventRegistry.register(GlobalMemoryDump, {
-    name: 'globalMemoryDump',
-    pluralName: 'globalMemoryDumps'
-  });
-
-  return {
-    GlobalMemoryDump: GlobalMemoryDump
-  };
-});
+"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":41,"../base/unit.js":57,"../value/numeric.js":190,"./container_memory_dump.js":109,"./event_registry.js":119,"./memory_allocator_dump.js":134}],124:[function(require,module,exports){
+},{"../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";
-/**
-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.
-**/
-
-require("../base/base.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-
-  /**
-   * HeapEntry represents a single value describing the state of the heap of an
-   * allocator in a single process.
-   *
-   * An entry specifies how much space (e.g. 19 MiB) was allocated in a
-   * particular context, which consists of a codepath (e.g. drawQuad <- draw <-
-   * MessageLoop::RunTask) and an object type (e.g. HTMLImportLoader).
-   *
-   * @{constructor}
-   */
-  function HeapEntry(heapDump, leafStackFrame, objectTypeName, size, count) {
-    this.heapDump = heapDump;
-
-    // The leaf stack frame of the associated backtrace (e.g. drawQuad for the
-    // drawQuad <- draw <- MessageLoop::RunTask backtrace). If undefined, the
-    // backtrace is empty.
-    this.leafStackFrame = leafStackFrame;
-
-    // The name of the allocated object type (e.g. 'HTMLImportLoader'). If
-    // undefined, the entry represents the sum over all object types.
-    this.objectTypeName = objectTypeName;
-
-    this.size = size;
-    this.count = count;
-  }
-
-  /**
-   * HeapDump represents a dump of the heap of an allocator in a single process
-   * at a particular timestamp.
-   *
-   * @{constructor}
-   */
-  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
-  };
-});
+"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":28}],125:[function(require,module,exports){
+},{"../base/base.js":34}],131:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../../base/iteration_helpers.js");
-require("./chrome_process_helper.js");
-
-'use strict';
-
-/**
- * @fileoverview Utilities for accessing trace data about the Chrome browser.
- */
-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,
-
-    // TODO(petrcermak): Pass browser name in a metadata event (see
-    // crbug.com/605088).
-    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' || // old-style URLRequest/Resource
-          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
-  };
-});
+"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":41,"./chrome_process_helper.js":128}],126:[function(require,module,exports){
+},{"../../base/iteration_helpers.js":47,"./chrome_process_helper.js":134}],132:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./chrome_process_helper.js");
-
-'use strict';
-
-/**
- * @fileoverview Utilities for accessing the Chrome GPU Process.
- */
-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) {
-    // In some android builds the GPU thread is not in a separate 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
-  };
-});
+"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":128}],127:[function(require,module,exports){
+},{"./chrome_process_helper.js":134}],133:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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';
-
-/**
- * @fileoverview Utilities for accessing trace data about the Chrome browser.
- */
-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];
-  }
-
-  /**
-   * @constructor
-   */
-  function ChromeModelHelper(model) {
-    this.model_ = model;
-
-    // Find browserHelpers.
-    var browserProcesses = findChromeBrowserProcesses(model);
-    this.browserHelpers_ = browserProcesses.map(p => new tr.model.helpers.ChromeBrowserHelper(this, p));
-
-    // Find gpuHelper.
-    var gpuProcess = findChromeGpuProcess(model);
-    if (gpuProcess) {
-      this.gpuHelper_ = new tr.model.helpers.ChromeGpuHelper(this, gpuProcess);
-    } else {
-      this.gpuHelper_ = undefined;
-    }
-
-    // Find rendererHelpers.
-    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_;
-    },
-
-    // TODO: Make all users of ChromeModelHelper support multiple browsers and
-    // remove this getter (see #2119).
-    get browserProcess() {
-      if (this.browserHelper === undefined) return undefined;
-      return this.browserHelper.process;
-    },
-
-    // TODO: Make all users of ChromeModelHelper support multiple browsers and
-    // remove this getter (see #2119).
-    get browserHelper() {
-      return this.browserHelpers_[0];
-    },
-
-    get browserHelpers() {
-      return this.browserHelpers_;
-    },
-
-    get gpuHelper() {
-      return this.gpuHelper_;
-    },
-
-    get rendererHelpers() {
-      return this.rendererHelpers_;
-    }
-  };
-
-  return {
-    ChromeModelHelper: ChromeModelHelper
-  };
-});
+"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":39,"../../base/iteration_helpers.js":41,"./chrome_browser_helper.js":125,"./chrome_gpu_helper.js":126,"./chrome_renderer_helper.js":129}],128:[function(require,module,exports){
+},{"../../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";
-/**
-Copyright (c) 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.
-**/
-
-require("../../base/base.js");
-
-'use strict';
-
-/**
- * @fileoverview Utilities for accessing trace data about the Chrome browser.
- */
-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
-  };
-});
+"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":28}],129:[function(require,module,exports){
+},{"../../base/base.js":34}],135:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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';
-
-  // Returns true if there is either a main thread or a compositor thread.
-  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,
-
-    // May be undefined.
-    get mainThread() {
-      return this.mainThread_;
-    },
-
-    // May be undefined.
-    get compositorThread() {
-      return this.compositorThread_;
-    },
-
-    // May be empty.
-    get rasterWorkerThreads() {
-      return this.rasterWorkerThreads_;
-    },
-
-    get isChromeTracingUI() {
-      return this.isChromeTracingUI_;
-    },
-
-    /**
-    * Generate a breakdown that attributes where time goes between |start| &
-    * |end| on renderer thread.
-    *
-    * @param {number} start
-    * @param {number} end
-    * @return {Object} A time breakdown object whose every key is a chrome
-    * userfriendly title & values are an object that show the total spent
-    * between |start| & |end|, and the list of event labels of the group and
-    * their total time between |start| & |end|.
-    *
-    * Example:
-    *   {layout: {
-    *        total: 100,
-    *        events: {'FrameView::performPreLayoutTasks': 20,..}},
-    *    v8_runtime: {
-    *        total: 500,
-    *        events: {'String::NewExternalTwoByte': 0.5,..}},
-    *    ...
-    *    }
-    *
-    *
-    */
-    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;
-                }
-                // V8 Runtime Call Stats data is in us, while the
-                // breakdown tree timing is in ms.
-                var runtimeTime = v8RuntimeObject[runtimeCall][1] * wallTimeIntersectionRatio / 1000;
-                breakdownMap['v8_runtime'].total += runtimeTime;
-                breakdownMap['v8_runtime'].events[runtimeCall] += runtimeTime;
-              }
-            }
-          } catch (e) {
-            console.warn(e);
-          }
-        }
-        //        [     Slice 1       ]   [      Slice  2   ]   [    Slice 3   ]
-        //            [  Slice 4    ]                             [ Slice 5 ]
-        //              [ Slice 6 ]                                  |
-        //                 |                                         |
-        //                 |                                         |
-        //                 v                                         v
-        //                start                                     end
-        //
-        // For the case where the |start| or |end| overlapped with some existing
-        // slice (see above diagram), we approximate the overlapped self-time
-        // by multiplying the ratio of overlapped wall time to the self-time.
-        // There should be way to compute the exact number, but in practice,
-        // this should rarely happen, and when it does, the overlapped range
-        // is relative small so that using approximation here should be good
-        // enough.
-        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
-  };
-});
+"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":47,"../../extras/chrome/chrome_user_friendly_category_driver.js":63,"./chrome_process_helper.js":128}],130:[function(require,module,exports){
+},{"../../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";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/unit.js");
-require("./timed_event.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-  var InstantEventType = {
-    GLOBAL: 1,
-    PROCESS: 2
-  };
-
-  /**
-   * An InstantEvent is a zero-duration event.
-   *
-   * @constructor
-   */
-  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
-  };
-
-  /**
-   * A GlobalInstantEvent is a zero-duration event that's not tied to any
-   * particular process.
-   *
-   * An example is a trace event that's issued when a new USB device is plugged
-   * into the machine.
-   *
-   * @constructor
-   */
-  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);
-    }
-  };
-
-  /**
-   * A ProcessInstantEvent is a zero-duration event that's tied to a
-   * particular process.
-   *
-   * An example is a trace event that's issued when a kill signal is received.
-   *
-   * @constructor
-   */
-  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
-  };
-});
+"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":57,"./timed_event.js":160}],131:[function(require,module,exports){
+},{"../base/unit.js":63,"./timed_event.js":166}],137:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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) {
-        // FlowEvents don't have parentContainers or cpuDurations, and it's
-        // annoying to highlight them.
-        if (event instanceof tr.model.FlowEvent) return;
-        allAssociatedEvents.push(event);
-      });
-    });
-    return allAssociatedEvents;
-  }
-
-  function getUnassociatedEvents(model, associatedEvents) {
-    var unassociatedEvents = new tr.model.EventSet();
-    // The set of unassociated events contains only events that are not in
-    // the set of associated events.
-    // Only add event to the set of unassociated events if it is not in
-    // the set of associated events.
-    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) {
-      // Add up events' cpu self time if they have any.
-      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
-  };
-});
+"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":41,"./event_set.js":120}],132:[function(require,module,exports){
+},{"../base/iteration_helpers.js":47,"./event_set.js":126}],138:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/iteration_helpers.js");
-require("./cpu.js");
-require("./process_base.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the Process class.
- */
-global.tr.exportTo('tr.model', function () {
-  var Cpu = tr.model.Cpu;
-  var ProcessBase = tr.model.ProcessBase;
-
-  /**
-   * The Kernel represents kernel-level objects in the model.
-   * @constructor
-   */
-  function Kernel(model) {
-    ProcessBase.call(this, model);
-
-    this.cpus = {};
-    this.softwareMeasuredCpuCount_ = undefined;
-  };
-
-  /**
-   * Comparison between kernels is pretty meaningless.
-   */
-  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';
-    },
-
-    /**
-     * @return {Cpu} Gets a specific Cpu or creates one if
-     * it does not exist.
-     */
-    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;
-    },
-
-    /**
-     * Estimates how many cpus are in the system, for use in system load
-     * estimation.
-     *
-     * If kernel trace was provided, uses that data. Otherwise, uses the
-     * software measured cpu count.
-     */
-    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
-  };
-});
+"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":41,"./cpu.js":113,"./process_base.js":144}],133:[function(require,module,exports){
+},{"../base/iteration_helpers.js":47,"./cpu.js":119,"./process_base.js":150}],139:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/base.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-  /**
-   * YComponent is a class that handles storing the stableId and the percentage
-   * offset in the y direction of all tracks within a specific viewX and viewY
-   * coordinate.
-   * @constructor
-   */
-  function YComponent(stableId, yPercentOffset) {
-    this.stableId = stableId;
-    this.yPercentOffset = yPercentOffset;
-  }
-
-  YComponent.prototype = {
-    toDict: function () {
-      return {
-        stableId: this.stableId,
-        yPercentOffset: this.yPercentOffset
-      };
-    }
-  };
-
-  /**
-   * Location is a class that represents a spatial location on the timeline
-   * that is specified by percent offsets within tracks rather than specific
-   * points.
-   *
-   * @constructor
-   */
-  function Location(xWorld, yComponents) {
-    this.xWorld_ = xWorld;
-    this.yComponents_ = yComponents;
-  };
-
-  /**
-   * Returns a new Location given by x and y coordinates with respect to
-   * the timeline's drawing canvas.
-   */
-  Location.fromViewCoordinates = function (viewport, viewX, viewY) {
-    var dt = viewport.currentDisplayTransform;
-    var xWorld = dt.xViewToWorld(viewX);
-    var yComponents = [];
-
-    // Since we're given coordinates within the timeline canvas, we need to
-    // convert them to document coordinates to get the element.
-    var elem = document.elementFromPoint(viewX + viewport.modelTrackContainer.canvas.offsetLeft, viewY + viewport.modelTrackContainer.canvas.offsetTop);
-    // Build yComponents by calculating percentage offset with respect to
-    // each parent track.
-    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 = [];
-
-    // The y components' percentage offsets will be calculated with respect to
-    // the boundingRect's top of containing track.
-    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_;
-    },
-
-    /**
-     * Returns the first valid containing track based on the
-     * internal yComponents.
-     */
-    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;
-      }
-    },
-
-    /**
-     * Calculates and returns x and y coordinates of the current location with
-     * respect to the timeline's canvas.
-     */
-    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
-  };
-});
+"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":28}],134:[function(require,module,exports){
+},{"../base/base.js":34}],140:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/iteration_helpers.js");
-require("../base/unit.js");
-require("../value/numeric.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the MemoryAllocatorDump class.
- */
-global.tr.exportTo('tr.model', function () {
-  /**
-   * @constructor
-   */
-  function MemoryAllocatorDump(containerMemoryDump, fullName, opt_guid) {
-    this.fullName = fullName;
-    this.parent = undefined;
-    this.children = [];
-
-    // String -> ScalarNumeric.
-    this.numerics = {};
-
-    // String -> string.
-    this.diagnostics = {};
-
-    // The associated container memory dump.
-    this.containerMemoryDump = containerMemoryDump;
-
-    // Ownership relationship between memory allocator dumps.
-    this.owns = undefined;
-    this.ownedBy = [];
-
-    // Map from sibling dumps (other children of this dump's parent) to the
-    // proportion of this dump's size which they (or their descendants) own.
-    this.ownedBySiblingSizes = new Map();
-
-    // Retention relationship between memory allocator dumps.
-    this.retains = [];
-    this.retainedBy = [];
-
-    // Weak memory allocator dumps are removed from the model after import in
-    // tr.model.GlobalMemoryDump.removeWeakDumps(). See
-    // base::trace_event::MemoryAllocatorDump::Flags::WEAK in the Chromium
-    // codebase.
-    this.weak = false;
-
-    // A list of information about the memory allocator dump (e.g. about how
-    // its fields were calculated). Each item should be an object with
-    // a mandatory 'type' property and type-specific extra arguments (see
-    // MemoryAllocatorDumpInfoType).
-    this.infos = [];
-
-    // For debugging purposes.
-    this.guid = opt_guid;
-  }
-
-  /**
-   * Size numeric names. Please refer to the Memory Dump Graph Metric
-   * Calculation design document for more details (https://goo.gl/fKg0dt).
-   */
-  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();
-
-      // Aggregate descendants's numerics recursively and gather children's
-      // numeric names.
-      this.children.forEach(function (child) {
-        child.aggregateNumericsRecursively(opt_model);
-        tr.b.iterItems(child.numerics, numericNames.add, numericNames);
-      }, this);
-
-      // Aggregate children's numerics.
-      numericNames.forEach(function (numericName) {
-        if (numericName === MemoryAllocatorDump.SIZE_NUMERIC_NAME || numericName === MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME || this.numerics[numericName] !== undefined) {
-          // Don't aggregate size and effective size numerics. These are
-          // calculated in GlobalMemoryDump.prototype.calculateSizes() and
-          // GlobalMemoryDump.prototype.calculateEffectiveSizes respectively.
-          // Also don't aggregate numerics that the parent already has.
-          return;
-        }
-
-        this.numerics[numericName] = MemoryAllocatorDump.aggregateNumerics(this.children.map(function (child) {
-          return child.numerics[numericName];
-        }), opt_model);
-      }, this);
-    }
-  };
-
-  // TODO(petrcermak): Consider moving this to tr.v.Histogram.
-  MemoryAllocatorDump.aggregateNumerics = function (numerics, opt_model) {
-    var shouldLogWarning = !!opt_model;
-    var aggregatedUnit = undefined;
-    var aggregatedValue = 0;
-
-    // Aggregate the units and sum up the values of the numerics.
-    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; // Don't log multiple warnings.
-        }
-        // Use the most generic unit when the numerics don't agree (best
-        // effort).
-        aggregatedUnit = tr.b.Unit.byName.unitlessNumber_smallerIsBetter;
-      }
-
-      aggregatedValue += numeric.value;
-    }, this);
-
-    if (aggregatedUnit === undefined) return undefined;
-
-    return new tr.v.ScalarNumeric(aggregatedUnit, aggregatedValue);
-  };
-
-  /**
-   * @constructor
-   */
-  function MemoryAllocatorDumpLink(source, target, opt_importance) {
-    this.source = source;
-    this.target = target;
-    this.importance = opt_importance;
-    this.size = undefined;
-  }
-
-  /**
-   * Types of size numeric information.
-   *
-   * @enum
-   */
-  var MemoryAllocatorDumpInfoType = {
-    // The provided size of a MemoryAllocatorDump was less than the aggregated
-    // size of its children.
-    //
-    // Mandatory extra args:
-    //   * providedSize: The inconsistent provided size.
-    //   * dependencySize: The aggregated size of the children.
-    PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN: 0,
-
-    // The provided size of a MemoryAllocatorDump was less than the size of its
-    // largest owner.
-    //
-    // Mandatory extra args:
-    //   * providedSize: The inconsistent provided size.
-    //   * dependencySize: The size of the largest owner.
-    PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER: 1
-  };
-
-  return {
-    MemoryAllocatorDump: MemoryAllocatorDump,
-    MemoryAllocatorDumpLink: MemoryAllocatorDumpLink,
-    MemoryAllocatorDumpInfoType: MemoryAllocatorDumpInfoType
-  };
-});
+"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":41,"../base/unit.js":57,"../value/numeric.js":190}],135:[function(require,module,exports){
+},{"../base/iteration_helpers.js":47,"../base/unit.js":63,"../value/numeric.js":196}],141:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2012 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.
-**/
-
-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';
-
-/**
- * @fileoverview Model is a parsed representation of the
- * TraceEvents obtained from base/trace_event in which the begin-end
- * tokens are converted into a hierarchy of processes, threads,
- * subrows, and slices.
- *
- * The building block of the model is a slice. A slice is roughly
- * equivalent to function call executing on a specific thread. As a
- * result, slices may have one or more subslices.
- *
- * A thread contains one or more subrows of slices. Row 0 corresponds to
- * the "root" slices, e.g. the topmost slices. Row 1 contains slices that
- * are nested 1 deep in the stack, and so on. We use these subrows to draw
- * nesting tasks.
- *
- */
-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;
-
-  /**
-   * @constructor
-   */
-  function Model() {
-    tr.model.EventContainer.call(this);
-    tr.b.EventTarget.decorate(this);
-
-    this.timestampShiftToZeroAmount_ = 0;
-
-    this.faviconHue = 'blue'; // Should be a key from favicons.html
-
-    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);
-    },
-
-    /**
-     * Some objects in the model can persist their state in ModelSettings.
-     *
-     * This iterates through them.
-     */
-    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;
-    },
-
-    /**
-     * @return {Process} Gets a TimelineProcess for a specified pid. Returns
-     * undefined if the process doesn't exist.
-     */
-    getProcess: function (pid) {
-      return this.processes[pid];
-    },
-
-    /**
-     * @return {Process} Gets a TimelineProcess for a specified pid or
-     * creates one if it does not exist.
-     */
-    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;
-    },
-
-    /**
-     * Generates the set of categories from the slices and counters.
-     */
-    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;
-    },
-
-    /**
-     * @param {(!function(!tr.model.Process): boolean)=} opt_predicate Optional
-     *     predicate for filtering the returned processes. If undefined, all
-     *     process in the model will be returned.
-     * @return {!Array<!tr.model.Process>} An array of processes in the model.
-     */
-    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;
-    },
-
-    /**
-     * @return {Array} An array of all the counters in the model.
-     */
-    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);
-    },
-
-    /**
-     * Gets the user friendly category string from an event.
-     *
-     * Returns undefined if none is known.
-     */
-    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;
-    },
-
-    /**
-     * @param {String} The name of the thread to find.
-     * @return {Array} An array of all the matched threads.
-     */
-    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;
-    },
-
-    /**
-     * Returns a time unit that is used to format values and determines the
-     * precision of the timestamp values.
-     */
-    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;
-    },
-
-    /**
-     * Returns a link to a trace data file that this model was imported from.
-     * This is NOT the URL of a site being traced, but instead an indicator of
-     * where the data is stored.
-     */
-    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;
-    },
-
-    /**
-     * Saves a warning that happened during import.
-     *
-     * Warnings are typically logged to the console, and optionally, the
-     * more critical ones are shown to the user.
-     *
-     * @param {Object} data The import warning data. Data must provide two
-     *    accessors: type, message. The types are used to determine if we
-     *    should output the message, we'll only output one message of each type.
-     *    The message is the actual warning content.
-     */
-    importWarning: function (data) {
-      data.showToUser = !!data.showToUser;
-
-      this.importWarnings_.push(data);
-
-      // Only log each warning type once. We may want to add some kind of
-      // flag to allow reporting all importer warnings.
-      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 () {
-      // Sort the samples.
-      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 () {
-      // Change all the fields pointing at id_refs to their real values.
-      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;
-        }
-      });
-    },
-
-    /**
-     * Called by the model to join references between objects, after final model
-     * bounds have been computed.
-     */
-    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) {
-      // Iterate the world, looking for id_refs
-      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];
-        // We have to delay the actual change to the new value until after all
-        // refs have been located. Otherwise, we could end up recursing in
-        // ways we definitely didn't intend.
-        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
-  };
-});
+"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":28,"../base/event.js":33,"../base/interval_tree.js":40,"../base/quad.js":45,"../base/range.js":47,"../base/task.js":54,"../base/time_display_modes.js":55,"../base/unit.js":57,"../core/auditor.js":60,"../core/filter.js":61,"./alert.js":101,"./clock_sync_manager.js":105,"./constants.js":108,"./device.js":115,"./flow_event.js":121,"./frame.js":122,"./global_memory_dump.js":123,"./instant_event.js":130,"./kernel.js":132,"./model_indices.js":136,"./model_stats.js":137,"./object_snapshot.js":140,"./process.js":143,"./process_memory_dump.js":145,"./sample.js":147,"./stack_frame.js":155,"./user_model/user_expectation.js":166,"./user_model/user_model.js":167}],136:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/base.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the Event Index class.
- */
-global.tr.exportTo('tr.model', function () {
-  /**
-   * A Event Index maps an id to all the events that have that particular id
-   *
-   * @constructor
-   */
-  function ModelIndices(model) {
-    // For now the only indices we construct are for flowEvents
-    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
-  };
-});
+"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":28}],137:[function(require,module,exports){
+},{"../base/base.js":34}],143:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/unit.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-
-  /**
-   * @constructor
-   */
-  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
-  };
-});
+"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":57}],138:[function(require,module,exports){
+},{"../base/unit.js":63}],144:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-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';
-
-/**
- * @fileoverview Provides the ObjectCollection class.
- */
-global.tr.exportTo('tr.model', function () {
-  var ObjectInstance = tr.model.ObjectInstance;
-  var ObjectSnapshot = tr.model.ObjectSnapshot;
-
-  /**
-   * A collection of object instances and their snapshots, accessible by id and
-   * time, or by object name.
-   *
-   * @constructor
-   */
-  function ObjectCollection(parent) {
-    tr.model.EventContainer.call(this);
-    this.parent = parent;
-    // scope -> {id -> TimeToObjectInstanceMap}
-    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);
-          // idWasDeleted will cause lastInstance.deletionTsWasExplicit to be
-          // set to true. Unset it here.
-          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
-  };
-});
+"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":47,"../base/sorted_array_utils.js":52,"../base/utils.js":59,"./event_container.js":117,"./object_instance.js":139,"./time_to_object_instance_map.js":159}],139:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/range.js");
-require("../base/sorted_array_utils.js");
-require("./event.js");
-require("./object_snapshot.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the ObjectSnapshot and ObjectHistory classes.
- */
-global.tr.exportTo('tr.model', function () {
-  var ObjectSnapshot = tr.model.ObjectSnapshot;
-
-  /**
-   * An object with a specific id, whose state has been snapshotted several
-   * times.
-   *
-   * @constructor
-   */
-  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');
-        }
-      }
-
-      // Update baseTypeName if needed.
-      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;
-    },
-
-    /**
-     * See ObjectSnapshot constructor notes on object initialization.
-     */
-    preInitialize: function () {
-      for (var i = 0; i < this.snapshots.length; i++) this.snapshots[i].preInitialize();
-    },
-
-    /**
-     * See ObjectSnapshot constructor notes on object initialization.
-     */
-    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) {
-        // Note, this is a little bit sketchy: this lets early ts point at the
-        // first snapshot, even before it is taken. We do this because raster
-        // tasks usually post before their tile snapshots are dumped. This may
-        // be a good line of code to re-visit if we start seeing strange and
-        // confusing object references showing up in the traces.
-        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
-  };
-});
+"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":47,"../base/sorted_array_utils.js":52,"./event.js":116,"./object_snapshot.js":140}],140:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/unit.js");
-require("./event.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-  /**
-   * A snapshot of an object instance, at a given moment in time.
-   *
-   * Initialization of snapshots and instances is three phased:
-   *
-   * 1. Instances and snapshots are constructed. This happens during event
-   *    importing. Little should be done here, because the object's data
-   *    are still being used by the importer to reconstruct object references.
-   *
-   * 2. Instances and snapshtos are preinitialized. This happens after implicit
-   *    objects have been found, but before any references have been found and
-   *    switched to direct references. Thus, every snapshot stands on its own.
-   *    This is a good time to do global field renaming and type conversion,
-   *    e.g. recognizing domain-specific types and converting from C++ naming
-   *    convention to JS.
-   *
-   * 3. Instances and snapshtos are initialized. At this point, {id_ref:
-   *    '0x1000'} fields have been converted to snapshot references. This is a
-   *    good time to generic initialization steps and argument verification.
-   *
-   * @constructor
-   */
-  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,
-
-    /**
-     * See ObjectSnapshot constructor notes on object initialization.
-     */
-    preInitialize: function () {},
-
-    /**
-     * See ObjectSnapshot constructor notes on object initialization.
-     */
-    initialize: function () {},
-
-    /**
-     * Called when an object reference is resolved as this ObjectSnapshot.
-     * @param {Object} item The event (async slice, slice or object) containing
-     *     the resolved reference.
-     * @param {Object} object The object directly containing the reference.
-     * @param {String} field The field name of the reference in |object|.
-     */
-    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
-  };
-});
+"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":57,"./event.js":116}],141:[function(require,module,exports){
+},{"../base/unit.js":63,"./event.js":122}],147:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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;
-
-  /**
-   * A sample that contains a power measurement (in W).
-   *
-   * @constructor
-   * @extends {Event}
-   */
-  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
-  };
-});
+"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":116,"./event_registry.js":119}],142:[function(require,module,exports){
+},{"./event.js":122,"./event_registry.js":125}],148:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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;
-
-  /**
-   * A container holding a time series of power samples.
-   *
-   * @constructor
-   * @extends {EventContainer}
-   */
-  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';
-    },
-
-    /**
-     * Adds a power sample to the series and returns it.
-     *
-     * Note: Samples must be added in chronological order.
-     */
-    addPowerSample: function (ts, val) {
-      var sample = new PowerSample(this, ts, val);
-      this.samples_.push(sample);
-      return sample;
-    },
-
-    /**
-     * Returns the total energy (in Joules) consumed between the specified
-     * start and end timestamps (in milliseconds).
-     */
-    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
-  };
-});
+"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":47,"../base/sorted_array_utils.js":52,"../base/unit_scale.js":58,"./event_container.js":117,"./power_sample.js":141}],143:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("./process_base.js");
-require("./process_memory_dump.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the Process class.
- */
-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;
-
-  /**
-   * The Process represents a single userland process in the
-   * trace.
-   * @constructor
-   */
-  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 = [];
-  };
-
-  /**
-   * Comparison between processes that orders by pid.
-   */
-  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
-  };
-});
+"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":144,"./process_memory_dump.js":145}],144:[function(require,module,exports){
+},{"./process_base.js":150,"./process_memory_dump.js":151}],150:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/guid.js");
-require("../base/range.js");
-require("./counter.js");
-require("./event_container.js");
-require("./object_collection.js");
-require("./thread.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the ProcessBase class.
- */
-global.tr.exportTo('tr.model', function () {
-
-  var Thread = tr.model.Thread;
-  var Counter = tr.model.Counter;
-
-  /**
-   * The ProcessBase is a partial base class, upon which Kernel
-   * and Process are built.
-   *
-   * @constructor
-   * @extends {tr.model.EventContainer}
-   */
-  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);
-    },
-
-    /**
-     * Gets the number of threads in this process.
-     */
-    get numThreads() {
-      var n = 0;
-      for (var p in this.threads) {
-        n++;
-      }
-      return n;
-    },
-
-    /**
-     * Shifts all the timestamps inside this process forward by the amount
-     * specified.
-     */
-    shiftTimestampsForward: function (amount) {
-      for (var child of this.childEventContainers()) child.shiftTimestampsForward(amount);
-    },
-
-    /**
-     * Closes any open slices.
-     */
-    autoCloseOpenSlices: function () {
-      for (var tid in this.threads) {
-        var thread = this.threads[tid];
-        thread.autoCloseOpenSlices();
-      }
-    },
-
-    autoDeleteObjects: function (maxTimestamp) {
-      this.objects.autoDeleteObjects(maxTimestamp);
-    },
-
-    /**
-     * Called by the model after finalizing imports,
-     * but before joining refs.
-     */
-    preInitializeObjects: function () {
-      this.objects.preInitializeAllObjects();
-    },
-
-    /**
-     * Called by the model after joining refs.
-     */
-    initializeObjects: function () {
-      this.objects.initializeAllObjects();
-    },
-
-    /**
-     * Merge slices from the kernel with those from userland for each thread.
-     */
-    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;
-    },
-
-    /**
-     * @param {String} The name of the thread to find.
-     * @return {Array} An array of all the matched 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];
-    },
-
-    /**
-     * Removes threads from the process that are fully empty.
-     */
-    pruneEmptyContainers: function () {
-      var threadsToKeep = {};
-      for (var tid in this.threads) {
-        var thread = this.threads[tid];
-        if (!thread.isEmpty) threadsToKeep[tid] = thread;
-      }
-      this.threads = threadsToKeep;
-    },
-
-    /**
-     * @return {TimelineThread} The thread identified by tid on this process,
-     * or undefined if it doesn't exist.
-     */
-    getThread: function (tid) {
-      return this.threads[tid];
-    },
-
-    /**
-     * @return {TimelineThread} The thread identified by tid on this process,
-     * creating it if it doesn't exist.
-     */
-    getOrCreateThread: function (tid) {
-      if (!this.threads[tid]) this.threads[tid] = new Thread(this, tid);
-      return this.threads[tid];
-    },
-
-    /**
-     * @return {Counter} The counter on this process with the given
-     * category/name combination, creating it if it doesn't exist.
-     */
-    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
-  };
-});
+"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":39,"../base/range.js":47,"./counter.js":110,"./event_container.js":117,"./object_collection.js":138,"./thread.js":156}],145:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/unit.js");
-require("./container_memory_dump.js");
-require("./memory_allocator_dump.js");
-require("./vm_region.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the ProcessMemoryDump class.
- */
-global.tr.exportTo('tr.model', function () {
-
-  // Names of MemoryAllocatorDump(s) from which tracing overhead should be
-  // discounted.
-  var DISCOUNTED_ALLOCATOR_NAMES = ['winheap', 'malloc'];
-
-  // The path to where the tracing overhead dump should be added to the
-  // winheap/malloc allocator dump tree.
-  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;
-  }
-
-  /**
-   * The ProcessMemoryDump represents a memory dump of a single process.
-   * @constructor
-   */
-  function ProcessMemoryDump(globalMemoryDump, process, start) {
-    tr.model.ContainerMemoryDump.call(this, start);
-    this.process = process;
-    this.globalMemoryDump = globalMemoryDump;
-
-    // Process memory totals (optional object) with the following fields (also
-    // optional):
-    //   - residentBytes: Total resident bytes (number)
-    //   - peakResidentBytes: Peak resident bytes (number)
-    //   - arePeakResidentBytesResettable: Flag whether peak resident bytes are
-    //     resettable (boolean)
-    //   - platformSpecific: Map from OS-specific total names (string) to sizes
-    //     (number)
-    this.totals = undefined;
-
-    this.vmRegions = undefined;
-
-    // Map from allocator names to heap dumps.
-    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) {
-      // Make sure that calling this method twice won't lead to
-      // 'double-discounting'.
-      if (this.tracingOverheadOwnershipSetUp_) return;
-      this.tracingOverheadOwnershipSetUp_ = true;
-
-      var tracingDump = this.getMemoryAllocatorDumpByFullName('tracing');
-      if (tracingDump === undefined || tracingDump.owns !== undefined) {
-        // The tracing dump either doesn't exist, or it already owns another
-        // dump.
-        return;
-      }
-
-      if (tracingDump.owns !== undefined) return;
-
-      // Add an ownership link from tracing to
-      // malloc/allocated_objects/tracing_overhead or
-      // winheap/allocated_objects/tracing_overhead.
-      var hasDiscountedFromAllocatorDumps = DISCOUNTED_ALLOCATOR_NAMES.some(function (allocatorName) {
-        // First check if the allocator root exists.
-        var allocatorDump = this.getMemoryAllocatorDumpByFullName(allocatorName);
-        if (allocatorDump === undefined) return false; // Allocator doesn't exist, try another one.
-
-        var nextPathIndex = 0;
-        var currentDump = allocatorDump;
-        var currentFullName = allocatorName;
-
-        // Descend from the root towards tracing_overhead as long as the dumps
-        // on the path exist.
-        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;
-        }
-
-        // Create the missing descendant dumps on the path from the root
-        // towards tracing_overhead.
-        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;
-        }
-
-        // Add the ownership link.
-        var ownershipLink = new tr.model.MemoryAllocatorDumpLink(tracingDump, currentDump);
-        tracingDump.owns = ownershipLink;
-        currentDump.ownedBy.push(ownershipLink);
-        return true;
-      }, this);
-
-      // Force rebuilding the memory allocator dump index (if we've just added
-      // a new memory allocator dump).
-      if (hasDiscountedFromAllocatorDumps) this.forceRebuildingMemoryAllocatorDumpByFullNameIndex();
-    },
-
-    discountTracingOverheadFromVmRegions: function (opt_model) {
-      // Make sure that calling this method twice won't lead to
-      // 'double-discounting'.
-      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;
-
-      // Subtract the tracing size from the totals.
-      if (this.totals !== undefined) {
-        if (this.totals.residentBytes !== undefined) this.totals.residentBytes -= discountedResidentSize;
-        if (this.totals.peakResidentBytes !== undefined) this.totals.peakResidentBytes -= discountedResidentSize;
-      }
-
-      // Subtract the tracing size from VM regions. More precisely, subtract
-      // tracing resident_size from byte stats (private dirty and PSS) and
-      // tracing size from virtual size by injecting a fake VM region with
-      // negative values.
-      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) {
-      // Update the most recent VM regions from the current dump.
-      if (processDump.vmRegions !== undefined) mostRecentVmRegions = processDump.vmRegions;
-
-      // Set the most recent VM regions of the current dump.
-      processDump.mostRecentVmRegions = mostRecentVmRegions;
-    });
-  };
-
-  tr.model.EventRegistry.register(ProcessMemoryDump, {
-    name: 'processMemoryDump',
-    pluralName: 'processMemoryDumps'
-  });
-
-  return {
-    ProcessMemoryDump: ProcessMemoryDump
-  };
-});
+"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":57,"./container_memory_dump.js":109,"./memory_allocator_dump.js":134,"./vm_region.js":168}],146:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 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.
-**/
-
-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; // Location of top-left corner.
-    this.endLocation_ = end; // Location of bottom-right corner.
-    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
-  };
-});
+"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":172,"./annotation.js":102,"./location.js":133}],147:[function(require,module,exports){
+},{"../ui/annotations/rect_annotation_view.js":178,"./annotation.js":108,"./location.js":139}],153:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/unit.js");
-require("./timed_event.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the Sample class.
- */
-global.tr.exportTo('tr.model', function () {
-  /**
-   * A Sample represents a sample taken at an instant in time, plus its stack
-   * frame and parameters associated with that sample.
-   *
-   * @constructor
-   */
-  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
-  };
-});
+"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":57,"./timed_event.js":160}],148:[function(require,module,exports){
+},{"../base/unit.js":63,"./timed_event.js":166}],154:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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
-  };
-});
+"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":28,"./constants.js":108}],149:[function(require,module,exports){
+},{"../base/base.js":34,"./constants.js":114}],155:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./selection_state.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the SelectableItem class.
- */
-global.tr.exportTo('tr.model', function () {
-  var SelectionState = tr.model.SelectionState;
-
-  /**
-   * A SelectableItem is the abstract base class for any non-container data that
-   * has an associated model item in the trace model (possibly itself).
-   *
-   * Subclasses must provide a selectionState property (or getter).
-   *
-   * @constructor
-   */
-  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
-  };
-});
+"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":150}],150:[function(require,module,exports){
+},{"./selection_state.js":156}],156:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../base/base.js");
-require("../base/color_scheme.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the SelectionState class.
- */
-global.tr.exportTo('tr.model', function () {
-  var ColorScheme = tr.b.ColorScheme;
-
-  /**
-   * Describes the level of visual highlighting to apply to an event when shown.
-   *
-   * color_scheme.html defines N variations off of a base color palette,
-   * one for each selection state, all concatenated into one flat array. To
-   * pick the final colorId for a given variations, the SelectionState is
-   * multiplied by the number of base colors.
-   *
-   * Thus, the values here must be kept in sync with color_scheme's palette
-   * layout.
-   */
-  var SelectionState = {
-    NONE: 0,
-
-    // Legacy names.
-    SELECTED: ColorScheme.properties.brightenedOffsets[0],
-    HIGHLIGHTED: ColorScheme.properties.brightenedOffsets[1],
-    DIMMED: ColorScheme.properties.dimmedOffsets[0],
-
-    // Modern names.
-    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
-  };
-});
+"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":28,"../base/color_scheme.js":32}],151:[function(require,module,exports){
+},{"../base/base.js":34,"../base/color_scheme.js":38}],157:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/unit.js");
-require("./timed_event.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the Slice class.
- */
-global.tr.exportTo('tr.model', function () {
-  /**
-   * A Slice represents an interval of time plus parameters associated
-   * with that interval.
-   *
-   * @constructor
-   */
-  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;
-
-    // parentSlice and isTopLevel will be set by SliceGroup.
-    this.parentSlice = undefined;
-    this.isTopLevel = false;
-    // After SliceGroup processes Slices, isTopLevel should be equivalent to
-    // !parentSlice.
-
-    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;
-    },
-
-    /**
-     * Finds all topmost slices relative to this slice.
-     *
-     * Slices may have multiple direct descendants which satisfy
-     * |eventPredicate|, and in this case, all of them are topmost as long as
-     * this slice does not satisfy the predicate.
-     *
-     * For instance, suppose we are passing a predicate that checks whether
-     * events titles begin with 'C'.
-     *  C1.findTopmostSlicesRelativeToThisSlice() returns C1 in this example:
-     * [   C1  ]
-     *   [ C2 ]
-     *
-     * and D.findTopmostSlicesRelativeToThisSlice() returns C1 and C2 in this
-     * example:
-     * [      D     ]
-     *   [C1]  [C2]
-     */
-    findTopmostSlicesRelativeToThisSlice: function* (eventPredicate) {
-      if (eventPredicate(this)) {
-        yield this;
-        return;
-      }
-      for (var s of this.subSlices) yield* s.findTopmostSlicesRelativeToThisSlice(eventPredicate);
-    },
-
-    /**
-     * Obtains all subsequent slices of this slice.
-     *
-     * Subsequent slices are slices that get executed after a particular
-     * slice, i.e., all the functions that are called after the current one.
-     *
-     * For instance, E.iterateAllSubsequentSlices() in the following example:
-     * [     A          ]
-     * [ B][  D   ][ G  ]
-     *  [C] [E][F]  [H]
-     * will pass F, G, then H to the provided callback.
-     *
-     * The reason we need subsequent slices of a particular slice is that
-     * when there is flow event goes into, e.g., E, we only want to highlight
-     * E's subsequent slices to indicate the execution order.
-     *
-     * The idea to calculate the subsequent slices of slice E is to view
-     * the slice group as a tree where the top-level slice A is the root node.
-     * The preorder depth-first-search (DFS) order is naturally equivalent
-     * to the function call order. We just need to perform a DFS, and start
-     * recording the slices after we see the occurance of E.
-     */
-    iterateAllSubsequentSlices: function (callback, opt_this) {
-      var parentStack = [];
-      var started = false;
-
-      // get the root node and push it to the DFS stack
-      var topmostSlice = this.mostTopLevelSlice;
-      parentStack.push(topmostSlice);
-
-      // Using the stack to perform DFS
-      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;
-    },
-
-    /**
-     * Obtains the parents of a slice, from the most immediate to the root.
-     *
-     * For instance, E.enumerateAllAncestors() in the following example:
-     * [     A          ]
-     * [ B][  D   ][ G  ]
-     *  [C] [E][F]  [H]
-     * will yield D, then A, in the order from the leaves to the root.
-     */
-    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;
-    },
-
-    /**
-     * Returns this slice, and its ancestor and subsequent slices.
-     *
-     * For instance, E.ancestorAndSubsequentSlices in the following example:
-     * [     A          ]
-     * [ B][  D   ][ G  ]
-     *  [C] [E][F]  [H]
-     * will return E, D, A, F, G, and H, where E is itself, D and A are
-     * E's ancestors, and F, G, and H are subsequent slices of E
-     */
-    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
-  };
-});
+"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":57,"./timed_event.js":160}],152:[function(require,module,exports){
+},{"../base/unit.js":63,"./timed_event.js":166}],158:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-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';
-
-/**
- * @fileoverview Provides the SliceGroup class.
- */
-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;
-  }
-
-  /**
-   * A group of Slices, plus code to create them from B/E events, as
-   * well as arrange them into subRows.
-   *
-   * Do not mutate the slices array directly. Modify it only by
-   * SliceGroup mutation methods.
-   *
-   * @constructor
-   * @param {function(new:Slice, category, title, colorId, start, args)=}
-   *     opt_sliceConstructor The constructor to use when creating slices.
-   * @extends {tr.model.EventContainer}
-   */
-  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;
-    },
-
-    /**
-     * @return {Number} The number of slices in this group.
-     */
-    get length() {
-      return this.slices.length;
-    },
-
-    /**
-     * Helper function that pushes the provided slice onto the slices array.
-     * @param {Slice} slice The slice to be added to the slices array.
-     */
-    pushSlice: function (slice) {
-      this.haveTopLevelSlicesBeenBuilt = false;
-      slice.parentContainer = this.parentContainer_;
-      this.slices.push(slice);
-      return slice;
-    },
-
-    /**
-     * Helper function that pushes the provided slices onto the slices array.
-     * @param {Array.<Slice>} slices An array of slices to be added.
-     */
-    pushSlices: function (slices) {
-      this.haveTopLevelSlicesBeenBuilt = false;
-      slices.forEach(function (slice) {
-        slice.parentContainer = this.parentContainer_;
-        this.slices.push(slice);
-      }, this);
-    },
-
-    /**
-     * Opens a new slice in the group's slices.
-     *
-     * Calls to beginSlice and
-     * endSlice must be made with non-monotonically-decreasing timestamps.
-     *
-     * @param {String} category Category name of the slice to add.
-     * @param {String} title Title of the slice to add.
-     * @param {Number} ts The timetsamp of the slice, in milliseconds.
-     * @param {Object.<string, Object>=} opt_args Arguments associated with
-     * the slice.
-     * @param {Number=} opt_colorId The color of the slice, defined by
-     * its palette id (see base/color_scheme.html).
-     */
-    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;
-    },
-
-    /**
-     * @return {Number} The number of beginSlices for which an endSlice has not
-     * been issued.
-     */
-    get openSliceCount() {
-      return this.openPartialSlices_.length;
-    },
-
-    get mostRecentlyOpenedPartialSlice() {
-      if (!this.openPartialSlices_.length) return undefined;
-      return this.openPartialSlices_[this.openPartialSlices_.length - 1];
-    },
-
-    /**
-     * Ends the last begun slice in this group and pushes it onto the slice
-     * array.
-     *
-     * @param {Number} ts Timestamp when the slice ended
-     * @param {Number=} opt_colorId The color of the slice, defined by
-     * its palette id (see base/color_scheme.html).
-     * @return {Slice} slice.
-     */
-    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;
-    },
-
-    /**
-     * Push a complete event as a Slice into the slice list.
-     * The timestamp can be in any order.
-     *
-     * @param {String} category Category name of the slice to add.
-     * @param {String} title Title of the slice to add.
-     * @param {Number} ts The timetsamp of the slice, in milliseconds.
-     * @param {Number} duration The duration of the slice, in milliseconds.
-     * @param {Object.<string, Object>=} opt_args Arguments associated with
-     * the slice.
-     * @param {Number=} opt_colorId The color of the slice, as defined by
-     * its palette id (see base/color_scheme.html).
-     */
-    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;
-    },
-
-    /**
-     * Closes any open slices.
-     * @param {Number=} opt_maxTimestamp The end time to use for the closed
-     * slices. If not provided,
-     * the max timestamp for this slice is provided.
-     */
-    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_ = [];
-    },
-
-    /**
-     * Shifts all the timestamps inside this group forward by the amount
-     * specified.
-     */
-    shiftTimestampsForward: function (amount) {
-      for (var sI = 0; sI < this.slices.length; sI++) {
-        var slice = this.slices[sI];
-        slice.start = slice.start + amount;
-      }
-    },
-
-    /**
-     * Updates the bounds for this group based on the slices it contains.
-     */
-    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];
-
-      // Now recurse on slice looking for subSlice of given ts.
-      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;
-    },
-
-    /**
-     * Construct subSlices for this group.
-     * Populate the group topLevelSlices, parent slices get a subSlices[],
-     * a selfThreadTime and a selfTime, child slices get a parentSlice
-     * reference.
-     */
-    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;
-
-      // Note that this doesn't check whether |child| should be added to
-      // |parent|'s descendant slices instead of |parent| directly.
-      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;
-
-        // Elements get inserted into the slices array in order of when the
-        // slices start. Because slices must be properly nested, we break
-        // start-time ties by assuming that the elements appearing earlier
-        // in the slices array (and thus ending earlier) start earlier.
-        return ix - iy;
-      });
-
-      var slices = new Array(this.slices.length);
-      for (var i = 0; i < ops.length; i++) {
-        slices[i] = originalSlices[ops[i]];
-      }
-
-      // Actually build the subrows.
-      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;
-      }
-
-      // Keep the slices in sorted form.
-      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)) {
-              // The current top-level slice and children are fully
-              // accounted for, proceed to next top-level slice.
-              sliceIdx++;
-            } else {
-              // The current top-level runs beyond the time slice, break out
-              // so we can potentially add more time slices to it
-              break;
-            }
-          }
-        }
-      }, this);
-    },
-    /* Add run-time of this timeSlice to the passed in slice
-     * and all of it's children (recursively).
-     * Returns whether the slice ends before or at the end of the
-     * time slice, signaling we are done with this slice.
-     */
-    addCpuTimeToSubslice_: function (slice, timeSlice) {
-      // Make sure they overlap
-      if (slice.start > timeSlice.end || slice.end < timeSlice.start) return slice.end <= timeSlice.end;
-
-      // Compute actual overlap
-      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;
-    }
-  };
-
-  /**
-   * Merge two slice groups.
-   *
-   * If the two groups do not nest properly some of the slices of groupB will
-   * be split to accomodate the improper nesting.  This is done to accomodate
-   * combined kernel and userland call stacks on Android.  Because userland
-   * tracing is done by writing to the trace_marker file, the kernel calls
-   * that get invoked as part of that write may not be properly nested with
-   * the userland call trace.  For example the following sequence may occur:
-   *
-   *     kernel enter sys_write        (the write to trace_marker)
-   *     user   enter some_function
-   *     kernel exit  sys_write
-   *     ...
-   *     kernel enter sys_write        (the write to trace_marker)
-   *     user   exit  some_function
-   *     kernel exit  sys_write
-   *
-   * This is handled by splitting the sys_write call into two slices as
-   * follows:
-   *
-   *     | sys_write |            some_function            | sys_write (cont.) |
-   *                 | sys_write (cont.) |     | sys_write |
-   *
-   * The colorId of both parts of the split slices are kept the same, and the
-   * " (cont.)" suffix is appended to the later parts of a split slice.
-   *
-   * The two input SliceGroups are not modified by this, and the merged
-   * SliceGroup will contain a copy of each of the input groups' slices (those
-   * copies may be split).
-   */
-  SliceGroup.merge = function (groupA, groupB) {
-    // This is implemented by traversing the two slice groups in reverse
-    // order.  The slices in each group are sorted by ascending end-time, so
-    // we must do the traversal from back to front in order to maintain the
-    // sorting.
-    //
-    // We traverse the two groups simultaneously, merging as we go.  At each
-    // iteration we choose the group from which to take the next slice based
-    // on which group's next slice has the greater end-time.  During this
-    // traversal we maintain a stack of currently "open" slices for each input
-    // group.  A slice is considered "open" from the time it gets reached in
-    // our input group traversal to the time we reach an slice in this
-    // traversal with an end-time before the start time of the "open" slice.
-    //
-    // Each time a slice from groupA is opened or closed (events corresponding
-    // to the end-time and start-time of the input slice, respectively) we
-    // split all of the currently open slices from 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
-  };
-});
+"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":32,"../base/guid.js":39,"../base/sorted_array_utils.js":52,"../core/filter.js":61,"./event_container.js":117,"./thread_slice.js":157}],153:[function(require,module,exports){
+},{"../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";
-/**
-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.
-**/
-
-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'
-    }
-  };
-});
+"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":154}],154:[function(require,module,exports){
+},{"./source_info.js":160}],160:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-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.
-**/
-
-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
-  };
-});
+"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":28}],155:[function(require,module,exports){
+},{"../../base/base.js":34}],161:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-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_;
-    },
-
-    /**
-     * Attempts to find the domain of the origin of the script either from this
-     * stack trace or from its ancestors.
-     */
-    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);
-    },
-
-    /**
-     * Returns stackFrames where the most specific frame is first.
-     */
-    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
-  };
-});
+"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":28}],156:[function(require,module,exports){
+},{"../base/base.js":34}],162:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-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';
-
-/**
- * @fileoverview Provides the Thread class.
- */
-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;
-
-  /**
-   * A Thread stores all the trace events collected for a particular
-   * thread. We organize the synchronous slices on a thread by "subrows," where
-   * subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on.
-   * The asynchronous slices are stored in an AsyncSliceGroup object.
-   *
-   * The slices stored on a Thread should be instances of
-   * ThreadSlice.
-   *
-   * @constructor
-   * @extends {tr.model.EventContainer}
-   */
-  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; // Set during createSubSlices
-
-    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);
-    },
-
-    /**
-     * Shifts all the timestamps inside this thread forward by the amount
-     * specified.
-     */
-    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);
-    },
-
-    /**
-     * Determines whether this thread is empty. If true, it usually implies
-     * that it should be pruned from the model.
-     */
-    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;
-    },
-
-    /**
-     * Updates the bounds based on the
-     * current objects associated with the thread.
-     */
-    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);
-    },
-
-    /**
-     * @return {String} A user-friendly name for this thread.
-     */
-    get userFriendlyName() {
-      return this.name || this.tid;
-    },
-
-    /**
-     * @return {String} User friendly details about this thread.
-     */
-    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;
-    },
-
-    /*
-     * Returns the index of the slice in the timeSlices array, or undefined.
-     */
-    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;
-    },
-
-    /*
-     * Returns an object with the CPU number used as keys,
-     * and the value of each key object is the amount of milliseconds spent
-     * running on this CPU.
-     * Additionally, stats.total contains the total time
-     * spent running on all CPUs.
-     */
-    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_;
-    }
-  };
-
-  /**
-   * Comparison between threads that orders first by parent.compareTo,
-   * then by names, then by tid.
-   */
-  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
-  };
-});
+"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":39,"../base/range.js":47,"./async_slice_group.js":104,"./event_container.js":117,"./slice_group.js":152,"./thread_slice.js":157}],157:[function(require,module,exports){
+},{"../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";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("./slice.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the Thread class.
- */
-global.tr.exportTo('tr.model', function () {
-  var Slice = tr.model.Slice;
-
-  /**
-   * A ThreadSlice represents an interval of time on a thread resource
-   * with associated nesting slice information.
-   *
-   * ThreadSlices are typically associated with a specific trace event pair on a
-   * specific thread.
-   * For example,
-   *   TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms
-   *   TRACE_EVENT_END0()                 at time=0.3ms
-   * This results in a single slice from 0.1 with duration 0.2 on a
-   * specific thread.
-   *
-   * @constructor
-   */
-  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);
-    // Do not modify this directly.
-    // subSlices is configured by SliceGroup.rebuildSubRows_.
-    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
-  };
-});
+"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":151}],158:[function(require,module,exports){
+},{"./slice.js":157}],164:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-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'
-  };
-
-  /**
-   * A ThreadTimeSlice is a slice of time on a specific thread where that thread
-   * was running on a specific CPU, or in a specific sleep state.
-   *
-   * As a thread switches moves through its life, it sometimes goes to sleep and
-   * can't run. Other times, its runnable but isn't actually assigned to a CPU.
-   * Finally, sometimes it gets put on a CPU to actually execute. Each of these
-   * states is represented by a ThreadTimeSlice:
-   *
-   *   Sleeping or runnable: cpuOnWhichThreadWasRunning is undefined
-   *   Running:  cpuOnWhichThreadWasRunning is set.
-   *
-   * @constructor
-   */
-  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
-  };
-});
+"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":47,"./slice.js":151}],159:[function(require,module,exports){
+},{"../base/range.js":53,"./slice.js":157}],165:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/range.js");
-require("../base/sorted_array_utils.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides the TimeToObjectInstanceMap class.
- */
-global.tr.exportTo('tr.model', function () {
-  /**
-   * Tracks all the instances associated with a given ID over its lifetime.
-   *
-   * A scoped id can be used multiple times throughout a trace, referring to
-   * different objects at different times. This data structure does the
-   * bookkeeping to figure out what ObjectInstance is referred to at a given
-   * timestamp.
-   *
-   * @constructor
-   */
-  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) {
-          // The snap is added after our oldest and deleted instance. This means
-          // that this is a new implicit instance.
-          instance = this.createObjectInstanceFunction_(this.parent, this.scopedId, category, name, ts, opt_baseTypeName);
-          this.instances.push(instance);
-        } else {
-          // If the ts is before the last objects deletion time, then the caller
-          // is trying to add a snapshot when there may have been an instance
-          // alive. In that case, try to move an instance's creationTs to
-          // include this ts, provided that it has an implicit creationTs.
-
-          // Search backward from the right for an instance that was definitely
-          // deleted before this ts. Any time an instance is found that has a
-          // moveable creationTs
-          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.');
-
-      // A new instance was deleted with no snapshots in-between.
-      // Create an instance then kill it.
-      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
-  };
-});
+"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":47,"../base/sorted_array_utils.js":52}],160:[function(require,module,exports){
+},{"../base/range.js":53,"../base/sorted_array_utils.js":58}],166:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 2013 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.
-**/
-
-require("../base/guid.js");
-require("../base/time_display_modes.js");
-require("./event.js");
-
-'use strict';
-
-global.tr.exportTo('tr.model', function () {
-  /**
-   * TimedEvent is a base type for any entity in the trace model with a specific
-   * start and duration.
-   *
-   * @constructor
-   */
-  function TimedEvent(start) {
-    tr.model.Event.call(this);
-    this.start = start;
-    this.duration = 0;
-    this.cpuStart = undefined;
-    this.cpuDuration = undefined;
-    // The set of contexts this event belongs to (order is unimportant). This
-    // array should never be modified.
-    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);
-    },
-
-    // TODO(charliea): Can this be implemented in terms of Event.range()?
-    // Returns true if 'that' TimedEvent is fully contained within 'this' timed
-    // event.
-    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
-  };
-});
+"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":39,"../base/time_display_modes.js":55,"./event.js":116}],161:[function(require,module,exports){
+},{"../base/guid.js":45,"../base/time_display_modes.js":61,"./event.js":122}],167:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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
-  };
-});
+"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":166}],162:[function(require,module,exports){
+},{"./user_expectation.js":172}],168:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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
-  };
-});
+"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":166}],163:[function(require,module,exports){
+},{"./user_expectation.js":172}],169:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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);
-
-    // |renderProcess| is the renderer process that contains the loading
-    // RenderFrame.
-    this.renderProcess = undefined;
-
-    // |renderMainThread| is the CrRendererMain thread in the |renderProcess|
-    // that contains the loading RenderFrame.
-    this.renderMainThread = undefined;
-
-    // |routingId| identifies the loading RenderFrame within the renderer
-    // process.
-    this.routingId = undefined;
-
-    // |parentRoutingId| identifies the RenderFrame that created and contains
-    // the loading RenderFrame.
-    this.parentRoutingId = undefined;
-
-    // |loadFinishedEvent|, if present, signals that this is a main frame.
-    this.loadFinishedEvent = undefined;
-
-    // Startup LoadIRs do not have renderProcess, routingId, or
-    // parentRoutingId. Maybe RenderLoadIR should be a separate class?
-  }
-
-  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
-  };
-});
+"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":166}],164:[function(require,module,exports){
+},{"./user_expectation.js":172}],170:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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
-  };
-});
+"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":166}],165:[function(require,module,exports){
+},{"./user_expectation.js":172}],171:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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
-  };
-});
+"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":166}],166:[function(require,module,exports){
+},{"./user_expectation.js":172}],172:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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;
-
-    // sourceEvents are the ones that caused the UserModelBuilder to create this
-    // UserExpectation.
-    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;
-    },
-
-    // Returns samples which are overlapping with V8.Execute
-    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 you set Subclass.prototype = {}, then you must explicitly specify
-      // constructor in that prototype object!
-      // http://javascript.info/tutorial/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;
-    },
-
-    /**
-     * Returns the sum of the number of CPU ms spent by this UserExpectation.
-     */
-    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
-  };
-});
+"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":48,"../../base/statistics.js":53,"../../base/unit.js":57,"../compound_event_selection_state.js":107,"../event_set.js":120,"../timed_event.js":160}],167:[function(require,module,exports){
+},{"../../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";
-/**
-Copyright (c) 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.
-**/
-
-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
-  };
-});
+"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":117}],168:[function(require,module,exports){
+},{"../event_container.js":123}],174:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-require("../base/base.js");
-
-'use strict';
-
-/**
- * @fileoverview Provides classes for representing and classifying VM regions.
- *
- * See https://goo.gl/5SSPv0 for more details.
- */
-global.tr.exportTo('tr.model', function () {
-
-  /**
-   * A single virtual memory region (also called a memory map).
-   *
-   * @constructor
-   */
-  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() {
-      // This value is assumed to be unique within a process.
-      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);
-  };
-
-  /**
-   * Node in a VM region classification tree.
-   *
-   * Note: Most users of this class should use the
-   * VMRegionClassificationNode.fromRegions static method instead of this
-   * constructor because it leads to better performance due to fewer memory
-   * allocations.
-   *
-   * @constructor
-   */
-  function VMRegionClassificationNode(opt_rule) {
-    this.rule_ = opt_rule || VMRegionClassificationNode.CLASSIFICATION_RULES;
-
-    // True iff this node or any of its descendant classification nodes has at
-    // least one classified VM region.
-    this.hasRegions = false;
-
-    // Total virtual size and byte stats of all regions matching this node's
-    // rule (including its sub-rules).
-    this.sizeInBytes = undefined;
-    this.byteStats = {};
-
-    // Array of child classification nodes if this is an intermediate node.
-    this.children_ = undefined;
-
-    // Array of VM regions. If this is an intermediate node, then the regions
-    // are cached for lazy tree construction (i.e. its child classification
-    // nodes yet have to be built).
-    this.regions_ = [];
-  }
-
-  /**
-   * Rules for classifying memory maps.
-   *
-   * These rules are derived from core/jni/android_os_Debug.cpp in Android.
-   */
-  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/, // @suppress longLineCheck
-          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\])|$)/ // @suppress longLineCheck
-    }, {
-      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; // Leaf nodes don't have children (by definition).
-      if (this.children_ === undefined) this.buildTree_(); // Lazily classify VM regions.
-      return this.children_;
-    },
-
-    get regions() {
-      if (!this.isLeafNode) {
-        // Intermediate nodes only temporarily cache VM regions for lazy tree
-        // construction.
-        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');
-        }
-        // Leaf node (or caching internal node).
-        return this.regions_;
-      }
-
-      // Intermediate node.
-      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 /* addStatsToThisNode */);
-    },
-
-    someRegion: function (fn, opt_this) {
-      if (this.regions_ !== undefined) {
-        // Leaf node (or caching internal node).
-        return this.regions_.some(fn, opt_this);
-      }
-
-      // Intermediate node.
-      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');
-        }
-        // Leaf node or an intermediate node caching VM regions (add the
-        // region to this node and don't classify further).
-        this.regions_.push(region);
-        return;
-      }
-
-      // Non-leaf rule (classify region row further down the tree).
-      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) {
-        // Region belongs to the 'Other' node (created lazily).
-        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++) {
-        // Note that we don't add the VM region's stats to this node because
-        // they have already been added to it.
-        this.addRegionRecursively_(cachedRegions[i], false /* addStatsToThisNode */);
-      }
-    },
-
-    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; // Leaf node: Nothing to do.
-
-      // Intermediate node: Clear regions and build children recursively.
-      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;
-
-      // Aggregate virtual size.
-      var regionSizeInBytes = region.sizeInBytes;
-      if (regionSizeInBytes !== undefined) this.sizeInBytes = (this.sizeInBytes || 0) + regionSizeInBytes;
-
-      // Aggregate byte stats.
-      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
-  };
-});
+"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":28}],169:[function(require,module,exports){
+},{"../base/base.js":34}],175:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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
-  };
-});
+"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":173,"./annotation.js":102}],170:[function(require,module,exports){
+},{"../ui/annotations/x_marker_annotation_view.js":179,"./annotation.js":108}],176:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../../base/base.js");
-
-'use strict';
-
-global.tr.exportTo('tr.ui.annotations', function () {
-  /**
-   * A base class for all annotation views.
-   * @constructor
-   */
-  function AnnotationView(viewport, annotation) {}
-
-  AnnotationView.prototype = {
-    draw: function (ctx) {
-      throw new Error('Not implemented');
-    }
-  };
-
-  return {
-    AnnotationView: AnnotationView
-  };
-});
+"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":28}],171:[function(require,module,exports){
+},{"../../base/base.js":34}],177:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./annotation_view.js");
-
-'use strict';
-
-global.tr.exportTo('tr.ui.annotations', function () {
-  /**
-   * A view of a comment box consisting of a textarea and a line to the
-   * actual location.
-   * @extends {AnnotationView}
-   * @constructor
-   */
-  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;
-      }
-
-      // Set up textarea element.
-      if (!this.textArea_) {
-        this.textArea_ = document.createElement('textarea');
-        this.textArea_.style.position = 'absolute';
-        this.textArea_.readOnly = true;
-        this.textArea_.value = this.annotation_.text;
-        // Set the z-index so that this is shown on top of canvas.
-        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';
-
-      // Update positions to latest coordinate.
-      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';
-
-      // Draw pointer line from offset to actual location.
-      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
-  };
-});
+"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":170}],172:[function(require,module,exports){
+},{"./annotation_view.js":176}],178:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./annotation_view.js");
-
-'use strict';
-
-global.tr.exportTo('tr.ui.annotations', function () {
-  /**
-   * A view responsible for drawing a single highlight rectangle box on
-   * the timeline.
-   * @extends {AnnotationView}
-   * @constructor
-   */
-  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_);
-
-      // Prevent drawing into the ruler track by clamping the initial Y
-      // point and the rect's Y size.
-      var startY = startCoords.viewY - ctx.canvas.getBoundingClientRect().top;
-      var sizeY = endCoords.viewY - startCoords.viewY;
-      if (startY + sizeY < 0) {
-        // In this case sizeY is negative. If final Y is negative,
-        // overwrite startY so that the rectangle ends at y=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
-  };
-});
+"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":170}],173:[function(require,module,exports){
+},{"./annotation_view.js":176}],179:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("./annotation_view.js");
-
-'use strict';
-
-global.tr.exportTo('tr.ui.annotations', function () {
-  /**
-   * A view that draws a vertical line on the timeline at a specific timestamp.
-   * @extends {AnnotationView}
-   * @constructor
-   */
-  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
-  };
-});
+"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":170}],174:[function(require,module,exports){
+},{"./annotation_view.js":176}],180:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../../base/event.js");
-require("../../base/utils.js");
-require("./ui.js");
-require("./utils.js");
-
-'use strict';
-
-/**
- * @fileoverview Implements an element that is hidden by default, but
- * when shown, dims and (attempts to) disable the main document.
- *
- * You can turn any div into an overlay. Note that while an
- * overlay element is shown, its parent is changed. Hiding the overlay
- * restores its original parentage.
- *
- */
-global.tr.exportTo('tr.ui.b', function () {
-  if (tr.isHeadless) return {};
-
-  return;
-
-  /**
-   * Creates a new overlay element. It will not be visible until shown.
-   * @constructor
-   * @extends {HTMLDivElement}
-   */
-  var Overlay = tr.ui.b.define('overlay');
-
-  Overlay.prototype = {
-    __proto__: HTMLDivElement.prototype,
-
-    /**
-     * Initializes the overlay element.
-     */
-    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);
-
-      // Setup the shadow root
-      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 });
-
-      // title is a variable on regular HTMLElements. However, we want to
-      // use it for something more useful.
-      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;
-
-      // Focus the first thing we find that makes sense. (Skip the close button
-      // as it doesn't make sense as the first thing to focus.)
-      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) {
-      // Disallow shift-tab back to another element.
-      if (e.keyCode === 9 && // tab
-      e.shiftKey && e.target === this) {
-        e.preventDefault();
-        return;
-      }
-
-      if (e.keyCode !== 27) // escape
-        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
-  };
-});
+"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":33,"../../base/utils.js":59,"./ui.js":175,"./utils.js":176}],175:[function(require,module,exports){
+},{"../../base/event.js":39,"../../base/utils.js":65,"./ui.js":181,"./utils.js":182}],181:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-require("../../base/base.js");
-
-'use strict';
-
-global.tr.exportTo('tr.ui.b', function () {
-
-  /**
-   * Decorates elements as an instance of a class.
-   * @param {string|!Element} source The way to find the element(s) to decorate.
-   *     If this is a string then {@code querySeletorAll} is used to find the
-   *     elements to decorate.
-   * @param {!Function} constr The constructor to decorate with. The constr
-   *     needs to have a {@code decorate} 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);
-    }
-  }
-
-  /**
-   * Defines a tracing UI component, a function that can be called to construct
-   * the component.
-   *
-   * tr class:
-   * var List = tr.ui.b.define('list');
-   * List.prototype = {
-   *   __proto__: HTMLUListElement.prototype,
-   *   decorate: function() {
-   *     ...
-   *   },
-   *   ...
-   * };
-   *
-   * Derived class:
-   * var CustomList = tr.ui.b.define('custom-list', List);
-   * CustomList.prototype = {
-   *   __proto__: List.prototype,
-   *   decorate: function() {
-   *     ...
-   *   },
-   *   ...
-   * };
-   *
-   * @param {string} className The className of the newly created subtype. If
-   *     subclassing by passing in opt_parentConstructor, this is used for
-   *     debugging. If not subclassing, then it is the tag name that will be
-   *     created by the component.
-    * @param {function=} opt_parentConstructor The parent class for this new
-   *     element, if subclassing is desired. If provided, the parent class must
-   *     be also a function created by tr.ui.b.define.
-   *
-   * @param {string=} opt_tagNS The namespace in which to create the base
-   *     element. Has no meaning when opt_parentConstructor is passed and must
-   *     either be undefined or the same namespace as the parent class.
-   *
-   * @return {function(Object=):Element} The newly created component
-   *     constructor.
-   */
-  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');
-
-    // Walk up the parent constructors until we can find the type of tag
-    // to create.
-    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;
-    }
-
-    /**
-     * Creates a new UI element constructor.
-     * Arguments passed to the constuctor are provided to the decorate method.
-     * You will need to call the parent elements decorate method from within
-     * your decorate method and pass any required parameters.
-     * @constructor
-     */
-    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;
-    }
-
-    /**
-     * Decorates an element as a UI element class.
-     * @param {!Element} el The element to decorate.
-     */
-    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
-  };
-});
+"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":28}],176:[function(require,module,exports){
+},{"../../base/base.js":34}],182:[function(require,module,exports){
 (function (global){
-"use strict";
-/**
-Copyright (c) 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.
-**/
-
-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);
-    //    return el.createInstance();
-  }
-
-  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');
-
-    // In newer versions of chrome, the contents of url() will be quoted. Remove
-    // these quotes as well. If quotes are not present, match will fail and this
-    // becomes a no-op.
-    extracted = extracted.replace(/\"(.*)\"/, '$1');
-
-    return extracted;
-  }
-
-  function toThreeDigitLocaleString(value) {
-    return value.toLocaleString(undefined, { minimumFractionDigits: 3, maximumFractionDigits: 3 });
-  }
-
-  /**
-   * Returns true if |name| is the name of an unknown HTML element.  Registered
-   * polymer elements are known, so this returns false.  Typos of registered
-   * polymer element names are unknown, so this returns true for typos.
-   *
-   * @return {boolean}
-   */
-  function isUnknownElementName(name) {
-    return document.createElement(name) instanceof HTMLUnknownElement;
-  }
-
-  return {
-    isUnknownElementName: isUnknownElementName,
-    toThreeDigitLocaleString: toThreeDigitLocaleString,
-    instantiateTemplate: instantiateTemplate,
-    windowRectForElement: windowRectForElement,
-    scrollIntoViewIfNeeded: scrollIntoViewIfNeeded,
-    extractUrlString: extractUrlString
-  };
-});
+"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":28,"../../base/rect.js":49}],177:[function(require,module,exports){
+},{"../../base/base.js":34,"../../base/rect.js":55}],183:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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;
-    }
-
-    /**
-     * Add a Value by an explicit name to this map.
-     *
-     * @param {string} name
-     * @param {!(tr.v.d.ValueRef|tr.v.Value)} value
-     */
-    set(name, value) {
-      if (typeof name !== 'string' || typeof value !== 'number') {
-        throw new Error('Breakdown maps from strings to numbers');
-      }
-      this.values_.set(name, value);
-    }
-
-    /**
-     * @param {string} name
-     * @return {number}
-     */
-    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
-  };
-});
+"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":185}],178:[function(require,module,exports){
+},{"./related_value_map.js":191}],184:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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
-  };
-});
+"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":35}],179:[function(require,module,exports){
+},{"../../base/extension_registry.js":41}],185:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-/**
-  Include all Diagnostic subclasses here so that DiagnosticMap.addDicts() and
-  DiagnosticMap.fromDict() always have access to all subclasses in the
-  Diagnostic registry.
-**/
-
-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 {
-    /**
-     * Add a new Diagnostic to this map.
-     *
-     * @param {string} name
-     * @param {!tr.v.d.Diagnostic} diagnostic
-     */
-    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);
-    }
-
-    /**
-     * Add Diagnostics from a dictionary of dictionaries.
-     *
-     * @param {Object} dict
-     */
-    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
-  };
-});
+"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":177,"./generic.js":181,"./iteration_info.js":182,"./related_event_set.js":183,"./related_histogram_breakdown.js":184,"./related_value_map.js":185,"./related_value_set.js":186,"./scalar.js":187}],180:[function(require,module,exports){
+},{"./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";
-/**
-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.
-**/
-
-require("../../base/guid.js");
-
-'use strict';
-
-global.tr.exportTo('tr.v.d', function () {
-  /**
-   * Similar to ValueRef, this is a placeholder in case the referenced Event
-   * isn't available in memory to point to directly.
-   */
-  class EventRef {
-    /**
-     * @param {!Object} event
-     * @param {string} event.stableId
-     * @param {string} event.title
-     * @param {number} event.start
-     * @param {number} event.duration
-     */
-    constructor(event) {
-      this.stableId = event.stableId;
-      this.title = event.title;
-      this.start = event.start;
-      this.duration = event.duration;
-      this.end = this.start + this.duration;
-
-      // tr.v.d.RelatedEventSet identifies events using stableId, but
-      // tr.model.EventSet uses guid.
-      this.guid = tr.b.GUID.allocateSimple();
-    }
-  }
-
-  return {
-    EventRef: EventRef
-  };
-});
+"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":39}],181:[function(require,module,exports){
+},{"../../base/guid.js":45}],187:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-require("./diagnostic.js");
-
-'use strict';
-
-global.tr.exportTo('tr.v.d', function () {
-  /**
-   * A Generic diagnostic can contain any Plain-Ol'-Data objects that can be
-   * serialized using JSON.stringify(): null, boolean, number, string, array,
-   * dict. Generic diagnostics cannot contain tr.v.Value objects!
-   */
-  class Generic extends tr.v.d.Diagnostic {
-    /**
-     * @param {*} value
-     */
-    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
-  };
-});
+"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":178}],182:[function(require,module,exports){
+},{"./diagnostic.js":184}],188:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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);
-    }
-
-    /**
-     * @param {!Object} info
-     * @param {string} info.benchmarkName
-     * @param {undefined|string} info.label
-     * @param {undefined|!Object} info.storyGroupingKeys
-     * @param {undefined|string} info.storyDisplayName
-     * @param {undefined|string} info.storyUrl
-     * @param {undefined|number} info.storyRepeatCounter
-     * @param {number} info.storysetRepeatCounter
-     * @param {number} info.benchmarkStartMs Milliseconds since Unix epoch.
-     */
-    addInfo(info) {
-      // IterationInfo from Telemetry
-      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;
-
-      // From platform tracing metadata
-      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);
-    }
-
-    /**
-     * @param {!tr.v.Value} value
-     * @return {(undefined|!tr.v.d.IterationInfo)}
-     */
-    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);
-    }
-
-    /**
-     * @param {!tr.v.Value} value
-     * @param {string} fieldName
-     * @param {*} defaultValue
-     * @return {*}
-     */
-    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];
-    }
-
-    /**
-     * @param {!tr.v.Value} value
-     * @param {string} storyGroupingKey
-     * @return {string}
-     */
-    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];
-    }
-  }
-
-  // Diagnostics generally do not need a constant name or getFromValue().
-  // IterationInfo is a special kind of Diagnostic that is produced by
-  // telemetry, which shepherds whole flocks of traces at once, and needs a
-  // system to identify and find traces by these attributes.
-
-  // Values produced by telemetry all have a single IterationInfo at this key in
-  // their DiagnosticMap.
-  IterationInfo.NAME = 'iteration';
-
-  tr.v.d.Diagnostic.register(IterationInfo, {
-    elementName: 'tr-v-ui-iteration-info-span'
-  });
-
-  return {
-    IterationInfo: IterationInfo
-  };
-});
+"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":59,"./diagnostic.js":178}],183:[function(require,module,exports){
+},{"../../base/utils.js":65,"./diagnostic.js":184}],189:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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 () {
-  /**
-   * @typedef {!(tr.v.d.EventRef|tr.model.Event)} EventLike
-   */
-
-  /**
-   * A RelatedEventSet diagnostic contains references to Events
-   */
-  class RelatedEventSet extends tr.v.d.Diagnostic {
-    /**
-     * @param {!(tr.model.EventSet|Array.<EventLike>|EventLike)=} opt_events
-     */
-    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);
-        }
-      }
-    }
-
-    /**
-     * @param {!(tr.v.d.EventRef|tr.model.Event)} event
-     */
-    add(event) {
-      this.eventsByStableId_.set(event.stableId, event);
-    }
-
-    /**
-     * @param {!(tr.v.d.EventRef|tr.model.Event)} event
-     * @return {boolean}
-     */
-    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 all EventRefs into Events by finding their stableIds in |model|.
-     * If a stableId cannot be found and |opt_required| is true, then throw an
-     * Error.
-     * If a stableId cannot be found and |opt_required| is false, then the
-     * EventRef will remain an EventRef.
-     *
-     * @param {!tr.model.Model} model
-     * @param {boolean=} opt_required
-     */
-    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
-  };
-});
+"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":120,"./diagnostic.js":178,"./event_ref.js":180}],184:[function(require,module,exports){
+},{"../../model/event_set.js":126,"./diagnostic.js":184,"./event_ref.js":186}],190:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-require("./related_value_map.js");
-
-'use strict';
-
-global.tr.exportTo('tr.v.d', function () {
-  var COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER = 'ChromeUserFriendlyCategory';
-
-  /**
-   * RelatedHistogramBreakdown encapsulates an additive relationship between
-   * Histograms: the Histogram that contains this RelatedHistogramBreakdown
-   * diagnostic is composed of the Histograms referenced by this
-   * RelatedHistogramBreakdown diagnostic. RelatedHistogramBreakdown is a
-   * "breakdown" of its containing Histogram into its contained Histograms. This
-   * additive relationship can apply to groups of other things besides Events,
-   * such as memory allocations. RelatedHistogramBreakdowns over groups of
-   * Events is expected to be the most common way of building
-   * RelatedHistogramBreakdowns, though it is not the only way. See
-   * buildFromEvents() for an example of how to build a
-   * RelatedHistogramBreakdown from an EventSet and a grouping function.
-   */
-  class RelatedHistogramBreakdown extends tr.v.d.RelatedValueMap {
-    constructor() {
-      super();
-      this.colorScheme = undefined;
-    }
-
-    /**
-     * Add a Histogram by an explicit name to this map.
-     *
-     * @param {string} name
-     * @param {!(tr.v.d.ValueRef|tr.v.Histogram)} value
-     */
-    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;
-    }
-
-    /**
-    * Build a RelatedHistogramBreakdown and its Histograms from |events|.  Group
-    * events using |categoryForEvent|. Add the Histograms to |values|.
-    * Histograms' names are prefixed with |namePrefix|. Histograms are built
-    * with |opt_binBoundaries|. The numeric sample for each Event is derived
-    * from |opt_sampleForEvent|, which defaults to event.cpuSelfTime. The caller
-    * must add the result RelatedHistogramBreakdown to their Histogram's
-    * diagnostics.
-    *
-    * @param {!tr.v.ValueSet} values
-    * @param {string} namePrefix
-    * @param {!tr.model.EventSet} events
-    * @param {!function(!tr.model.Event):string} categoryForEvent
-    * @param {!tr.b.Unit} unit
-    * @param {!function(!tr.model.Event):number=} opt_sampleForEvent
-    * @param {!tr.v.HistogramBinBoundaries=} opt_binBoundaries
-    * @param {*=} opt_this
-    * @return {!RelatedHistogramBreakdown}
-    */
-    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
-  };
-});
+"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":185}],185:[function(require,module,exports){
+},{"./related_value_map.js":191}],191:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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();
-    }
-
-    /**
-     * Lookup a Histogram by name. Returns undefined if |name| is not found.
-     *
-     * @param {string} name
-     * @return {!tr.v.d.ValueRef|!tr.v.Histogram|undefined}
-     */
-    get(name) {
-      return this.valuesByName_.get(name);
-    }
-
-    /**
-     * Add a Histogram by an explicit name to this map.
-     *
-     * @param {string} name
-     * @param {!(tr.v.d.ValueRef|tr.v.Histogram)} value
-     */
-    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 a Histogram implicitly by its own name to this map.
-     *
-     * @param {!(tr.v.d.ValueRef|tr.v.Histogram)} 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 all ValueRefs into Values by looking up their guids in
-     * |valueSet|.
-     * If a value cannot be found and |opt_required| is true, then throw an
-     * Error.
-     * If a value cannot be found and |opt_required| is false, then the ValueRef
-     * will remain a ValueRef.
-     *
-     * @param {!tr.v.ValueSet} valueSet
-     * @param {boolean=} opt_required
-     */
-    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
-  };
-});
+"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":41,"./diagnostic.js":178,"./value_ref.js":188}],186:[function(require,module,exports){
+},{"../../base/iteration_helpers.js":47,"./diagnostic.js":184,"./value_ref.js":194}],192:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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);
-    }
-
-    /**
-     * @param {!(tr.v.d.ValueRef|tr.v.Histogram)} v
-     */
-    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 all ValueRefs into Values by looking up their guids in
-     * |valueSet|.
-     * If a value cannot be found and |opt_required| is true, then throw an
-     * Error.
-     * If a value cannot be found and |opt_required| is false, then the ValueRef
-     * will remain a ValueRef.
-     *
-     * @param {!tr.v.ValueSet} valueSet
-     * @param {boolean=} opt_required
-     */
-    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
-  };
-});
+"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":41,"./diagnostic.js":178,"./value_ref.js":188}],187:[function(require,module,exports){
+},{"../../base/iteration_helpers.js":47,"./diagnostic.js":184,"./value_ref.js":194}],193:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-require("./diagnostic.js");
-require("../numeric.js");
-
-'use strict';
-
-global.tr.exportTo('tr.v.d', function () {
-  class Scalar extends tr.v.d.Diagnostic {
-    /**
-     * @param {!tr.v.ScalarNumeric} value
-     */
-    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
-  };
-});
+"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":190,"./diagnostic.js":178}],188:[function(require,module,exports){
+},{"../numeric.js":196,"./diagnostic.js":184}],194:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-require("../../base/base.js");
-
-'use strict';
-
-global.tr.exportTo('tr.v.d', function () {
-  /** @constructor */
-  function ValueRef(guid) {
-    this.guid = guid;
-  }
-
-  return {
-    ValueRef: ValueRef
-  };
-});
+"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":28}],189:[function(require,module,exports){
+},{"../../base/base.js":34}],195:[function(require,module,exports){
 (function (global){
-"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.
-**/
-
-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 {
-    /**
-     * @param {!tr.b.Range} range
-     */
-    constructor(range) {
-      this.range = range;
-      this.count = 0;
-      this.diagnosticMaps = [];
-    }
-
-    /**
-     * @param {*} value
-     */
-    addSample(value) {
-      this.count += 1;
-    }
-
-    /**
-     * @param {!tr.v.d.DiagnosticMap} diagnostics
-     */
-    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];
-      }
-      // It's more efficient to serialize these 2 fields in an array. If you
-      // add any other fields, you should re-evaluate whether it would be more
-      // efficient to serialize as a dict.
-      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]]);
-
-  /**
-   * This is basically a histogram, but so much more.
-   * Histogram is serializable using asDict/fromDict.
-   * Histogram computes several statistics of its contents.
-   * Histograms can be merged.
-   * getDifferenceSignificance() test whether one Histogram is statistically
-   * significantly different from another Histogram.
-   * Histogram stores a random sample of the exact number values added to it.
-   * Histogram stores a random sample of optional per-sample DiagnosticMaps.
-   * Histogram is visualized by <tr-v-ui-histogram-span>, which supports
-   * selecting bins, and visualizing the DiagnosticMaps of selected bins.
-   *
-   * @param {!tr.b.Unit} unit
-   * @param {!tr.v.HistogramBinBoundaries=} opt_binBoundaries
-   */
-  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);
-      }
-
-      // If this Histogram is being deserialized, then its guid will be set by
-      // fromDict().
-      // If this Histogram is being computed by a metric, then its guid will be
-      // allocated the first time the guid is gotten by asDict().
-      this.guid_ = undefined;
-
-      // Serialize binBoundaries here instead of holding a reference to it in
-      // case it is modified.
-      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;
-    }
-
-    /**
-     * Build a Histogram from a set of samples in order to effectively merge a
-     * set of ScalarNumerics.
-     * The range of the resulting histogram is determined by the smallest and
-     * largest sample value, which is unpredictable.
-     * https://github.com/catapult-project/catapult/issues/2685
-     *
-     * @param {!tr.b.Unit} unit
-     * @param {!Array.<number>} samples
-     * @return {!Histogram}
-     */
-    static buildFromSamples(unit, samples) {
-      var boundaries = HistogramBinBoundaries.createFromSamples(samples);
-      var result = new Histogram(unit, boundaries);
-      result.maxNumSampleValues = 1000;
-
-      // TODO(eakuefner): Propagate diagnosticMaps?
-      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_;
-    }
-
-    /**
-     * Requires that units agree.
-     * Returns DONT_CARE if that is the units' improvementDirection.
-     * Returns SIGNIFICANT if the Mann-Whitney U test returns a
-     * p-value less than opt_alpha or DEFAULT_ALPHA. Returns INSIGNIFICANT if
-     * the p-value is greater than alpha.
-     *
-     * @param {!tr.v.Histogram} other
-     * @param {number=} opt_alpha
-     * @return {!tr.b.Statistics.Significance}
-     */
-    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;
-    }
-
-    /*
-     * Compute an approximation of percentile based on the counts in the bins.
-     * If the real percentile lies within |this.range| then the result of
-     * the function will deviate from the real percentile by at most
-     * the maximum width of the bin(s) within which the point(s)
-     * from which the real percentile would be calculated lie.
-     * If the real percentile is outside |this.range| then the function
-     * returns the closest range limit: |this.range.min| or |this.range.max|.
-     *
-     * @param {number} percent The percent must be between 0.0 and 1.0.
-     */
-    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) {
-      // Don't use subtraction to avoid arithmetic overflow.
-      var binIndex = tr.b.findHighIndexInSortedArray(this.allBins, b => value < b.range.max ? -1 : 1);
-      return this.allBins[binIndex] || this.overflowBin;
-    }
-
-    /**
-     * @param {number|*} value
-     * @param {(!Object|!tr.v.d.DiagnosticMap)=} opt_diagnostics
-     */
-    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);
-    }
-
-    /**
-     * Return true if this Histogram can be added to |other|.
-     *
-     * @param {!tr.v.Histogram} other
-     * @return {boolean}
-     */
-    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;
-    }
-
-    /**
-     * Add |other| to this Histogram in-place if they can be added.
-     *
-     * @param {!tr.v.Histogram} other
-     */
-    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]);
-      }
-    }
-
-    /**
-     * Controls which statistics are exported to dashboard for this numeric.
-     * The |summaryOptions| parameter is a dictionary with optional boolean
-     * fields |count|, |sum|, |avg|, |std|, |min|, |max| and an optional
-     * array field |percentile|.
-     * Each percentile should be a number between 0.0 and 1.0.
-     * The options not included in the |summaryOptions| will not change.
-     */
-    customizeSummaryOptions(summaryOptions) {
-      tr.b.iterItems(summaryOptions, (key, value) => this.summaryOptions.set(key, value));
-    }
-
-    /**
-     * Returns a Map {statisticName: ScalarNumeric}.
-     *
-     * Each enabled summary option produces the corresponding value:
-     * min, max, count, sum, avg, or std.
-     * Each percentile 0.x produces pct_0x0.
-     * Each percentile 0.xx produces pct_0xx.
-     * Each percentile 0.xxy produces pct_0xx_y.
-     * Percentile 1.0 produces pct_100.
-     *
-     * @return {!Map.<string, ScalarNumeric>}
-     */
-    get statisticsScalars() {
-      function statNameToKey(stat) {
-        switch (stat) {
-          case 'std':
-            return 'stddev';
-          case 'avg':
-            return 'mean';
-        }
-        return stat;
-      }
-      /**
-       * Converts the given percent to a string in the format specified above.
-       * @param {number} percent The percent must be between 0.0 and 1.0.
-       */
-      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');
-        // Pad short strings with zeros.
-        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_;
-    }
-
-    /**
-     * Create a new Histogram object that is exactly the same as this one, with
-     * this Histogram's name, unit, and binBoundaries, guid, bin counts, and
-     * diagnostics.
-     * @return {!tr.v.Histogram}
-     */
-    clone() {
-      return Histogram.fromDict(this.asDict());
-    }
-
-    /**
-     * Create a new Histogram with this Histogram's name, unit, and
-     * binBoundaries, but not its guid, bin counts, or diagnostics.
-     * @return {!tr.v.Histogram}
-     */
-    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_() {
-      // dict.centralBins may be either an array or a dict, whichever is more
-      // efficient.
-      // The overhead of the array form is significant when the histogram is
-      // sparse, and the overhead of the dict form is significant when the
-      // histogram is dense.
-      // The dict form is more efficient when more than half of centralBins are
-      // empty. The array form is more efficient when fewer than half of
-      // centralBins are empty.
-
-      var numCentralBins = this.centralBins.length;
-
-      // If all centralBins are empty, then don't serialize anything for them.
-      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();
-
-  /**
-   * Reusable builder for tr.v.Histogram objects.
-   *
-   * The bins of the numeric are specified by adding the desired boundaries
-   * between bins. Initially, the builder has only a single boundary:
-   *
-   *            range.min=range.max
-   *                     |
-   *                     |
-   *   -MAX_INT <--------|------------------------------------------> +MAX_INT
-   *       :  resulting  :                   resulting                    :
-   *       :  underflow  :                    overflow                    :
-   *       :     bin     :                      bin                       :
-   *
-   * More boundaries can be added (in increasing order) using addBinBoundary,
-   * addLinearBins and addExponentialBins:
-   *
-   *                range.min                           range.max
-   *                     |         |         |     |         |
-   *                     |         |         |     |         |
-   *   -MAX_INT <--------|---------|---------|-----|---------|------> +MAX_INT
-   *       :  resulting  : result. : result. :     : result. : resulting  :
-   *       :  underflow  : central : central : ... : central :  overflow  :
-   *       :     bin     :  bin 0  :  bin 1  :     : bin N-1 :    bin     :
-   *
-   * An important feature of the builder is that it's reusable, i.e. it can be
-   * used to build multiple numerics with the same unit and bin structure.
-   *
-   */
-  class HistogramBinBoundaries {
-    /**
-     * Create a linearly scaled tr.v.HistogramBinBoundaries with |numBins| bins
-     * ranging from |min| to |max|.
-     *
-     * @param {number} min
-     * @param {number} max
-     * @param {number} numBins
-     * @return {tr.v.HistogramBinBoundaries}
-     */
-    static createLinear(min, max, numBins) {
-      return new HistogramBinBoundaries(min).addLinearBins(max, numBins);
-    }
-
-    /**
-     * Create an exponentially scaled tr.v.HistogramBinBoundaries with |numBins|
-     * bins ranging from |min| to |max|.
-     *
-     * @param {number} min
-     * @param {number} max
-     * @param {number} numBins
-     * @return {tr.v.HistogramBinBoundaries}
-     */
-    static createExponential(min, max, numBins) {
-      return new HistogramBinBoundaries(min).addExponentialBins(max, numBins);
-    }
-
-    /**
-     * @param {Array.<number>} binBoundaries
-     */
-    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();
-      // Prevent non-numeric samples from introducing NaNs into the range.
-      for (var sample of samples) if (!isNaN(Math.max(sample))) range.addValue(sample);
-
-      // HistogramBinBoundaries.addLinearBins() requires this.
-      if (range.isEmpty) range.addValue(1);
-      if (range.min === range.max) range.addValue(range.min - 1);
-
-      // This optimizes the resolution when samples are uniformly distributed
-      // (which is almost never the case).
-      var numBins = Math.ceil(Math.sqrt(samples.length));
-      var builder = new HistogramBinBoundaries(range.min);
-      builder.addLinearBins(range.max, numBins);
-      return builder;
-    }
-
-    /**
-     * @param {number} minBinBoundary The minimum boundary between bins, namely
-     *     the underflow bin and the first central bin (or the overflow bin if
-     *     no other boundaries are added later).
-     */
-    constructor(minBinBoundary) {
-      this.boundaries_ = undefined;
-      this.builder_ = [minBinBoundary];
-      this.range_ = new tr.b.Range();
-      this.range_.addValue(minBinBoundary);
-    }
-
-    get range() {
-      return this.range_;
-    }
-
-    asDict() {
-      // Copy builder_ in case ours is modified later.
-      return this.builder_.slice();
-    }
-
-    static fromDict(dict) {
-      // When loading a results2.html with many Histograms with the same bin
-      // boundaries, caching the HistogramBinBoundaries not only speeds up
-      // loading, but also prevents a bug where build_ is occasionally
-      // non-deterministic, which causes similar Histograms to be unmergeable.
-      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;
-    }
-
-    /**
-     * Yield Ranges of adjacent boundaries.
-     */
-    *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);
-      }
-    }
-
-    /**
-     * Add a bin boundary |nextMaxBinBoundary| to the builder.
-     *
-     * This operation effectively corresponds to appending a new central bin
-     * with the range [this.range.max, nextMaxBinBoundary].
-     *
-     * @param {number} nextMaxBinBoundary The added bin boundary (must be
-     *     greater than |this.maxMinBoundary|).
-     */
-    addBinBoundary(nextMaxBinBoundary) {
-      if (nextMaxBinBoundary <= this.range.max) {
-        throw new Error('The added max bin boundary must be larger than ' + 'the current max boundary');
-      }
-
-      // If boundaries_ had been built, then clear them.
-      this.boundaries_ = undefined;
-
-      this.builder_.push(nextMaxBinBoundary);
-      this.range.addValue(nextMaxBinBoundary);
-      return this;
-    }
-
-    /**
-     * Add |binCount| linearly scaled bin boundaries up to |nextMaxBinBoundary|
-     * to the builder.
-     *
-     * This operation corresponds to appending |binCount| central bins of
-     * constant range width
-     * W = ((|nextMaxBinBoundary| - |this.range.max|) / |binCount|)
-     * with the following ranges:
-     *
-     *   [|this.maxMinBoundary|, |this.maxMinBoundary| + W]
-     *   [|this.maxMinBoundary| + W, |this.maxMinBoundary| + 2W]
-     *   [|this.maxMinBoundary| + 2W, |this.maxMinBoundary| + 3W]
-     *   ...
-     *   [|this.maxMinBoundary| + (|binCount| - 2) * W,
-     *    |this.maxMinBoundary| + (|binCount| - 2) * W]
-     *   [|this.maxMinBoundary| + (|binCount| - 1) * W,
-     *    |nextMaxBinBoundary|]
-     *
-     * @param {number} nextBinBoundary The last added bin boundary (must be
-     *     greater than |this.maxMinBoundary|).
-     * @param {number} binCount Number of bins to be added (must be positive).
-     */
-    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');
-      }
-
-      // If boundaries_ had been built, then clear them.
-      this.boundaries_ = undefined;
-
-      this.builder_.push([HistogramBinBoundaries.SLICE_TYPE.LINEAR, nextMaxBinBoundary, binCount]);
-      this.range.addValue(nextMaxBinBoundary);
-      return this;
-    }
-
-    /**
-     * Add |binCount| exponentially scaled bin boundaries up to
-     * |nextMaxBinBoundary| to the builder.
-     *
-     * This operation corresponds to appending |binCount| central bins with
-     * a constant difference between the logarithms of their range min and max
-     * D = ((ln(|nextMaxBinBoundary|) - ln(|this.range.max|)) / |binCount|)
-     * with the following ranges:
-     *
-     *   [|this.maxMinBoundary|, |this.maxMinBoundary| * exp(D)]
-     *   [|this.maxMinBoundary| * exp(D), |this.maxMinBoundary| * exp(2D)]
-     *   [|this.maxMinBoundary| * exp(2D), |this.maxMinBoundary| * exp(3D)]
-     *   ...
-     *   [|this.maxMinBoundary| * exp((|binCount| - 2) * D),
-     *    |this.maxMinBoundary| * exp((|binCount| - 2) * D)]
-     *   [|this.maxMinBoundary| * exp((|binCount| - 1) * D),
-     *    |nextMaxBinBoundary|]
-     *
-     * This method requires that the current max bin boundary is positive.
-     *
-     * @param {number} nextBinBoundary The last added bin boundary (must be
-     *     greater than |this.maxMinBoundary|).
-     * @param {number} binCount Number of bins to be added (must be positive).
-     */
-    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');
-      }
-
-      // If boundaries_ had been built, then clear them.
-      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
-  };
-});
+"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":41,"../base/range.js":47,"../base/running_statistics.js":50,"../base/sorted_array_utils.js":52,"../base/statistics.js":53,"../base/unit.js":57,"./diagnostics/diagnostic_map.js":179,"./numeric.js":190}],190:[function(require,module,exports){
+},{"../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";
-/**
-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.
-**/
-
-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';
-
-      // Infinity and NaN are left out of JSON for security reasons that do not
-      // apply to our use cases.
-      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) {
-      // Infinity and NaN are left out of JSON for security reasons that do not
-      // apply to our use cases.
-      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
-  };
-});
+"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":41,"../base/unit.js":57}],191:[function(require,module,exports){
+},{"../base/iteration_helpers.js":47,"../base/unit.js":63}],197:[function(require,module,exports){
 /**
  * @license
  * Copyright 2016 Google Inc. All rights reserved.
@@ -35863,79 +12875,159 @@
  */
 'use strict';
 
-const ExtensionProtocol = require('../../../lighthouse-core/gather/drivers/extension');
-const RawProtocol = require('../../../lighthouse-core/gather/drivers/raw');
+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 STORAGE_KEY = 'lighthouse_audits';
-const _flatten = arr => [].concat.apply([], arr);
+const ReportGenerator = require('../../../lighthouse-core/report/report-generator');
 
-window.createPageAndPopulate = function(results) {
-  const tabURL = chrome.extension.getURL('/pages/report.html');
-  chrome.tabs.create({url: tabURL}, tab => {
-    // Results will be lost when using sendMessage without waiting for the
-    // receiving side to load. Once it loads, we get a message -
-    // ready=true. Respond to this message with the results.
-    chrome.runtime.onMessage.addListener((message, sender, respond) => {
-      if (message && message.ready && sender.tab.id === tab.id) {
-        return respond(results);
-      }
-    });
-  });
-};
+const STORAGE_KEY = 'lighthouse_v2';
+const isExtension = window.chrome && chrome.runtime;
+const _flatten = arr => [].concat(...arr);
+const _uniq = arr => Array.from(new Set(arr));
+
+let lighthouseIsRunning = false;
+let latestStatusLog = [];
+
+/**
+ * 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 (isExtension) {
+    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});
+  }
+}
+
+/**
+ * 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>} requestedAudits Names of audits to run.
+ * @param {!Object<boolean>} aggregationTags Ids of aggregation tags to include.
  * @return {!Promise}
  */
-window.runLighthouseForConnection = function(connection, url, options, requestedAudits) {
+window.runLighthouseForConnection = function(connection, url, options, aggregationTags) {
   // Always start with a freshly parsed default config.
   const runConfig = JSON.parse(JSON.stringify(defaultConfig));
 
-  // Filter out audits not requested.
-  requestedAudits = new Set(requestedAudits);
-  runConfig.audits = runConfig.audits.filter(audit => requestedAudits.has(audit));
+   // Change tags object to a plain array of tag strings
+  const chosenTags = aggregationTags.filter(tag => tag.value).map(tag => tag.id);
+  Config.rebuildConfigFromTags(runConfig, chosenTags);
   const config = new Config(runConfig);
 
   // Add url and config to fresh options object.
   const runOptions = Object.assign({}, options, {url, config});
 
+  lighthouseIsRunning = true;
+  updateBadgeUI(url);
+
   // Run Lighthouse.
-  return Runner.run(connection, runOptions);
+  return Runner.run(connection, runOptions)
+    .then(result => {
+      lighthouseIsRunning = false;
+      updateBadgeUI();
+      filterOutArtifacts(result);
+      return result;
+    })
+    .catch(err => {
+      lighthouseIsRunning = false;
+      updateBadgeUI();
+      throw err;
+    });
 };
 
 /**
  * @param {!Object} options Lighthouse options.
- * @param {!Array<string>} requestedAudits Names of audits to run.
+ * @param {!Object<boolean>} aggregationTags Ids of aggregation tags to include.
  * @return {!Promise}
  */
-window.runLighthouseInExtension = function(options, requestedAudits) {
+window.runLighthouseInExtension = function(options, aggregationTags) {
   // Default to 'info' logging level.
   log.setLevel('info');
   const connection = new ExtensionProtocol();
   return connection.getCurrentTabURL()
-    .then(url => window.runLighthouseForConnection(connection, url, options, requestedAudits))
-    .then(results => window.createPageAndPopulate(results));
+    .then(url => window.runLighthouseForConnection(connection, url, options, aggregationTags))
+    .then(results => {
+      const blobURL = window.createReportPageAsBlob(results, 'extension');
+      chrome.tabs.create({url: blobURL});
+    });
 };
 
 /**
  * @param {!RawProtocol.Port} port
  * @param {string} url
  * @param {!Object} options Lighthouse options.
- * @param {!Array<string>} requestedAudits Names of audits to run.
+ * @param {!Array<!{id: string, value: boolean}>} aggregationTags Ids of aggregation tags to include.
  * @return {!Promise}
  */
-window.runLighthouseInWorker = function(port, url, options, requestedAudits) {
+window.runLighthouseInWorker = function(port, url, options, aggregationTags) {
   // Default to 'info' logging level.
   log.setLevel('info');
   const connection = new RawProtocol(port);
-  return window.runLighthouseForConnection(connection, url, options, requestedAudits);
+  return window.runLighthouseForConnection(connection, url, options, aggregationTags);
+};
+
+/**
+ * @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;
+};
+
+const tagMap = {
+  'pwa': 'Progressive Web App audits',
+  'perf': 'Performance metrics & diagnostics',
+  'best_practices': 'Developer best practices'
+};
+
+window.getDefaultAggregationTags = function() {
+  return _uniq(_flatten(getDefaultAggregations().map(agg => agg.tags))).map(tag => {
+    return {
+      id: tag,
+      value: true,
+      name: tagMap[tag]
+    };
+  });
 };
 
 /**
@@ -35949,6 +13041,9 @@
       if (aggregation.items.length === 1) {
         return {
           name: aggregation.name,
+          id: aggregation.id,
+          tags: aggregation.tags,
+          description: aggregation.description,
           audits: aggregation.items[0].audits,
         };
       }
@@ -35958,6 +13053,9 @@
   ).map(aggregation => {
     return {
       name: aggregation.name,
+      id: aggregation.id,
+      tags: aggregation.tags,
+      description: aggregation.description,
       audits: Object.keys(aggregation.audits)
     };
   });
@@ -35965,18 +13063,12 @@
 
 /**
  * Save currently selected set of aggregation categories to local storage.
- * @param {!Array<{name: string, audits: !Array<string>}>} selectedAggregations
+ * @param {!Array<{id: string, value: boolean}>} selectedAggregations
  */
-window.saveSelectedAggregations = function(selectedAggregations) {
+window.saveSelectedTags = function(selectedTags) {
   const storage = {
-    [STORAGE_KEY]: {}
+    [STORAGE_KEY]: selectedTags
   };
-
-  window.getDefaultAggregations().forEach(audit => {
-    const selected = selectedAggregations.indexOf(audit.name) > -1;
-    storage[STORAGE_KEY][audit.name] = selected;
-  });
-
   chrome.storage.local.set(storage);
 };
 
@@ -35984,29 +13076,53 @@
  * Load selected aggregation categories from local storage.
  * @return {!Promise<!Object<boolean>>}
  */
-window.loadSelectedAggregations = function() {
+window.loadSavedTags = function() {
   return new Promise(resolve => {
     chrome.storage.local.get(STORAGE_KEY, result => {
-      // Start with list of all default aggregations set to true so list is
-      // always up to date.
-      const defaultAggregations = {};
-      window.getDefaultAggregations().forEach(aggregation => {
-        defaultAggregations[aggregation.name] = true;
-      });
-
-      // Load saved aggregation selections.
-      const savedSelections = result[STORAGE_KEY];
-
-      // Overwrite defaults with any saved aggregation selections.
-      resolve(
-        Object.assign(defaultAggregations, savedSelections)
-      );
+      const tags = result && result[STORAGE_KEY];
+      resolve(Array.isArray(tags) ? tags : []);
     });
   });
 };
 
+/**
+ * Combine saved settings with any new tags
+ */
+window.resolveTags = function() {
+  return loadSavedTags().then(selectedTags => {
+    // start with all default tags, so the list is up to date
+    const tags = [].concat(window.getDefaultAggregationTags());
+
+    if (Array.isArray(selectedTags)) {
+      // Override the tags with anything disabled by the user
+      selectedTags.forEach(selectedTag => {
+        const setting = tags.find(t => t.id == selectedTag.id);
+        if (setting) {
+          setting.value = selectedTag.value;
+        }
+      });
+    }
+
+    return tags;
+  });
+};
+
+
 window.listenForStatus = function(callback) {
-  log.events.addListener('status', 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;
 };
 
 if (window.chrome && chrome.runtime) {
@@ -36017,7 +13133,11 @@
   });
 }
 
-},{"../../../lighthouse-core/config/config":2,"../../../lighthouse-core/config/default.json":3,"../../../lighthouse-core/gather/drivers/extension":14,"../../../lighthouse-core/gather/drivers/raw":15,"../../../lighthouse-core/lib/log":21,"../../../lighthouse-core/runner":27}],192:[function(require,module,exports){
+window.getManifest = function() {
+  return isExtension && chrome.runtime.getManifest();
+};
+
+},{"../../../lighthouse-core/config/config":4,"../../../lighthouse-core/config/default.json":5,"../../../lighthouse-core/gather/connections/extension":14,"../../../lighthouse-core/gather/connections/raw":15,"../../../lighthouse-core/lib/log":24,"../../../lighthouse-core/report/report-generator":32,"../../../lighthouse-core/runner":33}],198:[function(require,module,exports){
 'use strict'
 
 exports.byteLength = byteLength
@@ -36133,9 +13253,9 @@
   return parts.join('')
 }
 
-},{}],193:[function(require,module,exports){
+},{}],199:[function(require,module,exports){
 
-},{}],194:[function(require,module,exports){
+},{}],200:[function(require,module,exports){
 (function (global){
 /*!
  * The buffer module from node.js, for the browser.
@@ -37928,7 +15048,7 @@
 }
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"base64-js":192,"ieee754":196,"isarray":197}],195:[function(require,module,exports){
+},{"base64-js":198,"ieee754":202,"isarray":203}],201:[function(require,module,exports){
 // Copyright Joyent, Inc. and other Node contributors.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a
@@ -38232,7 +15352,7 @@
   return arg === void 0;
 }
 
-},{}],196:[function(require,module,exports){
+},{}],202:[function(require,module,exports){
 exports.read = function (buffer, offset, isLE, mLen, nBytes) {
   var e, m
   var eLen = nBytes * 8 - mLen - 1
@@ -38318,14 +15438,14 @@
   buffer[offset + i - d] |= s * 128
 }
 
-},{}],197:[function(require,module,exports){
+},{}],203:[function(require,module,exports){
 var toString = {}.toString;
 
 module.exports = Array.isArray || function (arr) {
   return toString.call(arr) == '[object Array]';
 };
 
-},{}],198:[function(require,module,exports){
+},{}],204:[function(require,module,exports){
 (function (process){
 // Copyright Joyent, Inc. and other Node contributors.
 //
@@ -38553,7 +15673,7 @@
 ;
 
 }).call(this,require('_process'))
-},{"_process":199}],199:[function(require,module,exports){
+},{"_process":205}],205:[function(require,module,exports){
 // shim for using process in browser
 var process = module.exports = {};
 
@@ -38735,1474 +15855,6 @@
 };
 process.umask = function() { return 0; };
 
-},{}],200:[function(require,module,exports){
-(function (global){
-/*! https://mths.be/punycode v1.4.1 by @mathias */
-;(function(root) {
-
-	/** Detect free variables */
-	var freeExports = typeof exports == 'object' && exports &&
-		!exports.nodeType && exports;
-	var freeModule = typeof module == 'object' && module &&
-		!module.nodeType && module;
-	var freeGlobal = typeof global == 'object' && global;
-	if (
-		freeGlobal.global === freeGlobal ||
-		freeGlobal.window === freeGlobal ||
-		freeGlobal.self === freeGlobal
-	) {
-		root = freeGlobal;
-	}
-
-	/**
-	 * The `punycode` object.
-	 * @name punycode
-	 * @type Object
-	 */
-	var punycode,
-
-	/** Highest positive signed 32-bit float value */
-	maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
-
-	/** Bootstring parameters */
-	base = 36,
-	tMin = 1,
-	tMax = 26,
-	skew = 38,
-	damp = 700,
-	initialBias = 72,
-	initialN = 128, // 0x80
-	delimiter = '-', // '\x2D'
-
-	/** Regular expressions */
-	regexPunycode = /^xn--/,
-	regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars
-	regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators
-
-	/** Error messages */
-	errors = {
-		'overflow': 'Overflow: input needs wider integers to process',
-		'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
-		'invalid-input': 'Invalid input'
-	},
-
-	/** Convenience shortcuts */
-	baseMinusTMin = base - tMin,
-	floor = Math.floor,
-	stringFromCharCode = String.fromCharCode,
-
-	/** Temporary variable */
-	key;
-
-	/*--------------------------------------------------------------------------*/
-
-	/**
-	 * A generic error utility function.
-	 * @private
-	 * @param {String} type The error type.
-	 * @returns {Error} Throws a `RangeError` with the applicable error message.
-	 */
-	function error(type) {
-		throw new RangeError(errors[type]);
-	}
-
-	/**
-	 * A generic `Array#map` utility function.
-	 * @private
-	 * @param {Array} array The array to iterate over.
-	 * @param {Function} callback The function that gets called for every array
-	 * item.
-	 * @returns {Array} A new array of values returned by the callback function.
-	 */
-	function map(array, fn) {
-		var length = array.length;
-		var result = [];
-		while (length--) {
-			result[length] = fn(array[length]);
-		}
-		return result;
-	}
-
-	/**
-	 * A simple `Array#map`-like wrapper to work with domain name strings or email
-	 * addresses.
-	 * @private
-	 * @param {String} domain The domain name or email address.
-	 * @param {Function} callback The function that gets called for every
-	 * character.
-	 * @returns {Array} A new string of characters returned by the callback
-	 * function.
-	 */
-	function mapDomain(string, fn) {
-		var parts = string.split('@');
-		var result = '';
-		if (parts.length > 1) {
-			// In email addresses, only the domain name should be punycoded. Leave
-			// the local part (i.e. everything up to `@`) intact.
-			result = parts[0] + '@';
-			string = parts[1];
-		}
-		// Avoid `split(regex)` for IE8 compatibility. See #17.
-		string = string.replace(regexSeparators, '\x2E');
-		var labels = string.split('.');
-		var encoded = map(labels, fn).join('.');
-		return result + encoded;
-	}
-
-	/**
-	 * Creates an array containing the numeric code points of each Unicode
-	 * character in the string. While JavaScript uses UCS-2 internally,
-	 * this function will convert a pair of surrogate halves (each of which
-	 * UCS-2 exposes as separate characters) into a single code point,
-	 * matching UTF-16.
-	 * @see `punycode.ucs2.encode`
-	 * @see <https://mathiasbynens.be/notes/javascript-encoding>
-	 * @memberOf punycode.ucs2
-	 * @name decode
-	 * @param {String} string The Unicode input string (UCS-2).
-	 * @returns {Array} The new array of code points.
-	 */
-	function ucs2decode(string) {
-		var output = [],
-		    counter = 0,
-		    length = string.length,
-		    value,
-		    extra;
-		while (counter < length) {
-			value = string.charCodeAt(counter++);
-			if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
-				// high surrogate, and there is a next character
-				extra = string.charCodeAt(counter++);
-				if ((extra & 0xFC00) == 0xDC00) { // low surrogate
-					output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
-				} else {
-					// unmatched surrogate; only append this code unit, in case the next
-					// code unit is the high surrogate of a surrogate pair
-					output.push(value);
-					counter--;
-				}
-			} else {
-				output.push(value);
-			}
-		}
-		return output;
-	}
-
-	/**
-	 * Creates a string based on an array of numeric code points.
-	 * @see `punycode.ucs2.decode`
-	 * @memberOf punycode.ucs2
-	 * @name encode
-	 * @param {Array} codePoints The array of numeric code points.
-	 * @returns {String} The new Unicode string (UCS-2).
-	 */
-	function ucs2encode(array) {
-		return map(array, function(value) {
-			var output = '';
-			if (value > 0xFFFF) {
-				value -= 0x10000;
-				output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
-				value = 0xDC00 | value & 0x3FF;
-			}
-			output += stringFromCharCode(value);
-			return output;
-		}).join('');
-	}
-
-	/**
-	 * Converts a basic code point into a digit/integer.
-	 * @see `digitToBasic()`
-	 * @private
-	 * @param {Number} codePoint The basic numeric code point value.
-	 * @returns {Number} The numeric value of a basic code point (for use in
-	 * representing integers) in the range `0` to `base - 1`, or `base` if
-	 * the code point does not represent a value.
-	 */
-	function basicToDigit(codePoint) {
-		if (codePoint - 48 < 10) {
-			return codePoint - 22;
-		}
-		if (codePoint - 65 < 26) {
-			return codePoint - 65;
-		}
-		if (codePoint - 97 < 26) {
-			return codePoint - 97;
-		}
-		return base;
-	}
-
-	/**
-	 * Converts a digit/integer into a basic code point.
-	 * @see `basicToDigit()`
-	 * @private
-	 * @param {Number} digit The numeric value of a basic code point.
-	 * @returns {Number} The basic code point whose value (when used for
-	 * representing integers) is `digit`, which needs to be in the range
-	 * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
-	 * used; else, the lowercase form is used. The behavior is undefined
-	 * if `flag` is non-zero and `digit` has no uppercase form.
-	 */
-	function digitToBasic(digit, flag) {
-		//  0..25 map to ASCII a..z or A..Z
-		// 26..35 map to ASCII 0..9
-		return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
-	}
-
-	/**
-	 * Bias adaptation function as per section 3.4 of RFC 3492.
-	 * https://tools.ietf.org/html/rfc3492#section-3.4
-	 * @private
-	 */
-	function adapt(delta, numPoints, firstTime) {
-		var k = 0;
-		delta = firstTime ? floor(delta / damp) : delta >> 1;
-		delta += floor(delta / numPoints);
-		for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
-			delta = floor(delta / baseMinusTMin);
-		}
-		return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
-	}
-
-	/**
-	 * Converts a Punycode string of ASCII-only symbols to a string of Unicode
-	 * symbols.
-	 * @memberOf punycode
-	 * @param {String} input The Punycode string of ASCII-only symbols.
-	 * @returns {String} The resulting string of Unicode symbols.
-	 */
-	function decode(input) {
-		// Don't use UCS-2
-		var output = [],
-		    inputLength = input.length,
-		    out,
-		    i = 0,
-		    n = initialN,
-		    bias = initialBias,
-		    basic,
-		    j,
-		    index,
-		    oldi,
-		    w,
-		    k,
-		    digit,
-		    t,
-		    /** Cached calculation results */
-		    baseMinusT;
-
-		// Handle the basic code points: let `basic` be the number of input code
-		// points before the last delimiter, or `0` if there is none, then copy
-		// the first basic code points to the output.
-
-		basic = input.lastIndexOf(delimiter);
-		if (basic < 0) {
-			basic = 0;
-		}
-
-		for (j = 0; j < basic; ++j) {
-			// if it's not a basic code point
-			if (input.charCodeAt(j) >= 0x80) {
-				error('not-basic');
-			}
-			output.push(input.charCodeAt(j));
-		}
-
-		// Main decoding loop: start just after the last delimiter if any basic code
-		// points were copied; start at the beginning otherwise.
-
-		for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
-
-			// `index` is the index of the next character to be consumed.
-			// Decode a generalized variable-length integer into `delta`,
-			// which gets added to `i`. The overflow checking is easier
-			// if we increase `i` as we go, then subtract off its starting
-			// value at the end to obtain `delta`.
-			for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
-
-				if (index >= inputLength) {
-					error('invalid-input');
-				}
-
-				digit = basicToDigit(input.charCodeAt(index++));
-
-				if (digit >= base || digit > floor((maxInt - i) / w)) {
-					error('overflow');
-				}
-
-				i += digit * w;
-				t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
-
-				if (digit < t) {
-					break;
-				}
-
-				baseMinusT = base - t;
-				if (w > floor(maxInt / baseMinusT)) {
-					error('overflow');
-				}
-
-				w *= baseMinusT;
-
-			}
-
-			out = output.length + 1;
-			bias = adapt(i - oldi, out, oldi == 0);
-
-			// `i` was supposed to wrap around from `out` to `0`,
-			// incrementing `n` each time, so we'll fix that now:
-			if (floor(i / out) > maxInt - n) {
-				error('overflow');
-			}
-
-			n += floor(i / out);
-			i %= out;
-
-			// Insert `n` at position `i` of the output
-			output.splice(i++, 0, n);
-
-		}
-
-		return ucs2encode(output);
-	}
-
-	/**
-	 * Converts a string of Unicode symbols (e.g. a domain name label) to a
-	 * Punycode string of ASCII-only symbols.
-	 * @memberOf punycode
-	 * @param {String} input The string of Unicode symbols.
-	 * @returns {String} The resulting Punycode string of ASCII-only symbols.
-	 */
-	function encode(input) {
-		var n,
-		    delta,
-		    handledCPCount,
-		    basicLength,
-		    bias,
-		    j,
-		    m,
-		    q,
-		    k,
-		    t,
-		    currentValue,
-		    output = [],
-		    /** `inputLength` will hold the number of code points in `input`. */
-		    inputLength,
-		    /** Cached calculation results */
-		    handledCPCountPlusOne,
-		    baseMinusT,
-		    qMinusT;
-
-		// Convert the input in UCS-2 to Unicode
-		input = ucs2decode(input);
-
-		// Cache the length
-		inputLength = input.length;
-
-		// Initialize the state
-		n = initialN;
-		delta = 0;
-		bias = initialBias;
-
-		// Handle the basic code points
-		for (j = 0; j < inputLength; ++j) {
-			currentValue = input[j];
-			if (currentValue < 0x80) {
-				output.push(stringFromCharCode(currentValue));
-			}
-		}
-
-		handledCPCount = basicLength = output.length;
-
-		// `handledCPCount` is the number of code points that have been handled;
-		// `basicLength` is the number of basic code points.
-
-		// Finish the basic string - if it is not empty - with a delimiter
-		if (basicLength) {
-			output.push(delimiter);
-		}
-
-		// Main encoding loop:
-		while (handledCPCount < inputLength) {
-
-			// All non-basic code points < n have been handled already. Find the next
-			// larger one:
-			for (m = maxInt, j = 0; j < inputLength; ++j) {
-				currentValue = input[j];
-				if (currentValue >= n && currentValue < m) {
-					m = currentValue;
-				}
-			}
-
-			// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
-			// but guard against overflow
-			handledCPCountPlusOne = handledCPCount + 1;
-			if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
-				error('overflow');
-			}
-
-			delta += (m - n) * handledCPCountPlusOne;
-			n = m;
-
-			for (j = 0; j < inputLength; ++j) {
-				currentValue = input[j];
-
-				if (currentValue < n && ++delta > maxInt) {
-					error('overflow');
-				}
-
-				if (currentValue == n) {
-					// Represent delta as a generalized variable-length integer
-					for (q = delta, k = base; /* no condition */; k += base) {
-						t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
-						if (q < t) {
-							break;
-						}
-						qMinusT = q - t;
-						baseMinusT = base - t;
-						output.push(
-							stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
-						);
-						q = floor(qMinusT / baseMinusT);
-					}
-
-					output.push(stringFromCharCode(digitToBasic(q, 0)));
-					bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
-					delta = 0;
-					++handledCPCount;
-				}
-			}
-
-			++delta;
-			++n;
-
-		}
-		return output.join('');
-	}
-
-	/**
-	 * Converts a Punycode string representing a domain name or an email address
-	 * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
-	 * it doesn't matter if you call it on a string that has already been
-	 * converted to Unicode.
-	 * @memberOf punycode
-	 * @param {String} input The Punycoded domain name or email address to
-	 * convert to Unicode.
-	 * @returns {String} The Unicode representation of the given Punycode
-	 * string.
-	 */
-	function toUnicode(input) {
-		return mapDomain(input, function(string) {
-			return regexPunycode.test(string)
-				? decode(string.slice(4).toLowerCase())
-				: string;
-		});
-	}
-
-	/**
-	 * Converts a Unicode string representing a domain name or an email address to
-	 * Punycode. Only the non-ASCII parts of the domain name will be converted,
-	 * i.e. it doesn't matter if you call it with a domain that's already in
-	 * ASCII.
-	 * @memberOf punycode
-	 * @param {String} input The domain name or email address to convert, as a
-	 * Unicode string.
-	 * @returns {String} The Punycode representation of the given domain name or
-	 * email address.
-	 */
-	function toASCII(input) {
-		return mapDomain(input, function(string) {
-			return regexNonASCII.test(string)
-				? 'xn--' + encode(string)
-				: string;
-		});
-	}
-
-	/*--------------------------------------------------------------------------*/
-
-	/** Define the public API */
-	punycode = {
-		/**
-		 * A string representing the current Punycode.js version number.
-		 * @memberOf punycode
-		 * @type String
-		 */
-		'version': '1.4.1',
-		/**
-		 * An object of methods to convert from JavaScript's internal character
-		 * representation (UCS-2) to Unicode code points, and back.
-		 * @see <https://mathiasbynens.be/notes/javascript-encoding>
-		 * @memberOf punycode
-		 * @type Object
-		 */
-		'ucs2': {
-			'decode': ucs2decode,
-			'encode': ucs2encode
-		},
-		'decode': decode,
-		'encode': encode,
-		'toASCII': toASCII,
-		'toUnicode': toUnicode
-	};
-
-	/** Expose `punycode` */
-	// Some AMD build optimizers, like r.js, check for specific condition patterns
-	// like the following:
-	if (
-		typeof define == 'function' &&
-		typeof define.amd == 'object' &&
-		define.amd
-	) {
-		define('punycode', function() {
-			return punycode;
-		});
-	} else if (freeExports && freeModule) {
-		if (module.exports == freeExports) {
-			// in Node.js, io.js, or RingoJS v0.8.0+
-			freeModule.exports = punycode;
-		} else {
-			// in Narwhal or RingoJS v0.7.0-
-			for (key in punycode) {
-				punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
-			}
-		}
-	} else {
-		// in Rhino or a web browser
-		root.punycode = punycode;
-	}
-
-}(this));
-
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{}],201:[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.
-
-'use strict';
-
-// If obj.hasOwnProperty has been overridden, then calling
-// obj.hasOwnProperty(prop) will break.
-// See: https://github.com/joyent/node/issues/1707
-function hasOwnProperty(obj, prop) {
-  return Object.prototype.hasOwnProperty.call(obj, prop);
-}
-
-module.exports = function(qs, sep, eq, options) {
-  sep = sep || '&';
-  eq = eq || '=';
-  var obj = {};
-
-  if (typeof qs !== 'string' || qs.length === 0) {
-    return obj;
-  }
-
-  var regexp = /\+/g;
-  qs = qs.split(sep);
-
-  var maxKeys = 1000;
-  if (options && typeof options.maxKeys === 'number') {
-    maxKeys = options.maxKeys;
-  }
-
-  var len = qs.length;
-  // maxKeys <= 0 means that we should not limit keys count
-  if (maxKeys > 0 && len > maxKeys) {
-    len = maxKeys;
-  }
-
-  for (var i = 0; i < len; ++i) {
-    var x = qs[i].replace(regexp, '%20'),
-        idx = x.indexOf(eq),
-        kstr, vstr, k, v;
-
-    if (idx >= 0) {
-      kstr = x.substr(0, idx);
-      vstr = x.substr(idx + 1);
-    } else {
-      kstr = x;
-      vstr = '';
-    }
-
-    k = decodeURIComponent(kstr);
-    v = decodeURIComponent(vstr);
-
-    if (!hasOwnProperty(obj, k)) {
-      obj[k] = v;
-    } else if (isArray(obj[k])) {
-      obj[k].push(v);
-    } else {
-      obj[k] = [obj[k], v];
-    }
-  }
-
-  return obj;
-};
-
-var isArray = Array.isArray || function (xs) {
-  return Object.prototype.toString.call(xs) === '[object Array]';
-};
-
-},{}],202:[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.
-
-'use strict';
-
-var stringifyPrimitive = function(v) {
-  switch (typeof v) {
-    case 'string':
-      return v;
-
-    case 'boolean':
-      return v ? 'true' : 'false';
-
-    case 'number':
-      return isFinite(v) ? v : '';
-
-    default:
-      return '';
-  }
-};
-
-module.exports = function(obj, sep, eq, name) {
-  sep = sep || '&';
-  eq = eq || '=';
-  if (obj === null) {
-    obj = undefined;
-  }
-
-  if (typeof obj === 'object') {
-    return map(objectKeys(obj), function(k) {
-      var ks = encodeURIComponent(stringifyPrimitive(k)) + eq;
-      if (isArray(obj[k])) {
-        return map(obj[k], function(v) {
-          return ks + encodeURIComponent(stringifyPrimitive(v));
-        }).join(sep);
-      } else {
-        return ks + encodeURIComponent(stringifyPrimitive(obj[k]));
-      }
-    }).join(sep);
-
-  }
-
-  if (!name) return '';
-  return encodeURIComponent(stringifyPrimitive(name)) + eq +
-         encodeURIComponent(stringifyPrimitive(obj));
-};
-
-var isArray = Array.isArray || function (xs) {
-  return Object.prototype.toString.call(xs) === '[object Array]';
-};
-
-function map (xs, f) {
-  if (xs.map) return xs.map(f);
-  var res = [];
-  for (var i = 0; i < xs.length; i++) {
-    res.push(f(xs[i], i));
-  }
-  return res;
-}
-
-var objectKeys = Object.keys || function (obj) {
-  var res = [];
-  for (var key in obj) {
-    if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key);
-  }
-  return res;
-};
-
-},{}],203:[function(require,module,exports){
-'use strict';
-
-exports.decode = exports.parse = require('./decode');
-exports.encode = exports.stringify = require('./encode');
-
-},{"./decode":201,"./encode":202}],204:[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.
-
-'use strict';
-
-var punycode = require('punycode');
-var util = require('./util');
-
-exports.parse = urlParse;
-exports.resolve = urlResolve;
-exports.resolveObject = urlResolveObject;
-exports.format = urlFormat;
-
-exports.Url = Url;
-
-function Url() {
-  this.protocol = null;
-  this.slashes = null;
-  this.auth = null;
-  this.host = null;
-  this.port = null;
-  this.hostname = null;
-  this.hash = null;
-  this.search = null;
-  this.query = null;
-  this.pathname = null;
-  this.path = null;
-  this.href = null;
-}
-
-// Reference: RFC 3986, RFC 1808, RFC 2396
-
-// define these here so at least they only have to be
-// compiled once on the first module load.
-var protocolPattern = /^([a-z0-9.+-]+:)/i,
-    portPattern = /:[0-9]*$/,
-
-    // Special case for a simple path URL
-    simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,
-
-    // RFC 2396: characters reserved for delimiting URLs.
-    // We actually just auto-escape these.
-    delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'],
-
-    // RFC 2396: characters not allowed for various reasons.
-    unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims),
-
-    // Allowed by RFCs, but cause of XSS attacks.  Always escape these.
-    autoEscape = ['\''].concat(unwise),
-    // Characters that are never ever allowed in a hostname.
-    // Note that any invalid chars are also handled, but these
-    // are the ones that are *expected* to be seen, so we fast-path
-    // them.
-    nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape),
-    hostEndingChars = ['/', '?', '#'],
-    hostnameMaxLen = 255,
-    hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/,
-    hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/,
-    // protocols that can allow "unsafe" and "unwise" chars.
-    unsafeProtocol = {
-      'javascript': true,
-      'javascript:': true
-    },
-    // protocols that never have a hostname.
-    hostlessProtocol = {
-      'javascript': true,
-      'javascript:': true
-    },
-    // protocols that always contain a // bit.
-    slashedProtocol = {
-      'http': true,
-      'https': true,
-      'ftp': true,
-      'gopher': true,
-      'file': true,
-      'http:': true,
-      'https:': true,
-      'ftp:': true,
-      'gopher:': true,
-      'file:': true
-    },
-    querystring = require('querystring');
-
-function urlParse(url, parseQueryString, slashesDenoteHost) {
-  if (url && util.isObject(url) && url instanceof Url) return url;
-
-  var u = new Url;
-  u.parse(url, parseQueryString, slashesDenoteHost);
-  return u;
-}
-
-Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
-  if (!util.isString(url)) {
-    throw new TypeError("Parameter 'url' must be a string, not " + typeof url);
-  }
-
-  // Copy chrome, IE, opera backslash-handling behavior.
-  // Back slashes before the query string get converted to forward slashes
-  // See: https://code.google.com/p/chromium/issues/detail?id=25916
-  var queryIndex = url.indexOf('?'),
-      splitter =
-          (queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#',
-      uSplit = url.split(splitter),
-      slashRegex = /\\/g;
-  uSplit[0] = uSplit[0].replace(slashRegex, '/');
-  url = uSplit.join(splitter);
-
-  var rest = url;
-
-  // trim before proceeding.
-  // This is to support parse stuff like "  http://foo.com  \n"
-  rest = rest.trim();
-
-  if (!slashesDenoteHost && url.split('#').length === 1) {
-    // Try fast path regexp
-    var simplePath = simplePathPattern.exec(rest);
-    if (simplePath) {
-      this.path = rest;
-      this.href = rest;
-      this.pathname = simplePath[1];
-      if (simplePath[2]) {
-        this.search = simplePath[2];
-        if (parseQueryString) {
-          this.query = querystring.parse(this.search.substr(1));
-        } else {
-          this.query = this.search.substr(1);
-        }
-      } else if (parseQueryString) {
-        this.search = '';
-        this.query = {};
-      }
-      return this;
-    }
-  }
-
-  var proto = protocolPattern.exec(rest);
-  if (proto) {
-    proto = proto[0];
-    var lowerProto = proto.toLowerCase();
-    this.protocol = lowerProto;
-    rest = rest.substr(proto.length);
-  }
-
-  // figure out if it's got a host
-  // user@server is *always* interpreted as a hostname, and url
-  // resolution will treat //foo/bar as host=foo,path=bar because that's
-  // how the browser resolves relative URLs.
-  if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {
-    var slashes = rest.substr(0, 2) === '//';
-    if (slashes && !(proto && hostlessProtocol[proto])) {
-      rest = rest.substr(2);
-      this.slashes = true;
-    }
-  }
-
-  if (!hostlessProtocol[proto] &&
-      (slashes || (proto && !slashedProtocol[proto]))) {
-
-    // there's a hostname.
-    // the first instance of /, ?, ;, or # ends the host.
-    //
-    // If there is an @ in the hostname, then non-host chars *are* allowed
-    // to the left of the last @ sign, unless some host-ending character
-    // comes *before* the @-sign.
-    // URLs are obnoxious.
-    //
-    // ex:
-    // http://a@b@c/ => user:a@b host:c
-    // http://a@b?@c => user:a host:c path:/?@c
-
-    // v0.12 TODO(isaacs): This is not quite how Chrome does things.
-    // Review our test case against browsers more comprehensively.
-
-    // find the first instance of any hostEndingChars
-    var hostEnd = -1;
-    for (var i = 0; i < hostEndingChars.length; i++) {
-      var hec = rest.indexOf(hostEndingChars[i]);
-      if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
-        hostEnd = hec;
-    }
-
-    // at this point, either we have an explicit point where the
-    // auth portion cannot go past, or the last @ char is the decider.
-    var auth, atSign;
-    if (hostEnd === -1) {
-      // atSign can be anywhere.
-      atSign = rest.lastIndexOf('@');
-    } else {
-      // atSign must be in auth portion.
-      // http://a@b/c@d => host:b auth:a path:/c@d
-      atSign = rest.lastIndexOf('@', hostEnd);
-    }
-
-    // Now we have a portion which is definitely the auth.
-    // Pull that off.
-    if (atSign !== -1) {
-      auth = rest.slice(0, atSign);
-      rest = rest.slice(atSign + 1);
-      this.auth = decodeURIComponent(auth);
-    }
-
-    // the host is the remaining to the left of the first non-host char
-    hostEnd = -1;
-    for (var i = 0; i < nonHostChars.length; i++) {
-      var hec = rest.indexOf(nonHostChars[i]);
-      if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
-        hostEnd = hec;
-    }
-    // if we still have not hit it, then the entire thing is a host.
-    if (hostEnd === -1)
-      hostEnd = rest.length;
-
-    this.host = rest.slice(0, hostEnd);
-    rest = rest.slice(hostEnd);
-
-    // pull out port.
-    this.parseHost();
-
-    // we've indicated that there is a hostname,
-    // so even if it's empty, it has to be present.
-    this.hostname = this.hostname || '';
-
-    // if hostname begins with [ and ends with ]
-    // assume that it's an IPv6 address.
-    var ipv6Hostname = this.hostname[0] === '[' &&
-        this.hostname[this.hostname.length - 1] === ']';
-
-    // validate a little.
-    if (!ipv6Hostname) {
-      var hostparts = this.hostname.split(/\./);
-      for (var i = 0, l = hostparts.length; i < l; i++) {
-        var part = hostparts[i];
-        if (!part) continue;
-        if (!part.match(hostnamePartPattern)) {
-          var newpart = '';
-          for (var j = 0, k = part.length; j < k; j++) {
-            if (part.charCodeAt(j) > 127) {
-              // we replace non-ASCII char with a temporary placeholder
-              // we need this to make sure size of hostname is not
-              // broken by replacing non-ASCII by nothing
-              newpart += 'x';
-            } else {
-              newpart += part[j];
-            }
-          }
-          // we test again with ASCII char only
-          if (!newpart.match(hostnamePartPattern)) {
-            var validParts = hostparts.slice(0, i);
-            var notHost = hostparts.slice(i + 1);
-            var bit = part.match(hostnamePartStart);
-            if (bit) {
-              validParts.push(bit[1]);
-              notHost.unshift(bit[2]);
-            }
-            if (notHost.length) {
-              rest = '/' + notHost.join('.') + rest;
-            }
-            this.hostname = validParts.join('.');
-            break;
-          }
-        }
-      }
-    }
-
-    if (this.hostname.length > hostnameMaxLen) {
-      this.hostname = '';
-    } else {
-      // hostnames are always lower case.
-      this.hostname = this.hostname.toLowerCase();
-    }
-
-    if (!ipv6Hostname) {
-      // IDNA Support: Returns a punycoded representation of "domain".
-      // It only converts parts of the domain name that
-      // have non-ASCII characters, i.e. it doesn't matter if
-      // you call it with a domain that already is ASCII-only.
-      this.hostname = punycode.toASCII(this.hostname);
-    }
-
-    var p = this.port ? ':' + this.port : '';
-    var h = this.hostname || '';
-    this.host = h + p;
-    this.href += this.host;
-
-    // strip [ and ] from the hostname
-    // the host field still retains them, though
-    if (ipv6Hostname) {
-      this.hostname = this.hostname.substr(1, this.hostname.length - 2);
-      if (rest[0] !== '/') {
-        rest = '/' + rest;
-      }
-    }
-  }
-
-  // now rest is set to the post-host stuff.
-  // chop off any delim chars.
-  if (!unsafeProtocol[lowerProto]) {
-
-    // First, make 100% sure that any "autoEscape" chars get
-    // escaped, even if encodeURIComponent doesn't think they
-    // need to be.
-    for (var i = 0, l = autoEscape.length; i < l; i++) {
-      var ae = autoEscape[i];
-      if (rest.indexOf(ae) === -1)
-        continue;
-      var esc = encodeURIComponent(ae);
-      if (esc === ae) {
-        esc = escape(ae);
-      }
-      rest = rest.split(ae).join(esc);
-    }
-  }
-
-
-  // chop off from the tail first.
-  var hash = rest.indexOf('#');
-  if (hash !== -1) {
-    // got a fragment string.
-    this.hash = rest.substr(hash);
-    rest = rest.slice(0, hash);
-  }
-  var qm = rest.indexOf('?');
-  if (qm !== -1) {
-    this.search = rest.substr(qm);
-    this.query = rest.substr(qm + 1);
-    if (parseQueryString) {
-      this.query = querystring.parse(this.query);
-    }
-    rest = rest.slice(0, qm);
-  } else if (parseQueryString) {
-    // no query string, but parseQueryString still requested
-    this.search = '';
-    this.query = {};
-  }
-  if (rest) this.pathname = rest;
-  if (slashedProtocol[lowerProto] &&
-      this.hostname && !this.pathname) {
-    this.pathname = '/';
-  }
-
-  //to support http.request
-  if (this.pathname || this.search) {
-    var p = this.pathname || '';
-    var s = this.search || '';
-    this.path = p + s;
-  }
-
-  // finally, reconstruct the href based on what has been validated.
-  this.href = this.format();
-  return this;
-};
-
-// format a parsed object into a url string
-function urlFormat(obj) {
-  // ensure it's an object, and not a string url.
-  // If it's an obj, this is a no-op.
-  // this way, you can call url_format() on strings
-  // to clean up potentially wonky urls.
-  if (util.isString(obj)) obj = urlParse(obj);
-  if (!(obj instanceof Url)) return Url.prototype.format.call(obj);
-  return obj.format();
-}
-
-Url.prototype.format = function() {
-  var auth = this.auth || '';
-  if (auth) {
-    auth = encodeURIComponent(auth);
-    auth = auth.replace(/%3A/i, ':');
-    auth += '@';
-  }
-
-  var protocol = this.protocol || '',
-      pathname = this.pathname || '',
-      hash = this.hash || '',
-      host = false,
-      query = '';
-
-  if (this.host) {
-    host = auth + this.host;
-  } else if (this.hostname) {
-    host = auth + (this.hostname.indexOf(':') === -1 ?
-        this.hostname :
-        '[' + this.hostname + ']');
-    if (this.port) {
-      host += ':' + this.port;
-    }
-  }
-
-  if (this.query &&
-      util.isObject(this.query) &&
-      Object.keys(this.query).length) {
-    query = querystring.stringify(this.query);
-  }
-
-  var search = this.search || (query && ('?' + query)) || '';
-
-  if (protocol && protocol.substr(-1) !== ':') protocol += ':';
-
-  // only the slashedProtocols get the //.  Not mailto:, xmpp:, etc.
-  // unless they had them to begin with.
-  if (this.slashes ||
-      (!protocol || slashedProtocol[protocol]) && host !== false) {
-    host = '//' + (host || '');
-    if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname;
-  } else if (!host) {
-    host = '';
-  }
-
-  if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
-  if (search && search.charAt(0) !== '?') search = '?' + search;
-
-  pathname = pathname.replace(/[?#]/g, function(match) {
-    return encodeURIComponent(match);
-  });
-  search = search.replace('#', '%23');
-
-  return protocol + host + pathname + search + hash;
-};
-
-function urlResolve(source, relative) {
-  return urlParse(source, false, true).resolve(relative);
-}
-
-Url.prototype.resolve = function(relative) {
-  return this.resolveObject(urlParse(relative, false, true)).format();
-};
-
-function urlResolveObject(source, relative) {
-  if (!source) return relative;
-  return urlParse(source, false, true).resolveObject(relative);
-}
-
-Url.prototype.resolveObject = function(relative) {
-  if (util.isString(relative)) {
-    var rel = new Url();
-    rel.parse(relative, false, true);
-    relative = rel;
-  }
-
-  var result = new Url();
-  var tkeys = Object.keys(this);
-  for (var tk = 0; tk < tkeys.length; tk++) {
-    var tkey = tkeys[tk];
-    result[tkey] = this[tkey];
-  }
-
-  // hash is always overridden, no matter what.
-  // even href="" will remove it.
-  result.hash = relative.hash;
-
-  // if the relative url is empty, then there's nothing left to do here.
-  if (relative.href === '') {
-    result.href = result.format();
-    return result;
-  }
-
-  // hrefs like //foo/bar always cut to the protocol.
-  if (relative.slashes && !relative.protocol) {
-    // take everything except the protocol from relative
-    var rkeys = Object.keys(relative);
-    for (var rk = 0; rk < rkeys.length; rk++) {
-      var rkey = rkeys[rk];
-      if (rkey !== 'protocol')
-        result[rkey] = relative[rkey];
-    }
-
-    //urlParse appends trailing / to urls like http://www.example.com
-    if (slashedProtocol[result.protocol] &&
-        result.hostname && !result.pathname) {
-      result.path = result.pathname = '/';
-    }
-
-    result.href = result.format();
-    return result;
-  }
-
-  if (relative.protocol && relative.protocol !== result.protocol) {
-    // if it's a known url protocol, then changing
-    // the protocol does weird things
-    // first, if it's not file:, then we MUST have a host,
-    // and if there was a path
-    // to begin with, then we MUST have a path.
-    // if it is file:, then the host is dropped,
-    // because that's known to be hostless.
-    // anything else is assumed to be absolute.
-    if (!slashedProtocol[relative.protocol]) {
-      var keys = Object.keys(relative);
-      for (var v = 0; v < keys.length; v++) {
-        var k = keys[v];
-        result[k] = relative[k];
-      }
-      result.href = result.format();
-      return result;
-    }
-
-    result.protocol = relative.protocol;
-    if (!relative.host && !hostlessProtocol[relative.protocol]) {
-      var relPath = (relative.pathname || '').split('/');
-      while (relPath.length && !(relative.host = relPath.shift()));
-      if (!relative.host) relative.host = '';
-      if (!relative.hostname) relative.hostname = '';
-      if (relPath[0] !== '') relPath.unshift('');
-      if (relPath.length < 2) relPath.unshift('');
-      result.pathname = relPath.join('/');
-    } else {
-      result.pathname = relative.pathname;
-    }
-    result.search = relative.search;
-    result.query = relative.query;
-    result.host = relative.host || '';
-    result.auth = relative.auth;
-    result.hostname = relative.hostname || relative.host;
-    result.port = relative.port;
-    // to support http.request
-    if (result.pathname || result.search) {
-      var p = result.pathname || '';
-      var s = result.search || '';
-      result.path = p + s;
-    }
-    result.slashes = result.slashes || relative.slashes;
-    result.href = result.format();
-    return result;
-  }
-
-  var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'),
-      isRelAbs = (
-          relative.host ||
-          relative.pathname && relative.pathname.charAt(0) === '/'
-      ),
-      mustEndAbs = (isRelAbs || isSourceAbs ||
-                    (result.host && relative.pathname)),
-      removeAllDots = mustEndAbs,
-      srcPath = result.pathname && result.pathname.split('/') || [],
-      relPath = relative.pathname && relative.pathname.split('/') || [],
-      psychotic = result.protocol && !slashedProtocol[result.protocol];
-
-  // if the url is a non-slashed url, then relative
-  // links like ../.. should be able
-  // to crawl up to the hostname, as well.  This is strange.
-  // result.protocol has already been set by now.
-  // Later on, put the first path part into the host field.
-  if (psychotic) {
-    result.hostname = '';
-    result.port = null;
-    if (result.host) {
-      if (srcPath[0] === '') srcPath[0] = result.host;
-      else srcPath.unshift(result.host);
-    }
-    result.host = '';
-    if (relative.protocol) {
-      relative.hostname = null;
-      relative.port = null;
-      if (relative.host) {
-        if (relPath[0] === '') relPath[0] = relative.host;
-        else relPath.unshift(relative.host);
-      }
-      relative.host = null;
-    }
-    mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === '');
-  }
-
-  if (isRelAbs) {
-    // it's absolute.
-    result.host = (relative.host || relative.host === '') ?
-                  relative.host : result.host;
-    result.hostname = (relative.hostname || relative.hostname === '') ?
-                      relative.hostname : result.hostname;
-    result.search = relative.search;
-    result.query = relative.query;
-    srcPath = relPath;
-    // fall through to the dot-handling below.
-  } else if (relPath.length) {
-    // it's relative
-    // throw away the existing file, and take the new path instead.
-    if (!srcPath) srcPath = [];
-    srcPath.pop();
-    srcPath = srcPath.concat(relPath);
-    result.search = relative.search;
-    result.query = relative.query;
-  } else if (!util.isNullOrUndefined(relative.search)) {
-    // just pull out the search.
-    // like href='?foo'.
-    // Put this after the other two cases because it simplifies the booleans
-    if (psychotic) {
-      result.hostname = result.host = srcPath.shift();
-      //occationaly the auth can get stuck only in host
-      //this especially happens in cases like
-      //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
-      var authInHost = result.host && result.host.indexOf('@') > 0 ?
-                       result.host.split('@') : false;
-      if (authInHost) {
-        result.auth = authInHost.shift();
-        result.host = result.hostname = authInHost.shift();
-      }
-    }
-    result.search = relative.search;
-    result.query = relative.query;
-    //to support http.request
-    if (!util.isNull(result.pathname) || !util.isNull(result.search)) {
-      result.path = (result.pathname ? result.pathname : '') +
-                    (result.search ? result.search : '');
-    }
-    result.href = result.format();
-    return result;
-  }
-
-  if (!srcPath.length) {
-    // no path at all.  easy.
-    // we've already handled the other stuff above.
-    result.pathname = null;
-    //to support http.request
-    if (result.search) {
-      result.path = '/' + result.search;
-    } else {
-      result.path = null;
-    }
-    result.href = result.format();
-    return result;
-  }
-
-  // if a url ENDs in . or .., then it must get a trailing slash.
-  // however, if it ends in anything else non-slashy,
-  // then it must NOT get a trailing slash.
-  var last = srcPath.slice(-1)[0];
-  var hasTrailingSlash = (
-      (result.host || relative.host || srcPath.length > 1) &&
-      (last === '.' || last === '..') || last === '');
-
-  // strip single dots, resolve double dots to parent dir
-  // if the path tries to go above the root, `up` ends up > 0
-  var up = 0;
-  for (var i = srcPath.length; i >= 0; i--) {
-    last = srcPath[i];
-    if (last === '.') {
-      srcPath.splice(i, 1);
-    } else if (last === '..') {
-      srcPath.splice(i, 1);
-      up++;
-    } else if (up) {
-      srcPath.splice(i, 1);
-      up--;
-    }
-  }
-
-  // if the path is allowed to go above the root, restore leading ..s
-  if (!mustEndAbs && !removeAllDots) {
-    for (; up--; up) {
-      srcPath.unshift('..');
-    }
-  }
-
-  if (mustEndAbs && srcPath[0] !== '' &&
-      (!srcPath[0] || srcPath[0].charAt(0) !== '/')) {
-    srcPath.unshift('');
-  }
-
-  if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) {
-    srcPath.push('');
-  }
-
-  var isAbsolute = srcPath[0] === '' ||
-      (srcPath[0] && srcPath[0].charAt(0) === '/');
-
-  // put the host back
-  if (psychotic) {
-    result.hostname = result.host = isAbsolute ? '' :
-                                    srcPath.length ? srcPath.shift() : '';
-    //occationaly the auth can get stuck only in host
-    //this especially happens in cases like
-    //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
-    var authInHost = result.host && result.host.indexOf('@') > 0 ?
-                     result.host.split('@') : false;
-    if (authInHost) {
-      result.auth = authInHost.shift();
-      result.host = result.hostname = authInHost.shift();
-    }
-  }
-
-  mustEndAbs = mustEndAbs || (result.host && srcPath.length);
-
-  if (mustEndAbs && !isAbsolute) {
-    srcPath.unshift('');
-  }
-
-  if (!srcPath.length) {
-    result.pathname = null;
-    result.path = null;
-  } else {
-    result.pathname = srcPath.join('/');
-  }
-
-  //to support request.http
-  if (!util.isNull(result.pathname) || !util.isNull(result.search)) {
-    result.path = (result.pathname ? result.pathname : '') +
-                  (result.search ? result.search : '');
-  }
-  result.auth = relative.auth || result.auth;
-  result.slashes = result.slashes || relative.slashes;
-  result.href = result.format();
-  return result;
-};
-
-Url.prototype.parseHost = function() {
-  var host = this.host;
-  var port = portPattern.exec(host);
-  if (port) {
-    port = port[0];
-    if (port !== ':') {
-      this.port = port.substr(1);
-    }
-    host = host.substr(0, host.length - port.length);
-  }
-  if (host) this.hostname = host;
-};
-
-},{"./util":205,"punycode":200,"querystring":203}],205:[function(require,module,exports){
-'use strict';
-
-module.exports = {
-  isString: function(arg) {
-    return typeof(arg) === 'string';
-  },
-  isObject: function(arg) {
-    return typeof(arg) === 'object' && arg !== null;
-  },
-  isNull: function(arg) {
-    return arg === null;
-  },
-  isNullOrUndefined: function(arg) {
-    return arg == null;
-  }
-};
-
 },{}],206:[function(require,module,exports){
 /*
  * Copyright (C) 2013 Google Inc. All rights reserved.
@@ -60190,7 +35842,7 @@
   return val;
 }
 
-},{"ms":252}],236:[function(require,module,exports){
+},{"ms":282}],236:[function(require,module,exports){
 
 // this duplicates some work inside of TimelineTreeView, SortedDataGrid and beyond.
 // It's pretty difficult to extract, so we forked.
@@ -66774,6 +42426,4673 @@
 module.exports = vec4;
 
 },{"./common.js":238}],247:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+// istanbul ignore next
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _handlebarsRuntime = require('./handlebars.runtime');
+
+var _handlebarsRuntime2 = _interopRequireDefault(_handlebarsRuntime);
+
+// Compiler imports
+
+var _handlebarsCompilerAst = require('./handlebars/compiler/ast');
+
+var _handlebarsCompilerAst2 = _interopRequireDefault(_handlebarsCompilerAst);
+
+var _handlebarsCompilerBase = require('./handlebars/compiler/base');
+
+var _handlebarsCompilerCompiler = require('./handlebars/compiler/compiler');
+
+var _handlebarsCompilerJavascriptCompiler = require('./handlebars/compiler/javascript-compiler');
+
+var _handlebarsCompilerJavascriptCompiler2 = _interopRequireDefault(_handlebarsCompilerJavascriptCompiler);
+
+var _handlebarsCompilerVisitor = require('./handlebars/compiler/visitor');
+
+var _handlebarsCompilerVisitor2 = _interopRequireDefault(_handlebarsCompilerVisitor);
+
+var _handlebarsNoConflict = require('./handlebars/no-conflict');
+
+var _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict);
+
+var _create = _handlebarsRuntime2['default'].create;
+function create() {
+  var hb = _create();
+
+  hb.compile = function (input, options) {
+    return _handlebarsCompilerCompiler.compile(input, options, hb);
+  };
+  hb.precompile = function (input, options) {
+    return _handlebarsCompilerCompiler.precompile(input, options, hb);
+  };
+
+  hb.AST = _handlebarsCompilerAst2['default'];
+  hb.Compiler = _handlebarsCompilerCompiler.Compiler;
+  hb.JavaScriptCompiler = _handlebarsCompilerJavascriptCompiler2['default'];
+  hb.Parser = _handlebarsCompilerBase.parser;
+  hb.parse = _handlebarsCompilerBase.parse;
+
+  return hb;
+}
+
+var inst = create();
+inst.create = create;
+
+_handlebarsNoConflict2['default'](inst);
+
+inst.Visitor = _handlebarsCompilerVisitor2['default'];
+
+inst['default'] = inst;
+
+exports['default'] = inst;
+module.exports = exports['default'];
+
+
+},{"./handlebars.runtime":248,"./handlebars/compiler/ast":250,"./handlebars/compiler/base":251,"./handlebars/compiler/compiler":253,"./handlebars/compiler/javascript-compiler":255,"./handlebars/compiler/visitor":258,"./handlebars/no-conflict":272}],248:[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":249,"./handlebars/exception":262,"./handlebars/no-conflict":272,"./handlebars/runtime":273,"./handlebars/safe-string":274,"./handlebars/utils":275}],249:[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":260,"./exception":262,"./helpers":263,"./logger":271,"./utils":275}],250:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+var AST = {
+  // Public API used to evaluate derived attributes regarding AST nodes
+  helpers: {
+    // a mustache is definitely a helper if:
+    // * it is an eligible helper, and
+    // * it has at least one parameter or hash segment
+    helperExpression: function helperExpression(node) {
+      return node.type === 'SubExpression' || (node.type === 'MustacheStatement' || node.type === 'BlockStatement') && !!(node.params && node.params.length || node.hash);
+    },
+
+    scopedId: function scopedId(path) {
+      return (/^\.|this\b/.test(path.original)
+      );
+    },
+
+    // an ID is simple if it only has one part, and that part is not
+    // `..` or `this`.
+    simpleId: function simpleId(path) {
+      return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth;
+    }
+  }
+};
+
+// Must be exported as an object rather than the root of the module as the jison lexer
+// must modify the object to operate properly.
+exports['default'] = AST;
+module.exports = exports['default'];
+
+
+},{}],251:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+exports.parse = parse;
+// 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; } }
+
+// istanbul ignore next
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _parser = require('./parser');
+
+var _parser2 = _interopRequireDefault(_parser);
+
+var _whitespaceControl = require('./whitespace-control');
+
+var _whitespaceControl2 = _interopRequireDefault(_whitespaceControl);
+
+var _helpers = require('./helpers');
+
+var Helpers = _interopRequireWildcard(_helpers);
+
+var _utils = require('../utils');
+
+exports.parser = _parser2['default'];
+
+var yy = {};
+_utils.extend(yy, Helpers);
+
+function parse(input, options) {
+  // Just return if an already-compiled AST was passed in.
+  if (input.type === 'Program') {
+    return input;
+  }
+
+  _parser2['default'].yy = yy;
+
+  // Altering the shared object here, but this is ok as parser is a sync operation
+  yy.locInfo = function (locInfo) {
+    return new yy.SourceLocation(options && options.srcName, locInfo);
+  };
+
+  var strip = new _whitespaceControl2['default'](options);
+  return strip.accept(_parser2['default'].parse(input));
+}
+
+
+},{"../utils":275,"./helpers":254,"./parser":256,"./whitespace-control":259}],252:[function(require,module,exports){
+/* global define */
+'use strict';
+
+exports.__esModule = true;
+
+var _utils = require('../utils');
+
+var SourceNode = undefined;
+
+try {
+  /* istanbul ignore next */
+  if (typeof define !== 'function' || !define.amd) {
+    // We don't support this in AMD environments. For these environments, we asusme that
+    // they are running on the browser and thus have no need for the source-map library.
+    var SourceMap = require('source-map');
+    SourceNode = SourceMap.SourceNode;
+  }
+} catch (err) {}
+/* NOP */
+
+/* istanbul ignore if: tested but not covered in istanbul due to dist build  */
+if (!SourceNode) {
+  SourceNode = function (line, column, srcFile, chunks) {
+    this.src = '';
+    if (chunks) {
+      this.add(chunks);
+    }
+  };
+  /* istanbul ignore next */
+  SourceNode.prototype = {
+    add: function add(chunks) {
+      if (_utils.isArray(chunks)) {
+        chunks = chunks.join('');
+      }
+      this.src += chunks;
+    },
+    prepend: function prepend(chunks) {
+      if (_utils.isArray(chunks)) {
+        chunks = chunks.join('');
+      }
+      this.src = chunks + this.src;
+    },
+    toStringWithSourceMap: function toStringWithSourceMap() {
+      return { code: this.toString() };
+    },
+    toString: function toString() {
+      return this.src;
+    }
+  };
+}
+
+function castChunk(chunk, codeGen, loc) {
+  if (_utils.isArray(chunk)) {
+    var ret = [];
+
+    for (var i = 0, len = chunk.length; i < len; i++) {
+      ret.push(codeGen.wrap(chunk[i], loc));
+    }
+    return ret;
+  } else if (typeof chunk === 'boolean' || typeof chunk === 'number') {
+    // Handle primitives that the SourceNode will throw up on
+    return chunk + '';
+  }
+  return chunk;
+}
+
+function CodeGen(srcFile) {
+  this.srcFile = srcFile;
+  this.source = [];
+}
+
+CodeGen.prototype = {
+  isEmpty: function isEmpty() {
+    return !this.source.length;
+  },
+  prepend: function prepend(source, loc) {
+    this.source.unshift(this.wrap(source, loc));
+  },
+  push: function push(source, loc) {
+    this.source.push(this.wrap(source, loc));
+  },
+
+  merge: function merge() {
+    var source = this.empty();
+    this.each(function (line) {
+      source.add(['  ', line, '\n']);
+    });
+    return source;
+  },
+
+  each: function each(iter) {
+    for (var i = 0, len = this.source.length; i < len; i++) {
+      iter(this.source[i]);
+    }
+  },
+
+  empty: function empty() {
+    var loc = this.currentLocation || { start: {} };
+    return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
+  },
+  wrap: function wrap(chunk) {
+    var loc = arguments.length <= 1 || arguments[1] === undefined ? this.currentLocation || { start: {} } : arguments[1];
+
+    if (chunk instanceof SourceNode) {
+      return chunk;
+    }
+
+    chunk = castChunk(chunk, this, loc);
+
+    return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk);
+  },
+
+  functionCall: function functionCall(fn, type, params) {
+    params = this.generateList(params);
+    return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
+  },
+
+  quotedString: function quotedString(str) {
+    return '"' + (str + '').replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
+    .replace(/\u2029/g, '\\u2029') + '"';
+  },
+
+  objectLiteral: function objectLiteral(obj) {
+    var pairs = [];
+
+    for (var key in obj) {
+      if (obj.hasOwnProperty(key)) {
+        var value = castChunk(obj[key], this);
+        if (value !== 'undefined') {
+          pairs.push([this.quotedString(key), ':', value]);
+        }
+      }
+    }
+
+    var ret = this.generateList(pairs);
+    ret.prepend('{');
+    ret.add('}');
+    return ret;
+  },
+
+  generateList: function generateList(entries) {
+    var ret = this.empty();
+
+    for (var i = 0, len = entries.length; i < len; i++) {
+      if (i) {
+        ret.add(',');
+      }
+
+      ret.add(castChunk(entries[i], this));
+    }
+
+    return ret;
+  },
+
+  generateArray: function generateArray(entries) {
+    var ret = this.generateList(entries);
+    ret.prepend('[');
+    ret.add(']');
+
+    return ret;
+  }
+};
+
+exports['default'] = CodeGen;
+module.exports = exports['default'];
+
+
+},{"../utils":275,"source-map":199}],253:[function(require,module,exports){
+/* eslint-disable new-cap */
+
+'use strict';
+
+exports.__esModule = true;
+exports.Compiler = Compiler;
+exports.precompile = precompile;
+exports.compile = compile;
+// istanbul ignore next
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _exception = require('../exception');
+
+var _exception2 = _interopRequireDefault(_exception);
+
+var _utils = require('../utils');
+
+var _ast = require('./ast');
+
+var _ast2 = _interopRequireDefault(_ast);
+
+var slice = [].slice;
+
+function Compiler() {}
+
+// the foundHelper register will disambiguate helper lookup from finding a
+// function in a context. This is necessary for mustache compatibility, which
+// requires that context functions in blocks are evaluated by blockHelperMissing,
+// and then proceed as if the resulting value was provided to blockHelperMissing.
+
+Compiler.prototype = {
+  compiler: Compiler,
+
+  equals: function equals(other) {
+    var len = this.opcodes.length;
+    if (other.opcodes.length !== len) {
+      return false;
+    }
+
+    for (var i = 0; i < len; i++) {
+      var opcode = this.opcodes[i],
+          otherOpcode = other.opcodes[i];
+      if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
+        return false;
+      }
+    }
+
+    // We know that length is the same between the two arrays because they are directly tied
+    // to the opcode behavior above.
+    len = this.children.length;
+    for (var i = 0; i < len; i++) {
+      if (!this.children[i].equals(other.children[i])) {
+        return false;
+      }
+    }
+
+    return true;
+  },
+
+  guid: 0,
+
+  compile: function compile(program, options) {
+    this.sourceNode = [];
+    this.opcodes = [];
+    this.children = [];
+    this.options = options;
+    this.stringParams = options.stringParams;
+    this.trackIds = options.trackIds;
+
+    options.blockParams = options.blockParams || [];
+
+    // These changes will propagate to the other compiler components
+    var knownHelpers = options.knownHelpers;
+    options.knownHelpers = {
+      'helperMissing': true,
+      'blockHelperMissing': true,
+      'each': true,
+      'if': true,
+      'unless': true,
+      'with': true,
+      'log': true,
+      'lookup': true
+    };
+    if (knownHelpers) {
+      for (var _name in knownHelpers) {
+        /* istanbul ignore else */
+        if (_name in knownHelpers) {
+          options.knownHelpers[_name] = knownHelpers[_name];
+        }
+      }
+    }
+
+    return this.accept(program);
+  },
+
+  compileProgram: function compileProgram(program) {
+    var childCompiler = new this.compiler(),
+        // eslint-disable-line new-cap
+    result = childCompiler.compile(program, this.options),
+        guid = this.guid++;
+
+    this.usePartial = this.usePartial || result.usePartial;
+
+    this.children[guid] = result;
+    this.useDepths = this.useDepths || result.useDepths;
+
+    return guid;
+  },
+
+  accept: function accept(node) {
+    /* istanbul ignore next: Sanity code */
+    if (!this[node.type]) {
+      throw new _exception2['default']('Unknown type: ' + node.type, node);
+    }
+
+    this.sourceNode.unshift(node);
+    var ret = this[node.type](node);
+    this.sourceNode.shift();
+    return ret;
+  },
+
+  Program: function Program(program) {
+    this.options.blockParams.unshift(program.blockParams);
+
+    var body = program.body,
+        bodyLength = body.length;
+    for (var i = 0; i < bodyLength; i++) {
+      this.accept(body[i]);
+    }
+
+    this.options.blockParams.shift();
+
+    this.isSimple = bodyLength === 1;
+    this.blockParams = program.blockParams ? program.blockParams.length : 0;
+
+    return this;
+  },
+
+  BlockStatement: function BlockStatement(block) {
+    transformLiteralToPath(block);
+
+    var program = block.program,
+        inverse = block.inverse;
+
+    program = program && this.compileProgram(program);
+    inverse = inverse && this.compileProgram(inverse);
+
+    var type = this.classifySexpr(block);
+
+    if (type === 'helper') {
+      this.helperSexpr(block, program, inverse);
+    } else if (type === 'simple') {
+      this.simpleSexpr(block);
+
+      // now that the simple mustache is resolved, we need to
+      // evaluate it by executing `blockHelperMissing`
+      this.opcode('pushProgram', program);
+      this.opcode('pushProgram', inverse);
+      this.opcode('emptyHash');
+      this.opcode('blockValue', block.path.original);
+    } else {
+      this.ambiguousSexpr(block, program, inverse);
+
+      // now that the simple mustache is resolved, we need to
+      // evaluate it by executing `blockHelperMissing`
+      this.opcode('pushProgram', program);
+      this.opcode('pushProgram', inverse);
+      this.opcode('emptyHash');
+      this.opcode('ambiguousBlockValue');
+    }
+
+    this.opcode('append');
+  },
+
+  DecoratorBlock: function DecoratorBlock(decorator) {
+    var program = decorator.program && this.compileProgram(decorator.program);
+    var params = this.setupFullMustacheParams(decorator, program, undefined),
+        path = decorator.path;
+
+    this.useDecorators = true;
+    this.opcode('registerDecorator', params.length, path.original);
+  },
+
+  PartialStatement: function PartialStatement(partial) {
+    this.usePartial = true;
+
+    var program = partial.program;
+    if (program) {
+      program = this.compileProgram(partial.program);
+    }
+
+    var params = partial.params;
+    if (params.length > 1) {
+      throw new _exception2['default']('Unsupported number of partial arguments: ' + params.length, partial);
+    } else if (!params.length) {
+      if (this.options.explicitPartialContext) {
+        this.opcode('pushLiteral', 'undefined');
+      } else {
+        params.push({ type: 'PathExpression', parts: [], depth: 0 });
+      }
+    }
+
+    var partialName = partial.name.original,
+        isDynamic = partial.name.type === 'SubExpression';
+    if (isDynamic) {
+      this.accept(partial.name);
+    }
+
+    this.setupFullMustacheParams(partial, program, undefined, true);
+
+    var indent = partial.indent || '';
+    if (this.options.preventIndent && indent) {
+      this.opcode('appendContent', indent);
+      indent = '';
+    }
+
+    this.opcode('invokePartial', isDynamic, partialName, indent);
+    this.opcode('append');
+  },
+  PartialBlockStatement: function PartialBlockStatement(partialBlock) {
+    this.PartialStatement(partialBlock);
+  },
+
+  MustacheStatement: function MustacheStatement(mustache) {
+    this.SubExpression(mustache);
+
+    if (mustache.escaped && !this.options.noEscape) {
+      this.opcode('appendEscaped');
+    } else {
+      this.opcode('append');
+    }
+  },
+  Decorator: function Decorator(decorator) {
+    this.DecoratorBlock(decorator);
+  },
+
+  ContentStatement: function ContentStatement(content) {
+    if (content.value) {
+      this.opcode('appendContent', content.value);
+    }
+  },
+
+  CommentStatement: function CommentStatement() {},
+
+  SubExpression: function SubExpression(sexpr) {
+    transformLiteralToPath(sexpr);
+    var type = this.classifySexpr(sexpr);
+
+    if (type === 'simple') {
+      this.simpleSexpr(sexpr);
+    } else if (type === 'helper') {
+      this.helperSexpr(sexpr);
+    } else {
+      this.ambiguousSexpr(sexpr);
+    }
+  },
+  ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {
+    var path = sexpr.path,
+        name = path.parts[0],
+        isBlock = program != null || inverse != null;
+
+    this.opcode('getContext', path.depth);
+
+    this.opcode('pushProgram', program);
+    this.opcode('pushProgram', inverse);
+
+    path.strict = true;
+    this.accept(path);
+
+    this.opcode('invokeAmbiguous', name, isBlock);
+  },
+
+  simpleSexpr: function simpleSexpr(sexpr) {
+    var path = sexpr.path;
+    path.strict = true;
+    this.accept(path);
+    this.opcode('resolvePossibleLambda');
+  },
+
+  helperSexpr: function helperSexpr(sexpr, program, inverse) {
+    var params = this.setupFullMustacheParams(sexpr, program, inverse),
+        path = sexpr.path,
+        name = path.parts[0];
+
+    if (this.options.knownHelpers[name]) {
+      this.opcode('invokeKnownHelper', params.length, name);
+    } else if (this.options.knownHelpersOnly) {
+      throw new _exception2['default']('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr);
+    } else {
+      path.strict = true;
+      path.falsy = true;
+
+      this.accept(path);
+      this.opcode('invokeHelper', params.length, path.original, _ast2['default'].helpers.simpleId(path));
+    }
+  },
+
+  PathExpression: function PathExpression(path) {
+    this.addDepth(path.depth);
+    this.opcode('getContext', path.depth);
+
+    var name = path.parts[0],
+        scoped = _ast2['default'].helpers.scopedId(path),
+        blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
+
+    if (blockParamId) {
+      this.opcode('lookupBlockParam', blockParamId, path.parts);
+    } else if (!name) {
+      // Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
+      this.opcode('pushContext');
+    } else if (path.data) {
+      this.options.data = true;
+      this.opcode('lookupData', path.depth, path.parts, path.strict);
+    } else {
+      this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);
+    }
+  },
+
+  StringLiteral: function StringLiteral(string) {
+    this.opcode('pushString', string.value);
+  },
+
+  NumberLiteral: function NumberLiteral(number) {
+    this.opcode('pushLiteral', number.value);
+  },
+
+  BooleanLiteral: function BooleanLiteral(bool) {
+    this.opcode('pushLiteral', bool.value);
+  },
+
+  UndefinedLiteral: function UndefinedLiteral() {
+    this.opcode('pushLiteral', 'undefined');
+  },
+
+  NullLiteral: function NullLiteral() {
+    this.opcode('pushLiteral', 'null');
+  },
+
+  Hash: function Hash(hash) {
+    var pairs = hash.pairs,
+        i = 0,
+        l = pairs.length;
+
+    this.opcode('pushHash');
+
+    for (; i < l; i++) {
+      this.pushParam(pairs[i].value);
+    }
+    while (i--) {
+      this.opcode('assignToHash', pairs[i].key);
+    }
+    this.opcode('popHash');
+  },
+
+  // HELPERS
+  opcode: function opcode(name) {
+    this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc });
+  },
+
+  addDepth: function addDepth(depth) {
+    if (!depth) {
+      return;
+    }
+
+    this.useDepths = true;
+  },
+
+  classifySexpr: function classifySexpr(sexpr) {
+    var isSimple = _ast2['default'].helpers.simpleId(sexpr.path);
+
+    var isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);
+
+    // a mustache is an eligible helper if:
+    // * its id is simple (a single part, not `this` or `..`)
+    var isHelper = !isBlockParam && _ast2['default'].helpers.helperExpression(sexpr);
+
+    // if a mustache is an eligible helper but not a definite
+    // helper, it is ambiguous, and will be resolved in a later
+    // pass or at runtime.
+    var isEligible = !isBlockParam && (isHelper || isSimple);
+
+    // if ambiguous, we can possibly resolve the ambiguity now
+    // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
+    if (isEligible && !isHelper) {
+      var _name2 = sexpr.path.parts[0],
+          options = this.options;
+
+      if (options.knownHelpers[_name2]) {
+        isHelper = true;
+      } else if (options.knownHelpersOnly) {
+        isEligible = false;
+      }
+    }
+
+    if (isHelper) {
+      return 'helper';
+    } else if (isEligible) {
+      return 'ambiguous';
+    } else {
+      return 'simple';
+    }
+  },
+
+  pushParams: function pushParams(params) {
+    for (var i = 0, l = params.length; i < l; i++) {
+      this.pushParam(params[i]);
+    }
+  },
+
+  pushParam: function pushParam(val) {
+    var value = val.value != null ? val.value : val.original || '';
+
+    if (this.stringParams) {
+      if (value.replace) {
+        value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
+      }
+
+      if (val.depth) {
+        this.addDepth(val.depth);
+      }
+      this.opcode('getContext', val.depth || 0);
+      this.opcode('pushStringParam', value, val.type);
+
+      if (val.type === 'SubExpression') {
+        // SubExpressions get evaluated and passed in
+        // in string params mode.
+        this.accept(val);
+      }
+    } else {
+      if (this.trackIds) {
+        var blockParamIndex = undefined;
+        if (val.parts && !_ast2['default'].helpers.scopedId(val) && !val.depth) {
+          blockParamIndex = this.blockParamIndex(val.parts[0]);
+        }
+        if (blockParamIndex) {
+          var blockParamChild = val.parts.slice(1).join('.');
+          this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild);
+        } else {
+          value = val.original || value;
+          if (value.replace) {
+            value = value.replace(/^this(?:\.|$)/, '').replace(/^\.\//, '').replace(/^\.$/, '');
+          }
+
+          this.opcode('pushId', val.type, value);
+        }
+      }
+      this.accept(val);
+    }
+  },
+
+  setupFullMustacheParams: function setupFullMustacheParams(sexpr, program, inverse, omitEmpty) {
+    var params = sexpr.params;
+    this.pushParams(params);
+
+    this.opcode('pushProgram', program);
+    this.opcode('pushProgram', inverse);
+
+    if (sexpr.hash) {
+      this.accept(sexpr.hash);
+    } else {
+      this.opcode('emptyHash', omitEmpty);
+    }
+
+    return params;
+  },
+
+  blockParamIndex: function blockParamIndex(name) {
+    for (var depth = 0, len = this.options.blockParams.length; depth < len; depth++) {
+      var blockParams = this.options.blockParams[depth],
+          param = blockParams && _utils.indexOf(blockParams, name);
+      if (blockParams && param >= 0) {
+        return [depth, param];
+      }
+    }
+  }
+};
+
+function precompile(input, options, env) {
+  if (input == null || typeof input !== 'string' && input.type !== 'Program') {
+    throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' + input);
+  }
+
+  options = options || {};
+  if (!('data' in options)) {
+    options.data = true;
+  }
+  if (options.compat) {
+    options.useDepths = true;
+  }
+
+  var ast = env.parse(input, options),
+      environment = new env.Compiler().compile(ast, options);
+  return new env.JavaScriptCompiler().compile(environment, options);
+}
+
+function compile(input, options, env) {
+  if (options === undefined) options = {};
+
+  if (input == null || typeof input !== 'string' && input.type !== 'Program') {
+    throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input);
+  }
+
+  if (!('data' in options)) {
+    options.data = true;
+  }
+  if (options.compat) {
+    options.useDepths = true;
+  }
+
+  var compiled = undefined;
+
+  function compileInput() {
+    var ast = env.parse(input, options),
+        environment = new env.Compiler().compile(ast, options),
+        templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
+    return env.template(templateSpec);
+  }
+
+  // Template is only compiled on first use and cached after that point.
+  function ret(context, execOptions) {
+    if (!compiled) {
+      compiled = compileInput();
+    }
+    return compiled.call(this, context, execOptions);
+  }
+  ret._setup = function (setupOptions) {
+    if (!compiled) {
+      compiled = compileInput();
+    }
+    return compiled._setup(setupOptions);
+  };
+  ret._child = function (i, data, blockParams, depths) {
+    if (!compiled) {
+      compiled = compileInput();
+    }
+    return compiled._child(i, data, blockParams, depths);
+  };
+  return ret;
+}
+
+function argEquals(a, b) {
+  if (a === b) {
+    return true;
+  }
+
+  if (_utils.isArray(a) && _utils.isArray(b) && a.length === b.length) {
+    for (var i = 0; i < a.length; i++) {
+      if (!argEquals(a[i], b[i])) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
+
+function transformLiteralToPath(sexpr) {
+  if (!sexpr.path.parts) {
+    var literal = sexpr.path;
+    // Casting to string here to make false and 0 literal values play nicely with the rest
+    // of the system.
+    sexpr.path = {
+      type: 'PathExpression',
+      data: false,
+      depth: 0,
+      parts: [literal.original + ''],
+      original: literal.original + '',
+      loc: literal.loc
+    };
+  }
+}
+
+
+},{"../exception":262,"../utils":275,"./ast":250}],254:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+exports.SourceLocation = SourceLocation;
+exports.id = id;
+exports.stripFlags = stripFlags;
+exports.stripComment = stripComment;
+exports.preparePath = preparePath;
+exports.prepareMustache = prepareMustache;
+exports.prepareRawBlock = prepareRawBlock;
+exports.prepareBlock = prepareBlock;
+exports.prepareProgram = prepareProgram;
+exports.preparePartialBlock = preparePartialBlock;
+// istanbul ignore next
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _exception = require('../exception');
+
+var _exception2 = _interopRequireDefault(_exception);
+
+function validateClose(open, close) {
+  close = close.path ? close.path.original : close;
+
+  if (open.path.original !== close) {
+    var errorNode = { loc: open.path.loc };
+
+    throw new _exception2['default'](open.path.original + " doesn't match " + close, errorNode);
+  }
+}
+
+function SourceLocation(source, locInfo) {
+  this.source = source;
+  this.start = {
+    line: locInfo.first_line,
+    column: locInfo.first_column
+  };
+  this.end = {
+    line: locInfo.last_line,
+    column: locInfo.last_column
+  };
+}
+
+function id(token) {
+  if (/^\[.*\]$/.test(token)) {
+    return token.substr(1, token.length - 2);
+  } else {
+    return token;
+  }
+}
+
+function stripFlags(open, close) {
+  return {
+    open: open.charAt(2) === '~',
+    close: close.charAt(close.length - 3) === '~'
+  };
+}
+
+function stripComment(comment) {
+  return comment.replace(/^\{\{~?\!-?-?/, '').replace(/-?-?~?\}\}$/, '');
+}
+
+function preparePath(data, parts, loc) {
+  loc = this.locInfo(loc);
+
+  var original = data ? '@' : '',
+      dig = [],
+      depth = 0,
+      depthString = '';
+
+  for (var i = 0, l = parts.length; i < l; i++) {
+    var part = parts[i].part,
+
+    // If we have [] syntax then we do not treat path references as operators,
+    // i.e. foo.[this] resolves to approximately context.foo['this']
+    isLiteral = parts[i].original !== part;
+    original += (parts[i].separator || '') + part;
+
+    if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
+      if (dig.length > 0) {
+        throw new _exception2['default']('Invalid path: ' + original, { loc: loc });
+      } else if (part === '..') {
+        depth++;
+        depthString += '../';
+      }
+    } else {
+      dig.push(part);
+    }
+  }
+
+  return {
+    type: 'PathExpression',
+    data: data,
+    depth: depth,
+    parts: dig,
+    original: original,
+    loc: loc
+  };
+}
+
+function prepareMustache(path, params, hash, open, strip, locInfo) {
+  // Must use charAt to support IE pre-10
+  var escapeFlag = open.charAt(3) || open.charAt(2),
+      escaped = escapeFlag !== '{' && escapeFlag !== '&';
+
+  var decorator = /\*/.test(open);
+  return {
+    type: decorator ? 'Decorator' : 'MustacheStatement',
+    path: path,
+    params: params,
+    hash: hash,
+    escaped: escaped,
+    strip: strip,
+    loc: this.locInfo(locInfo)
+  };
+}
+
+function prepareRawBlock(openRawBlock, contents, close, locInfo) {
+  validateClose(openRawBlock, close);
+
+  locInfo = this.locInfo(locInfo);
+  var program = {
+    type: 'Program',
+    body: contents,
+    strip: {},
+    loc: locInfo
+  };
+
+  return {
+    type: 'BlockStatement',
+    path: openRawBlock.path,
+    params: openRawBlock.params,
+    hash: openRawBlock.hash,
+    program: program,
+    openStrip: {},
+    inverseStrip: {},
+    closeStrip: {},
+    loc: locInfo
+  };
+}
+
+function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) {
+  if (close && close.path) {
+    validateClose(openBlock, close);
+  }
+
+  var decorator = /\*/.test(openBlock.open);
+
+  program.blockParams = openBlock.blockParams;
+
+  var inverse = undefined,
+      inverseStrip = undefined;
+
+  if (inverseAndProgram) {
+    if (decorator) {
+      throw new _exception2['default']('Unexpected inverse block on decorator', inverseAndProgram);
+    }
+
+    if (inverseAndProgram.chain) {
+      inverseAndProgram.program.body[0].closeStrip = close.strip;
+    }
+
+    inverseStrip = inverseAndProgram.strip;
+    inverse = inverseAndProgram.program;
+  }
+
+  if (inverted) {
+    inverted = inverse;
+    inverse = program;
+    program = inverted;
+  }
+
+  return {
+    type: decorator ? 'DecoratorBlock' : 'BlockStatement',
+    path: openBlock.path,
+    params: openBlock.params,
+    hash: openBlock.hash,
+    program: program,
+    inverse: inverse,
+    openStrip: openBlock.strip,
+    inverseStrip: inverseStrip,
+    closeStrip: close && close.strip,
+    loc: this.locInfo(locInfo)
+  };
+}
+
+function prepareProgram(statements, loc) {
+  if (!loc && statements.length) {
+    var firstLoc = statements[0].loc,
+        lastLoc = statements[statements.length - 1].loc;
+
+    /* istanbul ignore else */
+    if (firstLoc && lastLoc) {
+      loc = {
+        source: firstLoc.source,
+        start: {
+          line: firstLoc.start.line,
+          column: firstLoc.start.column
+        },
+        end: {
+          line: lastLoc.end.line,
+          column: lastLoc.end.column
+        }
+      };
+    }
+  }
+
+  return {
+    type: 'Program',
+    body: statements,
+    strip: {},
+    loc: loc
+  };
+}
+
+function preparePartialBlock(open, program, close, locInfo) {
+  validateClose(open, close);
+
+  return {
+    type: 'PartialBlockStatement',
+    name: open.path,
+    params: open.params,
+    hash: open.hash,
+    program: program,
+    openStrip: open.strip,
+    closeStrip: close && close.strip,
+    loc: this.locInfo(locInfo)
+  };
+}
+
+
+},{"../exception":262}],255:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+// istanbul ignore next
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _base = require('../base');
+
+var _exception = require('../exception');
+
+var _exception2 = _interopRequireDefault(_exception);
+
+var _utils = require('../utils');
+
+var _codeGen = require('./code-gen');
+
+var _codeGen2 = _interopRequireDefault(_codeGen);
+
+function Literal(value) {
+  this.value = value;
+}
+
+function JavaScriptCompiler() {}
+
+JavaScriptCompiler.prototype = {
+  // PUBLIC API: You can override these methods in a subclass to provide
+  // alternative compiled forms for name lookup and buffering semantics
+  nameLookup: function nameLookup(parent, name /* , type*/) {
+    if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
+      return [parent, '.', name];
+    } else {
+      return [parent, '[', JSON.stringify(name), ']'];
+    }
+  },
+  depthedLookup: function depthedLookup(name) {
+    return [this.aliasable('container.lookup'), '(depths, "', name, '")'];
+  },
+
+  compilerInfo: function compilerInfo() {
+    var revision = _base.COMPILER_REVISION,
+        versions = _base.REVISION_CHANGES[revision];
+    return [revision, versions];
+  },
+
+  appendToBuffer: function appendToBuffer(source, location, explicit) {
+    // Force a source as this simplifies the merge logic.
+    if (!_utils.isArray(source)) {
+      source = [source];
+    }
+    source = this.source.wrap(source, location);
+
+    if (this.environment.isSimple) {
+      return ['return ', source, ';'];
+    } else if (explicit) {
+      // This is a case where the buffer operation occurs as a child of another
+      // construct, generally braces. We have to explicitly output these buffer
+      // operations to ensure that the emitted code goes in the correct location.
+      return ['buffer += ', source, ';'];
+    } else {
+      source.appendToBuffer = true;
+      return source;
+    }
+  },
+
+  initializeBuffer: function initializeBuffer() {
+    return this.quotedString('');
+  },
+  // END PUBLIC API
+
+  compile: function compile(environment, options, context, asObject) {
+    this.environment = environment;
+    this.options = options;
+    this.stringParams = this.options.stringParams;
+    this.trackIds = this.options.trackIds;
+    this.precompile = !asObject;
+
+    this.name = this.environment.name;
+    this.isChild = !!context;
+    this.context = context || {
+      decorators: [],
+      programs: [],
+      environments: []
+    };
+
+    this.preamble();
+
+    this.stackSlot = 0;
+    this.stackVars = [];
+    this.aliases = {};
+    this.registers = { list: [] };
+    this.hashes = [];
+    this.compileStack = [];
+    this.inlineStack = [];
+    this.blockParams = [];
+
+    this.compileChildren(environment, options);
+
+    this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat;
+    this.useBlockParams = this.useBlockParams || environment.useBlockParams;
+
+    var opcodes = environment.opcodes,
+        opcode = undefined,
+        firstLoc = undefined,
+        i = undefined,
+        l = undefined;
+
+    for (i = 0, l = opcodes.length; i < l; i++) {
+      opcode = opcodes[i];
+
+      this.source.currentLocation = opcode.loc;
+      firstLoc = firstLoc || opcode.loc;
+      this[opcode.opcode].apply(this, opcode.args);
+    }
+
+    // Flush any trailing content that might be pending.
+    this.source.currentLocation = firstLoc;
+    this.pushSource('');
+
+    /* istanbul ignore next */
+    if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
+      throw new _exception2['default']('Compile completed with content left on stack');
+    }
+
+    if (!this.decorators.isEmpty()) {
+      this.useDecorators = true;
+
+      this.decorators.prepend('var decorators = container.decorators;\n');
+      this.decorators.push('return fn;');
+
+      if (asObject) {
+        this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]);
+      } else {
+        this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n');
+        this.decorators.push('}\n');
+        this.decorators = this.decorators.merge();
+      }
+    } else {
+      this.decorators = undefined;
+    }
+
+    var fn = this.createFunctionContext(asObject);
+    if (!this.isChild) {
+      var ret = {
+        compiler: this.compilerInfo(),
+        main: fn
+      };
+
+      if (this.decorators) {
+        ret.main_d = this.decorators; // eslint-disable-line camelcase
+        ret.useDecorators = true;
+      }
+
+      var _context = this.context;
+      var programs = _context.programs;
+      var decorators = _context.decorators;
+
+      for (i = 0, l = programs.length; i < l; i++) {
+        if (programs[i]) {
+          ret[i] = programs[i];
+          if (decorators[i]) {
+            ret[i + '_d'] = decorators[i];
+            ret.useDecorators = true;
+          }
+        }
+      }
+
+      if (this.environment.usePartial) {
+        ret.usePartial = true;
+      }
+      if (this.options.data) {
+        ret.useData = true;
+      }
+      if (this.useDepths) {
+        ret.useDepths = true;
+      }
+      if (this.useBlockParams) {
+        ret.useBlockParams = true;
+      }
+      if (this.options.compat) {
+        ret.compat = true;
+      }
+
+      if (!asObject) {
+        ret.compiler = JSON.stringify(ret.compiler);
+
+        this.source.currentLocation = { start: { line: 1, column: 0 } };
+        ret = this.objectLiteral(ret);
+
+        if (options.srcName) {
+          ret = ret.toStringWithSourceMap({ file: options.destName });
+          ret.map = ret.map && ret.map.toString();
+        } else {
+          ret = ret.toString();
+        }
+      } else {
+        ret.compilerOptions = this.options;
+      }
+
+      return ret;
+    } else {
+      return fn;
+    }
+  },
+
+  preamble: function preamble() {
+    // track the last context pushed into place to allow skipping the
+    // getContext opcode when it would be a noop
+    this.lastContext = 0;
+    this.source = new _codeGen2['default'](this.options.srcName);
+    this.decorators = new _codeGen2['default'](this.options.srcName);
+  },
+
+  createFunctionContext: function createFunctionContext(asObject) {
+    var varDeclarations = '';
+
+    var locals = this.stackVars.concat(this.registers.list);
+    if (locals.length > 0) {
+      varDeclarations += ', ' + locals.join(', ');
+    }
+
+    // Generate minimizer alias mappings
+    //
+    // When using true SourceNodes, this will update all references to the given alias
+    // as the source nodes are reused in situ. For the non-source node compilation mode,
+    // aliases will not be used, but this case is already being run on the client and
+    // we aren't concern about minimizing the template size.
+    var aliasCount = 0;
+    for (var alias in this.aliases) {
+      // eslint-disable-line guard-for-in
+      var node = this.aliases[alias];
+
+      if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) {
+        varDeclarations += ', alias' + ++aliasCount + '=' + alias;
+        node.children[0] = 'alias' + aliasCount;
+      }
+    }
+
+    var params = ['container', 'depth0', 'helpers', 'partials', 'data'];
+
+    if (this.useBlockParams || this.useDepths) {
+      params.push('blockParams');
+    }
+    if (this.useDepths) {
+      params.push('depths');
+    }
+
+    // Perform a second pass over the output to merge content when possible
+    var source = this.mergeSource(varDeclarations);
+
+    if (asObject) {
+      params.push(source);
+
+      return Function.apply(this, params);
+    } else {
+      return this.source.wrap(['function(', params.join(','), ') {\n  ', source, '}']);
+    }
+  },
+  mergeSource: function mergeSource(varDeclarations) {
+    var isSimple = this.environment.isSimple,
+        appendOnly = !this.forceBuffer,
+        appendFirst = undefined,
+        sourceSeen = undefined,
+        bufferStart = undefined,
+        bufferEnd = undefined;
+    this.source.each(function (line) {
+      if (line.appendToBuffer) {
+        if (bufferStart) {
+          line.prepend('  + ');
+        } else {
+          bufferStart = line;
+        }
+        bufferEnd = line;
+      } else {
+        if (bufferStart) {
+          if (!sourceSeen) {
+            appendFirst = true;
+          } else {
+            bufferStart.prepend('buffer += ');
+          }
+          bufferEnd.add(';');
+          bufferStart = bufferEnd = undefined;
+        }
+
+        sourceSeen = true;
+        if (!isSimple) {
+          appendOnly = false;
+        }
+      }
+    });
+
+    if (appendOnly) {
+      if (bufferStart) {
+        bufferStart.prepend('return ');
+        bufferEnd.add(';');
+      } else if (!sourceSeen) {
+        this.source.push('return "";');
+      }
+    } else {
+      varDeclarations += ', buffer = ' + (appendFirst ? '' : this.initializeBuffer());
+
+      if (bufferStart) {
+        bufferStart.prepend('return buffer + ');
+        bufferEnd.add(';');
+      } else {
+        this.source.push('return buffer;');
+      }
+    }
+
+    if (varDeclarations) {
+      this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n'));
+    }
+
+    return this.source.merge();
+  },
+
+  // [blockValue]
+  //
+  // On stack, before: hash, inverse, program, value
+  // On stack, after: return value of blockHelperMissing
+  //
+  // The purpose of this opcode is to take a block of the form
+  // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
+  // replace it on the stack with the result of properly
+  // invoking blockHelperMissing.
+  blockValue: function blockValue(name) {
+    var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
+        params = [this.contextName(0)];
+    this.setupHelperArgs(name, 0, params);
+
+    var blockName = this.popStack();
+    params.splice(1, 0, blockName);
+
+    this.push(this.source.functionCall(blockHelperMissing, 'call', params));
+  },
+
+  // [ambiguousBlockValue]
+  //
+  // On stack, before: hash, inverse, program, value
+  // Compiler value, before: lastHelper=value of last found helper, if any
+  // On stack, after, if no lastHelper: same as [blockValue]
+  // On stack, after, if lastHelper: value
+  ambiguousBlockValue: function ambiguousBlockValue() {
+    // We're being a bit cheeky and reusing the options value from the prior exec
+    var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
+        params = [this.contextName(0)];
+    this.setupHelperArgs('', 0, params, true);
+
+    this.flushInline();
+
+    var current = this.topStack();
+    params.splice(1, 0, current);
+
+    this.pushSource(['if (!', this.lastHelper, ') { ', current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params), '}']);
+  },
+
+  // [appendContent]
+  //
+  // On stack, before: ...
+  // On stack, after: ...
+  //
+  // Appends the string value of `content` to the current buffer
+  appendContent: function appendContent(content) {
+    if (this.pendingContent) {
+      content = this.pendingContent + content;
+    } else {
+      this.pendingLocation = this.source.currentLocation;
+    }
+
+    this.pendingContent = content;
+  },
+
+  // [append]
+  //
+  // On stack, before: value, ...
+  // On stack, after: ...
+  //
+  // Coerces `value` to a String and appends it to the current buffer.
+  //
+  // If `value` is truthy, or 0, it is coerced into a string and appended
+  // Otherwise, the empty string is appended
+  append: function append() {
+    if (this.isInline()) {
+      this.replaceStack(function (current) {
+        return [' != null ? ', current, ' : ""'];
+      });
+
+      this.pushSource(this.appendToBuffer(this.popStack()));
+    } else {
+      var local = this.popStack();
+      this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']);
+      if (this.environment.isSimple) {
+        this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']);
+      }
+    }
+  },
+
+  // [appendEscaped]
+  //
+  // On stack, before: value, ...
+  // On stack, after: ...
+  //
+  // Escape `value` and append it to the buffer
+  appendEscaped: function appendEscaped() {
+    this.pushSource(this.appendToBuffer([this.aliasable('container.escapeExpression'), '(', this.popStack(), ')']));
+  },
+
+  // [getContext]
+  //
+  // On stack, before: ...
+  // On stack, after: ...
+  // Compiler value, after: lastContext=depth
+  //
+  // Set the value of the `lastContext` compiler value to the depth
+  getContext: function getContext(depth) {
+    this.lastContext = depth;
+  },
+
+  // [pushContext]
+  //
+  // On stack, before: ...
+  // On stack, after: currentContext, ...
+  //
+  // Pushes the value of the current context onto the stack.
+  pushContext: function pushContext() {
+    this.pushStackLiteral(this.contextName(this.lastContext));
+  },
+
+  // [lookupOnContext]
+  //
+  // On stack, before: ...
+  // On stack, after: currentContext[name], ...
+  //
+  // Looks up the value of `name` on the current context and pushes
+  // it onto the stack.
+  lookupOnContext: function lookupOnContext(parts, falsy, strict, scoped) {
+    var i = 0;
+
+    if (!scoped && this.options.compat && !this.lastContext) {
+      // The depthed query is expected to handle the undefined logic for the root level that
+      // is implemented below, so we evaluate that directly in compat mode
+      this.push(this.depthedLookup(parts[i++]));
+    } else {
+      this.pushContext();
+    }
+
+    this.resolvePath('context', parts, i, falsy, strict);
+  },
+
+  // [lookupBlockParam]
+  //
+  // On stack, before: ...
+  // On stack, after: blockParam[name], ...
+  //
+  // Looks up the value of `parts` on the given block param and pushes
+  // it onto the stack.
+  lookupBlockParam: function lookupBlockParam(blockParamId, parts) {
+    this.useBlockParams = true;
+
+    this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']);
+    this.resolvePath('context', parts, 1);
+  },
+
+  // [lookupData]
+  //
+  // On stack, before: ...
+  // On stack, after: data, ...
+  //
+  // Push the data lookup operator
+  lookupData: function lookupData(depth, parts, strict) {
+    if (!depth) {
+      this.pushStackLiteral('data');
+    } else {
+      this.pushStackLiteral('container.data(data, ' + depth + ')');
+    }
+
+    this.resolvePath('data', parts, 0, true, strict);
+  },
+
+  resolvePath: function resolvePath(type, parts, i, falsy, strict) {
+    // istanbul ignore next
+
+    var _this = this;
+
+    if (this.options.strict || this.options.assumeObjects) {
+      this.push(strictLookup(this.options.strict && strict, this, parts, type));
+      return;
+    }
+
+    var len = parts.length;
+    for (; i < len; i++) {
+      /* eslint-disable no-loop-func */
+      this.replaceStack(function (current) {
+        var lookup = _this.nameLookup(current, parts[i], type);
+        // We want to ensure that zero and false are handled properly if the context (falsy flag)
+        // needs to have the special handling for these values.
+        if (!falsy) {
+          return [' != null ? ', lookup, ' : ', current];
+        } else {
+          // Otherwise we can use generic falsy handling
+          return [' && ', lookup];
+        }
+      });
+      /* eslint-enable no-loop-func */
+    }
+  },
+
+  // [resolvePossibleLambda]
+  //
+  // On stack, before: value, ...
+  // On stack, after: resolved value, ...
+  //
+  // If the `value` is a lambda, replace it on the stack by
+  // the return value of the lambda
+  resolvePossibleLambda: function resolvePossibleLambda() {
+    this.push([this.aliasable('container.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']);
+  },
+
+  // [pushStringParam]
+  //
+  // On stack, before: ...
+  // On stack, after: string, currentContext, ...
+  //
+  // This opcode is designed for use in string mode, which
+  // provides the string value of a parameter along with its
+  // depth rather than resolving it immediately.
+  pushStringParam: function pushStringParam(string, type) {
+    this.pushContext();
+    this.pushString(type);
+
+    // If it's a subexpression, the string result
+    // will be pushed after this opcode.
+    if (type !== 'SubExpression') {
+      if (typeof string === 'string') {
+        this.pushString(string);
+      } else {
+        this.pushStackLiteral(string);
+      }
+    }
+  },
+
+  emptyHash: function emptyHash(omitEmpty) {
+    if (this.trackIds) {
+      this.push('{}'); // hashIds
+    }
+    if (this.stringParams) {
+      this.push('{}'); // hashContexts
+      this.push('{}'); // hashTypes
+    }
+    this.pushStackLiteral(omitEmpty ? 'undefined' : '{}');
+  },
+  pushHash: function pushHash() {
+    if (this.hash) {
+      this.hashes.push(this.hash);
+    }
+    this.hash = { values: [], types: [], contexts: [], ids: [] };
+  },
+  popHash: function popHash() {
+    var hash = this.hash;
+    this.hash = this.hashes.pop();
+
+    if (this.trackIds) {
+      this.push(this.objectLiteral(hash.ids));
+    }
+    if (this.stringParams) {
+      this.push(this.objectLiteral(hash.contexts));
+      this.push(this.objectLiteral(hash.types));
+    }
+
+    this.push(this.objectLiteral(hash.values));
+  },
+
+  // [pushString]
+  //
+  // On stack, before: ...
+  // On stack, after: quotedString(string), ...
+  //
+  // Push a quoted version of `string` onto the stack
+  pushString: function pushString(string) {
+    this.pushStackLiteral(this.quotedString(string));
+  },
+
+  // [pushLiteral]
+  //
+  // On stack, before: ...
+  // On stack, after: value, ...
+  //
+  // Pushes a value onto the stack. This operation prevents
+  // the compiler from creating a temporary variable to hold
+  // it.
+  pushLiteral: function pushLiteral(value) {
+    this.pushStackLiteral(value);
+  },
+
+  // [pushProgram]
+  //
+  // On stack, before: ...
+  // On stack, after: program(guid), ...
+  //
+  // Push a program expression onto the stack. This takes
+  // a compile-time guid and converts it into a runtime-accessible
+  // expression.
+  pushProgram: function pushProgram(guid) {
+    if (guid != null) {
+      this.pushStackLiteral(this.programExpression(guid));
+    } else {
+      this.pushStackLiteral(null);
+    }
+  },
+
+  // [registerDecorator]
+  //
+  // On stack, before: hash, program, params..., ...
+  // On stack, after: ...
+  //
+  // Pops off the decorator's parameters, invokes the decorator,
+  // and inserts the decorator into the decorators list.
+  registerDecorator: function registerDecorator(paramSize, name) {
+    var foundDecorator = this.nameLookup('decorators', name, 'decorator'),
+        options = this.setupHelperArgs(name, paramSize);
+
+    this.decorators.push(['fn = ', this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]), ' || fn;']);
+  },
+
+  // [invokeHelper]
+  //
+  // On stack, before: hash, inverse, program, params..., ...
+  // On stack, after: result of helper invocation
+  //
+  // Pops off the helper's parameters, invokes the helper,
+  // and pushes the helper's return value onto the stack.
+  //
+  // If the helper is not found, `helperMissing` is called.
+  invokeHelper: function invokeHelper(paramSize, name, isSimple) {
+    var nonHelper = this.popStack(),
+        helper = this.setupHelper(paramSize, name),
+        simple = isSimple ? [helper.name, ' || '] : '';
+
+    var lookup = ['('].concat(simple, nonHelper);
+    if (!this.options.strict) {
+      lookup.push(' || ', this.aliasable('helpers.helperMissing'));
+    }
+    lookup.push(')');
+
+    this.push(this.source.functionCall(lookup, 'call', helper.callParams));
+  },
+
+  // [invokeKnownHelper]
+  //
+  // On stack, before: hash, inverse, program, params..., ...
+  // On stack, after: result of helper invocation
+  //
+  // This operation is used when the helper is known to exist,
+  // so a `helperMissing` fallback is not required.
+  invokeKnownHelper: function invokeKnownHelper(paramSize, name) {
+    var helper = this.setupHelper(paramSize, name);
+    this.push(this.source.functionCall(helper.name, 'call', helper.callParams));
+  },
+
+  // [invokeAmbiguous]
+  //
+  // On stack, before: hash, inverse, program, params..., ...
+  // On stack, after: result of disambiguation
+  //
+  // This operation is used when an expression like `{{foo}}`
+  // is provided, but we don't know at compile-time whether it
+  // is a helper or a path.
+  //
+  // This operation emits more code than the other options,
+  // and can be avoided by passing the `knownHelpers` and
+  // `knownHelpersOnly` flags at compile-time.
+  invokeAmbiguous: function invokeAmbiguous(name, helperCall) {
+    this.useRegister('helper');
+
+    var nonHelper = this.popStack();
+
+    this.emptyHash();
+    var helper = this.setupHelper(0, name, helperCall);
+
+    var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
+
+    var lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];
+    if (!this.options.strict) {
+      lookup[0] = '(helper = ';
+      lookup.push(' != null ? helper : ', this.aliasable('helpers.helperMissing'));
+    }
+
+    this.push(['(', lookup, helper.paramsInit ? ['),(', helper.paramsInit] : [], '),', '(typeof helper === ', this.aliasable('"function"'), ' ? ', this.source.functionCall('helper', 'call', helper.callParams), ' : helper))']);
+  },
+
+  // [invokePartial]
+  //
+  // On stack, before: context, ...
+  // On stack after: result of partial invocation
+  //
+  // This operation pops off a context, invokes a partial with that context,
+  // and pushes the result of the invocation back.
+  invokePartial: function invokePartial(isDynamic, name, indent) {
+    var params = [],
+        options = this.setupParams(name, 1, params);
+
+    if (isDynamic) {
+      name = this.popStack();
+      delete options.name;
+    }
+
+    if (indent) {
+      options.indent = JSON.stringify(indent);
+    }
+    options.helpers = 'helpers';
+    options.partials = 'partials';
+    options.decorators = 'container.decorators';
+
+    if (!isDynamic) {
+      params.unshift(this.nameLookup('partials', name, 'partial'));
+    } else {
+      params.unshift(name);
+    }
+
+    if (this.options.compat) {
+      options.depths = 'depths';
+    }
+    options = this.objectLiteral(options);
+    params.push(options);
+
+    this.push(this.source.functionCall('container.invokePartial', '', params));
+  },
+
+  // [assignToHash]
+  //
+  // On stack, before: value, ..., hash, ...
+  // On stack, after: ..., hash, ...
+  //
+  // Pops a value off the stack and assigns it to the current hash
+  assignToHash: function assignToHash(key) {
+    var value = this.popStack(),
+        context = undefined,
+        type = undefined,
+        id = undefined;
+
+    if (this.trackIds) {
+      id = this.popStack();
+    }
+    if (this.stringParams) {
+      type = this.popStack();
+      context = this.popStack();
+    }
+
+    var hash = this.hash;
+    if (context) {
+      hash.contexts[key] = context;
+    }
+    if (type) {
+      hash.types[key] = type;
+    }
+    if (id) {
+      hash.ids[key] = id;
+    }
+    hash.values[key] = value;
+  },
+
+  pushId: function pushId(type, name, child) {
+    if (type === 'BlockParam') {
+      this.pushStackLiteral('blockParams[' + name[0] + '].path[' + name[1] + ']' + (child ? ' + ' + JSON.stringify('.' + child) : ''));
+    } else if (type === 'PathExpression') {
+      this.pushString(name);
+    } else if (type === 'SubExpression') {
+      this.pushStackLiteral('true');
+    } else {
+      this.pushStackLiteral('null');
+    }
+  },
+
+  // HELPERS
+
+  compiler: JavaScriptCompiler,
+
+  compileChildren: function compileChildren(environment, options) {
+    var children = environment.children,
+        child = undefined,
+        compiler = undefined;
+
+    for (var i = 0, l = children.length; i < l; i++) {
+      child = children[i];
+      compiler = new this.compiler(); // eslint-disable-line new-cap
+
+      var index = this.matchExistingProgram(child);
+
+      if (index == null) {
+        this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
+        index = this.context.programs.length;
+        child.index = index;
+        child.name = 'program' + index;
+        this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
+        this.context.decorators[index] = compiler.decorators;
+        this.context.environments[index] = child;
+
+        this.useDepths = this.useDepths || compiler.useDepths;
+        this.useBlockParams = this.useBlockParams || compiler.useBlockParams;
+      } else {
+        child.index = index;
+        child.name = 'program' + index;
+
+        this.useDepths = this.useDepths || child.useDepths;
+        this.useBlockParams = this.useBlockParams || child.useBlockParams;
+      }
+    }
+  },
+  matchExistingProgram: function matchExistingProgram(child) {
+    for (var i = 0, len = this.context.environments.length; i < len; i++) {
+      var environment = this.context.environments[i];
+      if (environment && environment.equals(child)) {
+        return i;
+      }
+    }
+  },
+
+  programExpression: function programExpression(guid) {
+    var child = this.environment.children[guid],
+        programParams = [child.index, 'data', child.blockParams];
+
+    if (this.useBlockParams || this.useDepths) {
+      programParams.push('blockParams');
+    }
+    if (this.useDepths) {
+      programParams.push('depths');
+    }
+
+    return 'container.program(' + programParams.join(', ') + ')';
+  },
+
+  useRegister: function useRegister(name) {
+    if (!this.registers[name]) {
+      this.registers[name] = true;
+      this.registers.list.push(name);
+    }
+  },
+
+  push: function push(expr) {
+    if (!(expr instanceof Literal)) {
+      expr = this.source.wrap(expr);
+    }
+
+    this.inlineStack.push(expr);
+    return expr;
+  },
+
+  pushStackLiteral: function pushStackLiteral(item) {
+    this.push(new Literal(item));
+  },
+
+  pushSource: function pushSource(source) {
+    if (this.pendingContent) {
+      this.source.push(this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation));
+      this.pendingContent = undefined;
+    }
+
+    if (source) {
+      this.source.push(source);
+    }
+  },
+
+  replaceStack: function replaceStack(callback) {
+    var prefix = ['('],
+        stack = undefined,
+        createdStack = undefined,
+        usedLiteral = undefined;
+
+    /* istanbul ignore next */
+    if (!this.isInline()) {
+      throw new _exception2['default']('replaceStack on non-inline');
+    }
+
+    // We want to merge the inline statement into the replacement statement via ','
+    var top = this.popStack(true);
+
+    if (top instanceof Literal) {
+      // Literals do not need to be inlined
+      stack = [top.value];
+      prefix = ['(', stack];
+      usedLiteral = true;
+    } else {
+      // Get or create the current stack name for use by the inline
+      createdStack = true;
+      var _name = this.incrStack();
+
+      prefix = ['((', this.push(_name), ' = ', top, ')'];
+      stack = this.topStack();
+    }
+
+    var item = callback.call(this, stack);
+
+    if (!usedLiteral) {
+      this.popStack();
+    }
+    if (createdStack) {
+      this.stackSlot--;
+    }
+    this.push(prefix.concat(item, ')'));
+  },
+
+  incrStack: function incrStack() {
+    this.stackSlot++;
+    if (this.stackSlot > this.stackVars.length) {
+      this.stackVars.push('stack' + this.stackSlot);
+    }
+    return this.topStackName();
+  },
+  topStackName: function topStackName() {
+    return 'stack' + this.stackSlot;
+  },
+  flushInline: function flushInline() {
+    var inlineStack = this.inlineStack;
+    this.inlineStack = [];
+    for (var i = 0, len = inlineStack.length; i < len; i++) {
+      var entry = inlineStack[i];
+      /* istanbul ignore if */
+      if (entry instanceof Literal) {
+        this.compileStack.push(entry);
+      } else {
+        var stack = this.incrStack();
+        this.pushSource([stack, ' = ', entry, ';']);
+        this.compileStack.push(stack);
+      }
+    }
+  },
+  isInline: function isInline() {
+    return this.inlineStack.length;
+  },
+
+  popStack: function popStack(wrapped) {
+    var inline = this.isInline(),
+        item = (inline ? this.inlineStack : this.compileStack).pop();
+
+    if (!wrapped && item instanceof Literal) {
+      return item.value;
+    } else {
+      if (!inline) {
+        /* istanbul ignore next */
+        if (!this.stackSlot) {
+          throw new _exception2['default']('Invalid stack pop');
+        }
+        this.stackSlot--;
+      }
+      return item;
+    }
+  },
+
+  topStack: function topStack() {
+    var stack = this.isInline() ? this.inlineStack : this.compileStack,
+        item = stack[stack.length - 1];
+
+    /* istanbul ignore if */
+    if (item instanceof Literal) {
+      return item.value;
+    } else {
+      return item;
+    }
+  },
+
+  contextName: function contextName(context) {
+    if (this.useDepths && context) {
+      return 'depths[' + context + ']';
+    } else {
+      return 'depth' + context;
+    }
+  },
+
+  quotedString: function quotedString(str) {
+    return this.source.quotedString(str);
+  },
+
+  objectLiteral: function objectLiteral(obj) {
+    return this.source.objectLiteral(obj);
+  },
+
+  aliasable: function aliasable(name) {
+    var ret = this.aliases[name];
+    if (ret) {
+      ret.referenceCount++;
+      return ret;
+    }
+
+    ret = this.aliases[name] = this.source.wrap(name);
+    ret.aliasable = true;
+    ret.referenceCount = 1;
+
+    return ret;
+  },
+
+  setupHelper: function setupHelper(paramSize, name, blockHelper) {
+    var params = [],
+        paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
+    var foundHelper = this.nameLookup('helpers', name, 'helper'),
+        callContext = this.aliasable(this.contextName(0) + ' != null ? ' + this.contextName(0) + ' : {}');
+
+    return {
+      params: params,
+      paramsInit: paramsInit,
+      name: foundHelper,
+      callParams: [callContext].concat(params)
+    };
+  },
+
+  setupParams: function setupParams(helper, paramSize, params) {
+    var options = {},
+        contexts = [],
+        types = [],
+        ids = [],
+        objectArgs = !params,
+        param = undefined;
+
+    if (objectArgs) {
+      params = [];
+    }
+
+    options.name = this.quotedString(helper);
+    options.hash = this.popStack();
+
+    if (this.trackIds) {
+      options.hashIds = this.popStack();
+    }
+    if (this.stringParams) {
+      options.hashTypes = this.popStack();
+      options.hashContexts = this.popStack();
+    }
+
+    var inverse = this.popStack(),
+        program = this.popStack();
+
+    // Avoid setting fn and inverse if neither are set. This allows
+    // helpers to do a check for `if (options.fn)`
+    if (program || inverse) {
+      options.fn = program || 'container.noop';
+      options.inverse = inverse || 'container.noop';
+    }
+
+    // The parameters go on to the stack in order (making sure that they are evaluated in order)
+    // so we need to pop them off the stack in reverse order
+    var i = paramSize;
+    while (i--) {
+      param = this.popStack();
+      params[i] = param;
+
+      if (this.trackIds) {
+        ids[i] = this.popStack();
+      }
+      if (this.stringParams) {
+        types[i] = this.popStack();
+        contexts[i] = this.popStack();
+      }
+    }
+
+    if (objectArgs) {
+      options.args = this.source.generateArray(params);
+    }
+
+    if (this.trackIds) {
+      options.ids = this.source.generateArray(ids);
+    }
+    if (this.stringParams) {
+      options.types = this.source.generateArray(types);
+      options.contexts = this.source.generateArray(contexts);
+    }
+
+    if (this.options.data) {
+      options.data = 'data';
+    }
+    if (this.useBlockParams) {
+      options.blockParams = 'blockParams';
+    }
+    return options;
+  },
+
+  setupHelperArgs: function setupHelperArgs(helper, paramSize, params, useRegister) {
+    var options = this.setupParams(helper, paramSize, params);
+    options = this.objectLiteral(options);
+    if (useRegister) {
+      this.useRegister('options');
+      params.push('options');
+      return ['options=', options];
+    } else if (params) {
+      params.push(options);
+      return '';
+    } else {
+      return options;
+    }
+  }
+};
+
+(function () {
+  var reservedWords = ('break else new var' + ' case finally return void' + ' catch for switch while' + ' continue function this with' + ' default if throw' + ' delete in try' + ' do instanceof typeof' + ' abstract enum int short' + ' boolean export interface static' + ' byte extends long super' + ' char final native synchronized' + ' class float package throws' + ' const goto private transient' + ' debugger implements protected volatile' + ' double import public let yield await' + ' null true false').split(' ');
+
+  var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
+
+  for (var i = 0, l = reservedWords.length; i < l; i++) {
+    compilerWords[reservedWords[i]] = true;
+  }
+})();
+
+JavaScriptCompiler.isValidJavaScriptVariableName = function (name) {
+  return !JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name);
+};
+
+function strictLookup(requireTerminal, compiler, parts, type) {
+  var stack = compiler.popStack(),
+      i = 0,
+      len = parts.length;
+  if (requireTerminal) {
+    len--;
+  }
+
+  for (; i < len; i++) {
+    stack = compiler.nameLookup(stack, parts[i], type);
+  }
+
+  if (requireTerminal) {
+    return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')'];
+  } else {
+    return stack;
+  }
+}
+
+exports['default'] = JavaScriptCompiler;
+module.exports = exports['default'];
+
+
+},{"../base":249,"../exception":262,"../utils":275,"./code-gen":252}],256:[function(require,module,exports){
+/* istanbul ignore next */
+/* Jison generated parser */
+"use strict";
+
+var handlebars = (function () {
+    var parser = { trace: function trace() {},
+        yy: {},
+        symbols_: { "error": 2, "root": 3, "program": 4, "EOF": 5, "program_repetition0": 6, "statement": 7, "mustache": 8, "block": 9, "rawBlock": 10, "partial": 11, "partialBlock": 12, "content": 13, "COMMENT": 14, "CONTENT": 15, "openRawBlock": 16, "rawBlock_repetition_plus0": 17, "END_RAW_BLOCK": 18, "OPEN_RAW_BLOCK": 19, "helperName": 20, "openRawBlock_repetition0": 21, "openRawBlock_option0": 22, "CLOSE_RAW_BLOCK": 23, "openBlock": 24, "block_option0": 25, "closeBlock": 26, "openInverse": 27, "block_option1": 28, "OPEN_BLOCK": 29, "openBlock_repetition0": 30, "openBlock_option0": 31, "openBlock_option1": 32, "CLOSE": 33, "OPEN_INVERSE": 34, "openInverse_repetition0": 35, "openInverse_option0": 36, "openInverse_option1": 37, "openInverseChain": 38, "OPEN_INVERSE_CHAIN": 39, "openInverseChain_repetition0": 40, "openInverseChain_option0": 41, "openInverseChain_option1": 42, "inverseAndProgram": 43, "INVERSE": 44, "inverseChain": 45, "inverseChain_option0": 46, "OPEN_ENDBLOCK": 47, "OPEN": 48, "mustache_repetition0": 49, "mustache_option0": 50, "OPEN_UNESCAPED": 51, "mustache_repetition1": 52, "mustache_option1": 53, "CLOSE_UNESCAPED": 54, "OPEN_PARTIAL": 55, "partialName": 56, "partial_repetition0": 57, "partial_option0": 58, "openPartialBlock": 59, "OPEN_PARTIAL_BLOCK": 60, "openPartialBlock_repetition0": 61, "openPartialBlock_option0": 62, "param": 63, "sexpr": 64, "OPEN_SEXPR": 65, "sexpr_repetition0": 66, "sexpr_option0": 67, "CLOSE_SEXPR": 68, "hash": 69, "hash_repetition_plus0": 70, "hashSegment": 71, "ID": 72, "EQUALS": 73, "blockParams": 74, "OPEN_BLOCK_PARAMS": 75, "blockParams_repetition_plus0": 76, "CLOSE_BLOCK_PARAMS": 77, "path": 78, "dataName": 79, "STRING": 80, "NUMBER": 81, "BOOLEAN": 82, "UNDEFINED": 83, "NULL": 84, "DATA": 85, "pathSegments": 86, "SEP": 87, "$accept": 0, "$end": 1 },
+        terminals_: { 2: "error", 5: "EOF", 14: "COMMENT", 15: "CONTENT", 18: "END_RAW_BLOCK", 19: "OPEN_RAW_BLOCK", 23: "CLOSE_RAW_BLOCK", 29: "OPEN_BLOCK", 33: "CLOSE", 34: "OPEN_INVERSE", 39: "OPEN_INVERSE_CHAIN", 44: "INVERSE", 47: "OPEN_ENDBLOCK", 48: "OPEN", 51: "OPEN_UNESCAPED", 54: "CLOSE_UNESCAPED", 55: "OPEN_PARTIAL", 60: "OPEN_PARTIAL_BLOCK", 65: "OPEN_SEXPR", 68: "CLOSE_SEXPR", 72: "ID", 73: "EQUALS", 75: "OPEN_BLOCK_PARAMS", 77: "CLOSE_BLOCK_PARAMS", 80: "STRING", 81: "NUMBER", 82: "BOOLEAN", 83: "UNDEFINED", 84: "NULL", 85: "DATA", 87: "SEP" },
+        productions_: [0, [3, 2], [4, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [13, 1], [10, 3], [16, 5], [9, 4], [9, 4], [24, 6], [27, 6], [38, 6], [43, 2], [45, 3], [45, 1], [26, 3], [8, 5], [8, 5], [11, 5], [12, 3], [59, 5], [63, 1], [63, 1], [64, 5], [69, 1], [71, 3], [74, 3], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [56, 1], [56, 1], [79, 2], [78, 1], [86, 3], [86, 1], [6, 0], [6, 2], [17, 1], [17, 2], [21, 0], [21, 2], [22, 0], [22, 1], [25, 0], [25, 1], [28, 0], [28, 1], [30, 0], [30, 2], [31, 0], [31, 1], [32, 0], [32, 1], [35, 0], [35, 2], [36, 0], [36, 1], [37, 0], [37, 1], [40, 0], [40, 2], [41, 0], [41, 1], [42, 0], [42, 1], [46, 0], [46, 1], [49, 0], [49, 2], [50, 0], [50, 1], [52, 0], [52, 2], [53, 0], [53, 1], [57, 0], [57, 2], [58, 0], [58, 1], [61, 0], [61, 2], [62, 0], [62, 1], [66, 0], [66, 2], [67, 0], [67, 1], [70, 1], [70, 2], [76, 1], [76, 2]],
+        performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$
+        /**/) {
+
+            var $0 = $$.length - 1;
+            switch (yystate) {
+                case 1:
+                    return $$[$0 - 1];
+                    break;
+                case 2:
+                    this.$ = yy.prepareProgram($$[$0]);
+                    break;
+                case 3:
+                    this.$ = $$[$0];
+                    break;
+                case 4:
+                    this.$ = $$[$0];
+                    break;
+                case 5:
+                    this.$ = $$[$0];
+                    break;
+                case 6:
+                    this.$ = $$[$0];
+                    break;
+                case 7:
+                    this.$ = $$[$0];
+                    break;
+                case 8:
+                    this.$ = $$[$0];
+                    break;
+                case 9:
+                    this.$ = {
+                        type: 'CommentStatement',
+                        value: yy.stripComment($$[$0]),
+                        strip: yy.stripFlags($$[$0], $$[$0]),
+                        loc: yy.locInfo(this._$)
+                    };
+
+                    break;
+                case 10:
+                    this.$ = {
+                        type: 'ContentStatement',
+                        original: $$[$0],
+                        value: $$[$0],
+                        loc: yy.locInfo(this._$)
+                    };
+
+                    break;
+                case 11:
+                    this.$ = yy.prepareRawBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$);
+                    break;
+                case 12:
+                    this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1] };
+                    break;
+                case 13:
+                    this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], false, this._$);
+                    break;
+                case 14:
+                    this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], true, this._$);
+                    break;
+                case 15:
+                    this.$ = { open: $$[$0 - 5], path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) };
+                    break;
+                case 16:
+                    this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) };
+                    break;
+                case 17:
+                    this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) };
+                    break;
+                case 18:
+                    this.$ = { strip: yy.stripFlags($$[$0 - 1], $$[$0 - 1]), program: $$[$0] };
+                    break;
+                case 19:
+                    var inverse = yy.prepareBlock($$[$0 - 2], $$[$0 - 1], $$[$0], $$[$0], false, this._$),
+                        program = yy.prepareProgram([inverse], $$[$0 - 1].loc);
+                    program.chained = true;
+
+                    this.$ = { strip: $$[$0 - 2].strip, program: program, chain: true };
+
+                    break;
+                case 20:
+                    this.$ = $$[$0];
+                    break;
+                case 21:
+                    this.$ = { path: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 2], $$[$0]) };
+                    break;
+                case 22:
+                    this.$ = yy.prepareMustache($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0 - 4], yy.stripFlags($$[$0 - 4], $$[$0]), this._$);
+                    break;
+                case 23:
+                    this.$ = yy.prepareMustache($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0 - 4], yy.stripFlags($$[$0 - 4], $$[$0]), this._$);
+                    break;
+                case 24:
+                    this.$ = {
+                        type: 'PartialStatement',
+                        name: $$[$0 - 3],
+                        params: $$[$0 - 2],
+                        hash: $$[$0 - 1],
+                        indent: '',
+                        strip: yy.stripFlags($$[$0 - 4], $$[$0]),
+                        loc: yy.locInfo(this._$)
+                    };
+
+                    break;
+                case 25:
+                    this.$ = yy.preparePartialBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$);
+                    break;
+                case 26:
+                    this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 4], $$[$0]) };
+                    break;
+                case 27:
+                    this.$ = $$[$0];
+                    break;
+                case 28:
+                    this.$ = $$[$0];
+                    break;
+                case 29:
+                    this.$ = {
+                        type: 'SubExpression',
+                        path: $$[$0 - 3],
+                        params: $$[$0 - 2],
+                        hash: $$[$0 - 1],
+                        loc: yy.locInfo(this._$)
+                    };
+
+                    break;
+                case 30:
+                    this.$ = { type: 'Hash', pairs: $$[$0], loc: yy.locInfo(this._$) };
+                    break;
+                case 31:
+                    this.$ = { type: 'HashPair', key: yy.id($$[$0 - 2]), value: $$[$0], loc: yy.locInfo(this._$) };
+                    break;
+                case 32:
+                    this.$ = yy.id($$[$0 - 1]);
+                    break;
+                case 33:
+                    this.$ = $$[$0];
+                    break;
+                case 34:
+                    this.$ = $$[$0];
+                    break;
+                case 35:
+                    this.$ = { type: 'StringLiteral', value: $$[$0], original: $$[$0], loc: yy.locInfo(this._$) };
+                    break;
+                case 36:
+                    this.$ = { type: 'NumberLiteral', value: Number($$[$0]), original: Number($$[$0]), loc: yy.locInfo(this._$) };
+                    break;
+                case 37:
+                    this.$ = { type: 'BooleanLiteral', value: $$[$0] === 'true', original: $$[$0] === 'true', loc: yy.locInfo(this._$) };
+                    break;
+                case 38:
+                    this.$ = { type: 'UndefinedLiteral', original: undefined, value: undefined, loc: yy.locInfo(this._$) };
+                    break;
+                case 39:
+                    this.$ = { type: 'NullLiteral', original: null, value: null, loc: yy.locInfo(this._$) };
+                    break;
+                case 40:
+                    this.$ = $$[$0];
+                    break;
+                case 41:
+                    this.$ = $$[$0];
+                    break;
+                case 42:
+                    this.$ = yy.preparePath(true, $$[$0], this._$);
+                    break;
+                case 43:
+                    this.$ = yy.preparePath(false, $$[$0], this._$);
+                    break;
+                case 44:
+                    $$[$0 - 2].push({ part: yy.id($$[$0]), original: $$[$0], separator: $$[$0 - 1] });this.$ = $$[$0 - 2];
+                    break;
+                case 45:
+                    this.$ = [{ part: yy.id($$[$0]), original: $$[$0] }];
+                    break;
+                case 46:
+                    this.$ = [];
+                    break;
+                case 47:
+                    $$[$0 - 1].push($$[$0]);
+                    break;
+                case 48:
+                    this.$ = [$$[$0]];
+                    break;
+                case 49:
+                    $$[$0 - 1].push($$[$0]);
+                    break;
+                case 50:
+                    this.$ = [];
+                    break;
+                case 51:
+                    $$[$0 - 1].push($$[$0]);
+                    break;
+                case 58:
+                    this.$ = [];
+                    break;
+                case 59:
+                    $$[$0 - 1].push($$[$0]);
+                    break;
+                case 64:
+                    this.$ = [];
+                    break;
+                case 65:
+                    $$[$0 - 1].push($$[$0]);
+                    break;
+                case 70:
+                    this.$ = [];
+                    break;
+                case 71:
+                    $$[$0 - 1].push($$[$0]);
+                    break;
+                case 78:
+                    this.$ = [];
+                    break;
+                case 79:
+                    $$[$0 - 1].push($$[$0]);
+                    break;
+                case 82:
+                    this.$ = [];
+                    break;
+                case 83:
+                    $$[$0 - 1].push($$[$0]);
+                    break;
+                case 86:
+                    this.$ = [];
+                    break;
+                case 87:
+                    $$[$0 - 1].push($$[$0]);
+                    break;
+                case 90:
+                    this.$ = [];
+                    break;
+                case 91:
+                    $$[$0 - 1].push($$[$0]);
+                    break;
+                case 94:
+                    this.$ = [];
+                    break;
+                case 95:
+                    $$[$0 - 1].push($$[$0]);
+                    break;
+                case 98:
+                    this.$ = [$$[$0]];
+                    break;
+                case 99:
+                    $$[$0 - 1].push($$[$0]);
+                    break;
+                case 100:
+                    this.$ = [$$[$0]];
+                    break;
+                case 101:
+                    $$[$0 - 1].push($$[$0]);
+                    break;
+            }
+        },
+        table: [{ 3: 1, 4: 2, 5: [2, 46], 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 1: [3] }, { 5: [1, 4] }, { 5: [2, 2], 7: 5, 8: 6, 9: 7, 10: 8, 11: 9, 12: 10, 13: 11, 14: [1, 12], 15: [1, 20], 16: 17, 19: [1, 23], 24: 15, 27: 16, 29: [1, 21], 34: [1, 22], 39: [2, 2], 44: [2, 2], 47: [2, 2], 48: [1, 13], 51: [1, 14], 55: [1, 18], 59: 19, 60: [1, 24] }, { 1: [2, 1] }, { 5: [2, 47], 14: [2, 47], 15: [2, 47], 19: [2, 47], 29: [2, 47], 34: [2, 47], 39: [2, 47], 44: [2, 47], 47: [2, 47], 48: [2, 47], 51: [2, 47], 55: [2, 47], 60: [2, 47] }, { 5: [2, 3], 14: [2, 3], 15: [2, 3], 19: [2, 3], 29: [2, 3], 34: [2, 3], 39: [2, 3], 44: [2, 3], 47: [2, 3], 48: [2, 3], 51: [2, 3], 55: [2, 3], 60: [2, 3] }, { 5: [2, 4], 14: [2, 4], 15: [2, 4], 19: [2, 4], 29: [2, 4], 34: [2, 4], 39: [2, 4], 44: [2, 4], 47: [2, 4], 48: [2, 4], 51: [2, 4], 55: [2, 4], 60: [2, 4] }, { 5: [2, 5], 14: [2, 5], 15: [2, 5], 19: [2, 5], 29: [2, 5], 34: [2, 5], 39: [2, 5], 44: [2, 5], 47: [2, 5], 48: [2, 5], 51: [2, 5], 55: [2, 5], 60: [2, 5] }, { 5: [2, 6], 14: [2, 6], 15: [2, 6], 19: [2, 6], 29: [2, 6], 34: [2, 6], 39: [2, 6], 44: [2, 6], 47: [2, 6], 48: [2, 6], 51: [2, 6], 55: [2, 6], 60: [2, 6] }, { 5: [2, 7], 14: [2, 7], 15: [2, 7], 19: [2, 7], 29: [2, 7], 34: [2, 7], 39: [2, 7], 44: [2, 7], 47: [2, 7], 48: [2, 7], 51: [2, 7], 55: [2, 7], 60: [2, 7] }, { 5: [2, 8], 14: [2, 8], 15: [2, 8], 19: [2, 8], 29: [2, 8], 34: [2, 8], 39: [2, 8], 44: [2, 8], 47: [2, 8], 48: [2, 8], 51: [2, 8], 55: [2, 8], 60: [2, 8] }, { 5: [2, 9], 14: [2, 9], 15: [2, 9], 19: [2, 9], 29: [2, 9], 34: [2, 9], 39: [2, 9], 44: [2, 9], 47: [2, 9], 48: [2, 9], 51: [2, 9], 55: [2, 9], 60: [2, 9] }, { 20: 25, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 36, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 37, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 4: 38, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 13: 40, 15: [1, 20], 17: 39 }, { 20: 42, 56: 41, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 45, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 5: [2, 10], 14: [2, 10], 15: [2, 10], 18: [2, 10], 19: [2, 10], 29: [2, 10], 34: [2, 10], 39: [2, 10], 44: [2, 10], 47: [2, 10], 48: [2, 10], 51: [2, 10], 55: [2, 10], 60: [2, 10] }, { 20: 46, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 47, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 48, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 42, 56: 49, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [2, 78], 49: 50, 65: [2, 78], 72: [2, 78], 80: [2, 78], 81: [2, 78], 82: [2, 78], 83: [2, 78], 84: [2, 78], 85: [2, 78] }, { 23: [2, 33], 33: [2, 33], 54: [2, 33], 65: [2, 33], 68: [2, 33], 72: [2, 33], 75: [2, 33], 80: [2, 33], 81: [2, 33], 82: [2, 33], 83: [2, 33], 84: [2, 33], 85: [2, 33] }, { 23: [2, 34], 33: [2, 34], 54: [2, 34], 65: [2, 34], 68: [2, 34], 72: [2, 34], 75: [2, 34], 80: [2, 34], 81: [2, 34], 82: [2, 34], 83: [2, 34], 84: [2, 34], 85: [2, 34] }, { 23: [2, 35], 33: [2, 35], 54: [2, 35], 65: [2, 35], 68: [2, 35], 72: [2, 35], 75: [2, 35], 80: [2, 35], 81: [2, 35], 82: [2, 35], 83: [2, 35], 84: [2, 35], 85: [2, 35] }, { 23: [2, 36], 33: [2, 36], 54: [2, 36], 65: [2, 36], 68: [2, 36], 72: [2, 36], 75: [2, 36], 80: [2, 36], 81: [2, 36], 82: [2, 36], 83: [2, 36], 84: [2, 36], 85: [2, 36] }, { 23: [2, 37], 33: [2, 37], 54: [2, 37], 65: [2, 37], 68: [2, 37], 72: [2, 37], 75: [2, 37], 80: [2, 37], 81: [2, 37], 82: [2, 37], 83: [2, 37], 84: [2, 37], 85: [2, 37] }, { 23: [2, 38], 33: [2, 38], 54: [2, 38], 65: [2, 38], 68: [2, 38], 72: [2, 38], 75: [2, 38], 80: [2, 38], 81: [2, 38], 82: [2, 38], 83: [2, 38], 84: [2, 38], 85: [2, 38] }, { 23: [2, 39], 33: [2, 39], 54: [2, 39], 65: [2, 39], 68: [2, 39], 72: [2, 39], 75: [2, 39], 80: [2, 39], 81: [2, 39], 82: [2, 39], 83: [2, 39], 84: [2, 39], 85: [2, 39] }, { 23: [2, 43], 33: [2, 43], 54: [2, 43], 65: [2, 43], 68: [2, 43], 72: [2, 43], 75: [2, 43], 80: [2, 43], 81: [2, 43], 82: [2, 43], 83: [2, 43], 84: [2, 43], 85: [2, 43], 87: [1, 51] }, { 72: [1, 35], 86: 52 }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 52: 53, 54: [2, 82], 65: [2, 82], 72: [2, 82], 80: [2, 82], 81: [2, 82], 82: [2, 82], 83: [2, 82], 84: [2, 82], 85: [2, 82] }, { 25: 54, 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 55, 47: [2, 54] }, { 28: 60, 43: 61, 44: [1, 59], 47: [2, 56] }, { 13: 63, 15: [1, 20], 18: [1, 62] }, { 15: [2, 48], 18: [2, 48] }, { 33: [2, 86], 57: 64, 65: [2, 86], 72: [2, 86], 80: [2, 86], 81: [2, 86], 82: [2, 86], 83: [2, 86], 84: [2, 86], 85: [2, 86] }, { 33: [2, 40], 65: [2, 40], 72: [2, 40], 80: [2, 40], 81: [2, 40], 82: [2, 40], 83: [2, 40], 84: [2, 40], 85: [2, 40] }, { 33: [2, 41], 65: [2, 41], 72: [2, 41], 80: [2, 41], 81: [2, 41], 82: [2, 41], 83: [2, 41], 84: [2, 41], 85: [2, 41] }, { 20: 65, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 66, 47: [1, 67] }, { 30: 68, 33: [2, 58], 65: [2, 58], 72: [2, 58], 75: [2, 58], 80: [2, 58], 81: [2, 58], 82: [2, 58], 83: [2, 58], 84: [2, 58], 85: [2, 58] }, { 33: [2, 64], 35: 69, 65: [2, 64], 72: [2, 64], 75: [2, 64], 80: [2, 64], 81: [2, 64], 82: [2, 64], 83: [2, 64], 84: [2, 64], 85: [2, 64] }, { 21: 70, 23: [2, 50], 65: [2, 50], 72: [2, 50], 80: [2, 50], 81: [2, 50], 82: [2, 50], 83: [2, 50], 84: [2, 50], 85: [2, 50] }, { 33: [2, 90], 61: 71, 65: [2, 90], 72: [2, 90], 80: [2, 90], 81: [2, 90], 82: [2, 90], 83: [2, 90], 84: [2, 90], 85: [2, 90] }, { 20: 75, 33: [2, 80], 50: 72, 63: 73, 64: 76, 65: [1, 44], 69: 74, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 72: [1, 80] }, { 23: [2, 42], 33: [2, 42], 54: [2, 42], 65: [2, 42], 68: [2, 42], 72: [2, 42], 75: [2, 42], 80: [2, 42], 81: [2, 42], 82: [2, 42], 83: [2, 42], 84: [2, 42], 85: [2, 42], 87: [1, 51] }, { 20: 75, 53: 81, 54: [2, 84], 63: 82, 64: 76, 65: [1, 44], 69: 83, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 84, 47: [1, 67] }, { 47: [2, 55] }, { 4: 85, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 47: [2, 20] }, { 20: 86, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 87, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 26: 88, 47: [1, 67] }, { 47: [2, 57] }, { 5: [2, 11], 14: [2, 11], 15: [2, 11], 19: [2, 11], 29: [2, 11], 34: [2, 11], 39: [2, 11], 44: [2, 11], 47: [2, 11], 48: [2, 11], 51: [2, 11], 55: [2, 11], 60: [2, 11] }, { 15: [2, 49], 18: [2, 49] }, { 20: 75, 33: [2, 88], 58: 89, 63: 90, 64: 76, 65: [1, 44], 69: 91, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 65: [2, 94], 66: 92, 68: [2, 94], 72: [2, 94], 80: [2, 94], 81: [2, 94], 82: [2, 94], 83: [2, 94], 84: [2, 94], 85: [2, 94] }, { 5: [2, 25], 14: [2, 25], 15: [2, 25], 19: [2, 25], 29: [2, 25], 34: [2, 25], 39: [2, 25], 44: [2, 25], 47: [2, 25], 48: [2, 25], 51: [2, 25], 55: [2, 25], 60: [2, 25] }, { 20: 93, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 31: 94, 33: [2, 60], 63: 95, 64: 76, 65: [1, 44], 69: 96, 70: 77, 71: 78, 72: [1, 79], 75: [2, 60], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 66], 36: 97, 63: 98, 64: 76, 65: [1, 44], 69: 99, 70: 77, 71: 78, 72: [1, 79], 75: [2, 66], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 22: 100, 23: [2, 52], 63: 101, 64: 76, 65: [1, 44], 69: 102, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 92], 62: 103, 63: 104, 64: 76, 65: [1, 44], 69: 105, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 106] }, { 33: [2, 79], 65: [2, 79], 72: [2, 79], 80: [2, 79], 81: [2, 79], 82: [2, 79], 83: [2, 79], 84: [2, 79], 85: [2, 79] }, { 33: [2, 81] }, { 23: [2, 27], 33: [2, 27], 54: [2, 27], 65: [2, 27], 68: [2, 27], 72: [2, 27], 75: [2, 27], 80: [2, 27], 81: [2, 27], 82: [2, 27], 83: [2, 27], 84: [2, 27], 85: [2, 27] }, { 23: [2, 28], 33: [2, 28], 54: [2, 28], 65: [2, 28], 68: [2, 28], 72: [2, 28], 75: [2, 28], 80: [2, 28], 81: [2, 28], 82: [2, 28], 83: [2, 28], 84: [2, 28], 85: [2, 28] }, { 23: [2, 30], 33: [2, 30], 54: [2, 30], 68: [2, 30], 71: 107, 72: [1, 108], 75: [2, 30] }, { 23: [2, 98], 33: [2, 98], 54: [2, 98], 68: [2, 98], 72: [2, 98], 75: [2, 98] }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 73: [1, 109], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 23: [2, 44], 33: [2, 44], 54: [2, 44], 65: [2, 44], 68: [2, 44], 72: [2, 44], 75: [2, 44], 80: [2, 44], 81: [2, 44], 82: [2, 44], 83: [2, 44], 84: [2, 44], 85: [2, 44], 87: [2, 44] }, { 54: [1, 110] }, { 54: [2, 83], 65: [2, 83], 72: [2, 83], 80: [2, 83], 81: [2, 83], 82: [2, 83], 83: [2, 83], 84: [2, 83], 85: [2, 83] }, { 54: [2, 85] }, { 5: [2, 13], 14: [2, 13], 15: [2, 13], 19: [2, 13], 29: [2, 13], 34: [2, 13], 39: [2, 13], 44: [2, 13], 47: [2, 13], 48: [2, 13], 51: [2, 13], 55: [2, 13], 60: [2, 13] }, { 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 112, 46: 111, 47: [2, 76] }, { 33: [2, 70], 40: 113, 65: [2, 70], 72: [2, 70], 75: [2, 70], 80: [2, 70], 81: [2, 70], 82: [2, 70], 83: [2, 70], 84: [2, 70], 85: [2, 70] }, { 47: [2, 18] }, { 5: [2, 14], 14: [2, 14], 15: [2, 14], 19: [2, 14], 29: [2, 14], 34: [2, 14], 39: [2, 14], 44: [2, 14], 47: [2, 14], 48: [2, 14], 51: [2, 14], 55: [2, 14], 60: [2, 14] }, { 33: [1, 114] }, { 33: [2, 87], 65: [2, 87], 72: [2, 87], 80: [2, 87], 81: [2, 87], 82: [2, 87], 83: [2, 87], 84: [2, 87], 85: [2, 87] }, { 33: [2, 89] }, { 20: 75, 63: 116, 64: 76, 65: [1, 44], 67: 115, 68: [2, 96], 69: 117, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 118] }, { 32: 119, 33: [2, 62], 74: 120, 75: [1, 121] }, { 33: [2, 59], 65: [2, 59], 72: [2, 59], 75: [2, 59], 80: [2, 59], 81: [2, 59], 82: [2, 59], 83: [2, 59], 84: [2, 59], 85: [2, 59] }, { 33: [2, 61], 75: [2, 61] }, { 33: [2, 68], 37: 122, 74: 123, 75: [1, 121] }, { 33: [2, 65], 65: [2, 65], 72: [2, 65], 75: [2, 65], 80: [2, 65], 81: [2, 65], 82: [2, 65], 83: [2, 65], 84: [2, 65], 85: [2, 65] }, { 33: [2, 67], 75: [2, 67] }, { 23: [1, 124] }, { 23: [2, 51], 65: [2, 51], 72: [2, 51], 80: [2, 51], 81: [2, 51], 82: [2, 51], 83: [2, 51], 84: [2, 51], 85: [2, 51] }, { 23: [2, 53] }, { 33: [1, 125] }, { 33: [2, 91], 65: [2, 91], 72: [2, 91], 80: [2, 91], 81: [2, 91], 82: [2, 91], 83: [2, 91], 84: [2, 91], 85: [2, 91] }, { 33: [2, 93] }, { 5: [2, 22], 14: [2, 22], 15: [2, 22], 19: [2, 22], 29: [2, 22], 34: [2, 22], 39: [2, 22], 44: [2, 22], 47: [2, 22], 48: [2, 22], 51: [2, 22], 55: [2, 22], 60: [2, 22] }, { 23: [2, 99], 33: [2, 99], 54: [2, 99], 68: [2, 99], 72: [2, 99], 75: [2, 99] }, { 73: [1, 109] }, { 20: 75, 63: 126, 64: 76, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 23], 14: [2, 23], 15: [2, 23], 19: [2, 23], 29: [2, 23], 34: [2, 23], 39: [2, 23], 44: [2, 23], 47: [2, 23], 48: [2, 23], 51: [2, 23], 55: [2, 23], 60: [2, 23] }, { 47: [2, 19] }, { 47: [2, 77] }, { 20: 75, 33: [2, 72], 41: 127, 63: 128, 64: 76, 65: [1, 44], 69: 129, 70: 77, 71: 78, 72: [1, 79], 75: [2, 72], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 24], 14: [2, 24], 15: [2, 24], 19: [2, 24], 29: [2, 24], 34: [2, 24], 39: [2, 24], 44: [2, 24], 47: [2, 24], 48: [2, 24], 51: [2, 24], 55: [2, 24], 60: [2, 24] }, { 68: [1, 130] }, { 65: [2, 95], 68: [2, 95], 72: [2, 95], 80: [2, 95], 81: [2, 95], 82: [2, 95], 83: [2, 95], 84: [2, 95], 85: [2, 95] }, { 68: [2, 97] }, { 5: [2, 21], 14: [2, 21], 15: [2, 21], 19: [2, 21], 29: [2, 21], 34: [2, 21], 39: [2, 21], 44: [2, 21], 47: [2, 21], 48: [2, 21], 51: [2, 21], 55: [2, 21], 60: [2, 21] }, { 33: [1, 131] }, { 33: [2, 63] }, { 72: [1, 133], 76: 132 }, { 33: [1, 134] }, { 33: [2, 69] }, { 15: [2, 12] }, { 14: [2, 26], 15: [2, 26], 19: [2, 26], 29: [2, 26], 34: [2, 26], 47: [2, 26], 48: [2, 26], 51: [2, 26], 55: [2, 26], 60: [2, 26] }, { 23: [2, 31], 33: [2, 31], 54: [2, 31], 68: [2, 31], 72: [2, 31], 75: [2, 31] }, { 33: [2, 74], 42: 135, 74: 136, 75: [1, 121] }, { 33: [2, 71], 65: [2, 71], 72: [2, 71], 75: [2, 71], 80: [2, 71], 81: [2, 71], 82: [2, 71], 83: [2, 71], 84: [2, 71], 85: [2, 71] }, { 33: [2, 73], 75: [2, 73] }, { 23: [2, 29], 33: [2, 29], 54: [2, 29], 65: [2, 29], 68: [2, 29], 72: [2, 29], 75: [2, 29], 80: [2, 29], 81: [2, 29], 82: [2, 29], 83: [2, 29], 84: [2, 29], 85: [2, 29] }, { 14: [2, 15], 15: [2, 15], 19: [2, 15], 29: [2, 15], 34: [2, 15], 39: [2, 15], 44: [2, 15], 47: [2, 15], 48: [2, 15], 51: [2, 15], 55: [2, 15], 60: [2, 15] }, { 72: [1, 138], 77: [1, 137] }, { 72: [2, 100], 77: [2, 100] }, { 14: [2, 16], 15: [2, 16], 19: [2, 16], 29: [2, 16], 34: [2, 16], 44: [2, 16], 47: [2, 16], 48: [2, 16], 51: [2, 16], 55: [2, 16], 60: [2, 16] }, { 33: [1, 139] }, { 33: [2, 75] }, { 33: [2, 32] }, { 72: [2, 101], 77: [2, 101] }, { 14: [2, 17], 15: [2, 17], 19: [2, 17], 29: [2, 17], 34: [2, 17], 39: [2, 17], 44: [2, 17], 47: [2, 17], 48: [2, 17], 51: [2, 17], 55: [2, 17], 60: [2, 17] }],
+        defaultActions: { 4: [2, 1], 55: [2, 55], 57: [2, 20], 61: [2, 57], 74: [2, 81], 83: [2, 85], 87: [2, 18], 91: [2, 89], 102: [2, 53], 105: [2, 93], 111: [2, 19], 112: [2, 77], 117: [2, 97], 120: [2, 63], 123: [2, 69], 124: [2, 12], 136: [2, 75], 137: [2, 32] },
+        parseError: function parseError(str, hash) {
+            throw new Error(str);
+        },
+        parse: function parse(input) {
+            var self = this,
+                stack = [0],
+                vstack = [null],
+                lstack = [],
+                table = this.table,
+                yytext = "",
+                yylineno = 0,
+                yyleng = 0,
+                recovering = 0,
+                TERROR = 2,
+                EOF = 1;
+            this.lexer.setInput(input);
+            this.lexer.yy = this.yy;
+            this.yy.lexer = this.lexer;
+            this.yy.parser = this;
+            if (typeof this.lexer.yylloc == "undefined") this.lexer.yylloc = {};
+            var yyloc = this.lexer.yylloc;
+            lstack.push(yyloc);
+            var ranges = this.lexer.options && this.lexer.options.ranges;
+            if (typeof this.yy.parseError === "function") this.parseError = this.yy.parseError;
+            function popStack(n) {
+                stack.length = stack.length - 2 * n;
+                vstack.length = vstack.length - n;
+                lstack.length = lstack.length - n;
+            }
+            function lex() {
+                var token;
+                token = self.lexer.lex() || 1;
+                if (typeof token !== "number") {
+                    token = self.symbols_[token] || token;
+                }
+                return token;
+            }
+            var symbol,
+                preErrorSymbol,
+                state,
+                action,
+                a,
+                r,
+                yyval = {},
+                p,
+                len,
+                newState,
+                expected;
+            while (true) {
+                state = stack[stack.length - 1];
+                if (this.defaultActions[state]) {
+                    action = this.defaultActions[state];
+                } else {
+                    if (symbol === null || typeof symbol == "undefined") {
+                        symbol = lex();
+                    }
+                    action = table[state] && table[state][symbol];
+                }
+                if (typeof action === "undefined" || !action.length || !action[0]) {
+                    var errStr = "";
+                    if (!recovering) {
+                        expected = [];
+                        for (p in table[state]) if (this.terminals_[p] && p > 2) {
+                            expected.push("'" + this.terminals_[p] + "'");
+                        }
+                        if (this.lexer.showPosition) {
+                            errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'";
+                        } else {
+                            errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1 ? "end of input" : "'" + (this.terminals_[symbol] || symbol) + "'");
+                        }
+                        this.parseError(errStr, { text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected });
+                    }
+                }
+                if (action[0] instanceof Array && action.length > 1) {
+                    throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
+                }
+                switch (action[0]) {
+                    case 1:
+                        stack.push(symbol);
+                        vstack.push(this.lexer.yytext);
+                        lstack.push(this.lexer.yylloc);
+                        stack.push(action[1]);
+                        symbol = null;
+                        if (!preErrorSymbol) {
+                            yyleng = this.lexer.yyleng;
+                            yytext = this.lexer.yytext;
+                            yylineno = this.lexer.yylineno;
+                            yyloc = this.lexer.yylloc;
+                            if (recovering > 0) recovering--;
+                        } else {
+                            symbol = preErrorSymbol;
+                            preErrorSymbol = null;
+                        }
+                        break;
+                    case 2:
+                        len = this.productions_[action[1]][1];
+                        yyval.$ = vstack[vstack.length - len];
+                        yyval._$ = { first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column };
+                        if (ranges) {
+                            yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]];
+                        }
+                        r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
+                        if (typeof r !== "undefined") {
+                            return r;
+                        }
+                        if (len) {
+                            stack = stack.slice(0, -1 * len * 2);
+                            vstack = vstack.slice(0, -1 * len);
+                            lstack = lstack.slice(0, -1 * len);
+                        }
+                        stack.push(this.productions_[action[1]][0]);
+                        vstack.push(yyval.$);
+                        lstack.push(yyval._$);
+                        newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
+                        stack.push(newState);
+                        break;
+                    case 3:
+                        return true;
+                }
+            }
+            return true;
+        }
+    };
+    /* Jison generated lexer */
+    var lexer = (function () {
+        var lexer = { EOF: 1,
+            parseError: function parseError(str, hash) {
+                if (this.yy.parser) {
+                    this.yy.parser.parseError(str, hash);
+                } else {
+                    throw new Error(str);
+                }
+            },
+            setInput: function setInput(input) {
+                this._input = input;
+                this._more = this._less = this.done = false;
+                this.yylineno = this.yyleng = 0;
+                this.yytext = this.matched = this.match = '';
+                this.conditionStack = ['INITIAL'];
+                this.yylloc = { first_line: 1, first_column: 0, last_line: 1, last_column: 0 };
+                if (this.options.ranges) this.yylloc.range = [0, 0];
+                this.offset = 0;
+                return this;
+            },
+            input: function input() {
+                var ch = this._input[0];
+                this.yytext += ch;
+                this.yyleng++;
+                this.offset++;
+                this.match += ch;
+                this.matched += ch;
+                var lines = ch.match(/(?:\r\n?|\n).*/g);
+                if (lines) {
+                    this.yylineno++;
+                    this.yylloc.last_line++;
+                } else {
+                    this.yylloc.last_column++;
+                }
+                if (this.options.ranges) this.yylloc.range[1]++;
+
+                this._input = this._input.slice(1);
+                return ch;
+            },
+            unput: function unput(ch) {
+                var len = ch.length;
+                var lines = ch.split(/(?:\r\n?|\n)/g);
+
+                this._input = ch + this._input;
+                this.yytext = this.yytext.substr(0, this.yytext.length - len - 1);
+                //this.yyleng -= len;
+                this.offset -= len;
+                var oldLines = this.match.split(/(?:\r\n?|\n)/g);
+                this.match = this.match.substr(0, this.match.length - 1);
+                this.matched = this.matched.substr(0, this.matched.length - 1);
+
+                if (lines.length - 1) this.yylineno -= lines.length - 1;
+                var r = this.yylloc.range;
+
+                this.yylloc = { first_line: this.yylloc.first_line,
+                    last_line: this.yylineno + 1,
+                    first_column: this.yylloc.first_column,
+                    last_column: lines ? (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length : this.yylloc.first_column - len
+                };
+
+                if (this.options.ranges) {
+                    this.yylloc.range = [r[0], r[0] + this.yyleng - len];
+                }
+                return this;
+            },
+            more: function more() {
+                this._more = true;
+                return this;
+            },
+            less: function less(n) {
+                this.unput(this.match.slice(n));
+            },
+            pastInput: function pastInput() {
+                var past = this.matched.substr(0, this.matched.length - this.match.length);
+                return (past.length > 20 ? '...' : '') + past.substr(-20).replace(/\n/g, "");
+            },
+            upcomingInput: function upcomingInput() {
+                var next = this.match;
+                if (next.length < 20) {
+                    next += this._input.substr(0, 20 - next.length);
+                }
+                return (next.substr(0, 20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
+            },
+            showPosition: function showPosition() {
+                var pre = this.pastInput();
+                var c = new Array(pre.length + 1).join("-");
+                return pre + this.upcomingInput() + "\n" + c + "^";
+            },
+            next: function next() {
+                if (this.done) {
+                    return this.EOF;
+                }
+                if (!this._input) this.done = true;
+
+                var token, match, tempMatch, index, col, lines;
+                if (!this._more) {
+                    this.yytext = '';
+                    this.match = '';
+                }
+                var rules = this._currentRules();
+                for (var i = 0; i < rules.length; i++) {
+                    tempMatch = this._input.match(this.rules[rules[i]]);
+                    if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
+                        match = tempMatch;
+                        index = i;
+                        if (!this.options.flex) break;
+                    }
+                }
+                if (match) {
+                    lines = match[0].match(/(?:\r\n?|\n).*/g);
+                    if (lines) this.yylineno += lines.length;
+                    this.yylloc = { first_line: this.yylloc.last_line,
+                        last_line: this.yylineno + 1,
+                        first_column: this.yylloc.last_column,
+                        last_column: lines ? lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length };
+                    this.yytext += match[0];
+                    this.match += match[0];
+                    this.matches = match;
+                    this.yyleng = this.yytext.length;
+                    if (this.options.ranges) {
+                        this.yylloc.range = [this.offset, this.offset += this.yyleng];
+                    }
+                    this._more = false;
+                    this._input = this._input.slice(match[0].length);
+                    this.matched += match[0];
+                    token = this.performAction.call(this, this.yy, this, rules[index], this.conditionStack[this.conditionStack.length - 1]);
+                    if (this.done && this._input) this.done = false;
+                    if (token) return token;else return;
+                }
+                if (this._input === "") {
+                    return this.EOF;
+                } else {
+                    return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { text: "", token: null, line: this.yylineno });
+                }
+            },
+            lex: function lex() {
+                var r = this.next();
+                if (typeof r !== 'undefined') {
+                    return r;
+                } else {
+                    return this.lex();
+                }
+            },
+            begin: function begin(condition) {
+                this.conditionStack.push(condition);
+            },
+            popState: function popState() {
+                return this.conditionStack.pop();
+            },
+            _currentRules: function _currentRules() {
+                return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
+            },
+            topState: function topState() {
+                return this.conditionStack[this.conditionStack.length - 2];
+            },
+            pushState: function begin(condition) {
+                this.begin(condition);
+            } };
+        lexer.options = {};
+        lexer.performAction = function anonymous(yy, yy_, $avoiding_name_collisions, YY_START
+        /**/) {
+
+            function strip(start, end) {
+                return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng - end);
+            }
+
+            var YYSTATE = YY_START;
+            switch ($avoiding_name_collisions) {
+                case 0:
+                    if (yy_.yytext.slice(-2) === "\\\\") {
+                        strip(0, 1);
+                        this.begin("mu");
+                    } else if (yy_.yytext.slice(-1) === "\\") {
+                        strip(0, 1);
+                        this.begin("emu");
+                    } else {
+                        this.begin("mu");
+                    }
+                    if (yy_.yytext) return 15;
+
+                    break;
+                case 1:
+                    return 15;
+                    break;
+                case 2:
+                    this.popState();
+                    return 15;
+
+                    break;
+                case 3:
+                    this.begin('raw');return 15;
+                    break;
+                case 4:
+                    this.popState();
+                    // Should be using `this.topState()` below, but it currently
+                    // returns the second top instead of the first top. Opened an
+                    // issue about it at https://github.com/zaach/jison/issues/291
+                    if (this.conditionStack[this.conditionStack.length - 1] === 'raw') {
+                        return 15;
+                    } else {
+                        yy_.yytext = yy_.yytext.substr(5, yy_.yyleng - 9);
+                        return 'END_RAW_BLOCK';
+                    }
+
+                    break;
+                case 5:
+                    return 15;
+                    break;
+                case 6:
+                    this.popState();
+                    return 14;
+
+                    break;
+                case 7:
+                    return 65;
+                    break;
+                case 8:
+                    return 68;
+                    break;
+                case 9:
+                    return 19;
+                    break;
+                case 10:
+                    this.popState();
+                    this.begin('raw');
+                    return 23;
+
+                    break;
+                case 11:
+                    return 55;
+                    break;
+                case 12:
+                    return 60;
+                    break;
+                case 13:
+                    return 29;
+                    break;
+                case 14:
+                    return 47;
+                    break;
+                case 15:
+                    this.popState();return 44;
+                    break;
+                case 16:
+                    this.popState();return 44;
+                    break;
+                case 17:
+                    return 34;
+                    break;
+                case 18:
+                    return 39;
+                    break;
+                case 19:
+                    return 51;
+                    break;
+                case 20:
+                    return 48;
+                    break;
+                case 21:
+                    this.unput(yy_.yytext);
+                    this.popState();
+                    this.begin('com');
+
+                    break;
+                case 22:
+                    this.popState();
+                    return 14;
+
+                    break;
+                case 23:
+                    return 48;
+                    break;
+                case 24:
+                    return 73;
+                    break;
+                case 25:
+                    return 72;
+                    break;
+                case 26:
+                    return 72;
+                    break;
+                case 27:
+                    return 87;
+                    break;
+                case 28:
+                    // ignore whitespace
+                    break;
+                case 29:
+                    this.popState();return 54;
+                    break;
+                case 30:
+                    this.popState();return 33;
+                    break;
+                case 31:
+                    yy_.yytext = strip(1, 2).replace(/\\"/g, '"');return 80;
+                    break;
+                case 32:
+                    yy_.yytext = strip(1, 2).replace(/\\'/g, "'");return 80;
+                    break;
+                case 33:
+                    return 85;
+                    break;
+                case 34:
+                    return 82;
+                    break;
+                case 35:
+                    return 82;
+                    break;
+                case 36:
+                    return 83;
+                    break;
+                case 37:
+                    return 84;
+                    break;
+                case 38:
+                    return 81;
+                    break;
+                case 39:
+                    return 75;
+                    break;
+                case 40:
+                    return 77;
+                    break;
+                case 41:
+                    return 72;
+                    break;
+                case 42:
+                    yy_.yytext = yy_.yytext.replace(/\\([\\\]])/g, '$1');return 72;
+                    break;
+                case 43:
+                    return 'INVALID';
+                    break;
+                case 44:
+                    return 5;
+                    break;
+            }
+        };
+        lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/, /^(?:[^\x00]+)/, /^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/, /^(?:\{\{\{\{(?=[^\/]))/, /^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/, /^(?:[^\x00]*?(?=(\{\{\{\{)))/, /^(?:[\s\S]*?--(~)?\}\})/, /^(?:\()/, /^(?:\))/, /^(?:\{\{\{\{)/, /^(?:\}\}\}\})/, /^(?:\{\{(~)?>)/, /^(?:\{\{(~)?#>)/, /^(?:\{\{(~)?#\*?)/, /^(?:\{\{(~)?\/)/, /^(?:\{\{(~)?\^\s*(~)?\}\})/, /^(?:\{\{(~)?\s*else\s*(~)?\}\})/, /^(?:\{\{(~)?\^)/, /^(?:\{\{(~)?\s*else\b)/, /^(?:\{\{(~)?\{)/, /^(?:\{\{(~)?&)/, /^(?:\{\{(~)?!--)/, /^(?:\{\{(~)?![\s\S]*?\}\})/, /^(?:\{\{(~)?\*?)/, /^(?:=)/, /^(?:\.\.)/, /^(?:\.(?=([=~}\s\/.)|])))/, /^(?:[\/.])/, /^(?:\s+)/, /^(?:\}(~)?\}\})/, /^(?:(~)?\}\})/, /^(?:"(\\["]|[^"])*")/, /^(?:'(\\[']|[^'])*')/, /^(?:@)/, /^(?:true(?=([~}\s)])))/, /^(?:false(?=([~}\s)])))/, /^(?:undefined(?=([~}\s)])))/, /^(?:null(?=([~}\s)])))/, /^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/, /^(?:as\s+\|)/, /^(?:\|)/, /^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)|]))))/, /^(?:\[(\\\]|[^\]])*\])/, /^(?:.)/, /^(?:$)/];
+        lexer.conditions = { "mu": { "rules": [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44], "inclusive": false }, "emu": { "rules": [2], "inclusive": false }, "com": { "rules": [6], "inclusive": false }, "raw": { "rules": [3, 4, 5], "inclusive": false }, "INITIAL": { "rules": [0, 1, 44], "inclusive": true } };
+        return lexer;
+    })();
+    parser.lexer = lexer;
+    function Parser() {
+        this.yy = {};
+    }Parser.prototype = parser;parser.Parser = Parser;
+    return new Parser();
+})();exports.__esModule = true;
+exports['default'] = handlebars;
+
+
+},{}],257:[function(require,module,exports){
+/* eslint-disable new-cap */
+'use strict';
+
+exports.__esModule = true;
+exports.print = print;
+exports.PrintVisitor = PrintVisitor;
+// istanbul ignore next
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _visitor = require('./visitor');
+
+var _visitor2 = _interopRequireDefault(_visitor);
+
+function print(ast) {
+  return new PrintVisitor().accept(ast);
+}
+
+function PrintVisitor() {
+  this.padding = 0;
+}
+
+PrintVisitor.prototype = new _visitor2['default']();
+
+PrintVisitor.prototype.pad = function (string) {
+  var out = '';
+
+  for (var i = 0, l = this.padding; i < l; i++) {
+    out += '  ';
+  }
+
+  out += string + '\n';
+  return out;
+};
+
+PrintVisitor.prototype.Program = function (program) {
+  var out = '',
+      body = program.body,
+      i = undefined,
+      l = undefined;
+
+  if (program.blockParams) {
+    var blockParams = 'BLOCK PARAMS: [';
+    for (i = 0, l = program.blockParams.length; i < l; i++) {
+      blockParams += ' ' + program.blockParams[i];
+    }
+    blockParams += ' ]';
+    out += this.pad(blockParams);
+  }
+
+  for (i = 0, l = body.length; i < l; i++) {
+    out += this.accept(body[i]);
+  }
+
+  this.padding--;
+
+  return out;
+};
+
+PrintVisitor.prototype.MustacheStatement = function (mustache) {
+  return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
+};
+PrintVisitor.prototype.Decorator = function (mustache) {
+  return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
+};
+
+PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function (block) {
+  var out = '';
+
+  out += this.pad((block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:');
+  this.padding++;
+  out += this.pad(this.SubExpression(block));
+  if (block.program) {
+    out += this.pad('PROGRAM:');
+    this.padding++;
+    out += this.accept(block.program);
+    this.padding--;
+  }
+  if (block.inverse) {
+    if (block.program) {
+      this.padding++;
+    }
+    out += this.pad('{{^}}');
+    this.padding++;
+    out += this.accept(block.inverse);
+    this.padding--;
+    if (block.program) {
+      this.padding--;
+    }
+  }
+  this.padding--;
+
+  return out;
+};
+
+PrintVisitor.prototype.PartialStatement = function (partial) {
+  var content = 'PARTIAL:' + partial.name.original;
+  if (partial.params[0]) {
+    content += ' ' + this.accept(partial.params[0]);
+  }
+  if (partial.hash) {
+    content += ' ' + this.accept(partial.hash);
+  }
+  return this.pad('{{> ' + content + ' }}');
+};
+PrintVisitor.prototype.PartialBlockStatement = function (partial) {
+  var content = 'PARTIAL BLOCK:' + partial.name.original;
+  if (partial.params[0]) {
+    content += ' ' + this.accept(partial.params[0]);
+  }
+  if (partial.hash) {
+    content += ' ' + this.accept(partial.hash);
+  }
+
+  content += ' ' + this.pad('PROGRAM:');
+  this.padding++;
+  content += this.accept(partial.program);
+  this.padding--;
+
+  return this.pad('{{> ' + content + ' }}');
+};
+
+PrintVisitor.prototype.ContentStatement = function (content) {
+  return this.pad("CONTENT[ '" + content.value + "' ]");
+};
+
+PrintVisitor.prototype.CommentStatement = function (comment) {
+  return this.pad("{{! '" + comment.value + "' }}");
+};
+
+PrintVisitor.prototype.SubExpression = function (sexpr) {
+  var params = sexpr.params,
+      paramStrings = [],
+      hash = undefined;
+
+  for (var i = 0, l = params.length; i < l; i++) {
+    paramStrings.push(this.accept(params[i]));
+  }
+
+  params = '[' + paramStrings.join(', ') + ']';
+
+  hash = sexpr.hash ? ' ' + this.accept(sexpr.hash) : '';
+
+  return this.accept(sexpr.path) + ' ' + params + hash;
+};
+
+PrintVisitor.prototype.PathExpression = function (id) {
+  var path = id.parts.join('/');
+  return (id.data ? '@' : '') + 'PATH:' + path;
+};
+
+PrintVisitor.prototype.StringLiteral = function (string) {
+  return '"' + string.value + '"';
+};
+
+PrintVisitor.prototype.NumberLiteral = function (number) {
+  return 'NUMBER{' + number.value + '}';
+};
+
+PrintVisitor.prototype.BooleanLiteral = function (bool) {
+  return 'BOOLEAN{' + bool.value + '}';
+};
+
+PrintVisitor.prototype.UndefinedLiteral = function () {
+  return 'UNDEFINED';
+};
+
+PrintVisitor.prototype.NullLiteral = function () {
+  return 'NULL';
+};
+
+PrintVisitor.prototype.Hash = function (hash) {
+  var pairs = hash.pairs,
+      joinedPairs = [];
+
+  for (var i = 0, l = pairs.length; i < l; i++) {
+    joinedPairs.push(this.accept(pairs[i]));
+  }
+
+  return 'HASH{' + joinedPairs.join(', ') + '}';
+};
+PrintVisitor.prototype.HashPair = function (pair) {
+  return pair.key + '=' + this.accept(pair.value);
+};
+/* eslint-enable new-cap */
+
+
+},{"./visitor":258}],258:[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);
+
+function Visitor() {
+  this.parents = [];
+}
+
+Visitor.prototype = {
+  constructor: Visitor,
+  mutating: false,
+
+  // Visits a given value. If mutating, will replace the value if necessary.
+  acceptKey: function acceptKey(node, name) {
+    var value = this.accept(node[name]);
+    if (this.mutating) {
+      // Hacky sanity check: This may have a few false positives for type for the helper
+      // methods but will generally do the right thing without a lot of overhead.
+      if (value && !Visitor.prototype[value.type]) {
+        throw new _exception2['default']('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type);
+      }
+      node[name] = value;
+    }
+  },
+
+  // Performs an accept operation with added sanity check to ensure
+  // required keys are not removed.
+  acceptRequired: function acceptRequired(node, name) {
+    this.acceptKey(node, name);
+
+    if (!node[name]) {
+      throw new _exception2['default'](node.type + ' requires ' + name);
+    }
+  },
+
+  // Traverses a given array. If mutating, empty respnses will be removed
+  // for child elements.
+  acceptArray: function acceptArray(array) {
+    for (var i = 0, l = array.length; i < l; i++) {
+      this.acceptKey(array, i);
+
+      if (!array[i]) {
+        array.splice(i, 1);
+        i--;
+        l--;
+      }
+    }
+  },
+
+  accept: function accept(object) {
+    if (!object) {
+      return;
+    }
+
+    /* istanbul ignore next: Sanity code */
+    if (!this[object.type]) {
+      throw new _exception2['default']('Unknown type: ' + object.type, object);
+    }
+
+    if (this.current) {
+      this.parents.unshift(this.current);
+    }
+    this.current = object;
+
+    var ret = this[object.type](object);
+
+    this.current = this.parents.shift();
+
+    if (!this.mutating || ret) {
+      return ret;
+    } else if (ret !== false) {
+      return object;
+    }
+  },
+
+  Program: function Program(program) {
+    this.acceptArray(program.body);
+  },
+
+  MustacheStatement: visitSubExpression,
+  Decorator: visitSubExpression,
+
+  BlockStatement: visitBlock,
+  DecoratorBlock: visitBlock,
+
+  PartialStatement: visitPartial,
+  PartialBlockStatement: function PartialBlockStatement(partial) {
+    visitPartial.call(this, partial);
+
+    this.acceptKey(partial, 'program');
+  },
+
+  ContentStatement: function ContentStatement() /* content */{},
+  CommentStatement: function CommentStatement() /* comment */{},
+
+  SubExpression: visitSubExpression,
+
+  PathExpression: function PathExpression() /* path */{},
+
+  StringLiteral: function StringLiteral() /* string */{},
+  NumberLiteral: function NumberLiteral() /* number */{},
+  BooleanLiteral: function BooleanLiteral() /* bool */{},
+  UndefinedLiteral: function UndefinedLiteral() /* literal */{},
+  NullLiteral: function NullLiteral() /* literal */{},
+
+  Hash: function Hash(hash) {
+    this.acceptArray(hash.pairs);
+  },
+  HashPair: function HashPair(pair) {
+    this.acceptRequired(pair, 'value');
+  }
+};
+
+function visitSubExpression(mustache) {
+  this.acceptRequired(mustache, 'path');
+  this.acceptArray(mustache.params);
+  this.acceptKey(mustache, 'hash');
+}
+function visitBlock(block) {
+  visitSubExpression.call(this, block);
+
+  this.acceptKey(block, 'program');
+  this.acceptKey(block, 'inverse');
+}
+function visitPartial(partial) {
+  this.acceptRequired(partial, 'name');
+  this.acceptArray(partial.params);
+  this.acceptKey(partial, 'hash');
+}
+
+exports['default'] = Visitor;
+module.exports = exports['default'];
+
+
+},{"../exception":262}],259:[function(require,module,exports){
+'use strict';
+
+exports.__esModule = true;
+// istanbul ignore next
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _visitor = require('./visitor');
+
+var _visitor2 = _interopRequireDefault(_visitor);
+
+function WhitespaceControl() {
+  var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+  this.options = options;
+}
+WhitespaceControl.prototype = new _visitor2['default']();
+
+WhitespaceControl.prototype.Program = function (program) {
+  var doStandalone = !this.options.ignoreStandalone;
+
+  var isRoot = !this.isRootSeen;
+  this.isRootSeen = true;
+
+  var body = program.body;
+  for (var i = 0, l = body.length; i < l; i++) {
+    var current = body[i],
+        strip = this.accept(current);
+
+    if (!strip) {
+      continue;
+    }
+
+    var _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
+        _isNextWhitespace = isNextWhitespace(body, i, isRoot),
+        openStandalone = strip.openStandalone && _isPrevWhitespace,
+        closeStandalone = strip.closeStandalone && _isNextWhitespace,
+        inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
+
+    if (strip.close) {
+      omitRight(body, i, true);
+    }
+    if (strip.open) {
+      omitLeft(body, i, true);
+    }
+
+    if (doStandalone && inlineStandalone) {
+      omitRight(body, i);
+
+      if (omitLeft(body, i)) {
+        // If we are on a standalone node, save the indent info for partials
+        if (current.type === 'PartialStatement') {
+          // Pull out the whitespace from the final line
+          current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1];
+        }
+      }
+    }
+    if (doStandalone && openStandalone) {
+      omitRight((current.program || current.inverse).body);
+
+      // Strip out the previous content node if it's whitespace only
+      omitLeft(body, i);
+    }
+    if (doStandalone && closeStandalone) {
+      // Always strip the next node
+      omitRight(body, i);
+
+      omitLeft((current.inverse || current.program).body);
+    }
+  }
+
+  return program;
+};
+
+WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function (block) {
+  this.accept(block.program);
+  this.accept(block.inverse);
+
+  // Find the inverse program that is involed with whitespace stripping.
+  var program = block.program || block.inverse,
+      inverse = block.program && block.inverse,
+      firstInverse = inverse,
+      lastInverse = inverse;
+
+  if (inverse && inverse.chained) {
+    firstInverse = inverse.body[0].program;
+
+    // Walk the inverse chain to find the last inverse that is actually in the chain.
+    while (lastInverse.chained) {
+      lastInverse = lastInverse.body[lastInverse.body.length - 1].program;
+    }
+  }
+
+  var strip = {
+    open: block.openStrip.open,
+    close: block.closeStrip.close,
+
+    // Determine the standalone candiacy. Basically flag our content as being possibly standalone
+    // so our parent can determine if we actually are standalone
+    openStandalone: isNextWhitespace(program.body),
+    closeStandalone: isPrevWhitespace((firstInverse || program).body)
+  };
+
+  if (block.openStrip.close) {
+    omitRight(program.body, null, true);
+  }
+
+  if (inverse) {
+    var inverseStrip = block.inverseStrip;
+
+    if (inverseStrip.open) {
+      omitLeft(program.body, null, true);
+    }
+
+    if (inverseStrip.close) {
+      omitRight(firstInverse.body, null, true);
+    }
+    if (block.closeStrip.open) {
+      omitLeft(lastInverse.body, null, true);
+    }
+
+    // Find standalone else statments
+    if (!this.options.ignoreStandalone && isPrevWhitespace(program.body) && isNextWhitespace(firstInverse.body)) {
+      omitLeft(program.body);
+      omitRight(firstInverse.body);
+    }
+  } else if (block.closeStrip.open) {
+    omitLeft(program.body, null, true);
+  }
+
+  return strip;
+};
+
+WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function (mustache) {
+  return mustache.strip;
+};
+
+WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function (node) {
+  /* istanbul ignore next */
+  var strip = node.strip || {};
+  return {
+    inlineStandalone: true,
+    open: strip.open,
+    close: strip.close
+  };
+};
+
+function isPrevWhitespace(body, i, isRoot) {
+  if (i === undefined) {
+    i = body.length;
+  }
+
+  // Nodes that end with newlines are considered whitespace (but are special
+  // cased for strip operations)
+  var prev = body[i - 1],
+      sibling = body[i - 2];
+  if (!prev) {
+    return isRoot;
+  }
+
+  if (prev.type === 'ContentStatement') {
+    return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(prev.original);
+  }
+}
+function isNextWhitespace(body, i, isRoot) {
+  if (i === undefined) {
+    i = -1;
+  }
+
+  var next = body[i + 1],
+      sibling = body[i + 2];
+  if (!next) {
+    return isRoot;
+  }
+
+  if (next.type === 'ContentStatement') {
+    return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(next.original);
+  }
+}
+
+// Marks the node to the right of the position as omitted.
+// I.e. {{foo}}' ' will mark the ' ' node as omitted.
+//
+// If i is undefined, then the first child will be marked as such.
+//
+// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
+// content is met.
+function omitRight(body, i, multiple) {
+  var current = body[i == null ? 0 : i + 1];
+  if (!current || current.type !== 'ContentStatement' || !multiple && current.rightStripped) {
+    return;
+  }
+
+  var original = current.value;
+  current.value = current.value.replace(multiple ? /^\s+/ : /^[ \t]*\r?\n?/, '');
+  current.rightStripped = current.value !== original;
+}
+
+// Marks the node to the left of the position as omitted.
+// I.e. ' '{{foo}} will mark the ' ' node as omitted.
+//
+// If i is undefined then the last child will be marked as such.
+//
+// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
+// content is met.
+function omitLeft(body, i, multiple) {
+  var current = body[i == null ? body.length - 1 : i - 1];
+  if (!current || current.type !== 'ContentStatement' || !multiple && current.leftStripped) {
+    return;
+  }
+
+  // We omit the last node if it's whitespace only and not preceeded by a non-content node.
+  var original = current.value;
+  current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, '');
+  current.leftStripped = current.value !== original;
+  return current.leftStripped;
+}
+
+exports['default'] = WhitespaceControl;
+module.exports = exports['default'];
+
+
+},{"./visitor":258}],260:[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');
+
+var _decoratorsInline2 = _interopRequireDefault(_decoratorsInline);
+
+function registerDefaultDecorators(instance) {
+  _decoratorsInline2['default'](instance);
+}
+
+
+},{"./decorators/inline":261}],261:[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":275}],262:[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'];
+
+
+},{}],263:[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 }; }
+
+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":264,"./helpers/each":265,"./helpers/helper-missing":266,"./helpers/if":267,"./helpers/log":268,"./helpers/lookup":269,"./helpers/with":270}],264:[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":275}],265:[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":262,"../utils":275}],266:[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":262}],267:[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);
+    }
+
+    // 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":275}],268:[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'];
+
+
+},{}],269:[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'];
+
+
+},{}],270:[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":275}],271:[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":275}],272:[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 : {})
+},{}],273:[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":249,"./exception":262,"./utils":275}],274:[function(require,module,exports){
+// Build out our basic SafeString type
+'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'];
+
+
+},{}],275:[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;
+}
+
+
+},{}],276:[function(require,module,exports){
+// USAGE:
+// var handlebars = require('handlebars');
+/* eslint-disable no-var */
+
+// var local = handlebars.create();
+
+var handlebars = require('../dist/cjs/handlebars')['default'];
+
+var printer = require('../dist/cjs/handlebars/compiler/printer');
+handlebars.PrintVisitor = printer.PrintVisitor;
+handlebars.print = printer.print;
+
+module.exports = handlebars;
+
+// Publish a Node.js require() handler for .handlebars and .hbs files
+function extension(module, filename) {
+  
+  var templateString = fs.readFileSync(filename, 'utf8');
+  module.exports = handlebars.compile(templateString);
+}
+/* istanbul ignore else */
+if (typeof require !== 'undefined' && require.extensions) {
+  require.extensions['.handlebars'] = extension;
+  require.extensions['.hbs'] = extension;
+}
+
+},{"../dist/cjs/handlebars":247,"../dist/cjs/handlebars/compiler/printer":257}],277:[function(require,module,exports){
 /**
  * @preserve
  * Copyright 2015 Igor Bezkrovny
@@ -66918,7 +47237,7 @@
 })(ImageSSIM || (ImageSSIM = {}));
 module.exports = ImageSSIM;
 
-},{}],248:[function(require,module,exports){
+},{}],278:[function(require,module,exports){
 var encode = require('./lib/encoder'),
     decode = require('./lib/decoder');
 
@@ -66927,7 +47246,7 @@
   decode: decode
 };
 
-},{"./lib/decoder":249,"./lib/encoder":250}],249:[function(require,module,exports){
+},{"./lib/decoder":279,"./lib/encoder":280}],279:[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: */
@@ -67917,7 +48236,7 @@
 }
 
 }).call(this,require("buffer").Buffer)
-},{"buffer":194}],250:[function(require,module,exports){
+},{"buffer":200}],280:[function(require,module,exports){
 (function (Buffer){
 /*
   Copyright (c) 2008, Adobe Systems Incorporated
@@ -68687,24 +49006,1297 @@
 }
 
 }).call(this,require("buffer").Buffer)
-},{"buffer":194}],251:[function(require,module,exports){
-(function (global,Buffer){
-/*!
+},{"buffer":200}],281:[function(require,module,exports){
+(function (global){
+/**
+ * marked - a markdown parser
+ * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
+ * https://github.com/chjj/marked
+ */
 
-JSZip - A Javascript class for generating and reading zip files
-<http://stuartk.com/jszip>
+;(function() {
 
-(c) 2009-2014 Stuart Knightley <stuart [at] stuartk.com>
-Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown.
+/**
+ * Block-Level Grammar
+ */
 
-JSZip uses the library pako released under the MIT license :
-https://github.com/nodeca/pako/blob/master/LICENSE
-*/
-!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.JSZip=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){"use strict";function d(a){if(a){this.data=a,this.length=this.data.length,this.index=0,this.zero=0;for(var b=0;b<this.data.length;b++)a[b]=255&a[b]}}var e=a("./dataReader");d.prototype=new e,d.prototype.byteAt=function(a){return this.data[this.zero+a]},d.prototype.lastIndexOfSignature=function(a){for(var b=a.charCodeAt(0),c=a.charCodeAt(1),d=a.charCodeAt(2),e=a.charCodeAt(3),f=this.length-4;f>=0;--f)if(this.data[f]===b&&this.data[f+1]===c&&this.data[f+2]===d&&this.data[f+3]===e)return f-this.zero;return-1},d.prototype.readData=function(a){if(this.checkOffset(a),0===a)return[];var b=this.data.slice(this.zero+this.index,this.zero+this.index+a);return this.index+=a,b},b.exports=d},{"./dataReader":6}],2:[function(a,b,c){"use strict";var d="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";c.encode=function(a,b){for(var c,e,f,g,h,i,j,k="",l=0;l<a.length;)c=a.charCodeAt(l++),e=a.charCodeAt(l++),f=a.charCodeAt(l++),g=c>>2,h=(3&c)<<4|e>>4,i=(15&e)<<2|f>>6,j=63&f,isNaN(e)?i=j=64:isNaN(f)&&(j=64),k=k+d.charAt(g)+d.charAt(h)+d.charAt(i)+d.charAt(j);return k},c.decode=function(a,b){var c,e,f,g,h,i,j,k="",l=0;for(a=a.replace(/[^A-Za-z0-9\+\/\=]/g,"");l<a.length;)g=d.indexOf(a.charAt(l++)),h=d.indexOf(a.charAt(l++)),i=d.indexOf(a.charAt(l++)),j=d.indexOf(a.charAt(l++)),c=g<<2|h>>4,e=(15&h)<<4|i>>2,f=(3&i)<<6|j,k+=String.fromCharCode(c),64!=i&&(k+=String.fromCharCode(e)),64!=j&&(k+=String.fromCharCode(f));return k}},{}],3:[function(a,b,c){"use strict";function d(){this.compressedSize=0,this.uncompressedSize=0,this.crc32=0,this.compressionMethod=null,this.compressedContent=null}d.prototype={getContent:function(){return null},getCompressedContent:function(){return null}},b.exports=d},{}],4:[function(a,b,c){"use strict";c.STORE={magic:"\x00\x00",compress:function(a,b){return a},uncompress:function(a){return a},compressInputType:null,uncompressInputType:null},c.DEFLATE=a("./flate")},{"./flate":9}],5:[function(a,b,c){"use strict";var d=a("./utils"),e=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,936918e3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117];b.exports=function(a,b){if("undefined"==typeof a||!a.length)return 0;var c="string"!==d.getTypeOf(a);"undefined"==typeof b&&(b=0);var f=0,g=0,h=0;b=-1^b;for(var i=0,j=a.length;j>i;i++)h=c?a[i]:a.charCodeAt(i),g=255&(b^h),f=e[g],b=b>>>8^f;return-1^b}},{"./utils":22}],6:[function(a,b,c){"use strict";function d(a){this.data=null,this.length=0,this.index=0,this.zero=0}var e=a("./utils");d.prototype={checkOffset:function(a){this.checkIndex(this.index+a)},checkIndex:function(a){if(this.length<this.zero+a||0>a)throw new Error("End of data reached (data length = "+this.length+", asked index = "+a+"). Corrupted zip ?")},setIndex:function(a){this.checkIndex(a),this.index=a},skip:function(a){this.setIndex(this.index+a)},byteAt:function(a){},readInt:function(a){var b,c=0;for(this.checkOffset(a),b=this.index+a-1;b>=this.index;b--)c=(c<<8)+this.byteAt(b);return this.index+=a,c},readString:function(a){return e.transformTo("string",this.readData(a))},readData:function(a){},lastIndexOfSignature:function(a){},readDate:function(){var a=this.readInt(4);return new Date((a>>25&127)+1980,(a>>21&15)-1,a>>16&31,a>>11&31,a>>5&63,(31&a)<<1)}},b.exports=d},{"./utils":22}],7:[function(a,b,c){"use strict";c.base64=!1,c.binary=!1,c.dir=!1,c.createFolders=!1,c.date=null,c.compression=null,c.compressionOptions=null,c.comment=null,c.unixPermissions=null,c.dosPermissions=null},{}],8:[function(a,b,c){"use strict";var d=a("./utils");c.string2binary=function(a){return d.string2binary(a)},c.string2Uint8Array=function(a){return d.transformTo("uint8array",a)},c.uint8Array2String=function(a){return d.transformTo("string",a)},c.string2Blob=function(a){var b=d.transformTo("arraybuffer",a);return d.arrayBuffer2Blob(b)},c.arrayBuffer2Blob=function(a){return d.arrayBuffer2Blob(a)},c.transformTo=function(a,b){return d.transformTo(a,b)},c.getTypeOf=function(a){return d.getTypeOf(a)},c.checkSupport=function(a){return d.checkSupport(a)},c.MAX_VALUE_16BITS=d.MAX_VALUE_16BITS,c.MAX_VALUE_32BITS=d.MAX_VALUE_32BITS,c.pretty=function(a){return d.pretty(a)},c.findCompression=function(a){return d.findCompression(a)},c.isRegExp=function(a){return d.isRegExp(a)}},{"./utils":22}],9:[function(a,b,c){"use strict";var d="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,e=a("pako");c.uncompressInputType=d?"uint8array":"array",c.compressInputType=d?"uint8array":"array",c.magic="\b\x00",c.compress=function(a,b){return e.deflateRaw(a,{level:b.level||-1})},c.uncompress=function(a){return e.inflateRaw(a)}},{pako:25}],10:[function(a,b,c){"use strict";function d(a,b){return this instanceof d?(this.files={},this.comment=null,this.root="",a&&this.load(a,b),void(this.clone=function(){var a=new d;for(var b in this)"function"!=typeof this[b]&&(a[b]=this[b]);return a})):new d(a,b)}var e=a("./base64");d.prototype=a("./object"),d.prototype.load=a("./load"),d.support=a("./support"),d.defaults=a("./defaults"),d.utils=a("./deprecatedPublicUtils"),d.base64={encode:function(a){return e.encode(a)},decode:function(a){return e.decode(a)}},d.compressions=a("./compressions"),b.exports=d},{"./base64":2,"./compressions":4,"./defaults":7,"./deprecatedPublicUtils":8,"./load":11,"./object":14,"./support":18}],11:[function(a,b,c){"use strict";var d=a("./base64"),e=a("./utf8"),f=a("./utils"),g=a("./zipEntries");b.exports=function(a,b){var c,h,i,j;for(b=f.extend(b||{},{base64:!1,checkCRC32:!1,optimizedBinaryString:!1,createFolders:!1,decodeFileName:e.utf8decode}),b.base64&&(a=d.decode(a)),h=new g(a,b),c=h.files,i=0;i<c.length;i++)j=c[i],this.file(j.fileNameStr,j.decompressed,{binary:!0,optimizedBinaryString:!0,date:j.date,dir:j.dir,comment:j.fileCommentStr.length?j.fileCommentStr:null,unixPermissions:j.unixPermissions,dosPermissions:j.dosPermissions,createFolders:b.createFolders});return h.zipComment.length&&(this.comment=h.zipComment),this}},{"./base64":2,"./utf8":21,"./utils":22,"./zipEntries":23}],12:[function(a,b,c){(function(a){"use strict";b.exports=function(b,c){return new a(b,c)},b.exports.test=function(b){return a.isBuffer(b)}}).call(this,"undefined"!=typeof Buffer?Buffer:void 0)},{}],13:[function(a,b,c){"use strict";function d(a){this.data=a,this.length=this.data.length,this.index=0,this.zero=0}var e=a("./uint8ArrayReader");d.prototype=new e,d.prototype.readData=function(a){this.checkOffset(a);var b=this.data.slice(this.zero+this.index,this.zero+this.index+a);return this.index+=a,b},b.exports=d},{"./uint8ArrayReader":19}],14:[function(a,b,c){"use strict";var d=a("./support"),e=a("./utils"),f=a("./crc32"),g=a("./signature"),h=a("./defaults"),i=a("./base64"),j=a("./compressions"),k=a("./compressedObject"),l=a("./nodeBuffer"),m=a("./utf8"),n=a("./stringWriter"),o=a("./uint8ArrayWriter"),p=function(a){if(a._data instanceof k&&(a._data=a._data.getContent(),a.options.binary=!0,a.options.base64=!1,"uint8array"===e.getTypeOf(a._data))){var b=a._data;a._data=new Uint8Array(b.length),0!==b.length&&a._data.set(b,0)}return a._data},q=function(a){var b=p(a),c=e.getTypeOf(b);return"string"===c?!a.options.binary&&d.nodebuffer?l(b,"utf-8"):a.asBinary():b},r=function(a){var b=p(this);return null===b||"undefined"==typeof b?"":(this.options.base64&&(b=i.decode(b)),b=a&&this.options.binary?D.utf8decode(b):e.transformTo("string",b),a||this.options.binary||(b=e.transformTo("string",D.utf8encode(b))),b)},s=function(a,b,c){this.name=a,this.dir=c.dir,this.date=c.date,this.comment=c.comment,this.unixPermissions=c.unixPermissions,this.dosPermissions=c.dosPermissions,this._data=b,this.options=c,this._initialMetadata={dir:c.dir,date:c.date}};s.prototype={asText:function(){return r.call(this,!0)},asBinary:function(){return r.call(this,!1)},asNodeBuffer:function(){var a=q(this);return e.transformTo("nodebuffer",a)},asUint8Array:function(){var a=q(this);return e.transformTo("uint8array",a)},asArrayBuffer:function(){return this.asUint8Array().buffer}};var t=function(a,b){var c,d="";for(c=0;b>c;c++)d+=String.fromCharCode(255&a),a>>>=8;return d},u=function(a){return a=a||{},a.base64!==!0||null!==a.binary&&void 0!==a.binary||(a.binary=!0),a=e.extend(a,h),a.date=a.date||new Date,null!==a.compression&&(a.compression=a.compression.toUpperCase()),a},v=function(a,b,c){var d,f=e.getTypeOf(b);if(c=u(c),"string"==typeof c.unixPermissions&&(c.unixPermissions=parseInt(c.unixPermissions,8)),c.unixPermissions&&16384&c.unixPermissions&&(c.dir=!0),c.dosPermissions&&16&c.dosPermissions&&(c.dir=!0),c.dir&&(a=x(a)),c.createFolders&&(d=w(a))&&y.call(this,d,!0),c.dir||null===b||"undefined"==typeof b)c.base64=!1,c.binary=!1,b=null,f=null;else if("string"===f)c.binary&&!c.base64&&c.optimizedBinaryString!==!0&&(b=e.string2binary(b));else{if(c.base64=!1,c.binary=!0,!(f||b instanceof k))throw new Error("The data of '"+a+"' is in an unsupported format !");"arraybuffer"===f&&(b=e.transformTo("uint8array",b))}var g=new s(a,b,c);return this.files[a]=g,g},w=function(a){"/"==a.slice(-1)&&(a=a.substring(0,a.length-1));var b=a.lastIndexOf("/");return b>0?a.substring(0,b):""},x=function(a){return"/"!=a.slice(-1)&&(a+="/"),a},y=function(a,b){return b="undefined"!=typeof b?b:!1,a=x(a),this.files[a]||v.call(this,a,null,{dir:!0,createFolders:b}),this.files[a]},z=function(a,b,c){var d,g=new k;return a._data instanceof k?(g.uncompressedSize=a._data.uncompressedSize,g.crc32=a._data.crc32,0===g.uncompressedSize||a.dir?(b=j.STORE,g.compressedContent="",g.crc32=0):a._data.compressionMethod===b.magic?g.compressedContent=a._data.getCompressedContent():(d=a._data.getContent(),g.compressedContent=b.compress(e.transformTo(b.compressInputType,d),c))):(d=q(a),d&&0!==d.length&&!a.dir||(b=j.STORE,d=""),g.uncompressedSize=d.length,g.crc32=f(d),g.compressedContent=b.compress(e.transformTo(b.compressInputType,d),c)),g.compressedSize=g.compressedContent.length,g.compressionMethod=b.magic,g},A=function(a,b){var c=a;return a||(c=b?16893:33204),(65535&c)<<16},B=function(a,b){return 63&(a||0)},C=function(a,b,c,d,h,i){var j,k,l,n,o=(c.compressedContent,i!==m.utf8encode),p=e.transformTo("string",i(b.name)),q=e.transformTo("string",m.utf8encode(b.name)),r=b.comment||"",s=e.transformTo("string",i(r)),u=e.transformTo("string",m.utf8encode(r)),v=q.length!==b.name.length,w=u.length!==r.length,x=b.options,y="",z="",C="";l=b._initialMetadata.dir!==b.dir?b.dir:x.dir,n=b._initialMetadata.date!==b.date?b.date:x.date;var D=0,E=0;l&&(D|=16),"UNIX"===h?(E=798,D|=A(b.unixPermissions,l)):(E=20,D|=B(b.dosPermissions,l)),j=n.getHours(),j<<=6,j|=n.getMinutes(),j<<=5,j|=n.getSeconds()/2,k=n.getFullYear()-1980,k<<=4,k|=n.getMonth()+1,k<<=5,k|=n.getDate(),v&&(z=t(1,1)+t(f(p),4)+q,y+="up"+t(z.length,2)+z),w&&(C=t(1,1)+t(this.crc32(s),4)+u,y+="uc"+t(C.length,2)+C);var F="";F+="\n\x00",F+=o||!v&&!w?"\x00\x00":"\x00\b",F+=c.compressionMethod,F+=t(j,2),F+=t(k,2),F+=t(c.crc32,4),F+=t(c.compressedSize,4),F+=t(c.uncompressedSize,4),F+=t(p.length,2),F+=t(y.length,2);var G=g.LOCAL_FILE_HEADER+F+p+y,H=g.CENTRAL_FILE_HEADER+t(E,2)+F+t(s.length,2)+"\x00\x00\x00\x00"+t(D,4)+t(d,4)+p+y+s;return{fileRecord:G,dirRecord:H,compressedObject:c}},D={load:function(a,b){throw new Error("Load method is not defined. Is the file jszip-load.js included ?")},filter:function(a){var b,c,d,f,g=[];for(b in this.files)this.files.hasOwnProperty(b)&&(d=this.files[b],f=new s(d.name,d._data,e.extend(d.options)),c=b.slice(this.root.length,b.length),b.slice(0,this.root.length)===this.root&&a(c,f)&&g.push(f));return g},file:function(a,b,c){if(1===arguments.length){if(e.isRegExp(a)){var d=a;return this.filter(function(a,b){return!b.dir&&d.test(a)})}return this.filter(function(b,c){return!c.dir&&b===a})[0]||null}return a=this.root+a,v.call(this,a,b,c),this},folder:function(a){if(!a)return this;if(e.isRegExp(a))return this.filter(function(b,c){return c.dir&&a.test(b)});var b=this.root+a,c=y.call(this,b),d=this.clone();return d.root=c.name,d},remove:function(a){a=this.root+a;var b=this.files[a];if(b||("/"!=a.slice(-1)&&(a+="/"),b=this.files[a]),b&&!b.dir)delete this.files[a];else for(var c=this.filter(function(b,c){return c.name.slice(0,a.length)===a}),d=0;d<c.length;d++)delete this.files[c[d].name];return this},generate:function(a){a=e.extend(a||{},{base64:!0,compression:"STORE",compressionOptions:null,type:"base64",platform:"DOS",comment:null,mimeType:"application/zip",encodeFileName:m.utf8encode}),e.checkSupport(a.type),"darwin"!==a.platform&&"freebsd"!==a.platform&&"linux"!==a.platform&&"sunos"!==a.platform||(a.platform="UNIX"),"win32"===a.platform&&(a.platform="DOS");var b,c,d=[],f=0,h=0,k=e.transformTo("string",a.encodeFileName(a.comment||this.comment||""));for(var l in this.files)if(this.files.hasOwnProperty(l)){var p=this.files[l],q=p.options.compression||a.compression.toUpperCase(),r=j[q];if(!r)throw new Error(q+" is not a valid compression method !");var s=p.options.compressionOptions||a.compressionOptions||{},u=z.call(this,p,r,s),v=C.call(this,l,p,u,f,a.platform,a.encodeFileName);f+=v.fileRecord.length+u.compressedSize,h+=v.dirRecord.length,d.push(v)}var w="";w=g.CENTRAL_DIRECTORY_END+"\x00\x00\x00\x00"+t(d.length,2)+t(d.length,2)+t(h,4)+t(f,4)+t(k.length,2)+k;var x=a.type.toLowerCase();for(b="uint8array"===x||"arraybuffer"===x||"blob"===x||"nodebuffer"===x?new o(f+h+w.length):new n(f+h+w.length),c=0;c<d.length;c++)b.append(d[c].fileRecord),b.append(d[c].compressedObject.compressedContent);for(c=0;c<d.length;c++)b.append(d[c].dirRecord);b.append(w);var y=b.finalize();switch(a.type.toLowerCase()){case"uint8array":case"arraybuffer":case"nodebuffer":return e.transformTo(a.type.toLowerCase(),y);case"blob":return e.arrayBuffer2Blob(e.transformTo("arraybuffer",y),a.mimeType);case"base64":return a.base64?i.encode(y):y;default:return y}},crc32:function(a,b){return f(a,b)},utf8encode:function(a){return e.transformTo("string",m.utf8encode(a))},utf8decode:function(a){return m.utf8decode(a)}};b.exports=D},{"./base64":2,"./compressedObject":3,"./compressions":4,"./crc32":5,"./defaults":7,"./nodeBuffer":12,"./signature":15,"./stringWriter":17,"./support":18,"./uint8ArrayWriter":20,"./utf8":21,"./utils":22}],15:[function(a,b,c){"use strict";c.LOCAL_FILE_HEADER="PK",c.CENTRAL_FILE_HEADER="PK",c.CENTRAL_DIRECTORY_END="PK",c.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",c.ZIP64_CENTRAL_DIRECTORY_END="PK",c.DATA_DESCRIPTOR="PK\b"},{}],16:[function(a,b,c){"use strict";function d(a,b){this.data=a,b||(this.data=f.string2binary(this.data)),this.length=this.data.length,this.index=0,this.zero=0}var e=a("./dataReader"),f=a("./utils");d.prototype=new e,d.prototype.byteAt=function(a){return this.data.charCodeAt(this.zero+a)},d.prototype.lastIndexOfSignature=function(a){return this.data.lastIndexOf(a)-this.zero},d.prototype.readData=function(a){this.checkOffset(a);var b=this.data.slice(this.zero+this.index,this.zero+this.index+a);return this.index+=a,b},b.exports=d},{"./dataReader":6,"./utils":22}],17:[function(a,b,c){"use strict";var d=a("./utils"),e=function(){this.data=[]};e.prototype={append:function(a){a=d.transformTo("string",a),this.data.push(a)},finalize:function(){return this.data.join("")}},b.exports=e},{"./utils":22}],18:[function(a,b,c){(function(a){"use strict";if(c.base64=!0,c.array=!0,c.string=!0,c.arraybuffer="undefined"!=typeof ArrayBuffer&&"undefined"!=typeof Uint8Array,c.nodebuffer="undefined"!=typeof a,c.uint8array="undefined"!=typeof Uint8Array,"undefined"==typeof ArrayBuffer)c.blob=!1;else{var b=new ArrayBuffer(0);try{c.blob=0===new Blob([b],{type:"application/zip"}).size}catch(d){try{var e=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder,f=new e;f.append(b),c.blob=0===f.getBlob("application/zip").size}catch(d){c.blob=!1}}}}).call(this,"undefined"!=typeof Buffer?Buffer:void 0)},{}],19:[function(a,b,c){"use strict";function d(a){a&&(this.data=a,this.length=this.data.length,this.index=0,this.zero=0)}var e=a("./arrayReader");d.prototype=new e,d.prototype.readData=function(a){if(this.checkOffset(a),0===a)return new Uint8Array(0);var b=this.data.subarray(this.zero+this.index,this.zero+this.index+a);return this.index+=a,b},b.exports=d},{"./arrayReader":1}],20:[function(a,b,c){"use strict";var d=a("./utils"),e=function(a){this.data=new Uint8Array(a),this.index=0};e.prototype={append:function(a){0!==a.length&&(a=d.transformTo("uint8array",a),this.data.set(a,this.index),this.index+=a.length)},finalize:function(){return this.data}},b.exports=e},{"./utils":22}],21:[function(a,b,c){"use strict";for(var d=a("./utils"),e=a("./support"),f=a("./nodeBuffer"),g=new Array(256),h=0;256>h;h++)g[h]=h>=252?6:h>=248?5:h>=240?4:h>=224?3:h>=192?2:1;g[254]=g[254]=1;var i=function(a){var b,c,d,f,g,h=a.length,i=0;for(f=0;h>f;f++)c=a.charCodeAt(f),55296===(64512&c)&&h>f+1&&(d=a.charCodeAt(f+1),56320===(64512&d)&&(c=65536+(c-55296<<10)+(d-56320),f++)),i+=128>c?1:2048>c?2:65536>c?3:4;for(b=e.uint8array?new Uint8Array(i):new Array(i),g=0,f=0;i>g;f++)c=a.charCodeAt(f),55296===(64512&c)&&h>f+1&&(d=a.charCodeAt(f+1),56320===(64512&d)&&(c=65536+(c-55296<<10)+(d-56320),f++)),128>c?b[g++]=c:2048>c?(b[g++]=192|c>>>6,b[g++]=128|63&c):65536>c?(b[g++]=224|c>>>12,b[g++]=128|c>>>6&63,b[g++]=128|63&c):(b[g++]=240|c>>>18,b[g++]=128|c>>>12&63,b[g++]=128|c>>>6&63,b[g++]=128|63&c);return b},j=function(a,b){var c;for(b=b||a.length,b>a.length&&(b=a.length),c=b-1;c>=0&&128===(192&a[c]);)c--;return 0>c?b:0===c?b:c+g[a[c]]>b?c:b},k=function(a){var b,c,e,f,h=a.length,i=new Array(2*h);for(c=0,b=0;h>b;)if(e=a[b++],128>e)i[c++]=e;else if(f=g[e],f>4)i[c++]=65533,b+=f-1;else{for(e&=2===f?31:3===f?15:7;f>1&&h>b;)e=e<<6|63&a[b++],f--;f>1?i[c++]=65533:65536>e?i[c++]=e:(e-=65536,i[c++]=55296|e>>10&1023,i[c++]=56320|1023&e)}return i.length!==c&&(i.subarray?i=i.subarray(0,c):i.length=c),d.applyFromCharCode(i)};c.utf8encode=function(a){return e.nodebuffer?f(a,"utf-8"):i(a)},c.utf8decode=function(a){if(e.nodebuffer)return d.transformTo("nodebuffer",a).toString("utf-8");a=d.transformTo(e.uint8array?"uint8array":"array",a);for(var b=[],c=0,f=a.length,g=65536;f>c;){var h=j(a,Math.min(c+g,f));e.uint8array?b.push(k(a.subarray(c,h))):b.push(k(a.slice(c,h))),c=h}return b.join("")}},{"./nodeBuffer":12,"./support":18,"./utils":22}],22:[function(a,b,c){"use strict";function d(a){return a}function e(a,b){for(var c=0;c<a.length;++c)b[c]=255&a.charCodeAt(c);return b}function f(a){var b=65536,d=[],e=a.length,f=c.getTypeOf(a),g=0,h=!0;try{switch(f){case"uint8array":String.fromCharCode.apply(null,new Uint8Array(0));break;case"nodebuffer":String.fromCharCode.apply(null,j(0))}}catch(i){h=!1}if(!h){for(var k="",l=0;l<a.length;l++)k+=String.fromCharCode(a[l]);return k}for(;e>g&&b>1;)try{"array"===f||"nodebuffer"===f?d.push(String.fromCharCode.apply(null,a.slice(g,Math.min(g+b,e)))):d.push(String.fromCharCode.apply(null,a.subarray(g,Math.min(g+b,e)))),g+=b}catch(i){b=Math.floor(b/2)}return d.join("")}function g(a,b){for(var c=0;c<a.length;c++)b[c]=a[c];return b}var h=a("./support"),i=a("./compressions"),j=a("./nodeBuffer");c.string2binary=function(a){for(var b="",c=0;c<a.length;c++)b+=String.fromCharCode(255&a.charCodeAt(c));return b},c.arrayBuffer2Blob=function(a,b){c.checkSupport("blob"),b=b||"application/zip";try{return new Blob([a],{type:b})}catch(d){try{var e=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder,f=new e;return f.append(a),f.getBlob(b)}catch(d){throw new Error("Bug : can't construct the Blob.")}}},c.applyFromCharCode=f;var k={};k.string={string:d,array:function(a){return e(a,new Array(a.length))},arraybuffer:function(a){return k.string.uint8array(a).buffer},uint8array:function(a){return e(a,new Uint8Array(a.length))},nodebuffer:function(a){return e(a,j(a.length))}},k.array={string:f,array:d,arraybuffer:function(a){return new Uint8Array(a).buffer},uint8array:function(a){return new Uint8Array(a)},nodebuffer:function(a){return j(a)}},k.arraybuffer={string:function(a){return f(new Uint8Array(a))},array:function(a){return g(new Uint8Array(a),new Array(a.byteLength))},arraybuffer:d,uint8array:function(a){return new Uint8Array(a)},nodebuffer:function(a){return j(new Uint8Array(a))}},k.uint8array={string:f,array:function(a){return g(a,new Array(a.length))},arraybuffer:function(a){return a.buffer},uint8array:d,nodebuffer:function(a){return j(a)}},k.nodebuffer={string:f,array:function(a){return g(a,new Array(a.length))},arraybuffer:function(a){return k.nodebuffer.uint8array(a).buffer},uint8array:function(a){return g(a,new Uint8Array(a.length))},nodebuffer:d},c.transformTo=function(a,b){if(b||(b=""),!a)return b;c.checkSupport(a);var d=c.getTypeOf(b),e=k[d][a](b);return e},c.getTypeOf=function(a){return"string"==typeof a?"string":"[object Array]"===Object.prototype.toString.call(a)?"array":h.nodebuffer&&j.test(a)?"nodebuffer":h.uint8array&&a instanceof Uint8Array?"uint8array":h.arraybuffer&&a instanceof ArrayBuffer?"arraybuffer":void 0},c.checkSupport=function(a){var b=h[a.toLowerCase()];if(!b)throw new Error(a+" is not supported by this browser")},c.MAX_VALUE_16BITS=65535,c.MAX_VALUE_32BITS=-1,c.pretty=function(a){var b,c,d="";for(c=0;c<(a||"").length;c++)b=a.charCodeAt(c),d+="\\x"+(16>b?"0":"")+b.toString(16).toUpperCase();return d},c.findCompression=function(a){for(var b in i)if(i.hasOwnProperty(b)&&i[b].magic===a)return i[b];return null},c.isRegExp=function(a){return"[object RegExp]"===Object.prototype.toString.call(a)},c.extend=function(){var a,b,c={};for(a=0;a<arguments.length;a++)for(b in arguments[a])arguments[a].hasOwnProperty(b)&&"undefined"==typeof c[b]&&(c[b]=arguments[a][b]);return c}},{"./compressions":4,"./nodeBuffer":12,"./support":18}],23:[function(a,b,c){"use strict";function d(a,b){this.files=[],this.loadOptions=b,a&&this.load(a)}var e=a("./stringReader"),f=a("./nodeBufferReader"),g=a("./uint8ArrayReader"),h=a("./arrayReader"),i=a("./utils"),j=a("./signature"),k=a("./zipEntry"),l=a("./support");a("./object");d.prototype={checkSignature:function(a){var b=this.reader.readString(4);if(b!==a)throw new Error("Corrupted zip or bug : unexpected signature ("+i.pretty(b)+", expected "+i.pretty(a)+")")},isSignature:function(a,b){var c=this.reader.index;this.reader.setIndex(a);var d=this.reader.readString(4),e=d===b;return this.reader.setIndex(c),e},readBlockEndOfCentral:function(){this.diskNumber=this.reader.readInt(2),this.diskWithCentralDirStart=this.reader.readInt(2),this.centralDirRecordsOnThisDisk=this.reader.readInt(2),this.centralDirRecords=this.reader.readInt(2),this.centralDirSize=this.reader.readInt(4),this.centralDirOffset=this.reader.readInt(4),this.zipCommentLength=this.reader.readInt(2);var a=this.reader.readData(this.zipCommentLength),b=l.uint8array?"uint8array":"array",c=i.transformTo(b,a);this.zipComment=this.loadOptions.decodeFileName(c)},readBlockZip64EndOfCentral:function(){this.zip64EndOfCentralSize=this.reader.readInt(8),this.versionMadeBy=this.reader.readString(2),this.versionNeeded=this.reader.readInt(2),this.diskNumber=this.reader.readInt(4),this.diskWithCentralDirStart=this.reader.readInt(4),this.centralDirRecordsOnThisDisk=this.reader.readInt(8),this.centralDirRecords=this.reader.readInt(8),this.centralDirSize=this.reader.readInt(8),this.centralDirOffset=this.reader.readInt(8),this.zip64ExtensibleData={};for(var a,b,c,d=this.zip64EndOfCentralSize-44,e=0;d>e;)a=this.reader.readInt(2),b=this.reader.readInt(4),c=this.reader.readString(b),this.zip64ExtensibleData[a]={id:a,length:b,value:c}},readBlockZip64EndOfCentralLocator:function(){if(this.diskWithZip64CentralDirStart=this.reader.readInt(4),this.relativeOffsetEndOfZip64CentralDir=this.reader.readInt(8),this.disksCount=this.reader.readInt(4),this.disksCount>1)throw new Error("Multi-volumes zip are not supported")},readLocalFiles:function(){var a,b;for(a=0;a<this.files.length;a++)b=this.files[a],this.reader.setIndex(b.localHeaderOffset),this.checkSignature(j.LOCAL_FILE_HEADER),b.readLocalPart(this.reader),b.handleUTF8(),b.processAttributes()},readCentralDir:function(){var a;for(this.reader.setIndex(this.centralDirOffset);this.reader.readString(4)===j.CENTRAL_FILE_HEADER;)a=new k({zip64:this.zip64},this.loadOptions),a.readCentralPart(this.reader),this.files.push(a);if(this.centralDirRecords!==this.files.length&&0!==this.centralDirRecords&&0===this.files.length)throw new Error("Corrupted zip or bug: expected "+this.centralDirRecords+" records in central dir, got "+this.files.length)},readEndOfCentral:function(){var a=this.reader.lastIndexOfSignature(j.CENTRAL_DIRECTORY_END);if(0>a){var b=!this.isSignature(0,j.LOCAL_FILE_HEADER);throw b?new Error("Can't find end of central directory : is this a zip file ? If it is, see http://stuk.github.io/jszip/documentation/howto/read_zip.html"):new Error("Corrupted zip : can't find end of central directory")}this.reader.setIndex(a);var c=a;if(this.checkSignature(j.CENTRAL_DIRECTORY_END),this.readBlockEndOfCentral(),this.diskNumber===i.MAX_VALUE_16BITS||this.diskWithCentralDirStart===i.MAX_VALUE_16BITS||this.centralDirRecordsOnThisDisk===i.MAX_VALUE_16BITS||this.centralDirRecords===i.MAX_VALUE_16BITS||this.centralDirSize===i.MAX_VALUE_32BITS||this.centralDirOffset===i.MAX_VALUE_32BITS){if(this.zip64=!0,a=this.reader.lastIndexOfSignature(j.ZIP64_CENTRAL_DIRECTORY_LOCATOR),0>a)throw new Error("Corrupted zip : can't find the ZIP64 end of central directory locator");if(this.reader.setIndex(a),this.checkSignature(j.ZIP64_CENTRAL_DIRECTORY_LOCATOR),this.readBlockZip64EndOfCentralLocator(),!this.isSignature(this.relativeOffsetEndOfZip64CentralDir,j.ZIP64_CENTRAL_DIRECTORY_END)&&(this.relativeOffsetEndOfZip64CentralDir=this.reader.lastIndexOfSignature(j.ZIP64_CENTRAL_DIRECTORY_END),this.relativeOffsetEndOfZip64CentralDir<0))throw new Error("Corrupted zip : can't find the ZIP64 end of central directory");this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir),this.checkSignature(j.ZIP64_CENTRAL_DIRECTORY_END),this.readBlockZip64EndOfCentral()}var d=this.centralDirOffset+this.centralDirSize;this.zip64&&(d+=20,d+=12+this.zip64EndOfCentralSize);var e=c-d;if(e>0)this.isSignature(c,j.CENTRAL_FILE_HEADER)||(this.reader.zero=e);else if(0>e)throw new Error("Corrupted zip: missing "+Math.abs(e)+" bytes.")},prepareReader:function(a){var b=i.getTypeOf(a);if(i.checkSupport(b),"string"!==b||l.uint8array)if("nodebuffer"===b)this.reader=new f(a);else if(l.uint8array)this.reader=new g(i.transformTo("uint8array",a));else{if(!l.array)throw new Error("Unexpected error: unsupported type '"+b+"'");this.reader=new h(i.transformTo("array",a))}else this.reader=new e(a,this.loadOptions.optimizedBinaryString)},load:function(a){this.prepareReader(a),this.readEndOfCentral(),this.readCentralDir(),this.readLocalFiles()}},b.exports=d},{"./arrayReader":1,"./nodeBufferReader":13,"./object":14,"./signature":15,"./stringReader":16,"./support":18,"./uint8ArrayReader":19,"./utils":22,"./zipEntry":24}],24:[function(a,b,c){"use strict";function d(a,b){this.options=a,this.loadOptions=b}var e=a("./stringReader"),f=a("./utils"),g=a("./compressedObject"),h=a("./object"),i=a("./support"),j=0,k=3;d.prototype={isEncrypted:function(){return 1===(1&this.bitFlag)},useUTF8:function(){return 2048===(2048&this.bitFlag)},prepareCompressedContent:function(a,b,c){return function(){var d=a.index;a.setIndex(b);var e=a.readData(c);return a.setIndex(d),e}},prepareContent:function(a,b,c,d,e){return function(){var a=f.transformTo(d.uncompressInputType,this.getCompressedContent()),b=d.uncompress(a);if(b.length!==e)throw new Error("Bug : uncompressed data size mismatch");return b}},readLocalPart:function(a){var b,c;if(a.skip(22),this.fileNameLength=a.readInt(2),c=a.readInt(2),this.fileName=a.readData(this.fileNameLength),a.skip(c),-1==this.compressedSize||-1==this.uncompressedSize)throw new Error("Bug or corrupted zip : didn't get enough informations from the central directory (compressedSize == -1 || uncompressedSize == -1)");if(b=f.findCompression(this.compressionMethod),null===b)throw new Error("Corrupted zip : compression "+f.pretty(this.compressionMethod)+" unknown (inner file : "+f.transformTo("string",this.fileName)+")");if(this.decompressed=new g,this.decompressed.compressedSize=this.compressedSize,this.decompressed.uncompressedSize=this.uncompressedSize,this.decompressed.crc32=this.crc32,this.decompressed.compressionMethod=this.compressionMethod,this.decompressed.getCompressedContent=this.prepareCompressedContent(a,a.index,this.compressedSize,b),this.decompressed.getContent=this.prepareContent(a,a.index,this.compressedSize,b,this.uncompressedSize),this.loadOptions.checkCRC32&&(this.decompressed=f.transformTo("string",this.decompressed.getContent()),
-h.crc32(this.decompressed)!==this.crc32))throw new Error("Corrupted zip : CRC32 mismatch")},readCentralPart:function(a){if(this.versionMadeBy=a.readInt(2),this.versionNeeded=a.readInt(2),this.bitFlag=a.readInt(2),this.compressionMethod=a.readString(2),this.date=a.readDate(),this.crc32=a.readInt(4),this.compressedSize=a.readInt(4),this.uncompressedSize=a.readInt(4),this.fileNameLength=a.readInt(2),this.extraFieldsLength=a.readInt(2),this.fileCommentLength=a.readInt(2),this.diskNumberStart=a.readInt(2),this.internalFileAttributes=a.readInt(2),this.externalFileAttributes=a.readInt(4),this.localHeaderOffset=a.readInt(4),this.isEncrypted())throw new Error("Encrypted zip are not supported");this.fileName=a.readData(this.fileNameLength),this.readExtraFields(a),this.parseZIP64ExtraField(a),this.fileComment=a.readData(this.fileCommentLength)},processAttributes:function(){this.unixPermissions=null,this.dosPermissions=null;var a=this.versionMadeBy>>8;this.dir=!!(16&this.externalFileAttributes),a===j&&(this.dosPermissions=63&this.externalFileAttributes),a===k&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||"/"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(a){if(this.extraFields[1]){var b=new e(this.extraFields[1].value);this.uncompressedSize===f.MAX_VALUE_32BITS&&(this.uncompressedSize=b.readInt(8)),this.compressedSize===f.MAX_VALUE_32BITS&&(this.compressedSize=b.readInt(8)),this.localHeaderOffset===f.MAX_VALUE_32BITS&&(this.localHeaderOffset=b.readInt(8)),this.diskNumberStart===f.MAX_VALUE_32BITS&&(this.diskNumberStart=b.readInt(4))}},readExtraFields:function(a){var b,c,d,e=a.index;for(this.extraFields=this.extraFields||{};a.index<e+this.extraFieldsLength;)b=a.readInt(2),c=a.readInt(2),d=a.readString(c),this.extraFields[b]={id:b,length:c,value:d}},handleUTF8:function(){var a=i.uint8array?"uint8array":"array";if(this.useUTF8())this.fileNameStr=h.utf8decode(this.fileName),this.fileCommentStr=h.utf8decode(this.fileComment);else{var b=this.findExtraFieldUnicodePath();if(null!==b)this.fileNameStr=b;else{var c=f.transformTo(a,this.fileName);this.fileNameStr=this.loadOptions.decodeFileName(c)}var d=this.findExtraFieldUnicodeComment();if(null!==d)this.fileCommentStr=d;else{var e=f.transformTo(a,this.fileComment);this.fileCommentStr=this.loadOptions.decodeFileName(e)}}},findExtraFieldUnicodePath:function(){var a=this.extraFields[28789];if(a){var b=new e(a.value);return 1!==b.readInt(1)?null:h.crc32(this.fileName)!==b.readInt(4)?null:h.utf8decode(b.readString(a.length-5))}return null},findExtraFieldUnicodeComment:function(){var a=this.extraFields[25461];if(a){var b=new e(a.value);return 1!==b.readInt(1)?null:h.crc32(this.fileComment)!==b.readInt(4)?null:h.utf8decode(b.readString(a.length-5))}return null}},b.exports=d},{"./compressedObject":3,"./object":14,"./stringReader":16,"./support":18,"./utils":22}],25:[function(a,b,c){"use strict";var d=a("./lib/utils/common").assign,e=a("./lib/deflate"),f=a("./lib/inflate"),g=a("./lib/zlib/constants"),h={};d(h,e,f,g),b.exports=h},{"./lib/deflate":26,"./lib/inflate":27,"./lib/utils/common":28,"./lib/zlib/constants":31}],26:[function(a,b,c){"use strict";function d(a){if(!(this instanceof d))return new d(a);this.options=i.assign({level:s,method:u,chunkSize:16384,windowBits:15,memLevel:8,strategy:t,to:""},a||{});var b=this.options;b.raw&&b.windowBits>0?b.windowBits=-b.windowBits:b.gzip&&b.windowBits>0&&b.windowBits<16&&(b.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new l,this.strm.avail_out=0;var c=h.deflateInit2(this.strm,b.level,b.method,b.windowBits,b.memLevel,b.strategy);if(c!==p)throw new Error(k[c]);b.header&&h.deflateSetHeader(this.strm,b.header)}function e(a,b){var c=new d(b);if(c.push(a,!0),c.err)throw c.msg;return c.result}function f(a,b){return b=b||{},b.raw=!0,e(a,b)}function g(a,b){return b=b||{},b.gzip=!0,e(a,b)}var h=a("./zlib/deflate"),i=a("./utils/common"),j=a("./utils/strings"),k=a("./zlib/messages"),l=a("./zlib/zstream"),m=Object.prototype.toString,n=0,o=4,p=0,q=1,r=2,s=-1,t=0,u=8;d.prototype.push=function(a,b){var c,d,e=this.strm,f=this.options.chunkSize;if(this.ended)return!1;d=b===~~b?b:b===!0?o:n,"string"==typeof a?e.input=j.string2buf(a):"[object ArrayBuffer]"===m.call(a)?e.input=new Uint8Array(a):e.input=a,e.next_in=0,e.avail_in=e.input.length;do{if(0===e.avail_out&&(e.output=new i.Buf8(f),e.next_out=0,e.avail_out=f),c=h.deflate(e,d),c!==q&&c!==p)return this.onEnd(c),this.ended=!0,!1;0!==e.avail_out&&(0!==e.avail_in||d!==o&&d!==r)||("string"===this.options.to?this.onData(j.buf2binstring(i.shrinkBuf(e.output,e.next_out))):this.onData(i.shrinkBuf(e.output,e.next_out)))}while((e.avail_in>0||0===e.avail_out)&&c!==q);return d===o?(c=h.deflateEnd(this.strm),this.onEnd(c),this.ended=!0,c===p):d===r?(this.onEnd(p),e.avail_out=0,!0):!0},d.prototype.onData=function(a){this.chunks.push(a)},d.prototype.onEnd=function(a){a===p&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=i.flattenChunks(this.chunks)),this.chunks=[],this.err=a,this.msg=this.strm.msg},c.Deflate=d,c.deflate=e,c.deflateRaw=f,c.gzip=g},{"./utils/common":28,"./utils/strings":29,"./zlib/deflate":33,"./zlib/messages":38,"./zlib/zstream":40}],27:[function(a,b,c){"use strict";function d(a){if(!(this instanceof d))return new d(a);this.options=h.assign({chunkSize:16384,windowBits:0,to:""},a||{});var b=this.options;b.raw&&b.windowBits>=0&&b.windowBits<16&&(b.windowBits=-b.windowBits,0===b.windowBits&&(b.windowBits=-15)),!(b.windowBits>=0&&b.windowBits<16)||a&&a.windowBits||(b.windowBits+=32),b.windowBits>15&&b.windowBits<48&&0===(15&b.windowBits)&&(b.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new l,this.strm.avail_out=0;var c=g.inflateInit2(this.strm,b.windowBits);if(c!==j.Z_OK)throw new Error(k[c]);this.header=new m,g.inflateGetHeader(this.strm,this.header)}function e(a,b){var c=new d(b);if(c.push(a,!0),c.err)throw c.msg;return c.result}function f(a,b){return b=b||{},b.raw=!0,e(a,b)}var g=a("./zlib/inflate"),h=a("./utils/common"),i=a("./utils/strings"),j=a("./zlib/constants"),k=a("./zlib/messages"),l=a("./zlib/zstream"),m=a("./zlib/gzheader"),n=Object.prototype.toString;d.prototype.push=function(a,b){var c,d,e,f,k,l=this.strm,m=this.options.chunkSize,o=!1;if(this.ended)return!1;d=b===~~b?b:b===!0?j.Z_FINISH:j.Z_NO_FLUSH,"string"==typeof a?l.input=i.binstring2buf(a):"[object ArrayBuffer]"===n.call(a)?l.input=new Uint8Array(a):l.input=a,l.next_in=0,l.avail_in=l.input.length;do{if(0===l.avail_out&&(l.output=new h.Buf8(m),l.next_out=0,l.avail_out=m),c=g.inflate(l,j.Z_NO_FLUSH),c===j.Z_BUF_ERROR&&o===!0&&(c=j.Z_OK,o=!1),c!==j.Z_STREAM_END&&c!==j.Z_OK)return this.onEnd(c),this.ended=!0,!1;l.next_out&&(0!==l.avail_out&&c!==j.Z_STREAM_END&&(0!==l.avail_in||d!==j.Z_FINISH&&d!==j.Z_SYNC_FLUSH)||("string"===this.options.to?(e=i.utf8border(l.output,l.next_out),f=l.next_out-e,k=i.buf2string(l.output,e),l.next_out=f,l.avail_out=m-f,f&&h.arraySet(l.output,l.output,e,f,0),this.onData(k)):this.onData(h.shrinkBuf(l.output,l.next_out)))),0===l.avail_in&&0===l.avail_out&&(o=!0)}while((l.avail_in>0||0===l.avail_out)&&c!==j.Z_STREAM_END);return c===j.Z_STREAM_END&&(d=j.Z_FINISH),d===j.Z_FINISH?(c=g.inflateEnd(this.strm),this.onEnd(c),this.ended=!0,c===j.Z_OK):d===j.Z_SYNC_FLUSH?(this.onEnd(j.Z_OK),l.avail_out=0,!0):!0},d.prototype.onData=function(a){this.chunks.push(a)},d.prototype.onEnd=function(a){a===j.Z_OK&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=h.flattenChunks(this.chunks)),this.chunks=[],this.err=a,this.msg=this.strm.msg},c.Inflate=d,c.inflate=e,c.inflateRaw=f,c.ungzip=e},{"./utils/common":28,"./utils/strings":29,"./zlib/constants":31,"./zlib/gzheader":34,"./zlib/inflate":36,"./zlib/messages":38,"./zlib/zstream":40}],28:[function(a,b,c){"use strict";var d="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Int32Array;c.assign=function(a){for(var b=Array.prototype.slice.call(arguments,1);b.length;){var c=b.shift();if(c){if("object"!=typeof c)throw new TypeError(c+"must be non-object");for(var d in c)c.hasOwnProperty(d)&&(a[d]=c[d])}}return a},c.shrinkBuf=function(a,b){return a.length===b?a:a.subarray?a.subarray(0,b):(a.length=b,a)};var e={arraySet:function(a,b,c,d,e){if(b.subarray&&a.subarray)return void a.set(b.subarray(c,c+d),e);for(var f=0;d>f;f++)a[e+f]=b[c+f]},flattenChunks:function(a){var b,c,d,e,f,g;for(d=0,b=0,c=a.length;c>b;b++)d+=a[b].length;for(g=new Uint8Array(d),e=0,b=0,c=a.length;c>b;b++)f=a[b],g.set(f,e),e+=f.length;return g}},f={arraySet:function(a,b,c,d,e){for(var f=0;d>f;f++)a[e+f]=b[c+f]},flattenChunks:function(a){return[].concat.apply([],a)}};c.setTyped=function(a){a?(c.Buf8=Uint8Array,c.Buf16=Uint16Array,c.Buf32=Int32Array,c.assign(c,e)):(c.Buf8=Array,c.Buf16=Array,c.Buf32=Array,c.assign(c,f))},c.setTyped(d)},{}],29:[function(a,b,c){"use strict";function d(a,b){if(65537>b&&(a.subarray&&g||!a.subarray&&f))return String.fromCharCode.apply(null,e.shrinkBuf(a,b));for(var c="",d=0;b>d;d++)c+=String.fromCharCode(a[d]);return c}var e=a("./common"),f=!0,g=!0;try{String.fromCharCode.apply(null,[0])}catch(h){f=!1}try{String.fromCharCode.apply(null,new Uint8Array(1))}catch(h){g=!1}for(var i=new e.Buf8(256),j=0;256>j;j++)i[j]=j>=252?6:j>=248?5:j>=240?4:j>=224?3:j>=192?2:1;i[254]=i[254]=1,c.string2buf=function(a){var b,c,d,f,g,h=a.length,i=0;for(f=0;h>f;f++)c=a.charCodeAt(f),55296===(64512&c)&&h>f+1&&(d=a.charCodeAt(f+1),56320===(64512&d)&&(c=65536+(c-55296<<10)+(d-56320),f++)),i+=128>c?1:2048>c?2:65536>c?3:4;for(b=new e.Buf8(i),g=0,f=0;i>g;f++)c=a.charCodeAt(f),55296===(64512&c)&&h>f+1&&(d=a.charCodeAt(f+1),56320===(64512&d)&&(c=65536+(c-55296<<10)+(d-56320),f++)),128>c?b[g++]=c:2048>c?(b[g++]=192|c>>>6,b[g++]=128|63&c):65536>c?(b[g++]=224|c>>>12,b[g++]=128|c>>>6&63,b[g++]=128|63&c):(b[g++]=240|c>>>18,b[g++]=128|c>>>12&63,b[g++]=128|c>>>6&63,b[g++]=128|63&c);return b},c.buf2binstring=function(a){return d(a,a.length)},c.binstring2buf=function(a){for(var b=new e.Buf8(a.length),c=0,d=b.length;d>c;c++)b[c]=a.charCodeAt(c);return b},c.buf2string=function(a,b){var c,e,f,g,h=b||a.length,j=new Array(2*h);for(e=0,c=0;h>c;)if(f=a[c++],128>f)j[e++]=f;else if(g=i[f],g>4)j[e++]=65533,c+=g-1;else{for(f&=2===g?31:3===g?15:7;g>1&&h>c;)f=f<<6|63&a[c++],g--;g>1?j[e++]=65533:65536>f?j[e++]=f:(f-=65536,j[e++]=55296|f>>10&1023,j[e++]=56320|1023&f)}return d(j,e)},c.utf8border=function(a,b){var c;for(b=b||a.length,b>a.length&&(b=a.length),c=b-1;c>=0&&128===(192&a[c]);)c--;return 0>c?b:0===c?b:c+i[a[c]]>b?c:b}},{"./common":28}],30:[function(a,b,c){"use strict";function d(a,b,c,d){for(var e=65535&a|0,f=a>>>16&65535|0,g=0;0!==c;){g=c>2e3?2e3:c,c-=g;do e=e+b[d++]|0,f=f+e|0;while(--g);e%=65521,f%=65521}return e|f<<16|0}b.exports=d},{}],31:[function(a,b,c){"use strict";b.exports={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,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_BUF_ERROR:-5,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,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8}},{}],32:[function(a,b,c){"use strict";function d(){for(var a,b=[],c=0;256>c;c++){a=c;for(var d=0;8>d;d++)a=1&a?3988292384^a>>>1:a>>>1;b[c]=a}return b}function e(a,b,c,d){var e=f,g=d+c;a^=-1;for(var h=d;g>h;h++)a=a>>>8^e[255&(a^b[h])];return-1^a}var f=d();b.exports=e},{}],33:[function(a,b,c){"use strict";function d(a,b){return a.msg=H[b],b}function e(a){return(a<<1)-(a>4?9:0)}function f(a){for(var b=a.length;--b>=0;)a[b]=0}function g(a){var b=a.state,c=b.pending;c>a.avail_out&&(c=a.avail_out),0!==c&&(D.arraySet(a.output,b.pending_buf,b.pending_out,c,a.next_out),a.next_out+=c,b.pending_out+=c,a.total_out+=c,a.avail_out-=c,b.pending-=c,0===b.pending&&(b.pending_out=0))}function h(a,b){E._tr_flush_block(a,a.block_start>=0?a.block_start:-1,a.strstart-a.block_start,b),a.block_start=a.strstart,g(a.strm)}function i(a,b){a.pending_buf[a.pending++]=b}function j(a,b){a.pending_buf[a.pending++]=b>>>8&255,a.pending_buf[a.pending++]=255&b}function k(a,b,c,d){var e=a.avail_in;return e>d&&(e=d),0===e?0:(a.avail_in-=e,D.arraySet(b,a.input,a.next_in,e,c),1===a.state.wrap?a.adler=F(a.adler,b,e,c):2===a.state.wrap&&(a.adler=G(a.adler,b,e,c)),a.next_in+=e,a.total_in+=e,e)}function l(a,b){var c,d,e=a.max_chain_length,f=a.strstart,g=a.prev_length,h=a.nice_match,i=a.strstart>a.w_size-ka?a.strstart-(a.w_size-ka):0,j=a.window,k=a.w_mask,l=a.prev,m=a.strstart+ja,n=j[f+g-1],o=j[f+g];a.prev_length>=a.good_match&&(e>>=2),h>a.lookahead&&(h=a.lookahead);do if(c=b,j[c+g]===o&&j[c+g-1]===n&&j[c]===j[f]&&j[++c]===j[f+1]){f+=2,c++;do;while(j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&j[++f]===j[++c]&&m>f);if(d=ja-(m-f),f=m-ja,d>g){if(a.match_start=b,g=d,d>=h)break;n=j[f+g-1],o=j[f+g]}}while((b=l[b&k])>i&&0!==--e);return g<=a.lookahead?g:a.lookahead}function m(a){var b,c,d,e,f,g=a.w_size;do{if(e=a.window_size-a.lookahead-a.strstart,a.strstart>=g+(g-ka)){D.arraySet(a.window,a.window,g,g,0),a.match_start-=g,a.strstart-=g,a.block_start-=g,c=a.hash_size,b=c;do d=a.head[--b],a.head[b]=d>=g?d-g:0;while(--c);c=g,b=c;do d=a.prev[--b],a.prev[b]=d>=g?d-g:0;while(--c);e+=g}if(0===a.strm.avail_in)break;if(c=k(a.strm,a.window,a.strstart+a.lookahead,e),a.lookahead+=c,a.lookahead+a.insert>=ia)for(f=a.strstart-a.insert,a.ins_h=a.window[f],a.ins_h=(a.ins_h<<a.hash_shift^a.window[f+1])&a.hash_mask;a.insert&&(a.ins_h=(a.ins_h<<a.hash_shift^a.window[f+ia-1])&a.hash_mask,a.prev[f&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=f,f++,a.insert--,!(a.lookahead+a.insert<ia)););}while(a.lookahead<ka&&0!==a.strm.avail_in)}function n(a,b){var c=65535;for(c>a.pending_buf_size-5&&(c=a.pending_buf_size-5);;){if(a.lookahead<=1){if(m(a),0===a.lookahead&&b===I)return ta;if(0===a.lookahead)break}a.strstart+=a.lookahead,a.lookahead=0;var d=a.block_start+c;if((0===a.strstart||a.strstart>=d)&&(a.lookahead=a.strstart-d,a.strstart=d,h(a,!1),0===a.strm.avail_out))return ta;if(a.strstart-a.block_start>=a.w_size-ka&&(h(a,!1),0===a.strm.avail_out))return ta}return a.insert=0,b===L?(h(a,!0),0===a.strm.avail_out?va:wa):a.strstart>a.block_start&&(h(a,!1),0===a.strm.avail_out)?ta:ta}function o(a,b){for(var c,d;;){if(a.lookahead<ka){if(m(a),a.lookahead<ka&&b===I)return ta;if(0===a.lookahead)break}if(c=0,a.lookahead>=ia&&(a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+ia-1])&a.hash_mask,c=a.prev[a.strstart&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=a.strstart),0!==c&&a.strstart-c<=a.w_size-ka&&(a.match_length=l(a,c)),a.match_length>=ia)if(d=E._tr_tally(a,a.strstart-a.match_start,a.match_length-ia),a.lookahead-=a.match_length,a.match_length<=a.max_lazy_match&&a.lookahead>=ia){a.match_length--;do a.strstart++,a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+ia-1])&a.hash_mask,c=a.prev[a.strstart&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=a.strstart;while(0!==--a.match_length);a.strstart++}else a.strstart+=a.match_length,a.match_length=0,a.ins_h=a.window[a.strstart],a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+1])&a.hash_mask;else d=E._tr_tally(a,0,a.window[a.strstart]),a.lookahead--,a.strstart++;if(d&&(h(a,!1),0===a.strm.avail_out))return ta}return a.insert=a.strstart<ia-1?a.strstart:ia-1,b===L?(h(a,!0),0===a.strm.avail_out?va:wa):a.last_lit&&(h(a,!1),0===a.strm.avail_out)?ta:ua}function p(a,b){for(var c,d,e;;){if(a.lookahead<ka){if(m(a),a.lookahead<ka&&b===I)return ta;if(0===a.lookahead)break}if(c=0,a.lookahead>=ia&&(a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+ia-1])&a.hash_mask,c=a.prev[a.strstart&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=a.strstart),a.prev_length=a.match_length,a.prev_match=a.match_start,a.match_length=ia-1,0!==c&&a.prev_length<a.max_lazy_match&&a.strstart-c<=a.w_size-ka&&(a.match_length=l(a,c),a.match_length<=5&&(a.strategy===T||a.match_length===ia&&a.strstart-a.match_start>4096)&&(a.match_length=ia-1)),a.prev_length>=ia&&a.match_length<=a.prev_length){e=a.strstart+a.lookahead-ia,d=E._tr_tally(a,a.strstart-1-a.prev_match,a.prev_length-ia),a.lookahead-=a.prev_length-1,a.prev_length-=2;do++a.strstart<=e&&(a.ins_h=(a.ins_h<<a.hash_shift^a.window[a.strstart+ia-1])&a.hash_mask,c=a.prev[a.strstart&a.w_mask]=a.head[a.ins_h],a.head[a.ins_h]=a.strstart);while(0!==--a.prev_length);if(a.match_available=0,a.match_length=ia-1,a.strstart++,d&&(h(a,!1),0===a.strm.avail_out))return ta}else if(a.match_available){if(d=E._tr_tally(a,0,a.window[a.strstart-1]),d&&h(a,!1),a.strstart++,a.lookahead--,0===a.strm.avail_out)return ta}else a.match_available=1,a.strstart++,a.lookahead--}return a.match_available&&(d=E._tr_tally(a,0,a.window[a.strstart-1]),a.match_available=0),a.insert=a.strstart<ia-1?a.strstart:ia-1,b===L?(h(a,!0),0===a.strm.avail_out?va:wa):a.last_lit&&(h(a,!1),0===a.strm.avail_out)?ta:ua}function q(a,b){for(var c,d,e,f,g=a.window;;){if(a.lookahead<=ja){if(m(a),a.lookahead<=ja&&b===I)return ta;if(0===a.lookahead)break}if(a.match_length=0,a.lookahead>=ia&&a.strstart>0&&(e=a.strstart-1,d=g[e],d===g[++e]&&d===g[++e]&&d===g[++e])){f=a.strstart+ja;do;while(d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&d===g[++e]&&f>e);a.match_length=ja-(f-e),a.match_length>a.lookahead&&(a.match_length=a.lookahead)}if(a.match_length>=ia?(c=E._tr_tally(a,1,a.match_length-ia),a.lookahead-=a.match_length,a.strstart+=a.match_length,a.match_length=0):(c=E._tr_tally(a,0,a.window[a.strstart]),a.lookahead--,a.strstart++),c&&(h(a,!1),0===a.strm.avail_out))return ta}return a.insert=0,b===L?(h(a,!0),0===a.strm.avail_out?va:wa):a.last_lit&&(h(a,!1),0===a.strm.avail_out)?ta:ua}function r(a,b){for(var c;;){if(0===a.lookahead&&(m(a),0===a.lookahead)){if(b===I)return ta;break}if(a.match_length=0,c=E._tr_tally(a,0,a.window[a.strstart]),a.lookahead--,a.strstart++,c&&(h(a,!1),0===a.strm.avail_out))return ta}return a.insert=0,b===L?(h(a,!0),0===a.strm.avail_out?va:wa):a.last_lit&&(h(a,!1),0===a.strm.avail_out)?ta:ua}function s(a,b,c,d,e){this.good_length=a,this.max_lazy=b,this.nice_length=c,this.max_chain=d,this.func=e}function t(a){a.window_size=2*a.w_size,f(a.head),a.max_lazy_match=C[a.level].max_lazy,a.good_match=C[a.level].good_length,a.nice_match=C[a.level].nice_length,a.max_chain_length=C[a.level].max_chain,a.strstart=0,a.block_start=0,a.lookahead=0,a.insert=0,a.match_length=a.prev_length=ia-1,a.match_available=0,a.ins_h=0}function u(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=Z,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new D.Buf16(2*ga),this.dyn_dtree=new D.Buf16(2*(2*ea+1)),this.bl_tree=new D.Buf16(2*(2*fa+1)),f(this.dyn_ltree),f(this.dyn_dtree),f(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new D.Buf16(ha+1),this.heap=new D.Buf16(2*da+1),f(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new D.Buf16(2*da+1),f(this.depth),this.l_buf=0,this.lit_bufsize=0,this.last_lit=0,this.d_buf=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}function v(a){var b;return a&&a.state?(a.total_in=a.total_out=0,a.data_type=Y,b=a.state,b.pending=0,b.pending_out=0,b.wrap<0&&(b.wrap=-b.wrap),b.status=b.wrap?ma:ra,a.adler=2===b.wrap?0:1,b.last_flush=I,E._tr_init(b),N):d(a,P)}function w(a){var b=v(a);return b===N&&t(a.state),b}function x(a,b){return a&&a.state?2!==a.state.wrap?P:(a.state.gzhead=b,N):P}function y(a,b,c,e,f,g){if(!a)return P;var h=1;if(b===S&&(b=6),0>e?(h=0,e=-e):e>15&&(h=2,e-=16),1>f||f>$||c!==Z||8>e||e>15||0>b||b>9||0>g||g>W)return d(a,P);8===e&&(e=9);var i=new u;return a.state=i,i.strm=a,i.wrap=h,i.gzhead=null,i.w_bits=e,i.w_size=1<<i.w_bits,i.w_mask=i.w_size-1,i.hash_bits=f+7,i.hash_size=1<<i.hash_bits,i.hash_mask=i.hash_size-1,i.hash_shift=~~((i.hash_bits+ia-1)/ia),i.window=new D.Buf8(2*i.w_size),i.head=new D.Buf16(i.hash_size),i.prev=new D.Buf16(i.w_size),i.lit_bufsize=1<<f+6,i.pending_buf_size=4*i.lit_bufsize,i.pending_buf=new D.Buf8(i.pending_buf_size),i.d_buf=i.lit_bufsize>>1,i.l_buf=3*i.lit_bufsize,i.level=b,i.strategy=g,i.method=c,w(a)}function z(a,b){return y(a,b,Z,_,aa,X)}function A(a,b){var c,h,k,l;if(!a||!a.state||b>M||0>b)return a?d(a,P):P;if(h=a.state,!a.output||!a.input&&0!==a.avail_in||h.status===sa&&b!==L)return d(a,0===a.avail_out?R:P);if(h.strm=a,c=h.last_flush,h.last_flush=b,h.status===ma)if(2===h.wrap)a.adler=0,i(h,31),i(h,139),i(h,8),h.gzhead?(i(h,(h.gzhead.text?1:0)+(h.gzhead.hcrc?2:0)+(h.gzhead.extra?4:0)+(h.gzhead.name?8:0)+(h.gzhead.comment?16:0)),i(h,255&h.gzhead.time),i(h,h.gzhead.time>>8&255),i(h,h.gzhead.time>>16&255),i(h,h.gzhead.time>>24&255),i(h,9===h.level?2:h.strategy>=U||h.level<2?4:0),i(h,255&h.gzhead.os),h.gzhead.extra&&h.gzhead.extra.length&&(i(h,255&h.gzhead.extra.length),i(h,h.gzhead.extra.length>>8&255)),h.gzhead.hcrc&&(a.adler=G(a.adler,h.pending_buf,h.pending,0)),h.gzindex=0,h.status=na):(i(h,0),i(h,0),i(h,0),i(h,0),i(h,0),i(h,9===h.level?2:h.strategy>=U||h.level<2?4:0),i(h,xa),h.status=ra);else{var m=Z+(h.w_bits-8<<4)<<8,n=-1;n=h.strategy>=U||h.level<2?0:h.level<6?1:6===h.level?2:3,m|=n<<6,0!==h.strstart&&(m|=la),m+=31-m%31,h.status=ra,j(h,m),0!==h.strstart&&(j(h,a.adler>>>16),j(h,65535&a.adler)),a.adler=1}if(h.status===na)if(h.gzhead.extra){for(k=h.pending;h.gzindex<(65535&h.gzhead.extra.length)&&(h.pending!==h.pending_buf_size||(h.gzhead.hcrc&&h.pending>k&&(a.adler=G(a.adler,h.pending_buf,h.pending-k,k)),g(a),k=h.pending,h.pending!==h.pending_buf_size));)i(h,255&h.gzhead.extra[h.gzindex]),h.gzindex++;h.gzhead.hcrc&&h.pending>k&&(a.adler=G(a.adler,h.pending_buf,h.pending-k,k)),h.gzindex===h.gzhead.extra.length&&(h.gzindex=0,h.status=oa)}else h.status=oa;if(h.status===oa)if(h.gzhead.name){k=h.pending;do{if(h.pending===h.pending_buf_size&&(h.gzhead.hcrc&&h.pending>k&&(a.adler=G(a.adler,h.pending_buf,h.pending-k,k)),g(a),k=h.pending,h.pending===h.pending_buf_size)){l=1;break}l=h.gzindex<h.gzhead.name.length?255&h.gzhead.name.charCodeAt(h.gzindex++):0,i(h,l)}while(0!==l);h.gzhead.hcrc&&h.pending>k&&(a.adler=G(a.adler,h.pending_buf,h.pending-k,k)),0===l&&(h.gzindex=0,h.status=pa)}else h.status=pa;if(h.status===pa)if(h.gzhead.comment){k=h.pending;do{if(h.pending===h.pending_buf_size&&(h.gzhead.hcrc&&h.pending>k&&(a.adler=G(a.adler,h.pending_buf,h.pending-k,k)),g(a),k=h.pending,h.pending===h.pending_buf_size)){l=1;break}l=h.gzindex<h.gzhead.comment.length?255&h.gzhead.comment.charCodeAt(h.gzindex++):0,i(h,l)}while(0!==l);h.gzhead.hcrc&&h.pending>k&&(a.adler=G(a.adler,h.pending_buf,h.pending-k,k)),0===l&&(h.status=qa)}else h.status=qa;if(h.status===qa&&(h.gzhead.hcrc?(h.pending+2>h.pending_buf_size&&g(a),h.pending+2<=h.pending_buf_size&&(i(h,255&a.adler),i(h,a.adler>>8&255),a.adler=0,h.status=ra)):h.status=ra),0!==h.pending){if(g(a),0===a.avail_out)return h.last_flush=-1,N}else if(0===a.avail_in&&e(b)<=e(c)&&b!==L)return d(a,R);if(h.status===sa&&0!==a.avail_in)return d(a,R);if(0!==a.avail_in||0!==h.lookahead||b!==I&&h.status!==sa){var o=h.strategy===U?r(h,b):h.strategy===V?q(h,b):C[h.level].func(h,b);if(o!==va&&o!==wa||(h.status=sa),o===ta||o===va)return 0===a.avail_out&&(h.last_flush=-1),N;if(o===ua&&(b===J?E._tr_align(h):b!==M&&(E._tr_stored_block(h,0,0,!1),b===K&&(f(h.head),0===h.lookahead&&(h.strstart=0,h.block_start=0,h.insert=0))),g(a),0===a.avail_out))return h.last_flush=-1,N}return b!==L?N:h.wrap<=0?O:(2===h.wrap?(i(h,255&a.adler),i(h,a.adler>>8&255),i(h,a.adler>>16&255),i(h,a.adler>>24&255),i(h,255&a.total_in),i(h,a.total_in>>8&255),i(h,a.total_in>>16&255),i(h,a.total_in>>24&255)):(j(h,a.adler>>>16),j(h,65535&a.adler)),g(a),h.wrap>0&&(h.wrap=-h.wrap),0!==h.pending?N:O)}function B(a){var b;return a&&a.state?(b=a.state.status,b!==ma&&b!==na&&b!==oa&&b!==pa&&b!==qa&&b!==ra&&b!==sa?d(a,P):(a.state=null,b===ra?d(a,Q):N)):P}var C,D=a("../utils/common"),E=a("./trees"),F=a("./adler32"),G=a("./crc32"),H=a("./messages"),I=0,J=1,K=3,L=4,M=5,N=0,O=1,P=-2,Q=-3,R=-5,S=-1,T=1,U=2,V=3,W=4,X=0,Y=2,Z=8,$=9,_=15,aa=8,ba=29,ca=256,da=ca+1+ba,ea=30,fa=19,ga=2*da+1,ha=15,ia=3,ja=258,ka=ja+ia+1,la=32,ma=42,na=69,oa=73,pa=91,qa=103,ra=113,sa=666,ta=1,ua=2,va=3,wa=4,xa=3;C=[new s(0,0,0,0,n),new s(4,4,8,4,o),new s(4,5,16,8,o),new s(4,6,32,32,o),new s(4,4,16,16,p),new s(8,16,32,32,p),new s(8,16,128,128,p),new s(8,32,128,256,p),new s(32,128,258,1024,p),new s(32,258,258,4096,p)],c.deflateInit=z,c.deflateInit2=y,c.deflateReset=w,c.deflateResetKeep=v,c.deflateSetHeader=x,c.deflate=A,c.deflateEnd=B,c.deflateInfo="pako deflate (from Nodeca project)"},{"../utils/common":28,"./adler32":30,"./crc32":32,"./messages":38,"./trees":39}],34:[function(a,b,c){"use strict";function d(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1}b.exports=d},{}],35:[function(a,b,c){"use strict";var d=30,e=12;b.exports=function(a,b){var c,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C;c=a.state,f=a.next_in,B=a.input,g=f+(a.avail_in-5),h=a.next_out,C=a.output,i=h-(b-a.avail_out),j=h+(a.avail_out-257),k=c.dmax,l=c.wsize,m=c.whave,n=c.wnext,o=c.window,p=c.hold,q=c.bits,r=c.lencode,s=c.distcode,t=(1<<c.lenbits)-1,u=(1<<c.distbits)-1;a:do{15>q&&(p+=B[f++]<<q,q+=8,p+=B[f++]<<q,q+=8),v=r[p&t];b:for(;;){if(w=v>>>24,p>>>=w,q-=w,w=v>>>16&255,0===w)C[h++]=65535&v;else{if(!(16&w)){if(0===(64&w)){v=r[(65535&v)+(p&(1<<w)-1)];continue b}if(32&w){c.mode=e;break a}a.msg="invalid literal/length code",c.mode=d;break a}x=65535&v,w&=15,w&&(w>q&&(p+=B[f++]<<q,q+=8),x+=p&(1<<w)-1,p>>>=w,q-=w),15>q&&(p+=B[f++]<<q,q+=8,p+=B[f++]<<q,q+=8),v=s[p&u];c:for(;;){if(w=v>>>24,p>>>=w,q-=w,w=v>>>16&255,!(16&w)){if(0===(64&w)){v=s[(65535&v)+(p&(1<<w)-1)];continue c}a.msg="invalid distance code",c.mode=d;break a}if(y=65535&v,w&=15,w>q&&(p+=B[f++]<<q,q+=8,w>q&&(p+=B[f++]<<q,q+=8)),y+=p&(1<<w)-1,y>k){a.msg="invalid distance too far back",c.mode=d;break a}if(p>>>=w,q-=w,w=h-i,y>w){if(w=y-w,w>m&&c.sane){a.msg="invalid distance too far back",c.mode=d;break a}if(z=0,A=o,0===n){if(z+=l-w,x>w){x-=w;do C[h++]=o[z++];while(--w);z=h-y,A=C}}else if(w>n){if(z+=l+n-w,w-=n,x>w){x-=w;do C[h++]=o[z++];while(--w);if(z=0,x>n){w=n,x-=w;do C[h++]=o[z++];while(--w);z=h-y,A=C}}}else if(z+=n-w,x>w){x-=w;do C[h++]=o[z++];while(--w);z=h-y,A=C}for(;x>2;)C[h++]=A[z++],C[h++]=A[z++],C[h++]=A[z++],x-=3;x&&(C[h++]=A[z++],x>1&&(C[h++]=A[z++]))}else{z=h-y;do C[h++]=C[z++],C[h++]=C[z++],C[h++]=C[z++],x-=3;while(x>2);x&&(C[h++]=C[z++],x>1&&(C[h++]=C[z++]))}break}}break}}while(g>f&&j>h);x=q>>3,f-=x,q-=x<<3,p&=(1<<q)-1,a.next_in=f,a.next_out=h,a.avail_in=g>f?5+(g-f):5-(f-g),a.avail_out=j>h?257+(j-h):257-(h-j),c.hold=p,c.bits=q}},{}],36:[function(a,b,c){"use strict";function d(a){return(a>>>24&255)+(a>>>8&65280)+((65280&a)<<8)+((255&a)<<24)}function e(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new r.Buf16(320),this.work=new r.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function f(a){var b;return a&&a.state?(b=a.state,a.total_in=a.total_out=b.total=0,a.msg="",b.wrap&&(a.adler=1&b.wrap),b.mode=K,b.last=0,b.havedict=0,b.dmax=32768,b.head=null,b.hold=0,b.bits=0,b.lencode=b.lendyn=new r.Buf32(oa),b.distcode=b.distdyn=new r.Buf32(pa),b.sane=1,b.back=-1,C):F}function g(a){var b;return a&&a.state?(b=a.state,b.wsize=0,b.whave=0,b.wnext=0,f(a)):F}function h(a,b){var c,d;return a&&a.state?(d=a.state,0>b?(c=0,b=-b):(c=(b>>4)+1,48>b&&(b&=15)),b&&(8>b||b>15)?F:(null!==d.window&&d.wbits!==b&&(d.window=null),d.wrap=c,d.wbits=b,g(a))):F}function i(a,b){var c,d;return a?(d=new e,a.state=d,d.window=null,c=h(a,b),c!==C&&(a.state=null),c):F}function j(a){return i(a,ra)}function k(a){if(sa){var b;for(p=new r.Buf32(512),q=new r.Buf32(32),b=0;144>b;)a.lens[b++]=8;for(;256>b;)a.lens[b++]=9;for(;280>b;)a.lens[b++]=7;for(;288>b;)a.lens[b++]=8;for(v(x,a.lens,0,288,p,0,a.work,{bits:9}),b=0;32>b;)a.lens[b++]=5;v(y,a.lens,0,32,q,0,a.work,{bits:5}),sa=!1}a.lencode=p,a.lenbits=9,a.distcode=q,a.distbits=5}function l(a,b,c,d){var e,f=a.state;return null===f.window&&(f.wsize=1<<f.wbits,f.wnext=0,f.whave=0,f.window=new r.Buf8(f.wsize)),d>=f.wsize?(r.arraySet(f.window,b,c-f.wsize,f.wsize,0),f.wnext=0,f.whave=f.wsize):(e=f.wsize-f.wnext,e>d&&(e=d),r.arraySet(f.window,b,c-d,e,f.wnext),d-=e,d?(r.arraySet(f.window,b,c-d,d,0),f.wnext=d,f.whave=f.wsize):(f.wnext+=e,f.wnext===f.wsize&&(f.wnext=0),f.whave<f.wsize&&(f.whave+=e))),0}function m(a,b){var c,e,f,g,h,i,j,m,n,o,p,q,oa,pa,qa,ra,sa,ta,ua,va,wa,xa,ya,za,Aa=0,Ba=new r.Buf8(4),Ca=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];if(!a||!a.state||!a.output||!a.input&&0!==a.avail_in)return F;c=a.state,c.mode===V&&(c.mode=W),h=a.next_out,f=a.output,j=a.avail_out,g=a.next_in,e=a.input,i=a.avail_in,m=c.hold,n=c.bits,o=i,p=j,xa=C;a:for(;;)switch(c.mode){case K:if(0===c.wrap){c.mode=W;break}for(;16>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(2&c.wrap&&35615===m){c.check=0,Ba[0]=255&m,Ba[1]=m>>>8&255,c.check=t(c.check,Ba,2,0),m=0,n=0,c.mode=L;break}if(c.flags=0,c.head&&(c.head.done=!1),!(1&c.wrap)||(((255&m)<<8)+(m>>8))%31){a.msg="incorrect header check",c.mode=la;break}if((15&m)!==J){a.msg="unknown compression method",c.mode=la;break}if(m>>>=4,n-=4,wa=(15&m)+8,0===c.wbits)c.wbits=wa;else if(wa>c.wbits){a.msg="invalid window size",c.mode=la;break}c.dmax=1<<wa,a.adler=c.check=1,c.mode=512&m?T:V,m=0,n=0;break;case L:for(;16>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(c.flags=m,(255&c.flags)!==J){a.msg="unknown compression method",c.mode=la;break}if(57344&c.flags){a.msg="unknown header flags set",c.mode=la;break}c.head&&(c.head.text=m>>8&1),512&c.flags&&(Ba[0]=255&m,Ba[1]=m>>>8&255,c.check=t(c.check,Ba,2,0)),m=0,n=0,c.mode=M;case M:for(;32>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.head&&(c.head.time=m),512&c.flags&&(Ba[0]=255&m,Ba[1]=m>>>8&255,Ba[2]=m>>>16&255,Ba[3]=m>>>24&255,c.check=t(c.check,Ba,4,0)),m=0,n=0,c.mode=N;case N:for(;16>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.head&&(c.head.xflags=255&m,c.head.os=m>>8),512&c.flags&&(Ba[0]=255&m,Ba[1]=m>>>8&255,c.check=t(c.check,Ba,2,0)),m=0,n=0,c.mode=O;case O:if(1024&c.flags){for(;16>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.length=m,c.head&&(c.head.extra_len=m),512&c.flags&&(Ba[0]=255&m,Ba[1]=m>>>8&255,c.check=t(c.check,Ba,2,0)),m=0,n=0}else c.head&&(c.head.extra=null);c.mode=P;case P:if(1024&c.flags&&(q=c.length,q>i&&(q=i),q&&(c.head&&(wa=c.head.extra_len-c.length,c.head.extra||(c.head.extra=new Array(c.head.extra_len)),r.arraySet(c.head.extra,e,g,q,wa)),512&c.flags&&(c.check=t(c.check,e,q,g)),i-=q,g+=q,c.length-=q),c.length))break a;c.length=0,c.mode=Q;case Q:if(2048&c.flags){if(0===i)break a;q=0;do wa=e[g+q++],c.head&&wa&&c.length<65536&&(c.head.name+=String.fromCharCode(wa));while(wa&&i>q);if(512&c.flags&&(c.check=t(c.check,e,q,g)),i-=q,g+=q,wa)break a}else c.head&&(c.head.name=null);c.length=0,c.mode=R;case R:if(4096&c.flags){if(0===i)break a;q=0;do wa=e[g+q++],c.head&&wa&&c.length<65536&&(c.head.comment+=String.fromCharCode(wa));while(wa&&i>q);if(512&c.flags&&(c.check=t(c.check,e,q,g)),i-=q,g+=q,wa)break a}else c.head&&(c.head.comment=null);c.mode=S;case S:if(512&c.flags){for(;16>n;){
-if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(m!==(65535&c.check)){a.msg="header crc mismatch",c.mode=la;break}m=0,n=0}c.head&&(c.head.hcrc=c.flags>>9&1,c.head.done=!0),a.adler=c.check=0,c.mode=V;break;case T:for(;32>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}a.adler=c.check=d(m),m=0,n=0,c.mode=U;case U:if(0===c.havedict)return a.next_out=h,a.avail_out=j,a.next_in=g,a.avail_in=i,c.hold=m,c.bits=n,E;a.adler=c.check=1,c.mode=V;case V:if(b===A||b===B)break a;case W:if(c.last){m>>>=7&n,n-=7&n,c.mode=ia;break}for(;3>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}switch(c.last=1&m,m>>>=1,n-=1,3&m){case 0:c.mode=X;break;case 1:if(k(c),c.mode=ba,b===B){m>>>=2,n-=2;break a}break;case 2:c.mode=$;break;case 3:a.msg="invalid block type",c.mode=la}m>>>=2,n-=2;break;case X:for(m>>>=7&n,n-=7&n;32>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if((65535&m)!==(m>>>16^65535)){a.msg="invalid stored block lengths",c.mode=la;break}if(c.length=65535&m,m=0,n=0,c.mode=Y,b===B)break a;case Y:c.mode=Z;case Z:if(q=c.length){if(q>i&&(q=i),q>j&&(q=j),0===q)break a;r.arraySet(f,e,g,q,h),i-=q,g+=q,j-=q,h+=q,c.length-=q;break}c.mode=V;break;case $:for(;14>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(c.nlen=(31&m)+257,m>>>=5,n-=5,c.ndist=(31&m)+1,m>>>=5,n-=5,c.ncode=(15&m)+4,m>>>=4,n-=4,c.nlen>286||c.ndist>30){a.msg="too many length or distance symbols",c.mode=la;break}c.have=0,c.mode=_;case _:for(;c.have<c.ncode;){for(;3>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.lens[Ca[c.have++]]=7&m,m>>>=3,n-=3}for(;c.have<19;)c.lens[Ca[c.have++]]=0;if(c.lencode=c.lendyn,c.lenbits=7,ya={bits:c.lenbits},xa=v(w,c.lens,0,19,c.lencode,0,c.work,ya),c.lenbits=ya.bits,xa){a.msg="invalid code lengths set",c.mode=la;break}c.have=0,c.mode=aa;case aa:for(;c.have<c.nlen+c.ndist;){for(;Aa=c.lencode[m&(1<<c.lenbits)-1],qa=Aa>>>24,ra=Aa>>>16&255,sa=65535&Aa,!(n>=qa);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(16>sa)m>>>=qa,n-=qa,c.lens[c.have++]=sa;else{if(16===sa){for(za=qa+2;za>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(m>>>=qa,n-=qa,0===c.have){a.msg="invalid bit length repeat",c.mode=la;break}wa=c.lens[c.have-1],q=3+(3&m),m>>>=2,n-=2}else if(17===sa){for(za=qa+3;za>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}m>>>=qa,n-=qa,wa=0,q=3+(7&m),m>>>=3,n-=3}else{for(za=qa+7;za>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}m>>>=qa,n-=qa,wa=0,q=11+(127&m),m>>>=7,n-=7}if(c.have+q>c.nlen+c.ndist){a.msg="invalid bit length repeat",c.mode=la;break}for(;q--;)c.lens[c.have++]=wa}}if(c.mode===la)break;if(0===c.lens[256]){a.msg="invalid code -- missing end-of-block",c.mode=la;break}if(c.lenbits=9,ya={bits:c.lenbits},xa=v(x,c.lens,0,c.nlen,c.lencode,0,c.work,ya),c.lenbits=ya.bits,xa){a.msg="invalid literal/lengths set",c.mode=la;break}if(c.distbits=6,c.distcode=c.distdyn,ya={bits:c.distbits},xa=v(y,c.lens,c.nlen,c.ndist,c.distcode,0,c.work,ya),c.distbits=ya.bits,xa){a.msg="invalid distances set",c.mode=la;break}if(c.mode=ba,b===B)break a;case ba:c.mode=ca;case ca:if(i>=6&&j>=258){a.next_out=h,a.avail_out=j,a.next_in=g,a.avail_in=i,c.hold=m,c.bits=n,u(a,p),h=a.next_out,f=a.output,j=a.avail_out,g=a.next_in,e=a.input,i=a.avail_in,m=c.hold,n=c.bits,c.mode===V&&(c.back=-1);break}for(c.back=0;Aa=c.lencode[m&(1<<c.lenbits)-1],qa=Aa>>>24,ra=Aa>>>16&255,sa=65535&Aa,!(n>=qa);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(ra&&0===(240&ra)){for(ta=qa,ua=ra,va=sa;Aa=c.lencode[va+((m&(1<<ta+ua)-1)>>ta)],qa=Aa>>>24,ra=Aa>>>16&255,sa=65535&Aa,!(n>=ta+qa);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}m>>>=ta,n-=ta,c.back+=ta}if(m>>>=qa,n-=qa,c.back+=qa,c.length=sa,0===ra){c.mode=ha;break}if(32&ra){c.back=-1,c.mode=V;break}if(64&ra){a.msg="invalid literal/length code",c.mode=la;break}c.extra=15&ra,c.mode=da;case da:if(c.extra){for(za=c.extra;za>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.length+=m&(1<<c.extra)-1,m>>>=c.extra,n-=c.extra,c.back+=c.extra}c.was=c.length,c.mode=ea;case ea:for(;Aa=c.distcode[m&(1<<c.distbits)-1],qa=Aa>>>24,ra=Aa>>>16&255,sa=65535&Aa,!(n>=qa);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(0===(240&ra)){for(ta=qa,ua=ra,va=sa;Aa=c.distcode[va+((m&(1<<ta+ua)-1)>>ta)],qa=Aa>>>24,ra=Aa>>>16&255,sa=65535&Aa,!(n>=ta+qa);){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}m>>>=ta,n-=ta,c.back+=ta}if(m>>>=qa,n-=qa,c.back+=qa,64&ra){a.msg="invalid distance code",c.mode=la;break}c.offset=sa,c.extra=15&ra,c.mode=fa;case fa:if(c.extra){for(za=c.extra;za>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}c.offset+=m&(1<<c.extra)-1,m>>>=c.extra,n-=c.extra,c.back+=c.extra}if(c.offset>c.dmax){a.msg="invalid distance too far back",c.mode=la;break}c.mode=ga;case ga:if(0===j)break a;if(q=p-j,c.offset>q){if(q=c.offset-q,q>c.whave&&c.sane){a.msg="invalid distance too far back",c.mode=la;break}q>c.wnext?(q-=c.wnext,oa=c.wsize-q):oa=c.wnext-q,q>c.length&&(q=c.length),pa=c.window}else pa=f,oa=h-c.offset,q=c.length;q>j&&(q=j),j-=q,c.length-=q;do f[h++]=pa[oa++];while(--q);0===c.length&&(c.mode=ca);break;case ha:if(0===j)break a;f[h++]=c.length,j--,c.mode=ca;break;case ia:if(c.wrap){for(;32>n;){if(0===i)break a;i--,m|=e[g++]<<n,n+=8}if(p-=j,a.total_out+=p,c.total+=p,p&&(a.adler=c.check=c.flags?t(c.check,f,p,h-p):s(c.check,f,p,h-p)),p=j,(c.flags?m:d(m))!==c.check){a.msg="incorrect data check",c.mode=la;break}m=0,n=0}c.mode=ja;case ja:if(c.wrap&&c.flags){for(;32>n;){if(0===i)break a;i--,m+=e[g++]<<n,n+=8}if(m!==(4294967295&c.total)){a.msg="incorrect length check",c.mode=la;break}m=0,n=0}c.mode=ka;case ka:xa=D;break a;case la:xa=G;break a;case ma:return H;case na:default:return F}return a.next_out=h,a.avail_out=j,a.next_in=g,a.avail_in=i,c.hold=m,c.bits=n,(c.wsize||p!==a.avail_out&&c.mode<la&&(c.mode<ia||b!==z))&&l(a,a.output,a.next_out,p-a.avail_out)?(c.mode=ma,H):(o-=a.avail_in,p-=a.avail_out,a.total_in+=o,a.total_out+=p,c.total+=p,c.wrap&&p&&(a.adler=c.check=c.flags?t(c.check,f,p,a.next_out-p):s(c.check,f,p,a.next_out-p)),a.data_type=c.bits+(c.last?64:0)+(c.mode===V?128:0)+(c.mode===ba||c.mode===Y?256:0),(0===o&&0===p||b===z)&&xa===C&&(xa=I),xa)}function n(a){if(!a||!a.state)return F;var b=a.state;return b.window&&(b.window=null),a.state=null,C}function o(a,b){var c;return a&&a.state?(c=a.state,0===(2&c.wrap)?F:(c.head=b,b.done=!1,C)):F}var p,q,r=a("../utils/common"),s=a("./adler32"),t=a("./crc32"),u=a("./inffast"),v=a("./inftrees"),w=0,x=1,y=2,z=4,A=5,B=6,C=0,D=1,E=2,F=-2,G=-3,H=-4,I=-5,J=8,K=1,L=2,M=3,N=4,O=5,P=6,Q=7,R=8,S=9,T=10,U=11,V=12,W=13,X=14,Y=15,Z=16,$=17,_=18,aa=19,ba=20,ca=21,da=22,ea=23,fa=24,ga=25,ha=26,ia=27,ja=28,ka=29,la=30,ma=31,na=32,oa=852,pa=592,qa=15,ra=qa,sa=!0;c.inflateReset=g,c.inflateReset2=h,c.inflateResetKeep=f,c.inflateInit=j,c.inflateInit2=i,c.inflate=m,c.inflateEnd=n,c.inflateGetHeader=o,c.inflateInfo="pako inflate (from Nodeca project)"},{"../utils/common":28,"./adler32":30,"./crc32":32,"./inffast":35,"./inftrees":37}],37:[function(a,b,c){"use strict";var d=a("../utils/common"),e=15,f=852,g=592,h=0,i=1,j=2,k=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],l=[16,16,16,16,16,16,16,16,17,17,17,17,18,18,18,18,19,19,19,19,20,20,20,20,21,21,21,21,16,72,78],m=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0],n=[16,16,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25,26,26,27,27,28,28,29,29,64,64];b.exports=function(a,b,c,o,p,q,r,s){var t,u,v,w,x,y,z,A,B,C=s.bits,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=null,O=0,P=new d.Buf16(e+1),Q=new d.Buf16(e+1),R=null,S=0;for(D=0;e>=D;D++)P[D]=0;for(E=0;o>E;E++)P[b[c+E]]++;for(H=C,G=e;G>=1&&0===P[G];G--);if(H>G&&(H=G),0===G)return p[q++]=20971520,p[q++]=20971520,s.bits=1,0;for(F=1;G>F&&0===P[F];F++);for(F>H&&(H=F),K=1,D=1;e>=D;D++)if(K<<=1,K-=P[D],0>K)return-1;if(K>0&&(a===h||1!==G))return-1;for(Q[1]=0,D=1;e>D;D++)Q[D+1]=Q[D]+P[D];for(E=0;o>E;E++)0!==b[c+E]&&(r[Q[b[c+E]]++]=E);if(a===h?(N=R=r,y=19):a===i?(N=k,O-=257,R=l,S-=257,y=256):(N=m,R=n,y=-1),M=0,E=0,D=F,x=q,I=H,J=0,v=-1,L=1<<H,w=L-1,a===i&&L>f||a===j&&L>g)return 1;for(var T=0;;){T++,z=D-J,r[E]<y?(A=0,B=r[E]):r[E]>y?(A=R[S+r[E]],B=N[O+r[E]]):(A=96,B=0),t=1<<D-J,u=1<<I,F=u;do u-=t,p[x+(M>>J)+u]=z<<24|A<<16|B|0;while(0!==u);for(t=1<<D-1;M&t;)t>>=1;if(0!==t?(M&=t-1,M+=t):M=0,E++,0===--P[D]){if(D===G)break;D=b[c+r[E]]}if(D>H&&(M&w)!==v){for(0===J&&(J=H),x+=F,I=D-J,K=1<<I;G>I+J&&(K-=P[I+J],!(0>=K));)I++,K<<=1;if(L+=1<<I,a===i&&L>f||a===j&&L>g)return 1;v=M&w,p[v]=H<<24|I<<16|x-q|0}}return 0!==M&&(p[x+M]=D-J<<24|64<<16|0),s.bits=H,0}},{"../utils/common":28}],38:[function(a,b,c){"use strict";b.exports={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"}},{}],39:[function(a,b,c){"use strict";function d(a){for(var b=a.length;--b>=0;)a[b]=0}function e(a,b,c,d,e){this.static_tree=a,this.extra_bits=b,this.extra_base=c,this.elems=d,this.max_length=e,this.has_stree=a&&a.length}function f(a,b){this.dyn_tree=a,this.max_code=0,this.stat_desc=b}function g(a){return 256>a?ia[a]:ia[256+(a>>>7)]}function h(a,b){a.pending_buf[a.pending++]=255&b,a.pending_buf[a.pending++]=b>>>8&255}function i(a,b,c){a.bi_valid>X-c?(a.bi_buf|=b<<a.bi_valid&65535,h(a,a.bi_buf),a.bi_buf=b>>X-a.bi_valid,a.bi_valid+=c-X):(a.bi_buf|=b<<a.bi_valid&65535,a.bi_valid+=c)}function j(a,b,c){i(a,c[2*b],c[2*b+1])}function k(a,b){var c=0;do c|=1&a,a>>>=1,c<<=1;while(--b>0);return c>>>1}function l(a){16===a.bi_valid?(h(a,a.bi_buf),a.bi_buf=0,a.bi_valid=0):a.bi_valid>=8&&(a.pending_buf[a.pending++]=255&a.bi_buf,a.bi_buf>>=8,a.bi_valid-=8)}function m(a,b){var c,d,e,f,g,h,i=b.dyn_tree,j=b.max_code,k=b.stat_desc.static_tree,l=b.stat_desc.has_stree,m=b.stat_desc.extra_bits,n=b.stat_desc.extra_base,o=b.stat_desc.max_length,p=0;for(f=0;W>=f;f++)a.bl_count[f]=0;for(i[2*a.heap[a.heap_max]+1]=0,c=a.heap_max+1;V>c;c++)d=a.heap[c],f=i[2*i[2*d+1]+1]+1,f>o&&(f=o,p++),i[2*d+1]=f,d>j||(a.bl_count[f]++,g=0,d>=n&&(g=m[d-n]),h=i[2*d],a.opt_len+=h*(f+g),l&&(a.static_len+=h*(k[2*d+1]+g)));if(0!==p){do{for(f=o-1;0===a.bl_count[f];)f--;a.bl_count[f]--,a.bl_count[f+1]+=2,a.bl_count[o]--,p-=2}while(p>0);for(f=o;0!==f;f--)for(d=a.bl_count[f];0!==d;)e=a.heap[--c],e>j||(i[2*e+1]!==f&&(a.opt_len+=(f-i[2*e+1])*i[2*e],i[2*e+1]=f),d--)}}function n(a,b,c){var d,e,f=new Array(W+1),g=0;for(d=1;W>=d;d++)f[d]=g=g+c[d-1]<<1;for(e=0;b>=e;e++){var h=a[2*e+1];0!==h&&(a[2*e]=k(f[h]++,h))}}function o(){var a,b,c,d,f,g=new Array(W+1);for(c=0,d=0;Q-1>d;d++)for(ka[d]=c,a=0;a<1<<ba[d];a++)ja[c++]=d;for(ja[c-1]=d,f=0,d=0;16>d;d++)for(la[d]=f,a=0;a<1<<ca[d];a++)ia[f++]=d;for(f>>=7;T>d;d++)for(la[d]=f<<7,a=0;a<1<<ca[d]-7;a++)ia[256+f++]=d;for(b=0;W>=b;b++)g[b]=0;for(a=0;143>=a;)ga[2*a+1]=8,a++,g[8]++;for(;255>=a;)ga[2*a+1]=9,a++,g[9]++;for(;279>=a;)ga[2*a+1]=7,a++,g[7]++;for(;287>=a;)ga[2*a+1]=8,a++,g[8]++;for(n(ga,S+1,g),a=0;T>a;a++)ha[2*a+1]=5,ha[2*a]=k(a,5);ma=new e(ga,ba,R+1,S,W),na=new e(ha,ca,0,T,W),oa=new e(new Array(0),da,0,U,Y)}function p(a){var b;for(b=0;S>b;b++)a.dyn_ltree[2*b]=0;for(b=0;T>b;b++)a.dyn_dtree[2*b]=0;for(b=0;U>b;b++)a.bl_tree[2*b]=0;a.dyn_ltree[2*Z]=1,a.opt_len=a.static_len=0,a.last_lit=a.matches=0}function q(a){a.bi_valid>8?h(a,a.bi_buf):a.bi_valid>0&&(a.pending_buf[a.pending++]=a.bi_buf),a.bi_buf=0,a.bi_valid=0}function r(a,b,c,d){q(a),d&&(h(a,c),h(a,~c)),G.arraySet(a.pending_buf,a.window,b,c,a.pending),a.pending+=c}function s(a,b,c,d){var e=2*b,f=2*c;return a[e]<a[f]||a[e]===a[f]&&d[b]<=d[c]}function t(a,b,c){for(var d=a.heap[c],e=c<<1;e<=a.heap_len&&(e<a.heap_len&&s(b,a.heap[e+1],a.heap[e],a.depth)&&e++,!s(b,d,a.heap[e],a.depth));)a.heap[c]=a.heap[e],c=e,e<<=1;a.heap[c]=d}function u(a,b,c){var d,e,f,h,k=0;if(0!==a.last_lit)do d=a.pending_buf[a.d_buf+2*k]<<8|a.pending_buf[a.d_buf+2*k+1],e=a.pending_buf[a.l_buf+k],k++,0===d?j(a,e,b):(f=ja[e],j(a,f+R+1,b),h=ba[f],0!==h&&(e-=ka[f],i(a,e,h)),d--,f=g(d),j(a,f,c),h=ca[f],0!==h&&(d-=la[f],i(a,d,h)));while(k<a.last_lit);j(a,Z,b)}function v(a,b){var c,d,e,f=b.dyn_tree,g=b.stat_desc.static_tree,h=b.stat_desc.has_stree,i=b.stat_desc.elems,j=-1;for(a.heap_len=0,a.heap_max=V,c=0;i>c;c++)0!==f[2*c]?(a.heap[++a.heap_len]=j=c,a.depth[c]=0):f[2*c+1]=0;for(;a.heap_len<2;)e=a.heap[++a.heap_len]=2>j?++j:0,f[2*e]=1,a.depth[e]=0,a.opt_len--,h&&(a.static_len-=g[2*e+1]);for(b.max_code=j,c=a.heap_len>>1;c>=1;c--)t(a,f,c);e=i;do c=a.heap[1],a.heap[1]=a.heap[a.heap_len--],t(a,f,1),d=a.heap[1],a.heap[--a.heap_max]=c,a.heap[--a.heap_max]=d,f[2*e]=f[2*c]+f[2*d],a.depth[e]=(a.depth[c]>=a.depth[d]?a.depth[c]:a.depth[d])+1,f[2*c+1]=f[2*d+1]=e,a.heap[1]=e++,t(a,f,1);while(a.heap_len>=2);a.heap[--a.heap_max]=a.heap[1],m(a,b),n(f,j,a.bl_count)}function w(a,b,c){var d,e,f=-1,g=b[1],h=0,i=7,j=4;for(0===g&&(i=138,j=3),b[2*(c+1)+1]=65535,d=0;c>=d;d++)e=g,g=b[2*(d+1)+1],++h<i&&e===g||(j>h?a.bl_tree[2*e]+=h:0!==e?(e!==f&&a.bl_tree[2*e]++,a.bl_tree[2*$]++):10>=h?a.bl_tree[2*_]++:a.bl_tree[2*aa]++,h=0,f=e,0===g?(i=138,j=3):e===g?(i=6,j=3):(i=7,j=4))}function x(a,b,c){var d,e,f=-1,g=b[1],h=0,k=7,l=4;for(0===g&&(k=138,l=3),d=0;c>=d;d++)if(e=g,g=b[2*(d+1)+1],!(++h<k&&e===g)){if(l>h){do j(a,e,a.bl_tree);while(0!==--h)}else 0!==e?(e!==f&&(j(a,e,a.bl_tree),h--),j(a,$,a.bl_tree),i(a,h-3,2)):10>=h?(j(a,_,a.bl_tree),i(a,h-3,3)):(j(a,aa,a.bl_tree),i(a,h-11,7));h=0,f=e,0===g?(k=138,l=3):e===g?(k=6,l=3):(k=7,l=4)}}function y(a){var b;for(w(a,a.dyn_ltree,a.l_desc.max_code),w(a,a.dyn_dtree,a.d_desc.max_code),v(a,a.bl_desc),b=U-1;b>=3&&0===a.bl_tree[2*ea[b]+1];b--);return a.opt_len+=3*(b+1)+5+5+4,b}function z(a,b,c,d){var e;for(i(a,b-257,5),i(a,c-1,5),i(a,d-4,4),e=0;d>e;e++)i(a,a.bl_tree[2*ea[e]+1],3);x(a,a.dyn_ltree,b-1),x(a,a.dyn_dtree,c-1)}function A(a){var b,c=4093624447;for(b=0;31>=b;b++,c>>>=1)if(1&c&&0!==a.dyn_ltree[2*b])return I;if(0!==a.dyn_ltree[18]||0!==a.dyn_ltree[20]||0!==a.dyn_ltree[26])return J;for(b=32;R>b;b++)if(0!==a.dyn_ltree[2*b])return J;return I}function B(a){pa||(o(),pa=!0),a.l_desc=new f(a.dyn_ltree,ma),a.d_desc=new f(a.dyn_dtree,na),a.bl_desc=new f(a.bl_tree,oa),a.bi_buf=0,a.bi_valid=0,p(a)}function C(a,b,c,d){i(a,(L<<1)+(d?1:0),3),r(a,b,c,!0)}function D(a){i(a,M<<1,3),j(a,Z,ga),l(a)}function E(a,b,c,d){var e,f,g=0;a.level>0?(a.strm.data_type===K&&(a.strm.data_type=A(a)),v(a,a.l_desc),v(a,a.d_desc),g=y(a),e=a.opt_len+3+7>>>3,f=a.static_len+3+7>>>3,e>=f&&(e=f)):e=f=c+5,e>=c+4&&-1!==b?C(a,b,c,d):a.strategy===H||f===e?(i(a,(M<<1)+(d?1:0),3),u(a,ga,ha)):(i(a,(N<<1)+(d?1:0),3),z(a,a.l_desc.max_code+1,a.d_desc.max_code+1,g+1),u(a,a.dyn_ltree,a.dyn_dtree)),p(a),d&&q(a)}function F(a,b,c){return a.pending_buf[a.d_buf+2*a.last_lit]=b>>>8&255,a.pending_buf[a.d_buf+2*a.last_lit+1]=255&b,a.pending_buf[a.l_buf+a.last_lit]=255&c,a.last_lit++,0===b?a.dyn_ltree[2*c]++:(a.matches++,b--,a.dyn_ltree[2*(ja[c]+R+1)]++,a.dyn_dtree[2*g(b)]++),a.last_lit===a.lit_bufsize-1}var G=a("../utils/common"),H=4,I=0,J=1,K=2,L=0,M=1,N=2,O=3,P=258,Q=29,R=256,S=R+1+Q,T=30,U=19,V=2*S+1,W=15,X=16,Y=7,Z=256,$=16,_=17,aa=18,ba=[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],ca=[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],da=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],ea=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],fa=512,ga=new Array(2*(S+2));d(ga);var ha=new Array(2*T);d(ha);var ia=new Array(fa);d(ia);var ja=new Array(P-O+1);d(ja);var ka=new Array(Q);d(ka);var la=new Array(T);d(la);var ma,na,oa,pa=!1;c._tr_init=B,c._tr_stored_block=C,c._tr_flush_block=E,c._tr_tally=F,c._tr_align=D},{"../utils/common":28}],40:[function(a,b,c){"use strict";function d(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}b.exports=d},{}]},{},[10])(10)});
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer)
-},{"buffer":194}],252:[function(require,module,exports){
+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 : {})
+},{}],282:[function(require,module,exports){
 /**
  * Helpers.
  */
@@ -68831,7 +50423,7 @@
   return Math.ceil(ms / n) + ' ' + name + 's';
 }
 
-},{}],253:[function(require,module,exports){
+},{}],283:[function(require,module,exports){
 (function (Buffer){
 'use strict';
 
@@ -68995,7 +50587,7 @@
 };
 
 }).call(this,require("buffer").Buffer)
-},{"buffer":194,"jpeg-js":248}],254:[function(require,module,exports){
+},{"buffer":200,"jpeg-js":278}],284:[function(require,module,exports){
 'use strict';
 
 const frame = require('./frame');
@@ -69033,7 +50625,7 @@
 	});
 };
 
-},{"./frame":253,"./speed-index":255}],255:[function(require,module,exports){
+},{"./frame":283,"./speed-index":285}],285:[function(require,module,exports){
 'use strict';
 
 const imageSSIM = require('image-ssim');
@@ -69176,64 +50768,68 @@
 	calculateSpeedIndexes
 };
 
-},{"image-ssim":247}],256:[function(require,module,exports){
+},{"image-ssim":277}],286:[function(require,module,exports){
 module.exports={
   "name": "lighthouse",
-  "version": "1.1.6",
+  "version": "1.4.1",
   "description": "Lighthouse",
   "main": "./lighthouse-core/index.js",
   "bin": {
     "lighthouse": "./lighthouse-cli/index.js",
-    "chrome-debug": "lighthouse-core/scripts/launch-chrome.sh"
+    "chrome-debug": "./lighthouse-cli/manual-chrome-launcher.js"
   },
   "engines": {
     "node": ">=5"
   },
   "scripts": {
-    "lint": "[ \"$CI\" = true ] && eslint --quiet . || eslint .",
-    "smoke": "lighthouse-cli/test/smokehouse/offline-local/run-tests.sh",
+    "lint": "[ \"$CI\" = true ] && eslint --quiet -f codeframe . || eslint .",
+    "smoke": "lighthouse-cli/test/smokehouse/offline-local/run-tests.sh && lighthouse-cli/test/smokehouse/dobetterweb/run-tests.sh",
     "coverage": "node $__node_harmony $(npm bin)/istanbul cover -x \"**/third_party/**\" _mocha -- $(find */test -name '*-test.js') --timeout 10000 --reporter progress",
     "coveralls": "npm run coverage && cat ./coverage/lcov.info | coveralls",
     "start": "node ./lighthouse-cli/index.js",
     "test": "npm run lint --silent && npm run unit",
     "cli-unit": "lighthouse-core/scripts/run-mocha.sh --cli",
+    "viewer-unit": "lighthouse-core/scripts/run-mocha.sh --viewer",
     "unit": "lighthouse-core/scripts/run-mocha.sh --default",
     "closure": "cd lighthouse-core && closure/closure-type-checking.js",
     "watch": "lighthouse-core/scripts/run-mocha.sh --watch",
-    "chrome": "lighthouse-core/scripts/launch-chrome.sh",
-    "dbw": "npm run start -- --config-path=lighthouse-core/config/dobetterweb.json --mobile=false --output=html --output-path=results.html",
-    "smokehouse": "node $__node_harmony lighthouse-cli/test/smokehouse/smokehouse.js"
+    "chrome": "./lighthouse-cli/manual-chrome-launcher.js",
+    "fast": "npm run start -- --disable-device-emulation --disable-cpu-throttling --disable-network-throttling",
+    "smokehouse": "node $__node_harmony lighthouse-cli/test/smokehouse/smokehouse.js",
+    "deploy-viewer": "cd lighthouse-viewer && gulp deploy"
   },
   "devDependencies": {
+    "@types/node": "^6.0.45",
     "babel-core": "^6.16.0",
     "babel-plugin-transform-es2015-destructuring": "^6.9.0",
     "coveralls": "^2.11.9",
-    "eslint": "^2.4.0",
-    "eslint-config-google": "^0.4.0",
-    "google-closure-compiler": "^20160517.0.0",
+    "eslint": "^3.12.0",
+    "eslint-config-google": "^0.7.1",
+    "google-closure-compiler": "^20161201.0.0",
     "gulp": "^3.9.1",
     "gulp-replace": "^0.5.4",
     "gulp-util": "^3.0.7",
     "istanbul": "^0.4.3",
     "jsdom": "^9.0.0",
-    "mkdirp": "^0.5.1",
     "mocha": "^2.3.3",
-    "request": "^2.69.0",
-    "walk": "^2.3.9"
+    "walk": "^2.3.9",
+    "zone.js": "^0.7.3"
   },
   "dependencies": {
-    "axe-core": "^1.1.1",
+    "axe-core": "2.1.7",
     "chrome-devtools-frontend": "1.0.422034",
     "debug": "^2.2.0",
     "devtools-timeline-model": "1.1.6",
     "gl-matrix": "2.3.2",
     "handlebars": "^4.0.5",
     "json-stringify-safe": "^5.0.1",
-    "jszip": "2.6.0",
+    "marked": "^0.3.6",
     "mkdirp": "^0.5.1",
+    "opn": "^4.0.2",
     "rimraf": "^2.2.8",
     "semver": ">=4.3.3",
     "speedline": "1.0.3",
+    "whatwg-url": "^4.0.0",
     "ws": "^1.1.1",
     "yargs": "3.30.0"
   },
@@ -69251,4 +50847,4 @@
   "homepage": "https://github.com/googlechrome/lighthouse#readme"
 }
 
-},{}]},{},[191]);
+},{}]},{},[197]);
diff --git a/third_party/WebKit/Source/devtools/front_end/data_grid/ViewportDataGrid.js b/third_party/WebKit/Source/devtools/front_end/data_grid/ViewportDataGrid.js
index 200c37a2..581eba91 100644
--- a/third_party/WebKit/Source/devtools/front_end/data_grid/ViewportDataGrid.js
+++ b/third_party/WebKit/Source/devtools/front_end/data_grid/ViewportDataGrid.js
@@ -315,11 +315,14 @@
     return this._isStriped;
   }
 
-  _clearFlatNodes() {
+  /**
+   * @protected
+   */
+  clearFlatNodes() {
     this._flatNodes = null;
     var parent = /** @type {!DataGrid.ViewportDataGridNode} */ (this.parent);
     if (parent)
-      parent._clearFlatNodes();
+      parent.clearFlatNodes();
   }
 
   /**
@@ -359,7 +362,7 @@
    * @param {number} index
    */
   insertChild(child, index) {
-    this._clearFlatNodes();
+    this.clearFlatNodes();
     if (child.parent === this) {
       var currentIndex = this.children.indexOf(child);
       if (currentIndex < 0)
@@ -385,7 +388,7 @@
    * @param {!NODE_TYPE} child
    */
   removeChild(child) {
-    this._clearFlatNodes();
+    this.clearFlatNodes();
     if (this.dataGrid)
       this.dataGrid.updateSelectionBeforeRemoval(child, false);
     if (child.previousSibling)
@@ -407,7 +410,7 @@
    * @override
    */
   removeChildren() {
-    this._clearFlatNodes();
+    this.clearFlatNodes();
     if (this.dataGrid)
       this.dataGrid.updateSelectionBeforeRemoval(this, true);
     for (var i = 0; i < this.children.length; ++i)
@@ -435,7 +438,7 @@
   collapse() {
     if (!this._expanded)
       return;
-    this._clearFlatNodes();
+    this.clearFlatNodes();
     this._expanded = false;
     if (this.existingElement())
       this.existingElement().classList.remove('expanded');
@@ -448,7 +451,7 @@
   expand() {
     if (this._expanded)
       return;
-    this._clearFlatNodes();
+    this.clearFlatNodes();
     super.expand();
     this.dataGrid.scheduleUpdateStructure();
   }
@@ -502,7 +505,7 @@
    * @param {number} index
    */
   recalculateSiblings(index) {
-    this._clearFlatNodes();
+    this.clearFlatNodes();
     super.recalculateSiblings(index);
   }
 };
diff --git a/third_party/WebKit/Source/devtools/front_end/network/NetworkDataGridNode.js b/third_party/WebKit/Source/devtools/front_end/network/NetworkDataGridNode.js
index 6e64b55e..e5444aaf 100644
--- a/third_party/WebKit/Source/devtools/front_end/network/NetworkDataGridNode.js
+++ b/third_party/WebKit/Source/devtools/front_end/network/NetworkDataGridNode.js
@@ -40,6 +40,8 @@
     this._parentView = parentView;
     this._isHovered = false;
     this._showingInitiatorChain = false;
+    /** @type {?SDK.NetworkRequest} */
+    this._requestOrFirstKnownChildRequest = null;
   }
 
   /**
@@ -130,6 +132,45 @@
   asRequestNode() {
     return null;
   }
+
+  /**
+   * @return {?Network.NetworkGroupNode}
+   */
+  asGroupNode() {
+    return null;
+  }
+
+  /**
+   * @override
+   */
+  clearFlatNodes() {
+    super.clearFlatNodes();
+    this._requestOrFirstKnownChildRequest = null;
+  }
+
+  /**
+   * @protected
+   * @return {?SDK.NetworkRequest}
+   */
+  requestOrFirstKnownChildRequest() {
+    if (this._requestOrFirstKnownChildRequest)
+      return this._requestOrFirstKnownChildRequest;
+    var request = this.request();
+    if (request || !this.hasChildren()) {
+      this._requestOrFirstKnownChildRequest = request;
+      return this._requestOrFirstKnownChildRequest;
+    }
+
+    var firstChildRequest = null;
+    var flatChildren = this.flatChildren();
+    for (var i = 0; i < flatChildren.length; i++) {
+      request = flatChildren[i].request();
+      if (!firstChildRequest || (request && request.issueTime() < firstChildRequest.issueTime()))
+        firstChildRequest = request;
+    }
+    this._requestOrFirstKnownChildRequest = firstChildRequest;
+    return this._requestOrFirstKnownChildRequest;
+  }
 };
 
 /**
@@ -155,19 +196,30 @@
    * @return {number}
    */
   static NameComparator(a, b) {
-    // TODO(allada) Handle this properly for group nodes.
-    var aRequest = a.request();
-    var bRequest = b.request();
-    if (!aRequest || !bRequest)
-      return !aRequest ? -1 : 1;
+    var aGroupNode = a.asGroupNode();
+    var bGroupNode = b.asGroupNode();
 
-    var aFileName = aRequest.name();
-    var bFileName = bRequest.name();
-    if (aFileName > bFileName)
-      return 1;
-    if (bFileName > aFileName)
-      return -1;
-    return aRequest.indentityCompare(bRequest);
+    if ((!aGroupNode && bGroupNode) || (aGroupNode && !bGroupNode))
+      return aGroupNode ? 1 : -1;
+
+    var aName;
+    var bName;
+    if (aGroupNode && bGroupNode) {
+      aName = aGroupNode.displayName();
+      bName = bGroupNode.displayName();
+      if (aName === bName)
+        return Network.NetworkRequestNode.RequestPropertyComparator('startTime', a, b);
+    } else {
+      var aRequest = a.requestOrFirstKnownChildRequest();
+      var bRequest = b.requestOrFirstKnownChildRequest();
+      if (!aRequest || !bRequest)
+        return aRequest ? 1 : -1;
+      aName = aRequest.name();
+      bName = bRequest.name();
+      if (aName === bName)
+        return aRequest.indentityCompare(bRequest);
+    }
+    return aName < bName ? -1 : 1;
   }
 
   /**
@@ -177,8 +229,8 @@
    */
   static RemoteAddressComparator(a, b) {
     // TODO(allada) Handle this properly for group nodes.
-    var aRequest = a.request();
-    var bRequest = b.request();
+    var aRequest = a.requestOrFirstKnownChildRequest();
+    var bRequest = b.requestOrFirstKnownChildRequest();
     if (!aRequest || !bRequest)
       return !aRequest ? -1 : 1;
     var aRemoteAddress = aRequest.remoteAddress();
@@ -197,8 +249,8 @@
    */
   static SizeComparator(a, b) {
     // TODO(allada) Handle this properly for group nodes.
-    var aRequest = a.request();
-    var bRequest = b.request();
+    var aRequest = a.requestOrFirstKnownChildRequest();
+    var bRequest = b.requestOrFirstKnownChildRequest();
     if (!aRequest || !bRequest)
       return !aRequest ? -1 : 1;
     if (bRequest.cached() && !aRequest.cached())
@@ -215,8 +267,8 @@
    */
   static TypeComparator(a, b) {
     // TODO(allada) Handle this properly for group nodes.
-    var aRequest = a.request();
-    var bRequest = b.request();
+    var aRequest = a.requestOrFirstKnownChildRequest();
+    var bRequest = b.requestOrFirstKnownChildRequest();
     if (!aRequest || !bRequest)
       return !aRequest ? -1 : 1;
     var aSimpleType = a.asRequestNode().displayType();
@@ -236,8 +288,8 @@
    */
   static InitiatorComparator(a, b) {
     // TODO(allada) Handle this properly for group nodes.
-    var aRequest = a.request();
-    var bRequest = b.request();
+    var aRequest = a.requestOrFirstKnownChildRequest();
+    var bRequest = b.requestOrFirstKnownChildRequest();
     if (!aRequest || !bRequest)
       return !aRequest ? -1 : 1;
     var aInitiator = SDK.NetworkLog.initiatorInfoForRequest(aRequest);
@@ -278,8 +330,8 @@
    */
   static RequestCookiesCountComparator(a, b) {
     // TODO(allada) Handle this properly for group nodes.
-    var aRequest = a.request();
-    var bRequest = b.request();
+    var aRequest = a.requestOrFirstKnownChildRequest();
+    var bRequest = b.requestOrFirstKnownChildRequest();
     if (!aRequest || !bRequest)
       return !aRequest ? -1 : 1;
     var aScore = aRequest.requestCookies ? aRequest.requestCookies.length : 0;
@@ -295,8 +347,8 @@
    */
   static ResponseCookiesCountComparator(a, b) {
     // TODO(allada) Handle this properly for group nodes.
-    var aRequest = a.request();
-    var bRequest = b.request();
+    var aRequest = a.requestOrFirstKnownChildRequest();
+    var bRequest = b.requestOrFirstKnownChildRequest();
     if (!aRequest || !bRequest)
       return !aRequest ? -1 : 1;
     var aScore = aRequest.responseCookies ? aRequest.responseCookies.length : 0;
@@ -311,8 +363,8 @@
    */
   static InitialPriorityComparator(a, b) {
     // TODO(allada) Handle this properly for group nodes.
-    var aRequest = a.request();
-    var bRequest = b.request();
+    var aRequest = a.requestOrFirstKnownChildRequest();
+    var bRequest = b.requestOrFirstKnownChildRequest();
     if (!aRequest || !bRequest)
       return !aRequest ? -1 : 1;
     var priorityMap = Components.prioritySymbolToNumericMap();
@@ -333,9 +385,8 @@
    * @return {number}
    */
   static RequestPropertyComparator(propertyName, a, b) {
-    // TODO(allada) Handle this properly for group nodes.
-    var aRequest = a.request();
-    var bRequest = b.request();
+    var aRequest = a.requestOrFirstKnownChildRequest();
+    var bRequest = b.requestOrFirstKnownChildRequest();
     if (!aRequest || !bRequest)
       return !aRequest ? -1 : 1;
     var aValue = aRequest[propertyName];
@@ -353,8 +404,8 @@
    */
   static ResponseHeaderStringComparator(propertyName, a, b) {
     // TODO(allada) Handle this properly for group nodes.
-    var aRequest = a.request();
-    var bRequest = b.request();
+    var aRequest = a.requestOrFirstKnownChildRequest();
+    var bRequest = b.requestOrFirstKnownChildRequest();
     if (!aRequest || !bRequest)
       return !aRequest ? -1 : 1;
     var aValue = String(aRequest.responseHeaderValue(propertyName) || '');
@@ -370,8 +421,8 @@
    */
   static ResponseHeaderNumberComparator(propertyName, a, b) {
     // TODO(allada) Handle this properly for group nodes.
-    var aRequest = a.request();
-    var bRequest = b.request();
+    var aRequest = a.requestOrFirstKnownChildRequest();
+    var bRequest = b.requestOrFirstKnownChildRequest();
     if (!aRequest || !bRequest)
       return !aRequest ? -1 : 1;
     var aValue = (aRequest.responseHeaderValue(propertyName) !== undefined) ?
@@ -393,8 +444,8 @@
    */
   static ResponseHeaderDateComparator(propertyName, a, b) {
     // TODO(allada) Handle this properly for group nodes.
-    var aRequest = a.request();
-    var bRequest = b.request();
+    var aRequest = a.requestOrFirstKnownChildRequest();
+    var bRequest = b.requestOrFirstKnownChildRequest();
     if (!aRequest || !bRequest)
       return !aRequest ? -1 : 1;
     var aHeader = aRequest.responseHeaderValue(propertyName);
@@ -856,11 +907,25 @@
   constructor(parentView, displayName, sortKey) {
     super(parentView);
     this._displayName = displayName;
-    // TODO(allada) This is here because you can always sort by _name. This class deserves it's own sorting functions.
     this._name = sortKey;
   }
 
   /**
+   * @override
+   * @return {?Network.NetworkGroupNode}
+   */
+  asGroupNode() {
+    return this;
+  }
+
+  /**
+   * @return {string}
+   */
+  displayName() {
+    return this._displayName;
+  }
+
+  /**
    * @param {!Element} element
    * @param {string} text
    */
diff --git a/third_party/WebKit/Source/devtools/front_end/profiler/HeapProfilerPanel.js b/third_party/WebKit/Source/devtools/front_end/profiler/HeapProfilerPanel.js
index 49c7d66..0be71254 100644
--- a/third_party/WebKit/Source/devtools/front_end/profiler/HeapProfilerPanel.js
+++ b/third_party/WebKit/Source/devtools/front_end/profiler/HeapProfilerPanel.js
@@ -11,7 +11,10 @@
     var registry = Profiler.ProfileTypeRegistry.instance;
     super(
         'heap_profiler',
-        [registry.heapSnapshotProfileType, registry.samplingHeapProfileType, registry.trackingHeapSnapshotProfileType],
+        [
+          registry.cpuProfileType, registry.heapSnapshotProfileType, registry.samplingHeapProfileType,
+          registry.trackingHeapSnapshotProfileType
+        ],
         'profiler.heap-toggle-recording');
   }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/profiler/module.json b/third_party/WebKit/Source/devtools/front_end/profiler/module.json
index 54351bc6f..e7d7a81 100644
--- a/third_party/WebKit/Source/devtools/front_end/profiler/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/profiler/module.json
@@ -4,30 +4,11 @@
             "type": "view",
             "location": "panel",
             "id": "heap_profiler",
-            "title": "Memory",
+            "title": "Profiles",
             "order": 60,
             "className": "Profiler.HeapProfilerPanel"
         },
         {
-            "type": "view",
-            "location": "panel",
-            "id": "js_profiler",
-            "title": "Profiler",
-            "order": 65,
-            "className": "Profiler.JSProfilerPanel",
-            "condition": "v8only"
-        },
-        {
-            "type": "view",
-            "location": "panel",
-            "id": "js_profiler",
-            "title": "JavaScript Profiler",
-            "persistence": "closeable",
-            "order": 65,
-            "className": "Profiler.JSProfilerPanel",
-            "condition": "!v8only"
-        },
-        {
             "type": "@UI.ContextMenu.Provider",
             "contextTypes": [
                 "SDK.RemoteObject"
diff --git a/third_party/WebKit/Source/devtools/front_end/profiler/profilesPanel.css b/third_party/WebKit/Source/devtools/front_end/profiler/profilesPanel.css
index 2ef347e..b960191 100644
--- a/third_party/WebKit/Source/devtools/front_end/profiler/profilesPanel.css
+++ b/third_party/WebKit/Source/devtools/front_end/profiler/profilesPanel.css
@@ -171,7 +171,6 @@
     margin: 0 0 0 10px !important;
 }
 
-.js_profiler.panel select.chrome-select,
 .heap_profiler.panel select.chrome-select
  {
     font-size: 12px;
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBModel.js b/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBModel.js
index b336ebf..c8bbc665 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBModel.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBModel.js
@@ -177,6 +177,19 @@
     this._addOrigin(origin);
   }
 
+  /**
+   * @param {!Resources.IndexedDBModel.DatabaseId} databaseId
+   */
+  deleteDatabase(databaseId) {
+    if (!this._enabled)
+      return;
+    this._agent.deleteDatabase(databaseId.securityOrigin, databaseId.name, error => {
+      if (error)
+        console.error('Unable to delete ' + databaseId.name, error);
+      this._loadDatabaseNames(databaseId.securityOrigin);
+    });
+  }
+
   refreshDatabaseNames() {
     for (var securityOrigin in this._databaseNamesBySecurityOrigin)
       this._loadDatabaseNames(securityOrigin);
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBViews.js b/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBViews.js
index 9c4c429f..79978b43 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBViews.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/IndexedDBViews.js
@@ -33,38 +33,32 @@
  */
 Resources.IDBDatabaseView = class extends UI.VBox {
   /**
+   * @param {!Resources.IndexedDBModel} model
    * @param {!Resources.IndexedDBModel.Database} database
    */
-  constructor(database) {
+  constructor(model, database) {
     super();
-    this.registerRequiredCSS('resources/indexedDBViews.css');
 
-    this.element.classList.add('indexed-db-database-view');
-    this.element.classList.add('storage-view');
+    this._model = model;
 
-    this._securityOriginElement = this.element.createChild('div', 'header-row');
-    this._nameElement = this.element.createChild('div', 'header-row');
-    this._versionElement = this.element.createChild('div', 'header-row');
+    this._reportView = new UI.ReportView(database.databaseId.name);
+    this._reportView.show(this.contentElement);
+
+    var bodySection = this._reportView.appendSection('');
+    this._securityOriginElement = bodySection.appendField(Common.UIString('Security origin'));
+    this._versionElement = bodySection.appendField(Common.UIString('Version'));
+
+    var footer = this._reportView.appendSection('').appendRow();
+    this._clearButton = UI.createTextButton(
+        Common.UIString('Delete database'), () => this._deleteDatabase(), Common.UIString('Delete database'));
+    footer.appendChild(this._clearButton);
 
     this.update(database);
   }
 
-  /**
-   * @param {!Element} element
-   * @param {string} name
-   * @param {string} value
-   */
-  _formatHeader(element, name, value) {
-    element.removeChildren();
-    element.createChild('div', 'attribute-name').textContent = name + ':';
-    element.createChild('div', 'attribute-value source-code').textContent = value;
-  }
-
   _refreshDatabase() {
-    this._formatHeader(
-        this._securityOriginElement, Common.UIString('Security origin'), this._database.databaseId.securityOrigin);
-    this._formatHeader(this._nameElement, Common.UIString('Name'), this._database.databaseId.name);
-    this._formatHeader(this._versionElement, Common.UIString('Version'), this._database.version);
+    this._securityOriginElement.textContent = this._database.databaseId.securityOrigin;
+    this._versionElement.textContent = this._database.version;
   }
 
   /**
@@ -74,6 +68,12 @@
     this._database = database;
     this._refreshDatabase();
   }
+
+  _deleteDatabase() {
+    UI.ConfirmDialog.show(
+        Common.UIString('Are you sure you want to delete "%s"?', this._database.databaseId.name),
+        () => this._model.deleteDatabase(this._database.databaseId));
+  }
 };
 
 /**
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/ResourcesPanel.js b/third_party/WebKit/Source/devtools/front_end/resources/ResourcesPanel.js
index f2efc63..867b1534 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/ResourcesPanel.js
+++ b/third_party/WebKit/Source/devtools/front_end/resources/ResourcesPanel.js
@@ -1671,7 +1671,7 @@
   onselect(selectedByUser) {
     super.onselect(selectedByUser);
     if (!this._view)
-      this._view = new Resources.IDBDatabaseView(this._database);
+      this._view = new Resources.IDBDatabaseView(this._model, this._database);
 
     this._storagePanel._innerShowView(this._view);
     return false;
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/indexedDBViews.css b/third_party/WebKit/Source/devtools/front_end/resources/indexedDBViews.css
index 596a2cf5..ce90b8c8 100644
--- a/third_party/WebKit/Source/devtools/front_end/resources/indexedDBViews.css
+++ b/third_party/WebKit/Source/devtools/front_end/resources/indexedDBViews.css
@@ -33,24 +33,6 @@
     margin-top: 5px;
 }
 
-.indexed-db-database-view .header-row {
-    white-space: nowrap;
-    margin: 0 0 2px 10px;
-}
-
-.indexed-db-database-view .header-row .attribute-name {
-    color: rgb(33%, 33%, 33%);
-    display: inline-block;
-    margin-right: 0.5em;
-    font-weight: bold;
-    vertical-align: top;
-}
-
-.indexed-db-database-view .header-row .attribute-value {
-    display: inline;
-    margin-top: 1px;
-}
-
 .indexed-db-data-view .data-view-toolbar {
     position: relative;
     background-color: #eee;
diff --git a/third_party/WebKit/Source/devtools/front_end/timeline/TimelinePanel.js b/third_party/WebKit/Source/devtools/front_end/timeline/TimelinePanel.js
index 036bdaee..701e1b9 100644
--- a/third_party/WebKit/Source/devtools/front_end/timeline/TimelinePanel.js
+++ b/third_party/WebKit/Source/devtools/front_end/timeline/TimelinePanel.js
@@ -680,34 +680,20 @@
     var centered = this._landingPage.contentElement.createChild('div');
 
     var p = centered.createChild('p');
-    p.createTextChild(Common.UIString(
-        'The Performance panel lets you record what the browser does during page load and user interaction. ' +
-        'The timeline it generates can help you determine why certain parts of your page are slow.'));
-
-    p = centered.createChild('p');
     p.appendChild(UI.formatLocalized(
         'To capture a new recording, click the record toolbar button or hit %s. ' +
-        'To evaluate page load performance, hit %s to record the reload.',
+            'To evaluate page load performance, hit %s to record the reload.',
         [recordNode, reloadNode]));
 
     p = centered.createChild('p');
     p.appendChild(UI.formatLocalized(
         'After recording, select an area of interest in the overview by dragging. ' +
-        'Then, zoom and pan the timeline with the mousewheel or %s keys.',
+            'Then, zoom and pan the timeline with the mousewheel or %s keys.',
         [navigateNode]));
 
     p = centered.createChild('p');
     p.appendChild(learnMoreNode);
 
-    var timelineSpan = encloseWithTag('b', Common.UIString('Timeline'));
-    var performanceSpan = encloseWithTag('b', Common.UIString('Performance'));
-
-    p = centered.createChild('p', 'timeline-landing-warning');
-    p.appendChild(UI.formatLocalized(
-        'The %s panel has been enriched with the JavaScript profiler capabilities and is now called %s.%s' +
-        'You can find the legacy JavaScript CPU profiler under %s%s \u2192 More Tools \u2192 JavaScript Profiler.',
-        [timelineSpan, performanceSpan, createElement('p'), createElement('br'), UI.Icon.create('largeicon-menu')]));
-
     this._landingPage.show(this._statusPaneContainer);
   }
 
diff --git a/third_party/WebKit/Source/devtools/front_end/timeline/module.json b/third_party/WebKit/Source/devtools/front_end/timeline/module.json
index 39ef5fd..ce3a79ce 100644
--- a/third_party/WebKit/Source/devtools/front_end/timeline/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/timeline/module.json
@@ -4,7 +4,7 @@
             "type": "view",
             "location": "panel",
             "id": "timeline",
-            "title": "Performance",
+            "title": "Timeline",
             "order": 50,
             "className": "Timeline.TimelinePanel"
         },
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/ShortcutsScreen.js b/third_party/WebKit/Source/devtools/front_end/ui/ShortcutsScreen.js
index ead1725e..4d46ab24 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/ShortcutsScreen.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/ShortcutsScreen.js
@@ -141,7 +141,7 @@
         Common.UIString('Switch between files with the same name and different extensions.'));
 
     // Performance panel
-    section = UI.shortcutsScreen.section(Common.UIString('Performance Panel'));
+    section = UI.shortcutsScreen.section(Common.UIString('Timeline Panel'));
     section.addAlternateKeys(
         UI.shortcutRegistry.shortcutDescriptorsForAction('timeline.toggle-recording'),
         Common.UIString('Start/stop recording'));
@@ -157,7 +157,7 @@
         Common.UIString('Jump to previous/next frame'));
 
     // Memory panel
-    section = UI.shortcutsScreen.section(Common.UIString('Memory Panel'));
+    section = UI.shortcutsScreen.section(Common.UIString('Profiles Panel'));
     section.addAlternateKeys(
         UI.shortcutRegistry.shortcutDescriptorsForAction('profiler.heap-toggle-recording'),
         Common.UIString('Start/stop recording'));
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js b/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js
index ef8aed63..237f5064 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/UIUtils.js
@@ -2038,3 +2038,43 @@
  * @type {number}
  */
 UI.MaxLengthForDisplayedURLs = 150;
+
+/**
+ * @unrestricted
+ */
+UI.ConfirmDialog = class extends UI.VBox {
+  /**
+   * @param {string} message
+   * @param {!Function} callback
+   */
+  static show(message, callback) {
+    var dialog = new UI.Dialog();
+    dialog.setWrapsContent(true);
+    dialog.addCloseButton();
+    dialog.setDimmed(true);
+    new UI
+        .ConfirmDialog(
+            message,
+            () => {
+              dialog.detach();
+              callback();
+            },
+            () => dialog.detach())
+        .show(dialog.element);
+    dialog.show();
+  }
+
+  /**
+   * @param {string} message
+   * @param {!Function} okCallback
+   * @param {!Function} cancelCallback
+   */
+  constructor(message, okCallback, cancelCallback) {
+    super(true);
+    this.registerRequiredCSS('ui/confirmDialog.css');
+    this.contentElement.createChild('div', 'message').createChild('span').textContent = message;
+    var buttonsBar = this.contentElement.createChild('div', 'button');
+    buttonsBar.appendChild(UI.createTextButton(Common.UIString('Ok'), okCallback));
+    buttonsBar.appendChild(UI.createTextButton(Common.UIString('Cancel'), cancelCallback));
+  }
+};
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/confirmDialog.css b/third_party/WebKit/Source/devtools/front_end/ui/confirmDialog.css
new file mode 100644
index 0000000..aade0dfe
--- /dev/null
+++ b/third_party/WebKit/Source/devtools/front_end/ui/confirmDialog.css
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 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.
+ */
+
+.widget {
+    padding: 20px;
+}
+
+.message, .button {
+    font-size: larger;
+    white-space: pre;
+    margin: 5px;
+}
+
+.button {
+    text-align: center;
+    margin-top: 10px;
+}
+
+.reason {
+    color: #8b0000;
+}
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/module.json b/third_party/WebKit/Source/devtools/front_end/ui/module.json
index 83097b2..b4c5895 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/module.json
+++ b/third_party/WebKit/Source/devtools/front_end/ui/module.json
@@ -54,6 +54,7 @@
     "resources": [
         "checkboxTextLabel.css",
         "closeButton.css",
+        "confirmDialog.css",
         "dialog.css",
         "dropTarget.css",
         "emptyWidget.css",
diff --git a/third_party/WebKit/Source/modules/indexeddb/InspectorIndexedDBAgent.cpp b/third_party/WebKit/Source/modules/indexeddb/InspectorIndexedDBAgent.cpp
index decb032..39ada99a 100644
--- a/third_party/WebKit/Source/modules/indexeddb/InspectorIndexedDBAgent.cpp
+++ b/third_party/WebKit/Source/modules/indexeddb/InspectorIndexedDBAgent.cpp
@@ -77,6 +77,8 @@
     RequestDataCallback;
 typedef blink::protocol::IndexedDB::Backend::ClearObjectStoreCallback
     ClearObjectStoreCallback;
+typedef blink::protocol::IndexedDB::Backend::DeleteDatabaseCallback
+    DeleteDatabaseCallback;
 
 namespace blink {
 
@@ -141,6 +143,43 @@
   String m_securityOrigin;
 };
 
+class DeleteCallback final : public EventListener {
+  WTF_MAKE_NONCOPYABLE(DeleteCallback);
+
+ public:
+  static DeleteCallback* create(
+      std::unique_ptr<DeleteDatabaseCallback> requestCallback,
+      const String& securityOrigin) {
+    return new DeleteCallback(std::move(requestCallback), securityOrigin);
+  }
+
+  ~DeleteCallback() override {}
+
+  bool operator==(const EventListener& other) const override {
+    return this == &other;
+  }
+
+  void handleEvent(ExecutionContext*, Event* event) override {
+    if (event->type() != EventTypeNames::success) {
+      m_requestCallback->sendFailure(
+          Response::Error("Failed to delete database."));
+      return;
+    }
+    m_requestCallback->sendSuccess();
+  }
+
+  DEFINE_INLINE_VIRTUAL_TRACE() { EventListener::trace(visitor); }
+
+ private:
+  DeleteCallback(std::unique_ptr<DeleteDatabaseCallback> requestCallback,
+                 const String& securityOrigin)
+      : EventListener(EventListener::CPPEventListenerType),
+        m_requestCallback(std::move(requestCallback)),
+        m_securityOrigin(securityOrigin) {}
+  std::unique_ptr<DeleteDatabaseCallback> m_requestCallback;
+  String m_securityOrigin;
+};
+
 template <typename RequestCallback>
 class OpenDatabaseCallback;
 template <typename RequestCallback>
@@ -951,6 +990,44 @@
                           databaseName);
 }
 
+void InspectorIndexedDBAgent::deleteDatabase(
+    const String& securityOrigin,
+    const String& databaseName,
+    std::unique_ptr<DeleteDatabaseCallback> requestCallback) {
+  LocalFrame* frame =
+      m_inspectedFrames->frameWithSecurityOrigin(securityOrigin);
+  Document* document = frame ? frame->document() : nullptr;
+  if (!document) {
+    requestCallback->sendFailure(Response::Error(kNoDocumentError));
+    return;
+  }
+  IDBFactory* idbFactory = nullptr;
+  Response response = assertIDBFactory(document, idbFactory);
+  if (!response.isSuccess()) {
+    requestCallback->sendFailure(response);
+    return;
+  }
+
+  ScriptState* scriptState = ScriptState::forMainWorld(frame);
+  if (!scriptState) {
+    requestCallback->sendFailure(Response::InternalError());
+    return;
+  }
+  ScriptState::Scope scope(scriptState);
+  DummyExceptionStateForTesting exceptionState;
+  IDBRequest* idbRequest = idbFactory->closeConnectionsAndDeleteDatabase(
+      scriptState, databaseName, exceptionState);
+  if (exceptionState.hadException()) {
+    requestCallback->sendFailure(Response::Error("Could not delete database."));
+    return;
+  }
+  idbRequest->addEventListener(
+      EventTypeNames::success,
+      DeleteCallback::create(std::move(requestCallback),
+                             document->getSecurityOrigin()->toRawString()),
+      false);
+}
+
 DEFINE_TRACE(InspectorIndexedDBAgent) {
   visitor->trace(m_inspectedFrames);
   InspectorBaseAgent::trace(visitor);
diff --git a/third_party/WebKit/Source/modules/indexeddb/InspectorIndexedDBAgent.h b/third_party/WebKit/Source/modules/indexeddb/InspectorIndexedDBAgent.h
index 1d41cd1..1a6d1107 100644
--- a/third_party/WebKit/Source/modules/indexeddb/InspectorIndexedDBAgent.h
+++ b/third_party/WebKit/Source/modules/indexeddb/InspectorIndexedDBAgent.h
@@ -73,6 +73,9 @@
                         const String& databaseName,
                         const String& objectStoreName,
                         std::unique_ptr<ClearObjectStoreCallback>) override;
+  void deleteDatabase(const String& securityOrigin,
+                      const String& databaseName,
+                      std::unique_ptr<DeleteDatabaseCallback>) override;
 
  private:
   Member<InspectedFrames> m_inspectedFrames;
diff --git a/third_party/WebKit/Source/platform/graphics/DEPS b/third_party/WebKit/Source/platform/graphics/DEPS
index 7dcab9c..fc51aa12 100644
--- a/third_party/WebKit/Source/platform/graphics/DEPS
+++ b/third_party/WebKit/Source/platform/graphics/DEPS
@@ -11,6 +11,7 @@
     "+gpu/command_buffer/client/gles2_interface.h",
     "+gpu/command_buffer/client/gpu_memory_buffer_manager.h",
     "+gpu/command_buffer/common/capabilities.h",
+    "+gpu/command_buffer/common/gpu_memory_buffer_support.h",
     "+gpu/command_buffer/common/mailbox.h",
     "+gpu/command_buffer/common/sync_token.h",
 ]
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp
index f2130968..7be1e65 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.cpp
@@ -33,7 +33,9 @@
 #include "cc/resources/shared_bitmap.h"
 #include "gpu/GLES2/gl2extchromium.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
+#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
 #include "gpu/command_buffer/common/capabilities.h"
+#include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
 #include "platform/RuntimeEnabledFeatures.h"
 #include "platform/graphics/AcceleratedStaticBitmapImage.h"
 #include "platform/graphics/GraphicsLayer.h"
@@ -152,6 +154,7 @@
       m_softwareRendering(this->contextProvider()->isSoftwareRendering()),
       m_wantDepth(wantDepth),
       m_wantStencil(wantStencil),
+      m_colorSpace(gfx::ColorSpace::CreateSRGB()),
       m_chromiumImageUsage(chromiumImageUsage) {
   // Used by browser tests to detect the use of a DrawingBuffer.
   TRACE_EVENT_INSTANT0("test_gpu", "DrawingBufferCreation",
@@ -311,6 +314,7 @@
   }
 
   *outMailbox = cc::TextureMailbox(bitmap.get(), m_size);
+  outMailbox->set_color_space(m_colorSpace);
 
   // This holds a ref on the DrawingBuffer that will keep it alive until the
   // mailbox is released (and while the release callback is running). It also
@@ -385,6 +389,7 @@
         colorBufferForMailbox->mailbox, colorBufferForMailbox->produceSyncToken,
         colorBufferForMailbox->parameters.target, gfx::Size(m_size),
         isOverlayCandidate, secureOutputOnly);
+    outMailbox->set_color_space(m_colorSpace);
 
     // This holds a ref on the DrawingBuffer that will keep it alive until the
     // mailbox is released (and while the release callback is running).
@@ -574,16 +579,19 @@
   return createColorBuffer(m_size);
 }
 
-DrawingBuffer::ColorBuffer::ColorBuffer(DrawingBuffer* drawingBuffer,
-                                        const ColorBufferParameters& parameters,
-                                        const IntSize& size,
-                                        GLuint textureId,
-                                        GLuint imageId)
+DrawingBuffer::ColorBuffer::ColorBuffer(
+    DrawingBuffer* drawingBuffer,
+    const ColorBufferParameters& parameters,
+    const IntSize& size,
+    GLuint textureId,
+    GLuint imageId,
+    std::unique_ptr<gfx::GpuMemoryBuffer> gpuMemoryBuffer)
     : drawingBuffer(drawingBuffer),
       parameters(parameters),
       size(size),
       textureId(textureId),
-      imageId(imageId) {
+      imageId(imageId),
+      gpuMemoryBuffer(std::move(gpuMemoryBuffer)) {
   drawingBuffer->contextGL()->GenMailboxCHROMIUM(mailbox.name);
 }
 
@@ -610,6 +618,7 @@
         NOTREACHED();
         break;
     }
+    gpuMemoryBuffer.reset();
   }
   gl->DeleteTextures(1, &textureId);
 }
@@ -1131,11 +1140,25 @@
   // GpuMemoryBuffer and GLImage, if one is going to be used.
   ColorBufferParameters parameters;
   GLuint imageId = 0;
-  if (shouldUseChromiumImage()) {
+  std::unique_ptr<gfx::GpuMemoryBuffer> gpuMemoryBuffer;
+  gpu::GpuMemoryBufferManager* gpuMemoryBufferManager =
+      Platform::current()->getGpuMemoryBufferManager();
+  if (shouldUseChromiumImage() && gpuMemoryBufferManager) {
     parameters = gpuMemoryBufferColorBufferParameters();
-    imageId = m_gl->CreateGpuMemoryBufferImageCHROMIUM(
-        size.width(), size.height(), parameters.creationInternalColorFormat,
-        GC3D_SCANOUT_CHROMIUM);
+    gfx::BufferFormat bufferFormat = gpu::DefaultBufferFormatForImageFormat(
+        parameters.creationInternalColorFormat);
+    gpuMemoryBuffer = gpuMemoryBufferManager->CreateGpuMemoryBuffer(
+        gfx::Size(size), bufferFormat, gfx::BufferUsage::SCANOUT,
+        gpu::kNullSurfaceHandle);
+    if (gpuMemoryBuffer) {
+      if (RuntimeEnabledFeatures::colorCorrectRenderingEnabled())
+        gpuMemoryBuffer->SetColorSpaceForScanout(m_colorSpace);
+      imageId = m_gl->CreateImageCHROMIUM(
+          gpuMemoryBuffer->AsClientBuffer(), size.width(), size.height(),
+          parameters.creationInternalColorFormat);
+      if (!imageId)
+        gpuMemoryBuffer.reset();
+    }
   } else {
     parameters = textureColorBufferParameters();
   }
@@ -1193,7 +1216,8 @@
     m_gl->DeleteFramebuffers(1, &fbo);
   }
 
-  return adoptRef(new ColorBuffer(this, parameters, size, textureId, imageId));
+  return adoptRef(new ColorBuffer(this, parameters, size, textureId, imageId,
+                                  std::move(gpuMemoryBuffer)));
 }
 
 void DrawingBuffer::attachColorBufferToReadFramebuffer() {
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.h b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.h
index 967a59b..fe1c8b32 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.h
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBuffer.h
@@ -40,6 +40,7 @@
 #include "platform/graphics/gpu/WebGLImageConversion.h"
 #include "third_party/khronos/GLES2/gl2.h"
 #include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/color_space.h"
 #include "wtf/Deque.h"
 #include "wtf/Noncopyable.h"
 #include "wtf/RefCounted.h"
@@ -50,6 +51,10 @@
 class SharedBitmap;
 }
 
+namespace gfx {
+class GpuMemoryBuffer;
+}
+
 namespace gpu {
 namespace gles2 {
 class GLES2Interface;
@@ -301,7 +306,8 @@
                 const ColorBufferParameters&,
                 const IntSize&,
                 GLuint textureId,
-                GLuint imageId);
+                GLuint imageId,
+                std::unique_ptr<gfx::GpuMemoryBuffer>);
     ~ColorBuffer();
 
     // The owning DrawingBuffer. Note that DrawingBuffer is explicitly destroyed
@@ -314,6 +320,7 @@
 
     const GLuint textureId = 0;
     const GLuint imageId = 0;
+    std::unique_ptr<gfx::GpuMemoryBuffer> gpuMemoryBuffer;
 
     // The mailbox used to send this buffer to the compositor.
     gpu::Mailbox mailbox;
@@ -483,6 +490,10 @@
   const bool m_wantDepth;
   const bool m_wantStencil;
 
+  // The color space of this buffer. All buffers are assumed to be sRGB until
+  // a mechanism for creating otherwise is exposed to the web.
+  const gfx::ColorSpace m_colorSpace;
+
   enum AntialiasingMode {
     None,
     MSAAImplicitResolve,
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTest.cpp b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTest.cpp
index 184a278..6f6a6b98 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTest.cpp
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTest.cpp
@@ -32,6 +32,7 @@
 
 #include "cc/resources/single_release_callback.h"
 #include "cc/resources/texture_mailbox.h"
+#include "cc/test/test_gpu_memory_buffer_manager.h"
 #include "gpu/command_buffer/client/gles2_interface_stub.h"
 #include "gpu/command_buffer/common/mailbox.h"
 #include "gpu/command_buffer/common/sync_token.h"
@@ -40,6 +41,7 @@
 #include "platform/graphics/gpu/DrawingBufferTestHelpers.h"
 #include "public/platform/Platform.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/Source/platform/testing/TestingPlatformSupport.h"
 #include "wtf/PtrUtil.h"
 #include "wtf/RefPtr.h"
 #include <memory>
@@ -49,6 +51,19 @@
 
 namespace blink {
 
+namespace {
+
+class FakePlatformSupport : public TestingPlatformSupport {
+  gpu::GpuMemoryBufferManager* getGpuMemoryBufferManager() override {
+    return &m_testGpuMemoryBufferManager;
+  }
+
+ private:
+  cc::TestGpuMemoryBufferManager m_testGpuMemoryBufferManager;
+};
+
+}  // anonymous namespace
+
 class DrawingBufferTest : public Test {
  protected:
   void SetUp() override {
@@ -346,6 +361,8 @@
 class DrawingBufferImageChromiumTest : public DrawingBufferTest {
  protected:
   void SetUp() override {
+    m_platform.reset(new ScopedTestingPlatformSupport<FakePlatformSupport>);
+
     IntSize initialSize(InitialWidth, InitialHeight);
     std::unique_ptr<GLES2InterfaceForTests> gl =
         WTF::wrapUnique(new GLES2InterfaceForTests);
@@ -365,9 +382,11 @@
 
   void TearDown() override {
     RuntimeEnabledFeatures::setWebGLImageChromiumEnabled(false);
+    m_platform.reset();
   }
 
   GLuint m_imageId0;
+  std::unique_ptr<ScopedTestingPlatformSupport<FakePlatformSupport>> m_platform;
 };
 
 TEST_F(DrawingBufferImageChromiumTest, verifyResizingReallocatesImages) {
diff --git a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTestHelpers.h b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTestHelpers.h
index 87bf7c8..d099d05 100644
--- a/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTestHelpers.h
+++ b/third_party/WebKit/Source/platform/graphics/gpu/DrawingBufferTestHelpers.h
@@ -239,10 +239,10 @@
     }
   }
 
-  GLuint CreateGpuMemoryBufferImageCHROMIUM(GLsizei width,
-                                            GLsizei height,
-                                            GLenum internalformat,
-                                            GLenum usage) override {
+  GLuint CreateImageCHROMIUM(ClientBuffer buffer,
+                             GLsizei width,
+                             GLsizei height,
+                             GLenum internalformat) override {
     if (m_createImageChromiumFail)
       return 0;
     m_imageSizes.set(m_currentImageId, IntSize(width, height));
diff --git a/third_party/WebKit/Tools/Scripts/generate-w3c-directory-owner-json b/third_party/WebKit/Tools/Scripts/generate-w3c-directory-owner-json
deleted file mode 100755
index decda65..0000000
--- a/third_party/WebKit/Tools/Scripts/generate-w3c-directory-owner-json
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/usr/bin/python
-# 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.
-
-"""Converts CSV files to machine-readable JSON.
-
-Example usage:
-
-./csv_to_json input.csv --output directory_owners.json\
---skip-keys number-tests\
--f directory test-number team notification-email other-contact component
-"""
-
-import argparse
-import csv
-import json
-import sys
-
-
-def main(argv):
-    parser = argparse.ArgumentParser()
-    parser.add_argument('filename', metavar='filename',
-                        help='The path to the input CSV file.')
-    parser.add_argument('-o', '--output',
-                        help='The output file name.')
-    parser.add_argument('-f', '--field-names', nargs='*',
-                        help='The ordered field names of the CSV file. Defaults to first row.')
-    parser.add_argument('-s', '--skip-keys', nargs='*',
-                        help='Fields that should be skipped.')
-    args = parser.parse_args()
-    convert_csv_to_json(args.filename, args.output, args.field_names, args.skip_keys)
-
-
-def convert_csv_to_json(filename, output_filename=None, field_names=None, skip_keys=None):
-    out = output_filename or (filename + '.json')
-    dict_list = []
-    json_file = open(out, 'w')
-    with open(filename) as csv_file:
-        reader = csv.DictReader(csv_file, fieldnames=field_names)
-        for row in reader:
-            if not row['directory'].startswith('external'):
-                continue
-            if skip_keys:
-                for s in skip_keys:
-                    del row[s]
-            dict_list.append(row)
-        json.dump(dict_list, json_file, indent=4)
-
-
-if __name__ == '__main__':
-    main(sys.argv[1:])
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/deps_updater.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/deps_updater.py
index 3f3388e21..4340bf5 100644
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/deps_updater.py
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/deps_updater.py
@@ -23,9 +23,11 @@
 from webkitpy.common.net.git_cl import GitCL
 from webkitpy.common.webkit_finder import WebKitFinder
 from webkitpy.layout_tests.models.test_expectations import TestExpectations, TestExpectationParser
-from webkitpy.w3c.update_w3c_test_expectations import W3CExpectationsLineAdder
-from webkitpy.w3c.test_importer import TestImporter
 from webkitpy.w3c.common import WPT_REPO_URL, CSS_REPO_URL, WPT_DEST_NAME, CSS_DEST_NAME
+from webkitpy.w3c.directory_owners_extractor import DirectoryOwnersExtractor
+from webkitpy.w3c.test_importer import TestImporter
+from webkitpy.w3c.update_w3c_test_expectations import W3CExpectationsLineAdder
+
 
 # Settings for how often to check try job results and how long to wait.
 POLL_DELAY_SECONDS = 2 * 60
@@ -324,7 +326,7 @@
 
     def _upload_cl(self):
         _log.info('Uploading change list.')
-        cc_list = self.get_directory_owners_to_cc()
+        cc_list = self.get_directory_owners()
         description = self._cl_description()
         self.git_cl.run([
             'upload',
@@ -334,6 +336,14 @@
             description,
         ] + ['--cc=' + email for email in cc_list])
 
+    def get_directory_owners(self):
+        """Returns a list of email addresses of owners of changed tests."""
+        _log.info('Gathering directory owners emails to CC.')
+        changed_files = self.host.cwd().changed_files()
+        extractor = DirectoryOwnersExtractor(self.fs)
+        extractor.read_owner_map()
+        return extractor.list_owners(changed_files)
+
     def _cl_description(self):
         description = self.check_run(['git', 'log', '-1', '--format=%B'])
         build_link = self._build_link()
@@ -355,17 +365,6 @@
             return None
         return 'https://build.chromium.org/p/%s/builders/%s/builds/%s' % (master_name, builder_name, build_number)
 
-    def get_directory_owners_to_cc(self):
-        """Returns a list of email addresses to CC for the current import."""
-        _log.info('Gathering directory owners emails to CC.')
-        directory_owners_file_path = self.finder.path_from_webkit_base(
-            'Tools', 'Scripts', 'webkitpy', 'w3c', 'directory_owners.json')
-        with open(directory_owners_file_path) as data_file:
-            directory_to_owner = self.parse_directory_owners(json.load(data_file))
-        out = self.check_run(['git', 'diff', 'origin/master', '--name-only'])
-        changed_files = out.splitlines()
-        return self.generate_email_list(changed_files, directory_to_owner)
-
     @staticmethod
     def parse_directory_owners(decoded_data_file):
         directory_dict = {}
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/directory_owners.json b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/directory_owners.json
deleted file mode 100644
index e67c578..0000000
--- a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/directory_owners.json
+++ /dev/null
@@ -1,1661 +0,0 @@
-[
-    {
-        "component": "Blink>WebComponents", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/custom-elements/concepts", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/custom-elements/creating-and-passing-registries", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/custom-elements/custom-element-lifecycle/enqueuing-and-invoking-callbacks", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/custom-elements/custom-element-lifecycle/types-of-callbacks", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/custom-elements/instantiating-custom-elements", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/custom-elements/instantiating-custom-elements/extensions-to-document-interface", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/custom-elements/registering-custom-elements", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/custom-elements/registering-custom-elements/extensions-to-document-interface", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "Blink>DOM", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/dom", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/dom/collections", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/dom/events", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/dom/lists", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/dom/nodes", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/dom/nodes/Document-contentType/contentType", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/dom/ranges", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/dom/traversal", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "Blink>HTML", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/dom/documents/dom-tree-accessors", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/dom/documents/dom-tree-accessors/document.getElementsByName", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/dom/dynamic-markup-insertion/closing-the-input-stream", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/dom/dynamic-markup-insertion/document-write", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/dom/dynamic-markup-insertion/document-writeln", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/dom/dynamic-markup-insertion/opening-the-input-stream", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/dom/elements/elements-in-the-dom", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/dom/elements/global-attributes", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "Blink>Editing", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/editing/activation", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/editing/editing-0/contenteditable", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/editing/editing-0/making-entire-documents-editable-the-designmode-idl-attribute", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/editing/editing-0/spelling-and-grammar-checking", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/editing/focus/document-level-focus-apis", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/editing/focus/focus-management", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "Blink>WebComponents", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html-imports/document", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html-imports/fetching", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html-imports/html-link-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "Blink>HTML", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/infrastructure/common-dom-interfaces/collections", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/infrastructure/conformance-requirements/extensibility", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/infrastructure/terminology/plugins", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/obsolete/requirements-for-implementations/other-elements-attributes-and-apis", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/obsolete/requirements-for-implementations/the-marquee-element-0", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/rendering/bindings/the-input-element-as-a-text-entry-widget", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/rendering/bindings/the-select-element-0", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/rendering/bindings/the-textarea-element-0", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/disabled-elements", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/document-metadata/the-meta-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/document-metadata/the-title-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/edits/the-del-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/edits/the-ins-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/embedded-content/the-embed-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/embedded-content/the-iframe-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/embedded-content/the-object-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "Blink>Forms", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/attributes-common-to-form-controls", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/constraints", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/form-control-infrastructure", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/resetting-a-form", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/textfieldselection", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/the-button-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/the-datalist-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/the-fieldset-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/the-form-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/the-input-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/the-label-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/the-legend-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/the-meter-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/the-option-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/the-output-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/the-progress-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/the-select-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/forms/the-textarea-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "Blink>HTML", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/grouping-content/the-dd-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/grouping-content/the-div-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/grouping-content/the-dl-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/grouping-content/the-dt-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/grouping-content/the-figcaption-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/grouping-content/the-figure-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/grouping-content/the-hr-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/grouping-content/the-li-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/grouping-content/the-ol-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/grouping-content/the-p-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/grouping-content/the-pre-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/grouping-content/the-ul-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/interactive-elements/the-details-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/interactive-elements/the-dialog-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/links/linktypes", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/scripting-1/the-script-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/scripting-1/the-script-element/fetch-src", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/scripting-1/the-script-element/fetch-src/alpha", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/selectors/pseudo-classes", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/tabular-data/attributes-common-to-td-and-th-elements", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/tabular-data/the-caption-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/tabular-data/the-table-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/tabular-data/the-tbody-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/tabular-data/the-tr-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/text-level-semantics/the-a-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/text-level-semantics/the-bdi-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/text-level-semantics/the-bdo-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/text-level-semantics/the-br-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/text-level-semantics/the-time-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/semantics/text-level-semantics/the-wbr-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/syntax/parsing", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/syntax/serializing-html-fragments", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/syntax/serializing-xml-fragments", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/webappapis/scripting/event-loops", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/webappapis/scripting/events", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/html/webappapis/scripting/processing-model-2", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "Blink>WebComponents", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/styles", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/elements-and-dom-objects/extensions-to-element-interface/attributes", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/elements-and-dom-objects/extensions-to-element-interface/methods", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/elements-and-dom-objects/extensions-to-event-interface", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/elements-and-dom-objects/shadowroot-object/shadowroot-attributes", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/elements-and-dom-objects/shadowroot-object/shadowroot-methods", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/events", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/events/event-dispatch", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/events/event-retargeting", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/events/events-created-by-users-do-not-stop", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/events/retargeting-focus-events", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/events/retargeting-relatedtarget", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/html-elements-in-shadow-trees/html-forms", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/html-elements-in-shadow-trees/inert-html-elements", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/shadow-trees", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/shadow-trees/nested-shadow-trees", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/shadow-trees/reprojection", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/shadow-trees/upper-boundary-encapsulation", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/styles", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/user-interaction/active-element", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/user-interaction/editing", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/user-interaction/focus-navigation", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "DOM", 
-        "directory": "external/wpt/shadow-dom/untriaged/user-interaction/ranges-and-selections", 
-        "notification-email": "dom-dev@chromium.org"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "input-dev", 
-        "directory": "external/wpt/touch-events", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "jsbell+jshin", 
-        "directory": "external/wpt/encoding", 
-        "notification-email": "jsbell+jshin (yes, not a team, this should be fixed)"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/csswg-test/css21/linebox", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/csswg-test/css-flexbox-1", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/csswg-test/css-flexbox-1/flex-lines", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/csswg-test/css-flexbox-1/getcomputedstyle", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/csswg-test/css-flexbox-1/order", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/wpt/html/rendering/non-replaced-elements/flow-content-0", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/wpt/html/rendering/non-replaced-elements/lists", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/wpt/html/rendering/non-replaced-elements/phrasing-content-0/font-element-text-decoration-color", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/wpt/html/rendering/non-replaced-elements/tables", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/wpt/html/rendering/non-replaced-elements/the-fieldset-element-0", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/wpt/html/rendering/non-replaced-elements/the-hr-element-0", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/wpt/html/rendering/replaced-elements/attributes-for-embedded-content-and-images", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/wpt/html/rendering/replaced-elements/embedded-content-rendering-rules", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "layout-dev", 
-        "directory": "external/wpt/html/rendering/replaced-elements/images", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "owp-storage", 
-        "directory": "external/wpt/FileAPI", 
-        "notification-email": "chrome-owp-storage@google.com"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "owp-storage", 
-        "directory": "external/wpt/FileAPI/blob", 
-        "notification-email": "chrome-owp-storage@google.com"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "owp-storage", 
-        "directory": "external/wpt/FileAPI/file", 
-        "notification-email": "chrome-owp-storage@google.com"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "owp-storage", 
-        "directory": "external/wpt/FileAPI/filelist-section", 
-        "notification-email": "chrome-owp-storage@google.com"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "owp-storage", 
-        "directory": "external/wpt/FileAPI/FileReader", 
-        "notification-email": "chrome-owp-storage@google.com"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "owp-storage", 
-        "directory": "external/wpt/FileAPI/reading-data-section", 
-        "notification-email": "chrome-owp-storage@google.com"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "owp-storage", 
-        "directory": "external/wpt/FileAPI/url", 
-        "notification-email": "chrome-owp-storage@google.com"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "owp-storage", 
-        "directory": "external/wpt/IndexedDB", 
-        "notification-email": "chrome-owp-storage@google.com"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "owp-storage", 
-        "directory": "external/wpt/webstorage", 
-        "notification-email": "chrome-owp-storage@google.com"
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/css-shapes-1", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/css-shapes-1/shape-outside/shape-box", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/css-shapes-1/shape-outside/shape-image", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/css-shapes-1/shape-outside/shape-image/gradients", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/css-shapes-1/shape-outside/supported-shapes/circle", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/css-shapes-1/shape-outside/supported-shapes/ellipse", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/css-shapes-1/shape-outside/supported-shapes/inset", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/css-shapes-1/shape-outside/supported-shapes/polygon", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/css-shapes-1/shape-outside/values", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/css-shapes-1/spec-examples", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/css-snap-size-1", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/css-text-3/overflow-wrap", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/css-writing-modes-3", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/csswg-test/vendor-imports/mozilla/mozilla-central-reftests/flexbox", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/gamepad", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/hr-time", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/browsing-the-web/history-traversal", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/browsing-the-web/read-media", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/browsing-the-web/scroll-to-fragid", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/history/the-history-interface", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/history/the-location-interface", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/offline", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/offline/application-cache-api", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/offline/browser-state", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/offline/introduction-4", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/the-window-object", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/the-window-object/accessing-other-browsing-contexts", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/the-window-object/named-access-on-the-window-object", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/windows", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/windows/browsing-context-names", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/browsers/windows/nested-browsing-contexts", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/dom/documents/resource-metadata-management", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/dom/elements/requirements-relating-to-bidirectional-algorithm-formatting-characters", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/editing/dnd/dom", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/editing/dnd/synthetic", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/editing/dnd/target-origin", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/editing/dnd/the-dropzone-attribute", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/rendering/replaced-elements/svg-embedded-sizing", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/rendering/replaced-elements/svg-inline-sizing", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/document-metadata/styling", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/document-metadata/the-link-element", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/document-metadata/the-style-element", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLMediaElement", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/interfaces/HTMLElement/HTMLTrackElement", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/interfaces/TextTrack", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/interfaces/TextTrackCue", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/interfaces/TextTrackCueList", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/interfaces/TextTrackList", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/interfaces/TrackEvent", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/loading-the-media-resource", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/location-of-the-media-resource", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/mime-types", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/offsets-into-the-media-resource", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/playing-the-media-resource", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/synchronising-multiple-media-elements/media-controllers", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/media-elements/track/track-element", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/the-audio-element", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/the-img-element", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/the-img-element/current-pixel-density", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/the-img-element/sizes", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/the-img-element/srcset", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/the-img-element/update-the-image-data", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/semantics/embedded-content/the-video-element", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/webappapis/animation-frames", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/webappapis/atob", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/content", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/html/webappapis/timers", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/mediacapture-streams", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/pointerevents", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/user-timing", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/web-animations/animatable", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/web-animations/animation", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/web-animations/animation-effect-timing", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/web-animations/animation-timeline", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/web-animations/keyframe-effect", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/WebIDL/ecmascript-binding", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/WebIDL/ecmascript-binding/es-exceptions", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/webrtc", 
-        "notification-email": ""
-    }, 
-    {
-        "component": "", 
-        "other-contact": "", 
-        "team": "", 
-        "directory": "external/wpt/webrtc/rtcpeerconnection", 
-        "notification-email": ""
-    }
-]
\ No newline at end of file
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/directory_owners_extractor.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/directory_owners_extractor.py
new file mode 100644
index 0000000..b6c9329
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/directory_owners_extractor.py
@@ -0,0 +1,75 @@
+# 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 re
+
+from webkitpy.common.system.filesystem import FileSystem
+from webkitpy.common.webkit_finder import WebKitFinder
+
+
+class DirectoryOwnersExtractor(object):
+
+    def __init__(self, filesystem=None):
+        self.filesystem = filesystem or FileSystem
+        self.finder = WebKitFinder(filesystem)
+        self.owner_map = None
+
+    def read_owner_map(self):
+        """Reads the W3CImportExpectations file and returns a map of directories to owners."""
+        input_path = self.finder.path_from_webkit_base('LayoutTests', 'W3CImportExpectations')
+        input_contents = self.filesystem.read_text_file(input_path)
+        self.owner_map = self.lines_to_owner_map(input_contents.splitlines())
+
+    def lines_to_owner_map(self, lines):
+        current_owners = []
+        owner_map = {}
+        for line in lines:
+            owners = self.extract_owners(line)
+            if owners:
+                current_owners = owners
+            directory = self.extract_directory(line)
+            if current_owners and directory:
+                owner_map[directory] = current_owners
+        return owner_map
+
+    @staticmethod
+    def extract_owners(line):
+        """Extracts owner email addresses listed on a line."""
+        match = re.match(r'##? Owners?: (?P<addresses>.*)', line)
+        if not match or not match.group('addresses'):
+            return None
+        email_part = match.group('addresses')
+        addresses = [email_part] if ',' not in email_part else re.split(r',\s*', email_part)
+        addresses = [s for s in addresses if re.match(r'\S+@\S+', s)]
+        return addresses or None
+
+    @staticmethod
+    def extract_directory(line):
+        match = re.match(r'# ?(?P<directory>\S+) \[ (Pass|Skip) \]', line)
+        if match and match.group('directory'):
+            return match.group('directory')
+        match = re.match(r'(?P<directory>\S+) \[ Pass \]', line)
+        if match and match.group('directory'):
+            return match.group('directory')
+        return None
+
+    def list_owners(self, changed_files):
+        """Looks up the owners for the given set of changed files.
+
+        Args:
+            changed_files: A list of file paths relative to the repository root.
+
+        Returns:
+            A dict mapping (owner) email addresses to (owned) directories.
+        """
+        tests = [self.finder.layout_test_name(path) for path in changed_files]
+        print tests
+        tests = [t for t in tests if t is not None]
+        email_map = {}
+        for directory, owners in self.owner_map.iteritems():
+            owned_tests = [t for t in tests if t.startswith(directory)]
+            if owned_tests:
+                for owner in owners:
+                    email_map[owner] = directory
+        return email_map
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/w3c/directory_owners_extractor_unittest.py b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/directory_owners_extractor_unittest.py
new file mode 100644
index 0000000..189570e8
--- /dev/null
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/w3c/directory_owners_extractor_unittest.py
@@ -0,0 +1,75 @@
+# 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 unittest
+
+from webkitpy.common.system.filesystem_mock import MockFileSystem
+from webkitpy.w3c.directory_owners_extractor import DirectoryOwnersExtractor
+
+
+class DirectoryOwnersExtractorTest(unittest.TestCase):
+
+    def setUp(self):
+        self.filesystem = MockFileSystem()
+        self.extractor = DirectoryOwnersExtractor(self.filesystem)
+
+    def test_lines_to_owner_map(self):
+        lines = [
+            'external/wpt/webgl [ Skip ]',
+            '## Owners: mek@chromium.org',
+            '# external/wpt/webmessaging [ Pass ]',
+            '## Owners: hta@chromium.org',
+            '# external/wpt/webrtc [ Pass ]',
+            'external/wpt/websockets [ Skip ]',
+            '## Owners: michaeln@chromium.org,jsbell@chromium.org',
+            '# external/wpt/webstorage [ Pass ]',
+            'external/wpt/webvtt [ Skip ]',
+        ]
+
+        self.assertEqual(
+            self.extractor.lines_to_owner_map(lines),
+            {
+                'external/wpt/webmessaging': ['mek@chromium.org'],
+                'external/wpt/webrtc': ['hta@chromium.org'],
+                'external/wpt/webstorage': ['michaeln@chromium.org', 'jsbell@chromium.org'],
+            })
+
+    def test_list_owners(self):
+        self.extractor.owner_map = {
+            'external/wpt/foo': ['a@chromium.org'],
+            'external/wpt/bar': ['b@chromium.org'],
+        }
+        self.filesystem.files = {
+            '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/foo/x/y.html': '',
+            '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/bar/x/y.html': '',
+            '/mock-checkout/third_party/WebKit/LayoutTests/external/wpt/quux/x/y.html': '',
+        }
+        changed_files = [
+            'third_party/WebKit/LayoutTests/external/wpt/foo/x/y.html',
+            'third_party/WebKit/LayoutTests/external/wpt/quux/x/y.html',
+        ]
+        self.assertEqual(self.extractor.list_owners(changed_files), {'a@chromium.org': 'external/wpt/foo'})
+
+    def test_extract_owner_positive_cases(self):
+        self.assertEqual(self.extractor.extract_owners('## Owners: foo@chromium.org'), ['foo@chromium.org'])
+        self.assertEqual(self.extractor.extract_owners('# Owners: foo@chromium.org'), ['foo@chromium.org'])
+        self.assertEqual(self.extractor.extract_owners('## Owners: a@x.com,b@x.com'), ['a@x.com', 'b@x.com'])
+        self.assertEqual(self.extractor.extract_owners('## Owners: a@x.com, b@x.com'), ['a@x.com', 'b@x.com'])
+        self.assertEqual(self.extractor.extract_owners('## Owner: foo@chromium.org'), ['foo@chromium.org'])
+
+    def test_extract_owner_negative_cases(self):
+        self.assertIsNone(self.extractor.extract_owners(''))
+        self.assertIsNone(self.extractor.extract_owners('## Something: foo@chromium.org'))
+        self.assertIsNone(self.extractor.extract_owners('## Owners: not an email address'))
+
+    def test_extract_directory_positive_cases(self):
+        self.assertEqual(self.extractor.extract_directory('external/a/b [ Pass ]'), 'external/a/b')
+        self.assertEqual(self.extractor.extract_directory('# external/c/d [ Pass ]'), 'external/c/d')
+        self.assertEqual(self.extractor.extract_directory('# external/e/f [ Skip ]'), 'external/e/f')
+        self.assertEqual(self.extractor.extract_directory('# external/g/h/i [ Skip ]'), 'external/g/h/i')
+
+    def test_extract_directory_negative_cases(self):
+        self.assertIsNone(self.extractor.extract_directory(''))
+        self.assertIsNone(self.extractor.extract_directory('external/a/b [ Skip ]'))
+        self.assertIsNone(self.extractor.extract_directory('# some comment'))
diff --git a/third_party/closure_compiler/externs/automation.js b/third_party/closure_compiler/externs/automation.js
index fd080cbb..0256082 100644
--- a/third_party/closure_compiler/externs/automation.js
+++ b/third_party/closure_compiler/externs/automation.js
@@ -1,14 +1,12 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
+// 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.
 
 // This file was generated by:
-//   tools/json_schema_compiler/compiler.py -g externs
-//       chrome/common/extensions/api/automation.idl
-//
-// Further edits were applied by hand due to bugs / limitations in
-// json_schema_compiler.
-//
+//   tools/json_schema_compiler/compiler.py.
+// NOTE: The format of types has changed. 'FooType' is now
+//   'chrome.automation.FooType'.
+// Please run the closure compiler before committing changes.
 // See https://chromium.googlesource.com/chromium/src/+/master/docs/closure_compilation.md
 
 /** @fileoverview Externs generated from namespace: automation */
@@ -20,247 +18,251 @@
 
 /**
  * @enum {string}
+ * @see https://developer.chrome.com/extensions/automation#type-EventType
  */
 chrome.automation.EventType = {
-  activedescendantchanged: '',
-  alert: '',
-  ariaAttributeChanged: '',
-  autocorrectionOccured: '',
-  blur: '',
-  checkedStateChanged: '',
-  childrenChanged: '',
-  documentSelectionChanged: '',
-  expandedChanged: '',
-  focus: '',
-  hide: '',
-  hover: '',
-  invalidStatusChanged: '',
-  layoutComplete: '',
-  liveRegionChanged: '',
-  loadComplete: '',
-  locationChanged: '',
-  menuEnd: '',
-  menuListItemSelected: '',
-  menuListValueChanged: '',
-  menuPopupEnd: '',
-  menuPopupStart: '',
-  menuStart: '',
-  mouseCanceled: '',
-  mouseDragged: '',
-  mouseMoved: '',
-  mousePressed: '',
-  mouseReleased: '',
-  rowCollapsed: '',
-  rowCountChanged: '',
-  rowExpanded: '',
-  scrollPositionChanged: '',
-  scrolledToAnchor: '',
-  selectedChildrenChanged: '',
-  selection: '',
-  selectionAdd: '',
-  selectionRemove: '',
-  show: '',
-  textChanged: '',
-  textSelectionChanged: '',
-  treeChanged: '',
-  valueChanged: '',
+  ACTIVEDESCENDANTCHANGED: 'activedescendantchanged',
+  ALERT: 'alert',
+  ARIA_ATTRIBUTE_CHANGED: 'ariaAttributeChanged',
+  AUTOCORRECTION_OCCURED: 'autocorrectionOccured',
+  BLUR: 'blur',
+  CHECKED_STATE_CHANGED: 'checkedStateChanged',
+  CHILDREN_CHANGED: 'childrenChanged',
+  CLICKED: 'clicked',
+  DOCUMENT_SELECTION_CHANGED: 'documentSelectionChanged',
+  EXPANDED_CHANGED: 'expandedChanged',
+  FOCUS: 'focus',
+  IMAGE_FRAME_UPDATED: 'imageFrameUpdated',
+  HIDE: 'hide',
+  HOVER: 'hover',
+  INVALID_STATUS_CHANGED: 'invalidStatusChanged',
+  LAYOUT_COMPLETE: 'layoutComplete',
+  LIVE_REGION_CREATED: 'liveRegionCreated',
+  LIVE_REGION_CHANGED: 'liveRegionChanged',
+  LOAD_COMPLETE: 'loadComplete',
+  LOCATION_CHANGED: 'locationChanged',
+  MEDIA_STARTED_PLAYING: 'mediaStartedPlaying',
+  MEDIA_STOPPED_PLAYING: 'mediaStoppedPlaying',
+  MENU_END: 'menuEnd',
+  MENU_LIST_ITEM_SELECTED: 'menuListItemSelected',
+  MENU_LIST_VALUE_CHANGED: 'menuListValueChanged',
+  MENU_POPUP_END: 'menuPopupEnd',
+  MENU_POPUP_START: 'menuPopupStart',
+  MENU_START: 'menuStart',
+  MOUSE_CANCELED: 'mouseCanceled',
+  MOUSE_DRAGGED: 'mouseDragged',
+  MOUSE_MOVED: 'mouseMoved',
+  MOUSE_PRESSED: 'mousePressed',
+  MOUSE_RELEASED: 'mouseReleased',
+  ROW_COLLAPSED: 'rowCollapsed',
+  ROW_COUNT_CHANGED: 'rowCountChanged',
+  ROW_EXPANDED: 'rowExpanded',
+  SCROLL_POSITION_CHANGED: 'scrollPositionChanged',
+  SCROLLED_TO_ANCHOR: 'scrolledToAnchor',
+  SELECTED_CHILDREN_CHANGED: 'selectedChildrenChanged',
+  SELECTION: 'selection',
+  SELECTION_ADD: 'selectionAdd',
+  SELECTION_REMOVE: 'selectionRemove',
+  SHOW: 'show',
+  TEXT_CHANGED: 'textChanged',
+  TEXT_SELECTION_CHANGED: 'textSelectionChanged',
+  TREE_CHANGED: 'treeChanged',
+  VALUE_CHANGED: 'valueChanged',
 };
 
 /**
  * @enum {string}
+ * @see https://developer.chrome.com/extensions/automation#type-RoleType
  */
 chrome.automation.RoleType = {
-  alertDialog: '',
-  alert: '',
-  annotation: '',
-  application: '',
-  article: '',
-  banner: '',
-  blockquote: '',
-  busyIndicator: '',
-  button: '',
-  buttonDropDown: '',
-  canvas: '',
-  caption: '',
-  cell: '',
-  checkBox: '',
-  client: '',
-  colorWell: '',
-  columnHeader: '',
-  column: '',
-  comboBox: '',
-  complementary: '',
-  contentInfo: '',
-  date: '',
-  dateTime: '',
-  definition: '',
-  descriptionListDetail: '',
-  descriptionList: '',
-  descriptionListTerm: '',
-  desktop: '',
-  details: '',
-  dialog: '',
-  directory: '',
-  disclosureTriangle: '',
-  div: '',
-  document: '',
-  embeddedObject: '',
-  figcaption: '',
-  figure: '',
-  footer: '',
-  form: '',
-  grid: '',
-  group: '',
-  heading: '',
-  iframe: '',
-  iframePresentational: '',
-  ignored: '',
-  imageMapLink: '',
-  imageMap: '',
-  image: '',
-  inlineTextBox: '',
-  labelText: '',
-  legend: '',
-  lineBreak: '',
-  link: '',
-  listBoxOption: '',
-  listBox: '',
-  listItem: '',
-  listMarker: '',
-  list: '',
-  locationBar: '',
-  log: '',
-  main: '',
-  marquee: '',
-  math: '',
-  menuBar: '',
-  menuButton: '',
-  menuItem: '',
-  menuItemCheckBox: '',
-  menuItemRadio: '',
-  menuListOption: '',
-  menuListPopup: '',
-  menu: '',
-  meter: '',
-  navigation: '',
-  note: '',
-  outline: '',
-  pane: '',
-  paragraph: '',
-  popUpButton: '',
-  pre: '',
-  presentational: '',
-  progressIndicator: '',
-  radioButton: '',
-  radioGroup: '',
-  region: '',
-  rootWebArea: '',
-  rowHeader: '',
-  row: '',
-  ruby: '',
-  ruler: '',
-  svgRoot: '',
-  scrollArea: '',
-  scrollBar: '',
-  seamlessWebArea: '',
-  search: '',
-  searchBox: '',
-  slider: '',
-  sliderThumb: '',
-  spinButtonPart: '',
-  spinButton: '',
-  splitter: '',
-  staticText: '',
-  status: '',
-  switch: '',
-  tabGroup: '',
-  tabList: '',
-  tabPanel: '',
-  tab: '',
-  tableHeaderContainer: '',
-  table: '',
-  textField: '',
-  time: '',
-  timer: '',
-  titleBar: '',
-  toggleButton: '',
-  toolbar: '',
-  treeGrid: '',
-  treeItem: '',
-  tree: '',
-  unknown: '',
-  tooltip: '',
-  webArea: '',
-  webView: '',
-  window: '',
+  ABBR: 'abbr',
+  ALERT_DIALOG: 'alertDialog',
+  ALERT: 'alert',
+  ANNOTATION: 'annotation',
+  APPLICATION: 'application',
+  ARTICLE: 'article',
+  AUDIO: 'audio',
+  BANNER: 'banner',
+  BLOCKQUOTE: 'blockquote',
+  BUSY_INDICATOR: 'busyIndicator',
+  BUTTON: 'button',
+  BUTTON_DROP_DOWN: 'buttonDropDown',
+  CANVAS: 'canvas',
+  CAPTION: 'caption',
+  CELL: 'cell',
+  CHECK_BOX: 'checkBox',
+  CLIENT: 'client',
+  COLOR_WELL: 'colorWell',
+  COLUMN_HEADER: 'columnHeader',
+  COLUMN: 'column',
+  COMBO_BOX: 'comboBox',
+  COMPLEMENTARY: 'complementary',
+  CONTENT_INFO: 'contentInfo',
+  DATE: 'date',
+  DATE_TIME: 'dateTime',
+  DEFINITION: 'definition',
+  DESCRIPTION_LIST_DETAIL: 'descriptionListDetail',
+  DESCRIPTION_LIST: 'descriptionList',
+  DESCRIPTION_LIST_TERM: 'descriptionListTerm',
+  DESKTOP: 'desktop',
+  DETAILS: 'details',
+  DIALOG: 'dialog',
+  DIRECTORY: 'directory',
+  DISCLOSURE_TRIANGLE: 'disclosureTriangle',
+  DIV: 'div',
+  DOCUMENT: 'document',
+  EMBEDDED_OBJECT: 'embeddedObject',
+  FEED: 'feed',
+  FIGCAPTION: 'figcaption',
+  FIGURE: 'figure',
+  FOOTER: 'footer',
+  FORM: 'form',
+  GRID: 'grid',
+  GROUP: 'group',
+  HEADING: 'heading',
+  IFRAME: 'iframe',
+  IFRAME_PRESENTATIONAL: 'iframePresentational',
+  IGNORED: 'ignored',
+  IMAGE_MAP_LINK: 'imageMapLink',
+  IMAGE_MAP: 'imageMap',
+  IMAGE: 'image',
+  INLINE_TEXT_BOX: 'inlineTextBox',
+  INPUT_TIME: 'inputTime',
+  LABEL_TEXT: 'labelText',
+  LEGEND: 'legend',
+  LINE_BREAK: 'lineBreak',
+  LINK: 'link',
+  LIST_BOX_OPTION: 'listBoxOption',
+  LIST_BOX: 'listBox',
+  LIST_ITEM: 'listItem',
+  LIST_MARKER: 'listMarker',
+  LIST: 'list',
+  LOCATION_BAR: 'locationBar',
+  LOG: 'log',
+  MAIN: 'main',
+  MARK: 'mark',
+  MARQUEE: 'marquee',
+  MATH: 'math',
+  MENU_BAR: 'menuBar',
+  MENU_BUTTON: 'menuButton',
+  MENU_ITEM: 'menuItem',
+  MENU_ITEM_CHECK_BOX: 'menuItemCheckBox',
+  MENU_ITEM_RADIO: 'menuItemRadio',
+  MENU_LIST_OPTION: 'menuListOption',
+  MENU_LIST_POPUP: 'menuListPopup',
+  MENU: 'menu',
+  METER: 'meter',
+  NAVIGATION: 'navigation',
+  NOTE: 'note',
+  OUTLINE: 'outline',
+  PANE: 'pane',
+  PARAGRAPH: 'paragraph',
+  POP_UP_BUTTON: 'popUpButton',
+  PRE: 'pre',
+  PRESENTATIONAL: 'presentational',
+  PROGRESS_INDICATOR: 'progressIndicator',
+  RADIO_BUTTON: 'radioButton',
+  RADIO_GROUP: 'radioGroup',
+  REGION: 'region',
+  ROOT_WEB_AREA: 'rootWebArea',
+  ROW_HEADER: 'rowHeader',
+  ROW: 'row',
+  RUBY: 'ruby',
+  RULER: 'ruler',
+  SVG_ROOT: 'svgRoot',
+  SCROLL_AREA: 'scrollArea',
+  SCROLL_BAR: 'scrollBar',
+  SEAMLESS_WEB_AREA: 'seamlessWebArea',
+  SEARCH: 'search',
+  SEARCH_BOX: 'searchBox',
+  SLIDER: 'slider',
+  SLIDER_THUMB: 'sliderThumb',
+  SPIN_BUTTON_PART: 'spinButtonPart',
+  SPIN_BUTTON: 'spinButton',
+  SPLITTER: 'splitter',
+  STATIC_TEXT: 'staticText',
+  STATUS: 'status',
+  SWITCH: 'switch',
+  TAB_GROUP: 'tabGroup',
+  TAB_LIST: 'tabList',
+  TAB_PANEL: 'tabPanel',
+  TAB: 'tab',
+  TABLE_HEADER_CONTAINER: 'tableHeaderContainer',
+  TABLE: 'table',
+  TERM: 'term',
+  TEXT_FIELD: 'textField',
+  TIME: 'time',
+  TIMER: 'timer',
+  TITLE_BAR: 'titleBar',
+  TOGGLE_BUTTON: 'toggleButton',
+  TOOLBAR: 'toolbar',
+  TREE_GRID: 'treeGrid',
+  TREE_ITEM: 'treeItem',
+  TREE: 'tree',
+  UNKNOWN: 'unknown',
+  TOOLTIP: 'tooltip',
+  VIDEO: 'video',
+  WEB_AREA: 'webArea',
+  WEB_VIEW: 'webView',
+  WINDOW: 'window',
 };
 
 /**
  * @enum {string}
+ * @see https://developer.chrome.com/extensions/automation#type-StateType
  */
 chrome.automation.StateType = {
-  busy: '',
-  checked: '',
-  collapsed: '',
-  default: '',
-  disabled: '',
-  editable: '',
-  expanded: '',
-  focusable: '',
-  focused: '',
-  haspopup: '',
-  horizontal: '',
-  hovered: '',
-  indeterminate: '',
-  invisible: '',
-  linked: '',
-  multiline: '',
-  multiselectable: '',
-  offscreen: '',
-  pressed: '',
-  protected: '',
-  readOnly: '',
-  required: '',
-  richlyEditable: '',
-  selectable: '',
-  selected: '',
-  vertical: '',
-  visited: '',
-};
-
-/**
- * @enum {number}
- */
-chrome.automation.NameFromType = {
-  0: '',
-  1: 'uninitialized',
-  2: 'attribute',
-  3: 'contents',
-  4: 'placeholder',
-  5: 'relatedElement',
-  6: 'value'
-};
-
-/**
- * @enum {number}
- */
-chrome.automation.DescriptionFromType = {
-  0: '',
-  1: 'uninitialized',
-  2: 'attribute',
-  3: 'contents',
-  4: 'placeholder',
-  5: 'relatedElement'
+  BUSY: 'busy',
+  CHECKED: 'checked',
+  COLLAPSED: 'collapsed',
+  DEFAULT: 'default',
+  DISABLED: 'disabled',
+  EDITABLE: 'editable',
+  EXPANDED: 'expanded',
+  FOCUSABLE: 'focusable',
+  FOCUSED: 'focused',
+  HASPOPUP: 'haspopup',
+  HORIZONTAL: 'horizontal',
+  HOVERED: 'hovered',
+  INVISIBLE: 'invisible',
+  LINKED: 'linked',
+  MULTILINE: 'multiline',
+  MULTISELECTABLE: 'multiselectable',
+  OFFSCREEN: 'offscreen',
+  PRESSED: 'pressed',
+  PROTECTED: 'protected',
+  READ_ONLY: 'readOnly',
+  REQUIRED: 'required',
+  RICHLY_EDITABLE: 'richlyEditable',
+  SELECTABLE: 'selectable',
+  SELECTED: 'selected',
+  VERTICAL: 'vertical',
+  VISITED: 'visited',
 };
 
 /**
  * @enum {string}
+ * @see https://developer.chrome.com/extensions/automation#type-TreeChangeType
  */
 chrome.automation.TreeChangeType = {
-  nodeCreated: 'nodeCreated',
-  subtreeCreated: 'subtreeCreated',
-  nodeChanged: 'nodeChanged',
-  nodeRemoved: 'nodeRemoved',
+  NODE_CREATED: 'nodeCreated',
+  SUBTREE_CREATED: 'subtreeCreated',
+  NODE_CHANGED: 'nodeChanged',
+  TEXT_CHANGED: 'textChanged',
+  NODE_REMOVED: 'nodeRemoved',
+};
+
+/**
+ * @enum {string}
+ * @see https://developer.chrome.com/extensions/automation#type-NameFromType
+ */
+chrome.automation.NameFromType = {
+  UNINITIALIZED: 'uninitialized',
+  ATTRIBUTE: 'attribute',
+  CONTENTS: 'contents',
+  PLACEHOLDER: 'placeholder',
+  RELATED_ELEMENT: 'related_element',
+  VALUE: 'value',
 };
 
 /**
@@ -270,6 +272,7 @@
  *   width: number,
  *   height: number
  * }}
+ * @see https://developer.chrome.com/extensions/automation#type-Rect
  */
 chrome.automation.Rect;
 
@@ -279,419 +282,890 @@
  *   state: (Object|undefined),
  *   attributes: (Object|undefined)
  * }}
+ * @see https://developer.chrome.com/extensions/automation#type-FindParams
  */
 chrome.automation.FindParams;
 
 /**
- * @constructor
- * @param {chrome.automation.EventType} type
- * @param {chrome.automation.AutomationNode} node
- * @param {string} eventFrom
+ * @typedef {{
+ *   anchorObject: Object,
+ *   anchorOffset: number,
+ *   focusObject: Object,
+ *   focusOffset: number
+ * }}
+ * @see https://developer.chrome.com/extensions/automation#type-SetDocumentSelectionParams
  */
-chrome.automation.AutomationEvent = function(type, node, eventFrom) {};
+chrome.automation.SetDocumentSelectionParams;
 
 /**
+ * @constructor
+ * @private
+ * @see https://developer.chrome.com/extensions/automation#type-AutomationEvent
+ */
+chrome.automation.AutomationEvent = function() {};
+
+/**
+ * The $(ref:automation.AutomationNode) to which the event was targeted.
  * @type {!chrome.automation.AutomationNode}
+ * @see https://developer.chrome.com/extensions/automation#type-target
  */
 chrome.automation.AutomationEvent.prototype.target;
 
 /**
+ * The type of the event.
  * @type {!chrome.automation.EventType}
+ * @see https://developer.chrome.com/extensions/automation#type-type
  */
 chrome.automation.AutomationEvent.prototype.type;
 
 /**
+ * The source of this event.
  * @type {string}
+ * @see https://developer.chrome.com/extensions/automation#type-eventFrom
  */
 chrome.automation.AutomationEvent.prototype.eventFrom;
 
 /**
  * @type {number}
+ * @see https://developer.chrome.com/extensions/automation#type-mouseX
  */
 chrome.automation.AutomationEvent.prototype.mouseX;
 
 /**
  * @type {number}
+ * @see https://developer.chrome.com/extensions/automation#type-mouseY
  */
 chrome.automation.AutomationEvent.prototype.mouseY;
 
+/**
+ * Stops this event from further processing except for any remaining listeners
+ * on $(ref:AutomationEvent.target).
+ * @see https://developer.chrome.com/extensions/automation#method-stopPropagation
+ */
 chrome.automation.AutomationEvent.prototype.stopPropagation = function() {};
 
+
 /**
  * @typedef {{
- *   target: chrome.automation.AutomationNode,
+ *   target: !chrome.automation.AutomationNode,
  *   type: !chrome.automation.TreeChangeType
  * }}
+ * @see https://developer.chrome.com/extensions/automation#type-TreeChange
  */
 chrome.automation.TreeChange;
 
 /**
+ * @enum {string}
+ * @see https://developer.chrome.com/extensions/automation#type-TreeChangeObserverFilter
+ */
+chrome.automation.TreeChangeObserverFilter = {
+  NO_TREE_CHANGES: 'noTreeChanges',
+  LIVE_REGION_TREE_CHANGES: 'liveRegionTreeChanges',
+  TEXT_MARKER_CHANGES: 'textMarkerChanges',
+  ALL_TREE_CHANGES: 'allTreeChanges',
+};
+
+/**
  * @constructor
+ * @private
+ * @see https://developer.chrome.com/extensions/automation#type-AutomationNode
  */
 chrome.automation.AutomationNode = function() {};
 
-
 /**
- * @param {number} tabId
- * @param {function(chrome.automation.AutomationNode):void} callback
+ * The root node of the tree containing this AutomationNode.
+ * @type {(!chrome.automation.AutomationNode|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-root
  */
-chrome.automation.getTree = function(tabId, callback) {};
-
-/** @param {function(!chrome.automation.AutomationNode):void} callback */
-chrome.automation.getDesktop = function(callback) {};
-
-/** @param {function(!chrome.automation.AutomationNode):void} callback */
-chrome.automation.getFocus = function(callback) {};
+chrome.automation.AutomationNode.prototype.root;
 
 /**
- * @param {string} filter
- * @param {function(chrome.automation.TreeChange) : void}
- *    observer
+ * Whether this AutomationNode is a root node.
+ * @type {boolean}
+ * @see https://developer.chrome.com/extensions/automation#type-isRootNode
  */
-chrome.automation.addTreeChangeObserver = function(filter, observer) {};
+chrome.automation.AutomationNode.prototype.isRootNode;
 
 /**
- * @param {function(chrome.automation.TreeChange) : void} observer
- */
-chrome.automation.removeTreeChangeObserver = function(observer) {};
-
-//
-// End auto generated externs; do not edit.
-//
-
-
-
-/**
- * @type {chrome.automation.RoleType}
+ * The role of this node.
+ * @type {(!chrome.automation.RoleType|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-role
  */
 chrome.automation.AutomationNode.prototype.role;
 
-
 /**
- * @type {!Object<chrome.automation.StateType, boolean>}
+ * The $(ref:automation.StateType)s describing this node.
+ * @type {Object<chrome.automation.StateType, boolean>}
+ * @see https://developer.chrome.com/extensions/automation#type-state
  */
 chrome.automation.AutomationNode.prototype.state;
 
-
 /**
- * @type {chrome.automation.NameFromType}
+ * The rendered location (as a bounding box) of this node in global screen coordinates.
+ * @type {(!chrome.automation.Rect|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-location
  */
-chrome.automation.AutomationNode.prototype.nameFrom;
-
+chrome.automation.AutomationNode.prototype.location;
 
 /**
- * @type {chrome.automation.DescriptionFromType}
+ * Computes the bounding box of a subrange of this node in global screen
+ * coordinates. Returns the same as |location| if range information is not
+ * available. The start and end indices are zero-based offsets into the node's
+ * "name" string attribute.
+ * @param {number} startIndex
+ * @param {number} endIndex
+ * @return {!chrome.automation.Rect}
+ * @see https://developer.chrome.com/extensions/automation#method-boundsForRange
  */
-chrome.automation.AutomationNode.prototype.descriptionFrom;
-
+chrome.automation.AutomationNode.prototype.boundsForRange = function(startIndex, endIndex) {};
 
 /**
- * @type {number}
+ * The purpose of the node, other than the role, if any.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-description
  */
-chrome.automation.AutomationNode.prototype.indexInParent;
-
+chrome.automation.AutomationNode.prototype.description;
 
 /**
- * @type {string}
+ * The placeholder for this text field, if any.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-placeholder
+ */
+chrome.automation.AutomationNode.prototype.placeholder;
+
+/**
+ * The accessible name for this node, via the <a href="http://www.w3.org/TR/wai-aria/roles#namecalculation"> Accessible Name Calculation</a> process.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-name
  */
 chrome.automation.AutomationNode.prototype.name;
 
 /**
- * @type {string}
+ * The source of the name.
+ * @type {(!chrome.automation.NameFromType|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-nameFrom
  */
-chrome.automation.AutomationNode.prototype.description;
-
+chrome.automation.AutomationNode.prototype.nameFrom;
 
 /**
- * @type {string}
- */
-chrome.automation.AutomationNode.prototype.url;
-
-
-/**
- * @type {string}
- */
-chrome.automation.AutomationNode.prototype.docUrl;
-
-
-/**
- * @type {string}
+ * The value for this node: for example the <code>value</code> attribute of an <code>&lt;input&gt; element.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-value
  */
 chrome.automation.AutomationNode.prototype.value;
 
-
 /**
- * @type {number}
+ * The HTML tag for this element, if this node is an HTML element.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-htmlTag
  */
-chrome.automation.AutomationNode.prototype.textSelStart;
-
+chrome.automation.AutomationNode.prototype.htmlTag;
 
 /**
- * @type {number}
+ * The level of a heading or tree item.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-hierarchicalLevel
  */
-chrome.automation.AutomationNode.prototype.textSelEnd;
-
+chrome.automation.AutomationNode.prototype.hierarchicalLevel;
 
 /**
- * @type {Array<number>}
+ * The start and end index of each word in an inline text box.
+ * @type {(!Array<number>|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-wordStarts
  */
 chrome.automation.AutomationNode.prototype.wordStarts;
 
-
 /**
- * @type {Array<number>}
+ * @type {(!Array<number>|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-wordEnds
  */
 chrome.automation.AutomationNode.prototype.wordEnds;
 
+/**
+ * The nodes, if any, which this node is specified to control via <a href="http://www.w3.org/TR/wai-aria/states_and_properties#aria-controls"> <code>aria-controls</code></a>.
+ * @type {(!Array<!chrome.automation.AutomationNode>|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-controls
+ */
+chrome.automation.AutomationNode.prototype.controls;
 
 /**
- * @type {chrome.automation.AutomationRootNode}
+ * The nodes, if any, which form a description for this node.
+ * @type {(!Array<!chrome.automation.AutomationNode>|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-describedBy
  */
-chrome.automation.AutomationNode.prototype.root;
-
+chrome.automation.AutomationNode.prototype.describedBy;
 
 /**
- * @type {chrome.automation.AutomationNode}
+ * The nodes, if any, which may optionally be navigated to after this one. See <a href="http://www.w3.org/TR/wai-aria/states_and_properties#aria-flowto"> <code>aria-flowto</code></a>.
+ * @type {(!Array<!chrome.automation.AutomationNode>|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-flowTo
  */
-chrome.automation.AutomationNode.prototype.firstChild;
-
+chrome.automation.AutomationNode.prototype.flowTo;
 
 /**
- * @type {chrome.automation.AutomationNode}
+ * The nodes, if any, which form a label for this element. Generally, the text from these elements will also be exposed as the element's accessible name, via the $(ref:automation.AutomationNode.name) attribute.
+ * @type {(!Array<!chrome.automation.AutomationNode>|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-labelledBy
  */
-chrome.automation.AutomationNode.prototype.lastChild;
-
+chrome.automation.AutomationNode.prototype.labelledBy;
 
 /**
- * @type {chrome.automation.AutomationNode}
+ * The node referred to by <code>aria-activedescendant</code>, where applicable
+ * @type {(!chrome.automation.AutomationNode|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-activeDescendant
  */
-chrome.automation.AutomationNode.prototype.nextSibling;
-
+chrome.automation.AutomationNode.prototype.activeDescendant;
 
 /**
- * @type {chrome.automation.AutomationNode}
+ * The URL that this link will navigate to.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-url
  */
-chrome.automation.AutomationNode.prototype.previousSibling;
-
+chrome.automation.AutomationNode.prototype.url;
 
 /**
- * @type {chrome.automation.AutomationNode}
+ * The URL of this document.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-docUrl
  */
-chrome.automation.AutomationNode.prototype.parent;
-
+chrome.automation.AutomationNode.prototype.docUrl;
 
 /**
- * @type {!Array<chrome.automation.AutomationNode>}
+ * The title of this document.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-docTitle
  */
-chrome.automation.AutomationNode.prototype.children;
-
+chrome.automation.AutomationNode.prototype.docTitle;
 
 /**
- * @type {{top: number, left: number, height: number, width: number}|undefined}
+ * Whether this document has finished loading.
+ * @type {(boolean|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-docLoaded
  */
-chrome.automation.AutomationNode.prototype.location;
-
+chrome.automation.AutomationNode.prototype.docLoaded;
 
 /**
- * @param {number} start
- * @param {number} end
- * @return {
- *     ({top: number, left: number, height: number, width: number})|undefined}
+ * The proportion (out of 1.0) that this doc has completed loading.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-docLoadingProgress
  */
-chrome.automation.AutomationNode.prototype.boundsForRange =
-    function(start, end) {};
-
-
-chrome.automation.AutomationNode.prototype.makeVisible = function() {};
-
+chrome.automation.AutomationNode.prototype.docLoadingProgress;
 
 /**
- * @param {chrome.automation.EventType} eventType
- * @param {function(!chrome.automation.AutomationEvent) : void} callback
- * @param {boolean} capture
+ * Scrollable container attributes.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-scrollX
  */
-chrome.automation.AutomationNode.prototype.addEventListener =
-    function(eventType, callback, capture) {};
-
-
-/**
- * @param {chrome.automation.EventType} eventType
- * @param {function(!chrome.automation.AutomationEvent) : void} callback
- * @param {boolean} capture
- */
-chrome.automation.AutomationNode.prototype.removeEventListener =
-    function(eventType, callback, capture) {};
-
-
-/**
- * @type {chrome.automation.AutomationNode}
- */
-chrome.automation.TreeChange.prototype.target;
-
-
-/**
- * @type {chrome.automation.TreeChangeType}
- */
-chrome.automation.TreeChange.prototype.type;
-
-
-chrome.automation.AutomationNode.prototype.doDefault = function() {};
-
-
-chrome.automation.AutomationNode.prototype.focus = function() {};
-
-
-chrome.automation.AutomationNode.prototype.showContextMenu = function() {};
-
-
-chrome.automation.AutomationNode.prototype
-    .setSequentialFocusNavigationStartingPoint = function() {};
-
-
-/**
- * @param {number} start
- * @param {number} end
- */
-chrome.automation.AutomationNode.prototype.setSelection =
-    function(start, end) {};
-
-/** @type {string} */
-chrome.automation.AutomationNode.prototype.containerLiveStatus;
-
-/** @type {string} */
-chrome.automation.AutomationNode.prototype.containerLiveRelevant;
-
-/** @type {boolean} */
-chrome.automation.AutomationNode.prototype.containerLiveAtomic;
-
-/** @type {boolean} */
-chrome.automation.AutomationNode.prototype.containerLiveBusy;
-
-/** @type {string} */
-chrome.automation.AutomationNode.prototype.language;
-
-/** @type {string} */
-chrome.automation.AutomationNode.prototype.liveStatus;
-
-/** @type {string} */
-chrome.automation.AutomationNode.prototype.liveRelevant;
-
-/** @type {boolean} */
-chrome.automation.AutomationNode.prototype.liveAtomic;
-
-/** @type {boolean} */
-chrome.automation.AutomationNode.prototype.liveBusy;
-
-
-/**
- * @param {Object} findParams
- */
-chrome.automation.AutomationNode.prototype.find = function(findParams) {};
-
-/**
- * @param {Object} findParams
- * @return {Array<chrome.automation.AutomationNode>}
- */
-chrome.automation.AutomationNode.prototype.findAll = function(findParams) {};
-
-/**
- * @type {string}
- */
-chrome.automation.AutomationNode.prototype.inputType;
-
-/**
- * @type {(chrome.automation.AutomationNode|undefined)}
- */
-chrome.automation.AutomationNode.prototype.anchorObject;
-
-/**
- * @param {{anchorObject: !chrome.automation.AutomationNode,
- *          anchorOffset: number,
- *          focusObject: !chrome.automation.AutomationNode,
- *          focusOffset: number}} selectionParams
- */
-chrome.automation.setDocumentSelection = function(selectionParams) {};
+chrome.automation.AutomationNode.prototype.scrollX;
 
 /**
  * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-scrollXMin
  */
-chrome.automation.anchorOffset;
+chrome.automation.AutomationNode.prototype.scrollXMin;
 
 /**
- * @type {(chrome.automation.AutomationNode|undefined)}
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-scrollXMax
  */
-chrome.automation.AutomationNode.prototype.focusObject;
+chrome.automation.AutomationNode.prototype.scrollXMax;
 
 /**
- * @type {(Array<number>|undefined)}
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-scrollY
+ */
+chrome.automation.AutomationNode.prototype.scrollY;
+
+/**
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-scrollYMin
+ */
+chrome.automation.AutomationNode.prototype.scrollYMin;
+
+/**
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-scrollYMax
+ */
+chrome.automation.AutomationNode.prototype.scrollYMax;
+
+/**
+ * The character index of the start of the selection within this editable text element; -1 if no selection.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-textSelStart
+ */
+chrome.automation.AutomationNode.prototype.textSelStart;
+
+/**
+ * The character index of the end of the selection within this editable text element; -1 if no selection.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-textSelEnd
+ */
+chrome.automation.AutomationNode.prototype.textSelEnd;
+
+/**
+ * The input type, like email or number.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-textInputType
+ */
+chrome.automation.AutomationNode.prototype.textInputType;
+
+/**
+ * An array of indexes of the break between lines in editable text.
+ * @type {!Array<number>}
+ * @see https://developer.chrome.com/extensions/automation#type-lineBreaks
  */
 chrome.automation.AutomationNode.prototype.lineBreaks;
 
 /**
- * @type {(number|undefined)}
+ * An array of indexes of the start position of each text marker.
+ * @type {!Array<number>}
+ * @see https://developer.chrome.com/extensions/automation#type-markerStarts
  */
-chrome.automation.focusOffset;
+chrome.automation.AutomationNode.prototype.markerStarts;
 
 /**
- * @type {(chrome.automation.AutomationNode|undefined)}
+ * An array of indexes of the end position of each text marker.
+ * @type {!Array<number>}
+ * @see https://developer.chrome.com/extensions/automation#type-markerEnds
  */
-chrome.automation.AutomationNode.prototype.activeDescendant;
-
-/** @type {number} */
-chrome.automation.AutomationNode.prototype.tableCellColumnIndex;
-
-/** @type {number} */
-chrome.automation.AutomationNode.prototype.tableCellRowIndex;
-
-/** @type {number} */
-chrome.automation.AutomationNode.prototype.tableColumnCount;
-
-/** @type {number} */
-chrome.automation.AutomationNode.prototype.tableRowCount;
-
-/** @type {number} */
-chrome.automation.AutomationNode.prototype.hierarchicalLevel;
-
-/** @type {Array<number>} */
-chrome.automation.AutomationNode.prototype.markerTypes;
-/** @type {Array<number>} */
-chrome.automation.AutomationNode.prototype.markerStarts;
-/** @type {Array<number>} */
 chrome.automation.AutomationNode.prototype.markerEnds;
 
-/** @type {boolean} */
-chrome.automation.AutomationNode.prototype.ariaReadonly;
+/**
+ * An array of numerical types indicating the type of each text marker, such as a spelling error.
+ * @type {!Array<number>}
+ * @see https://developer.chrome.com/extensions/automation#type-markerTypes
+ */
+chrome.automation.AutomationNode.prototype.markerTypes;
 
-/** @type {chrome.automation.AutomationNode} */
-chrome.automation.AutomationNode.prototype.nextOnLine;
-/** @type {chrome.automation.AutomationNode} */
-chrome.automation.AutomationNode.prototype.previousOnLine;
+/**
+ * The anchor node of the tree selection, if any.
+ * @type {(!chrome.automation.AutomationNode|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-anchorObject
+ */
+chrome.automation.AutomationNode.prototype.anchorObject;
 
-/** @type {Object<string, string>} */
+/**
+ * The anchor offset of the tree selection, if any.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-anchorOffset
+ */
+chrome.automation.AutomationNode.prototype.anchorOffset;
+
+/**
+ * The affinity of the tree selection anchor, if any.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-anchorAffinity
+ */
+chrome.automation.AutomationNode.prototype.anchorAffinity;
+
+/**
+ * The focus node of the tree selection, if any.
+ * @type {(!chrome.automation.AutomationNode|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-focusObject
+ */
+chrome.automation.AutomationNode.prototype.focusObject;
+
+/**
+ * The focus offset of the tree selection, if any.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-focusOffset
+ */
+chrome.automation.AutomationNode.prototype.focusOffset;
+
+/**
+ * The affinity of the tree selection focus, if any.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-focusAffinity
+ */
+chrome.automation.AutomationNode.prototype.focusAffinity;
+
+/**
+ * The current value for this range.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-valueForRange
+ */
+chrome.automation.AutomationNode.prototype.valueForRange;
+
+/**
+ * The minimum possible value for this range.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-minValueForRange
+ */
+chrome.automation.AutomationNode.prototype.minValueForRange;
+
+/**
+ * The maximum possible value for this range.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-maxValueForRange
+ */
+chrome.automation.AutomationNode.prototype.maxValueForRange;
+
+/**
+ * The 1-based index of an item in a set.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-posInSet
+ */
+chrome.automation.AutomationNode.prototype.posInSet;
+
+/**
+ * The number of items in a set;
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-setSize
+ */
+chrome.automation.AutomationNode.prototype.setSize;
+
+/**
+ * The number of rows in this table.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-tableRowCount
+ */
+chrome.automation.AutomationNode.prototype.tableRowCount;
+
+/**
+ * The number of columns in this table.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-tableColumnCount
+ */
+chrome.automation.AutomationNode.prototype.tableColumnCount;
+
+/**
+ * The zero-based index of the column that this cell is in.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-tableCellColumnIndex
+ */
+chrome.automation.AutomationNode.prototype.tableCellColumnIndex;
+
+/**
+ * The number of columns that this cell spans (default is 1).
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-tableCellColumnSpan
+ */
+chrome.automation.AutomationNode.prototype.tableCellColumnSpan;
+
+/**
+ * The zero-based index of the row that this cell is in.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-tableCellRowIndex
+ */
+chrome.automation.AutomationNode.prototype.tableCellRowIndex;
+
+/**
+ * The number of rows that this cell spans (default is 1).
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-tableCellRowSpan
+ */
+chrome.automation.AutomationNode.prototype.tableCellRowSpan;
+
+/**
+ * The corresponding column header for this cell.
+ * @type {(!chrome.automation.AutomationNode|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-tableColumnHeader
+ */
+chrome.automation.AutomationNode.prototype.tableColumnHeader;
+
+/**
+ * The corresponding row header for this cell.
+ * @type {(!chrome.automation.AutomationNode|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-tableRowHeader
+ */
+chrome.automation.AutomationNode.prototype.tableRowHeader;
+
+/**
+ * The type of region if this is the root of a live region. Possible values are 'polite' and 'assertive'.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-liveStatus
+ */
+chrome.automation.AutomationNode.prototype.liveStatus;
+
+/**
+ * The value of aria-relevant for a live region.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-liveRelevant
+ */
+chrome.automation.AutomationNode.prototype.liveRelevant;
+
+/**
+ * The value of aria-atomic for a live region.
+ * @type {(boolean|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-liveAtomic
+ */
+chrome.automation.AutomationNode.prototype.liveAtomic;
+
+/**
+ * The value of aria-busy for a live region.
+ * @type {(boolean|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-liveBusy
+ */
+chrome.automation.AutomationNode.prototype.liveBusy;
+
+/**
+ * The type of live region if this node is inside a live region.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-containerLiveStatus
+ */
+chrome.automation.AutomationNode.prototype.containerLiveStatus;
+
+/**
+ * The value of aria-relevant if this node is inside a live region.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-containerLiveRelevant
+ */
+chrome.automation.AutomationNode.prototype.containerLiveRelevant;
+
+/**
+ * The value of aria-atomic if this node is inside a live region.
+ * @type {(boolean|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-containerLiveAtomic
+ */
+chrome.automation.AutomationNode.prototype.containerLiveAtomic;
+
+/**
+ * The value of aria-busy if this node is inside a live region.
+ * @type {(boolean|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-containerLiveBusy
+ */
+chrome.automation.AutomationNode.prototype.containerLiveBusy;
+
+/**
+ * A map containing all HTML attributes and their values
+ * @type {Object<string>}
+ * @see https://developer.chrome.com/extensions/automation#type-htmlAttributes
+ */
 chrome.automation.AutomationNode.prototype.htmlAttributes;
 
 /**
- * @extends {chrome.automation.AutomationNode}
- * @constructor
+ * The input type of a text field, such as "text" or "email".
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-inputType
  */
-chrome.automation.AutomationRootNode = function() {};
+chrome.automation.AutomationNode.prototype.inputType;
 
 /**
- * @type {chrome.automation.AutomationNode}
+ * The key that activates this widget.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-accessKey
  */
-chrome.automation.AutomationRootNode.prototype.anchorObject;
+chrome.automation.AutomationNode.prototype.accessKey;
 
 /**
- * @type {number}
+ * The value of the aria-invalid attribute, indicating the error type.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-ariaInvalidValue
  */
-chrome.automation.AutomationRootNode.prototype.anchorOffset;
+chrome.automation.AutomationNode.prototype.ariaInvalidValue;
 
 /**
- * @type {chrome.automation.AutomationNode}
+ * The value of the aria-readonly attribute, if applicable.
+ * @type {(boolean|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-ariaReadonly
  */
-chrome.automation.AutomationRootNode.prototype.focusObject;
+chrome.automation.AutomationNode.prototype.ariaReadonly;
 
 /**
- * @type {number}
+ * The CSS display attribute for this node, if applicable.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-display
  */
-chrome.automation.AutomationRootNode.prototype.focusOffset;
+chrome.automation.AutomationNode.prototype.display;
+
+/**
+ * A data url with the contents of this object's image or thumbnail.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-imageDataUrl
+ */
+chrome.automation.AutomationNode.prototype.imageDataUrl;
+
+/**
+ * The language code for this subtree.
+ * @type {(string|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-language
+ */
+chrome.automation.AutomationNode.prototype.language;
+
+/**
+ * If a checkbox or toggle button is in the mixed state.
+ * @type {(boolean|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-buttonMixed
+ */
+chrome.automation.AutomationNode.prototype.buttonMixed;
+
+/**
+ * The RGBA foreground color of this subtree, as an integer.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-color
+ */
+chrome.automation.AutomationNode.prototype.color;
+
+/**
+ * The RGBA background color of this subtree, as an integer.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-backgroundColor
+ */
+chrome.automation.AutomationNode.prototype.backgroundColor;
+
+/**
+ * The RGBA color of an input element whose value is a color.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-colorValue
+ */
+chrome.automation.AutomationNode.prototype.colorValue;
+
+/**
+ * Walking the tree.
+ * @type {!Array<!chrome.automation.AutomationNode>}
+ * @see https://developer.chrome.com/extensions/automation#type-children
+ */
+chrome.automation.AutomationNode.prototype.children;
+
+/**
+ * @type {(!chrome.automation.AutomationNode|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-parent
+ */
+chrome.automation.AutomationNode.prototype.parent;
+
+/**
+ * @type {(!chrome.automation.AutomationNode|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-firstChild
+ */
+chrome.automation.AutomationNode.prototype.firstChild;
+
+/**
+ * @type {(!chrome.automation.AutomationNode|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-lastChild
+ */
+chrome.automation.AutomationNode.prototype.lastChild;
+
+/**
+ * @type {(!chrome.automation.AutomationNode|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-previousSibling
+ */
+chrome.automation.AutomationNode.prototype.previousSibling;
+
+/**
+ * @type {(!chrome.automation.AutomationNode|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-nextSibling
+ */
+chrome.automation.AutomationNode.prototype.nextSibling;
+
+/**
+ * @type {(!chrome.automation.AutomationNode|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-nextOnLine
+ */
+chrome.automation.AutomationNode.prototype.nextOnLine;
+
+/**
+ * @type {(!chrome.automation.AutomationNode|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-previousOnLine
+ */
+chrome.automation.AutomationNode.prototype.previousOnLine;
+
+/**
+ * The index of this node in its parent node's list of children. If this is the root node, this will be undefined.
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/automation#type-indexInParent
+ */
+chrome.automation.AutomationNode.prototype.indexInParent;
+
+/**
+ * Does the default action based on this node's role. This is generally the same
+ * action that would result from clicking the node such as expanding a treeitem,
+ * toggling a checkbox, selecting a radiobutton, or activating a button.
+ * @see https://developer.chrome.com/extensions/automation#method-doDefault
+ */
+chrome.automation.AutomationNode.prototype.doDefault = function() {};
+
+/**
+ * Places focus on this node.
+ * @see https://developer.chrome.com/extensions/automation#method-focus
+ */
+chrome.automation.AutomationNode.prototype.focus = function() {};
+
+/**
+ * Request a data url for the contents of an image, optionally resized.  Pass
+ * zero for maxWidth and/or maxHeight for the original size.
+ * @param {number} maxWidth
+ * @param {number} maxHeight
+ * @see https://developer.chrome.com/extensions/automation#method-getImageData
+ */
+chrome.automation.AutomationNode.prototype.getImageData = function(maxWidth, maxHeight) {};
+
+/**
+ * Scrolls this node to make it visible.
+ * @see https://developer.chrome.com/extensions/automation#method-makeVisible
+ */
+chrome.automation.AutomationNode.prototype.makeVisible = function() {};
+
+/**
+ * Sets selection within a text field.
+ * @param {number} startIndex
+ * @param {number} endIndex
+ * @see https://developer.chrome.com/extensions/automation#method-setSelection
+ */
+chrome.automation.AutomationNode.prototype.setSelection = function(startIndex, endIndex) {};
+
+/**
+ * Clears focus and sets this node as the starting point for the next time the
+ * user presses Tab or Shift+Tab.
+ * @see https://developer.chrome.com/extensions/automation#method-setSequentialFocusNavigationStartingPoint
+ */
+chrome.automation.AutomationNode.prototype.setSequentialFocusNavigationStartingPoint = function() {};
+
+/**
+ * Show the context menu for this element, as if the user right-clicked.
+ * @see https://developer.chrome.com/extensions/automation#method-showContextMenu
+ */
+chrome.automation.AutomationNode.prototype.showContextMenu = function() {};
+
+/**
+ * Resume playing any media within this tree.
+ * @see https://developer.chrome.com/extensions/automation#method-resumeMedia
+ */
+chrome.automation.AutomationNode.prototype.resumeMedia = function() {};
+
+/**
+ * Start ducking any media within this tree.
+ * @see https://developer.chrome.com/extensions/automation#method-startDuckingMedia
+ */
+chrome.automation.AutomationNode.prototype.startDuckingMedia = function() {};
+
+/**
+ * Stop ducking any media within this tree.
+ * @see https://developer.chrome.com/extensions/automation#method-stopDuckingMedia
+ */
+chrome.automation.AutomationNode.prototype.stopDuckingMedia = function() {};
+
+/**
+ * Suspend any media playing within this tree.
+ * @see https://developer.chrome.com/extensions/automation#method-suspendMedia
+ */
+chrome.automation.AutomationNode.prototype.suspendMedia = function() {};
+
+/**
+ * Adds a listener for the given event type and event phase.
+ * @param {!chrome.automation.EventType} eventType
+ * @param {function(!chrome.automation.AutomationEvent):void} listener A
+ *     listener for events on an <code>AutomationNode</code>.
+ * @param {boolean} capture
+ * @see https://developer.chrome.com/extensions/automation#method-addEventListener
+ */
+chrome.automation.AutomationNode.prototype.addEventListener = function(eventType, listener, capture) {};
+
+/**
+ * Removes a listener for the given event type and event phase.
+ * @param {!chrome.automation.EventType} eventType
+ * @param {function(!chrome.automation.AutomationEvent):void} listener A
+ *     listener for events on an <code>AutomationNode</code>.
+ * @param {boolean} capture
+ * @see https://developer.chrome.com/extensions/automation#method-removeEventListener
+ */
+chrome.automation.AutomationNode.prototype.removeEventListener = function(eventType, listener, capture) {};
+
+/**
+ * <p>Gets the first node in this node's subtree which matches the given CSS
+ * selector and is within the same DOM context.</p><p>If this node doesn't
+ * correspond directly with an HTML node in the DOM, querySelector will be run
+ * on this node's nearest HTML node ancestor. Note that this may result in the
+ * query returning a node which is not a descendant of this node.</p><p>If the
+ * selector matches a node which doesn't directly correspond to an automation
+ * node (for example an element within an ARIA widget, where the ARIA widget
+ * forms one node of the automation tree, or an element which is hidden from
+ * accessibility via hiding it using CSS or using aria-hidden), this will return
+ * the nearest ancestor which does correspond to an automation node.</p>
+ * @param {string} selector
+ * @param {function(!chrome.automation.AutomationNode):void} callback Called
+ *     when the result for a <code>query</code> is available.
+ * @see https://developer.chrome.com/extensions/automation#method-domQuerySelector
+ */
+chrome.automation.AutomationNode.prototype.domQuerySelector = function(selector, callback) {};
+
+/**
+ * Finds the first AutomationNode in this node's subtree which matches the given
+ * search parameters.
+ * @param {!chrome.automation.FindParams} params
+ * @return {!chrome.automation.AutomationNode}
+ * @see https://developer.chrome.com/extensions/automation#method-find
+ */
+chrome.automation.AutomationNode.prototype.find = function(params) {};
+
+/**
+ * Finds all the AutomationNodes in this node's subtree which matches the given
+ * search parameters.
+ * @param {!chrome.automation.FindParams} params
+ * @return {!Array<!chrome.automation.AutomationNode>}
+ * @see https://developer.chrome.com/extensions/automation#method-findAll
+ */
+chrome.automation.AutomationNode.prototype.findAll = function(params) {};
+
+/**
+ * Returns whether this node matches the given $(ref:automation.FindParams).
+ * @param {!chrome.automation.FindParams} params
+ * @return {boolean}
+ * @see https://developer.chrome.com/extensions/automation#method-matches
+ */
+chrome.automation.AutomationNode.prototype.matches = function(params) {};
+
+
+/**
+ * Get the automation tree for the tab with the given tabId, or the current tab
+ * if no tabID is given, enabling automation if necessary. Returns a tree with a
+ * placeholder root node; listen for the "loadComplete" event to get a
+ * notification that the tree has fully loaded (the previous root node reference
+ * will stop working at or before this point).
+ * @param {number} tabId
+ * @param {function(!chrome.automation.AutomationNode):void} callback Called
+ *     when the <code>AutomationNode</code> for the page is available.
+ * @see https://developer.chrome.com/extensions/automation#method-getTree
+ */
+chrome.automation.getTree = function(tabId, callback) {};
+
+/**
+ * Get the automation tree for the whole desktop which consists of all on screen
+ * views. Note this API is currently only supported on Chrome OS.
+ * @param {function(!chrome.automation.AutomationNode):void} callback Called
+ *     when the <code>AutomationNode</code> for the page is available.
+ * @see https://developer.chrome.com/extensions/automation#method-getDesktop
+ */
+chrome.automation.getDesktop = function(callback) {};
+
+/**
+ * Get the automation node that currently has focus, globally. Will return null
+ * if none of the nodes in any loaded trees have focus.
+ * @param {function(!chrome.automation.AutomationNode):void} callback Called
+ *     with the <code>AutomationNode</code> that currently has focus.
+ * @see https://developer.chrome.com/extensions/automation#method-getFocus
+ */
+chrome.automation.getFocus = function(callback) {};
+
+/**
+ * Add a tree change observer. Tree change observers are static/global, they
+ * listen to changes across all trees. Pass a filter to determine what specific
+ * tree changes to listen to, and note that listnening to all tree changes can
+ * be expensive.
+ * @param {!chrome.automation.TreeChangeObserverFilter} filter
+ * @param {function(!chrome.automation.TreeChange):void} observer A listener for
+ *     changes on the <code>AutomationNode</code> tree.
+ * @see https://developer.chrome.com/extensions/automation#method-addTreeChangeObserver
+ */
+chrome.automation.addTreeChangeObserver = function(filter, observer) {};
+
+/**
+ * Remove a tree change observer.
+ * @param {function(!chrome.automation.TreeChange):void} observer A listener for
+ *     changes on the <code>AutomationNode</code> tree.
+ * @see https://developer.chrome.com/extensions/automation#method-removeTreeChangeObserver
+ */
+chrome.automation.removeTreeChangeObserver = function(observer) {};
+
+/**
+ * Sets the selection in a tree. This creates a selection in a single tree
+ * (anchorObject and focusObject must have the same root). Everything in the
+ * tree between the two node/offset pairs gets included in the selection. The
+ * anchor is where the user started the selection, while the focus is the point
+ * at which the selection gets extended e.g. when dragging with a mouse or using
+ * the keyboard. For nodes with the role staticText, the offset gives the
+ * character offset within the value where the selection starts or ends,
+ * respectively.
+ * @param {!chrome.automation.SetDocumentSelectionParams} params
+ * @see https://developer.chrome.com/extensions/automation#method-setDocumentSelection
+ */
+chrome.automation.setDocumentSelection = function(params) {};
diff --git a/tools/clang/blink_gc_plugin/BlinkGCPluginConsumer.cpp b/tools/clang/blink_gc_plugin/BlinkGCPluginConsumer.cpp
index c3e277a..c15b8e8 100644
--- a/tools/clang/blink_gc_plugin/BlinkGCPluginConsumer.cpp
+++ b/tools/clang/blink_gc_plugin/BlinkGCPluginConsumer.cpp
@@ -528,7 +528,6 @@
     CXXMethodDecl* method) {
   Config::TraceMethodType trace_type = Config::GetTraceMethodType(method);
   if (trace_type == Config::TRACE_AFTER_DISPATCH_METHOD ||
-      trace_type == Config::TRACE_AFTER_DISPATCH_IMPL_METHOD ||
       !parent->GetTraceDispatchMethod()) {
     CheckTraceMethod(parent, method, trace_type);
   }
@@ -549,12 +548,6 @@
   CheckTraceVisitor visitor(trace, parent, &cache_);
   visitor.TraverseCXXMethodDecl(trace);
 
-  // Skip reporting if this trace method is a just delegate to
-  // traceImpl (or traceAfterDispatchImpl) method. We will report on
-  // CheckTraceMethod on traceImpl method.
-  if (visitor.delegates_to_traceimpl())
-    return;
-
   for (auto& base : parent->GetBases())
     if (!base.second.IsProperlyTraced())
       reporter_.BaseRequiresTracing(parent, trace, base.first);
diff --git a/tools/clang/blink_gc_plugin/CheckTraceVisitor.cpp b/tools/clang/blink_gc_plugin/CheckTraceVisitor.cpp
index c996ca7d..8f6639e 100644
--- a/tools/clang/blink_gc_plugin/CheckTraceVisitor.cpp
+++ b/tools/clang/blink_gc_plugin/CheckTraceVisitor.cpp
@@ -13,15 +13,7 @@
 CheckTraceVisitor::CheckTraceVisitor(CXXMethodDecl* trace,
                                      RecordInfo* info,
                                      RecordCache* cache)
-    : trace_(trace),
-      info_(info),
-      cache_(cache),
-      delegates_to_traceimpl_(false) {
-}
-
-bool CheckTraceVisitor::delegates_to_traceimpl() const {
-  return delegates_to_traceimpl_;
-}
+    : trace_(trace), info_(info), cache_(cache) {}
 
 bool CheckTraceVisitor::VisitMemberExpr(MemberExpr* member) {
   // In weak callbacks, consider any occurrence as a correct usage.
@@ -72,8 +64,6 @@
     CXXRecordDecl* decl = base->getPointeeType()->getAsCXXRecordDecl();
     if (decl)
       CheckTraceFieldCall(expr->getMemberName().getAsString(), decl, arg);
-    if (Config::IsTraceImplName(expr->getMemberName().getAsString()))
-      delegates_to_traceimpl_ = true;
     return true;
   }
 
@@ -81,10 +71,6 @@
     if (CheckTraceFieldMemberCall(expr) || CheckRegisterWeakMembers(expr))
       return true;
 
-    if (Config::IsTraceImplName(expr->getMethodDecl()->getNameAsString())) {
-      delegates_to_traceimpl_ = true;
-      return true;
-    }
   }
 
   CheckTraceBaseCall(call);
@@ -92,10 +78,6 @@
 }
 
 bool CheckTraceVisitor::IsTraceCallName(const std::string& name) {
-  if (trace_->getName() == kTraceImplName)
-    return name == kTraceName;
-  if (trace_->getName() == kTraceAfterDispatchImplName)
-    return name == kTraceAfterDispatchName;
   // Currently, a manually dispatched class cannot have mixin bases (having
   // one would add a vtable which we explicitly check against). This means
   // that we can only make calls to a trace method of the same name. Revisit
@@ -232,8 +214,7 @@
              dyn_cast<UnresolvedMemberExpr>(call->getCallee())) {
     // Callee part may become unresolved if the type of the argument
     // ("visitor") is a template parameter and the called function is
-    // overloaded (i.e. trace(Visitor*) and
-    // trace(InlinedGlobalMarkingVisitor)).
+    // overloaded.
     //
     // Here, we try to find a function that looks like trace() from the
     // candidate overloaded functions, and if we find one, we assume it is
@@ -351,6 +332,8 @@
           nested_visitor.TraverseStmt(callback->getBody());
         }
       }
+      // TODO: mark all WeakMember<>s as traced even if
+      // the body isn't available?
     }
   }
   return true;
diff --git a/tools/clang/blink_gc_plugin/CheckTraceVisitor.h b/tools/clang/blink_gc_plugin/CheckTraceVisitor.h
index 580a6fb..e1afd9b 100644
--- a/tools/clang/blink_gc_plugin/CheckTraceVisitor.h
+++ b/tools/clang/blink_gc_plugin/CheckTraceVisitor.h
@@ -23,8 +23,6 @@
                     RecordInfo* info,
                     RecordCache* cache);
 
-  bool delegates_to_traceimpl() const;
-
   bool VisitMemberExpr(clang::MemberExpr* member);
   bool VisitCallExpr(clang::CallExpr* call);
 
@@ -53,7 +51,6 @@
   clang::CXXMethodDecl* trace_;
   RecordInfo* info_;
   RecordCache* cache_;
-  bool delegates_to_traceimpl_;
 };
 
 #endif  // TOOLS_BLINK_GC_PLUGIN_CHECK_TRACE_VISITOR_H_
diff --git a/tools/clang/blink_gc_plugin/Config.cpp b/tools/clang/blink_gc_plugin/Config.cpp
index bb32ad44..fa00782c 100644
--- a/tools/clang/blink_gc_plugin/Config.cpp
+++ b/tools/clang/blink_gc_plugin/Config.cpp
@@ -14,10 +14,8 @@
 namespace legacy {
 const char kCreateName[] = "create";
 const char kTraceName[] = "trace";
-const char kTraceImplName[] = "traceImpl";
 const char kFinalizeName[] = "finalizeGarbageCollectedObject";
 const char kTraceAfterDispatchName[] = "traceAfterDispatch";
-const char kTraceAfterDispatchImplName[] = "traceAfterDispatchImpl";
 const char kRegisterWeakMembersName[] = "registerWeakMembers";
 const char kAdjustAndMarkName[] = "adjustAndMark";
 const char kIsHeapObjectAliveName[] = "isHeapObjectAlive";
@@ -26,10 +24,8 @@
 const char kNewOperatorName[] = "operator new";
 const char* kCreateName = "Create";
 const char* kTraceName = "Trace";
-const char* kTraceImplName = "TraceImpl";
 const char* kFinalizeName = "FinalizeGarbageCollectedObject";
 const char* kTraceAfterDispatchName = "TraceAfterDispatch";
-const char* kTraceAfterDispatchImplName = "TraceAfterDispatchImpl";
 const char* kRegisterWeakMembersName = "RegisterWeakMembers";
 const char kHeapAllocatorName[] = "HeapAllocator";
 const char kTraceIfNeededName[] = "TraceIfNeeded";
@@ -46,10 +42,8 @@
 void Config::UseLegacyNames() {
   kCreateName = legacy::kCreateName;
   kTraceName = legacy::kTraceName;
-  kTraceImplName = legacy::kTraceImplName;
   kFinalizeName = legacy::kFinalizeName;
   kTraceAfterDispatchName = legacy::kTraceAfterDispatchName;
-  kTraceAfterDispatchImplName = legacy::kTraceAfterDispatchImplName;
   kRegisterWeakMembersName = legacy::kRegisterWeakMembersName;
   kAdjustAndMarkName = legacy::kAdjustAndMarkName;
   kIsHeapObjectAliveName = legacy::kIsHeapObjectAliveName;
diff --git a/tools/clang/blink_gc_plugin/Config.h b/tools/clang/blink_gc_plugin/Config.h
index 2ab933f..f0c4aec 100644
--- a/tools/clang/blink_gc_plugin/Config.h
+++ b/tools/clang/blink_gc_plugin/Config.h
@@ -20,10 +20,8 @@
 extern const char kNewOperatorName[];
 extern const char* kCreateName;
 extern const char* kTraceName;
-extern const char* kTraceImplName;
 extern const char* kFinalizeName;
 extern const char* kTraceAfterDispatchName;
-extern const char* kTraceAfterDispatchImplName;
 extern const char* kRegisterWeakMembersName;
 extern const char kHeapAllocatorName[];
 extern const char kTraceIfNeededName[];
@@ -219,8 +217,6 @@
     NOT_TRACE_METHOD,
     TRACE_METHOD,
     TRACE_AFTER_DISPATCH_METHOD,
-    TRACE_IMPL_METHOD,
-    TRACE_AFTER_DISPATCH_IMPL_METHOD
   };
 
   static TraceMethodType GetTraceMethodType(const clang::FunctionDecl* method) {
@@ -228,15 +224,11 @@
       return NOT_TRACE_METHOD;
 
     const std::string& name = method->getNameAsString();
-    if (name != kTraceName && name != kTraceAfterDispatchName &&
-        name != kTraceImplName && name != kTraceAfterDispatchImplName)
+    if (name != kTraceName && name != kTraceAfterDispatchName)
       return NOT_TRACE_METHOD;
 
     const clang::QualType& formal_type = method->getParamDecl(0)->getType();
-    if (name == kTraceImplName || name == kTraceAfterDispatchImplName) {
-      if (!IsVisitorDispatcherType(formal_type))
-        return NOT_TRACE_METHOD;
-    } else if (!IsVisitorPtrType(formal_type)) {
+    if (!IsVisitorPtrType(formal_type)) {
       return NOT_TRACE_METHOD;
     }
 
@@ -244,10 +236,6 @@
       return TRACE_METHOD;
     if (name == kTraceAfterDispatchName)
       return TRACE_AFTER_DISPATCH_METHOD;
-    if (name == kTraceImplName)
-      return TRACE_IMPL_METHOD;
-    if (name == kTraceAfterDispatchImplName)
-      return TRACE_AFTER_DISPATCH_IMPL_METHOD;
 
     assert(false && "Should not reach here");
     return NOT_TRACE_METHOD;
@@ -257,10 +245,6 @@
     return GetTraceMethodType(method) != NOT_TRACE_METHOD;
   }
 
-  static bool IsTraceImplName(const std::string& name) {
-    return name == kTraceImplName || name == kTraceAfterDispatchImplName;
-  }
-
   static bool StartsWith(const std::string& str, const std::string& prefix) {
     if (prefix.size() > str.size())
       return false;
diff --git a/tools/clang/blink_gc_plugin/RecordInfo.cpp b/tools/clang/blink_gc_plugin/RecordInfo.cpp
index fe211ac..419ed7a 100644
--- a/tools/clang/blink_gc_plugin/RecordInfo.cpp
+++ b/tools/clang/blink_gc_plugin/RecordInfo.cpp
@@ -438,7 +438,6 @@
   if (Config::IsGCBase(name_))
     return;
   CXXMethodDecl* trace = nullptr;
-  CXXMethodDecl* trace_impl = nullptr;
   CXXMethodDecl* trace_after_dispatch = nullptr;
   bool has_adjust_and_mark = false;
   bool has_is_heap_object_alive = false;
@@ -459,11 +458,6 @@
       case Config::TRACE_AFTER_DISPATCH_METHOD:
         trace_after_dispatch = method;
         break;
-      case Config::TRACE_IMPL_METHOD:
-        trace_impl = method;
-        break;
-      case Config::TRACE_AFTER_DISPATCH_IMPL_METHOD:
-        break;
       case Config::NOT_TRACE_METHOD:
         if (method->getNameAsString() == kFinalizeName) {
           finalize_dispatch_method_ = method;
@@ -481,7 +475,7 @@
       has_adjust_and_mark && has_is_heap_object_alive ? kTrue : kFalse;
   if (trace_after_dispatch) {
     trace_method_ = trace_after_dispatch;
-    trace_dispatch_method_ = trace_impl ? trace_impl : trace;
+    trace_dispatch_method_ = trace;
   } else {
     // TODO: Can we never have a dispatch method called trace without the same
     // class defining a traceAfterDispatch method?
diff --git a/tools/clang/blink_gc_plugin/tests/heap/stubs.h b/tools/clang/blink_gc_plugin/tests/heap/stubs.h
index b674048..4f17273 100644
--- a/tools/clang/blink_gc_plugin/tests/heap/stubs.h
+++ b/tools/clang/blink_gc_plugin/tests/heap/stubs.h
@@ -276,26 +276,13 @@
 template<typename T>
 class PersistentHeapVector : public Vector<T, 0, HeapAllocator> { };
 
-template <typename Derived>
-class VisitorHelper {
-public:
-    template<typename T>
-    void Trace(const T&);
-};
+class Visitor {
+ public:
+  template <typename T, void (T::*method)(Visitor*)>
+  void RegisterWeakMembers(const T* obj);
 
-class Visitor : public VisitorHelper<Visitor> {
-public:
-    template<typename T, void (T::*method)(Visitor*)>
-    void RegisterWeakMembers(const T* obj);
-};
-
-class InlinedGlobalMarkingVisitor
-    : public VisitorHelper<InlinedGlobalMarkingVisitor> {
-public:
-    InlinedGlobalMarkingVisitor* operator->() { return this; }
-
-    template<typename T, void (T::*method)(Visitor*)>
-    void RegisterWeakMembers(const T* obj);
+  template <typename T>
+  void Trace(const T&);
 };
 
 class GarbageCollectedMixin {
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/heap/stubs.h b/tools/clang/blink_gc_plugin/tests/legacy_naming/heap/stubs.h
index f8fde06..2b965df 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/heap/stubs.h
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/heap/stubs.h
@@ -276,26 +276,13 @@
 template<typename T>
 class PersistentHeapVector : public Vector<T, 0, HeapAllocator> { };
 
-template <typename Derived>
-class VisitorHelper {
-public:
-    template<typename T>
-    void trace(const T&);
-};
+class Visitor {
+ public:
+  template <typename T, void (T::*method)(Visitor*)>
+  void registerWeakMembers(const T* obj);
 
-class Visitor : public VisitorHelper<Visitor> {
-public:
-    template<typename T, void (T::*method)(Visitor*)>
-    void registerWeakMembers(const T* obj);
-};
-
-class InlinedGlobalMarkingVisitor
-    : public VisitorHelper<InlinedGlobalMarkingVisitor> {
-public:
-    InlinedGlobalMarkingVisitor* operator->() { return this; }
-
-    template<typename T, void (T::*method)(Visitor*)>
-    void registerWeakMembers(const T* obj);
+  template <typename T>
+  void trace(const T&);
 };
 
 class GarbageCollectedMixin {
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/register_weak_members_template.h b/tools/clang/blink_gc_plugin/tests/legacy_naming/register_weak_members_template.h
index 7d3905a..4df3ade 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/register_weak_members_template.h
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/register_weak_members_template.h
@@ -11,30 +11,20 @@
 
 class X : public GarbageCollected<X> {
  public:
-  void trace(Visitor* visitor) { traceImpl(visitor); }
-  void trace(InlinedGlobalMarkingVisitor visitor) { traceImpl(visitor); }
-
- private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {}
+  void trace(Visitor*) {}
 };
 
 class HasUntracedWeakMembers : public GarbageCollected<HasUntracedWeakMembers> {
  public:
-  void trace(Visitor* visitor) { traceImpl(visitor); }
-  void trace(InlinedGlobalMarkingVisitor visitor) { traceImpl(visitor); }
+  void trace(Visitor* visitor) {
+    visitor->registerWeakMembers<HasUntracedWeakMembers,
+                                 &HasUntracedWeakMembers::clearWeakMembers>(
+        this);
+  }
 
-  // Don't have to be defined for the purpose of this test.
   void clearWeakMembers(Visitor* visitor);
 
  private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {
-    visitor->template registerWeakMembers<
-        HasUntracedWeakMembers,
-        &HasUntracedWeakMembers::clearWeakMembers>(this);
-  }
-
   WeakMember<X> x_;
 };
 
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/register_weak_members_template.txt b/tools/clang/blink_gc_plugin/tests/legacy_naming/register_weak_members_template.txt
index e69de29..b168673 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/register_weak_members_template.txt
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/register_weak_members_template.txt
@@ -0,0 +1,8 @@
+In file included from register_weak_members_template.cpp:5:
+./register_weak_members_template.h:19:3: warning: [blink-gc] Class 'HasUntracedWeakMembers' has untraced fields that require tracing.
+  void trace(Visitor* visitor) {
+  ^
+./register_weak_members_template.h:28:3: note: [blink-gc] Untraced field 'x_' declared here:
+  WeakMember<X> x_;
+  ^
+1 warning generated.
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl.cpp b/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl.cpp
index 53a6855..ae5ec1e 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl.cpp
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl.cpp
@@ -6,9 +6,7 @@
 
 namespace blink {
 
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchInlinedBase::traceImpl(
-    VisitorDispatcher visitor) {
+void TraceAfterDispatchInlinedBase::trace(Visitor* visitor) {
   // Implement a simple form of manual dispatching, because BlinkGCPlugin
   // checks if the tracing is dispatched to all derived classes.
   //
@@ -23,15 +21,6 @@
 }
 
 void TraceAfterDispatchExternBase::trace(Visitor* visitor) {
-  traceImpl(visitor);
-}
-
-void TraceAfterDispatchExternBase::trace(InlinedGlobalMarkingVisitor visitor) {
-  traceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchExternBase::traceImpl(VisitorDispatcher visitor) {
   if (tag_ == DERIVED) {
     static_cast<TraceAfterDispatchExternDerived*>(this)->traceAfterDispatch(
         visitor);
@@ -41,32 +30,10 @@
 }
 
 void TraceAfterDispatchExternBase::traceAfterDispatch(Visitor* visitor) {
-  traceAfterDispatchImpl(visitor);
-}
-
-void TraceAfterDispatchExternBase::traceAfterDispatch(
-    InlinedGlobalMarkingVisitor visitor) {
-  traceAfterDispatchImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchExternBase::traceAfterDispatchImpl(
-    VisitorDispatcher visitor) {
   visitor->trace(x_base_);
 }
 
 void TraceAfterDispatchExternDerived::traceAfterDispatch(Visitor* visitor) {
-  traceAfterDispatchImpl(visitor);
-}
-
-void TraceAfterDispatchExternDerived::traceAfterDispatch(
-    InlinedGlobalMarkingVisitor visitor) {
-  traceAfterDispatchImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchExternDerived::traceAfterDispatchImpl(
-    VisitorDispatcher visitor) {
   visitor->trace(x_derived_);
   TraceAfterDispatchExternBase::traceAfterDispatch(visitor);
 }
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl.h b/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl.h
index fe25279..3913f3ad 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl.h
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl.h
@@ -23,23 +23,10 @@
  public:
   explicit TraceAfterDispatchInlinedBase(ClassTag tag) : tag_(tag) {}
 
-  void trace(Visitor* visitor) { traceImpl(visitor); }
-  void trace(InlinedGlobalMarkingVisitor visitor) { traceImpl(visitor); }
-
-  void traceAfterDispatch(Visitor* visitor) { traceAfterDispatchImpl(visitor); }
-  void traceAfterDispatch(InlinedGlobalMarkingVisitor visitor) {
-    traceAfterDispatchImpl(visitor);
-  }
+  void trace(Visitor*);
+  void traceAfterDispatch(Visitor* visitor) { visitor->trace(x_base_); }
 
  private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor);
-
-  template <typename VisitorDispatcher>
-  void traceAfterDispatchImpl(VisitorDispatcher visitor) {
-    visitor->trace(x_base_);
-  }
-
   ClassTag tag_;
   Member<X> x_base_;
 };
@@ -48,18 +35,12 @@
  public:
   TraceAfterDispatchInlinedDerived() : TraceAfterDispatchInlinedBase(DERIVED) {}
 
-  void traceAfterDispatch(Visitor* visitor) { traceAfterDispatchImpl(visitor); }
-  void traceAfterDispatch(InlinedGlobalMarkingVisitor visitor) {
-    traceAfterDispatchImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void traceAfterDispatchImpl(VisitorDispatcher visitor) {
+  void traceAfterDispatch(Visitor* visitor) {
     visitor->trace(x_derived_);
     TraceAfterDispatchInlinedBase::traceAfterDispatch(visitor);
   }
 
+ private:
   Member<X> x_derived_;
 };
 
@@ -69,18 +50,9 @@
   explicit TraceAfterDispatchExternBase(ClassTag tag) : tag_(tag) {}
 
   void trace(Visitor* visitor);
-  void trace(InlinedGlobalMarkingVisitor visitor);
-
   void traceAfterDispatch(Visitor* visitor);
-  void traceAfterDispatch(InlinedGlobalMarkingVisitor visitor);
 
  private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor);
-
-  template <typename VisitorDispatcher>
-  void traceAfterDispatchImpl(VisitorDispatcher visitor);
-
   ClassTag tag_;
   Member<X> x_base_;
 };
@@ -90,12 +62,8 @@
   TraceAfterDispatchExternDerived() : TraceAfterDispatchExternBase(DERIVED) {}
 
   void traceAfterDispatch(Visitor* visitor);
-  void traceAfterDispatch(InlinedGlobalMarkingVisitor visitor);
 
  private:
-  template <typename VisitorDispatcher>
-  void traceAfterDispatchImpl(VisitorDispatcher visitor);
-
   Member<X> x_derived_;
 };
 
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl_error.cpp b/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl_error.cpp
index 23798f7..9157bc0 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl_error.cpp
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl_error.cpp
@@ -6,9 +6,7 @@
 
 namespace blink {
 
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchInlinedBase::traceImpl(
-    VisitorDispatcher visitor) {
+inline void TraceAfterDispatchInlinedBase::trace(Visitor* visitor) {
   // Implement a simple form of manual dispatching, because BlinkGCPlugin
   // checks if the tracing is dispatched to all derived classes.
   //
@@ -24,15 +22,6 @@
 }
 
 void TraceAfterDispatchExternBase::trace(Visitor* visitor) {
-  traceImpl(visitor);
-}
-
-void TraceAfterDispatchExternBase::trace(InlinedGlobalMarkingVisitor visitor) {
-  traceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchExternBase::traceImpl(VisitorDispatcher visitor) {
   if (tag_ == DERIVED) {
     // Missing dispatch call:
     // static_cast<TraceAfterDispatchExternDerived*>(this)->traceAfterDispatch(
@@ -43,32 +32,10 @@
 }
 
 void TraceAfterDispatchExternBase::traceAfterDispatch(Visitor* visitor) {
-  traceAfterDispatchImpl(visitor);
-}
-
-void TraceAfterDispatchExternBase::traceAfterDispatch(
-    InlinedGlobalMarkingVisitor visitor) {
-  traceAfterDispatchImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchExternBase::traceAfterDispatchImpl(
-    VisitorDispatcher visitor) {
   // No trace call.
 }
 
 void TraceAfterDispatchExternDerived::traceAfterDispatch(Visitor* visitor) {
-  traceAfterDispatchImpl(visitor);
-}
-
-void TraceAfterDispatchExternDerived::traceAfterDispatch(
-    InlinedGlobalMarkingVisitor visitor) {
-  traceAfterDispatchImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchExternDerived::traceAfterDispatchImpl(
-    VisitorDispatcher visitor) {
   // Ditto.
 }
 
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl_error.h b/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl_error.h
index b480e39..f9878f8 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl_error.h
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl_error.h
@@ -23,23 +23,12 @@
  public:
   explicit TraceAfterDispatchInlinedBase(ClassTag tag) : tag_(tag) {}
 
-  void trace(Visitor* visitor) { traceImpl(visitor); }
-  void trace(InlinedGlobalMarkingVisitor visitor) { traceImpl(visitor); }
+  void trace(Visitor*);
 
-  void traceAfterDispatch(Visitor* visitor) { traceAfterDispatchImpl(visitor); }
-  void traceAfterDispatch(InlinedGlobalMarkingVisitor visitor) {
-    traceAfterDispatchImpl(visitor);
-  }
+  // No trace call; should get a warning.
+  void traceAfterDispatch(Visitor*) {}
 
  private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor);
-
-  template <typename VisitorDispatcher>
-  void traceAfterDispatchImpl(VisitorDispatcher visitor) {
-    // No trace call; should get a warning.
-  }
-
   ClassTag tag_;
   Member<X> x_base_;
 };
@@ -48,17 +37,11 @@
  public:
   TraceAfterDispatchInlinedDerived() : TraceAfterDispatchInlinedBase(DERIVED) {}
 
-  void traceAfterDispatch(Visitor* visitor) { traceAfterDispatchImpl(visitor); }
-  void traceAfterDispatch(InlinedGlobalMarkingVisitor visitor) {
-    traceAfterDispatchImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void traceAfterDispatchImpl(VisitorDispatcher visitor) {
+  void traceAfterDispatch(Visitor* visitor) {
     // No trace call (for member and base class).
   }
 
+ private:
   Member<X> x_derived_;
 };
 
@@ -68,18 +51,10 @@
   explicit TraceAfterDispatchExternBase(ClassTag tag) : tag_(tag) {}
 
   void trace(Visitor* visitor);
-  void trace(InlinedGlobalMarkingVisitor visitor);
 
   void traceAfterDispatch(Visitor* visitor);
-  void traceAfterDispatch(InlinedGlobalMarkingVisitor visitor);
 
  private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor);
-
-  template <typename VisitorDispatcher>
-  void traceAfterDispatchImpl(VisitorDispatcher visitor);
-
   ClassTag tag_;
   Member<X> x_base_;
 };
@@ -89,12 +64,8 @@
   TraceAfterDispatchExternDerived() : TraceAfterDispatchExternBase(DERIVED) {}
 
   void traceAfterDispatch(Visitor* visitor);
-  void traceAfterDispatch(InlinedGlobalMarkingVisitor visitor);
 
  private:
-  template <typename VisitorDispatcher>
-  void traceAfterDispatchImpl(VisitorDispatcher visitor);
-
   Member<X> x_derived_;
 };
 
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl_error.txt b/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl_error.txt
index 058fccb8..5cb7cac 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl_error.txt
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/trace_after_dispatch_impl_error.txt
@@ -1,34 +1,34 @@
-trace_after_dispatch_impl_error.cpp:10:1: warning: [blink-gc] Missing dispatch to class 'TraceAfterDispatchInlinedDerived' in manual trace dispatch.
-inline void TraceAfterDispatchInlinedBase::traceImpl(
+trace_after_dispatch_impl_error.cpp:9:1: warning: [blink-gc] Missing dispatch to class 'TraceAfterDispatchInlinedDerived' in manual trace dispatch.
+inline void TraceAfterDispatchInlinedBase::trace(Visitor* visitor) {
 ^
-trace_after_dispatch_impl_error.cpp:35:1: warning: [blink-gc] Missing dispatch to class 'TraceAfterDispatchExternDerived' in manual trace dispatch.
-inline void TraceAfterDispatchExternBase::traceImpl(VisitorDispatcher visitor) {
+trace_after_dispatch_impl_error.cpp:24:1: warning: [blink-gc] Missing dispatch to class 'TraceAfterDispatchExternDerived' in manual trace dispatch.
+void TraceAfterDispatchExternBase::trace(Visitor* visitor) {
 ^
 In file included from trace_after_dispatch_impl_error.cpp:5:
-./trace_after_dispatch_impl_error.h:39:3: warning: [blink-gc] Class 'TraceAfterDispatchInlinedBase' has untraced fields that require tracing.
-  void traceAfterDispatchImpl(VisitorDispatcher visitor) {
+./trace_after_dispatch_impl_error.h:29:3: warning: [blink-gc] Class 'TraceAfterDispatchInlinedBase' has untraced fields that require tracing.
+  void traceAfterDispatch(Visitor*) {}
   ^
-./trace_after_dispatch_impl_error.h:44:3: note: [blink-gc] Untraced field 'x_base_' declared here:
+./trace_after_dispatch_impl_error.h:33:3: note: [blink-gc] Untraced field 'x_base_' declared here:
   Member<X> x_base_;
   ^
-./trace_after_dispatch_impl_error.h:58:3: warning: [blink-gc] Base class 'TraceAfterDispatchInlinedBase' of derived class 'TraceAfterDispatchInlinedDerived' requires tracing.
-  void traceAfterDispatchImpl(VisitorDispatcher visitor) {
+./trace_after_dispatch_impl_error.h:40:3: warning: [blink-gc] Base class 'TraceAfterDispatchInlinedBase' of derived class 'TraceAfterDispatchInlinedDerived' requires tracing.
+  void traceAfterDispatch(Visitor* visitor) {
   ^
-./trace_after_dispatch_impl_error.h:58:3: warning: [blink-gc] Class 'TraceAfterDispatchInlinedDerived' has untraced fields that require tracing.
-./trace_after_dispatch_impl_error.h:62:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
+./trace_after_dispatch_impl_error.h:40:3: warning: [blink-gc] Class 'TraceAfterDispatchInlinedDerived' has untraced fields that require tracing.
+./trace_after_dispatch_impl_error.h:45:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
   Member<X> x_derived_;
   ^
-trace_after_dispatch_impl_error.cpp:55:1: warning: [blink-gc] Class 'TraceAfterDispatchExternBase' has untraced fields that require tracing.
-inline void TraceAfterDispatchExternBase::traceAfterDispatchImpl(
+trace_after_dispatch_impl_error.cpp:34:1: warning: [blink-gc] Class 'TraceAfterDispatchExternBase' has untraced fields that require tracing.
+void TraceAfterDispatchExternBase::traceAfterDispatch(Visitor* visitor) {
 ^
-./trace_after_dispatch_impl_error.h:84:3: note: [blink-gc] Untraced field 'x_base_' declared here:
+./trace_after_dispatch_impl_error.h:59:3: note: [blink-gc] Untraced field 'x_base_' declared here:
   Member<X> x_base_;
   ^
-trace_after_dispatch_impl_error.cpp:70:1: warning: [blink-gc] Base class 'TraceAfterDispatchExternBase' of derived class 'TraceAfterDispatchExternDerived' requires tracing.
-inline void TraceAfterDispatchExternDerived::traceAfterDispatchImpl(
+trace_after_dispatch_impl_error.cpp:38:1: warning: [blink-gc] Base class 'TraceAfterDispatchExternBase' of derived class 'TraceAfterDispatchExternDerived' requires tracing.
+void TraceAfterDispatchExternDerived::traceAfterDispatch(Visitor* visitor) {
 ^
-trace_after_dispatch_impl_error.cpp:70:1: warning: [blink-gc] Class 'TraceAfterDispatchExternDerived' has untraced fields that require tracing.
-./trace_after_dispatch_impl_error.h:98:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
+trace_after_dispatch_impl_error.cpp:38:1: warning: [blink-gc] Class 'TraceAfterDispatchExternDerived' has untraced fields that require tracing.
+./trace_after_dispatch_impl_error.h:69:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
   Member<X> x_derived_;
   ^
 8 warnings generated.
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl.cpp b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl.cpp
index c8849cc..a5f3a7a4 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl.cpp
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl.cpp
@@ -7,20 +7,10 @@
 namespace blink {
 
 void TraceImplExtern::trace(Visitor* visitor) {
-  traceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceImplExtern::traceImpl(VisitorDispatcher visitor) {
   visitor->trace(x_);
 }
 
 void TraceImplBaseExtern::trace(Visitor* visitor) {
-  traceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceImplBaseExtern::traceImpl(VisitorDispatcher visitor) {
   visitor->trace(x_);
   Base::trace(visitor);
 }
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl.h b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl.h
index 64fae26..57f69c6f 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl.h
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl.h
@@ -16,12 +16,7 @@
 
 class TraceImplInlined : public GarbageCollected<TraceImplInlined> {
  public:
-  void trace(Visitor* visitor) { traceImpl(visitor); }
-
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {
-    visitor->trace(x_);
-  }
+  void trace(Visitor* visitor) { visitor->trace(x_); }
 
  private:
   Member<X> x_;
@@ -30,8 +25,6 @@
 class TraceImplExtern : public GarbageCollected<TraceImplExtern> {
  public:
   void trace(Visitor* visitor);
-  template <typename VisitorDispatcher>
-  inline void traceImpl(VisitorDispatcher);
 
  private:
   Member<X> x_;
@@ -44,21 +37,13 @@
 
 class TraceImplBaseInlined : public Base {
  public:
-  void trace(Visitor* visitor) override { traceImpl(visitor); }
-
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {
-    Base::trace(visitor);
-  }
+  void trace(Visitor* visitor) override { Base::trace(visitor); }
 };
 
 class TraceImplBaseExtern : public Base {
  public:
   void trace(Visitor* visitor) override;
 
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher);
-
  private:
   Member<X> x_;
 };
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_dependent_scope.h b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_dependent_scope.h
index 0d079f6f..d0a723b 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_dependent_scope.h
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_dependent_scope.h
@@ -17,42 +17,19 @@
 template <typename T>
 class Base : public GarbageCollected<Base<T> > {
  public:
-  virtual void trace(Visitor* visitor) { traceImpl(visitor); }
-  virtual void trace(InlinedGlobalMarkingVisitor visitor) {
-    traceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {}
+  virtual void trace(Visitor*) {}
 };
 
 template <typename T>
 class Derived : public Base<T> {
  public:
-  void trace(Visitor* visitor) override { traceImpl(visitor); }
-  void trace(InlinedGlobalMarkingVisitor visitor) override {
-    traceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {
-    Base<T>::trace(visitor);
-  }
+  void trace(Visitor* visitor) override { Base<T>::trace(visitor); }
 };
 
 template <typename T>
 class DerivedMissingTrace : public Base<T> {
  public:
-  void trace(Visitor* visitor) override { traceImpl(visitor); }
-  void trace(InlinedGlobalMarkingVisitor visitor) override {
-    traceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {
+  void trace(Visitor* visitor) override {
     // Missing Base<T>::trace(visitor).
   }
 };
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_dependent_scope.txt b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_dependent_scope.txt
index e1aab335..985b7f9 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_dependent_scope.txt
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_dependent_scope.txt
@@ -1,5 +1,5 @@
 In file included from traceimpl_dependent_scope.cpp:5:
-./traceimpl_dependent_scope.h:55:3: warning: [blink-gc] Base class 'Base<int>' of derived class 'DerivedMissingTrace<int>' requires tracing.
-  void traceImpl(VisitorDispatcher visitor) {
+./traceimpl_dependent_scope.h:32:3: warning: [blink-gc] Base class 'Base<int>' of derived class 'DerivedMissingTrace<int>' requires tracing.
+  void trace(Visitor* visitor) override {
   ^
 1 warning generated.
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_derived_from_templated_base.h b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_derived_from_templated_base.h
index 21b99789..b382545 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_derived_from_templated_base.h
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_derived_from_templated_base.h
@@ -18,12 +18,7 @@
 class TraceImplTemplatedBase
     : public GarbageCollected<TraceImplTemplatedBase<Y> > {
  public:
-  void trace(Visitor* visitor) { traceImpl(visitor); }
-
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {
-    visitor->trace(x_);
-  }
+  void trace(Visitor* visitor) { visitor->trace(x_); }
 
  private:
   Member<X> x_;
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_error.cpp b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_error.cpp
index 041c565..3a7638a3 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_error.cpp
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_error.cpp
@@ -7,22 +7,10 @@
 namespace blink {
 
 void TraceImplExternWithUntracedMember::trace(Visitor* visitor) {
-  traceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceImplExternWithUntracedMember::traceImpl(
-    VisitorDispatcher visitor) {
   // Should get a warning as well.
 }
 
 void TraceImplExternWithUntracedBase::trace(Visitor* visitor) {
-  traceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceImplExternWithUntracedBase::traceImpl(
-    VisitorDispatcher visitor) {
   // Ditto.
 }
 
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_error.h b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_error.h
index 5a883b4e..0960199 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_error.h
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_error.h
@@ -17,10 +17,7 @@
 class TraceImplInlinedWithUntracedMember
     : public GarbageCollected<TraceImplInlinedWithUntracedMember> {
  public:
-  void trace(Visitor* visitor) { traceImpl(visitor); }
-
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {
+  void trace(Visitor* visitor) {
     // Empty; should get complaints from the plugin for untraced x_.
   }
 
@@ -33,9 +30,6 @@
  public:
   void trace(Visitor* visitor);
 
-  template <typename VisitorDispatcher>
-  inline void traceImpl(VisitorDispatcher);
-
  private:
   Member<X> x_;
 };
@@ -47,10 +41,7 @@
 
 class TraceImplInlineWithUntracedBase : public Base {
  public:
-  void trace(Visitor* visitor) override { traceImpl(visitor); }
-
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {
+  void trace(Visitor* visitor) override {
     // Empty; should get complaints from the plugin for untraced Base.
   }
 };
@@ -58,9 +49,6 @@
 class TraceImplExternWithUntracedBase : public Base {
  public:
   void trace(Visitor*) override;
-
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor);
 };
 
 }
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_error.txt b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_error.txt
index 070b0296..056711a 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_error.txt
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_error.txt
@@ -1,20 +1,20 @@
 In file included from traceimpl_error.cpp:5:
-./traceimpl_error.h:23:3: warning: [blink-gc] Class 'TraceImplInlinedWithUntracedMember' has untraced fields that require tracing.
-  void traceImpl(VisitorDispatcher visitor) {
+./traceimpl_error.h:20:3: warning: [blink-gc] Class 'TraceImplInlinedWithUntracedMember' has untraced fields that require tracing.
+  void trace(Visitor* visitor) {
   ^
-./traceimpl_error.h:28:3: note: [blink-gc] Untraced field 'x_' declared here:
+./traceimpl_error.h:25:3: note: [blink-gc] Untraced field 'x_' declared here:
   Member<X> x_;
   ^
-./traceimpl_error.h:53:3: warning: [blink-gc] Base class 'Base' of derived class 'TraceImplInlineWithUntracedBase' requires tracing.
-  void traceImpl(VisitorDispatcher visitor) {
+./traceimpl_error.h:44:3: warning: [blink-gc] Base class 'Base' of derived class 'TraceImplInlineWithUntracedBase' requires tracing.
+  void trace(Visitor* visitor) override {
   ^
-traceimpl_error.cpp:14:1: warning: [blink-gc] Class 'TraceImplExternWithUntracedMember' has untraced fields that require tracing.
-inline void TraceImplExternWithUntracedMember::traceImpl(
+traceimpl_error.cpp:9:1: warning: [blink-gc] Class 'TraceImplExternWithUntracedMember' has untraced fields that require tracing.
+void TraceImplExternWithUntracedMember::trace(Visitor* visitor) {
 ^
-./traceimpl_error.h:40:3: note: [blink-gc] Untraced field 'x_' declared here:
+./traceimpl_error.h:34:3: note: [blink-gc] Untraced field 'x_' declared here:
   Member<X> x_;
   ^
-traceimpl_error.cpp:24:1: warning: [blink-gc] Base class 'Base' of derived class 'TraceImplExternWithUntracedBase' requires tracing.
-inline void TraceImplExternWithUntracedBase::traceImpl(
+traceimpl_error.cpp:13:1: warning: [blink-gc] Base class 'Base' of derived class 'TraceImplExternWithUntracedBase' requires tracing.
+void TraceImplExternWithUntracedBase::trace(Visitor* visitor) {
 ^
 4 warnings generated.
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_omitted_trace.h b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_omitted_trace.h
index 3c5e955..ec5c0b3e 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_omitted_trace.h
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_omitted_trace.h
@@ -11,14 +11,7 @@
 
 class A : public GarbageCollected<A> {
  public:
-  virtual void trace(Visitor* visitor) { traceImpl(visitor); }
-  virtual void trace(InlinedGlobalMarkingVisitor visitor) {
-    traceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {}
+  virtual void trace(Visitor* visitor) {}
 };
 
 class B : public A {
@@ -27,14 +20,7 @@
 
 class C : public B {
  public:
-  void trace(Visitor* visitor) override { traceImpl(visitor); }
-  void trace(InlinedGlobalMarkingVisitor visitor) override {
-    traceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {
+  void trace(Visitor* visitor) override {
     // B::trace() is actually A::trace(), and in certain cases we only get
     // limited information like "there is a function call that will be resolved
     // to A::trace()". We still want to mark B as traced.
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded.cpp b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded.cpp
index 02d4858..dc7051c 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded.cpp
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded.cpp
@@ -7,28 +7,10 @@
 namespace blink {
 
 void ExternBase::trace(Visitor* visitor) {
-  traceImpl(visitor);
-}
-
-void ExternBase::trace(InlinedGlobalMarkingVisitor visitor) {
-  traceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void ExternBase::traceImpl(VisitorDispatcher visitor) {
   visitor->trace(x_base_);
 }
 
 void ExternDerived::trace(Visitor* visitor) {
-  traceImpl(visitor);
-}
-
-void ExternDerived::trace(InlinedGlobalMarkingVisitor visitor) {
-  traceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void ExternDerived::traceImpl(VisitorDispatcher visitor) {
   visitor->trace(x_derived_);
   ExternBase::trace(visitor);
 }
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded.h b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded.h
index 808821df..34185a39 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded.h
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded.h
@@ -12,61 +12,40 @@
 class X : public GarbageCollected<X> {
  public:
   void trace(Visitor*) {}
-  void trace(InlinedGlobalMarkingVisitor) {}
 };
 
 class InlinedBase : public GarbageCollected<InlinedBase> {
  public:
-  virtual void trace(Visitor* visitor) { traceImpl(visitor); }
-  virtual void trace(InlinedGlobalMarkingVisitor visitor) {
-    traceImpl(visitor);
-  }
+  virtual void trace(Visitor* visitor) { visitor->trace(x_base_); }
 
  private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) { visitor->trace(x_base_); }
-
   Member<X> x_base_;
 };
 
 class InlinedDerived : public InlinedBase {
  public:
-  void trace(Visitor* visitor) override { traceImpl(visitor); }
-  void trace(InlinedGlobalMarkingVisitor visitor) override {
-    traceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {
+  void trace(Visitor* visitor) override {
     visitor->trace(x_derived_);
     InlinedBase::trace(visitor);
   }
 
+ private:
   Member<X> x_derived_;
 };
 
 class ExternBase : public GarbageCollected<ExternBase> {
  public:
   virtual void trace(Visitor*);
-  virtual void trace(InlinedGlobalMarkingVisitor);
 
  private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher);
-
   Member<X> x_base_;
 };
 
 class ExternDerived : public ExternBase {
  public:
   void trace(Visitor*) override;
-  void trace(InlinedGlobalMarkingVisitor) override;
 
  private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher);
-
   Member<X> x_derived_;
 };
 
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded_error.cpp b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded_error.cpp
index 07cab63..478ad7d6 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded_error.cpp
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded_error.cpp
@@ -7,28 +7,10 @@
 namespace blink {
 
 void ExternBase::trace(Visitor* visitor) {
-  traceImpl(visitor);
-}
-
-void ExternBase::trace(InlinedGlobalMarkingVisitor visitor) {
-  traceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void ExternBase::traceImpl(VisitorDispatcher visitor) {
   // Missing visitor->trace(x_base_).
 }
 
 void ExternDerived::trace(Visitor* visitor) {
-  traceImpl(visitor);
-}
-
-void ExternDerived::trace(InlinedGlobalMarkingVisitor visitor) {
-  traceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void ExternDerived::traceImpl(VisitorDispatcher visitor) {
   // Missing visitor->trace(x_derived_) and ExternBase::trace(visitor).
 }
 
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded_error.h b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded_error.h
index 7d7a038..8d6e9504 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded_error.h
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded_error.h
@@ -12,19 +12,11 @@
 class X : public GarbageCollected<X> {
  public:
   void trace(Visitor*) {}
-  void trace(InlinedGlobalMarkingVisitor) {}
 };
 
 class InlinedBase : public GarbageCollected<InlinedBase> {
  public:
-  virtual void trace(Visitor* visitor) { traceImpl(visitor); }
-  virtual void trace(InlinedGlobalMarkingVisitor visitor) {
-    traceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {
+  virtual void trace(Visitor* visitor) {
     // Missing visitor->trace(x_base_).
   }
 
@@ -33,14 +25,7 @@
 
 class InlinedDerived : public InlinedBase {
  public:
-  void trace(Visitor* visitor) override { traceImpl(visitor); }
-  void trace(InlinedGlobalMarkingVisitor visitor) override {
-    traceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher visitor) {
+  void trace(Visitor* visitor) override {
     // Missing visitor->trace(x_derived_) and InlinedBase::trace(visitor).
   }
 
@@ -50,24 +35,16 @@
 class ExternBase : public GarbageCollected<ExternBase> {
  public:
   virtual void trace(Visitor*);
-  virtual void trace(InlinedGlobalMarkingVisitor);
 
  private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher);
-
   Member<X> x_base_;
 };
 
 class ExternDerived : public ExternBase {
  public:
   void trace(Visitor*) override;
-  void trace(InlinedGlobalMarkingVisitor) override;
 
  private:
-  template <typename VisitorDispatcher>
-  void traceImpl(VisitorDispatcher);
-
   Member<X> x_derived_;
 };
 
diff --git a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded_error.txt b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded_error.txt
index 644f9f0..bfbabd8 100644
--- a/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded_error.txt
+++ b/tools/clang/blink_gc_plugin/tests/legacy_naming/traceimpl_overloaded_error.txt
@@ -1,28 +1,28 @@
 In file included from traceimpl_overloaded_error.cpp:5:
-./traceimpl_overloaded_error.h:27:3: warning: [blink-gc] Class 'InlinedBase' has untraced fields that require tracing.
-  void traceImpl(VisitorDispatcher visitor) {
+./traceimpl_overloaded_error.h:19:3: warning: [blink-gc] Class 'InlinedBase' has untraced fields that require tracing.
+  virtual void trace(Visitor* visitor) {
   ^
-./traceimpl_overloaded_error.h:31:3: note: [blink-gc] Untraced field 'x_base_' declared here:
+./traceimpl_overloaded_error.h:23:3: note: [blink-gc] Untraced field 'x_base_' declared here:
   Member<X> x_base_;
   ^
-./traceimpl_overloaded_error.h:43:3: warning: [blink-gc] Base class 'InlinedBase' of derived class 'InlinedDerived' requires tracing.
-  void traceImpl(VisitorDispatcher visitor) {
+./traceimpl_overloaded_error.h:28:3: warning: [blink-gc] Base class 'InlinedBase' of derived class 'InlinedDerived' requires tracing.
+  void trace(Visitor* visitor) override {
   ^
-./traceimpl_overloaded_error.h:43:3: warning: [blink-gc] Class 'InlinedDerived' has untraced fields that require tracing.
-./traceimpl_overloaded_error.h:47:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
+./traceimpl_overloaded_error.h:28:3: warning: [blink-gc] Class 'InlinedDerived' has untraced fields that require tracing.
+./traceimpl_overloaded_error.h:32:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
   Member<X> x_derived_;
   ^
-traceimpl_overloaded_error.cpp:18:1: warning: [blink-gc] Class 'ExternBase' has untraced fields that require tracing.
-inline void ExternBase::traceImpl(VisitorDispatcher visitor) {
+traceimpl_overloaded_error.cpp:9:1: warning: [blink-gc] Class 'ExternBase' has untraced fields that require tracing.
+void ExternBase::trace(Visitor* visitor) {
 ^
-./traceimpl_overloaded_error.h:59:3: note: [blink-gc] Untraced field 'x_base_' declared here:
+./traceimpl_overloaded_error.h:40:3: note: [blink-gc] Untraced field 'x_base_' declared here:
   Member<X> x_base_;
   ^
-traceimpl_overloaded_error.cpp:31:1: warning: [blink-gc] Base class 'ExternBase' of derived class 'ExternDerived' requires tracing.
-inline void ExternDerived::traceImpl(VisitorDispatcher visitor) {
+traceimpl_overloaded_error.cpp:13:1: warning: [blink-gc] Base class 'ExternBase' of derived class 'ExternDerived' requires tracing.
+void ExternDerived::trace(Visitor* visitor) {
 ^
-traceimpl_overloaded_error.cpp:31:1: warning: [blink-gc] Class 'ExternDerived' has untraced fields that require tracing.
-./traceimpl_overloaded_error.h:71:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
+traceimpl_overloaded_error.cpp:13:1: warning: [blink-gc] Class 'ExternDerived' has untraced fields that require tracing.
+./traceimpl_overloaded_error.h:48:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
   Member<X> x_derived_;
   ^
 6 warnings generated.
diff --git a/tools/clang/blink_gc_plugin/tests/register_weak_members_template.h b/tools/clang/blink_gc_plugin/tests/register_weak_members_template.h
index 61d8fbb..81d917c 100644
--- a/tools/clang/blink_gc_plugin/tests/register_weak_members_template.h
+++ b/tools/clang/blink_gc_plugin/tests/register_weak_members_template.h
@@ -11,30 +11,20 @@
 
 class X : public GarbageCollected<X> {
  public:
-  void Trace(Visitor* visitor) { TraceImpl(visitor); }
-  void Trace(InlinedGlobalMarkingVisitor visitor) { TraceImpl(visitor); }
-
- private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {}
+  void Trace(Visitor* visitor) {}
 };
 
 class HasUntracedWeakMembers : public GarbageCollected<HasUntracedWeakMembers> {
  public:
-  void Trace(Visitor* visitor) { TraceImpl(visitor); }
-  void Trace(InlinedGlobalMarkingVisitor visitor) { TraceImpl(visitor); }
-
-  // Don't have to be defined for the purpose of this test.
-  void clearWeakMembers(Visitor* visitor);
-
- private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {
+  void Trace(Visitor* visitor) {
     visitor->template RegisterWeakMembers<
-        HasUntracedWeakMembers,
-        &HasUntracedWeakMembers::clearWeakMembers>(this);
+        HasUntracedWeakMembers, &HasUntracedWeakMembers::ClearWeakMembers>(
+        this);
   }
 
+  void ClearWeakMembers(Visitor* visitor);
+
+ private:
   WeakMember<X> x_;
 };
 
diff --git a/tools/clang/blink_gc_plugin/tests/register_weak_members_template.txt b/tools/clang/blink_gc_plugin/tests/register_weak_members_template.txt
index e69de29..b1fb1800 100644
--- a/tools/clang/blink_gc_plugin/tests/register_weak_members_template.txt
+++ b/tools/clang/blink_gc_plugin/tests/register_weak_members_template.txt
@@ -0,0 +1,8 @@
+In file included from register_weak_members_template.cpp:5:
+./register_weak_members_template.h:19:3: warning: [blink-gc] Class 'HasUntracedWeakMembers' has untraced fields that require tracing.
+  void Trace(Visitor* visitor) {
+  ^
+./register_weak_members_template.h:28:3: note: [blink-gc] Untraced field 'x_' declared here:
+  WeakMember<X> x_;
+  ^
+1 warning generated.
diff --git a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.cpp b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.cpp
index 17bd1f8b..c8cd1df 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.cpp
+++ b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.cpp
@@ -6,9 +6,7 @@
 
 namespace blink {
 
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchInlinedBase::TraceImpl(
-    VisitorDispatcher visitor) {
+void TraceAfterDispatchInlinedBase::Trace(Visitor* visitor) {
   // Implement a simple form of manual dispatching, because BlinkGCPlugin
   // checks if the tracing is dispatched to all derived classes.
   //
@@ -23,15 +21,6 @@
 }
 
 void TraceAfterDispatchExternBase::Trace(Visitor* visitor) {
-  TraceImpl(visitor);
-}
-
-void TraceAfterDispatchExternBase::Trace(InlinedGlobalMarkingVisitor visitor) {
-  TraceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchExternBase::TraceImpl(VisitorDispatcher visitor) {
   if (tag_ == DERIVED) {
     static_cast<TraceAfterDispatchExternDerived*>(this)->TraceAfterDispatch(
         visitor);
@@ -41,32 +30,10 @@
 }
 
 void TraceAfterDispatchExternBase::TraceAfterDispatch(Visitor* visitor) {
-  TraceAfterDispatchImpl(visitor);
-}
-
-void TraceAfterDispatchExternBase::TraceAfterDispatch(
-    InlinedGlobalMarkingVisitor visitor) {
-  TraceAfterDispatchImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchExternBase::TraceAfterDispatchImpl(
-    VisitorDispatcher visitor) {
   visitor->Trace(x_base_);
 }
 
 void TraceAfterDispatchExternDerived::TraceAfterDispatch(Visitor* visitor) {
-  TraceAfterDispatchImpl(visitor);
-}
-
-void TraceAfterDispatchExternDerived::TraceAfterDispatch(
-    InlinedGlobalMarkingVisitor visitor) {
-  TraceAfterDispatchImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchExternDerived::TraceAfterDispatchImpl(
-    VisitorDispatcher visitor) {
   visitor->Trace(x_derived_);
   TraceAfterDispatchExternBase::TraceAfterDispatch(visitor);
 }
diff --git a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.h b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.h
index c5e3063..967793f 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.h
+++ b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl.h
@@ -23,23 +23,11 @@
  public:
   explicit TraceAfterDispatchInlinedBase(ClassTag tag) : tag_(tag) {}
 
-  void Trace(Visitor* visitor) { TraceImpl(visitor); }
-  void Trace(InlinedGlobalMarkingVisitor visitor) { TraceImpl(visitor); }
+  void Trace(Visitor*);
 
-  void TraceAfterDispatch(Visitor* visitor) { TraceAfterDispatchImpl(visitor); }
-  void TraceAfterDispatch(InlinedGlobalMarkingVisitor visitor) {
-    TraceAfterDispatchImpl(visitor);
-  }
+  void TraceAfterDispatch(Visitor* visitor) { visitor->Trace(x_base_); }
 
  private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor);
-
-  template <typename VisitorDispatcher>
-  void TraceAfterDispatchImpl(VisitorDispatcher visitor) {
-    visitor->Trace(x_base_);
-  }
-
   ClassTag tag_;
   Member<X> x_base_;
 };
@@ -48,18 +36,12 @@
  public:
   TraceAfterDispatchInlinedDerived() : TraceAfterDispatchInlinedBase(DERIVED) {}
 
-  void TraceAfterDispatch(Visitor* visitor) { TraceAfterDispatchImpl(visitor); }
-  void TraceAfterDispatch(InlinedGlobalMarkingVisitor visitor) {
-    TraceAfterDispatchImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void TraceAfterDispatchImpl(VisitorDispatcher visitor) {
+  void TraceAfterDispatch(Visitor* visitor) {
     visitor->Trace(x_derived_);
     TraceAfterDispatchInlinedBase::TraceAfterDispatch(visitor);
   }
 
+ private:
   Member<X> x_derived_;
 };
 
@@ -69,18 +51,10 @@
   explicit TraceAfterDispatchExternBase(ClassTag tag) : tag_(tag) {}
 
   void Trace(Visitor* visitor);
-  void Trace(InlinedGlobalMarkingVisitor visitor);
 
   void TraceAfterDispatch(Visitor* visitor);
-  void TraceAfterDispatch(InlinedGlobalMarkingVisitor visitor);
 
  private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor);
-
-  template <typename VisitorDispatcher>
-  void TraceAfterDispatchImpl(VisitorDispatcher visitor);
-
   ClassTag tag_;
   Member<X> x_base_;
 };
@@ -90,12 +64,8 @@
   TraceAfterDispatchExternDerived() : TraceAfterDispatchExternBase(DERIVED) {}
 
   void TraceAfterDispatch(Visitor* visitor);
-  void TraceAfterDispatch(InlinedGlobalMarkingVisitor visitor);
 
  private:
-  template <typename VisitorDispatcher>
-  void TraceAfterDispatchImpl(VisitorDispatcher visitor);
-
   Member<X> x_derived_;
 };
 
diff --git a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.cpp b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.cpp
index 46553f3..76dc9761 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.cpp
+++ b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.cpp
@@ -6,9 +6,7 @@
 
 namespace blink {
 
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchInlinedBase::TraceImpl(
-    VisitorDispatcher visitor) {
+inline void TraceAfterDispatchInlinedBase::Trace(Visitor* visitor) {
   // Implement a simple form of manual dispatching, because BlinkGCPlugin
   // checks if the tracing is dispatched to all derived classes.
   //
@@ -24,15 +22,6 @@
 }
 
 void TraceAfterDispatchExternBase::Trace(Visitor* visitor) {
-  TraceImpl(visitor);
-}
-
-void TraceAfterDispatchExternBase::Trace(InlinedGlobalMarkingVisitor visitor) {
-  TraceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchExternBase::TraceImpl(VisitorDispatcher visitor) {
   if (tag_ == DERIVED) {
     // Missing dispatch call:
     // static_cast<TraceAfterDispatchExternDerived*>(this)->TraceAfterDispatch(
@@ -43,32 +32,10 @@
 }
 
 void TraceAfterDispatchExternBase::TraceAfterDispatch(Visitor* visitor) {
-  TraceAfterDispatchImpl(visitor);
-}
-
-void TraceAfterDispatchExternBase::TraceAfterDispatch(
-    InlinedGlobalMarkingVisitor visitor) {
-  TraceAfterDispatchImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchExternBase::TraceAfterDispatchImpl(
-    VisitorDispatcher visitor) {
   // No Trace call.
 }
 
 void TraceAfterDispatchExternDerived::TraceAfterDispatch(Visitor* visitor) {
-  TraceAfterDispatchImpl(visitor);
-}
-
-void TraceAfterDispatchExternDerived::TraceAfterDispatch(
-    InlinedGlobalMarkingVisitor visitor) {
-  TraceAfterDispatchImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceAfterDispatchExternDerived::TraceAfterDispatchImpl(
-    VisitorDispatcher visitor) {
   // Ditto.
 }
 
diff --git a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.h b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.h
index 29f9a8a..00c7986 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.h
+++ b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.h
@@ -23,23 +23,13 @@
  public:
   explicit TraceAfterDispatchInlinedBase(ClassTag tag) : tag_(tag) {}
 
-  void Trace(Visitor* visitor) { TraceImpl(visitor); }
-  void Trace(InlinedGlobalMarkingVisitor visitor) { TraceImpl(visitor); }
+  void Trace(Visitor*);
 
-  void TraceAfterDispatch(Visitor* visitor) { TraceAfterDispatchImpl(visitor); }
-  void TraceAfterDispatch(InlinedGlobalMarkingVisitor visitor) {
-    TraceAfterDispatchImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor);
-
-  template <typename VisitorDispatcher>
-  void TraceAfterDispatchImpl(VisitorDispatcher visitor) {
+  void TraceAfterDispatch(Visitor* visitor) {
     // No Trace call; should get a warning.
   }
 
+ private:
   ClassTag tag_;
   Member<X> x_base_;
 };
@@ -48,17 +38,11 @@
  public:
   TraceAfterDispatchInlinedDerived() : TraceAfterDispatchInlinedBase(DERIVED) {}
 
-  void TraceAfterDispatch(Visitor* visitor) { TraceAfterDispatchImpl(visitor); }
-  void TraceAfterDispatch(InlinedGlobalMarkingVisitor visitor) {
-    TraceAfterDispatchImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void TraceAfterDispatchImpl(VisitorDispatcher visitor) {
+  void TraceAfterDispatch(Visitor* visitor) {
     // No Trace call (for member and base class).
   }
 
+ private:
   Member<X> x_derived_;
 };
 
@@ -68,18 +52,10 @@
   explicit TraceAfterDispatchExternBase(ClassTag tag) : tag_(tag) {}
 
   void Trace(Visitor* visitor);
-  void Trace(InlinedGlobalMarkingVisitor visitor);
 
   void TraceAfterDispatch(Visitor* visitor);
-  void TraceAfterDispatch(InlinedGlobalMarkingVisitor visitor);
 
  private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor);
-
-  template <typename VisitorDispatcher>
-  void TraceAfterDispatchImpl(VisitorDispatcher visitor);
-
   ClassTag tag_;
   Member<X> x_base_;
 };
@@ -89,12 +65,8 @@
   TraceAfterDispatchExternDerived() : TraceAfterDispatchExternBase(DERIVED) {}
 
   void TraceAfterDispatch(Visitor* visitor);
-  void TraceAfterDispatch(InlinedGlobalMarkingVisitor visitor);
 
  private:
-  template <typename VisitorDispatcher>
-  void TraceAfterDispatchImpl(VisitorDispatcher visitor);
-
   Member<X> x_derived_;
 };
 
diff --git a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.txt b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.txt
index 5637daa..85f57af 100644
--- a/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.txt
+++ b/tools/clang/blink_gc_plugin/tests/trace_after_dispatch_impl_error.txt
@@ -1,34 +1,34 @@
-trace_after_dispatch_impl_error.cpp:10:1: warning: [blink-gc] Missing dispatch to class 'TraceAfterDispatchInlinedDerived' in manual trace dispatch.
-inline void TraceAfterDispatchInlinedBase::TraceImpl(
+trace_after_dispatch_impl_error.cpp:9:1: warning: [blink-gc] Missing dispatch to class 'TraceAfterDispatchInlinedDerived' in manual trace dispatch.
+inline void TraceAfterDispatchInlinedBase::Trace(Visitor* visitor) {
 ^
-trace_after_dispatch_impl_error.cpp:35:1: warning: [blink-gc] Missing dispatch to class 'TraceAfterDispatchExternDerived' in manual trace dispatch.
-inline void TraceAfterDispatchExternBase::TraceImpl(VisitorDispatcher visitor) {
+trace_after_dispatch_impl_error.cpp:24:1: warning: [blink-gc] Missing dispatch to class 'TraceAfterDispatchExternDerived' in manual trace dispatch.
+void TraceAfterDispatchExternBase::Trace(Visitor* visitor) {
 ^
 In file included from trace_after_dispatch_impl_error.cpp:5:
-./trace_after_dispatch_impl_error.h:39:3: warning: [blink-gc] Class 'TraceAfterDispatchInlinedBase' has untraced fields that require tracing.
-  void TraceAfterDispatchImpl(VisitorDispatcher visitor) {
+./trace_after_dispatch_impl_error.h:28:3: warning: [blink-gc] Class 'TraceAfterDispatchInlinedBase' has untraced fields that require tracing.
+  void TraceAfterDispatch(Visitor* visitor) {
   ^
-./trace_after_dispatch_impl_error.h:44:3: note: [blink-gc] Untraced field 'x_base_' declared here:
+./trace_after_dispatch_impl_error.h:34:3: note: [blink-gc] Untraced field 'x_base_' declared here:
   Member<X> x_base_;
   ^
-./trace_after_dispatch_impl_error.h:58:3: warning: [blink-gc] Base class 'TraceAfterDispatchInlinedBase' of derived class 'TraceAfterDispatchInlinedDerived' requires tracing.
-  void TraceAfterDispatchImpl(VisitorDispatcher visitor) {
+./trace_after_dispatch_impl_error.h:41:3: warning: [blink-gc] Base class 'TraceAfterDispatchInlinedBase' of derived class 'TraceAfterDispatchInlinedDerived' requires tracing.
+  void TraceAfterDispatch(Visitor* visitor) {
   ^
-./trace_after_dispatch_impl_error.h:58:3: warning: [blink-gc] Class 'TraceAfterDispatchInlinedDerived' has untraced fields that require tracing.
-./trace_after_dispatch_impl_error.h:62:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
+./trace_after_dispatch_impl_error.h:41:3: warning: [blink-gc] Class 'TraceAfterDispatchInlinedDerived' has untraced fields that require tracing.
+./trace_after_dispatch_impl_error.h:46:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
   Member<X> x_derived_;
   ^
-trace_after_dispatch_impl_error.cpp:55:1: warning: [blink-gc] Class 'TraceAfterDispatchExternBase' has untraced fields that require tracing.
-inline void TraceAfterDispatchExternBase::TraceAfterDispatchImpl(
+trace_after_dispatch_impl_error.cpp:34:1: warning: [blink-gc] Class 'TraceAfterDispatchExternBase' has untraced fields that require tracing.
+void TraceAfterDispatchExternBase::TraceAfterDispatch(Visitor* visitor) {
 ^
-./trace_after_dispatch_impl_error.h:84:3: note: [blink-gc] Untraced field 'x_base_' declared here:
+./trace_after_dispatch_impl_error.h:60:3: note: [blink-gc] Untraced field 'x_base_' declared here:
   Member<X> x_base_;
   ^
-trace_after_dispatch_impl_error.cpp:70:1: warning: [blink-gc] Base class 'TraceAfterDispatchExternBase' of derived class 'TraceAfterDispatchExternDerived' requires tracing.
-inline void TraceAfterDispatchExternDerived::TraceAfterDispatchImpl(
+trace_after_dispatch_impl_error.cpp:38:1: warning: [blink-gc] Base class 'TraceAfterDispatchExternBase' of derived class 'TraceAfterDispatchExternDerived' requires tracing.
+void TraceAfterDispatchExternDerived::TraceAfterDispatch(Visitor* visitor) {
 ^
-trace_after_dispatch_impl_error.cpp:70:1: warning: [blink-gc] Class 'TraceAfterDispatchExternDerived' has untraced fields that require tracing.
-./trace_after_dispatch_impl_error.h:98:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
+trace_after_dispatch_impl_error.cpp:38:1: warning: [blink-gc] Class 'TraceAfterDispatchExternDerived' has untraced fields that require tracing.
+./trace_after_dispatch_impl_error.h:70:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
   Member<X> x_derived_;
   ^
 8 warnings generated.
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl.cpp b/tools/clang/blink_gc_plugin/tests/traceimpl.cpp
index fe28a0b..84e7120 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl.cpp
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl.cpp
@@ -7,20 +7,10 @@
 namespace blink {
 
 void TraceImplExtern::Trace(Visitor* visitor) {
-  TraceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceImplExtern::TraceImpl(VisitorDispatcher visitor) {
   visitor->Trace(x_);
 }
 
 void TraceImplBaseExtern::Trace(Visitor* visitor) {
-  TraceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceImplBaseExtern::TraceImpl(VisitorDispatcher visitor) {
   visitor->Trace(x_);
   Base::Trace(visitor);
 }
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl.h b/tools/clang/blink_gc_plugin/tests/traceimpl.h
index 8cb51b1..c2a93be 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl.h
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl.h
@@ -16,12 +16,7 @@
 
 class TraceImplInlined : public GarbageCollected<TraceImplInlined> {
  public:
-  void Trace(Visitor* visitor) { TraceImpl(visitor); }
-
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {
-    visitor->Trace(x_);
-  }
+  void Trace(Visitor* visitor) { visitor->Trace(x_); }
 
  private:
   Member<X> x_;
@@ -30,8 +25,6 @@
 class TraceImplExtern : public GarbageCollected<TraceImplExtern> {
  public:
   void Trace(Visitor* visitor);
-  template <typename VisitorDispatcher>
-  inline void TraceImpl(VisitorDispatcher);
 
  private:
   Member<X> x_;
@@ -44,21 +37,13 @@
 
 class TraceImplBaseInlined : public Base {
  public:
-  void Trace(Visitor* visitor) override { TraceImpl(visitor); }
-
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {
-    Base::Trace(visitor);
-  }
+  void Trace(Visitor* visitor) override { Base::Trace(visitor); }
 };
 
 class TraceImplBaseExtern : public Base {
  public:
   void Trace(Visitor* visitor) override;
 
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher);
-
  private:
   Member<X> x_;
 };
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl_dependent_scope.h b/tools/clang/blink_gc_plugin/tests/traceimpl_dependent_scope.h
index 1c54627..631921b 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl_dependent_scope.h
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl_dependent_scope.h
@@ -17,42 +17,19 @@
 template <typename T>
 class Base : public GarbageCollected<Base<T> > {
  public:
-  virtual void Trace(Visitor* visitor) { TraceImpl(visitor); }
-  virtual void Trace(InlinedGlobalMarkingVisitor visitor) {
-    TraceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {}
+  virtual void Trace(Visitor* visitor) {}
 };
 
 template <typename T>
 class Derived : public Base<T> {
  public:
-  void Trace(Visitor* visitor) override { TraceImpl(visitor); }
-  void Trace(InlinedGlobalMarkingVisitor visitor) override {
-    TraceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {
-    Base<T>::Trace(visitor);
-  }
+  void Trace(Visitor* visitor) override { Base<T>::Trace(visitor); }
 };
 
 template <typename T>
 class DerivedMissingTrace : public Base<T> {
  public:
-  void Trace(Visitor* visitor) override { TraceImpl(visitor); }
-  void Trace(InlinedGlobalMarkingVisitor visitor) override {
-    TraceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {
+  void Trace(Visitor* visitor) override {
     // Missing Base<T>::Trace(visitor).
   }
 };
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl_dependent_scope.txt b/tools/clang/blink_gc_plugin/tests/traceimpl_dependent_scope.txt
index b4779a09..66c1d26 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl_dependent_scope.txt
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl_dependent_scope.txt
@@ -1,5 +1,5 @@
 In file included from traceimpl_dependent_scope.cpp:5:
-./traceimpl_dependent_scope.h:55:3: warning: [blink-gc] Base class 'Base<int>' of derived class 'DerivedMissingTrace<int>' requires tracing.
-  void TraceImpl(VisitorDispatcher visitor) {
+./traceimpl_dependent_scope.h:32:3: warning: [blink-gc] Base class 'Base<int>' of derived class 'DerivedMissingTrace<int>' requires tracing.
+  void Trace(Visitor* visitor) override {
   ^
 1 warning generated.
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl_derived_from_templated_base.h b/tools/clang/blink_gc_plugin/tests/traceimpl_derived_from_templated_base.h
index e5ebdd5..ae3b5098 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl_derived_from_templated_base.h
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl_derived_from_templated_base.h
@@ -18,12 +18,7 @@
 class TraceImplTemplatedBase
     : public GarbageCollected<TraceImplTemplatedBase<Y> > {
  public:
-  void Trace(Visitor* visitor) { TraceImpl(visitor); }
-
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {
-    visitor->Trace(x_);
-  }
+  void Trace(Visitor* visitor) { visitor->Trace(x_); }
 
  private:
   Member<X> x_;
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl_error.cpp b/tools/clang/blink_gc_plugin/tests/traceimpl_error.cpp
index c14e52d..0c49939 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl_error.cpp
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl_error.cpp
@@ -7,22 +7,10 @@
 namespace blink {
 
 void TraceImplExternWithUntracedMember::Trace(Visitor* visitor) {
-  TraceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceImplExternWithUntracedMember::TraceImpl(
-    VisitorDispatcher visitor) {
   // Should get a warning as well.
 }
 
 void TraceImplExternWithUntracedBase::Trace(Visitor* visitor) {
-  TraceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void TraceImplExternWithUntracedBase::TraceImpl(
-    VisitorDispatcher visitor) {
   // Ditto.
 }
 
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl_error.h b/tools/clang/blink_gc_plugin/tests/traceimpl_error.h
index c7254d4..d05c99c 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl_error.h
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl_error.h
@@ -17,10 +17,7 @@
 class TraceImplInlinedWithUntracedMember
     : public GarbageCollected<TraceImplInlinedWithUntracedMember> {
  public:
-  void Trace(Visitor* visitor) { TraceImpl(visitor); }
-
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {
+  void Trace(Visitor* visitor) {
     // Empty; should get complaints from the plugin for untraced x_.
   }
 
@@ -33,9 +30,6 @@
  public:
   void Trace(Visitor* visitor);
 
-  template <typename VisitorDispatcher>
-  inline void TraceImpl(VisitorDispatcher);
-
  private:
   Member<X> x_;
 };
@@ -47,10 +41,7 @@
 
 class TraceImplInlineWithUntracedBase : public Base {
  public:
-  void Trace(Visitor* visitor) override { TraceImpl(visitor); }
-
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {
+  void Trace(Visitor* visitor) override {
     // Empty; should get complaints from the plugin for untraced Base.
   }
 };
@@ -58,9 +49,6 @@
 class TraceImplExternWithUntracedBase : public Base {
  public:
   void Trace(Visitor*) override;
-
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor);
 };
 
 }
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl_error.txt b/tools/clang/blink_gc_plugin/tests/traceimpl_error.txt
index ec976a0..86bef28b 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl_error.txt
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl_error.txt
@@ -1,20 +1,20 @@
 In file included from traceimpl_error.cpp:5:
-./traceimpl_error.h:23:3: warning: [blink-gc] Class 'TraceImplInlinedWithUntracedMember' has untraced fields that require tracing.
-  void TraceImpl(VisitorDispatcher visitor) {
+./traceimpl_error.h:20:3: warning: [blink-gc] Class 'TraceImplInlinedWithUntracedMember' has untraced fields that require tracing.
+  void Trace(Visitor* visitor) {
   ^
-./traceimpl_error.h:28:3: note: [blink-gc] Untraced field 'x_' declared here:
+./traceimpl_error.h:25:3: note: [blink-gc] Untraced field 'x_' declared here:
   Member<X> x_;
   ^
-./traceimpl_error.h:53:3: warning: [blink-gc] Base class 'Base' of derived class 'TraceImplInlineWithUntracedBase' requires tracing.
-  void TraceImpl(VisitorDispatcher visitor) {
+./traceimpl_error.h:44:3: warning: [blink-gc] Base class 'Base' of derived class 'TraceImplInlineWithUntracedBase' requires tracing.
+  void Trace(Visitor* visitor) override {
   ^
-traceimpl_error.cpp:14:1: warning: [blink-gc] Class 'TraceImplExternWithUntracedMember' has untraced fields that require tracing.
-inline void TraceImplExternWithUntracedMember::TraceImpl(
+traceimpl_error.cpp:9:1: warning: [blink-gc] Class 'TraceImplExternWithUntracedMember' has untraced fields that require tracing.
+void TraceImplExternWithUntracedMember::Trace(Visitor* visitor) {
 ^
-./traceimpl_error.h:40:3: note: [blink-gc] Untraced field 'x_' declared here:
+./traceimpl_error.h:34:3: note: [blink-gc] Untraced field 'x_' declared here:
   Member<X> x_;
   ^
-traceimpl_error.cpp:24:1: warning: [blink-gc] Base class 'Base' of derived class 'TraceImplExternWithUntracedBase' requires tracing.
-inline void TraceImplExternWithUntracedBase::TraceImpl(
+traceimpl_error.cpp:13:1: warning: [blink-gc] Base class 'Base' of derived class 'TraceImplExternWithUntracedBase' requires tracing.
+void TraceImplExternWithUntracedBase::Trace(Visitor* visitor) {
 ^
 4 warnings generated.
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl_omitted_trace.h b/tools/clang/blink_gc_plugin/tests/traceimpl_omitted_trace.h
index 7a171be..6b35965 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl_omitted_trace.h
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl_omitted_trace.h
@@ -11,14 +11,7 @@
 
 class A : public GarbageCollected<A> {
  public:
-  virtual void Trace(Visitor* visitor) { TraceImpl(visitor); }
-  virtual void Trace(InlinedGlobalMarkingVisitor visitor) {
-    TraceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {}
+  virtual void Trace(Visitor* visitor) {}
 };
 
 class B : public A {
@@ -27,14 +20,7 @@
 
 class C : public B {
  public:
-  void Trace(Visitor* visitor) override { TraceImpl(visitor); }
-  void Trace(InlinedGlobalMarkingVisitor visitor) override {
-    TraceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {
+  void Trace(Visitor* visitor) override {
     // B::Trace() is actually A::Trace(), and in certain cases we only get
     // limited information like "there is a function call that will be resolved
     // to A::Trace()". We still want to mark B as Traced.
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded.cpp b/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded.cpp
index e5a2fee..ed04f8276c 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded.cpp
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded.cpp
@@ -7,28 +7,10 @@
 namespace blink {
 
 void ExternBase::Trace(Visitor* visitor) {
-  TraceImpl(visitor);
-}
-
-void ExternBase::Trace(InlinedGlobalMarkingVisitor visitor) {
-  TraceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void ExternBase::TraceImpl(VisitorDispatcher visitor) {
   visitor->Trace(x_base_);
 }
 
 void ExternDerived::Trace(Visitor* visitor) {
-  TraceImpl(visitor);
-}
-
-void ExternDerived::Trace(InlinedGlobalMarkingVisitor visitor) {
-  TraceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void ExternDerived::TraceImpl(VisitorDispatcher visitor) {
   visitor->Trace(x_derived_);
   ExternBase::Trace(visitor);
 }
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded.h b/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded.h
index 63ba65aa6..73ef379 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded.h
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded.h
@@ -12,33 +12,19 @@
 class X : public GarbageCollected<X> {
  public:
   void Trace(Visitor*) {}
-  void Trace(InlinedGlobalMarkingVisitor) {}
 };
 
 class InlinedBase : public GarbageCollected<InlinedBase> {
  public:
-  virtual void Trace(Visitor* visitor) { TraceImpl(visitor); }
-  virtual void Trace(InlinedGlobalMarkingVisitor visitor) {
-    TraceImpl(visitor);
-  }
+  virtual void Trace(Visitor* visitor) { visitor->Trace(x_base_); }
 
  private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) { visitor->Trace(x_base_); }
-
   Member<X> x_base_;
 };
 
 class InlinedDerived : public InlinedBase {
  public:
-  void Trace(Visitor* visitor) override { TraceImpl(visitor); }
-  void Trace(InlinedGlobalMarkingVisitor visitor) override {
-    TraceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {
+  void Trace(Visitor* visitor) override {
     visitor->Trace(x_derived_);
     InlinedBase::Trace(visitor);
   }
@@ -49,24 +35,16 @@
 class ExternBase : public GarbageCollected<ExternBase> {
  public:
   virtual void Trace(Visitor*);
-  virtual void Trace(InlinedGlobalMarkingVisitor);
 
  private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher);
-
   Member<X> x_base_;
 };
 
 class ExternDerived : public ExternBase {
  public:
   void Trace(Visitor*) override;
-  void Trace(InlinedGlobalMarkingVisitor) override;
 
  private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher);
-
   Member<X> x_derived_;
 };
 
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded_error.cpp b/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded_error.cpp
index 80d0f65..d0d18e5 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded_error.cpp
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded_error.cpp
@@ -7,28 +7,10 @@
 namespace blink {
 
 void ExternBase::Trace(Visitor* visitor) {
-  TraceImpl(visitor);
-}
-
-void ExternBase::Trace(InlinedGlobalMarkingVisitor visitor) {
-  TraceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void ExternBase::TraceImpl(VisitorDispatcher visitor) {
   // Missing visitor->Trace(x_base_).
 }
 
 void ExternDerived::Trace(Visitor* visitor) {
-  TraceImpl(visitor);
-}
-
-void ExternDerived::Trace(InlinedGlobalMarkingVisitor visitor) {
-  TraceImpl(visitor);
-}
-
-template <typename VisitorDispatcher>
-inline void ExternDerived::TraceImpl(VisitorDispatcher visitor) {
   // Missing visitor->Trace(x_derived_) and ExternBase::Trace(visitor).
 }
 
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded_error.h b/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded_error.h
index be587de..611b076 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded_error.h
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded_error.h
@@ -12,62 +12,41 @@
 class X : public GarbageCollected<X> {
  public:
   void Trace(Visitor*) {}
-  void Trace(InlinedGlobalMarkingVisitor) {}
 };
 
 class InlinedBase : public GarbageCollected<InlinedBase> {
  public:
-  virtual void Trace(Visitor* visitor) { TraceImpl(visitor); }
-  virtual void Trace(InlinedGlobalMarkingVisitor visitor) {
-    TraceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {
+  virtual void Trace(Visitor* visitor) {
     // Missing visitor->Trace(x_base_).
   }
 
+ private:
   Member<X> x_base_;
 };
 
 class InlinedDerived : public InlinedBase {
  public:
-  void Trace(Visitor* visitor) override { TraceImpl(visitor); }
-  void Trace(InlinedGlobalMarkingVisitor visitor) override {
-    TraceImpl(visitor);
-  }
-
- private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher visitor) {
+  void Trace(Visitor* visitor) override {
     // Missing visitor->Trace(x_derived_) and InlinedBase::Trace(visitor).
   }
 
+ private:
   Member<X> x_derived_;
 };
 
 class ExternBase : public GarbageCollected<ExternBase> {
  public:
   virtual void Trace(Visitor*);
-  virtual void Trace(InlinedGlobalMarkingVisitor);
 
  private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher);
-
   Member<X> x_base_;
 };
 
 class ExternDerived : public ExternBase {
  public:
   void Trace(Visitor*) override;
-  void Trace(InlinedGlobalMarkingVisitor) override;
 
  private:
-  template <typename VisitorDispatcher>
-  void TraceImpl(VisitorDispatcher);
-
   Member<X> x_derived_;
 };
 
diff --git a/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded_error.txt b/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded_error.txt
index b603a086..eccfb03 100644
--- a/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded_error.txt
+++ b/tools/clang/blink_gc_plugin/tests/traceimpl_overloaded_error.txt
@@ -1,28 +1,28 @@
 In file included from traceimpl_overloaded_error.cpp:5:
-./traceimpl_overloaded_error.h:27:3: warning: [blink-gc] Class 'InlinedBase' has untraced fields that require tracing.
-  void TraceImpl(VisitorDispatcher visitor) {
+./traceimpl_overloaded_error.h:19:3: warning: [blink-gc] Class 'InlinedBase' has untraced fields that require tracing.
+  virtual void Trace(Visitor* visitor) {
   ^
-./traceimpl_overloaded_error.h:31:3: note: [blink-gc] Untraced field 'x_base_' declared here:
+./traceimpl_overloaded_error.h:24:3: note: [blink-gc] Untraced field 'x_base_' declared here:
   Member<X> x_base_;
   ^
-./traceimpl_overloaded_error.h:43:3: warning: [blink-gc] Base class 'InlinedBase' of derived class 'InlinedDerived' requires tracing.
-  void TraceImpl(VisitorDispatcher visitor) {
+./traceimpl_overloaded_error.h:29:3: warning: [blink-gc] Base class 'InlinedBase' of derived class 'InlinedDerived' requires tracing.
+  void Trace(Visitor* visitor) override {
   ^
-./traceimpl_overloaded_error.h:43:3: warning: [blink-gc] Class 'InlinedDerived' has untraced fields that require tracing.
-./traceimpl_overloaded_error.h:47:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
+./traceimpl_overloaded_error.h:29:3: warning: [blink-gc] Class 'InlinedDerived' has untraced fields that require tracing.
+./traceimpl_overloaded_error.h:34:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
   Member<X> x_derived_;
   ^
-traceimpl_overloaded_error.cpp:18:1: warning: [blink-gc] Class 'ExternBase' has untraced fields that require tracing.
-inline void ExternBase::TraceImpl(VisitorDispatcher visitor) {
+traceimpl_overloaded_error.cpp:9:1: warning: [blink-gc] Class 'ExternBase' has untraced fields that require tracing.
+void ExternBase::Trace(Visitor* visitor) {
 ^
-./traceimpl_overloaded_error.h:59:3: note: [blink-gc] Untraced field 'x_base_' declared here:
+./traceimpl_overloaded_error.h:42:3: note: [blink-gc] Untraced field 'x_base_' declared here:
   Member<X> x_base_;
   ^
-traceimpl_overloaded_error.cpp:31:1: warning: [blink-gc] Base class 'ExternBase' of derived class 'ExternDerived' requires tracing.
-inline void ExternDerived::TraceImpl(VisitorDispatcher visitor) {
+traceimpl_overloaded_error.cpp:13:1: warning: [blink-gc] Base class 'ExternBase' of derived class 'ExternDerived' requires tracing.
+void ExternDerived::Trace(Visitor* visitor) {
 ^
-traceimpl_overloaded_error.cpp:31:1: warning: [blink-gc] Class 'ExternDerived' has untraced fields that require tracing.
-./traceimpl_overloaded_error.h:71:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
+traceimpl_overloaded_error.cpp:13:1: warning: [blink-gc] Class 'ExternDerived' has untraced fields that require tracing.
+./traceimpl_overloaded_error.h:50:3: note: [blink-gc] Untraced field 'x_derived_' declared here:
   Member<X> x_derived_;
   ^
 6 warnings generated.
diff --git a/tools/idl_parser/OWNERS b/tools/idl_parser/OWNERS
index afc0d893..ff5f0e4 100644
--- a/tools/idl_parser/OWNERS
+++ b/tools/idl_parser/OWNERS
@@ -2,5 +2,5 @@
 haraken@chromium.org
 yukishiino@chromium.org
 
-# TEAM: blink-bindings-reviews@chromium.org
+# TEAM: blink-reviews-bindings@chromium.org
 # COMPONENT: Blink>Bindings
diff --git a/tools/json_schema_compiler/idl_schema.py b/tools/json_schema_compiler/idl_schema.py
index 58efe28..fcad49b 100755
--- a/tools/json_schema_compiler/idl_schema.py
+++ b/tools/json_schema_compiler/idl_schema.py
@@ -50,13 +50,20 @@
 
   Returns: A tuple that looks like:
     (
-      "The processed comment, minus all |parameter| mentions.",
+      "The processed comment, minus all |parameter| mentions and jsexterns.",
+      "Any block wrapped in <jsexterns></jsexterns>.",
       {
         'parameter_name_1': "The comment that followed |parameter_name_1|:",
         ...
       }
     )
   '''
+  jsexterns = None
+  match = re.search('<jsexterns>(.*)</jsexterns>', comment, re.DOTALL)
+  if match:
+    jsexterns = match.group(1).strip()
+    comment = comment[:match.start()] + comment[match.end():]
+
   def add_paragraphs(content):
     paragraphs = content.split('\n\n')
     if len(paragraphs) < 2:
@@ -85,7 +92,7 @@
         add_paragraphs(comment[param_comment_start:param_comment_end].strip())
         .replace('\n', ''))
 
-  return (parent_comment, params)
+  return (parent_comment, jsexterns, params)
 
 
 class Callspec(object):
@@ -195,8 +202,10 @@
     parameter_comments = OrderedDict()
     for node in self.node.GetChildren():
       if node.cls == 'Comment':
-        (parent_comment, parameter_comments) = ProcessComment(node.GetName())
+        (parent_comment, jsexterns, parameter_comments) = ProcessComment(
+            node.GetName())
         properties['description'] = parent_comment
+        properties['jsexterns'] = jsexterns
       elif node.cls == 'Callspec':
         name, parameters, return_type = (Callspec(node, parameter_comments)
                                          .process(callbacks))
diff --git a/tools/json_schema_compiler/idl_schema_test.py b/tools/json_schema_compiler/idl_schema_test.py
index 3e0d3cc..f9a4d87 100755
--- a/tools/json_schema_compiler/idl_schema_test.py
+++ b/tools/json_schema_compiler/idl_schema_test.py
@@ -70,7 +70,8 @@
   def testLegalValues(self):
     self.assertEquals({
         'x': {'name': 'x', 'type': 'integer', 'enum': [1,2],
-              'description': 'This comment tests "double-quotes".'},
+              'description': 'This comment tests "double-quotes".',
+              'jsexterns': None},
         'y': {'name': 'y', 'type': 'string'},
         'z': {'name': 'z', 'type': 'string'},
         'a': {'name': 'a', 'type': 'string'},
@@ -402,21 +403,25 @@
     self.assertEquals(OrderedDict([
       ('first', OrderedDict([
         ('description', 'Integer property.'),
+        ('jsexterns', None),
         ('type', 'integer'),
         ('value', 42),
       ])),
       ('second', OrderedDict([
         ('description', 'Double property.'),
+        ('jsexterns', None),
         ('type', 'number'),
         ('value', 42.0),
       ])),
       ('third', OrderedDict([
         ('description', 'String property.'),
+        ('jsexterns', None),
         ('type', 'string'),
         ('value', 'hello world'),
       ])),
       ('fourth', OrderedDict([
         ('description', 'Unvalued property.'),
+        ('jsexterns', None),
         ('type', 'integer'),
       ])),
     ]), schema.get('properties'))
diff --git a/tools/json_schema_compiler/js_externs_generator.py b/tools/json_schema_compiler/js_externs_generator.py
index 065e4d3..a5ef075 100644
--- a/tools/json_schema_compiler/js_externs_generator.py
+++ b/tools/json_schema_compiler/js_externs_generator.py
@@ -30,6 +30,7 @@
 class _Generator(object):
   def __init__(self, namespace):
     self._namespace = namespace
+    self._class_name = None
     self._js_util = JsUtil()
 
   def Generate(self):
@@ -79,7 +80,7 @@
       .Append(self._js_util.GetSeeLink(self._namespace.name, 'type',
                                        js_type.simple_name))
       .Eblock(' */'))
-    c.Append('chrome.%s.%s = {' % (self._namespace.name, js_type.name))
+    c.Append('%s.%s = {' % (self._GetNamespace(), js_type.name))
 
     def get_property_name(e):
       # Enum properties are normified to be in ALL_CAPS_STYLE.
@@ -105,7 +106,7 @@
     return any(prop.type_.property_type is PropertyType.FUNCTION
                for prop in js_type.properties.values())
 
-  def _AppendTypeJsDoc(self, c, js_type):
+  def _AppendTypeJsDoc(self, c, js_type, optional=False):
     """Appends the documentation for a type as a Code.
     """
     c.Sblock(line='/**', line_prefix=' * ')
@@ -114,9 +115,16 @@
       for line in js_type.description.splitlines():
         c.Append(line)
 
+    if js_type.jsexterns:
+      for line in js_type.jsexterns.splitlines():
+        c.Append(line)
+
     is_constructor = self._IsTypeConstructor(js_type)
-    if is_constructor:
-      c.Comment('@constructor', comment_prefix = ' * ', wrap_indent=4)
+    if js_type.property_type is not PropertyType.OBJECT:
+      self._js_util.AppendTypeJsDoc(c, self._namespace.name, js_type, optional)
+    elif is_constructor:
+      c.Comment('@constructor', comment_prefix = '', wrap_indent=4)
+      c.Comment('@private', comment_prefix = '', wrap_indent=4)
     else:
       self._AppendTypedef(c, js_type.properties)
 
@@ -124,11 +132,22 @@
                                       js_type.simple_name))
     c.Eblock(' */')
 
-    var = 'chrome.%s.%s' % (js_type.namespace.name, js_type.simple_name)
+    var = '%s.%s' % (self._GetNamespace(), js_type.simple_name)
     if is_constructor: var += ' = function() {}'
     var += ';'
     c.Append(var)
 
+    if is_constructor:
+      c.Append()
+      self._class_name = js_type.name
+      for prop in js_type.properties.values():
+        if prop.type_.property_type is PropertyType.FUNCTION:
+          self._AppendFunction(c, prop.type_.function)
+        else:
+          self._AppendTypeJsDoc(c, prop.type_, prop.optional)
+          c.Append()
+      self._class_name = None
+
   def _AppendTypedef(self, c, properties):
     """Given an OrderedDict of properties, Appends code containing a @typedef.
     """
@@ -150,8 +169,8 @@
     """
     self._js_util.AppendFunctionJsDoc(c, self._namespace.name, function)
     params = self._GetFunctionParams(function)
-    c.Append('chrome.%s.%s = function(%s) {};' % (self._namespace.name,
-                                                  function.name, params))
+    c.Append('%s.%s = function(%s) {};' % (self._GetNamespace(),
+                                           function.name, params))
     c.Append()
 
   def _AppendEvent(self, c, event):
@@ -168,7 +187,7 @@
     c.Append(self._js_util.GetSeeLink(self._namespace.name, 'event',
                                       event.name))
     c.Eblock(' */')
-    c.Append('chrome.%s.%s;' % (self._namespace.name, event.name))
+    c.Append('%s.%s;' % (self._GetNamespace(), event.name))
     c.Append()
 
   def _AppendNamespaceObject(self, c):
@@ -193,3 +212,17 @@
     if function.callback:
       params.append(function.callback)
     return ', '.join(param.name for param in params)
+
+  def _GetNamespace(self):
+    """Returns the namespace to be prepended to a top-level typedef.
+
+       For example, it might return "chrome.namespace".
+
+       Also optionally includes the class name if this is in the context
+       of outputting the members of a class.
+
+       For example, "chrome.namespace.ClassName.prototype"
+    """
+    if self._class_name:
+      return 'chrome.%s.%s.prototype' % (self._namespace.name, self._class_name)
+    return 'chrome.%s' % self._namespace.name
diff --git a/tools/json_schema_compiler/js_externs_generator_test.py b/tools/json_schema_compiler/js_externs_generator_test.py
index 917ce637..88c2a09f 100755
--- a/tools/json_schema_compiler/js_externs_generator_test.py
+++ b/tools/json_schema_compiler/js_externs_generator_test.py
@@ -47,6 +47,18 @@
     ArrayBuffer arrayBuff;
   };
 
+  dictionary Qux {
+    long notOptionalLong;
+    long? optionalLong;
+
+    // A map from string to number.
+    // <jsexterns>@type {Object<string, number>}</jsexterns>
+    object dict;
+
+    static void go();
+    static void stop();
+  };
+
   callback VoidCallback = void();
 
   callback BazGreekCallback = void(Baz baz, Greek greek);
@@ -132,6 +144,43 @@
 chrome.fakeApi.Baz;
 
 /**
+ * @constructor
+ * @private
+ * @see https://developer.chrome.com/extensions/fakeApi#type-Qux
+ */
+chrome.fakeApi.Qux = function() {};
+
+/**
+ * @type {number}
+ * @see https://developer.chrome.com/extensions/fakeApi#type-notOptionalLong
+ */
+chrome.fakeApi.Qux.prototype.notOptionalLong;
+
+/**
+ * @type {(number|undefined)}
+ * @see https://developer.chrome.com/extensions/fakeApi#type-optionalLong
+ */
+chrome.fakeApi.Qux.prototype.optionalLong;
+
+/**
+ * A map from string to number.
+ * @type {Object<string, number>}
+ * @see https://developer.chrome.com/extensions/fakeApi#type-dict
+ */
+chrome.fakeApi.Qux.prototype.dict;
+
+/**
+ * @see https://developer.chrome.com/extensions/fakeApi#method-go
+ */
+chrome.fakeApi.Qux.prototype.go = function() {};
+
+/**
+ * @see https://developer.chrome.com/extensions/fakeApi#method-stop
+ */
+chrome.fakeApi.Qux.prototype.stop = function() {};
+
+
+/**
  * Does something exciting! And what's more, this is a multiline function
  * comment! It goes onto multiple lines!
  * @param {!chrome.fakeApi.Baz} baz The baz to use.
diff --git a/tools/json_schema_compiler/js_util.py b/tools/json_schema_compiler/js_util.py
index 2549aef..e563b88 100644
--- a/tools/json_schema_compiler/js_util.py
+++ b/tools/json_schema_compiler/js_util.py
@@ -101,6 +101,18 @@
 
     c.Eblock(' */')
 
+  def AppendTypeJsDoc(self, c, namespace_name, js_type, optional):
+    """Appends the documentation for a type as a Code.
+    """
+    c.Append('@type {')
+    if optional:
+      c.Append('(', new_line=False)
+      c.Concat(self._TypeToJsType(namespace_name, js_type), new_line=False)
+      c.Append('|undefined)', new_line=False)
+    else:
+      c.Concat(self._TypeToJsType(namespace_name, js_type), new_line=False)
+    c.Append('}', new_line=False)
+
   def _FunctionToJsFunction(self, namespace_name, function):
     """Converts a model.Function to a JS type (i.e., function([params])...)"""
     c = Code()
diff --git a/tools/json_schema_compiler/model.py b/tools/json_schema_compiler/model.py
index e0147b5..6dc87a96 100644
--- a/tools/json_schema_compiler/model.py
+++ b/tools/json_schema_compiler/model.py
@@ -183,6 +183,7 @@
     self.simple_name = _StripNamespace(self.name, namespace)
     self.unix_name = UnixName(self.name)
     self.description = json.get('description', None)
+    self.jsexterns = json.get('jsexterns', None)
     self.origin = origin
     self.parent = parent
     self.instance_of = json.get('isInstanceOf', None)
@@ -317,8 +318,9 @@
     self.filters = [GeneratePropertyFromParam(filter_instance)
                     for filter_instance in json.get('filters', [])]
     callback_param = None
-    for param in json.get('parameters', []):
-      if param.get('type') == 'function':
+    params = json.get('parameters', [])
+    for i, param in enumerate(params):
+      if param.get('type') == 'function' and i == len(params) - 1:
         if callback_param:
           # No ParseException because the webstore has this.
           # Instead, pretend all intermediate callbacks are properties.
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index e61b959..07502cd 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -96089,6 +96089,7 @@
   <int value="730024226" label="enable-out-of-process-pdf"/>
   <int value="730750097" label="PermissionsBlacklist:disabled"/>
   <int value="732703958" label="enable-gesture-tap-highlight"/>
+  <int value="738868972" label="GdiTextPrinting:disabled"/>
   <int value="745868416" label="disable-system-timezone-automatic-detection"/>
   <int value="746944193" label="enable-automatic-password-saving:disabled"/>
   <int value="747847237" label="PhysicalWeb:enabled"/>
@@ -96261,6 +96262,7 @@
   <int value="1454363479" label="disable-storage-manager"/>
   <int value="1458583431" label="arc-use-auth-endpoint"/>
   <int value="1459529277" label="disable-text-input-focus-manager"/>
+  <int value="1460747747" label="GdiTextPrinting:enabled"/>
   <int value="1460958818" label="NTPForeignSessionsSuggestions:enabled"/>
   <int value="1465624446" label="disable-zero-copy"/>
   <int value="1466380480" label="enable-device-discovery-notifications"/>
diff --git a/ui/accessibility/PRESUBMIT.py b/ui/accessibility/PRESUBMIT.py
index ef41ab0..ffadd00 100644
--- a/ui/accessibility/PRESUBMIT.py
+++ b/ui/accessibility/PRESUBMIT.py
@@ -82,6 +82,8 @@
                     output_api)
   CheckMatchingEnum(ax_enums, 'AXEvent', automation_enums, 'EventType', errs,
                     output_api)
+  CheckMatchingEnum(ax_enums, 'AXNameFrom', automation_enums, 'NameFromType',
+                    errs, output_api)
   return errs
 
 def CheckChangeOnUpload(input_api, output_api):
diff --git a/ui/views/mus/desktop_window_tree_host_mus.cc b/ui/views/mus/desktop_window_tree_host_mus.cc
index 3dc3c368..9cff6ce 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus.cc
@@ -272,9 +272,12 @@
   aura::client::SetCursorClient(window(), cursor_manager_.get());
   InitHost();
 
-  if (params.parent) {
-    aura::client::GetTransientWindowClient()->AddTransientChild(params.parent,
-                                                                window());
+  // Transient parents are connected using the Window created by WindowTreeHost,
+  // which is owned by the window manager. This way the window manager can
+  // properly identify and honor transients.
+  if (params.parent && params.parent->GetHost()) {
+    aura::client::GetTransientWindowClient()->AddTransientChild(
+        params.parent->GetHost()->window(), window());
   }
 }
 
diff --git a/ui/views/mus/desktop_window_tree_host_mus_unittest.cc b/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
index 419b48d..a2609df 100644
--- a/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/memory/ptr_util.h"
 #include "ui/aura/client/cursor_client.h"
+#include "ui/aura/client/transient_window_client.h"
 #include "ui/aura/mus/in_flight_change.h"
 #include "ui/aura/test/mus/change_completion_waiter.h"
 #include "ui/aura/window.h"
@@ -29,12 +30,14 @@
   ~DesktopWindowTreeHostMusTest() override {}
 
   // Creates a test widget. Takes ownership of |delegate|.
-  std::unique_ptr<Widget> CreateWidget(WidgetDelegate* delegate) {
+  std::unique_ptr<Widget> CreateWidget(WidgetDelegate* delegate = nullptr,
+                                       aura::Window* parent = nullptr) {
     std::unique_ptr<Widget> widget = base::MakeUnique<Widget>();
     Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
     params.delegate = delegate;
     params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
     params.bounds = gfx::Rect(0, 1, 111, 123);
+    params.parent = parent;
     widget->Init(params);
     widget->AddObserver(this);
     return widget;
@@ -105,7 +108,7 @@
 };
 
 TEST_F(DesktopWindowTreeHostMusTest, Visibility) {
-  std::unique_ptr<Widget> widget(CreateWidget(nullptr));
+  std::unique_ptr<Widget> widget(CreateWidget());
   EXPECT_FALSE(widget->IsVisible());
   EXPECT_FALSE(widget->GetNativeView()->IsVisible());
   // It's important the parent is also hidden as this value is sent to the
@@ -122,10 +125,10 @@
 }
 
 TEST_F(DesktopWindowTreeHostMusTest, Deactivate) {
-  std::unique_ptr<Widget> widget1(CreateWidget(nullptr));
+  std::unique_ptr<Widget> widget1(CreateWidget());
   widget1->Show();
 
-  std::unique_ptr<Widget> widget2(CreateWidget(nullptr));
+  std::unique_ptr<Widget> widget2(CreateWidget());
   widget2->Show();
 
   widget1->Activate();
@@ -138,7 +141,7 @@
 }
 
 TEST_F(DesktopWindowTreeHostMusTest, CursorClientDuringTearDown) {
-  std::unique_ptr<Widget> widget(CreateWidget(nullptr));
+  std::unique_ptr<Widget> widget(CreateWidget());
   widget->Show();
 
   std::unique_ptr<aura::Window> window(new aura::Window(nullptr));
@@ -150,10 +153,10 @@
 }
 
 TEST_F(DesktopWindowTreeHostMusTest, StackAtTop) {
-  std::unique_ptr<Widget> widget1(CreateWidget(nullptr));
+  std::unique_ptr<Widget> widget1(CreateWidget());
   widget1->Show();
 
-  std::unique_ptr<Widget> widget2(CreateWidget(nullptr));
+  std::unique_ptr<Widget> widget2(CreateWidget());
   widget2->Show();
 
   aura::test::ChangeCompletionWaiter waiter(
@@ -168,10 +171,10 @@
 }
 
 TEST_F(DesktopWindowTreeHostMusTest, StackAtTopAlreadyOnTop) {
-  std::unique_ptr<Widget> widget1(CreateWidget(nullptr));
+  std::unique_ptr<Widget> widget1(CreateWidget());
   widget1->Show();
 
-  std::unique_ptr<Widget> widget2(CreateWidget(nullptr));
+  std::unique_ptr<Widget> widget2(CreateWidget());
   widget2->Show();
 
   aura::test::ChangeCompletionWaiter waiter(
@@ -181,4 +184,21 @@
   waiter.Wait();
 }
 
+TEST_F(DesktopWindowTreeHostMusTest, TransientParentWiredToHostWindow) {
+  std::unique_ptr<Widget> widget1(CreateWidget());
+  widget1->Show();
+
+  std::unique_ptr<Widget> widget2(
+      CreateWidget(nullptr, widget1->GetNativeView()));
+  widget2->Show();
+
+  aura::client::TransientWindowClient* transient_window_client =
+      aura::client::GetTransientWindowClient();
+  // Even though the widget1->GetNativeView() was specified as the parent we
+  // expect the transient parents to be marked at the host level.
+  EXPECT_EQ(widget1->GetNativeView()->GetHost()->window(),
+            transient_window_client->GetTransientParent(
+                widget2->GetNativeView()->GetHost()->window()));
+}
+
 }  // namespace views
diff --git a/ui/webui/resources/cr_elements/shared_style_css.html b/ui/webui/resources/cr_elements/shared_style_css.html
index e34ab9a..6118fb2 100644
--- a/ui/webui/resources/cr_elements/shared_style_css.html
+++ b/ui/webui/resources/cr_elements/shared_style_css.html
@@ -52,11 +52,11 @@
       }
 
       button[is='paper-icon-button-light'].subpage-arrow {
-        background-image: url(../images/arrow_right.svg);
+        background-image: url(chrome://resources/images/arrow_right.svg);
       }
 
       button[is='paper-icon-button-light'].icon-external {
-        background-image: url(../images/open_in_new.svg);
+        background-image: url(chrome://resources/images/open_in_new.svg);
       }
 
       .subpage-arrow,
diff --git a/ui/webui/resources/cr_elements_images.grdp b/ui/webui/resources/cr_elements_images.grdp
index 744935f..61b9dc8 100644
--- a/ui/webui/resources/cr_elements_images.grdp
+++ b/ui/webui/resources/cr_elements_images.grdp
@@ -1,5 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <grit-part>
+  <include name="IDR_WEBUI_IMAGES_ARROW_DOWN"
+           file="images/arrow_down.svg" type="BINDATA" />
+  <include name="IDR_WEBUI_IMAGES_ARROW_RIGHT"
+           file="images/arrow_right.svg" type="BINDATA" />
+  <include name="IDR_WEBUI_IMAGES_OPEN_IN_NEW"
+           file="images/open_in_new.svg" type="BINDATA" />
   <if expr="chromeos">
     <include name="IDR_CR_ELEMENTS_CELLULAR_0_SVG"
              file="cr_elements/network/cellular_0.svg" type="BINDATA" />
diff --git a/ui/webui/resources/html/md_select_css.html b/ui/webui/resources/html/md_select_css.html
index 92ea60a..be9b42cba 100644
--- a/ui/webui/resources/html/md_select_css.html
+++ b/ui/webui/resources/html/md_select_css.html
@@ -9,7 +9,8 @@
         -webkit-appearance: none;
         /* Ensure that the text does not overlap with the down arrow. */
         -webkit-padding-end: calc(var(--md-arrow-width) * 1.8);
-        background: url(../images/arrow_down.svg) 97% center no-repeat;
+        background: url(
+          chrome://resources/images/arrow_down.svg) 97% center no-repeat;
         background-size: var(--md-arrow-width);
         border-bottom: 1px solid var(--paper-grey-300);
         border-left: none;