Merge "Update to latest catapult (eae13a4)"
am: 5796f4d84c

Change-Id: I061999e83022fd7518e64b44ed8ff935ce758a6d
diff --git a/UPSTREAM_REVISION b/UPSTREAM_REVISION
index 872cc65..ef75e06 100644
--- a/UPSTREAM_REVISION
+++ b/UPSTREAM_REVISION
@@ -1 +1 @@
-3fe65c60e0314212730f3e5363a58dfda949d761
+eae13a4b34ccf54974dfacf7d72effd99729f543
diff --git a/catapult/common/battor/battor/__init__.py b/catapult/common/battor/battor/__init__.py
deleted file mode 100644
index f18f330..0000000
--- a/catapult/common/battor/battor/__init__.py
+++ /dev/null
@@ -1,27 +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.
-import os
-import sys
-
-def _JoinPath(*path_parts):
-  return os.path.abspath(os.path.join(*path_parts))
-
-
-def _AddDirToPythonPath(*path_parts):
-  path = _JoinPath(*path_parts)
-  if os.path.isdir(path) and path not in sys.path:
-    # Some call sites that use Telemetry assume that sys.path[0] is the
-    # directory containing the script, so we add these extra paths to right
-    # after sys.path[0].
-    sys.path.insert(1, path)
-
-_CATAPULT_DIR = os.path.join(
-    os.path.dirname(os.path.abspath(__file__)),
-                    os.path.pardir, os.path.pardir, os.path.pardir)
-
-_AddDirToPythonPath(_CATAPULT_DIR, 'common', 'battor')
-_AddDirToPythonPath(_CATAPULT_DIR, 'common', 'py_utils')
-_AddDirToPythonPath(_CATAPULT_DIR, 'dependency_manager')
-_AddDirToPythonPath(_CATAPULT_DIR, 'devil')
-_AddDirToPythonPath(_CATAPULT_DIR, 'third_party', 'pyserial')
diff --git a/catapult/common/battor/battor/battor_binary_dependencies.json b/catapult/common/battor/battor/battor_binary_dependencies.json
deleted file mode 100644
index 23be1a3..0000000
--- a/catapult/common/battor/battor/battor_binary_dependencies.json
+++ /dev/null
@@ -1,100 +0,0 @@
-{
-  "config_type": "BaseConfig",
-  "dependencies": {
-    "avrdude_binary": {
-      "cloud_storage_base_folder": "binary_dependencies/battor",
-      "cloud_storage_bucket": "chromium-telemetry",
-      "file_info": {
-        "mac_x86_64": {
-          "cloud_storage_hash": "6de6324c279ea75c79c68cab4c2ddcc68da1b286",
-          "download_path": "../bin/darwin/x86_64/avrdude",
-          "local_paths": [
-            "../bin/override/avrdude"
-          ]
-        },
-        "linux_x86_64": {
-          "cloud_storage_hash": "db29526605f6f95a75ab33f4060b8c330152de69",
-          "download_path": "../bin/linux/x86_64/avrdude",
-          "local_paths": [
-            "../bin/override/avrdude"
-          ]
-        },
-        "win_AMD64": {
-          "cloud_storage_hash": "517aa73b093e254007076cf5ac7afb94151df2ed",
-          "download_path": "../bin/win/x86_64/avrdude.exe",
-          "local_paths": [
-            "../bin/override/avrdude.exe"
-          ]
-        },
-        "win_x86": {
-          "cloud_storage_hash": "517aa73b093e254007076cf5ac7afb94151df2ed",
-          "download_path": "../bin/win/x86_64/avrdude.exe",
-          "local_paths": [
-            "../bin/override/avrdude.exe"
-          ]
-        }
-      }
-    },
-    "avrdude_config": {
-      "cloud_storage_base_folder": "binary_dependencies/battor",
-      "cloud_storage_bucket": "chromium-telemetry",
-      "file_info": {
-        "default": {
-          "cloud_storage_hash": "ccdfa12743429b8b92b61a20163d6311ab55a4fa",
-          "download_path": "../bin/battor/avrdude.conf",
-          "local_paths": [
-            "../bin/override/avrdude.conf"
-          ]
-        }
-      }
-    },
-    "battor_agent_binary": {
-      "cloud_storage_base_folder": "binary_dependencies/battor",
-      "cloud_storage_bucket": "chromium-telemetry",
-      "file_info": {
-        "mac_x86_64": {
-          "cloud_storage_hash": "154613804a1855a4871422bf99aa8e0dd0d7f62e",
-          "download_path": "../bin/darwin/x86_64/battor_agent",
-          "local_paths": [
-            "../bin/override/battor_agent"
-          ]
-        },
-        "linux_x86_64": {
-          "cloud_storage_hash": "f53e15b97301e9dab00e893800ec67a98e9f13ea",
-          "download_path": "../bin/linux/x86_64/battor_agent",
-          "local_paths": [
-            "../bin/override/battor_agent"
-          ]
-        },
-        "win_AMD64": {
-          "cloud_storage_hash": "4095a9159dd189ec591679e43b14d34ae30335eb",
-          "download_path": "../bin/win/x86_64/battor_agent.exe",
-          "local_paths": [
-            "../bin/override/battor_agent.exe"
-          ]
-        },
-        "win_x86": {
-          "cloud_storage_hash": "4095a9159dd189ec591679e43b14d34ae30335eb",
-          "download_path": "../bin/win/x86_64/battor_agent.exe",
-          "local_paths": [
-            "../bin/override/battor_agent.exe"
-          ]
-        }
-      }
-    },
-    "battor_firmware": {
-      "cloud_storage_base_folder": "binary_dependencies/battor",
-      "cloud_storage_bucket": "chromium-telemetry",
-      "file_info": {
-        "default": {
-          "cloud_storage_hash": "0649655f78698368a16a4e9ec37967b80528fdbf",
-          "download_path": "../bin/battor/battor_firmware.hex",
-          "local_paths": [
-            "../bin/override/battor_firmware.hex"
-          ],
-          "version_in_cs": "de05458"
-        }
-      }
-    }
-  }
-}
diff --git a/catapult/common/battor/battor/battor_error.py b/catapult/common/battor/battor/battor_error.py
deleted file mode 100644
index 3ea6efc..0000000
--- a/catapult/common/battor/battor/battor_error.py
+++ /dev/null
@@ -1,8 +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.
-
-from devil import base_error
-
-class BattOrError(base_error.BaseError):
-  pass
diff --git a/catapult/common/battor/battor/battor_wrapper.py b/catapult/common/battor/battor/battor_wrapper.py
deleted file mode 100644
index 93aed8b..0000000
--- a/catapult/common/battor/battor/battor_wrapper.py
+++ /dev/null
@@ -1,436 +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.
-
-import datetime
-import os
-import logging
-import platform
-import random
-import subprocess
-import sys
-import tempfile
-import time
-
-from battor import battor_error
-import py_utils
-from py_utils import atexit_with_log
-from py_utils import cloud_storage
-import dependency_manager
-from devil.utils import battor_device_mapping
-from devil.utils import find_usb_devices
-
-import serial
-from serial.tools import list_ports
-
-
-DEFAULT_SHELL_CLOSE_TIMEOUT_S = 60
-
-
-def IsBattOrConnected(*args, **kwargs):
-  """Returns True if BattOr is detected.
-
-  See _IsBattOrConnected below for arguments.
-  """
-  is_connected = _IsBattOrConnected(*args, **kwargs)
-  if is_connected:
-    logging.info('BattOr power monitor is connected.')
-  else:
-    logging.info('BattOr power monitor is not connected.')
-  return is_connected
-
-
-def _IsBattOrConnected(test_platform, android_device=None,
-                      android_device_map=None, android_device_file=None):
-  """Returns True if BattOr is detected."""
-  if test_platform == 'android':
-    if not android_device:
-      raise ValueError('Must pass android device serial when determining '
-                       'support on android platform')
-
-    if not android_device_map:
-      device_tree = find_usb_devices.GetBusNumberToDeviceTreeMap()
-      if device_tree:
-        for _, node in sorted(device_tree.iteritems()):
-          node.Display()
-      if len(battor_device_mapping.GetBattOrList(device_tree)) == 1:
-        return True
-      if android_device_file:
-        android_device_map = battor_device_mapping.ReadSerialMapFile(
-            android_device_file)
-      else:
-        try:
-          android_device_map = battor_device_mapping.GenerateSerialMap()
-        except battor_error.BattOrError:
-          return False
-
-    # If neither if statement above is triggered, it means that an
-    # android_device_map was passed in and will be used.
-    return str(android_device) in android_device_map
-
-  elif test_platform == 'win':
-    for (_1, desc, _2) in serial.tools.list_ports.comports():
-      if 'USB Serial Port' in desc:
-        return True
-    logging.info('No usb serial port discovered. Available ones are: %s' %
-                 list(serial.tools.list_ports.comports()))
-    return False
-
-  elif test_platform == 'mac':
-    for (_1, desc, _2) in serial.tools.list_ports.comports():
-      if 'BattOr' in desc:
-        return True
-    return False
-
-  elif test_platform == 'linux':
-    device_tree = find_usb_devices.GetBusNumberToDeviceTreeMap(fast=True)
-    return bool(battor_device_mapping.GetBattOrList(device_tree))
-
-  return False
-
-
-class BattOrWrapper(object):
-  """A class for communicating with a BattOr in python."""
-  _EXIT_CMD = 'Exit'
-  _GET_FIRMWARE_GIT_HASH_CMD = 'GetFirmwareGitHash'
-  _START_TRACING_CMD = 'StartTracing'
-  _STOP_TRACING_CMD = 'StopTracing'
-  _SUPPORTS_CLOCKSYNC_CMD = 'SupportsExplicitClockSync'
-  _RECORD_CLOCKSYNC_CMD = 'RecordClockSyncMarker'
-  _SUPPORTED_PLATFORMS = ['android', 'chromeos', 'linux', 'mac', 'win']
-
-  _BATTOR_PARTNO = 'x192a3u'
-  _BATTOR_PROGRAMMER = 'avr109'
-  _BATTOR_BAUDRATE = '115200'
-
-  def __init__(self, target_platform, android_device=None, battor_path=None,
-               battor_map_file=None, battor_map=None, serial_log_bucket=None,
-               autoflash=True):
-    """Constructor.
-
-    Args:
-      target_platform: Platform BattOr is attached to.
-      android_device: Serial number of Android device.
-      battor_path: Path to BattOr device.
-      battor_map_file: File giving map of [device serial: BattOr path]
-      battor_map: Map of [device serial: BattOr path]
-      serial_log_bucket: The cloud storage bucket to which BattOr agent serial
-        logs are uploaded on failure.
-
-    Attributes:
-      _battor_path: Path to BattOr. Typically similar to /tty/USB0.
-      _battor_agent_binary: Path to the BattOr agent binary used to communicate
-        with the BattOr.
-      _tracing: A bool saying if tracing has been started.
-      _battor_shell: A subprocess running the battor_agent_binary
-      _trace_results_path: Path to BattOr trace results file.
-      _serial_log_bucket: Cloud storage bucket to which BattOr agent serial logs
-        are uploaded on failure.
-      _serial_log_file: Temp file for the BattOr agent serial log.
-    """
-    self._battor_path = self._GetBattOrPath(target_platform, android_device,
-        battor_path, battor_map_file, battor_map)
-    config = os.path.join(
-        os.path.dirname(os.path.abspath(__file__)),
-        'battor_binary_dependencies.json')
-
-    self._dm = dependency_manager.DependencyManager(
-        [dependency_manager.BaseConfig(config)])
-    self._battor_agent_binary = self._dm.FetchPath(
-        'battor_agent_binary',
-        '%s_%s' % (py_utils.GetHostOsName(), py_utils.GetHostArchName()))
-
-    self._autoflash = autoflash
-    self._serial_log_bucket = serial_log_bucket
-    self._tracing = False
-    self._battor_shell = None
-    self._trace_results_path = None
-    self._start_tracing_time = None
-    self._stop_tracing_time = None
-    self._trace_results = None
-    self._serial_log_file = None
-    self._target_platform = target_platform
-    self._git_hash = None
-
-    atexit_with_log.Register(self.KillBattOrShell)
-
-  def _FlashBattOr(self):
-    assert self._battor_shell, (
-        'Must start shell before attempting to flash BattOr')
-
-    try:
-      device_git_hash = self.GetFirmwareGitHash()
-      battor_firmware, cs_git_hash = self._dm.FetchPathWithVersion(
-          'battor_firmware', 'default')
-      if cs_git_hash != device_git_hash:
-        logging.info(
-            'Flashing BattOr with old firmware version <%s> with new '
-            'version <%s>.', device_git_hash, cs_git_hash)
-        avrdude_config = self._dm.FetchPath('avrdude_config', 'default')
-        self.StopShell()
-        return self.FlashFirmware(battor_firmware, avrdude_config)
-      return False
-    except ValueError:
-      logging.exception('Git hash returned from BattOr was not as expected: %s'
-                        % self._git_hash)
-      self.StopShell()
-
-    finally:
-      if not self._battor_shell:
-        # TODO(charliea): Once we understand why BattOrs are crashing, remove
-        # this log.
-        # http://crbug.com/699581
-        logging.info('_FlashBattOr serial log:')
-        self._UploadSerialLogToCloudStorage()
-        self._serial_log_file = None
-
-        self.StartShell()
-
-  def KillBattOrShell(self):
-    if self._battor_shell:
-      logging.critical('BattOr shell was not properly closed. Killing now.')
-      self._battor_shell.kill()
-
-  def GetShellReturnCode(self):
-    """Gets the return code of the BattOr agent shell."""
-    rc = self._battor_shell.poll()
-    return rc
-
-  def StartShell(self):
-    """Start BattOr binary shell."""
-    assert not self._battor_shell, 'Attempting to start running BattOr shell.'
-
-    battor_cmd = [self._battor_agent_binary]
-    if self._serial_log_bucket:
-      # Create and immediately close a temp file in order to get a filename
-      # for the serial log.
-      self._serial_log_file = tempfile.NamedTemporaryFile(delete=False)
-      self._serial_log_file.close()
-      battor_cmd.append('--battor-serial-log=%s' % self._serial_log_file.name)
-    if self._battor_path:
-      battor_cmd.append('--battor-path=%s' % self._battor_path)
-    self._battor_shell = self._StartShellImpl(battor_cmd)
-    assert self.GetShellReturnCode() is None, 'Shell failed to start.'
-
-  def StopShell(self, timeout=None):
-    """Stop BattOr binary shell."""
-    assert self._battor_shell, 'Attempting to stop a non-running BattOr shell.'
-    assert not self._tracing, 'Attempting to stop a BattOr shell while tracing.'
-    timeout = timeout if timeout else DEFAULT_SHELL_CLOSE_TIMEOUT_S
-
-    try:
-      self._SendBattOrCommand(self._EXIT_CMD, check_return=False)
-      py_utils.WaitFor(lambda: self.GetShellReturnCode() != None, timeout)
-    except:
-      # If graceful shutdown failed, resort to a simple kill command.
-      self.KillBattOrShell()
-    finally:
-      self._battor_shell = None
-
-  def StartTracing(self):
-    """Start tracing on the BattOr."""
-    assert self._battor_shell, 'Must start shell before tracing'
-    assert not self._tracing, 'Tracing already started.'
-    self._FlashBattOr()
-    self._SendBattOrCommand(self._START_TRACING_CMD)
-    self._tracing = True
-    self._start_tracing_time = int(time.time())
-
-  def StopTracing(self):
-    """Stop tracing on the BattOr."""
-    assert self._tracing, 'Must run StartTracing before StopTracing'
-    # Create temp file to reserve location for saving results.
-    temp_file = tempfile.NamedTemporaryFile(delete=False)
-    self._trace_results_path = temp_file.name
-    temp_file.close()
-    self._SendBattOrCommand(
-        '%s %s' % (self._STOP_TRACING_CMD, self._trace_results_path))
-    self._tracing = False
-    self._stop_tracing_time = int(time.time())
-
-  def CollectTraceData(self, timeout=None):
-    """Collect trace data from battor.
-    Args:
-      timeout: timeout for waiting on the BattOr process to terminate in
-        seconds.
-    Returns: Trace data in form of a list.
-    """
-    if not self._stop_tracing_time or not self._start_tracing_time:
-      raise battor_error.BattOrError(
-          'No start or stop time detected when collecting BattOr trace.\n'
-          'Start: %s \n Stop: %s' % (self._start_tracing_time,
-                                     self._stop_tracing_time))
-
-    # The BattOr shell terminates after returning the results.
-    if timeout is None:
-      timeout = self._stop_tracing_time - self._start_tracing_time
-    py_utils.WaitFor(lambda: self.GetShellReturnCode() != None, timeout)
-
-    # TODO(charliea): Once we understand why BattOrs are crashing, only do
-    # this on failure.
-    # http://crbug.com/699581
-    logging.info('CollectTraceData serial log:')
-    self._UploadSerialLogToCloudStorage()
-
-    with open(self._trace_results_path) as results:
-      self._trace_results = results.read()
-    self._battor_shell = None
-    self._serial_log_file = None
-    return self._trace_results
-
-  def SupportsExplicitClockSync(self):
-    """Returns if BattOr supports Clock Sync events."""
-    return bool(int(self._SendBattOrCommand(self._SUPPORTS_CLOCKSYNC_CMD,
-                                            check_return=False)))
-
-  def RecordClockSyncMarker(self, sync_id):
-    """Record clock sync event on BattOr."""
-    if not isinstance(sync_id, basestring):
-      raise TypeError('sync_id must be a string.')
-    self._SendBattOrCommand('%s %s' % (self._RECORD_CLOCKSYNC_CMD, sync_id))
-
-  def _GetBattOrPath(self, target_platform, android_device=None,
-                     battor_path=None, battor_map_file=None, battor_map=None):
-    """Determines most likely path to the correct BattOr."""
-    if target_platform not in self._SUPPORTED_PLATFORMS:
-      raise battor_error.BattOrError(
-          '%s is an unsupported platform.' % target_platform)
-    if target_platform in ['win']:
-      # Right now, the BattOr agent binary isn't able to automatically detect
-      # the BattOr port on Windows. To get around this, we know that the BattOr
-      # shows up with a name of 'USB Serial Port', so use the COM port that
-      # corresponds to a device with that name.
-      for (port, desc, _) in serial.tools.list_ports.comports():
-        if 'USB Serial Port' in desc:
-          return port
-      raise battor_error.BattOrError(
-          'Could not find BattOr attached to machine.')
-    if target_platform in ['mac']:
-      for (port, desc, _) in serial.tools.list_ports.comports():
-        if 'BattOr' in desc:
-          return port
-
-    if target_platform in ['android', 'linux']:
-      if battor_path:
-        if not isinstance(battor_path, basestring):
-          raise battor_error.BattOrError(
-              'An invalid BattOr path was specified.')
-        return battor_path
-
-      if target_platform == 'android':
-        if not android_device:
-          raise battor_error.BattOrError(
-              'Must specify device for Android platform.')
-        if not battor_map_file and not battor_map:
-          # No map was passed, so must create one.
-          battor_map = battor_device_mapping.GenerateSerialMap()
-
-        return battor_device_mapping.GetBattOrPathFromPhoneSerial(
-            str(android_device), serial_map_file=battor_map_file,
-            serial_map=battor_map)
-
-      # Not Android and no explicitly passed BattOr.
-      device_tree = find_usb_devices.GetBusNumberToDeviceTreeMap(fast=True)
-      battors = battor_device_mapping.GetBattOrList(device_tree)
-      if len(battors) != 1:
-        raise battor_error.BattOrError(
-            'For non-Android platforms, exactly one BattOr must be '
-            'attached unless address is explicitly given.')
-      return '/dev/%s' % battors.pop()
-
-    raise NotImplementedError(
-        'BattOr Wrapper not implemented for given platform')
-
-  def _SendBattOrCommandImpl(self, cmd):
-    """Sends command to the BattOr."""
-    self._battor_shell.stdin.write('%s\n' % cmd)
-    self._battor_shell.stdin.flush()
-    return self._battor_shell.stdout.readline()
-
-  def _SendBattOrCommand(self, cmd, check_return=True):
-    status = self._SendBattOrCommandImpl(cmd)
-
-    if check_return and not 'Done.' in status:
-      self.KillBattOrShell()
-      self._UploadSerialLogToCloudStorage()
-      self._serial_log_file = None
-      raise battor_error.BattOrError(
-          'BattOr did not complete command \'%s\' correctly.\n'
-          'Outputted: %s' % (cmd, status))
-    return status
-
-  def _StartShellImpl(self, battor_cmd):
-    return subprocess.Popen(
-        battor_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
-        stderr=subprocess.STDOUT, shell=False)
-
-  def _UploadSerialLogToCloudStorage(self):
-    """Uploads the BattOr serial log to cloud storage."""
-    if not self._serial_log_file or not cloud_storage.IsNetworkIOEnabled():
-      return
-
-    remote_path = ('battor-serial-log-%s-%d.txt' % (
-        datetime.datetime.now().strftime('%Y-%m-%d_%H-%M.txt'),
-        random.randint(1, 100000)))
-
-    try:
-      cloud_url = cloud_storage.Insert(
-          self._serial_log_bucket, remote_path, self._serial_log_file.name)
-      sys.stderr.write('View BattOr serial log at %s\n' % cloud_url)
-    except cloud_storage.PermissionError as e:
-      logging.error('Cannot upload BattOr serial log file to cloud storage due '
-                    'to permission error: %s' % e.message)
-
-  def GetFirmwareGitHash(self):
-    """Gets the git hash for the BattOr firmware.
-
-    Returns: Git hash for firmware currently on the BattOr.
-        Also sets self._git_hash to this value.
-
-    Raises: ValueException if the git hash is not in hex.
-    """
-    assert self._battor_shell, ('Must start shell before getting firmware git '
-                                'hash')
-    self._git_hash = self._SendBattOrCommand(self._GET_FIRMWARE_GIT_HASH_CMD,
-                                       check_return=False).strip()
-    # We expect the git hash to be a valid 6 character hexstring. This will
-    # throw a ValueError exception otherwise.
-    int(self._git_hash, 16)
-    return self._git_hash
-
-  def FlashFirmware(self, hex_path, avrdude_config_path):
-    """Flashes the BattOr using an avrdude config at config_path with the new
-       firmware at hex_path.
-    """
-    assert not self._battor_shell, 'Cannot flash BattOr with open shell'
-
-    avrdude_binary = self._dm.FetchPath(
-        'avrdude_binary', '%s_%s' % (sys.platform, platform.machine()))
-    # Sanitize hex file path for windows. It contains <drive>:/ which avrdude
-    # is not capable of handling.
-    _, hex_path = os.path.splitdrive(hex_path)
-    avr_cmd = [
-        avrdude_binary,
-        '-e',  # Specify to erase data on chip.
-        '-p', self._BATTOR_PARTNO,  # Specify AVR device.
-        # Specify which microcontroller programmer to use.
-        '-c', self._BATTOR_PROGRAMMER,
-        '-b', self._BATTOR_BAUDRATE,  # Specify the baud rate to communicate at.
-        '-P', self._battor_path,  # Serial path to the battor.
-        # Command to execute with hex file and path to hex file.
-        '-U', 'flash:w:%s' % hex_path,
-        '-C', avrdude_config_path, # AVRdude config file path.
-        '2>&1'  # All output goes to stderr for some reason.
-    ]
-    try:
-      subprocess.check_output(avr_cmd)
-    except subprocess.CalledProcessError as e:
-      raise BattOrFlashError('BattOr flash failed with return code %s.'
-                             % e.returncode)
-
-    self._git_hash = None
-    return True
-
-
-class BattOrFlashError(Exception):
-  pass
diff --git a/catapult/common/battor/battor/battor_wrapper_devicetest.py b/catapult/common/battor/battor/battor_wrapper_devicetest.py
deleted file mode 100644
index 29c36e7..0000000
--- a/catapult/common/battor/battor/battor_wrapper_devicetest.py
+++ /dev/null
@@ -1,103 +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.
-
-import logging
-import platform
-import os
-import sys
-import time
-import unittest
-
-if __name__ == '__main__':
-  sys.path.append(
-      os.path.join(os.path.dirname(__file__), '..'))
-
-from battor import battor_wrapper
-from devil.utils import battor_device_mapping
-from devil.utils import find_usb_devices
-import py_utils
-from py_utils import cloud_storage
-
-
-_SUPPORTED_CQ_PLATFORMS = ['win', 'linux', 'mac']
-
-class BattOrWrapperDeviceTest(unittest.TestCase):
-  def setUp(self):
-    self._platform = py_utils.GetHostOsName()
-    self._battor_list = None
-
-    if self._platform == 'linux':
-      device_tree  = find_usb_devices.GetBusNumberToDeviceTreeMap()
-      self._battor_list = battor_device_mapping.GetBattOrList(device_tree)
-
-    if not battor_wrapper.IsBattOrConnected(self._platform):
-      self._battor_list = []
-
-  def testFullRun(self):
-    # If battor_list is an empty list, a BattOr was expected but not found.
-    if self._battor_list is not None and not self._battor_list:
-      logging.critical('No BattOrs attached. Cannot run tests.')
-      return
-
-    if self._platform not in _SUPPORTED_CQ_PLATFORMS:
-      logging.critical('Platform %s is not supported on CQ.' % self._platform)
-      return
-
-
-    battor_path = (None if not self._battor_list
-                   else '/dev/%s' % self._battor_list[0])
-    battor = battor_wrapper.BattOrWrapper(
-        self._platform, battor_path=battor_path,
-        serial_log_bucket=cloud_storage.TELEMETRY_OUTPUT)
-    try:
-      battor.StartShell()
-      self.assertTrue(isinstance(battor.GetFirmwareGitHash(), basestring))
-      # We expect the git hash to be a valid 6 character hexstring. This will
-      # throw a ValueError exception otherwise.
-      int(battor.GetFirmwareGitHash(), 16)
-      self.assertTrue(len(battor.GetFirmwareGitHash()) == 7)
-      battor.StopShell()
-
-      battor.StartShell()
-      battor.StartTracing()
-      # TODO(rnephew): This sleep is required for now because crbug.com/602266
-      # causes the BattOr to crash when the trace time is too short. Once that
-      # bug is fixed, we should remove this delay.
-      time.sleep(1)
-      battor.RecordClockSyncMarker('abc')
-      # Sleep here because clock sync marker will be flaky if not.
-      time.sleep(1)
-      battor.StopTracing()
-
-      # Below is a work around for crbug.com/603309. On this short of a trace, 5
-      # seconds is enough to ensure that the trace will finish flushing to the
-      # file. The process is then killed so that BattOrWrapper knows that the
-      # process has been closed after tracing stops.
-      if self._platform == 'win':
-        time.sleep(5)
-        battor._battor_shell.kill()
-      results = battor.CollectTraceData().splitlines()
-    except:
-      if battor._battor_shell is not None:
-        battor._battor_shell.kill()
-        battor._battor_shell = None
-      raise
-
-    self.assertTrue('# BattOr' in results[0])
-    self.assertTrue('# voltage_range' in results[1])
-    self.assertTrue('# current_range' in results[2])
-    self.assertTrue('# sample_rate' in results[3])
-    # First line with results. Should be 3 'words'.
-    self.assertTrue(len(results[4].split()) == 3)
-    clock_sync_found = False
-    for entry in results:
-      if '<abc>' in entry:
-        clock_sync_found = True
-        break
-    self.assertTrue(clock_sync_found, 'BattOr Data:%s\n' % repr(results))
-
-
-if __name__ == '__main__':
-  logging.getLogger().setLevel(logging.DEBUG)
-  unittest.main(verbosity=2)
diff --git a/catapult/common/battor/battor/battor_wrapper_unittest.py b/catapult/common/battor/battor/battor_wrapper_unittest.py
deleted file mode 100644
index 7e14c34..0000000
--- a/catapult/common/battor/battor/battor_wrapper_unittest.py
+++ /dev/null
@@ -1,387 +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.
-
-import dependency_manager
-import logging
-import mock
-import subprocess
-import unittest
-
-from battor import battor_error
-from battor import battor_wrapper
-from devil.utils import battor_device_mapping
-from devil.utils import find_usb_devices
-
-import serial
-from serial.tools import list_ports
-
-
-class DependencyManagerMock(object):
-  def __init__(self, _):
-    self._fetch_return = 'path'
-    self._version_return = 'cbaa843'
-
-  def FetchPath(self, _, *unused):
-    del unused
-    return self._fetch_return
-
-  def FetchPathWithVersion(self, _, *unused):
-    del unused
-    return self._fetch_return, self._version_return
-
-class PopenMock(object):
-  def __init__(self, *unused):
-    pass
-
-  def poll(self):
-    pass
-
-  def kill(self):
-    pass
-
-
-class IsBattOrConnectedTest(unittest.TestCase):
-  def setUp(self):
-    # Windows monkey patches.
-    self._serial_tools_return = []
-    self._comports = serial.tools.list_ports.comports
-    serial.tools.list_ports.comports = lambda: self._serial_tools_return
-
-    # Linux/Android monkey patches.
-    self._generate_serial_map_return = {}
-    self._generate_serial_map = battor_device_mapping.GenerateSerialMap
-    battor_device_mapping.GenerateSerialMap = (
-        lambda: self._generate_serial_map_return)
-
-    self._read_serial_map_file_return = {}
-    self._read_serial_map_file = battor_device_mapping.ReadSerialMapFile
-    battor_device_mapping.ReadSerialMapFile = (
-        lambda f: self._read_serial_map_file_return)
-
-    self._get_bus_number_to_device_tree_map = (
-        find_usb_devices.GetBusNumberToDeviceTreeMap)
-    find_usb_devices.GetBusNumberToDeviceTreeMap = lambda fast=None: {}
-
-    self._get_battor_list_return = []
-    self._get_battor_list = battor_device_mapping.GetBattOrList
-    battor_device_mapping.GetBattOrList = lambda x: self._get_battor_list_return
-
-  def tearDown(self):
-    serial.tools.list_ports.comports = self._comports
-    battor_device_mapping.GenerateSerialMap = self._generate_serial_map
-    battor_device_mapping.ReadSerialMapFile = self._read_serial_map_file
-    find_usb_devices.GetBusNumberToDeviceTreeMap = (
-        self._get_bus_number_to_device_tree_map)
-    battor_device_mapping.GetBattOrList = self._get_battor_list
-
-  def forceException(self):
-    raise NotImplementedError
-
-  def testAndroidWithBattOr(self):
-    self._generate_serial_map_return = {'abc': '123'}
-    self.assertTrue(battor_wrapper.IsBattOrConnected('android', 'abc'))
-
-  def testAndroidWithoutMatchingBattOr(self):
-    self._generate_serial_map_return = {'notabc': 'not123'}
-    self.assertFalse(battor_wrapper.IsBattOrConnected('android', 'abc'))
-
-  def testAndroidNoDevicePassed(self):
-    with self.assertRaises(ValueError):
-      battor_wrapper.IsBattOrConnected('android')
-
-  def testAndroidWithMapAndFile(self):
-    device_map = {'abc': '123'}
-    battor_device_mapping.ReadSerialMapFile = self.forceException
-    self.assertTrue(
-        battor_wrapper.IsBattOrConnected('android', android_device='abc',
-                                        android_device_map=device_map,
-                                        android_device_file='file'))
-
-  def testAndroidWithMap(self):
-    self.assertTrue(
-        battor_wrapper.IsBattOrConnected('android', android_device='abc',
-                                        android_device_map={'abc', '123'}))
-
-  def testAndroidWithFile(self):
-    self._read_serial_map_file_return = {'abc': '123'}
-    self.assertTrue(
-      battor_wrapper.IsBattOrConnected('android', android_device='abc',
-                                      android_device_file='file'))
-
-  def testLinuxWithBattOr(self):
-    self._get_battor_list_return = ['battor']
-    self.assertTrue(battor_wrapper.IsBattOrConnected('linux'))
-
-  def testLinuxWithoutBattOr(self):
-    self._get_battor_list_return = []
-    self.assertFalse(battor_wrapper.IsBattOrConnected('linux'))
-
-  def testMacWithBattOr(self):
-    self._serial_tools_return = [('/dev/tty.usbserial-MAA', 'BattOr v3.3', '')]
-    self.assertTrue(battor_wrapper.IsBattOrConnected('mac'))
-
-  def testMacWithoutBattOr(self):
-    self._serial_tools_return = [('/dev/tty.usbserial-MAA', 'not_one', '')]
-    self.assertFalse(battor_wrapper.IsBattOrConnected('mac'))
-
-  def testWinWithBattOr(self):
-    self._serial_tools_return = [('COM4', 'USB Serial Port', '')]
-    self.assertTrue(battor_wrapper.IsBattOrConnected('win'))
-
-  def testWinWithoutBattOr(self):
-    self._get_battor_list_return = []
-    self.assertFalse(battor_wrapper.IsBattOrConnected('win'))
-
-
-class BattOrWrapperTest(unittest.TestCase):
-  def setUp(self):
-    self._battor = None
-    self._is_battor = True
-    self._battor_list = ['battor1']
-    self._should_pass = True
-    self._fake_map = {'battor1': 'device1'}
-    self._fake_return_code = None
-    self._fake_battor_return = 'Done.\n'
-
-    self._get_battor_path_from_phone_serial = (
-        battor_device_mapping.GetBattOrPathFromPhoneSerial)
-    self._get_bus_number_to_device_tree_map = (
-        find_usb_devices.GetBusNumberToDeviceTreeMap)
-    self._dependency_manager = dependency_manager.DependencyManager
-    self._get_battor_list = battor_device_mapping.GetBattOrList
-    self._is_battor = battor_device_mapping.IsBattOr
-    self._generate_serial_map = battor_device_mapping.GenerateSerialMap
-    self._serial_tools = serial.tools.list_ports.comports
-
-    battor_device_mapping.GetBattOrPathFromPhoneSerial = (
-        lambda x, serial_map_file=None, serial_map=None: x + '_battor')
-    find_usb_devices.GetBusNumberToDeviceTreeMap = lambda fast=False: True
-    dependency_manager.DependencyManager = DependencyManagerMock
-    battor_device_mapping.GetBattOrList = lambda x: self._battor_list
-    battor_device_mapping.IsBattOr = lambda x, y: self._is_battor
-    battor_device_mapping.GenerateSerialMap = lambda: self._fake_map
-    serial.tools.list_ports.comports = lambda: [('COM4', 'USB Serial Port', '')]
-
-    self._subprocess_check_output_code = 0
-    def subprocess_check_output_mock(*unused):
-      if self._subprocess_check_output_code != 0:
-        raise subprocess.CalledProcessError(None, None)
-      return 0
-    self._subprocess_check_output = subprocess.check_output
-    subprocess.check_output = subprocess_check_output_mock
-
-  def tearDown(self):
-    battor_device_mapping.GetBattOrPathFromPhoneSerial = (
-        self._get_battor_path_from_phone_serial)
-    find_usb_devices.GetBusNumberToDeviceTreeMap = (
-        self._get_bus_number_to_device_tree_map)
-    dependency_manager.DependencyManager = self._dependency_manager
-    battor_device_mapping.GetBattOrList = self._get_battor_list
-    battor_device_mapping.IsBattOr = self._is_battor
-    battor_device_mapping.GenerateSerialMap = self._generate_serial_map
-    serial.tools.list_ports.comports = self._serial_tools
-    subprocess.check_output = self._subprocess_check_output
-
-  def _DefaultBattOrReplacements(self):
-    battor_wrapper.DEFAULT_SHELL_CLOSE_TIMEOUT_S = .1
-    self._battor._StartShellImpl = lambda *unused: PopenMock()
-    self._battor.GetShellReturnCode = lambda *unused: self._fake_return_code
-    self._battor._SendBattOrCommandImpl = lambda x: self._fake_battor_return
-    self._battor._StopTracingImpl = lambda *unused: (self._fake_battor_return,
-                                                     None)
-
-  def testBadPlatform(self):
-    with self.assertRaises(battor_error.BattOrError):
-      self._battor = battor_wrapper.BattOrWrapper('unknown')
-
-  def testInitAndroidWithBattOr(self):
-    self._battor = battor_wrapper.BattOrWrapper('android', android_device='abc')
-    self.assertEquals(self._battor._battor_path, 'abc_battor')
-
-  def testInitAndroidWithoutBattOr(self):
-    self._battor_list = []
-    self._fake_map = {}
-    battor_device_mapping.GetBattOrPathFromPhoneSerial = (
-        self._get_battor_path_from_phone_serial)
-    with self.assertRaises(battor_error.BattOrError):
-      self._battor = battor_wrapper.BattOrWrapper('android',
-                                                  android_device='abc')
-
-  def testInitBattOrPathIsBattOr(self):
-    battor_path = 'battor/path/here'
-    self._battor = battor_wrapper.BattOrWrapper(
-        'android', android_device='abc', battor_path=battor_path)
-    self.assertEquals(self._battor._battor_path, battor_path)
-
-  def testInitNonAndroidWithBattOr(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self.assertEquals(self._battor._battor_path, 'COM4')
-
-  def testInitNonAndroidWithMultipleBattOr(self):
-    self._battor_list.append('battor2')
-    with self.assertRaises(battor_error.BattOrError):
-      self._battor = battor_wrapper.BattOrWrapper('linux')
-
-  def testInitNonAndroidWithoutBattOr(self):
-    self._battor_list = []
-    serial.tools.list_ports.comports = lambda: [('COM4', 'None', '')]
-    with self.assertRaises(battor_error.BattOrError):
-      self._battor = battor_wrapper.BattOrWrapper('win')
-
-  def testStartShellPass(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self._DefaultBattOrReplacements()
-    self._battor.StartShell()
-    self.assertIsNotNone(self._battor._battor_shell)
-
-  def testStartShellDoubleStart(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self._DefaultBattOrReplacements()
-    self._battor.StartShell()
-    with self.assertRaises(AssertionError):
-      self._battor.StartShell()
-
-  def testStartShellFail(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self._DefaultBattOrReplacements()
-    self._battor.GetShellReturnCode = lambda *unused: 1
-    with self.assertRaises(AssertionError):
-      self._battor.StartShell()
-
-  def testStartTracingPass(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self._DefaultBattOrReplacements()
-    self._battor.StartShell()
-    self._battor.StartTracing()
-    self.assertTrue(self._battor._tracing)
-
-  def testStartTracingDoubleStart(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self._DefaultBattOrReplacements()
-    self._battor.StartShell()
-    self._battor.StartTracing()
-    with self.assertRaises(AssertionError):
-      self._battor.StartTracing()
-
-  def testStartTracingCommandFails(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self._DefaultBattOrReplacements()
-    self._battor._SendBattOrCommandImpl = lambda *unused: 'Fail.\n'
-    self._battor.StartShell()
-    with self.assertRaises(battor_error.BattOrError):
-      self._battor.StartTracing()
-
-  def testStopTracingPass(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self._DefaultBattOrReplacements()
-    self._battor.StartShell()
-    self._battor.StartTracing()
-    self._battor.GetShellReturnCode = lambda *unused: 0
-    self._battor.StopTracing()
-    self.assertFalse(self._battor._tracing)
-
-  def testStopTracingNotRunning(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self._DefaultBattOrReplacements()
-    with self.assertRaises(AssertionError):
-      self._battor.StopTracing()
-
-  def testFlashFirmwarePass(self):
-    self._battor = battor_wrapper.BattOrWrapper('linux')
-    self._DefaultBattOrReplacements()
-    self.assertTrue(self._battor.FlashFirmware('hex_path', 'config_path'))
-
-  def testFlashFirmwareFail(self):
-    self._battor = battor_wrapper.BattOrWrapper('linux')
-    self._DefaultBattOrReplacements()
-    self._subprocess_check_output_code = 1
-    with self.assertRaises(battor_wrapper.BattOrFlashError):
-      self._battor.FlashFirmware('hex_path', 'config_path')
-
-  def testFlashFirmwareShellRunning(self):
-    self._battor = battor_wrapper.BattOrWrapper('linux')
-    self._DefaultBattOrReplacements()
-    self._battor.StartShell()
-    with self.assertRaises(AssertionError):
-      self._battor.FlashFirmware('hex_path', 'config_path')
-
-  def testGetFirmwareGitHashNotRunning(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self._DefaultBattOrReplacements()
-    with self.assertRaises(AssertionError):
-      self._battor.GetFirmwareGitHash()
-
-  def testGetFirmwareGitHashPass(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self._DefaultBattOrReplacements()
-    self._battor.StartShell()
-    self._battor.GetFirmwareGitHash = lambda: 'cbaa843'
-    self.assertTrue(isinstance(self._battor.GetFirmwareGitHash(), basestring))
-
-  def testStopShellPass(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self._DefaultBattOrReplacements()
-    self._battor.StartShell()
-    self._fake_return_code = 0
-    self._battor.StopShell()
-    self.assertIsNone(self._battor._battor_shell)
-
-  @mock.patch('time.sleep', mock.Mock)
-  def testStopShellTimeOutAndKill(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self._DefaultBattOrReplacements()
-    self._battor.StartShell()
-    self._battor.StopShell()
-    self.assertIsNone(self._battor._battor_shell)
-
-  def testStopShellNotStarted(self):
-    self._battor = battor_wrapper.BattOrWrapper('win')
-    self._DefaultBattOrReplacements()
-    with self.assertRaises(AssertionError):
-      self._battor.StopShell()
-
-  @mock.patch('time.sleep', mock.Mock)
-  def testFlashBattOrSameGitHash(self):
-    self._battor = battor_wrapper.BattOrWrapper('linux')
-    self._DefaultBattOrReplacements()
-    self._battor.StartShell()
-    self._battor.GetFirmwareGitHash = lambda: 'cbaa843'
-    dependency_manager.DependencyManager._version_return = 'cbaa843'
-    self.assertFalse(self._battor._FlashBattOr())
-
-  @mock.patch('time.sleep', mock.Mock)
-  def testFlashBattOrDifferentGitHash(self):
-    self._battor = battor_wrapper.BattOrWrapper('linux')
-    self._DefaultBattOrReplacements()
-    self._battor.StartShell()
-    self._battor.GetFirmwareGitHash = lambda: 'bazz732'
-    dependency_manager.DependencyManager._version_return = 'cbaa843'
-    self.assertTrue(self._battor._FlashBattOr())
-
-  def testCollectTraceDataNoStartTime(self):
-    self._battor = battor_wrapper.BattOrWrapper('linux')
-    self._DefaultBattOrReplacements()
-    self._battor.StartShell()
-    self._battor.StartTracing()
-    self._battor.GetShellReturnCode = lambda *unused: 0
-    self._battor.StopTracing()
-    self._battor._start_tracing_time = None
-    with self.assertRaises(battor_error.BattOrError):
-      self._battor.CollectTraceData()
-
-  def testCollectTraceDataNoStopTime(self):
-    self._battor = battor_wrapper.BattOrWrapper('linux')
-    self._DefaultBattOrReplacements()
-    self._battor.StartShell()
-    self._battor.StartTracing()
-    self._battor.GetShellReturnCode = lambda *unused: 0
-    self._battor.StopTracing()
-    self._battor._stop_tracing_time = None
-    with self.assertRaises(battor_error.BattOrError):
-      self._battor.CollectTraceData()
-
-
-if __name__ == '__main__':
-  logging.getLogger().setLevel(logging.DEBUG)
-  unittest.main(verbosity=2)
diff --git a/catapult/common/battor/bin/run_py_tests b/catapult/common/battor/bin/run_py_tests
deleted file mode 100755
index 61103bd..0000000
--- a/catapult/common/battor/bin/run_py_tests
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env 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.
-
-import os
-import sys
-
-_CATAPULT_PATH = os.path.abspath(os.path.join(
-    os.path.dirname(__file__), '..', '..', '..'))
-_BATTOR_PATH = os.path.abspath(os.path.join(
-    os.path.dirname(__file__), '..'))
-
-sys.path.append(_CATAPULT_PATH)
-from catapult_build import run_with_typ
-
-
-def main():
-  return run_with_typ.Run(top_level_dir=_BATTOR_PATH)
-
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/catapult/common/battor/bin/upload_battor_binaries.py b/catapult/common/battor/bin/upload_battor_binaries.py
deleted file mode 100755
index 8901cfc..0000000
--- a/catapult/common/battor/bin/upload_battor_binaries.py
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/env 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.
-
-import argparse
-import os
-import sys
-
-sys.path.append(
-    os.path.abspath(os.path.join(os.path.dirname(__file__),
-                                 '..', '..', '..', 'dependency_manager')))
-from dependency_manager import base_config # pylint: disable=import-error
-
-
-_SUPPORTED_ARCHS = [
-    'linux2_x86_64', 'darwin_x86_64', 'win_AMD64', 'win32_AMD64', 'win32_x86',
-    'default'
-]
-_DEFAULT_DEP = 'battor_agent_binary'
-_DEFAULT_CONFIG = os.path.join(os.path.dirname(__file__), '..', 'battor',
-                               'battor_binary_dependencies.json')
-
-
-def UploadBinary(arch, path, config, dep):
-  print 'Uploading binary:'
-  print '  arch: %s' % arch
-  print '  path: %s' % path
-  print '  config: %s' % config
-  print '  dep: %s' % dep
-  c = base_config.BaseConfig(config, writable=True)
-  c.AddCloudStorageDependencyUpdateJob(
-      dep, arch, path, version=None, execute_job=True)
-  print 'Upload complete.'
-
-
-def main():
-  parser = argparse.ArgumentParser()
-  parser.add_argument('--arch', '--architecture', required=True,
-                      help='Architecture binary is built for.')
-  parser.add_argument('--path', required=True, help='Path to binary.')
-  parser.add_argument('--config', default=_DEFAULT_CONFIG,
-                      help='Path to dependency manager config')
-  parser.add_argument('--dep', default=_DEFAULT_DEP,
-                      help='Name of dependency to update.')
-  args = parser.parse_args()
-  if args.arch not in _SUPPORTED_ARCHS:
-    print 'Arch must be one of: %s' % _SUPPORTED_ARCHS
-    return 1
-  UploadBinary(args.arch, args.path, args.config, args.dep)
-  return 0
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/catapult/common/bin/update_chrome_reference_binaries b/catapult/common/bin/update_chrome_reference_binaries
index 641fdc4..02070f0 100755
--- a/catapult/common/bin/update_chrome_reference_binaries
+++ b/catapult/common/bin/update_chrome_reference_binaries
@@ -42,11 +42,11 @@
 # Add one to enable updating it. (Must also update _PLATFORM_MAP.)
 _PLATFORMS_TO_UPDATE = ['mac_x86_64', 'win_x86', 'win_AMD64', 'linux_x86_64',
                         'android_k_armeabi-v7a', 'android_l_arm64-v8a',
-                        'android_l_armeabi-v7a']
+                        'android_l_armeabi-v7a', 'android_n_armeabi-v7a']
 
 # Remove a channal name from this list to disable updating it.
 # Add one to enable updating it.
-_CHANNELS_TO_UPDATE = ['stable'] # 'canary', 'dev'
+_CHANNELS_TO_UPDATE = ['stable', 'canary', 'dev']
 
 
 # Omaha is Chrome's autoupdate server. It reports the current versions used
@@ -62,34 +62,39 @@
 #   destination: Name of the folder to download the reference build to.
 UpdateInfo = collections.namedtuple('UpdateInfo',
     'omaha, gs_folder, gs_build, zip_name')
-_PLATFORM_MAP = { 'mac_x86_64': UpdateInfo(omaha='mac',
-                                           gs_folder='desktop-*',
-                                           gs_build='mac64',
-                                           zip_name='chrome-mac.zip'),
-                  'win_x86': UpdateInfo(omaha='win',
-                                        gs_folder='desktop-*',
-                                        gs_build='win',
-                                        zip_name='chrome-win.zip'),
-                  'win_AMD64': UpdateInfo(omaha='win',
+_PLATFORM_MAP = {'mac_x86_64': UpdateInfo(omaha='mac',
                                           gs_folder='desktop-*',
-                                          gs_build='win64',
-                                          zip_name='chrome-win64.zip'),
-                  'linux_x86_64': UpdateInfo(omaha='linux',
-                                             gs_folder='desktop-*',
-                                             gs_build='linux64',
-                                             zip_name='chrome-linux64.zip'),
-                  'android_k_armeabi-v7a': UpdateInfo(omaha='android',
-                                                      gs_folder='android-*',
-                                                      gs_build='arm',
-                                                      zip_name='Chrome.apk'),
-                  'android_l_arm64-v8a': UpdateInfo(omaha='android',
-                                                    gs_folder='android-*',
-                                                    gs_build='arm_64',
-                                                    zip_name='ChromeModern.apk'),
-                  'android_l_armeabi-v7a': UpdateInfo(omaha='android',
-                                                      gs_folder='android-*',
-                                                      gs_build='arm',
-                                                      zip_name='Chrome.apk'),
+                                          gs_build='mac64',
+                                          zip_name='chrome-mac.zip'),
+                 'win_x86': UpdateInfo(omaha='win',
+                                       gs_folder='desktop-*',
+                                       gs_build='win-clang',
+                                       zip_name='chrome-win-clang.zip'),
+                 'win_AMD64': UpdateInfo(omaha='win',
+                                         gs_folder='desktop-*',
+                                         gs_build='win64-clang',
+                                         zip_name='chrome-win64-clang.zip'),
+                 'linux_x86_64': UpdateInfo(omaha='linux',
+                                            gs_folder='desktop-*',
+                                            gs_build='linux64',
+                                            zip_name='chrome-linux64.zip'),
+                 'android_k_armeabi-v7a': UpdateInfo(omaha='android',
+                                                     gs_folder='android-*',
+                                                     gs_build='arm',
+                                                     zip_name='Chrome.apk'),
+                 'android_l_arm64-v8a': UpdateInfo(omaha='android',
+                                                   gs_folder='android-*',
+                                                   gs_build='arm_64',
+                                                   zip_name='ChromeModern.apk'),
+                 'android_l_armeabi-v7a': UpdateInfo(omaha='android',
+                                                     gs_folder='android-*',
+                                                     gs_build='arm',
+                                                     zip_name='Chrome.apk'),
+                 'android_n_armeabi-v7a': UpdateInfo(omaha='android',
+                                                     gs_folder='android-*',
+                                                     gs_build='arm',
+                                                     zip_name='Monochrome.apk'),
+
 }
 
 
@@ -139,8 +144,10 @@
   remote_path = '%s/%s/%s/%s' % (
       platform_info.gs_folder, version, platform_info.gs_build, filename)
   if not cloud_storage.Exists(CHROME_GS_BUCKET, remote_path):
+    cloud_storage_path = 'gs://%s/%s' % (CHROME_GS_BUCKET, remote_path)
     raise BuildNotFoundError(
-        'Failed to find %s build for version %s at path %s.' % (platform, version, remote_path))
+        'Failed to find %s build for version %s at path %s.' % (
+            platform, version, cloud_storage_path))
   reference_builds_folder = os.path.join(
       os.path.dirname(os.path.abspath(__file__)), 'chrome_telemetry_build',
       'reference_builds', channel)
diff --git a/catapult/common/node_runner/node_runner/minify b/catapult/common/node_runner/node_runner/minify
new file mode 100755
index 0000000..9d5bb2f
--- /dev/null
+++ b/catapult/common/node_runner/node_runner/minify
@@ -0,0 +1,58 @@
+#!/usr/bin/env node
+'use strict';
+/*
+Copyright 2018 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 script wraps common HTML transformations including stripping whitespace and
+comments from HTML, CSS, and Javascript.
+*/
+const dom5 = require('dom5');
+const escodegen = require('escodegen');
+const espree = require('espree');
+const fs = require('fs');
+const nopt = require('nopt');
+
+const ESPREE_OPTIONS = {
+  attachComment: false,
+  comments: false,
+  ecmaVersion: 2018,
+};
+
+const args = nopt();
+const filename = args.argv.remain[0];
+
+let html = fs.readFileSync(filename).toString('utf8');
+let parsedHtml = dom5.parse(html);
+// First, collapse text nodes around comments (by removing comment nodes,
+// re-serializing, and re-parsing) in order to prevent multiple extraneous
+// newlines.
+for (const node of dom5.nodeWalkAll(parsedHtml, () => true)) {
+  if (dom5.isCommentNode(node)) {
+    dom5.remove(node);
+  }
+}
+html = dom5.serialize(parsedHtml);
+parsedHtml = dom5.parse(html);
+// Some of these transformations are based on polyclean:
+// https://github.com/googlearchive/polyclean
+for (const node of dom5.nodeWalkAll(parsedHtml, () => true)) {
+  if (dom5.isTextNode(node)) {
+    dom5.setTextContent(node, dom5.getTextContent(node)
+      .replace(/ *\n+ */g, '\n')
+      .replace(/\n+/g, '\n'));
+  } else if (dom5.predicates.hasTagName('script')(node) &&
+             !dom5.predicates.hasAttr('src')(node)) {
+    dom5.setTextContent(node, escodegen.generate(
+      espree.parse(dom5.getTextContent(node), ESPREE_OPTIONS),
+      {format: {indent: {style: ''}}}));
+  } else if (dom5.predicates.hasTagName('style')(node)) {
+    dom5.setTextContent(node, dom5.getTextContent(node)
+      .replace(/[\r\n]/g, '')
+      .replace(/ {2,}/g, ' ')
+      .replace(/(^|[;,\:\{\}]) /g, '$1')
+      .replace(/ ($|[;,\{\}])/g, '$1'));
+  }
+}
+fs.writeFileSync(filename, dom5.serialize(parsedHtml));
diff --git a/catapult/common/node_runner/node_runner/package.json b/catapult/common/node_runner/node_runner/package.json
index 27d0325..9a92270 100644
--- a/catapult/common/node_runner/node_runner/package.json
+++ b/catapult/common/node_runner/node_runner/package.json
@@ -15,8 +15,12 @@
   "gypfile": false,
   "private": true,
   "dependencies": {
-    "eslint": "^3.14.1",
+    "dom5": "^1.0.0",
+    "escodegen": "^1.0.0",
+    "eslint": "^4.0.0",
     "eslint-config-google": "^0.6.0",
-    "eslint-plugin-html": "^2.0.0"
+    "eslint-plugin-html": "^4.0.0",
+    "espree": "^3.0.0",
+    "vulcanize": "^1.16.0"
   }
 }
diff --git a/catapult/common/py_utils/py_utils/__init__.py b/catapult/common/py_utils/py_utils/__init__.py
index fba0897..dcec4ed 100644
--- a/catapult/common/py_utils/py_utils/__init__.py
+++ b/catapult/common/py_utils/py_utils/__init__.py
@@ -74,8 +74,8 @@
 _AddDirToPythonPath(
     os.path.join(GetCatapultDir(), 'third_party', 'pyfakefs'))
 
-from devil.utils import timeout_retry
-from devil.utils import reraiser_thread
+from devil.utils import timeout_retry  # pylint: disable=wrong-import-position
+from devil.utils import reraiser_thread  # pylint: disable=wrong-import-position
 
 
 # Decorator that adds timeout functionality to a function.
diff --git a/catapult/common/py_utils/py_utils/binary_manager.py b/catapult/common/py_utils/py_utils/binary_manager.py
index 8af08cf..2d3ac8a 100644
--- a/catapult/common/py_utils/py_utils/binary_manager.py
+++ b/catapult/common/py_utils/py_utils/binary_manager.py
@@ -13,7 +13,7 @@
   """
 
   def __init__(self, config_files):
-    if not config_files or type(config_files) != list:
+    if not config_files or not isinstance(config_files, list):
       raise ValueError(
           'Must supply a list of config files to the BinaryManager')
     configs = [dependency_manager.BaseConfig(config) for config in config_files]
diff --git a/catapult/common/py_utils/py_utils/chrome_binaries.json b/catapult/common/py_utils/py_utils/chrome_binaries.json
index ce357c7..8a9b6bf 100644
--- a/catapult/common/py_utils/py_utils/chrome_binaries.json
+++ b/catapult/common/py_utils/py_utils/chrome_binaries.json
@@ -6,22 +6,22 @@
       "cloud_storage_bucket": "chrome-telemetry",
       "file_info": {
         "mac_x86_64": {
-          "cloud_storage_hash": "b321a01b2c98fe62b1876655b10436c2226b1b76",
+          "cloud_storage_hash": "6278cf24b700076fd17ae8616fd980d18f33ed5d",
           "download_path": "bin/reference_builds/chrome-mac64.zip",
           "path_within_archive": "chrome-mac/Google Chrome.app/Contents/MacOS/Google Chrome",
-          "version_in_cs": "62.0.3194.0"
+          "version_in_cs": "70.0.3509.0"
         },
         "win_AMD64": {
-          "cloud_storage_hash": "2da1c7861745ab0e8f666f119eeb58c1410710cc",
-          "download_path": "bin\\reference_build\\chrome-win64-pgo.zip",
-          "path_within_archive": "chrome-win64-pgo\\chrome.exe",
-          "version_in_cs": "62.0.3194.0"
+          "cloud_storage_hash": "a78facdb295d2ee36aaf5af89e54b5c5fcd48f7c",
+          "download_path": "bin\\reference_build\\chrome-win64-clang.zip",
+          "path_within_archive": "chrome-win64-clang\\chrome.exe",
+          "version_in_cs": "70.0.3509.0"
         },
         "win_x86": {
-          "cloud_storage_hash": "270abd11621386be612af02b707844cba06c0dbd",
-          "download_path": "bin\\reference_build\\chrome-win32-pgo.zip",
-          "path_within_archive": "chrome-win32-pgo\\chrome.exe",
-          "version_in_cs": "62.0.3194.0"
+          "cloud_storage_hash": "348e8133c5fa687864a3d8eff13ed5be6852e95d",
+          "download_path": "bin\\reference_build\\chrome-win32-clang.zip",
+          "path_within_archive": "chrome-win32-clang\\chrome.exe",
+          "version_in_cs": "70.0.3509.0"
         }
       }
     },
@@ -30,10 +30,10 @@
       "cloud_storage_bucket": "chrome-telemetry",
       "file_info": {
         "linux_x86_64": {
-          "cloud_storage_hash": "2592ec6f8dd56227c3c281e3cccecd6c9ba72cad",
+          "cloud_storage_hash": "1ef8d8ebf114b47aecf11a36c21377376ced3794",
           "download_path": "bin/reference_build/chrome-linux64.zip",
           "path_within_archive": "chrome-linux64/chrome",
-          "version_in_cs": "62.0.3192.0"
+          "version_in_cs": "69.0.3497.23"
         }
       }
     },
@@ -42,45 +42,50 @@
       "cloud_storage_bucket": "chrome-telemetry",
       "file_info": {
         "android_k_armeabi-v7a": {
-          "cloud_storage_hash": "948c776335d3a38d6e8de0dba576e109c6b5724c",
+          "cloud_storage_hash": "5a4cc68b2ef5e6073f9f8f42987155d5fc8a3c48",
           "download_path": "bin/reference_build/android_k_armeabi-v7a/ChromeStable.apk",
-          "version_in_cs": "63.0.3239.111"
+          "version_in_cs": "68.0.3440.85"
         },
         "android_l_arm64-v8a": {
-          "cloud_storage_hash": "a25663aad7397002f6dfe44fb97087fdd77df119",
+          "cloud_storage_hash": "42d527ca74e99fb9398826204db09c8740df7fd4",
           "download_path": "bin/reference_build/android_l_arm64-v8a/ChromeStable.apk",
-          "version_in_cs": "63.0.3239.111"
+          "version_in_cs": "68.0.3440.85"
         },
         "android_l_armeabi-v7a": {
-          "cloud_storage_hash": "948c776335d3a38d6e8de0dba576e109c6b5724c",
+          "cloud_storage_hash": "5a4cc68b2ef5e6073f9f8f42987155d5fc8a3c48",
           "download_path": "bin/reference_build/android_l_armeabi-v7a/ChromeStable.apk",
-          "version_in_cs": "63.0.3239.111"
+          "version_in_cs": "68.0.3440.85"
+        },
+        "android_n_armeabi-v7a": {
+          "cloud_storage_hash": "d37f47a804e815daf001f65d0c13a2cf38641f3e",
+          "download_path": "bin/reference_build/android_n_armeabi-v7a/Monochrome.apk",
+          "version_in_cs": "68.0.3440.85"
         },
         "linux_x86_64": {
-          "cloud_storage_hash": "b0506e43d268eadb887ccc847695674f9d2e51a5",
+          "cloud_storage_hash": "aab60e4a4ee4f3d638aa6a33e52ffb6423fa7080",
           "download_path": "bin/reference_build/chrome-linux64.zip",
           "path_within_archive": "chrome-linux64/chrome",
-          "version_in_cs": "63.0.3239.108"
+          "version_in_cs": "68.0.3440.84"
         },
         "mac_x86_64": {
-          "cloud_storage_hash": "56a3de45b37b7eb563006c30a548a48928cffb39",
+          "cloud_storage_hash": "8a020bc9caa2526408dc23044e8dcfaaf6b6948e",
           "download_path": "bin/reference_builds/chrome-mac64.zip",
           "path_within_archive": "chrome-mac/Google Chrome.app/Contents/MacOS/Google Chrome",
-          "version_in_cs": "63.0.3239.108"
+          "version_in_cs": "68.0.3440.84"
         },
         "win_AMD64": {
-          "cloud_storage_hash": "d1511334055c88fd9fa5e6e63fee666d9be8c433",
-          "download_path": "bin\\reference_build\\chrome-win64.zip",
-          "path_within_archive": "chrome-win64\\chrome.exe",
-          "version_in_cs": "63.0.3239.108"
+          "cloud_storage_hash": "19da10346662d8e791076a0ddcfbf2a435b6915a",
+          "download_path": "bin\\reference_build\\chrome-win64-clang.zip",
+          "path_within_archive": "chrome-win64-clang\\chrome.exe",
+          "version_in_cs": "68.0.3440.84"
         },
         "win_x86": {
-          "cloud_storage_hash": "9e869b3b25ee7b682712cde6eaddc2d7fa84cc90",
-          "download_path": "bin\\reference_build\\chrome-win32.zip",
-          "path_within_archive": "chrome-win32\\chrome.exe",
-          "version_in_cs": "63.0.3239.108"
+          "cloud_storage_hash": "760ff8661550f6aebadedba99075efe6adae3414",
+          "download_path": "bin\\reference_build\\chrome-win-clang.zip",
+          "path_within_archive": "chrome-win-clang\\chrome.exe",
+          "version_in_cs": "68.0.3440.84"
         }
       }
     }
   }
-}
+}
\ No newline at end of file
diff --git a/catapult/common/py_utils/py_utils/cloud_storage.py b/catapult/common/py_utils/py_utils/cloud_storage.py
index f601380..df5589b 100644
--- a/catapult/common/py_utils/py_utils/cloud_storage.py
+++ b/catapult/common/py_utils/py_utils/cloud_storage.py
@@ -120,6 +120,9 @@
     os.chmod(gsutil, st.st_mode | stat.S_IEXEC)
 
 
+def _IsRunningOnSwarming():
+  return os.environ.get('SWARMING_HEADLESS') is not None
+
 def _RunCommand(args):
   # On cros device, as telemetry is running as root, home will be set to /root/,
   # which is not writable. gsutil will attempt to create a download tracker dir
@@ -132,6 +135,8 @@
   if py_utils.IsRunningOnCrosDevice():
     gsutil_env = os.environ.copy()
     gsutil_env['HOME'] = _CROS_GSUTIL_HOME_WAR
+  elif _IsRunningOnSwarming():
+    gsutil_env = os.environ.copy()
 
   if os.name == 'nt':
     # If Windows, prepend python. Python scripts aren't directly executable.
diff --git a/catapult/common/py_utils/py_utils/cloud_storage_unittest.py b/catapult/common/py_utils/py_utils/cloud_storage_unittest.py
index ae2f748..7648db6 100644
--- a/catapult/common/py_utils/py_utils/cloud_storage_unittest.py
+++ b/catapult/common/py_utils/py_utils/cloud_storage_unittest.py
@@ -154,6 +154,19 @@
     finally:
       cloud_storage._RunCommand = orig_run_command
 
+  @mock.patch('py_utils.cloud_storage.subprocess.Popen')
+  def testSwarmingUsesExistingEnv(self, mock_popen):
+    os.environ['SWARMING_HEADLESS'] = '1'
+
+    mock_gsutil = mock_popen()
+    mock_gsutil.communicate = mock.MagicMock(return_value=('a', 'b'))
+    mock_gsutil.returncode = None
+
+    cloud_storage.Copy('bucket1', 'bucket2', 'remote_path1', 'remote_path2')
+
+    mock_popen.assert_called_with(
+        mock.ANY, stderr=-1, env=os.environ, stdout=-1)
+
   @mock.patch('py_utils.cloud_storage._FileLock')
   def testDisableCloudStorageIo(self, unused_lock_mock):
     os.environ['DISABLE_CLOUD_STORAGE_IO'] = '1'
diff --git a/catapult/common/py_utils/py_utils/file_util.py b/catapult/common/py_utils/py_utils/file_util.py
new file mode 100644
index 0000000..36cc42f
--- /dev/null
+++ b/catapult/common/py_utils/py_utils/file_util.py
@@ -0,0 +1,23 @@
+# Copyright 2018 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 errno
+import os
+import shutil
+
+
+def CopyFileWithIntermediateDirectories(source_path, dest_path):
+  """Copies a file and creates intermediate directories as needed.
+
+  Args:
+    source_path: Path to the source file.
+    dest_path: Path to the destination where the source file should be copied.
+  """
+  assert os.path.exists(source_path)
+  try:
+    os.makedirs(os.path.dirname(dest_path))
+  except OSError, e:
+    if e.errno != errno.EEXIST:
+      raise
+  shutil.copy(source_path, dest_path)
diff --git a/catapult/common/py_utils/py_utils/file_util_unittest.py b/catapult/common/py_utils/py_utils/file_util_unittest.py
new file mode 100644
index 0000000..4bb19a1
--- /dev/null
+++ b/catapult/common/py_utils/py_utils/file_util_unittest.py
@@ -0,0 +1,66 @@
+# Copyright 2018 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 errno
+import os
+import shutil
+import tempfile
+import unittest
+
+from py_utils import file_util
+
+
+class FileUtilTest(unittest.TestCase):
+
+  def setUp(self):
+    self._tempdir = tempfile.mkdtemp()
+
+  def tearDown(self):
+    shutil.rmtree(self._tempdir)
+
+  def testCopySimple(self):
+    source_path = os.path.join(self._tempdir, 'source')
+    with open(source_path, 'w') as f:
+      f.write('data')
+
+    dest_path = os.path.join(self._tempdir, 'dest')
+
+    self.assertFalse(os.path.exists(dest_path))
+    file_util.CopyFileWithIntermediateDirectories(source_path, dest_path)
+    self.assertTrue(os.path.exists(dest_path))
+    self.assertEqual('data', open(dest_path, 'r').read())
+
+  def testCopyMakeDirectories(self):
+    source_path = os.path.join(self._tempdir, 'source')
+    with open(source_path, 'w') as f:
+      f.write('data')
+
+    dest_path = os.path.join(self._tempdir, 'path', 'to', 'dest')
+
+    self.assertFalse(os.path.exists(dest_path))
+    file_util.CopyFileWithIntermediateDirectories(source_path, dest_path)
+    self.assertTrue(os.path.exists(dest_path))
+    self.assertEqual('data', open(dest_path, 'r').read())
+
+  def testCopyOverwrites(self):
+    source_path = os.path.join(self._tempdir, 'source')
+    with open(source_path, 'w') as f:
+      f.write('source_data')
+
+    dest_path = os.path.join(self._tempdir, 'dest')
+    with open(dest_path, 'w') as f:
+      f.write('existing_data')
+
+    file_util.CopyFileWithIntermediateDirectories(source_path, dest_path)
+    self.assertEqual('source_data', open(dest_path, 'r').read())
+
+  def testRaisesError(self):
+    source_path = os.path.join(self._tempdir, 'source')
+    with open(source_path, 'w') as f:
+      f.write('data')
+
+    dest_path = ""
+    with self.assertRaises(OSError) as cm:
+      file_util.CopyFileWithIntermediateDirectories(source_path, dest_path)
+      self.assertEqual(errno.ENOENT, cm.exception.error_code)
diff --git a/catapult/common/py_utils/py_utils/lock.py b/catapult/common/py_utils/py_utils/lock.py
index aa9a095..f655618 100644
--- a/catapult/common/py_utils/py_utils/lock.py
+++ b/catapult/common/py_utils/py_utils/lock.py
@@ -14,19 +14,23 @@
   pass
 
 
+# pylint: disable=import-error
+# pylint: disable=wrong-import-position
 if os.name == 'nt':
-  import win32con    # pylint: disable=import-error
-  import win32file   # pylint: disable=import-error
-  import pywintypes  # pylint: disable=import-error
+  import win32con
+  import win32file
+  import pywintypes
   LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
   LOCK_SH = 0  # the default
   LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
   _OVERLAPPED = pywintypes.OVERLAPPED()
 elif os.name == 'posix':
-  import fcntl       # pylint: disable=import-error
+  import fcntl
   LOCK_EX = fcntl.LOCK_EX
   LOCK_SH = fcntl.LOCK_SH
   LOCK_NB = fcntl.LOCK_NB
+# pylint: enable=import-error
+# pylint: enable=wrong-import-position
 
 
 @contextlib.contextmanager
diff --git a/catapult/common/py_utils/py_utils/memory_debug.py b/catapult/common/py_utils/py_utils/memory_debug.py
index 864725d..e63938f 100755
--- a/catapult/common/py_utils/py_utils/memory_debug.py
+++ b/catapult/common/py_utils/py_utils/memory_debug.py
@@ -6,8 +6,11 @@
 import heapq
 import logging
 import os
-import psutil
 import sys
+try:
+  import psutil
+except ImportError:
+  psutil = None
 
 
 BYTE_UNITS = ['B', 'KiB', 'MiB', 'GiB']
@@ -39,6 +42,9 @@
 
 
 def LogHostMemoryUsage(top_n=10, level=logging.INFO):
+  if not psutil:
+    logging.warning('psutil module is not found, skipping logging memory info')
+    return
   if psutil.version_info < (2, 0):
     logging.warning('psutil %s too old, upgrade to version 2.0 or higher'
                     ' for memory usage information.', psutil.__version__)
@@ -55,7 +61,11 @@
   logging.log(level, 'Memory usage of top %i processes groups', top_n)
   pinfos_by_names = {}
   for p in psutil.process_iter():
-    pinfo = _GetProcessInfo(p)
+    try:
+      pinfo = _GetProcessInfo(p)
+    except psutil.NoSuchProcess:
+      logging.exception('process %s no longer exists', p)
+      continue
     pname = pinfo['name']
     if pname not in pinfos_by_names:
       pinfos_by_names[pname] = {'name': pname, 'total_mem_rss': 0, 'pids': []}
diff --git a/catapult/common/py_utils/py_utils/refactor/annotated_symbol/class_definition.py b/catapult/common/py_utils/py_utils/refactor/annotated_symbol/class_definition.py
index 814958f..a83ac96 100644
--- a/catapult/common/py_utils/py_utils/refactor/annotated_symbol/class_definition.py
+++ b/catapult/common/py_utils/py_utils/refactor/annotated_symbol/class_definition.py
@@ -13,8 +13,6 @@
 
 
 class Class(base_symbol.AnnotatedSymbol):
-  # pylint: disable=abstract-class-not-used
-
   @classmethod
   def Annotate(cls, symbol_type, children):
     if symbol_type != symbol.stmt:
diff --git a/catapult/common/py_utils/py_utils/refactor/annotated_symbol/function_definition.py b/catapult/common/py_utils/py_utils/refactor/annotated_symbol/function_definition.py
index 50a1672..384d3cf 100644
--- a/catapult/common/py_utils/py_utils/refactor/annotated_symbol/function_definition.py
+++ b/catapult/common/py_utils/py_utils/refactor/annotated_symbol/function_definition.py
@@ -13,8 +13,6 @@
 
 
 class Function(base_symbol.AnnotatedSymbol):
-  # pylint: disable=abstract-class-not-used
-
   @classmethod
   def Annotate(cls, symbol_type, children):
     if symbol_type != symbol.stmt:
diff --git a/catapult/common/py_utils/py_utils/refactor/annotated_symbol/import_statement.py b/catapult/common/py_utils/py_utils/refactor/annotated_symbol/import_statement.py
index 5c38c10..94e608c 100644
--- a/catapult/common/py_utils/py_utils/refactor/annotated_symbol/import_statement.py
+++ b/catapult/common/py_utils/py_utils/refactor/annotated_symbol/import_statement.py
@@ -39,6 +39,7 @@
         raise ValueError('%s is a reserved keyword.' % value_part)
 
     # If we have too many children, cut the list down to size.
+    # pylint: disable=attribute-defined-outside-init
     self._children = self._children[:len(value_parts)*2-1]
 
     # Update child nodes.
@@ -82,18 +83,22 @@
       raise ValueError('%s is a reserved keyword.' % value)
 
     if value:
+       # pylint: disable=access-member-before-definition
       if len(self.children) < 3:
         # If we currently have no alias, add one.
+         # pylint: disable=access-member-before-definition
         self.children.append(
             snippet.TokenSnippet.Create(token.NAME, 'as', (0, 1)))
+         # pylint: disable=access-member-before-definition
         self.children.append(
             snippet.TokenSnippet.Create(token.NAME, value, (0, 1)))
       else:
         # We already have an alias. Just update the value.
+        # pylint: disable=access-member-before-definition
         self.children[2].value = value
     else:
       # Removing the alias. Strip the "as foo".
-      self.children = [self.children[0]]
+      self.children = [self.children[0]] # pylint: disable=line-too-long, attribute-defined-outside-init
 
 
 class Import(base_symbol.AnnotatedSymbol):
diff --git a/catapult/common/py_utils/py_utils/refactor/annotated_symbol/reference.py b/catapult/common/py_utils/py_utils/refactor/annotated_symbol/reference.py
index 757c57f..9102c86 100644
--- a/catapult/common/py_utils/py_utils/refactor/annotated_symbol/reference.py
+++ b/catapult/common/py_utils/py_utils/refactor/annotated_symbol/reference.py
@@ -58,6 +58,7 @@
     value_parts = value.split('.')
 
     # If we have too many children, cut the list down to size.
+    # pylint: disable=attribute-defined-outside-init
     self._children = self._children[:len(value_parts)]
 
     # Update child nodes.
diff --git a/catapult/common/py_utils/py_utils/retry_util_unittest.py b/catapult/common/py_utils/py_utils/retry_util_unittest.py
index 151f88e..f24577f 100644
--- a/catapult/common/py_utils/py_utils/retry_util_unittest.py
+++ b/catapult/common/py_utils/py_utils/retry_util_unittest.py
@@ -1,9 +1,10 @@
 # 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.
-import mock
 import unittest
 
+import mock
+
 from py_utils import retry_util
 
 
diff --git a/catapult/dependency_manager/PRESUBMIT.py b/catapult/dependency_manager/PRESUBMIT.py
index a34480c..04039d5 100644
--- a/catapult/dependency_manager/PRESUBMIT.py
+++ b/catapult/dependency_manager/PRESUBMIT.py
@@ -29,4 +29,5 @@
 
       input_api.os_path.join(catapult_dir, 'third_party', 'mock'),
       input_api.os_path.join(catapult_dir, 'third_party', 'pyfakefs'),
+      input_api.os_path.join(catapult_dir, 'third_party', 'zipfile'),
   ]
diff --git a/catapult/dependency_manager/dependency_manager/archive_info.py b/catapult/dependency_manager/dependency_manager/archive_info.py
index ff13f90..ff80b63 100644
--- a/catapult/dependency_manager/dependency_manager/archive_info.py
+++ b/catapult/dependency_manager/dependency_manager/archive_info.py
@@ -48,7 +48,7 @@
       # Remove stale unzip results
       if self._stale_unzip_path_glob:
         for path in glob.glob(self._stale_unzip_path_glob):
-          shutil.rmtree(path)
+          shutil.rmtree(path, ignore_errors=True)
       # TODO(aiolos): Replace UnzipFile with zipfile.extractall once python
       # version 2.7.4 or later can safely be assumed.
       dependency_manager_util.UnzipArchive(
diff --git a/catapult/dependency_manager/dependency_manager/base_config.py b/catapult/dependency_manager/dependency_manager/base_config.py
index c735688..a23d00a 100644
--- a/catapult/dependency_manager/dependency_manager/base_config.py
+++ b/catapult/dependency_manager/dependency_manager/base_config.py
@@ -190,6 +190,26 @@
   def config_path(self):
     return self._config_path
 
+  def AddNewDependency(
+      self, dependency, cloud_storage_base_folder, cloud_storage_bucket):
+    self._ValidateIsConfigWritable()
+    if dependency in self:
+      raise ValueError('Config already contains dependency %s' % dependency)
+    self._config_data[dependency] = {
+        'cloud_storage_base_folder': cloud_storage_base_folder,
+        'cloud_storage_bucket': cloud_storage_bucket,
+        'file_info': {},
+    }
+
+  def SetDownloadPath(self, dependency, platform, download_path):
+    self._ValidateIsConfigWritable()
+    if not dependency in self:
+      raise ValueError('Config does not contain dependency %s' % dependency)
+    platform_dicts = self._config_data[dependency]['file_info']
+    if platform not in platform_dicts:
+      platform_dicts[platform] = {}
+    platform_dicts[platform]['download_path'] = download_path
+
   def AddCloudStorageDependencyUpdateJob(
       self, dependency, platform, dependency_path, version=None,
       execute_job=True):
@@ -293,6 +313,14 @@
     return self._GetPlatformData(
         dependency, platform, data_type='version_in_cs')
 
+  def __contains__(self, dependency):
+    """ Returns whether this config contains |dependency|
+
+    Args:
+      dependency: the string name of dependency
+    """
+    return dependency in self._config_data
+
   def _IsDirty(self):
     with open(self._config_path, 'r') as fstream:
       curr_config_data = json.load(fstream)
diff --git a/catapult/dependency_manager/dependency_manager/base_config_unittest.py b/catapult/dependency_manager/dependency_manager/base_config_unittest.py
index 0dc775b..c10d2a7 100755
--- a/catapult/dependency_manager/dependency_manager/base_config_unittest.py
+++ b/catapult/dependency_manager/dependency_manager/base_config_unittest.py
@@ -1119,6 +1119,48 @@
     self.fs.CreateFile(self.file_path,
                        contents='\n'.join(self.expected_file_lines))
 
+  def testContaining(self):
+    config = dependency_manager.BaseConfig(self.file_path)
+    self.assertTrue('dep1' in config)
+    self.assertTrue('dep2' in config)
+    self.assertFalse('dep3' in config)
+
+  def testAddNewDependencyNotWriteable(self):
+    config = dependency_manager.BaseConfig(self.file_path)
+    with self.assertRaises(dependency_manager.ReadWriteError):
+      config.AddNewDependency('dep4', 'foo', 'bar')
+
+  def testAddNewDependencyWriteableButDependencyAlreadyExists(self):
+    config = dependency_manager.BaseConfig(self.file_path, writable=True)
+    with self.assertRaises(ValueError):
+      config.AddNewDependency('dep2', 'foo', 'bar')
+
+  def testAddNewDependencySuccessfully(self):
+    config = dependency_manager.BaseConfig(self.file_path, writable=True)
+    config.AddNewDependency('dep3', 'foo', 'bar')
+    self.assertTrue('dep3' in config)
+
+  def testSetDownloadPathNotWritable(self):
+    config = dependency_manager.BaseConfig(self.file_path)
+    with self.assertRaises(dependency_manager.ReadWriteError):
+      config.SetDownloadPath('dep2', 'plat1', '../../relative/dep1/path1')
+
+  def testSetDownloadPathOnExistingPlatformSuccesfully(self):
+    config = dependency_manager.BaseConfig(self.file_path, writable=True)
+    download_path = '../../relative/dep1/foo.bar'
+    config.SetDownloadPath('dep2', 'plat1', download_path)
+    self.assertEqual(
+        download_path,
+        config._GetPlatformData('dep2', 'plat1', 'download_path'))
+
+  def testSetDownloadPathOnNewPlatformSuccesfully(self):
+    config = dependency_manager.BaseConfig(self.file_path, writable=True)
+    download_path = '../../relative/dep1/foo.bar'
+    config.SetDownloadPath('dep2', 'newplat', download_path)
+    self.assertEqual(
+        download_path,
+        config._GetPlatformData('dep2', 'newplat', 'download_path'))
+
 
   def testSetPlatformDataFailureNotWritable(self):
     config = dependency_manager.BaseConfig(self.file_path)
@@ -1410,7 +1452,6 @@
       self.assertEqual(self.GetConfigDataFromDict(self.empty_dict),
                        config._config_data)
 
-
   @mock.patch('dependency_manager.dependency_info.DependencyInfo')
   @mock.patch('os.path')
   @mock.patch('__builtin__.open')
diff --git a/catapult/devil/devil/android/apk_helper.py b/catapult/devil/devil/android/apk_helper.py
index 8acb41e..ab7649f 100644
--- a/catapult/devil/devil/android/apk_helper.py
+++ b/catapult/devil/devil/android/apk_helper.py
@@ -5,6 +5,7 @@
 """Module containing utilities for apk packages."""
 
 import re
+import zipfile
 
 from devil import base_error
 from devil.android.sdk import aapt
@@ -242,3 +243,30 @@
     if '.' not in name:
       return '%s.%s' % (self.GetPackageName(), name)
     return name
+
+  def _ListApkPaths(self):
+    with zipfile.ZipFile(self._apk_path) as z:
+      return z.namelist()
+
+  def GetAbis(self):
+    """Returns a list of ABIs in the apk (empty list if no native code)."""
+    # Use lib/* to determine the compatible ABIs.
+    libs = set()
+    for path in self._ListApkPaths():
+      path_tokens = path.split('/')
+      if len(path_tokens) >= 2 and path_tokens[0] == 'lib':
+        libs.add(path_tokens[1])
+    lib_to_abi = {
+        'armeabi-v7a': ['armeabi-v7a', 'arm64-v8a'],
+        'arm64-v8a': ['arm64-v8a'],
+        'x86': ['x86', 'x64'],
+        'x64': ['x64']
+    }
+    try:
+      output = set()
+      for lib in libs:
+        for abi in lib_to_abi[lib]:
+          output.add(abi)
+      return sorted(output)
+    except KeyError:
+      raise base_error.BaseError('Unexpected ABI in lib/* folder.')
diff --git a/catapult/devil/devil/android/apk_helper_test.py b/catapult/devil/devil/android/apk_helper_test.py
index 12137db..3be9d81 100755
--- a/catapult/devil/devil/android/apk_helper_test.py
+++ b/catapult/devil/devil/android/apk_helper_test.py
@@ -3,6 +3,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import collections
+import os
 import unittest
 
 from devil import base_error
@@ -125,6 +127,11 @@
       'devil.android.sdk.aapt.Dump',
       mock.Mock(side_effect=None, return_value=manifest_dump.split('\n')))
 
+def _MockListApkPaths(files):
+  return mock.patch(
+      'devil.android.apk_helper.ApkHelper._ListApkPaths',
+      mock.Mock(side_effect=None, return_value=files))
+
 class ApkHelperTest(mock_calls.TestCase):
 
   def testGetInstrumentationName(self):
@@ -220,6 +227,25 @@
       self.assertEquals('org.chromium.RandomTestRunner',
                         helper.GetInstrumentationName())
 
+  def testGetArchitectures(self):
+    AbiPair = collections.namedtuple('AbiPair', ['abi32bit', 'abi64bit'])
+    for abi_pair in [AbiPair('lib/armeabi-v7a', 'lib/arm64-v8a'),
+                     AbiPair('lib/x86', 'lib/x64')]:
+      with _MockListApkPaths([abi_pair.abi32bit]):
+        helper = apk_helper.ApkHelper('')
+        self.assertEquals(set([os.path.basename(abi_pair.abi32bit),
+                               os.path.basename(abi_pair.abi64bit)]),
+                          set(helper.GetAbis()))
+      with _MockListApkPaths([abi_pair.abi32bit, abi_pair.abi64bit]):
+        helper = apk_helper.ApkHelper('')
+        self.assertEquals(set([os.path.basename(abi_pair.abi32bit),
+                               os.path.basename(abi_pair.abi64bit)]),
+                          set(helper.GetAbis()))
+      with _MockListApkPaths([abi_pair.abi64bit]):
+        helper = apk_helper.ApkHelper('')
+        self.assertEquals(set([os.path.basename(abi_pair.abi64bit)]),
+                          set(helper.GetAbis()))
+
 
 if __name__ == '__main__':
   unittest.main(verbosity=2)
diff --git a/catapult/devil/devil/android/device_errors.py b/catapult/devil/devil/android/device_errors.py
index 57f3615..cd0266c 100644
--- a/catapult/devil/devil/android/device_errors.py
+++ b/catapult/devil/devil/android/device_errors.py
@@ -55,15 +55,15 @@
     self.status = status
     if not message:
       adb_cmd = ' '.join(cmd_helper.SingleQuote(arg) for arg in self.args)
-      message = ['adb %s: failed ' % adb_cmd]
+      segments = ['adb %s: failed ' % adb_cmd]
       if status:
-        message.append('with exit status %s ' % self.status)
+        segments.append('with exit status %s ' % self.status)
       if output:
-        message.append('and output:\n')
-        message.extend('- %s\n' % line for line in output.splitlines())
+        segments.append('and output:\n')
+        segments.extend('- %s\n' % line for line in output.splitlines())
       else:
-        message.append('and no output.')
-      message = ''.join(message)
+        segments.append('and no output.')
+      message = ''.join(segments)
     super(_BaseCommandFailedError, self).__init__(message, device_serial)
 
   def __eq__(self, other):
@@ -79,8 +79,7 @@
     """Support pickling."""
     result = [None, None, None, None, None]
     super_result = super(_BaseCommandFailedError, self).__reduce__()
-    for i in range(len(super_result)):
-      result[i] = super_result[i]
+    result[:len(super_result)] = super_result
 
     # Update the args used to reconstruct this exception.
     result[1] = (
@@ -120,19 +119,19 @@
 
   def __init__(self, command, output, status, device_serial=None):
     self.command = command
-    message = ['shell command run via adb failed on the device:\n',
+    segments = ['shell command run via adb failed on the device:\n',
                '  command: %s\n' % command]
-    message.append('  exit status: %s\n' % status)
+    segments.append('  exit status: %s\n' % status)
     if output:
-      message.append('  output:\n')
+      segments.append('  output:\n')
       if isinstance(output, basestring):
         output_lines = output.splitlines()
       else:
         output_lines = output
-      message.extend('  - %s\n' % line for line in output_lines)
+      segments.extend('  - %s\n' % line for line in output_lines)
     else:
-      message.append("  output: ''\n")
-    message = ''.join(message)
+      segments.append("  output: ''\n")
+    message = ''.join(segments)
     super(AdbShellCommandFailedError, self).__init__(
       ['shell', command], output, status, device_serial, message)
 
@@ -140,8 +139,7 @@
     """Support pickling."""
     result = [None, None, None, None, None]
     super_result = super(AdbShellCommandFailedError, self).__reduce__()
-    for i in range(len(super_result)):
-      result[i] = super_result[i]
+    result[:len(super_result)] = super_result
 
     # Update the args used to reconstruct this exception.
     result[1] = (self.command, self.output, self.status, self.device_serial)
diff --git a/catapult/devil/devil/android/device_utils.py b/catapult/devil/devil/android/device_utils.py
index 5a3db41..518e439 100644
--- a/catapult/devil/devil/android/device_utils.py
+++ b/catapult/devil/devil/android/device_utils.py
@@ -59,6 +59,29 @@
 # the timeout_retry decorators.
 DEFAULT = object()
 
+# A sentinel object to require that calls to RunShellCommand force running the
+# command with su even if the device has been rooted. To use, pass into the
+# as_root param.
+_FORCE_SU = object()
+
+_RECURSIVE_DIRECTORY_LIST_SCRIPT = """
+  function list_subdirs() {
+    for f in "$1"/* ;
+    do
+      if [ -d "$f" ] ;
+      then
+        if [ "$f" == "." ] || [ "$f" == ".." ] ;
+        then
+          continue ;
+        fi ;
+        echo "$f" ;
+        list_subdirs "$f" ;
+      fi ;
+    done ;
+  } ;
+  list_subdirs %s
+"""
+
 _RESTART_ADBD_SCRIPT = """
   trap '' HUP
   trap '' TERM
@@ -88,6 +111,7 @@
     'android.permission.DISABLE_KEYGUARD',
     'android.permission.DOWNLOAD_WITHOUT_NOTIFICATION',
     'android.permission.EXPAND_STATUS_BAR',
+    'android.permission.FOREGROUND_SERVICE',
     'android.permission.GET_PACKAGE_SIZE',
     'android.permission.INSTALL_SHORTCUT',
     'android.permission.INJECT_EVENTS',
@@ -649,6 +673,22 @@
         'Version name for %s not found on dumpsys output' % package, str(self))
 
   @decorators.WithTimeoutAndRetriesFromInstance()
+  def GetPackageArchitecture(self, package, timeout=None, retries=None):
+    """Get the architecture of a package installed on the device.
+
+    Args:
+      package: Name of the package.
+
+    Returns:
+      A string with the architecture, or None if the package is missing.
+    """
+    lines = self._GetDumpsysOutput(['package', package], 'primaryCpuAbi')
+    if lines:
+      _, _, package_arch = lines[-1].partition('=')
+      return package_arch.strip()
+    return None
+
+  @decorators.WithTimeoutAndRetriesFromInstance()
   def GetApplicationDataDirectory(self, package, timeout=None, retries=None):
     """Get the data directory on the device for the given package.
 
@@ -670,6 +710,31 @@
     raise device_errors.CommandFailedError(
         'Could not find data directory for %s', package)
 
+  @decorators.WithTimeoutAndRetriesFromInstance()
+  def GetSecurityContextForPackage(self, package, encrypted=False, timeout=None,
+      retries=None):
+    """Gets the SELinux security context for the given package.
+
+    Args:
+      package: Name of the package.
+      encrypted: Whether to check in the encrypted data directory
+          (/data/user_de/0/) or the unencrypted data directory (/data/data/).
+
+    Returns:
+      The package's security context as a string, or None if not found.
+    """
+    directory = '/data/user_de/0/' if encrypted else '/data/data/'
+    for line in self.RunShellCommand(['ls', '-Z', directory],
+                                     as_root=True, check_return=True):
+      split_line = line.split()
+      # ls -Z output differs between Android versions, but the package is
+      # always last and the context always starts with "u:object"
+      if split_line[-1] == package:
+        for column in split_line:
+          if column.startswith('u:object'):
+            return column
+    return None
+
   def TakeBugReport(self, path, timeout=60*5, retries=None):
     """Takes a bug report and dumps it to the specified path.
 
@@ -1064,7 +1129,7 @@
     if run_as:
       cmd = 'run-as %s sh -c %s' % (cmd_helper.SingleQuote(run_as),
                                     cmd_helper.SingleQuote(cmd))
-    if as_root and self.NeedsSU():
+    if (as_root is _FORCE_SU) or (as_root and self.NeedsSU()):
       # "su -c sh -c" allows using shell features in |cmd|
       cmd = self._Su('sh -c %s' % cmd_helper.SingleQuote(cmd))
 
@@ -1202,6 +1267,33 @@
         raise device_errors.CommandFailedError(line, str(self))
 
   @decorators.WithTimeoutAndRetriesFromInstance()
+  def StartService(self, intent_obj, user_id=None, timeout=None, retries=None):
+    """Start a service on the device.
+
+    Args:
+      intent_obj: An Intent object to send describing the service to start.
+      user_id: A specific user to start the service as, defaults to current.
+      timeout: Timeout in seconds.
+      retries: Number of retries
+
+    Raises:
+      CommandFailedError if the service could not be started.
+      CommandTimeoutError on timeout.
+      DeviceUnreachableError on missing device.
+    """
+    # For whatever reason, startservice was changed to start-service on O and
+    # above.
+    cmd = ['am', 'startservice']
+    if self.build_version_sdk >= version_codes.OREO:
+      cmd[1] = 'start-service'
+    if user_id:
+      cmd.extend(['--user', str(user_id)])
+    cmd.extend(intent_obj.am_args)
+    for line in self.RunShellCommand(cmd, check_return=True):
+      if line.startswith('Error:'):
+        raise device_errors.CommandFailedError(line, str(self))
+
+  @decorators.WithTimeoutAndRetriesFromInstance()
   def StartInstrumentation(self, component, finish=True, raw=False,
                            extras=None, timeout=None, retries=None):
     if extras is None:
@@ -1383,7 +1475,7 @@
           missing_dirs.add(posixpath.dirname(d))
 
     if delete_device_stale and all_stale_files:
-      self.RunShellCommand(['rm', '-f'] + all_stale_files, check_return=True)
+      self.RemovePath(all_stale_files, force=True, recursive=True)
 
     if all_changed_files:
       if missing_dirs:
@@ -1483,15 +1575,66 @@
         else:
           to_push.append((host_abs_path, device_abs_path))
       to_delete = device_checksums.keys()
+    # We can't rely solely on the checksum approach since it does not catch
+    # stale directories, which can result in empty directories that cause issues
+    # during copying in efficient_android_directory_copy.sh. So, find any stale
+    # directories here so they can be removed in addition to stale files.
+    if track_stale:
+      to_delete.extend(self._GetStaleDirectories(host_path, device_path))
 
     def cache_commit_func():
-      new_sums = {posixpath.join(device_path, path[len(host_path) + 1:]): val
-                  for path, val in host_checksums.iteritems()}
+      # When host_path is a not a directory, the path.join() call below would
+      # have an '' as the second argument, causing an unwanted / to be appended.
+      if os.path.isfile(host_path):
+        assert len(host_checksums) == 1
+        new_sums = {device_path: host_checksums[host_path]}
+      else:
+        new_sums = {posixpath.join(device_path, path[len(host_path) + 1:]): val
+                    for path, val in host_checksums.iteritems()}
       cache_entry = [ignore_other_files, new_sums]
       self._cache['device_path_checksums'][device_path] = cache_entry
 
     return (to_push, up_to_date, to_delete, cache_commit_func)
 
+  def _GetStaleDirectories(self, host_path, device_path):
+    """Gets a list of stale directories on the device.
+
+    Args:
+      host_path: an absolute path of a directory on the host
+      device_path: an absolute path of a directory on the device
+
+    Returns:
+      A list containing absolute paths to directories on the device that are
+      considered stale.
+    """
+    def get_device_dirs(path):
+      directories = set()
+      command = _RECURSIVE_DIRECTORY_LIST_SCRIPT % cmd_helper.SingleQuote(path)
+      # We use shell=True to evaluate the command as a script through the shell,
+      # otherwise RunShellCommand tries to interpret it as the name of a (non
+      # existent) command to run.
+      for line in self.RunShellCommand(
+          command, shell=True, check_return=True):
+        directories.add(posixpath.relpath(posixpath.normpath(line), path))
+      return directories
+
+    def get_host_dirs(path):
+      directories = set()
+      if not os.path.isdir(path):
+        return directories
+      for root, _, _ in os.walk(path):
+        if root != path:
+          # Strip off the top level directory so we can compare the device and
+          # host.
+          directories.add(
+              os.path.relpath(root, path).replace(os.sep, posixpath.sep))
+      return directories
+
+    host_dirs = get_host_dirs(host_path)
+    device_dirs = get_device_dirs(device_path)
+    stale_dirs = device_dirs - host_dirs
+    return [posixpath.join(device_path, d) for d in stale_dirs]
+
   def _ComputeDeviceChecksumsForApks(self, package_name):
     ret = self._cache['package_apk_checksums'].get(package_name)
     if ret is None:
@@ -1608,6 +1751,8 @@
       except zip_utils.ZipFailedError:
         return False
 
+      logger.info('Pushing %d files via .zip of size %d', len(files),
+                  os.path.getsize(zip_path))
       self.NeedsSU()
       with device_temp_file.DeviceTempFile(
           self.adb, suffix='.zip') as device_temp:
@@ -2279,9 +2424,8 @@
     """
     try:
       ps_cmd = 'ps'
-      # ps behavior was changed in Android above N, http://crbug.com/686716
-      if (self.build_version_sdk >= version_codes.NOUGAT_MR1
-          and self.build_id[0] > 'N'):
+      # ps behavior was changed in Android O and above, http://crbug.com/686716
+      if self.build_version_sdk >= version_codes.OREO:
         ps_cmd = 'ps -e'
       if pattern:
         return self._RunPipedShellCommand(
@@ -2331,6 +2475,29 @@
       processes.append(ProcessInfo(**row))
     return processes
 
+  def _GetDumpsysOutput(self, extra_args, pattern=None):
+    """Runs |dumpsys| command on the device and returns its output.
+
+    This private method implements support for filtering the output by a given
+    |pattern|, but does not do any output parsing.
+    """
+    try:
+      cmd = ['dumpsys'] + extra_args
+      if pattern:
+        cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd)
+        return self._RunPipedShellCommand(
+            '%s | grep -F %s' % (cmd, cmd_helper.SingleQuote(pattern)))
+      else:
+        cmd = ['dumpsys'] + extra_args
+        return self.RunShellCommand(cmd, check_return=True, large_output=True)
+    except device_errors.AdbShellCommandFailedError as e:
+      if e.status and isinstance(e.status, list) and not e.status[0]:
+        # If dumpsys succeeded but grep failed, there were no lines matching
+        # the given pattern.
+        return []
+      else:
+        raise
+
   # TODO(#4103): Remove after migrating clients to ListProcesses.
   @decorators.WithTimeoutAndRetriesFromInstance()
   def GetPids(self, process_name=None, timeout=None, retries=None):
@@ -2438,6 +2605,30 @@
         check_return=True)
 
   @decorators.WithTimeoutAndRetriesFromInstance()
+  def SetWebViewImplementation(self, package_name, timeout=None, retries=None):
+    """Select the WebView implementation to the specified package.
+
+    Args:
+      package_name: The package name of a WebView implementation. The package
+        must be already installed on the device.
+      timeout: timeout in seconds
+      retries: number of retries
+
+    Raises:
+      CommandFailedError on failure.
+      CommandTimeoutError on timeout.
+      DeviceUnreachableError on missing device.
+    """
+    output = self.RunShellCommand(
+        ['cmd', 'webviewupdate', 'set-webview-implementation', package_name],
+        single_line=True, check_return=True)
+    if output == 'Success':
+      logging.info('WebView provider set to: %s', package_name)
+    else:
+      raise device_errors.CommandFailedError(
+          'Error setting WebView provider: %s' % output, str(self))
+
+  @decorators.WithTimeoutAndRetriesFromInstance()
   def TakeScreenshot(self, host_path=None, timeout=None, retries=None):
     """Takes a screenshot of the device.
 
@@ -2615,7 +2806,7 @@
 
   @classmethod
   def HealthyDevices(cls, blacklist=None, device_arg='default', retry=True,
-                     **kwargs):
+                     abis=None, **kwargs):
     """Returns a list of DeviceUtils instances.
 
     Returns a list of DeviceUtils instances that are attached, not blacklisted,
@@ -2639,6 +2830,8 @@
               blacklisted.
       retry: If true, will attempt to restart adb server and query it again if
           no devices are found.
+      abis: A list of ABIs for which the device needs to support at least one of
+          (optional).
       A device serial, or a list of device serials (optional).
 
     Returns:
@@ -2674,14 +2867,23 @@
         return True
       return False
 
+    def supports_abi(abi, serial):
+      if abis and abi not in abis:
+        logger.warning("Device %s doesn't support required ABIs.", serial)
+        return False
+      return True
+
     def _get_devices():
       if device_arg:
         devices = [cls(x, **kwargs) for x in device_arg if not blacklisted(x)]
       else:
         devices = []
         for adb in adb_wrapper.AdbWrapper.Devices():
-          if not blacklisted(adb.GetDeviceSerial()):
-            devices.append(cls(_CreateAdbWrapper(adb), **kwargs))
+          serial = adb.GetDeviceSerial()
+          if not blacklisted(serial):
+            device = cls(_CreateAdbWrapper(adb), **kwargs)
+            if supports_abi(device.GetABI(), serial):
+              devices.append(device)
 
       if len(devices) == 0 and not allow_no_devices:
         raise device_errors.NoDevicesError()
@@ -2806,3 +3008,39 @@
       return
     self.SendKeyEvent(keyevent.KEYCODE_POWER)
     timeout_retry.WaitFor(screen_test, wait_period=1)
+
+  @decorators.WithTimeoutAndRetriesFromInstance()
+  def ChangeOwner(self, owner_group, paths, timeout=None, retries=None):
+    """Changes file system ownership for permissions.
+
+    Args:
+      owner_group: New owner and group to assign. Note that this should be a
+        string in the form user[.group] where the group is option.
+      paths: Paths to change ownership of.
+
+      Note that the -R recursive option is not supported by all Android
+      versions.
+    """
+    if not paths:
+      return
+    self.RunShellCommand(['chown', owner_group] + paths, check_return=True)
+
+  @decorators.WithTimeoutAndRetriesFromInstance()
+  def ChangeSecurityContext(self, security_context, paths, timeout=None,
+                            retries=None):
+    """Changes the SELinux security context for files.
+
+    Args:
+      security_context: The new security context as a string
+      paths: Paths to change the security context of.
+
+      Note that the -R recursive option is not supported by all Android
+      versions.
+    """
+    if not paths:
+      return
+    command = ['chcon', security_context] + paths
+
+    # Note, need to force su because chcon can fail with permission errors even
+    # if the device is rooted.
+    self.RunShellCommand(command, as_root=_FORCE_SU, check_return=True)
diff --git a/catapult/devil/devil/android/device_utils_devicetest.py b/catapult/devil/devil/android/device_utils_devicetest.py
index 173094b..0836f3e 100755
--- a/catapult/devil/devil/android/device_utils_devicetest.py
+++ b/catapult/devil/devil/android/device_utils_devicetest.py
@@ -210,6 +210,39 @@
     cmd_helper.RunCmd(['rm', '-rf', host_tmp_dir])
     self.device.RemovePath(_DEVICE_DIR, recursive=True, force=True)
 
+  def testPushWithStaleDirectories(self):
+    # Make a few files and directories to push.
+    host_tmp_dir = tempfile.mkdtemp()
+    host_sub_dir1 = '%s/%s' % (host_tmp_dir, _SUB_DIR1)
+    host_sub_dir2 = "%s/%s/%s" % (host_tmp_dir, _SUB_DIR, _SUB_DIR2)
+    os.makedirs(host_sub_dir1)
+    os.makedirs(host_sub_dir2)
+
+    self._MakeTempFileGivenDir(host_sub_dir1, _OLD_CONTENTS)
+    self._MakeTempFileGivenDir(host_sub_dir2, _OLD_CONTENTS)
+
+    # Push all our created files/directories and verify they're on the device.
+    self.device.PushChangedFiles([(host_tmp_dir, _DEVICE_DIR)],
+                                   delete_device_stale=True)
+    top_level_dirs = self.device.ListDirectory(_DEVICE_DIR)
+    self.assertIn(_SUB_DIR1, top_level_dirs)
+    self.assertIn(_SUB_DIR, top_level_dirs)
+    sub_dir = self.device.ListDirectory('%s/%s' % (_DEVICE_DIR, _SUB_DIR))
+    self.assertIn(_SUB_DIR2, sub_dir)
+
+    # Remove one of the directories on the host and push again.
+    cmd_helper.RunCmd(['rm', '-rf', host_sub_dir2])
+    self.device.PushChangedFiles([(host_tmp_dir, _DEVICE_DIR)],
+                                   delete_device_stale=True)
+
+    # Verify that the directory we removed is no longer on the device, but the
+    # other directories still are.
+    top_level_dirs = self.device.ListDirectory(_DEVICE_DIR)
+    self.assertIn(_SUB_DIR1, top_level_dirs)
+    self.assertIn(_SUB_DIR, top_level_dirs)
+    sub_dir = self.device.ListDirectory('%s/%s' % (_DEVICE_DIR, _SUB_DIR))
+    self.assertEqual([], sub_dir)
+
   def testRestartAdbd(self):
     def get_adbd_pid():
       try:
diff --git a/catapult/devil/devil/android/device_utils_test.py b/catapult/devil/devil/android/device_utils_test.py
index b5660ac..88c91b5 100755
--- a/catapult/devil/devil/android/device_utils_test.py
+++ b/catapult/devil/devil/android/device_utils_test.py
@@ -31,6 +31,8 @@
 with devil_env.SysPath(devil_env.PYMOCK_PATH):
   import mock  # pylint: disable=import-error
 
+ARM32_ABI = 'armeabi-v7a'
+ARM64_ABI = 'arm64-v8a'
 
 def Process(name, pid, ppid='1'):
   return device_utils.ProcessInfo(name=name, pid=pid, ppid=ppid)
@@ -57,6 +59,7 @@
     self.path = path
     self.package_name = package_name
     self.perms = perms
+    self.abis = [ARM32_ABI]
 
   def GetPackageName(self):
     return self.package_name
@@ -64,6 +67,9 @@
   def GetPermissions(self):
     return self.perms
 
+  def GetAbis(self):
+    return self.abis
+
 
 class _MockMultipleDevicesError(Exception):
   pass
@@ -465,6 +471,27 @@
         self.device.GetApplicationVersion('com.android.chrome')
 
 
+class DeviceUtils_GetPackageArchitectureTest(DeviceUtilsTest):
+
+  def test_GetPackageArchitecture_exists(self):
+    with self.assertCall(
+        self.call.device._RunPipedShellCommand(
+            'dumpsys package com.android.chrome | grep -F primaryCpuAbi'),
+        ['  primaryCpuAbi=armeabi-v7a']):
+      self.assertEquals(
+          ARM32_ABI,
+          self.device.GetPackageArchitecture('com.android.chrome'))
+
+  def test_GetPackageArchitecture_notExists(self):
+    with self.assertCall(
+        self.call.device._RunPipedShellCommand(
+            'dumpsys package com.android.chrome | grep -F primaryCpuAbi'),
+        []):
+      self.assertEquals(
+          None,
+          self.device.GetPackageArchitecture('com.android.chrome'))
+
+
 class DeviceUtilsGetApplicationDataDirectoryTest(DeviceUtilsTest):
 
   def testGetApplicationDataDirectory_exists(self):
@@ -1415,6 +1442,62 @@
       self.device.StartActivity(test_intent)
 
 
+class DeviceUtilsStartServiceTest(DeviceUtilsTest):
+  def testStartService_success(self):
+    test_intent = intent.Intent(action='android.intent.action.START',
+                                package='test.package',
+                                activity='.Main')
+    with self.patch_call(self.call.device.build_version_sdk,
+                         return_value=version_codes.NOUGAT):
+      with self.assertCall(
+          self.call.adb.Shell('am startservice '
+                              '-a android.intent.action.START '
+                              '-n test.package/.Main'),
+          'Starting service: Intent { act=android.intent.action.START }'):
+        self.device.StartService(test_intent)
+
+  def testStartService_failure(self):
+    test_intent = intent.Intent(action='android.intent.action.START',
+                                package='test.package',
+                                activity='.Main')
+    with self.patch_call(self.call.device.build_version_sdk,
+                         return_value=version_codes.NOUGAT):
+      with self.assertCall(
+          self.call.adb.Shell('am startservice '
+                              '-a android.intent.action.START '
+                              '-n test.package/.Main'),
+          'Error: Failed to start test service'):
+        with self.assertRaises(device_errors.CommandFailedError):
+          self.device.StartService(test_intent)
+
+  def testStartService_withUser(self):
+    test_intent = intent.Intent(action='android.intent.action.START',
+                                package='test.package',
+                                activity='.Main')
+    with self.patch_call(self.call.device.build_version_sdk,
+                         return_value=version_codes.NOUGAT):
+      with self.assertCall(
+          self.call.adb.Shell('am startservice '
+                              '--user TestUser '
+                              '-a android.intent.action.START '
+                              '-n test.package/.Main'),
+          'Starting service: Intent { act=android.intent.action.START }'):
+        self.device.StartService(test_intent, user_id='TestUser')
+
+  def testStartService_onOreo(self):
+    test_intent = intent.Intent(action='android.intent.action.START',
+                                package='test.package',
+                                activity='.Main')
+    with self.patch_call(self.call.device.build_version_sdk,
+                         return_value=version_codes.OREO):
+      with self.assertCall(
+          self.call.adb.Shell('am start-service '
+                              '-a android.intent.action.START '
+                              '-n test.package/.Main'),
+          'Starting service: Intent { act=android.intent.action.START }'):
+        self.device.StartService(test_intent)
+
+
 class DeviceUtilsStartInstrumentationTest(DeviceUtilsTest):
 
   def testStartInstrumentation_nothing(self):
@@ -1691,6 +1774,8 @@
          mock_zip_temp_dir),
         (mock.call.devil.utils.zip_utils.WriteZipFile(
             '/test/temp/dir/tmp.zip', test_files)),
+        (mock.call.os.path.getsize(
+            '/test/temp/dir/tmp.zip'), 123),
         (self.call.device.NeedsSU(), True),
         (mock.call.devil.android.device_temp_file.DeviceTempFile(self.adb,
                                                                  suffix='.zip'),
@@ -2525,6 +2610,20 @@
       self.device.SetEnforce(enabled='0')  # Not recommended but it works!
 
 
+class DeviceUtilsSetWebViewImplementationTest(DeviceUtilsTest):
+
+  def testSetWebViewImplementation_success(self):
+    with self.assertCall(self.call.adb.Shell(
+        'cmd webviewupdate set-webview-implementation foo.org'), 'Success'):
+      self.device.SetWebViewImplementation('foo.org')
+
+  def testSetWebViewImplementation_failure(self):
+    with self.assertCall(self.call.adb.Shell(
+        'cmd webviewupdate set-webview-implementation foo.org'), 'Oops!'):
+      with self.assertRaises(device_errors.CommandFailedError):
+        self.device.SetWebViewImplementation('foo.org')
+
+
 class DeviceUtilsTakeScreenshotTest(DeviceUtilsTest):
 
   def testTakeScreenshot_fileNameProvided(self):
@@ -2621,7 +2720,11 @@
     test_serials = ['0123456789abcdef', 'fedcba9876543210']
     with self.assertCalls(
         (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
-         [_AdbWrapperMock(s) for s in test_serials])):
+         [_AdbWrapperMock(s) for s in test_serials]),
+        (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
+         ARM32_ABI),
+        (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
+         ARM32_ABI)):
       blacklist = mock.NonCallableMock(**{'Read.return_value': []})
       devices = device_utils.DeviceUtils.HealthyDevices(blacklist)
     for serial, device in zip(test_serials, devices):
@@ -2632,7 +2735,9 @@
     test_serials = ['0123456789abcdef', 'fedcba9876543210']
     with self.assertCalls(
         (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
-         [_AdbWrapperMock(s) for s in test_serials])):
+         [_AdbWrapperMock(s) for s in test_serials]),
+        (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
+         ARM32_ABI)):
       blacklist = mock.NonCallableMock(
           **{'Read.return_value': ['fedcba9876543210']})
       devices = device_utils.DeviceUtils.HealthyDevices(blacklist)
@@ -2645,6 +2750,10 @@
     with self.assertCalls(
         (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
          [_AdbWrapperMock(s) for s in test_serials]),
+        (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
+         ARM32_ABI),
+        (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
+         ARM32_ABI),
         (mock.call.devil.android.device_errors.MultipleDevicesError(mock.ANY),
          _MockMultipleDevicesError())):
       with self.assertRaises(_MockMultipleDevicesError):
@@ -2654,7 +2763,9 @@
     test_serials = ['0123456789abcdef']
     with self.assertCalls(
         (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
-         [_AdbWrapperMock(s) for s in test_serials])):
+         [_AdbWrapperMock(s) for s in test_serials]),
+        (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
+         ARM32_ABI)):
       devices = device_utils.DeviceUtils.HealthyDevices(device_arg=None)
     self.assertEquals(1, len(devices))
 
@@ -2684,7 +2795,11 @@
     test_serials = ['0123456789abcdef', 'fedcba9876543210']
     with self.assertCalls(
         (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
-         [_AdbWrapperMock(s) for s in test_serials])):
+         [_AdbWrapperMock(s) for s in test_serials]),
+        (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
+         ARM32_ABI),
+        (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
+         ARM32_ABI)):
       devices = device_utils.DeviceUtils.HealthyDevices(device_arg=())
     self.assertEquals(2, len(devices))
 
@@ -2725,6 +2840,33 @@
       del os.environ['ANDROID_SERIAL']
     self.assertEquals(2, len(devices))
 
+  def testHealthyDevices_abisArg_no_matching_abi(self):
+    test_serials = ['0123456789abcdef', 'fedcba9876543210']
+    with self.assertCalls(
+        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
+         [_AdbWrapperMock(s) for s in test_serials]),
+        (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
+         ARM32_ABI),
+        (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
+         ARM32_ABI)):
+      with self.assertRaises(device_errors.NoDevicesError):
+        device_utils.DeviceUtils.HealthyDevices(device_arg=[], retry=False,
+                                                abis=[ARM64_ABI])
+
+  def testHealthyDevices_abisArg_filter_on_abi(self):
+    test_serials = ['0123456789abcdef', 'fedcba9876543210']
+    with self.assertCalls(
+        (mock.call.devil.android.sdk.adb_wrapper.AdbWrapper.Devices(),
+         [_AdbWrapperMock(s) for s in test_serials]),
+        (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
+         ARM64_ABI),
+        (mock.call.devil.android.device_utils.DeviceUtils.GetABI(),
+         ARM32_ABI)):
+      devices = device_utils.DeviceUtils.HealthyDevices(device_arg=[],
+                                                        retry=False,
+                                                        abis=[ARM64_ABI])
+    self.assertEquals(1, len(devices))
+
 
 class DeviceUtilsRestartAdbdTest(DeviceUtilsTest):
 
@@ -2967,6 +3109,27 @@
         self.device.GetIMEI()
 
 
+class DeviceUtilsChangeOwner(DeviceUtilsTest):
+
+  def testChangeOwner(self):
+    with self.assertCalls(
+        (self.call.device.RunShellCommand(
+            ['chown', 'user.group', '/path/to/file1', 'file2'],
+            check_return=True))):
+      self.device.ChangeOwner('user.group', ['/path/to/file1', 'file2'])
+
+
+class DeviceUtilsChangeSecurityContext(DeviceUtilsTest):
+
+
+  def testChangeSecurityContext(self):
+    with self.assertCalls(
+        (self.call.device.RunShellCommand(
+            ['chcon', 'u:object_r:system_data_file:s0', '/path', '/path2'],
+            as_root=device_utils._FORCE_SU, check_return=True))):
+      self.device.ChangeSecurityContext('u:object_r:system_data_file:s0',
+                                        ['/path', '/path2'])
+
 if __name__ == '__main__':
   logging.getLogger().setLevel(logging.DEBUG)
   unittest.main(verbosity=2)
diff --git a/catapult/devil/devil/android/flag_changer.py b/catapult/devil/devil/android/flag_changer.py
index 0055e23..9cb1c02 100644
--- a/catapult/devil/devil/android/flag_changer.py
+++ b/catapult/devil/devil/android/flag_changer.py
@@ -52,12 +52,14 @@
     once the tests have completed.
   """
 
-  def __init__(self, device, cmdline_file):
+  def __init__(self, device, cmdline_file, use_legacy_path=False):
     """Initializes the FlagChanger and records the original arguments.
 
     Args:
       device: A DeviceUtils instance.
       cmdline_file: Name of the command line file where to store flags.
+      use_legacy_path: Whether to use the legacy commandline path (needed for
+        M54 and earlier)
     """
     self._device = device
     self._should_reset_enforce = False
@@ -66,13 +68,18 @@
       raise ValueError(
           'cmdline_file should be a file name only, do not include path'
           ' separators in: %s' % cmdline_file)
-    self._cmdline_path = posixpath.join(_CMDLINE_DIR, cmdline_file)
+    cmdline_path = posixpath.join(_CMDLINE_DIR, cmdline_file)
+    alternate_cmdline_path = posixpath.join(_CMDLINE_DIR_LEGACY, cmdline_file)
 
-    cmdline_path_legacy = posixpath.join(_CMDLINE_DIR_LEGACY, cmdline_file)
-    if self._device.PathExists(cmdline_path_legacy):
+    if use_legacy_path:
+      cmdline_path, alternate_cmdline_path = (
+          alternate_cmdline_path, cmdline_path)
+    self._cmdline_path = cmdline_path
+
+    if self._device.PathExists(alternate_cmdline_path):
       logger.warning(
-            'Removing legacy command line file %r.', cmdline_path_legacy)
-      self._device.RemovePath(cmdline_path_legacy, as_root=True)
+          'Removing alternate command line file %r.', alternate_cmdline_path)
+      self._device.RemovePath(alternate_cmdline_path, as_root=True)
 
     self._state_stack = [None]  # Actual state is set by GetCurrentFlags().
     self.GetCurrentFlags()
@@ -195,7 +202,7 @@
     """
     # The initial state must always remain on the stack.
     assert len(self._state_stack) > 1, (
-      "Mismatch between calls to Add/RemoveFlags and Restore")
+        'Mismatch between calls to Add/RemoveFlags and Restore')
     self._state_stack.pop()
     if len(self._state_stack) == 1:
       self._ResetEnforce()
@@ -237,6 +244,7 @@
   current_quote = None
   current_flag = None
 
+  # pylint: disable=unsubscriptable-object
   for c in line:
     # Detect start or end of quote block.
     if (current_quote is None and c in _QUOTES) or c == current_quote:
diff --git a/catapult/devil/devil/android/flag_changer_test.py b/catapult/devil/devil/android/flag_changer_test.py
index 5342cf4..dbe6fac 100755
--- a/catapult/devil/devil/android/flag_changer_test.py
+++ b/catapult/devil/devil/android/flag_changer_test.py
@@ -42,7 +42,7 @@
     self.cmdline_path_legacy = posixpath.join(
         flag_changer._CMDLINE_DIR_LEGACY, _CMDLINE_FILE)
 
-  def testFlagChanger_removeLegacyCmdLine(self):
+  def testFlagChanger_removeAlternateCmdLine(self):
     self.device.WriteFile(self.cmdline_path_legacy, 'chrome --old --stuff')
     self.assertTrue(self.device.PathExists(self.cmdline_path_legacy))
 
@@ -52,6 +52,17 @@
         self.cmdline_path)
     self.assertFalse(self.device.PathExists(self.cmdline_path_legacy))
 
+  def testFlagChanger_removeAlternateCmdLineLegacyPath(self):
+    self.device.WriteFile(self.cmdline_path, 'chrome --old --stuff')
+    self.assertTrue(self.device.PathExists(self.cmdline_path))
+
+    changer = flag_changer.FlagChanger(self.device, 'chrome-command-line',
+                                       use_legacy_path=True)
+    self.assertEquals(
+      changer._cmdline_path,  # pylint: disable=protected-access
+      self.cmdline_path_legacy)
+    self.assertFalse(self.device.PathExists(self.cmdline_path))
+
   def testFlagChanger_mustBeFileName(self):
     with self.assertRaises(ValueError):
       flag_changer.FlagChanger(self.device, '/data/local/chrome-command-line')
diff --git a/catapult/devil/devil/android/forwarder.py b/catapult/devil/devil/android/forwarder.py
index cf1fbe1..6be4651 100644
--- a/catapult/devil/devil/android/forwarder.py
+++ b/catapult/devil/devil/android/forwarder.py
@@ -306,7 +306,7 @@
     instance = Forwarder._GetInstanceLocked(None)
     serial = str(device)
     serial_with_port = (serial, device_port)
-    if not serial_with_port in instance._device_to_host_port_map:
+    if serial_with_port not in instance._device_to_host_port_map:
       logger.error('Trying to unmap non-forwarded port %d', device_port)
       return
 
diff --git a/catapult/devil/devil/android/perf/perf_control.py b/catapult/devil/devil/android/perf/perf_control.py
index 06a5db6..9ac85eb 100644
--- a/catapult/devil/devil/android/perf/perf_control.py
+++ b/catapult/devil/devil/android/perf/perf_control.py
@@ -50,12 +50,12 @@
 
     product_model = self._device.product_model
     # TODO(epenner): Enable on all devices (http://crbug.com/383566)
-    if 'Nexus 4' == product_model:
+    if product_model == 'Nexus 4':
       self._ForceAllCpusOnline(True)
       if not self._AllCpusAreOnline():
         logger.warning('Failed to force CPUs online. Results may be NOISY!')
       self.SetScalingGovernor('performance')
-    elif 'Nexus 5' == product_model:
+    elif product_model == 'Nexus 5':
       self._ForceAllCpusOnline(True)
       if not self._AllCpusAreOnline():
         logger.warning('Failed to force CPUs online. Results may be NOISY!')
@@ -79,7 +79,7 @@
     if not self._device.HasRoot():
       return
     product_model = self._device.product_model
-    if 'Nexus 5' == product_model:
+    if product_model == 'Nexus 5':
       if self._AllCpusAreOnline():
         self._SetScalingMaxFreq(2265600)
         self._SetMaxGpuClock(450000000)
diff --git a/catapult/devil/devil/android/perf/thermal_throttle.py b/catapult/devil/devil/android/perf/thermal_throttle.py
index 546a92e..fd6b08f 100644
--- a/catapult/devil/devil/android/perf/thermal_throttle.py
+++ b/catapult/devil/devil/android/perf/thermal_throttle.py
@@ -76,6 +76,7 @@
     self._device = device
     self._throttled = False
     self._detector = None
+    # pylint: disable=redefined-variable-type
     if OmapThrottlingDetector.IsSupported(device):
       self._detector = OmapThrottlingDetector(device)
     elif ExynosThrottlingDetector.IsSupported(device):
diff --git a/catapult/devil/devil/android/sdk/adb_wrapper.py b/catapult/devil/devil/android/sdk/adb_wrapper.py
index 5d24d47..099a0f8 100644
--- a/catapult/devil/devil/android/sdk/adb_wrapper.py
+++ b/catapult/devil/devil/android/sdk/adb_wrapper.py
@@ -9,7 +9,9 @@
 """
 
 import collections
-import distutils.version
+# pylint: disable=import-error
+# pylint: disable=no-name-in-module
+import distutils.version as du_version
 import errno
 import logging
 import os
@@ -39,8 +41,10 @@
 _EMULATOR_RE = re.compile(r'^emulator-[0-9]+$')
 _DEVICE_NOT_FOUND_RE = re.compile(r"error: device '(?P<serial>.+)' not found")
 _READY_STATE = 'device'
-_VERITY_DISABLE_RE = re.compile(r'Verity (already )?disabled')
-_VERITY_ENABLE_RE = re.compile(r'Verity (already )?enabled')
+_VERITY_DISABLE_RE = re.compile(r'(V|v)erity (is )?(already )?disabled'
+                                r'|Successfully disabled verity')
+_VERITY_ENABLE_RE = re.compile(r'(V|v)erity (is )?(already )?enabled'
+                               r'|Successfully enabled verity')
 _WAITING_FOR_DEVICE_RE = re.compile(r'- waiting for device -')
 
 
@@ -422,8 +426,8 @@
     """
     VerifyLocalFileExists(local)
 
-    if (distutils.version.LooseVersion(self.Version()) <
-        distutils.version.LooseVersion('1.0.36')):
+    if (du_version.LooseVersion(self.Version()) <
+        du_version.LooseVersion('1.0.36')):
 
       # Different versions of adb handle pushing a directory to an existing
       # directory differently.
@@ -480,6 +484,19 @@
           'File pulled from the device did not arrive on the host: %s' % local,
           device_serial=str(self))
 
+  def StartShell(self, cmd):
+    """Starts a subprocess on the device and returns a handle to the process.
+
+    Args:
+      args: A sequence of program arguments. The executable to run is the first
+        item in the sequence.
+
+    Returns:
+      An instance of subprocess.Popen associated with the live process.
+    """
+    return cmd_helper.StartCmd(
+        self._BuildAdbCmd(['shell'] + cmd, self._device_serial))
+
   def Shell(self, command, expect_status=0, timeout=DEFAULT_TIMEOUT,
             retries=DEFAULT_RETRIES):
     """Runs a shell command on the device.
@@ -671,8 +688,8 @@
     Returns:
       The output of adb forward --list as a string.
     """
-    if (distutils.version.LooseVersion(self.Version()) >=
-        distutils.version.LooseVersion('1.0.36')):
+    if (du_version.LooseVersion(self.Version()) >=
+        du_version.LooseVersion('1.0.36')):
       # Starting in 1.0.36, this can occasionally fail with a protocol fault.
       # As this interrupts all connections with all devices, we instead just
       # return an empty list. This may give clients an inaccurate result, but
diff --git a/catapult/devil/devil/android/sdk/fastboot.py b/catapult/devil/devil/android/sdk/fastboot.py
index ae99d39..47f4167 100644
--- a/catapult/devil/devil/android/sdk/fastboot.py
+++ b/catapult/devil/devil/android/sdk/fastboot.py
@@ -54,7 +54,7 @@
     Raises:
       TypeError: If cmd is not of type list.
     """
-    if type(cmd) == list:
+    if isinstance(cmd, list):
       cmd = [cls._fastboot_path.read()] + cmd
     else:
       raise TypeError(
@@ -76,7 +76,7 @@
     Raises:
       TypeError: If cmd is not of type list.
     """
-    if type(cmd) == list:
+    if isinstance(cmd, list):
       cmd = ['-s', self._device_serial] + cmd
     return self._RunFastbootCommand(cmd)
 
diff --git a/catapult/devil/devil/android/sdk/shared_prefs.py b/catapult/devil/devil/android/sdk/shared_prefs.py
index 2fa2e6a..c985cac 100644
--- a/catapult/devil/devil/android/sdk/shared_prefs.py
+++ b/catapult/devil/devil/android/sdk/shared_prefs.py
@@ -10,10 +10,10 @@
 
 import logging
 import posixpath
+from xml.etree import ElementTree
 
 from devil.android import device_errors
 from devil.android.sdk import version_codes
-from xml.etree import ElementTree
 
 logger = logging.getLogger(__name__)
 
@@ -167,7 +167,7 @@
 
 class SharedPrefs(object):
 
-  def __init__(self, device, package, filename):
+  def __init__(self, device, package, filename, use_encrypted_path=False):
     """Helper object to read and update "Shared Prefs" of Android apps.
 
     Such files typically look like, e.g.:
@@ -196,12 +196,29 @@
       package: A string with the package name of the app that owns the shared
         preferences file.
       filename: A string with the name of the preferences file to read/write.
+      use_encrypted_path: Whether to read and write to the shared prefs location
+        in the device-encrypted path (/data/user_de) instead of the older,
+        unencrypted path (/data/data). Only supported on N+, but falls back to
+        the unencrypted path if the encrypted path is not supported on the given
+        device.
     """
     self._device = device
     self._xml = None
     self._package = package
     self._filename = filename
-    self._path = '/data/data/%s/shared_prefs/%s' % (package, filename)
+    self._unencrypted_path = '/data/data/%s/shared_prefs/%s' % (package,
+                                                                filename)
+    self._encrypted_path = '/data/user_de/0/%s/shared_prefs/%s' % (package,
+                                                                   filename)
+    self._path = self._unencrypted_path
+    self._encrypted = use_encrypted_path
+    if use_encrypted_path:
+      if self._device.build_version_sdk < version_codes.NOUGAT:
+        logging.info('SharedPrefs set to use encrypted path, but given device '
+                     'is not running N+. Falling back to unencrypted path')
+        self._encrypted = False
+      else:
+        self._path = self._encrypted_path
     self._changed = False
 
   def __repr__(self):
@@ -277,14 +294,24 @@
     # to the shared_prefs directory, which mimics the behavior of a file
     # created by the app itself
     if self._device.build_version_sdk >= version_codes.MARSHMALLOW:
-      security_context = self._GetSecurityContext(self.package)
-      if security_context == None:
+      security_context = self._device.GetSecurityContextForPackage(self.package,
+          encrypted=self._encrypted)
+      if security_context is None:
         raise device_errors.CommandFailedError(
             'Failed to get security context for %s' % self.package)
-      self._device.RunShellCommand(
-          ['chcon', '-R', security_context,
-           '/data/data/%s/shared_prefs' % self.package],
-          as_root=True, check_return=True)
+      paths = [posixpath.dirname(self.path), self.path]
+      self._device.ChangeSecurityContext(security_context, paths)
+
+    # Ensure that there isn't both an encrypted and unencrypted version of the
+    # file on the device at the same time.
+    if self._device.build_version_sdk >= version_codes.NOUGAT:
+      remove_path = (self._unencrypted_path if self._encrypted
+                     else self._encrypted_path)
+      if self._device.PathExists(remove_path, as_root=True):
+        logging.warning('Found an equivalent shared prefs file at %s, removing',
+            remove_path)
+        self._device.RemovePath(remove_path, as_root=True)
+
     self._device.KillAll(self.package, exact=True, as_root=True, quiet=True)
     self._changed = False
 
@@ -406,15 +433,3 @@
       pref.set(value)
       self._changed = True
       logger.info('Setting property: %s', pref)
-
-  def _GetSecurityContext(self, package):
-    for line in self._device.RunShellCommand(['ls', '-Z', '/data/data/'],
-                                             as_root=True, check_return=True):
-      split_line = line.split()
-      # ls -Z output differs between Android versions, but the package is
-      # always last and the context always starts with "u:object"
-      if split_line[-1] == package:
-        for column in split_line:
-          if column.startswith('u:object'):
-            return column
-    return None
diff --git a/catapult/devil/devil/android/sdk/shared_prefs_test.py b/catapult/devil/devil/android/sdk/shared_prefs_test.py
index 4c31c56..49587c8 100755
--- a/catapult/devil/devil/android/sdk/shared_prefs_test.py
+++ b/catapult/devil/devil/android/sdk/shared_prefs_test.py
@@ -166,6 +166,19 @@
       # contents were not modified
       self.assertEquals(prefs.AsDict(), self.expected_data)
 
+  def testEncryptedPath(self):
+    type(self.device).build_version_sdk = mock.PropertyMock(
+        return_value=version_codes.MARSHMALLOW)
+    with shared_prefs.SharedPrefs(self.device, 'com.some.package',
+        'prefs.xml', use_encrypted_path=True) as prefs:
+      self.assertTrue(prefs.path.startswith('/data/data'))
+
+    type(self.device).build_version_sdk = mock.PropertyMock(
+        return_value=version_codes.NOUGAT)
+    with shared_prefs.SharedPrefs(self.device, 'com.some.package',
+        'prefs.xml', use_encrypted_path=True) as prefs:
+      self.assertTrue(prefs.path.startswith('/data/user_de/0'))
+
 if __name__ == '__main__':
   logging.getLogger().setLevel(logging.DEBUG)
   unittest.main(verbosity=2)
diff --git a/catapult/devil/devil/android/sdk/version_codes.py b/catapult/devil/devil/android/sdk/version_codes.py
index ec14359..1750f00 100644
--- a/catapult/devil/devil/android/sdk/version_codes.py
+++ b/catapult/devil/devil/android/sdk/version_codes.py
@@ -17,5 +17,5 @@
 MARSHMALLOW = 23
 NOUGAT = 24
 NOUGAT_MR1 = 25
-O = 26
-O_MR1 = 27
+OREO = 26
+OREO_MR1 = 27
diff --git a/catapult/devil/devil/android/tools/device_monitor.py b/catapult/devil/devil/android/tools/device_monitor.py
index 10e0333..565f865 100755
--- a/catapult/devil/devil/android/tools/device_monitor.py
+++ b/catapult/devil/devil/android/tools/device_monitor.py
@@ -112,9 +112,9 @@
       except ValueError:
         continue
       key = line.split(':')[0].strip()
-      if 'MemTotal' == key:
+      if key == 'MemTotal':
         status['mem']['total'] = value
-      elif 'MemFree' == key:
+      elif key == 'MemFree':
         status['mem']['free'] = value
 
   # Process
@@ -162,7 +162,8 @@
   try:
     status = get_device_status_unsafe(device)
   except device_errors.DeviceUnreachableError:
-    status = {'state': 'offline'}
+    status = collections.defaultdict(dict)
+    status['state'] = 'offline'
   return status
 
 
diff --git a/catapult/devil/devil/android/tools/device_recovery.py b/catapult/devil/devil/android/tools/device_recovery.py
index 0925aae..e8b9ba3 100755
--- a/catapult/devil/devil/android/tools/device_recovery.py
+++ b/catapult/devil/devil/android/tools/device_recovery.py
@@ -8,10 +8,11 @@
 import argparse
 import logging
 import os
-import psutil
 import signal
 import sys
 
+import psutil
+
 if __name__ == '__main__':
   sys.path.append(
       os.path.abspath(os.path.join(os.path.dirname(__file__),
@@ -38,7 +39,7 @@
         # as newer (v2 and over) versions of psutil.
         # See: http://grodola.blogspot.com/2014/01/psutil-20-porting.html
         pinfo = p.as_dict(attrs=['pid', 'name', 'cmdline'])
-        if 'adb' == pinfo['name']:
+        if pinfo['name'] == 'adb':
           pinfo['cmdline'] = ' '.join(pinfo['cmdline'])
           yield p, pinfo
       except (psutil.NoSuchProcess, psutil.AccessDenied):
diff --git a/catapult/devil/devil/android/tools/provision_devices.py b/catapult/devil/devil/android/tools/provision_devices.py
index 68aca3b..3a726e5 100755
--- a/catapult/devil/devil/android/tools/provision_devices.py
+++ b/catapult/devil/devil/android/tools/provision_devices.py
@@ -509,6 +509,8 @@
     logger.info('  %s', prop)
 
 
+# TODO(jbudorick): Relocate this either to device_utils or a separate
+# and more intentionally reusable layer on top of device_utils.
 def CheckExternalStorage(device):
   """Checks that storage is writable and if not makes it writable.
 
diff --git a/catapult/devil/devil/android/tools/script_common_test.py b/catapult/devil/devil/android/tools/script_common_test.py
index 3ddb1c1..30f9aaf 100755
--- a/catapult/devil/devil/android/tools/script_common_test.py
+++ b/catapult/devil/devil/android/tools/script_common_test.py
@@ -18,6 +18,7 @@
   import mock  # pylint: disable=import-error
 
 with devil_env.SysPath(devil_env.DEPENDENCY_MANAGER_PATH):
+  # pylint: disable=wrong-import-order
   from dependency_manager import exceptions
 
 
@@ -90,4 +91,3 @@
 
 if __name__ == '__main__':
   sys.exit(unittest.main())
-
diff --git a/catapult/devil/devil/utils/__init__.py b/catapult/devil/devil/utils/__init__.py
index ff84988..e69de29 100644
--- a/catapult/devil/devil/utils/__init__.py
+++ b/catapult/devil/devil/utils/__init__.py
@@ -1,23 +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.
-
-import os
-import sys
-
-def _JoinPath(*path_parts):
-  return os.path.abspath(os.path.join(*path_parts))
-
-
-def _AddDirToPythonPath(*path_parts):
-  path = _JoinPath(*path_parts)
-  if os.path.isdir(path) and path not in sys.path:
-    # Some call sites that use Telemetry assume that sys.path[0] is the
-    # directory containing the script, so we add these extra paths to right
-    # after sys.path[0].
-    sys.path.insert(1, path)
-
-_CATAPULT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
-                             os.path.pardir, os.path.pardir, os.path.pardir)
-
-_AddDirToPythonPath(_CATAPULT_DIR, 'common', 'battor')
diff --git a/catapult/devil/devil/utils/battor_device_mapping.py b/catapult/devil/devil/utils/battor_device_mapping.py
deleted file mode 100755
index 8cabb83..0000000
--- a/catapult/devil/devil/utils/battor_device_mapping.py
+++ /dev/null
@@ -1,309 +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.
-
-
-'''
-This script provides tools to map BattOrs to phones.
-
-Phones are identified by the following string:
-
-"Phone serial number" - Serial number of the phone. This can be
-obtained via 'adb devices' or 'usb-devices', and is not expected
-to change for a given phone.
-
-BattOrs are identified by the following two strings:
-
-"BattOr serial number" - Serial number of the BattOr. This can be
-obtained via 'usb-devices', and is not expected to change for
-a given BattOr.
-
-"BattOr path" - The path of the form '/dev/ttyUSB*' that is used
-to communicate with the BattOr (the battor_agent binary takes
-this BattOr path as a parameter). The BattOr path is frequently
-reassigned by the OS, most often when the device is disconnected
-and then reconnected. Thus, the BattOr path cannot be expected
-to be stable.
-
-In a typical application, the user will require the BattOr path
-for the BattOr that is plugged into a given phone. For instance,
-the user will be running tracing on a particular phone, and will
-need to know which BattOr path to use to communicate with the BattOr
-to get the corresponding power trace.
-
-Getting this mapping requires two steps: (1) determining the
-mapping between phone serial numbers and BattOr serial numbers, and
-(2) getting the BattOr path corresponding to a given BattOr serial
-number.
-
-For step (1), we generate a JSON file giving this mapping. This
-JSON file consists of a list of items of the following form:
-[{'phone': <phone serial 1>, 'battor': <battor serial 1>},
-{'phone': <phone serial 2>, 'battor': <battor serial 2>}, ...]
-
-The default way to generate this JSON file is using the function
-GenerateSerialMapFile, which generates a mapping based on assuming
-that the system has two identical USB hubs connected to it, and
-the phone plugged into physical port number 1 on one hub corresponds
-to the BattOr plugged into physical port number 1 on the other hub,
-and similarly with physical port numbers 2, 3, etc. This generates
-the map file based on the structure at the time GenerateSerialMapFile called.
-Note that after the map file is generated, port numbers are no longer used;
-the user could move around the devices in the ports without affecting
-which phone goes with which BattOr. (Thus, if the user wanted to update the
-mapping to match the new port connections, the user would have to
-re-generate this file.)
-
-The script update_mapping.py will do this updating from the command line.
-
-If the user wanted to specify a custom mapping, the user could instead
-create the JSON file manually. (In this case, hubs would not be necessary
-and the physical ports connected would be irrelevant.)
-
-Step (2) is conducted through the function GetBattOrPathFromPhoneSerial,
-which takes a serial number mapping generated via step (1) and a phone
-serial number, then gets the corresponding BattOr serial number from the
-map and determines its BattOr path (e.g. /dev/ttyUSB0). Since BattOr paths
-can change if devices are connected and disconnected (even if connected
-or disconnected via the same port) this function should be called to
-determine the BattOr path every time before connecting to the BattOr.
-
-Note that if there is only one BattOr connected to the system, then
-GetBattOrPathFromPhoneSerial will always return that BattOr and will ignore
-the mapping file. Thus, if the user never has more than one BattOr connected
-to the system, the user will not need to generate mapping files.
-'''
-
-
-import json
-import collections
-
-from battor import battor_error
-from devil.utils import find_usb_devices
-from devil.utils import usb_hubs
-
-
-def GetBattOrList(device_tree_map):
-  return [x for x in find_usb_devices.GetTTYList()
-          if IsBattOr(x, device_tree_map)]
-
-
-def IsBattOr(tty_string, device_tree_map):
-  (bus, device) = find_usb_devices.GetBusDeviceFromTTY(tty_string)
-  node = device_tree_map[bus].FindDeviceNumber(device)
-  return '0403:6001' in node.desc
-
-
-def GetBattOrSerialNumbers(device_tree_map):
-  for x in find_usb_devices.GetTTYList():
-    if IsBattOr(x, device_tree_map):
-      (bus, device) = find_usb_devices.GetBusDeviceFromTTY(x)
-      devnode = device_tree_map[bus].FindDeviceNumber(device)
-      yield devnode.serial
-
-
-def ReadSerialMapFile(filename):
-  """Reads JSON file giving phone-to-battor serial number map.
-
-  Parses a JSON file consisting of a list of items of the following form:
-  [{'phone': <phone serial 1>, 'battor': <battor serial 1>},
-  {'phone': <phone serial 2>, 'battor': <battor serial 2>}, ...]
-
-  indicating which phone serial numbers should be matched with
-  which BattOr serial numbers. Returns dictionary of the form:
-
-  {<phone serial 1>: <BattOr serial 1>,
-   <phone serial 2>: <BattOr serial 2>}
-
-  Args:
-      filename: Name of file to read.
-  """
-  result = {}
-  with open(filename, 'r') as infile:
-    in_dict = json.load(infile)
-  for x in in_dict:
-    result[x['phone']] = x['battor']
-  return result
-
-def WriteSerialMapFile(filename, serial_map):
-  """Writes a map of phone serial numbers to BattOr serial numbers to file.
-
-  Writes a JSON file consisting of a list of items of the following form:
-  [{'phone': <phone serial 1>, 'battor': <battor serial 1>},
-  {'phone': <phone serial 2>, 'battor': <battor serial 2>}, ...]
-
-  indicating which phone serial numbers should be matched with
-  which BattOr serial numbers. Mapping is based on the physical port numbers
-  of the hubs that the BattOrs and phones are connected to.
-
-  Args:
-      filename: Name of file to write.
-      serial_map: Serial map {phone: battor}
-  """
-  result = []
-  for (phone, battor) in serial_map.iteritems():
-    result.append({'phone': phone, 'battor': battor})
-  with open(filename, 'w') as outfile:
-    json.dump(result, outfile)
-
-def GenerateSerialMap(hub_types=None):
-  """Generates a map of phone serial numbers to BattOr serial numbers.
-
-  Generates a dict of:
-  {<phone serial 1>: <battor serial 1>,
-   <phone serial 2>: <battor serial 2>}
-  indicating which phone serial numbers should be matched with
-  which BattOr serial numbers. Mapping is based on the physical port numbers
-  of the hubs that the BattOrs and phones are connected to.
-
-  Args:
-      hub_types: List of hub types to check for. If not specified, checks
-      for all defined hub types. (see usb_hubs.py for details)
-  """
-  if hub_types:
-    hub_types = [usb_hubs.GetHubType(x) for x in hub_types]
-  else:
-    hub_types = usb_hubs.ALL_HUBS
-
-  devtree = find_usb_devices.GetBusNumberToDeviceTreeMap()
-
-  # List of serial numbers in the system that represent BattOrs.
-  battor_serials = list(GetBattOrSerialNumbers(devtree))
-
-  # If there's only one BattOr in the system, then a serial number ma
-  # is not necessary.
-  if len(battor_serials) == 1:
-    return {}
-
-  # List of dictionaries, one for each hub, that maps the physical
-  # port number to the serial number of that hub. For instance, in a 2
-  # hub system, this could return [{1:'ab', 2:'cd'}, {1:'jkl', 2:'xyz'}]
-  # where 'ab' and 'cd' are the phone serial numbers and 'jkl' and 'xyz'
-  # are the BattOr serial numbers.
-  port_to_serial = find_usb_devices.GetAllPhysicalPortToSerialMaps(
-      hub_types, device_tree_map=devtree)
-
-  class serials(object):
-    def __init__(self):
-      self.phone = None
-      self.battor = None
-
-  # Map of {physical port number: [phone serial #, BattOr serial #]. This
-  # map is populated by executing the code below. For instance, in the above
-  # example, after the code below is executed, port_to_devices would equal
-  # {1: ['ab', 'jkl'], 2: ['cd', 'xyz']}
-  port_to_devices = collections.defaultdict(serials)
-  for hub in port_to_serial:
-    for (port, serial) in hub.iteritems():
-      if serial in battor_serials:
-        if port_to_devices[port].battor is not None:
-          raise battor_error.BattOrError('Multiple BattOrs on same port number')
-        else:
-          port_to_devices[port].battor = serial
-      else:
-        if port_to_devices[port].phone is not None:
-          raise battor_error.BattOrError('Multiple phones on same port number')
-        else:
-          port_to_devices[port].phone = serial
-
-  # Turn the port_to_devices map into a map of the form
-  # {phone serial number: BattOr serial number}.
-  result = {}
-  for pair in port_to_devices.values():
-    if pair.phone is None:
-      continue
-    if pair.battor is None:
-      raise battor_error.BattOrError(
-          'Phone detected with no corresponding BattOr')
-    result[pair.phone] = pair.battor
-  return result
-
-def GenerateSerialMapFile(filename, hub_types=None):
-  """Generates a serial map file and writes it."""
-  WriteSerialMapFile(filename, GenerateSerialMap(hub_types))
-
-def _PhoneToPathMap(serial, serial_map, devtree):
-  """Maps phone serial number to TTY path, assuming serial map is provided."""
-  try:
-    battor_serial = serial_map[serial]
-  except KeyError:
-    raise battor_error.BattOrError('Serial number not found in serial map.')
-  for tree in devtree.values():
-    for node in tree.AllNodes():
-      if isinstance(node, find_usb_devices.USBDeviceNode):
-        if node.serial == battor_serial:
-          bus_device_to_tty = find_usb_devices.GetBusDeviceToTTYMap()
-          bus_device = (node.bus_num, node.device_num)
-          try:
-            return bus_device_to_tty[bus_device]
-          except KeyError:
-            raise battor_error.BattOrError(
-                'Device with given serial number not a BattOr '
-                '(does not have TTY path)')
-
-
-def GetBattOrPathFromPhoneSerial(serial, serial_map=None,
-                                 serial_map_file=None):
-  """Gets the TTY path (e.g. '/dev/ttyUSB0')  to communicate with the BattOr.
-
-  (1) If serial_map is given, it is treated as a dictionary mapping
-  phone serial numbers to BattOr serial numbers. This function will get the
-  TTY path for the given BattOr serial number.
-
-  (2) If serial_map_file is given, it is treated as the name of a
-  phone-to-BattOr mapping file (generated with GenerateSerialMapFile)
-  and this will be loaded and used as the dict to map port numbers to
-  BattOr serial numbers.
-
-  You can only give one of serial_map and serial_map_file.
-
-  Args:
-    serial: Serial number of phone connected on the same physical port that
-    the BattOr is connected to.
-    serial_map: Map of phone serial numbers to BattOr serial numbers, given
-    as a dictionary.
-    serial_map_file: Map of phone serial numbers to BattOr serial numbers,
-    given as a file.
-    hub_types: List of hub types to check for. Used only if serial_map_file
-    is None.
-
-  Returns:
-    Device string used to communicate with device.
-
-  Raises:
-    ValueError: If serial number is not given.
-    BattOrError: If BattOr not found or unexpected USB topology.
-  """
-  # If there's only one BattOr connected to the system, just use that one.
-  # This allows for use on, e.g., a developer's workstation with no hubs.
-  devtree = find_usb_devices.GetBusNumberToDeviceTreeMap()
-  all_battors = GetBattOrList(devtree)
-  if len(all_battors) == 1:
-    return '/dev/' + all_battors[0]
-
-  if not serial:
-    raise battor_error.BattOrError(
-        'Two or more BattOrs connected, no serial provided')
-
-  if serial_map and serial_map_file:
-    raise ValueError('Cannot specify both serial_map and serial_map_file')
-
-  if serial_map_file:
-    serial_map = ReadSerialMapFile(serial_map_file)
-
-  tty_string = _PhoneToPathMap(serial, serial_map, devtree)
-
-  if not tty_string:
-    raise battor_error.BattOrError(
-        'No device with given serial number detected.')
-
-  if IsBattOr(tty_string, devtree):
-    return '/dev/' + tty_string
-  else:
-    raise battor_error.BattOrError(
-        'Device with given serial number is not a BattOr.')
-
-if __name__ == '__main__':
-  # Main function for testing purposes
-  print GenerateSerialMap()
diff --git a/catapult/devil/devil/utils/cmd_helper.py b/catapult/devil/devil/utils/cmd_helper.py
index b477c70..5ea85d8 100644
--- a/catapult/devil/devil/utils/cmd_helper.py
+++ b/catapult/devil/devil/utils/cmd_helper.py
@@ -187,6 +187,27 @@
   return (status, stdout)
 
 
+def StartCmd(args, cwd=None, shell=False, env=None):
+  """Starts a subprocess and returns a handle to the process.
+
+  Args:
+    args: A string or a sequence of program arguments. The program to execute is
+      the string or the first item in the args sequence.
+    cwd: If not None, the subprocess's current directory will be changed to
+      |cwd| before it's executed.
+    shell: Whether to execute args as a shell command. Must be True if args
+      is a string and False if args is a sequence.
+    env: If not None, a mapping that defines environment variables for the
+      subprocess.
+
+  Returns:
+    A process handle from subprocess.Popen.
+  """
+  _ValidateAndLogCommand(args, cwd, shell)
+  return Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+               shell=shell, cwd=cwd, env=env)
+
+
 def GetCmdStatusOutputAndError(args, cwd=None, shell=False, env=None):
   """Executes a subprocess and returns its exit code, output, and errors.
 
@@ -226,6 +247,7 @@
     process, iter_timeout=None, timeout=None, buffer_size=4096,
     poll_interval=1):
   """An fcntl-based implementation of _IterProcessStdout."""
+  # pylint: disable=too-many-nested-blocks
   import fcntl
   try:
     # Enable non-blocking reads from the child's stdout.
diff --git a/catapult/devil/devil/utils/find_usb_devices.py b/catapult/devil/devil/utils/find_usb_devices.py
index 74b888d..b6dcc70 100755
--- a/catapult/devil/devil/utils/find_usb_devices.py
+++ b/catapult/devil/devil/utils/find_usb_devices.py
@@ -452,9 +452,9 @@
   for line in _GetTtyUSBInfo(tty_string).splitlines():
     bus_match = _BUS_NUM_REGEX.match(line)
     device_match = _DEVICE_NUM_REGEX.match(line)
-    if bus_match and bus_num == None:
+    if bus_match and bus_num is None:
       bus_num = int(bus_match.group(1))
-    if device_match and device_num == None:
+    if device_match and device_num is None:
       device_num = int(device_match.group(1))
   if bus_num is None or device_num is None:
     raise ValueError('Info not found')
diff --git a/catapult/devil/devil/utils/find_usb_devices_test.py b/catapult/devil/devil/utils/find_usb_devices_test.py
index e8b00c8..c22f21b 100755
--- a/catapult/devil/devil/utils/find_usb_devices_test.py
+++ b/catapult/devil/devil/utils/find_usb_devices_test.py
@@ -17,23 +17,21 @@
 Bus 002:
 1: Device 011 "quux"
 2: Device 020 "My Test HUB" #hub 1
-2:1: Device 021 "battor_p7_h1_t0" #physical port 7 on hub 1, on ttyUSB0
-2:3: Device 022 "battor_p5_h1_t1" #physical port 5 on hub 1, on ttyUSB1
+2:1: Device 021 "usb_device_p7_h1_t0" #physical port 7 on hub 1, on ttyUSB0
+2:3: Device 022 "usb_device_p5_h1_t1" #physical port 5 on hub 1, on ttyUSB1
 2:4: Device 023 "My Test Internal HUB" #internal section of hub 1
-2:4:2: Device 024 "battor_p3_h1_t2" #physical port 3 on hub 1, on ttyUSB2
+2:4:2: Device 024 "usb_device_p3_h1_t2" #physical port 3 on hub 1, on ttyUSB2
 2:4:3: Device 026 "Not a Battery Monitor" #physical port 1 on hub 1, on ttyUSB3
-2:4:4: Device 025 "battor_p1_h1_t3" #physical port 1 on hub 1, on ttyUSB3
+2:4:4: Device 025 "usb_device_p1_h1_t3" #physical port 1 on hub 1, on ttyUSB3
 3: Device 100 "My Test HUB" #hub 2
 3:4: Device 101 "My Test Internal HUB" #internal section of hub 2
-3:4:4: Device 102 "battor_p1_h2_t4" #physical port 1 on hub 2, on ttyusb4
+3:4:4: Device 102 "usb_device_p1_h2_t4" #physical port 1 on hub 2, on ttyusb4
 """
 
 import logging
-import os
 import unittest
 
 from devil import devil_env
-from devil.utils import battor_device_mapping
 from devil.utils import find_usb_devices
 from devil.utils import lsusb
 from devil.utils import usb_hubs
@@ -51,15 +49,15 @@
            (1, 13, 'baz'),
            (2, 11, 'quux'),
            (2, 20, 'My Test HUB'),
-           (2, 21, 'ID 0403:6001 battor_p7_h1_t0'),
-           (2, 22, 'ID 0403:6001 battor_p5_h1_t1'),
+           (2, 21, 'ID 0403:6001 usb_device_p7_h1_t0'),
+           (2, 22, 'ID 0403:6001 usb_device_p5_h1_t1'),
            (2, 23, 'My Test Internal HUB'),
-           (2, 24, 'ID 0403:6001 battor_p3_h1_t2'),
-           (2, 25, 'ID 0403:6001 battor_p1_h1_t3'),
+           (2, 24, 'ID 0403:6001 usb_device_p3_h1_t2'),
+           (2, 25, 'ID 0403:6001 usb_device_p1_h1_t3'),
            (2, 26, 'Not a Battery Monitor'),
            (2, 100, 'My Test HUB'),
            (2, 101, 'My Test Internal HUB'),
-           (2, 102, 'ID 0403:6001 battor_p1_h1_t4')]
+           (2, 102, 'ID 0403:6001 usb_device_p1_h1_t4')]
 
 LSUSB_OUTPUT = [
   {'bus': b, 'device': d, 'desc': t, 'id': (1000*b)+d}
@@ -82,14 +80,14 @@
 
 T:  Bus=02 Lev=00 Prnt=00 Port=01 Cnt=00 Dev#= 20 Spd=000 MxCh=00
 T:  Bus=02 Lev=00 Prnt=20 Port=00 Cnt=00 Dev#= 21 Spd=000 MxCh=00
-S:  SerialNumber=BattOr0
+S:  SerialNumber=UsbDevice0
 T:  Bus=02 Lev=00 Prnt=20 Port=02 Cnt=00 Dev#= 22 Spd=000 MxCh=00
-S:  SerialNumber=BattOr1
+S:  SerialNumber=UsbDevice1
 T:  Bus=02 Lev=00 Prnt=20 Port=03 Cnt=00 Dev#= 23 Spd=000 MxCh=00
 T:  Bus=02 Lev=00 Prnt=23 Port=01 Cnt=00 Dev#= 24 Spd=000 MxCh=00
-S:  SerialNumber=BattOr2
+S:  SerialNumber=UsbDevice2
 T:  Bus=02 Lev=00 Prnt=23 Port=03 Cnt=00 Dev#= 25 Spd=000 MxCh=00
-S:  SerialNumber=BattOr3
+S:  SerialNumber=UsbDevice3
 T:  Bus=02 Lev=00 Prnt=23 Port=02 Cnt=00 Dev#= 26 Spd=000 MxCh=00
 
 T:  Bus=02 Lev=00 Prnt=00 Port=02 Cnt=00 Dev#=100 Spd=000 MxCh=00
@@ -103,15 +101,15 @@
 Bus 001 Device 013: baz
 Bus 002 Device 011: quux
 Bus 002 Device 020: My Test HUB
-Bus 002 Device 021: ID 0403:6001 battor_p7_h1_t0
-Bus 002 Device 022: ID 0403:6001 battor_p5_h1_t1
+Bus 002 Device 021: ID 0403:6001 usb_device_p7_h1_t0
+Bus 002 Device 022: ID 0403:6001 usb_device_p5_h1_t1
 Bus 002 Device 023: My Test Internal HUB
-Bus 002 Device 024: ID 0403:6001 battor_p3_h1_t2
-Bus 002 Device 025: ID 0403:6001 battor_p1_h1_t3
+Bus 002 Device 024: ID 0403:6001 usb_device_p3_h1_t2
+Bus 002 Device 025: ID 0403:6001 usb_device_p1_h1_t3
 Bus 002 Device 026: Not a Battery Monitor
 Bus 002 Device 100: My Test HUB
 Bus 002 Device 101: My Test Internal HUB
-Bus 002 Device 102: ID 0403:6001 battor_p1_h1_t4
+Bus 002 Device 102: ID 0403:6001 usb_device_p1_h1_t4
 '''
 
 LIST_TTY_OUTPUT = '''
@@ -213,17 +211,6 @@
     lsusb.raw_lsusb = mock.Mock(
         return_value=RAW_LSUSB_OUTPUT)
 
-  def testIsBattOr(self):
-    bd = find_usb_devices.GetBusNumberToDeviceTreeMap()
-    self.assertTrue(battor_device_mapping.IsBattOr('ttyUSB3', bd))
-    self.assertFalse(battor_device_mapping.IsBattOr('ttyUSB5', bd))
-
-  def testGetBattOrs(self):
-    bd = find_usb_devices.GetBusNumberToDeviceTreeMap()
-    self.assertEquals(battor_device_mapping.GetBattOrList(bd),
-                          ['ttyUSB0', 'ttyUSB1', 'ttyUSB2',
-                           'ttyUSB3', 'ttyUSB4'])
-
   def testGetTTYDevices(self):
     pp = find_usb_devices.GetAllPhysicalPortToTTYMaps([TEST_HUB])
     result = list(pp)
@@ -247,131 +234,49 @@
   def testGetSerialMapping(self):
     pp = find_usb_devices.GetAllPhysicalPortToSerialMaps([TEST_HUB])
     result = list(pp)
-    self.assertEquals(result[0], {7:'BattOr0',
-                                  5:'BattOr1',
-                                  3:'BattOr2',
-                                  1:'BattOr3'})
+    self.assertEquals(result[0], {7:'UsbDevice0',
+                                  5:'UsbDevice1',
+                                  3:'UsbDevice2',
+                                  1:'UsbDevice3'})
     self.assertEquals(result[1], {})
 
   def testFastDeviceDescriptions(self):
     bd = find_usb_devices.GetBusNumberToDeviceTreeMap()
     dev_foo = bd[1].FindDeviceNumber(11)
     dev_bar = bd[1].FindDeviceNumber(12)
-    dev_battor_p7_h1_t0 = bd[2].FindDeviceNumber(21)
+    dev_usb_device_p7_h1_t0 = bd[2].FindDeviceNumber(21)
     self.assertEquals(dev_foo.desc, 'FAST foo')
     self.assertEquals(dev_bar.desc, 'FAST bar')
-    self.assertEquals(dev_battor_p7_h1_t0.desc,
-        'ID 0403:6001 battor_p7_h1_t0')
+    self.assertEquals(dev_usb_device_p7_h1_t0.desc,
+        'ID 0403:6001 usb_device_p7_h1_t0')
 
   def testDeviceDescriptions(self):
     bd = find_usb_devices.GetBusNumberToDeviceTreeMap(fast=False)
     dev_foo = bd[1].FindDeviceNumber(11)
     dev_bar = bd[1].FindDeviceNumber(12)
-    dev_battor_p7_h1_t0 = bd[2].FindDeviceNumber(21)
+    dev_usb_device_p7_h1_t0 = bd[2].FindDeviceNumber(21)
     self.assertEquals(dev_foo.desc, 'foo')
     self.assertEquals(dev_bar.desc, 'bar')
-    self.assertEquals(dev_battor_p7_h1_t0.desc,
-        'ID 0403:6001 battor_p7_h1_t0')
+    self.assertEquals(dev_usb_device_p7_h1_t0.desc,
+        'ID 0403:6001 usb_device_p7_h1_t0')
 
   def testDeviceInformation(self):
     bd = find_usb_devices.GetBusNumberToDeviceTreeMap(fast=False)
     dev_foo = bd[1].FindDeviceNumber(11)
     dev_bar = bd[1].FindDeviceNumber(12)
-    dev_battor_p7_h1_t0 = bd[2].FindDeviceNumber(21)
+    dev_usb_device_p7_h1_t0 = bd[2].FindDeviceNumber(21)
     self.assertEquals(dev_foo.info['id'], 1011)
     self.assertEquals(dev_bar.info['id'], 1012)
-    self.assertEquals(dev_battor_p7_h1_t0.info['id'], 2021)
+    self.assertEquals(dev_usb_device_p7_h1_t0.info['id'], 2021)
 
   def testSerialNumber(self):
     bd = find_usb_devices.GetBusNumberToDeviceTreeMap(fast=False)
     dev_foo = bd[1].FindDeviceNumber(11)
     dev_bar = bd[1].FindDeviceNumber(12)
-    dev_battor_p7_h1_t0 = bd[2].FindDeviceNumber(21)
+    dev_usb_device_p7_h1_t0 = bd[2].FindDeviceNumber(21)
     self.assertEquals(dev_foo.serial, 'FooSerial')
     self.assertEquals(dev_bar.serial, 'BarSerial')
-    self.assertEquals(dev_battor_p7_h1_t0.serial, 'BattOr0')
-
-  def testBattOrDictMapping(self):
-    map_dict = {'Phone1':'BattOr1', 'Phone2':'BattOr2', 'Phone3':'BattOr3'}
-    a1 = battor_device_mapping.GetBattOrPathFromPhoneSerial(
-             'Phone1', serial_map=map_dict)
-    a2 = battor_device_mapping.GetBattOrPathFromPhoneSerial(
-             'Phone2', serial_map=map_dict)
-    a3 = battor_device_mapping.GetBattOrPathFromPhoneSerial(
-             'Phone3', serial_map=map_dict)
-    self.assertEquals(a1, '/dev/ttyUSB1')
-    self.assertEquals(a2, '/dev/ttyUSB2')
-    self.assertEquals(a3, '/dev/ttyUSB3')
-
-  def testBattOrDictFromFileMapping(self):
-    try:
-      map_dict = {'Phone1':'BattOr1', 'Phone2':'BattOr2', 'Phone3':'BattOr3'}
-      curr_dir = os.path.dirname(os.path.realpath(__file__))
-      filename = os.path.join(curr_dir, 'test', 'data', 'test_write_map.json')
-      battor_device_mapping.WriteSerialMapFile(filename, map_dict)
-      a1 = battor_device_mapping.GetBattOrPathFromPhoneSerial(
-               'Phone1', serial_map_file=filename)
-      a2 = battor_device_mapping.GetBattOrPathFromPhoneSerial(
-               'Phone2', serial_map_file=filename)
-      a3 = battor_device_mapping.GetBattOrPathFromPhoneSerial(
-               'Phone3', serial_map_file=filename)
-    finally:
-      os.remove(filename)
-    self.assertEquals(a1, '/dev/ttyUSB1')
-    self.assertEquals(a2, '/dev/ttyUSB2')
-    self.assertEquals(a3, '/dev/ttyUSB3')
-
-  def testReadSerialMapFile(self):
-    curr_dir = os.path.dirname(os.path.realpath(__file__))
-    map_dict = battor_device_mapping.ReadSerialMapFile(
-        os.path.join(curr_dir, 'test', 'data', 'test_serial_map.json'))
-    self.assertEquals(len(map_dict.keys()), 3)
-    self.assertEquals(map_dict['Phone1'], 'BattOr1')
-    self.assertEquals(map_dict['Phone2'], 'BattOr2')
-    self.assertEquals(map_dict['Phone3'], 'BattOr3')
-
-original_PPTSM = find_usb_devices.GetAllPhysicalPortToSerialMaps
-original_PPTTM = find_usb_devices.GetAllPhysicalPortToTTYMaps
-original_GBL = battor_device_mapping.GetBattOrList
-original_GBNDM = find_usb_devices.GetBusNumberToDeviceTreeMap
-original_IB = battor_device_mapping.IsBattOr
-original_GBSM = battor_device_mapping.GetBattOrSerialNumbers
-
-def setup_battor_test(serial, tty, battor, bser=None):
-  serial_mapper = mock.Mock(return_value=serial)
-  tty_mapper = mock.Mock(return_value=tty)
-  battor_lister = mock.Mock(return_value=battor)
-  devtree = mock.Mock(return_value=None)
-  is_battor = mock.Mock(side_effect=lambda x, y: x in battor)
-  battor_serials = mock.Mock(return_value=bser)
-  find_usb_devices.GetAllPhysicalPortToSerialMaps = serial_mapper
-  find_usb_devices.GetAllPhysicalPortToTTYMaps = tty_mapper
-  battor_device_mapping.GetBattOrList = battor_lister
-  find_usb_devices.GetBusNumberToDeviceTreeMap = devtree
-  battor_device_mapping.IsBattOr = is_battor
-  battor_device_mapping.GetBattOrSerialNumbers = battor_serials
-
-class BattOrMappingTest(unittest.TestCase):
-  def tearDown(self):
-    find_usb_devices.GetAllPhysicalPortToSerialMaps = original_PPTSM
-    find_usb_devices.GetAllPhysicalPortToTTYMaps = original_PPTTM
-    battor_device_mapping.GetBattOrList = original_GBL
-    find_usb_devices.GetBusNumberToDeviceTreeMap = original_GBNDM
-    battor_device_mapping.IsBattOr = original_IB
-    battor_device_mapping.GetBattOrSerialNumbers = original_GBSM
-
-  def test_generate_serial_map(self):
-    setup_battor_test([{1:'Phn1', 2:'Phn2', 3:'Phn3'},
-                       {1:'Bat1', 2:'Bat2', 3:'Bat3'}],
-                      [{},
-                       {1:'ttyUSB0', 2:'ttyUSB1', 3:'ttyUSB2'}],
-                      ['ttyUSB0', 'ttyUSB1', 'ttyUSB2'],
-                      ['Bat1', 'Bat2', 'Bat3'])
-    result = battor_device_mapping.GenerateSerialMap()
-    self.assertEqual(len(result), 3)
-    self.assertEqual(result['Phn1'], 'Bat1')
-    self.assertEqual(result['Phn2'], 'Bat2')
-    self.assertEqual(result['Phn3'], 'Bat3')
+    self.assertEquals(dev_usb_device_p7_h1_t0.serial, 'UsbDevice0')
 
 
 if __name__ == "__main__":
diff --git a/catapult/devil/devil/utils/lazy/weak_constant.py b/catapult/devil/devil/utils/lazy/weak_constant.py
index 3558f29..24ad940 100644
--- a/catapult/devil/devil/utils/lazy/weak_constant.py
+++ b/catapult/devil/devil/utils/lazy/weak_constant.py
@@ -4,6 +4,9 @@
 
 import threading
 
+from devil.utils import reraiser_thread
+from devil.utils import timeout_retry
+
 
 class WeakConstant(object):
   """A thread-safe, lazily initialized object.
@@ -13,17 +16,27 @@
   """
 
   def __init__(self, initializer):
-    self._initialized = False
+    self._initialized = threading.Event()
     self._initializer = initializer
     self._lock = threading.Lock()
     self._val = None
 
   def read(self):
     """Get the object, creating it if necessary."""
-    if self._initialized:
+    if self._initialized.is_set():
       return self._val
     with self._lock:
-      if not self._initialized:
-        self._val = self._initializer()
-        self._initialized = True
+      if not self._initialized.is_set():
+        # We initialize the value on a separate thread to protect
+        # from holding self._lock indefinitely in the event that
+        # self._initializer hangs.
+        initializer_thread = reraiser_thread.ReraiserThread(
+            self._initializer)
+        initializer_thread.start()
+        timeout_retry.WaitFor(
+            lambda: initializer_thread.join(1) or not initializer_thread.isAlive(),
+            wait_period=0)
+        self._val = initializer_thread.GetReturnValue()
+        self._initialized.set()
+
     return self._val
diff --git a/catapult/devil/devil/utils/lazy/weak_constant_test.py b/catapult/devil/devil/utils/lazy/weak_constant_test.py
new file mode 100644
index 0000000..9519150
--- /dev/null
+++ b/catapult/devil/devil/utils/lazy/weak_constant_test.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# Copyright 2018 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.
+
+# pylint: disable=protected-access
+
+import time
+import unittest
+
+from devil import devil_env
+from devil.utils import lazy
+from devil.utils import timeout_retry
+
+with devil_env.SysPath(devil_env.PYMOCK_PATH):
+  import mock
+
+
+class DynamicSideEffect(object):
+  """A helper object for handling a sequence of single-use side effects."""
+
+  def __init__(self, side_effects):
+    self._side_effects = iter(side_effects or [])
+
+  def __call__(self):
+    val = next(self._side_effects)()
+    if isinstance(val, Exception):
+      raise val
+    return val
+
+
+class WeakConstantTest(unittest.TestCase):
+
+  def testUninitialized(self):
+    """Ensure that the first read calls the initializer."""
+    initializer = mock.Mock(return_value='initializer called')
+    test_constant = lazy.WeakConstant(initializer)
+    self.assertEquals(
+        'initializer called',
+        test_constant.read())
+    initializer.assert_called_once()
+
+  def testInitialized(self):
+    """Ensure that reading doesn't reinitialize the value."""
+    initializer = mock.Mock(return_value='initializer called')
+    test_constant = lazy.WeakConstant(initializer)
+    test_constant._initialized.set()
+    test_constant._val = 'initializer not called'
+    self.assertEquals(
+        'initializer not called',
+        test_constant.read())
+    initializer.assert_not_called()
+
+  def testFirstCallHangs(self):
+    """Ensure that reading works even if the first initializer call hangs."""
+    dyn = DynamicSideEffect([
+        lambda: time.sleep(10),
+        lambda: 'second try worked!'
+    ])
+
+    initializer = mock.Mock(side_effect=dyn)
+    test_constant = lazy.WeakConstant(initializer)
+    self.assertEquals(
+        'second try worked!',
+        timeout_retry.Run(test_constant.read, 1, 1))
+    initializer.assert_has_calls([mock.call(), mock.call()])
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/catapult/devil/devil/utils/markdown.py b/catapult/devil/devil/utils/markdown.py
index 54e7ed5..6867e9d 100755
--- a/catapult/devil/devil/utils/markdown.py
+++ b/catapult/devil/devil/utils/markdown.py
@@ -182,7 +182,7 @@
     A list of markdown-formatted lines.
   """
   def should_doc(name):
-    return (type(module_obj.__dict__[name]) != types.ModuleType
+    return (not isinstance(module_obj.__dict__[name], types.ModuleType)
             and not name.startswith('_'))
 
   stuff_to_doc = sorted(
@@ -193,9 +193,9 @@
   functions_to_doc = []
 
   for s in stuff_to_doc:
-    if type(s) == types.TypeType:
+    if isinstance(s, types.TypeType):
       classes_to_doc.append(s)
-    elif type(s) == types.FunctionType:
+    elif isinstance(s, types.FunctionType):
       functions_to_doc.append(s)
 
   command = ['devil/utils/markdown.py']
@@ -243,7 +243,7 @@
     content.extend(md_docstring(class_obj.__doc__))
 
   def should_doc(name, obj):
-    return (type(obj) == types.FunctionType
+    return (isinstance(obj, types.FunctionType)
             and (name.startswith('__') or not name.startswith('_')))
 
   methods_to_doc = sorted(
diff --git a/catapult/devil/devil/utils/reraiser_thread.py b/catapult/devil/devil/utils/reraiser_thread.py
index 56d95f3..cb9764e 100644
--- a/catapult/devil/devil/utils/reraiser_thread.py
+++ b/catapult/devil/devil/utils/reraiser_thread.py
@@ -47,10 +47,13 @@
       func: callable to call on a new thread.
       args: list of positional arguments for callable, defaults to empty.
       kwargs: dictionary of keyword arguments for callable, defaults to empty.
-      name: thread name, defaults to Thread-N.
+      name: thread name, defaults to the function name.
     """
-    if not name and func.__name__ != '<lambda>':
-      name = func.__name__
+    if not name:
+      if hasattr(func, '__name__') and func.__name__ != '<lambda>':
+        name = func.__name__
+      else:
+        name = 'anonymous'
     super(ReraiserThread, self).__init__(name=name)
     if not args:
       args = []
diff --git a/catapult/devil/devil/utils/test/data/test_serial_map.json b/catapult/devil/devil/utils/test/data/test_serial_map.json
deleted file mode 100644
index f068281..0000000
--- a/catapult/devil/devil/utils/test/data/test_serial_map.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"phone": "Phone1", "battor": "BattOr1"}, {"phone": "Phone2", "battor": "BattOr2"}, {"phone": "Phone3", "battor": "BattOr3"}]
diff --git a/catapult/devil/devil/utils/timeout_retry.py b/catapult/devil/devil/utils/timeout_retry.py
index 2327b6b..d662c1d 100644
--- a/catapult/devil/devil/utils/timeout_retry.py
+++ b/catapult/devil/devil/utils/timeout_retry.py
@@ -109,7 +109,8 @@
       # pylint: disable=no-member
       timeout_thread_group.GetRemainingTime(wait_period,
           suffix=' waiting for condition %r' % condition_name)
-    time.sleep(wait_period)
+    if wait_period:
+      time.sleep(wait_period)
   return None
 
 
diff --git a/catapult/devil/devil/utils/update_mapping.py b/catapult/devil/devil/utils/update_mapping.py
deleted file mode 100755
index 6666b9b..0000000
--- a/catapult/devil/devil/utils/update_mapping.py
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env 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.
-
-import argparse
-import sys
-
-from devil.utils import battor_device_mapping
-
-def parse_options():
-  """Parses and checks the command-line options.
-
-  Returns:
-    A tuple containing the options structure.
-  """
-  usage = 'Usage: ./update_mapping.py [options]'
-  desc = ('Example: ./update_mapping.py -o mapping.json.\n'
-  'This script generates and stores a file that gives the\n'
-  'mapping between phone serial numbers and BattOr serial numbers\n'
-  'Mapping is based on which physical ports on the USB hubs the\n'
-  'devices are plugged in to. For instance, if there are two hubs,\n'
-  'the phone connected to port N on the first hub is mapped to the\n'
-  'BattOr connected to port N on the second hub, for each N.')
-  parser = argparse.ArgumentParser(usage=usage, description=desc)
-  parser.add_argument('-o', '--output', dest='out_file',
-                      default='mapping.json', type=str,
-                      action='store', help='mapping file name')
-  parser.add_argument('-u', '--hub', dest='hub_types',
-                      action='append', choices=['plugable_7port',
-                                                'plugable_7port_usb3_part2',
-                                                'plugable_7port_usb3_part3'],
-                      help='USB hub types.')
-  options = parser.parse_args()
-  if not options.hub_types:
-    options.hub_types = ['plugable_7port', 'plugable_7port_usb3_part2',
-                         'plugable_7port_usb3_part3']
-  return options
-
-def main():
-  options = parse_options()
-  battor_device_mapping.GenerateSerialMapFile(options.out_file,
-                                              options.hub_types)
-
-if __name__ == "__main__":
-  sys.exit(main())
diff --git a/catapult/devil/pylintrc b/catapult/devil/pylintrc
index 7e024a2..1a059cf 100644
--- a/catapult/devil/pylintrc
+++ b/catapult/devil/pylintrc
@@ -14,6 +14,7 @@
   locally-enabled,
   missing-docstring,
   star-args,
+  wrong-import-position,
 
 
 [REPORTS]
diff --git a/catapult/systrace/PRESUBMIT.py b/catapult/systrace/PRESUBMIT.py
index 1234a8d..275dfdb 100644
--- a/catapult/systrace/PRESUBMIT.py
+++ b/catapult/systrace/PRESUBMIT.py
@@ -25,7 +25,6 @@
   return [
       project_dir,
 
-      input_api.os_path.join(catapult_dir, 'common', 'battor'),
       input_api.os_path.join(catapult_dir, 'common', 'py_trace_event'),
       input_api.os_path.join(catapult_dir, 'common', 'py_utils'),
       input_api.os_path.join(catapult_dir, 'devil'),
diff --git a/catapult/systrace/atrace_helper/jni/atrace_process_dump.cc b/catapult/systrace/atrace_helper/jni/atrace_process_dump.cc
index ef72f78..d4dada0 100644
--- a/catapult/systrace/atrace_helper/jni/atrace_process_dump.cc
+++ b/catapult/systrace/atrace_helper/jni/atrace_process_dump.cc
@@ -4,6 +4,7 @@
 
 #include "atrace_process_dump.h"
 
+#include <inttypes.h>
 #include <stdint.h>
 
 #include <limits>
@@ -37,7 +38,7 @@
 void AtraceProcessDump::RunAndPrintJson(FILE* stream) {
   out_ = stream;
 
-  fprintf(out_, "{\"start_ts\": \"%llu\", \"snapshots\":[\n",
+  fprintf(out_, "{\"start_ts\": \"%" PRIu64 "\", \"snapshots\":[\n",
       time_utils::GetTimestamp());
 
   CHECK(snapshot_timer_);
@@ -132,13 +133,14 @@
 }
 
 void AtraceProcessDump::SerializeSnapshot() {
-  fprintf(out_, "{\"ts\":\"%llu\",\"memdump\":{\n", snapshot_timestamp_);
+  fprintf(out_, "{\"ts\":\"%" PRIu64 "\",\"memdump\":{\n",
+          snapshot_timestamp_);
   for (auto it = snapshot_.begin(); it != snapshot_.end();) {
     const ProcessSnapshot* process = it->second.get();
     const ProcessMemoryStats* mem = &process->memory;
     fprintf(out_, "\"%d\":{", process->pid);
 
-    fprintf(out_, "\"vm\":%llu,\"rss\":%llu",
+    fprintf(out_, "\"vm\":%" PRIu64 ",\"rss\":%" PRIu64,
             mem->virt_kb(), mem->rss_kb());
 
     fprintf(out_, ",\"oom_sc\":%d,\"oom_sc_adj\":%d"
@@ -149,17 +151,18 @@
             process->utime, process->stime);
 
     if (mem->full_stats_available()) {
-      fprintf(out_, ",\"pss\":%llu,\"swp\":%llu"
-                    ",\"pc\":%llu,\"pd\":%llu,\"sc\":%llu,\"sd\":%llu",
+      fprintf(out_, ",\"pss\":%" PRIu64 ",\"swp\":%" PRIu64
+                    ",\"pc\":%" PRIu64 ",\"pd\":%" PRIu64
+                    ",\"sc\":%" PRIu64 ",\"sd\":%" PRIu64,
               mem->pss_kb(), mem->swapped_kb(),
               mem->private_clean_kb(), mem->private_dirty_kb(),
               mem->shared_clean_kb(), mem->shared_dirty_kb());
     }
 
     if (mem->gpu_stats_available()) {
-      fprintf(out_, ",\"gpu_egl\":%llu,\"gpu_egl_pss\":%llu"
-                    ",\"gpu_gl\":%llu,\"gpu_gl_pss\":%llu"
-                    ",\"gpu_etc\":%llu,\"gpu_etc_pss\":%llu",
+      fprintf(out_, ",\"gpu_egl\":%" PRIu64 ",\"gpu_egl_pss\":%" PRIu64
+                    ",\"gpu_gl\":%" PRIu64 ",\"gpu_gl_pss\":%" PRIu64
+                    ",\"gpu_etc\":%" PRIu64 ",\"gpu_etc_pss\":%" PRIu64,
               mem->gpu_graphics_kb(), mem->gpu_graphics_pss_kb(),
               mem->gpu_gl_kb(), mem->gpu_gl_pss_kb(),
               mem->gpu_other_kb(), mem->gpu_other_pss_kb());
@@ -176,11 +179,13 @@
       for (size_t k = 0; k < n_mmaps; ++k) {
         const ProcessMemoryStats::MmapInfo* mm = mem->mmap(k);
         fprintf(out_,
-                "{\"vm\":\"%llx-%llx\",\"file\":\"%s\",\"flags\":\"%s\","
-                "\"pss\":%llu,\"rss\":%llu,\"swp\":%llu,"
-                "\"pc\":%llu,\"pd\":%llu,"
-                "\"sc\":%llu,\"sd\":%llu}",
-                mm->start_addr, mm->end_addr, mm->mapped_file, mm->prot_flags,
+                "{\"vm\":\"%" PRIx64 "-%" PRIx64 "\","
+                "\"file\":\"%s\",\"flags\":\"%s\","
+                "\"pss\":%" PRIu64 ",\"rss\":%" PRIu64 ",\"swp\":%" PRIu64 ","
+                "\"pc\":%" PRIu64 ",\"pd\":%" PRIu64 ","
+                "\"sc\":%" PRIu64 ",\"sd\":%" PRIu64 "}",
+                mm->start_addr, mm->end_addr,
+                mm->mapped_file, mm->prot_flags,
                 mm->pss_kb, mm->rss_kb, mm->swapped_kb,
                 mm->private_clean_kb, mm->private_dirty_kb,
                 mm->shared_clean_kb, mm->shared_dirty_kb);
@@ -233,11 +238,12 @@
 void AtraceProcessDump::TakeAndSerializeMemInfo() {
   std::map<std::string, uint64_t> mem_info;
   CHECK(procfs_utils::ReadMemInfoStats(&mem_info));
-  fprintf(out_, "{\"ts\":\"%llu\",\"meminfo\":{\n", time_utils::GetTimestamp());
+  fprintf(out_, "{\"ts\":\"%" PRIu64 "\",\"meminfo\":{\n",
+          time_utils::GetTimestamp());
   for (auto it = mem_info.begin(); it != mem_info.end(); ++it) {
     if (it != mem_info.begin())
       fprintf(out_, ",");
-    fprintf(out_, "\"%s\":%llu", it->first.c_str(), it->second);
+    fprintf(out_, "\"%s\":%" PRIu64, it->first.c_str(), it->second);
   }
   fprintf(out_, "}}");
 }
diff --git a/catapult/systrace/atrace_helper/jni/logging.h b/catapult/systrace/atrace_helper/jni/logging.h
index c132051..824fddd 100644
--- a/catapult/systrace/atrace_helper/jni/logging.h
+++ b/catapult/systrace/atrace_helper/jni/logging.h
@@ -9,6 +9,7 @@
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 
 #define CHECK_ARGS(COND, ERR)                                          \
   "FAILED CHECK(%s) @ %s:%d (errno: %s)\n", #COND, __FILE__, __LINE__, \
diff --git a/catapult/systrace/atrace_helper/jni/main.cc b/catapult/systrace/atrace_helper/jni/main.cc
index 764b573..68593b4 100644
--- a/catapult/systrace/atrace_helper/jni/main.cc
+++ b/catapult/systrace/atrace_helper/jni/main.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <inttypes.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -45,7 +46,7 @@
   if (argc == 2 && !strcmp(argv[1], "--echo-ts")) {
     // Used by clock sync marker to correct the difference between
     // Linux monotonic clocks on the device and host.
-    printf("%llu\n", time_utils::GetTimestamp());
+    printf("%" PRIu64 "\n", time_utils::GetTimestamp());
     return 0;
   }
 
diff --git a/catapult/systrace/atrace_helper/jni/process_memory_stats.cc b/catapult/systrace/atrace_helper/jni/process_memory_stats.cc
index e13dc22..fbad23b 100644
--- a/catapult/systrace/atrace_helper/jni/process_memory_stats.cc
+++ b/catapult/systrace/atrace_helper/jni/process_memory_stats.cc
@@ -4,6 +4,7 @@
 
 #include "process_memory_stats.h"
 
+#include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
 
@@ -73,7 +74,8 @@
       // Note that the mapped file name ([stack]) is optional and won't be
       // present on anonymous memory maps (hence res >= 3 below).
       int res = sscanf(line,
-          "%llx-%llx %4s %*llx %*[:0-9a-f] %*[0-9a-f]%*[ \t]%127[^\n]",
+          "%" PRIx64 "-%" PRIx64 " %4s %*" PRIx64 " %*[:0-9a-f] "
+          "%*[0-9a-f]%*[ \t]%127[^\n]",
           &new_mmap->start_addr, &new_mmap->end_addr, new_mmap->prot_flags,
           new_mmap->mapped_file);
       last_mmap_entry = new_mmap.get();
diff --git a/catapult/systrace/atrace_helper/jni/time_utils.cc b/catapult/systrace/atrace_helper/jni/time_utils.cc
index 2da7d79..3c8aa16 100644
--- a/catapult/systrace/atrace_helper/jni/time_utils.cc
+++ b/catapult/systrace/atrace_helper/jni/time_utils.cc
@@ -7,6 +7,7 @@
 #include <sys/time.h>
 #include <sys/timerfd.h>
 #include <time.h>
+#include <unistd.h>
 
 #include "logging.h"
 
diff --git a/catapult/systrace/profile_chrome/chrome_startup_tracing_agent.py b/catapult/systrace/profile_chrome/chrome_startup_tracing_agent.py
index e389c15..c1456c1 100644
--- a/catapult/systrace/profile_chrome/chrome_startup_tracing_agent.py
+++ b/catapult/systrace/profile_chrome/chrome_startup_tracing_agent.py
@@ -5,9 +5,10 @@
 import logging
 import optparse
 import os
-import py_utils
 import re
 
+import py_utils
+
 from devil.android import flag_changer
 from devil.android.constants import webapk
 from devil.android.perf import cache_control
diff --git a/catapult/systrace/profile_chrome/chrome_tracing_agent.py b/catapult/systrace/profile_chrome/chrome_tracing_agent.py
index 1e8895b..285d299 100644
--- a/catapult/systrace/profile_chrome/chrome_tracing_agent.py
+++ b/catapult/systrace/profile_chrome/chrome_tracing_agent.py
@@ -5,9 +5,10 @@
 import json
 import optparse
 import os
-import py_utils
 import re
 
+import py_utils
+
 from devil.android import device_errors
 from devil.android.sdk import intent
 from systrace import trace_result
@@ -209,6 +210,8 @@
     categories.append('disabled-by-default-blink.scheduler')
     categories.append('disabled-by-default-cc.debug.scheduler')
     categories.append('disabled-by-default-renderer.scheduler')
+    categories.append('disabled-by-default-sequence_manager')
+    categories.append('sequence_manager')
   if config.chrome_categories:
     categories += config.chrome_categories.split(',')
   return categories
diff --git a/catapult/systrace/profile_chrome/chrome_tracing_agent_unittest.py b/catapult/systrace/profile_chrome/chrome_tracing_agent_unittest.py
index 25c701a..1556762 100644
--- a/catapult/systrace/profile_chrome/chrome_tracing_agent_unittest.py
+++ b/catapult/systrace/profile_chrome/chrome_tracing_agent_unittest.py
@@ -17,7 +17,7 @@
   @decorators.Disabled
   def testGetCategories(self):
     curr_browser = self.GetChromeProcessID()
-    if curr_browser == None:
+    if curr_browser is None:
       self.StartBrowser()
 
     categories = \
@@ -35,7 +35,7 @@
   @decorators.Disabled
   def testTracing(self):
     curr_browser = self.GetChromeProcessID()
-    if curr_browser == None:
+    if curr_browser is None:
       self.StartBrowser()
 
     categories = '*'
diff --git a/catapult/systrace/profile_chrome/ddms_tracing_agent.py b/catapult/systrace/profile_chrome/ddms_tracing_agent.py
index 9d041b9..d978939 100644
--- a/catapult/systrace/profile_chrome/ddms_tracing_agent.py
+++ b/catapult/systrace/profile_chrome/ddms_tracing_agent.py
@@ -4,9 +4,10 @@
 
 import optparse
 import os
-import py_utils
 import re
 
+import py_utils
+
 from profile_chrome import util
 from systrace import trace_result
 from systrace import tracing_agents
diff --git a/catapult/systrace/profile_chrome/perf_tracing_agent.py b/catapult/systrace/profile_chrome/perf_tracing_agent.py
index 65831df..c7ad946 100644
--- a/catapult/systrace/profile_chrome/perf_tracing_agent.py
+++ b/catapult/systrace/profile_chrome/perf_tracing_agent.py
@@ -5,12 +5,13 @@
 import logging
 import optparse
 import os
-import py_utils
 import signal
 import subprocess
 import sys
 import tempfile
 
+import py_utils
+
 from devil.android import device_temp_file
 from devil.android.perf import perf_control
 
@@ -22,9 +23,10 @@
     os.path.dirname(os.path.abspath(__file__)), '..', '..')
 sys.path.append(os.path.join(_CATAPULT_DIR, 'telemetry'))
 try:
-  # pylint: disable=F0401
+  # pylint: disable=F0401,no-name-in-module,wrong-import-position
   from telemetry.internal.platform.profiler import android_profiling_helper
   from telemetry.internal.util import binary_manager
+  # pylint: enable=wrong-import-position
 except ImportError:
   android_profiling_helper = None
   binary_manager = None
diff --git a/catapult/systrace/systrace/__init__.py b/catapult/systrace/systrace/__init__.py
index 831f774..730c164 100644
--- a/catapult/systrace/systrace/__init__.py
+++ b/catapult/systrace/systrace/__init__.py
@@ -19,7 +19,6 @@
 _CATAPULT_DIR = os.path.join(
     os.path.dirname(os.path.abspath(__file__)), os.path.pardir, os.path.pardir)
 
-_AddDirToPythonPath(_CATAPULT_DIR, 'common', 'battor')
 _AddDirToPythonPath(_CATAPULT_DIR, 'common', 'py_utils')
 _AddDirToPythonPath(_CATAPULT_DIR, 'common', 'py_trace_event')
 _AddDirToPythonPath(_CATAPULT_DIR, 'common', 'py_trace_event', 'py_trace_event')
diff --git a/catapult/systrace/systrace/output_generator.py b/catapult/systrace/systrace/output_generator.py
index 6b63ac8..9b2666e 100644
--- a/catapult/systrace/systrace/output_generator.py
+++ b/catapult/systrace/systrace/output_generator.py
@@ -25,7 +25,6 @@
     'androidProcessDump': trace_data.ANDROID_PROCESS_DATA_PART,
     'atraceProcessDump': trace_data.ATRACE_PROCESS_DUMP_PART,
     'systemTraceEvents': trace_data.ATRACE_PART,
-    'powerTraceAsString': trace_data.BATTOR_TRACE_PART,
     'systraceController': trace_data.TELEMETRY_PART,
     'traceEvents': trace_data.CHROME_TRACE_PART,
     'waltTrace': trace_data.WALT_TRACE_PART,
diff --git a/catapult/systrace/systrace/output_generator_unittest.py b/catapult/systrace/systrace/output_generator_unittest.py
index 58e11ac..dba4771 100644
--- a/catapult/systrace/systrace/output_generator_unittest.py
+++ b/catapult/systrace/systrace/output_generator_unittest.py
@@ -19,7 +19,7 @@
 
 TEST_DIR = os.path.join(os.path.dirname(__file__), 'test_data')
 ATRACE_DATA = os.path.join(TEST_DIR, 'atrace_data')
-BATTOR_DATA = os.path.join(TEST_DIR, 'battor_test_data.txt')
+ATRACE_PROCESS_DUMP_DATA = os.path.join(TEST_DIR, 'atrace_procfs_dump')
 COMBINED_PROFILE_CHROME_DATA = os.path.join(
     TEST_DIR, 'profile-chrome_systrace_perf_chrome_data')
 
@@ -76,19 +76,18 @@
     trace_results = []
     trace_data_builder = trace_data_module.TraceDataBuilder()
 
-    with open(BATTOR_DATA) as fp:
-      battor_data = fp.read()
-    trace_results.append(
-        trace_result.TraceResult('powerTraceAsString', battor_data))
-    trace_data_builder.AddTraceFor(
-        trace_data_module.BATTOR_TRACE_PART, battor_data)
-
     with open(ATRACE_DATA) as fp:
       atrace_data = fp.read()
     trace_results.append(
         trace_result.TraceResult('systemTraceEvents', atrace_data))
     trace_data_builder.AddTraceFor(trace_data_module.ATRACE_PART, atrace_data)
 
+    with open(ATRACE_PROCESS_DUMP_DATA) as fp:
+      atrace_process_dump_data = fp.read()
+    trace_results.append(
+        trace_result.TraceResult('atraceProcessDump', atrace_process_dump_data))
+    trace_data_builder.AddTraceFor(trace_data_module.ATRACE_PROCESS_DUMP_PART,
+                                   atrace_process_dump_data)
 
     with open(COMBINED_PROFILE_CHROME_DATA) as fp:
       chrome_data = fp.read()
diff --git a/catapult/systrace/systrace/run_systrace.py b/catapult/systrace/systrace/run_systrace.py
index cb32036..2ca8504 100755
--- a/catapult/systrace/systrace/run_systrace.py
+++ b/catapult/systrace/systrace/run_systrace.py
@@ -14,6 +14,7 @@
 # The flags= parameter of re.sub() is new in Python 2.7. And Systrace does not
 # support Python 3 yet.
 
+# pylint: disable=wrong-import-position
 import sys
 
 version = sys.version_info[:2]
@@ -44,13 +45,13 @@
 from systrace.tracing_agents import atrace_agent
 from systrace.tracing_agents import atrace_from_file_agent
 from systrace.tracing_agents import atrace_process_dump
-from systrace.tracing_agents import battor_trace_agent
 from systrace.tracing_agents import ftrace_agent
 from systrace.tracing_agents import walt_agent
+# pylint: enable=wrong-import-position
 
 
 ALL_MODULES = [atrace_agent, atrace_from_file_agent, atrace_process_dump,
-               battor_trace_agent, ftrace_agent, walt_agent]
+               ftrace_agent, walt_agent]
 
 
 def parse_options(argv):
@@ -158,13 +159,16 @@
 
   if options.target == 'android' and not options.from_file:
     initialize_devil()
+    devices = [a.GetDeviceSerial() for a in adb_wrapper.AdbWrapper.Devices()]
     if not options.device_serial_number:
-      devices = [a.GetDeviceSerial() for a in adb_wrapper.AdbWrapper.Devices()]
       if len(devices) == 0:
         raise RuntimeError('No ADB devices connected.')
       elif len(devices) >= 2:
         raise RuntimeError('Multiple devices connected, serial number required')
       options.device_serial_number = devices[0]
+    elif options.device_serial_number not in devices:
+      raise RuntimeError('Device with the serial number "%s" is not connected.'
+                         % options.device_serial_number)
 
   # If list_categories is selected, just print the list of categories.
   # In this case, use of the tracing controller is not necessary.
diff --git a/catapult/systrace/systrace/systrace_runner.py b/catapult/systrace/systrace/systrace_runner.py
index b3d9324..514f5bb 100644
--- a/catapult/systrace/systrace/systrace_runner.py
+++ b/catapult/systrace/systrace/systrace_runner.py
@@ -14,13 +14,12 @@
 from systrace.tracing_agents import atrace_agent
 from systrace.tracing_agents import atrace_from_file_agent
 from systrace.tracing_agents import atrace_process_dump
-from systrace.tracing_agents import battor_trace_agent
 from systrace.tracing_agents import ftrace_agent
 from systrace.tracing_agents import walt_agent
 
 AGENT_MODULES = [android_process_data_agent, atrace_agent,
                  atrace_from_file_agent, atrace_process_dump,
-                 battor_trace_agent, ftrace_agent, walt_agent]
+                 ftrace_agent, walt_agent]
 
 class SystraceRunner(object):
   def __init__(self, script_dir, options):
diff --git a/catapult/systrace/systrace/systrace_trace_viewer.html b/catapult/systrace/systrace/systrace_trace_viewer.html
index 1b7fb39..29f8d59 100644
--- a/catapult/systrace/systrace/systrace_trace_viewer.html
+++ b/catapult/systrace/systrace/systrace_trace_viewer.html
@@ -614,23 +614,21 @@
     </style>
     <div class="header">
       {{name}}
-      <template if="{{_computeIf(richDetails)}}" is="dom-if">
+      <template if="{{_computeIfSKP(richDetails)}}" is="dom-if">
         <a class="extra" download="drawing.skp" href$="{{_computeHref(richDetails)}}" on-click="{{stopPropagation}}">SKP</a>
       </template>
     </div>
     <div class="details">
-      <template if="{{rawDetails}}">
+      <template if="{{rawDetails}}" is="dom-if">
         <div class="raw-details">{{rawDetails}}</div>
       </template>
-      <template bind="{{richDetails}}" if="{{richDetails}}" is="dom-if">
+      <template if="{{richDetails}}" is="dom-if">
         <dl>
-          <template bind="{{cullRect}}" if="{{cullRect}}" is="dom-if">
-            <dt>Cull rect</dt>
-            <dd>{{x}},{{y}} {{width}}×{{height}}</dd>
-          </template>
-          <template bind="{{visualRect}}" if="{{visualRect}}" is="dom-if">
+          <template if="{{richDetails.visualRect}}" is="dom-if">
             <dt>Visual rect</dt>
-            <dd>{{x}},{{y}} {{width}}×{{height}}</dd>
+            <dd>{{richDetails.visualRect.x}},{{richDetails.visualRect.y}}
+                {{richDetails.visualRect.width}}×{{richDetails.visualRect.height}}
+            </dd>
           </template>
         </dl>
       </template>
@@ -655,7 +653,11 @@
     </display-item-info>
   </left-panel>
   <right-panel>
-    <raster-area><canvas></canvas></raster-area>
+    <raster-area>
+      <canvas-scroller>
+        <canvas></canvas>
+      </canvas-scroller>
+    </raster-area>
   </right-panel>
 </template><template id="quad-stack-view-template">
   <style>
@@ -676,7 +678,9 @@
   <div id="header"></div>
   <input id="stacking-distance-slider" max="400" min="1" step="1" type="range"/>
   
-  <canvas id="canvas"></canvas>
+  <div id="canvas-scroller">
+    <canvas id="canvas"></canvas>
+  </div>
   <img id="chrome-left"/>
   <img id="chrome-mid"/>
   <img id="chrome-right"/>
@@ -756,6 +760,20 @@
     </div>
     <tr-ui-b-table id="content"></tr-ui-b-table>
   </template>
+</dom-module><dom-module id="tr-ui-e-chrome-codesearch">
+  <template>
+    <style>
+      :host {
+        white-space: nowrap;
+      }
+      #codesearchLink {
+        font-size: x-small;
+        margin-left: 20px;
+        text-decoration: none;
+      }
+    </style>
+    <a id="codesearchLink" on-click="onClick" target="_blank">🔍</a>
+  </template>
 </dom-module><style>
 .tr-ui-e-chrome-gpu-state-snapshot-view{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAAZiS0dEAEwATABMYqp3KAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB90JCQsBMCH7ZqYAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAUElEQVRYw+3WwQkAIAiF4Vc0hTO5/wiuURvYIcQOv1cRPhDlDXffSsrMsrYiQi/zU80FAACAVX3nt3lWAABA/x+ovnPyAAAA5AHyAAAA3wMOd34Xd+lsglgAAAAASUVORK5CYII=);display:flex;overflow:auto}.tr-ui-e-chrome-gpu-state-snapshot-view img{display:block;margin:16px auto 16px auto}
 </style><dom-module id="tr-ui-a-layout-tree-sub-view">
@@ -1071,7 +1089,13 @@
 
       #subView {
         flex: 1 1 auto;
-        overflow: auto;
+        min-width: 0;
+        display: flex;
+      }
+
+      #subView > * {
+        flex: 1 1 auto;
+        min-width: 0;
       }
     </style>
     <div hidden="[[tabsHidden]]" id="tabs">
@@ -1802,6 +1826,7 @@
     #content {
       display: flex;
       flex: 1 1 auto;
+      min-width: 0;
     }
     #content > tr-ui-a-related-events {
       margin-left: 8px;
@@ -1818,6 +1843,7 @@
     }
     #content {
       flex: 1 1 auto;
+      min-width: 0;
     }
     </style>
     <tr-ui-a-multi-event-sub-view id="content"></tr-ui-a-multi-event-sub-view>
@@ -4266,7 +4292,7 @@
 const 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){let nameSuffix=isDelta?'Delta':'';nameSuffix+=Unit.nameSuffixForImprovementDirection(improvementDirection);const unitName=params.baseUnitName+nameSuffix;const 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');}
 let scaleBaseUnit=params.scaleBaseUnit;if(!scaleBaseUnit){let formatSpec=params.formatSpec;if(typeof formatSpec==='function')formatSpec=formatSpec();const baseSymbol=formatSpec.unitScale?formatSpec.unitScale[0].baseSymbol:(formatSpec.baseSymbol||'');scaleBaseUnit={value:1,symbol:baseSymbol,baseSymbol};}
-const unit=new Unit(unitName,jsonName,scaleBaseUnit,isDelta,improvementDirection,params.formatSpec);Unit.byName[unitName]=unit;Unit.byJSONName[jsonName]=unit;return unit;};tr.b.EventTarget.decorate(Unit);Unit.reset();Unit.define({baseUnitName:'timeInMsAutoFormat',baseJsonName:'msBestFitFormat',scaleBaseUnit:tr.b.UnitScale.TIME.MILLI_SEC,formatSpec:{unitScale:tr.b.UnitScale.TIME.AUTO,minimumFractionDigits:0,maximumFractionDigits:3}});Unit.define({baseUnitName:'timeDurationInMs',baseJsonName:'ms',scaleBaseUnit:tr.b.UnitScale.TIME.MILLI_SEC,formatSpec(){return Unit.currentTimeDisplayMode_.formatSpec;}});Unit.define({baseUnitName:'timeStampInMs',baseJsonName:'tsMs',scaleBaseUnit:tr.b.UnitScale.TIME.MILLI_SEC,formatSpec(){return Unit.currentTimeDisplayMode_.formatSpec;}});Unit.define({baseUnitName:'normalizedPercentage',baseJsonName:'n%',formatSpec:{unitScale:[{value:0.01,symbol:'%'}],avoidSpacePrecedingUnit:true,minimumFractionDigits:3,maximumFractionDigits:3}});Unit.define({baseUnitName:'sizeInBytes',baseJsonName:'sizeInBytes',formatSpec:{unitScale:tr.b.UnitScale.MEMORY.AUTO,minimumFractionDigits:1,maximumFractionDigits:1}});Unit.define({baseUnitName:'energyInJoules',baseJsonName:'J',formatSpec:{baseSymbol:'J',minimumFractionDigits:3}});Unit.define({baseUnitName:'powerInWatts',baseJsonName:'W',formatSpec:{baseSymbol:'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:{baseSymbol:String.fromCharCode(963),minimumFractionDigits:1,maximumFractionDigits:1}});return{ImprovementDirection,Unit,};});'use strict';tr.exportTo('tr.b',function(){class Scalar{constructor(unit,value){if(!(unit instanceof tr.b.Unit)){throw new Error('Expected Unit');}
+const unit=new Unit(unitName,jsonName,scaleBaseUnit,isDelta,improvementDirection,params.formatSpec);Unit.byName[unitName]=unit;Unit.byJSONName[jsonName]=unit;return unit;};tr.b.EventTarget.decorate(Unit);Unit.reset();Unit.define({baseUnitName:'timeInMsAutoFormat',baseJsonName:'msBestFitFormat',scaleBaseUnit:tr.b.UnitScale.TIME.MILLI_SEC,formatSpec:{unitScale:tr.b.UnitScale.TIME.AUTO,minimumFractionDigits:0,maximumFractionDigits:3}});Unit.define({baseUnitName:'timeDurationInMs',baseJsonName:'ms',scaleBaseUnit:tr.b.UnitScale.TIME.MILLI_SEC,formatSpec(){return Unit.currentTimeDisplayMode_.formatSpec;}});Unit.define({baseUnitName:'timeStampInMs',baseJsonName:'tsMs',scaleBaseUnit:tr.b.UnitScale.TIME.MILLI_SEC,formatSpec(){return Unit.currentTimeDisplayMode_.formatSpec;}});Unit.define({baseUnitName:'normalizedPercentage',baseJsonName:'n%',formatSpec:{unitScale:[{value:0.01,symbol:'%'}],avoidSpacePrecedingUnit:true,minimumFractionDigits:1,maximumFractionDigits:1}});Unit.define({baseUnitName:'sizeInBytes',baseJsonName:'sizeInBytes',formatSpec:{unitScale:tr.b.UnitScale.MEMORY.AUTO,minimumFractionDigits:1,maximumFractionDigits:1}});Unit.define({baseUnitName:'energyInJoules',baseJsonName:'J',formatSpec:{baseSymbol:'J',minimumFractionDigits:3}});Unit.define({baseUnitName:'powerInWatts',baseJsonName:'W',formatSpec:{baseSymbol:'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:{baseSymbol:String.fromCharCode(963),minimumFractionDigits:1,maximumFractionDigits:1}});return{ImprovementDirection,Unit,};});'use strict';tr.exportTo('tr.b',function(){class Scalar{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');}
 this.unit=unit;this.value=value;}
 asDict(){return{unit:this.unit.asJSON(),value:tr.b.numberToJson(this.value),};}
@@ -4293,7 +4319,11 @@
 h/=6;}
 return{h,s,l,a:this.a};},toStringWithAlphaOverride(alpha){return'rgba('+
 this.r+','+this.g+','+
-this.b+','+alpha+')';}};return{Color,};});'use strict';tr.exportTo('tr.b',function(){const 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)];const 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),vsync_highlight_color:new tr.b.Color(0,0,255),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),grey:new tr.b.Color(221,221,221),white:new tr.b.Color(255,255,255),yellow:new tr.b.Color(255,255,0),olive:new tr.b.Color(100,100,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),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)};const numGeneralPurposeColorIds=generalPurposeColors.length;const numReservedColorIds=Object.keys(reservedColorsByName).length;const numColorsPerVariant=numGeneralPurposeColorIds+numReservedColorIds;function ColorScheme(){}
+this.b+','+alpha+')';}};return{Color,};});'use strict';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(key){if(!this.keyToColor[key]){this.keyToColor[key]=this.nextColor();}
+return this.keyToColor[key];},nextColor(){const 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;let r=Math.sin(Math.PI*h);let g=Math.sin(Math.PI*(h+1/3));let b=Math.sin(Math.PI*(h+2/3));r*=r;g*=g;b*=b;const 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.math.lerp(tr.b.math.normalize(brightness,1,2),r,255);g=tr.b.math.lerp(tr.b.math.normalize(brightness,1,2),g,255);b=tr.b.math.lerp(tr.b.math.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,};});'use strict';tr.exportTo('tr.b',function(){const numGeneralPurposeColorIds=23;const generalPurposeColors=new Array(numGeneralPurposeColorIds);const sinebowAlpha=1.0;const sinebowBrightness=1.5;const sinebowColorGenerator=new tr.b.SinebowColorGenerator(sinebowAlpha,sinebowBrightness);for(let i=0;i<numGeneralPurposeColorIds;i++){generalPurposeColors[i]=sinebowColorGenerator.nextColor();}
+const 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),vsync_highlight_color:new tr.b.Color(0,0,255),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),grey:new tr.b.Color(221,221,221),white:new tr.b.Color(255,255,255),yellow:new tr.b.Color(255,255,0),olive:new tr.b.Color(100,100,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),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)};const numReservedColorIds=Object.keys(reservedColorsByName).length;const numColorsPerVariant=numGeneralPurposeColorIds+numReservedColorIds;function ColorScheme(){}
 const paletteBase=[];paletteBase.push.apply(paletteBase,generalPurposeColors);paletteBase.push.apply(paletteBase,Object.values(reservedColorsByName));ColorScheme.colors=[];ColorScheme.properties={};ColorScheme.properties={numColorsPerVariant,};function pushVariant(func){const 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.8);});ColorScheme.properties.brightenedOffsets.push(ColorScheme.colors.length);pushVariant(function(c){return c.lighten(0.48,0.85);});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();});const reservedColorNameToIdMap=(function(){const m=new Map();let i=generalPurposeColors.length;for(const key of Object.keys(reservedColorsByName)){m.set(key,i++);}
 return m;})();ColorScheme.getColorIdForReservedName=function(name){const id=reservedColorNameToIdMap.get(name);if(id===undefined){throw new Error('Unrecognized color '+name);}
@@ -4327,7 +4357,7 @@
 SelectableItem.prototype={get modelItem(){return this.modelItem_;},get selected(){return this.selectionState===SelectionState.SELECTED;},addToSelection(selection){const modelItem=this.modelItem_;if(!modelItem)return;selection.push(modelItem);},addToTrackMap(eventToTrackMap,track){const modelItem=this.modelItem_;if(!modelItem)return;eventToTrackMap.addEvent(modelItem,track);}};return{SelectableItem,};});'use strict';tr.exportTo('tr.model',function(){const SelectableItem=tr.model.SelectableItem;const SelectionState=tr.model.SelectionState;const 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(){const range=new tr.b.math.Range();this.addBoundsToRange(range);return range;},associatedAlerts:IMMUTABLE_EMPTY_SET,addAssociatedAlert(alert){if(this.associatedAlerts===IMMUTABLE_EMPTY_SET){this.associatedAlerts=new tr.model.EventSet();}
 this.associatedAlerts.push(alert);},addBoundsToRange(range){}};return{Event,};});'use strict';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(range){range.addValue(this.start);range.addValue(this.end);},bounds(that,opt_precisionUnit){if(opt_precisionUnit===undefined){opt_precisionUnit=tr.b.TimeDisplayModes.ms;}
+TimedEvent.prototype={__proto__:tr.model.Event.prototype,get end(){return this.start+this.duration;},get boundsRange(){return tr.b.math.Range.fromExplicitRange(this.start,this.end);},addBoundsToRange(range){range.addValue(this.start);range.addValue(this.end);},bounds(that,opt_precisionUnit){if(opt_precisionUnit===undefined){opt_precisionUnit=tr.b.TimeDisplayModes.ms;}
 const startsBefore=opt_precisionUnit.roundedLess(that.start,this.start);const endsAfter=opt_precisionUnit.roundedLess(this.end,that.end);return!startsBefore&&!endsAfter;}};return{TimedEvent,};});'use strict';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,};});'use strict';tr.exportTo('tr.model',function(){const ColorScheme=tr.b.ColorScheme;const Statistics=tr.b.math.Statistics;const 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;}
@@ -4474,9 +4504,13 @@
 for(const s of this.subSlices){yield*s.findTopmostSlicesRelativeToThisSlice(eventPredicate);}},findDescendentSlice(targetTitle){if(!this.subSlices)return undefined;for(let i=0;i<this.subSlices.length;i++){if(this.subSlices[i].title===targetTitle){return this.subSlices[i];}
 const slice=this.subSlices[i].findDescendentSlice(targetTitle);if(slice)return slice;}
 return undefined;},*enumerateAllDescendents(){for(const slice of this.subSlices){yield slice;}
-for(const slice of this.subSlices){if(slice.enumerateAllDescendents!==undefined){yield*slice.enumerateAllDescendents();}}},compareTo(that){return this.title.localeCompare(that.title);}};tr.model.EventRegistry.register(AsyncSlice,{name:'asyncSlice',pluralName:'asyncSlices'});return{AsyncSlice,};});'use strict';tr.exportTo('tr.e.blink',function(){class BlinkSchedulerAsyncSlice extends tr.model.AsyncSlice{get viewSubGroupGroupingKey(){if(this.title.startsWith('WebFrameScheduler.')){return'WebFrame'+this.id;}
-return undefined;}}
-tr.model.AsyncSlice.subTypes.register(BlinkSchedulerAsyncSlice,{categoryParts:['renderer.scheduler','disabled-by-default-renderer.scheduler',]});return{BlinkSchedulerAsyncSlice,};});'use strict';tr.exportTo('tr.model.helpers',function(){const MAIN_FRAMETIME_TYPE='main_frametime_type';const IMPL_FRAMETIME_TYPE='impl_frametime_type';const MAIN_RENDERING_STATS='BenchmarkInstrumentation::MainThreadRenderingStats';const IMPL_RENDERING_STATS='BenchmarkInstrumentation::ImplThreadRenderingStats';function getSlicesIntersectingRange(rangeOfInterest,slices){const slicesInFilterRange=[];for(let i=0;i<slices.length;i++){const slice=slices[i];if(rangeOfInterest.intersectsExplicitRangeInclusive(slice.start,slice.end)){slicesInFilterRange.push(slice);}}
+for(const slice of this.subSlices){if(slice.enumerateAllDescendents!==undefined){yield*slice.enumerateAllDescendents();}}},compareTo(that){return this.title.localeCompare(that.title);}};tr.model.EventRegistry.register(AsyncSlice,{name:'asyncSlice',pluralName:'asyncSlices'});return{AsyncSlice,};});'use strict';tr.exportTo('tr.e.blink',function(){class BlinkSchedulerAsyncSlice extends tr.model.AsyncSlice{get viewSubGroupGroupingKey(){if(this.title.startsWith('FrameScheduler.')){return'Frame'+this.id;}
+if(this.title.startsWith('Scheduler.')){return'Renderer Scheduler';}
+return undefined;}
+get viewSubGroupTitle(){if(this.title.startsWith('FrameScheduler.')){return this.title.substring(15);}
+if(this.title.startsWith('Scheduler.')){return this.title.substring(10);}
+return this.title;}}
+tr.model.AsyncSlice.subTypes.register(BlinkSchedulerAsyncSlice,{categoryParts:['renderer.scheduler','disabled-by-default-renderer.scheduler','disabled-by-default-renderer.scheduler.debug',]});return{BlinkSchedulerAsyncSlice,};});'use strict';tr.exportTo('tr.model.helpers',function(){const MAIN_FRAMETIME_TYPE='main_frametime_type';const IMPL_FRAMETIME_TYPE='impl_frametime_type';const MAIN_RENDERING_STATS='BenchmarkInstrumentation::MainThreadRenderingStats';const IMPL_RENDERING_STATS='BenchmarkInstrumentation::ImplThreadRenderingStats';function getSlicesIntersectingRange(rangeOfInterest,slices){const slicesInFilterRange=[];for(let i=0;i<slices.length;i++){const 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;this.telemetryInternalRanges_=undefined;}
 ChromeProcessHelper.prototype={get pid(){return this.process.pid;},isTelemetryInternalEvent(slice){if(this.telemetryInternalRanges_===undefined){this.findTelemetryInternalRanges_();}
@@ -4534,10 +4568,7 @@
 const INPUT_GSU='InputLatency::GestureScrollUpdate';if(this.title===INPUT_GSU){this.addScrollUpdateEvents(rendererHelper);}},get associatedEvents(){if(this.associatedEvents_.length!==0){return this.associatedEvents_;}
 const modelIndices=this.startThread.parent.model.modelIndices;const flowEvents=modelIndices.getFlowEventsWithId(this.id);if(flowEvents.length===0){return this.associatedEvents_;}
 const sourceSlices=this.addDirectlyAssociatedEvents(flowEvents);const rendererHelper=this.getRendererHelper(sourceSlices);this.addOtherCausallyRelatedEvents(rendererHelper,sourceSlices,flowEvents);return this.associatedEvents_;},get inputLatency(){if(!('data'in this.args))return undefined;const data=this.args.data;if(!(END_COMP_NAME in data))return undefined;let latency=0;const 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;}};const 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'];const allTypeNames=['InputLatency'];eventTypeNames.forEach(function(eventTypeName){allTypeNames.push('InputLatency:'+eventTypeName);allTypeNames.push('InputLatency::'+eventTypeName);});AsyncSlice.subTypes.register(InputLatencyAsyncSlice,{typeNames:allTypeNames,categoryParts:['latencyInfo']});return{InputLatencyAsyncSlice,INPUT_EVENT_TYPE_NAMES,};});'use strict';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(key){if(!this.keyToColor[key]){this.keyToColor[key]=this.nextColor();}
-return this.keyToColor[key];},nextColor(){const 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;let r=Math.sin(Math.PI*h);let g=Math.sin(Math.PI*(h+1/3));let b=Math.sin(Math.PI*(h+2/3));r*=r;g*=g;b*=b;const 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.math.lerp(tr.b.math.normalize(brightness,1,2),r,255);g=tr.b.math.lerp(tr.b.math.normalize(brightness,1,2),g,255);b=tr.b.math.lerp(tr.b.math.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,};});'use strict';tr.exportTo('tr.e.chrome',function(){const SAME_AS_PARENT='same-as-parent';const 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','ImageResourceContent::updateImage',],input:['HitTest','ScrollableArea::scrollPositionChanged','EventHandler::handleMouseMoveEvent',],layout:['DisplayItemList::Finalize','IntersectionObserverController::computeTrackedIntersectionObservations','LocalFrameView::invalidateTree','LocalFrameView::layout','LocalFrameView::performLayout','LocalFrameView::performPostLayoutTasks','LocalFrameView::performPreLayoutTasks','FrameView::invalidateTree','FrameView::layout','FrameView::performLayout','FrameView::performPostLayoutTasks','FrameView::performPreLayoutTasks','Layer::updateLayerPositionsAfterLayout','LayerTreeHostInProcess::UpdateLayers::BuildPropertyTrees','Layout','LayoutView::hitTest','PaintLayer::updateLayerPositionsAfterLayout','ResourceLoadPriorityOptimizer::updateAllImageResourcePriorities','WebViewImpl::layout',],parseHTML:['BackgroundHTMLParser::pumpTokenizer','BackgroundHTMLParser::sendTokensToMainThread','HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser','HTMLDocumentParser::documentElementAvailable','HTMLDocumentParser::notifyPendingTokenizedChunks','HTMLDocumentParser::processParsedChunkFromBackgroundParser','HTMLDocumentParser::processTokenizedChunkFromBackgroundParser','ParseHTML',],raster:['DisplayListRasterSource::PerformSolidColorAnalysis','Picture::Raster','RasterBufferImpl::Playback','RasterTask','RasterizerTaskImpl::RunOnWorkerThread','SkCanvas::drawImageRect()','SkCanvas::drawPicture()','SkCanvas::drawTextBlob()','TileTaskWorkerPool::PlaybackToMemory',],record:['Canvas2DLayerBridge::flushRecordingOnly','CompositingRequirementsUpdater::updateRecursive','ContentLayerDelegate::paintContents','DeprecatedPaintLayerCompositor::updateIfNeededRecursive','DeprecatedPaintLayerCompositor::updateLayerPositionsAfterLayout','LocalFrameView::paintTree','LocalFrameView::prePaint','Paint','PaintController::commitNewDisplayItems','PaintLayerCompositor::updateIfNeededRecursive','Picture::Record','PictureLayer::Update','RenderLayer::updateLayerPositionsAfterLayout',],style:['CSSParserImpl::parseStyleSheet.parse','CSSParserImpl::parseStyleSheet.tokenize','Document::rebuildLayoutTree','Document::recalcStyle','Document::updateActiveStyle','Document::updateStyle','Document::updateStyleInvalidationIfNeeded','LocalFrameView::updateStyleAndLayoutIfNeededRecursive','ParseAuthorStyleSheet','RuleSet::addRulesFromSheet','StyleElement::processStyleSheet','StyleEngine::createResolver','StyleEngine::updateActiveStyleSheets','StyleSheetContents::parseAuthorStyleSheet','UpdateLayoutTree',],script_parse_and_compile:['V8.CompileFullCode','V8.NewContext','V8.Parse','V8.ParseLazy','V8.RecompileSynchronous','V8.ScriptCompiler','v8.compile','v8.parseOnBackground',],script_execute:['EvaluateScript','FunctionCall','HTMLParserScriptRunner ExecuteScript','V8.Execute','V8.RunMicrotasks','V8.Task','WindowProxy::initialize','v8.callFunction','v8.run',],resource_loading:['RenderFrameImpl::didFinishDocumentLoad','RenderFrameImpl::didFinishLoad','Resource::appendData','ResourceDispatcher::OnReceivedData','ResourceDispatcher::OnReceivedResponse','ResourceDispatcher::OnRequestComplete','ResourceFetcher::requestResource','WebURLLoaderImpl::Context::Cancel','WebURLLoaderImpl::Context::OnCompletedRequest','WebURLLoaderImpl::Context::OnReceivedData','WebURLLoaderImpl::Context::OnReceivedRedirect','WebURLLoaderImpl::Context::OnReceivedResponse','WebURLLoaderImpl::Context::Start','WebURLLoaderImpl::loadAsynchronously','WebURLLoaderImpl::loadSynchronously','content::mojom::URLLoaderClient',],renderer_misc:['DecodeFont','ThreadState::completeSweep',],v8_runtime:[],[SAME_AS_PARENT]:['SyncChannel::Send',]};const COLOR_FOR_USER_FRIENDLY_CATEGORY=new tr.b.SinebowColorGenerator();const USER_FRIENDLY_CATEGORY_FOR_TITLE=new Map();for(const category in TITLES_FOR_USER_FRIENDLY_CATEGORY){TITLES_FOR_USER_FRIENDLY_CATEGORY[category].forEach(function(title){USER_FRIENDLY_CATEGORY_FOR_TITLE.set(title,category);});}
+return latency;}};const 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'];const allTypeNames=['InputLatency'];eventTypeNames.forEach(function(eventTypeName){allTypeNames.push('InputLatency:'+eventTypeName);allTypeNames.push('InputLatency::'+eventTypeName);});AsyncSlice.subTypes.register(InputLatencyAsyncSlice,{typeNames:allTypeNames,categoryParts:['latencyInfo']});return{InputLatencyAsyncSlice,INPUT_EVENT_TYPE_NAMES,};});'use strict';tr.exportTo('tr.e.chrome',function(){const SAME_AS_PARENT='same-as-parent';const 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','ImageResourceContent::updateImage',],input:['HitTest','ScrollableArea::scrollPositionChanged','EventHandler::handleMouseMoveEvent',],layout:['DisplayItemList::Finalize','IntersectionObserverController::computeTrackedIntersectionObservations','LocalFrameView::invalidateTree','LocalFrameView::layout','LocalFrameView::performLayout','LocalFrameView::performPostLayoutTasks','LocalFrameView::performPreLayoutTasks','FrameView::invalidateTree','FrameView::layout','FrameView::performLayout','FrameView::performPostLayoutTasks','FrameView::performPreLayoutTasks','Layer::updateLayerPositionsAfterLayout','LayerTreeHostInProcess::UpdateLayers::BuildPropertyTrees','Layout','LayoutView::hitTest','PaintLayer::updateLayerPositionsAfterLayout','ResourceLoadPriorityOptimizer::updateAllImageResourcePriorities','WebViewImpl::layout',],parseHTML:['BackgroundHTMLParser::pumpTokenizer','BackgroundHTMLParser::sendTokensToMainThread','HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser','HTMLDocumentParser::documentElementAvailable','HTMLDocumentParser::notifyPendingTokenizedChunks','HTMLDocumentParser::processParsedChunkFromBackgroundParser','HTMLDocumentParser::processTokenizedChunkFromBackgroundParser','ParseHTML',],raster:['DisplayListRasterSource::PerformSolidColorAnalysis','Picture::Raster','RasterBufferImpl::Playback','RasterTask','RasterizerTaskImpl::RunOnWorkerThread','SkCanvas::drawImageRect()','SkCanvas::drawPicture()','SkCanvas::drawTextBlob()','TileTaskWorkerPool::PlaybackToMemory',],record:['Canvas2DLayerBridge::flushRecordingOnly','CompositingRequirementsUpdater::updateRecursive','ContentLayerDelegate::paintContents','DeprecatedPaintLayerCompositor::updateIfNeededRecursive','DeprecatedPaintLayerCompositor::updateLayerPositionsAfterLayout','LocalFrameView::paintTree','LocalFrameView::prePaint','Paint','PaintController::commitNewDisplayItems','PaintLayerCompositor::updateIfNeededRecursive','Picture::Record','PictureLayer::Update','RenderLayer::updateLayerPositionsAfterLayout',],style:['CSSParserImpl::parseStyleSheet.parse','CSSParserImpl::parseStyleSheet.tokenize','Document::rebuildLayoutTree','Document::recalcStyle','Document::updateActiveStyle','Document::updateStyle','Document::updateStyleInvalidationIfNeeded','LocalFrameView::updateStyleAndLayoutIfNeededRecursive','ParseAuthorStyleSheet','RuleSet::addRulesFromSheet','StyleElement::processStyleSheet','StyleEngine::createResolver','StyleEngine::updateActiveStyleSheets','StyleSheetContents::parseAuthorStyleSheet','UpdateLayoutTree',],script_parse_and_compile:['V8.CompileFullCode','V8.NewContext','V8.Parse','V8.ParseLazy','V8.RecompileSynchronous','V8.ScriptCompiler','v8.compile','v8.parseOnBackground',],script_execute:['EvaluateScript','FunctionCall','HTMLParserScriptRunner ExecuteScript','V8.Execute','V8.RunMicrotasks','V8.Task','WindowProxy::initialize','v8.callFunction','v8.run',],resource_loading:['RenderFrameImpl::didFinishDocumentLoad','RenderFrameImpl::didFinishLoad','Resource::appendData','ResourceDispatcher::OnReceivedData','ResourceDispatcher::OnReceivedResponse','ResourceDispatcher::OnRequestComplete','ResourceFetcher::requestResource','WebURLLoaderImpl::Context::Cancel','WebURLLoaderImpl::Context::OnCompletedRequest','WebURLLoaderImpl::Context::OnReceivedData','WebURLLoaderImpl::Context::OnReceivedRedirect','WebURLLoaderImpl::Context::OnReceivedResponse','WebURLLoaderImpl::Context::Start','WebURLLoaderImpl::loadAsynchronously','WebURLLoaderImpl::loadSynchronously','content::mojom::URLLoaderClient',],renderer_misc:['DecodeFont','ThreadState::completeSweep',],v8_runtime:[],[SAME_AS_PARENT]:['SyncChannel::Send',]};const COLOR_FOR_USER_FRIENDLY_CATEGORY=new tr.b.SinebowColorGenerator();const USER_FRIENDLY_CATEGORY_FOR_TITLE=new Map();for(const category in TITLES_FOR_USER_FRIENDLY_CATEGORY){TITLES_FOR_USER_FRIENDLY_CATEGORY[category].forEach(function(title){USER_FRIENDLY_CATEGORY_FOR_TITLE.set(title,category);});}
 const USER_FRIENDLY_CATEGORY_FOR_EVENT_CATEGORY={netlog:'net',overhead:'overhead',startup:'startup',gpu:'gpu',};function ChromeUserFriendlyCategoryDriver(){}
 ChromeUserFriendlyCategoryDriver.fromEvent=function(event){let 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;}}
 const eventCategoryParts=tr.b.getCategoryParts(event.category);for(let i=0;i<eventCategoryParts.length;++i){const eventCategory=eventCategoryParts[i];userFriendlyCategory=USER_FRIENDLY_CATEGORY_FOR_EVENT_CATEGORY[eventCategory];if(userFriendlyCategory){return userFriendlyCategory;}}
@@ -4662,20 +4693,23 @@
 function forceAllPendingTasksToRunForTest(){if(!rafScheduled&&!idleWorkScheduled)return;processRequests(true,0);}
 function timeout(ms){return new Promise(resolve=>window.setTimeout(resolve,ms));}
 function idle(){return new Promise(resolve=>requestIdleCallback(resolve));}
-return{animationFrame,forceAllPendingTasksToRunForTest,forcePendingRAFTasksToRun,idle,onAnimationFrameError,requestAnimationFrame,requestAnimationFrameInThisFrameIfPossible,requestIdleCallback,requestPreAnimationFrame,timeout,};});'use strict';tr.exportTo('tr.b',function(){class Mark{constructor(groupName,functionName){if(tr.isHeadless)return;this.groupName_=groupName;this.functionName_=functionName;const guid=tr.b.GUID.allocateSimple();this.measureName_=`${groupName} ${functionName}`;this.startMarkName_=`${this.measureName} ${guid} start`;this.endMarkName_=`${this.measureName} ${guid} end`;window.performance.mark(this.startMarkName_);}
+return{animationFrame,forceAllPendingTasksToRunForTest,forcePendingRAFTasksToRun,idle,onAnimationFrameError,requestAnimationFrame,requestAnimationFrameInThisFrameIfPossible,requestIdleCallback,requestPreAnimationFrame,timeout,};});'use strict';tr.exportTo('tr.b',function(){class Mark{constructor(groupName,functionName,opt_timestamp){if(tr.isHeadless)return;this.groupName_=groupName;this.functionName_=functionName;const guid=tr.b.GUID.allocateSimple();this.measureName_=`${groupName} ${functionName}`;if(opt_timestamp){this.startMark_={startTime:opt_timestamp};}else{this.startMarkName_=`${this.measureName} ${guid} start`;}
+this.endMark_=undefined;this.endMarkName_=`${this.measureName} ${guid} end`;window.performance.mark(this.startMarkName_);}
 get groupName(){return this.groupName_;}
 get functionName(){return this.functionName_;}
 get measureName(){return this.measureName_;}
-get startMark(){return tr.b.getOnlyElement(window.performance.getEntriesByName(this.startMarkName_));}
-get endMark(){return tr.b.getOnlyElement(window.performance.getEntriesByName(this.endMarkName_));}
+get startMark(){return this.startMark_||tr.b.getOnlyElement(window.performance.getEntriesByName(this.startMarkName_));}
+get endMark(){return this.endMark_||tr.b.getOnlyElement(window.performance.getEntriesByName(this.endMarkName_));}
 get durationMs(){return this.endMark.startTime-this.startMark.startTime;}
-end(){if(tr.isHeadless)return;window.performance.mark(this.endMarkName_);window.performance.measure(this.measureName_,this.startMarkName_,this.endMarkName_);if(!(window.ga instanceof Function))return;ga('send',{hitType:'event',eventCategory:this.groupName,eventAction:this.functionName,eventValue:this.durationMs,});}}
-class Timing{static mark(groupName,functionName){return new Mark(groupName,functionName);}
+end(opt_timestamp){if(tr.isHeadless)return;if(opt_timestamp){this.endMark_={startTime:opt_timestamp};}else{window.performance.mark(this.endMarkName_);}
+if(!this.startMark_&&!this.endMark_){window.performance.measure(this.measureName_,this.startMarkName_,this.endMarkName_);}else if(Timing.logVoidMarks&&!(window.ga instanceof Function)){console.log('void mark',this.groupName,this.functionName,this.durationMs);}
+if(!(window.ga instanceof Function))return;ga('send',{hitType:'event',eventCategory:this.groupName,eventAction:this.functionName,eventValue:this.durationMs,});}}
+class Timing{static mark(groupName,functionName,opt_timestamp){return new Mark(groupName,functionName,opt_timestamp);}
 static instant(groupName,functionName,opt_value){const valueString=opt_value===undefined?'':' '+opt_value;if(console&&console.timeStamp){console.timeStamp(`${groupName} ${functionName}${valueString}`);}
 if(window&&window.ga instanceof Function){ga('send',{hitType:'event',eventCategory:groupName,eventAction:functionName,eventValue:opt_value,});}}
 static getCurrentTimeMs(){try{return performance.now();}catch(error){}
 return 0;}}
-return{Timing,};});'use strict';tr.exportTo('tr.b',function(){const Timing=tr.b.Timing;function Task(runCb,thisArg){if(runCb!==undefined&&thisArg===undefined&&runCb.prototype!==undefined){throw new Error('Almost certainly you meant to pass a bound callback '+'or thisArg.');}
+Timing.logVoidMarks=false;return{Timing,};});'use strict';tr.exportTo('tr.b',function(){const Timing=tr.b.Timing;function Task(runCb,thisArg){if(runCb!==undefined&&thisArg===undefined&&runCb.prototype!==undefined){throw new Error('Almost certainly you meant to pass a bound callback '+'or thisArg.');}
 this.runCb_=runCb;this.thisArg_=thisArg;this.afterTask_=undefined;this.subTasks_=[];this.updatesUi_=false;}
 Task.prototype={get name(){return this.runCb_.name;},set updatesUi(value){this.updatesUi_=value;},subTask(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(){if(this.runCb_!==undefined)this.runCb_.call(this.thisArg_,this);const subTasks=this.subTasks_;this.subTasks_=undefined;if(!subTasks.length)return this.afterTask_;for(let i=1;i<subTasks.length;i++){subTasks[i-1].afterTask_=subTasks[i];}
@@ -4788,8 +4822,7 @@
 function getSliceHi(s){return s.end;}
 function SliceGroup(parentContainer,opt_sliceConstructor,opt_name){tr.model.EventContainer.call(this);this.parentContainer_=parentContainer;const 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(){if(!this.name_)return undefined;const parentKey=this.parentContainer_.getSettingsKey();if(!parentKey)return undefined;return parentKey+'.'+this.name;},get length(){return this.slices.length;},pushSlice(slice){this.haveTopLevelSlicesBeenBuilt=false;slice.parentContainer=this.parentContainer_;this.slices.push(slice);return slice;},pushSlices(slices){this.haveTopLevelSlicesBeenBuilt=false;slices.forEach(function(slice){slice.parentContainer=this.parentContainer_;this.slices.push(slice);},this);},beginSlice(category,title,ts,opt_args,opt_tts,opt_argsStripped,opt_colorId){if(this.openPartialSlices_.length){const prevSlice=this.openPartialSlices_[this.openPartialSlices_.length-1];if(ts<prevSlice.start){throw new Error('Slices must be added in increasing timestamp order');}}
-const colorId=opt_colorId||ColorScheme.getColorIdForGeneralPurposeString(title);const sliceConstructorSubTypes=this.sliceConstructorSubTypes;const sliceType=sliceConstructorSubTypes.getConstructor(category,title);const 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(ts){if(!this.openPartialSlices_.length)return true;const 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(ts,opt_tts,opt_colorId){if(!this.openSliceCount){throw new Error('endSlice called without an open slice');}
+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(){if(!this.name_)return undefined;const parentKey=this.parentContainer_.getSettingsKey();if(!parentKey)return undefined;return parentKey+'.'+this.name;},get length(){return this.slices.length;},pushSlice(slice){this.haveTopLevelSlicesBeenBuilt=false;slice.parentContainer=this.parentContainer_;this.slices.push(slice);return slice;},pushSlices(slices){this.haveTopLevelSlicesBeenBuilt=false;slices.forEach(function(slice){slice.parentContainer=this.parentContainer_;this.slices.push(slice);},this);},beginSlice(category,title,ts,opt_args,opt_tts,opt_argsStripped,opt_colorId){const colorId=opt_colorId||ColorScheme.getColorIdForGeneralPurposeString(title);const sliceConstructorSubTypes=this.sliceConstructorSubTypes;const sliceType=sliceConstructorSubTypes.getConstructor(category,title);const 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(ts){if(!this.openPartialSlices_.length)return true;const 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(ts,opt_tts,opt_colorId){if(!this.openSliceCount){throw new Error('endSlice called without an open slice');}
 const 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(category,title,ts,duration,tts,cpuDuration,opt_args,opt_argsStripped,opt_colorId,opt_bindId){const colorId=opt_colorId||ColorScheme.getColorIdForGeneralPurposeString(title);const sliceConstructorSubTypes=this.sliceConstructorSubTypes;const sliceType=sliceConstructorSubTypes.getConstructor(category,title);const slice=new sliceType(category,title,colorId,ts,opt_args?opt_args:{},duration,tts,cpuDuration,opt_argsStripped,opt_bindId);if(duration===undefined){slice.didNotFinish=true;}
@@ -4839,9 +4872,7 @@
 for(let i=0;i<this.kernelSliceGroup.length;i++){categoriesDict[this.kernelSliceGroup.slices[i].category]=true;}
 for(let i=0;i<this.asyncSliceGroup.length;i++){categoriesDict[this.asyncSliceGroup.slices[i].category]=true;}
 if(this.samples_){for(let i=0;i<this.samples_.length;i++){categoriesDict[this.samples_[i].category]=true;}}},autoCloseOpenSlices(){this.sliceGroup.autoCloseOpenSlices();this.asyncSliceGroup.autoCloseOpenSlices();this.kernelSliceGroup.autoCloseOpenSlices();},mergeKernelWithUserland(){if(this.kernelSliceGroup.length>0){const newSlices=SliceGroup.merge(this.sliceGroup,this.kernelSliceGroup);this.sliceGroup.slices=newSlices.slices;this.kernelSliceGroup=new SliceGroup(this);this.updateBounds();}},createSubSlices(){this.sliceGroup.createSubSlices();this.samples_=this.parent.model.samples.filter(sample=>sample.thread===this);},get userFriendlyName(){return this.name||this.tid;},get userFriendlyDetails(){return'tid: '+this.tid+
-(this.name?', name: '+this.name:'');},getSettingsKey(){if(!this.name)return undefined;const parentKey=this.parent.getSettingsKey();if(!parentKey)return undefined;return parentKey+'.'+this.name;},getProcess(){return this.parent;},indexOfTimeSlice(timeSlice){const i=tr.b.findLowIndexInSortedArray(this.timeSlices,function(slice){return slice.start;},timeSlice.start);if(this.timeSlices[i]!==timeSlice)return undefined;return i;},getCpuStatsForRange(range){const stats={};stats.total=0;if(!this.timeSlices)return stats;function addStatsForSlice(threadTimeSlice){const freqRange=tr.b.math.Range.fromExplicitRange(threadTimeSlice.start,threadTimeSlice.end);const intersection=freqRange.findIntersection(range);if(threadTimeSlice.schedulingState===tr.model.SCHEDULING_STATE.RUNNING){const 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(start,end){const stats={};if(!this.timeSlices)return stats;function addStatsForSlice(threadTimeSlice){const overlapStart=Math.max(threadTimeSlice.start,start);const overlapEnd=Math.min(threadTimeSlice.end,end);const schedulingState=threadTimeSlice.schedulingState;if(!(schedulingState in stats))stats[schedulingState]=0;stats[schedulingState]+=overlapEnd-overlapStart;}
+(this.name?', name: '+this.name:'');},getSettingsKey(){if(!this.name)return undefined;const parentKey=this.parent.getSettingsKey();if(!parentKey)return undefined;return parentKey+'.'+this.name;},getProcess(){return this.parent;},indexOfTimeSlice(timeSlice){const i=tr.b.findLowIndexInSortedArray(this.timeSlices,function(slice){return slice.start;},timeSlice.start);if(this.timeSlices[i]!==timeSlice)return undefined;return i;},getCpuTimeForRange(range){let totalCpuTime=0;tr.b.iterateOverIntersectingIntervals(this.sliceGroup.topLevelSlices,slice=>slice.start,slice=>slice.end,range.min,range.max,slice=>{if(slice.duration===0)return;if(!slice.cpuDuration)return;const intersection=range.findIntersection(slice.range);const fractionOfSliceInsideRangeOfInterest=intersection.duration/slice.duration;totalCpuTime+=slice.cpuDuration*fractionOfSliceInsideRangeOfInterest;});return totalCpuTime;},getSchedulingStatsForRange(start,end){const stats={};if(!this.timeSlices)return stats;function addStatsForSlice(threadTimeSlice){const overlapStart=Math.max(threadTimeSlice.start,start);const overlapEnd=Math.min(threadTimeSlice.end,end);const 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_;},get type(){const re=/^[^0-9|\/]+/;const matches=re.exec(this.name);if(matches&&matches[0])return matches[0];throw new Error('Could not determine thread type for thread name '+
 this.name);}};Thread.compare=function(x,y){let tmp=x.parent.compareTo(y.parent);if(tmp)return tmp;tmp=x.sortIndex-y.sortIndex;if(tmp)return tmp;if(x.name!==undefined){if(y.name!==undefined){tmp=x.name.localeCompare(y.name);}else{tmp=-1;}}else if(y.name!==undefined){tmp=1;}
 if(tmp)return tmp;return x.tid-y.tid;};return{Thread,};});'use strict';tr.exportTo('tr.model',function(){const Thread=tr.model.Thread;const Counter=tr.model.Counter;function ProcessBase(model){if(!model){throw new Error('Must provide a model');}
@@ -4975,7 +5006,14 @@
 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){const child=new VMRegionClassificationNode(childRule);child.buildChildNodesRecursively_();return child;});},addStatsFromRegion_(region){this.hasRegions=true;const regionSizeInBytes=region.sizeInBytes;if(regionSizeInBytes!==undefined){this.sizeInBytes=(this.sizeInBytes||0)+regionSizeInBytes;}
-const thisByteStats=this.byteStats;const regionByteStats=region.byteStats;for(const byteStatName in regionByteStats){const regionByteStatValue=regionByteStats[byteStatName];if(regionByteStatValue===undefined)continue;thisByteStats[byteStatName]=(thisByteStats[byteStatName]||0)+regionByteStatValue;}}};return{VMRegion,VMRegionClassificationNode,};});'use strict';tr.exportTo('tr.model',function(){const DISCOUNTED_ALLOCATOR_NAMES=['winheap','malloc'];const TRACING_OVERHEAD_PATH=['allocated_objects','tracing_overhead'];const SIZE_NUMERIC_NAME=tr.model.MemoryAllocatorDump.SIZE_NUMERIC_NAME;const RESIDENT_SIZE_NUMERIC_NAME=tr.model.MemoryAllocatorDump.RESIDENT_SIZE_NUMERIC_NAME;function getSizeNumericValue(dump,sizeNumericName){const sizeNumeric=dump.numerics[sizeNumericName];if(sizeNumeric===undefined)return 0;return sizeNumeric.value;}
+const thisByteStats=this.byteStats;const regionByteStats=region.byteStats;for(const byteStatName in regionByteStats){const regionByteStatValue=regionByteStats[byteStatName];if(regionByteStatValue===undefined)continue;thisByteStats[byteStatName]=(thisByteStats[byteStatName]||0)+regionByteStatValue;}
+const textProtectionFlags=(VMRegion.PROTECTION_FLAG_READ|VMRegion.PROTECTION_FLAG_EXECUTE);if((region.protectionFlags===textProtectionFlags)&&(region.mappedFile.includes('/base.apk')||region.mappedFile.includes('/libchrome.so'))){if(regionSizeInBytes!==undefined){this.nativeLibrarySizeInBytes=(this.nativeLibrarySizeInBytes||0)+regionSizeInBytes;}
+if(region.byteStats.privateCleanResident!==undefined){thisByteStats.nativeLibraryPrivateCleanResident=(thisByteStats.nativeLibraryPrivateCleanResident||0)+
+region.byteStats.privateCleanResident;}
+if(region.byteStats.sharedCleanResident!==undefined){thisByteStats.nativeLibrarySharedCleanResident=(thisByteStats.nativeLibrarySharedCleanResident||0)+
+region.byteStats.sharedCleanResident;}
+if(region.byteStats.proportionalResident!==undefined){thisByteStats.nativeLibraryProportionalResident=(thisByteStats.nativeLibraryProportionalResident||0)+
+region.byteStats.proportionalResident;}}}};return{VMRegion,VMRegionClassificationNode,};});'use strict';tr.exportTo('tr.model',function(){const DISCOUNTED_ALLOCATOR_NAMES=['winheap','malloc'];const TRACING_OVERHEAD_PATH=['allocated_objects','tracing_overhead'];const SIZE_NUMERIC_NAME=tr.model.MemoryAllocatorDump.SIZE_NUMERIC_NAME;const RESIDENT_SIZE_NUMERIC_NAME=tr.model.MemoryAllocatorDump.RESIDENT_SIZE_NUMERIC_NAME;function getSizeNumericValue(dump,sizeNumericName){const 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(){const dumps={};dumps[this.process.pid]=this;return dumps;},get hasOwnVmRegions(){return this.vmRegions!==undefined;},setUpTracingOverheadOwnership(opt_model){if(this.tracingOverheadOwnershipSetUp_)return;this.tracingOverheadOwnershipSetUp_=true;const tracingDump=this.getMemoryAllocatorDumpByFullName('tracing');if(tracingDump===undefined||tracingDump.owns!==undefined){return;}
@@ -5093,7 +5131,7 @@
 readNumBytes(opt_size){throw new Error('Not implemented');}
 rewind(){throw new Error('Not implemented');}
 substream(offset,opt_length,opt_headerSize){throw new Error('Not implemented');}}
-return{TraceStream,};});'use strict';tr.exportTo('tr.e.importer.fuchsia',function(){const IMPORT_PRIORITY=0;const IDLE_THREAD_THRESHOLD=6444000000;class FuchsiaImporter extends tr.importer.Importer{constructor(model,eventData){super(model,eventData);this.importPriority=IMPORT_PRIORITY;this.model_=model;this.events_=eventData.events;this.parsers_=[];this.threadInfo_=new Map();this.processNames_=new Map();}
+return{TraceStream,};});'use strict';tr.exportTo('tr.e.importer.fuchsia',function(){const IMPORT_PRIORITY=0;const IDLE_THREAD_THRESHOLD=6444000000;const ZX_THREAD_STATE_NEW=0;const ZX_THREAD_STATE_RUNNING=1;const ZX_THREAD_STATE_SUSPENDED=2;const ZX_THREAD_STATE_BLOCKED=3;const ZX_THREAD_STATE_DYING=4;const ZX_THREAD_STATE_DEAD=5;class FuchsiaImporter extends tr.importer.Importer{constructor(model,eventData){super(model,eventData);this.importPriority=IMPORT_PRIORITY;this.model_=model;this.events_=eventData.events;this.parsers_=[];this.threadInfo_=new Map();this.processNames_=new Map();this.threadStates_=new Map();}
 static canImport(eventData){if(eventData instanceof tr.b.TraceStream){if(eventData.isBinary)return false;eventData=eventData.header;}
 if(eventData instanceof Object&&eventData.type==='fuchsia'){return true;}
 return false;}
@@ -5101,13 +5139,22 @@
 get model(){return this.model_;}
 importClockSyncMarkers(){}
 finalizeImport(){}
-processContextSwitchEvent_(event){let tid=event.in.tid;let threadName=tid.toString();let procName='';if(this.threadInfo_.has(tid)){const threadInfo=this.threadInfo_.get(tid);threadName=threadInfo.name;const pid=threadInfo.pid;if(this.processNames_.has(pid)){procName=this.processNames_.get(pid)+':';}}
-const name=procName+threadName;if(tid>IDLE_THREAD_THRESHOLD){tid=undefined;}
-const cpu=this.model_.kernel.getOrCreateCpu(event.cpu);cpu.switchActiveThread(tr.b.Unit.timestampFromUs(event.ts),{},tid,name,tid);}
+isIdleThread(prio,tid){if(prio===undefined){return tid>IDLE_THREAD_THRESHOLD;}
+return prio===0;}
+recordThreadState_(tid,timestamp,state,prio){if(this.isIdleThread(prio,tid)){return;}
+const states=this.threadStates_.has(tid)?this.threadStates_.get(tid):[];states.push({'ts':timestamp,state});this.threadStates_.set(tid,states);}
+processContextSwitchEvent_(event){let tid=event.in.tid;let threadName=tid.toString();let procName='';const prio=event.in.prio;if(this.threadInfo_.has(tid)){const threadInfo=this.threadInfo_.get(tid);threadName=threadInfo.name;const pid=threadInfo.pid;if(this.processNames_.has(pid)){procName=this.processNames_.get(pid)+':';}}
+const name=procName+threadName;if(this.isIdleThread(prio,tid)){tid=undefined;}
+const cpu=this.model_.kernel.getOrCreateCpu(event.cpu);const timestamp=tr.b.Unit.timestampFromUs(event.ts);cpu.switchActiveThread(timestamp,{},tid,name,tid);const SCHEDULING_STATE=tr.model.SCHEDULING_STATE;this.recordThreadState_(tid,timestamp,SCHEDULING_STATE.RUNNING,prio);let outState=SCHEDULING_STATE.UNKNOWN;switch(event.out.state){case ZX_THREAD_STATE_NEW:outState=SCHEDULING_STATE.RUNNABLE;break;case ZX_THREAD_STATE_RUNNING:outState=SCHEDULING_STATE.RUNNABLE;break;case ZX_THREAD_STATE_BLOCKED:outState=SCHEDULING_STATE.SLEEPING;break;case ZX_THREAD_STATE_SUSPENDED:outState=SCHEDULING_STATE.STOPPED;break;case ZX_THREAD_STATE_DEAD:outState=SCHEDULING_STATE.TASK_DEAD;break;}
+this.recordThreadState_(event.out.tid,timestamp,outState,event.out.prio);}
 processProcessInfoEvent_(event){const process=this.model_.getOrCreateProcess(event.pid);process.name=event.name;this.processNames_.set(event.pid,event.name);if('sort_index'in event){process.sortIndex=event.sort_index;}}
 processThreadInfoEvent_(event){const thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);thread.name=event.name;this.threadInfo_.set(event.tid,{'name':event.name,'pid':event.pid});if('sort_index'in event){const thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);thread.sortIndex=event.sort_index;}}
 processEvent_(event){switch(event.ph){case'k':this.processContextSwitchEvent_(event);break;case'p':this.processProcessInfoEvent_(event);break;case't':this.processThreadInfoEvent_(event);break;}}
-importEvents(){for(const event of this.events_){this.processEvent_(event);}}}
+postProcessStates_(){for(const[tid,states]of this.threadStates_){if(!this.threadInfo_.has(tid)){continue;}
+const pid=this.threadInfo_.get(tid).pid;const thread=this.model_.getOrCreateProcess(pid).getOrCreateThread(tid);const slices=[];for(let i=0;i<states.length-1;i++){slices.push(new tr.model.ThreadTimeSlice(thread,states[i].state,'',states[i].ts,{},states[i+1].ts-states[i].ts));}
+thread.timeSlices=slices;}}
+importEvents(){for(const event of this.events_){this.processEvent_(event);}
+this.postProcessStates_();}}
 tr.importer.Importer.register(FuchsiaImporter);return{FuchsiaImporter,IMPORT_PRIORITY,};});'use strict';tr.exportTo('tr.b',function(){const MAX_FUNCTION_ARGS_COUNT=Math.pow(2,15)-1;class InMemoryTraceStream extends tr.b.TraceStream{constructor(buffer,isBinary,opt_headerSize){super();if(!buffer instanceof Uint8Array){throw new Error('buffer should be a Uint8Array');}
 const headerSize=opt_headerSize||tr.b.TraceStream.HEADER_SIZE;this.data_=buffer;this.isBinary_=isBinary;this.header_=InMemoryTraceStream.uint8ArrayToString_(this.data_.subarray(0,headerSize));this.cursor_=0;}
 get isBinary(){return this.isBinary_;}
@@ -5540,7 +5587,7 @@
 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;}}
 const ts=this.toModelTimeFromUs_(event.ts);ctr.series.forEach(function(series){const val=event.args[series.name]?event.args[series.name]:0;series.addCounterSample(ts,val);});},processObjectEvent(event){const thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);this.allObjectEvents_.push({sequenceNumber:this.allObjectEvents_.length,event,thread});if(thread.guid in this.contextProcessorPerThread){const processor=this.contextProcessorPerThread[thread.guid];const scopedId=TraceEventImporter.scopedIdForEvent_(event);if(event.ph==='D'){processor.destroyContext(scopedId);}
 processor.invalidateContextCacheForSnapshot(scopedId);}},processContextEvent(event){const 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_);}
-const scopedId=TraceEventImporter.scopedIdForEvent_(event);const contextType=event.name;const 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_(thread,slice){if(thread.guid in this.contextProcessorPerThread){slice.contexts=this.contextProcessorPerThread[thread.guid].activeContexts;}},processDurationEvent(event){const thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);const ts=this.toModelTimeFromUs_(event.ts);if(!thread.sliceGroup.isTimestampValidForBeginOrEnd(ts)){this.model_.importWarning({type:'duration_parse_error',message:'Timestamps are moving backward.'});return;}
+const scopedId=TraceEventImporter.scopedIdForEvent_(event);const contextType=event.name;const 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_(thread,slice){if(thread.guid in this.contextProcessorPerThread){slice.contexts=this.contextProcessorPerThread[thread.guid].activeContexts;}},processDurationEvent(event){const thread=this.model_.getOrCreateProcess(event.pid).getOrCreateThread(event.tid);const ts=this.toModelTimeFromUs_(event.ts);if(event.dur===0&&!thread.sliceGroup.isTimestampValidForBeginOrEnd(ts)){this.model_.importWarning({type:'duration_parse_error',message:'Timestamps are moving backward.'});return;}
 if(event.ph==='B'){const 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));const 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;}
 const 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 '+
@@ -5812,7 +5859,7 @@
 case'S':{const ppid=parseInt(eventData[1]);const name=eventData[2];const cookie=parseInt(eventData[3]);const args=parseArgs(eventData[4]);let category=eventData[5];if(category===undefined)category='android';const thread=this.model_.getOrCreateProcess(ppid).getOrCreateThread(pid);thread.name=eventBase.threadName;this.ppids_[pid]=ppid;this.openAsyncSlice(thread,category,name,cookie,ts,args);break;}
 case'F':{const ppid=parseInt(eventData[1]);const name=eventData[2];const cookie=parseInt(eventData[3]);const args=parseArgs(eventData[4]);let category=eventData[5];if(category===undefined)category='android';const thread=this.model_.getOrCreateProcess(ppid).getOrCreateThread(pid);thread.name=eventBase.threadName;this.ppids_[pid]=ppid;this.closeAsyncSlice(thread,category,name,cookie,ts,args);break;}
 default:return false;}
-return true;}};Parser.register(AndroidParser);return{AndroidParser,};});'use strict';tr.exportTo('tr.e.importer.linux_perf',function(){const ColorScheme=tr.b.ColorScheme;const Parser=tr.e.importer.linux_perf.Parser;const binderTransRE=new RegExp('transaction=(\\d+) dest_node=(\\d+) '+'dest_proc=(\\d+) dest_thread=(\\d+) '+'reply=(\\d+) flags=(0x[0-9a-fA-F]+) '+'code=(0x[0-9a-fA-F]+)');const binderTransReceivedRE=/transaction=(\d+)/;function isBinderThread(name){return(name.indexOf('Binder')>-1);}
+return true;}};Parser.register(AndroidParser);return{AndroidParser,};});'use strict';tr.exportTo('tr.e.importer.linux_perf',function(){const ColorScheme=tr.b.ColorScheme;const Parser=tr.e.importer.linux_perf.Parser;const binderTransRE=new RegExp('transaction=(\\d+) dest_node=(\\d+) '+'dest_proc=(\\d+) dest_thread=(\\d+) '+'reply=(\\d+) flags=(0x[0-9a-fA-F]+) '+'code=(0x[0-9a-fA-F]+)');const binderAllocRE=new RegExp('transaction=(\\d+) data_size=(\\d+) '+'offsets_size=(\\d+)');const binderTransReceivedRE=/transaction=(\d+)/;function isBinderThread(name){return(name.indexOf('Binder')>-1);}
 const TF_ONE_WAY=0x01;const TF_ROOT_OBJECT=0x04;const TF_STATUS_CODE=0x08;const TF_ACCEPT_FDS=0x10;const NO_FLAGS=0;function binderFlagsToHuman(num){const flag=parseInt(num,16);let str='';if(flag&TF_ONE_WAY){str+='this is a one-way call: async, no return; ';}
 if(flag&TF_ROOT_OBJECT){str+='contents are the components root object; ';}
 if(flag&TF_STATUS_CODE){str+='contents are a 32-bit status code; ';}
@@ -5827,7 +5874,7 @@
 binderFlagsToHuman(trans.flags),'Code':trans.code+' '+
 binderCodeToHuman(trans.code),'Calling PID':trans.calling_pid,'Calling tgid':trans.calling_kthread.thread.parent.pid};}
 function BinderTransaction(events,callingPid,callingTs,callingKthread){this.transaction_key=parseInt(events[1]);this.dest_node=parseInt(events[2]);this.dest_proc=parseInt(events[3]);this.dest_thread=parseInt(events[4]);this.is_reply_transaction=parseInt(events[5])===1?true:false;this.expect_reply=((this.is_reply_transaction===false)&&(parseInt(events[6],16)&TF_ONE_WAY)===0);this.flags=events[6];this.code=events[7];this.calling_pid=callingPid;this.calling_ts=callingTs;this.calling_kthread=callingKthread;}
-function BinderParser(importer){Parser.call(this,importer);importer.registerEventHandler('binder_locked',BinderParser.prototype.binderLocked.bind(this));importer.registerEventHandler('binder_unlock',BinderParser.prototype.binderUnlock.bind(this));importer.registerEventHandler('binder_lock',BinderParser.prototype.binderLock.bind(this));importer.registerEventHandler('binder_transaction',BinderParser.prototype.binderTransaction.bind(this));importer.registerEventHandler('binder_transaction_received',BinderParser.prototype.binderTransactionReceived.bind(this));this.model_=importer.model;this.kthreadlookup={};this.importer_=importer;this.transWaitingRecv={};this.syncTransWaitingCompletion={};this.recursiveSyncTransWaitingCompletion_ByPID={};this.receivedTransWaitingConversion={};}
+function BinderParser(importer){Parser.call(this,importer);importer.registerEventHandler('binder_locked',BinderParser.prototype.binderLocked.bind(this));importer.registerEventHandler('binder_unlock',BinderParser.prototype.binderUnlock.bind(this));importer.registerEventHandler('binder_lock',BinderParser.prototype.binderLock.bind(this));importer.registerEventHandler('binder_transaction',BinderParser.prototype.binderTransaction.bind(this));importer.registerEventHandler('binder_transaction_received',BinderParser.prototype.binderTransactionReceived.bind(this));importer.registerEventHandler('binder_transaction_alloc_buf',BinderParser.prototype.binderTransactionAllocBuf.bind(this));this.model_=importer.model;this.kthreadlookup={};this.importer_=importer;this.transWaitingRecv={};this.syncTransWaitingCompletion={};this.recursiveSyncTransWaitingCompletion_ByPID={};this.receivedTransWaitingConversion={};}
 BinderParser.prototype={__proto__:Parser.prototype,binderLock(eventName,cpuNumber,pid,ts,eventBase){const tgid=parseInt(eventBase.tgid);if(isNaN(tgid))return false;this.doNameMappings(pid,tgid,eventName.threadName);const kthread=this.importer_.getOrCreateBinderKernelThread(eventBase.threadName,tgid,pid);kthread.binderAttemptLockTS=ts;kthread.binderOpenTsA=ts;return true;},binderLocked(eventName,cpuNumber,pid,ts,eventBase){const tgid=parseInt(eventBase.tgid);if(isNaN(tgid))return false;const binderThread=isBinderThread(eventBase.threadName);const name=eventBase.threadName;const kthread=this.importer_.getOrCreateBinderKernelThread(eventBase.threadName,tgid,pid);this.doNameMappings(pid,tgid,name);const rthread=kthread.thread;kthread.binderLockAquiredTS=ts;if(kthread.binderAttemptLockTS===undefined)return false;const args=this.generateArgsForSlice(tgid,pid,name,kthread);rthread.sliceGroup.pushCompleteSlice('binder','binder lock waiting',kthread.binderAttemptLockTS,ts-kthread.binderAttemptLockTS,0,0,args);kthread.binderAttemptLockTS=undefined;return true;},binderUnlock(eventName,cpuNumber,pid,ts,eventBase){const tgid=parseInt(eventBase.tgid);if(isNaN(tgid))return false;const kthread=this.importer_.getOrCreateBinderKernelThread(eventBase.threadName,tgid,pid);if(kthread.binderLockAquiredTS===undefined)return false;const args=this.generateArgsForSlice(tgid,pid,eventBase.threadName,kthread);kthread.thread.sliceGroup.pushCompleteSlice('binder','binder lock held',kthread.binderLockAquiredTS,ts-kthread.binderLockAquiredTS,0,0,args);kthread.binderLockAquiredTS=undefined;return true;},binderTransaction(eventName,cpuNumber,pid,ts,eventBase){const event=binderTransRE.exec(eventBase.details);if(event===undefined)return false;const tgid=parseInt(eventBase.tgid);if(isNaN(tgid))return false;this.doNameMappings(pid,tgid,eventBase.threadName);const kthread=this.importer_.getOrCreateBinderKernelThread(eventBase.threadName,tgid,pid);const trans=new BinderTransaction(event,pid,ts,kthread);const args=generateBinderArgsForSlice(trans,eventBase.threadName);const priorReceive=this.getPriorReceiveOnPID(pid);if(priorReceive!==false){return this.modelPriorReceive(priorReceive,ts,pid,tgid,kthread,trans,args,event);}
 const recursiveTrans=this.getRecursiveTransactionNeedingCompletion(pid);if(recursiveTrans!==false){return this.modelRecursiveTransactions(recursiveTrans,ts,pid,kthread,trans,args);}
 const slice=kthread.thread.sliceGroup.pushCompleteSlice('binder','',ts,.03,0,0,args);slice.colorId=ColorScheme.getColorIdForGeneralPurposeString(ts.toString());trans.slice=slice;if(trans.expect_reply){slice.title='binder transaction';}else{slice.title='binder transaction async';}
@@ -5836,15 +5883,16 @@
 return true;}
 const trForRecv=this.getTransactionWaitingForRecv(transactionkey);if(trForRecv!==false){if(!trForRecv.expect_reply){const args=generateBinderArgsForSlice(trForRecv,eventBase.threadName);const slice=kthread.thread.sliceGroup.pushCompleteSlice('binder','binder Async recv',ts,.03,0,0,args);const fakeEvent=[0,0,0,0,0,0,0];const fakeTrans=new BinderTransaction(fakeEvent,pid,ts,kthread);const flow=this.generateFlow(trForRecv.slice,slice,trForRecv,fakeTrans);this.model_.flowEvents.push(flow);trForRecv.slice.title='binder transaction async';trForRecv.slice.duration=.03;return true;}
 trForRecv.slice.title='binder transaction';this.setCurrentReceiveOnPID(pid,[ts,trForRecv]);return true;}
-return false;},modelRecursiveTransactions(recursiveTrans,ts,pid,kthread,trans,args){const recursiveSlice=recursiveTrans[1].slice;const origSlice=recursiveTrans[0].slice;recursiveSlice.duration=ts-recursiveSlice.start;trans.slice=recursiveSlice;if(trans.is_reply_transaction){origSlice.duration=ts-origSlice.start;this.addSyncTransNeedingCompletion(trans.transaction_key,recursiveTrans);if(isReplyToOrigin(recursiveTrans[0],trans)){this.removeRecursiveTransaction(pid);}}else{const slice=kthread.thread.sliceGroup.pushCompleteSlice('binder','',ts,.03,0,0,args);trans.slice=slice;this.addTransactionWaitingForRecv(trans.transaction_key,trans);}
-return true;},modelPriorReceive(priorReceive,ts,pid,tgid,kthread,trans,args,event){const calleeSlice=priorReceive[1].slice;const calleeTrans=priorReceive[1];const recvTs=priorReceive[0];let slice=kthread.thread.sliceGroup.pushCompleteSlice('binder','',recvTs,ts-recvTs,0,0,args);const flow=this.generateFlow(calleeSlice,slice,calleeTrans,trans);this.model_.flowEvents.push(flow);trans.slice=slice;if(trans.is_reply_transaction){slice.title='binder reply';this.addSyncTransNeedingCompletion(trans.transaction_key,[calleeTrans,trans]);}else{slice.title='binder reply';const trans1=new BinderTransaction(event,pid,ts,kthread);slice=kthread.thread.sliceGroup.pushCompleteSlice('binder','binder transaction',recvTs,(ts-recvTs),0,0,args);if(!trans.expect_reply){slice.title='binder transaction async';slice.duration=.03;}else{}
+return false;},binderTransactionAllocBuf(eventName,cpuNumber,pid,ts,eventBase){const event=binderAllocRE.exec(eventBase.details);if(event===null)return false;const tgid=parseInt(eventBase.tgid);if(isNaN(tgid))return false;const transactionkey=parseInt(event[1]);const kthread=this.importer_.getOrCreateBinderKernelThread(eventBase.threadName,tgid,pid);const trans=this.peekTransactionWaitingForRecv(transactionkey);if(trans&&trans.slice){trans.slice.args['Data Size']=parseInt(event[2]);trans.slice.args['Offsets Size']=parseInt(event[3]);return true;}
+return false;},modelRecursiveTransactions(recursiveTrans,ts,pid,kthread,trans,args){const recursiveSlice=recursiveTrans[1].slice;const origSlice=recursiveTrans[0].slice;recursiveSlice.duration=ts-recursiveSlice.start;recursiveSlice.args=args;trans.slice=recursiveSlice;if(trans.is_reply_transaction){origSlice.duration=ts-origSlice.start;this.addSyncTransNeedingCompletion(trans.transaction_key,recursiveTrans);if(isReplyToOrigin(recursiveTrans[0],trans)){this.removeRecursiveTransaction(pid);}}else{const slice=kthread.thread.sliceGroup.pushCompleteSlice('binder','',ts,.03,0,0,args);trans.slice=slice;this.addTransactionWaitingForRecv(trans.transaction_key,trans);}
+return true;},modelPriorReceive(priorReceive,ts,pid,tgid,kthread,trans,args,event){const calleeSlice=priorReceive[1].slice;const calleeTrans=priorReceive[1];const recvTs=priorReceive[0];let slice=kthread.thread.sliceGroup.pushCompleteSlice('binder','',recvTs,ts-recvTs,0,0);const flow=this.generateFlow(calleeSlice,slice,calleeTrans,trans);this.model_.flowEvents.push(flow);trans.slice=slice;if(trans.is_reply_transaction){slice.title='binder reply';slice.args=args;this.addSyncTransNeedingCompletion(trans.transaction_key,[calleeTrans,trans]);}else{slice.title='binder reply';const trans1=new BinderTransaction(event,pid,ts,kthread);slice=kthread.thread.sliceGroup.pushCompleteSlice('binder','binder transaction',recvTs,(ts-recvTs),0,0,args);if(!trans.expect_reply){slice.title='binder transaction async';slice.duration=.03;}else{}
 trans1.slice=slice;this.addRecursiveSyncTransNeedingCompletion(pid,[calleeTrans,trans]);this.addTransactionWaitingForRecv(trans.transaction_key,trans1);}
 return true;},getRecursiveTransactionNeedingCompletion(pid){if(this.recursiveSyncTransWaitingCompletion_ByPID[pid]===undefined){return false;}
 const len=this.recursiveSyncTransWaitingCompletion_ByPID[pid].length;if(len===0)return false;return this.recursiveSyncTransWaitingCompletion_ByPID[pid][len-1];},addRecursiveSyncTransNeedingCompletion(pid,tuple){if(this.recursiveSyncTransWaitingCompletion_ByPID[pid]===undefined){this.recursiveSyncTransWaitingCompletion_ByPID[pid]=[];}
 this.recursiveSyncTransWaitingCompletion_ByPID[pid].push(tuple);},removeRecursiveTransaction(pid){const len=this.recursiveSyncTransWaitingCompletion_ByPID[pid].length;if(len===0){delete this.recursiveSyncTransWaitingCompletion_ByPID[pid];return;}
 this.recursiveSyncTransWaitingCompletion_ByPID[pid].splice(len-1,1);},setCurrentReceiveOnPID(pid,tuple){if(this.receivedTransWaitingConversion[pid]===undefined){this.receivedTransWaitingConversion[pid]=[];}
 this.receivedTransWaitingConversion[pid].push(tuple);},getPriorReceiveOnPID(pid){if(this.receivedTransWaitingConversion[pid]===undefined){return false;}
-const len=this.receivedTransWaitingConversion[pid].length;if(len===0)return false;return this.receivedTransWaitingConversion[pid].splice(len-1,1)[0];},addSyncTransNeedingCompletion(transactionkey,tuple){const dict=this.syncTransWaitingCompletion;dict[transactionkey]=tuple;},getSyncTransNeedsCompletion(transactionkey){const ret=this.syncTransWaitingCompletion[transactionkey];if(ret===undefined)return false;delete this.syncTransWaitingCompletion[transactionkey];return ret;},getTransactionWaitingForRecv(transactionkey){const ret=this.transWaitingRecv[transactionkey];if(ret===undefined)return false;delete this.transWaitingRecv[transactionkey];return ret;},addTransactionWaitingForRecv(transactionkey,transaction){this.transWaitingRecv[transactionkey]=transaction;},generateFlow(from,to,fromTrans,toTrans){const title='Transaction from : '+
+const len=this.receivedTransWaitingConversion[pid].length;if(len===0)return false;return this.receivedTransWaitingConversion[pid].splice(len-1,1)[0];},addSyncTransNeedingCompletion(transactionkey,tuple){const dict=this.syncTransWaitingCompletion;dict[transactionkey]=tuple;},getSyncTransNeedsCompletion(transactionkey){const ret=this.syncTransWaitingCompletion[transactionkey];if(ret===undefined)return false;delete this.syncTransWaitingCompletion[transactionkey];return ret;},getTransactionWaitingForRecv(transactionkey){const ret=this.transWaitingRecv[transactionkey];if(ret===undefined)return false;delete this.transWaitingRecv[transactionkey];return ret;},peekTransactionWaitingForRecv(transactionkey){const ret=this.transWaitingRecv[transactionkey];if(ret===undefined)return false;return ret;},addTransactionWaitingForRecv(transactionkey,transaction){this.transWaitingRecv[transactionkey]=transaction;},generateFlow(from,to,fromTrans,toTrans){const title='Transaction from : '+
 this.pid2name(fromTrans.calling_pid)+' From PID: '+fromTrans.calling_pid+' to pid: '+
 toTrans.calling_pid+' Thread Name: '+this.pid2name(toTrans.calling_pid);const ts=from.start;const flow=new tr.model.FlowEvent('binder','binder',title,1,ts,[]);flow.startSlice=from;flow.endSlice=to;flow.start=from.start;flow.duration=to.start-ts;from.outFlowEvents.push(flow);to.inFlowEvents.push(flow);return flow;},generateArgsForSlice(tgid,pid,name,kthread){return{'Thread Name':name,pid,'gid':tgid};},pid2name(pid){return this.kthreadlookup[pid];},doNameMappings(pid,tgid,name){this.registerPidName(pid,name);this.registerPidName(tgid,name);},registerPidName(pid,name){if(this.pid2name(pid)===undefined){this.kthreadlookup[pid]=name;}}};Parser.register(BinderParser);return{BinderParser,};});'use strict';tr.exportTo('tr.e.importer.linux_perf',function(){const ColorScheme=tr.b.ColorScheme;const Parser=tr.e.importer.linux_perf.Parser;function BusParser(importer){Parser.call(this,importer);importer.registerEventHandler('memory_bus_usage',BusParser.prototype.traceMarkWriteBusEvent.bind(this));this.model_=importer.model_;this.ppids_={};}
 BusParser.prototype={__proto__:Parser.prototype,traceMarkWriteBusEvent(eventName,cpuNumber,pid,ts,eventBase,threadName){const re=new RegExp('bus=(\\S+) rw_bytes=(\\d+) r_bytes=(\\d+) '+'w_bytes=(\\d+) cycles=(\\d+) ns=(\\d+)');const event=re.exec(eventBase.details);const name=event[1];const rwBytes=parseInt(event[2]);const rBytes=parseInt(event[3]);const wBytes=parseInt(event[4]);const cycles=parseInt(event[5]);const ns=parseInt(event[6]);const sec=tr.b.convertUnit(ns,tr.b.UnitPrefixScale.METRIC.NANO,tr.b.UnitPrefixScale.METRIC.NONE);const readBandwidthInBps=rBytes/sec;const readBandwidthInMiBps=tr.b.convertUnit(readBandwidthInBps,tr.b.UnitPrefixScale.BINARY.NONE,tr.b.UnitPrefixScale.BINARY.MEBI);const writeBandwidthInBps=wBytes/sec;const writeBandwidthInMiBps=tr.b.convertUnit(writeBandwidthInBps,tr.b.UnitPrefixScale.BINARY.NONE,tr.b.UnitPrefixScale.BINARY.MEBI);let ctr=this.model_.kernel.getOrCreateCounter(null,'bus '+name+' read');if(ctr.numSeries===0){ctr.addSeries(new tr.model.CounterSeries('value',ColorScheme.getColorIdForGeneralPurposeString(ctr.name+'.'+'value')));}
@@ -5867,7 +5915,18 @@
 ExynosParser.prototype={__proto__:Parser.prototype,exynosBusfreqSample(name,ts,frequency){const targetCpu=this.importer.getOrCreateCpu(0);const counter=targetCpu.getOrCreateCounter('',name);if(counter.numSeries===0){counter.addSeries(new tr.model.CounterSeries('frequency',ColorScheme.getColorIdForGeneralPurposeString(counter.name+'.'+'frequency')));}
 counter.series.forEach(function(series){series.addCounterSample(ts,frequency);});},busfreqTargetIntEvent(eventName,cpuNumber,pid,ts,eventBase){const event=/frequency=(\d+)/.exec(eventBase.details);if(!event)return false;this.exynosBusfreqSample('INT Frequency',ts,parseInt(event[1]));return true;},busfreqTargetMifEvent(eventName,cpuNumber,pid,ts,eventBase){const event=/frequency=(\d+)/.exec(eventBase.details);if(!event)return false;this.exynosBusfreqSample('MIF Frequency',ts,parseInt(event[1]));return true;},exynosPageFlipStateOpenSlice(ts,pipe,fb,state){const kthread=this.importer.getOrCreatePseudoThread('exynos_flip_state (pipe:'+pipe+', fb:'+fb+')');kthread.openSliceTS=ts;kthread.openSlice=state;},exynosPageFlipStateCloseSlice(ts,pipe,fb,args){const kthread=this.importer.getOrCreatePseudoThread('exynos_flip_state (pipe:'+pipe+', fb:'+fb+')');if(kthread.openSlice){const slice=new tr.model.ThreadSlice('',kthread.openSlice,ColorScheme.getColorIdForGeneralPurposeString(kthread.openSlice),kthread.openSliceTS,args,ts-kthread.openSliceTS);kthread.thread.sliceGroup.pushSlice(slice);}
 kthread.openSlice=undefined;},pageFlipStateEvent(eventName,cpuNumber,pid,ts,eventBase){const event=/pipe=(\d+), fb=(\d+), state=(.*)/.exec(eventBase.details);if(!event)return false;const pipe=parseInt(event[1]);const fb=parseInt(event[2]);const state=event[3];this.exynosPageFlipStateCloseSlice(ts,pipe,fb,{pipe,fb});if(state!=='flipped'){this.exynosPageFlipStateOpenSlice(ts,pipe,fb,state);}
-return true;}};Parser.register(ExynosParser);return{ExynosParser,};});'use strict';tr.exportTo('tr.e.importer.linux_perf',function(){const Parser=tr.e.importer.linux_perf.Parser;function GestureParser(importer){Parser.call(this,importer);importer.registerEventHandler('tracing_mark_write:log',GestureParser.prototype.logEvent.bind(this));importer.registerEventHandler('tracing_mark_write:SyncInterpret',GestureParser.prototype.syncEvent.bind(this));importer.registerEventHandler('tracing_mark_write:HandleTimer',GestureParser.prototype.timerEvent.bind(this));}
+return true;}};Parser.register(ExynosParser);return{ExynosParser,};});'use strict';tr.exportTo('tr.e.importer.linux_perf',function(){const ColorScheme=tr.b.ColorScheme;const Parser=tr.e.importer.linux_perf.Parser;function FenceParser(importer){Parser.call(this,importer);this.model_=importer.model_;importer.registerEventHandler('fence_init',FenceParser.prototype.initEvent.bind(this));importer.registerEventHandler('fence_destroy',FenceParser.prototype.fenceDestroyEvent.bind(this));importer.registerEventHandler('fence_enable_signal',FenceParser.prototype.fenceEnableSignalEvent.bind(this));importer.registerEventHandler('fence_signaled',FenceParser.prototype.fenceSignaledEvent.bind(this));this.model_=importer.model_;}
+const fenceRE=/driver=(\S+) timeline=(\S+) context=(\d+) seqno=(\d+)/;FenceParser.prototype={__proto__:Parser.prototype,initEvent(eventName,cpuNumber,pid,ts,eventBase){const event=fenceRE.exec(eventBase.details);if(!event)return false;if(eventBase.tgid===undefined){return false;}
+const thread=this.importer.getOrCreatePseudoThread(event[2]);thread.lastActiveTs=ts;return true;},fenceDestroyEvent(eventName,cpuNumber,pid,ts,eventBase){const event=fenceRE.exec(eventBase.details);if(!event)return false;if(eventBase.tgid===undefined){return false;}
+const thread=this.importer.getOrCreatePseudoThread(event[2]);const name='fence_destroy('+event[4]+')';const colorName='fence('+event[4]+')';if(thread.lastActiveTs!==undefined){const duration=ts-thread.lastActiveTs;const slice=new tr.model.ThreadSlice('',name,ColorScheme.getColorIdForGeneralPurposeString(colorName),thread.lastActiveTs,{'driver':event[1],'context':event[3]},duration);thread.thread.sliceGroup.pushSlice(slice);}
+if(thread.thread.sliceGroup.openSliceCount>0){thread.thread.sliceGroup.endSlice(ts);}
+thread.lastActiveTs=ts;},fenceEnableSignalEvent(eventName,cpuNumber,pid,ts,eventBase){const event=fenceRE.exec(eventBase.details);if(!event)return false;if(eventBase.tgid===undefined){return false;}
+const thread=this.importer.getOrCreatePseudoThread(event[2]);const name='fence_enable('+event[4]+')';const colorName='fence('+event[4]+')';if(thread.lastActiveTs!==undefined){const duration=ts-thread.lastActiveTs;const slice=new tr.model.ThreadSlice('',name,ColorScheme.getColorIdForGeneralPurposeString(colorName),thread.lastActiveTs,{'driver':event[1],'context':event[3]},duration);thread.thread.sliceGroup.pushSlice(slice);}
+if(thread.thread.sliceGroup.openSliceCount>0){thread.thread.sliceGroup.endSlice(ts);}
+thread.lastActiveTs=ts;},fenceSignaledEvent(eventName,cpuNumber,pid,ts,eventBase){const event=fenceRE.exec(eventBase.details);if(!event)return false;if(eventBase.tgid===undefined){return false;}
+const thread=this.importer.getOrCreatePseudoThread(event[2]);const name='fence_signal('+event[4]+')';const colorName='fence('+event[4]+')';if(thread.lastActiveTs!==undefined){const duration=ts-thread.lastActiveTs;const slice=new tr.model.ThreadSlice('',name,ColorScheme.getColorIdForGeneralPurposeString(colorName),thread.lastActiveTs,{'driver':event[1],'context':event[3]},duration);thread.thread.sliceGroup.pushSlice(slice);}
+if(thread.thread.sliceGroup.openSliceCount>0){thread.thread.sliceGroup.endSlice(ts);}
+thread.lastActiveTs=ts;return true;},};Parser.register(FenceParser);return{FenceParser,};});'use strict';tr.exportTo('tr.e.importer.linux_perf',function(){const Parser=tr.e.importer.linux_perf.Parser;function GestureParser(importer){Parser.call(this,importer);importer.registerEventHandler('tracing_mark_write:log',GestureParser.prototype.logEvent.bind(this));importer.registerEventHandler('tracing_mark_write:SyncInterpret',GestureParser.prototype.syncEvent.bind(this));importer.registerEventHandler('tracing_mark_write:HandleTimer',GestureParser.prototype.timerEvent.bind(this));}
 GestureParser.prototype={__proto__:Parser.prototype,gestureOpenSlice(title,ts,opt_args){const thread=this.importer.getOrCreatePseudoThread('gesture').thread;thread.sliceGroup.beginSlice('touchpad_gesture',title,ts,opt_args);},gestureCloseSlice(title,ts){const thread=this.importer.getOrCreatePseudoThread('gesture').thread;if(thread.sliceGroup.openSliceCount){const slice=thread.sliceGroup.mostRecentlyOpenedPartialSlice;if(slice.title!==title){this.importer.model.importWarning({type:'title_match_error',message:'Titles do not match. Title is '+
 slice.title+' in openSlice, and is '+
 title+' in endSlice'});}else{thread.sliceGroup.endSlice(ts);}}},logEvent(eventName,cpuNumber,pid,ts,eventBase){const innerEvent=/^\s*(\w+):\s*(\w+)$/.exec(eventBase.details);switch(innerEvent[1]){case'start':this.gestureOpenSlice('GestureLog',ts,{name:innerEvent[2]});break;case'end':this.gestureCloseSlice('GestureLog',ts);}
@@ -5892,12 +5951,17 @@
 const tgid=parseInt(eventBase.tgid);const thread=this.model_.getOrCreateProcess(tgid).getOrCreateThread(pid);thread.name=eventBase.threadName;const slices=thread.kernelSliceGroup;if(!slices.isTimestampValidForBeginOrEnd(ts)){this.model_.importWarning({type:'parse_error',message:'Timestamps are moving backward.'});return false;}
 if(slices.openSliceCount>0){slices.endSlice(ts);}
 return true;}};LinuxPerfParser.register(KernelFuncParser);return{KernelFuncParser,};});'use strict';tr.exportTo('tr.e.importer.linux_perf',function(){const ColorScheme=tr.b.ColorScheme;const Parser=tr.e.importer.linux_perf.Parser;function MaliParser(importer){Parser.call(this,importer);importer.registerEventHandler('mali_dvfs_event',MaliParser.prototype.dvfsEventEvent.bind(this));importer.registerEventHandler('mali_dvfs_set_clock',MaliParser.prototype.dvfsSetClockEvent.bind(this));importer.registerEventHandler('mali_dvfs_set_voltage',MaliParser.prototype.dvfsSetVoltageEvent.bind(this));this.addJMCounter('mali_hwc_MESSAGES_SENT','Messages Sent');this.addJMCounter('mali_hwc_MESSAGES_RECEIVED','Messages Received');this.addJMCycles('mali_hwc_GPU_ACTIVE','GPU Active');this.addJMCycles('mali_hwc_IRQ_ACTIVE','IRQ Active');for(let i=0;i<7;i++){const jobStr='JS'+i;const jobHWCStr='mali_hwc_'+jobStr;this.addJMCounter(jobHWCStr+'_JOBS',jobStr+' Jobs');this.addJMCounter(jobHWCStr+'_TASKS',jobStr+' Tasks');this.addJMCycles(jobHWCStr+'_ACTIVE',jobStr+' Active');this.addJMCycles(jobHWCStr+'_WAIT_READ',jobStr+' Wait Read');this.addJMCycles(jobHWCStr+'_WAIT_ISSUE',jobStr+' Wait Issue');this.addJMCycles(jobHWCStr+'_WAIT_DEPEND',jobStr+' Wait Depend');this.addJMCycles(jobHWCStr+'_WAIT_FINISH',jobStr+' Wait Finish');}
-this.addTilerCounter('mali_hwc_TRIANGLES','Triangles');this.addTilerCounter('mali_hwc_QUADS','Quads');this.addTilerCounter('mali_hwc_POLYGONS','Polygons');this.addTilerCounter('mali_hwc_POINTS','Points');this.addTilerCounter('mali_hwc_LINES','Lines');this.addTilerCounter('mali_hwc_VCACHE_HIT','VCache Hit');this.addTilerCounter('mali_hwc_VCACHE_MISS','VCache Miss');this.addTilerCounter('mali_hwc_FRONT_FACING','Front Facing');this.addTilerCounter('mali_hwc_BACK_FACING','Back Facing');this.addTilerCounter('mali_hwc_PRIM_VISIBLE','Prim Visible');this.addTilerCounter('mali_hwc_PRIM_CULLED','Prim Culled');this.addTilerCounter('mali_hwc_PRIM_CLIPPED','Prim Clipped');this.addTilerCounter('mali_hwc_WRBUF_HIT','Wrbuf Hit');this.addTilerCounter('mali_hwc_WRBUF_MISS','Wrbuf Miss');this.addTilerCounter('mali_hwc_WRBUF_LINE','Wrbuf Line');this.addTilerCounter('mali_hwc_WRBUF_PARTIAL','Wrbuf Partial');this.addTilerCounter('mali_hwc_WRBUF_STALL','Wrbuf Stall');this.addTilerCycles('mali_hwc_ACTIVE','Tiler Active');this.addTilerCycles('mali_hwc_INDEX_WAIT','Index Wait');this.addTilerCycles('mali_hwc_INDEX_RANGE_WAIT','Index Range Wait');this.addTilerCycles('mali_hwc_VERTEX_WAIT','Vertex Wait');this.addTilerCycles('mali_hwc_PCACHE_WAIT','Pcache Wait');this.addTilerCycles('mali_hwc_WRBUF_WAIT','Wrbuf Wait');this.addTilerCycles('mali_hwc_BUS_READ','Bus Read');this.addTilerCycles('mali_hwc_BUS_WRITE','Bus Write');this.addTilerCycles('mali_hwc_TILER_UTLB_STALL','Tiler UTLB Stall');this.addTilerCycles('mali_hwc_TILER_UTLB_HIT','Tiler UTLB Hit');this.addFragCycles('mali_hwc_FRAG_ACTIVE','Active');this.addFragCounter('mali_hwc_FRAG_PRIMATIVES','Primitives');this.addFragCounter('mali_hwc_FRAG_PRIMATIVES_DROPPED','Primitives Dropped');this.addFragCycles('mali_hwc_FRAG_CYCLE_DESC','Descriptor Processing');this.addFragCycles('mali_hwc_FRAG_CYCLES_PLR','PLR Processing??');this.addFragCycles('mali_hwc_FRAG_CYCLES_VERT','Vertex Processing');this.addFragCycles('mali_hwc_FRAG_CYCLES_TRISETUP','Triangle Setup');this.addFragCycles('mali_hwc_FRAG_CYCLES_RAST','Rasterization???');this.addFragCounter('mali_hwc_FRAG_THREADS','Threads');this.addFragCounter('mali_hwc_FRAG_DUMMY_THREADS','Dummy Threads');this.addFragCounter('mali_hwc_FRAG_QUADS_RAST','Quads Rast');this.addFragCounter('mali_hwc_FRAG_QUADS_EZS_TEST','Quads EZS Test');this.addFragCounter('mali_hwc_FRAG_QUADS_EZS_KILLED','Quads EZS Killed');this.addFragCounter('mali_hwc_FRAG_QUADS_LZS_TEST','Quads LZS Test');this.addFragCounter('mali_hwc_FRAG_QUADS_LZS_KILLED','Quads LZS Killed');this.addFragCycles('mali_hwc_FRAG_CYCLE_NO_TILE','No Tiles');this.addFragCounter('mali_hwc_FRAG_NUM_TILES','Tiles');this.addFragCounter('mali_hwc_FRAG_TRANS_ELIM','Transactions Eliminated');this.addComputeCycles('mali_hwc_COMPUTE_ACTIVE','Active');this.addComputeCounter('mali_hwc_COMPUTE_TASKS','Tasks');this.addComputeCounter('mali_hwc_COMPUTE_THREADS','Threads Started');this.addComputeCycles('mali_hwc_COMPUTE_CYCLES_DESC','Waiting for Descriptors');this.addTripipeCycles('mali_hwc_TRIPIPE_ACTIVE','Active');this.addArithCounter('mali_hwc_ARITH_WORDS','Instructions (/Pipes)');this.addArithCycles('mali_hwc_ARITH_CYCLES_REG','Reg scheduling stalls (/Pipes)');this.addArithCycles('mali_hwc_ARITH_CYCLES_L0','L0 cache miss stalls (/Pipes)');this.addArithCounter('mali_hwc_ARITH_FRAG_DEPEND','Frag dep check failures (/Pipes)');this.addLSCounter('mali_hwc_LS_WORDS','Instruction Words Completed');this.addLSCounter('mali_hwc_LS_ISSUES','Full Pipeline Issues');this.addLSCounter('mali_hwc_LS_RESTARTS','Restarts (unpairable insts)');this.addLSCounter('mali_hwc_LS_REISSUES_MISS','Pipeline reissue (cache miss/uTLB)');this.addLSCounter('mali_hwc_LS_REISSUES_VD','Pipeline reissue (varying data)');this.addLSCounter('mali_hwc_LS_REISSUE_ATTRIB_MISS','Pipeline reissue (attribute cache miss)');this.addLSCounter('mali_hwc_LS_REISSUE_NO_WB','Writeback not used');this.addTexCounter('mali_hwc_TEX_WORDS','Words');this.addTexCounter('mali_hwc_TEX_BUBBLES','Bubbles');this.addTexCounter('mali_hwc_TEX_WORDS_L0','Words L0');this.addTexCounter('mali_hwc_TEX_WORDS_DESC','Words Desc');this.addTexCounter('mali_hwc_TEX_THREADS','Threads');this.addTexCounter('mali_hwc_TEX_RECIRC_FMISS','Recirc due to Full Miss');this.addTexCounter('mali_hwc_TEX_RECIRC_DESC','Recirc due to Desc Miss');this.addTexCounter('mali_hwc_TEX_RECIRC_MULTI','Recirc due to Multipass');this.addTexCounter('mali_hwc_TEX_RECIRC_PMISS','Recirc due to Partial Cache Miss');this.addTexCounter('mali_hwc_TEX_RECIRC_CONF','Recirc due to Cache Conflict');this.addLSCCounter('mali_hwc_LSC_READ_HITS','Read Hits');this.addLSCCounter('mali_hwc_LSC_READ_MISSES','Read Misses');this.addLSCCounter('mali_hwc_LSC_WRITE_HITS','Write Hits');this.addLSCCounter('mali_hwc_LSC_WRITE_MISSES','Write Misses');this.addLSCCounter('mali_hwc_LSC_ATOMIC_HITS','Atomic Hits');this.addLSCCounter('mali_hwc_LSC_ATOMIC_MISSES','Atomic Misses');this.addLSCCounter('mali_hwc_LSC_LINE_FETCHES','Line Fetches');this.addLSCCounter('mali_hwc_LSC_DIRTY_LINE','Dirty Lines');this.addLSCCounter('mali_hwc_LSC_SNOOPS','Snoops');this.addAXICounter('mali_hwc_AXI_TLB_STALL','Address channel stall');this.addAXICounter('mali_hwc_AXI_TLB_MISS','Cache Miss');this.addAXICounter('mali_hwc_AXI_TLB_TRANSACTION','Transactions');this.addAXICounter('mali_hwc_LS_TLB_MISS','LS Cache Miss');this.addAXICounter('mali_hwc_LS_TLB_HIT','LS Cache Hit');this.addAXICounter('mali_hwc_AXI_BEATS_READ','Read Beats');this.addAXICounter('mali_hwc_AXI_BEATS_WRITE','Write Beats');this.addMMUCounter('mali_hwc_MMU_TABLE_WALK','Page Table Walks');this.addMMUCounter('mali_hwc_MMU_REPLAY_MISS','Cache Miss from Replay Buffer');this.addMMUCounter('mali_hwc_MMU_REPLAY_FULL','Replay Buffer Full');this.addMMUCounter('mali_hwc_MMU_NEW_MISS','Cache Miss on New Request');this.addMMUCounter('mali_hwc_MMU_HIT','Cache Hit');this.addMMUCycles('mali_hwc_UTLB_STALL','UTLB Stalled');this.addMMUCycles('mali_hwc_UTLB_REPLAY_MISS','UTLB Replay Miss');this.addMMUCycles('mali_hwc_UTLB_REPLAY_FULL','UTLB Replay Full');this.addMMUCycles('mali_hwc_UTLB_NEW_MISS','UTLB New Miss');this.addMMUCycles('mali_hwc_UTLB_HIT','UTLB Hit');this.addL2Counter('mali_hwc_L2_READ_BEATS','Read Beats');this.addL2Counter('mali_hwc_L2_WRITE_BEATS','Write Beats');this.addL2Counter('mali_hwc_L2_ANY_LOOKUP','Any Lookup');this.addL2Counter('mali_hwc_L2_READ_LOOKUP','Read Lookup');this.addL2Counter('mali_hwc_L2_SREAD_LOOKUP','Shareable Read Lookup');this.addL2Counter('mali_hwc_L2_READ_REPLAY','Read Replayed');this.addL2Counter('mali_hwc_L2_READ_SNOOP','Read Snoop');this.addL2Counter('mali_hwc_L2_READ_HIT','Read Cache Hit');this.addL2Counter('mali_hwc_L2_CLEAN_MISS','CleanUnique Miss');this.addL2Counter('mali_hwc_L2_WRITE_LOOKUP','Write Lookup');this.addL2Counter('mali_hwc_L2_SWRITE_LOOKUP','Shareable Write Lookup');this.addL2Counter('mali_hwc_L2_WRITE_REPLAY','Write Replayed');this.addL2Counter('mali_hwc_L2_WRITE_SNOOP','Write Snoop');this.addL2Counter('mali_hwc_L2_WRITE_HIT','Write Cache Hit');this.addL2Counter('mali_hwc_L2_EXT_READ_FULL','ExtRD with BIU Full');this.addL2Counter('mali_hwc_L2_EXT_READ_HALF','ExtRD with BIU >1/2 Full');this.addL2Counter('mali_hwc_L2_EXT_WRITE_FULL','ExtWR with BIU Full');this.addL2Counter('mali_hwc_L2_EXT_WRITE_HALF','ExtWR with BIU >1/2 Full');this.addL2Counter('mali_hwc_L2_EXT_READ','External Read (ExtRD)');this.addL2Counter('mali_hwc_L2_EXT_READ_LINE','ExtRD (linefill)');this.addL2Counter('mali_hwc_L2_EXT_WRITE','External Write (ExtWR)');this.addL2Counter('mali_hwc_L2_EXT_WRITE_LINE','ExtWR (linefill)');this.addL2Counter('mali_hwc_L2_EXT_WRITE_SMALL','ExtWR (burst size <64B)');this.addL2Counter('mali_hwc_L2_EXT_BARRIER','External Barrier');this.addL2Counter('mali_hwc_L2_EXT_AR_STALL','Address Read stalls');this.addL2Counter('mali_hwc_L2_EXT_R_BUF_FULL','Response Buffer full stalls');this.addL2Counter('mali_hwc_L2_EXT_RD_BUF_FULL','Read Data Buffer full stalls');this.addL2Counter('mali_hwc_L2_EXT_R_RAW','RAW hazard stalls');this.addL2Counter('mali_hwc_L2_EXT_W_STALL','Write Data stalls');this.addL2Counter('mali_hwc_L2_EXT_W_BUF_FULL','Write Data Buffer full');this.addL2Counter('mali_hwc_L2_EXT_R_W_HAZARD','WAW or WAR hazard stalls');this.addL2Counter('mali_hwc_L2_TAG_HAZARD','Tag hazard replays');this.addL2Cycles('mali_hwc_L2_SNOOP_FULL','Snoop buffer full');this.addL2Cycles('mali_hwc_L2_REPLAY_FULL','Replay buffer full');importer.registerEventHandler('tracing_mark_write:mali_driver',MaliParser.prototype.maliDDKEvent.bind(this));this.model_=importer.model_;}
+this.addTilerCounter('mali_hwc_TRIANGLES','Triangles');this.addTilerCounter('mali_hwc_QUADS','Quads');this.addTilerCounter('mali_hwc_POLYGONS','Polygons');this.addTilerCounter('mali_hwc_POINTS','Points');this.addTilerCounter('mali_hwc_LINES','Lines');this.addTilerCounter('mali_hwc_VCACHE_HIT','VCache Hit');this.addTilerCounter('mali_hwc_VCACHE_MISS','VCache Miss');this.addTilerCounter('mali_hwc_FRONT_FACING','Front Facing');this.addTilerCounter('mali_hwc_BACK_FACING','Back Facing');this.addTilerCounter('mali_hwc_PRIM_VISIBLE','Prim Visible');this.addTilerCounter('mali_hwc_PRIM_CULLED','Prim Culled');this.addTilerCounter('mali_hwc_PRIM_CLIPPED','Prim Clipped');this.addTilerCounter('mali_hwc_WRBUF_HIT','Wrbuf Hit');this.addTilerCounter('mali_hwc_WRBUF_MISS','Wrbuf Miss');this.addTilerCounter('mali_hwc_WRBUF_LINE','Wrbuf Line');this.addTilerCounter('mali_hwc_WRBUF_PARTIAL','Wrbuf Partial');this.addTilerCounter('mali_hwc_WRBUF_STALL','Wrbuf Stall');this.addTilerCycles('mali_hwc_ACTIVE','Tiler Active');this.addTilerCycles('mali_hwc_INDEX_WAIT','Index Wait');this.addTilerCycles('mali_hwc_INDEX_RANGE_WAIT','Index Range Wait');this.addTilerCycles('mali_hwc_VERTEX_WAIT','Vertex Wait');this.addTilerCycles('mali_hwc_PCACHE_WAIT','Pcache Wait');this.addTilerCycles('mali_hwc_WRBUF_WAIT','Wrbuf Wait');this.addTilerCycles('mali_hwc_BUS_READ','Bus Read');this.addTilerCycles('mali_hwc_BUS_WRITE','Bus Write');this.addTilerCycles('mali_hwc_TILER_UTLB_STALL','Tiler UTLB Stall');this.addTilerCycles('mali_hwc_TILER_UTLB_HIT','Tiler UTLB Hit');this.addFragCycles('mali_hwc_FRAG_ACTIVE','Active');this.addFragCounter('mali_hwc_FRAG_PRIMATIVES','Primitives');this.addFragCounter('mali_hwc_FRAG_PRIMATIVES_DROPPED','Primitives Dropped');this.addFragCycles('mali_hwc_FRAG_CYCLE_DESC','Descriptor Processing');this.addFragCycles('mali_hwc_FRAG_CYCLES_PLR','PLR Processing??');this.addFragCycles('mali_hwc_FRAG_CYCLES_VERT','Vertex Processing');this.addFragCycles('mali_hwc_FRAG_CYCLES_TRISETUP','Triangle Setup');this.addFragCycles('mali_hwc_FRAG_CYCLES_RAST','Rasterization???');this.addFragCounter('mali_hwc_FRAG_THREADS','Threads');this.addFragCounter('mali_hwc_FRAG_DUMMY_THREADS','Dummy Threads');this.addFragCounter('mali_hwc_FRAG_QUADS_RAST','Quads Rast');this.addFragCounter('mali_hwc_FRAG_QUADS_EZS_TEST','Quads EZS Test');this.addFragCounter('mali_hwc_FRAG_QUADS_EZS_KILLED','Quads EZS Killed');this.addFragCounter('mali_hwc_FRAG_QUADS_LZS_TEST','Quads LZS Test');this.addFragCounter('mali_hwc_FRAG_QUADS_LZS_KILLED','Quads LZS Killed');this.addFragCycles('mali_hwc_FRAG_CYCLE_NO_TILE','No Tiles');this.addFragCounter('mali_hwc_FRAG_NUM_TILES','Tiles');this.addFragCounter('mali_hwc_FRAG_TRANS_ELIM','Transactions Eliminated');this.addComputeCycles('mali_hwc_COMPUTE_ACTIVE','Active');this.addComputeCounter('mali_hwc_COMPUTE_TASKS','Tasks');this.addComputeCounter('mali_hwc_COMPUTE_THREADS','Threads Started');this.addComputeCycles('mali_hwc_COMPUTE_CYCLES_DESC','Waiting for Descriptors');this.addTripipeCycles('mali_hwc_TRIPIPE_ACTIVE','Active');this.addArithCounter('mali_hwc_ARITH_WORDS','Instructions (/Pipes)');this.addArithCycles('mali_hwc_ARITH_CYCLES_REG','Reg scheduling stalls (/Pipes)');this.addArithCycles('mali_hwc_ARITH_CYCLES_L0','L0 cache miss stalls (/Pipes)');this.addArithCounter('mali_hwc_ARITH_FRAG_DEPEND','Frag dep check failures (/Pipes)');this.addLSCounter('mali_hwc_LS_WORDS','Instruction Words Completed');this.addLSCounter('mali_hwc_LS_ISSUES','Full Pipeline Issues');this.addLSCounter('mali_hwc_LS_RESTARTS','Restarts (unpairable insts)');this.addLSCounter('mali_hwc_LS_REISSUES_MISS','Pipeline reissue (cache miss/uTLB)');this.addLSCounter('mali_hwc_LS_REISSUES_VD','Pipeline reissue (varying data)');this.addLSCounter('mali_hwc_LS_REISSUE_ATTRIB_MISS','Pipeline reissue (attribute cache miss)');this.addLSCounter('mali_hwc_LS_REISSUE_NO_WB','Writeback not used');this.addTexCounter('mali_hwc_TEX_WORDS','Words');this.addTexCounter('mali_hwc_TEX_BUBBLES','Bubbles');this.addTexCounter('mali_hwc_TEX_WORDS_L0','Words L0');this.addTexCounter('mali_hwc_TEX_WORDS_DESC','Words Desc');this.addTexCounter('mali_hwc_TEX_THREADS','Threads');this.addTexCounter('mali_hwc_TEX_RECIRC_FMISS','Recirc due to Full Miss');this.addTexCounter('mali_hwc_TEX_RECIRC_DESC','Recirc due to Desc Miss');this.addTexCounter('mali_hwc_TEX_RECIRC_MULTI','Recirc due to Multipass');this.addTexCounter('mali_hwc_TEX_RECIRC_PMISS','Recirc due to Partial Cache Miss');this.addTexCounter('mali_hwc_TEX_RECIRC_CONF','Recirc due to Cache Conflict');this.addLSCCounter('mali_hwc_LSC_READ_HITS','Read Hits');this.addLSCCounter('mali_hwc_LSC_READ_MISSES','Read Misses');this.addLSCCounter('mali_hwc_LSC_WRITE_HITS','Write Hits');this.addLSCCounter('mali_hwc_LSC_WRITE_MISSES','Write Misses');this.addLSCCounter('mali_hwc_LSC_ATOMIC_HITS','Atomic Hits');this.addLSCCounter('mali_hwc_LSC_ATOMIC_MISSES','Atomic Misses');this.addLSCCounter('mali_hwc_LSC_LINE_FETCHES','Line Fetches');this.addLSCCounter('mali_hwc_LSC_DIRTY_LINE','Dirty Lines');this.addLSCCounter('mali_hwc_LSC_SNOOPS','Snoops');this.addAXICounter('mali_hwc_AXI_TLB_STALL','Address channel stall');this.addAXICounter('mali_hwc_AXI_TLB_MISS','Cache Miss');this.addAXICounter('mali_hwc_AXI_TLB_TRANSACTION','Transactions');this.addAXICounter('mali_hwc_LS_TLB_MISS','LS Cache Miss');this.addAXICounter('mali_hwc_LS_TLB_HIT','LS Cache Hit');this.addAXICounter('mali_hwc_AXI_BEATS_READ','Read Beats');this.addAXICounter('mali_hwc_AXI_BEATS_WRITE','Write Beats');this.addMMUCounter('mali_hwc_MMU_TABLE_WALK','Page Table Walks');this.addMMUCounter('mali_hwc_MMU_REPLAY_MISS','Cache Miss from Replay Buffer');this.addMMUCounter('mali_hwc_MMU_REPLAY_FULL','Replay Buffer Full');this.addMMUCounter('mali_hwc_MMU_NEW_MISS','Cache Miss on New Request');this.addMMUCounter('mali_hwc_MMU_HIT','Cache Hit');this.addMMUCycles('mali_hwc_UTLB_STALL','UTLB Stalled');this.addMMUCycles('mali_hwc_UTLB_REPLAY_MISS','UTLB Replay Miss');this.addMMUCycles('mali_hwc_UTLB_REPLAY_FULL','UTLB Replay Full');this.addMMUCycles('mali_hwc_UTLB_NEW_MISS','UTLB New Miss');this.addMMUCycles('mali_hwc_UTLB_HIT','UTLB Hit');this.addL2Counter('mali_hwc_L2_READ_BEATS','Read Beats');this.addL2Counter('mali_hwc_L2_WRITE_BEATS','Write Beats');this.addL2Counter('mali_hwc_L2_ANY_LOOKUP','Any Lookup');this.addL2Counter('mali_hwc_L2_READ_LOOKUP','Read Lookup');this.addL2Counter('mali_hwc_L2_SREAD_LOOKUP','Shareable Read Lookup');this.addL2Counter('mali_hwc_L2_READ_REPLAY','Read Replayed');this.addL2Counter('mali_hwc_L2_READ_SNOOP','Read Snoop');this.addL2Counter('mali_hwc_L2_READ_HIT','Read Cache Hit');this.addL2Counter('mali_hwc_L2_CLEAN_MISS','CleanUnique Miss');this.addL2Counter('mali_hwc_L2_WRITE_LOOKUP','Write Lookup');this.addL2Counter('mali_hwc_L2_SWRITE_LOOKUP','Shareable Write Lookup');this.addL2Counter('mali_hwc_L2_WRITE_REPLAY','Write Replayed');this.addL2Counter('mali_hwc_L2_WRITE_SNOOP','Write Snoop');this.addL2Counter('mali_hwc_L2_WRITE_HIT','Write Cache Hit');this.addL2Counter('mali_hwc_L2_EXT_READ_FULL','ExtRD with BIU Full');this.addL2Counter('mali_hwc_L2_EXT_READ_HALF','ExtRD with BIU >1/2 Full');this.addL2Counter('mali_hwc_L2_EXT_WRITE_FULL','ExtWR with BIU Full');this.addL2Counter('mali_hwc_L2_EXT_WRITE_HALF','ExtWR with BIU >1/2 Full');this.addL2Counter('mali_hwc_L2_EXT_READ','External Read (ExtRD)');this.addL2Counter('mali_hwc_L2_EXT_READ_LINE','ExtRD (linefill)');this.addL2Counter('mali_hwc_L2_EXT_WRITE','External Write (ExtWR)');this.addL2Counter('mali_hwc_L2_EXT_WRITE_LINE','ExtWR (linefill)');this.addL2Counter('mali_hwc_L2_EXT_WRITE_SMALL','ExtWR (burst size <64B)');this.addL2Counter('mali_hwc_L2_EXT_BARRIER','External Barrier');this.addL2Counter('mali_hwc_L2_EXT_AR_STALL','Address Read stalls');this.addL2Counter('mali_hwc_L2_EXT_R_BUF_FULL','Response Buffer full stalls');this.addL2Counter('mali_hwc_L2_EXT_RD_BUF_FULL','Read Data Buffer full stalls');this.addL2Counter('mali_hwc_L2_EXT_R_RAW','RAW hazard stalls');this.addL2Counter('mali_hwc_L2_EXT_W_STALL','Write Data stalls');this.addL2Counter('mali_hwc_L2_EXT_W_BUF_FULL','Write Data Buffer full');this.addL2Counter('mali_hwc_L2_EXT_R_W_HAZARD','WAW or WAR hazard stalls');this.addL2Counter('mali_hwc_L2_TAG_HAZARD','Tag hazard replays');this.addL2Cycles('mali_hwc_L2_SNOOP_FULL','Snoop buffer full');this.addL2Cycles('mali_hwc_L2_REPLAY_FULL','Replay buffer full');importer.registerEventHandler('tracing_mark_write:mali_driver',MaliParser.prototype.maliDDKEvent.bind(this));importer.registerEventHandler('mali_job_systrace_event_start',MaliParser.prototype.maliJobEvent.bind(this));importer.registerEventHandler('mali_job_systrace_event_stop',MaliParser.prototype.maliJobEvent.bind(this));this.model_=importer.model_;this.deferredJobs_={};}
 MaliParser.prototype={__proto__:Parser.prototype,maliDDKOpenSlice(pid,tid,ts,func,blockinfo){const thread=this.importer.model_.getOrCreateProcess(pid).getOrCreateThread(tid);const funcArgs=/^([\w\d_]*)(?:\(\))?:?\s*(.*)$/.exec(func);thread.sliceGroup.beginSlice('gpu-driver',funcArgs[1],ts,{'args':funcArgs[2],blockinfo});},maliDDKCloseSlice(pid,tid,ts,args,blockinfo){const thread=this.importer.model_.getOrCreateProcess(pid).getOrCreateThread(tid);if(!thread.sliceGroup.openSliceCount){return;}
 thread.sliceGroup.endSlice(ts);},autoDetectLineRE(line){const lineREWithThread=/^\s*\(([\w\-]*)\)\s*(\w+):\s*([\w\\\/\.\-]*@\d*):?\s*(.*)$/;if(lineREWithThread.test(line)){return lineREWithThread;}
 const lineRENoThread=/^s*()(\w+):\s*([\w\\\/.\-]*):?\s*(.*)$/;if(lineRENoThread.test(line)){return lineRENoThread;}
 return null;},lineRE:null,maliDDKEvent(eventName,cpuNumber,pid,ts,eventBase){if(this.lineRE===null){this.lineRE=this.autoDetectLineRE(eventBase.details);if(this.lineRE===null)return false;}
 const maliEvent=this.lineRE.exec(eventBase.details);const tid=(maliEvent[1]===''?'mali':maliEvent[1]);switch(maliEvent[2]){case'cros_trace_print_enter':this.maliDDKOpenSlice(pid,tid,ts,maliEvent[4],maliEvent[3]);break;case'cros_trace_print_exit':this.maliDDKCloseSlice(pid,tid,ts,[],maliEvent[3]);}
+return true;},maliJobEvent(eventName,cpuNumber,pid,ts,eventBase){const jobEventRE=/^.*tracing_mark_write: (S|F)\|(\d+)\|(\w+)-job\|(\d+)\|(\d+)\|(\d+)\|(\d+)\|(\d+)\|([a-z0-9]+)\|(\d+)$/;const jobEvent=jobEventRE.exec(eventBase.details);if(!jobEvent){this.model_.importWarning({type:'parse_error',args:'unexpected mali_job_systrace_event_* event syntax'});return;}
+const jobType=jobEvent[3];const jobId=jobEvent[4];const thread=this.importer.model_.getOrCreateProcess(0).getOrCreateThread('mali:'+jobType);switch(jobEvent[1]){case'S':{const args={ctx:jobEvent[9],pid:parseInt(jobEvent[2],10),dep0:parseInt(jobEvent[5],10),dep1:parseInt(jobEvent[7],10)};if(thread.sliceGroup.openSliceCount){if(!(jobType in this.deferredJobs_)){this.deferredJobs_[jobType]=[];}
+this.deferredJobs_[jobType].push({id:jobId,args});}else{thread.sliceGroup.beginSlice(null,jobId,ts,args);}}break;case'F':{if(!thread.sliceGroup.openSliceCount){return;}
+if(thread.sliceGroup.mostRecentlyOpenedPartialSlice.title!==jobId){this.model_.importWarning({type:'invalid event nesting',message:'non-sequential jobs in same mali job slot'});}
+thread.sliceGroup.endSlice(ts);const deferredJobs=this.deferredJobs_[jobType];if(deferredJobs&&deferredJobs.length){const job=deferredJobs.shift();thread.sliceGroup.beginSlice(null,job.id,ts,job.args);}}break;}
 return true;},dvfsSample(counterName,seriesName,ts,s){const value=parseInt(s);const counter=this.model_.kernel.getOrCreateCounter('DVFS',counterName);if(counter.numSeries===0){counter.addSeries(new tr.model.CounterSeries(seriesName,ColorScheme.getColorIdForGeneralPurposeString(counter.name)));}
 counter.series.forEach(function(series){series.addCounterSample(ts,value);});},dvfsEventEvent(eventName,cpuNumber,pid,ts,eventBase){const event=/utilization=(\d+)/.exec(eventBase.details);if(!event)return false;this.dvfsSample('DVFS Utilization','utilization',ts,event[1]);return true;},dvfsSetClockEvent(eventName,cpuNumber,pid,ts,eventBase){const event=/frequency=(\d+)/.exec(eventBase.details);if(!event)return false;this.dvfsSample('DVFS Frequency','frequency',ts,event[1]);return true;},dvfsSetVoltageEvent(eventName,cpuNumber,pid,ts,eventBase){const event=/voltage=(\d+)/.exec(eventBase.details);if(!event)return false;this.dvfsSample('DVFS Voltage','voltage',ts,event[1]);return true;},hwcSample(cat,counterName,seriesName,ts,eventBase){const event=/val=(\d+)/.exec(eventBase.details);if(!event)return false;const value=parseInt(event[1]);const counter=this.model_.kernel.getOrCreateCounter(cat,counterName);if(counter.numSeries===0){counter.addSeries(new tr.model.CounterSeries(seriesName,ColorScheme.getColorIdForGeneralPurposeString(counter.name)));}
 counter.series.forEach(function(series){series.addCounterSample(ts,value);});return true;},jmSample(ctrName,seriesName,ts,eventBase){return this.hwcSample('mali:jm','JM: '+ctrName,seriesName,ts,eventBase);},addJMCounter(hwcEventName,hwcTitle){function handler(eventName,cpuNumber,pid,ts,eventBase){return this.jmSample(hwcTitle,'count',ts,eventBase);}
@@ -5954,8 +6018,8 @@
 let events=[];if(produceResult){for(let i=0;i<rawEvents.length;i++){let event=rawEvents[i];event=stripSuffix(event,'\\n\\');events.push(event);}}else{events=[rawEvents[rawEvents.length-1]];}
 const oldLastEvent=events[events.length-1];const newLastEvent=stripSuffix(oldLastEvent,'\\n";');if(newLastEvent===oldLastEvent)return failure;events[events.length-1]=newLastEvent;return{ok:true,lines:produceResult?events:undefined,eventsBeginAtLine};};FTraceImporter._extractEventsFromSystraceMultiHTML=function(incomingEvents,produceResult){const failure={ok:false};if(produceResult===undefined)produceResult=true;const header=incomingEvents instanceof tr.b.TraceStream?incomingEvents.header:incomingEvents;if(!(new RegExp('^<!DOCTYPE HTML>','i').test(header)))return failure;const r=new tr.importer.SimpleLineReader(incomingEvents);let events=[];let eventsBeginAtLine;while(!/^# tracer:/.test(events)){if(!r.advanceToLineMatching(/^  <script class="trace-data" type="application\/text">$/)){return failure;}
 eventsBeginAtLine=r.curLineNumber+1;r.beginSavingLines();if(!r.advanceToLineMatching(/^  <\/script>$/))return failure;events=r.endSavingLinesAndGetResult();events=events.slice(1,events.length-1);}
-if(!r.advanceToLineMatching(/^<\/body>$/))return failure;if(!r.advanceToLineMatching(/^<\/html>$/))return failure;return{ok:true,lines:produceResult?events:undefined,eventsBeginAtLine,};};FTraceImporter.prototype={__proto__:tr.importer.Importer.prototype,get importerName(){return'FTraceImporter';},get model(){return this.model_;},importClockSyncMarkers(){this.lazyInit_();this.forEachLine_(function(text,eventBase,cpuNumber,pid,ts){const eventName=eventBase.eventName;if(eventName!=='tracing_mark_write'&&eventName!=='0')return;if(traceEventClockSyncRE.exec(eventBase.details)||genericClockSyncRE.exec(eventBase.details)){this.traceClockSyncEvent_(eventName,cpuNumber,pid,ts,eventBase);}else if(realTimeClockSyncRE.exec(eventBase.details)){const match=realTimeClockSyncRE.exec(eventBase.details);this.model_.realtime_to_monotonic_offset_ms=ts-match[1];}}.bind(this));},importEvents(){const modelTimeTransformer=this.model_.clockSyncManager.getModelTimeTransformer(this.clockDomainId_);this.importCpuData_(modelTimeTransformer);this.buildMapFromLinuxPidsToThreads_();this.buildPerThreadCpuSlicesFromCpuState_();},registerEventHandler(eventName,handler){this.eventHandlers_[eventName]=handler;},getOrCreateCpu(cpuNumber){return this.model_.kernel.getOrCreateCpu(cpuNumber);},getOrCreateKernelThread(kernelThreadName,pid,tid){if(!this.kernelThreadStates_[kernelThreadName]){const thread=this.model_.getOrCreateProcess(pid).getOrCreateThread(tid);thread.name=kernelThreadName;this.kernelThreadStates_[kernelThreadName]={pid,thread,openSlice:undefined,openSliceTS:undefined};this.threadsByLinuxPid[pid]=thread;}
-return this.kernelThreadStates_[kernelThreadName];},getOrCreateBinderKernelThread(kernelThreadName,pid,tid){const key=kernelThreadName+pid+tid;if(!this.kernelThreadStates_[key]){const thread=this.model_.getOrCreateProcess(pid).getOrCreateThread(tid);thread.name=kernelThreadName;this.kernelThreadStates_[key]={pid,thread,openSlice:undefined,openSliceTS:undefined};this.threadsByLinuxPid[pid]=thread;}
+if(!r.advanceToLineMatching(/^<\/body>$/))return failure;if(!r.advanceToLineMatching(/^<\/html>$/))return failure;return{ok:true,lines:produceResult?events:undefined,eventsBeginAtLine,};};FTraceImporter.prototype={__proto__:tr.importer.Importer.prototype,get importerName(){return'FTraceImporter';},get model(){return this.model_;},importClockSyncMarkers(){this.lazyInit_();this.forEachLine_(function(text,eventBase,cpuNumber,pid,ts){const eventName=eventBase.eventName;if(eventName!=='tracing_mark_write'&&eventName!=='0')return;if(traceEventClockSyncRE.exec(eventBase.details)||genericClockSyncRE.exec(eventBase.details)){this.traceClockSyncEvent_(eventName,cpuNumber,pid,ts,eventBase);}else if(realTimeClockSyncRE.exec(eventBase.details)){const match=realTimeClockSyncRE.exec(eventBase.details);this.model_.realtime_to_monotonic_offset_ms=ts-match[1];}}.bind(this));},importEvents(){const modelTimeTransformer=this.model_.clockSyncManager.getModelTimeTransformer(this.clockDomainId_);this.importCpuData_(modelTimeTransformer);this.buildMapFromLinuxPidsToThreads_();this.buildPerThreadCpuSlicesFromCpuState_();},registerEventHandler(eventName,handler){this.eventHandlers_[eventName]=handler;},getOrCreateCpu(cpuNumber){return this.model_.kernel.getOrCreateCpu(cpuNumber);},getOrCreateKernelThread(kernelThreadName,pid,tid){if(!this.kernelThreadStates_[kernelThreadName]){const thread=this.model_.getOrCreateProcess(pid).getOrCreateThread(tid);thread.name=kernelThreadName;this.kernelThreadStates_[kernelThreadName]={pid,thread,openSlice:undefined,openSliceTS:undefined};this.threadsByLinuxPid[tid]=thread;}
+return this.kernelThreadStates_[kernelThreadName];},getOrCreateBinderKernelThread(kernelThreadName,pid,tid){const key=kernelThreadName+pid+tid;if(!this.kernelThreadStates_[key]){const thread=this.model_.getOrCreateProcess(pid).getOrCreateThread(tid);thread.name=kernelThreadName;this.kernelThreadStates_[key]={pid,thread,openSlice:undefined,openSliceTS:undefined};this.threadsByLinuxPid[tid]=thread;}
 return this.kernelThreadStates_[key];},getOrCreatePseudoThread(threadName){let thread=this.kernelThreadStates_[threadName];if(!thread){thread=this.getOrCreateKernelThread(threadName,pseudoKernelPID,this.pseudoThreadCounter);this.pseudoThreadCounter++;}
 return thread;},markPidRunnable(ts,pid,comm,prio,fromPid){this.wakeups_.push({ts,tid:pid,fromTid:fromPid});},addPidBlockedReason(ts,pid,iowait,caller){this.blockedReasons_.push({ts,tid:pid,iowait,caller});},buildMapFromLinuxPidsToThreads_(){this.threadsByLinuxPid={};this.model_.getAllThreads().forEach(function(thread){this.threadsByLinuxPid[thread.tid]=thread;}.bind(this));},buildPerThreadCpuSlicesFromCpuState_(){const SCHEDULING_STATE=tr.model.SCHEDULING_STATE;for(const cpuNumber in this.model_.kernel.cpus){const cpu=this.model_.kernel.cpus[cpuNumber];for(let i=0;i<cpu.slices.length;i++){const cpuSlice=cpu.slices[i];const thread=this.threadsByLinuxPid[cpuSlice.args.tid];if(!thread)continue;cpuSlice.threadThatWasRunning=thread;if(!thread.tempCpuSlices){thread.tempCpuSlices=[];}
 thread.tempCpuSlices.push(cpuSlice);}}
@@ -6071,7 +6135,7 @@
 ProtoExpectation.prototype={get isValid(){return this.end>this.start;},containsTypeNames(typeNames){return this.associatedEvents.some(x=>typeNames.indexOf(x.typeName)>=0);},containsSliceTitle(title){return this.associatedEvents.some(x=>title===x.title);},createInteractionRecord(model){if(this.type!==ProtoExpectation.IGNORED_TYPE&&!this.isValid){model.importWarning({type:'ProtoExpectation',message:'Please file a bug with this trace. '+this.debug(),showToUser:true});return undefined;}
 const duration=this.end-this.start;let ir=undefined;switch(this.type){case ProtoExpectation.RESPONSE_TYPE:ir=new tr.model.um.ResponseExpectation(model,this.initiatorType,this.start,duration,this.isAnimationBegin);break;case ProtoExpectation.ANIMATION_TYPE:ir=new tr.model.um.AnimationExpectation(model,this.initiatorType,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(other){this.initiatorType=combineInitiatorTypes(this.initiatorType,other.initiatorType);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(event){this.start=Math.min(this.start,event.start);this.end=Math.max(this.end,event.end);this.associatedEvents.push(event);},pushSample(sample){this.start=Math.min(this.start,sample.timestamp);this.end=Math.max(this.end,sample.timestamp);this.associatedEvents.push(sample);},containsTimestampInclusive(timestamp){return(this.start<=timestamp)&&(timestamp<=this.end);},intersects(other){return(other.start<this.end)&&(other.end>this.start);},isNear(event,threshold){return(this.end+threshold)>event.start;},debug(){let debugString=this.type+'(';debugString+=parseInt(this.start)+' ';debugString+=parseInt(this.end);this.associatedEvents.forEach(function(event){debugString+=' '+event.typeName;});return debugString+')';}};return{ProtoExpectation,};});'use strict';tr.exportTo('tr.importer',function(){const ProtoExpectation=tr.importer.ProtoExpectation;const INITIATOR_TYPE=tr.model.um.INITIATOR_TYPE;const INPUT_TYPE=tr.e.cc.INPUT_EVENT_TYPE_NAMES;const KEYBOARD_TYPE_NAMES=[INPUT_TYPE.CHAR,INPUT_TYPE.KEY_DOWN_RAW,INPUT_TYPE.KEY_DOWN,INPUT_TYPE.KEY_UP];const MOUSE_RESPONSE_TYPE_NAMES=[INPUT_TYPE.CLICK,INPUT_TYPE.CONTEXT_MENU];const MOUSE_WHEEL_TYPE_NAMES=[INPUT_TYPE.MOUSE_WHEEL];const MOUSE_DRAG_TYPE_NAMES=[INPUT_TYPE.MOUSE_DOWN,INPUT_TYPE.MOUSE_MOVE,INPUT_TYPE.MOUSE_UP];const TAP_TYPE_NAMES=[INPUT_TYPE.TAP,INPUT_TYPE.TAP_CANCEL,INPUT_TYPE.TAP_DOWN];const PINCH_TYPE_NAMES=[INPUT_TYPE.PINCH_BEGIN,INPUT_TYPE.PINCH_END,INPUT_TYPE.PINCH_UPDATE];const FLING_TYPE_NAMES=[INPUT_TYPE.FLING_CANCEL,INPUT_TYPE.FLING_START];const TOUCH_TYPE_NAMES=[INPUT_TYPE.TOUCH_END,INPUT_TYPE.TOUCH_MOVE,INPUT_TYPE.TOUCH_START];const SCROLL_TYPE_NAMES=[INPUT_TYPE.SCROLL_BEGIN,INPUT_TYPE.SCROLL_END,INPUT_TYPE.SCROLL_UPDATE];const 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);const RENDERER_FLING_TITLE='InputHandlerProxy::HandleGestureFling::started';const PLAYBACK_EVENT_TITLE='VideoPlayback';const CSS_ANIMATION_TITLE='Animation';const VR_COUNTER_NAMES=['gpu.WebVR FPS','gpu.WebVR frame time (ms)','gpu.WebVR pose prediction (ms)',];const VR_EVENT_NAMES=['VrShellGl::AcquireFrame','VrShellGl::DrawFrame','VrShellGl::DrawSubmitFrameWhenReady','VrShellGl::DrawUiView','VrShellGl::UpdateController',];const VR_RESPONSE_MS=500;const INPUT_MERGE_THRESHOLD_MS=200;const ANIMATION_MERGE_THRESHOLD_MS=32;const MOUSE_WHEEL_THRESHOLD_MS=40;const MOUSE_MOVE_THRESHOLD_MS=40;function compareEvents(x,y){if(x.start!==y.start){return x.start-y.start;}
+this.associatedEvents.forEach(function(event){pushAssociatedEvents(event);if(event.subSlices){event.subSlices.forEach(pushAssociatedEvents);}});return ir;},merge(other){this.initiatorType=combineInitiatorTypes(this.initiatorType,other.initiatorType);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(event){this.start=Math.min(this.start,event.start);this.end=Math.max(this.end,event.end);this.associatedEvents.push(event);},pushSample(sample){this.start=Math.min(this.start,sample.timestamp);this.end=Math.max(this.end,sample.timestamp);this.associatedEvents.push(sample);},containsTimestampInclusive(timestamp){return(this.start<=timestamp)&&(timestamp<=this.end);},intersects(other){return(other.start<this.end)&&(other.end>this.start);},isNear(event,threshold){return(this.end+threshold)>event.start;},debug(){let debugString=this.type+'(';debugString+=parseInt(this.start)+' ';debugString+=parseInt(this.end);this.associatedEvents.forEach(function(event){debugString+=' '+event.typeName;});return debugString+')';}};return{ProtoExpectation,};});'use strict';tr.exportTo('tr.importer',function(){const ProtoExpectation=tr.importer.ProtoExpectation;const INITIATOR_TYPE=tr.model.um.INITIATOR_TYPE;const INPUT_TYPE=tr.e.cc.INPUT_EVENT_TYPE_NAMES;const KEYBOARD_TYPE_NAMES=[INPUT_TYPE.CHAR,INPUT_TYPE.KEY_DOWN_RAW,INPUT_TYPE.KEY_DOWN,INPUT_TYPE.KEY_UP];const MOUSE_RESPONSE_TYPE_NAMES=[INPUT_TYPE.CLICK,INPUT_TYPE.CONTEXT_MENU];const MOUSE_WHEEL_TYPE_NAMES=[INPUT_TYPE.MOUSE_WHEEL];const MOUSE_DRAG_TYPE_NAMES=[INPUT_TYPE.MOUSE_DOWN,INPUT_TYPE.MOUSE_MOVE,INPUT_TYPE.MOUSE_UP];const TAP_TYPE_NAMES=[INPUT_TYPE.TAP,INPUT_TYPE.TAP_CANCEL,INPUT_TYPE.TAP_DOWN];const PINCH_TYPE_NAMES=[INPUT_TYPE.PINCH_BEGIN,INPUT_TYPE.PINCH_END,INPUT_TYPE.PINCH_UPDATE];const FLING_TYPE_NAMES=[INPUT_TYPE.FLING_CANCEL,INPUT_TYPE.FLING_START];const TOUCH_TYPE_NAMES=[INPUT_TYPE.TOUCH_END,INPUT_TYPE.TOUCH_MOVE,INPUT_TYPE.TOUCH_START];const SCROLL_TYPE_NAMES=[INPUT_TYPE.SCROLL_BEGIN,INPUT_TYPE.SCROLL_END,INPUT_TYPE.SCROLL_UPDATE];const 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);const RENDERER_FLING_TITLE='InputHandlerProxy::HandleGestureFling::started';const PLAYBACK_EVENT_TITLE='VideoPlayback';const CSS_ANIMATION_TITLE='Animation';const VR_COUNTER_NAMES=['gpu.WebVR FPS','gpu.WebVR frame time (ms)','gpu.WebVR pose prediction (ms)',];const VR_EVENT_NAMES=['VrShellGl::AcquireFrame','VrShellGl::DrawFrame','VrShellGl::DrawSubmitFrameWhenReady','VrShellGl::DrawUiView','VrShellGl::UpdateController',];const VR_RESPONSE_MS=1000;const INPUT_MERGE_THRESHOLD_MS=200;const ANIMATION_MERGE_THRESHOLD_MS=32;const MOUSE_WHEEL_THRESHOLD_MS=40;const MOUSE_MOVE_THRESHOLD_MS=40;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;}
@@ -6194,7 +6258,7 @@
 map.set(uniqueKey,value);}
 function skipDumpsThatDoNotIntersectRange(dumps,opt_range){if(!opt_range)return dumps;return dumps.filter(d=>opt_range.intersectsExplicitRangeInclusive(d.start,d.end));}
 function hasCategoryAndName(event,category,title){return event.title===title&&event.category&&tr.b.getCategoryParts(event.category).includes(category);}
-return{hasCategoryAndName,filterExpectationsByRange,perceptualBlend,splitGlobalDumpsByBrowserName};});'use strict';tr.exportTo('tr.e.chrome',function(){class EventFinderUtils{static hasCategoryAndName(event,category,title){return event.title===title&&event.category&&tr.b.getCategoryParts(event.category).includes(category);}
+return{hasCategoryAndName,filterExpectationsByRange,perceptualBlend,splitGlobalDumpsByBrowserName};});'use strict';tr.exportTo('tr.e.chrome',function(){const CHROME_INTERNAL_URLS=['','about:blank','data:text/html,pluginplaceholderdata','chrome-error://chromewebdata/'];const TOP_LEVEL_TASK_TITLES=['TaskQueueManager::ProcessTaskFromWorkQueue','ThreadControllerImpl::DoWork',];class EventFinderUtils{static hasCategoryAndName(event,category,title){return event.title===title&&event.category&&tr.b.getCategoryParts(event.category).includes(category);}
 static getSortedMainThreadEventsByFrame(rendererHelper,eventTitle,eventCategory){const eventsByFrame=new Map();for(const ev of rendererHelper.mainThread.sliceGroup.childEvents()){if(rendererHelper.isTelemetryInternalEvent(ev))continue;if(!this.hasCategoryAndName(ev,eventCategory,eventTitle)){continue;}
 const frameIdRef=ev.args.frame;if(frameIdRef===undefined)continue;if(!eventsByFrame.has(frameIdRef)){eventsByFrame.set(frameIdRef,[]);}
 eventsByFrame.get(frameIdRef).push(ev);}
@@ -6204,8 +6268,9 @@
 static findNextEventStartingOnOrAfterTimestamp(sortedEvents,timestamp){const firstIndexOnOrAfterTimestamp=tr.b.findFirstTrueIndexInSortedArray(sortedEvents,e=>e.start>=timestamp);if(firstIndexOnOrAfterTimestamp===sortedEvents.length){return undefined;}
 return sortedEvents[firstIndexOnOrAfterTimestamp];}
 static findNextEventStartingAfterTimestamp(sortedEvents,timestamp){const firstIndexOnOrAfterTimestamp=tr.b.findFirstTrueIndexInSortedArray(sortedEvents,e=>e.start>timestamp);if(firstIndexOnOrAfterTimestamp===sortedEvents.length){return undefined;}
-return sortedEvents[firstIndexOnOrAfterTimestamp];}}
-return{EventFinderUtils,};});'use strict';tr.exportTo('tr.e.chrome',function(){const TIME_TO_INTERACTIVE_WINDOW_SIZE_MS=5000;const ACTIVE_REQUEST_TOLERANCE=2;const FCI_MIN_CLUSTER_SEPARATION_MS=1000;const TASK_CLUSTER_HEAVINESS_THRESHOLD_MS=250;const ENDPOINT_TYPES={LONG_TASK_START:'LONG_TASK_START',LONG_TASK_END:'LONG_TASK_END',REQUEST_START:'REQUEST_START',REQUEST_END:'REQUEST_END'};function getEndpoints_(events,startType,endType){const endpoints=[];for(const event of events){endpoints.push({time:event.start,type:startType});endpoints.push({time:event.end,type:endType});}
+return sortedEvents[firstIndexOnOrAfterTimestamp];}
+static findToplevelSchedulerTasks(mainThread){const titles=new Set(TOP_LEVEL_TASK_TITLES);const tasks=[];tasks.push(...mainThread.findTopmostSlices(slice=>titles.has(slice.title)));return tasks;}}
+return{EventFinderUtils,CHROME_INTERNAL_URLS,};});'use strict';tr.exportTo('tr.e.chrome',function(){const TIME_TO_INTERACTIVE_WINDOW_SIZE_MS=5000;const ACTIVE_REQUEST_TOLERANCE=2;const FCI_MIN_CLUSTER_SEPARATION_MS=1000;const TASK_CLUSTER_HEAVINESS_THRESHOLD_MS=250;const ENDPOINT_TYPES={LONG_TASK_START:'LONG_TASK_START',LONG_TASK_END:'LONG_TASK_END',REQUEST_START:'REQUEST_START',REQUEST_END:'REQUEST_END'};function getEndpoints_(events,startType,endType){const endpoints=[];for(const event of events){endpoints.push({time:event.start,type:startType});endpoints.push({time:event.end,type:endType});}
 return endpoints;}
 function reachedTTIQuiscence_(timestamp,networkQuietWindowStart,mainThreadQuietWindowStart){if(networkQuietWindowStart===undefined||mainThreadQuietWindowStart===undefined){return false;}
 const mainThreadQuietForLongEnough=timestamp-mainThreadQuietWindowStart>=TIME_TO_INTERACTIVE_WINDOW_SIZE_MS;const networkQuietForLongEnough=timestamp-networkQuietWindowStart>=TIME_TO_INTERACTIVE_WINDOW_SIZE_MS;return mainThreadQuietForLongEnough&&networkQuietForLongEnough;}
@@ -6234,7 +6299,7 @@
 return{findInteractiveTime,findFirstCpuIdleTime,requiredFCIWindowSizeMs,findFCITaskClusters,};});'use strict';tr.exportTo('tr.model.um',function(){const LOAD_SUBTYPE_NAMES={SUCCESSFUL:'Successful',FAILED:'Failed',};const DOES_LOAD_SUBTYPE_NAME_EXIST={};for(const key in LOAD_SUBTYPE_NAMES){DOES_LOAD_SUBTYPE_NAME_EXIST[LOAD_SUBTYPE_NAMES[key]]=true;}
 function LoadExpectation(parentModel,initiatorTitle,start,duration,renderer,navigationStart,fmpEvent,dclEndEvent,cpuIdleTime,timeToInteractive,url,frameId){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=renderer;this.renderMainThread=undefined;this.routingId=undefined;this.parentRoutingId=undefined;this.loadFinishedEvent=undefined;this.navigationStart=navigationStart;this.fmpEvent=fmpEvent;this.domContentLoadedEndEvent=dclEndEvent;this.firstCpuIdleTime=cpuIdleTime;this.timeToInteractive=timeToInteractive;this.url=url;this.frameId=frameId;}
-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,LoadExpectation,};});'use strict';tr.exportTo('tr.importer',function(){const LONG_TASK_THRESHOLD_MS=50;const TOP_LEVEL_TASK_TITLES=['TaskQueueManager::ProcessTaskFromWorkQueue','ThreadControllerImpl::DoWork',];function getNetworkEventsInRange(process,range){const networkEvents=[];for(const thread of Object.values(process.threads)){const threadHelper=new tr.model.helpers.ChromeThreadHelper(thread);const events=threadHelper.getNetworkEvents();for(const event of events){if(range.intersectsExplicitRangeInclusive(event.start,event.end)){networkEvents.push(event);}}}
+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,LoadExpectation,};});'use strict';tr.exportTo('tr.importer',function(){const LONG_TASK_THRESHOLD_MS=50;const IGNORE_URLS=['','about:blank',];function getNetworkEventsInRange(process,range){const networkEvents=[];for(const thread of Object.values(process.threads)){const threadHelper=new tr.model.helpers.ChromeThreadHelper(thread);const events=threadHelper.getNetworkEvents();for(const event of events){if(range.intersectsExplicitRangeInclusive(event.start,event.end)){networkEvents.push(event);}}}
 return networkEvents;}
 function findFrameLoaderSnapshotAt(rendererHelper,frameIdRef,ts){const objects=rendererHelper.process.objects;const frameLoaderInstances=objects.instancesByTypeName_.FrameLoader;if(frameLoaderInstances===undefined)return undefined;let snapshot;for(const instance of frameLoaderInstances){if(!instance.isAliveAt(ts))continue;const maybeSnapshot=instance.getSnapshotAt(ts);if(frameIdRef!==maybeSnapshot.args.frame.id_ref)continue;snapshot=maybeSnapshot;}
 return snapshot;}
@@ -6243,13 +6308,15 @@
 list.push(ev);}
 return candidatesForFrameId;}
 function computeInteractivityMetricSample_(rendererHelper,navigationStart,fmpEvent,domContentLoadedEndEvent,searchWindowEnd){if(domContentLoadedEndEvent===undefined||fmpEvent===undefined){return{interactiveTime:undefined,firstCpuIdleTime:undefined};}
-const firstMeaningfulPaintTime=fmpEvent.start;const mainThreadTasks=[];for(const title of TOP_LEVEL_TASK_TITLES){mainThreadTasks.push(...rendererHelper.mainThread.findTopmostSlicesNamed(title));}
-const longTasks=mainThreadTasks.filter(task=>task.duration>=LONG_TASK_THRESHOLD_MS);const longTasksInWindow=longTasks.filter(task=>task.range.intersectsExplicitRangeInclusive(firstMeaningfulPaintTime,searchWindowEnd));const resourceLoadEvents=getNetworkEventsInRange(rendererHelper.process,tr.b.math.Range.fromExplicitRange(navigationStart.start,searchWindowEnd));const firstCpuIdleTime=tr.e.chrome.findFirstCpuIdleTime(firstMeaningfulPaintTime,searchWindowEnd,domContentLoadedEndEvent.start,longTasksInWindow);const interactiveTime=tr.e.chrome.findInteractiveTime(firstMeaningfulPaintTime,searchWindowEnd,domContentLoadedEndEvent.start,longTasksInWindow,resourceLoadEvents);return{interactiveTime,firstCpuIdleTime};}
-function constructLoadingExpectation_(rendererHelper,frameToDomContentLoadedEndEvents,navigationStart,fmpEvent,nextNavigationStart,url,frameId){const searchWindowEnd=nextNavigationStart!==undefined?nextNavigationStart.start:rendererHelper.modelHelper.chromeBounds.max;const dclTimesForFrame=frameToDomContentLoadedEndEvents.get(frameId)||[];const dclSearchRange=tr.b.math.Range.fromExplicitRange(navigationStart.start,searchWindowEnd);const dclTimesInWindow=dclSearchRange.filterArray(dclTimesForFrame,event=>event.start);let domContentLoadedEndEvent=undefined;if(dclTimesInWindow.length!==0){domContentLoadedEndEvent=dclTimesInWindow[dclTimesInWindow.length-1];}
+const firstMeaningfulPaintTime=fmpEvent.start;const mainThreadTasks=tr.e.chrome.EventFinderUtils.findToplevelSchedulerTasks(rendererHelper.mainThread);const longTasks=mainThreadTasks.filter(task=>task.duration>=LONG_TASK_THRESHOLD_MS);const longTasksInWindow=longTasks.filter(task=>task.range.intersectsExplicitRangeInclusive(firstMeaningfulPaintTime,searchWindowEnd));const resourceLoadEvents=getNetworkEventsInRange(rendererHelper.process,tr.b.math.Range.fromExplicitRange(navigationStart.start,searchWindowEnd));const firstCpuIdleTime=tr.e.chrome.findFirstCpuIdleTime(firstMeaningfulPaintTime,searchWindowEnd,domContentLoadedEndEvent.start,longTasksInWindow);const interactiveTime=resourceLoadEvents.length>0?tr.e.chrome.findInteractiveTime(firstMeaningfulPaintTime,searchWindowEnd,domContentLoadedEndEvent.start,longTasksInWindow,resourceLoadEvents):undefined;return{interactiveTime,firstCpuIdleTime};}
+function constructLoadingExpectation_(rendererHelper,frameToDomContentLoadedEndEvents,navigationStart,fmpEvent,searchWindowEnd,url,frameId){const dclTimesForFrame=frameToDomContentLoadedEndEvents.get(frameId)||[];const dclSearchRange=tr.b.math.Range.fromExplicitRange(navigationStart.start,searchWindowEnd);const dclTimesInWindow=dclSearchRange.filterArray(dclTimesForFrame,event=>event.start);let domContentLoadedEndEvent=undefined;if(dclTimesInWindow.length!==0){domContentLoadedEndEvent=dclTimesInWindow[dclTimesInWindow.length-1];}
 const{interactiveTime,firstCpuIdleTime}=computeInteractivityMetricSample_(rendererHelper,navigationStart,fmpEvent,domContentLoadedEndEvent,searchWindowEnd);const duration=(interactiveTime===undefined)?searchWindowEnd-navigationStart.start:interactiveTime-navigationStart.start;return new tr.model.um.LoadExpectation(rendererHelper.modelHelper.model,tr.model.um.LOAD_SUBTYPE_NAMES.SUCCESSFUL,navigationStart.start,duration,rendererHelper.process,navigationStart,fmpEvent,domContentLoadedEndEvent,firstCpuIdleTime,interactiveTime,url,frameId);}
-function collectLoadExpectationsForRenderer(rendererHelper){const samples=[];const frameToNavStartEvents=tr.e.chrome.EventFinderUtils.getSortedMainThreadEventsByFrame(rendererHelper,'navigationStart','blink.user_timing');const frameToDomContentLoadedEndEvents=tr.e.chrome.EventFinderUtils.getSortedMainThreadEventsByFrame(rendererHelper,'domContentLoadedEventEnd','blink.user_timing');function addSamples(frameIdRef,navigationStart,fmpMarkerEvent,nextNavigationStart){const timestamp=fmpMarkerEvent===undefined?navigationStart.start:fmpMarkerEvent.start;const snapshot=findFrameLoaderSnapshotAt(rendererHelper,frameIdRef,timestamp);if(!snapshot||!snapshot.args.isLoadingMainFrame)return;const url=snapshot.args.documentLoaderURL;samples.push(constructLoadingExpectation_(rendererHelper,frameToDomContentLoadedEndEvents,navigationStart,fmpMarkerEvent,nextNavigationStart,url,frameIdRef));}
-const candidatesForFrameId=findFirstMeaningfulPaintCandidates(rendererHelper);for(const[frameIdRef,navStartEvents]of frameToNavStartEvents){const fmpCandidateList=candidatesForFrameId[frameIdRef]||[];for(let index=0;index<navStartEvents.length-1;index++){const currNavigation=navStartEvents[index];const nextNavigation=navStartEvents[index+1];const fmpCandidate=tr.e.chrome.EventFinderUtils.findLastEventStartingBeforeTimestamp(fmpCandidateList,nextNavigation.start);if(fmpCandidate!==undefined&&currNavigation.start<=fmpCandidate.start){addSamples(frameIdRef,currNavigation,fmpCandidate,nextNavigation);}else{addSamples(frameIdRef,currNavigation,undefined,nextNavigation);}}
-addSamples(frameIdRef,navStartEvents[navStartEvents.length-1],fmpCandidateList[fmpCandidateList.length-1],undefined);}
+function collectLoadExpectationsForRenderer(rendererHelper){const samples=[];const frameToNavStartEvents=tr.e.chrome.EventFinderUtils.getSortedMainThreadEventsByFrame(rendererHelper,'navigationStart','blink.user_timing');const frameToDomContentLoadedEndEvents=tr.e.chrome.EventFinderUtils.getSortedMainThreadEventsByFrame(rendererHelper,'domContentLoadedEventEnd','blink.user_timing');function addSamples(frameIdRef,navigationStart,fmpCandidateEvents,searchWindowEnd,url){let fmpMarkerEvent=tr.e.chrome.EventFinderUtils.findLastEventStartingOnOrBeforeTimestamp(fmpCandidateEvents,searchWindowEnd);if(fmpMarkerEvent!==undefined&&navigationStart.start>fmpMarkerEvent.start){fmpMarkerEvent=undefined;}
+samples.push(constructLoadingExpectation_(rendererHelper,frameToDomContentLoadedEndEvents,navigationStart,fmpMarkerEvent,searchWindowEnd,url,frameIdRef));}
+const candidatesForFrameId=findFirstMeaningfulPaintCandidates(rendererHelper);for(const[frameIdRef,navStartEvents]of frameToNavStartEvents){const fmpCandidateEvents=candidatesForFrameId[frameIdRef]||[];let prevNavigation={navigationEvent:undefined,url:undefined};for(let index=0;index<navStartEvents.length;index++){const currNavigation=navStartEvents[index];let url;let isLoadingMainFrame=false;if(currNavigation.args.data){url=currNavigation.args.data.documentLoaderURL;isLoadingMainFrame=currNavigation.args.data.isLoadingMainFrame;}else{const snapshot=findFrameLoaderSnapshotAt(rendererHelper,frameIdRef,currNavigation.start);if(snapshot){url=snapshot.args.documentLoaderURL;isLoadingMainFrame=snapshot.args.isLoadingMainFrame;}}
+if(!isLoadingMainFrame)continue;if(url===undefined||IGNORE_URLS.includes(url))continue;if(prevNavigation.navigationEvent!==undefined){addSamples(frameIdRef,prevNavigation.navigationEvent,fmpCandidateEvents,currNavigation.start,prevNavigation.url);}
+prevNavigation={navigationEvent:currNavigation,url};}
+if(prevNavigation.navigationEvent!==undefined){addSamples(frameIdRef,prevNavigation.navigationEvent,fmpCandidateEvents,rendererHelper.modelHelper.chromeBounds.max,prevNavigation.url);}}
 return samples;}
 function findLoadExpectations(modelHelper){const loads=[];const chromeHelper=modelHelper.model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);for(const pid in chromeHelper.rendererHelpers){const rendererHelper=chromeHelper.rendererHelpers[pid];if(rendererHelper.isChromeTracingUI)continue;loads.push.apply(loads,collectLoadExpectationsForRenderer(rendererHelper));}
 return loads;}
@@ -6369,6 +6436,7 @@
 for(let opIndex=0;opIndex<ops.length;opIndex++){let min=Number.MAX_VALUE;for(let i=0;i<OPS_TIMING_ITERATIONS;i++){min=Math.min(min,opTimings[i].cmd_times[opIndex]);}
 ops[opIndex].cmd_time=min;}
 return ops;},rasterize(params,rasterCompleteCallback){if(!PictureSnapshot.CanRasterize()||!PictureSnapshot.CanGetOps()){rasterCompleteCallback(new tr.e.cc.PictureAsImageData(this,tr.e.cc.PictureSnapshot.HowToEnablePictureDebugging()));return;}
+if(!this.layerRect_.width||!this.layerRect_.height){rasterCompleteCallback(new tr.e.cc.PictureAsImageData(this,null));return;}
 const raster=window.chrome.skiaBenchmarking.rasterize({skp64:this.skp64_,params:{layer_rect:this.layerRect_.toArray()}},{stop:params.stopIndex===undefined?-1:params.stopIndex,overdraw:!!params.showOverdraw,params:{}});if(raster){const canvas=document.createElement('canvas');const ctx=canvas.getContext('2d');canvas.width=raster.width;canvas.height=raster.height;const imageData=ctx.createImageData(raster.width,raster.height);imageData.data.set(new Uint8ClampedArray(raster.data));rasterCompleteCallback(new tr.e.cc.PictureAsImageData(this,imageData));}else{const error='Failed to rasterize picture. '+'Your recording may be from an old Chrome version. '+'The SkPicture format is not backward compatible.';rasterCompleteCallback(new tr.e.cc.PictureAsImageData(this,error));}}};function LayeredPicture(pictures){this.guid_=tr.b.GUID.allocateSimple();this.pictures_=pictures;this.layerRect_=undefined;}
 LayeredPicture.prototype={__proto__:Picture.prototype,get canSave(){return false;},get typeName(){return'cc::LayeredPicture';},get layerRect(){if(this.layerRect_!==undefined){return this.layerRect_;}
 this.layerRect_={x:0,y:0,width:0,height:0};for(let i=0;i<this.pictures_.length;++i){const rect=this.pictures_[i].layerRect;this.layerRect_.x=Math.min(this.layerRect_.x,rect.x);this.layerRect_.y=Math.min(this.layerRect_.y,rect.y);this.layerRect_.width=Math.max(this.layerRect_.width,rect.x+rect.width);this.layerRect_.height=Math.max(this.layerRect_.height,rect.y+rect.height);}
@@ -6389,12 +6457,12 @@
 return true;};PictureSnapshot.CanGetInfo=function(){if(!PictureSnapshot.HasSkiaBenchmarking()){return false;}
 if(!window.chrome.skiaBenchmarking.getInfo){return false;}
 return true;};PictureSnapshot.HowToEnablePictureDebugging=function(){if(tr.isHeadless){return'Pictures only work in chrome';}
-const usualReason=['For pictures to show up, you need to have Chrome running with ','--enable-skia-benchmarking. Please restart chrome with this flag ','and try again.'].join('');if(!tr.isExported('global.chrome.skiaBenchmarking')){return usualReason;}
-if(!global.chrome.skiaBenchmarking.rasterize){return'Your chrome is old';}
-if(!global.chrome.skiaBenchmarking.getOps){return'Your chrome is old: skiaBenchmarking.getOps not found';}
-if(!global.chrome.skiaBenchmarking.getOpTimings){return'Your chrome is old: skiaBenchmarking.getOpTimings not found';}
-if(!global.chrome.skiaBenchmarking.getInfo){return'Your chrome is old: skiaBenchmarking.getInfo not found';}
-return'Rasterizing is on';};PictureSnapshot.prototype={__proto__:ObjectSnapshot.prototype,preInitialize(){tr.e.cc.preInitializeObject(this);this.rasterResult_=undefined;},initialize(){if(this.args.alias){this.args=this.args.alias.args;}
+const usualReason=['For pictures to show up, you need to have Chrome running with ','--enable-skia-benchmarking. Please restart chrome with this flag ','and try again.'].join('');if(!PictureSnapshot.HasSkiaBenchmarking()){return usualReason;}
+if(!PictureSnapshot.CanRasterize()){return'Your chrome is old: chrome.skipBenchmarking.rasterize not found';}
+if(!PictureSnapshot.CanGetOps()){return'Your chrome is old: chrome.skiaBenchmarking.getOps not found';}
+if(!PictureSnapshot.CanGetOpTimings()){return'Your chrome is old: '+'chrome.skiaBenchmarking.getOpTimings not found';}
+if(!PictureSnapshot.CanGetInfo()){return'Your chrome is old: chrome.skiaBenchmarking.getInfo not found';}
+return undefined;};PictureSnapshot.CanDebugPicture=function(){return PictureSnapshot.HowToEnablePictureDebugging()===undefined;};PictureSnapshot.prototype={__proto__:ObjectSnapshot.prototype,preInitialize(){tr.e.cc.preInitializeObject(this);this.rasterResult_=undefined;},initialize(){if(this.args.alias){this.args=this.args.alias.args;}
 if(!this.args.params.layerRect){throw new Error('Missing layer rect');}
 this.layerRect_=this.args.params.layerRect;this.picture_=new Picture(this.args.skp64,this.args.params.layerRect);},set picture(picture){this.picture_=picture;},get canSave(){return this.picture_.canSave;},get layerRect(){return this.layerRect_?this.layerRect_:this.picture_.layerRect;},get guid(){return this.picture_.guid;},getBase64SkpData(){return this.picture_.getBase64SkpData();},getOps(){return this.picture_.getOps();},getOpTimings(){return this.picture_.getOpTimings();},tagOpsWithTimings(ops){return this.picture_.tagOpsWithTimings(ops);},rasterize(params,rasterCompleteCallback){this.picture_.rasterize(params,rasterCompleteCallback);}};ObjectSnapshot.subTypes.register(PictureSnapshot,{typeNames:['cc::Picture']});return{PictureSnapshot,Picture,LayeredPicture,};});'use strict';tr.exportTo('tr.e.cc',function(){const ObjectSnapshot=tr.model.ObjectSnapshot;function DisplayItemList(skp64,layerRect){tr.e.cc.Picture.apply(this,arguments);}
 DisplayItemList.prototype={__proto__:tr.e.cc.Picture.prototype};function DisplayItemListSnapshot(){tr.e.cc.PictureSnapshot.apply(this,arguments);}
@@ -6411,7 +6479,7 @@
 return r;};Region.fromArrayOrUndefined=function(array){if(array===undefined)return new Region();return Region.fromArray(array);};Region.prototype={__proto__:Region.prototype,rectIntersects(r){for(let i=0;i<this.rects.length;i++){if(this.rects[i].intersects(r))return true;}
 return false;},addRect(r){this.rects.push(r);}};return{Region,};});'use strict';tr.exportTo('tr.e.cc',function(){function TileCoverageRect(rect,tile){this.geometryRect=rect;this.tile=tile;}
 return{TileCoverageRect,};});'use strict';tr.exportTo('tr.e.cc',function(){const constants=tr.e.cc.constants;const ObjectSnapshot=tr.model.ObjectSnapshot;function LayerImplSnapshot(){ObjectSnapshot.apply(this,arguments);}
-LayerImplSnapshot.prototype={__proto__:ObjectSnapshot.prototype,preInitialize(){tr.e.cc.preInitializeObject(this);this.layerTreeImpl_=undefined;this.parentLayer=undefined;},initialize(){this.invalidation=new tr.e.cc.Region();this.annotatedInvalidation=new tr.e.cc.Region();this.unrecordedRegion=new tr.e.cc.Region();this.pictures=[];tr.e.cc.moveRequiredFieldsFromArgsToToplevel(this,['layerId','layerQuad']);tr.e.cc.moveOptionalFieldsFromArgsToToplevel(this,['children','maskLayer','replicaLayer','idealContentsScale','geometryContentsScale','layoutRects','usingGpuRasterization']);this.gpuMemoryUsageInBytes=this.args.gpuMemoryUsage;this.bounds=tr.b.math.Rect.fromXYWH(0,0,this.args.bounds.width,this.args.bounds.height);if(this.args.animationBounds){this.animationBoundsRect=tr.b.math.Rect.fromXYWH(this.args.animationBounds[0],this.args.animationBounds[1],this.args.animationBounds[3],this.args.animationBounds[4]);}
+LayerImplSnapshot.prototype={__proto__:ObjectSnapshot.prototype,preInitialize(){tr.e.cc.preInitializeObject(this);this.layerTreeImpl_=undefined;this.parentLayer=undefined;},initialize(){this.invalidation=new tr.e.cc.Region();this.unrecordedRegion=new tr.e.cc.Region();this.pictures=[];tr.e.cc.moveRequiredFieldsFromArgsToToplevel(this,['layerId','layerQuad']);tr.e.cc.moveOptionalFieldsFromArgsToToplevel(this,['children','maskLayer','replicaLayer','idealContentsScale','geometryContentsScale','layoutRects','usingGpuRasterization']);this.gpuMemoryUsageInBytes=this.args.gpuMemoryUsage;this.bounds=tr.b.math.Rect.fromXYWH(0,0,this.args.bounds.width,this.args.bounds.height);if(this.args.animationBounds){this.animationBoundsRect=tr.b.math.Rect.fromXYWH(this.args.animationBounds[0],this.args.animationBounds[1],this.args.animationBounds[3],this.args.animationBounds[4]);}
 if(this.children){for(let i=0;i<this.children.length;i++){this.children[i].parentLayer=this;}}
 if(this.maskLayer){this.maskLayer.parentLayer=this;}
 if(this.replicaLayer){this.replicaLayer.parentLayer=this;}
@@ -6422,10 +6490,11 @@
 return undefined;},set layerTreeImpl(layerTreeImpl){this.layerTreeImpl_=layerTreeImpl;},get activeLayer(){if(this.layerTreeImpl.whichTree===constants.ACTIVE_TREE){return this;}
 const activeTree=this.layerTreeImpl.layerTreeHostImpl.activeTree;return activeTree.findLayerWithId(this.layerId);},get pendingLayer(){if(this.layerTreeImpl.whichTree===constants.PENDING_TREE){return this;}
 const pendingTree=this.layerTreeImpl.layerTreeHostImpl.pendingTree;return pendingTree.findLayerWithId(this.layerId);}};function PictureLayerImplSnapshot(){LayerImplSnapshot.apply(this,arguments);}
-PictureLayerImplSnapshot.prototype={__proto__:LayerImplSnapshot.prototype,initialize(){LayerImplSnapshot.prototype.initialize.call(this);if(this.args.invalidation){this.invalidation=tr.e.cc.Region.fromArray(this.args.invalidation);delete this.args.invalidation;}
-if(this.args.annotatedInvalidationRects){this.annotatedInvalidation=new tr.e.cc.Region();for(let i=0;i<this.args.annotatedInvalidationRects.length;++i){const annotatedRect=this.args.annotatedInvalidationRects[i];const rect=annotatedRect.geometryRect;rect.reason=annotatedRect.reason;this.annotatedInvalidation.addRect(rect);}
-delete this.args.annotatedInvalidationRects;}
-if(this.args.unrecordedRegion){this.unrecordedRegion=tr.e.cc.Region.fromArray(this.args.unrecordedRegion);delete this.args.unrecordedRegion;}
+PictureLayerImplSnapshot.prototype={__proto__:LayerImplSnapshot.prototype,initialize(){LayerImplSnapshot.prototype.initialize.call(this);if(this.args.debugInfo){for(const i in this.args.debugInfo){this.args[i]=this.args.debugInfo[i];}
+delete this.args.debugInfo;}
+if(this.args.annotatedInvalidationRects){this.invalidation=new tr.e.cc.Region();for(const annotatedRect of this.args.annotatedInvalidationRects){const rect=annotatedRect.geometryRect;rect.reason=annotatedRect.reason;rect.client=annotatedRect.client;this.invalidation.addRect(rect);}
+delete this.args.annotatedInvalidationRects;}else if(this.args.invalidation){this.invalidation=tr.e.cc.Region.fromArray(this.args.invalidation);}
+delete this.args.invalidation;if(this.args.unrecordedRegion){this.unrecordedRegion=tr.e.cc.Region.fromArray(this.args.unrecordedRegion);delete this.args.unrecordedRegion;}
 if(this.args.pictures){this.pictures=this.args.pictures;this.pictures.sort(function(a,b){return a.ts-b.ts;});}
 this.tileCoverageRects=[];if(this.args.coverageTiles){for(let i=0;i<this.args.coverageTiles.length;++i){const rect=this.args.coverageTiles[i].geometryRect.scale(this.idealContentsScale);const tile=this.args.coverageTiles[i].tile;this.tileCoverageRects.push(new tr.e.cc.TileCoverageRect(rect,tile));}
 delete this.args.coverageTiles;}}};ObjectSnapshot.subTypes.register(PictureLayerImplSnapshot,{typeName:'cc::PictureLayerImpl'});ObjectSnapshot.subTypes.register(LayerImplSnapshot,{typeNames:['cc::LayerImpl','cc::DelegatedRendererLayerImpl','cc::HeadsUpDisplayLayerImpl','cc::IOSurfaceLayerImpl','cc::NinePatchLayerImpl','cc::PictureImageLayerImpl','cc::ScrollbarLayerImpl','cc::SolidColorLayerImpl','cc::SolidColorScrollbarLayerImpl','cc::SurfaceLayerImpl','cc::TextureLayerImpl','cc::TiledLayerImpl','cc::VideoLayerImpl','cc::PaintedScrollbarLayerImpl','ClankPatchLayer','TabBorderLayer','CounterLayer']});return{LayerImplSnapshot,PictureLayerImplSnapshot,};});'use strict';tr.exportTo('tr.e.cc',function(){const constants=tr.e.cc.constants;const ObjectSnapshot=tr.model.ObjectSnapshot;function LayerTreeImplSnapshot(){ObjectSnapshot.apply(this,arguments);}
@@ -6475,9 +6544,9 @@
 this.selection_=selection;},get findMatches(){return this.findMatches_;},set findMatches(findMatches){if(this.appliedToModel_){throw new Error('Cannot mutate this state right now');}
 if(findMatches===undefined){findMatches=new EventSet();}
 this.findMatches_=findMatches;},get analysisViewRelatedEvents(){return this.analysisViewRelatedEvents_;},set analysisViewRelatedEvents(analysisViewRelatedEvents){if(this.appliedToModel_){throw new Error('Cannot mutate this state right now');}
-if(analysisViewRelatedEvents===undefined){analysisViewRelatedEvents=new EventSet();}
+if(!(analysisViewRelatedEvents instanceof EventSet)){analysisViewRelatedEvents=new EventSet();}
 this.analysisViewRelatedEvents_=analysisViewRelatedEvents;},get analysisLinkHoveredEvents(){return this.analysisLinkHoveredEvents_;},set analysisLinkHoveredEvents(analysisLinkHoveredEvents){if(this.appliedToModel_){throw new Error('Cannot mutate this state right now');}
-if(analysisLinkHoveredEvents===undefined){analysisLinkHoveredEvents=new EventSet();}
+if(!(analysisLinkHoveredEvents instanceof EventSet)){analysisLinkHoveredEvents=new EventSet();}
 this.analysisLinkHoveredEvents_=analysisLinkHoveredEvents;},get isAppliedToModel(){return this.appliedToModel_!==undefined;},get viewSpecificBrushingStates(){return this.viewSpecificBrushingStates_;},set viewSpecificBrushingStates(viewSpecificBrushingStates){this.viewSpecificBrushingStates_=viewSpecificBrushingStates;},get dimmedEvents_(){const dimmedEvents=new EventSet();dimmedEvents.addEventSet(this.findMatches);dimmedEvents.addEventSet(this.analysisViewRelatedEvents_);return dimmedEvents;},get brightenedEvents_(){const brightenedEvents=new EventSet();brightenedEvents.addEventSet(this.selection_);brightenedEvents.addEventSet(this.analysisLinkHoveredEvents_);return brightenedEvents;},applyToEventSelectionStates(model){this.appliedToModel_=model;const dimmedEvents=this.dimmedEvents_;if(model){const newDefaultState=(dimmedEvents.length?SelectionState.DIMMED0:SelectionState.NONE);const currentDefaultState=tr.b.getFirstElement(model.getDescendantEvents()).selectionState;if(currentDefaultState!==newDefaultState){for(const e of model.getDescendantEvents()){e.selectionState=newDefaultState;}}}
 let score;for(const e of dimmedEvents){score=0;if(this.findMatches_.contains(e)){score++;}
 if(this.analysisViewRelatedEvents_.contains(e)){score++;}
@@ -6837,20 +6906,26 @@
 tr.v.d.Diagnostic.register(DateRange,{elementName:'tr-v-ui-date-range-span'});return{DateRange,};});'use strict';tr.exportTo('tr.v.d',function(){class DiagnosticRef{constructor(guid){this.guid=guid;}
 asDict(){return this.guid;}
 asDictOrReference(){return this.asDict();}}
-return{DiagnosticRef,};});'use strict';tr.exportTo('tr.v.d',function(){function stableStringify(obj){let replacer;if(!(obj instanceof Array))replacer=Object.keys(obj).sort();return JSON.stringify(obj,replacer);}
-class GenericSet extends tr.v.d.Diagnostic{constructor(values){super();this.values_=new Set(values);}
+return{DiagnosticRef,};});'use strict';tr.exportTo('tr.v.d',function(){function stableStringify(obj){let replacer;if(!(obj instanceof Array)&&obj!==null){replacer=Object.keys(obj).sort();}
+return JSON.stringify(obj,replacer);}
+class GenericSet extends tr.v.d.Diagnostic{constructor(values){super();if(typeof values[Symbol.iterator]!=='function'){throw new Error('GenericSet must be constructed from an interable.');}
+this.values_=new Set(values);this.has_objects_=false;for(const value of values){if(typeof value==='object'){this.has_objects_=true;}}}
 get size(){return this.values_.size;}
 get length(){return this.values_.size;}*[Symbol.iterator](){for(const value of this.values_){yield value;}}
 has(value){if(typeof value!=='object')return this.values_.has(value);const json=JSON.stringify(value);for(const x of this){if(typeof x!=='object')continue;if(json===JSON.stringify(x))return true;}
 return false;}
 equals(other){if(!(other instanceof GenericSet))return false;if(this.size!==other.size)return false;for(const value of this){if(!other.has(value))return false;}
 return true;}
+get hashKey(){if(this.has_objects_)return undefined;if(this.hash_key_!==undefined){return this.hash_key_;}
+let key='';for(const value of Array.from(this.values_.values()).sort()){key+=value;}
+this.hash_key_=key;return key;}
 asDictInto_(d){d.values=Array.from(this);}
 static fromDict(d){return new GenericSet(d.values);}
 clone(){return new GenericSet(this.values_);}
 canAddDiagnostic(otherDiagnostic){return otherDiagnostic instanceof GenericSet;}
 addDiagnostic(otherDiagnostic){const jsons=new Set();for(const value of this){if(typeof value!=='object')continue;jsons.add(stableStringify(value));}
-for(const value of otherDiagnostic){if(typeof value==='object'&&jsons.has(stableStringify(value))){continue;}
+for(const value of otherDiagnostic){if(typeof value==='object'){if(jsons.has(stableStringify(value))){continue;}
+this.has_objects_=true;}
 this.values_.add(value);}}}
 tr.v.d.Diagnostic.register(GenericSet,{elementName:'tr-v-ui-generic-set-span'});return{GenericSet,};});'use strict';tr.exportTo('tr.v.d',function(){class GroupingPath extends tr.v.d.Diagnostic{constructor(groupingPath){super();this.groupingPath_=groupingPath;}
 clone(){return new GroupingPath(Array.from(this.groupingPath_));}
@@ -6943,7 +7018,7 @@
 get length(){return this._diagnostics.length;}*[Symbol.iterator](){for(const diagnostic of this._diagnostics)yield diagnostic;}
 asDictInto_(d){d.diagnostics=this._diagnostics.map(d=>d.asDictOrReference());}
 static fromDict(d){return new UnmergeableDiagnosticSet(d.diagnostics.map(d=>((typeof d==='string')?new tr.v.d.DiagnosticRef(d):tr.v.d.Diagnostic.fromDict(d))));}}
-tr.v.d.Diagnostic.register(UnmergeableDiagnosticSet,{elementName:'tr-v-ui-unmergeable-diagnostic-set-span'});return{UnmergeableDiagnosticSet,};});'use strict';tr.exportTo('tr.v.d',function(){const RESERVED_INFOS={ANGLE_REVISIONS:{name:'angleRevisions',type:tr.v.d.GenericSet},ARCHITECTURES:{name:'architectures',type:tr.v.d.GenericSet},BENCHMARKS:{name:'benchmarks',type:tr.v.d.GenericSet},BENCHMARK_START:{name:'benchmarkStart',type:tr.v.d.DateRange},BENCHMARK_DESCRIPTIONS:{name:'benchmarkDescriptions',type:tr.v.d.GenericSet},BOTS:{name:'bots',type:tr.v.d.GenericSet},BUG_COMPONENTS:{name:'bugComponents',type:tr.v.d.GenericSet},BUILDS:{name:'builds',type:tr.v.d.GenericSet},CATAPULT_REVISIONS:{name:'catapultRevisions',type:tr.v.d.GenericSet},CHROMIUM_COMMIT_POSITIONS:{name:'chromiumCommitPositions',type:tr.v.d.GenericSet},CHROMIUM_REVISIONS:{name:'chromiumRevisions',type:tr.v.d.GenericSet},DEVICE_IDS:{name:'deviceIds',type:tr.v.d.GenericSet},GPUS:{name:'gpus',type:tr.v.d.GenericSet},GROUPING_PATH:{name:'groupingPath',type:tr.v.d.GroupingPath},IS_REFERENCE_BUILD:{name:'isReferenceBuild',type:tr.v.d.GenericSet},LABELS:{name:'labels',type:tr.v.d.GenericSet},LOG_URLS:{name:'logUrls',type:tr.v.d.GenericSet},MASTERS:{name:'masters',type:tr.v.d.GenericSet},MEMORY_AMOUNTS:{name:'memoryAmounts',type:tr.v.d.GenericSet},MERGED_FROM:{name:'mergedFrom',type:tr.v.d.RelatedHistogramMap},MERGED_TO:{name:'mergedTo',type:tr.v.d.RelatedHistogramMap},OS_NAMES:{name:'osNames',type:tr.v.d.GenericSet},OS_VERSIONS:{name:'osVersions',type:tr.v.d.GenericSet},OWNERS:{name:'owners',type:tr.v.d.GenericSet},PRODUCT_VERSIONS:{name:'productVersions',type:tr.v.d.GenericSet},RELATED_NAMES:{name:'relatedNames',type:tr.v.d.GenericSet},SKIA_REVISIONS:{name:'skiaRevisions',type:tr.v.d.GenericSet},STORIES:{name:'stories',type:tr.v.d.GenericSet},STORYSET_REPEATS:{name:'storysetRepeats',type:tr.v.d.GenericSet},STORY_TAGS:{name:'storyTags',type:tr.v.d.GenericSet},TAG_MAP:{name:'tagmap',type:tr.v.d.TagMap},TRACE_START:{name:'traceStart',type:tr.v.d.DateRange},TRACE_URLS:{name:'traceUrls',type:tr.v.d.GenericSet},V8_COMMIT_POSITIONS:{name:'v8CommitPositions',type:tr.v.d.DateRange},V8_REVISIONS:{name:'v8Revisions',type:tr.v.d.GenericSet},WEBRTC_REVISIONS:{name:'webrtcRevisions',type:tr.v.d.GenericSet},};const RESERVED_NAMES={};const RESERVED_NAMES_TO_TYPES=new Map();for(const[codename,info]of Object.entries(RESERVED_INFOS)){RESERVED_NAMES[codename]=info.name;if(RESERVED_NAMES_TO_TYPES.has(info.name)){throw new Error(`Duplicate reserved name "${info.name}"`);}
+tr.v.d.Diagnostic.register(UnmergeableDiagnosticSet,{elementName:'tr-v-ui-unmergeable-diagnostic-set-span'});return{UnmergeableDiagnosticSet,};});'use strict';tr.exportTo('tr.v.d',function(){const RESERVED_INFOS={ANGLE_REVISIONS:{name:'angleRevisions',type:tr.v.d.GenericSet},ARCHITECTURES:{name:'architectures',type:tr.v.d.GenericSet},BENCHMARKS:{name:'benchmarks',type:tr.v.d.GenericSet},BENCHMARK_START:{name:'benchmarkStart',type:tr.v.d.DateRange},BENCHMARK_DESCRIPTIONS:{name:'benchmarkDescriptions',type:tr.v.d.GenericSet},BOTS:{name:'bots',type:tr.v.d.GenericSet},BUG_COMPONENTS:{name:'bugComponents',type:tr.v.d.GenericSet},BUILDS:{name:'builds',type:tr.v.d.GenericSet},CATAPULT_REVISIONS:{name:'catapultRevisions',type:tr.v.d.GenericSet},CHROMIUM_COMMIT_POSITIONS:{name:'chromiumCommitPositions',type:tr.v.d.GenericSet},CHROMIUM_REVISIONS:{name:'chromiumRevisions',type:tr.v.d.GenericSet},DEVICE_IDS:{name:'deviceIds',type:tr.v.d.GenericSet},DOCUMENTATION_URLS:{name:'documentationUrls',type:tr.v.d.GenericSet},FUCHSIA_GARNET_REVISIONS:{name:'fuchsiaGarnetRevisions',type:tr.v.d.GenericSet},FUCHSIA_PERIDOT_REVISIONS:{name:'fuchsiaPeridotRevisions',type:tr.v.d.GenericSet},FUCHSIA_TOPAZ_REVISIONS:{name:'fuchsiaTopazRevisions',type:tr.v.d.GenericSet},FUCHSIA_ZIRCON_REVISIONS:{name:'fuchsiaZirconRevisions',type:tr.v.d.GenericSet},GPUS:{name:'gpus',type:tr.v.d.GenericSet},GROUPING_PATH:{name:'groupingPath',type:tr.v.d.GroupingPath},IS_REFERENCE_BUILD:{name:'isReferenceBuild',type:tr.v.d.GenericSet},LABELS:{name:'labels',type:tr.v.d.GenericSet},LOG_URLS:{name:'logUrls',type:tr.v.d.GenericSet},MASTERS:{name:'masters',type:tr.v.d.GenericSet},MEMORY_AMOUNTS:{name:'memoryAmounts',type:tr.v.d.GenericSet},MERGED_FROM:{name:'mergedFrom',type:tr.v.d.RelatedHistogramMap},MERGED_TO:{name:'mergedTo',type:tr.v.d.RelatedHistogramMap},OS_NAMES:{name:'osNames',type:tr.v.d.GenericSet},OS_VERSIONS:{name:'osVersions',type:tr.v.d.GenericSet},OWNERS:{name:'owners',type:tr.v.d.GenericSet},POINT_ID:{name:'pointId',type:tr.v.d.GenericSet},PRODUCT_VERSIONS:{name:'productVersions',type:tr.v.d.GenericSet},RELATED_NAMES:{name:'relatedNames',type:tr.v.d.GenericSet},SKIA_REVISIONS:{name:'skiaRevisions',type:tr.v.d.GenericSet},STORIES:{name:'stories',type:tr.v.d.GenericSet},STORYSET_REPEATS:{name:'storysetRepeats',type:tr.v.d.GenericSet},STORY_TAGS:{name:'storyTags',type:tr.v.d.GenericSet},SUMMARY_KEYS:{name:'summaryKeys',type:tr.v.d.GenericSet},TAG_MAP:{name:'tagmap',type:tr.v.d.TagMap},TEST_PATH:{name:'testPath',type:tr.v.d.GenericSet},TRACE_START:{name:'traceStart',type:tr.v.d.DateRange},TRACE_URLS:{name:'traceUrls',type:tr.v.d.GenericSet},V8_COMMIT_POSITIONS:{name:'v8CommitPositions',type:tr.v.d.DateRange},V8_REVISIONS:{name:'v8Revisions',type:tr.v.d.GenericSet},WEBRTC_REVISIONS:{name:'webrtcRevisions',type:tr.v.d.GenericSet},};const RESERVED_NAMES={};const RESERVED_NAMES_TO_TYPES=new Map();for(const[codename,info]of Object.entries(RESERVED_INFOS)){RESERVED_NAMES[codename]=info.name;if(RESERVED_NAMES_TO_TYPES.has(info.name)){throw new Error(`Duplicate reserved name "${info.name}"`);}
 RESERVED_NAMES_TO_TYPES.set(info.name,info.type);}
 const RESERVED_NAMES_SET=new Set(Object.values(RESERVED_NAMES));return{RESERVED_INFOS,RESERVED_NAMES,RESERVED_NAMES_SET,RESERVED_NAMES_TO_TYPES,};});'use strict';tr.exportTo('tr.v.d',function(){class DiagnosticMap extends Map{constructor(opt_allowReservedNames){super();if(opt_allowReservedNames===undefined){opt_allowReservedNames=true;}
 this.allowReservedNames_=opt_allowReservedNames;}
@@ -7142,7 +7217,7 @@
 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.binRanges_=undefined;this.bins_=undefined;this.pushBuilderSlice_([HistogramBinBoundaries.SLICE_TYPE.EXPONENTIAL,nextMaxBinBoundary,binCount]);this.range.addValue(nextMaxBinBoundary);return this;}}
-HistogramBinBoundaries.SLICE_TYPE={LINEAR:0,EXPONENTIAL:1,};HistogramBinBoundaries.SINGULAR=new HistogramBinBoundaries(Number.MAX_VALUE);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));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.sigma.unitName,HistogramBinBoundaries.createLinear(-5,5,50));return{DEFAULT_REBINNED_COUNT,DELTA,Histogram,HistogramBinBoundaries,P_VALUE_NAME,U_STATISTIC_NAME,Z_SCORE_NAME,percentFromString,percentToString,};});'use strict';tr.exportTo('tr.v.ui',function(){Polymer({is:'tr-v-ui-scalar-context-controller',created(){this.host_=undefined;this.groupToContext_=new Map();this.dirtyGroups_=new Set();},attached(){if(this.host_){throw new Error('Scalar context controller is already attached to a host');}
+HistogramBinBoundaries.SLICE_TYPE={LINEAR:0,EXPONENTIAL:1,};HistogramBinBoundaries.SINGULAR=new HistogramBinBoundaries(Number.MAX_VALUE);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.timeInMsAutoFormat.unitName,new HistogramBinBoundaries(0).addBinBoundary(1).addExponentialBins(1e3,3).addBinBoundary(tr.b.convertUnit(2,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(tr.b.convertUnit(5,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(tr.b.convertUnit(10,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(tr.b.convertUnit(30,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(tr.b.convertUnit(tr.b.UnitScale.TIME.MINUTE.value,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(2*tr.b.convertUnit(tr.b.UnitScale.TIME.MINUTE.value,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(5*tr.b.convertUnit(tr.b.UnitScale.TIME.MINUTE.value,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(10*tr.b.convertUnit(tr.b.UnitScale.TIME.MINUTE.value,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(30*tr.b.convertUnit(tr.b.UnitScale.TIME.MINUTE.value,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(tr.b.convertUnit(tr.b.UnitScale.TIME.HOUR.value,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(2*tr.b.convertUnit(tr.b.UnitScale.TIME.HOUR.value,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(6*tr.b.convertUnit(tr.b.UnitScale.TIME.HOUR.value,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(12*tr.b.convertUnit(tr.b.UnitScale.TIME.HOUR.value,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(tr.b.convertUnit(tr.b.UnitScale.TIME.DAY.value,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(tr.b.convertUnit(tr.b.UnitScale.TIME.WEEK.value,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(tr.b.convertUnit(tr.b.UnitScale.TIME.MONTH.value,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)).addBinBoundary(tr.b.convertUnit(tr.b.UnitScale.TIME.YEAR.value,tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)));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));DEFAULT_BOUNDARIES_FOR_UNIT.set(tr.b.Unit.byName.sigma.unitName,HistogramBinBoundaries.createLinear(-5,5,50));return{DEFAULT_REBINNED_COUNT,DELTA,Histogram,HistogramBinBoundaries,P_VALUE_NAME,U_STATISTIC_NAME,Z_SCORE_NAME,percentFromString,percentToString,};});'use strict';tr.exportTo('tr.v.ui',function(){Polymer({is:'tr-v-ui-scalar-context-controller',created(){this.host_=undefined;this.groupToContext_=new Map();this.dirtyGroups_=new Set();},attached(){if(this.host_){throw new Error('Scalar context controller is already attached to a host');}
 const host=findParentOrHost(this);if(host.__scalarContextController){throw new Error('Multiple scalar context controllers attached to this host');}
 host.__scalarContextController=this;this.host_=host;},detached(){if(!this.host_){throw new Error('Scalar context controller is not attached to a host');}
 if(this.host_.__scalarContextController!==this){throw new Error('Scalar context controller is not attached to its host');}
@@ -7208,10 +7283,9 @@
 this.appendElementsForType_(label+'[',object[0],indent,depth+1,maxDepth,object.length>1?',':']'+suffix);for(let i=1;i<object.length;i++){this.appendElementsForType_('',object[i],indent+label.length+1,depth+1,maxDepth,i<object.length-1?',':']'+suffix);}
 return;},appendElementsForObject_(label,object,indent,depth,maxDepth,suffix){const keys=Object.keys(object);if(keys.length===0){this.appendSimpleText_(label,indent,'{}',suffix);return;}
 this.appendElementsForType_(label+'{'+keys[0]+': ',object[keys[0]],indent,depth,maxDepth,keys.length>1?',':'}'+suffix);for(let i=1;i<keys.length;i++){this.appendElementsForType_(keys[i]+': ',object[keys[i]],indent+label.length+1,depth+1,maxDepth,i<keys.length-1?',':'}'+suffix);}},appendElementWithLabel_(label,indent,dataElement,suffix){const row=document.createElement('div');const indentSpan=document.createElement('span');indentSpan.style.whiteSpace='pre';for(let i=0;i<indent;i++){Polymer.dom(indentSpan).textContent+=' ';}
-Polymer.dom(row).appendChild(indentSpan);const labelSpan=document.createElement('span');Polymer.dom(labelSpan).textContent=label;Polymer.dom(row).appendChild(labelSpan);Polymer.dom(row).appendChild(dataElement);const suffixSpan=document.createElement('span');Polymer.dom(suffixSpan).textContent=suffix;Polymer.dom(row).appendChild(suffixSpan);row.dataElement=dataElement;Polymer.dom(this.$.content).appendChild(row);},appendSimpleText_(label,indent,text,suffix){const el=this.ownerDocument.createElement('span');Polymer.dom(el).textContent=text;this.appendElementWithLabel_(label,indent,el,suffix);return el;}});'use strict';Polymer({is:'tr-ui-a-generic-object-view-with-label',ready(){this.labelEl_=document.createElement('div');this.genericObjectView_=document.createElement('tr-ui-a-generic-object-view');Polymer.dom(this.root).appendChild(this.labelEl_);Polymer.dom(this.root).appendChild(this.genericObjectView_);},get label(){return Polymer.dom(this.labelEl_).textContent;},set label(label){Polymer.dom(this.labelEl_).textContent=label;},get object(){return this.genericObjectView_.object;},set object(object){this.genericObjectView_.object=object;}});'use strict';tr.exportTo('tr.ui.analysis',function(){const ObjectSnapshotView=tr.ui.b.define('object-snapshot-view');ObjectSnapshotView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.objectSnapshot_=undefined;},get requiresTallView(){return true;},set modelEvent(obj){this.objectSnapshot=obj;},get modelEvent(){return this.objectSnapshot;},get objectSnapshot(){return this.objectSnapshot_;},set objectSnapshot(i){this.objectSnapshot_=i;this.updateContents();},updateContents(){throw new Error('Not implemented');}};const options=new tr.b.ExtensionRegistryOptions(tr.b.TYPE_BASED_REGISTRY_MODE);options.mandatoryBaseClass=ObjectSnapshotView;options.defaultMetadata={showInstances:true,showInTrackView:true};tr.b.decorateExtensionRegistry(ObjectSnapshotView,options);return{ObjectSnapshotView,};});'use strict';Polymer({is:'tr-ui-b-drag-handle',created(){this.lastMousePos_=0;this.onMouseMove_=this.onMouseMove_.bind(this);this.onMouseUp_=this.onMouseUp_.bind(this);this.addEventListener('mousedown',this.onMouseDown_);this.target_=undefined;this.horizontal=true;this.observer_=new WebKitMutationObserver(this.didTargetMutate_.bind(this));this.targetSizesByModeKey_={};},get modeKey_(){return this.target_.className===''?'.':this.target_.className;},get target(){return this.target_;},set target(target){this.observer_.disconnect();this.target_=target;if(!this.target_)return;this.observer_.observe(this.target_,{attributes:true,attributeFilter:['class']});},get horizontal(){return this.horizontal_;},set horizontal(h){this.horizontal_=h;if(this.horizontal_){this.className='horizontal-drag-handle';}else{this.className='vertical-drag-handle';}},get vertical(){return!this.horizontal_;},set vertical(v){this.horizontal=!v;},forceMutationObserverFlush_(){const records=this.observer_.takeRecords();if(records.length){this.didTargetMutate_(records);}},didTargetMutate_(e){const modeSize=this.targetSizesByModeKey_[this.modeKey_];if(modeSize!==undefined){this.setTargetSize_(modeSize);return;}
-this.target_.style[this.targetStyleKey_]='';},get targetStyleKey_(){return this.horizontal_?'height':'width';},getTargetSize_(){const targetStyleKey=this.targetStyleKey_;if(!this.target_.style[targetStyleKey]){this.target_.style[targetStyleKey]=window.getComputedStyle(this.target_)[targetStyleKey];}
-const size=parseInt(this.target_.style[targetStyleKey]);this.targetSizesByModeKey_[this.modeKey_]=size;return size;},setTargetSize_(s){this.target_.style[this.targetStyleKey_]=s+'px';this.targetSizesByModeKey_[this.modeKey_]=s;tr.b.dispatchSimpleEvent(this,'drag-handle-resize',true,false);},applyDelta_(delta){const curSize=this.getTargetSize_();let newSize;if(this.target_===this.nextElementSibling){newSize=curSize+delta;}else{newSize=curSize-delta;}
-this.setTargetSize_(newSize);},onMouseMove_(e){const curMousePos=this.horizontal_?e.clientY:e.clientX;const delta=this.lastMousePos_-curMousePos;this.applyDelta_(delta);this.lastMousePos_=curMousePos;e.preventDefault();return true;},onMouseDown_(e){if(!this.target_)return;this.forceMutationObserverFlush_();this.lastMousePos_=this.horizontal_?e.clientY:e.clientX;document.addEventListener('mousemove',this.onMouseMove_);document.addEventListener('mouseup',this.onMouseUp_);e.preventDefault();return true;},onMouseUp_(e){document.removeEventListener('mousemove',this.onMouseMove_);document.removeEventListener('mouseup',this.onMouseUp_);e.preventDefault();}});'use strict';tr.exportTo('tr.ui.b',function(){function HotKey(dict){if(dict.eventType===undefined){throw new Error('eventType must be given');}
+Polymer.dom(row).appendChild(indentSpan);const labelSpan=document.createElement('span');Polymer.dom(labelSpan).textContent=label;Polymer.dom(row).appendChild(labelSpan);Polymer.dom(row).appendChild(dataElement);const suffixSpan=document.createElement('span');Polymer.dom(suffixSpan).textContent=suffix;Polymer.dom(row).appendChild(suffixSpan);row.dataElement=dataElement;Polymer.dom(this.$.content).appendChild(row);},appendSimpleText_(label,indent,text,suffix){const el=this.ownerDocument.createElement('span');Polymer.dom(el).textContent=text;this.appendElementWithLabel_(label,indent,el,suffix);return el;}});'use strict';Polymer({is:'tr-ui-a-generic-object-view-with-label',ready(){this.labelEl_=document.createElement('div');this.genericObjectView_=document.createElement('tr-ui-a-generic-object-view');Polymer.dom(this.root).appendChild(this.labelEl_);Polymer.dom(this.root).appendChild(this.genericObjectView_);},get label(){return Polymer.dom(this.labelEl_).textContent;},set label(label){Polymer.dom(this.labelEl_).textContent=label;},get object(){return this.genericObjectView_.object;},set object(object){this.genericObjectView_.object=object;}});'use strict';tr.exportTo('tr.ui.analysis',function(){const ObjectSnapshotView=tr.ui.b.define('object-snapshot-view');ObjectSnapshotView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.objectSnapshot_=undefined;},get requiresTallView(){return true;},set modelEvent(obj){this.objectSnapshot=obj;},get modelEvent(){return this.objectSnapshot;},get objectSnapshot(){return this.objectSnapshot_;},set objectSnapshot(i){this.objectSnapshot_=i;this.updateContents();},updateContents(){throw new Error('Not implemented');}};const options=new tr.b.ExtensionRegistryOptions(tr.b.TYPE_BASED_REGISTRY_MODE);options.mandatoryBaseClass=ObjectSnapshotView;options.defaultMetadata={showInstances:true,showInTrackView:true};tr.b.decorateExtensionRegistry(ObjectSnapshotView,options);return{ObjectSnapshotView,};});'use strict';Polymer({is:'tr-ui-b-drag-handle',created(){this.lastMousePos_=0;this.onMouseMove_=this.onMouseMove_.bind(this);this.onMouseUp_=this.onMouseUp_.bind(this);this.addEventListener('mousedown',this.onMouseDown_);this.target_=undefined;this.horizontal=true;this.observer_=new WebKitMutationObserver(this.didTargetMutate_.bind(this));this.targetSizesByModeKey_={};this.currentDraggingSize_=undefined;},get modeKey_(){return this.target_.className===''?'.':this.target_.className;},get target(){return this.target_;},set target(target){this.observer_.disconnect();this.target_=target;if(!this.target_)return;this.observer_.observe(this.target_,{attributes:true,attributeFilter:['class']});},get horizontal(){return this.horizontal_;},set horizontal(h){this.horizontal_=h;if(this.horizontal_){this.className='horizontal-drag-handle';}else{this.className='vertical-drag-handle';}},get vertical(){return!this.horizontal_;},set vertical(v){this.horizontal=!v;},forceMutationObserverFlush_(){const records=this.observer_.takeRecords();if(records.length){this.didTargetMutate_(records);}},didTargetMutate_(e){const modeSize=this.targetSizesByModeKey_[this.modeKey_];if(modeSize!==undefined){this.setTargetSize_(modeSize);return;}
+this.target_.style[this.targetStyleKey_]='';},get targetStyleKey_(){return this.horizontal_?'height':'width';},getTargetSize_(){const size=parseInt(window.getComputedStyle(this.target_)[this.targetStyleKey_]);this.targetSizesByModeKey_[this.modeKey_]=size;return size;},setTargetSize_(s){this.target_.style[this.targetStyleKey_]=s+'px';this.targetSizesByModeKey_[this.modeKey_]=this.getTargetSize_();tr.b.dispatchSimpleEvent(this,'drag-handle-resize',true,false);},applyDelta_(delta){if(this.target_===this.nextElementSibling){this.currentDraggingSize_+=delta;}else{this.currentDraggingSize_-=delta;}
+this.setTargetSize_(this.currentDraggingSize_);},onMouseMove_(e){const curMousePos=this.horizontal_?e.clientY:e.clientX;const delta=this.lastMousePos_-curMousePos;this.applyDelta_(delta);this.lastMousePos_=curMousePos;e.preventDefault();return true;},onMouseDown_(e){if(!this.target_)return;this.forceMutationObserverFlush_();this.currentDraggingSize_=this.getTargetSize_();this.lastMousePos_=this.horizontal_?e.clientY:e.clientX;document.addEventListener('mousemove',this.onMouseMove_);document.addEventListener('mouseup',this.onMouseUp_);e.preventDefault();return true;},onMouseUp_(e){document.removeEventListener('mousemove',this.onMouseMove_);document.removeEventListener('mouseup',this.onMouseUp_);e.preventDefault();this.currentDraggingSize_=undefined;}});'use strict';tr.exportTo('tr.ui.b',function(){function HotKey(dict){if(dict.eventType===undefined){throw new Error('eventType must be given');}
 if(dict.keyCode===undefined&&dict.keyCodes===undefined){throw new Error('keyCode or keyCodes must be given');}
 if(dict.keyCode!==undefined&&dict.keyCodes!==undefined){throw new Error('Only keyCode or keyCodes can be given');}
 if(dict.callback===undefined){throw new Error('callback must be given');}
@@ -7292,7 +7366,7 @@
 if(didHandleKey){e.preventDefault();e.stopPropagation();return;}
 this.updateAlternativeModeState_(e);},updateAlternativeModeState_(e){const shiftPressed=e.shiftKey;const spacePressed=this.spacePressed_;const cmdOrCtrlPressed=isCmdOrCtrlPressed(e);const smm=this.supportedModeMask_;let newMode;let isNewModeAnAlternativeMode=false;if(shiftPressed&&(this.modifierToModeMap_[MODIFIER.SHIFT]&smm)!==0){newMode=this.modifierToModeMap_[MODIFIER.SHIFT];isNewModeAnAlternativeMode=true;}else if(spacePressed&&(this.modifierToModeMap_[MODIFIER.SPACE]&smm)!==0){newMode=this.modifierToModeMap_[MODIFIER.SPACE];isNewModeAnAlternativeMode=true;}else if(cmdOrCtrlPressed&&(this.modifierToModeMap_[MODIFIER.CMD_OR_CTRL]&smm)!==0){newMode=this.modifierToModeMap_[MODIFIER.CMD_OR_CTRL];isNewModeAnAlternativeMode=true;}else{if(this.isInAlternativeMode_){newMode=this.modeBeforeAlternativeModeActivated_;isNewModeAnAlternativeMode=false;}else{newMode=undefined;}}
 if(this.mode===newMode||newMode===undefined)return;if(isNewModeAnAlternativeMode){this.modeBeforeAlternativeModeActivated_=this.mode;}
-this.mode=newMode;},get isInAlternativeMode_(){return!!this.modeBeforeAlternativeModeActivated_;},setModifierForAlternateMode(mode,modifier){this.modifierToModeMap_[modifier]=mode;},get pos(){return{x:parseInt(this.style.left),y:parseInt(this.style.top)};},set pos(pos){pos=this.constrainPositionToBounds_(pos);this.style.left=pos.x+'px';this.style.top=pos.y+'px';if(this.settingsKey_){tr.b.Settings.set(this.settingsKey_+'.pos',this.pos);}},constrainPositionToBounds_(pos){const parent=this.offsetParent||document.body;const parentRect=tr.ui.b.windowRectForElement(parent);const top=0;const bottom=parentRect.height-this.offsetHeight;const left=0;const right=parentRect.width-this.offsetWidth;const res={};res.x=Math.max(pos.x,left);res.x=Math.min(res.x,right);res.y=Math.max(pos.y,top);res.y=Math.min(res.y,bottom);return res;},onDragHandleMouseDown_(e){e.preventDefault();e.stopImmediatePropagation();const mouseDownPos={x:e.clientX-this.offsetLeft,y:e.clientY-this.offsetTop};tr.ui.b.trackMouseMovesUntilMouseUp(function(e){const pos={};pos.x=e.clientX-mouseDownPos.x;pos.y=e.clientY-mouseDownPos.y;this.pos=pos;}.bind(this));},checkIsClick_(e){if(!this.isInteracting_||!this.isClick_)return;const deltaX=this.mousePos_.x-this.mouseDownPos_.x;const deltaY=this.mousePos_.y-this.mouseDownPos_.y;const minDist=MIN_MOUSE_SELECTION_DISTANCE;if(deltaX*deltaX+deltaY*deltaY>minDist*minDist){this.isClick_=false;}},dispatchClickEvents_(e){if(!this.isClick_)return;const modeInfo=MOUSE_SELECTOR_MODE_INFOS[MOUSE_SELECTOR_MODE.SELECTION];const eventNames=modeInfo.eventNames;let mouseEvent=this.createEvent_(eventNames.begin);mouseEvent.appendSelection=isCmdOrCtrlPressed(e);this.dispatchEvent(mouseEvent);mouseEvent=this.createEvent_(eventNames.end);this.dispatchEvent(mouseEvent);}});return{MIN_MOUSE_SELECTION_DISTANCE,MODIFIER,};});'use strict';(function(){const DETAILS_SPLIT_REGEX=/^(\S*)\s*([\S\s]*)$/;Polymer({is:'tr-ui-e-chrome-cc-display-item-list-item',created(){Polymer.dom(this).setAttribute('name','');Polymer.dom(this).setAttribute('rawDetails','');Polymer.dom(this).setAttribute('richDetails',undefined);Polymer.dom(this).setAttribute('data_',undefined);},get data(){return this.data_;},set data(data){this.data_=data;if(!data){this.name='DATA MISSING';this.rawDetails='';this.richDetails=undefined;}else if(typeof data==='string'){const match=data.match(DETAILS_SPLIT_REGEX);this.name=match[1];this.rawDetails=match[2];this.richDetails=undefined;}else{this.name=data.name;this.rawDetails='';this.richDetails=data;}},stopPropagation(e){e.stopPropagation();},_computeIf(richDetails){return richDetails&&richDetails.skp64;},_computeHref(richDetails){return'data:application/octet-stream;base64,'+richDetails.skp64;}});})();'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){function Selection(){this.selectionToSetIfClicked=undefined;}
+this.mode=newMode;},get isInAlternativeMode_(){return!!this.modeBeforeAlternativeModeActivated_;},setModifierForAlternateMode(mode,modifier){this.modifierToModeMap_[modifier]=mode;},get pos(){return{x:parseInt(this.style.left),y:parseInt(this.style.top)};},set pos(pos){pos=this.constrainPositionToBounds_(pos);this.style.left=pos.x+'px';this.style.top=pos.y+'px';if(this.settingsKey_){tr.b.Settings.set(this.settingsKey_+'.pos',this.pos);}},constrainPositionToBounds_(pos){const parent=this.offsetParent||document.body;const parentRect=tr.ui.b.windowRectForElement(parent);const top=0;const bottom=parentRect.height-this.offsetHeight;const left=0;const right=parentRect.width-this.offsetWidth;const res={};res.x=Math.max(pos.x,left);res.x=Math.min(res.x,right);res.y=Math.max(pos.y,top);res.y=Math.min(res.y,bottom);return res;},onDragHandleMouseDown_(e){e.preventDefault();e.stopImmediatePropagation();const mouseDownPos={x:e.clientX-this.offsetLeft,y:e.clientY-this.offsetTop};tr.ui.b.trackMouseMovesUntilMouseUp(function(e){const pos={};pos.x=e.clientX-mouseDownPos.x;pos.y=e.clientY-mouseDownPos.y;this.pos=pos;}.bind(this));},checkIsClick_(e){if(!this.isInteracting_||!this.isClick_)return;const deltaX=this.mousePos_.x-this.mouseDownPos_.x;const deltaY=this.mousePos_.y-this.mouseDownPos_.y;const minDist=MIN_MOUSE_SELECTION_DISTANCE;if(deltaX*deltaX+deltaY*deltaY>minDist*minDist){this.isClick_=false;}},dispatchClickEvents_(e){if(!this.isClick_)return;const modeInfo=MOUSE_SELECTOR_MODE_INFOS[MOUSE_SELECTOR_MODE.SELECTION];const eventNames=modeInfo.eventNames;let mouseEvent=this.createEvent_(eventNames.begin);mouseEvent.appendSelection=isCmdOrCtrlPressed(e);this.dispatchEvent(mouseEvent);mouseEvent=this.createEvent_(eventNames.end);this.dispatchEvent(mouseEvent);}});return{MIN_MOUSE_SELECTION_DISTANCE,MODIFIER,};});'use strict';(function(){const DETAILS_SPLIT_REGEX=/^(\S*)\s*([\S\s]*)$/;Polymer({is:'tr-ui-e-chrome-cc-display-item-list-item',created(){Polymer.dom(this).setAttribute('name','');Polymer.dom(this).setAttribute('rawDetails','');Polymer.dom(this).setAttribute('richDetails',undefined);Polymer.dom(this).setAttribute('data_',undefined);},get data(){return this.data_;},set data(data){this.data_=data;if(!data){this.name='DATA MISSING';this.rawDetails='';this.richDetails=undefined;}else if(typeof data==='string'){const match=data.match(DETAILS_SPLIT_REGEX);this.name=match[1];this.rawDetails=match[2];this.richDetails=undefined;}else{this.name=data.name;this.rawDetails='';this.richDetails=data;}},stopPropagation(e){e.stopPropagation();},_computeIfSKP(richDetails){return richDetails&&richDetails.skp64;},_computeHref(richDetails){return'data:application/octet-stream;base64,'+richDetails.skp64;}});})();'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){function Selection(){this.selectionToSetIfClicked=undefined;}
 Selection.prototype={get specicifity(){throw new Error('Not implemented');},get associatedLayerId(){throw new Error('Not implemented');},get associatedRenderPassId(){throw new Error('Not implemented');},get highlightsByLayerId(){return{};},createAnalysis(){throw new Error('Not implemented');},findEquivalent(lthi){throw new Error('Not implemented');}};function RenderPassSelection(renderPass,renderPassId){if(!renderPass||(renderPassId===undefined)){throw new Error('Render pass (with id) is required');}
 this.renderPass_=renderPass;this.renderPassId_=renderPassId;}
 RenderPassSelection.prototype={__proto__:Selection.prototype,get specicifity(){return 1;},get associatedLayerId(){return undefined;},get associatedRenderPassId(){return this.renderPassId_;},get renderPass(){return this.renderPass_;},createAnalysis(){const dataView=document.createElement('tr-ui-a-generic-object-view-with-label');dataView.label='RenderPass '+this.renderPassId_;dataView.object=this.renderPass_.args;return dataView;},get title(){return this.renderPass_.objectInstance.typeName;}};function LayerSelection(layer){if(!layer){throw new Error('Layer is required');}
@@ -7304,7 +7378,7 @@
 return analysis;},findEquivalent(lthi){const tileInstance=this.tile_.tileInstance;if(lthi.ts<tileInstance.creationTs||lthi.ts>=tileInstance.deletionTs){return undefined;}
 const tileSnapshot=tileInstance.getSnapshotAt(lthi.ts);if(!tileSnapshot)return undefined;return new TileSelection(tileSnapshot);}};function LayerRectSelection(layer,rectType,rect,opt_data){this.layer_=layer;this.rectType_=rectType;this.rect_=rect;this.data_=opt_data!==undefined?opt_data:rect;}
 LayerRectSelection.prototype={__proto__:Selection.prototype,get specicifity(){return 2;},get associatedLayerId(){return this.layer_.layerId;},get highlightsByLayerId(){const highlights={};highlights[this.layer_.layerId]=[{colorKey:this.rectType_,rect:this.rect_}];return highlights;},createAnalysis(){const analysis=document.createElement('tr-ui-a-generic-object-view-with-label');analysis.label=this.rectType_+' on layer '+this.layer_.layerId;analysis.object=this.data_;return analysis;},findEquivalent(lthi){return undefined;}};function AnimationRectSelection(layer,rect){this.layer_=layer;this.rect_=rect;}
-AnimationRectSelection.prototype={__proto__:Selection.prototype,get specicifity(){return 0;},get associatedLayerId(){return this.layer_.layerId;},createAnalysis(){const analysis=document.createElement('tr-ui-a-generic-object-view-with-label');analysis.label='Animation Bounds of layer '+this.layer_.layerId;analysis.object=this.rect_;return analysis;}};return{Selection,RenderPassSelection,LayerSelection,TileSelection,LayerRectSelection,AnimationRectSelection,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const OPS_TIMING_ITERATIONS=3;const ANNOTATION='Comment';const BEGIN_ANNOTATION='BeginCommentGroup';const END_ANNOTATION='EndCommentGroup';const ANNOTATION_ID='ID: ';const ANNOTATION_CLASS='CLASS: ';const ANNOTATION_TAG='TAG: ';const constants=tr.e.cc.constants;const PictureOpsListView=tr.ui.b.define('tr-ui-e-chrome-cc-picture-ops-list-view');PictureOpsListView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.style.flexDirection='column';this.style.borderTop='1px solid grey';this.style.display='flex';this.opsList_=new tr.ui.b.ListView();this.opsList_.style.flexGrow=1;this.opsList_.style.flexShrink=1;this.opsList_.style.flexBasis='auto';this.opsList_.style.overflow='auto';Polymer.dom(this).appendChild(this.opsList_);this.selectedOp_=undefined;this.selectedOpIndex_=undefined;this.opsList_.addEventListener('selection-changed',this.onSelectionChanged_.bind(this));this.picture_=undefined;},get picture(){return this.picture_;},set picture(picture){this.picture_=picture;this.updateContents_();},updateContents_(){this.opsList_.clear();if(!this.picture_)return;let ops=this.picture_.getOps();if(!ops)return;ops=this.picture_.tagOpsWithTimings(ops);ops=this.opsTaggedWithAnnotations_(ops);for(let i=0;i<ops.length;i++){const op=ops[i];const item=document.createElement('div');item.opIndex=op.opIndex;Polymer.dom(item).textContent=i+') '+op.cmd_string;if(op.elementInfo.tag||op.elementInfo.id||op.elementInfo.class){const elementInfo=document.createElement('span');Polymer.dom(elementInfo).classList.add('elementInfo');elementInfo.style.color='purple';elementInfo.style.fontSize='small';elementInfo.style.fontWeight='bold';elementInfo.style.color='#777';const tag=op.elementInfo.tag?op.elementInfo.tag:'unknown';const id=op.elementInfo.id?'id='+op.elementInfo.id:undefined;const className=op.elementInfo.class?'class='+
+AnimationRectSelection.prototype={__proto__:Selection.prototype,get specicifity(){return 0;},get associatedLayerId(){return this.layer_.layerId;},createAnalysis(){const analysis=document.createElement('tr-ui-a-generic-object-view-with-label');analysis.label='Animation Bounds of layer '+this.layer_.layerId;analysis.object=this.rect_;return analysis;}};return{Selection,RenderPassSelection,LayerSelection,TileSelection,LayerRectSelection,AnimationRectSelection,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const OPS_TIMING_ITERATIONS=3;const ANNOTATION='Comment';const BEGIN_ANNOTATION='BeginCommentGroup';const END_ANNOTATION='EndCommentGroup';const ANNOTATION_ID='ID: ';const ANNOTATION_CLASS='CLASS: ';const ANNOTATION_TAG='TAG: ';const constants=tr.e.cc.constants;const PictureOpsListView=tr.ui.b.define('tr-ui-e-chrome-cc-picture-ops-list-view');PictureOpsListView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.style.borderTop='1px solid grey';this.style.overflow='auto';this.opsList_=new tr.ui.b.ListView();Polymer.dom(this).appendChild(this.opsList_);this.selectedOp_=undefined;this.selectedOpIndex_=undefined;this.opsList_.addEventListener('selection-changed',this.onSelectionChanged_.bind(this));this.picture_=undefined;},get picture(){return this.picture_;},set picture(picture){this.picture_=picture;this.updateContents_();},updateContents_(){this.opsList_.clear();if(!this.picture_)return;let ops=this.picture_.getOps();if(!ops)return;ops=this.picture_.tagOpsWithTimings(ops);ops=this.opsTaggedWithAnnotations_(ops);for(let i=0;i<ops.length;i++){const op=ops[i];const item=document.createElement('div');item.opIndex=op.opIndex;Polymer.dom(item).textContent=i+') '+op.cmd_string;if(op.elementInfo.tag||op.elementInfo.id||op.elementInfo.class){const elementInfo=document.createElement('span');Polymer.dom(elementInfo).classList.add('elementInfo');elementInfo.style.color='purple';elementInfo.style.fontSize='small';elementInfo.style.fontWeight='bold';elementInfo.style.color='#777';const tag=op.elementInfo.tag?op.elementInfo.tag:'unknown';const id=op.elementInfo.id?'id='+op.elementInfo.id:undefined;const className=op.elementInfo.class?'class='+
 op.elementInfo.class:undefined;Polymer.dom(elementInfo).textContent='<'+tag+(id?' ':'')+
 (id?id:'')+(className?' ':'')+
 (className?className:'')+'>';Polymer.dom(item).appendChild(elementInfo);}
@@ -7318,8 +7392,8 @@
 ANNOTATION_ID.length);}else if(info.includes(ANNOTATION_CLASS)){elementInfo.class=info.substring(info.indexOf(ANNOTATION_CLASS)+
 ANNOTATION_CLASS.length);}
 annotations.push(info);});});});op.annotations=annotations;op.elementInfo=elementInfo;opsWithoutAnnotations.push(op);}}}
-return opsWithoutAnnotations;}};return{PictureOpsListView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const THIS_DOC=document.currentScript.ownerDocument;const DisplayItemDebugger=tr.ui.b.define('tr-ui-e-chrome-cc-display-item-debugger');DisplayItemDebugger.prototype={__proto__:HTMLDivElement.prototype,decorate(){const node=tr.ui.b.instantiateTemplate('#tr-ui-e-chrome-cc-display-item-debugger-template',THIS_DOC);Polymer.dom(this).appendChild(node);this.style.flexGrow=1;this.style.flexShrink=1;this.style.flexBasis='auto';this.style.display='flex';this.pictureAsImageData_=undefined;this.zoomScaleValue_=1;this.sizeInfo_=Polymer.dom(this).querySelector('.size');this.rasterArea_=Polymer.dom(this).querySelector('raster-area');this.rasterArea_.style.flexGrow=1;this.rasterArea_.style.flexShrink=1;this.rasterArea_.style.flexBasis='auto';this.rasterArea_.style.backgroundColor='#ddd';this.rasterArea_.style.minHeight='200px';this.rasterArea_.style.minWidth='200px';this.rasterArea_.style.overflowY='auto';this.rasterArea_.style.paddingLeft='5px';this.rasterCanvas_=Polymer.dom(this.rasterArea_).querySelector('canvas');this.rasterCtx_=this.rasterCanvas_.getContext('2d');this.trackMouse_();this.displayItemInfo_=Polymer.dom(this).querySelector('display-item-info');this.displayItemInfo_.addEventListener('click',this.onDisplayItemInfoClick_.bind(this),false);this.displayItemListView_=new tr.ui.b.ListView();this.displayItemListView_.addEventListener('selection-changed',this.onDisplayItemListSelection_.bind(this));Polymer.dom(this.displayItemInfo_).appendChild(this.displayItemListView_);this.displayListFilename_=Polymer.dom(this).querySelector('.dlfilename');this.displayListExportButton_=Polymer.dom(this).querySelector('.dlexport');this.displayListExportButton_.addEventListener('click',this.onExportDisplayListClicked_.bind(this));this.skpFilename_=Polymer.dom(this).querySelector('.skpfilename');this.skpExportButton_=Polymer.dom(this).querySelector('.skpexport');this.skpExportButton_.addEventListener('click',this.onExportSkPictureClicked_.bind(this));const leftPanel=Polymer.dom(this).querySelector('left-panel');leftPanel.style.display='flex';leftPanel.style.flexDirection='column';leftPanel.style.minWidth='300px';leftPanel.style.overflowY='auto';leftPanel.children[0].paddingTop='2px';leftPanel.children[0].flexGrow=1;leftPanel.children[0].flexShrink=1;leftPanel.children[0].flexBasis='auto';leftPanel.children[0].children[0].style.borderBottom='1px solid #555';const leftPanelTitle=leftPanel.querySelector('.title');leftPanelTitle.style.fontWeight='bold';leftPanelTitle.style.marginLeft='5px';leftPanelTitle.style.marginright='5px';for(const div of leftPanel.querySelectorAll('.export')){div.style.margin='5px';}
-const middleDragHandle=document.createElement('tr-ui-b-drag-handle');middleDragHandle.style.flexGrow=0;middleDragHandle.style.flexShrink=0;middleDragHandle.style.flexBasis='auto';middleDragHandle.horizontal=false;middleDragHandle.target=leftPanel;const rightPanel=Polymer.dom(this).querySelector('right-panel');rightPanel.style.display='flex';rightPanel.style.flexGrow=1;rightPanel.style.flexShrink=1;rightPanel.style.flexBasis='auto';this.infoBar_=document.createElement('tr-ui-b-info-bar');Polymer.dom(this.rasterArea_).insertBefore(this.infoBar_,this.rasterCanvas_);Polymer.dom(this).insertBefore(middleDragHandle,rightPanel);this.picture_=undefined;this.pictureOpsListView_=new tr.ui.e.chrome.cc.PictureOpsListView();this.pictureOpsListView_.style.overflowY='auto';Polymer.dom(rightPanel).insertBefore(this.pictureOpsListView_,this.rasterArea_);this.pictureOpsListDragHandle_=document.createElement('tr-ui-b-drag-handle');this.pictureOpsListDragHandle_.horizontal=false;this.pictureOpsListDragHandle_.target=this.pictureOpsListView_;Polymer.dom(rightPanel).insertBefore(this.pictureOpsListDragHandle_,this.rasterArea_);},get picture(){return this.picture_;},set displayItemList(displayItemList){this.displayItemList_=displayItemList;this.picture=this.displayItemList_;this.displayItemListView_.clear();this.displayItemList_.items.forEach(function(item){const listItem=document.createElement('tr-ui-e-chrome-cc-display-item-list-item');listItem.data=item;Polymer.dom(this.displayItemListView_).appendChild(listItem);}.bind(this));},set picture(picture){this.picture_=picture;const showOpsList=picture&&picture!==this.displayItemList_;this.updateDrawOpsList_(showOpsList);if(picture){const size=this.getRasterCanvasSize_();this.rasterCanvas_.width=size.width;this.rasterCanvas_.height=size.height;}
+return opsWithoutAnnotations;}};return{PictureOpsListView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const THIS_DOC=document.currentScript.ownerDocument;const DisplayItemDebugger=tr.ui.b.define('tr-ui-e-chrome-cc-display-item-debugger');DisplayItemDebugger.prototype={__proto__:HTMLDivElement.prototype,decorate(){const node=tr.ui.b.instantiateTemplate('#tr-ui-e-chrome-cc-display-item-debugger-template',THIS_DOC);Polymer.dom(this).appendChild(node);this.style.flexGrow=1;this.style.flexShrink=1;this.style.flexBasis='auto';this.style.display='flex';this.style.minWidth=0;this.pictureAsImageData_=undefined;this.zoomScaleValue_=1;this.sizeInfo_=Polymer.dom(this).querySelector('.size');this.rasterArea_=Polymer.dom(this).querySelector('raster-area');this.rasterArea_.style.flexGrow=1;this.rasterArea_.style.flexShrink=1;this.rasterArea_.style.flexBasis='auto';this.rasterArea_.style.backgroundColor='#ddd';this.rasterArea_.style.minHeight='200px';this.rasterArea_.style.minWidth='200px';this.rasterArea_.style.paddingLeft='5px';this.rasterArea_.style.display='flex';this.rasterArea_.style.flexDirection='column';this.rasterCanvas_=Polymer.dom(this.rasterArea_).querySelector('canvas');this.rasterCtx_=this.rasterCanvas_.getContext('2d');const canvasScroller=Polymer.dom(this).querySelector('canvas-scroller');canvasScroller.style.flexGrow=1;canvasScroller.style.flexShrink=1;canvasScroller.style.flexBasis='auto';canvasScroller.style.minWidth=0;canvasScroller.style.minHeight=0;canvasScroller.style.overflow='auto';this.trackMouse_();this.displayItemInfo_=Polymer.dom(this).querySelector('display-item-info');this.displayItemInfo_.addEventListener('click',this.onDisplayItemInfoClick_.bind(this),false);this.displayItemListView_=new tr.ui.b.ListView();this.displayItemListView_.addEventListener('selection-changed',this.onDisplayItemListSelection_.bind(this));Polymer.dom(this.displayItemInfo_).appendChild(this.displayItemListView_);this.displayListFilename_=Polymer.dom(this).querySelector('.dlfilename');this.displayListExportButton_=Polymer.dom(this).querySelector('.dlexport');this.displayListExportButton_.addEventListener('click',this.onExportDisplayListClicked_.bind(this));this.skpFilename_=Polymer.dom(this).querySelector('.skpfilename');this.skpExportButton_=Polymer.dom(this).querySelector('.skpexport');this.skpExportButton_.addEventListener('click',this.onExportSkPictureClicked_.bind(this));const leftPanel=Polymer.dom(this).querySelector('left-panel');leftPanel.style.flexGrow=0;leftPanel.style.flexShrink=0;leftPanel.style.flexBasis='auto';leftPanel.style.minWidth='200px';leftPanel.style.overflow='auto';leftPanel.children[0].paddingTop='2px';leftPanel.children[0].children[0].style.borderBottom='1px solid #555';const leftPanelTitle=leftPanel.querySelector('.title');leftPanelTitle.style.fontWeight='bold';leftPanelTitle.style.marginLeft='5px';leftPanelTitle.style.marginright='5px';for(const div of leftPanel.querySelectorAll('.export')){div.style.margin='5px';}
+const middleDragHandle=document.createElement('tr-ui-b-drag-handle');middleDragHandle.style.flexGrow=0;middleDragHandle.style.flexShrink=0;middleDragHandle.style.flexBasis='auto';middleDragHandle.horizontal=false;middleDragHandle.target=leftPanel;const rightPanel=Polymer.dom(this).querySelector('right-panel');rightPanel.style.display='flex';rightPanel.style.flexGrow=1;rightPanel.style.flexShrink=1;rightPanel.style.flexBasis='auto';rightPanel.style.minWidth=0;this.infoBar_=document.createElement('tr-ui-b-info-bar');Polymer.dom(this.rasterArea_).insertBefore(this.infoBar_,canvasScroller);Polymer.dom(this).insertBefore(middleDragHandle,rightPanel);this.picture_=undefined;this.pictureOpsListView_=new tr.ui.e.chrome.cc.PictureOpsListView();this.pictureOpsListView_.style.flexGrow=0;this.pictureOpsListView_.style.flexShrink=0;this.pictureOpsListView_.style.flexBasis='auto';this.pictureOpsListView_.style.overflow='auto';this.pictureOpsListView_.style.minWidth='100px';Polymer.dom(rightPanel).insertBefore(this.pictureOpsListView_,this.rasterArea_);this.pictureOpsListDragHandle_=document.createElement('tr-ui-b-drag-handle');this.pictureOpsListDragHandle_.horizontal=false;this.pictureOpsListDragHandle_.target=this.pictureOpsListView_;Polymer.dom(rightPanel).insertBefore(this.pictureOpsListDragHandle_,this.rasterArea_);},get picture(){return this.picture_;},set displayItemList(displayItemList){this.displayItemList_=displayItemList;this.picture=this.displayItemList_;this.displayItemListView_.clear();this.displayItemList_.items.forEach(function(item){const listItem=document.createElement('tr-ui-e-chrome-cc-display-item-list-item');listItem.data=item;Polymer.dom(this.displayItemListView_).appendChild(listItem);}.bind(this));},set picture(picture){this.picture_=picture;const showOpsList=picture&&picture!==this.displayItemList_;this.updateDrawOpsList_(showOpsList);if(picture){const size=this.getRasterCanvasSize_();this.rasterCanvas_.width=size.width;this.rasterCanvas_.height=size.height;}
 const bounds=this.rasterArea_.getBoundingClientRect();const selectorBounds=this.mouseModeSelector_.getBoundingClientRect();this.mouseModeSelector_.pos={x:(bounds.right-selectorBounds.width-10),y:bounds.top};this.rasterize_();this.scheduleUpdateContents_();},getRasterCanvasSize_(){const style=window.getComputedStyle(this.rasterArea_);let width=parseInt(style.width);let height=parseInt(style.height);if(this.picture_){width=Math.max(width,this.picture_.layerRect.width);height=Math.max(height,this.picture_.layerRect.height);}
 return{width,height};},scheduleUpdateContents_(){if(this.updateContentsPending_)return;this.updateContentsPending_=true;tr.b.requestAnimationFrameInThisFrameIfPossible(this.updateContents_.bind(this));},updateContents_(){this.updateContentsPending_=false;if(this.picture_){Polymer.dom(this.sizeInfo_).textContent='('+
 this.picture_.layerRect.width+' x '+
@@ -7329,13 +7403,11 @@
 if(size.height!==this.rasterCanvas_.height){this.rasterCanvas_.height=size.height;}
 this.rasterCtx_.clearRect(0,0,size.width,size.height);if(!this.picture_||!this.pictureAsImageData_.imageData)return;const imgCanvas=this.pictureAsImageData_.asCanvas();const w=imgCanvas.width;const h=imgCanvas.height;this.rasterCtx_.drawImage(imgCanvas,0,0,w,h,0,0,w*this.zoomScaleValue_,h*this.zoomScaleValue_);},rasterize_(){if(this.picture_){this.picture_.rasterize({showOverdraw:false},this.onRasterComplete_.bind(this));}},onRasterComplete_(pictureAsImageData){this.pictureAsImageData_=pictureAsImageData;this.scheduleUpdateContents_();},onDisplayItemListSelection_(e){const selected=this.displayItemListView_.selectedElement;if(!selected){this.picture=this.displayItemList_;return;}
 const index=Array.prototype.indexOf.call(this.displayItemListView_.children,selected);const displayItem=this.displayItemList_.items[index];if(displayItem&&displayItem.skp64){this.picture=new tr.e.cc.Picture(displayItem.skp64,this.displayItemList_.layerRect);}else{this.picture=undefined;}},onDisplayItemInfoClick_(e){if(e&&e.target===this.displayItemInfo_){this.displayItemListView_.selectedElement=undefined;}},updateDrawOpsList_(showOpsList){if(showOpsList){this.pictureOpsListView_.picture=this.picture_;if(this.pictureOpsListView_.numOps>0){this.pictureOpsListView_.style.display='block';this.pictureOpsListDragHandle_.style.display='block';}}else{this.pictureOpsListView_.style.display='none';this.pictureOpsListDragHandle_.style.display='none';}},trackMouse_(){this.mouseModeSelector_=document.createElement('tr-ui-b-mouse-mode-selector');this.mouseModeSelector_.targetElement=this.rasterArea_;Polymer.dom(this.rasterArea_).appendChild(this.mouseModeSelector_);this.mouseModeSelector_.supportedModeMask=tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;this.mouseModeSelector_.mode=tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;this.mouseModeSelector_.defaultMode=tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;this.mouseModeSelector_.settingsKey='pictureDebugger.mouseModeSelector';this.mouseModeSelector_.addEventListener('beginzoom',this.onBeginZoom_.bind(this));this.mouseModeSelector_.addEventListener('updatezoom',this.onUpdateZoom_.bind(this));this.mouseModeSelector_.addEventListener('endzoom',this.onEndZoom_.bind(this));},onBeginZoom_(e){this.isZooming_=true;this.lastMouseViewPos_=this.extractRelativeMousePosition_(e);e.preventDefault();},onUpdateZoom_(e){if(!this.isZooming_)return;const currentMouseViewPos=this.extractRelativeMousePosition_(e);this.zoomScaleValue_+=((this.lastMouseViewPos_.y-currentMouseViewPos.y)*0.001);this.zoomScaleValue_=Math.max(this.zoomScaleValue_,0.1);this.drawPicture_();this.lastMouseViewPos_=currentMouseViewPos;},onEndZoom_(e){this.lastMouseViewPos_=undefined;this.isZooming_=false;e.preventDefault();},extractRelativeMousePosition_(e){return{x:e.clientX-this.rasterArea_.offsetLeft,y:e.clientY-this.rasterArea_.offsetTop};},saveFile_(filename,rawData){if(!rawData)return;const length=rawData.length;const arrayBuffer=new ArrayBuffer(length);const uint8Array=new Uint8Array(arrayBuffer);for(let c=0;c<length;c++){uint8Array[c]=rawData.charCodeAt(c);}
-const blob=new Blob([uint8Array],{type:'application/octet-binary'});const blobUrl=window.URL.createObjectURL(blob);const link=document.createElementNS('http://www.w3.org/1999/xhtml','a');link.href=blobUrl;link.download=filename;const event=document.createEvent('MouseEvents');event.initMouseEvent('click',true,false,window,0,0,0,0,0,false,false,false,false,0,null);link.dispatchEvent(event);},onExportDisplayListClicked_(){const rawData=JSON.stringify(this.displayItemList_.items);this.saveFile_(this.displayListFilename_.value,rawData);},onExportSkPictureClicked_(){const rawData=tr.b.Base64.atob(this.picture_.getBase64SkpData());this.saveFile_(this.skpFilename_.value,rawData);}};return{DisplayItemDebugger,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const DisplayItemSnapshotView=tr.ui.b.define('tr-ui-e-chrome-cc-display-item-list-view',tr.ui.analysis.ObjectSnapshotView);DisplayItemSnapshotView.prototype={__proto__:tr.ui.analysis.ObjectSnapshotView.prototype,decorate(){this.style.display='flex';this.style.flexGrow=1;this.style.flexShrink=1;this.style.flexBasis='auto';this.displayItemDebugger_=new tr.ui.e.chrome.cc.DisplayItemDebugger();Polymer.dom(this).appendChild(this.displayItemDebugger_);},updateContents(){if(this.objectSnapshot_&&this.displayItemDebugger_){this.displayItemDebugger_.displayItemList=this.objectSnapshot_;}}};tr.ui.analysis.ObjectSnapshotView.register(DisplayItemSnapshotView,{typeNames:['cc::DisplayItemList'],showInstances:false});return{DisplayItemSnapshotView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const constants=tr.e.cc.constants;const RENDER_PASS_QUADS=Math.max(constants.ACTIVE_TREE,constants.PENDING_TREE)+1;const LayerPicker=tr.ui.b.define('tr-ui-e-chrome-cc-layer-picker');LayerPicker.prototype={__proto__:HTMLUnknownElement.prototype,decorate(){this.lthi_=undefined;this.controls_=document.createElement('top-controls');this.renderPassQuads_=false;this.style.display='flex';this.style.flexDirection='column';this.controls_.style.flexGrow=0;this.controls_.style.flexShrink=0;this.controls_.style.flexBasis='auto';this.controls_.style.backgroundImage='-webkit-gradient(linear, 0 0, 100% 0, from(#E5E5E5), to(#D1D1D1))';this.controls_.style.borderBottom='1px solid #8e8e8e';this.controls_.style.borderTop='1px solid white';this.controls_.style.display='inline';this.controls_.style.fontSize='14px';this.controls_.style.paddingLeft='2px';this.itemList_=new tr.ui.b.ListView();this.itemList_.style.flexGrow=1;this.itemList_.style.flexShrink=1;this.itemList_.style.flexBasis='auto';this.itemList_.style.fontFamily='monospace';this.itemList_.style.overflow='auto';Polymer.dom(this).appendChild(this.controls_);Polymer.dom(this).appendChild(this.itemList_);this.itemList_.addEventListener('selection-changed',this.onItemSelectionChanged_.bind(this));Polymer.dom(this.controls_).appendChild(tr.ui.b.createSelector(this,'whichTree','layerPicker.whichTree',constants.ACTIVE_TREE,[{label:'Active tree',value:constants.ACTIVE_TREE},{label:'Pending tree',value:constants.PENDING_TREE},{label:'Render pass quads',value:RENDER_PASS_QUADS}]));this.showPureTransformLayers_=false;const showPureTransformLayers=tr.ui.b.createCheckBox(this,'showPureTransformLayers','layerPicker.showPureTransformLayers',false,'Transform layers');Polymer.dom(showPureTransformLayers).classList.add('show-transform-layers');showPureTransformLayers.title='When checked, pure transform layers are shown';Polymer.dom(this.controls_).appendChild(showPureTransformLayers);},get lthiSnapshot(){return this.lthiSnapshot_;},set lthiSnapshot(lthiSnapshot){this.lthiSnapshot_=lthiSnapshot;this.updateContents_();},get whichTree(){return this.renderPassQuads_?constants.ACTIVE_TREE:this.whichTree_;},set whichTree(whichTree){this.whichTree_=whichTree;this.renderPassQuads_=(whichTree===RENDER_PASS_QUADS);this.updateContents_();tr.b.dispatchSimpleEvent(this,'selection-change',false);},get layerTreeImpl(){if(this.lthiSnapshot===undefined)return undefined;return this.lthiSnapshot.getTree(this.whichTree);},get isRenderPassQuads(){return this.renderPassQuads_;},get showPureTransformLayers(){return this.showPureTransformLayers_;},set showPureTransformLayers(show){if(this.showPureTransformLayers_===show)return;this.showPureTransformLayers_=show;this.updateContents_();},getRenderPassInfos_(){if(!this.lthiSnapshot_)return[];const renderPassInfo=[];if(!this.lthiSnapshot_.args.frame||!this.lthiSnapshot_.args.frame.renderPasses){return renderPassInfo;}
+const blob=new Blob([uint8Array],{type:'application/octet-binary'});const blobUrl=window.URL.createObjectURL(blob);const link=document.createElementNS('http://www.w3.org/1999/xhtml','a');link.href=blobUrl;link.download=filename;const event=document.createEvent('MouseEvents');event.initMouseEvent('click',true,false,window,0,0,0,0,0,false,false,false,false,0,null);link.dispatchEvent(event);},onExportDisplayListClicked_(){const rawData=JSON.stringify(this.displayItemList_.items);this.saveFile_(this.displayListFilename_.value,rawData);},onExportSkPictureClicked_(){const rawData=tr.b.Base64.atob(this.picture_.getBase64SkpData());this.saveFile_(this.skpFilename_.value,rawData);}};return{DisplayItemDebugger,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const DisplayItemSnapshotView=tr.ui.b.define('tr-ui-e-chrome-cc-display-item-list-view',tr.ui.analysis.ObjectSnapshotView);DisplayItemSnapshotView.prototype={__proto__:tr.ui.analysis.ObjectSnapshotView.prototype,decorate(){this.style.display='flex';this.style.flexGrow=1;this.style.flexShrink=1;this.style.flexBasis='auto';this.style.minWidth=0;this.displayItemDebugger_=new tr.ui.e.chrome.cc.DisplayItemDebugger();this.displayItemDebugger_.style.flexGrow=1;this.displayItemDebugger_.style.flexShrink=1;this.displayItemDebugger_.style.flexBasis='auto';this.displayItemDebugger_.style.minWidth=0;Polymer.dom(this).appendChild(this.displayItemDebugger_);},updateContents(){if(this.objectSnapshot_&&this.displayItemDebugger_){this.displayItemDebugger_.displayItemList=this.objectSnapshot_;}}};tr.ui.analysis.ObjectSnapshotView.register(DisplayItemSnapshotView,{typeNames:['cc::DisplayItemList'],showInstances:false});return{DisplayItemSnapshotView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const constants=tr.e.cc.constants;const RENDER_PASS_QUADS=Math.max(constants.ACTIVE_TREE,constants.PENDING_TREE)+1;const LayerPicker=tr.ui.b.define('tr-ui-e-chrome-cc-layer-picker');LayerPicker.prototype={__proto__:HTMLUnknownElement.prototype,decorate(){this.lthi_=undefined;this.controls_=document.createElement('top-controls');this.renderPassQuads_=false;this.style.display='flex';this.style.flexDirection='column';this.controls_.style.flexGrow=0;this.controls_.style.flexShrink=0;this.controls_.style.flexBasis='auto';this.controls_.style.backgroundImage='-webkit-gradient(linear, 0 0, 100% 0, from(#E5E5E5), to(#D1D1D1))';this.controls_.style.borderBottom='1px solid #8e8e8e';this.controls_.style.borderTop='1px solid white';this.controls_.style.display='inline';this.controls_.style.fontSize='14px';this.controls_.style.paddingLeft='2px';this.itemList_=new tr.ui.b.ListView();this.itemList_.style.flexGrow=1;this.itemList_.style.flexShrink=1;this.itemList_.style.flexBasis='auto';this.itemList_.style.fontFamily='monospace';this.itemList_.style.overflow='auto';Polymer.dom(this).appendChild(this.controls_);Polymer.dom(this).appendChild(this.itemList_);this.itemList_.addEventListener('selection-changed',this.onItemSelectionChanged_.bind(this));Polymer.dom(this.controls_).appendChild(tr.ui.b.createSelector(this,'whichTree','layerPicker.whichTree',constants.ACTIVE_TREE,[{label:'Active tree',value:constants.ACTIVE_TREE},{label:'Pending tree',value:constants.PENDING_TREE},{label:'Render pass quads',value:RENDER_PASS_QUADS}]));this.showPureTransformLayers_=false;const showPureTransformLayers=tr.ui.b.createCheckBox(this,'showPureTransformLayers','layerPicker.showPureTransformLayers',false,'Transform layers');Polymer.dom(showPureTransformLayers).classList.add('show-transform-layers');showPureTransformLayers.title='When checked, pure transform layers are shown';Polymer.dom(this.controls_).appendChild(showPureTransformLayers);},get lthiSnapshot(){return this.lthiSnapshot_;},set lthiSnapshot(lthiSnapshot){this.lthiSnapshot_=lthiSnapshot;this.updateContents_();},get whichTree(){return this.renderPassQuads_?constants.ACTIVE_TREE:this.whichTree_;},set whichTree(whichTree){this.whichTree_=whichTree;this.renderPassQuads_=(whichTree===RENDER_PASS_QUADS);this.updateContents_();tr.b.dispatchSimpleEvent(this,'selection-change',false);},get layerTreeImpl(){if(this.lthiSnapshot===undefined)return undefined;return this.lthiSnapshot.getTree(this.whichTree);},get isRenderPassQuads(){return this.renderPassQuads_;},get showPureTransformLayers(){return this.showPureTransformLayers_;},set showPureTransformLayers(show){if(this.showPureTransformLayers_===show)return;this.showPureTransformLayers_=show;this.updateContents_();},getRenderPassInfos_(){if(!this.lthiSnapshot_)return[];const renderPassInfo=[];if(!this.lthiSnapshot_.args.frame||!this.lthiSnapshot_.args.frame.renderPasses){return renderPassInfo;}
 const renderPasses=this.lthiSnapshot_.args.frame.renderPasses;for(let i=0;i<renderPasses.length;++i){const info={renderPass:renderPasses[i],depth:0,id:i,name:'cc::RenderPass'};renderPassInfo.push(info);}
-return renderPassInfo;},getLayerInfos_(){if(!this.lthiSnapshot_)return[];const tree=this.lthiSnapshot_.getTree(this.whichTree_);if(!tree)return[];const layerInfos=[];const showPureTransformLayers=this.showPureTransformLayers_;function isPureTransformLayer(layer){if(layer.args.compositingReasons&&layer.args.compositingReasons.length!==1&&layer.args.compositingReasons[0]!=='No reasons given'){return false;}
-if(layer.args.drawsContent)return false;return true;}
-const visitedLayers={};function visitLayer(layer,depth,isMask,isReplica){if(visitedLayers[layer.layerId])return;visitedLayers[layer.layerId]=true;const info={layer,depth};if(layer.args.drawsContent){info.name=layer.objectInstance.name;}else{info.name='cc::LayerImpl';}
+return renderPassInfo;},getLayerInfos_(){if(!this.lthiSnapshot_)return[];const tree=this.lthiSnapshot_.getTree(this.whichTree_);if(!tree)return[];const layerInfos=[];const showPureTransformLayers=this.showPureTransformLayers_;const visitedLayers={};function visitLayer(layer,depth,isMask,isReplica){if(visitedLayers[layer.layerId])return;visitedLayers[layer.layerId]=true;const info={layer,depth};if(layer.args.drawsContent){info.name=layer.objectInstance.name;}else{info.name='cc::LayerImpl';}
 if(layer.usingGpuRasterization){info.name+=' (G)';}
-info.isMaskLayer=isMask;info.replicaLayer=isReplica;if(showPureTransformLayers||!isPureTransformLayer(layer)){layerInfos.push(info);}}
+info.isMaskLayer=isMask;info.replicaLayer=isReplica;if(showPureTransformLayers||layer.args.drawsContent){layerInfos.push(info);}}
 tree.iterLayers(visitLayer);return layerInfos;},updateContents_(){if(this.renderPassQuads_){this.updateRenderPassContents_();}else{this.updateLayerContents_();}},updateRenderPassContents_(){this.itemList_.clear();let selectedRenderPassId;if(this.selection_&&this.selection_.associatedRenderPassId){selectedRenderPassId=this.selection_.associatedRenderPassId;}
 const renderPassInfos=this.getRenderPassInfos_();renderPassInfos.forEach(function(renderPassInfo){const renderPass=renderPassInfo.renderPass;const id=renderPassInfo.id;const item=this.createElementWithDepth_(renderPassInfo.depth);const labelEl=Polymer.dom(item).appendChild(tr.ui.b.createSpan());Polymer.dom(labelEl).textContent=renderPassInfo.name+' '+id;item.renderPass=renderPass;item.renderPassId=id;Polymer.dom(this.itemList_).appendChild(item);if(id===selectedRenderPassId){renderPass.selectionState=tr.model.SelectionState.SELECTED;}},this);},updateLayerContents_(){this.changingItemSelection_=true;try{this.itemList_.clear();let selectedLayerId;if(this.selection_&&this.selection_.associatedLayerId){selectedLayerId=this.selection_.associatedLayerId;}
 const layerInfos=this.getLayerInfos_();layerInfos.forEach(function(layerInfo){const layer=layerInfo.layer;const id=layer.layerId;const item=this.createElementWithDepth_(layerInfo.depth);const labelEl=Polymer.dom(item).appendChild(tr.ui.b.createSpan());Polymer.dom(labelEl).textContent=layerInfo.name+' '+id;const notesEl=Polymer.dom(item).appendChild(tr.ui.b.createSpan());if(layerInfo.isMaskLayer){Polymer.dom(notesEl).textContent+='(mask)';}
@@ -7372,7 +7444,7 @@
 function drawProjectedQuadSelectionOutlineToContext(quad,p1,p2,p3,p4,ctx,quadCanvas){if(!quad.upperBorderColor)return;ctx.lineWidth=8;ctx.strokeStyle=quad.upperBorderColor;ctx.beginPath();ctx.moveTo(p1[0],p1[1]);ctx.lineTo(p2[0],p2[1]);ctx.lineTo(p3[0],p3[1]);ctx.lineTo(p4[0],p4[1]);ctx.closePath();ctx.stroke();}
 function drawProjectedQuadToContext(passNumber,quad,p1,p2,p3,p4,ctx,quadCanvas){if(passNumber===0){drawProjectedQuadBackgroundToContext(quad,p1,p2,p3,p4,ctx,quadCanvas);}else if(passNumber===1){drawProjectedQuadOutlineToContext(quad,p1,p2,p3,p4,ctx,quadCanvas);}else if(passNumber===2){drawProjectedQuadSelectionOutlineToContext(quad,p1,p2,p3,p4,ctx,quadCanvas);}else{throw new Error('Invalid pass number');}}
 const tmpP1=vec3.create();const tmpP2=vec3.create();const tmpP3=vec3.create();const tmpP4=vec3.create();function transformAndProcessQuads(matrix,viewport,quads,numPasses,handleQuadFunc,opt_arg1,opt_arg2){for(let passNumber=0;passNumber<numPasses;passNumber++){for(let i=0;i<quads.length;i++){const quad=quads[i];transform(tmpP1,quad.p1,matrix,viewport);transform(tmpP2,quad.p2,matrix,viewport);transform(tmpP3,quad.p3,matrix,viewport);transform(tmpP4,quad.p4,matrix,viewport);handleQuadFunc(passNumber,quad,tmpP1,tmpP2,tmpP3,tmpP4,opt_arg1,opt_arg2);}}}
-const QuadStackView=tr.ui.b.define('quad-stack-view');QuadStackView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.className='quad-stack-view';this.style.display='block';this.style.float='left';this.style.height='100%';this.style.overflow='hidden';this.style.position='relative';this.style.width='100%';const node=tr.ui.b.instantiateTemplate('#quad-stack-view-template',THIS_DOC);Polymer.dom(this).appendChild(node);this.updateHeaderVisibility_();const header=Polymer.dom(this).querySelector('#header');header.style.position='absolute';header.style.fontSize='70%';header.style.top='10px';header.style.left='10px';header.style.width='800px';this.canvas_=Polymer.dom(this).querySelector('#canvas');this.chromeImages_={left:Polymer.dom(this).querySelector('#chrome-left'),mid:Polymer.dom(this).querySelector('#chrome-mid'),right:Polymer.dom(this).querySelector('#chrome-right')};const stackingDistanceSlider=Polymer.dom(this).querySelector('#stacking-distance-slider');stackingDistanceSlider.style.position='absolute';stackingDistanceSlider.style.fontSize='70%';stackingDistanceSlider.style.top='10px';stackingDistanceSlider.style.right='10px';stackingDistanceSlider.value=tr.b.Settings.get('quadStackView.stackingDistance',45);stackingDistanceSlider.addEventListener('change',this.onStackingDistanceChange_.bind(this));stackingDistanceSlider.addEventListener('input',this.onStackingDistanceChange_.bind(this));this.trackMouse_();this.camera_=new tr.ui.b.Camera(this.mouseModeSelector_);this.camera_.addEventListener('renderrequired',this.onRenderRequired_.bind(this));this.cameraWasReset_=false;this.camera_.canvas=this.canvas_;this.viewportRect_=tr.b.math.Rect.fromXYWH(0,0,0,0);this.pixelRatio_=window.devicePixelRatio||1;},updateHeaderVisibility_(){if(this.headerText){Polymer.dom(this).querySelector('#header').style.display='';}else{Polymer.dom(this).querySelector('#header').style.display='none';}},get headerText(){return Polymer.dom(this).querySelector('#header').textContent;},set headerText(headerText){Polymer.dom(this).querySelector('#header').textContent=headerText;this.updateHeaderVisibility_();},onStackingDistanceChange_(e){tr.b.Settings.set('quadStackView.stackingDistance',this.stackingDistance);this.scheduleRender();e.stopPropagation();},get stackingDistance(){return Polymer.dom(this).querySelector('#stacking-distance-slider').value;},get mouseModeSelector(){return this.mouseModeSelector_;},get camera(){return this.camera_;},set quads(q){this.quads_=q;this.scheduleRender();},set deviceRect(rect){if(!rect||rect.equalTo(this.deviceRect_))return;this.deviceRect_=rect;this.camera_.deviceRect=rect;this.chromeQuad_=undefined;},resize(){if(!this.offsetParent)return true;const width=parseInt(window.getComputedStyle(this.offsetParent).width);const height=parseInt(window.getComputedStyle(this.offsetParent).height);const rect=tr.b.math.Rect.fromXYWH(0,0,width,height);if(rect.equalTo(this.viewportRect_))return false;this.viewportRect_=rect;this.style.width=width+'px';this.style.height=height+'px';this.canvas_.style.width=width+'px';this.canvas_.style.height=height+'px';this.canvas_.width=this.pixelRatio_*width;this.canvas_.height=this.pixelRatio_*height;if(!this.cameraWasReset_){this.camera_.resetCamera();this.cameraWasReset_=true;}
+const QuadStackView=tr.ui.b.define('quad-stack-view');QuadStackView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.className='quad-stack-view';this.style.display='flex';this.style.position='relative';const node=tr.ui.b.instantiateTemplate('#quad-stack-view-template',THIS_DOC);Polymer.dom(this).appendChild(node);this.updateHeaderVisibility_();const header=Polymer.dom(this).querySelector('#header');header.style.position='absolute';header.style.fontSize='70%';header.style.top='10px';header.style.left='10px';header.style.right='150px';const scroller=Polymer.dom(this).querySelector('#canvas-scroller');scroller.style.flexGrow=1;scroller.style.flexShrink=1;scroller.style.flexBasis='auto';scroller.style.minWidth=0;scroller.style.minHeight=0;scroller.style.overflow='auto';this.canvas_=Polymer.dom(this).querySelector('#canvas');this.chromeImages_={left:Polymer.dom(this).querySelector('#chrome-left'),mid:Polymer.dom(this).querySelector('#chrome-mid'),right:Polymer.dom(this).querySelector('#chrome-right')};const stackingDistanceSlider=Polymer.dom(this).querySelector('#stacking-distance-slider');stackingDistanceSlider.style.position='absolute';stackingDistanceSlider.style.fontSize='70%';stackingDistanceSlider.style.top='10px';stackingDistanceSlider.style.right='10px';stackingDistanceSlider.value=tr.b.Settings.get('quadStackView.stackingDistance',45);stackingDistanceSlider.addEventListener('change',this.onStackingDistanceChange_.bind(this));stackingDistanceSlider.addEventListener('input',this.onStackingDistanceChange_.bind(this));this.trackMouse_();this.camera_=new tr.ui.b.Camera(this.mouseModeSelector_);this.camera_.addEventListener('renderrequired',this.onRenderRequired_.bind(this));this.cameraWasReset_=false;this.camera_.canvas=this.canvas_;this.viewportRect_=tr.b.math.Rect.fromXYWH(0,0,0,0);this.pixelRatio_=window.devicePixelRatio||1;},updateHeaderVisibility_(){if(this.headerText){Polymer.dom(this).querySelector('#header').style.display='';}else{Polymer.dom(this).querySelector('#header').style.display='none';}},get headerText(){return Polymer.dom(this).querySelector('#header').textContent;},set headerText(headerText){Polymer.dom(this).querySelector('#header').textContent=headerText;this.updateHeaderVisibility_();},onStackingDistanceChange_(e){tr.b.Settings.set('quadStackView.stackingDistance',this.stackingDistance);this.scheduleRender();e.stopPropagation();},get stackingDistance(){return Polymer.dom(this).querySelector('#stacking-distance-slider').value;},get mouseModeSelector(){return this.mouseModeSelector_;},get camera(){return this.camera_;},set quads(q){this.quads_=q;this.scheduleRender();},set deviceRect(rect){if(!rect||rect.equalTo(this.deviceRect_))return;this.deviceRect_=rect;this.camera_.deviceRect=rect;this.chromeQuad_=undefined;},resize(){if(!this.offsetParent)return true;const width=parseInt(window.getComputedStyle(this.offsetParent).width);const height=parseInt(window.getComputedStyle(this.offsetParent).height);const rect=tr.b.math.Rect.fromXYWH(0,0,width,height);if(rect.equalTo(this.viewportRect_))return false;this.viewportRect_=rect;this.canvas_.style.width=width+'px';this.canvas_.style.height=height+'px';this.canvas_.width=this.pixelRatio_*width;this.canvas_.height=this.pixelRatio_*height;if(!this.cameraWasReset_){this.camera_.resetCamera();this.cameraWasReset_=true;}
 return true;},readyToDraw(){if(!this.chromeImages_.left.src){let leftContent=window.getComputedStyle(this.chromeImages_.left).backgroundImage;leftContent=tr.ui.b.extractUrlString(leftContent);let midContent=window.getComputedStyle(this.chromeImages_.mid).backgroundImage;midContent=tr.ui.b.extractUrlString(midContent);let rightContent=window.getComputedStyle(this.chromeImages_.right).backgroundImage;rightContent=tr.ui.b.extractUrlString(rightContent);this.chromeImages_.left.src=leftContent;this.chromeImages_.mid.src=midContent;this.chromeImages_.right.src=rightContent;}
 return(this.chromeImages_.left.height>0)&&(this.chromeImages_.mid.height>0)&&(this.chromeImages_.right.height>0);},get chromeQuad(){if(this.chromeQuad_)return this.chromeQuad_;const chromeCanvas=document.createElement('canvas');const offsetY=this.chromeImages_.left.height;chromeCanvas.width=this.deviceRect_.width;chromeCanvas.height=this.deviceRect_.height+offsetY;const leftWidth=this.chromeImages_.left.width;const midWidth=this.chromeImages_.mid.width;const rightWidth=this.chromeImages_.right.width;const chromeCtx=chromeCanvas.getContext('2d');chromeCtx.drawImage(this.chromeImages_.left,0,0);chromeCtx.save();chromeCtx.translate(leftWidth,0);const s=(this.deviceRect_.width-leftWidth-rightWidth)/midWidth;chromeCtx.scale(s,1);chromeCtx.drawImage(this.chromeImages_.mid,0,0);chromeCtx.restore();chromeCtx.drawImage(this.chromeImages_.right,leftWidth+s*midWidth,0);const chromeRect=tr.b.math.Rect.fromXYWH(this.deviceRect_.x,this.deviceRect_.y-offsetY,this.deviceRect_.width,this.deviceRect_.height+offsetY);const chromeQuad=tr.b.math.Quad.fromRect(chromeRect);chromeQuad.stackingGroupId=this.maxStackingGroupId_+1;chromeQuad.imageData=chromeCtx.getImageData(0,0,chromeCanvas.width,chromeCanvas.height);chromeQuad.shadowOffset=[0,0];chromeQuad.shadowBlur=5;chromeQuad.borderWidth=3;this.chromeQuad_=chromeQuad;return this.chromeQuad_;},scheduleRender(){if(this.redrawScheduled_)return false;this.redrawScheduled_=true;tr.b.requestAnimationFrame(this.render,this);},onRenderRequired_(e){this.scheduleRender();},stackTransformAndProcessQuads_(numPasses,handleQuadFunc,includeChromeQuad,opt_arg1,opt_arg2){const mv=this.camera_.modelViewMatrix;const p=this.camera_.projectionMatrix;const viewport=tr.b.math.Rect.fromXYWH(0,0,this.canvas_.width,this.canvas_.height);const quadStacks=[];for(let i=0;i<this.quads_.length;++i){const quad=this.quads_[i];const stackingId=quad.stackingGroupId||0;while(stackingId>=quadStacks.length){quadStacks.push([]);}
 quadStacks[stackingId].push(quad);}
@@ -7381,8 +7453,9 @@
 if(!this.quads_)return;const canvasCtx=this.canvas_.getContext('2d');if(!this.resize()){canvasCtx.clearRect(0,0,this.canvas_.width,this.canvas_.height);}
 const quadCanvas=document.createElement('canvas');this.stackTransformAndProcessQuads_(3,drawProjectedQuadToContext,true,canvasCtx,quadCanvas);quadCanvas.width=0;},trackMouse_(){this.mouseModeSelector_=document.createElement('tr-ui-b-mouse-mode-selector');this.mouseModeSelector_.targetElement=this.canvas_;this.mouseModeSelector_.supportedModeMask=tr.ui.b.MOUSE_SELECTOR_MODE.SELECTION|tr.ui.b.MOUSE_SELECTOR_MODE.PANSCAN|tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM|tr.ui.b.MOUSE_SELECTOR_MODE.ROTATE;this.mouseModeSelector_.mode=tr.ui.b.MOUSE_SELECTOR_MODE.PANSCAN;this.mouseModeSelector_.pos={x:0,y:100};Polymer.dom(this).appendChild(this.mouseModeSelector_);this.mouseModeSelector_.settingsKey='quadStackView.mouseModeSelector';this.mouseModeSelector_.setModifierForAlternateMode(tr.ui.b.MOUSE_SELECTOR_MODE.ROTATE,tr.ui.b.MODIFIER.SHIFT);this.mouseModeSelector_.setModifierForAlternateMode(tr.ui.b.MOUSE_SELECTOR_MODE.PANSCAN,tr.ui.b.MODIFIER.SPACE);this.mouseModeSelector_.setModifierForAlternateMode(tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM,tr.ui.b.MODIFIER.CMD_OR_CTRL);this.mouseModeSelector_.addEventListener('updateselection',this.onSelectionUpdate_.bind(this));this.mouseModeSelector_.addEventListener('endselection',this.onSelectionUpdate_.bind(this));},extractRelativeMousePosition_(e){const br=this.canvas_.getBoundingClientRect();return[this.pixelRatio_*(e.clientX-this.canvas_.offsetLeft-br.left),this.pixelRatio_*(e.clientY-this.canvas_.offsetTop-br.top)];},onSelectionUpdate_(e){const mousePos=this.extractRelativeMousePosition_(e);const res=[];function handleQuad(passNumber,quad,p1,p2,p3,p4){if(tr.b.math.pointInImplicitQuad(mousePos,p1,p2,p3,p4)){res.push(quad);}}
 this.stackTransformAndProcessQuads_(1,handleQuad,false);e=new tr.b.Event('selectionchange');e.quads=res;this.dispatchEvent(e);}};return{QuadStackView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const ColorScheme=tr.b.ColorScheme;const THIS_DOC=document.currentScript.ownerDocument;const TILE_HEATMAP_TYPE={};TILE_HEATMAP_TYPE.NONE='none';TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY='scheduledPriority';TILE_HEATMAP_TYPE.USING_GPU_MEMORY='usingGpuMemory';const cc=tr.ui.e.chrome.cc;function createTileRectsSelectorBaseOptions(){return[{label:'None',value:'none'},{label:'Coverage Rects',value:'coverage'}];}
-const LayerTreeQuadStackView=tr.ui.b.define('tr-ui-e-chrome-cc-layer-tree-quad-stack-view');LayerTreeQuadStackView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.style.flexGrow=1;this.style.flexShrink=1;this.style.flexBasis='100%';this.style.flexDirection='column';this.style.minHeight=0;this.style.display='flex';this.style.width='100%';this.isRenderPassQuads_=false;this.pictureAsImageData_={};this.messages_=[];this.controls_=document.createElement('top-controls');this.controls_.style.flexGrow=0;this.controls_.style.flexShrink=0;this.controls_.style.flexBasis='auto';this.controls_.style.backgroundImage='-webkit-gradient(linear, 0 0, 100% 0, from(#E5E5E5), to(#D1D1D1))';this.controls_.style.borderBottom='1px solid #8e8e8e';this.controls_.style.borderTop='1px solid white';this.controls_.style.display='flex';this.controls_.style.flexDirection='row';this.controls_.style.flexWrap='wrap';this.controls_.style.fontSize='14px';this.controls_.style.paddingLeft='2px';this.controls_.style.overflow='hidden';this.infoBar_=document.createElement('tr-ui-b-info-bar');this.quadStackView_=new tr.ui.b.QuadStackView();this.quadStackView_.addEventListener('selectionchange',this.onQuadStackViewSelectionChange_.bind(this));this.extraHighlightsByLayerId_=undefined;this.inputEventImageData_=undefined;const m=tr.ui.b.MOUSE_SELECTOR_MODE;const mms=this.quadStackView_.mouseModeSelector;mms.settingsKey='tr.e.cc.layerTreeQuadStackView.mouseModeSelector';mms.setKeyCodeForMode(m.SELECTION,'Z'.charCodeAt(0));mms.setKeyCodeForMode(m.PANSCAN,'X'.charCodeAt(0));mms.setKeyCodeForMode(m.ZOOM,'C'.charCodeAt(0));mms.setKeyCodeForMode(m.ROTATE,'V'.charCodeAt(0));const node=tr.ui.b.instantiateTemplate('#tr-ui-e-chrome-cc-layer-tree-quad-stack-view-template',THIS_DOC);Polymer.dom(this).appendChild(node);Polymer.dom(this).appendChild(this.controls_);Polymer.dom(this).appendChild(this.infoBar_);Polymer.dom(this).appendChild(this.quadStackView_);this.tileRectsSelector_=tr.ui.b.createSelector(this,'howToShowTiles','layerView.howToShowTiles','none',createTileRectsSelectorBaseOptions());Polymer.dom(this.controls_).appendChild(this.tileRectsSelector_);const tileHeatmapText=tr.ui.b.createSpan({textContent:'Tile heatmap:'});Polymer.dom(this.controls_).appendChild(tileHeatmapText);const tileHeatmapSelector=tr.ui.b.createSelector(this,'tileHeatmapType','layerView.tileHeatmapType',TILE_HEATMAP_TYPE.NONE,[{label:'None',value:TILE_HEATMAP_TYPE.NONE},{label:'Scheduled Priority',value:TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY},{label:'Is using GPU memory',value:TILE_HEATMAP_TYPE.USING_GPU_MEMORY}]);Polymer.dom(this.controls_).appendChild(tileHeatmapSelector);const showOtherLayersCheckbox=tr.ui.b.createCheckBox(this,'showOtherLayers','layerView.showOtherLayers',true,'Other layers/passes');showOtherLayersCheckbox.title='When checked, show all layers, selected or not.';Polymer.dom(this.controls_).appendChild(showOtherLayersCheckbox);const showInvalidationsCheckbox=tr.ui.b.createCheckBox(this,'showInvalidations','layerView.showInvalidations',true,'Invalidations');showInvalidationsCheckbox.title='When checked, compositing invalidations are highlighted in red';Polymer.dom(this.controls_).appendChild(showInvalidationsCheckbox);const showUnrecordedRegionCheckbox=tr.ui.b.createCheckBox(this,'showUnrecordedRegion','layerView.showUnrecordedRegion',true,'Unrecorded area');showUnrecordedRegionCheckbox.title='When checked, unrecorded areas are highlighted in yellow';Polymer.dom(this.controls_).appendChild(showUnrecordedRegionCheckbox);const showBottlenecksCheckbox=tr.ui.b.createCheckBox(this,'showBottlenecks','layerView.showBottlenecks',true,'Bottlenecks');showBottlenecksCheckbox.title='When checked, scroll bottlenecks are highlighted';Polymer.dom(this.controls_).appendChild(showBottlenecksCheckbox);const showLayoutRectsCheckbox=tr.ui.b.createCheckBox(this,'showLayoutRects','layerView.showLayoutRects',false,'Layout rects');showLayoutRectsCheckbox.title='When checked, shows rects for regions where layout happened';Polymer.dom(this.controls_).appendChild(showLayoutRectsCheckbox);const showContentsCheckbox=tr.ui.b.createCheckBox(this,'showContents','layerView.showContents',true,'Contents');showContentsCheckbox.title='When checked, show the rendered contents inside the layer outlines';Polymer.dom(this.controls_).appendChild(showContentsCheckbox);const showAnimationBoundsCheckbox=tr.ui.b.createCheckBox(this,'showAnimationBounds','layerView.showAnimationBounds',false,'Animation Bounds');showAnimationBoundsCheckbox.title='When checked, show a border around'+' a layer showing the extent of its animation.';Polymer.dom(this.controls_).appendChild(showAnimationBoundsCheckbox);const showInputEventsCheckbox=tr.ui.b.createCheckBox(this,'showInputEvents','layerView.showInputEvents',true,'Input events');showInputEventsCheckbox.title='When checked, input events are '+'displayed as circles.';Polymer.dom(this.controls_).appendChild(showInputEventsCheckbox);this.whatRasterizedLink_=document.createElement('a');Polymer.dom(this.whatRasterizedLink_).classList.add('what-rasterized');Polymer.dom(this.whatRasterizedLink_).textContent='What rasterized?';this.whatRasterizedLink_.addEventListener('click',this.onWhatRasterizedLinkClicked_.bind(this));Polymer.dom(this).appendChild(this.whatRasterizedLink_);},get layerTreeImpl(){return this.layerTreeImpl_;},set isRenderPassQuads(newValue){this.isRenderPassQuads_=newValue;},set layerTreeImpl(layerTreeImpl){if(this.layerTreeImpl_===layerTreeImpl)return;this.layerTreeImpl_=layerTreeImpl;this.selection=undefined;},get extraHighlightsByLayerId(){return this.extraHighlightsByLayerId_;},set extraHighlightsByLayerId(extraHighlightsByLayerId){this.extraHighlightsByLayerId_=extraHighlightsByLayerId;this.scheduleUpdateContents_();},get showOtherLayers(){return this.showOtherLayers_;},set showOtherLayers(show){this.showOtherLayers_=show;this.updateContents_();},get showAnimationBounds(){return this.showAnimationBounds_;},set showAnimationBounds(show){this.showAnimationBounds_=show;this.updateContents_();},get showInputEvents(){return this.showInputEvents_;},set showInputEvents(show){this.showInputEvents_=show;this.updateContents_();},get showContents(){return this.showContents_;},set showContents(show){this.showContents_=show;this.updateContents_();},get showInvalidations(){return this.showInvalidations_;},set showInvalidations(show){this.showInvalidations_=show;this.updateContents_();},get showUnrecordedRegion(){return this.showUnrecordedRegion_;},set showUnrecordedRegion(show){this.showUnrecordedRegion_=show;this.updateContents_();},get showBottlenecks(){return this.showBottlenecks_;},set showBottlenecks(show){this.showBottlenecks_=show;this.updateContents_();},get showLayoutRects(){return this.showLayoutRects_;},set showLayoutRects(show){this.showLayoutRects_=show;this.updateContents_();},get howToShowTiles(){return this.howToShowTiles_;},set howToShowTiles(val){if(val!=='none'&&val!=='coverage'&&isNaN(parseFloat(val))){throw new Error('howToShowTiles requires "none" or "coverage" or a number');}
-this.howToShowTiles_=val;this.updateContents_();},get tileHeatmapType(){return this.tileHeatmapType_;},set tileHeatmapType(val){this.tileHeatmapType_=val;this.updateContents_();},get selection(){return this.selection_;},set selection(selection){if(this.selection===selection)return;this.selection_=selection;tr.b.dispatchSimpleEvent(this,'selection-change');this.updateContents_();},regenerateContent(){this.updateTilesSelector_();this.updateContents_();},loadDataForImageElement_(image,callback){const imageContent=window.getComputedStyle(image).backgroundImage;image.src=tr.ui.b.extractUrlString(imageContent);image.onload=function(){const canvas=document.createElement('canvas');const ctx=canvas.getContext('2d');canvas.width=image.width;canvas.height=image.height;ctx.drawImage(image,0,0);const imageData=ctx.getImageData(0,0,canvas.width,canvas.height);callback(imageData);};},onQuadStackViewSelectionChange_(e){const selectableQuads=e.quads.filter(function(q){return q.selectionToSetIfClicked!==undefined;});if(selectableQuads.length===0){this.selection=undefined;return;}
+const LayerTreeQuadStackView=tr.ui.b.define('tr-ui-e-chrome-cc-layer-tree-quad-stack-view');LayerTreeQuadStackView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.style.flexGrow=1;this.style.flexShrink=1;this.style.flexBasis='auto';this.style.flexDirection='column';this.style.minHeight=0;this.style.display='flex';this.isRenderPassQuads_=false;this.pictureAsImageData_={};this.messages_=[];this.controls_=document.createElement('top-controls');this.controls_.style.flexGrow=0;this.controls_.style.flexShrink=0;this.controls_.style.flexBasis='auto';this.controls_.style.backgroundImage='-webkit-gradient(linear, 0 0, 100% 0, from(#E5E5E5), to(#D1D1D1))';this.controls_.style.borderBottom='1px solid #8e8e8e';this.controls_.style.borderTop='1px solid white';this.controls_.style.display='flex';this.controls_.style.flexDirection='row';this.controls_.style.flexWrap='wrap';this.controls_.style.fontSize='14px';this.controls_.style.paddingLeft='2px';this.controls_.style.overflow='hidden';this.infoBar_=document.createElement('tr-ui-b-info-bar');this.quadStackView_=new tr.ui.b.QuadStackView();this.quadStackView_.addEventListener('selectionchange',this.onQuadStackViewSelectionChange_.bind(this));this.quadStackView_.style.flexGrow=1;this.quadStackView_.style.flexShrink=1;this.quadStackView_.style.flexBasis='auto';this.quadStackView_.style.minWidth='200px';this.extraHighlightsByLayerId_=undefined;this.inputEventImageData_=undefined;const m=tr.ui.b.MOUSE_SELECTOR_MODE;const mms=this.quadStackView_.mouseModeSelector;mms.settingsKey='tr.e.cc.layerTreeQuadStackView.mouseModeSelector';mms.setKeyCodeForMode(m.SELECTION,'Z'.charCodeAt(0));mms.setKeyCodeForMode(m.PANSCAN,'X'.charCodeAt(0));mms.setKeyCodeForMode(m.ZOOM,'C'.charCodeAt(0));mms.setKeyCodeForMode(m.ROTATE,'V'.charCodeAt(0));const node=tr.ui.b.instantiateTemplate('#tr-ui-e-chrome-cc-layer-tree-quad-stack-view-template',THIS_DOC);Polymer.dom(this).appendChild(node);Polymer.dom(this).appendChild(this.controls_);Polymer.dom(this).appendChild(this.infoBar_);Polymer.dom(this).appendChild(this.quadStackView_);this.tileRectsSelector_=tr.ui.b.createSelector(this,'howToShowTiles','layerView.howToShowTiles','none',createTileRectsSelectorBaseOptions());Polymer.dom(this.controls_).appendChild(this.tileRectsSelector_);const tileHeatmapText=tr.ui.b.createSpan({textContent:'Tile heatmap:'});Polymer.dom(this.controls_).appendChild(tileHeatmapText);const tileHeatmapSelector=tr.ui.b.createSelector(this,'tileHeatmapType','layerView.tileHeatmapType',TILE_HEATMAP_TYPE.NONE,[{label:'None',value:TILE_HEATMAP_TYPE.NONE},{label:'Scheduled Priority',value:TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY},{label:'Is using GPU memory',value:TILE_HEATMAP_TYPE.USING_GPU_MEMORY}]);Polymer.dom(this.controls_).appendChild(tileHeatmapSelector);const showOtherLayersCheckbox=tr.ui.b.createCheckBox(this,'showOtherLayers','layerView.showOtherLayers',true,'Other layers/passes');showOtherLayersCheckbox.title='When checked, show all layers, selected or not.';Polymer.dom(this.controls_).appendChild(showOtherLayersCheckbox);const showInvalidationsCheckbox=tr.ui.b.createCheckBox(this,'showInvalidations','layerView.showInvalidations',true,'Invalidations');showInvalidationsCheckbox.title='When checked, compositing invalidations are highlighted in red';Polymer.dom(this.controls_).appendChild(showInvalidationsCheckbox);const showUnrecordedRegionCheckbox=tr.ui.b.createCheckBox(this,'showUnrecordedRegion','layerView.showUnrecordedRegion',true,'Unrecorded area');showUnrecordedRegionCheckbox.title='When checked, unrecorded areas are highlighted in yellow';Polymer.dom(this.controls_).appendChild(showUnrecordedRegionCheckbox);const showBottlenecksCheckbox=tr.ui.b.createCheckBox(this,'showBottlenecks','layerView.showBottlenecks',true,'Bottlenecks');showBottlenecksCheckbox.title='When checked, scroll bottlenecks are highlighted';Polymer.dom(this.controls_).appendChild(showBottlenecksCheckbox);const showLayoutRectsCheckbox=tr.ui.b.createCheckBox(this,'showLayoutRects','layerView.showLayoutRects',false,'Layout rects');showLayoutRectsCheckbox.title='When checked, shows rects for regions where layout happened';Polymer.dom(this.controls_).appendChild(showLayoutRectsCheckbox);const showContentsCheckbox=tr.ui.b.createCheckBox(this,'showContents','layerView.showContents',true,'Contents');showContentsCheckbox.title='When checked, show the rendered contents inside the layer outlines';Polymer.dom(this.controls_).appendChild(showContentsCheckbox);const showAnimationBoundsCheckbox=tr.ui.b.createCheckBox(this,'showAnimationBounds','layerView.showAnimationBounds',false,'Animation Bounds');showAnimationBoundsCheckbox.title='When checked, show a border around'+' a layer showing the extent of its animation.';Polymer.dom(this.controls_).appendChild(showAnimationBoundsCheckbox);const showInputEventsCheckbox=tr.ui.b.createCheckBox(this,'showInputEvents','layerView.showInputEvents',true,'Input events');showInputEventsCheckbox.title='When checked, input events are '+'displayed as circles.';Polymer.dom(this.controls_).appendChild(showInputEventsCheckbox);this.whatRasterizedLink_=document.createElement('tr-ui-a-analysis-link');this.whatRasterizedLink_.style.position='absolute';this.whatRasterizedLink_.style.bottom='15px';this.whatRasterizedLink_.style.left='10px';this.whatRasterizedLink_.selection=this.getWhatRasterizedEventSet_.bind(this);Polymer.dom(this.quadStackView_).appendChild(this.whatRasterizedLink_);},get layerTreeImpl(){return this.layerTreeImpl_;},set isRenderPassQuads(newValue){this.isRenderPassQuads_=newValue;},set layerTreeImpl(layerTreeImpl){if(this.layerTreeImpl_===layerTreeImpl)return;this.layerTreeImpl_=layerTreeImpl;this.selection=undefined;},get extraHighlightsByLayerId(){return this.extraHighlightsByLayerId_;},set extraHighlightsByLayerId(extraHighlightsByLayerId){this.extraHighlightsByLayerId_=extraHighlightsByLayerId;this.scheduleUpdateContents_();},get showOtherLayers(){return this.showOtherLayers_;},set showOtherLayers(show){this.showOtherLayers_=show;this.updateContents_();},get showAnimationBounds(){return this.showAnimationBounds_;},set showAnimationBounds(show){this.showAnimationBounds_=show;this.updateContents_();},get showInputEvents(){return this.showInputEvents_;},set showInputEvents(show){this.showInputEvents_=show;this.updateContents_();},get showContents(){return this.showContents_;},set showContents(show){this.showContents_=show;this.updateContents_();},get showInvalidations(){return this.showInvalidations_;},set showInvalidations(show){this.showInvalidations_=show;this.updateContents_();},get showUnrecordedRegion(){return this.showUnrecordedRegion_;},set showUnrecordedRegion(show){this.showUnrecordedRegion_=show;this.updateContents_();},get showBottlenecks(){return this.showBottlenecks_;},set showBottlenecks(show){this.showBottlenecks_=show;this.updateContents_();},get showLayoutRects(){return this.showLayoutRects_;},set showLayoutRects(show){this.showLayoutRects_=show;this.updateContents_();},get howToShowTiles(){return this.howToShowTiles_;},set howToShowTiles(val){if(val!=='none'&&val!=='coverage'&&isNaN(parseFloat(val))){throw new Error('howToShowTiles requires "none" or "coverage" or a number');}
+this.howToShowTiles_=val;this.updateContents_();},get tileHeatmapType(){return this.tileHeatmapType_;},set tileHeatmapType(val){this.tileHeatmapType_=val;this.updateContents_();},get selection(){return this.selection_;},set selection(selection){if(this.selection===selection)return;this.selection_=selection;tr.b.dispatchSimpleEvent(this,'selection-change');this.updateContents_();},regenerateContent(){this.updateTilesSelector_();this.updateContents_();},loadDataForImageElement_(image,callback){const imageContent=window.getComputedStyle(image).backgroundImage;if(!imageContent){this.scheduleUpdateContents_();return;}
+image.src=tr.ui.b.extractUrlString(imageContent);image.onload=function(){const canvas=document.createElement('canvas');const ctx=canvas.getContext('2d');canvas.width=image.width;canvas.height=image.height;ctx.drawImage(image,0,0);const imageData=ctx.getImageData(0,0,canvas.width,canvas.height);callback(imageData);};},onQuadStackViewSelectionChange_(e){const selectableQuads=e.quads.filter(function(q){return q.selectionToSetIfClicked!==undefined;});if(selectableQuads.length===0){this.selection=undefined;return;}
 selectableQuads.sort(function(x,y){const z=x.stackingGroupId-y.stackingGroupId;if(z!==0)return z;return x.selectionToSetIfClicked.specicifity-
 y.selectionToSetIfClicked.specicifity;});const quadToSelect=selectableQuads[selectableQuads.length-1];this.selection=quadToSelect.selectionToSetIfClicked;},scheduleUpdateContents_(){if(this.updateContentsPending_)return;this.updateContentsPending_=true;tr.b.requestAnimationFrameInThisFrameIfPossible(this.updateContents_,this);},updateContents_(){if(!this.layerTreeImpl_){this.quadStackView_.headerText='No tree';this.quadStackView_.quads=[];return;}
 const status=this.computePictureLoadingStatus_();if(!status.picturesComplete)return;const lthi=this.layerTreeImpl_.layerTreeHostImpl;const lthiInstance=lthi.objectInstance;const worldViewportRect=tr.b.math.Rect.fromXYWH(0,0,lthi.deviceViewportSize.width,lthi.deviceViewportSize.height);this.quadStackView_.deviceRect=worldViewportRect;if(this.isRenderPassQuads_){this.quadStackView_.quads=this.generateRenderPassQuads();}else{this.quadStackView_.quads=this.generateLayerQuads();}
@@ -7413,9 +7486,10 @@
 return status;},get selectedRenderPass(){if(this.selection){return this.selection.renderPass_;}},get selectedLayer(){if(this.selection){const selectedLayerId=this.selection.associatedLayerId;return this.layerTreeImpl_.findLayerWithId(selectedLayerId);}},get renderPasses(){let renderPasses=this.layerTreeImpl.layerTreeHostImpl.args.frame.renderPasses;if(!this.showOtherLayers){const selectedRenderPass=this.selectedRenderPass;if(selectedRenderPass){renderPasses=[selectedRenderPass];}}
 return renderPasses;},get layers(){let layers=this.layerTreeImpl.renderSurfaceLayerList;if(!this.showOtherLayers){const selectedLayer=this.selectedLayer;if(selectedLayer){layers=[selectedLayer];}}
 return layers;},appendImageQuads_(quads,layer,layerQuad){for(let ir=0;ir<layer.pictures.length;++ir){const picture=layer.pictures[ir];if(!picture.layerRect)continue;const unitRect=picture.layerRect.asUVRectInside(layer.bounds);const iq=layerQuad.projectUnitRect(unitRect);const pictureData=this.pictureAsImageData_[picture.guid];if(this.showContents&&pictureData&&pictureData.imageData){iq.imageData=pictureData.imageData;iq.borderColor='rgba(0,0,0,0)';}else{iq.imageData=undefined;}
-iq.stackingGroupId=layerQuad.stackingGroupId;quads.push(iq);}},appendAnimationQuads_(quads,layer,layerQuad){if(!layer.animationBoundsRect)return;const rect=layer.animationBoundsRect;const abq=tr.b.math.Quad.fromRect(rect);abq.backgroundColor='rgba(164,191,48,0.5)';abq.borderColor='rgba(205,255,0,0.75)';abq.borderWidth=3.0;abq.stackingGroupId=layerQuad.stackingGroupId;abq.selectionToSetIfClicked=new cc.AnimationRectSelection(layer,rect);quads.push(abq);},appendInvalidationQuads_(quads,layer,layerQuad){if(layer.layerTreeImpl.hasSourceFrameBeenDrawnBefore)return;for(let ir=0;ir<layer.annotatedInvalidation.rects.length;ir++){const rect=layer.annotatedInvalidation.rects[ir];const unitRect=rect.asUVRectInside(layer.bounds);const iq=layerQuad.projectUnitRect(unitRect);iq.backgroundColor='rgba(0, 255, 0, 0.1)';if(rect.reason==='renderer insertion'){iq.backgroundColor='rgba(0, 255, 128, 0.1)';}
-iq.borderColor='rgba(0, 255, 0, 1)';iq.stackingGroupId=layerQuad.stackingGroupId;iq.selectionToSetIfClicked=new cc.LayerRectSelection(layer,'Invalidation rect ('+rect.reason+')',rect,rect);quads.push(iq);}
-if(layer.annotatedInvalidation.rects.length===0){for(let ir=0;ir<layer.invalidation.rects.length;ir++){const rect=layer.invalidation.rects[ir];const unitRect=rect.asUVRectInside(layer.bounds);const iq=layerQuad.projectUnitRect(unitRect);iq.backgroundColor='rgba(0, 255, 0, 0.1)';iq.borderColor='rgba(0, 255, 0, 1)';iq.stackingGroupId=layerQuad.stackingGroupId;iq.selectionToSetIfClicked=new cc.LayerRectSelection(layer,'Invalidation rect',rect,rect);quads.push(iq);}}},appendUnrecordedRegionQuads_(quads,layer,layerQuad){for(let ir=0;ir<layer.unrecordedRegion.rects.length;ir++){const rect=layer.unrecordedRegion.rects[ir];const unitRect=rect.asUVRectInside(layer.bounds);const iq=layerQuad.projectUnitRect(unitRect);iq.backgroundColor='rgba(240, 230, 140, 0.3)';iq.borderColor='rgba(240, 230, 140, 1)';iq.stackingGroupId=layerQuad.stackingGroupId;iq.selectionToSetIfClicked=new cc.LayerRectSelection(layer,'Unrecorded area',rect,rect);quads.push(iq);}},appendBottleneckQuads_(quads,layer,layerQuad,stackingGroupId){function processRegion(region,label,borderColor){const backgroundColor=borderColor.clone();backgroundColor.a=0.4*(borderColor.a||1.0);if(!region||!region.rects)return;for(let ir=0;ir<region.rects.length;ir++){const rect=region.rects[ir];const unitRect=rect.asUVRectInside(layer.bounds);const iq=layerQuad.projectUnitRect(unitRect);iq.backgroundColor=backgroundColor.toString();iq.borderColor=borderColor.toString();iq.borderWidth=4.0;iq.stackingGroupId=stackingGroupId;iq.selectionToSetIfClicked=new cc.LayerRectSelection(layer,label,rect,rect);quads.push(iq);}}
+iq.stackingGroupId=layerQuad.stackingGroupId;quads.push(iq);}},appendAnimationQuads_(quads,layer,layerQuad){if(!layer.animationBoundsRect)return;const rect=layer.animationBoundsRect;const abq=tr.b.math.Quad.fromRect(rect);abq.backgroundColor='rgba(164,191,48,0.5)';abq.borderColor='rgba(205,255,0,0.75)';abq.borderWidth=3.0;abq.stackingGroupId=layerQuad.stackingGroupId;abq.selectionToSetIfClicked=new cc.AnimationRectSelection(layer,rect);quads.push(abq);},appendInvalidationQuads_(quads,layer,layerQuad){if(layer.layerTreeImpl.hasSourceFrameBeenDrawnBefore)return;for(const rect of layer.invalidation.rects){const unitRect=rect.asUVRectInside(layer.bounds);const iq=layerQuad.projectUnitRect(unitRect);iq.backgroundColor='rgba(0, 255, 0, 0.1)';if(rect.reason==='appeared'){iq.backgroundColor='rgba(0, 255, 128, 0.1)';}
+iq.borderColor='rgba(0, 255, 0, 1)';iq.stackingGroupId=layerQuad.stackingGroupId;let message='Invalidation rect';if(rect.reason){message+=' ('+rect.reason+')';}
+if(rect.client){message+=' for '+rect.client;}
+iq.selectionToSetIfClicked=new cc.LayerRectSelection(layer,message,rect,rect);quads.push(iq);}},appendUnrecordedRegionQuads_(quads,layer,layerQuad){for(let ir=0;ir<layer.unrecordedRegion.rects.length;ir++){const rect=layer.unrecordedRegion.rects[ir];const unitRect=rect.asUVRectInside(layer.bounds);const iq=layerQuad.projectUnitRect(unitRect);iq.backgroundColor='rgba(240, 230, 140, 0.3)';iq.borderColor='rgba(240, 230, 140, 1)';iq.stackingGroupId=layerQuad.stackingGroupId;iq.selectionToSetIfClicked=new cc.LayerRectSelection(layer,'Unrecorded area',rect,rect);quads.push(iq);}},appendBottleneckQuads_(quads,layer,layerQuad,stackingGroupId){function processRegion(region,label,borderColor){const backgroundColor=borderColor.clone();backgroundColor.a=0.4*(borderColor.a||1.0);if(!region||!region.rects)return;for(let ir=0;ir<region.rects.length;ir++){const rect=region.rects[ir];const unitRect=rect.asUVRectInside(layer.bounds);const iq=layerQuad.projectUnitRect(unitRect);iq.backgroundColor=backgroundColor.toString();iq.borderColor=borderColor.toString();iq.borderWidth=4.0;iq.stackingGroupId=stackingGroupId;iq.selectionToSetIfClicked=new cc.LayerRectSelection(layer,label,rect,rect);quads.push(iq);}}
 processRegion(layer.touchEventHandlerRegion,'Touch listener',tr.b.Color.fromString('rgb(228, 226, 27)'));processRegion(layer.wheelEventHandlerRegion,'Wheel listener',tr.b.Color.fromString('rgb(176, 205, 29)'));processRegion(layer.nonFastScrollableRegion,'Repaints on scroll',tr.b.Color.fromString('rgb(213, 134, 32)'));},appendTileCoverageRectQuads_(quads,layer,layerQuad,heatmapType){if(!layer.tileCoverageRects)return;const tiles=[];for(let ct=0;ct<layer.tileCoverageRects.length;++ct){const tile=layer.tileCoverageRects[ct].tile;if(tile!==undefined)tiles.push(tile);}
 const lthi=this.layerTreeImpl_.layerTreeHostImpl;const minMax=this.getMinMaxForHeatmap_(lthi.activeTiles,heatmapType);const heatmapResult=this.computeHeatmapColors_(tiles,minMax,heatmapType);let heatIndex=0;for(let ct=0;ct<layer.tileCoverageRects.length;++ct){let rect=layer.tileCoverageRects[ct].geometryRect;rect=rect.scale(1.0/layer.geometryContentsScale);const tile=layer.tileCoverageRects[ct].tile;const unitRect=rect.asUVRectInside(layer.bounds);const quad=layerQuad.projectUnitRect(unitRect);quad.backgroundColor='rgba(0, 0, 0, 0)';quad.stackingGroupId=layerQuad.stackingGroupId;let type=tr.e.cc.tileTypes.missing;if(tile){type=tile.getTypeForLayer(layer);quad.backgroundColor=heatmapResult[heatIndex].color;++heatIndex;}
 quad.borderColor=tr.e.cc.tileBorder[type].color;quad.borderWidth=tr.e.cc.tileBorder[type].width;let label;if(tile){label='coverageRect';}else{label='checkerboard coverageRect';}
@@ -7442,14 +7516,15 @@
 highlights=selectionHighlightsByLayerId[layer.layerId];if(highlights){this.appendHighlightQuadsForLayer_(quads,layer,layerQuad,highlights);}}
 this.layerTreeImpl.iterLayers(function(layer,depth,isMask,isReplica){if(!this.showOtherLayers&&this.selectedLayer!==layer)return;if(alreadyVisitedLayerIds[layer.layerId])return;const layerQuad=layer.layerQuad;const stackingGroupId=nextStackingGroupId++;if(this.showBottlenecks){this.appendBottleneckQuads_(quads,layer,layerQuad,stackingGroupId);}},this);const tracedInputLatencies=this.layerTreeImpl.tracedInputLatencies;if(this.showInputEvents&&tracedInputLatencies){for(let i=0;i<tracedInputLatencies.length;i++){const coordinatesArray=tracedInputLatencies[i].args.data.coordinates;for(let j=0;j<coordinatesArray.length;j++){const inputQuad=tr.b.math.Quad.fromXYWH(coordinatesArray[j].x-25,coordinatesArray[j].y-25,50,50);inputQuad.borderColor='rgba(0, 0, 0, 0)';inputQuad.imageData=this.inputEventImageData_;quads.push(inputQuad);}}}
 return quads;},updateInfoBar_(infoBarMessages){if(infoBarMessages.length){this.infoBar_.removeAllButtons();this.infoBar_.message='Some problems were encountered...';this.infoBar_.addButton('More info...',function(e){const overlay=new tr.ui.b.Overlay();Polymer.dom(overlay).textContent='';infoBarMessages.forEach(function(message){const title=document.createElement('h3');Polymer.dom(title).textContent=message.header;const details=document.createElement('div');Polymer.dom(details).textContent=message.details;Polymer.dom(overlay).appendChild(title);Polymer.dom(overlay).appendChild(details);});overlay.visible=true;e.stopPropagation();return false;});this.infoBar_.visible=true;}else{this.infoBar_.removeAllButtons();this.infoBar_.message='';this.infoBar_.visible=false;}},getWhatRasterized_(){const lthi=this.layerTreeImpl_.layerTreeHostImpl;const renderProcess=lthi.objectInstance.parent;const tasks=[];for(const event of renderProcess.getDescendantEvents()){if(!(event instanceof tr.model.Slice))continue;const tile=tr.e.cc.getTileFromRasterTaskSlice(event);if(tile===undefined)continue;if(tile.containingSnapshot===lthi){tasks.push(event);}}
-return tasks;},updateWhatRasterizedLinkState_(){const tasks=this.getWhatRasterized_();if(tasks.length){Polymer.dom(this.whatRasterizedLink_).textContent=tasks.length+' raster tasks';this.whatRasterizedLink_.style.display='';}else{Polymer.dom(this.whatRasterizedLink_).textContent='';this.whatRasterizedLink_.style.display='none';}},onWhatRasterizedLinkClicked_(){const tasks=this.getWhatRasterized_();const event=new tr.model.RequestSelectionChangeEvent();event.selection=new tr.model.EventSet(tasks);this.dispatchEvent(event);}};return{LayerTreeQuadStackView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const constants=tr.e.cc.constants;const LayerView=tr.ui.b.define('tr-ui-e-chrome-cc-layer-view');LayerView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.style.flexDirection='column';this.style.display='flex';this.style.left=0;this.style.position='relative';this.style.top=0;this.layerTreeQuadStackView_=new tr.ui.e.chrome.cc.LayerTreeQuadStackView();this.dragBar_=document.createElement('tr-ui-b-drag-handle');this.analysisEl_=document.createElement('tr-ui-e-chrome-cc-layer-view-analysis');this.analysisEl_.style.height='150px';this.analysisEl_.style.overflowY='auto';this.analysisEl_.addEventListener('requestSelectionChange',this.onRequestSelectionChangeFromAnalysisEl_.bind(this));this.dragBar_.target=this.analysisEl_;Polymer.dom(this).appendChild(this.layerTreeQuadStackView_);Polymer.dom(this).appendChild(this.dragBar_);Polymer.dom(this).appendChild(this.analysisEl_);this.layerTreeQuadStackView_.addEventListener('selection-change',function(){this.layerTreeQuadStackViewSelectionChanged_();}.bind(this));this.layerTreeQuadStackViewSelectionChanged_();},get layerTreeImpl(){return this.layerTreeQuadStackView_.layerTreeImpl;},set layerTreeImpl(newValue){return this.layerTreeQuadStackView_.layerTreeImpl=newValue;},set isRenderPassQuads(newValue){return this.layerTreeQuadStackView_.isRenderPassQuads=newValue;},get selection(){return this.layerTreeQuadStackView_.selection;},set selection(newValue){this.layerTreeQuadStackView_.selection=newValue;},regenerateContent(){this.layerTreeQuadStackView_.regenerateContent();},layerTreeQuadStackViewSelectionChanged_(){const selection=this.layerTreeQuadStackView_.selection;if(selection){this.dragBar_.style.display='';this.analysisEl_.style.display='';Polymer.dom(this.analysisEl_).textContent='';const layer=selection.layer;if(layer&&layer.args&&layer.args.pictures){Polymer.dom(this.analysisEl_).appendChild(this.createPictureBtn_(layer.args.pictures));}
+return tasks;},updateWhatRasterizedLinkState_(){const tasks=this.getWhatRasterized_();if(tasks.length){Polymer.dom(this.whatRasterizedLink_).textContent=tasks.length+' raster tasks';this.whatRasterizedLink_.style.display='';}else{Polymer.dom(this.whatRasterizedLink_).textContent='';this.whatRasterizedLink_.style.display='none';}},getWhatRasterizedEventSet_(){return new tr.model.EventSet(this.getWhatRasterized_());}};return{LayerTreeQuadStackView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const constants=tr.e.cc.constants;const LayerView=tr.ui.b.define('tr-ui-e-chrome-cc-layer-view');LayerView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.style.flexDirection='column';this.style.display='flex';this.layerTreeQuadStackView_=new tr.ui.e.chrome.cc.LayerTreeQuadStackView();this.dragBar_=document.createElement('tr-ui-b-drag-handle');this.analysisEl_=document.createElement('tr-ui-e-chrome-cc-layer-view-analysis');this.analysisEl_.style.flexGrow=0;this.analysisEl_.style.flexShrink=0;this.analysisEl_.style.flexBasis='auto';this.analysisEl_.style.height='150px';this.analysisEl_.style.overflow='auto';this.analysisEl_.addEventListener('requestSelectionChange',this.onRequestSelectionChangeFromAnalysisEl_.bind(this));this.dragBar_.target=this.analysisEl_;Polymer.dom(this).appendChild(this.layerTreeQuadStackView_);Polymer.dom(this).appendChild(this.dragBar_);Polymer.dom(this).appendChild(this.analysisEl_);this.layerTreeQuadStackView_.addEventListener('selection-change',function(){this.layerTreeQuadStackViewSelectionChanged_();}.bind(this));this.layerTreeQuadStackViewSelectionChanged_();},get layerTreeImpl(){return this.layerTreeQuadStackView_.layerTreeImpl;},set layerTreeImpl(newValue){return this.layerTreeQuadStackView_.layerTreeImpl=newValue;},set isRenderPassQuads(newValue){return this.layerTreeQuadStackView_.isRenderPassQuads=newValue;},get selection(){return this.layerTreeQuadStackView_.selection;},set selection(newValue){this.layerTreeQuadStackView_.selection=newValue;},regenerateContent(){this.layerTreeQuadStackView_.regenerateContent();},layerTreeQuadStackViewSelectionChanged_(){const selection=this.layerTreeQuadStackView_.selection;if(selection){this.dragBar_.style.display='';this.analysisEl_.style.display='';Polymer.dom(this.analysisEl_).textContent='';const layer=selection.layer;if(tr.e.cc.PictureSnapshot.CanDebugPicture()&&layer&&layer.args&&layer.args.pictures&&layer.args.pictures.length){Polymer.dom(this.analysisEl_).appendChild(this.createPictureBtn_(layer.args.pictures));}
 const analysis=selection.createAnalysis();Polymer.dom(this.analysisEl_).appendChild(analysis);for(const child of this.analysisEl_.children){child.style.userSelect='text';}}else{this.dragBar_.style.display='none';this.analysisEl_.style.display='none';const analysis=Polymer.dom(this.analysisEl_).firstChild;if(analysis){Polymer.dom(this.analysisEl_).removeChild(analysis);}
 this.layerTreeQuadStackView_.style.height=window.getComputedStyle(this).height;}
 tr.b.dispatchSimpleEvent(this,'selection-change');},createPictureBtn_(pictures){if(!(pictures instanceof Array)){pictures=[pictures];}
 const link=document.createElement('tr-ui-a-analysis-link');link.selection=function(){const layeredPicture=new tr.e.cc.LayeredPicture(pictures);const snapshot=new tr.e.cc.PictureSnapshot(layeredPicture);snapshot.picture=layeredPicture;const selection=new tr.model.EventSet();selection.push(snapshot);return selection;};Polymer.dom(link).textContent='View in Picture Debugger';return link;},onRequestSelectionChangeFromAnalysisEl_(e){if(!(e.selection instanceof tr.ui.e.chrome.cc.Selection)){return;}
-e.stopPropagation();this.selection=e.selection;},get extraHighlightsByLayerId(){return this.layerTreeQuadStackView_.extraHighlightsByLayerId;},set extraHighlightsByLayerId(extraHighlightsByLayerId){this.layerTreeQuadStackView_.extraHighlightsByLayerId=extraHighlightsByLayerId;}};return{LayerView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const LayerTreeHostImplSnapshotView=tr.ui.b.define('tr-ui-e-chrome-cc-layer-tree-host-impl-snapshot-view',tr.ui.analysis.ObjectSnapshotView);LayerTreeHostImplSnapshotView.prototype={__proto__:tr.ui.analysis.ObjectSnapshotView.prototype,decorate(){Polymer.dom(this).classList.add('tr-ui-e-chrome-cc-lthi-s-view');this.style.display='flex';this.style.flexDirection='row';this.style.flexGrow=1;this.style.flexShrink=1;this.style.flexBasis='auto';this.selection_=undefined;this.layerPicker_=new tr.ui.e.chrome.cc.LayerPicker();this.layerPicker_.style.flexGrow=1;this.layerPicker_.style.flexShrink=1;this.layerPicker_.style.flexBasis='auto';this.layerPicker_.addEventListener('selection-change',this.onLayerPickerSelectionChanged_.bind(this));this.layerView_=new tr.ui.e.chrome.cc.LayerView();this.layerView_.addEventListener('selection-change',this.onLayerViewSelectionChanged_.bind(this));this.dragHandle_=document.createElement('tr-ui-b-drag-handle');this.dragHandle_.style.flexGrow=0;this.dragHandle_.style.flexShrink=0;this.dragHandle_.style.flexBasis='auto';this.dragHandle_.horizontal=false;this.dragHandle_.target=this.layerView_;Polymer.dom(this).appendChild(this.layerPicker_);Polymer.dom(this).appendChild(this.dragHandle_);Polymer.dom(this).appendChild(this.layerView_);this.onLayerViewSelectionChanged_();this.onLayerPickerSelectionChanged_();},get objectSnapshot(){return this.objectSnapshot_;},set objectSnapshot(objectSnapshot){this.objectSnapshot_=objectSnapshot;const lthi=this.objectSnapshot;let layerTreeImpl;if(lthi){layerTreeImpl=lthi.getTree(this.layerPicker_.whichTree);}
-this.layerPicker_.lthiSnapshot=lthi;this.layerView_.layerTreeImpl=layerTreeImpl;this.layerView_.regenerateContent();if(!this.selection_)return;this.selection=this.selection_.findEquivalent(lthi);},get selection(){return this.selection_;},set selection(selection){if(this.selection_===selection)return;this.selection_=selection;this.layerPicker_.selection=selection;this.layerView_.selection=selection;tr.b.dispatchSimpleEvent(this,'cc-selection-change');},onLayerPickerSelectionChanged_(){this.selection_=this.layerPicker_.selection;this.layerView_.selection=this.selection;this.layerView_.layerTreeImpl=this.layerPicker_.layerTreeImpl;this.layerView_.isRenderPassQuads=this.layerPicker_.isRenderPassQuads;this.layerView_.regenerateContent();tr.b.dispatchSimpleEvent(this,'cc-selection-change');},onLayerViewSelectionChanged_(){this.selection_=this.layerView_.selection;this.layerPicker_.selection=this.selection;tr.b.dispatchSimpleEvent(this,'cc-selection-change');},get extraHighlightsByLayerId(){return this.layerView_.extraHighlightsByLayerId;},set extraHighlightsByLayerId(extraHighlightsByLayerId){this.layerView_.extraHighlightsByLayerId=extraHighlightsByLayerId;}};tr.ui.analysis.ObjectSnapshotView.register(LayerTreeHostImplSnapshotView,{typeName:'cc::LayerTreeHostImpl'});return{LayerTreeHostImplSnapshotView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const OPS_TIMING_ITERATIONS=3;const CHART_PADDING_LEFT=65;const CHART_PADDING_RIGHT=40;const AXIS_PADDING_LEFT=60;const AXIS_PADDING_RIGHT=35;const AXIS_PADDING_TOP=25;const AXIS_PADDING_BOTTOM=45;const AXIS_LABEL_PADDING=5;const AXIS_TICK_SIZE=10;const LABEL_PADDING=5;const LABEL_INTERLEAVE_OFFSET=15;const BAR_PADDING=5;const VERTICAL_TICKS=5;const HUE_CHAR_CODE_ADJUSTMENT=5.7;const PictureOpsChartSummaryView=tr.ui.b.define('tr-ui-e-chrome-cc-picture-ops-chart-summary-view');PictureOpsChartSummaryView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.style.flexGrow=0;this.style.flexShrink=0;this.style.flexBasis='auto';this.style.fontSize=0;this.style.margin=0;this.style.minHeight='200px';this.style.minWidth='200px';this.style.overflow='hidden';this.style.padding=0;this.picture_=undefined;this.pictureDataProcessed_=false;this.chartScale_=window.devicePixelRatio;this.chart_=document.createElement('canvas');this.chartCtx_=this.chart_.getContext('2d');Polymer.dom(this).appendChild(this.chart_);this.opsTimingData_=[];this.chartWidth_=0;this.chartHeight_=0;this.requiresRedraw_=true;this.currentBarMouseOverTarget_=null;this.chart_.addEventListener('mousemove',this.onMouseMove_.bind(this));},get requiresRedraw(){return this.requiresRedraw_;},set requiresRedraw(requiresRedraw){this.requiresRedraw_=requiresRedraw;},get picture(){return this.picture_;},set picture(picture){this.picture_=picture;this.pictureDataProcessed_=false;if(Polymer.dom(this).classList.contains('hidden'))return;this.processPictureData_();this.requiresRedraw=true;this.updateChartContents();},hide(){Polymer.dom(this).classList.add('hidden');this.style.display='none';},show(){Polymer.dom(this).classList.remove('hidden');this.style.display='';if(this.pictureDataProcessed_)return;this.processPictureData_();this.requiresRedraw=true;this.updateChartContents();},onMouseMove_(e){const lastBarMouseOverTarget=this.currentBarMouseOverTarget_;this.currentBarMouseOverTarget_=null;const x=e.offsetX;const y=e.offsetY;const chartLeft=CHART_PADDING_LEFT;const chartRight=this.chartWidth_-CHART_PADDING_RIGHT;const chartTop=AXIS_PADDING_TOP;const chartBottom=this.chartHeight_-AXIS_PADDING_BOTTOM;const chartInnerWidth=chartRight-chartLeft;if(x>chartLeft&&x<chartRight&&y>chartTop&&y<chartBottom){this.currentBarMouseOverTarget_=Math.floor((x-chartLeft)/chartInnerWidth*this.opsTimingData_.length);this.currentBarMouseOverTarget_=tr.b.math.clamp(this.currentBarMouseOverTarget_,0,this.opsTimingData_.length-1);}
-if(this.currentBarMouseOverTarget_===lastBarMouseOverTarget)return;this.drawChartContents_();},updateChartContents(){if(this.requiresRedraw){this.updateChartDimensions_();}
+e.stopPropagation();this.selection=e.selection;},get extraHighlightsByLayerId(){return this.layerTreeQuadStackView_.extraHighlightsByLayerId;},set extraHighlightsByLayerId(extraHighlightsByLayerId){this.layerTreeQuadStackView_.extraHighlightsByLayerId=extraHighlightsByLayerId;}};return{LayerView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const LayerTreeHostImplSnapshotView=tr.ui.b.define('tr-ui-e-chrome-cc-layer-tree-host-impl-snapshot-view',tr.ui.analysis.ObjectSnapshotView);LayerTreeHostImplSnapshotView.prototype={__proto__:tr.ui.analysis.ObjectSnapshotView.prototype,decorate(){Polymer.dom(this).classList.add('tr-ui-e-chrome-cc-lthi-s-view');this.style.display='flex';this.style.flexDirection='row';this.style.flexGrow=1;this.style.flexShrink=1;this.style.flexBasis='auto';this.style.minWidth=0;this.selection_=undefined;this.layerPicker_=new tr.ui.e.chrome.cc.LayerPicker();this.layerPicker_.style.flexGrow=0;this.layerPicker_.style.flexShrink=0;this.layerPicker_.style.flexBasis='auto';this.layerPicker_.style.minWidth='200px';this.layerPicker_.addEventListener('selection-change',this.onLayerPickerSelectionChanged_.bind(this));this.layerView_=new tr.ui.e.chrome.cc.LayerView();this.layerView_.addEventListener('selection-change',this.onLayerViewSelectionChanged_.bind(this));this.layerView_.style.flexGrow=1;this.layerView_.style.flexShrink=1;this.layerView_.style.flexBasis='auto';this.layerView_.style.minWidth=0;this.dragHandle_=document.createElement('tr-ui-b-drag-handle');this.dragHandle_.style.flexGrow=0;this.dragHandle_.style.flexShrink=0;this.dragHandle_.style.flexBasis='auto';this.dragHandle_.horizontal=false;this.dragHandle_.target=this.layerPicker_;Polymer.dom(this).appendChild(this.layerPicker_);Polymer.dom(this).appendChild(this.dragHandle_);Polymer.dom(this).appendChild(this.layerView_);this.onLayerViewSelectionChanged_();this.onLayerPickerSelectionChanged_();},get objectSnapshot(){return this.objectSnapshot_;},set objectSnapshot(objectSnapshot){this.objectSnapshot_=objectSnapshot;const lthi=this.objectSnapshot;let layerTreeImpl;if(lthi){layerTreeImpl=lthi.getTree(this.layerPicker_.whichTree);}
+this.layerPicker_.lthiSnapshot=lthi;this.layerView_.layerTreeImpl=layerTreeImpl;this.layerView_.regenerateContent();if(!this.selection_)return;this.selection=this.selection_.findEquivalent(lthi);},get selection(){return this.selection_;},set selection(selection){if(this.selection_===selection)return;this.selection_=selection;this.layerPicker_.selection=selection;this.layerView_.selection=selection;tr.b.dispatchSimpleEvent(this,'cc-selection-change');},onLayerPickerSelectionChanged_(){this.selection_=this.layerPicker_.selection;this.layerView_.selection=this.selection;this.layerView_.layerTreeImpl=this.layerPicker_.layerTreeImpl;this.layerView_.isRenderPassQuads=this.layerPicker_.isRenderPassQuads;this.layerView_.regenerateContent();tr.b.dispatchSimpleEvent(this,'cc-selection-change');},onLayerViewSelectionChanged_(){this.selection_=this.layerView_.selection;this.layerPicker_.selection=this.selection;tr.b.dispatchSimpleEvent(this,'cc-selection-change');},get extraHighlightsByLayerId(){return this.layerView_.extraHighlightsByLayerId;},set extraHighlightsByLayerId(extraHighlightsByLayerId){this.layerView_.extraHighlightsByLayerId=extraHighlightsByLayerId;}};tr.ui.analysis.ObjectSnapshotView.register(LayerTreeHostImplSnapshotView,{typeName:'cc::LayerTreeHostImpl'});return{LayerTreeHostImplSnapshotView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const OPS_TIMING_ITERATIONS=3;const CHART_PADDING_LEFT=65;const CHART_PADDING_RIGHT=40;const AXIS_PADDING_LEFT=60;const AXIS_PADDING_RIGHT=35;const AXIS_PADDING_TOP=25;const AXIS_PADDING_BOTTOM=45;const AXIS_LABEL_PADDING=5;const AXIS_TICK_SIZE=10;const LABEL_PADDING=5;const LABEL_INTERLEAVE_OFFSET=15;const BAR_PADDING=5;const VERTICAL_TICKS=5;const HUE_CHAR_CODE_ADJUSTMENT=5.7;const PictureOpsChartSummaryView=tr.ui.b.define('tr-ui-e-chrome-cc-picture-ops-chart-summary-view');PictureOpsChartSummaryView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.style.flexGrow=0;this.style.flexShrink=0;this.style.flexBasis='auto';this.style.fontSize=0;this.style.margin=0;this.style.minHeight='200px';this.style.minWidth='200px';this.style.overflow='hidden';this.style.padding=0;this.picture_=undefined;this.pictureDataProcessed_=false;this.chartScale_=window.devicePixelRatio;this.chart_=document.createElement('canvas');this.chartCtx_=this.chart_.getContext('2d');Polymer.dom(this).appendChild(this.chart_);this.opsTimingData_=[];this.chartWidth_=0;this.chartHeight_=0;this.requiresRedraw_=true;this.currentBarMouseOverTarget_=null;this.chart_.addEventListener('mousemove',this.onMouseMove_.bind(this));new ResizeObserver(this.onResize_.bind(this)).observe(this);},get requiresRedraw(){return this.requiresRedraw_;},set requiresRedraw(requiresRedraw){this.requiresRedraw_=requiresRedraw;},get picture(){return this.picture_;},set picture(picture){this.picture_=picture;this.pictureDataProcessed_=false;if(Polymer.dom(this).classList.contains('hidden'))return;this.processPictureData_();this.requiresRedraw=true;this.updateChartContents();},hide(){Polymer.dom(this).classList.add('hidden');this.style.display='none';},show(){Polymer.dom(this).classList.remove('hidden');this.style.display='';if(!this.pictureDataProcessed_){this.processPictureData_();}
+this.requiresRedraw=true;this.updateChartContents();},onMouseMove_(e){const lastBarMouseOverTarget=this.currentBarMouseOverTarget_;this.currentBarMouseOverTarget_=null;const x=e.offsetX;const y=e.offsetY;const chartLeft=CHART_PADDING_LEFT;const chartRight=this.chartWidth_-CHART_PADDING_RIGHT;const chartTop=AXIS_PADDING_TOP;const chartBottom=this.chartHeight_-AXIS_PADDING_BOTTOM;const chartInnerWidth=chartRight-chartLeft;if(x>chartLeft&&x<chartRight&&y>chartTop&&y<chartBottom){this.currentBarMouseOverTarget_=Math.floor((x-chartLeft)/chartInnerWidth*this.opsTimingData_.length);this.currentBarMouseOverTarget_=tr.b.math.clamp(this.currentBarMouseOverTarget_,0,this.opsTimingData_.length-1);}
+if(this.currentBarMouseOverTarget_===lastBarMouseOverTarget)return;this.drawChartContents_();},onResize_(){this.requiresRedraw=true;this.updateChartContents();},updateChartContents(){if(this.requiresRedraw){this.updateChartDimensions_();}
 this.drawChartContents_();},updateChartDimensions_(){this.chartWidth_=this.offsetWidth;this.chartHeight_=this.offsetHeight;this.chart_.width=this.chartWidth_*this.chartScale_;this.chart_.height=this.chartHeight_*this.chartScale_;this.chart_.style.width=this.chartWidth_+'px';this.chart_.style.height=this.chartHeight_+'px';this.chartCtx_.scale(this.chartScale_,this.chartScale_);},processPictureData_(){this.resetOpsTimingData_();this.pictureDataProcessed_=true;if(!this.picture_)return;let ops=this.picture_.getOps();if(!ops)return;ops=this.picture_.tagOpsWithTimings(ops);if(ops[0].cmd_time===undefined)return;this.collapseOpsToTimingBuckets_(ops);},drawChartContents_(){this.clearChartContents_();if(this.opsTimingData_.length===0){this.showNoTimingDataMessage_();return;}
 this.drawChartAxes_();this.drawBars_();this.drawLineAtBottomOfChart_();if(this.currentBarMouseOverTarget_===null)return;this.drawTooltip_();},drawLineAtBottomOfChart_(){this.chartCtx_.strokeStyle='#AAA';this.chartCtx_.moveTo(0,this.chartHeight_-0.5);this.chartCtx_.lineTo(this.chartWidth_,this.chartHeight_-0.5);this.chartCtx_.stroke();},drawTooltip_(){const tooltipData=this.opsTimingData_[this.currentBarMouseOverTarget_];const tooltipTitle=tooltipData.cmd_string;const tooltipTime=tooltipData.cmd_time.toFixed(4);const tooltipWidth=110;const tooltipHeight=40;const chartInnerWidth=this.chartWidth_-CHART_PADDING_RIGHT-
 CHART_PADDING_LEFT;const barWidth=chartInnerWidth/this.opsTimingData_.length;const tooltipOffset=Math.round((tooltipWidth-barWidth)*0.5);const left=CHART_PADDING_LEFT+this.currentBarMouseOverTarget_*barWidth-tooltipOffset;const top=Math.round((this.chartHeight_-tooltipHeight)*0.5);this.chartCtx_.save();this.chartCtx_.shadowOffsetX=0;this.chartCtx_.shadowOffsetY=5;this.chartCtx_.shadowBlur=4;this.chartCtx_.shadowColor='rgba(0,0,0,0.4)';this.chartCtx_.strokeStyle='#888';this.chartCtx_.fillStyle='#EEE';this.chartCtx_.fillRect(left,top,tooltipWidth,tooltipHeight);this.chartCtx_.shadowColor='transparent';this.chartCtx_.translate(0.5,0.5);this.chartCtx_.strokeRect(left,top,tooltipWidth,tooltipHeight);this.chartCtx_.restore();this.chartCtx_.fillStyle='#222';this.chartCtx_.textBaseline='top';this.chartCtx_.font='800 12px Arial';this.chartCtx_.fillText(tooltipTitle,left+8,top+8);this.chartCtx_.fillStyle='#555';this.chartCtx_.textBaseline='top';this.chartCtx_.font='400 italic 10px Arial';this.chartCtx_.fillText('Total: '+tooltipTime+'ms',left+8,top+22);},drawBars_(){const len=this.opsTimingData_.length;const max=this.opsTimingData_[0].cmd_time;const min=this.opsTimingData_[len-1].cmd_time;const width=this.chartWidth_-CHART_PADDING_LEFT-CHART_PADDING_RIGHT;const height=this.chartHeight_-AXIS_PADDING_TOP-AXIS_PADDING_BOTTOM;const barWidth=Math.floor(width/len);let opData;let opTiming;let opHeight;let opLabel;let barLeft;for(let b=0;b<len;b++){opData=this.opsTimingData_[b];opTiming=opData.cmd_time/max;opHeight=Math.round(Math.max(1,opTiming*height));opLabel=opData.cmd_string;barLeft=CHART_PADDING_LEFT+b*barWidth;this.chartCtx_.fillStyle=this.getOpColor_(opLabel);this.chartCtx_.fillRect(barLeft+BAR_PADDING,AXIS_PADDING_TOP+
@@ -7458,11 +7533,11 @@
 this.chartCtx_.stroke();this.chartCtx_.restore();this.chartCtx_.save();this.chartCtx_.translate(CHART_PADDING_LEFT+Math.round(barWidth*0.5),AXIS_PADDING_TOP+height+LABEL_PADDING);this.chartCtx_.font='10px Arial';this.chartCtx_.textAlign='center';this.chartCtx_.textBaseline='top';let labelTickLeft;let labelTickBottom;for(let l=0;l<len;l++){labelTickLeft=Math.round(l*barWidth);labelTickBottom=l%2*LABEL_INTERLEAVE_OFFSET;this.chartCtx_.save();this.chartCtx_.moveTo(labelTickLeft,-LABEL_PADDING);this.chartCtx_.lineTo(labelTickLeft,labelTickBottom);this.chartCtx_.stroke();this.chartCtx_.restore();this.chartCtx_.fillText(this.opsTimingData_[l].cmd_string,labelTickLeft,labelTickBottom);}
 this.chartCtx_.restore();this.chartCtx_.restore();},clearChartContents_(){this.chartCtx_.clearRect(0,0,this.chartWidth_,this.chartHeight_);},showNoTimingDataMessage_(){this.chartCtx_.font='800 italic 14px Arial';this.chartCtx_.fillStyle='#333';this.chartCtx_.textAlign='center';this.chartCtx_.textBaseline='middle';this.chartCtx_.fillText('No timing data available.',this.chartWidth_*0.5,this.chartHeight_*0.5);},collapseOpsToTimingBuckets_(ops){const opsTimingDataIndexHash_={};const timingData=this.opsTimingData_;let op;let opIndex;for(let i=0;i<ops.length;i++){op=ops[i];if(op.cmd_time===undefined)continue;opIndex=opsTimingDataIndexHash_[op.cmd_string]||null;if(opIndex===null){timingData.push({cmd_time:0,cmd_string:op.cmd_string});opIndex=timingData.length-1;opsTimingDataIndexHash_[op.cmd_string]=opIndex;}
 timingData[opIndex].cmd_time+=op.cmd_time;}
-timingData.sort(this.sortTimingBucketsByOpTimeDescending_);this.collapseTimingBucketsToOther_(4);},collapseTimingBucketsToOther_(count){const timingData=this.opsTimingData_;const otherSource=timingData.splice(count,timingData.length-count);let otherDestination=null;if(!otherSource.length)return;timingData.push({cmd_time:0,cmd_string:'Other'});otherDestination=timingData[timingData.length-1];for(let i=0;i<otherSource.length;i++){otherDestination.cmd_time+=otherSource[i].cmd_time;}},sortTimingBucketsByOpTimeDescending_(a,b){return b.cmd_time-a.cmd_time;},resetOpsTimingData_(){this.opsTimingData_.length=0;}};return{PictureOpsChartSummaryView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const BAR_PADDING=1;const BAR_WIDTH=5;const CHART_PADDING_LEFT=65;const CHART_PADDING_RIGHT=30;const CHART_PADDING_BOTTOM=35;const CHART_PADDING_TOP=20;const AXIS_PADDING_LEFT=55;const AXIS_PADDING_RIGHT=30;const AXIS_PADDING_BOTTOM=35;const AXIS_PADDING_TOP=20;const AXIS_TICK_SIZE=5;const AXIS_LABEL_PADDING=5;const VERTICAL_TICKS=5;const HUE_CHAR_CODE_ADJUSTMENT=5.7;const PictureOpsChartView=tr.ui.b.define('tr-ui-e-chrome-cc-picture-ops-chart-view');PictureOpsChartView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.style.display='block';this.style.height='180px';this.style.margin=0;this.style.padding=0;this.style.position='relative';this.picture_=undefined;this.pictureOps_=undefined;this.opCosts_=undefined;this.chartScale_=window.devicePixelRatio;this.chart_=document.createElement('canvas');this.chartCtx_=this.chart_.getContext('2d');Polymer.dom(this).appendChild(this.chart_);this.selectedOpIndex_=undefined;this.chartWidth_=0;this.chartHeight_=0;this.dimensionsHaveChanged_=true;this.currentBarMouseOverTarget_=undefined;this.ninetyFifthPercentileCost_=0;this.totalOpCost_=0;this.chart_.addEventListener('click',this.onClick_.bind(this));this.chart_.addEventListener('mousemove',this.onMouseMove_.bind(this));this.usePercentileScale_=false;this.usePercentileScaleCheckbox_=tr.ui.b.createCheckBox(this,'usePercentileScale','PictureOpsChartView.usePercentileScale',false,'Limit to 95%-ile');Polymer.dom(this.usePercentileScaleCheckbox_).classList.add('use-percentile-scale');this.usePercentileScaleCheckbox_.style.position='absolute';this.usePercentileScaleCheckbox_.style.left=0;this.usePercentileScaleCheckbox_.style.top=0;Polymer.dom(this).appendChild(this.usePercentileScaleCheckbox_);},get dimensionsHaveChanged(){return this.dimensionsHaveChanged_;},set dimensionsHaveChanged(dimensionsHaveChanged){this.dimensionsHaveChanged_=dimensionsHaveChanged;},get usePercentileScale(){return this.usePercentileScale_;},set usePercentileScale(usePercentileScale){this.usePercentileScale_=usePercentileScale;this.drawChartContents_();},get numOps(){return this.opCosts_.length;},get selectedOpIndex(){return this.selectedOpIndex_;},set selectedOpIndex(selectedOpIndex){if(selectedOpIndex<0)throw new Error('Invalid index');if(selectedOpIndex>=this.numOps)throw new Error('Invalid index');this.selectedOpIndex_=selectedOpIndex;},get picture(){return this.picture_;},set picture(picture){this.picture_=picture;this.pictureOps_=picture.tagOpsWithTimings(picture.getOps());this.currentBarMouseOverTarget_=undefined;this.processPictureData_();this.dimensionsHaveChanged=true;},processPictureData_(){if(this.pictureOps_===undefined)return;let totalOpCost=0;this.opCosts_=this.pictureOps_.map(function(op){totalOpCost+=op.cmd_time;return op.cmd_time;});this.opCosts_.sort();const ninetyFifthPercentileCostIndex=Math.floor(this.opCosts_.length*0.95);this.ninetyFifthPercentileCost_=this.opCosts_[ninetyFifthPercentileCostIndex];this.maxCost_=this.opCosts_[this.opCosts_.length-1];this.totalOpCost_=totalOpCost;},extractBarIndex_(e){let index=undefined;if(this.pictureOps_===undefined||this.pictureOps_.length===0){return index;}
+timingData.sort(this.sortTimingBucketsByOpTimeDescending_);this.collapseTimingBucketsToOther_(4);},collapseTimingBucketsToOther_(count){const timingData=this.opsTimingData_;const otherSource=timingData.splice(count,timingData.length-count);let otherDestination=null;if(!otherSource.length)return;timingData.push({cmd_time:0,cmd_string:'Other'});otherDestination=timingData[timingData.length-1];for(let i=0;i<otherSource.length;i++){otherDestination.cmd_time+=otherSource[i].cmd_time;}},sortTimingBucketsByOpTimeDescending_(a,b){return b.cmd_time-a.cmd_time;},resetOpsTimingData_(){this.opsTimingData_.length=0;}};return{PictureOpsChartSummaryView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const BAR_PADDING=1;const BAR_WIDTH=5;const CHART_PADDING_LEFT=65;const CHART_PADDING_RIGHT=30;const CHART_PADDING_BOTTOM=35;const CHART_PADDING_TOP=20;const AXIS_PADDING_LEFT=55;const AXIS_PADDING_RIGHT=30;const AXIS_PADDING_BOTTOM=35;const AXIS_PADDING_TOP=20;const AXIS_TICK_SIZE=5;const AXIS_LABEL_PADDING=5;const VERTICAL_TICKS=5;const HUE_CHAR_CODE_ADJUSTMENT=5.7;const PictureOpsChartView=tr.ui.b.define('tr-ui-e-chrome-cc-picture-ops-chart-view');PictureOpsChartView.prototype={__proto__:HTMLDivElement.prototype,decorate(){this.style.display='block';this.style.height='180px';this.style.margin=0;this.style.padding=0;this.style.position='relative';this.picture_=undefined;this.pictureOps_=undefined;this.opCosts_=undefined;this.chartScale_=window.devicePixelRatio;this.chart_=document.createElement('canvas');this.chartCtx_=this.chart_.getContext('2d');Polymer.dom(this).appendChild(this.chart_);this.selectedOpIndex_=undefined;this.chartWidth_=0;this.chartHeight_=0;this.dimensionsHaveChanged_=true;this.currentBarMouseOverTarget_=undefined;this.ninetyFifthPercentileCost_=0;this.totalOpCost_=0;this.chart_.addEventListener('click',this.onClick_.bind(this));this.chart_.addEventListener('mousemove',this.onMouseMove_.bind(this));new ResizeObserver(this.onResize_.bind(this)).observe(this);this.usePercentileScale_=false;this.usePercentileScaleCheckbox_=tr.ui.b.createCheckBox(this,'usePercentileScale','PictureOpsChartView.usePercentileScale',false,'Limit to 95%-ile');Polymer.dom(this.usePercentileScaleCheckbox_).classList.add('use-percentile-scale');this.usePercentileScaleCheckbox_.style.position='absolute';this.usePercentileScaleCheckbox_.style.left=0;this.usePercentileScaleCheckbox_.style.top=0;Polymer.dom(this).appendChild(this.usePercentileScaleCheckbox_);},get dimensionsHaveChanged(){return this.dimensionsHaveChanged_;},set dimensionsHaveChanged(dimensionsHaveChanged){this.dimensionsHaveChanged_=dimensionsHaveChanged;},get usePercentileScale(){return this.usePercentileScale_;},set usePercentileScale(usePercentileScale){this.usePercentileScale_=usePercentileScale;this.drawChartContents_();},get numOps(){return this.opCosts_.length;},get selectedOpIndex(){return this.selectedOpIndex_;},set selectedOpIndex(selectedOpIndex){if(selectedOpIndex<0)throw new Error('Invalid index');if(selectedOpIndex>=this.numOps)throw new Error('Invalid index');this.selectedOpIndex_=selectedOpIndex;},get picture(){return this.picture_;},set picture(picture){this.picture_=picture;this.pictureOps_=picture.tagOpsWithTimings(picture.getOps());this.currentBarMouseOverTarget_=undefined;this.processPictureData_();this.dimensionsHaveChanged=true;},processPictureData_(){if(this.pictureOps_===undefined)return;let totalOpCost=0;this.opCosts_=this.pictureOps_.map(function(op){totalOpCost+=op.cmd_time;return op.cmd_time;});this.opCosts_.sort();const ninetyFifthPercentileCostIndex=Math.floor(this.opCosts_.length*0.95);this.ninetyFifthPercentileCost_=this.opCosts_[ninetyFifthPercentileCostIndex];this.maxCost_=this.opCosts_[this.opCosts_.length-1];this.totalOpCost_=totalOpCost;},extractBarIndex_(e){let index=undefined;if(this.pictureOps_===undefined||this.pictureOps_.length===0){return index;}
 const x=e.offsetX;const y=e.offsetY;const totalBarWidth=(BAR_WIDTH+BAR_PADDING)*this.pictureOps_.length;const chartLeft=CHART_PADDING_LEFT;const chartTop=0;const chartBottom=this.chartHeight_-CHART_PADDING_BOTTOM;const chartRight=chartLeft+totalBarWidth;if(x<chartLeft||x>chartRight||y<chartTop||y>chartBottom){return index;}
 index=Math.floor((x-chartLeft)/totalBarWidth*this.pictureOps_.length);index=tr.b.math.clamp(index,0,this.pictureOps_.length-1);return index;},onClick_(e){const barClicked=this.extractBarIndex_(e);if(barClicked===undefined)return;if(barClicked===this.selectedOpIndex){this.selectedOpIndex=undefined;}else{this.selectedOpIndex=barClicked;}
 e.preventDefault();tr.b.dispatchSimpleEvent(this,'selection-changed',false);},onMouseMove_(e){const lastBarMouseOverTarget=this.currentBarMouseOverTarget_;this.currentBarMouseOverTarget_=this.extractBarIndex_(e);if(this.currentBarMouseOverTarget_===lastBarMouseOverTarget){return;}
-this.drawChartContents_();},scrollSelectedItemIntoViewIfNecessary(){if(this.selectedOpIndex===undefined){return;}
+this.drawChartContents_();},onResize_(){this.dimensionsHaveChanged=true;this.updateChartContents();},scrollSelectedItemIntoViewIfNecessary(){if(this.selectedOpIndex===undefined){return;}
 const width=this.offsetWidth;const left=this.scrollLeft;const right=left+width;const targetLeft=CHART_PADDING_LEFT+
 (BAR_WIDTH+BAR_PADDING)*this.selectedOpIndex;if(targetLeft>left&&targetLeft<right){return;}
 this.scrollLeft=(targetLeft-width*0.5);},updateChartContents(){if(this.dimensionsHaveChanged){this.updateChartDimensions_();}
@@ -7479,7 +7554,7 @@
 toolTipTimePercentage+'%)',left+8,top+22);},drawBars_(){let op;let opColor=0;let opHeight=0;const opWidth=BAR_WIDTH+BAR_PADDING;let opHover=false;const bottom=this.chartHeight_-CHART_PADDING_BOTTOM;const maxHeight=this.chartHeight_-CHART_PADDING_BOTTOM-
 CHART_PADDING_TOP;let maxValue;if(this.usePercentileScale){maxValue=this.ninetyFifthPercentileCost_;}else{maxValue=this.maxCost_;}
 for(let b=0;b<this.pictureOps_.length;b++){op=this.pictureOps_[b];opHeight=Math.round((op.cmd_time/maxValue)*maxHeight);opHeight=Math.max(opHeight,1);opHover=(b===this.currentBarMouseOverTarget_);opColor=this.getOpColor_(op.cmd_string,opHover);if(b===this.selectedOpIndex){this.chartCtx_.fillStyle='#FFFF00';}else{this.chartCtx_.fillStyle=opColor;}
-this.chartCtx_.fillRect(CHART_PADDING_LEFT+b*opWidth,bottom-opHeight,BAR_WIDTH,opHeight);}},getOpColor_(opName,hover){const characters=opName.split('');const hue=characters.reduce(this.reduceNameToHue,0)%360;const saturation=30;const lightness=hover?'75%':'50%';return'hsl('+hue+', '+saturation+'%, '+lightness+'%)';},reduceNameToHue(previousValue,currentValue,index,array){return Math.round(previousValue+currentValue.charCodeAt(0)*HUE_CHAR_CODE_ADJUSTMENT);},clearChartContents_(){this.chartCtx_.clearRect(0,0,this.chartWidth_,this.chartHeight_);},showNoTimingDataMessage_(){this.chartCtx_.font='800 italic 14px Arial';this.chartCtx_.fillStyle='#333';this.chartCtx_.textAlign='center';this.chartCtx_.textBaseline='middle';this.chartCtx_.fillText('No timing data available.',this.chartWidth_*0.5,this.chartHeight_*0.5);}};return{PictureOpsChartView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const THIS_DOC=document.currentScript.ownerDocument;const PictureDebugger=tr.ui.b.define('tr-ui-e-chrome-cc-picture-debugger');PictureDebugger.prototype={__proto__:HTMLDivElement.prototype,decorate(){const node=tr.ui.b.instantiateTemplate('#tr-ui-e-chrome-cc-picture-debugger-template',THIS_DOC);Polymer.dom(this).appendChild(node);this.style.display='flex';this.style.flexGrow=1;this.style.flexShrink=1;this.style.flexBasis='auto';this.style.flexDirection='row';const title=this.querySelector('.title');title.style.fontWeight='bold';title.style.marginLeft='5px';title.style.marginRight='5px';this.pictureAsImageData_=undefined;this.showOverdraw_=false;this.zoomScaleValue_=1;this.sizeInfo_=Polymer.dom(this).querySelector('.size');this.rasterArea_=Polymer.dom(this).querySelector('raster-area');this.rasterArea_.style.backgroundColor='#ddd';this.rasterArea_.style.minHeight='200px';this.rasterArea_.style.minWidth='200px';this.rasterArea_.style.overflowY='auto';this.rasterArea_.style.paddingLeft='5px';this.rasterCanvas_=Polymer.dom(this.rasterArea_).querySelector('canvas');this.rasterCtx_=this.rasterCanvas_.getContext('2d');this.filename_=Polymer.dom(this).querySelector('.filename');this.filename_.style.userSelect='text';this.filename_.style.marginLeft='5px';this.drawOpsChartSummaryView_=new tr.ui.e.chrome.cc.PictureOpsChartSummaryView();this.drawOpsChartView_=new tr.ui.e.chrome.cc.PictureOpsChartView();this.drawOpsChartView_.addEventListener('selection-changed',this.onChartBarClicked_.bind(this));this.exportButton_=Polymer.dom(this).querySelector('.export');this.exportButton_.addEventListener('click',this.onSaveAsSkPictureClicked_.bind(this));this.trackMouse_();const overdrawCheckbox=tr.ui.b.createCheckBox(this,'showOverdraw','pictureView.showOverdraw',false,'Show overdraw');const chartCheckbox=tr.ui.b.createCheckBox(this,'showSummaryChart','pictureView.showSummaryChart',false,'Show timing summary');const pictureInfo=Polymer.dom(this).querySelector('picture-info');pictureInfo.style.flexGrow=0;pictureInfo.style.flexShrink=0;pictureInfo.style.flexBasis='auto';pictureInfo.style.paddingTop='2px';Polymer.dom(pictureInfo).appendChild(overdrawCheckbox);Polymer.dom(pictureInfo).appendChild(chartCheckbox);this.drawOpsView_=new tr.ui.e.chrome.cc.PictureOpsListView();this.drawOpsView_.addEventListener('selection-changed',this.onChangeDrawOps_.bind(this));const leftPanel=Polymer.dom(this).querySelector('left-panel');leftPanel.style.flexDirection='column';leftPanel.style.display='flex';leftPanel.style.minWidth='300px';Polymer.dom(leftPanel).appendChild(this.drawOpsChartSummaryView_);Polymer.dom(leftPanel).appendChild(this.drawOpsView_);const middleDragHandle=document.createElement('tr-ui-b-drag-handle');middleDragHandle.style.flexGrow=0;middleDragHandle.style.flexShrink=0;middleDragHandle.style.flexBasis='auto';middleDragHandle.horizontal=false;middleDragHandle.target=leftPanel;const rightPanel=Polymer.dom(this).querySelector('right-panel');rightPanel.style.flexGrow=1;rightPanel.style.flexShrink=1;rightPanel.style.flexBasis='auto';rightPanel.style.flexDirection='column';rightPanel.style.display='flex';const chartView=Polymer.dom(rightPanel).querySelector('tr-ui-e-chrome-cc-picture-ops-chart-view');chartView.style.minHeight='150px';chartView.style.minWidth=0;chartView.style.overflowX='auto';chartView.style.overflowY='hidden';rightPanel.replaceChild(this.drawOpsChartView_,chartView);this.infoBar_=document.createElement('tr-ui-b-info-bar');Polymer.dom(this.rasterArea_).appendChild(this.infoBar_);Polymer.dom(this).insertBefore(middleDragHandle,rightPanel);this.picture_=undefined;const hkc=document.createElement('tv-ui-b-hotkey-controller');hkc.addHotKey(new tr.ui.b.HotKey({eventType:'keypress',thisArg:this,keyCode:'h'.charCodeAt(0),callback(e){this.moveSelectedOpBy(-1);e.stopPropagation();}}));hkc.addHotKey(new tr.ui.b.HotKey({eventType:'keypress',thisArg:this,keyCode:'l'.charCodeAt(0),callback(e){this.moveSelectedOpBy(1);e.stopPropagation();}}));Polymer.dom(this).appendChild(hkc);this.mutationObserver_=new MutationObserver(this.onMutation_.bind(this));this.mutationObserver_.observe(leftPanel,{attributes:true});},onMutation_(mutations){for(let m=0;m<mutations.length;m++){if(mutations[m].attributeName==='style'){this.drawOpsChartSummaryView_.requiresRedraw=true;this.drawOpsChartSummaryView_.updateChartContents();this.drawOpsChartView_.dimensionsHaveChanged=true;this.drawOpsChartView_.updateChartContents();break;}}},onSaveAsSkPictureClicked_(){const rawData=tr.b.Base64.atob(this.picture_.getBase64SkpData());const length=rawData.length;const arrayBuffer=new ArrayBuffer(length);const uint8Array=new Uint8Array(arrayBuffer);for(let c=0;c<length;c++){uint8Array[c]=rawData.charCodeAt(c);}
+this.chartCtx_.fillRect(CHART_PADDING_LEFT+b*opWidth,bottom-opHeight,BAR_WIDTH,opHeight);}},getOpColor_(opName,hover){const characters=opName.split('');const hue=characters.reduce(this.reduceNameToHue,0)%360;const saturation=30;const lightness=hover?'75%':'50%';return'hsl('+hue+', '+saturation+'%, '+lightness+'%)';},reduceNameToHue(previousValue,currentValue,index,array){return Math.round(previousValue+currentValue.charCodeAt(0)*HUE_CHAR_CODE_ADJUSTMENT);},clearChartContents_(){this.chartCtx_.clearRect(0,0,this.chartWidth_,this.chartHeight_);},showNoTimingDataMessage_(){this.chartCtx_.font='800 italic 14px Arial';this.chartCtx_.fillStyle='#333';this.chartCtx_.textAlign='center';this.chartCtx_.textBaseline='middle';this.chartCtx_.fillText('No timing data available.',this.chartWidth_*0.5,this.chartHeight_*0.5);}};return{PictureOpsChartView,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const THIS_DOC=document.currentScript.ownerDocument;const PictureDebugger=tr.ui.b.define('tr-ui-e-chrome-cc-picture-debugger');PictureDebugger.prototype={__proto__:HTMLDivElement.prototype,decorate(){const node=tr.ui.b.instantiateTemplate('#tr-ui-e-chrome-cc-picture-debugger-template',THIS_DOC);Polymer.dom(this).appendChild(node);this.style.display='flex';this.style.flexDirection='row';const title=this.querySelector('.title');title.style.fontWeight='bold';title.style.marginLeft='5px';title.style.marginRight='5px';this.pictureAsImageData_=undefined;this.showOverdraw_=false;this.zoomScaleValue_=1;this.sizeInfo_=Polymer.dom(this).querySelector('.size');this.rasterArea_=Polymer.dom(this).querySelector('raster-area');this.rasterArea_.style.backgroundColor='#ddd';this.rasterArea_.style.minHeight='100px';this.rasterArea_.style.minWidth='200px';this.rasterArea_.style.overflow='auto';this.rasterArea_.style.paddingLeft='5px';this.rasterCanvas_=Polymer.dom(this.rasterArea_).querySelector('canvas');this.rasterCtx_=this.rasterCanvas_.getContext('2d');this.filename_=Polymer.dom(this).querySelector('.filename');this.filename_.style.userSelect='text';this.filename_.style.marginLeft='5px';this.drawOpsChartSummaryView_=new tr.ui.e.chrome.cc.PictureOpsChartSummaryView();this.drawOpsChartView_=new tr.ui.e.chrome.cc.PictureOpsChartView();this.drawOpsChartView_.addEventListener('selection-changed',this.onChartBarClicked_.bind(this));this.exportButton_=Polymer.dom(this).querySelector('.export');this.exportButton_.addEventListener('click',this.onSaveAsSkPictureClicked_.bind(this));this.trackMouse_();const overdrawCheckbox=tr.ui.b.createCheckBox(this,'showOverdraw','pictureView.showOverdraw',false,'Show overdraw');const chartCheckbox=tr.ui.b.createCheckBox(this,'showSummaryChart','pictureView.showSummaryChart',false,'Show timing summary');const pictureInfo=Polymer.dom(this).querySelector('picture-info');pictureInfo.style.flexGrow=0;pictureInfo.style.flexShrink=0;pictureInfo.style.flexBasis='auto';pictureInfo.style.paddingTop='2px';Polymer.dom(pictureInfo).appendChild(overdrawCheckbox);Polymer.dom(pictureInfo).appendChild(chartCheckbox);this.drawOpsView_=new tr.ui.e.chrome.cc.PictureOpsListView();this.drawOpsView_.flexGrow=1;this.drawOpsView_.flexShrink=1;this.drawOpsView_.flexBasis='auto';this.drawOpsView_.addEventListener('selection-changed',this.onChangeDrawOps_.bind(this));const leftPanel=Polymer.dom(this).querySelector('left-panel');leftPanel.style.flexDirection='column';leftPanel.style.display='flex';leftPanel.style.flexGrow=0;leftPanel.style.flexShrink=0;leftPanel.style.flexBasis='auto';leftPanel.style.minWidth='200px';leftPanel.style.overflow='auto';Polymer.dom(leftPanel).appendChild(this.drawOpsChartSummaryView_);Polymer.dom(leftPanel).appendChild(this.drawOpsView_);const middleDragHandle=document.createElement('tr-ui-b-drag-handle');middleDragHandle.style.flexGrow=0;middleDragHandle.style.flexShrink=0;middleDragHandle.style.flexBasis='auto';middleDragHandle.horizontal=false;middleDragHandle.target=leftPanel;const rightPanel=Polymer.dom(this).querySelector('right-panel');rightPanel.style.flexGrow=1;rightPanel.style.flexShrink=1;rightPanel.style.flexBasis='auto';rightPanel.style.minWidth=0;rightPanel.style.flexDirection='column';rightPanel.style.display='flex';const chartView=Polymer.dom(rightPanel).querySelector('tr-ui-e-chrome-cc-picture-ops-chart-view');this.drawOpsChartView_.style.flexGrow=0;this.drawOpsChartView_.style.flexShrink=0;this.drawOpsChartView_.style.flexBasis='auto';this.drawOpsChartView_.style.minWidth=0;this.drawOpsChartView_.style.overflowX='auto';this.drawOpsChartView_.style.overflowY='hidden';rightPanel.replaceChild(this.drawOpsChartView_,chartView);this.infoBar_=document.createElement('tr-ui-b-info-bar');Polymer.dom(this.rasterArea_).appendChild(this.infoBar_);Polymer.dom(this).insertBefore(middleDragHandle,rightPanel);this.picture_=undefined;const hkc=document.createElement('tv-ui-b-hotkey-controller');hkc.addHotKey(new tr.ui.b.HotKey({eventType:'keypress',thisArg:this,keyCode:'h'.charCodeAt(0),callback(e){this.moveSelectedOpBy(-1);e.stopPropagation();}}));hkc.addHotKey(new tr.ui.b.HotKey({eventType:'keypress',thisArg:this,keyCode:'l'.charCodeAt(0),callback(e){this.moveSelectedOpBy(1);e.stopPropagation();}}));Polymer.dom(this).appendChild(hkc);},onSaveAsSkPictureClicked_(){const rawData=tr.b.Base64.atob(this.picture_.getBase64SkpData());const length=rawData.length;const arrayBuffer=new ArrayBuffer(length);const uint8Array=new Uint8Array(arrayBuffer);for(let c=0;c<length;c++){uint8Array[c]=rawData.charCodeAt(c);}
 const blob=new Blob([uint8Array],{type:'application/octet-binary'});const blobUrl=window.webkitURL.createObjectURL(blob);const link=document.createElementNS('http://www.w3.org/1999/xhtml','a');link.href=blobUrl;link.download=this.filename_.value;const event=document.createEvent('MouseEvents');event.initMouseEvent('click',true,false,window,0,0,0,0,0,false,false,false,false,0,null);link.dispatchEvent(event);},get picture(){return this.picture_;},set picture(picture){this.drawOpsView_.picture=picture;this.drawOpsChartView_.picture=picture;this.drawOpsChartSummaryView_.picture=picture;this.picture_=picture;this.exportButton_.disabled=!this.picture_.canSave;if(picture){const size=this.getRasterCanvasSize_();this.rasterCanvas_.width=size.width;this.rasterCanvas_.height=size.height;}
 const bounds=this.rasterArea_.getBoundingClientRect();const selectorBounds=this.mouseModeSelector_.getBoundingClientRect();this.mouseModeSelector_.pos={x:(bounds.right-selectorBounds.width-10),y:bounds.top};this.rasterize_();this.scheduleUpdateContents_();},getRasterCanvasSize_(){const style=window.getComputedStyle(this.rasterArea_);const width=Math.max(parseInt(style.width),this.picture_.layerRect.width);const height=Math.max(parseInt(style.height),this.picture_.layerRect.height);return{width,height};},scheduleUpdateContents_(){if(this.updateContentsPending_)return;this.updateContentsPending_=true;tr.b.requestAnimationFrameInThisFrameIfPossible(this.updateContents_.bind(this));},updateContents_(){this.updateContentsPending_=false;if(this.picture_){Polymer.dom(this.sizeInfo_).textContent='('+
 this.picture_.layerRect.width+' x '+
@@ -7488,7 +7563,7 @@
 this.drawPicture_();},drawPicture_(){const size=this.getRasterCanvasSize_();if(size.width!==this.rasterCanvas_.width){this.rasterCanvas_.width=size.width;}
 if(size.height!==this.rasterCanvas_.height){this.rasterCanvas_.height=size.height;}
 this.rasterCtx_.clearRect(0,0,size.width,size.height);if(!this.pictureAsImageData_.imageData)return;const imgCanvas=this.pictureAsImageData_.asCanvas();const w=imgCanvas.width;const h=imgCanvas.height;this.rasterCtx_.drawImage(imgCanvas,0,0,w,h,0,0,w*this.zoomScaleValue_,h*this.zoomScaleValue_);},rasterize_(){if(this.picture_){this.picture_.rasterize({stopIndex:this.drawOpsView_.selectedOpIndex,showOverdraw:this.showOverdraw_},this.onRasterComplete_.bind(this));}},onRasterComplete_(pictureAsImageData){this.pictureAsImageData_=pictureAsImageData;this.scheduleUpdateContents_();},moveSelectedOpBy(increment){if(this.selectedOpIndex===undefined){this.selectedOpIndex=0;return;}
-this.selectedOpIndex=tr.b.math.clamp(this.selectedOpIndex+increment,0,this.numOps);},get numOps(){return this.drawOpsView_.numOps;},get selectedOpIndex(){return this.drawOpsView_.selectedOpIndex;},set selectedOpIndex(index){this.drawOpsView_.selectedOpIndex=index;this.drawOpsChartView_.selectedOpIndex=index;},onChartBarClicked_(e){this.drawOpsView_.selectedOpIndex=this.drawOpsChartView_.selectedOpIndex;},onChangeDrawOps_(e){this.rasterize_();this.scheduleUpdateContents_();this.drawOpsChartView_.selectedOpIndex=this.drawOpsView_.selectedOpIndex;},set showOverdraw(v){this.showOverdraw_=v;this.rasterize_();},set showSummaryChart(chartShouldBeVisible){if(chartShouldBeVisible){this.drawOpsChartSummaryView_.show();}else{this.drawOpsChartSummaryView_.hide();}},trackMouse_(){this.mouseModeSelector_=document.createElement('tr-ui-b-mouse-mode-selector');this.mouseModeSelector_.targetElement=this.rasterArea_;Polymer.dom(this.rasterArea_).appendChild(this.mouseModeSelector_);this.mouseModeSelector_.supportedModeMask=tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;this.mouseModeSelector_.mode=tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;this.mouseModeSelector_.defaultMode=tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;this.mouseModeSelector_.settingsKey='pictureDebugger.mouseModeSelector';this.mouseModeSelector_.addEventListener('beginzoom',this.onBeginZoom_.bind(this));this.mouseModeSelector_.addEventListener('updatezoom',this.onUpdateZoom_.bind(this));this.mouseModeSelector_.addEventListener('endzoom',this.onEndZoom_.bind(this));},onBeginZoom_(e){this.isZooming_=true;this.lastMouseViewPos_=this.extractRelativeMousePosition_(e);e.preventDefault();},onUpdateZoom_(e){if(!this.isZooming_)return;const currentMouseViewPos=this.extractRelativeMousePosition_(e);this.zoomScaleValue_+=((this.lastMouseViewPos_.y-currentMouseViewPos.y)*0.001);this.zoomScaleValue_=Math.max(this.zoomScaleValue_,0.1);this.drawPicture_();this.lastMouseViewPos_=currentMouseViewPos;},onEndZoom_(e){this.lastMouseViewPos_=undefined;this.isZooming_=false;e.preventDefault();},extractRelativeMousePosition_(e){return{x:e.clientX-this.rasterArea_.offsetLeft,y:e.clientY-this.rasterArea_.offsetTop};}};return{PictureDebugger,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const PictureSnapshotView=tr.ui.b.define('tr-ui-e-chrome-cc-picture-snapshot-view',tr.ui.analysis.ObjectSnapshotView);PictureSnapshotView.prototype={__proto__:tr.ui.analysis.ObjectSnapshotView.prototype,decorate(){Polymer.dom(this).classList.add('tr-ui-e-chrome-cc-picture-snapshot-view');this.style.display='flex';this.style.flexGrow=0;this.style.flexShrink=1;this.style.flexBasis='auto';this.pictureDebugger_=new tr.ui.e.chrome.cc.PictureDebugger();Polymer.dom(this).appendChild(this.pictureDebugger_);},updateContents(){if(this.objectSnapshot_&&this.pictureDebugger_){this.pictureDebugger_.picture=this.objectSnapshot_;}}};tr.ui.analysis.ObjectSnapshotView.register(PictureSnapshotView,{typeNames:['cc::Picture','cc::LayeredPicture'],showInstances:false});return{PictureSnapshotView,};});'use strict';tr.exportTo('tr.e.cc',function(){const knownRasterTaskNames=['TileManager::RunRasterTask','RasterWorkerPoolTaskImpl::RunRasterOnThread','RasterWorkerPoolTaskImpl::Raster','RasterTaskImpl::Raster','cc::RasterTask','RasterTask'];const knownAnalysisTaskNames=['TileManager::RunAnalyzeTask','RasterWorkerPoolTaskImpl::RunAnalysisOnThread','RasterWorkerPoolTaskImpl::Analyze','RasterTaskImpl::Analyze','cc::AnalyzeTask','AnalyzeTask'];function getTileFromRasterTaskSlice(slice){if(!(isSliceDoingRasterization(slice)||isSliceDoingAnalysis(slice))){return undefined;}
+this.selectedOpIndex=tr.b.math.clamp(this.selectedOpIndex+increment,0,this.numOps);},get numOps(){return this.drawOpsView_.numOps;},get selectedOpIndex(){return this.drawOpsView_.selectedOpIndex;},set selectedOpIndex(index){this.drawOpsView_.selectedOpIndex=index;this.drawOpsChartView_.selectedOpIndex=index;},onChartBarClicked_(e){this.drawOpsView_.selectedOpIndex=this.drawOpsChartView_.selectedOpIndex;},onChangeDrawOps_(e){this.rasterize_();this.scheduleUpdateContents_();this.drawOpsChartView_.selectedOpIndex=this.drawOpsView_.selectedOpIndex;},set showOverdraw(v){this.showOverdraw_=v;this.rasterize_();},set showSummaryChart(chartShouldBeVisible){if(chartShouldBeVisible){this.drawOpsChartSummaryView_.show();}else{this.drawOpsChartSummaryView_.hide();}},trackMouse_(){this.mouseModeSelector_=document.createElement('tr-ui-b-mouse-mode-selector');this.mouseModeSelector_.targetElement=this.rasterArea_;Polymer.dom(this.rasterArea_).appendChild(this.mouseModeSelector_);this.mouseModeSelector_.supportedModeMask=tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;this.mouseModeSelector_.mode=tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;this.mouseModeSelector_.defaultMode=tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;this.mouseModeSelector_.settingsKey='pictureDebugger.mouseModeSelector';this.mouseModeSelector_.addEventListener('beginzoom',this.onBeginZoom_.bind(this));this.mouseModeSelector_.addEventListener('updatezoom',this.onUpdateZoom_.bind(this));this.mouseModeSelector_.addEventListener('endzoom',this.onEndZoom_.bind(this));},onBeginZoom_(e){this.isZooming_=true;this.lastMouseViewPos_=this.extractRelativeMousePosition_(e);e.preventDefault();},onUpdateZoom_(e){if(!this.isZooming_)return;const currentMouseViewPos=this.extractRelativeMousePosition_(e);this.zoomScaleValue_+=((this.lastMouseViewPos_.y-currentMouseViewPos.y)*0.001);this.zoomScaleValue_=Math.max(this.zoomScaleValue_,0.1);this.drawPicture_();this.lastMouseViewPos_=currentMouseViewPos;},onEndZoom_(e){this.lastMouseViewPos_=undefined;this.isZooming_=false;e.preventDefault();},extractRelativeMousePosition_(e){return{x:e.clientX-this.rasterArea_.offsetLeft,y:e.clientY-this.rasterArea_.offsetTop};}};return{PictureDebugger,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const PictureSnapshotView=tr.ui.b.define('tr-ui-e-chrome-cc-picture-snapshot-view',tr.ui.analysis.ObjectSnapshotView);PictureSnapshotView.prototype={__proto__:tr.ui.analysis.ObjectSnapshotView.prototype,decorate(){Polymer.dom(this).classList.add('tr-ui-e-chrome-cc-picture-snapshot-view');this.style.display='flex';this.style.flexGrow=1;this.style.flexShrink=1;this.style.flexBasis='auto';this.style.minWidth=0;this.pictureDebugger_=new tr.ui.e.chrome.cc.PictureDebugger();this.pictureDebugger_.style.flexGrow=1;this.pictureDebugger_.style.flexShrink=1;this.pictureDebugger_.style.flexBasis='auto';this.pictureDebugger_.style.minWidth=0;Polymer.dom(this).appendChild(this.pictureDebugger_);},updateContents(){if(this.objectSnapshot_&&this.pictureDebugger_){this.pictureDebugger_.picture=this.objectSnapshot_;}}};tr.ui.analysis.ObjectSnapshotView.register(PictureSnapshotView,{typeNames:['cc::Picture','cc::LayeredPicture'],showInstances:false});return{PictureSnapshotView,};});'use strict';tr.exportTo('tr.e.cc',function(){const knownRasterTaskNames=['TileManager::RunRasterTask','RasterWorkerPoolTaskImpl::RunRasterOnThread','RasterWorkerPoolTaskImpl::Raster','RasterTaskImpl::Raster','cc::RasterTask','RasterTask'];const knownAnalysisTaskNames=['TileManager::RunAnalyzeTask','RasterWorkerPoolTaskImpl::RunAnalysisOnThread','RasterWorkerPoolTaskImpl::Analyze','RasterTaskImpl::Analyze','cc::AnalyzeTask','AnalyzeTask'];function getTileFromRasterTaskSlice(slice){if(!(isSliceDoingRasterization(slice)||isSliceDoingAnalysis(slice))){return undefined;}
 let tileData;if(slice.args.data){tileData=slice.args.data;}else{tileData=slice.args.tileData;}
 if(tileData===undefined)return undefined;if(tileData.tile_id)return tileData.tile_id;const tile=tileData.tileId;if(!(tile instanceof tr.e.cc.TileSnapshot)){return undefined;}
 return tileData.tileId;}
@@ -7519,7 +7594,8 @@
 table.tableColumns=cols;table.rebuild();},tableForTesting(){return this.$.table;}});'use strict';Polymer({is:'tr-ui-a-single-event-sub-view',behaviors:[tr.ui.analysis.AnalysisSubView],properties:{isFlow:{type:Boolean,value:false}},ready(){this.currentSelection_=undefined;this.$.table.tableColumns=[{title:'Label',value(row){return row.name;},width:'150px'},{title:'Value',width:'100%',value(row){return row.value;}}];this.$.table.showHeader=false;},get selection(){return this.currentSelection_;},set selection(selection){if(selection.length!==1){throw new Error('Only supports single slices');}
 this.setSelectionWithoutErrorChecks(selection);},setSelectionWithoutErrorChecks(selection){this.currentSelection_=selection;this.updateContents_();},getFlowEventRows_(event){const rows=this.getEventRowsHelper_(event);rows.splice(0,0,{name:'ID',value:event.id});function createLinkTo(slice){const linkEl=document.createElement('tr-ui-a-analysis-link');linkEl.setSelectionAndContent(function(){return new tr.model.EventSet(slice);});Polymer.dom(linkEl).textContent=slice.userFriendlyName;return linkEl;}
 rows.push({name:'From',value:createLinkTo(event.startSlice)});rows.push({name:'To',value:createLinkTo(event.endSlice)});return rows;},getEventRowsHelper_(event){const rows=[];if(event.error){rows.push({name:'Error',value:event.error});}
-if(event.title){rows.push({name:'Title',value:event.title});}
+if(event.title){let title=event.title;if(tr.isExported('tr-ui-e-chrome-codesearch')){const container=document.createElement('div');container.appendChild(document.createTextNode(title));const link=document.createElement('tr-ui-e-chrome-codesearch');link.searchPhrase=title;container.appendChild(link);title=container;}
+rows.push({name:'Title',value:title});}
 if(event.category){rows.push({name:'Category',value:event.category});}
 if(event.model!==undefined){const ufc=event.model.getUserFriendlyCategoryFromEvent(event);if(ufc!==undefined){rows.push({name:'User Friendly Category',value:ufc});}}
 if(event.name){rows.push({name:'Name',value:event.name});}
@@ -7539,7 +7615,7 @@
 if(n>0){const subRows=[];for(const argName in args){const argView=document.createElement('tr-ui-a-generic-object-view');argView.object=args[argName];subRows.push({name:argName,value:argView});}
 rows.push({name:'Args',value:'',isExpanded:true,subRows});}}},addContextsToRows_(rows,contexts){if(contexts.length){const subRows=contexts.map(function(context){const contextView=document.createElement('tr-ui-a-generic-object-view');contextView.object=context;return{name:'Context',value:contextView};});rows.push({name:'Contexts',value:'',isExpanded:true,subRows});}},updateContents_(){if(this.currentSelection_===undefined){this.$.table.rows=[];this.$.table.rebuild();return;}
 const event=tr.b.getOnlyElement(this.currentSelection_);const rows=this.getEventRows_(event);if(event.argsStripped){rows.push({name:'Args',value:'Stripped'});}else{this.addArgsToRows_(rows,event.args);}
-this.addContextsToRows_(rows,event.contexts);const customizeRowsEvent=new tr.b.Event('customize-rows');customizeRowsEvent.rows=rows;this.dispatchEvent(customizeRowsEvent);this.$.table.tableRows=rows;this.$.table.rebuild();}});'use strict';Polymer({is:'tr-ui-e-chrome-cc-raster-task-view',created(){this.selection_=undefined;},set selection(selection){this.selection_=selection;this.updateContents_();},updateColumns_(hadCpuDurations){const timeSpanConfig={unit:tr.b.Unit.byName.timeDurationInMs,ownerDocument:this.ownerDocument};const columns=[{title:'Layer',value(row){if(row.isTotals)return'Totals';if(row.layer){const linkEl=document.createElement('tr-ui-a-analysis-link');linkEl.setSelectionAndContent(function(){return new tr.ui.e.chrome.cc.LayerSelection(costs.layer);},'Layer '+row.layerId);return linkEl;}
+this.addContextsToRows_(rows,event.contexts);const customizeRowsEvent=new tr.b.Event('customize-rows');customizeRowsEvent.rows=rows;this.dispatchEvent(customizeRowsEvent);this.$.table.tableRows=rows;this.$.table.rebuild();}});'use strict';Polymer({is:'tr-ui-e-chrome-cc-raster-task-view',created(){this.selection_=undefined;},set selection(selection){this.selection_=selection;this.updateContents_();},updateColumns_(hadCpuDurations){const timeSpanConfig={unit:tr.b.Unit.byName.timeDurationInMs,ownerDocument:this.ownerDocument};const columns=[{title:'Layer',value(row){if(row.isTotals)return'Totals';if(row.layer){const linkEl=document.createElement('tr-ui-a-analysis-link');linkEl.setSelectionAndContent(function(){return new tr.ui.e.chrome.cc.LayerSelection(row.layer);},'Layer '+row.layerId);return linkEl;}
 return'Layer '+row.layerId;},width:'250px'},{title:'Num Tiles',value(row){return row.numTiles;},cmp(a,b){return a.numTiles-b.numTiles;}},{title:'Num Analysis Tasks',value(row){return row.numAnalysisTasks;},cmp(a,b){return a.numAnalysisTasks-b.numAnalysisTasks;}},{title:'Num Raster Tasks',value(row){return row.numRasterTasks;},cmp(a,b){return a.numRasterTasks-b.numRasterTasks;}},{title:'Wall Duration (ms)',value(row){return tr.v.ui.createScalarSpan(row.duration,timeSpanConfig);},cmp(a,b){return a.duration-b.duration;}}];if(hadCpuDurations){columns.push({title:'CPU Duration (ms)',value(row){return tr.v.ui.createScalarSpan(row.cpuDuration,timeSpanConfig);},cmp(a,b){return a.cpuDuration-b.cpuDuration;}});}
 let colWidthPercentage;if(columns.length===1){colWidthPercentage='100%';}else{colWidthPercentage=(100/(columns.length-1)).toFixed(3)+'%';}
 for(let i=1;i<columns.length;i++){columns[i].width=colWidthPercentage;}
@@ -7561,7 +7637,7 @@
 return{ok:true};};RasterTaskSelection.supports=function(selection){return RasterTaskSelection.whySuported(selection).ok;};RasterTaskSelection.prototype={__proto__:tr.ui.e.chrome.cc.Selection.prototype,get specicifity(){return 3;},get associatedLayerId(){const tile0=this.tiles_[0];const allSameLayer=this.tiles_.every(function(tile){tile.layerId===tile0.layerId;});if(allSameLayer){return tile0.layerId;}
 return undefined;},get extraHighlightsByLayerId(){const highlights={};this.tiles_.forEach(function(tile,i){if(highlights[tile.layerId]===undefined){highlights[tile.layerId]=[];}
 const slice=this.slices_[i];highlights[tile.layerId].push({colorKey:slice.title,rect:tile.layerRect});},this);return highlights;},createAnalysis(){const sel=new tr.model.EventSet();this.slices_.forEach(function(slice){sel.push(slice);});let analysis;if(sel.length===1){analysis=document.createElement('tr-ui-a-single-event-sub-view');}else{analysis=document.createElement('tr-ui-e-chrome-cc-raster-task-view');}
-analysis.selection=sel;return analysis;},findEquivalent(lthi){return undefined;},get containingSnapshot(){return this.tiles_[0].containingSnapshot;}};return{RasterTaskSelection,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const TileSnapshotView=tr.ui.b.define('tr-ui-e-chrome-cc-tile-snapshot-view',tr.ui.analysis.ObjectSnapshotView);TileSnapshotView.prototype={__proto__:tr.ui.analysis.ObjectSnapshotView.prototype,decorate(){Polymer.dom(this).classList.add('tr-ui-e-chrome-cc-tile-snapshot-view');this.layerTreeView_=new tr.ui.e.chrome.cc.LayerTreeHostImplSnapshotView();Polymer.dom(this).appendChild(this.layerTreeView_);},updateContents(){const tile=this.objectSnapshot_;const layerTreeHostImpl=tile.containingSnapshot;if(!layerTreeHostImpl)return;this.layerTreeView_.objectSnapshot=layerTreeHostImpl;this.layerTreeView_.selection=new tr.ui.e.chrome.cc.TileSelection(tile);}};tr.ui.analysis.ObjectSnapshotView.register(TileSnapshotView,{typeName:'cc::Tile',showInTrackView:false});return{TileSnapshotView,};});'use strict';tr.exportTo('tr.e.gpu',function(){const AsyncSlice=tr.model.AsyncSlice;function GpuAsyncSlice(){AsyncSlice.apply(this,arguments);}
+analysis.selection=sel;return analysis;},findEquivalent(lthi){return undefined;},get containingSnapshot(){return this.tiles_[0].containingSnapshot;}};return{RasterTaskSelection,};});'use strict';tr.exportTo('tr.ui.e.chrome.cc',function(){const TileSnapshotView=tr.ui.b.define('tr-ui-e-chrome-cc-tile-snapshot-view',tr.ui.analysis.ObjectSnapshotView);TileSnapshotView.prototype={__proto__:tr.ui.analysis.ObjectSnapshotView.prototype,decorate(){Polymer.dom(this).classList.add('tr-ui-e-chrome-cc-tile-snapshot-view');this.layerTreeView_=new tr.ui.e.chrome.cc.LayerTreeHostImplSnapshotView();Polymer.dom(this).appendChild(this.layerTreeView_);},updateContents(){const tile=this.objectSnapshot_;const layerTreeHostImpl=tile.containingSnapshot;if(!layerTreeHostImpl)return;this.layerTreeView_.objectSnapshot=layerTreeHostImpl;this.layerTreeView_.selection=new tr.ui.e.chrome.cc.TileSelection(tile);}};tr.ui.analysis.ObjectSnapshotView.register(TileSnapshotView,{typeName:'cc::Tile',showInTrackView:false});return{TileSnapshotView,};});'use strict';tr.exportTo('tr.ui.e.chrome',function(){Polymer({is:'tr-ui-e-chrome-codesearch',set searchPhrase(phrase){const link=Polymer.dom(this.$.codesearchLink);const codeSearchURL='https://cs.chromium.org/search/?sq=package:chromium&type=cs&q=';link.setAttribute('href',codeSearchURL+encodeURIComponent(phrase));},onClick(clickEvent){clickEvent.stopPropagation();}});return{};});'use strict';tr.exportTo('tr.e.gpu',function(){const AsyncSlice=tr.model.AsyncSlice;function GpuAsyncSlice(){AsyncSlice.apply(this,arguments);}
 GpuAsyncSlice.prototype={__proto__:AsyncSlice.prototype,get viewSubGroupTitle(){if(this.args.channel){if(this.category==='disabled-by-default-gpu.device'){return'Device.'+this.args.channel;}
 return'Service.'+this.args.channel;}
 return this.title;}};AsyncSlice.subTypes.register(GpuAsyncSlice,{categoryParts:['disabled-by-default-gpu.device','disabled-by-default-gpu.service']});return{GpuAsyncSlice,};});'use strict';tr.exportTo('tr.e.gpu',function(){const ObjectSnapshot=tr.model.ObjectSnapshot;function StateSnapshot(){ObjectSnapshot.apply(this,arguments);}
@@ -7858,7 +7934,12 @@
 if(slice.title==='RenderAccessibilityImpl::SendLocationChanges'){renderAccessibilityLocationsHist.addSample(slice.duration,{event:new tr.v.d.RelatedEventSet(slice)});}}}
 for(const browserHelper of Object.values(chromeHelper.browserHelpers)){const mainThread=browserHelper.mainThread;if(mainThread===undefined)continue;for(const slice of mainThread.getDescendantEvents()){if(slice.title==='BrowserAccessibilityManager::OnAccessibilityEvents'){browserAccessibilityEventsHist.addSample(slice.duration,{event:new tr.v.d.RelatedEventSet(slice)});}}}
 histograms.addHistogram(browserAccessibilityEventsHist);histograms.addHistogram(renderAccessibilityEventsHist);histograms.addHistogram(renderAccessibilityLocationsHist);}
-tr.metrics.MetricRegistry.register(accessibilityMetric);return{accessibilityMetric,};});'use strict';tr.exportTo('tr.metrics.sh',function(){function androidStartupMetric(histograms,model){const messageLoopStartHistogram=histograms.createHistogram('messageloop_start_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!chromeHelper)return;for(const helper of chromeHelper.browserHelpers){for(const ev of helper.mainThread.asyncSliceGroup.childEvents()){if(ev.title==='Startup.BrowserMessageLoopStartTimeFromMainEntry3'){messageLoopStartHistogram.addSample(ev.duration,{events:new tr.v.d.RelatedEventSet([ev])});}}}}
+tr.metrics.MetricRegistry.register(accessibilityMetric);return{accessibilityMetric,};});'use strict';tr.exportTo('tr.metrics.sh',function(){const MESSAGE_LOOP_EVENT_NAME='Startup.BrowserMessageLoopStartTimeFromMainEntry3';const NAVIGATION_TIME_TO_NETWORK_STACK_EVENT_NAME='Navigation timeToNetworkStack';const FIRST_CONTENTFUL_PAINT_EVENT_NAME='firstContentfulPaint';function androidStartupMetric(histograms,model){const browserStartTimestamps=[];const requestStartTimestampsAndEvents=[];const firstContentfulPaintTimestampsAndEvents=[];const messageLoopStartHistogram=histograms.createHistogram('messageloop_start_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const requestStartHistogram=histograms.createHistogram('request_start_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!chromeHelper)return;for(const helper of chromeHelper.browserHelpers){for(const ev of helper.mainThread.asyncSliceGroup.childEvents()){if(ev.title===MESSAGE_LOOP_EVENT_NAME){messageLoopStartHistogram.addSample(ev.duration,{events:new tr.v.d.RelatedEventSet([ev])});browserStartTimestamps.push(ev.start);}
+if(ev.title===NAVIGATION_TIME_TO_NETWORK_STACK_EVENT_NAME){requestStartTimestampsAndEvents.push({timestamp:ev.end,event:new tr.v.d.RelatedEventSet([ev])});}}}
+const firstContentfulPaintHistogram=histograms.createHistogram('first_contentful_paint_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[]);const rendererHelpers=chromeHelper.rendererHelpers;const pids=Object.keys(rendererHelpers);for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){for(const ev of rendererHelper.mainThread.sliceGroup.childEvents()){if(ev.title===FIRST_CONTENTFUL_PAINT_EVENT_NAME){firstContentfulPaintTimestampsAndEvents.push({timestamp:ev.end,event:new tr.v.d.RelatedEventSet([ev])});break;}}}
+const totalRequestStarts=requestStartTimestampsAndEvents.length;const totalBrowserStarts=browserStartTimestamps.length;if(totalRequestStarts!==totalBrowserStarts){throw new Error('Number of request starts ('+totalRequestStarts+') differs from number of browser starts ('+totalBrowserStarts+')');}
+const totalFcpTimestamps=firstContentfulPaintTimestampsAndEvents.length;if(totalFcpTimestamps!==totalBrowserStarts){throw new Error('Number of FCP events ('+totalFcpTimestamps+') differs from number of browser starts ('+totalBrowserStarts+')');}
+for(let i=0;i<totalBrowserStarts;i++){const browserStart=browserStartTimestamps[i];const requestStartInfo=requestStartTimestampsAndEvents[i];requestStartHistogram.addSample(requestStartInfo.timestamp-browserStart,{events:requestStartInfo.event});const fcpInfo=firstContentfulPaintTimestampsAndEvents[i];firstContentfulPaintHistogram.addSample(fcpInfo.timestamp-browserStart,{events:fcpInfo.event});}}
 tr.metrics.MetricRegistry.register(androidStartupMetric);return{androidStartupMetric,};});'use strict';tr.exportTo('tr.metrics.sh',function(){const MAX_INPUT_EVENT_TO_STARTUP_DELAY_IN_MS=2000;const MIN_DRAW_DELAY_IN_MS=80;const MAX_DRAW_DELAY_IN_MS=2000;function findProcess(processName,model){for(const pid in model.processes){const process=model.processes[pid];if(process.name===processName){return process;}}
 return undefined;}
 function findThreads(process,threadPrefix){if(process===undefined)return undefined;const threads=[];for(const tid in process.threads){const thread=process.threads[tid];if(thread.name.startsWith(threadPrefix)){threads.push(thread);}}
@@ -7915,7 +7996,7 @@
 function unionOfIntervals(intervals){if(intervals.length===0)return[];return tr.b.math.mergeRanges(intervals.map(x=>{return{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 hasV8Stats(globalMemoryDump){let v8stats=undefined;globalMemoryDump.iterateContainerDumps(function(dump){v8stats=v8stats||dump.getMemoryAllocatorDumpByFullName('v8');});return!!v8stats;}
 function rangeForMemoryDumps(model){const startOfFirstDumpWithV8=model.globalMemoryDumps.filter(hasV8Stats).reduce((start,dump)=>Math.min(start,dump.start),Infinity);if(startOfFirstDumpWithV8===Infinity)return new tr.b.math.Range();return tr.b.math.Range.fromExplicitRange(startOfFirstDumpWithV8,Infinity);}
-return{findParent,groupAndProcessEvents,isForcedGarbageCollectionEvent,isFullMarkCompactorEvent,isGarbageCollectionEvent,isIdleTask,isIncrementalMarkingEvent,isLatencyMarkCompactorEvent,isLowMemoryEvent,isMemoryMarkCompactorEvent,isScavengerEvent,isSubGarbageCollectionEvent,isTopGarbageCollectionEvent,isTopV8ExecuteEvent,isV8Event,isV8ExecuteEvent,isV8RCSEvent,isCompileRCSCategory,isCompileOptimizeRCSCategory,isCompileUnoptimizeRCSCategory,isCompileParseRCSCategory,rangeForMemoryDumps,subGarbageCollectionEventName,topGarbageCollectionEventName,unionOfIntervals,};});'use strict';tr.exportTo('tr.metrics.blink',function(){const 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;}
+return{findParent,groupAndProcessEvents,isForcedGarbageCollectionEvent,isFullMarkCompactorEvent,isGarbageCollectionEvent,isIdleTask,isIncrementalMarkingEvent,isLatencyMarkCompactorEvent,isLowMemoryEvent,isMemoryMarkCompactorEvent,isScavengerEvent,isSubGarbageCollectionEvent,isTopGarbageCollectionEvent,isTopV8ExecuteEvent,isV8Event,isV8ExecuteEvent,isV8RCSEvent,isCompileRCSCategory,isCompileOptimizeRCSCategory,isCompileUnoptimizeRCSCategory,isCompileParseRCSCategory,rangeForMemoryDumps,subGarbageCollectionEventName,topGarbageCollectionEventName,unionOfIntervals,};});'use strict';tr.exportTo('tr.metrics.blink',function(){const BLINK_GC_EVENTS={'BlinkGC.AtomicPhaseMarking':'blink-gc-marking','BlinkGC.CompleteSweep':'blink-gc-complete-sweep','BlinkGC.LazySweepInIdle':'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(histograms,model){addDurationOfTopEvents(histograms,model);addTotalDurationOfTopEvents(histograms,model);addIdleTimesOfTopEvents(histograms,model);addTotalIdleTimesOfTopEvents(histograms,model);}
 tr.metrics.MetricRegistry.register(blinkGcMetric);const timeDurationInMs_smallerIsBetter=tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;const percentage_biggerIsBetter=tr.b.Unit.byName.normalizedPercentage_biggerIsBetter;const CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createLinear(0,20,200).addExponentialBins(200,100);function createNumericForTopEventTime(name){const 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;}
@@ -7986,7 +8067,39 @@
 addMetricToHistograms(histograms){this.addSample_(histograms,'time_to_video_play',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,this.timeToVideoPlay);this.addSample_(histograms,'time_to_audio_play',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,this.timeToAudioPlay);this.addSample_(histograms,'dropped_frame_count',tr.b.Unit.byName.count_smallerIsBetter,this.droppedFrameCount);for(const[key,value]of this.seekTimes.entries()){const keyString=key.toString().replace('.','_');this.addSample_(histograms,'pipeline_seek_time_'+keyString,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,value.pipelineSeekTime);this.addSample_(histograms,'seek_time_'+keyString,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,value.seekTime);}
 this.addSample_(histograms,'buffering_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,this.bufferingTime);}
 addSample_(histograms,name,unit,sample){if(sample===undefined)return;const histogram=histograms.getHistogramNamed(name);if(histogram===undefined){histograms.createHistogram(name,unit,sample);}else{histogram.addSample(sample);}}}
-tr.metrics.MetricRegistry.register(mediaMetric);return{mediaMetric,};});'use strict';tr.exportTo('tr.metrics',function(){function sampleExceptionMetric(histograms,model){const hist=new tr.v.Histogram('foo',tr.b.Unit.byName.sizeInBytes_smallerIsBetter);hist.addSample(9);hist.addSample(91,{bar:new tr.v.d.GenericSet([{hello:42}])});for(const 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){}}
+tr.metrics.MetricRegistry.register(mediaMetric);return{mediaMetric,};});'use strict';tr.exportTo('tr.metrics.rendering',function(){const UNKNOWN_THREAD_NAME='Unknown';const CATEGORY_THREAD_MAP=new Map();CATEGORY_THREAD_MAP.set('all',[/.*/]);CATEGORY_THREAD_MAP.set('browser',[/^Browser Compositor$/,/^CrBrowserMain$/]);CATEGORY_THREAD_MAP.set('display_compositor',[/^VizCompositorThread$/]);CATEGORY_THREAD_MAP.set('fast_path',[/^Browser Compositor$/,/^Chrome_InProcGpuThread$/,/^Compositor$/,/^CrBrowserMain$/,/^CrGpuMain$/,/IOThread/,/^VizCompositorThread$/]);CATEGORY_THREAD_MAP.set('gpu',[/^Chrome_InProcGpuThread$/,/^CrGpuMain$/]);CATEGORY_THREAD_MAP.set('io',[/IOThread/]);CATEGORY_THREAD_MAP.set('raster',[/CompositorTileWorker/]);CATEGORY_THREAD_MAP.set('renderer_compositor',[/^Compositor$/]);CATEGORY_THREAD_MAP.set('renderer_main',[/^CrRendererMain$/]);const DRM_EVENT='DrmEventFlipComplete';const DISPLAY_EVENT='BenchmarkInstrumentation::DisplayRenderingStats';const GESTURE_EVENT='SyntheticGestureController::running';function addValueToMap_(map,key,value){const oldValue=map.get(key)||0;map.set(key,oldValue+value);}
+function*getCategories_(threadName){let isOther=true;for(const[category,regexps]of CATEGORY_THREAD_MAP){for(const regexp of regexps){if(regexp.test(threadName)){if(category!=='all')isOther=false;yield category;break;}}}
+if(isOther)yield'other';}
+function addCoresPerSecondHistograms(histograms,model,segments){const totalDuration=tr.b.math.Statistics.sum(segments,segment=>segment.duration);const threadValues=new Map();for(const thread of model.getAllThreads()){addValueToMap_(threadValues,thread.name||UNKNOWN_THREAD_NAME,tr.b.math.Statistics.sum(segments,segment=>thread.getCpuTimeForRange(segment.boundsRange))/totalDuration);}
+const categoryValues=new Map();const breakdowns=new Map();for(const[threadName,coresPerSec]of threadValues){for(const category of getCategories_(threadName)){addValueToMap_(categoryValues,category,coresPerSec);if(!breakdowns.has(category)){breakdowns.set(category,new tr.v.d.Breakdown());}
+breakdowns.get(category).set(threadName,coresPerSec);}}
+for(const[category,coresPerSec]of categoryValues){histograms.createHistogram(`cores_per_second_${category}_thread`,tr.b.Unit.byName.unitlessNumber_smallerIsBetter,{value:coresPerSec,diagnostics:{breakdown:breakdowns.get(category)}},{description:'CPU cores per second of a thread group'});}}
+function addFrameTimeHistograms(histograms,model,segments){const modelHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!modelHelper||!modelHelper.browserProcess)return;let events=[];if(modelHelper.gpuHelper){const gpuProcess=modelHelper.gpuHelper.process;events=[...gpuProcess.findTopmostSlicesNamed(DRM_EVENT)];if(events.length===0){events=[...gpuProcess.findTopmostSlicesNamed(DISPLAY_EVENT)];}}
+if(events.length===0){events=[...modelHelper.browserProcess.findTopmostSlicesNamed(DISPLAY_EVENT)];}
+if(events.length===0)return;const timestamps=events.map(event=>(event.title!==DRM_EVENT?event.start:(tr.b.convertUnit(event.args.data['vblank.tv_sec'],tr.b.UnitScale.TIME.SEC,tr.b.UnitScale.TIME.MILLI_SEC)+
+tr.b.convertUnit(event.args.data['vblank.tv_usec'],tr.b.UnitScale.TIME.MICRO_SEC,tr.b.UnitScale.TIME.MILLI_SEC))));const frameTimes=[];for(const segment of segments){const filteredTimestamps=segment.boundsRange.filterArray(timestamps);for(let i=1;i<filteredTimestamps.length;i++){frameTimes.push(filteredTimestamps[i]-filteredTimestamps[i-1]);}}
+histograms.createHistogram('frame_times_tbmv2',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,frameTimes,{binBoundaries:tr.v.HistogramBinBoundaries.createLinear(0,50,20),description:'Raw frame times.'});histograms.createHistogram('mean_frame_time_tbmv2',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,tr.b.math.Statistics.mean(frameTimes),{binBoundaries:tr.v.HistogramBinBoundaries.createLinear(0,50,20),description:'Arithmetic mean of frame times.'});histograms.createHistogram('smooth_frames',tr.b.Unit.byName.normalizedPercentage_biggerIsBetter,tr.b.math.Statistics.sum(frameTimes,(x=>(x<17?1:0)))/frameTimes.length,{description:'Percentage of frames that were hitting 60 FPS.'});}
+function eventIsValidGraphicsEvent(event,eventMap){if(!event.bindId||!event.args||!event.args.step){return false;}
+const bindId=event.bindId;if(bindId in eventMap&&event.args.step in eventMap[bindId]){if(event.args.step==='IssueBeginFrame'||event.args.step==='ReceiveBeginFrame'){throw new Error('Unexpected duplicate step: '+event.args.step);}
+return false;}
+return true;}
+function generateBreakdownForCompositorPipelineInClient(flow){const breakdown=new tr.v.d.Breakdown();breakdown.set('time before GenerateRenderPass',flow.GenerateRenderPass.start-flow.ReceiveBeginFrame.start);breakdown.set('GenerateRenderPass duration',flow.GenerateRenderPass.duration);breakdown.set('GenerateCompositorFrame duration',flow.GenerateCompositorFrame.duration);breakdown.set('SubmitCompositorFrame duration',flow.SubmitCompositorFrame.duration);return breakdown;}
+function generateBreakdownForCompositorPipelineInService(flow){const breakdown=new tr.v.d.Breakdown();breakdown.set('Processing CompositorFrame on reception',flow.ReceiveCompositorFrame.duration);breakdown.set('Delay before SurfaceAggregation',flow.SurfaceAggregation.start-flow.ReceiveCompositorFrame.end);breakdown.set('SurfaceAggregation duration',flow.SurfaceAggregation.duration);return breakdown;}
+function generateBreakdownForDraw(drawEvent){const breakdown=new tr.v.d.Breakdown();for(const slice of drawEvent.subSlices){breakdown.set(slice.title,slice.duration);}
+return breakdown;}
+function getDisplayCompositorThread(model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);const gpuHelper=chromeHelper.gpuHelper;if(gpuHelper){const thread=gpuHelper.process.findAtMostOneThreadNamed('VizCompositorThread');if(thread){return thread;}}
+const browserHelper=chromeHelper.browserHelpers[0];return browserHelper.process.findAtMostOneThreadNamed('CrBrowserMain');}
+function addPipelineHistograms(histograms,model,segments){let events=[];for(const thread of model.getAllThreads()){const graphicsEvents=thread.sliceGroup.slices.filter(slice=>slice.title==='Graphics.Pipeline');for(const segment of segments){const filteredEvents=segment.boundsRange.filterArray(graphicsEvents,evt=>evt.start);events=events.concat(filteredEvents);}}
+const bindEvents={};for(const event of events){if(!eventIsValidGraphicsEvent(event,bindEvents))continue;const bindId=event.bindId;if(!(bindId in bindEvents)){bindEvents[bindId]={};}
+bindEvents[bindId][event.args.step]=event;}
+const dcThread=getDisplayCompositorThread(model);const drawEvents={};if(dcThread){const events=[...dcThread.findTopmostSlicesNamed('Graphics.Pipeline.DrawAndSwap')];for(const segment of segments){const filteredEvents=segment.boundsRange.filterArray(events,evt=>evt.start);for(const event of filteredEvents){if(!event.id.startsWith(':ptr:'))continue;const id=parseInt(event.id.substring(5),16);if(id in drawEvents){throw new Error('Duplicate draw events: '+id);}
+drawEvents[id]=event;}}}
+const issueToReceipt=histograms.createHistogram('pipeline:begin_frame_transport',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{description:'Latency of begin-frame message from the display '+'compositor to the client, including the IPC latency and task-'+'queue time in the client.',});const receiptToSubmit=histograms.createHistogram('pipeline:begin_frame_to_frame_submission',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{description:'Latency between begin-frame reception and '+'CompositorFrame submission in the renderer.'});const submitToAggregate=histograms.createHistogram('pipeline:frame_submission_to_display',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{description:'Latency between CompositorFrame submission in the '+'renderer to display in the display-compositor, including IPC '+'latency, task-queue time in the display-compositor, and '+'additional processing (e.g. surface-sync etc.)',});const aggregateToDraw=histograms.createHistogram('pipeline:draw',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],{description:'How long it takes for the gpu-swap step.'});for(const flow of Object.values(bindEvents)){if(!flow.IssueBeginFrame||!flow.ReceiveBeginFrame||!flow.SubmitCompositorFrame||!flow.SurfaceAggregation){continue;}
+issueToReceipt.addSample(flow.ReceiveBeginFrame.start-
+flow.IssueBeginFrame.start);receiptToSubmit.addSample(flow.SubmitCompositorFrame.end-flow.ReceiveBeginFrame.start,{breakdown:generateBreakdownForCompositorPipelineInClient(flow)});submitToAggregate.addSample(flow.SurfaceAggregation.end-flow.SubmitCompositorFrame.end,{breakdown:generateBreakdownForCompositorPipelineInService(flow)});if(flow.SurfaceAggregation.args&&flow.SurfaceAggregation.args.display_trace){const displayTrace=flow.SurfaceAggregation.args.display_trace;if(!(displayTrace in drawEvents))continue;const drawEvent=drawEvents[displayTrace];aggregateToDraw.addSample(drawEvent.duration,{breakdown:generateBreakdownForDraw(drawEvent)});}}}
+function renderingMetric(histograms,model){const segments=[];const IRExp=/Interaction\.([^/]+)(\/[^/]*)?$/;for(const thread of model.getAllThreads()){for(const slice of thread.asyncSliceGroup.slices){if(slice.title===GESTURE_EVENT){segments.push(new tr.model.um.Segment(slice.start,slice.duration));}else{const parts=IRExp.exec(slice.title);if(parts&&!parts[1].startsWith('Gesture_')){segments.push(new tr.model.um.Segment(slice.start,slice.duration));}}}}
+if(segments.length===0)return;addCoresPerSecondHistograms(histograms,model,segments);addFrameTimeHistograms(histograms,model,segments);addPipelineHistograms(histograms,model,segments);}
+tr.metrics.MetricRegistry.register(renderingMetric);return{renderingMetric,};});'use strict';tr.exportTo('tr.metrics',function(){function sampleExceptionMetric(histograms,model){const hist=new tr.v.Histogram('foo',tr.b.Unit.byName.sizeInBytes_smallerIsBetter);hist.addSample(9);hist.addSample(91,{bar:new tr.v.d.GenericSet([{hello:42}])});for(const 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){}}
 const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);for(const[pid,process]of Object.entries(model.processes)){}
 histograms.addHistogram(hist);throw new Error('There was an error');}
 tr.metrics.MetricRegistry.register(sampleExceptionMetric);return{sampleExceptionMetric,};});'use strict';tr.exportTo('tr.metrics',function(){function sampleMetric(histograms,model){const hist=new tr.v.Histogram('foo',tr.b.Unit.byName.sizeInBytes_smallerIsBetter);hist.addSample(9);hist.addSample(91,{bar:new tr.v.d.GenericSet([{hello:42}])});for(const 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){}}
@@ -8012,27 +8125,20 @@
 function findSpaNavigationsOnRenderer(rendererHelper,browserHelper){const spaNavEventToNavStartMap=getSpaNavigationEventToNavigationStartMap_(rendererHelper,browserHelper);const spaNavEventToFirstPaintEventMap=getSpaNavigationEventToFirstPaintEventMap_(rendererHelper);const spaNavigations=[];for(const[spaNavEvent,navStartEvent]of
 spaNavEventToNavStartMap){if(spaNavEventToFirstPaintEventMap.has(spaNavEvent)){const firstPaintEvent=spaNavEventToFirstPaintEventMap.get(spaNavEvent);const isNavStartAsyncSlice=navStartEvent instanceof tr.model.AsyncSlice;spaNavigations.push({navStartCandidates:{inputLatencyAsyncSlice:isNavStartAsyncSlice?navStartEvent:undefined,goToIndexSlice:isNavStartAsyncSlice?undefined:navStartEvent},firstPaintEvent,url:spaNavEvent.args.url});}}
 return spaNavigations;}
-return{findSpaNavigationsOnRenderer,};});'use strict';tr.exportTo('tr.metrics.sh',function(){function generateTimeBreakdownTree(mainThread,rangeOfInterest,getEventStart,getEventDuration,getEventSelfTime){if(mainThread===null)return;const breakdownTree={};for(const title of
+return{findSpaNavigationsOnRenderer,};});'use strict';tr.exportTo('tr.metrics.sh',function(){function getWallClockSelfTime_(event,rangeOfInterest){if(event.duration===0)return 0;const selfTimeRanges=[rangeOfInterest.findIntersection(event.range)];for(const subSlice of event.subSlices){if(selfTimeRanges.length===0)return 0;const lastRange=selfTimeRanges.pop();selfTimeRanges.push(...tr.b.math.Range.findDifference(lastRange,subSlice.range));}
+return tr.b.math.Statistics.sum(selfTimeRanges,r=>r.duration);}
+function getCPUSelfTime_(event,rangeOfInterest){if(event.duration===0||event.selfTime===0)return 0;if(event.cpuSelfTime===undefined)return 0;const cpuTimeDensity=event.cpuSelfTime/event.selfTime;return getWallClockSelfTime_(event,rangeOfInterest)*cpuTimeDensity;}
+function generateTimeBreakdownTree(mainThread,rangeOfInterest,getEventSelfTime){if(mainThread===null)return;const breakdownTree={};for(const title of
 tr.e.chrome.ChromeUserFriendlyCategoryDriver.ALL_TITLES){breakdownTree[title]={total:0,events:{}};}
-for(const event of mainThread.getDescendantEvents()){const eventStart=getEventStart(event);const eventDuration=getEventDuration(event);const eventSelfTime=getEventSelfTime(event);const eventEnd=eventStart+eventDuration;if(!rangeOfInterest.intersectsExplicitRangeExclusive(eventStart,eventEnd)){continue;}
-if(eventSelfTime===undefined)continue;const title=tr.e.chrome.ChromeUserFriendlyCategoryDriver.fromEvent(event);let timeIntersectionRatio=0;if(eventDuration>0){timeIntersectionRatio=rangeOfInterest.findExplicitIntersectionDuration(eventStart,eventEnd)/eventDuration;}
+for(const event of mainThread.sliceGroup.childEvents()){if(!rangeOfInterest.intersectsRangeExclusive(event.range))continue;const eventSelfTime=getEventSelfTime(event,rangeOfInterest);const title=tr.e.chrome.ChromeUserFriendlyCategoryDriver.fromEvent(event);breakdownTree[title].total+=eventSelfTime;if(breakdownTree[title].events[event.title]===undefined){breakdownTree[title].events[event.title]=0;}
+breakdownTree[title].events[event.title]+=eventSelfTime;let timeIntersectionRatio=0;if(event.duration>0){timeIntersectionRatio=rangeOfInterest.findExplicitIntersectionDuration(event.start,event.end)/event.duration;}
 const v8Runtime=event.args['runtime-call-stat'];if(v8Runtime!==undefined){const v8RuntimeObject=JSON.parse(v8Runtime);for(const runtimeCall in v8RuntimeObject){if(v8RuntimeObject[runtimeCall].length===2){if(breakdownTree.v8_runtime.events[runtimeCall]===undefined){breakdownTree.v8_runtime.events[runtimeCall]=0;}
-const runtimeTime=tr.b.Unit.timestampFromUs(v8RuntimeObject[runtimeCall][1]*timeIntersectionRatio);breakdownTree.v8_runtime.total+=runtimeTime;breakdownTree.v8_runtime.events[runtimeCall]+=runtimeTime;}}}
-const approximatedSelfTimeContribution=eventSelfTime*timeIntersectionRatio;breakdownTree[title].total+=approximatedSelfTimeContribution;if(breakdownTree[title].events[event.title]===undefined){breakdownTree[title].events[event.title]=0;}
-breakdownTree[title].events[event.title]+=approximatedSelfTimeContribution;}
+const runtimeTime=tr.b.Unit.timestampFromUs(v8RuntimeObject[runtimeCall][1]*timeIntersectionRatio);breakdownTree.v8_runtime.total+=runtimeTime;breakdownTree.v8_runtime.events[runtimeCall]+=runtimeTime;}}}}
 return breakdownTree;}
-function addIdleAndBlockByNetworkBreakdown_(breakdownTree,mainThreadEvents,networkEvents,rangeOfInterest){let idleRanges=[rangeOfInterest];const mainThreadEventRanges=tr.b.math.convertEventsToRanges(mainThreadEvents);const networkEventRanges=tr.b.math.convertEventsToRanges(networkEvents);const eventRanges=mainThreadEventRanges.concat(networkEventRanges);eventRanges.sort((a,b)=>a.min-b.min);for(const eventRange of eventRanges){if(!eventRange||eventRange.isEmpty||eventRange.duration<0){throw new Error('Range is invalid');}
-const newLastIdleRanges=tr.b.math.Range.findDifference(idleRanges[idleRanges.length-1],eventRange);idleRanges.pop();idleRanges=idleRanges.concat(newLastIdleRanges);if(idleRanges.length===0)break;}
-const totalFreeDuration=tr.b.math.Statistics.sum(idleRanges,range=>range.duration);breakdownTree.idle={total:totalFreeDuration,events:{}};let totalBlockedDuration=rangeOfInterest.duration;for(const component of Object.values(breakdownTree)){totalBlockedDuration-=component.total;}
-breakdownTree.blocked_on_network={total:totalBlockedDuration,events:{}};}
-function generateWallClockTimeBreakdownTree(mainThread,networkEvents,rangeOfInterest){function getEventStart(e){return e.start;}
-function getEventDuration(e){return e.duration;}
-function getEventSelfTime(e){return e.selfTime;}
-const breakdownTree=generateTimeBreakdownTree(mainThread,rangeOfInterest,getEventStart,getEventDuration,getEventSelfTime);const mainThreadEventsInRange=tr.model.helpers.getSlicesIntersectingRange(rangeOfInterest,mainThread.sliceGroup.topLevelSlices);addIdleAndBlockByNetworkBreakdown_(breakdownTree,mainThreadEventsInRange,networkEvents,rangeOfInterest);return breakdownTree;}
-function generateCpuTimeBreakdownTree(mainThread,rangeOfInterestCpuTime){function getEventStart(e){return e.cpuStart;}
-function getEventDuration(e){return e.cpuDuration;}
-function getEventSelfTime(e){return e.cpuSelfTime;}
-return generateTimeBreakdownTree(mainThread,rangeOfInterestCpuTime,getEventStart,getEventDuration,getEventSelfTime);}
+function addIdleAndBlockByNetworkBreakdown_(breakdownTree,mainThreadEvents,networkEvents,rangeOfInterest){const mainThreadEventRanges=tr.b.math.convertEventsToRanges(mainThreadEvents);const networkEventRanges=tr.b.math.convertEventsToRanges(networkEvents);const eventRanges=mainThreadEventRanges.concat(networkEventRanges);const idleRanges=tr.b.math.findEmptyRangesBetweenRanges(eventRanges,rangeOfInterest);const totalFreeDuration=tr.b.math.Statistics.sum(idleRanges,range=>range.duration);breakdownTree.idle={total:totalFreeDuration,events:{}};let totalBlockedDuration=rangeOfInterest.duration;for(const[title,component]of Object.entries(breakdownTree)){if(title==='v8_runtime')continue;totalBlockedDuration-=component.total;}
+breakdownTree.blocked_on_network={total:Math.max(totalBlockedDuration,0),events:{}};}
+function generateWallClockTimeBreakdownTree(mainThread,networkEvents,rangeOfInterest){const breakdownTree=generateTimeBreakdownTree(mainThread,rangeOfInterest,getWallClockSelfTime_);const mainThreadEventsInRange=tr.model.helpers.getSlicesIntersectingRange(rangeOfInterest,mainThread.sliceGroup.topLevelSlices);addIdleAndBlockByNetworkBreakdown_(breakdownTree,mainThreadEventsInRange,networkEvents,rangeOfInterest);return breakdownTree;}
+function generateCpuTimeBreakdownTree(mainThread,rangeOfInterest){return generateTimeBreakdownTree(mainThread,rangeOfInterest,getCPUSelfTime_);}
 return{generateTimeBreakdownTree,generateWallClockTimeBreakdownTree,generateCpuTimeBreakdownTree,};});'use strict';tr.exportTo('tr.metrics.sh',function(){const LONG_TASK_THRESHOLD_MS=50;const timeDurationInMs_smallerIsBetter=tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;const RelatedEventSet=tr.v.d.RelatedEventSet;const hasCategoryAndName=tr.metrics.sh.hasCategoryAndName;const EventFinderUtils=tr.e.chrome.EventFinderUtils;function getNetworkEventsInRange(process,range){const networkEvents=[];for(const thread of Object.values(process.threads)){const threadHelper=new tr.model.helpers.ChromeThreadHelper(thread);const events=threadHelper.getNetworkEvents();for(const event of events){if(range.intersectsExplicitRangeInclusive(event.start,event.end)){networkEvents.push(event);}}}
 return networkEvents;}
 function createBreakdownDiagnostic(breakdownTree){const breakdownDiagnostic=new tr.v.d.Breakdown();breakdownDiagnostic.colorScheme=tr.v.d.COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER;for(const label in breakdownTree){breakdownDiagnostic.set(label,breakdownTree[label].total);}
@@ -8041,23 +8147,23 @@
 return snapshot;}
 function findAllEvents(rendererHelper,category,title){const targetEvents=[];for(const ev of rendererHelper.process.getDescendantEvents()){if(!hasCategoryAndName(ev,category,title))continue;targetEvents.push(ev);}
 return targetEvents;}
-const URL_BLACKLIST=['','about:blank','data:text/html,pluginplaceholderdata','chrome-error://chromewebdata/'];function shouldIgnoreURL(url){return URL_BLACKLIST.includes(url);}
-function collectTimeToEvent(category,eventName,rendererHelper,frameToNavStartEvents){const targetEvents=findAllEvents(rendererHelper,category,eventName);const samples=[];for(const ev of targetEvents){if(rendererHelper.isTelemetryInternalEvent(ev))continue;const frameIdRef=ev.args.frame;const snapshot=findFrameLoaderSnapshotAt(rendererHelper,frameIdRef,ev.start);if(snapshot===undefined||!snapshot.args.isLoadingMainFrame)continue;const url=snapshot.args.documentLoaderURL;if(shouldIgnoreURL(url))continue;const navigationStartEvent=EventFinderUtils.findLastEventStartingOnOrBeforeTimestamp(frameToNavStartEvents.get(frameIdRef)||[],ev.start);if(navigationStartEvent===undefined)continue;const navStartToEventRange=tr.b.math.Range.fromExplicitRange(navigationStartEvent.start,ev.start);const networkEvents=getNetworkEventsInRange(rendererHelper.process,navStartToEventRange);const breakdownTree=tr.metrics.sh.generateWallClockTimeBreakdownTree(rendererHelper.mainThread,networkEvents,navStartToEventRange);samples.push({value:navStartToEventRange.duration,breakdownTree,diagnostics:{breakdown:createBreakdownDiagnostic(breakdownTree),url:new tr.v.d.GenericSet([url]),Start:new RelatedEventSet(navigationStartEvent),End:new RelatedEventSet(ev)}});}
+function collectTimeToEvent(category,eventName,rendererHelper,frameToNavStartEvents){const targetEvents=findAllEvents(rendererHelper,category,eventName);const samples=[];for(const ev of targetEvents){if(rendererHelper.isTelemetryInternalEvent(ev))continue;const frameIdRef=ev.args.frame;const snapshot=findFrameLoaderSnapshotAt(rendererHelper,frameIdRef,ev.start);if(snapshot===undefined||!snapshot.args.isLoadingMainFrame)continue;const url=snapshot.args.documentLoaderURL;if(tr.e.chrome.CHROME_INTERNAL_URLS.includes(url))continue;const navigationStartEvent=EventFinderUtils.findLastEventStartingOnOrBeforeTimestamp(frameToNavStartEvents.get(frameIdRef)||[],ev.start);if(navigationStartEvent===undefined)continue;const navStartToEventRange=tr.b.math.Range.fromExplicitRange(navigationStartEvent.start,ev.start);const networkEvents=getNetworkEventsInRange(rendererHelper.process,navStartToEventRange);const breakdownTree=tr.metrics.sh.generateWallClockTimeBreakdownTree(rendererHelper.mainThread,networkEvents,navStartToEventRange);samples.push({value:navStartToEventRange.duration,breakdownTree,diagnostics:{breakdown:createBreakdownDiagnostic(breakdownTree),url:new tr.v.d.GenericSet([url]),Start:new RelatedEventSet(navigationStartEvent),End:new RelatedEventSet(ev)}});}
 return samples;}
 function addFirstMeaningfulPaintSample(samples,rendererHelper,navigationStart,fmpMarkerEvent,url){const navStartToFMPRange=tr.b.math.Range.fromExplicitRange(navigationStart.start,fmpMarkerEvent.start);const networkEvents=getNetworkEventsInRange(rendererHelper.process,navStartToFMPRange);const timeToFirstMeaningfulPaint=navStartToFMPRange.duration;const breakdownTree=tr.metrics.sh.generateWallClockTimeBreakdownTree(rendererHelper.mainThread,networkEvents,navStartToFMPRange);samples.push({value:timeToFirstMeaningfulPaint,breakdownTree,diagnostics:{breakdown:createBreakdownDiagnostic(breakdownTree),start:new RelatedEventSet(navigationStart),end:new RelatedEventSet(fmpMarkerEvent),infos:new tr.v.d.GenericSet([{url,pid:rendererHelper.pid,start:navigationStart.start,fmp:fmpMarkerEvent.start,}]),}});}
-function addFirstMeaningfulPaintCpuTimeSample(samples,rendererHelper,navigationStart,fmpMarkerEvent,url){const navStartToFMPCpuRange=tr.b.math.Range.fromExplicitRange(navigationStart.cpuStart,fmpMarkerEvent.cpuStart);const mainThreadCpuTime=getMainThreadCpuTime(rendererHelper,navStartToFMPCpuRange);const breakdownTree=tr.metrics.sh.generateCpuTimeBreakdownTree(rendererHelper.mainThread,navStartToFMPCpuRange);samples.push({value:mainThreadCpuTime,breakdownTree,diagnostics:{breakdown:createBreakdownDiagnostic(breakdownTree),start:new RelatedEventSet(navigationStart),end:new RelatedEventSet(fmpMarkerEvent),infos:new tr.v.d.GenericSet([{url,pid:rendererHelper.pid,start:navigationStart.start,fmp:fmpMarkerEvent.start,}]),}});}
-function getMainThreadCpuTime(rendererHelper,rangeOfInterest){let mainThreadCpuTime=0;for(const slice of rendererHelper.mainThread.sliceGroup.topLevelSlices){if(!slice.cpuDuration)continue;const sliceRange=tr.b.math.Range.fromExplicitRange(slice.cpuStart,slice.cpuStart+slice.cpuDuration);const intersection=rangeOfInterest.findIntersection(sliceRange);mainThreadCpuTime+=intersection.duration;}
-return mainThreadCpuTime;}
+function addFirstMeaningfulPaintCpuTimeSample(samples,rendererHelper,navigationStart,fmpMarkerEvent,url){const navStartToFMPRange=tr.b.math.Range.fromExplicitRange(navigationStart.start,fmpMarkerEvent.start);const mainThreadCpuTime=rendererHelper.mainThread.getCpuTimeForRange(navStartToFMPRange);const breakdownTree=tr.metrics.sh.generateCpuTimeBreakdownTree(rendererHelper.mainThread,navStartToFMPRange);samples.push({value:mainThreadCpuTime,breakdownTree,diagnostics:{breakdown:createBreakdownDiagnostic(breakdownTree),start:new RelatedEventSet(navigationStart),end:new RelatedEventSet(fmpMarkerEvent),infos:new tr.v.d.GenericSet([{url,pid:rendererHelper.pid,start:navigationStart.start,fmp:fmpMarkerEvent.start,}]),}});}
 function decorateInteractivitySampleWithDiagnostics_(rendererHelper,eventTimestamp,navigationStartEvent,firstMeaningfulPaintTime,domContentLoadedEndTime,url){if(eventTimestamp===undefined)return undefined;const navigationStartTime=navigationStartEvent.start;const navStartToEventTimeRange=tr.b.math.Range.fromExplicitRange(navigationStartTime,eventTimestamp);const networkEvents=getNetworkEventsInRange(rendererHelper.process,navStartToEventTimeRange);const breakdownTree=tr.metrics.sh.generateWallClockTimeBreakdownTree(rendererHelper.mainThread,networkEvents,navStartToEventTimeRange);const breakdownDiagnostic=createBreakdownDiagnostic(breakdownTree);return{value:navStartToEventTimeRange.duration,diagnostics:tr.v.d.DiagnosticMap.fromObject({'Start':new RelatedEventSet(navigationStartEvent),'Navigation infos':new tr.v.d.GenericSet([{url,pid:rendererHelper.pid,navigationStartTime,firstMeaningfulPaintTime,domContentLoadedEndTime,eventTimestamp,}]),'Breakdown of [navStart, eventTimestamp]':breakdownDiagnostic,}),};}
-function collectLoadingMetricsForRenderer(rendererHelper){const model=rendererHelper.modelHelper.model;const frameToNavStartEvents=EventFinderUtils.getSortedMainThreadEventsByFrame(rendererHelper,'navigationStart','blink.user_timing');const firstPaintSamples=collectTimeToEvent('loading','firstPaint',rendererHelper,frameToNavStartEvents);const firstContentfulPaintSamples=collectTimeToEvent('loading','firstContentfulPaint',rendererHelper,frameToNavStartEvents);const onLoadSamples=collectTimeToEvent('blink.user_timing','loadEventStart',rendererHelper,frameToNavStartEvents);const interactiveSamples=[];const firstCpuIdleSamples=[];const firstMeaningfulPaintSamples=[];const firstMeaningfulPaintCpuTimeSamples=[];for(const expectation of model.userModel.expectations){if(!(expectation instanceof tr.model.um.LoadExpectation))continue;if(shouldIgnoreURL(expectation.url))continue;if(expectation.renderProcess.pid!==rendererHelper.pid)continue;if(expectation.fmpEvent!==undefined){addFirstMeaningfulPaintSample(firstMeaningfulPaintSamples,rendererHelper,expectation.navigationStart,expectation.fmpEvent,expectation.url);addFirstMeaningfulPaintCpuTimeSample(firstMeaningfulPaintCpuTimeSamples,rendererHelper,expectation.navigationStart,expectation.fmpEvent,expectation.url);}
+function collectLoadingMetricsForRenderer(rendererHelper){const frameToNavStartEvents=EventFinderUtils.getSortedMainThreadEventsByFrame(rendererHelper,'navigationStart','blink.user_timing');const firstPaintSamples=collectTimeToEvent('loading','firstPaint',rendererHelper,frameToNavStartEvents);const firstContentfulPaintSamples=collectTimeToEvent('loading','firstContentfulPaint',rendererHelper,frameToNavStartEvents);const onLoadSamples=collectTimeToEvent('blink.user_timing','loadEventStart',rendererHelper,frameToNavStartEvents);return{frameToNavStartEvents,firstPaintSamples,firstContentfulPaintSamples,onLoadSamples,};}
+function collectMetricsFromLoadExpectations(model,chromeHelper){const interactiveSamples=[];const firstCpuIdleSamples=[];const firstMeaningfulPaintSamples=[];const firstMeaningfulPaintCpuTimeSamples=[];for(const expectation of model.userModel.expectations){if(!(expectation instanceof tr.model.um.LoadExpectation))continue;if(tr.e.chrome.CHROME_INTERNAL_URLS.includes(expectation.url)){continue;}
+const rendererHelper=chromeHelper.rendererHelpers[expectation.renderProcess.pid];if(expectation.fmpEvent!==undefined){addFirstMeaningfulPaintSample(firstMeaningfulPaintSamples,rendererHelper,expectation.navigationStart,expectation.fmpEvent,expectation.url);addFirstMeaningfulPaintCpuTimeSample(firstMeaningfulPaintCpuTimeSamples,rendererHelper,expectation.navigationStart,expectation.fmpEvent,expectation.url);}
 if(expectation.firstCpuIdleTime!==undefined){firstCpuIdleSamples.push(decorateInteractivitySampleWithDiagnostics_(rendererHelper,expectation.firstCpuIdleTime,expectation.navigationStart,expectation.fmpEvent.start,expectation.domContentLoadedEndEvent.start,expectation.url));}
 if(expectation.timeToInteractive!==undefined){interactiveSamples.push(decorateInteractivitySampleWithDiagnostics_(rendererHelper,expectation.timeToInteractive,expectation.navigationStart,expectation.fmpEvent.start,expectation.domContentLoadedEndEvent.start,expectation.url));}}
-return{firstPaintSamples,firstContentfulPaintSamples,onLoadSamples,firstMeaningfulPaintSamples,firstMeaningfulPaintCpuTimeSamples,firstCpuIdleSamples,interactiveSamples,};}
+return{firstMeaningfulPaintSamples,firstMeaningfulPaintCpuTimeSamples,firstCpuIdleSamples,interactiveSamples,};}
 function addSamplesToHistogram(samples,histogram,histograms){for(const sample of samples){histogram.addSample(sample.value,sample.diagnostics);if(histogram.name!=='timeToFirstContentfulPaint')continue;if(!sample.breakdownTree)continue;for(const[category,breakdown]of Object.entries(sample.breakdownTree)){const relatedName=`${histogram.name}:${category}`;let relatedHist=histograms.getHistogramsNamed(relatedName)[0];if(!relatedHist){relatedHist=histograms.createHistogram(relatedName,histogram.unit,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,summaryOptions:{count:false,max:false,min:false,sum:false,},});let relatedNames=histogram.diagnostics.get('breakdown');if(!relatedNames){relatedNames=new tr.v.d.RelatedNameMap();histogram.diagnostics.set('breakdown',relatedNames);}
 relatedNames.set(category,relatedName);}
 relatedHist.addSample(breakdown.total,{breakdown:tr.v.d.Breakdown.fromEntries(Object.entries(breakdown.events)),});}}}
-function loadingMetric(histograms,model){const firstPaintHistogram=histograms.createHistogram('timeToFirstPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to first paint',summaryOptions:SUMMARY_OPTIONS,});const firstContentfulPaintHistogram=histograms.createHistogram('timeToFirstContentfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to first contentful paint',summaryOptions:SUMMARY_OPTIONS,});const onLoadHistogram=histograms.createHistogram('timeToOnload',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to onload. '+'This is temporary metric used for PCv1/v2 sanity checking',summaryOptions:SUMMARY_OPTIONS,});const firstMeaningfulPaintHistogram=histograms.createHistogram('timeToFirstMeaningfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to first meaningful paint',summaryOptions:SUMMARY_OPTIONS,});const firstMeaningfulPaintCpuTimeHistogram=histograms.createHistogram('cpuTimeToFirstMeaningfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'CPU time to first meaningful paint',summaryOptions:SUMMARY_OPTIONS,});const timeToInteractiveHistogram=histograms.createHistogram('timeToInteractive',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time to Interactive',summaryOptions:SUMMARY_OPTIONS,});const timeToFirstCpuIdleHistogram=histograms.createHistogram('timeToFirstCpuIdle',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time to First CPU Idle',summaryOptions:SUMMARY_OPTIONS,});const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);for(const pid in chromeHelper.rendererHelpers){const rendererHelper=chromeHelper.rendererHelpers[pid];if(rendererHelper.isChromeTracingUI)continue;const samplesSet=collectLoadingMetricsForRenderer(rendererHelper);addSamplesToHistogram(samplesSet.firstPaintSamples,firstPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstContentfulPaintSamples,firstContentfulPaintHistogram,histograms);addSamplesToHistogram(samplesSet.onLoadSamples,onLoadHistogram,histograms);addSamplesToHistogram(samplesSet.firstMeaningfulPaintSamples,firstMeaningfulPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstMeaningfulPaintCpuTimeSamples,firstMeaningfulPaintCpuTimeHistogram,histograms);addSamplesToHistogram(samplesSet.interactiveSamples,timeToInteractiveHistogram,histograms);addSamplesToHistogram(samplesSet.firstCpuIdleSamples,timeToFirstCpuIdleHistogram,histograms);}}
-tr.metrics.MetricRegistry.register(loadingMetric);return{loadingMetric,getNetworkEventsInRange,collectLoadingMetricsForRenderer,};});'use strict';tr.exportTo('tr.metrics',function(){const SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION_BIN_BOUNDARY=tr.v.HistogramBinBoundaries.createExponential(1,1000,50);function spaNavigationMetric(histograms,model){const histogram=new tr.v.Histogram('spaNavigationStartToFpDuration',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION_BIN_BOUNDARY);histogram.description='Latency between the input event causing'+' a SPA navigation and the first paint event after it';histogram.customizeSummaryOptions({count:false,sum:false,});const modelHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!modelHelper){return;}
+function loadingMetric(histograms,model){const firstPaintHistogram=histograms.createHistogram('timeToFirstPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to first paint',summaryOptions:SUMMARY_OPTIONS,});const firstContentfulPaintHistogram=histograms.createHistogram('timeToFirstContentfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to first contentful paint',summaryOptions:SUMMARY_OPTIONS,});const onLoadHistogram=histograms.createHistogram('timeToOnload',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to onload. '+'This is temporary metric used for PCv1/v2 sanity checking',summaryOptions:SUMMARY_OPTIONS,});const firstMeaningfulPaintHistogram=histograms.createHistogram('timeToFirstMeaningfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'time to first meaningful paint',summaryOptions:SUMMARY_OPTIONS,});const firstMeaningfulPaintCpuTimeHistogram=histograms.createHistogram('cpuTimeToFirstMeaningfulPaint',timeDurationInMs_smallerIsBetter,[],{binBoundaries:LOADING_METRIC_BOUNDARIES,description:'CPU time to first meaningful paint',summaryOptions:SUMMARY_OPTIONS,});const timeToInteractiveHistogram=histograms.createHistogram('timeToInteractive',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time to Interactive',summaryOptions:SUMMARY_OPTIONS,});const timeToFirstCpuIdleHistogram=histograms.createHistogram('timeToFirstCpuIdle',timeDurationInMs_smallerIsBetter,[],{binBoundaries:TIME_TO_INTERACTIVE_BOUNDARIES,description:'Time to First CPU Idle',summaryOptions:SUMMARY_OPTIONS,});const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);for(const pid in chromeHelper.rendererHelpers){const rendererHelper=chromeHelper.rendererHelpers[pid];if(rendererHelper.isChromeTracingUI)continue;const samplesSet=collectLoadingMetricsForRenderer(rendererHelper);addSamplesToHistogram(samplesSet.firstPaintSamples,firstPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstContentfulPaintSamples,firstContentfulPaintHistogram,histograms);addSamplesToHistogram(samplesSet.onLoadSamples,onLoadHistogram,histograms);}
+const samplesSet=collectMetricsFromLoadExpectations(model,chromeHelper);addSamplesToHistogram(samplesSet.firstMeaningfulPaintSamples,firstMeaningfulPaintHistogram,histograms);addSamplesToHistogram(samplesSet.firstMeaningfulPaintCpuTimeSamples,firstMeaningfulPaintCpuTimeHistogram,histograms);addSamplesToHistogram(samplesSet.interactiveSamples,timeToInteractiveHistogram,histograms);addSamplesToHistogram(samplesSet.firstCpuIdleSamples,timeToFirstCpuIdleHistogram,histograms);}
+tr.metrics.MetricRegistry.register(loadingMetric);return{loadingMetric,getNetworkEventsInRange,};});'use strict';tr.exportTo('tr.metrics',function(){const SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION_BIN_BOUNDARY=tr.v.HistogramBinBoundaries.createExponential(1,1000,50);function spaNavigationMetric(histograms,model){const histogram=new tr.v.Histogram('spaNavigationStartToFpDuration',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,SPA_NAVIGATION_START_TO_FIRST_PAINT_DURATION_BIN_BOUNDARY);histogram.description='Latency between the input event causing'+' a SPA navigation and the first paint event after it';histogram.customizeSummaryOptions({count:false,sum:false,});const modelHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!modelHelper){return;}
 const rendererHelpers=modelHelper.rendererHelpers;if(!rendererHelpers){return;}
 const browserHelper=modelHelper.browserHelper;for(const rendererHelper of Object.values(rendererHelpers)){const spaNavigations=tr.metrics.findSpaNavigationsOnRenderer(rendererHelper,browserHelper);for(const spaNav of spaNavigations){let beginTs=0;if(spaNav.navStartCandidates.inputLatencyAsyncSlice){const beginData=spaNav.navStartCandidates.inputLatencyAsyncSlice.args.data;beginTs=model.convertTimestampToModelTime('traceEventClock',beginData.INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT.time);}else{beginTs=spaNav.navStartCandidates.goToIndexSlice.start;}
 const rangeOfInterest=tr.b.math.Range.fromExplicitRange(beginTs,spaNav.firstPaintEvent.start);const networkEvents=tr.metrics.sh.getNetworkEventsInRange(rendererHelper.process,rangeOfInterest);const breakdownDict=tr.metrics.sh.generateWallClockTimeBreakdownTree(rendererHelper.mainThread,networkEvents,rangeOfInterest);const breakdownDiagnostic=new tr.v.d.Breakdown();breakdownDiagnostic.colorScheme=tr.v.d.COLOR_SCHEME_CHROME_USER_FRIENDLY_CATEGORY_DRIVER;for(const label in breakdownDict){breakdownDiagnostic.set(label,parseInt(breakdownDict[label].total*1e3)/1e3);}
@@ -8067,7 +8173,7 @@
 domains[i].toLowerCase()+'_to_'+domains[j].toLowerCase(),tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,LATENCY_BOUNDS);hist.customizeSummaryOptions({avg:true,count:false,max:false,min:false,std:false,sum:false,});hist.description='Clock sync latency for domain '+domains[i]+' to domain '+domains[j];hist.addSample(latency);values.addHistogram(hist);}}}
 tr.metrics.MetricRegistry.register(clockSyncLatencyMetric);return{clockSyncLatencyMetric,};});'use strict';tr.exportTo('tr.metrics.sh',function(){const CPU_TIME_PERCENTAGE_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(0.01,50,200);function cpuTimeMetric(histograms,model,opt_options){let rangeOfInterest=model.bounds;if(opt_options&&opt_options.rangeOfInterest){rangeOfInterest=opt_options.rangeOfInterest;}else{const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(chromeHelper){const chromeBounds=chromeHelper.chromeBounds;if(chromeBounds){rangeOfInterest=chromeBounds;}}}
 let allProcessCpuTime=0;for(const pid in model.processes){const process=model.processes[pid];if(tr.model.helpers.ChromeRendererHelper.isTracingProcess(process)){continue;}
-let processCpuTime=0;for(const tid in process.threads){const thread=process.threads[tid];let threadCpuTime=0;thread.sliceGroup.topLevelSlices.forEach(function(slice){if(slice.duration===0)return;if(!slice.cpuDuration)return;const sliceRange=tr.b.math.Range.fromExplicitRange(slice.start,slice.end);const intersection=rangeOfInterest.findIntersection(sliceRange);const fractionOfSliceInsideRangeOfInterest=intersection.duration/slice.duration;threadCpuTime+=slice.cpuDuration*fractionOfSliceInsideRangeOfInterest;});processCpuTime+=threadCpuTime;}
+let processCpuTime=0;for(const tid in process.threads){const thread=process.threads[tid];processCpuTime+=thread.getCpuTimeForRange(rangeOfInterest);}
 allProcessCpuTime+=processCpuTime;}
 let normalizedAllProcessCpuTime=0;if(rangeOfInterest.duration>0){normalizedAllProcessCpuTime=allProcessCpuTime/rangeOfInterest.duration;}
 const unit=tr.b.Unit.byName.normalizedPercentage_smallerIsBetter;const 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.customizeSummaryOptions({avg:true,count:false,max:false,min:false,std:false,sum:false});cpuTimeHist.addSample(normalizedAllProcessCpuTime);histograms.addHistogram(cpuTimeHist);}
@@ -8090,7 +8196,7 @@
 const benchmarks=hist.diagnostics.get(tr.v.d.RESERVED_NAMES.BENCHMARKS);const start=hist.diagnostics.get(tr.v.d.RESERVED_NAMES.BENCHMARK_START);if(benchmarks===undefined){if(start===undefined)return'Value';return start.toString();}
 const benchmarksStr=Array.from(benchmarks).join('\n');if(start===undefined)return benchmarksStr;return benchmarksStr+'\n'+start.toString();});class GenericSetGrouping extends HistogramGrouping{constructor(name){super(name,undefined);this.callback_=this.compute_.bind(this);}
 compute_(hist){const diag=hist.diagnostics.get(this.key);if(diag===undefined)return'';const parts=Array.from(diag);parts.sort();return parts.join(',');}}
-GenericSetGrouping.NAMES=[tr.v.d.RESERVED_NAMES.ARCHITECTURES,tr.v.d.RESERVED_NAMES.BENCHMARKS,tr.v.d.RESERVED_NAMES.BOTS,tr.v.d.RESERVED_NAMES.BUILDS,tr.v.d.RESERVED_NAMES.DEVICE_IDS,tr.v.d.RESERVED_NAMES.MASTERS,tr.v.d.RESERVED_NAMES.MEMORY_AMOUNTS,tr.v.d.RESERVED_NAMES.OS_NAMES,tr.v.d.RESERVED_NAMES.OS_VERSIONS,tr.v.d.RESERVED_NAMES.PRODUCT_VERSIONS,tr.v.d.RESERVED_NAMES.STORIES,tr.v.d.RESERVED_NAMES.STORYSET_REPEATS,tr.v.d.RESERVED_NAMES.STORY_TAGS,];for(const name of GenericSetGrouping.NAMES){new GenericSetGrouping(name);}
+GenericSetGrouping.NAMES=[tr.v.d.RESERVED_NAMES.ARCHITECTURES,tr.v.d.RESERVED_NAMES.BENCHMARKS,tr.v.d.RESERVED_NAMES.BOTS,tr.v.d.RESERVED_NAMES.BUILDS,tr.v.d.RESERVED_NAMES.DEVICE_IDS,tr.v.d.RESERVED_NAMES.MASTERS,tr.v.d.RESERVED_NAMES.MEMORY_AMOUNTS,tr.v.d.RESERVED_NAMES.OS_NAMES,tr.v.d.RESERVED_NAMES.OS_VERSIONS,tr.v.d.RESERVED_NAMES.PRODUCT_VERSIONS,tr.v.d.RESERVED_NAMES.STORIES,tr.v.d.RESERVED_NAMES.STORYSET_REPEATS,tr.v.d.RESERVED_NAMES.STORY_TAGS,tr.v.d.RESERVED_NAMES.TEST_PATH,];for(const name of GenericSetGrouping.NAMES){new GenericSetGrouping(name);}
 class DateRangeGrouping extends HistogramGrouping{constructor(name){super(name,undefined);this.callback_=this.compute_.bind(this);}
 compute_(hist){const diag=hist.diagnostics.get(this.key);if(diag===undefined)return'';return diag.toString();}}
 DateRangeGrouping.NAMES=[tr.v.d.RESERVED_NAMES.BENCHMARK_START,tr.v.d.RESERVED_NAMES.TRACE_START,];for(const name of DateRangeGrouping.NAMES){new DateRangeGrouping(name);}
@@ -8125,7 +8231,9 @@
 for(const[key,group]of groupedHistograms){groupedHistograms.set(key,recurse(group,level+1));}
 return groupedHistograms;}
 return recurse([...this],0);}
-deduplicateDiagnostics(){const namesToCandidates=new Map();const diagnosticsToHistograms=new Map();for(const hist of this){for(const[name,candidate]of hist.diagnostics){if(candidate.equals===undefined){this.sharedDiagnosticsByGuid_.set(candidate.guid,candidate);continue;}
+deduplicateDiagnostics(){const namesToCandidates=new Map();const diagnosticsToHistograms=new Map();const keysToDiagnostics=new Map();for(const hist of this){for(const[name,candidate]of hist.diagnostics){if(candidate.equals===undefined){this.sharedDiagnosticsByGuid_.set(candidate.guid,candidate);continue;}
+const hashKey=candidate.hashKey;if(candidate.hashKey!==undefined){if(keysToDiagnostics.has(hashKey)){hist.diagnostics.set(name,keysToDiagnostics.get(hashKey));}else{keysToDiagnostics.set(hashKey,candidate);this.sharedDiagnosticsByGuid_.set(candidate.guid,candidate);}
+continue;}
 if(diagnosticsToHistograms.get(candidate)===undefined){diagnosticsToHistograms.set(candidate,[hist]);}else{diagnosticsToHistograms.get(candidate).push(hist);}
 if(!namesToCandidates.has(name)){namesToCandidates.set(name,new Set());}
 namesToCandidates.get(name).add(candidate);}}
@@ -8140,7 +8248,10 @@
 return{HistogramSet,};});'use strict';tr.exportTo('tr.e.chrome',function(){function hasTitleAndCategory(event,title,category){return event.title===title&&event.category&&tr.b.getCategoryParts(event.category).includes(category);}
 function getNavStartTimestamps(rendererHelper){const navStartTimestamps=[];for(const e of rendererHelper.mainThread.sliceGroup.childEvents()){if(hasTitleAndCategory(e,'navigationStart','blink.user_timing')){navStartTimestamps.push(e.start);}}
 return navStartTimestamps;}
-function getInteractiveTimestamps(model){const interactiveTimestampsMap=new Map();const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){const timestamps=[];interactiveTimestampsMap.set(rendererHelper.pid,timestamps);const samples=tr.metrics.sh.collectLoadingMetricsForRenderer(rendererHelper).interactiveSamples;for(const sample of samples){timestamps.push(tr.b.getOnlyElement(sample.diagnostics.get('Navigation infos')).eventTimestamp);}}
+function getInteractiveTimestamps(model){const interactiveTimestampsMap=new Map();const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){const timestamps=[];interactiveTimestampsMap.set(rendererHelper.pid,timestamps);}
+for(const expectation of model.userModel.expectations){if(!(expectation instanceof tr.model.um.LoadExpectation))continue;if(tr.e.chrome.CHROME_INTERNAL_URLS.includes(expectation.url)){continue;}
+if(expectation.timeToInteractive===undefined)continue;if(interactiveTimestampsMap.get(expectation.renderProcess.pid)===undefined){interactiveTimestampsMap.set(expectation.renderProcess.pid,[]);}
+interactiveTimestampsMap.get(expectation.renderProcess.pid).push(expectation.timeToInteractive);}
 return interactiveTimestampsMap;}
 function getPostInteractiveTaskWindows(interactiveTimestamps,navStartTimestamps,traceEndTimestamp){let navStartTsIndex=0;let lastTaskWindowEndTs=undefined;const taskWindows=[];for(const currTTI of interactiveTimestamps){while(navStartTsIndex<navStartTimestamps.length&&navStartTimestamps[navStartTsIndex]<currTTI){navStartTsIndex++;}
 const taskWindowEndTs=navStartTsIndex<navStartTimestamps.length?navStartTimestamps[navStartTsIndex]:traceEndTimestamp;if(taskWindowEndTs===lastTaskWindowEndTs){throw Error('Encountered two consecutive interactive timestamps '+'with no navigationStart between them. '+'PostInteractiveTaskWindow is not well defined in this case.');}
@@ -8179,7 +8290,7 @@
 totalEqtHistogram.diagnostics.set('v8',breakdownForTotal);interactiveEqtHistogram.diagnostics.set('v8',breakdownForInteractive);}
 function getV8EventNamesWithTaskExtractors_(getEventTimes,cpuMetrics){function durationOfTopmostSubSlices(slice,predicate,excludePredicate){let duration=0;for(const sub of slice.findTopmostSlicesRelativeToThisSlice(predicate)){duration+=getEventTimes(sub).duration;if(excludePredicate!==null&&excludePredicate!==undefined){duration-=durationOfTopmostSubSlices(sub,excludePredicate);}}
 return duration;}
-function taskExtractor(predicate,excludePredicate){return function(rendererHelper){const slices=rendererHelper.mainThread.sliceGroup.topLevelSlices;const result=[];for(const slice of slices){const times=getEventTimes(slice);if(times.duration>0&&!containsForcedGC_(slice)){const duration=durationOfTopmostSubSlices(slice,predicate,excludePredicate);result.push({start:times.start,end:times.start+duration});}}
+function taskExtractor(predicate,excludePredicate){return function(rendererHelper){const slices=tr.e.chrome.EventFinderUtils.findToplevelSchedulerTasks(rendererHelper.mainThread);const result=[];for(const slice of slices){const times=getEventTimes(slice);if(times.duration>0&&!containsForcedGC_(slice)){const duration=durationOfTopmostSubSlices(slice,predicate,excludePredicate);result.push({start:times.start,end:times.start+duration});}}
 return result;};}
 return new Map([['v8',taskExtractor(tr.metrics.v8.utils.isV8Event)],['v8:execute',taskExtractor(tr.metrics.v8.utils.isV8ExecuteEvent)],['v8:gc',taskExtractor(tr.metrics.v8.utils.isGarbageCollectionEvent)],['v8:gc:full-mark-compactor',taskExtractor(tr.metrics.v8.utils.isFullMarkCompactorEvent)],['v8:gc:incremental-marking',taskExtractor(tr.metrics.v8.utils.isIncrementalMarkingEvent)],['v8:gc:latency-mark-compactor',taskExtractor(tr.metrics.v8.utils.isLatencyMarkCompactorEvent)],['v8:gc:memory-mark-compactor',taskExtractor(tr.metrics.v8.utils.isMemoryMarkCompactorEvent)],['v8:gc:scavenger',taskExtractor(tr.metrics.v8.utils.isScavengerEvent)]]);}
 function extractTaskRCS(getEventTimes,predicate,rendererHelper){const result=[];for(const topSlice of
@@ -8232,14 +8343,12 @@
 if(i+maxLength-1>right){left=i;right=i+maxLength-1;}
 z[i]=maxLength;}
 return z;}
-return{MultiDimensionalViewBuilder,MultiDimensionalViewNode,RecursionDepthTracker,zFunction,};});'use strict';tr.exportTo('tr.e.chrome',function(){class CpuTime{static getCpuTimeForThread(thread,range){let totalCpuTime=0;tr.b.iterateOverIntersectingIntervals(thread.sliceGroup.topLevelSlices,slice=>slice.start,slice=>slice.end,range.min,range.max,slice=>{if(slice.duration===0)return;if(!slice.cpuDuration)return;const intersection=range.findIntersection(slice.range);const fractionOfSliceInsideRangeOfInterest=intersection.duration/slice.duration;totalCpuTime+=slice.cpuDuration*fractionOfSliceInsideRangeOfInterest;});return totalCpuTime;}
-static getStageToInitiatorToSegmentBounds(segments,rangeOfInterest){const stageToInitiatorToRanges=new Map();stageToInitiatorToRanges.set('all_stages',new Map([['all_initiators',new Set()]]));const allRanges=stageToInitiatorToRanges.get('all_stages').get('all_initiators');for(const segment of segments){if(!rangeOfInterest.intersectsRangeInclusive(segment.range))continue;const intersectingRange=rangeOfInterest.findIntersection(segment.range);allRanges.add(intersectingRange);for(const expectation of segment.expectations){const stageTitle=expectation.stageTitle;if(!stageToInitiatorToRanges.has(stageTitle)){stageToInitiatorToRanges.set(stageTitle,new Map([['all_initiators',new Set()]]));}
+return{MultiDimensionalViewBuilder,MultiDimensionalViewNode,RecursionDepthTracker,zFunction,};});'use strict';tr.exportTo('tr.e.chrome',function(){class CpuTime{static getStageToInitiatorToSegmentBounds(segments,rangeOfInterest){const stageToInitiatorToRanges=new Map();stageToInitiatorToRanges.set('all_stages',new Map([['all_initiators',new Set()]]));const allRanges=stageToInitiatorToRanges.get('all_stages').get('all_initiators');for(const segment of segments){if(!rangeOfInterest.intersectsRangeInclusive(segment.range))continue;const intersectingRange=rangeOfInterest.findIntersection(segment.range);allRanges.add(intersectingRange);for(const expectation of segment.expectations){const stageTitle=expectation.stageTitle;if(!stageToInitiatorToRanges.has(stageTitle)){stageToInitiatorToRanges.set(stageTitle,new Map([['all_initiators',new Set()]]));}
 const initiatorToRanges=stageToInitiatorToRanges.get(stageTitle);initiatorToRanges.get('all_initiators').add(intersectingRange);const initiatorType=expectation.initiatorType;if(initiatorType){if(!initiatorToRanges.has(initiatorType)){initiatorToRanges.set(initiatorType,new Set());}
 initiatorToRanges.get(initiatorType).add(intersectingRange);}}}
 return stageToInitiatorToRanges;}
-static computeCpuTimesForRanges_(ranges,thread){const rangeToCpuTime=new Map();for(const range of ranges){rangeToCpuTime.set(range,CpuTime.getCpuTimeForThread(thread,range));}
-return rangeToCpuTime;}
-static constructMultiDimensionalView(model,rangeOfInterest){const mdvBuilder=new tr.b.MultiDimensionalViewBuilder(3,2);const stageToInitiatorToRanges=CpuTime.getStageToInitiatorToSegmentBounds(model.userModel.segments,rangeOfInterest);const allSegmentBoundsInRange=stageToInitiatorToRanges.get('all_stages').get('all_initiators');for(const[pid,process]of Object.entries(model.processes)){const processType=tr.e.chrome.chrome_processes.canonicalizeProcessName(process.name);for(const[tid,thread]of Object.entries(process.threads)){const rangeToCpuTime=CpuTime.computeCpuTimesForRanges_(allSegmentBoundsInRange,thread);for(const[stage,initiatorToRanges]of stageToInitiatorToRanges){for(const[initiator,ranges]of initiatorToRanges){const cpuTime=tr.b.math.Statistics.sum(ranges,range=>rangeToCpuTime.get(range));const duration=tr.b.math.Statistics.sum(ranges,range=>range.duration);const cpuTimePerSecond=cpuTime/duration;mdvBuilder.addPath([[processType],[thread.type],[stage,initiator]],[cpuTimePerSecond,cpuTime],tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL);}}}}
+static constructMultiDimensionalView(model,rangeOfInterest){const mdvBuilder=new tr.b.MultiDimensionalViewBuilder(3,2);const stageToInitiatorToRanges=CpuTime.getStageToInitiatorToSegmentBounds(model.userModel.segments,rangeOfInterest);const allSegmentBoundsInRange=stageToInitiatorToRanges.get('all_stages').get('all_initiators');for(const[pid,process]of Object.entries(model.processes)){const processType=tr.e.chrome.chrome_processes.canonicalizeProcessName(process.name);for(const[tid,thread]of Object.entries(process.threads)){const rangeToCpuTime=new Map();for(const range of allSegmentBoundsInRange){rangeToCpuTime.set(range,thread.getCpuTimeForRange(range));}
+for(const[stage,initiatorToRanges]of stageToInitiatorToRanges){for(const[initiator,ranges]of initiatorToRanges){const cpuTime=tr.b.math.Statistics.sum(ranges,range=>rangeToCpuTime.get(range));const duration=tr.b.math.Statistics.sum(ranges,range=>range.duration);const cpuTimePerSecond=cpuTime/duration;mdvBuilder.addPath([[processType],[thread.type],[stage,initiator]],[cpuTimePerSecond,cpuTime],tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL);}}}}
 return mdvBuilder.buildTopDownTreeView();}}
 return{CpuTime,};});'use strict';tr.exportTo('tr.metrics.sh',function(){const CPU_PERCENTAGE_UNIT=tr.b.Unit.byName.normalizedPercentage_smallerIsBetter;const CPU_TIME_UNIT=tr.b.Unit.byName.timeDurationInMs_smallerIsBetter;function clonePath_(previousPath){return previousPath.map(subPath=>subPath.map(x=>x));}
 function decodePath_(path){return{processType:path[0][0],threadType:path[1][0],railStage:path[2][0],initiatorType:path[2][1]};}
@@ -8282,7 +8391,7 @@
 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(' ');}
-const RESIDENT_SIZE={name:'resident_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'resident set size (RSS)');}};const PEAK_RESIDENT_SIZE={name:'peak_resident_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'peak resident set size');}};const PROPORTIONAL_RESIDENT_SIZE={name:'proportional_resident_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'proportional resident size (PSS)');}};const PRIVATE_DIRTY_SIZE={name:'private_dirty_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'private dirty size');}};const PRIVATE_FOOTPRINT_SIZE={name:'private_footprint_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'private footprint size');}};function buildOsValueDescriptionPrefix(componentPath,processName,userFriendlyPropertyName){if(componentPath.length>2){throw new Error('OS value component path for \''+
+const RESIDENT_SIZE={name:'resident_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'resident set size (RSS)');}};const PEAK_RESIDENT_SIZE={name:'peak_resident_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'peak resident set size');}};const PROPORTIONAL_RESIDENT_SIZE={name:'proportional_resident_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'proportional resident size (PSS)');}};const PRIVATE_DIRTY_SIZE={name:'private_dirty_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'private dirty size');}};const PRIVATE_FOOTPRINT_SIZE={name:'private_footprint_size',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'private footprint size');}};const NATIVE_LIBRARY_PRIVATE_CLEAN_RESIDENT={name:'native_library_private_clean_resident',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'native library private clean resident size');}};const NATIVE_LIBRARY_SHARED_CLEAN_RESIDENT={name:'native_library_shared_clean_resident',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'native library shared clean resident size');}};const NATIVE_LIBRARY_PROPORTIONAL_RESIDENT={name:'native_library_proportional_resident',unit:sizeInBytes_smallerIsBetter,buildDescriptionPrefix(componentPath,processName){return buildOsValueDescriptionPrefix(componentPath,processName,'native library proportional resident size');}};function buildOsValueDescriptionPrefix(componentPath,processName,userFriendlyPropertyName){if(componentPath.length>2){throw new Error('OS value component path for \''+
 userFriendlyPropertyName+'\' too long: '+componentPath.join(':'));}
 const 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){const userFriendlyComponentName=SYSTEM_VALUE_COMPONENTS[componentPath[1]].userFriendlyName;if(userFriendlyComponentName===undefined){throw new Error('System value sub-component for \''+
@@ -8295,7 +8404,9 @@
 componentPath.join(':'));}}else{nameParts.push('reported by the OS for');}
 nameParts.push(convertProcessNameToUserFriendlyName(processName));return nameParts.join(' ');}
 function addDetailedMemoryDumpValues(browserNameToGlobalDumps,values){addMemoryDumpValues(browserNameToGlobalDumps,g=>g.levelOfDetail===DETAILED,function(processDump,addProcessScalar){for(const[componentName,componentSpec]of
-Object.entries(SYSTEM_VALUE_COMPONENTS)){const node=getDescendantVmRegionClassificationNode(processDump.vmRegions,componentSpec.classificationPath);const componentPath=['system_memory'];if(componentName)componentPath.push(componentName);addProcessScalar({source:'reported_by_os',component:componentPath,property:PROPORTIONAL_RESIDENT_SIZE,value:node===undefined?0:(node.byteStats.proportionalResident||0)});addProcessScalar({source:'reported_by_os',component:componentPath,property:PRIVATE_DIRTY_SIZE,value:node===undefined?0:(node.byteStats.privateDirtyResident||0)});}
+Object.entries(SYSTEM_VALUE_COMPONENTS)){const node=getDescendantVmRegionClassificationNode(processDump.vmRegions,componentSpec.classificationPath);const componentPath=['system_memory'];if(componentName)componentPath.push(componentName);addProcessScalar({source:'reported_by_os',component:componentPath,property:PROPORTIONAL_RESIDENT_SIZE,value:node===undefined?0:(node.byteStats.proportionalResident||0)});addProcessScalar({source:'reported_by_os',component:componentPath,property:PRIVATE_DIRTY_SIZE,value:node===undefined?0:(node.byteStats.privateDirtyResident||0)});if(node){if(node.byteStats.nativeLibraryPrivateCleanResident){addProcessScalar({source:'reported_by_os',component:componentPath,property:NATIVE_LIBRARY_PRIVATE_CLEAN_RESIDENT,value:node===undefined?0:(node.byteStats.nativeLibraryPrivateCleanResident||0)});}
+if(node.byteStats.nativeLibrarySharedCleanResident){addProcessScalar({source:'reported_by_os',component:componentPath,property:NATIVE_LIBRARY_SHARED_CLEAN_RESIDENT,value:node===undefined?0:(node.byteStats.nativeLibrarySharedCleanResident||0)});}
+if(node.byteStats.nativeLibraryProportionalResident){addProcessScalar({source:'reported_by_os',component:componentPath,property:NATIVE_LIBRARY_PROPORTIONAL_RESIDENT,value:node===undefined?0:(node.byteStats.nativeLibraryProportionalResident||0)});}}}
 const memtrackDump=processDump.getMemoryAllocatorDumpByFullName('gpu/android_memtrack');if(memtrackDump!==undefined){memtrackDump.children.forEach(function(memtrackChildDump){addProcessScalar({source:'reported_by_os',component:['gpu_memory',memtrackChildDump.name],property:PROPORTIONAL_RESIDENT_SIZE,value:memtrackChildDump.numerics.memtrack_pss});});}},function(componentTree){},values);}
 const 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'},'stack':{classificationPath:['Stack'],userFriendlyName:'the thread stacks'}};function getDescendantVmRegionClassificationNode(node,path){for(let i=0;i<path.length;i++){if(node===undefined)break;node=node.children.find(c=>c.title===path[i]);}
 return node;}
@@ -8338,7 +8449,8 @@
 return{perSecond:false,energy:histograms.createHistogram(`${interval.name}:energy`,tr.b.Unit.byName.energyInJoules_smallerIsBetter,[],{description:`Energy consumed in ${interval.description}`,summaryOptions:{avg:false,count:false,max:true,min:true,std:false,sum:true,},}),};}
 function createHistograms_(data,interval,histograms){if(data.histograms[interval.name]===undefined){data.histograms[interval.name]=createEmptyHistogram_(interval,histograms);}
 if(data.histograms[interval.name].perSecond){for(const sample of data.model.device.powerSeries.getSamplesWithinRange(interval.bounds.min,interval.bounds.max)){data.histograms[interval.name].energy.addSample(sample.powerInW);}}else{const energyInJ=data.model.device.powerSeries.getEnergyConsumedInJ(interval.bounds.min,interval.bounds.max);data.histograms[interval.name].energy.addSample(energyInJ);}}
-function getNavigationTTIIntervals_(model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);const intervals=[];for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){const samples=tr.metrics.sh.collectLoadingMetricsForRenderer(rendererHelper).interactiveSamples;for(const sample of samples){const info=tr.b.getOnlyElement(sample.diagnostics.get('Navigation infos'));intervals.push(tr.b.math.Range.fromExplicitRange(info.navigationStartTime,info.eventTimestamp));}}
+function getNavigationTTIIntervals_(model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);const intervals=[];for(const expectation of model.userModel.expectations){if(!(expectation instanceof tr.model.um.LoadExpectation))continue;if(tr.e.chrome.CHROME_INTERNAL_URLS.includes(expectation.url)){continue;}
+if(expectation.timeToInteractive!==undefined){intervals.push(tr.b.math.Range.fromExplicitRange(expectation.navigationStart.start,expectation.timeToInteractive));}}
 return intervals.sort((x,y)=>x.min-y.min);}
 function*computeTimeIntervals_(model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);const powerSeries=model.device.powerSeries;if(powerSeries===undefined||powerSeries.samples.length===0){return;}
 yield{bounds:model.bounds,name:'story',description:'user story',perSecond:true};const chromeBounds=computeChromeBounds_(model);if(chromeBounds.isEmpty)return;const powerSeriesBoundsWithGracePeriod=tr.b.math.Range.fromExplicitRange(powerSeries.bounds.min-CHROME_POWER_GRACE_PERIOD_MS,powerSeries.bounds.max+CHROME_POWER_GRACE_PERIOD_MS);if(!powerSeriesBoundsWithGracePeriod.containsRangeExclusive(chromeBounds)){return;}
@@ -8367,14 +8479,18 @@
 tr.metrics.MetricRegistry.register(responsivenessMetric,{supportsRangeOfInterest:true,requiredCategories:['rail'],});return{responsivenessMetric,};});'use strict';tr.exportTo('tr.metrics.sh',function(){function webviewStartupMetric(histograms,model){const startupWallHist=new tr.v.Histogram('webview_startup_wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);startupWallHist.description='WebView startup wall time';const startupCPUHist=new tr.v.Histogram('webview_startup_cpu_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);startupCPUHist.description='WebView startup CPU time';const loadWallHist=new tr.v.Histogram('webview_url_load_wall_time',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter);loadWallHist.description='WebView blank URL load wall time';const 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(const 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);}}
 histograms.addHistogram(startupWallHist);histograms.addHistogram(startupCPUHist);histograms.addHistogram(loadWallHist);histograms.addHistogram(loadCPUHist);}
-tr.metrics.MetricRegistry.register(webviewStartupMetric);return{webviewStartupMetric,};});'use strict';tr.exportTo('tr.metrics.tabs',function(){function tabsMetric(histograms,model,opt_options){const thread=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper).browserHelper.mainThread;const tabSwitchLatencies=[];if(thread){for(const slice of thread.asyncSliceGroup.slices){if(slice.title==='TabSwitching::Latency'){tabSwitchLatencies.push(slice.duration);}}}
-histograms.createHistogram('tab_switching_latency',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,tabSwitchLatencies,{description:'Tab switching time in ms',summaryOptions:{sum:false}});}
+tr.metrics.MetricRegistry.register(webviewStartupMetric);return{webviewStartupMetric,};});'use strict';tr.exportTo('tr.metrics.tabs',function(){function tabsMetric(histograms,model,opt_options){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);if(!chromeHelper){return;}
+const tabSwitchLatencies=[];const TAB_SWITCHING_SLICE_TITLE='TabSwitching::Latency';function extractLatencyFromHelpers(helpers,legacy){for(const helper of helpers){if(!helper.mainThread){continue;}
+const thread=helper.mainThread;for(const slice of thread.asyncSliceGroup.slices){if(slice.title===TAB_SWITCHING_SLICE_TITLE&&(legacy||slice.args.latency)){tabSwitchLatencies.push(legacy?slice.duration:slice.args.latency);}}}}
+extractLatencyFromHelpers(chromeHelper.browserHelpers);extractLatencyFromHelpers(Object.values(chromeHelper.rendererHelpers));if(tabSwitchLatencies.length===0){extractLatencyFromHelpers(chromeHelper.browserHelpers,true);}
+histograms.createHistogram('tab_switching_latency',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,tabSwitchLatencies,{description:'Tab switching time in ms',summaryOptions:{sum:false}});const tabSwitchRequestDelays=[];const TAB_SWITCHING_REQUEST_TITLE='TabSwitchVisibilityRequest';for(const helper of chromeHelper.browserHelpers){if(!helper.mainThread)continue;for(const slice of helper.mainThread.asyncSliceGroup.slices){if(slice.title===TAB_SWITCHING_REQUEST_TITLE&&!slice.error){tabSwitchRequestDelays.push(slice.duration);}}}
+histograms.createHistogram('tab_switching_request_delay',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,tabSwitchRequestDelays,{description:'Delay before tab-request is made',summaryOptions:{sum:false}});}
 tr.metrics.MetricRegistry.register(tabsMetric,{supportsRangeOfInterest:false,});return{tabsMetric,};});'use strict';tr.exportTo('tr.metrics',function(){const MEMORY_INFRA_TRACING_CATEGORY='disabled-by-default-memory-infra';const TIME_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1e-3,1e5,30);const BYTE_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1,1e9,30);const COUNT_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1,1e5,30);const SUMMARY_OPTIONS=tr.v.Histogram.AVERAGE_ONLY_SUMMARY_OPTIONS;function addMemoryInfraHistograms(histograms,model,categoryNamesToTotalEventSizes){const memoryDumpCount=model.globalMemoryDumps.length;if(memoryDumpCount===0)return;let totalOverhead=0;let nonMemoryInfraThreadOverhead=0;const overheadByProvider={};for(const process of Object.values(model.processes)){for(const thread of Object.values(process.threads)){for(const slice of Object.values(thread.sliceGroup.slices)){if(slice.category!==MEMORY_INFRA_TRACING_CATEGORY)continue;totalOverhead+=slice.duration;if(thread.name!=='MemoryInfra'){nonMemoryInfraThreadOverhead+=slice.duration;}
 if(slice.args&&slice.args['dump_provider.name']){const providerName=slice.args['dump_provider.name'];let durationAndCount=overheadByProvider[providerName];if(durationAndCount===undefined){overheadByProvider[providerName]=durationAndCount={duration:0,count:0};}
 durationAndCount.duration+=slice.duration;durationAndCount.count++;}}}}
 histograms.createHistogram('memory_dump_cpu_overhead',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,totalOverhead/memoryDumpCount,{binBoundaries:TIME_BOUNDARIES,description:'Average CPU overhead on all threads per memory-infra dump',summaryOptions:SUMMARY_OPTIONS,});histograms.createHistogram('nonmemory_thread_memory_dump_cpu_overhead',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,nonMemoryInfraThreadOverhead/memoryDumpCount,{binBoundaries:TIME_BOUNDARIES,description:'Average CPU overhead on non-memory-infra threads '+'per memory-infra dump',summaryOptions:SUMMARY_OPTIONS,});for(const[providerName,overhead]of Object.entries(overheadByProvider)){histograms.createHistogram(`${providerName}_memory_dump_cpu_overhead`,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,overhead.duration/overhead.count,{binBoundaries:TIME_BOUNDARIES,description:`Average CPU overhead of ${providerName} per OnMemoryDump call`,summaryOptions:SUMMARY_OPTIONS,});}
 const memoryInfraEventsSize=categoryNamesToTotalEventSizes.get(MEMORY_INFRA_TRACING_CATEGORY);const memoryInfraTraceBytesValue=new tr.v.Histogram('total_memory_dump_size',tr.b.Unit.byName.sizeInBytes_smallerIsBetter,BYTE_BOUNDARIES);memoryInfraTraceBytesValue.description='Total trace size of memory-infra dumps in bytes';memoryInfraTraceBytesValue.customizeSummaryOptions(SUMMARY_OPTIONS);memoryInfraTraceBytesValue.addSample(memoryInfraEventsSize);histograms.addHistogram(memoryInfraTraceBytesValue);const traceBytesPerDumpValue=new tr.v.Histogram('memory_dump_size',tr.b.Unit.byName.sizeInBytes_smallerIsBetter,BYTE_BOUNDARIES);traceBytesPerDumpValue.description='Average trace size of memory-infra dumps in bytes';traceBytesPerDumpValue.customizeSummaryOptions(SUMMARY_OPTIONS);traceBytesPerDumpValue.addSample(memoryInfraEventsSize/memoryDumpCount);histograms.addHistogram(traceBytesPerDumpValue);}
-function tracingMetric(histograms,model){histograms.createHistogram('trace_import_duration',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,model.stats.traceImportDurationMs,{binBoundaries:TIME_BOUNDARIES,description:'Duration that trace viewer required to import the trace',summaryOptions:SUMMARY_OPTIONS,});if(!model.stats.hasEventSizesinBytes)return;const eventStats=model.stats.allTraceEventStatsInTimeIntervals;eventStats.sort((a,b)=>a.timeInterval-b.timeInterval);const totalTraceBytes=eventStats.reduce((a,b)=>a+b.totalEventSizeinBytes,0);let maxEventCountPerSec=0;let maxEventBytesPerSec=0;const INTERVALS_PER_SEC=Math.floor(1000/model.stats.TIME_INTERVAL_SIZE_IN_MS);let runningEventNumPerSec=0;let runningEventBytesPerSec=0;let start=0;let end=0;while(end<eventStats.length){runningEventNumPerSec+=eventStats[end].numEvents;runningEventBytesPerSec+=eventStats[end].totalEventSizeinBytes;end++;while((eventStats[end-1].timeInterval-
+function tracingMetric(histograms,model){if(!model.stats.hasEventSizesinBytes)return;const eventStats=model.stats.allTraceEventStatsInTimeIntervals;eventStats.sort((a,b)=>a.timeInterval-b.timeInterval);const totalTraceBytes=eventStats.reduce((a,b)=>a+b.totalEventSizeinBytes,0);let maxEventCountPerSec=0;let maxEventBytesPerSec=0;const INTERVALS_PER_SEC=Math.floor(1000/model.stats.TIME_INTERVAL_SIZE_IN_MS);let runningEventNumPerSec=0;let runningEventBytesPerSec=0;let start=0;let 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);}
 const stats=model.stats.allTraceEventStats;const categoryNamesToTotalEventSizes=(stats.reduce((map,stat)=>(map.set(stat.category,((map.get(stat.category)||0)+
@@ -8413,7 +8529,8 @@
 cpuDurationTotal+=event.cpuDuration;});const percentage=createPercentage(name+'_percentage_in_v8_execute',cpuDurationInV8Execute,cpuDurationTotal,percentage_smallerIsBetter);histograms.addHistogram(percentage);}
 return{gcMetric,WINDOW_SIZE_MS,};});'use strict';tr.exportTo('tr.metrics.v8',function(){const COUNT_CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(1,1000000,50);const DURATION_CUSTOM_BOUNDARIES=tr.v.HistogramBinBoundaries.createExponential(0.1,10000,50);const SUMMARY_OPTIONS={std:false,count:false,sum:false,min:false,max:false,};function computeDomContentLoadedTime_(model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);let domContentLoadedTime=0;for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){for(const ev of rendererHelper.mainThread.sliceGroup.childEvents()){if(ev.title==='domContentLoadedEventEnd'&&ev.start>domContentLoadedTime){domContentLoadedTime=ev.start;}}}
 return domContentLoadedTime;}
-function computeInteractiveTime_(model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);let interactiveTime=0;for(const rendererHelper of Object.values(chromeHelper.rendererHelpers)){const samples=tr.metrics.sh.collectLoadingMetricsForRenderer(rendererHelper).interactiveSamples;if(samples.length===0)continue;if(interactiveTime!==0)throw new Error('Too many navigations');const diagnostics=tr.b.getOnlyElement(samples).diagnostics;interactiveTime=tr.b.getOnlyElement(diagnostics.get('Navigation infos')).eventTimestamp;}
+function computeInteractiveTime_(model){const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);let interactiveTime=0;for(const expectation of model.userModel.expectations){if(tr.e.chrome.CHROME_INTERNAL_URLS.includes(expectation.url)){continue;}
+if(!(expectation instanceof tr.model.um.LoadExpectation))continue;if(expectation.timeToInteractive===undefined)continue;if(interactiveTime!==0)throw new Error('Too many navigations');interactiveTime=expectation.timeToInteractive;}
 return interactiveTime;}
 function convertMicroToMilli_(time){return tr.b.convertUnit(time,tr.b.UnitPrefixScale.METRIC.MICRO,tr.b.UnitPrefixScale.METRIC.MILLI);}
 function computeRuntimeStats(histograms,slices){const runtimeGroupCollection=new tr.e.v8.RuntimeStatsGroupCollection();runtimeGroupCollection.addSlices(slices);function addHistogramsForRuntimeGroup(runtimeGroup,optRelatedNameMaps){histograms.createHistogram(`${runtimeGroup.name}:duration`,tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,{value:convertMicroToMilli_(runtimeGroup.time),diagnostics:optRelatedNameMaps?{samples:optRelatedNameMaps.durationBreakdown}:{}},{binBoundaries:DURATION_CUSTOM_BOUNDARIES,summaryOptions:SUMMARY_OPTIONS,diagnostics:optRelatedNameMaps?{samples:optRelatedNameMaps.durationNames}:{}});histograms.createHistogram(`${runtimeGroup.name}:count`,tr.b.Unit.byName.count_smallerIsBetter,{value:runtimeGroup.count,diagnostics:optRelatedNameMaps?{samples:optRelatedNameMaps.countBreakdown}:{}},{binBoundaries:COUNT_CUSTOM_BOUNDARIES,summaryOptions:SUMMARY_OPTIONS,diagnostics:optRelatedNameMaps?{samples:optRelatedNameMaps.countNames}:{}});}
@@ -8434,19 +8551,26 @@
 addTotalCountHistogram(runtimeGroup.name,runtimeGroup.count,histograms,countRelatedHistsByGroupName);if(runtimeGroup.name==='Blink C++'){overallV8Count-=runtimeGroup.count;}}
 if(runtimeGroupCollection.blinkRCSGroupCollection.totalTime>0){const blinkRCSGroupCollection=runtimeGroupCollection.blinkRCSGroupCollection;for(const group of blinkRCSGroupCollection.runtimeGroups){addTotalDurationHistogram(group.name,group.time,histograms,durationRelatedHistsByGroupName);addTotalCountHistogram(group.name,group.count,histograms,countRelatedHistsByGroupName);}}
 addTotalDurationHistogram('V8-Only',overallV8Time,histograms,durationRelatedHistsByGroupName);addTotalCountHistogram('V8-Only',overallV8Count,histograms,countRelatedHistsByGroupName);}
-function runtimeStatsTotalMetric(histograms,model){const v8ThreadSlices=[...model.getDescendantEvents()].filter(event=>event instanceof tr.e.v8.V8ThreadSlice).sort((e1,e2)=>e1.start-e2.start);const v8SlicesBucketOnUEMap=new Map();for(const expectation of model.userModel.expectations){const slices=expectation.range.filterArray(v8ThreadSlices,event=>event.start);if(slices.length===0)continue;const lastSlice=slices[slices.length-1];if(!expectation.range.intersectsRangeExclusive(lastSlice.range)){slices.pop();}
+function runtimeStatsTotalMetric(histograms,model){const v8ThreadSlices=[...model.getDescendantEvents()].filter(event=>event instanceof tr.e.v8.V8ThreadSlice).sort((e1,e2)=>e1.start-e2.start);const v8SlicesBucketOnUEMap=new Map();for(const expectation of model.userModel.expectations){if(tr.e.chrome.CHROME_INTERNAL_URLS.includes(expectation.url)){continue;}
+const slices=expectation.range.filterArray(v8ThreadSlices,event=>event.start);if(slices.length===0)continue;const lastSlice=slices[slices.length-1];if(!expectation.range.intersectsRangeExclusive(lastSlice.range)){slices.pop();}
 if(v8SlicesBucketOnUEMap.get(expectation.stageTitle)===undefined){v8SlicesBucketOnUEMap.set(expectation.stageTitle,slices);}else{const totalSlices=v8SlicesBucketOnUEMap.get(expectation.stageTitle).concat(slices);v8SlicesBucketOnUEMap.set(expectation.stageTitle,totalSlices);}}
 computeRuntimeStatsBucketOnUE(histograms,v8ThreadSlices,v8SlicesBucketOnUEMap);}
 tr.metrics.MetricRegistry.register(runtimeStatsTotalMetric);tr.metrics.MetricRegistry.register(runtimeStatsMetric);return{runtimeStatsMetric,runtimeStatsTotalMetric,};});'use strict';tr.exportTo('tr.metrics.v8',function(){function v8AndMemoryMetrics(histograms,model){tr.metrics.v8.executionMetric(histograms,model);tr.metrics.v8.gcMetric(histograms,model);tr.metrics.sh.memoryMetric(histograms,model,{rangeOfInterest:tr.metrics.v8.utils.rangeForMemoryDumps(model)});}
-tr.metrics.MetricRegistry.register(v8AndMemoryMetrics);return{v8AndMemoryMetrics,};});'use strict';tr.exportTo('tr.metrics.vr',function(){function createHistograms(histograms,name,options){return{wall:histograms.createHistogram(name+'_wall',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],options),cpu:histograms.createHistogram(name+'_cpu',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],options),};}
-function frameCycleDurationMetric(histograms,model,opt_options){const histogramsByEventTitle=new Map();histogramsByEventTitle.set('VrShellGl::DrawFrame',createHistograms(histograms,'draw_frame',{description:'Duration to render one frame'}));histogramsByEventTitle.set('VrShellGl::AcquireFrame',createHistograms(histograms,'acquire_frame',{description:'Duration acquire a frame from GVR'}));histogramsByEventTitle.set('VrShellGl::UpdateController',createHistograms(histograms,'update_controller',{description:'Duration to query input from the controller'}));histogramsByEventTitle.set('VrShellGl::DrawFrameSubmitWhenReady',createHistograms(histograms,'submit_frame',{description:'Duration to submit a frame to GVR'}));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateAnimationsAndOpacity',createHistograms(histograms,'update_animations_and_opacity',{description:'Duration to apply animation and opacity changes'}));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateBindings',createHistograms(histograms,'update_bindings',{description:'Duration to push binding values'}));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateTexturesAndSizes',createHistograms(histograms,'update_textures_and_sizes',{description:'Duration to redraw textures and update element sizes'}));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateLayout',createHistograms(histograms,'update_layout',{description:'Duration to reposition elements according to their layout'}));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateWorldSpaceTransform',createHistograms(histograms,'update_world_space_transforms',{description:'Duration to calculate element transforms in world space'}));const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);let rangeOfInterest=model.bounds;const userExpectationsOfInterest=[tr.model.um.AnimationExpectation];if(opt_options&&opt_options.rangeOfInterest){rangeOfInterest=opt_options.rangeOfInterest;userExpectationsOfInterest.push(tr.model.um.ResponseExpectation);}
+tr.metrics.MetricRegistry.register(v8AndMemoryMetrics);return{v8AndMemoryMetrics,};});'use strict';tr.exportTo('tr.metrics.vr',function(){function createHistograms(histograms,name,options,hasCpuTime){const createdHistograms={wall:histograms.createHistogram(name+'_wall',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],options)};if(hasCpuTime){createdHistograms.cpu=histograms.createHistogram(name+'_cpu',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[],options);}
+return createdHistograms;}
+function frameCycleDurationMetric(histograms,model,opt_options){const histogramsByEventTitle=new Map();histogramsByEventTitle.set('VrShellGl::DrawFrame',createHistograms(histograms,'draw_frame',{description:'Duration to render one frame'},true));histogramsByEventTitle.set('VrShellGl::AcquireFrame',createHistograms(histograms,'acquire_frame',{description:'Duration acquire a frame from GVR'},true));histogramsByEventTitle.set('VrShellGl::UpdateController',createHistograms(histograms,'update_controller',{description:'Duration to query input from the controller'},true));histogramsByEventTitle.set('VrShellGl::DrawFrameSubmitNow',createHistograms(histograms,'submit_frame',{description:'Duration to submit a frame to GVR'},true));histogramsByEventTitle.set('VrShellGl::PostSubmitDrawOnGpu',createHistograms(histograms,'post_submit_draw_on_gpu',{description:'Duration to draw a frame on GPU post submit to '+'GVR. Note this duration may include time spent on '+'reprojection'},false));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateAnimationsAndOpacity',createHistograms(histograms,'update_animations_and_opacity',{description:'Duration to apply animation and opacity changes'},true));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateBindings',createHistograms(histograms,'update_bindings',{description:'Duration to push binding values'},true));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateLayout',createHistograms(histograms,'update_layout',{description:'Duration to compute element sizes, layout and textures'},true));histogramsByEventTitle.set('UiScene::OnBeginFrame.UpdateWorldSpaceTransform',createHistograms(histograms,'update_world_space_transforms',{description:'Duration to calculate element transforms in world space'},true));histogramsByEventTitle.set('UiRenderer::DrawUiView',createHistograms(histograms,'draw_ui',{description:'Duration to draw the UI'},true));histogramsByEventTitle.set('UiElementRenderer::DrawTexturedQuad',createHistograms(histograms,'draw_textured_quad',{description:'Duration to draw a textured element'},true));histogramsByEventTitle.set('UiElementRenderer::DrawGradientQuad',createHistograms(histograms,'draw_gradient_quad',{description:'Duration to draw a gradient element'},true));histogramsByEventTitle.set('UiElementRenderer::DrawGradientGridQuad',createHistograms(histograms,'draw_gradient_grid_quad',{description:'Duration to draw a gradient grid element'},true));histogramsByEventTitle.set('UiElementRenderer::DrawController',createHistograms(histograms,'draw_controller',{description:'Duration to draw the controller'},true));histogramsByEventTitle.set('UiElementRenderer::DrawLaser',createHistograms(histograms,'draw_laser',{description:'Duration to draw the laser'},true));histogramsByEventTitle.set('UiElementRenderer::DrawReticle',createHistograms(histograms,'draw_reticle',{description:'Duration to draw the reticle'},true));histogramsByEventTitle.set('UiElementRenderer::DrawShadow',createHistograms(histograms,'draw_shadow',{description:'Duration to draw a shadow element'},true));histogramsByEventTitle.set('UiElementRenderer::DrawStars',createHistograms(histograms,'draw_stars',{description:'Duration to draw the stars'},true));histogramsByEventTitle.set('UiElementRenderer::DrawBackground',createHistograms(histograms,'draw_background',{description:'Duration to draw the textured background'},true));histogramsByEventTitle.set('UiElementRenderer::DrawKeyboard',createHistograms(histograms,'draw_keyboard',{description:'Duration to draw the keyboard'},true));const drawUiSubSlicesMap=new Map();const chromeHelper=model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);let rangeOfInterest=model.bounds;const userExpectationsOfInterest=[tr.model.um.AnimationExpectation];if(opt_options&&opt_options.rangeOfInterest){rangeOfInterest=opt_options.rangeOfInterest;userExpectationsOfInterest.push(tr.model.um.ResponseExpectation);}
 for(const ue of model.userModel.expectations){if(ue.initiatorType!==tr.model.um.INITIATOR_TYPE.VR){continue;}
 if(!userExpectationsOfInterest.some(function(ueOfInterest){return ue instanceof ueOfInterest;})){continue;}
 if(!rangeOfInterest.intersectsExplicitRangeInclusive(ue.start,ue.end)){continue;}
 for(const helper of chromeHelper.browserHelpers){const glThreads=helper.process.findAllThreadsMatching(thread=>!thread.name);for(const glThread of glThreads){for(const event of glThread.getDescendantEvents()){if(!(histogramsByEventTitle.has(event.title))){continue;}
 if(event.start<ue.start||event.end>ue.end){continue;}
 if(event.start<rangeOfInterest.min||event.end>rangeOfInterest.max){continue;}
-const{wall:wallHist,cpu:cpuHist}=histogramsByEventTitle.get(event.title);wallHist.addSample(event.duration);cpuHist.addSample(event.cpuDuration);}}}}}
+if(event.parentSlice&&event.parentSlice.title==='UiRenderer::DrawUiView'){const guid=event.parentSlice.guid;if(!drawUiSubSlicesMap.has(guid)){drawUiSubSlicesMap.set(guid,[]);}
+drawUiSubSlicesMap.get(guid).push(event);continue;}
+const{wall:wallHist,cpu:cpuHist}=histogramsByEventTitle.get(event.title);wallHist.addSample(event.duration);if(cpuHist!==undefined){cpuHist.addSample(event.cpuDuration);}}}}}
+for(const subSlices of drawUiSubSlicesMap.values()){const eventMap=new Map();for(const event of subSlices){if(!eventMap.has(event.title)){eventMap.set(event.title,{wall:0,cpu:0});}
+eventMap.get(event.title).wall+=event.duration;eventMap.get(event.title).cpu+=event.cpuDuration;}
+for(const[title,values]of eventMap.entries()){const{wall:wallHist,cpu:cpuHist}=histogramsByEventTitle.get(title);wallHist.addSample(values.wall);if(cpuHist!==undefined){cpuHist.addSample(values.cpu);}}}}
 tr.metrics.MetricRegistry.register(frameCycleDurationMetric,{supportsRangeOfInterest:true,});return{frameCycleDurationMetric,};});'use strict';tr.exportTo('tr.metrics.vr',function(){function webvrMetric(histograms,model,opt_options){const WEBVR_COUNTERS=new Map([['gpu.WebVR FPS',{name:'webvr_fps',unit:tr.b.Unit.byName.count_biggerIsBetter,samples:{},options:{description:'WebVR frame per second',binBoundaries:tr.v.HistogramBinBoundaries.createLinear(20,120,25),},}],['gpu.WebVR frame time (ms)',{name:'webvr_frame_time',unit:tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,samples:{},options:{description:'WebVR frame time in ms',binBoundaries:tr.v.HistogramBinBoundaries.createLinear(20,120,25),},}],['gpu.WebVR pose prediction (ms)',{name:'webvr_pose_prediction',unit:tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,samples:{},options:{description:'WebVR pose prediction in ms',binBoundaries:tr.v.HistogramBinBoundaries.createLinear(20,120,25),},}],]);for(const ue of model.userModel.expectations){const rangeOfInterestEnabled=opt_options&&opt_options.rangeOfInterest;if(rangeOfInterestEnabled&&!opt_options.rangeOfInterest.intersectsExplicitRangeInclusive(ue.start,ue.end)){continue;}
 if(ue.initiatorType!==tr.model.um.INITIATOR_TYPE.VR)continue;if(!rangeOfInterestEnabled){if(!(ue instanceof tr.model.um.AnimationExpectation))continue;}else{if(!(ue instanceof tr.model.um.AnimationExpectation||ue instanceof tr.model.um.ResponseExpectation))continue;}
 for(const counter of model.getAllCounters()){if(!(WEBVR_COUNTERS.has(counter.id)))continue;for(const series of counter.series){if(!(series.name in WEBVR_COUNTERS.get(counter.id).samples)){WEBVR_COUNTERS.get(counter.id).samples[series.name]=[];}
@@ -8704,7 +8828,8 @@
 totalledArgs[argName]+=argVal;}}
 this.untotallableArgs_=Object.keys(untotallableArgs);this.totalledArgs_=totalledArgs;}};return{MultiEventSummary,};});'use strict';Polymer({is:'tr-ui-a-multi-event-summary-table',ready(){this.showTotals_=false;this.eventsHaveDuration_=true;this.eventsHaveSubRows_=true;this.eventsByTitle_=undefined;},updateTableColumns_(rows,maxValues){let hasCpuData=false;let hasAlerts=false;rows.forEach(function(row){if(row.cpuDuration!==undefined){hasCpuData=true;}
 if(row.cpuSelfTime!==undefined){hasCpuData=true;}
-if(row.numAlerts){hasAlerts=true;}});const ownerDocument=this.ownerDocument;const columns=[];columns.push({title:'Name',value(row){if(row.title==='Totals')return'Totals';const linkEl=document.createElement('tr-ui-a-analysis-link');linkEl.setSelectionAndContent(function(){return new tr.model.EventSet(row.events);},row.title);return linkEl;},width:'350px',cmp(rowA,rowB){return rowA.title.localeCompare(rowB.title);}});if(this.eventsHaveDuration_){columns.push({title:'Wall Duration',value(row){return tr.v.ui.createScalarSpan(row.duration,{unit:tr.b.Unit.byName.timeDurationInMs,customContextRange:row.totalsRow?undefined:tr.b.math.Range.fromExplicitRange(0,maxValues.duration),ownerDocument,});},width:'<upated further down>',cmp(rowA,rowB){return rowA.duration-rowB.duration;}});}
+if(row.numAlerts){hasAlerts=true;}});const ownerDocument=this.ownerDocument;const columns=[];columns.push({title:'Name',value(row){if(row.title==='Totals')return'Totals';const container=document.createElement('div');const linkEl=document.createElement('tr-ui-a-analysis-link');linkEl.setSelectionAndContent(function(){return new tr.model.EventSet(row.events);},row.title);container.appendChild(linkEl);if(tr.isExported('tr-ui-e-chrome-codesearch')){const link=document.createElement('tr-ui-e-chrome-codesearch');link.searchPhrase=row.title;container.appendChild(link);}
+return container;},width:'350px',cmp(rowA,rowB){return rowA.title.localeCompare(rowB.title);}});if(this.eventsHaveDuration_){columns.push({title:'Wall Duration',value(row){return tr.v.ui.createScalarSpan(row.duration,{unit:tr.b.Unit.byName.timeDurationInMs,customContextRange:row.totalsRow?undefined:tr.b.math.Range.fromExplicitRange(0,maxValues.duration),ownerDocument,});},width:'<upated further down>',cmp(rowA,rowB){return rowA.duration-rowB.duration;}});}
 if(this.eventsHaveDuration_&&hasCpuData){columns.push({title:'CPU Duration',value(row){return tr.v.ui.createScalarSpan(row.cpuDuration,{unit:tr.b.Unit.byName.timeDurationInMs,customContextRange:row.totalsRow?undefined:tr.b.math.Range.fromExplicitRange(0,maxValues.cpuDuration),ownerDocument,});},width:'<upated further down>',cmp(rowA,rowB){return rowA.cpuDuration-rowB.cpuDuration;}});}
 if(this.eventsHaveSubRows_&&this.eventsHaveDuration_){columns.push({title:'Self time',value(row){return tr.v.ui.createScalarSpan(row.selfTime,{unit:tr.b.Unit.byName.timeDurationInMs,customContextRange:row.totalsRow?undefined:tr.b.math.Range.fromExplicitRange(0,maxValues.selfTime),ownerDocument,});},width:'<upated further down>',cmp(rowA,rowB){return rowA.selfTime-rowB.selfTime;}});}
 if(this.eventsHaveSubRows_&&this.eventsHaveDuration_&&hasCpuData){columns.push({title:'CPU Self Time',value(row){return tr.v.ui.createScalarSpan(row.cpuSelfTime,{unit:tr.b.Unit.byName.timeDurationInMs,customContextRange:row.totalsRow?undefined:tr.b.math.Range.fromExplicitRange(0,maxValues.cpuSelfTime),ownerDocument,});},width:'<upated further down>',cmp(rowA,rowB){return rowA.cpuSelfTime-rowB.cpuSelfTime;}});}
@@ -8824,7 +8949,7 @@
 get updateEventName_(){return this.constructor.name+'.update';}
 addUpdateListener(listener){this.addEventListener(this.updateEventName_,listener);}
 removeUpdateListener(listener){this.removeEventListener(this.updateEventName_,listener);}}
-return{ViewState,};});'use strict';tr.exportTo('tr.v.ui',function(){class HistogramSetViewState extends tr.b.ViewState{constructor(){super();this.define('searchQuery','');this.define('referenceDisplayLabel','');this.define('displayStatisticName','');this.define('showAll',false);this.define('groupings',[]);this.define('sortColumnIndex',0);this.define('sortDescending',false);this.define('constrainNameColumn',true);this.define('tableRowStates',new Map());this.define('alpha',0.01);}}
+return{ViewState,};});'use strict';tr.exportTo('tr.v.ui',function(){class HistogramSetViewState extends tr.b.ViewState{constructor(){super();this.define('searchQuery','');this.define('referenceDisplayLabel','');this.define('displayStatisticName','');this.define('showAll',true);this.define('groupings',[]);this.define('sortColumnIndex',0);this.define('sortDescending',false);this.define('constrainNameColumn',true);this.define('tableRowStates',new Map());this.define('alpha',0.01);}}
 tr.b.ViewState.register(HistogramSetViewState);class HistogramSetTableRowState extends tr.b.ViewState{constructor(){super();this.define('isExpanded',false);this.define('isOverviewed',false);this.define('cells',new Map());this.define('subRows',new Map());this.define('diagnosticsTab','');}
 asCompactDict(){const result={};if(this.isExpanded)result.e='1';if(this.isOverviewed)result.o='1';if(this.diagnosticsTab)result.d=this.diagnosticsTab;const cells={};for(const[name,cell]of this.cells){const cellDict=cell.asCompactDict();if(cellDict===undefined)continue;cells[name]=cellDict;}
 if(Object.keys(cells).length>0)result.c=cells;const subRows={};for(const[name,row]of this.subRows){const rowDict=row.asCompactDict();if(rowDict===undefined)continue;subRows[name]=rowDict;}
@@ -9333,9 +9458,11 @@
 return FunctionRegistry.allFunctions[numFunctionsNow-1];};FunctionHandle.fromDict=function(handleDict){const options=handleDict.options;let modulesToLoad;if(handleDict.modules_to_load!==undefined){modulesToLoad=handleDict.modules_to_load.map(function(module){return ModuleToLoad.fromDict(module);});}
 return new FunctionHandle(modulesToLoad,handleDict.function_name,options);};return{FunctionHandle,ModuleToLoad,FunctionRegistry,};});'use strict';tr.exportTo('tr.metrics',function(){function runMetrics(model,options,addFailureCb){if(options===undefined){throw new Error('Options are required.');}
 const metricNames=options.metrics;if(!metricNames){throw new Error('Metric names should be specified.');}
-const categories=getTraceCategories(model);const histograms=new tr.v.HistogramSet();for(const metricName of metricNames){const metric=tr.metrics.MetricRegistry.findTypeInfoWithName(metricName);if(metric===undefined){throw new Error(`"${metricName}" is not a registered metric.`);}
-validateTraceCategories(metric.metadata.requiredCategories,categories);try{metric.constructor(histograms,model,options);}catch(e){const err=tr.b.normalizeException(e);addFailureCb(new tr.mre.Failure(undefined,'metricMapFunction',model.canonicalUrl,err.typeName,err.message,err.stack));}}
-validateDiagnosticNames(histograms);return histograms;}
+const allMetricsStart=new Date();const durationBreakdown=new tr.v.d.Breakdown();const categories=getTraceCategories(model);const histograms=new tr.v.HistogramSet();histograms.createHistogram('trace_import_duration',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,model.stats.traceImportDurationMs,{binBoundaries:tr.v.HistogramBinBoundaries.createExponential(1e-3,1e5,30),description:'Duration that trace viewer required to import the trace',summaryOptions:tr.v.Histogram.AVERAGE_ONLY_SUMMARY_OPTIONS,});for(const metricName of metricNames){const metricStart=new Date();const metric=tr.metrics.MetricRegistry.findTypeInfoWithName(metricName);if(metric===undefined){throw new Error(`"${metricName}" is not a registered metric.`);}
+validateTraceCategories(metric.metadata.requiredCategories,categories);try{metric.constructor(histograms,model,options);}catch(e){const err=tr.b.normalizeException(e);addFailureCb(new tr.mre.Failure(undefined,'metricMapFunction',model.canonicalUrl,err.typeName,err.message,err.stack));}
+const metricMs=new Date()-metricStart;histograms.createHistogram(metricName+'_duration',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[metricMs]);durationBreakdown.set(metricName,metricMs);}
+validateDiagnosticNames(histograms);const allMetricsMs=new Date()-allMetricsStart+
+model.stats.traceImportDurationMs;durationBreakdown.set('traceImport',model.stats.traceImportDurationMs);durationBreakdown.set('other',allMetricsMs-tr.b.math.Statistics.sum(durationBreakdown,([metricName,metricMs])=>metricMs));const breakdownNames=tr.v.d.RelatedNameMap.fromEntries(new Map(metricNames.map(metricName=>[metricName,metricName+'_duration'])));breakdownNames.set('traceImport','trace_import_duration');histograms.createHistogram('metrics_duration',tr.b.Unit.byName.timeDurationInMs_smallerIsBetter,[{value:allMetricsMs,diagnostics:{breakdown:durationBreakdown},},],{diagnostics:{breakdown:breakdownNames},});return histograms;}
 function getTraceCategories(model){for(const metadata of model.metadata){let config;if(metadata.name==='TraceConfig'&&metadata.value){config=metadata.value;}
 if(metadata.name==='metadata'&&metadata.value&&metadata.value['trace-config']&&metadata.value['trace-config']!=='__stripped__'){config=JSON.parse(metadata.value['trace-config']);}
 if(config){return{excluded:config.excluded_categories||[],included:config.included_categories||[],};}}}
@@ -9386,7 +9513,7 @@
 changeAnalysisLinkHoveredEvents(eventSet){if(this.parentController&&(eventSet instanceof tr.model.EventSet)){this.parentController.changeAnalysisLinkHoveredEvents(eventSet);}}
 getViewSpecificBrushingState(viewId){if(this.parentController){this.parentController.getViewSpecificBrushingState(viewId);}}
 changeViewSpecificBrushingState(viewId,newState){if(this.parentController){this.parentController.changeViewSpecificBrushingState(viewId,newState);}}}
-return{NullBrushingStateController,};});'use strict';tr.exportTo('tr.v',function(){const IGNORE_GROUPING_KEYS=['name','storyTags',];class CSVBuilder{constructor(histograms){this.histograms_=histograms;this.table_=[];this.statisticsNames_=new Set();this.groupings_=[];}
+return{NullBrushingStateController,};});'use strict';tr.exportTo('tr.v',function(){const IGNORE_GROUPING_KEYS=['name','storyTags','testPath',];class CSVBuilder{constructor(histograms){this.histograms_=histograms;this.table_=[];this.statisticsNames_=new Set();this.groupings_=[];}
 build(){this.prepare_();this.buildHeader_();this.buildTable_();}
 prepare_(){for(const[key,grouping]of tr.v.HistogramGrouping.BY_KEY){if(IGNORE_GROUPING_KEYS.includes(key))continue;this.groupings_.push(grouping);}
 this.groupings_.push(new tr.v.GenericSetGrouping(tr.v.d.RESERVED_NAMES.TRACE_URLS));this.groupings_.sort((a,b)=>a.key.localeCompare(b.key));for(const hist of this.histograms_){for(const name of hist.statisticsNames){this.statisticsNames_.add(name);}}
@@ -9408,12 +9535,13 @@
 this.labelsToStartTimes_.set(displayLabel,startTime);for(const[groupingKey,values]of this.keysToValues_){const grouping=this.keysToGroupings_.get(groupingKey);const value=grouping.callback(hist);if(!value)continue;values.add(value);if(values.size>1){this.keysToValues_.delete(groupingKey);}}
 const storyTags=hist.diagnostics.get(tr.v.d.RESERVED_NAMES.STORY_TAGS);for(const tag of(storyTags||[])){allStoryTags.add(tag);}}
 tr.b.Timing.instant('HistogramParameterCollector','maxSampleCount',maxSampleCount);for(const tagGrouping of tr.v.HistogramGrouping.buildFromTags(allStoryTags,tr.v.d.RESERVED_NAMES.STORY_TAGS)){const values=new Set();for(const hist of histograms){values.add(tagGrouping.callback(hist));}
-if(values.size>1){this.keysToGroupings_.set(tagGrouping.key,tagGrouping);this.keysToValues_.set(tagGrouping.key,values);}}}
+if(values.size>1){this.keysToGroupings_.set(tagGrouping.key,tagGrouping);this.keysToValues_.set(tagGrouping.key,values);}}
+this.statisticNames_.add('pct_090');}
 get statisticNames(){return Array.from(this.statisticNames_);}
 get labels(){const displayLabels=Array.from(this.labelsToStartTimes_.keys());displayLabels.sort((x,y)=>this.labelsToStartTimes_.get(x)-this.labelsToStartTimes_.get(y));return displayLabels;}
 get possibleGroupings(){for(const[key,values]of this.keysToValues_){if(values.size>=2)continue;this.keysToGroupings_.delete(key);}
 return Array.from(this.keysToGroupings_.values());}}
-return{HistogramParameterCollector,};});'use strict';tr.exportTo('tr.v.ui',function(){Polymer({is:'tr-v-ui-histogram-set-controls-export',exportRawCsv_(){this.export_(false,'csv');},exportRawJson_(){this.export_(false,'json');},exportMergedCsv_(){this.export_(true,'csv');},exportMergedJson_(){this.export_(true,'json');},export_(merged,format){tr.b.dispatchSimpleEvent(this,'export',true,true,{merged,format});},});return{};});'use strict';tr.exportTo('tr.v.ui',function(){const ALPHA_OPTIONS=[];for(let i=1;i<10;++i)ALPHA_OPTIONS.push(i*1e-3);for(let i=1;i<10;++i)ALPHA_OPTIONS.push(i*1e-2);ALPHA_OPTIONS.push(0.1);Polymer({is:'tr-v-ui-histogram-set-controls',properties:{searchQuery:{type:String,value:'',observer:'onUserChange_',},showAll:{type:Boolean,value:false,observer:'onUserChange_',},referenceDisplayLabel:{type:String,value:'',observer:'onUserChange_',},displayStatisticName:{type:String,value:'',observer:'onUserChange_',},alphaString:{type:String,computed:'getAlphaString_(alphaIndex)',},alphaIndex:{type:Number,value:9,observer:'onUserChange_',},},created(){this.viewState_=undefined;this.rowListener_=this.onRowViewStateUpdate_.bind(this);this.baseStatisticNames_=[];this.isInOnViewStateUpdate_=false;},ready(){this.$.picker.addEventListener('current-groups-changed',this.onGroupsChanged_.bind(this));},get viewState(){return this.viewState_;},set viewState(vs){if(this.viewState_){throw new Error('viewState must be set exactly once.');}
+return{HistogramParameterCollector,};});'use strict';tr.exportTo('tr.v.ui',function(){Polymer({is:'tr-v-ui-histogram-set-controls-export',exportRawCsv_(){this.export_(false,'csv');},exportRawJson_(){this.export_(false,'json');},exportMergedCsv_(){this.export_(true,'csv');},exportMergedJson_(){this.export_(true,'json');},export_(merged,format){tr.b.dispatchSimpleEvent(this,'export',true,true,{merged,format});},});return{};});'use strict';tr.exportTo('tr.v.ui',function(){const ALPHA_OPTIONS=[];for(let i=1;i<10;++i)ALPHA_OPTIONS.push(i*1e-3);for(let i=1;i<10;++i)ALPHA_OPTIONS.push(i*1e-2);ALPHA_OPTIONS.push(0.1);Polymer({is:'tr-v-ui-histogram-set-controls',properties:{searchQuery:{type:String,value:'',observer:'onUserChange_',},showAll:{type:Boolean,value:true,observer:'onUserChange_',},referenceDisplayLabel:{type:String,value:'',observer:'onUserChange_',},displayStatisticName:{type:String,value:'',observer:'onUserChange_',},alphaString:{type:String,computed:'getAlphaString_(alphaIndex)',},alphaIndex:{type:Number,value:9,observer:'onUserChange_',},},created(){this.viewState_=undefined;this.rowListener_=this.onRowViewStateUpdate_.bind(this);this.baseStatisticNames_=[];this.isInOnViewStateUpdate_=false;},ready(){this.$.picker.addEventListener('current-groups-changed',this.onGroupsChanged_.bind(this));},get viewState(){return this.viewState_;},set viewState(vs){if(this.viewState_){throw new Error('viewState must be set exactly once.');}
 this.viewState_=vs;this.viewState.addUpdateListener(this.onViewStateUpdate_.bind(this));},async onUserChange_(){if(!this.viewState)return;if(this.isInOnViewStateUpdate_)return;const marks=[];if(this.searchQuery!==this.viewState.searchQuery){marks.push(tr.b.Timing.mark('histogram-set-controls','search'));}
 if(this.showAll!==this.viewState.showAll){marks.push(tr.b.Timing.mark('histogram-set-controls','showAll'));}
 if(this.referenceDisplayLabel!==this.viewState.referenceDisplayLabel){marks.push(tr.b.Timing.mark('histogram-set-controls','referenceColumn'));}
@@ -9516,9 +9644,10 @@
 return this.nameCell_;}
 getCell(columnName){if(this.cells.has(columnName))return this.cells.get(columnName);const cell=document.createElement('tr-v-ui-histogram-set-table-cell');cell.build(this,columnName,this.viewState.cells.get(columnName));this.cells.set(columnName,cell);return cell;}
 compareNames(other){return this.name.localeCompare(other.name);}
-compareCells(other,displayLabel){const cellA=this.columns.get(displayLabel);const cellB=other.columns.get(displayLabel);if(!(cellA instanceof tr.v.Histogram)||!(cellB instanceof tr.v.Histogram)){return undefined;}
-let referenceCellA;let referenceCellB;const referenceDisplayLabel=this.rootViewState.referenceDisplayLabel;if(referenceDisplayLabel&&referenceDisplayLabel!==displayLabel){referenceCellA=this.columns.get(referenceDisplayLabel);referenceCellB=other.columns.get(referenceDisplayLabel);}
-const statisticA=cellA.getAvailableStatisticName(this.rootViewState.displayStatisticName,referenceCellA);const statisticB=cellB.getAvailableStatisticName(this.rootViewState.displayStatisticName,referenceCellB);const scalarA=cellA.getStatisticScalar(statisticA,referenceCellA);const scalarB=cellB.getStatisticScalar(statisticB,referenceCellB);const valueA=scalarA?scalarA.value:undefined;const valueB=scalarB?scalarB.value:undefined;return valueA-valueB;}
+compareCells(other,displayLabel){const referenceDisplayLabel=this.rootViewState.referenceDisplayLabel;let referenceCellA;let referenceCellB;if(referenceDisplayLabel&&referenceDisplayLabel!==displayLabel){referenceCellA=this.columns.get(referenceDisplayLabel);referenceCellB=other.columns.get(referenceDisplayLabel);}
+const cellA=this.columns.get(displayLabel);let valueA=0;if(cellA instanceof tr.v.Histogram){const statisticA=cellA.getAvailableStatisticName(this.rootViewState.displayStatisticName,referenceCellA);const scalarA=cellA.getStatisticScalar(statisticA,referenceCellA);if(scalarA){valueA=scalarA.value;}}
+const cellB=other.columns.get(displayLabel);let valueB=0;if(cellB instanceof tr.v.Histogram){const statisticB=cellB.getAvailableStatisticName(this.rootViewState.displayStatisticName,referenceCellB);const scalarB=cellB.getStatisticScalar(statisticB,referenceCellB);if(scalarB){valueB=scalarB.value;}}
+return valueA-valueB;}
 onViewStateUpdate_(event){if(event.delta.isExpanded){this.baseTable_.setExpandedForTableRow(this,this.viewState.isExpanded);}
 if(event.delta.subRows){throw new Error('HistogramSetTableRow.subRows must not be reassigned.');}
 if(event.delta.cells){for(const[displayLabel,cell]of this.cells){if(cell.viewState!==this.viewState.cells.get(displayLabel)){throw new Error('Only HistogramSetTableRow may update cells');}}}}
diff --git a/catapult/systrace/systrace/test_data/battor_test_data.txt b/catapult/systrace/systrace/test_data/battor_test_data.txt
deleted file mode 100644
index 9672736..0000000
--- a/catapult/systrace/systrace/test_data/battor_test_data.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-# BattOr
-# voltage_range [-6079.7, 6196.8] mV
-# current_range [-6057.3, 5764.1] mA
-# sample_rate 10000 Hz, gain 10.1x
-0.00 6.5 4307.7
-0.10 15.1 4313.7
-0.20 15.1 4313.7
-0.30 12.2 4310.7
-0.40 12.2 4307.7
-0.50 18.0 4334.7
-0.60 15.1 4334.7
-0.70 18.0 4337.7
-0.80 18.0 4337.7
-0.90 20.9 4331.7
-1.00 9.3 4334.7
-1.10 9.3 4307.7
diff --git a/catapult/systrace/systrace/tracing_agents/agents_unittest.py b/catapult/systrace/systrace/tracing_agents/agents_unittest.py
index 4071caf..b9596ef 100644
--- a/catapult/systrace/systrace/tracing_agents/agents_unittest.py
+++ b/catapult/systrace/systrace/tracing_agents/agents_unittest.py
@@ -19,7 +19,7 @@
     self.device = devices[0]
 
     curr_browser = self.GetChromeProcessID()
-    if curr_browser == None:
+    if curr_browser is None:
       self.StartBrowser()
 
   def tearDown(self):
diff --git a/catapult/systrace/systrace/tracing_agents/atrace_agent.py b/catapult/systrace/systrace/tracing_agents/atrace_agent.py
index 47ed3fa..f699fc0 100644
--- a/catapult/systrace/systrace/tracing_agents/atrace_agent.py
+++ b/catapult/systrace/systrace/tracing_agents/atrace_agent.py
@@ -4,12 +4,13 @@
 
 import optparse
 import platform
-import py_utils
 import re
 import sys
 import threading
 import zlib
 
+import py_utils
+
 from devil.android import device_utils
 from devil.android.sdk import version_codes
 from py_trace_event import trace_time as trace_time_module
@@ -139,6 +140,11 @@
   if (config.trace_buf_size is not None) and (config.trace_buf_size > 0):
     atrace_args.extend(['-b', str(config.trace_buf_size)])
 
+  elif 'webview' in categories and 'sched' in categories:
+    # https://crbug.com/814330: webview_startup sometimes exceeds the buffer
+    # limit, so doubling this.
+    atrace_args.extend(['-b', '8192'])
+
   elif 'sched' in categories:
     # 'sched' is a high-volume tag, double the default buffer size
     # to accommodate that
diff --git a/catapult/systrace/systrace/tracing_agents/atrace_from_file_agent.py b/catapult/systrace/systrace/tracing_agents/atrace_from_file_agent.py
index 2a4e781..ee621d6 100644
--- a/catapult/systrace/systrace/tracing_agents/atrace_from_file_agent.py
+++ b/catapult/systrace/systrace/tracing_agents/atrace_from_file_agent.py
@@ -3,9 +3,10 @@
 # found in the LICENSE file.
 
 import os
-import py_utils
 import re
 
+import py_utils
+
 from systrace import trace_result
 from systrace import tracing_agents
 from systrace.tracing_agents import atrace_agent
diff --git a/catapult/systrace/systrace/tracing_agents/battor_trace_agent.py b/catapult/systrace/systrace/tracing_agents/battor_trace_agent.py
deleted file mode 100644
index a591b13..0000000
--- a/catapult/systrace/systrace/tracing_agents/battor_trace_agent.py
+++ /dev/null
@@ -1,165 +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.
-
-import atexit
-import logging
-import optparse
-import py_utils
-
-from battor import battor_wrapper
-from devil.android import battery_utils
-from devil.android import device_utils
-from devil.utils import battor_device_mapping
-from devil.utils import find_usb_devices
-from py_trace_event import trace_time
-from systrace import trace_result
-from systrace import tracing_agents
-
-
-def try_create_agent(config):
-  if config.from_file is not None:
-    return None
-  if config.battor:
-    return BattOrTraceAgent()
-  return None
-
-
-class BattOrConfig(tracing_agents.TracingConfig):
-  def __init__(self, battor_categories, serial_map, battor_path,
-               battor, target, from_file, device_serial_number):
-    tracing_agents.TracingConfig.__init__(self)
-    self.battor_categories = battor_categories
-    self.serial_map = serial_map
-    self.battor_path = battor_path
-    self.battor = battor
-    self.target = target
-    self.from_file = from_file
-    self.device_serial_number = device_serial_number
-
-
-def add_options(parser):
-  options = optparse.OptionGroup(parser, 'BattOr trace options')
-  options.add_option('--battor-categories', dest='battor_categories',
-                     help='Select battor categories with a comma-delimited '
-                     'list, e.g. --battor-categories=cat1,cat2,cat3')
-  options.add_option('--serial-map', dest='serial_map',
-                    default='serial_map.json',
-                    help='File containing pregenerated map of phone serial '
-                    'numbers to BattOr serial numbers.')
-  options.add_option('--battor-path', dest='battor_path', default=None,
-                    type='string', help='specify a BattOr path to use')
-  options.add_option('--battor', dest='battor', default=False,
-                    action='store_true', help='Use the BattOr tracing agent.')
-  return options
-
-def get_config(options):
-  return BattOrConfig(
-      options.battor_categories, options.serial_map, options.battor_path,
-      options.battor, options.target, options.from_file,
-      options.device_serial_number)
-
-def _reenable_charging_if_needed(battery):
-  if not battery.GetCharging():
-    battery.SetCharging(True)
-  logging.info('Charging status checked at exit.')
-
-
-class BattOrTraceAgent(tracing_agents.TracingAgent):
-  # Class representing tracing agent that gets data from a BattOr.
-  # BattOrs are high-frequency power monitors used for battery testing.
-  def __init__(self):
-    super(BattOrTraceAgent, self).__init__()
-    self._collection_process = None
-    self._recording_error = None
-    self._battor_wrapper = None
-    self._battery_utils = None
-
-  @staticmethod
-  def _FindBattOrPath(config):
-    battor_path = config.battor_path
-    if not config.battor_path and not config.serial_map:
-      device_tree = find_usb_devices.GetBusNumberToDeviceTreeMap()
-      battors = battor_device_mapping.GetBattOrList(device_tree)
-      assert len(battors) == 1, ('Must specify BattOr path if there is not '
-                                 'exactly one')
-      battor_path = battors[0]
-    return battor_path
-
-  @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT)
-  def StartAgentTracing(self, config, timeout=None):
-    """Starts tracing.
-
-    Args:
-        config: Tracing config.
-
-    Raises:
-        RuntimeError: If trace already in progress.
-        AssertionError: If There is no BattOr path given and more
-            than one BattOr is attached.
-    """
-    battor_path = self._FindBattOrPath(config)
-    self._battor_wrapper = battor_wrapper.BattOrWrapper(
-        target_platform=config.target,
-        android_device=config.device_serial_number,
-        battor_path=battor_path,
-        battor_map_file=config.serial_map)
-
-    dev_utils = device_utils.DeviceUtils(config.device_serial_number)
-    self._battery_utils = battery_utils.BatteryUtils(dev_utils)
-    self._battery_utils.SetCharging(False)
-    atexit.register(_reenable_charging_if_needed, self._battery_utils)
-    self._battor_wrapper.StartShell()
-    self._battor_wrapper.StartTracing()
-    return True
-
-  @py_utils.Timeout(tracing_agents.START_STOP_TIMEOUT)
-  def StopAgentTracing(self, timeout=None):
-    """Stops tracing and collects the results asynchronously.
-
-    Creates a new process that stops the tracing and collects the results.
-    Returns immediately after the process is created (does not wait for
-    trace results to be collected).
-    """
-    self._battor_wrapper.StopTracing()
-    self._battery_utils.SetCharging(True)
-    return True
-
-  def SupportsExplicitClockSync(self):
-    """Returns whether this function supports explicit clock sync."""
-    return self._battor_wrapper.SupportsExplicitClockSync()
-
-  def RecordClockSyncMarker(self, sync_id, did_record_sync_marker_callback):
-    """Records a clock sync marker.
-
-    Args:
-        sync_id: ID string for clock sync marker.
-        did_record_sync_marker_callback: Callback function to call after
-        the clock sync marker is recorded.
-    """
-    ts = trace_time.Now()
-    self._battor_wrapper.RecordClockSyncMarker(sync_id)
-    did_record_sync_marker_callback(ts, sync_id)
-
-  @py_utils.Timeout(tracing_agents.GET_RESULTS_TIMEOUT)
-  def GetResults(self, timeout=None):
-    """Waits until data collection is completed and get the trace data.
-
-    The trace data is the data that comes out of the BattOr, and is in the
-    format with the following lines:
-
-    time current voltage <sync_id>
-
-    where the sync_id is only there if a clock sync marker was recorded
-    during that sample.
-
-    time = time since start of trace (ms)
-    current = current through battery (mA) - this can be negative if the
-        battery is charging
-    voltage = voltage of battery (mV)
-
-    Returns:
-      The trace data.
-    """
-    return trace_result.TraceResult(
-        'powerTraceAsString', self._battor_wrapper.CollectTraceData())
diff --git a/catapult/systrace/systrace/tracing_agents/battor_trace_agent_unittest.py b/catapult/systrace/systrace/tracing_agents/battor_trace_agent_unittest.py
deleted file mode 100755
index 6347161..0000000
--- a/catapult/systrace/systrace/tracing_agents/battor_trace_agent_unittest.py
+++ /dev/null
@@ -1,182 +0,0 @@
-#!/usr/bin/env 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.
-
-from collections import namedtuple
-import unittest
-import logging
-
-from systrace import decorators
-from systrace.tracing_agents import battor_trace_agent
-from battor import battor_wrapper
-from devil.android import battery_utils
-from devil.utils import battor_device_mapping
-from devil.utils import find_usb_devices
-
-
-mock_opts = namedtuple('mock_opts', ['target', 'device_serial_number',
-                                     'battor_path', 'serial_map'])
-OPTIONS = mock_opts('android', 'Phn2', None, __file__)
-CATEGORIES = None
-_DEFAULT_BATTOR_LIST = ['dev/ttyUSB0']
-
-def raise_error(*args, **kwargs):
-  del args
-  del kwargs
-  raise RuntimeError('Should not call this function in the test')
-
-battor_device_mapping.GenerateSerialMapFile = raise_error
-
-def setup_battor_test(StartShell_error, StartTracing_error,
-                      StopTracing_error, CollectTraceData_error,
-                      battor_paths=None):
-  wrapper = MockBattOrWrapper(StartShell_error, StartTracing_error,
-                              StopTracing_error, CollectTraceData_error)
-  def wrapper_maker(*args, **kwargs):
-    del args
-    del kwargs
-    return wrapper
-  battor_wrapper.BattOrWrapper = wrapper_maker
-  find_usb_devices.GetBusNumberToDeviceTreeMap = lambda: None
-  if battor_paths is None:
-    battor_paths = _DEFAULT_BATTOR_LIST
-  battor_device_mapping.GetBattOrList = lambda x: battor_paths
-
-
-class MockBattOrWrapper(object):
-  def __init__(self, StartShell_error=False, StartTracing_error=False,
-               StopTracing_error=False, CollectTraceData_error=False):
-    self._StartShell_error = StartShell_error
-    self._StartTracing_error = StartTracing_error
-    self._StopTracing_error = StopTracing_error
-    self._CollectTraceData_error = CollectTraceData_error
-    self._running = False
-    self._tracing = False
-    self._output = False
-
-  def IsShellRunning(self):
-    return self._running
-
-  def StartShell(self):
-    assert not self._running
-    if self._StartShell_error:
-      raise RuntimeError('Simulated error in StartShell')
-    self._running = True
-
-  def StartTracing(self):
-    assert self._running
-    assert not self._tracing
-    if self._StartTracing_error:
-      raise RuntimeError('Simulated error in StartTracing')
-    self._tracing = True
-
-  def StopTracing(self):
-    assert self._running
-    assert self._tracing
-    if self._StopTracing_error:
-      raise RuntimeError('Simulated error in StopTracing')
-    self._running = False
-    self._tracing = False
-    self._output = True
-
-  def CollectTraceData(self):
-    assert self._output
-    if self._CollectTraceData_error:
-      raise RuntimeError('Simulated error in CollectTraceData')
-    return 'traceout1\ntraceout2'
-
-
-class MockBatteryUtils(object):
-  def __init__(self, _):
-    self._is_charging = True
-
-  def GetCharging(self):
-    return self._is_charging
-
-  def SetCharging(self, value):
-    self._is_charging = value
-
-
-battery_utils.BatteryUtils = MockBatteryUtils
-
-
-class BattOrAgentTest(unittest.TestCase):
-
-  @decorators.HostOnlyTest
-  def test_trace_double_start(self):
-    setup_battor_test(StartShell_error=False, StartTracing_error=False,
-                      StopTracing_error=False, CollectTraceData_error=False)
-    agent = battor_trace_agent.BattOrTraceAgent()
-    agent.StartAgentTracing(OPTIONS, CATEGORIES)
-    self.assertRaises(AssertionError,
-                      lambda: agent.StartAgentTracing(OPTIONS, CATEGORIES))
-
-  @decorators.HostOnlyTest
-  def test_trace_error_start_shell(self):
-    setup_battor_test(StartShell_error=True, StartTracing_error=False,
-                      StopTracing_error=False, CollectTraceData_error=False)
-    agent = battor_trace_agent.BattOrTraceAgent()
-    self.assertRaises(RuntimeError,
-                      lambda: agent.StartAgentTracing(OPTIONS, CATEGORIES))
-
-  @decorators.HostOnlyTest
-  def test_trace_error_start_tracing(self):
-    setup_battor_test(StartShell_error=False, StartTracing_error=True,
-                      StopTracing_error=False, CollectTraceData_error=False)
-    agent = battor_trace_agent.BattOrTraceAgent()
-    self.assertRaises(RuntimeError,
-                      lambda: agent.StartAgentTracing(OPTIONS, CATEGORIES))
-
-  @decorators.HostOnlyTest
-  def test_trace_error_stop_tracing(self):
-    setup_battor_test(StartShell_error=False, StartTracing_error=False,
-                      StopTracing_error=True, CollectTraceData_error=False)
-    agent = battor_trace_agent.BattOrTraceAgent()
-    agent.StartAgentTracing(OPTIONS, CATEGORIES)
-    self.assertRaises(RuntimeError, agent.StopAgentTracing)
-
-  @decorators.HostOnlyTest
-  def test_trace_error_get_results(self):
-    setup_battor_test(StartShell_error=False, StartTracing_error=False,
-                      StopTracing_error=False, CollectTraceData_error=True)
-    agent = battor_trace_agent.BattOrTraceAgent()
-    agent.StartAgentTracing(OPTIONS, CATEGORIES)
-    agent.StopAgentTracing()
-    self.assertRaises(RuntimeError, agent.GetResults)
-
-  @decorators.HostOnlyTest
-  def test_trace_complete(self):
-    setup_battor_test(StartShell_error=False, StartTracing_error=False,
-                      StopTracing_error=False, CollectTraceData_error=False)
-    agent = battor_trace_agent.BattOrTraceAgent()
-    agent.StartAgentTracing(OPTIONS, CATEGORIES)
-    agent.StopAgentTracing()
-    x = agent.GetResults()
-    self.assertEqual(x.raw_data, 'traceout1\ntraceout2')
-
-  @decorators.HostOnlyTest
-  def test_trace_error_no_battor(self):
-    setup_battor_test(StartShell_error=False, StartTracing_error=False,
-                      StopTracing_error=False, CollectTraceData_error=False,
-                      battor_paths=[])
-    agent = battor_trace_agent.BattOrTraceAgent()
-    options = mock_opts('android', 'Phn2', None, None)
-    with self.assertRaises(AssertionError):
-      agent.StartAgentTracing(options, CATEGORIES)
-
-  @decorators.HostOnlyTest
-  def test_trace_error_multiple_battors_no_battor_path(self):
-    setup_battor_test(StartShell_error=False, StartTracing_error=False,
-                      StopTracing_error=False, CollectTraceData_error=False,
-                      battor_paths=['a', 'b'])
-    agent = battor_trace_agent.BattOrTraceAgent()
-    options = mock_opts('android', 'Phn2', None, None)
-    with self.assertRaises(AssertionError):
-      agent.StartAgentTracing(options, CATEGORIES)
-
-
-if __name__ == "__main__":
-  logging.getLogger().setLevel(logging.DEBUG)
-  unittest.main(verbosity=2)
diff --git a/catapult/systrace/systrace/tracing_agents/walt_agent.py b/catapult/systrace/systrace/tracing_agents/walt_agent.py
index 72d84b5..6ff6061 100644
--- a/catapult/systrace/systrace/tracing_agents/walt_agent.py
+++ b/catapult/systrace/systrace/tracing_agents/walt_agent.py
@@ -2,10 +2,11 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import py_utils
 import optparse
 import threading
 
+import py_utils
+
 from devil.android import device_utils
 from systrace import trace_result
 from systrace import tracing_agents
diff --git a/catapult/systrace/systrace/tracing_agents/walt_agent_unittest.py b/catapult/systrace/systrace/tracing_agents/walt_agent_unittest.py
index b4fcaf7..3bd7135 100644
--- a/catapult/systrace/systrace/tracing_agents/walt_agent_unittest.py
+++ b/catapult/systrace/systrace/tracing_agents/walt_agent_unittest.py
@@ -15,9 +15,9 @@
 class WaltAgentTest(unittest.TestCase):
   """
   The WALT agent pulls the trace log from the Android phone, and does not
-  communicate with the WALT device directly. This makes the agent more similar
-  to atrace than BattOr. Since the host only connects to the Android phone,
-  more exhaustive testing would require mocking DeviceUtils.
+  communicate with the WALT device directly. This makes the agent similar
+  to atrace. Since the host only connects to the Android phone, more exhaustive
+  testing would require mocking DeviceUtils.
   """
 
   @decorators.HostOnlyTest
diff --git a/catapult/systrace/systrace/tracing_controller.py b/catapult/systrace/systrace/tracing_controller.py
index d0d2d7c..a40222c 100644
--- a/catapult/systrace/systrace/tracing_controller.py
+++ b/catapult/systrace/systrace/tracing_controller.py
@@ -12,10 +12,11 @@
 import ast
 import json
 import sys
-import py_utils
 import tempfile
 import uuid
 
+import py_utils
+
 from systrace import trace_result
 from systrace import tracing_agents
 from py_trace_event import trace_event
diff --git a/catapult/systrace/systrace/update_systrace_trace_viewer.py b/catapult/systrace/systrace/update_systrace_trace_viewer.py
index 72878a4..8460852 100755
--- a/catapult/systrace/systrace/update_systrace_trace_viewer.py
+++ b/catapult/systrace/systrace/update_systrace_trace_viewer.py
@@ -15,6 +15,8 @@
     os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
 sys.path.append(os.path.join(_CATAPULT_PATH, 'tracing'))
 
+# this import needs to be after the change to sys.path above
+#pylint: disable=wrong-import-position
 from tracing_build import vulcanize_trace_viewer
 
 
diff --git a/catapult/systrace/systrace/util.py b/catapult/systrace/systrace/util.py
index 61dc475..b13dd51 100644
--- a/catapult/systrace/systrace/util.py
+++ b/catapult/systrace/systrace/util.py
@@ -6,10 +6,10 @@
 import os
 import random
 import string
-import subprocess
 import sys
 
 from devil.android.constants import chrome
+from devil.android import device_utils, device_errors
 
 class OptionParserIgnoreErrors(optparse.OptionParser):
   """Wrapper for OptionParser that ignores errors and produces no output."""
@@ -30,61 +30,6 @@
     pass
 
 
-def add_adb_serial(adb_command, device_serial):
-  """Add serial number to ADB shell command.
-
-  ADB shell command is given as list, e.g.
-  ['adb','shell','some_command','some_args'].
-  This replaces it with:
-  ['adb','shell',-s',device_serial,'some_command','some_args']
-
-  Args:
-     adb_command: ADB command list.
-     device_serial: Device serial number.
-
-  Returns:
-     ADB command list with serial number added.
-  """
-  if device_serial is not None:
-    adb_command.insert(1, device_serial)
-    adb_command.insert(1, '-s')
-
-
-def construct_adb_shell_command(shell_args, device_serial):
-  """Construct an ADB shell command with given device serial and arguments.
-
-  Args:
-     shell_args: array of arguments to pass to adb shell.
-     device_serial: if not empty, will add the appropriate command-line
-        parameters so that adb targets the given device.
-  """
-  adb_command = ['adb', 'shell', ' '.join(shell_args)]
-  add_adb_serial(adb_command, device_serial)
-  return adb_command
-
-
-def run_adb_command(adb_command):
-  adb_output = []
-  adb_return_code = 0
-  try:
-    adb_output = subprocess.check_output(adb_command, stderr=subprocess.STDOUT,
-                                         shell=False, universal_newlines=True)
-  except OSError as error:
-    # This usually means that the adb executable was not found in the path.
-    print >> sys.stderr, ('\nThe command "%s" failed with the following error:'
-                          % ' '.join(adb_command))
-    print >> sys.stderr, '    %s' % str(error)
-    print >> sys.stderr, 'Is adb in your path?'
-    adb_return_code = error.errno
-    adb_output = error
-  except subprocess.CalledProcessError as error:
-    # The process exited with an error.
-    adb_return_code = error.returncode
-    adb_output = error.output
-
-  return (adb_output, adb_return_code)
-
-
 def run_adb_shell(shell_args, device_serial):
   """Runs "adb shell" with the given arguments.
 
@@ -96,8 +41,17 @@
     A tuple containing the adb output (stdout & stderr) and the return code
     from adb.  Will exit if adb fails to start.
   """
-  adb_command = construct_adb_shell_command(shell_args, device_serial)
-  return run_adb_command(adb_command)
+  adb_output = []
+  adb_return_code = 0
+  device = device_utils.DeviceUtils.HealthyDevices(device_arg=device_serial)[0]
+  try:
+    adb_output = device.RunShellCommand(shell_args, shell=False,
+                                        check_return=True, raw_output=True)
+  except device_errors.AdbShellCommandFailedError as error:
+    adb_return_code = error.status
+    adb_output = error.output
+
+  return (adb_output, adb_return_code)
 
 
 def get_device_sdk_version():
@@ -135,7 +89,8 @@
           success = True
 
   if not success:
-    sys.exit(1)
+    print >> sys.stderr, adb_output
+    raise Exception("Failed to get device sdk version")
 
   return version
 
@@ -202,9 +157,6 @@
                     'comma-separated list of app cmdlines')
   parser.add_option('-t', '--time', dest='trace_time', type='int',
                     help='trace for N seconds', metavar='N')
-  parser.add_option('--target', dest='target', default='android',
-                    type='string', help='choose tracing target (android or '
-                    ' linux)')
   parser.add_option('-b', '--buf-size', dest='trace_buf_size',
                     type='int', help='use a trace buffer size '
                     ' of N KB', metavar='N')
diff --git a/catapult/systrace/systrace/util_unittest.py b/catapult/systrace/systrace/util_unittest.py
deleted file mode 100644
index e88f835..0000000
--- a/catapult/systrace/systrace/util_unittest.py
+++ /dev/null
@@ -1,30 +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.
-
-import unittest
-
-from systrace import decorators
-from systrace import util
-
-
-DEVICE_SERIAL = 'AG8404EC0444AGC'
-LIST_TMP_ARGS = ['ls', '/data/local/tmp']
-ATRACE_ARGS = ['atrace', '-z', '-t', '10', '-b', '4096']
-ADB_SHELL = ['adb', '-s', DEVICE_SERIAL, 'shell']
-
-
-class UtilTest(unittest.TestCase):
-
-  @decorators.HostOnlyTest
-  def test_construct_adb_shell_command(self):
-    command = util.construct_adb_shell_command(LIST_TMP_ARGS, None)
-    self.assertEqual(' '.join(command), 'adb shell ls /data/local/tmp')
-
-    command = util.construct_adb_shell_command(LIST_TMP_ARGS, DEVICE_SERIAL)
-    self.assertEqual(' '.join(command),
-                     'adb -s AG8404EC0444AGC shell ls /data/local/tmp')
-
-    command = util.construct_adb_shell_command(ATRACE_ARGS, DEVICE_SERIAL)
-    self.assertEqual(' '.join(command),
-                     'adb -s AG8404EC0444AGC shell atrace -z -t 10 -b 4096')
diff --git a/catapult/tracing/tracing/trace_data/trace_data.py b/catapult/tracing/tracing/trace_data/trace_data.py
index 8d6f624..6c0ff04 100644
--- a/catapult/tracing/tracing/trace_data/trace_data.py
+++ b/catapult/tracing/tracing/trace_data/trace_data.py
@@ -9,6 +9,7 @@
 import shutil
 import subprocess
 import tempfile
+import time
 
 
 _TRACING_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
@@ -46,24 +47,20 @@
 ANDROID_PROCESS_DATA_PART = TraceDataPart('androidProcessDump')
 ATRACE_PART = TraceDataPart('systemTraceEvents')
 ATRACE_PROCESS_DUMP_PART = TraceDataPart('atraceProcessDump')
-BATTOR_TRACE_PART = TraceDataPart('powerTraceAsString')
 CHROME_TRACE_PART = TraceDataPart('traceEvents')
 CPU_TRACE_DATA = TraceDataPart('cpuSnapshots')
 INSPECTOR_TRACE_PART = TraceDataPart('inspectorTimelineEvents')
 SURFACE_FLINGER_PART = TraceDataPart('surfaceFlinger')
-TAB_ID_PART = TraceDataPart('tabIds')
 TELEMETRY_PART = TraceDataPart('telemetry')
 WALT_TRACE_PART = TraceDataPart('waltTraceEvents')
 
 ALL_TRACE_PARTS = {ANDROID_PROCESS_DATA_PART,
                    ATRACE_PART,
                    ATRACE_PROCESS_DUMP_PART,
-                   BATTOR_TRACE_PART,
                    CHROME_TRACE_PART,
                    CPU_TRACE_DATA,
                    INSPECTOR_TRACE_PART,
                    SURFACE_FLINGER_PART,
-                   TAB_ID_PART,
                    TELEMETRY_PART}
 
 ALL_TRACE_PARTS_RAW_NAMES = set(k.raw_field_name for k in ALL_TRACE_PARTS)
@@ -190,9 +187,14 @@
           trace_files.append(path)
       logging.info('Trace sizes in bytes: %s', trace_size_data)
 
-      cmd = (['python', _TRACE2HTML_PATH] + trace_files +
-             ['--output', file_path] + ['--title', trace_title])
+      start_time = time.time()
+      cmd = (
+          ['python', _TRACE2HTML_PATH] + trace_files +
+          ['--output', file_path] + ['--title', trace_title])
       subprocess.check_output(cmd)
+
+      elapsed_time = time.time() - start_time
+      logging.info('trace2html finished in %.02f seconds.', elapsed_time)
     finally:
       shutil.rmtree(temp_dir)
 
diff --git a/catapult/tracing/tracing/trace_data/trace_data_unittest.py b/catapult/tracing/tracing/trace_data/trace_data_unittest.py
index b065b4f..dd91596 100644
--- a/catapult/tracing/tracing/trace_data/trace_data_unittest.py
+++ b/catapult/tracing/tracing/trace_data/trace_data_unittest.py
@@ -21,7 +21,7 @@
       ri = trace_data.CreateTraceDataFromRawData({'traceEvents': [1, 2, 3]})
       ri.Serialize(trace_path)
       with open(trace_path) as f:
-        json_traces = html2trace.ReadTracesFromHTMLFilePath(f)
+        json_traces = html2trace.ReadTracesFromHTMLFile(f)
       self.assertEqual(json_traces, [{'traceEvents': [1, 2, 3]}])
     finally:
       shutil.rmtree(test_dir)
@@ -55,13 +55,9 @@
     builder = trace_data.TraceDataBuilder()
     builder.AddTraceFor(trace_data.CHROME_TRACE_PART,
                         {'traceEvents': [1, 2, 3]})
-    builder.AddTraceFor(trace_data.TAB_ID_PART, ['tab-7'])
-    builder.AddTraceFor(trace_data.BATTOR_TRACE_PART, 'battor data here')
 
     d = builder.AsData()
     self.assertTrue(d.HasTracesFor(trace_data.CHROME_TRACE_PART))
-    self.assertTrue(d.HasTracesFor(trace_data.TAB_ID_PART))
-    self.assertTrue(d.HasTracesFor(trace_data.BATTOR_TRACE_PART))
 
     self.assertRaises(Exception, builder.AsData)