Import Cobalt 24.master.0.1032339
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..3b65cb7
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,13 @@
+# pycov configuration
+
+[run]
+source=.
+
+omit=
+  # Exclude test files from coverage report
+  *_test.py
+  cobalt/black_box_tests/tests/*
+  cobalt/media_integration_tests/*
+
+  # Exclude scripts in third_party/
+  third_party/*
diff --git a/.github/actions/api_leak_detector/action.yaml b/.github/actions/api_leak_detector/action.yaml
new file mode 100644
index 0000000..bc2d532
--- /dev/null
+++ b/.github/actions/api_leak_detector/action.yaml
@@ -0,0 +1,21 @@
+name: Run API Leak Detector
+description: Runs the API leak detector
+inputs:
+  relative_manifest_path:
+    description: "Path to leak manifest file."
+    required: false
+    default: ""
+
+runs:
+  using: "composite"
+  steps:
+    - name: Run Detector
+      shell: bash
+      run: |
+        set -x
+        env
+        MANIFEST_FLAG=""
+        if [ "${{ inputs.relative_manifest_path }}" != "" ]; then
+          MANIFEST_FLAG="--relative-manifest-path ${{ matrix.target_platform }}/${{matrix.config}}/${{ inputs.relative_manifest_path }}"
+        fi
+        python3 starboard/tools/api_leak_detector/api_leak_detector.py -p ${{ matrix.target_platform }} -c ${{matrix.config}} --submit-check $MANIFEST_FLAG
diff --git a/.github/actions/build/action.yaml b/.github/actions/build/action.yaml
new file mode 100644
index 0000000..f641013
--- /dev/null
+++ b/.github/actions/build/action.yaml
@@ -0,0 +1,40 @@
+name: Build Cobalt
+description: Builds Cobalt targets
+runs:
+  using: "composite"
+  steps:
+    - name: Set up Cloud SDK
+      if: startsWith(${{matrix.target_platform}}, 'android')
+      uses: isarkis/setup-gcloud@40dce7857b354839efac498d3632050f568090b6 # v1.1.1
+    - name: Set Android env vars
+      if: startsWith(${{matrix.target_platform}}, 'android')
+      run: |
+        echo "ANDROID_HOME=/root/starboard-toolchains/AndroidSdk/" >> $GITHUB_ENV
+        PROJECT_NAME=$(gcloud config get-value project)
+        echo "GCS_NIGHTLY_PATH=gs://${PROJECT_NAME}-build-artifacts" >> $GITHUB_ENV
+      shell: bash
+    - name: Build
+      run: |
+        set -x
+        env
+        if [ -z ${COBALT_BOOTLOADER+x} ]; then
+          BUILD_PLATFORM=${{ matrix.target_platform }}
+          BUILD_TARGET=all
+          if [[ "${{matrix.config}}" =~ ^(qa|gold)$ ]]; then
+            BUILD_TARGET=default
+          fi
+        else
+          BUILD_PLATFORM=${COBALT_BOOTLOADER}
+          BUILD_TARGET='loader_app_install elf_loader_sandbox_install crashpad_handler_install'
+        fi
+        # GitHub Runners have home set to /github/home.
+        if [ -d /root/starboard-toolchains ]; then
+          ln -s /root/starboard-toolchains /github/home/starboard-toolchains
+        fi
+        # Set Ninja output format
+        NINJA_STATUS="[%e sec | %f/%t %u remaining | %c/sec | j%r] "
+        ninja -C ${GITHUB_WORKSPACE}/out/${BUILD_PLATFORM}_${{matrix.config}} ${BUILD_TARGET}
+      shell: bash
+    - name: Show Sccache Stats
+      run: sccache -s
+      shell: bash
diff --git a/.github/actions/docker/action.yaml b/.github/actions/docker/action.yaml
new file mode 100644
index 0000000..0c27851
--- /dev/null
+++ b/.github/actions/docker/action.yaml
@@ -0,0 +1,100 @@
+name: Docker Image Build
+description: Builds Cobalt build docker images.
+inputs:
+  docker_service:
+    description: "Docker compose service."
+    required: true
+  docker_image:
+    description: "Docker image name."
+    required: true
+
+runs:
+  using: "composite"
+  steps:
+    - name: Rename Limit
+      run: git config diff.renameLimit 999999
+      shell: bash
+    - name: Get docker file changes
+      id: changed-files
+      uses: tj-actions/changed-files@8953e851a137075e59e84b5c15fbeb3617e82f15 # v32.1.1
+      with:
+        files_ignore: third_party/**
+        files: |
+          docker-compose.yml
+          docker/linux/**
+          .github/actions/docker/**
+    - name: Retrieve Docker metadata
+      id: meta
+      uses: docker/metadata-action@507c2f2dc502c992ad446e3d7a5dfbe311567a96 # v4.3.0
+      with:
+        images: ${{env.REGISTRY}}/${{github.repository}}/${{inputs.docker_image}}
+        tags: |
+          type=ref,event=branch
+          type=ref,event=tag
+          type=ref,event=pr
+    - name: Set Docker Tag
+      id: set-docker-tag
+      run: |
+        set -x
+        docker_tag="${{ steps.meta.outputs.tags }}"
+        docker_tag="${docker_tag%.1[+,-]}"
+        echo "DOCKER_TAG=${docker_tag}" >> $GITHUB_ENV
+      shell: bash
+    # We need to set docker tag properly for pull requests.  In those scenarios where no docker related files
+    # were changed we need to use an existing image (e.g. main).  In cases where docker image is rebuilt we have
+    # to use tag generated by the image build.
+    - name: Set Docker Tag
+      id: set-docker-tag-presubmit-non-fork
+      env:
+        REPO: ${{ github.repository }}
+      if: ${{ (steps.changed-files.outputs.any_changed == 'false') && (github.event_name == 'pull_request') }}
+      run: echo "DOCKER_TAG=ghcr.io/${REPO}/${{inputs.docker_image}}:${GITHUB_BASE_REF%.1+}" >> $GITHUB_ENV
+      shell: bash
+    - name: Set up Cloud SDK
+      if: ${{ (steps.changed-files.outputs.any_changed == 'true') && (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.fork) }}
+      uses: isarkis/setup-gcloud@40dce7857b354839efac498d3632050f568090b6 # v1.1.1
+    - name: Set Docker Tag
+      id: set-docker-tag-presubmit-fork
+      env:
+        GITHUB_EVENT_NUMBER: ${{ github.event.number }}
+      if: ${{ (steps.changed-files.outputs.any_changed == 'true') && (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.fork) }}
+      run: |
+        # Need to login to GCR to be able to push images created by fork based PR workflows.
+        PROJECT_NAME=$(gcloud config get-value project)
+        METADATA="http://metadata.google.internal./computeMetadata/v1"
+        SVC_ACCT="${METADATA}/instance/service-accounts/default"
+        ACCESS_TOKEN=$(curl -H 'Metadata-Flavor: Google' ${SVC_ACCT}/token | cut -d'"' -f 4)
+        printf ${ACCESS_TOKEN} | docker login -u oauth2accesstoken --password-stdin https://gcr.io
+        echo "DOCKER_TAG=gcr.io/${PROJECT_NAME}/${{inputs.docker_image}}:pr-${GITHUB_EVENT_NUMBER}" >> $GITHUB_ENV
+      shell: bash
+    - name: Process Docker metadata
+      id: process-docker-metadata
+      run: |
+        set -x
+        set +e
+        docker manifest inspect $DOCKER_TAG > /dev/null
+        if [[ $? -ne 0 || ${{ steps.changed-files.outputs.any_changed }} == 'true' ]]; then
+          echo "need_to_build=true" >> $GITHUB_ENV
+        else
+          echo "need_to_build=false" >> $GITHUB_ENV
+        fi
+      shell: bash
+    - name: Build containers with docker-compose
+      id: build-image
+      if: env.need_to_build == 'true'
+      env:
+        SERVICE: ${{inputs.docker_service}}
+      shell: bash
+      run: |
+        set -xue
+        DOCKER_BUILDKIT=0 docker compose -f docker-compose.yml up --build --no-start "${SERVICE}"
+    - name: Tag images
+      id: tag-images
+      if: env.need_to_build == 'true'
+      run: docker tag ${{inputs.docker_image}} $DOCKER_TAG
+      shell: bash
+    - name: Push images
+      id: push-image
+      if: env.need_to_build == 'true'
+      run: docker push ${DOCKER_TAG}
+      shell: bash
diff --git a/.github/actions/docker_win/action.yaml b/.github/actions/docker_win/action.yaml
new file mode 100644
index 0000000..a082e1e
--- /dev/null
+++ b/.github/actions/docker_win/action.yaml
@@ -0,0 +1,76 @@
+name: Docker Image Build
+description: Builds Cobalt build docker images.
+inputs:
+  service:
+    description: "Service name from docker compose."
+    required: true
+runs:
+  using: "composite"
+  steps:
+    - name: Rename Limit
+      run: git config diff.renameLimit 999999
+      shell: bash
+    - name: Get docker file changes
+      id: changed-files
+      uses: tj-actions/changed-files@8953e851a137075e59e84b5c15fbeb3617e82f15 # v32.1.1
+      with:
+        files_ignore: third_party/**
+        files: |
+          docker-compose-windows.yml
+          docker/windows/**
+          .github/actions/docker_win/**
+    - name: Retrieve Docker metadata
+      id: meta
+      uses: docker/metadata-action@507c2f2dc502c992ad446e3d7a5dfbe311567a96 # v4.3.0
+      with:
+        images: ${{env.REGISTRY}}/${{github.repository}}/cobalt-${{inputs.service}}
+        tags: |
+          type=ref,event=branch
+          type=ref,event=tag
+          type=ref,event=pr
+    - name: Set Docker Tag
+      run: |
+        set -x
+        docker_tag="${{ steps.meta.outputs.tags }}"
+        docker_tag="${docker_tag%.1[+,-]}"
+        echo "DOCKER_TAG=${docker_tag}" >> $GITHUB_ENV
+      shell: bash
+    # We need to set docker tag properly for pull requests.  In those scenarios where no docker related files
+    # were changed we need to use an existing image (e.g. main).  In cases where docker image is rebuilt we have
+    # to use tag generated by the image build.
+    - name: Set Docker Tag
+      if: ${{ (steps.changed-files.outputs.any_changed == 'false') && (github.event_name == 'pull_request') }}
+      env:
+        REPO: ${{ github.repository }}
+      run: echo "DOCKER_TAG=ghcr.io/${REPO}/cobalt-${{inputs.service}}:${GITHUB_BASE_REF%.1+}" >> $GITHUB_ENV
+      shell: bash
+    - name: Process Docker metadata
+      id: process-docker
+      run: |
+        set -x
+        set +e
+        docker manifest inspect $DOCKER_TAG > /dev/null
+        if [[ $? -ne 0 || ${{ steps.changed-files.outputs.any_changed }} == 'true' ]]; then
+          echo "need_to_build=true" >> $GITHUB_ENV
+        else
+          echo "need_to_build=false" >> $GITHUB_ENV
+        fi
+      shell: bash
+    - name: Build containers with docker-compose
+      if: env.need_to_build == 'true'
+      env:
+        DOCKER_CPUS: 2
+        SERVICE: ${{inputs.service}}
+      shell: bash
+      run: |
+        set -xue
+        docker compose -f docker-compose-windows.yml up --no-start "${SERVICE}"
+    - name: Tag images
+      if: ${{ (env.need_to_build == 'true') && ((github.event_name != 'pull_request') || (!github.event.pull_request.head.repo.fork)) }}
+      run: |
+        docker tag cobalt-${{inputs.service}} $DOCKER_TAG
+      shell: bash
+    - name: Push images
+      if: ${{ (env.need_to_build == 'true') && ((github.event_name != 'pull_request') || (!github.event.pull_request.head.repo.fork)) }}
+      run: docker push ${DOCKER_TAG}
+      shell: bash
diff --git a/.github/actions/gn/action.yaml b/.github/actions/gn/action.yaml
new file mode 100644
index 0000000..7afac03
--- /dev/null
+++ b/.github/actions/gn/action.yaml
@@ -0,0 +1,36 @@
+name: GN
+description: Generates and checks GN.
+runs:
+  using: "composite"
+  steps:
+    - name: Configure Environment
+      shell: bash
+      run: |
+        echo "PYTHONPATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV
+    - name: Set up Cloud SDK
+      if: startsWith(${{matrix.target_platform}}, 'android')
+      uses: isarkis/setup-gcloud@40dce7857b354839efac498d3632050f568090b6 # v1.1.1
+    - name: Configure Android Environment
+      shell: bash
+      if: startsWith(${{matrix.target_platform}}, 'android')
+      run: |
+        echo "ANDROID_HOME=/root/starboard-toolchains/AndroidSdk/" >> $GITHUB_ENV
+        echo "COBALT_GRADLE_BUILD_COUNT=24" >> $GITHUB_ENV
+        PROJECT_NAME=$(gcloud config get-value project)
+        echo "GCS_NIGHTLY_PATH=gs://${PROJECT_NAME}-build-artifacts" >> $GITHUB_ENV
+    - name: GN
+      run: |
+        set -x
+        extra_arguments="${{matrix.extra_gn_arguments}}"
+        if [ -z ${COBALT_BOOTLOADER+x} ]; then
+          BUILD_PLATFORM=${{ matrix.target_platform }}
+        else
+          BUILD_PLATFORM=${COBALT_BOOTLOADER}
+          if [ ! -z "${{matrix.bootloader_extra_gn_arguments}}" ]
+          then
+            extra_arguments="${{matrix.bootloader_extra_gn_arguments}}"
+          fi
+        fi
+        gn gen $GITHUB_WORKSPACE/out/${BUILD_PLATFORM}_${{matrix.config}} --args="target_platform=\"${BUILD_PLATFORM}\" ${{matrix.sb_api_version}} ${{matrix.target_os}} ${{matrix.target_cpu}} ${extra_arguments} is_internal_build=false build_type=\"${{matrix.config}}\""
+        gn check $GITHUB_WORKSPACE/out/${BUILD_PLATFORM}_${{ matrix.config }}
+      shell: bash
diff --git a/.github/actions/on_device_tests/action.yaml b/.github/actions/on_device_tests/action.yaml
new file mode 100644
index 0000000..d07f984
--- /dev/null
+++ b/.github/actions/on_device_tests/action.yaml
@@ -0,0 +1,102 @@
+name: On Device Test
+description: Runs on-device tests.
+
+runs:
+  using: "composite"
+  steps:
+    - name: Install requirements
+      run: |
+        pip3 install grpcio==1.38.0 grpcio-tools==1.38.0
+      shell: bash
+    - name: Generate gRPC files
+      run: |
+        python -m grpc_tools.protoc -Itools/ --python_out=tools/ --grpc_python_out=tools/ tools/on_device_tests_gateway.proto
+      shell: bash
+    - name: Set up Cloud SDK
+      uses: isarkis/setup-gcloud@40dce7857b354839efac498d3632050f568090b6 # v1.1.1
+    - name: Set env vars
+      env:
+        WORKFLOW: ${{ github.workflow }}
+      run: |
+        echo "PROJECT_NAME=$(gcloud config get-value project)" >> $GITHUB_ENV
+        echo "GITHUB_RUN_NUMBER=${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
+        echo "WORKFLOW=${WORKFLOW}" >> $GITHUB_ENV
+
+        # Boot loader env
+        if [ "${COBALT_BOOTLOADER}" != "null" ]; then
+          echo "LOADER_CONFIG=${{ matrix.config }}" >> $GITHUB_ENV
+          echo "LOADER_PLATFORM=${COBALT_BOOTLOADER}" >> $GITHUB_ENV
+        fi
+
+        # Dimension env
+        if [ "${{ matrix.dimension }}" != "null" ]; then
+          echo "DIMENSION=${{ matrix.dimension }}" >> $GITHUB_ENV
+        fi
+
+        # Shard env
+        if [[ "${{matrix.shard}}" == 'black_box_test' || "${{matrix.shard}}" == 'evergreen_test' || "${{matrix.shard}}" == 'unit_test' ]]; then
+          echo "SHARD_NAME=${{ matrix.shard }}" >> $GITHUB_ENV
+          echo "TEST_TYPE=${{ matrix.shard }}" >> $GITHUB_ENV
+        else
+          echo "SHARD_NAME=unit_test_${{ matrix.shard }}" >> $GITHUB_ENV
+          echo "TEST_TYPE=unit_test" >> $GITHUB_ENV
+          echo "USE_SHARDING=1" >> $GITHUB_ENV
+        fi
+      shell: bash
+    - name: trigger ${{ env.SHARD_NAME }} tests on ${{ matrix.platform }} platform
+      env:
+        GITHUB_SHA: ${{ github.sha }}
+        GITHUB_TOKEN: ${{ github.token }}
+        GITHUB_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
+        GITHUB_EVENT_NAME: ${{ github.event_name }}
+        GITHUB_ACTOR: ${{ github.actor }}
+        GITHUB_TRIGGERING_ACTOR: ${{ github.triggering_actor }}
+        GITHUB_ACTOR_ID: ${{ github.actor_id }}
+        GITHUB_REPO: ${{ github.repository }}
+        GITHUB_PR_HEAD_USER_LOGIN: ${{ github.event.pull_request.head.user.login }}
+        GITHUB_PR_HEAD_USER_ID: ${{ github.event.pull_request.head.user.id }}
+        GITHUB_COMMIT_AUTHOR_USERNAME: ${{ github.event.commits[0].author.username }}
+        GITHUB_COMMIT_AUTHOR_EMAIL: ${{ github.event.commits[0].author.email }}
+      run: |
+        set -uxe
+        SESSION_ID=$(
+          python3 tools/on_device_tests_gateway_client.py \
+            --token ${GITHUB_TOKEN} \
+            --change_id "${GITHUB_PR_HEAD_SHA:-$GITHUB_SHA}" \
+            trigger \
+            --test_type ${{ env.TEST_TYPE }} \
+            --platform ${{ matrix.target_platform }} \
+            --config ${{ matrix.config }} \
+            --tag cobalt_github_${GITHUB_EVENT_NAME} \
+            --builder_name github_${{ matrix.platform }}_tests \
+            --build_number ${GITHUB_RUN_NUMBER} \
+            ${LOADER_PLATFORM:+"--loader_config" "$LOADER_CONFIG"} \
+            ${LOADER_PLATFORM:+"--loader_platform" "$LOADER_PLATFORM"} \
+            ${DIMENSION:+"--dimension" "$DIMENSION"} \
+            ${USE_SHARDING:+"--unittest_shard_index" "${{ matrix.shard }}"} \
+            ${ON_DEVICE_TEST_ATTEMPTS:+"--test_attempts" "$ON_DEVICE_TEST_ATTEMPTS"} \
+            --archive_path gs://${PROJECT_NAME}-test-artifacts/${WORKFLOW}/${GITHUB_RUN_NUMBER}/${{ matrix.platform }}_${{ matrix.config }}/artifacts.tar \
+            --label github \
+            --label ${GITHUB_EVENT_NAME} \
+            --label ${WORKFLOW} \
+            --label actor-${GITHUB_ACTOR} \
+            --label actor_id-${GITHUB_ACTOR_ID} \
+            --label triggering_actor-${GITHUB_TRIGGERING_ACTOR} \
+            --label sha-${GITHUB_SHA} \
+            --label repository-${GITHUB_REPO} \
+            --label author-${GITHUB_PR_HEAD_USER_LOGIN:-$GITHUB_COMMIT_AUTHOR_USERNAME} \
+            --label author_id-${GITHUB_PR_HEAD_USER_ID:-$GITHUB_COMMIT_AUTHOR_EMAIL}
+        )
+        echo "SESSION_ID=$SESSION_ID" >> $GITHUB_ENV
+      shell: bash
+    - name: watch ${{ env.SHARD_NAME }} tests on ${{ matrix.platform }} platform
+      env:
+        GITHUB_TOKEN: ${{ github.token }}
+        GITHUB_SHA: ${{ github.sha }}
+      run: |
+        set -uxe
+        python3 tools/on_device_tests_gateway_client.py \
+          --token "${GITHUB_TOKEN}" \
+          --change_id "${GITHUB_SHA}" \
+          watch ${{ env.SESSION_ID }}
+      shell: bash
diff --git a/.github/actions/on_host_test/action.yaml b/.github/actions/on_host_test/action.yaml
new file mode 100644
index 0000000..a10a8bf
--- /dev/null
+++ b/.github/actions/on_host_test/action.yaml
@@ -0,0 +1,99 @@
+name: On Host Test
+description: Runs on-host tests.
+inputs:
+  os:
+    description: "Host OS (either linux or windows)."
+    required: true
+runs:
+  using: "composite"
+  steps:
+    - name: Set up Cloud SDK
+      uses: isarkis/setup-gcloud@40dce7857b354839efac498d3632050f568090b6 # v1.1.1
+    - name: Configure Environment
+      id: configure-environment
+      shell: bash
+      run: |
+        set -x
+        if [ "${{inputs.os}}" == 'linux' ]
+        then
+          echo "ARCHIVE_EXTENSION=tar.xz" >> $GITHUB_ENV
+        elif [ "${{inputs.os}}" == 'windows' ]
+        then
+          echo "ARCHIVE_EXTENSION=tar.gz" >> $GITHUB_ENV
+        fi
+    - name: Download Archive
+      shell: bash
+      env:
+        WORKFLOW: ${{ github.workflow }}
+      run: |
+        set -x
+        PROJECT_NAME=$(gcloud config get-value project)
+        gsutil cp gs://${PROJECT_NAME}-test-artifacts/${WORKFLOW}/${GITHUB_RUN_NUMBER}/${{matrix.platform}}_${{matrix.config}}/${{matrix.platform}}_${{matrix.config}}.${ARCHIVE_EXTENSION} ${GITHUB_WORKSPACE}/out/tmp/${{matrix.platform}}_${{matrix.config}}.${ARCHIVE_EXTENSION}
+    - name: Extract Archive
+      shell: bash
+      run: |
+        set -x
+        parallel=
+        if [[ "${{inputs.os}}" == 'linux' ]]; then
+          parallel="--parallel"
+        fi
+        python3 ${GITHUB_WORKSPACE}/tools/create_archive.py -x -s ${GITHUB_WORKSPACE}/out/tmp/${{matrix.platform}}_${{matrix.config}}.${ARCHIVE_EXTENSION} -d ${GITHUB_WORKSPACE}/out ${parallel}
+        rm -rf ${GITHUB_WORKSPACE}/out/tmp
+    - name: Download Bootloader Archive
+      if: ${{ env.COBALT_BOOTLOADER != null && env.COBALT_BOOTLOADER != 'null' }}
+      shell: bash
+      env:
+        WORKFLOW: ${{ github.workflow }}
+      run: |
+        set -x
+        PROJECT_NAME=$(gcloud config get-value project)
+        gsutil cp gs://${PROJECT_NAME}-test-artifacts/${WORKFLOW}/${GITHUB_RUN_NUMBER}/${{matrix.platform}}_${{matrix.config}}/${COBALT_BOOTLOADER}_${{matrix.config}}.${ARCHIVE_EXTENSION} ${GITHUB_WORKSPACE}/out/tmp/${COBALT_BOOTLOADER}_${{matrix.config}}.${ARCHIVE_EXTENSION}
+    - name: Extract Bootloader Archive
+      if: ${{ env.COBALT_BOOTLOADER != null && env.COBALT_BOOTLOADER != 'null' }}
+      shell: bash
+      run: |
+        set -x
+        python3 ${GITHUB_WORKSPACE}/tools/create_archive.py -x -s ${GITHUB_WORKSPACE}/out/tmp/${COBALT_BOOTLOADER}_${{matrix.config}}.${ARCHIVE_EXTENSION} -d ${GITHUB_WORKSPACE}/out --parallel
+        rm -rf ${GITHUB_WORKSPACE}/out/tmp
+    - name: Set Env Variables
+      shell: bash
+      run: |
+        echo "PYTHONPATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV
+        echo "TEST_RESULTS_DIR=${GITHUB_WORKSPACE}/unit-test-results" >> $GITHUB_ENV
+        echo "TEST_REPORT_FILE=${GITHUB_WORKSPACE}/${{matrix.platform}}-${{matrix.shard}}" >> $GITHUB_ENV
+    - name: Run Tests
+      shell: bash
+      run: |
+        set -x
+        loader_args=''
+        if [ "${COBALT_BOOTLOADER}" != "null" ]; then
+          loader_args="--loader_platform ${COBALT_BOOTLOADER} --loader_config ${{matrix.config}}"
+        fi
+        if [[ "${{matrix.shard}}" == 'integration' ]]; then
+          xvfb-run -a --server-args="-screen 0 1920x1080x24i +render +extension GLX -noreset" python3 $GITHUB_WORKSPACE/cobalt/black_box_tests/black_box_tests.py --platform ${{matrix.target_platform}} --config ${{matrix.config}} ${loader_args}
+        elif [[ "${{matrix.shard}}" == 'blackbox' ]]; then
+          xvfb-run -a --server-args="-screen 0 1920x1080x24i +render +extension GLX -noreset" python3 $GITHUB_WORKSPACE/cobalt/black_box_tests/black_box_tests.py --platform ${{matrix.target_platform}} --config ${{matrix.config}} ${loader_args} --test_set blackbox
+        elif [[ "${{matrix.shard}}" == 'wpt' ]]; then
+          xvfb-run -a --server-args="-screen 0 1920x1080x24i +render +extension GLX -noreset" python3 $GITHUB_WORKSPACE/cobalt/black_box_tests/black_box_tests.py --platform ${{matrix.target_platform}} --config ${{matrix.config}} ${loader_args} --test_set wpt
+        elif [[ "${{matrix.shard}}" == 'evergreen' ]]; then
+          xvfb-run -a --server-args="-screen 0 1920x1080x24i +render +extension GLX -noreset" python3 $GITHUB_WORKSPACE/cobalt/evergreen_tests/evergreen_tests.py --platform ${{matrix.target_platform}} --config ${{matrix.config}} ${loader_args} --no-can_mount_tmpfs
+        else
+          if [[ "${{inputs.os}}" == 'windows' ]]; then
+            python3 ${GITHUB_WORKSPACE}/starboard/tools/testing/test_runner.py --platform ${{matrix.target_platform}} --config ${{matrix.config}} -s ${{matrix.shard}} -r
+          else
+            xvfb-run -a --server-args="-screen 0 1920x1080x24i +render +extension GLX -noreset" python3 ${GITHUB_WORKSPACE}/starboard/tools/testing/test_runner.py --platform ${{matrix.target_platform}} --config ${{matrix.config}} -s ${{matrix.shard}} -r ${loader_args} --xml_output_dir=${TEST_RESULTS_DIR}
+          fi
+        fi
+    - name: Process unit test results
+      if: failure()
+      shell: bash
+      run: |
+        set -x
+        echo "Saving unit test report to ${TEST_REPORT_FILE}"
+        python3 ${GITHUB_WORKSPACE}/starboard/tools/testing/test_report_parser.py ${TEST_RESULTS_DIR} > ${TEST_REPORT_FILE}
+    - name: Upload unit test report
+      uses: actions/upload-artifact@v3
+      if: failure()
+      with:
+        name: unit-test-reports
+        path: ${{env.TEST_REPORT_FILE}}
diff --git a/.github/actions/pre_commit/action.yaml b/.github/actions/pre_commit/action.yaml
new file mode 100644
index 0000000..d8bf9a8
--- /dev/null
+++ b/.github/actions/pre_commit/action.yaml
@@ -0,0 +1,25 @@
+name: pre-commit
+description: Runs pre-commit
+inputs:
+  base_ref:
+    description: "Ref to run from"
+    required: true
+runs:
+  using: "composite"
+  steps:
+    - run: python -m pip install pre-commit
+      shell: bash
+    - run: python -m pip freeze --local
+      shell: bash
+    - uses: actions/cache@v3
+      with:
+        path: ~/.cache/pre-commit
+        key: pre-commit-3|${{ env.pythonLocation }}|${{ hashFiles('.pre-commit-config.yaml') }}
+    - run: pre-commit run --show-diff-on-failure --color=always --from-ref ${{ inputs.base_ref }} --to-ref HEAD
+      shell: bash
+      env:
+        SKIP: 'run-py2-tests'
+    - run: pre-commit run --show-diff-on-failure --color=always --hook-stage push --from-ref ${{ inputs.base_ref }} --to-ref HEAD
+      shell: bash
+      env:
+        SKIP: 'test-download-from-gcs-helper,check-bug-in-commit-message,check-if-starboard-interface-changed'
diff --git a/.github/actions/upload_nightly_artifacts/action.yaml b/.github/actions/upload_nightly_artifacts/action.yaml
new file mode 100644
index 0000000..345e06b
--- /dev/null
+++ b/.github/actions/upload_nightly_artifacts/action.yaml
@@ -0,0 +1,39 @@
+name: Upload Nightly Artifacts
+description: Archives and uploads nightly artifacts to GCS bucket.
+runs:
+  using: "composite"
+  steps:
+    - name: Set up Cloud SDK
+      uses: isarkis/setup-gcloud@40dce7857b354839efac498d3632050f568090b6 # v1.1.1
+    - name: Set env vars
+      env:
+        WORKFLOW: ${{github.workflow}}
+      run: |
+        echo "ARCHIVE_FILE=cobalt-${{matrix.platform}}_${{matrix.config}}.tar.gz" >> $GITHUB_ENV
+        echo "ARCHIVE_PATH=$GITHUB_WORKSPACE/cobalt-${{matrix.platform}}_${{matrix.config}}.tar.gz" >> $GITHUB_ENV
+        echo "PROJECT_NAME=$(gcloud config get-value project)" >> $GITHUB_ENV
+        echo "TODAY=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
+        echo "GITHUB_RUN_NUMBER=${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV
+        echo "WORKFLOW=${WORKFLOW}" >> $GITHUB_ENV
+        echo "PYTHONPATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV
+      shell: bash
+    - name: Copy Out Folder
+      run: |
+        # Clean up.
+        [ -d "${GITHUB_WORKSPACE}/out/upload_out" ] && rm -rf "${GITHUB_WORKSPACE}/out/upload_out"
+        [ -f "${ARCHIVE_FILE}" ] && rm -rf "${ARCHIVE_FILE}"
+        # Create an archive.
+        python3 $GITHUB_WORKSPACE/tools/copy_and_filter_out_dir.py -d $GITHUB_WORKSPACE/out/upload_out/${{matrix.target_platform}}_${{matrix.config}} -s $GITHUB_WORKSPACE/out/${{matrix.target_platform}}_${{matrix.config}}
+      shell: bash
+    - name: Create Archive
+      run: |
+        set -x
+        cd "$GITHUB_WORKSPACE"
+        python3 $GITHUB_WORKSPACE/tools/create_archive.py --intermediate -d ${{env.ARCHIVE_FILE}} -s out/upload_out
+      shell: bash
+    - name: Upload Archive
+      id: upload-archive
+      shell: bash
+      run: |
+        set -uex
+        gsutil -d cp "${ARCHIVE_PATH}" "gs://${PROJECT_NAME}-build-artifacts/${WORKFLOW}/${TODAY}/${GITHUB_RUN_NUMBER}/"
diff --git a/.github/actions/upload_test_artifacts/action.yaml b/.github/actions/upload_test_artifacts/action.yaml
new file mode 100644
index 0000000..d2923eb
--- /dev/null
+++ b/.github/actions/upload_test_artifacts/action.yaml
@@ -0,0 +1,78 @@
+name: Test Artifact Upload
+description: Uploads test archives to GCS and runs on-device tests.
+inputs:
+  type:
+    description: "Type of artifacts to upload (ondevice or onhost)"
+    required: true
+  os:
+    description: "Host OS (either linux or windows)."
+    required: true
+runs:
+  using: "composite"
+  steps:
+    - name: Set up Cloud SDK
+      uses: isarkis/setup-gcloud@40dce7857b354839efac498d3632050f568090b6 # v1.1.1
+    - name: Configure Environment
+      env:
+        WORKFLOW: ${{ github.workflow }}
+      run: |
+        set -x
+        project_name=$(gcloud config get-value project)
+        if [ -z ${COBALT_BOOTLOADER+x} ]
+        then
+          PLATFORM=${{matrix.platform}}
+          echo "TARGET_PLATFORM=${{matrix.target_platform}}" >> $GITHUB_ENV
+        else
+          PLATFORM=${COBALT_BOOTLOADER}
+          echo "TARGET_PLATFORM=${COBALT_BOOTLOADER}" >> $GITHUB_ENV
+        fi
+
+        if [ "${{ inputs.type }}" == 'ondevice' ]
+        then
+          echo "ARCHIVE_FILE=artifacts.tar" >> $GITHUB_ENV
+          echo "ARCHIVE_PATH=$GITHUB_WORKSPACE/artifacts.tar" >> $GITHUB_ENV
+          echo "DESTINATION=${project_name}-test-artifacts/${WORKFLOW}/${GITHUB_RUN_NUMBER}/${{matrix.platform}}_${{matrix.config}}/" >> $GITHUB_ENV
+        elif [ "${{ inputs.type }}" == 'onhost' ]
+        then
+          if [ "${{ inputs.os }}" == 'linux' ]
+          then
+            echo "ARCHIVE_FILE=${PLATFORM}_${{matrix.config}}.tar.xz" >> $GITHUB_ENV
+            echo "ARCHIVE_PATH=$GITHUB_WORKSPACE/${PLATFORM}_${{matrix.config}}.tar.xz" >> $GITHUB_ENV
+          elif [ "${{ inputs.os }}" == 'windows' ]
+          then
+            echo "ARCHIVE_FILE=${PLATFORM}_${{matrix.config}}.tar.gz" >> $GITHUB_ENV
+            echo "ARCHIVE_PATH=$GITHUB_WORKSPACE/${PLATFORM}_${{matrix.config}}.tar.gz" >> $GITHUB_ENV
+          fi
+          echo "DESTINATION=${project_name}-test-artifacts/${WORKFLOW}/${GITHUB_RUN_NUMBER}/${{matrix.platform}}_${{matrix.config}}/" >> $GITHUB_ENV
+        fi
+
+        echo "PYTHONPATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV
+        project_name=$(gcloud config get-value project)
+      shell: bash
+    - name: Create Test Files Archive
+      run: |
+        set -x
+        [ -f "${ARCHIVE_PATH}" ] && rm -rf "${ARCHIVE_PATH}"
+        if [ "${{ inputs.type }}" == 'ondevice' ]
+        then
+          outdir="$GITHUB_WORKSPACE/out/${{matrix.target_platform}}_${{matrix.config}}"
+          if [ -n "${COBALT_BOOTLOADER}" ]
+          then
+            outdir="${outdir} $GITHUB_WORKSPACE/out/${COBALT_BOOTLOADER}_${{matrix.config}}"
+          fi
+          python3 $GITHUB_WORKSPACE/tools/create_archive.py --test_infra -d ${{env.ARCHIVE_FILE}} -s ${outdir}
+        elif [ "${{ inputs.type }}" == 'onhost' ]
+        then
+          parallel=
+          if [[ "${{inputs.os}}" == 'linux' ]]; then
+            parallel='--parallel'
+          fi
+          python3 $GITHUB_WORKSPACE/tools/create_archive.py --intermediate -d ${{env.ARCHIVE_FILE}} -s $GITHUB_WORKSPACE/out/${TARGET_PLATFORM}_${{matrix.config}} $parallel
+        fi
+      shell: bash
+    - name: Copy Test Files to GCS
+      id: upload-test-archive
+      shell: bash
+      run: |
+        set -eux
+        gsutil -d cp "${ARCHIVE_PATH}" "gs://${DESTINATION}"
diff --git a/.github/config/android-arm.json b/.github/config/android-arm.json
new file mode 100644
index 0000000..50e30ce
--- /dev/null
+++ b/.github/config/android-arm.json
@@ -0,0 +1,26 @@
+{
+  "docker_service": "build-android",
+  "on_device_test": {
+    "enabled": true,
+    "tests": [
+      "0",
+      "1",
+      "2",
+      "3",
+      "black_box_test"
+    ],
+    "test_attempts": 2
+  },
+  "platforms": [
+    "android-arm"
+  ],
+  "includes": [
+    {
+      "name":"arm",
+      "platform":"android-arm",
+      "target_platform":"android-arm",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "target_os":"target_os=\\\"android\\\""
+    }
+  ]
+}
diff --git a/.github/config/android-arm64.json b/.github/config/android-arm64.json
new file mode 100644
index 0000000..fe96371
--- /dev/null
+++ b/.github/config/android-arm64.json
@@ -0,0 +1,26 @@
+{
+  "docker_service": "build-android",
+  "on_device_test": {
+    "enabled": true,
+    "tests": [
+      "0",
+      "1",
+      "2",
+      "3",
+      "black_box_test"
+    ],
+    "test_attempts": 2
+  },
+  "platforms": [
+    "android-arm64"
+  ],
+  "includes": [
+    {
+      "name":"arm64",
+      "platform":"android-arm64",
+      "target_platform":"android-arm64",
+      "target_cpu":"target_cpu=\\\"arm64\\\"",
+      "target_os": "target_os=\\\"android\\\""
+    }
+  ]
+}
diff --git a/.github/config/android-x86.json b/.github/config/android-x86.json
new file mode 100644
index 0000000..7e371e9
--- /dev/null
+++ b/.github/config/android-x86.json
@@ -0,0 +1,26 @@
+{
+  "docker_service": "build-android",
+  "on_device_test": {
+    "enabled": true,
+    "tests": [
+      "0",
+      "1",
+      "2",
+      "3",
+      "black_box_test"
+    ],
+    "test_attempts": 2
+  },
+  "platforms": [
+    "android-x86"
+  ],
+  "includes": [
+    {
+      "name":"x86",
+      "platform":"android-x86",
+      "target_platform":"android-x86",
+      "target_cpu":"target_cpu=\\\"x86\\\"",
+      "target_os": "target_os=\\\"android\\\""
+    }
+  ]
+}
diff --git a/.github/config/evergreen-arm-hardfp.json b/.github/config/evergreen-arm-hardfp.json
new file mode 100644
index 0000000..8067c72
--- /dev/null
+++ b/.github/config/evergreen-arm-hardfp.json
@@ -0,0 +1,62 @@
+{
+  "docker_service": "build-raspi",
+  "bootloader": "raspi-2",
+  "on_device_test": {
+    "enabled": true,
+    "tests": [
+      "evergreen_test",
+      "0",
+      "1",
+      "2",
+      "3"
+    ],
+    "test_attempts": 2
+  },
+  "platforms": [
+    "evergreen-arm-hardfp",
+    "evergreen-arm-hardfp-sbversion-15",
+    "evergreen-arm-hardfp-sbversion-14",
+    "evergreen-arm-hardfp-sbversion-13"
+  ],
+  "includes": [
+    {
+      "name":"hardfp",
+      "platform":"evergreen-arm-hardfp",
+      "target_platform":"evergreen-arm-hardfp",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "bootloader_extra_gn_arguments": "use_asan=false is_clang=false",
+      "dimension": "release_version=regex:10.*"
+    },
+    {
+      "name":"sbversion-15",
+      "platform":"evergreen-arm-hardfp-sbversion-15",
+      "target_platform":"evergreen-arm-hardfp",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "bootloader_extra_gn_arguments":"use_asan=false is_clang=false",
+      "sb_api_version": "sb_api_version=15",
+      "dimension": "release_version=regex:10.*"
+    },
+    {
+      "name":"sbversion-14",
+      "platform":"evergreen-arm-hardfp-sbversion-14",
+      "target_platform":"evergreen-arm-hardfp",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "bootloader_extra_gn_arguments":"use_asan=false is_clang=false",
+      "sb_api_version": "sb_api_version=14",
+      "dimension": "release_version=regex:10.*"
+    },
+    {
+      "name":"sbversion-13",
+      "platform":"evergreen-arm-hardfp-sbversion-13",
+      "target_platform":"evergreen-arm-hardfp",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "bootloader_extra_gn_arguments":"use_asan=false is_clang=false",
+      "sb_api_version": "sb_api_version=13",
+      "dimension": "release_version=regex:10.*"
+    }
+  ]
+}
diff --git a/.github/config/evergreen-arm-softfp.json b/.github/config/evergreen-arm-softfp.json
new file mode 100644
index 0000000..a90304c
--- /dev/null
+++ b/.github/config/evergreen-arm-softfp.json
@@ -0,0 +1,42 @@
+{
+  "docker_service": "build-evergreen",
+  "platforms": [
+    "evergreen-arm-softfp",
+    "evergreen-arm-softfp-sbversion-15",
+		"evergreen-arm-softfp-sbversion-14",
+    "evergreen-arm-softfp-sbversion-13"
+  ],
+  "includes": [
+    {
+      "name":"softfp",
+      "platform":"evergreen-arm-softfp",
+      "target_platform":"evergreen-arm-softfp",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "extra_gn_arguments":"use_asan=false"
+    },
+    {
+      "name":"sbversion-15",
+      "platform":"evergreen-arm-softfp-sbversion-15",
+      "target_platform":"evergreen-arm-softfp",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "sb_api_version":"sb_api_version=15"
+    },
+    {
+      "name":"sbversion-14",
+      "platform":"evergreen-arm-softfp-sbversion-14",
+      "target_platform":"evergreen-arm-softfp",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "sb_api_version":"sb_api_version=14"
+    },
+    {
+      "name":"sbversion-13",
+      "platform":"evergreen-arm-softfp-sbversion-13",
+      "target_platform":"evergreen-arm-softfp",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "sb_api_version":"sb_api_version=13"
+    }
+  ]
+}
diff --git a/.github/config/evergreen-arm64.json b/.github/config/evergreen-arm64.json
new file mode 100644
index 0000000..607c332
--- /dev/null
+++ b/.github/config/evergreen-arm64.json
@@ -0,0 +1,42 @@
+{
+  "docker_service": "build-evergreen",
+  "platforms": [
+    "evergreen-arm64",
+    "evergreen-arm64-sbversion-15",
+    "evergreen-arm64-sbversion-14",
+    "evergreen-arm64-sbversion-13"
+  ],
+  "includes": [
+    {
+      "name":"arm64",
+      "platform":"evergreen-arm64",
+      "target_platform":"evergreen-arm64",
+      "target_cpu":"target_cpu=\\\"arm64\\\"",
+      "extra_gn_arguments":"use_asan=false"
+    },
+    {
+      "name":"sbversion-15",
+      "platform":"evergreen-arm64-sbversion-15",
+      "target_platform":"evergreen-arm64",
+      "target_cpu":"target_cpu=\\\"arm64\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "sb_api_version":"sb_api_version=15"
+    },
+    {
+      "name":"sbversion-14",
+      "platform":"evergreen-arm64-sbversion-14",
+      "target_platform":"evergreen-arm64",
+      "target_cpu":"target_cpu=\\\"arm64\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "sb_api_version":"sb_api_version=14"
+    },
+    {
+      "name":"sbversion-13",
+      "platform":"evergreen-arm64-sbversion-13",
+      "target_platform":"evergreen-arm64",
+      "target_cpu":"target_cpu=\\\"arm64\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "sb_api_version":"sb_api_version=13"
+    }
+  ]
+}
diff --git a/.github/config/evergreen-x64.json b/.github/config/evergreen-x64.json
new file mode 100644
index 0000000..f3171ec
--- /dev/null
+++ b/.github/config/evergreen-x64.json
@@ -0,0 +1,45 @@
+{
+  "docker_service": "build-linux-evergreen",
+  "on_host_test": true,
+  "bootloader": "linux-x64x11",
+  "on_host_test_shards": ["0", "1", "2", "3", "blackbox", "wpt", "evergreen"],
+  "platforms": [
+    "evergreen-x64",
+    "evergreen-x64-sbversion-15",
+		"evergreen-x64-sbversion-14",
+    "evergreen-x64-sbversion-13"
+  ],
+  "includes": [
+    {
+      "name":"x64",
+      "platform":"evergreen-x64",
+      "target_platform":"evergreen-x64",
+      "target_cpu":"target_cpu=\\\"x64\\\"",
+      "extra_gn_arguments":"use_asan=false"
+    },
+    {
+      "name":"sbversion-15",
+      "platform":"evergreen-x64-sbversion-15",
+      "target_platform":"evergreen-x64",
+      "target_cpu":"target_cpu=\\\"x64\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "sb_api_version":"sb_api_version=15"
+    },
+    {
+      "name":"sbversion-14",
+      "platform":"evergreen-x64-sbversion-14",
+      "target_platform":"evergreen-x64",
+      "target_cpu":"target_cpu=\\\"x64\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "sb_api_version":"sb_api_version=14"
+    },
+    {
+      "name":"sbversion-13",
+      "platform":"evergreen-x64-sbversion-13",
+      "target_platform":"evergreen-x64",
+      "target_cpu":"target_cpu=\\\"x64\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "sb_api_version":"sb_api_version=13"
+    }
+  ]
+}
diff --git a/.github/config/evergreen-x86.json b/.github/config/evergreen-x86.json
new file mode 100644
index 0000000..be80dde
--- /dev/null
+++ b/.github/config/evergreen-x86.json
@@ -0,0 +1,42 @@
+{
+  "docker_service": "build-evergreen",
+  "platforms": [
+    "evergreen-x86",
+    "evergreen-x86-sbversion-15",
+		"evergreen-x86-sbversion-14",
+    "evergreen-x86-sbversion-13"
+  ],
+  "includes": [
+    {
+      "name":"x86",
+      "platform":"evergreen-x86",
+      "target_platform":"evergreen-x86",
+      "target_cpu":"target_cpu=\\\"x86\\\"",
+      "extra_gn_arguments":"use_asan=false"
+    },
+    {
+      "name":"sbversion-15",
+      "platform":"evergreen-x86-sbversion-15",
+      "target_platform":"evergreen-x86",
+      "target_cpu":"target_cpu=\\\"x86\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "sb_api_version":"sb_api_version=15"
+    },
+    {
+      "name":"sbversion-14",
+      "platform":"evergreen-x86-sbversion-14",
+      "target_platform":"evergreen-x86",
+      "target_cpu":"target_cpu=\\\"x86\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "sb_api_version":"sb_api_version=14"
+    },
+    {
+      "name":"sbversion-13",
+      "platform":"evergreen-x86-sbversion-13",
+      "target_platform":"evergreen-x86",
+      "target_cpu":"target_cpu=\\\"x86\\\"",
+      "extra_gn_arguments":"use_asan=false",
+      "sb_api_version":"sb_api_version=13"
+    }
+  ]
+}
diff --git a/.github/config/linux-clang-3-9.json b/.github/config/linux-clang-3-9.json
new file mode 100644
index 0000000..59bcc19
--- /dev/null
+++ b/.github/config/linux-clang-3-9.json
@@ -0,0 +1,16 @@
+{
+  "docker_service": "build-linux-clang-3-9",
+  "on_host_test": true,
+  "on_host_test_shards": ["0", "1", "2", "3", "blackbox", "wpt"],
+  "platforms": [
+    "linux-x64x11-clang-3-9"
+  ],
+  "includes": [
+    {
+      "name":"clang-3-9",
+      "platform":"linux-x64x11-clang-3-9",
+      "target_platform":"linux-x64x11-clang-3-9",
+      "extra_gn_arguments":"using_old_compiler=true"
+    }
+  ]
+}
diff --git a/.github/config/linux-gcc-6-3.json b/.github/config/linux-gcc-6-3.json
new file mode 100644
index 0000000..9b65750
--- /dev/null
+++ b/.github/config/linux-gcc-6-3.json
@@ -0,0 +1,14 @@
+{
+  "docker_service": "build-linux-gcc",
+  "platforms": [
+    "linux-x64x11-gcc-6-3"
+  ],
+  "includes": [
+    {
+      "name":"gcc-6-3",
+      "platform":"linux-x64x11-gcc-6-3",
+      "target_platform":"linux-x64x11-gcc-6-3",
+      "extra_gn_arguments":"is_clang=false using_old_compiler=true"
+    }
+  ]
+}
diff --git a/.github/config/linux.json b/.github/config/linux.json
new file mode 100644
index 0000000..79ceb0b
--- /dev/null
+++ b/.github/config/linux.json
@@ -0,0 +1,48 @@
+{
+  "docker_service": "build-linux",
+  "on_host_test": true,
+  "on_host_test_shards": ["0", "1", "2", "3", "blackbox", "wpt"],
+  "platforms": [
+    "linux-x64x11",
+    "linux-x64x11-egl",
+    "linux-x64x11-skia",
+    "linux-x64x11-sbversion-13",
+    "linux-x64x11-sbversion-14",
+    "linux-x64x11-sbversion-15"
+  ],
+  "includes": [
+    {
+      "name":"x64",
+      "platform":"linux-x64x11",
+      "target_platform":"linux-x64x11"
+    },
+    {
+      "name":"egl",
+      "platform":"linux-x64x11-egl",
+      "target_platform":"linux-x64x11-egl"
+    },
+    {
+      "name":"skia",
+      "platform":"linux-x64x11-skia",
+      "target_platform":"linux-x64x11-skia"
+    },
+    {
+      "name":"sbversion-13",
+      "platform":"linux-x64x11-sbversion-13",
+      "target_platform":"linux-x64x11",
+      "sb_api_version":"sb_api_version=13"
+    },
+    {
+      "name":"sbversion-14",
+      "platform":"linux-x64x11-sbversion-14",
+      "target_platform":"linux-x64x11",
+      "sb_api_version":"sb_api_version=14"
+    },
+    {
+      "name":"sbversion-15",
+      "platform":"linux-x64x11-sbversion-15",
+      "target_platform":"linux-x64x11",
+      "sb_api_version":"sb_api_version=15"
+    }
+  ]
+}
diff --git a/.github/config/raspi-2-skia.json b/.github/config/raspi-2-skia.json
new file mode 100644
index 0000000..f99fb73
--- /dev/null
+++ b/.github/config/raspi-2-skia.json
@@ -0,0 +1,15 @@
+{
+  "docker_service": "build-raspi",
+  "platforms": [
+    "raspi-2-skia"
+  ],
+  "includes": [
+    {
+      "name":"raspi-2-skia",
+      "platform":"raspi-2-skia",
+      "target_platform":"raspi-2-skia",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "extra_gn_arguments": "is_clang=false"
+    }
+  ]
+}
diff --git a/.github/config/raspi-2.json b/.github/config/raspi-2.json
new file mode 100644
index 0000000..ab2a1e6
--- /dev/null
+++ b/.github/config/raspi-2.json
@@ -0,0 +1,58 @@
+{
+  "docker_service": "build-raspi",
+  "on_device_test": {
+    "enabled": true,
+    "tests": [
+      "0",
+      "1",
+      "2",
+      "3",
+      "4",
+      "5"
+    ],
+    "test_attempts": 2
+  },
+  "platforms": [
+    "raspi-2",
+    "raspi-2-sbversion-13",
+    "raspi-2-sbversion-14",
+    "raspi-2-sbversion-15"
+  ],
+  "includes": [
+    {
+      "name":"raspi",
+      "platform":"raspi-2",
+      "target_platform":"raspi-2",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "extra_gn_arguments": "is_clang=false",
+      "dimension": "release_version=regex:10.*"
+    },
+    {
+      "name":"sbversion-13",
+      "platform":"raspi-2-sbversion-13",
+      "target_platform":"raspi-2",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "extra_gn_arguments":"is_clang=false",
+      "sb_api_version": "sb_api_version=13",
+      "dimension": "release_version=regex:10.*"
+    },
+    {
+      "name":"sbversion-14",
+      "platform":"raspi-2-sbversion-14",
+      "target_platform":"raspi-2",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "extra_gn_arguments":"is_clang=false",
+      "sb_api_version": "sb_api_version=14",
+      "dimension": "release_version=regex:10.*"
+    },
+    {
+      "name":"sbversion-15",
+      "platform":"raspi-2-sbversion-15",
+      "target_platform":"raspi-2",
+      "target_cpu":"target_cpu=\\\"arm\\\"",
+      "extra_gn_arguments":"is_clang=false",
+      "sb_api_version": "sb_api_version=15",
+      "dimension": "release_version=regex:10.*"
+    }
+  ]
+}
diff --git a/.github/config/stub.json b/.github/config/stub.json
new file mode 100644
index 0000000..792466f
--- /dev/null
+++ b/.github/config/stub.json
@@ -0,0 +1,13 @@
+{
+  "docker_service": "build-linux-stub",
+  "platforms": [
+    "stub"
+  ],
+  "includes": [
+    {
+      "name":"stub",
+      "platform":"stub",
+      "target_platform":"stub"
+    }
+  ]
+}
diff --git a/.github/config/win32.json b/.github/config/win32.json
new file mode 100644
index 0000000..c65092c
--- /dev/null
+++ b/.github/config/win32.json
@@ -0,0 +1,17 @@
+{
+  "docker_service": "build-win-win32",
+  "docker_runner_service": "runner-win-win32",
+  "platforms": [
+    "win32"
+  ],
+  "on_host_test": true,
+  "on_host_test_shards": ["0", "1", "2", "3"],
+  "includes": [
+    {
+      "name":"win32",
+      "platform":"win32",
+      "target_platform":"win-win32",
+      "extra_gn_arguments":"is_clang=false visual_studio_path=\\\"C:/BuildTools/VC/Tools/MSVC/14.15.26726\\\" msvc_path=\\\"C:/BuildTools/VC/Tools/MSVC/14.15.26726\\\""
+    }
+  ]
+}
diff --git a/.github/workflows/android.yaml b/.github/workflows/android.yaml
new file mode 100644
index 0000000..f94dc7e
--- /dev/null
+++ b/.github/workflows/android.yaml
@@ -0,0 +1,48 @@
+name: android
+
+on:
+  pull_request:
+    types: [ready_for_review, opened, reopened, synchronize, labeled]
+    branches:
+      - main
+      - feature/*
+  push:
+    branches:
+      - main
+      - feature/*
+  schedule:
+    # GMT timezone.
+    - cron: '0 4 * * *'
+  workflow_dispatch:
+    inputs:
+      nightly:
+        description: 'Nightly workflow.'
+        required: true
+        type: boolean
+        default: false
+
+jobs:
+  android-arm64:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: android-arm64
+      nightly: ${{ github.event.inputs.nightly }}
+  android-x86:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: android-x86
+      nightly: ${{ github.event.inputs.nightly }}
+  android-arm:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: android-arm
+      nightly: ${{ github.event.inputs.nightly }}
diff --git a/.github/workflows/evergreen.yaml b/.github/workflows/evergreen.yaml
new file mode 100644
index 0000000..ef12fdf
--- /dev/null
+++ b/.github/workflows/evergreen.yaml
@@ -0,0 +1,69 @@
+name: evergreen
+
+on:
+  pull_request:
+    types: [ready_for_review, opened, reopened, synchronize, labeled]
+    branches:
+      - main
+      - feature/*
+  push:
+    branches:
+      - main
+      - feature/*
+  schedule:
+    # GMT timezone.
+    - cron: '0 5 * * *'
+  workflow_dispatch:
+    inputs:
+      nightly:
+        description: 'Nightly workflow.'
+        required: true
+        type: boolean
+        default: false
+
+jobs:
+  evergreen-x64:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: evergreen-x64
+      nightly: ${{ github.event.inputs.nightly }}
+      run_api_leak_detector: true
+  evergreen-arm-hardfp:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: evergreen-arm-hardfp
+      nightly: ${{ github.event.inputs.nightly }}
+      run_api_leak_detector: true
+  evergreen-arm-softfp:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: evergreen-arm-softfp
+      nightly: ${{ github.event.inputs.nightly }}
+      run_api_leak_detector: true
+  evergreen-arm64:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: evergreen-arm64
+      nightly: ${{ github.event.inputs.nightly }}
+      run_api_leak_detector: true
+  evergreen-x86:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: evergreen-x86
+      nightly: ${{ github.event.inputs.nightly }}
+      run_api_leak_detector: true
diff --git a/.github/workflows/gradle.yaml b/.github/workflows/gradle.yaml
new file mode 100644
index 0000000..88ce9fd
--- /dev/null
+++ b/.github/workflows/gradle.yaml
@@ -0,0 +1,33 @@
+name: Java Tests
+
+on:
+  pull_request:
+  push:
+    branches:
+      - main
+      - feature/*
+
+concurrency:
+  group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.pull_request.number || github.sha }}'
+  cancel-in-progress: true
+
+permissions: {}
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: kaidokert/checkout@v3.5.999
+      - name: Set up JDK 11
+        uses: actions/setup-java@v3
+        with:
+          distribution: 'zulu'
+          java-version: 11
+      - name: Validate Gradle wrapper
+        uses: gradle/wrapper-validation-action@ccb4328a959376b642e027874838f60f8e596de3 #v1.0.6
+      - name: Build with Gradle
+        uses: gradle/gradle-build-action@749f47bda3e44aa060e82d7b3ef7e40d953bd629 #v2.4.2
+        with:
+          arguments: test
+          build-root-directory: starboard/android/apk
diff --git a/.github/workflows/label-cherry-pick.yaml b/.github/workflows/label-cherry-pick.yaml
new file mode 100644
index 0000000..6ac390a
--- /dev/null
+++ b/.github/workflows/label-cherry-pick.yaml
@@ -0,0 +1,99 @@
+name: Label Cherry Pick
+
+on:
+  pull_request_target:
+    types:
+      - labeled
+      - closed
+
+jobs:
+  prepare_branch_list:
+    runs-on: ubuntu-latest
+    outputs:
+      target_branch: ${{ steps.set-branches.outputs.target_branch }}
+    steps:
+    - name: Set Branches
+      id: set-branches
+      env:
+        PR_LABELS: ${{ toJson(github.event.pull_request.labels) }}
+        EVENT_ACTION: ${{ github.event.action }}
+        LABEL_NAME: ${{ github.event.label.name }}
+        BASE_REF: ${{ github.base_ref }}
+      run: |
+        if [[ $EVENT_ACTION == 'closed' ]]; then
+          labels=$(echo "$PR_LABELS" | jq -r '.[].name')
+        else
+          labels=$LABEL_NAME
+        fi
+
+        branches=("24.lts.1+" "23.lts.1+" "22.lts.1+" "21.lts.1+" "20.lts.1+" "19.lts.1+" "rc_11" "COBALT_9")
+        filtered_branches=()
+        for branch in "${branches[@]}"; do
+          if [[ $branch == $BASE_REF ]]; then
+            continue
+          fi
+
+          for label in $labels; do
+            if [[ $label == "cp-$branch" ]]; then
+              filtered_branches+=("$branch")
+            fi
+          done
+        done
+
+        echo "target_branch=$(echo -n "$filtered_branches" | jq -cRs 'split("\n")')" >> $GITHUB_OUTPUT
+
+  cherry_pick:
+    runs-on: ubuntu-latest
+    needs: prepare_branch_list
+    if: |
+      needs.prepare_branch_list.outputs.target_branch != '[]' &&
+      github.event.pull_request.merged == true &&
+      github.event.pull_request.merge_commit_sha != null
+    strategy:
+      matrix:
+        target_branch: ${{ fromJson(needs.prepare_branch_list.outputs.target_branch) }}
+    env:
+      ACCESS_TOKEN: ${{ secrets.CHERRY_PICK_TOKEN }}
+      REPOSITORY: ${{ github.repository }}
+      GITHUB_REF: ${{ github.ref }}
+      MERGE_COMMIT_SHA: ${{ github.event.pull_request.merge_commit_sha }}
+    steps:
+      - name: Checkout repository
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          ref: ${{ matrix.target_branch }}
+          fetch-depth: 0
+          persist-credentials: false
+
+      - name: Setup Git
+        run: |
+          git config --global user.name "GitHub Release Automation"
+          git config --global user.email "github@google.com"
+
+      - name: Cherry pick merge commit
+        run: |
+          git fetch origin ${{ matrix.target_branch }}
+          set +e
+          git cherry-pick -x $MERGE_COMMIT_SHA
+          RES=$?
+          set -e
+          if [ $RES -eq 0 ]; then
+            echo "CREATE_PR_AS_DRAFT=false" >> $GITHUB_ENV
+          else
+            echo "CREATE_PR_AS_DRAFT=true" >> $GITHUB_ENV
+            git add .
+            git cherry-pick --continue
+          fi
+
+      - name: Create Pull Request
+        uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04 # v4.2.3
+        with:
+          token: ${{ secrets.CHERRY_PICK_TOKEN }}
+          draft: ${{ env.CREATE_PR_AS_DRAFT }}
+          base: ${{ matrix.target_branch }}
+          branch: "${{ matrix.target_branch }}-${{ github.event.pull_request.number }}"
+          committer: GitHub Release Automation <github@google.com>
+          reviewers: ${{ github.event.pull_request.user.login }}
+          title: "Cherry pick PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}"
+          body: |
+            "Refer to the original PR: https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
new file mode 100644
index 0000000..6a138cb
--- /dev/null
+++ b/.github/workflows/lint.yaml
@@ -0,0 +1,70 @@
+name: lint
+
+on:
+  pull_request:
+    types:
+      - opened
+      - edited
+      - reopened
+      - synchronize
+  push:
+    branches:
+      - main
+      - feature/*
+
+concurrency:
+  group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.pull_request.number || github.sha }}'
+  cancel-in-progress: true
+
+permissions: {}
+
+jobs:
+  lint:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Install clang-format Dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install libncurses5
+      - name: Download GN via CIPD
+        env:
+          GN_SHA256SUM: 'af7b2dcb3905bca56655e12131b365f1cba8e159db80d2022330c4f522fab2ef  /tmp/gn.zip'
+          GN_HASH: r3styzkFvKVmVeEhMbNl8cuo4VnbgNICIzDE9SL6su8C
+        run: |
+          set -e -x
+          curl --location --silent --output /tmp/gn.zip "https://chrome-infra-packages.appspot.com/dl/gn/gn/linux-amd64/+/${GN_HASH}"
+          echo ${GN_SHA256SUM} | sha256sum --check
+          unzip /tmp/gn.zip -d /usr/local/bin
+          rm /tmp/gn.zip
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 0
+          persist-credentials: false
+      - name: Setup Python
+        uses: actions/setup-python@v4
+        with:
+          python-version: '^3.7.x'
+      - name: Install Pip Packages
+        run: pip install -r ${GITHUB_WORKSPACE}/requirements.txt
+      - name: Download Resources
+        run: python ${GITHUB_WORKSPACE}/download_resources.py
+      - name: pre-commit
+        uses: ./.github/actions/pre_commit
+        with:
+          base_ref: ${{ github.event.pull_request.base.sha && github.event.pull_request.base.sha || github.event.before }}
+  check-bug-id:
+    name: Check Bug ID
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check Bug ID Present
+        # v2
+        uses: gsactions/commit-message-checker@16fa2d5de096ae0d35626443bcd24f1e756cafee
+        with:
+          excludeTitle: true
+          excludeDescription: true
+          checkAllCommitMessages: true
+          accessToken: ${{ secrets.GITHUB_TOKEN }}
+          pattern: '^b\/\d+$'
+          flags: 'gm'
+          error: 'Commit message should include at least one bug ID on a separate line (e.g. b/12345).'
diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml
new file mode 100644
index 0000000..cf18341
--- /dev/null
+++ b/.github/workflows/linux.yaml
@@ -0,0 +1,48 @@
+name: linux
+
+on:
+  pull_request:
+    types: [ready_for_review, opened, reopened, synchronize, labeled]
+    branches:
+      - main
+      - feature/*
+  push:
+    branches:
+      - main
+      - feature/*
+  schedule:
+    # GMT timezone.
+    - cron: '0 4 * * *'
+  workflow_dispatch:
+    inputs:
+      nightly:
+        description: 'Nightly workflow.'
+        required: true
+        type: boolean
+        default: false
+
+jobs:
+  linux-x64:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: linux
+      nightly: ${{ github.event.inputs.nightly }}
+  linux-clang-3-9:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: linux-clang-3-9
+      nightly: ${{ github.event.inputs.nightly }}
+  linux-gcc-6-3:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: linux-gcc-6-3
+      nightly: ${{ github.event.inputs.nightly }}
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
new file mode 100644
index 0000000..d187bb5
--- /dev/null
+++ b/.github/workflows/main.yaml
@@ -0,0 +1,338 @@
+# Reusable Cobalt CI workflow.
+
+name: main
+
+on:
+  workflow_call:
+    inputs:
+      platform:
+        description: 'Cobalt platform.'
+        required: true
+        type: string
+      nightly:
+        description: 'Nightly workflow.'
+        required: true
+        type: string
+        default: 'false'
+      run_api_leak_detector:
+        description: 'Whether to run the api leak detector.'
+        required: false
+        type: boolean
+        default: false
+      leak_manifest_filename:
+        description: 'Path to the leak manifest.'
+        required: false
+        type: string
+        default: ""
+
+# Global env vars.
+env:
+  REGISTRY: ghcr.io
+  IPV6_AVAILABLE: 0
+  LANG: en_US.UTF-8
+  IS_BUILDBOT_DOCKER: 1
+  IS_CI: 1
+  IS_DOCKER: 1
+  NINJA_STATUS: '[%e sec | %f/%t %u remaining | %c/sec | j%r]'
+  SCCACHE: 1
+  SCCACHE_GCS_BUCKET: cobalt-actions-sccache-linux
+  SCCACHE_GCS_OAUTH_URL: http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
+  SCCACHE_GCS_RW_MODE: READ_WRITE
+  SCCACHE_IDLE_TIMEOUT: 0 # prevent sccache server from shutting down after long idle.
+  STARBOARD_TOOLCHAINS_DIR: /root/starboard-toolchains
+
+concurrency:
+  group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.pull_request.number || github.sha }}'
+  cancel-in-progress: true
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+  # Retrieves configuration from json file.
+  initialize:
+    runs-on: ubuntu-latest
+    permissions:
+      pull-requests: write
+    env:
+      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      GITHUB_PR_REPO_URL: ${{ github.event.pull_request.base.repo.url }}
+      GITHUB_EVENT_NUMBER: ${{ github.event.number }}
+    # All triggers except draft PRs, unless PR is labeled with runtest
+    if: |
+      github.event_name != 'pull_request' ||
+      (
+        github.event.pull_request.draft == false ||
+        (
+          github.event.action == 'labeled' &&
+          github.event.label.name == 'runtest'
+        )
+      )
+    steps:
+      - id: checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          persist-credentials: false
+      - name: Remove runtest if exists
+        if: github.event_name == 'pull_request'
+        continue-on-error: true  # Ignore this step if we cannot remove the label.
+        run: |
+          set +e
+          curl \
+            -X DELETE \
+            -H "Accept: application/vnd.github+json" \
+            -H "Authorization: Bearer ${GITHUB_TOKEN}" \
+            ${GITHUB_PR_REPO_URL}/issues/${GITHUB_EVENT_NUMBER}/labels/runtest
+        shell: bash
+      - id: set-platforms
+        run: echo "platforms=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.platforms')" >> $GITHUB_ENV
+      - id: set-includes
+        run: echo "includes=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.includes')" >> $GITHUB_ENV
+      - id: set-on-device-test
+        run: echo "on_device_test=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.on_device_test')" >> $GITHUB_ENV
+      - id: set-on-device-test-attempts
+        run: echo "on_device_test_attempts=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.on_device_test.test_attempts // empty')" >> $GITHUB_ENV
+      - id: set-on-host-test
+        run: echo "on_host_test=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.on_host_test')" >> $GITHUB_ENV
+      - id: set-on-host-test-shards
+        run: echo "on_host_test_shards=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.on_host_test_shards')" >> $GITHUB_ENV
+      - id: set-on-host-test-bootloader
+        run: echo "bootloader=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.bootloader')" >> $GITHUB_ENV
+      - id: set-docker-service
+        run: |
+          echo "docker_service=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -r '.docker_service')" >> $GITHUB_ENV
+          echo $platforms
+    outputs:
+      platforms: ${{ env.platforms }}
+      includes: ${{ env.includes }}
+      on_device_test: ${{ env.on_device_test }}
+      on_device_test_attempts: ${{ env.on_device_test_attempts }}
+      on_host_test: ${{ env.on_host_test }}
+      on_host_test_shards: ${{ env.on_host_test_shards }}
+      bootloader: ${{ env.bootloader }}
+      docker_service: ${{ env.docker_service }}
+
+  # Builds, tags, and pushes Cobalt docker build images to ghr.
+  docker-build-image:
+    needs: [initialize]
+    runs-on: [self-hosted, linux, X64]
+    permissions:
+      packages: write
+    steps:
+      - name: Checkout files
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 2
+          persist-credentials: false
+      - name: Login to Docker Registry ${{env.REGISTRY}}
+        uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+      - name: Build docker image
+        id: build-docker-image
+        uses: ./.github/actions/docker
+        with:
+          docker_service: ${{ needs.initialize.outputs.docker_service }}
+          docker_image: cobalt-${{ needs.initialize.outputs.docker_service }}
+      - name: Set Docker Tag Output
+        id: set-docker-tag-output
+        run: |
+          echo $DOCKER_TAG
+          echo "docker_tag=$DOCKER_TAG"  >> $GITHUB_ENV
+    outputs:
+      docker_tag: ${{env.docker_tag}}
+
+  # Builds, tags, and pushes Cobalt unit test image to ghr.
+  docker-unittest-image:
+    if: needs.initialize.outputs.on_host_test == 'true'
+    needs: [initialize]
+    permissions:
+      packages: write
+    runs-on: [self-hosted, linux, X64]
+    steps:
+      - name: Checkout files
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 2
+          persist-credentials: false
+      - name: Login to Docker Registry ${{env.REGISTRY}}
+        uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+      - name: Build docker image
+        id: build-docker-image
+        uses: ./.github/actions/docker
+        with:
+          docker_service: linux-x64x11-unittest
+          docker_image: cobalt-linux-x64x11-unittest
+      - name: Set Docker Tag Output
+        id: set-docker-unittest-tag-output
+        run: |
+          echo $DOCKER_TAG
+          echo "docker_unittest_tag=$DOCKER_TAG" >> $GITHUB_ENV
+    outputs:
+      docker_unittest_tag: ${{env.docker_unittest_tag}}
+
+  # Runs builds.
+  build:
+    needs: [initialize, docker-build-image]
+    permissions: {}
+    runs-on: [self-hosted, linux, X64]
+    name: ${{matrix.name}}_${{matrix.config}}
+    strategy:
+      fail-fast: false
+      matrix:
+        platform: ${{ fromJson(needs.initialize.outputs.platforms) }}
+        include: ${{ fromJson(needs.initialize.outputs.includes) }}
+        config: [devel, debug, qa, gold]
+    container: ${{ needs.docker-build-image.outputs.docker_tag }}
+    env:
+      # We want temp folder to be on tmpfs which makes workloads faster.
+      # However, dind container ends up having / folder mounted on overlay
+      # filesystem, whereas /__w which contains Cobalt source code is on tmpfs.
+      TMPDIR: /__w/_temp
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          # Use fetch depth of 0 to get full history for a valid build id.
+          fetch-depth: 0
+          persist-credentials: false
+      - name: GN
+        uses: ./.github/actions/gn
+      - name: Build Cobalt
+        uses: ./.github/actions/build
+      - name: Run API Leak Detector
+        uses: ./.github/actions/api_leak_detector
+        if: inputs.run_api_leak_detector
+        with:
+          relative_manifest_path: ${{ inputs.leak_manifest_filename }}
+      - name: Upload Nightly Artifacts
+        if: ${{ ( inputs.nightly == 'true' || github.event_name == 'schedule' ) && matrix.config != 'debug' }}
+        uses: ./.github/actions/upload_nightly_artifacts
+      - name: Upload On Host Test Artifacts
+        if: ${{ matrix.config == 'devel' && needs.initialize.outputs.on_host_test == 'true' }}
+        uses: ./.github/actions/upload_test_artifacts
+        with:
+          type: onhost
+          os: linux
+      # For some reason passing needs.initialize.outputs.bootloader as parameter to build
+      # action didn't work, so instead we set an env var.
+      - name: Set bootloader config
+        if: ${{ needs.initialize.outputs.bootloader != 'null' }}
+        run: echo "COBALT_BOOTLOADER=${{needs.initialize.outputs.bootloader}}" >> $GITHUB_ENV
+      # Build bootloader for on-host tests if necessary.
+      - name: Bootloader GN
+        if: ${{ needs.initialize.outputs.bootloader != 'null' && matrix.config == 'devel' }}
+        uses: ./.github/actions/gn
+      - name: Build Bootloader
+        if: ${{ needs.initialize.outputs.bootloader != 'null' && matrix.config == 'devel' }}
+        uses: ./.github/actions/build
+      - name: Upload Bootloader On Host Test Artifacts
+        if: ${{ needs.initialize.outputs.bootloader != 'null' && matrix.config == 'devel' && needs.initialize.outputs.on_host_test == 'true'}}
+        uses: ./.github/actions/upload_test_artifacts
+        with:
+          type: onhost
+          os: linux
+      - name: Upload On Device Test Artifacts
+        if: |
+          matrix.config == 'devel' &&
+          fromJSON(needs.initialize.outputs.on_device_test).enabled == true &&
+          (
+            github.event_name != 'pull_request' ||
+            contains(github.event.pull_request.labels.*.name, 'on_device')
+          )
+        uses: ./.github/actions/upload_test_artifacts
+        with:
+          type: ondevice
+          os: linux
+
+  # Runs on-host integration and unit tests.
+  on-device-test:
+    needs: [initialize, build]
+    # Run ODT when on_device label is applied on PR.
+    # Also, run ODT on push and schedule if not explicitly disabled via repo vars.
+    if: |
+      fromJSON(needs.initialize.outputs.on_device_test).enabled == true && ((
+        github.event_name == 'pull_request' &&
+        contains(github.event.pull_request.labels.*.name, 'on_device') ) || ((
+            inputs.nightly == 'true' || github.event_name == 'schedule') &&
+            vars.RUN_ODT_TESTS_ON_NIGHTLY != 'False') ||
+          ( github.event_name == 'push' && vars.RUN_ODT_TESTS_ON_POSTSUBMIT != 'False' ) )
+    runs-on: [self-hosted, linux, X64]
+    name: ${{ matrix.name }}_on_device_${{ matrix.shard }}
+    container: ${{ needs.docker-unittest-image.outputs.docker_unittest_tag }}
+    permissions: {}
+    strategy:
+      fail-fast: false
+      matrix:
+        platform: ${{ fromJson(needs.initialize.outputs.platforms) }}
+        config: [devel]
+        shard: ${{ fromJson(needs.initialize.outputs.on_device_test).tests }}
+        include: ${{ fromJson(needs.initialize.outputs.includes) }}
+    env:
+      COBALT_BOOTLOADER: ${{ needs.initialize.outputs.bootloader }}
+      ON_DEVICE_TEST_ATTEMPTS: ${{ needs.initialize.outputs.on_device_test_attempts }}
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          persist-credentials: false
+      - name: Run Tests (${{ matrix.shard }})
+        uses: ./.github/actions/on_device_tests
+
+  # Runs on-host integration and unit tests.
+  on-host-test:
+    needs: [initialize, docker-unittest-image, build]
+    permissions: {}
+    if: needs.initialize.outputs.on_host_test == 'true'
+    runs-on: [self-hosted, linux, X64]
+    name: ${{matrix.name}}_${{matrix.shard}}_test
+    strategy:
+      fail-fast: false
+      matrix:
+        platform: ${{ fromJson(needs.initialize.outputs.platforms) }}
+        shard: ${{ fromJson(needs.initialize.outputs.on_host_test_shards) }}
+        config: [devel]
+        include: ${{ fromJson(needs.initialize.outputs.includes) }}
+    container: ${{ needs.docker-unittest-image.outputs.docker_unittest_tag }}
+    env:
+      DISPLAY: :99
+      # For some reason tests complaining about HOME set to /github/home
+      # with permission denied error.
+      HOME: /root
+      COBALT_BOOTLOADER: ${{needs.initialize.outputs.bootloader}}
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          persist-credentials: false
+      - name: Run Tests
+        uses: ./.github/actions/on_host_test
+        with:
+          os: linux
+
+  # Gets unit test report from on host tests and prints it.
+  on-host-unit-test-report:
+    needs: [on-host-test]
+    permissions: {}
+    if: failure()
+    runs-on: ubuntu-latest
+    steps:
+      - name: Collect Unit Test Reports
+        uses: actions/download-artifact@v3
+        with:
+          name: unit-test-reports
+          path: unit-test-reports
+      - name: Print Unit Test Reports
+        run: |
+          for filename in ${GITHUB_WORKSPACE}/unit-test-reports/*; do
+            basename $filename
+            cat $filename
+            echo
+          done
diff --git a/.github/workflows/main_win.yaml b/.github/workflows/main_win.yaml
new file mode 100644
index 0000000..4827f3d
--- /dev/null
+++ b/.github/workflows/main_win.yaml
@@ -0,0 +1,183 @@
+# Reusable Cobalt CI workflow.
+
+name: main
+
+on:
+  workflow_call:
+    inputs:
+      platform:
+        description: 'Cobalt platform.'
+        required: true
+        type: string
+      nightly:
+        description: 'Nightly workflow.'
+        required: true
+        type: string
+        default: 'false'
+
+# Global env vars.
+env:
+  REGISTRY: ghcr.io
+  IPV6_AVAILABLE: 0
+  LANG: en_US.UTF-8
+  IS_BUILDBOT_DOCKER: 1
+  #BUILD_ID_SERVER_URL:
+  IS_CI: 1
+  IS_DOCKER: 1
+  NINJA_STATUS: '[%e sec | %f/%t %u remaining | %c/sec | j%r]'
+  SCCACHE: 1
+  SCCACHE_GCS_BUCKET: cobalt-actions-sccache-windows
+  SCCACHE_GCS_OAUTH_URL: http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
+  SCCACHE_GCS_RW_MODE: READ_WRITE
+  SCCACHE_IDLE_TIMEOUT: 0 # prevent sccache server from shutting down after long idle.
+  STARBOARD_TOOLCHAINS_DIR: /root/starboard-toolchains
+
+concurrency:
+  group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.pull_request.number || github.sha }}'
+  cancel-in-progress: true
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+  # Generates build matrix based on json configuration file.
+  initialize:
+    runs-on: ubuntu-latest
+    permissions:
+      pull-requests: write
+    env:
+      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      GITHUB_PR_REPO_URL: ${{ github.event.pull_request.base.repo.url }}
+      GITHUB_EVENT_NUMBER: ${{ github.event.number }}
+    # All triggers except draft PRs, unless PR is labeled with runtest
+    if: |
+      github.event_name != 'pull_request' ||
+      (
+        github.event.pull_request.draft == false ||
+        (
+          github.event.action == 'labeled' &&
+          github.event.label.name == 'runtest'
+        )
+      )
+    steps:
+      - id: Checkout
+        uses: kaidokert/checkout@v3.5.999 # Temporary version
+        with:
+          fetch-depth: 1
+          persist-credentials: false
+      - name: Remove runtest if exists
+        if: github.event_name == 'pull_request'
+        continue-on-error: true  # Ignore this step if we cannot remove the label.
+        run: |
+          set +e
+          curl \
+            -X DELETE \
+            -H "Accept: application/vnd.github+json" \
+            -H "Authorization: Bearer ${GITHUB_TOKEN}" \
+            ${GITHUB_PR_REPO_URL}/issues/${GITHUB_EVENT_NUMBER}/labels/runtest
+        shell: bash
+      - id: set-platforms
+        run: echo "platforms=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.platforms')" >> $GITHUB_ENV
+      - id: set-includes
+        run: echo "includes=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.includes')" >> $GITHUB_ENV
+      - id: set-on-device-test
+        run: echo "on_device_test=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.on_device_test.enabled')" >> $GITHUB_ENV
+      - id: set-on-host-test
+        run: echo "on_host_test=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.on_host_test')" >> $GITHUB_ENV
+      - id: set-on-host-test-shards
+        run: echo "on_host_test_shards=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.on_host_test_shards')" >> $GITHUB_ENV
+      - id: set-docker-service
+        run: echo "docker_service=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.docker_service')" >> $GITHUB_ENV
+      - id: set-docker-runner-service
+        run: echo "docker_runner_service=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.docker_runner_service')" >> $GITHUB_ENV
+    outputs:
+      platforms: ${{ env.platforms }}
+      includes: ${{ env.includes }}
+      on_device_test: ${{ env.on_device_test }}
+      on_host_test: ${{ env.on_host_test }}
+      on_host_test_shards: ${{ env.on_host_test_shards }}
+      docker_service: ${{ env.docker_service }}
+      docker_runner_service: ${{ env.docker_runner_service }}
+  # Build windows docker images.
+  build-docker-image:
+    needs: [initialize]
+    permissions:
+      packages: write
+    runs-on: windows-2019
+    steps:
+      - name: Checkout files
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 2
+          persist-credentials: false
+      - name: Login to Docker Registry ${{env.REGISTRY}}
+        uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+      - name: Build docker image
+        id: build-docker-image
+        uses: ./.github/actions/docker_win
+        with:
+          service: ${{ needs.initialize.outputs.docker_service }}
+      - name: Build runner docker image
+        id: build-runner-docker-image
+        uses: ./.github/actions/docker_win
+        with:
+          service: ${{ needs.initialize.outputs.docker_runner_service }}
+  # Runs builds.
+  build:
+    needs: [initialize]
+    permissions: {}
+    runs-on: [self-hosted, X64, Windows]
+    name: ${{matrix.name}}_${{matrix.config}}
+    strategy:
+      fail-fast: false
+      matrix:
+        platform: ${{ fromJson(needs.initialize.outputs.platforms) }}
+        include: ${{ fromJson(needs.initialize.outputs.includes) }}
+        config: [devel, debug, qa, gold]
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          # Use fetch depth of 0 to get full history for a valid build id.
+          fetch-depth: 0
+          persist-credentials: false
+      - name: GN
+        uses: ./.github/actions/gn
+      - name: Build Cobalt
+        uses: ./.github/actions/build
+      - name: Upload Nightly Artifacts
+        if: ${{ ( inputs.nightly == 'true' || github.event_name == 'schedule' ) && matrix.config != 'debug' }}
+        uses: ./.github/actions/upload_nightly_artifacts
+      - name: Upload On Host Test Artifacts
+        if: ${{ matrix.config == 'devel' && needs.initialize.outputs.on_host_test == 'true' }}
+        uses: ./.github/actions/upload_test_artifacts
+        with:
+          type: onhost
+          os: windows
+
+  # Runs on the host unit and integration tests.
+  on-host-test:
+    needs: [initialize, build]
+    permissions: {}
+    if: needs.initialize.outputs.on_host_test == 'true'
+    runs-on: [self-hosted, Windows, X64]
+    name: ${{matrix.name}}_${{matrix.shard}}_test
+    strategy:
+      fail-fast: false
+      matrix:
+        platform: ${{ fromJson(needs.initialize.outputs.platforms) }}
+        shard: ${{ fromJson(needs.initialize.outputs.on_host_test_shards) }}
+        config: [devel]
+        include: ${{ fromJson(needs.initialize.outputs.includes) }}
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          persist-credentials: false
+      - name: Run Tests
+        uses: ./.github/actions/on_host_test
+        with:
+          os: windows
diff --git a/.github/workflows/manual-cherry-pick.yaml b/.github/workflows/manual-cherry-pick.yaml
new file mode 100644
index 0000000..d4bca26
--- /dev/null
+++ b/.github/workflows/manual-cherry-pick.yaml
@@ -0,0 +1,72 @@
+# Copyright 2023 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Usage: Go to
+# https://https://github.com/youtube/cobalt/actions/workflows/manual-cherry-pick.yaml
+# and click "Run Workflow." Leave "Use Workflow From" set to "main", then
+# input the branch name and paste the cherry-pick commit and click Run. A PR
+# will be created.
+
+name: Release Branch Cherrypick
+on:
+  workflow_dispatch:
+    inputs:
+      # We use this instead of the "run on branch" argument because GitHub looks
+      # on that branch for a workflow.yml file, and we'd have to cherry-pick
+      # this file into those branches.
+      release_branch:
+        description: 'Release branch name (e.g. 23.lts.1+)'
+        required: true
+        type: string
+      git_commit:
+        description: 'Git commit to cherry-pick'
+        required: true
+        type: string
+
+jobs:
+  cherrypick:
+    name: Cherrypick to ${{ github.event.inputs.release_branch}} - ${{ github.event.inputs.git_commit }}
+    runs-on: ubuntu-latest
+    env:
+      ACCESS_TOKEN: ${{ secrets.CHERRY_PICK_TOKEN }}
+      RELEASE_BRANCH: ${{ github.event.inputs.release_branch }}
+      COMMIT_HASH: ${{ github.event.inputs.git_commit }}
+      REPOSITORY: ${{ github.repository }}
+      GITHUB_REF: ${{ github.ref }}
+    steps:
+    - name: Checkout code
+      uses: kaidokert/checkout@v3.5.999
+      with:
+        ref: ${{ env.RELEASE_BRANCH }}
+        persist-credentials: false
+    - name: Get some helpful info for formatting
+      id: cherrypick
+      run: |
+          git config --global user.name "GitHub Release Automation"
+          git config --global user.email "github@google.com"
+          git fetch origin $GITHUB_REF
+          git cherry-pick -x $COMMIT_HASH
+          echo "SHORTSHA=$(git log -1 $COMMIT_HASH --format="%h")" >> "$GITHUB_OUTPUT"
+          echo "TITLE=$(git log -1 $COMMIT_HASH --format="%s")" >> "$GITHUB_OUTPUT"
+    - name: Create Pull Request with changes
+      uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04 # v4.2.3
+      with:
+        title: '${{ env.RELEASE_BRANCH }} cherry-pick: ${{ steps.cherrypick.outputs.SHORTSHA }} "${{ steps.cherrypick.outputs.TITLE }}"'
+        committer: GitHub Release Automation <github@google.com>
+        token: ${{ secrets.CHERRY_PICK_TOKEN }}
+        base: ${{ env.RELEASE_BRANCH }}
+        branch: ${{ env.RELEASE_BRANCH }}-${{ steps.cherrypick.outputs.SHORTSHA }}
+        reviewers: ${{ github.actor }}
+        body: |
+          Refer to the original commit: https://github.com/${{ github.repository }}/commit/${{ github.event.inputs.git_commit }}
diff --git a/.github/workflows/nightly_trigger.yaml b/.github/workflows/nightly_trigger.yaml
new file mode 100644
index 0000000..2cb01b6
--- /dev/null
+++ b/.github/workflows/nightly_trigger.yaml
@@ -0,0 +1,141 @@
+name: nightly_trigger
+
+on:
+  schedule:
+    # GMT timezone.
+    - cron: '30 4 * * *'
+  workflow_dispatch:
+
+jobs:
+  trigger_23:
+    permissions:
+      actions: write
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          ref: 23.lts.1+
+          persist-credentials: false
+      - name: Trigger Nightly
+        run: |
+          set -x
+          gh workflow run android_23.lts.1+ --ref 23.lts.1+ -f nightly=true
+          gh workflow run evergreen_23.lts.1+ --ref 23.lts.1+ -f nightly=true
+          gh workflow run linux_23.lts.1+ --ref 23.lts.1+ -f nightly=true
+          gh workflow run raspi-2_23.lts.1+ --ref 23.lts.1+ -f nightly=true
+          gh workflow run win32_23.lts.1+ --ref 23.lts.1+ -f nightly=true
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+  trigger_22:
+    permissions:
+      actions: write
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          ref: 22.lts.1+
+          persist-credentials: false
+      - name: Trigger Nightly
+        run: |
+          set -x
+          gh workflow run android_22.lts.1+ --ref 22.lts.1+ -f nightly=true
+          gh workflow run evergreen_22.lts.1+ --ref 22.lts.1+ -f nightly=true
+          gh workflow run linux_22.lts.1+ --ref 22.lts.1+ -f nightly=true
+          gh workflow run raspi-2_22.lts.1+ --ref 22.lts.1+ -f nightly=true
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+  trigger_21:
+    permissions:
+      actions: write
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          ref: 21.lts.1+
+          persist-credentials: false
+      - name: Trigger Nightly
+        run: |
+          set -x
+          gh workflow run evergreen_21.lts.1+ --ref 21.lts.1+ -f nightly=true
+          gh workflow run linux_21.lts.1+ --ref 21.lts.1+ -f nightly=true
+          gh workflow run raspi-2_21.lts.1+ --ref 21.lts.1+ -f nightly=true
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+  trigger_20:
+    permissions:
+      actions: write
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          ref: 20.lts.1+
+          persist-credentials: false
+      - name: Trigger Nightly
+        run: |
+          set -x
+          gh workflow run linux_20.lts.1+ --ref 20.lts.1+ -f nightly=true
+          gh workflow run raspi-2_20.lts.1+ --ref 20.lts.1+ -f nightly=true
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+  trigger_19:
+    permissions:
+      actions: write
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          ref: 19.lts.1+
+          persist-credentials: false
+      - name: Trigger Nightly
+        run: |
+          set -x
+          gh workflow run linux_19.lts.1+ --ref 19.lts.1+ -f nightly=true
+          gh workflow run raspi-2_19.lts.1+ --ref 19.lts.1+ -f nightly=true
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+  trigger_rc_11:
+    permissions:
+      actions: write
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          ref: rc_11
+          persist-credentials: false
+      - name: Trigger Nightly
+        run: |
+          set -x
+          gh workflow run linux_rc_11 --ref rc_11 -f nightly=true
+          gh workflow run raspi-2_rc_11 --ref rc_11 -f nightly=true
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+  trigger_cobalt_9:
+    permissions:
+      actions: write
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          ref: COBALT_9
+          persist-credentials: false
+      - name: Trigger Nightly
+        run: |
+          set -x
+          gh workflow run linux_COBALT_9 --ref COBALT_9 -f nightly=true
+          gh workflow run raspi-2_COBALT_9 --ref COBALT_9 -f nightly=true
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/nightly_trigger_24.lts.1+.yaml b/.github/workflows/nightly_trigger_24.lts.1+.yaml
new file mode 100644
index 0000000..f72b625
--- /dev/null
+++ b/.github/workflows/nightly_trigger_24.lts.1+.yaml
@@ -0,0 +1,30 @@
+name: nightly_trigger_24.lts.1+
+
+on:
+  schedule:
+    # GMT timezone.
+    - cron: '30 5 * * *'
+  workflow_dispatch:
+
+jobs:
+  trigger_24:
+    permissions:
+      actions: write
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          ref: 24.lts.1+
+          persist-credentials: false
+      - name: Trigger Nightly
+        run: |
+          set -x
+          gh workflow run android_24.lts.1+ --ref 24.lts.1+ -f nightly=true
+          gh workflow run evergreen_24.lts.1+ --ref 24.lts.1+ -f nightly=true
+          gh workflow run linux_24.lts.1+ --ref 24.lts.1+ -f nightly=true
+          gh workflow run raspi-2_24.lts.1+ --ref 24.lts.1+ -f nightly=true
+          gh workflow run win32_24.lts.1+ --ref 24.lts.1+ -f nightly=true
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/pr_badges.yaml b/.github/workflows/pr_badges.yaml
new file mode 100644
index 0000000..3fcd93e
--- /dev/null
+++ b/.github/workflows/pr_badges.yaml
@@ -0,0 +1,69 @@
+name: PR badges
+
+on:
+  pull_request_target:
+    branches:
+      - 'feature/*'
+      - 'main'
+      - '24.lts.1\+'
+      - '23.lts.1\+'
+      - '22.lts.1\+'
+      - '21.lts.1\+'
+      - '20.lts.1\+'
+      - '19.lts.1\+'
+      - 'rc_11'
+      - 'COBALT_9'
+
+concurrency:
+  group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.pull_request.number || github.sha }}'
+  cancel-in-progress: true
+
+permissions:
+  pull-requests: write
+
+jobs:
+  comment:
+    runs-on: ubuntu-latest
+    env:
+      GITHUB_SERVER_URL: ${{github.server_url}}
+      GITHUB_REPO: ${{github.repository}}
+      GITHUB_HEAD_REF: ${{ github.head_ref }}
+    steps:
+      - uses: actions/github-script@v6
+        with:
+          script: |
+            // Get env vars.
+            const { GITHUB_SERVER_URL, GITHUB_REPO, GITHUB_HEAD_REF } = process.env
+            // Get the existing comments.
+            const {data: comments} = await github.rest.issues.listComments({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: context.payload.number,
+            })
+
+            // Find any comment already made by the bot.
+            const botComment = comments.find(comment => {
+              return comment.user.type === 'Bot' && comment.body.includes('Build Status')
+            })
+            const workflows = ["lint", "android", "evergreen", "linux", "raspi-2", "stub", "win32"]
+            var commentBody = `
+            ## Build Status
+            | Workflow  | Status                                                                                                                                                                                                                                               |
+            | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+            `
+            for (let i = 0; i < workflows.length; i++) {
+              commentBody += "| " + workflows[i] + "      | [![" + workflows[i] + "](" + `${GITHUB_SERVER_URL}` + "/" + `${GITHUB_REPO}` + "/actions/workflows/" + workflows[i] + ".yaml/badge.svg?branch=" + `${GITHUB_HEAD_REF}` + ")](" + `${GITHUB_SERVER_URL}` + "/" + `${GITHUB_REPO}` + "/actions/workflows/" + workflows[i] + ".yaml?query=branch%3A" + `${GITHUB_HEAD_REF}` + ")                |\n"
+            }
+            if (botComment) {
+              await github.rest.issues.deleteComment({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                comment_id: botComment.id,
+              })
+            }
+            await github.rest.issues.createComment({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: context.payload.number,
+              body: commentBody
+            })
diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml
new file mode 100644
index 0000000..787f056
--- /dev/null
+++ b/.github/workflows/pytest.yaml
@@ -0,0 +1,40 @@
+name: python-tests
+
+on:
+  pull_request:
+  push:
+    branches:
+      - main
+      - feature/*
+
+concurrency:
+  group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.pull_request.number || github.sha }}'
+  cancel-in-progress: true
+
+permissions: {}
+
+jobs:
+  python-test:
+    strategy:
+      matrix:
+        os: [ubuntu-latest, windows-latest]
+        python-version: ['3.7', '3.11']
+      fail-fast: false
+    runs-on: ${{ matrix.os }}
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          persist-credentials: false
+      - name: Setup Python
+        uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+          cache: 'pip'
+      - name: Install Pip Packages
+        run: pip install --require-hashes --no-deps -r ${{ github.workspace }}/docker/pytest/requirements.txt
+      - name: Run Tests
+        run: coverage run -m pytest
+      - name: Coverage Report
+        run: coverage report -m
diff --git a/.github/workflows/raspi-2.yaml b/.github/workflows/raspi-2.yaml
new file mode 100644
index 0000000..62d0708
--- /dev/null
+++ b/.github/workflows/raspi-2.yaml
@@ -0,0 +1,40 @@
+name: raspi-2
+
+on:
+  pull_request:
+    types: [ready_for_review, opened, reopened, synchronize, labeled]
+    branches:
+      - main
+      - feature/*
+  push:
+    branches:
+      - main
+      - feature/*
+  schedule:
+    # GMT timezone.
+    - cron: '0 4 * * *'
+  workflow_dispatch:
+    inputs:
+      nightly:
+        description: 'Nightly workflow.'
+        required: true
+        type: boolean
+        default: false
+
+jobs:
+  raspi-2:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: raspi-2
+      nightly: ${{ github.event.inputs.nightly }}
+  raspi-2-skia:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: raspi-2-skia
+      nightly: ${{ github.event.inputs.nightly }}
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
new file mode 100644
index 0000000..4514e18
--- /dev/null
+++ b/.github/workflows/scorecards.yml
@@ -0,0 +1,58 @@
+# This workflow uses actions that are not certified by GitHub. They are provided
+# by a third-party and are governed by separate terms of service, privacy
+# policy, and support documentation.
+
+name: Scorecards supply-chain security
+on:
+  # For Branch-Protection check. Only the default branch is supported. See
+  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
+  branch_protection_rule:
+  # To guarantee Maintained check is occasionally updated. See
+  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
+  schedule:
+    - cron: '18 14 * * 1'
+  push:
+    branches: [ main ]
+  pull_request:
+    branches: [ main ]
+
+# Declare default permissions as read only.
+permissions: read-all
+
+jobs:
+  analysis:
+    name: Scorecards analysis
+    runs-on: ubuntu-latest
+    permissions:
+      # Needed to upload the results to code-scanning dashboard.
+      security-events: write
+      # Needed to publish results and get a badge (see publish_results below).
+      id-token: write
+
+    steps:
+      - name: "Checkout code"
+        uses: actions/checkout@v3
+        with:
+          persist-credentials: false
+
+      - name: "Run analysis"
+        uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
+        with:
+          results_file: results.sarif
+          results_format: sarif
+          publish_results: true
+
+      # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
+      # format to the repository Actions tab.
+      - name: "Upload artifact"
+        uses: actions/upload-artifact@v3.1.1
+        with:
+          name: SARIF file
+          path: results.sarif
+          retention-days: 5
+
+      # Upload the results to GitHub's code scanning dashboard.
+      - name: "Upload to code-scanning"
+        uses: github/codeql-action/upload-sarif@v2.1.37
+        with:
+          sarif_file: results.sarif
diff --git a/.github/workflows/stub.yaml b/.github/workflows/stub.yaml
new file mode 100644
index 0000000..c105a59
--- /dev/null
+++ b/.github/workflows/stub.yaml
@@ -0,0 +1,31 @@
+name: stub
+
+on:
+  pull_request:
+    types: [ready_for_review, opened, reopened, synchronize, labeled]
+    branches:
+      - main
+      - feature/*
+  push:
+    branches:
+      - main
+      - feature/*
+  workflow_dispatch:
+    inputs:
+      nightly:
+        description: 'Nightly workflow.'
+        required: true
+        type: boolean
+        default: false
+
+jobs:
+  stub:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: stub
+      nightly: 'false'
+      run_api_leak_detector: true
+      leak_manifest_filename: "gn_built_docker_debian10_manifest"
diff --git a/.github/workflows/win32.yaml b/.github/workflows/win32.yaml
new file mode 100644
index 0000000..449a9d2
--- /dev/null
+++ b/.github/workflows/win32.yaml
@@ -0,0 +1,32 @@
+name: win32
+
+on:
+  pull_request:
+    types: [ready_for_review, opened, reopened, synchronize, labeled]
+    branches:
+      - main
+      - feature/*
+  push:
+    branches:
+      - main
+      - feature/*
+  schedule:
+    # GTM timezone.
+    - cron: '0 4 * * *'
+  workflow_dispatch:
+    inputs:
+      nightly:
+        description: 'Nightly workflow.'
+        required: true
+        type: boolean
+        default: false
+
+jobs:
+  win32:
+    uses: ./.github/workflows/main_win.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: win32
+      nightly: ${{ github.event.inputs.nightly }}
diff --git a/.github/workflows/workflow_trigger.yaml b/.github/workflows/workflow_trigger.yaml
new file mode 100644
index 0000000..c4122d8
--- /dev/null
+++ b/.github/workflows/workflow_trigger.yaml
@@ -0,0 +1,49 @@
+name: workflow_trigger
+
+on:
+  workflow_dispatch:
+    inputs:
+      branch:
+        type: choice
+        description: Branch
+        options:
+          - '24.lts.1+'
+          - '23.lts.1+'
+          - '22.lts.1+'
+          - '21.lts.1+'
+          - '20.lts.1+'
+          - '19.lts.1+'
+          - 'rc_11'
+          - 'COBALT_9'
+      workflow:
+        type: choice
+        description: Workflow name
+        options:
+          - 'android'
+          - 'evergreen'
+          - 'linux'
+          - 'raspi'
+          - 'win32'
+      nightly:
+        description: 'Nightly workflow.'
+        required: true
+        type: boolean
+        default: false
+
+jobs:
+  trigger:
+    permissions:
+      actions: write
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: kaidokert/checkout@v3.5.999
+        with:
+          fetch-depth: 1
+          ref: ${{ github.event.branch }}
+      - name: Trigger Workflow
+        run: |
+          set -x
+          gh workflow run ${{ github.event.inputs.workflow }}_${{ github.event.inputs.branch }} --ref ${{ github.event.inputs.branch }} -f nightly=${{ github.event.inputs.nightly }}
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index cde10f9..c4bd214 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -11,8 +11,16 @@
             base|
             build|
             buildtools|
+            components/crash/core/common|
+            components/metrics|
+            components/metrics_services_manager|
+            components/ukm|
+            components/variations|
+            components/version_info|
             crypto|
+            extensions/buildflags|
             net|
+            internal/starboard/shared/playstation/glimp/shaders|
             testing|
             third_party|
             tools/gyp|
@@ -20,6 +28,8 @@
         )/
         |
         components/update_client/((?!cobalt).)*$
+        |
+        .*\.sig$
     )
 
 repos:
@@ -43,10 +53,12 @@
                --ignore-words-list, atleast]
         exclude: |
             (?x)^(
-                starboard/[^/]+/i18n/|
+                (internal/)?starboard/[^/]+/i18n/|
                 cobalt/content/licenses/|
                 cobalt/fetch/embedded_scripts|
-                cobalt/loader/cors_preflight.cc
+                cobalt/loader/cors_preflight.cc|
+                internal/cobalt/browser/splash_screen/youtube_splash_screen.html|
+                internal/starboard/shared/playstation/storage_internal.cc
             )
 
 -   repo: local
@@ -73,7 +85,7 @@
         exclude: |
             (?x)(
                 ^cobalt/bindings/(templates|generated)/|
-                ^starboard/shared/uikit/.*\.h$
+                ^internal/starboard/shared/uikit/.*\.h$
             )
     -   id: yapf
         name: yapf
diff --git a/BUILD_STATUS.md b/BUILD_STATUS.md
index c209877..28ea016 100644
--- a/BUILD_STATUS.md
+++ b/BUILD_STATUS.md
@@ -1,21 +1,22 @@
 # Build Status
 
-| Workflow  | Main | 23.lts.1+ | 22.lts.1+ | 21.lts.1+ | 20.lts.1+ | 19.lts.1+ | RC11 | COBALT 9 |
-| --------- | ---- | --------- | --------- | --------- | --------- | --------- | ---- | ---------|
-| Lint      | [![lint](https://github.com/youtube/cobalt/actions/workflows/lint.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/lint.yaml?query=event%3Apush+branch%3Amain) | | | | | | | |
-| Android   | [![android](https://github.com/youtube/cobalt/actions/workflows/android.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/android.yaml?query=event%3Apush+branch%3Amain) | [![android_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![android_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | | | | | |
-| Evergreen | [![evergreen](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml?query=event%3Apush+branch%3Amain) | [![evergreen_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![evergreen_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | [![evergreen_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml) | | | | |
-| Linux     | [![linux](https://github.com/youtube/cobalt/actions/workflows/linux.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux.yaml?query=event%3Apush+branch%3Amain) | [![linux_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![linux_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | [![linux_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml) | [![linux_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml?query=branch%3A20.lts.1%2B+event%3Apush) | [![linux_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml?query=branch%3A19.lts.1%2B+event%3Apush) | [![linux_rc_11](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml/badge.svg?branch=rc_11&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml?query=event%3Apush+branch%3Arc_11) | [![linux_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml?query=event%3Apush+branch%3ACOBALT_9) |
-| raspi-2   | [![raspi-2](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml?query=event%3Apush+branch%3Amain) | [![raspi-2_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![raspi-2_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | [![raspi-2_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml) | [![raspi-2_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml?query=branch%3A20.lts.1%2B+event%3Apush) | [![raspi-2_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml?query=branch%3A19.lts.1%2B+event%3Apush) | [![raspi-2_rc_11](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml/badge.svg?branch=rc_11&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml?query=event%3Apush+branch%3Arc_11) | [![raspi-2_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml?query=event%3Apush+branch%3ACOBALT_9) |
-| stub      | [![stub](https://github.com/youtube/cobalt/actions/workflows/stub.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/stub.yaml?query=event%3Apush+branch%3Amain) | [![stub_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/stub_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/stub_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | | | | | | |
-| Win32     | [![win32](https://github.com/youtube/cobalt/actions/workflows/win32.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/win32.yaml?query=event%3Apush+branch%3Amain) | [![win32_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | | | | | | |
-
+| Workflow  | Main | 24.lts.1+ | 23.lts.1+ | 22.lts.1+ | 21.lts.1+ | 20.lts.1+ | 19.lts.1+ | RC11 | COBALT 9 |
+| --------- | ---- | --------- | --------- | --------- | --------- | --------- | --------- | ---- | ---------|
+| Lint      | [![lint](https://github.com/youtube/cobalt/actions/workflows/lint.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/lint.yaml?query=event%3Apush+branch%3Amain) | | | | | | | | |
+| Android   | [![android](https://github.com/youtube/cobalt/actions/workflows/android.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/android.yaml?query=event%3Apush+branch%3Amain) | [![android_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/android_24.lts.1+.yaml?query=event%3Apush+branch%3A24.lts.1%2B) | [![android_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![android_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | | | | | |
+| Evergreen | [![evergreen](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml?query=event%3Apush+branch%3Amain) | [![evergreen_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_24.lts.1+.yaml?query=event%3Apush+branch%3A24.lts.1%2B) | [![evergreen_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![evergreen_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | [![evergreen_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml?query=branch%3A21.lts.1%2B+event%3Apush) | | | | |
+| Linux     | [![linux](https://github.com/youtube/cobalt/actions/workflows/linux.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux.yaml?query=event%3Apush+branch%3Amain) | [![linux_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_24.lts.1+.yaml?query=event%3Apush+branch%3A24.lts.1%2B) | [![linux_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![linux_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | [![linux_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml?query=branch%3A21.lts.1%2B+event%3Apush) | [![linux_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml?query=branch%3A20.lts.1%2B+event%3Apush) | [![linux_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml?query=branch%3A19.lts.1%2B+event%3Apush) | [![linux_rc_11](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml/badge.svg?branch=rc_11&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml?query=event%3Apush+branch%3Arc_11) | [![linux_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml?query=event%3Apush+branch%3ACOBALT_9) |
+| Raspi-2   | [![raspi-2](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml?query=event%3Apush+branch%3Amain) | [![raspi-2_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_24.lts.1+.yaml?query=event%3Apush+branch%3A24.lts.1%2B) | [![raspi-2_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![raspi-2_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | [![raspi-2_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml?query=branch%3A21.lts.1%2B+event%3Apush) | [![raspi-2_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml?query=branch%3A20.lts.1%2B+event%3Apush) | [![raspi-2_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml?query=branch%3A19.lts.1%2B+event%3Apush) | [![raspi-2_rc_11](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml/badge.svg?branch=rc_11&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml?query=event%3Apush+branch%3Arc_11) | [![raspi-2_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml?query=event%3Apush+branch%3ACOBALT_9) |
+| Stub      | [![stub](https://github.com/youtube/cobalt/actions/workflows/stub.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/stub.yaml?query=event%3Apush+branch%3Amain) | [![stub_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/stub_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/stub_24.lts.1+.yaml?query=event%3Apush+branch%3A24.lts.1%2B) | [![stub_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/stub_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/stub_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | | | | | | |
+| Win32     | [![win32](https://github.com/youtube/cobalt/actions/workflows/win32.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/win32.yaml?query=event%3Apush+branch%3Amain) | [![win32_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/win32_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/win32_24.lts.1+.yaml?query=event%3Apush+branch%3A24.lts.1%2B) | [![win32_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | | | | | | |
+| Python    | [![python](https://github.com/youtube/cobalt/actions/workflows/pytest.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/pytest.yaml?query=event%3Apush+branch%3Amain) | | | | | | | |
+| Java      | [![java](https://github.com/youtube/cobalt/actions/workflows/gradle.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/gradle.yaml?query=event%3Apush+branch%3Amain) | | | | | | | |
 
 # Nightly builds
-| Workflow  | main | 23.lts.1+ | 22.lts.1+ | 21.lts.1+ | 20.lts.1+ | 19.lts.1+ | RC11 | COBALT 9 |
-| --------- | ---- | --------- | --------- | --------- | --------- | --------- | ---- | ---------|
-| Android   | [![android](https://github.com/youtube/cobalt/actions/workflows/android.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/android.yaml?query=event%3Aschedule+branch%3Amain) | [![android_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![android_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | | | | | |
-| Evergreen | [![evergreen](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml?query=event%3Aschedule+branch%3Amain) | [![evergreen_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![evergreen_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | [![evergreen_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A21.lts.1%2B) | | | | |
-| Linux | [![linux](https://github.com/youtube/cobalt/actions/workflows/linux.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/linux.yaml?query=event%3Aschedule+branch%3Amain) | [![linux_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![linux_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | [![linux_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A21.lts.1%2B) | [![linux_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A20.lts.1%2B) | [![linux_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A19.lts.1%2B) | [![linux_rc_11](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml/badge.svg?branch=rc_11&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml?query=event%3Aworkflow_dispatch+branch%3Arc_11) | [![linux_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml?query=event%3Aworkflow_dispatch+branch%3ACOBALT_9) |
-| raspi-2 | [![raspi-2](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml?query=event%3Aschedule+branch%3Amain) | [![raspi-2_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![raspi-2_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | [![raspi-2_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A21.lts.1%2B) | [![raspi-2_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A20.lts.1%2B) | [![raspi-2_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A19.lts.1%2B) | [![raspi-2_rc_11](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml/badge.svg?branch=rc_11&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml?query=event%3Aworkflow_dispatch+branch%3Arc_11) | [![raspi-2_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml?query=event%3Aworkflow_dispatch+branch%3ACOBALT_9) |
-| Win32 | [![win32](https://github.com/youtube/cobalt/actions/workflows/win32.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/win32.yaml?query=event%3Aschedule+branch%3Amain) | [![win32_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | | | | | | |
+| Workflow  | main | 24.lts.1+ | 23.lts.1+ | 22.lts.1+ | 21.lts.1+ | 20.lts.1+ | 19.lts.1+ | RC11 | COBALT 9 |
+| --------- | ---- | --------- | --------- | --------- | --------- | --------- | --------- | ---- | ---------|
+| Android   | [![android](https://github.com/youtube/cobalt/actions/workflows/android.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/android.yaml?query=event%3Aschedule+branch%3Amain) | [![android_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/android_24.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A24.lts.1%2B) | [![android_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![android_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | | | | | |
+| Evergreen | [![evergreen](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml?query=event%3Aschedule+branch%3Amain) | [![evergreen_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_24.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A24.lts.1%2B) | [![evergreen_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![evergreen_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | [![evergreen_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A21.lts.1%2B) | | | | |
+| Linux | [![linux](https://github.com/youtube/cobalt/actions/workflows/linux.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/linux.yaml?query=event%3Aschedule+branch%3Amain) | [![linux_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_24.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A24.lts.1%2B) | [![linux_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![linux_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | [![linux_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A21.lts.1%2B) | [![linux_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A20.lts.1%2B) | [![linux_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A19.lts.1%2B) | [![linux_rc_11](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml/badge.svg?branch=rc_11&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml?query=event%3Aworkflow_dispatch+branch%3Arc_11) | [![linux_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml?query=event%3Aworkflow_dispatch+branch%3ACOBALT_9) |
+| Raspi-2 | [![raspi-2](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml?query=event%3Aschedule+branch%3Amain) | [![raspi-2_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_24.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A24.lts.1%2B) | [![raspi-2_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![raspi-2_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | [![raspi-2_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A21.lts.1%2B) | [![raspi-2_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A20.lts.1%2B) | [![raspi-2_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A19.lts.1%2B) | [![raspi-2_rc_11](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml/badge.svg?branch=rc_11&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml?query=event%3Aworkflow_dispatch+branch%3Arc_11) | [![raspi-2_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml?query=event%3Aworkflow_dispatch+branch%3ACOBALT_9) |
+| Win32 | [![win32](https://github.com/youtube/cobalt/actions/workflows/win32.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/win32.yaml?query=event%3Aschedule+branch%3Amain) | [![win32_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/win32_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/win32_24.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A24.lts.1%2B) | [![win32_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | | | | | | |
diff --git a/README.md b/README.md
index 1ffff01..fcfe4a6 100644
--- a/README.md
+++ b/README.md
@@ -173,7 +173,8 @@
   * [Linux](cobalt/site/docs/development/setup-linux.md)
   * [Raspi](cobalt/site/docs/development/setup-raspi.md)
   * [Android](cobalt/site/docs/development/setup-android.md)
-
+  * [Docker](cobalt/site/docs/development/setup-docker.md)
+  * [RDK](cobalt/site/docs/development/setup-rdk.md)
 
 ## Build Types
 
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 93f6256..25759ac 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -1215,8 +1215,6 @@
       "memory/platform_shared_memory_region.h",
       "memory/protected_memory_cfi.h",
       "memory/protected_memory_win.cc",
-      "memory/shared_memory_handle.cc",
-      "memory/shared_memory_handle.h",
       "memory/shared_memory_helper.cc",
       "memory/shared_memory_helper.h",
       "memory/shared_memory_mapping.cc",
@@ -1239,12 +1237,6 @@
       "message_loop/message_pump_mac.mm",
       "message_loop/message_pump_win.cc",
       "message_loop/message_pump_win.h",
-      "metrics/field_trial.cc",
-      "metrics/field_trial.h",
-      "metrics/field_trial_param_associator.cc",
-      "metrics/field_trial_param_associator.h",
-      "metrics/field_trial_params.cc",
-      "metrics/field_trial_params.h",
       "native_library.cc",
       "native_library.h",
       "native_library_ios.mm",
diff --git a/base/codereview.settings b/base/codereview.settings
new file mode 100644
index 0000000..a4b341a
--- /dev/null
+++ b/base/codereview.settings
@@ -0,0 +1,4 @@
+# This file is used by gcl to get repository specific information.
+GERRIT_HOST: lbshell-internal-review.googlesource.com
+GERRIT_AUTODETECT_BRANCH: true
+CODE_REVIEW_SERVER: lbshell-internal-review.googlesource.com
diff --git a/base/feature_list.cc b/base/feature_list.cc
index d8e00ac..5da3379 100644
--- a/base/feature_list.cc
+++ b/base/feature_list.cc
@@ -98,7 +98,6 @@
   initialized_from_command_line_ = true;
 }
 
-#if !defined(STARBOARD)
 void FeatureList::InitializeFromSharedMemory(
     PersistentMemoryAllocator* allocator) {
   DCHECK(!initialized_);
@@ -115,10 +114,13 @@
       continue;
 
     FieldTrial* trial = FieldTrialList::Find(trial_name.as_string());
+#if defined(STARBOARD)
+    RegisterOverride(feature_name, override_state);
+#else
     RegisterOverride(feature_name, override_state, trial);
+#endif
   }
 }
-#endif  // !defined(STARBOARD)
 
 bool FeatureList::IsFeatureOverriddenFromCommandLine(
     const std::string& feature_name,
@@ -212,7 +214,6 @@
   return g_feature_list_instance->IsFeatureEnabled(feature);
 }
 
-#if !defined(STARBOARD)
 // static
 FieldTrial* FeatureList::GetFieldTrial(const Feature& feature) {
   if (!g_feature_list_instance) {
@@ -221,7 +222,6 @@
   }
   return g_feature_list_instance->GetAssociatedFieldTrial(feature);
 }
-#endif  // !defined(STARBOARD)
 
 // static
 std::vector<base::StringPiece> FeatureList::SplitFeatureListString(
@@ -334,7 +334,6 @@
   return feature.default_state == FEATURE_ENABLED_BY_DEFAULT;
 }
 
-#if !defined(STARBOARD)
 FieldTrial* FeatureList::GetAssociatedFieldTrial(const Feature& feature) {
   DCHECK(initialized_);
   DCHECK(IsValidFeatureOrFieldTrialName(feature.name)) << feature.name;
@@ -348,7 +347,6 @@
 
   return nullptr;
 }
-#endif  // !defined(STARBOARD)
 
 void FeatureList::RegisterOverridesFromCommandLine(
     const std::string& feature_list,
@@ -389,7 +387,7 @@
   // one already exists for the key. Thus, only the first override for a given
   // feature name takes effect.
   overrides_.insert(std::make_pair(feature_name.as_string(),
-                                   OverrideEntry(overridden_state)));
+                                   OverrideEntry(overridden_state, nullptr)));
 }
 #else   // defined(STARBOARD)
 void FeatureList::RegisterOverride(StringPiece feature_name,
@@ -473,15 +471,10 @@
   return it->second == &feature;
 }
 
-#if defined(STARBOARD)
-FeatureList::OverrideEntry::OverrideEntry(OverrideState overridden_state)
-    : overridden_state(overridden_state) {}
-#else
 FeatureList::OverrideEntry::OverrideEntry(OverrideState overridden_state,
                                           FieldTrial* field_trial)
     : overridden_state(overridden_state),
       field_trial(field_trial),
       overridden_by_field_trial(field_trial != nullptr) {}
-#endif
 
 }  // namespace base
diff --git a/base/feature_list.h b/base/feature_list.h
index 5dcd336..621017a 100644
--- a/base/feature_list.h
+++ b/base/feature_list.h
@@ -116,12 +116,10 @@
   void InitializeFromCommandLine(const std::string& enable_features,
                                  const std::string& disable_features);
 
-#if !defined(STARBOARD)
   // Initializes feature overrides through the field trial allocator, which
   // we're using to store the feature names, their override state, and the name
   // of the associated field trial.
   void InitializeFromSharedMemory(PersistentMemoryAllocator* allocator);
-#endif  // !defined(STARBOARD)
 
   // Specifies whether a feature override enables or disables the feature.
   enum OverrideState {
@@ -135,7 +133,6 @@
   bool IsFeatureOverriddenFromCommandLine(const std::string& feature_name,
                                           OverrideState state) const;
 
-#if !defined(STARBOARD)
   // Associates a field trial for reporting purposes corresponding to the
   // command-line setting the feature state to |for_overridden_state|. The trial
   // will be activated when the state of the feature is first queried. This
@@ -156,9 +153,10 @@
                                   OverrideState override_state,
                                   FieldTrial* field_trial);
 
+#if !defined(STARBBOARD)
   // Loops through feature overrides and serializes them all into |allocator|.
   void AddFeaturesToAllocator(PersistentMemoryAllocator* allocator);
-#endif  // !defined(STARBOARD)
+#endif
 
   // Returns comma-separated lists of feature names (in the same format that is
   // accepted by InitializeFromCommandLine()) corresponding to features that
@@ -182,11 +180,9 @@
   // struct, which is checked in builds with DCHECKs enabled.
   static bool IsEnabled(const Feature& feature);
 
-#if !defined(STARBOARD)
   // Returns the field trial associated with the given |feature|. Must only be
   // called after the singleton instance has been registered via SetInstance().
   static FieldTrial* GetFieldTrial(const Feature& feature);
-#endif  // !defined(STARBOARD)
 
   // Splits a comma-separated string containing feature names into a vector. The
   // resulting pieces point to parts of |input|.
@@ -233,7 +229,6 @@
     // The overridden enable (on/off) state of the feature.
     const OverrideState overridden_state;
 
-#if !defined(STARBOARD)
     // An optional associated field trial, which will be activated when the
     // state of the feature is queried for the first time. Weak pointer to the
     // FieldTrial object that is owned by the FieldTrialList singleton.
@@ -246,14 +241,10 @@
     const bool overridden_by_field_trial;
 
     // TODO(asvitkine): Expand this as more support is added.
-
     // Constructs an OverrideEntry for the given |overridden_state|. If
     // |field_trial| is not null, it implies that |overridden_state| comes from
     // the trial, so |overridden_by_field_trial| will be set to true.
     OverrideEntry(OverrideState overridden_state, FieldTrial* field_trial);
-#else  // !defined(STARBOARD)
-    OverrideEntry(OverrideState overridden_state);
-#endif  // !defined(STARBOARD)
   };
 
   // Finalizes the initialization state of the FeatureList, so that no further
@@ -266,13 +257,11 @@
   // Requires the FeatureList to have already been fully initialized.
   bool IsFeatureEnabled(const Feature& feature);
 
-#if !defined(STARBOARD)
   // Returns the field trial associated with the given |feature|. This is
   // invoked by the public FeatureList::GetFieldTrial() static function on the
   // global singleton. Requires the FeatureList to have already been fully
   // initialized.
   base::FieldTrial* GetAssociatedFieldTrial(const Feature& feature);
-#endif  // !defined(STARBOARD)
 
   // For each feature name in comma-separated list of strings |feature_list|,
   // registers an override with the specified |overridden_state|. Also, will
@@ -292,7 +281,7 @@
   void RegisterOverride(StringPiece feature_name,
                         OverrideState overridden_state,
                         FieldTrial* field_trial);
-#else  // !defined(STARBOARD)
+#else   // !defined(STARBOARD)
   void RegisterOverride(StringPiece feature_name,
                         OverrideState overridden_state);
 #endif  // !defined(STARBOARD)
diff --git a/base/files/memory_mapped_file.h b/base/files/memory_mapped_file.h
index 13f2e58..1797576 100644
--- a/base/files/memory_mapped_file.h
+++ b/base/files/memory_mapped_file.h
@@ -16,8 +16,6 @@
 #include "starboard/types.h"
 #endif
 
-#if !defined(STARBOARD)
-
 namespace base {
 
 class FilePath;
@@ -136,6 +134,4 @@
 
 }  // namespace base
 
-#endif  // !defined(STARBOARD)
-
 #endif  // BASE_FILES_MEMORY_MAPPED_FILE_H_
diff --git a/base/memory/shared_memory_handle.cc b/base/memory/shared_memory_handle.cc
index 085bde4..00775d4 100644
--- a/base/memory/shared_memory_handle.cc
+++ b/base/memory/shared_memory_handle.cc
@@ -6,6 +6,16 @@
 
 namespace base {
 
+// As we don't support shared memory in Cobalt, we use the stub impl of
+// SharedMemoryHandle and needs a default ctor to make the compiler happy.
+#if defined(STARBOARD)
+SharedMemoryHandle::SharedMemoryHandle() {}
+
+bool SharedMemoryHandle::IsValid() const {
+  return false;
+}
+#endif
+
 SharedMemoryHandle::SharedMemoryHandle(const SharedMemoryHandle& handle) =
     default;
 
diff --git a/base/memory/shared_memory_handle.h b/base/memory/shared_memory_handle.h
index 8329829..4354617 100644
--- a/base/memory/shared_memory_handle.h
+++ b/base/memory/shared_memory_handle.h
@@ -5,8 +5,6 @@
 #ifndef BASE_MEMORY_SHARED_MEMORY_HANDLE_H_
 #define BASE_MEMORY_SHARED_MEMORY_HANDLE_H_
 
-// Starboard doesn't curretly support multiple processes or shared memory.
-#if !defined(STARBOARD)
 
 #include "base/unguessable_token.h"
 #include "build/build_config.h"
@@ -240,5 +238,4 @@
 
 }  // namespace base
 
-#endif  // !defined(STARBOARD)
 #endif  // BASE_MEMORY_SHARED_MEMORY_HANDLE_H_
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc
index 3130afd..eaaec1b 100644
--- a/base/metrics/field_trial.cc
+++ b/base/metrics/field_trial.cc
@@ -4,8 +4,6 @@
 
 #include "base/metrics/field_trial.h"
 
-#if !defined(STARBOARD)
-
 #include <algorithm>
 #include <utility>
 
@@ -54,7 +52,8 @@
 // This is safe from race conditions because MakeIterable is a release operation
 // and GetNextOfType is an acquire operation, so memory writes before
 // MakeIterable happen before memory reads after GetNextOfType.
-#if defined(OS_FUCHSIA)  // TODO(752368): Not yet supported on Fuchsia.
+// TODO(752368): Not yet supported on Fuchsia.
+#if defined(OS_FUCHSIA) || defined(STARBOARD)
 const bool kUseSharedMemoryForFieldTrials = false;
 #else
 const bool kUseSharedMemoryForFieldTrials = true;
@@ -218,7 +217,7 @@
 #endif
 }
 
-#if !defined(OS_NACL)
+#if !defined(OS_NACL) && !defined(STARBOARD)
 // Returns whether the operation succeeded.
 bool DeserializeGUIDFromStringPieces(base::StringPiece first,
                                      base::StringPiece second,
@@ -841,7 +840,7 @@
                           result);
     DCHECK(result);
   }
-#elif defined(OS_POSIX) && !defined(OS_NACL)
+#elif defined(OS_POSIX) && !defined(OS_NACL) && !defined(STARBOARD)
   // On POSIX, we check if the handle is valid by seeing if the browser process
   // sent over the switch (we don't care about the value). Invalid handles
   // occur in some browser tests which don't initialize the allocator.
@@ -895,7 +894,7 @@
       handles->push_back(global_->readonly_allocator_handle_.GetHandle());
   }
 }
-#elif defined(OS_FUCHSIA)
+#elif defined(OS_FUCHSIA) || defined(STARBOARD)
 // TODO(fuchsia): Implement shared-memory configuration (crbug.com/752368).
 #elif defined(OS_POSIX) && !defined(OS_NACL)
 // static
@@ -1218,6 +1217,9 @@
   ss << uintptr_handle << ",";
 #elif defined(OS_FUCHSIA)
   ss << shm.GetHandle() << ",";
+#elif defined(STARBOARD)
+  ss << "unsupported"
+     << ",";
 #elif !defined(OS_POSIX)
 #error Unsupported OS
 #endif
@@ -1303,7 +1305,7 @@
     return false;
   return FieldTrialList::CreateTrialsFromSharedMemoryHandle(shm);
 }
-#elif defined(OS_POSIX) && !defined(OS_NACL)
+#elif defined(OS_POSIX) && !defined(OS_NACL) && !defined(STARBOARD)
 // static
 bool FieldTrialList::CreateTrialsFromDescriptor(
     int fd_key,
@@ -1329,6 +1331,7 @@
 }
 #endif  // defined(OS_POSIX) && !defined(OS_NACL)
 
+#if !defined(STARBOARD)
 // static
 bool FieldTrialList::CreateTrialsFromSharedMemoryHandle(
     SharedMemoryHandle shm_handle) {
@@ -1372,9 +1375,13 @@
   }
   return true;
 }
+#endif
 
 // static
 void FieldTrialList::InstantiateFieldTrialAllocatorIfNeeded() {
+#if defined(STARBOARD)
+  return;
+#else
   if (!global_)
     return;
   AutoLock auto_lock(global_->lock_);
@@ -1414,6 +1421,7 @@
   global_->readonly_allocator_handle_ = GetSharedMemoryReadOnlyHandle(
       global_->field_trial_allocator_->shared_memory());
 #endif
+#endif
 }
 
 // static
@@ -1530,4 +1538,3 @@
 }
 
 }  // namespace base
-#endif  // !defined(STARBOARD)
diff --git a/base/metrics/field_trial.h b/base/metrics/field_trial.h
index 1460410..3d877f2 100644
--- a/base/metrics/field_trial.h
+++ b/base/metrics/field_trial.h
@@ -80,20 +80,6 @@
 #include "build/build_config.h"
 #include "starboard/types.h"
 
-#if defined(STARBOARD)
-namespace base {
-
-class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> {};
-
-class BASE_EXPORT FieldTrialList {
- public:
-  static std::string FindFullName(const std::string& trial_name) {
-    return std::string();
-  }
-};
-}  // namespace base
-#else
-
 namespace base {
 
 class FieldTrialList;
@@ -102,8 +88,14 @@
  public:
   typedef int Probability;  // Probability type for being selected in a trial.
 
+#if !defined(STARBOARD)
   // TODO(665129): Make private again after crash has been resolved.
   typedef SharedPersistentMemoryAllocator::Reference FieldTrialRef;
+#else
+  // In Cobalt, we don't export SharedPersistentMemoryAllocator, use the
+  // underlying type "uint32_t" directly here.
+  typedef uint32_t FieldTrialRef;
+#endif
 
   // Specifies the persistence of the field trial group choice.
   enum RandomizationType {
@@ -404,7 +396,12 @@
 // the entire life time of the process.
 class BASE_EXPORT FieldTrialList {
  public:
+#if !defined(STARBOARD)
   typedef SharedPersistentMemoryAllocator FieldTrialAllocator;
+#else
+  // In Cobalt, we don't import any shared memory constructs.
+  typedef LocalPersistentMemoryAllocator FieldTrialAllocator;
+#endif
 
   // Type for function pointer passed to |AllParamsToString| used to escape
   // special characters from |input|.
@@ -598,7 +595,7 @@
   // list of handles to be inherited.
   static void AppendFieldTrialHandleIfNeeded(
       base::HandlesToInheritVector* handles);
-#elif defined(OS_FUCHSIA)
+#elif defined(OS_FUCHSIA) || defined(STARBOARD)
   // TODO(fuchsia): Implement shared-memory configuration (crbug.com/752368).
 #elif defined(OS_POSIX) && !defined(OS_NACL)
   // On POSIX, we also need to explicitly pass down this file descriptor that
@@ -717,7 +714,7 @@
   // Returns true on success, false on failure.
   // |switch_value| also contains the serialized GUID.
   static bool CreateTrialsFromSwitchValue(const std::string& switch_value);
-#elif defined(OS_POSIX) && !defined(OS_NACL)
+#elif defined(OS_POSIX) && !defined(OS_NACL) && !defined(STARBOARD)
   // On POSIX systems that use the zygote, we look up the correct fd that backs
   // the shared memory segment containing the field trials by looking it up via
   // an fd key in GlobalDescriptors. Returns true on success, false on failure.
@@ -726,8 +723,9 @@
                                          const std::string& switch_value);
 #endif
 
-  // Takes an unmapped SharedMemoryHandle, creates a SharedMemory object from it
-  // and maps it with the correct size.
+#if !defined(STARBOARD)
+  // Takes an unmapped SharedMemoryHandle, creates a SharedMemory object from
+  // it and maps it with the correct size.
   static bool CreateTrialsFromSharedMemoryHandle(SharedMemoryHandle shm_handle);
 
   // Expects a mapped piece of shared memory |shm| that was created from the
@@ -737,6 +735,7 @@
   // successful and false otherwise.
   static bool CreateTrialsFromSharedMemory(
       std::unique_ptr<base::SharedMemory> shm);
+#endif
 
   // Instantiate the field trial allocator, add all existing field trials to it,
   // and duplicates its handle to a read-only handle, which gets stored in
@@ -813,5 +812,4 @@
 
 }  // namespace base
 
-#endif  // #if defined(STARBOARD)
 #endif  // BASE_METRICS_FIELD_TRIAL_H_
diff --git a/base/metrics/field_trial_params.h b/base/metrics/field_trial_params.h
index 0548945..8682226 100644
--- a/base/metrics/field_trial_params.h
+++ b/base/metrics/field_trial_params.h
@@ -5,8 +5,6 @@
 #ifndef BASE_METRICS_FIELD_TRIAL_PARAMS_H_
 #define BASE_METRICS_FIELD_TRIAL_PARAMS_H_
 
-#if !defined(STARBOARD)
-
 #include <map>
 #include <string>
 
@@ -257,6 +255,4 @@
 
 }  // namespace base
 
-#endif  // !defined(STARBOARD)
-
 #endif  // BASE_METRICS_FIELD_TRIAL_PARAMS_H_
diff --git a/base/metrics/persistent_memory_allocator.cc b/base/metrics/persistent_memory_allocator.cc
index e30e10e..24ac0c0 100644
--- a/base/metrics/persistent_memory_allocator.cc
+++ b/base/metrics/persistent_memory_allocator.cc
@@ -1128,6 +1128,11 @@
   int result = ::msync(const_cast<void*>(data()), length,
                        MS_INVALIDATE | (sync ? MS_SYNC : MS_ASYNC));
   DCHECK_NE(EINVAL, result);
+#elif defined(STARBOARD)
+  // TODO(b/283278127): This wont' work for platforms where
+  // SB_CAN_MAP_EXECUTABLE_MEMORY = 0. That's nxswitch, tvos, and playstation.
+  // Figure out how to make this work for all platforms.
+  SbMemoryFlush(const_cast<void*>(data()), length);
 #else
 #error Unsupported OS.
 #endif
diff --git a/base/metrics/persistent_memory_allocator.h b/base/metrics/persistent_memory_allocator.h
index ec85c3a..bcc9736 100644
--- a/base/metrics/persistent_memory_allocator.h
+++ b/base/metrics/persistent_memory_allocator.h
@@ -21,7 +21,9 @@
 
 class HistogramBase;
 class MemoryMappedFile;
+#if !defined(STARBOARD)
 class SharedMemory;
+#endif
 
 // Simple allocator for pieces of a memory block that may be persistent
 // to some storage or shared across multiple processes. This class resides
@@ -737,7 +739,7 @@
 #endif  // !defined(STARBOARD)
 
 // NACL doesn't support any kind of file access in build.
-#if !defined(OS_NACL) && !defined(STARBOARD)
+#if !defined(OS_NACL) || !defined(STARBOARD)
 // This allocator takes a memory-mapped file object and performs allocation
 // from it. The allocator takes ownership of the file object.
 class BASE_EXPORT FilePersistentMemoryAllocator
diff --git a/base/optional_unittest.cc b/base/optional_unittest.cc
index d504d2b..9fce853 100644
--- a/base/optional_unittest.cc
+++ b/base/optional_unittest.cc
@@ -1921,12 +1921,12 @@
   }
 
   {
-    Optional<std::string> o = make_optional(std::string("foo"));
+    Optional<std::string> o = base::make_optional(std::string("foo"));
     EXPECT_TRUE(o);
     EXPECT_EQ("foo", *o);
 
     std::string value = "bar";
-    o = make_optional(std::move(value));
+    o = base::make_optional(std::move(value));
     EXPECT_TRUE(o);
     EXPECT_EQ(std::string("bar"), *o);
   }
@@ -1968,7 +1968,7 @@
     EXPECT_EQ("123", *str1);
 
     auto str2 =
-        make_optional<std::string>({'a', 'b', 'c'}, std::allocator<char>());
+        base::make_optional<std::string>({'a', 'b', 'c'}, std::allocator<char>());
     EXPECT_EQ("abc", *str2);
   }
 }
diff --git a/base/sequenced_task_runner.cc b/base/sequenced_task_runner.cc
index 86771c6..0e12121 100644
--- a/base/sequenced_task_runner.cc
+++ b/base/sequenced_task_runner.cc
@@ -3,12 +3,48 @@
 // found in the LICENSE file.
 
 #include "base/sequenced_task_runner.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/trace_event/trace_event.h"
 
 #include <utility>
 
 #include "base/bind.h"
 
 namespace base {
+#if defined(STARBOARD)
+namespace {
+
+// Runs the given task, and then signals the given WaitableEvent.
+void RunAndSignal(const base::Closure& task, base::WaitableEvent* event) {
+  TRACE_EVENT0("task", "RunAndSignal");
+  task.Run();
+  event->Signal();
+}
+}  // namespace
+
+void SequencedTaskRunner::PostBlockingTask(const base::Location& from_here,
+                                              const base::Closure& task) {
+  TRACE_EVENT0("task", "MessageLoop::PostBlockingTask");
+  DCHECK(!RunsTasksInCurrentSequence())
+      << "PostBlockingTask can't be called from the MessageLoop's own thread. "
+      << from_here.ToString();
+  DCHECK(!task.is_null()) << from_here.ToString();
+
+  base::WaitableEvent task_finished(
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED);
+  bool task_may_run = PostTask(from_here,
+           base::Bind(&RunAndSignal, task, base::Unretained(&task_finished)));
+  DCHECK(task_may_run)
+      << "Task that will never run posted with PostBlockingTask.";
+
+  if (task_may_run) {
+    // Wait for the task to complete before proceeding.
+    task_finished.Wait();
+  }
+}
+#endif
 
 bool SequencedTaskRunner::PostNonNestableTask(const Location& from_here,
                                               OnceClosure task) {
diff --git a/base/sequenced_task_runner.h b/base/sequenced_task_runner.h
index 8138414..c26c3a9 100644
--- a/base/sequenced_task_runner.h
+++ b/base/sequenced_task_runner.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/base_export.h"
+#include "base/bind.h"
 #include "base/callback.h"
 #include "base/sequenced_task_runner_helpers.h"
 #include "base/task_runner.h"
@@ -140,6 +141,25 @@
                                        object);
   }
 
+#if defined(STARBOARD)
+  // Like PostTask, but blocks until the posted task completes. Returns false
+  // and does not block if task was not posted.
+  virtual void PostBlockingTask(const base::Location& from_here,
+                                const Closure& task);
+
+  // Adds a fence at the end of this MessageLoop's task queue, and then blocks
+  // until it has been reached. It is forbidden to call this method from the
+  // thread of the MessageLoop being posted to. One should exercise extreme
+  // caution when using this, as blocking between MessageLoops can cause
+  // deadlocks and is contraindicated in the Actor model of multiprogramming.
+  void WaitForFence() {
+    struct Fence {
+      static void Task() {}
+    };
+    PostBlockingTask(FROM_HERE, base::Bind(&Fence::Task));
+  }
+#endif
+
  protected:
   ~SequencedTaskRunner() override = default;
 
diff --git a/base/single_thread_task_runner.h b/base/single_thread_task_runner.h
index 74741d3..f8ba5a1 100644
--- a/base/single_thread_task_runner.h
+++ b/base/single_thread_task_runner.h
@@ -32,7 +32,7 @@
   // Like PostTask, but blocks until the posted task completes. Returns false
   // and does not block if task was not posted.
   virtual void PostBlockingTask(const base::Location& from_here,
-                                const Closure& task);
+                                const Closure& task) override;
 
   // Adds a fence at the end of this MessageLoop's task queue, and then blocks
   // until it has been reached. It is forbidden to call this method from the
diff --git a/base/strings/string_number_conversions.cc b/base/strings/string_number_conversions.cc
index 9995ac8..5d9c326 100644
--- a/base/strings/string_number_conversions.cc
+++ b/base/strings/string_number_conversions.cc
@@ -17,7 +17,6 @@
 #include "base/scoped_clear_last_error.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/third_party/dmg_fp/dmg_fp.h"
-#include "starboard/character.h"
 #include "starboard/common/string.h"
 #include "starboard/types.h"
 
diff --git a/base/third_party/symbolize/BUILD.gn b/base/third_party/symbolize/BUILD.gn
index 0dc7c2f..63febc8 100644
--- a/base/third_party/symbolize/BUILD.gn
+++ b/base/third_party/symbolize/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -13,6 +13,13 @@
   print_unsymbolized_stack_traces = is_asan || is_lsan || is_msan || is_tsan
 }
 
+config("symbolize_config") {
+  defines = [
+    # Only built for Linux and ChromeOS so no special export magic needed.
+    "GLOG_EXPORT=",
+  ]
+}
+
 static_library("symbolize") {
   visibility = [ "//base/*" ]
   sources = [
@@ -27,10 +34,13 @@
   ]
 
   defines = []
+
   if (print_unsymbolized_stack_traces) {
     defines += [ "PRINT_UNSYMBOLIZED_STACK_TRACES" ]
   }
 
   configs -= [ "//build/config/compiler:chromium_code" ]
   configs += [ "//build/config/compiler:no_chromium_code" ]
+
+  public_configs = [ ":symbolize_config" ]
 }
diff --git a/base/third_party/symbolize/README.chromium b/base/third_party/symbolize/README.chromium
index c8f50e8..d797349 100644
--- a/base/third_party/symbolize/README.chromium
+++ b/base/third_party/symbolize/README.chromium
@@ -1,23 +1,37 @@
 Name: google-glog's symbolization library
-URL: http://code.google.com/p/google-glog/
+URL: https://github.com/google/glog
 License: BSD
 
-The following files are copied AS-IS from:
-http://code.google.com/p/google-glog/source/browse/#svn/trunk/src (r76)
+The following files were copied from:
+https://github.com/google/glog/tree/b70ea80433c2a8f20b832be97b90f1f82b0d29e9
 
-- demangle.cc
-- demangle.h
-- symbolize.h
-
-The following files are minimal stubs created for use in Chromium:
-
-- config.h
 - glog/logging.h
 - glog/raw_logging.h
+- demangle.cc
+- demangle.h
+- symbolize.cc
+- symbolize.h
 - utilities.h
 
-The following file has been copied from:
-http://code.google.com/p/google-glog/source/browse/#svn/trunk/src (r76)
-  and then trivially modified to compile in C++11.
+config.h is auto-generated using the glog build and then trimmed down to only
+the macros used in the files above.
 
-- symbolize.cc
+Local modifications:
+- 001-fix-up-includes.patch: remove includes for "base/mutex.h" and change
+  logging headers to be included as user headers rather than system headers.
+- 002-minimal-logging.patch: remove everything except a RAW_LOG() macro that
+  does nothing.
+- 003-minimal-utilities.patch: remove everything except a macro for wrapping
+  the noninline compiler attribute.
+- 004-add-missing-symbolize-header.patch: add an include for symbolize.h to
+  symbolize.cc. This patch should be upstreamed.
+- 005-expose-file-helpers.patch: expose helpers for working with symbol files.
+- 006-use-sandbox-hook-for-open-object-file.patch: use the sandbox hook for
+  the exposed helper for opening object files. This patch should be upstreamed.
+- 007-sys-types-h.patch: include <sys/types.h> to get ssize_t on non-glibc
+  platforms.
+- 008-include-cstdlib.patch: include <cstdlib> for abort() rather than relying
+  on transitive includes.
+- 009-clone-absl-demangle.patch: Clone the demangling implementation from
+  abseil-cpp, which is itself a fork of https://github.com/google/glog/.
+- 010-clang-format.patch: format the source files using Chrome formatting rules.
diff --git a/base/third_party/symbolize/config.h b/base/third_party/symbolize/config.h
index 945f5a6..78654d9 100644
--- a/base/third_party/symbolize/config.h
+++ b/base/third_party/symbolize/config.h
@@ -1,7 +1,39 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+#ifndef GLOG_CONFIG_H
+#define GLOG_CONFIG_H
 
+/* Namespace for Google classes */
 #define GOOGLE_NAMESPACE google
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H
+
+/* define if your compiler has __attribute__ */
+#define HAVE___ATTRIBUTE__
+
+/* define if symbolize support is available */
+#define HAVE_SYMBOLIZE
+
+/* The size of `void *', as computed by sizeof. */
+#if defined(__LP64__)
+#define SIZEOF_VOID_P 8
+#else
+#define SIZEOF_VOID_P 4
+#endif
+
+#ifdef GLOG_BAZEL_BUILD
+
+/* TODO(rodrigoq): remove this workaround once bazel#3979 is resolved:
+ * https://github.com/bazelbuild/bazel/issues/3979 */
+#define _START_GOOGLE_NAMESPACE_ namespace GOOGLE_NAMESPACE {
 #define _END_GOOGLE_NAMESPACE_ }
+
+#else
+
+/* Stops putting the code inside the Google namespace */
+#define _END_GOOGLE_NAMESPACE_ }
+
+/* Puts following code inside the Google namespace */
 #define _START_GOOGLE_NAMESPACE_ namespace google {
+#endif
+
+#endif  // GLOG_CONFIG_H
diff --git a/base/third_party/symbolize/demangle.cc b/base/third_party/symbolize/demangle.cc
index db11e5c..8db75f0 100644
--- a/base/third_party/symbolize/demangle.cc
+++ b/base/third_party/symbolize/demangle.cc
@@ -28,132 +28,221 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 //
 // Author: Satoru Takabayashi
-
-// Note for Cobalt: Cobalt Starboard depends on the old version of Symbolize so
-// this file is from m27 Chromium. There are no Cobalt-introduced changes in
-// this file.
+//
+// For reference check out:
+// http://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling
+//
+// Note that we only have partial C++0x support yet.
 
 #include "demangle.h"
-#include <stdio.h>  // for NULL
+
+#if defined(GLOG_OS_WINDOWS)
+#include <dbghelp.h>
+#else
+#include <cstdint>
+#include <cstdio>
+#include <limits>
+#endif
 
 _START_GOOGLE_NAMESPACE_
 
+#if !defined(GLOG_OS_WINDOWS)
 typedef struct {
   const char *abbrev;
   const char *real_name;
+  // Number of arguments in <expression> context, or 0 if disallowed.
+  int arity;
 } AbbrevPair;
 
 // List of operators from Itanium C++ ABI.
 static const AbbrevPair kOperatorList[] = {
-  { "nw", "new" },
-  { "na", "new[]" },
-  { "dl", "delete" },
-  { "da", "delete[]" },
-  { "ps", "+" },
-  { "ng", "-" },
-  { "ad", "&" },
-  { "de", "*" },
-  { "co", "~" },
-  { "pl", "+" },
-  { "mi", "-" },
-  { "ml", "*" },
-  { "dv", "/" },
-  { "rm", "%" },
-  { "an", "&" },
-  { "or", "|" },
-  { "eo", "^" },
-  { "aS", "=" },
-  { "pL", "+=" },
-  { "mI", "-=" },
-  { "mL", "*=" },
-  { "dV", "/=" },
-  { "rM", "%=" },
-  { "aN", "&=" },
-  { "oR", "|=" },
-  { "eO", "^=" },
-  { "ls", "<<" },
-  { "rs", ">>" },
-  { "lS", "<<=" },
-  { "rS", ">>=" },
-  { "eq", "==" },
-  { "ne", "!=" },
-  { "lt", "<" },
-  { "gt", ">" },
-  { "le", "<=" },
-  { "ge", ">=" },
-  { "nt", "!" },
-  { "aa", "&&" },
-  { "oo", "||" },
-  { "pp", "++" },
-  { "mm", "--" },
-  { "cm", "," },
-  { "pm", "->*" },
-  { "pt", "->" },
-  { "cl", "()" },
-  { "ix", "[]" },
-  { "qu", "?" },
-  { "st", "sizeof" },
-  { "sz", "sizeof" },
-  { NULL, NULL },
+    // New has special syntax (not currently supported).
+    {"nw", "new", 0},
+    {"na", "new[]", 0},
+
+    // Works except that the 'gs' prefix is not supported.
+    {"dl", "delete", 1},
+    {"da", "delete[]", 1},
+
+    {"ps", "+", 1},  // "positive"
+    {"ng", "-", 1},  // "negative"
+    {"ad", "&", 1},  // "address-of"
+    {"de", "*", 1},  // "dereference"
+    {"co", "~", 1},
+
+    {"pl", "+", 2},
+    {"mi", "-", 2},
+    {"ml", "*", 2},
+    {"dv", "/", 2},
+    {"rm", "%", 2},
+    {"an", "&", 2},
+    {"or", "|", 2},
+    {"eo", "^", 2},
+    {"aS", "=", 2},
+    {"pL", "+=", 2},
+    {"mI", "-=", 2},
+    {"mL", "*=", 2},
+    {"dV", "/=", 2},
+    {"rM", "%=", 2},
+    {"aN", "&=", 2},
+    {"oR", "|=", 2},
+    {"eO", "^=", 2},
+    {"ls", "<<", 2},
+    {"rs", ">>", 2},
+    {"lS", "<<=", 2},
+    {"rS", ">>=", 2},
+    {"eq", "==", 2},
+    {"ne", "!=", 2},
+    {"lt", "<", 2},
+    {"gt", ">", 2},
+    {"le", "<=", 2},
+    {"ge", ">=", 2},
+    {"nt", "!", 1},
+    {"aa", "&&", 2},
+    {"oo", "||", 2},
+    {"pp", "++", 1},
+    {"mm", "--", 1},
+    {"cm", ",", 2},
+    {"pm", "->*", 2},
+    {"pt", "->", 0},  // Special syntax
+    {"cl", "()", 0},  // Special syntax
+    {"ix", "[]", 2},
+    {"qu", "?", 3},
+    {"st", "sizeof", 0},  // Special syntax
+    {"sz", "sizeof", 1},  // Not a real operator name, but used in expressions.
+    {nullptr, nullptr, 0},
 };
 
 // List of builtin types from Itanium C++ ABI.
+//
+// Invariant: only one- or two-character type abbreviations here.
 static const AbbrevPair kBuiltinTypeList[] = {
-  { "v", "void" },
-  { "w", "wchar_t" },
-  { "b", "bool" },
-  { "c", "char" },
-  { "a", "signed char" },
-  { "h", "unsigned char" },
-  { "s", "short" },
-  { "t", "unsigned short" },
-  { "i", "int" },
-  { "j", "unsigned int" },
-  { "l", "long" },
-  { "m", "unsigned long" },
-  { "x", "long long" },
-  { "y", "unsigned long long" },
-  { "n", "__int128" },
-  { "o", "unsigned __int128" },
-  { "f", "float" },
-  { "d", "double" },
-  { "e", "long double" },
-  { "g", "__float128" },
-  { "z", "ellipsis" },
-  { NULL, NULL }
+    {"v", "void", 0},
+    {"w", "wchar_t", 0},
+    {"b", "bool", 0},
+    {"c", "char", 0},
+    {"a", "signed char", 0},
+    {"h", "unsigned char", 0},
+    {"s", "short", 0},
+    {"t", "unsigned short", 0},
+    {"i", "int", 0},
+    {"j", "unsigned int", 0},
+    {"l", "long", 0},
+    {"m", "unsigned long", 0},
+    {"x", "long long", 0},
+    {"y", "unsigned long long", 0},
+    {"n", "__int128", 0},
+    {"o", "unsigned __int128", 0},
+    {"f", "float", 0},
+    {"d", "double", 0},
+    {"e", "long double", 0},
+    {"g", "__float128", 0},
+    {"z", "ellipsis", 0},
+
+    {"De", "decimal128", 0},  // IEEE 754r decimal floating point (128 bits)
+    {"Dd", "decimal64", 0},   // IEEE 754r decimal floating point (64 bits)
+    {"Dc", "decltype(auto)", 0},
+    {"Da", "auto", 0},
+    {"Dn", "std::nullptr_t", 0},  // i.e., decltype(nullptr)
+    {"Df", "decimal32", 0},       // IEEE 754r decimal floating point (32 bits)
+    {"Di", "char32_t", 0},
+    {"Du", "char8_t", 0},
+    {"Ds", "char16_t", 0},
+    {"Dh", "float16", 0},  // IEEE 754r half-precision float (16 bits)
+    {nullptr, nullptr, 0},
 };
 
 // List of substitutions Itanium C++ ABI.
 static const AbbrevPair kSubstitutionList[] = {
-  { "St", "" },
-  { "Sa", "allocator" },
-  { "Sb", "basic_string" },
-  // std::basic_string<char, std::char_traits<char>,std::allocator<char> >
-  { "Ss", "string"},
-  // std::basic_istream<char, std::char_traits<char> >
-  { "Si", "istream" },
-  // std::basic_ostream<char, std::char_traits<char> >
-  { "So", "ostream" },
-  // std::basic_iostream<char, std::char_traits<char> >
-  { "Sd", "iostream" },
-  { NULL, NULL }
+    {"St", "", 0},
+    {"Sa", "allocator", 0},
+    {"Sb", "basic_string", 0},
+    // std::basic_string<char, std::char_traits<char>,std::allocator<char> >
+    {"Ss", "string", 0},
+    // std::basic_istream<char, std::char_traits<char> >
+    {"Si", "istream", 0},
+    // std::basic_ostream<char, std::char_traits<char> >
+    {"So", "ostream", 0},
+    // std::basic_iostream<char, std::char_traits<char> >
+    {"Sd", "iostream", 0},
+    {nullptr, nullptr, 0},
 };
 
-// State needed for demangling.
+// State needed for demangling.  This struct is copied in almost every stack
+// frame, so every byte counts.
 typedef struct {
-  const char *mangled_cur;  // Cursor of mangled name.
-  const char* mangled_end;  // End of mangled name.
-  char *out_cur;            // Cursor of output string.
-  const char *out_begin;    // Beginning of output string.
-  const char *out_end;      // End of output string.
-  const char *prev_name;    // For constructors/destructors.
-  int prev_name_length;     // For constructors/destructors.
-  int nest_level;           // For nested names.
-  int number;               // Remember the previous number.
-  bool append;              // Append flag.
-  bool overflowed;          // True if output gets overflowed.
+  int mangled_idx;                     // Cursor of mangled name.
+  int out_cur_idx;                     // Cursor of output string.
+  int prev_name_idx;                   // For constructors/destructors.
+  unsigned int prev_name_length : 16;  // For constructors/destructors.
+  signed int nest_level : 15;          // For nested names.
+  unsigned int append : 1;             // Append flag.
+  // Note: for some reason MSVC can't pack "bool append : 1" into the same int
+  // with the above two fields, so we use an int instead.  Amusingly it can pack
+  // "signed bool" as expected, but relying on that to continue to be a legal
+  // type seems ill-advised (as it's illegal in at least clang).
+} ParseState;
+
+static_assert(sizeof(ParseState) == 4 * sizeof(int),
+              "unexpected size of ParseState");
+
+// One-off state for demangling that's not subject to backtracking -- either
+// constant data, data that's intentionally immune to backtracking (steps), or
+// data that would never be changed by backtracking anyway (recursion_depth).
+//
+// Only one copy of this exists for each call to Demangle, so the size of this
+// struct is nearly inconsequential.
+typedef struct {
+  const char* mangled_begin;  // Beginning of input string.
+  char* out;                  // Beginning of output string.
+  int out_end_idx;            // One past last allowed output character.
+  int recursion_depth;        // For stack exhaustion prevention.
+  int steps;               // Cap how much work we'll do, regardless of depth.
+  ParseState parse_state;  // Backtrackable state copied for most frames.
 } State;
 
+namespace {
+// Prevent deep recursion / stack exhaustion.
+// Also prevent unbounded handling of complex inputs.
+class ComplexityGuard {
+ public:
+  explicit ComplexityGuard(State* state) : state_(state) {
+    ++state->recursion_depth;
+    ++state->steps;
+  }
+  ~ComplexityGuard() { --state_->recursion_depth; }
+
+  // 256 levels of recursion seems like a reasonable upper limit on depth.
+  // 128 is not enough to demagle synthetic tests from demangle_unittest.txt:
+  // "_ZaaZZZZ..." and "_ZaaZcvZcvZ..."
+  static constexpr int kRecursionDepthLimit = 256;
+
+  // We're trying to pick a charitable upper-limit on how many parse steps are
+  // necessary to handle something that a human could actually make use of.
+  // This is mostly in place as a bound on how much work we'll do if we are
+  // asked to demangle an mangled name from an untrusted source, so it should be
+  // much larger than the largest expected symbol, but much smaller than the
+  // amount of work we can do in, e.g., a second.
+  //
+  // Some real-world symbols from an arbitrary binary started failing between
+  // 2^12 and 2^13, so we multiply the latter by an extra factor of 16 to set
+  // the limit.
+  //
+  // Spending one second on 2^17 parse steps would require each step to take
+  // 7.6us, or ~30000 clock cycles, so it's safe to say this can be done in
+  // under a second.
+  static constexpr int kParseStepsLimit = 1 << 17;
+
+  bool IsTooComplex() const {
+    return state_->recursion_depth > kRecursionDepthLimit ||
+           state_->steps > kParseStepsLimit;
+  }
+
+ private:
+  State* state_;
+};
+}  // namespace
+
 // We don't use strlen() in libc since it's not guaranteed to be async
 // signal safe.
 static size_t StrLen(const char *str) {
@@ -165,52 +254,73 @@
   return len;
 }
 
+// Returns true if "str" has at least "n" characters remaining.
+static bool AtLeastNumCharsRemaining(const char* str, size_t n) {
+  for (size_t i = 0; i < n; ++i) {
+    if (str[i] == '\0') {
+      return false;
+    }
+  }
+  return true;
+}
+
 // Returns true if "str" has "prefix" as a prefix.
 static bool StrPrefix(const char *str, const char *prefix) {
   size_t i = 0;
-  while (str[i] != '\0' && prefix[i] != '\0' &&
-         str[i] == prefix[i]) {
+  while (str[i] != '\0' && prefix[i] != '\0' && str[i] == prefix[i]) {
     ++i;
   }
   return prefix[i] == '\0';  // Consumed everything in "prefix".
 }
 
-static void InitState(State *state, const char *mangled,
-                      char *out, int out_size) {
-  state->mangled_cur = mangled;
-  state->mangled_end = mangled + StrLen(mangled);
-  state->out_cur = out;
-  state->out_begin = out;
-  state->out_end = out + out_size;
-  state->prev_name  = NULL;
-  state->prev_name_length = -1;
-  state->nest_level = -1;
-  state->number = -1;
-  state->append = true;
-  state->overflowed = false;
+static void InitState(State* state,
+                      const char* mangled,
+                      char* out,
+                      size_t out_size) {
+  state->mangled_begin = mangled;
+  state->out = out;
+  state->out_end_idx = static_cast<int>(out_size);
+  state->recursion_depth = 0;
+  state->steps = 0;
+
+  state->parse_state.mangled_idx = 0;
+  state->parse_state.out_cur_idx = 0;
+  state->parse_state.prev_name_idx = 0;
+  state->parse_state.prev_name_length = 0;
+  state->parse_state.nest_level = -1;
+  state->parse_state.append = true;
 }
 
-// Calculates the remaining length of the mangled name.
-static int RemainingLength(State* state) {
-  return state->mangled_end - state->mangled_cur;
+static inline const char* RemainingInput(State* state) {
+  return &state->mangled_begin[state->parse_state.mangled_idx];
 }
 
-// Returns true and advances "mangled_cur" if we find "c" at
-// "mangled_cur" position.
-static bool ParseChar(State* state, const char c) {
-  if (RemainingLength(state) >= 1 && *state->mangled_cur == c) {
-    ++state->mangled_cur;
+// Returns true and advances "mangled_idx" if we find "one_char_token"
+// at "mangled_idx" position.  It is assumed that "one_char_token" does
+// not contain '\0'.
+static bool ParseOneCharToken(State *state, const char one_char_token) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (RemainingInput(state)[0] == one_char_token) {
+    ++state->parse_state.mangled_idx;
     return true;
   }
   return false;
 }
 
-// Returns true and advances "mangled_cur" if we find "two_chars" at
-// "mangled_cur" position.
-static bool ParseTwoChar(State* state, const char* two_chars) {
-  if (RemainingLength(state) >= 2 && state->mangled_cur[0] == two_chars[0] &&
-      state->mangled_cur[1] == two_chars[1]) {
-    state->mangled_cur += 2;
+// Returns true and advances "mangled_cur" if we find "two_char_token"
+// at "mangled_cur" position.  It is assumed that "two_char_token" does
+// not contain '\0'.
+static bool ParseTwoCharToken(State *state, const char *two_char_token) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (RemainingInput(state)[0] == two_char_token[0] &&
+      RemainingInput(state)[1] == two_char_token[1]) {
+    state->parse_state.mangled_idx += 2;
     return true;
   }
   return false;
@@ -219,21 +329,36 @@
 // Returns true and advances "mangled_cur" if we find any character in
 // "char_class" at "mangled_cur" position.
 static bool ParseCharClass(State *state, const char *char_class) {
-  if (state->mangled_cur == state->mangled_end) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (RemainingInput(state)[0] == '\0') {
     return false;
   }
   const char *p = char_class;
   for (; *p != '\0'; ++p) {
-    if (*state->mangled_cur == *p) {
-      state->mangled_cur += 1;
+    if (RemainingInput(state)[0] == *p) {
+      ++state->parse_state.mangled_idx;
       return true;
     }
   }
   return false;
 }
 
+static bool ParseDigit(State* state, int* digit) {
+  char c = RemainingInput(state)[0];
+  if (ParseCharClass(state, "0123456789")) {
+    if (digit != nullptr) {
+      *digit = c - '0';
+    }
+    return true;
+  }
+  return false;
+}
+
 // This function is used for handling an optional non-terminal.
-static bool Optional(bool status) {
+static bool Optional(bool /*status*/) {
   return true;
 }
 
@@ -248,22 +373,33 @@
   return false;
 }
 
-// Append "str" at "out_cur".  If there is an overflow, "overflowed"
-// is set to true for later use.  The output string is ensured to
+// This function is used for handling <non-terminal>* syntax. The function
+// always returns true and must be followed by a termination token or a
+// terminating sequence not handled by parse_func (e.g.
+// ParseOneCharToken(state, 'E')).
+static bool ZeroOrMore(ParseFunc parse_func, State *state) {
+  while (parse_func(state)) {
+  }
+  return true;
+}
+
+// Append "str" at "out_cur_idx".  If there is an overflow, out_cur_idx is
+// set to out_end_idx+1.  The output string is ensured to
 // always terminate with '\0' as long as there is no overflow.
-static void Append(State *state, const char * const str, const int length) {
-  int i;
-  for (i = 0; i < length; ++i) {
-    if (state->out_cur + 1 < state->out_end) {  // +1 for '\0'
-      *state->out_cur = str[i];
-      ++state->out_cur;
+static void Append(State* state, const char* const str, const size_t length) {
+  for (size_t i = 0; i < length; ++i) {
+    if (state->parse_state.out_cur_idx + 1 <
+        state->out_end_idx) {  // +1 for '\0'
+      state->out[state->parse_state.out_cur_idx++] = str[i];
     } else {
-      state->overflowed = true;
+      // signal overflow
+      state->parse_state.out_cur_idx = state->out_end_idx + 1;
       break;
     }
   }
-  if (!state->overflowed) {
-    *state->out_cur = '\0';  // Terminate it with '\0'
+  if (state->parse_state.out_cur_idx < state->out_end_idx) {
+    state->out[state->parse_state.out_cur_idx] =
+        '\0';  // Terminate it with '\0'
   }
 }
 
@@ -273,33 +409,100 @@
 }
 
 static bool IsAlpha(char c) {
-  return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
+  return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+static bool IsDigit(char c) {
+  return c >= '0' && c <= '9';
+}
+
+// Returns true if "str" is a function clone suffix.  These suffixes are used
+// by GCC 4.5.x and later versions (and our locally-modified version of GCC
+// 4.4.x) to indicate functions which have been cloned during optimization.
+// We treat any sequence (.<alpha>+.<digit>+)+ as a function clone suffix.
+// Additionally, '_' is allowed along with the alphanumeric sequence.
+static bool IsFunctionCloneSuffix(const char *str) {
+  size_t i = 0;
+  while (str[i] != '\0') {
+    bool parsed = false;
+    // Consume a single [.<alpha> | _]*[.<digit>]* sequence.
+    if (str[i] == '.' && (IsAlpha(str[i + 1]) || str[i + 1] == '_')) {
+      parsed = true;
+      i += 2;
+      while (IsAlpha(str[i]) || str[i] == '_') {
+        ++i;
+      }
+    }
+    if (str[i] == '.' && IsDigit(str[i + 1])) {
+      parsed = true;
+      i += 2;
+      while (IsDigit(str[i])) {
+        ++i;
+      }
+    }
+    if (!parsed) {
+      return false;
+    }
+  }
+  return true;  // Consumed everything in "str".
+}
+
+static bool EndsWith(State* state, const char chr) {
+  return state->parse_state.out_cur_idx > 0 &&
+         state->parse_state.out_cur_idx < state->out_end_idx &&
+         chr == state->out[state->parse_state.out_cur_idx - 1];
 }
 
 // Append "str" with some tweaks, iff "append" state is true.
-// Returns true so that it can be placed in "if" conditions.
-static void MaybeAppendWithLength(State *state, const char * const str,
-                                  const int length) {
-  if (state->append && length > 0) {
+static void MaybeAppendWithLength(State* state,
+                                  const char* const str,
+                                  const size_t length) {
+  if (state->parse_state.append && length > 0) {
     // Append a space if the output buffer ends with '<' and "str"
     // starts with '<' to avoid <<<.
-    if (str[0] == '<' && state->out_begin < state->out_cur  &&
-        state->out_cur[-1] == '<') {
+    if (str[0] == '<' && EndsWith(state, '<')) {
       Append(state, " ", 1);
     }
-    // Remember the last identifier name for ctors/dtors.
-    if (IsAlpha(str[0]) || str[0] == '_') {
-      state->prev_name = state->out_cur;
-      state->prev_name_length = length;
+    // Remember the last identifier name for ctors/dtors,
+    // but only if we haven't yet overflown the buffer.
+    if (state->parse_state.out_cur_idx < state->out_end_idx &&
+        (IsAlpha(str[0]) || str[0] == '_')) {
+      state->parse_state.prev_name_idx = state->parse_state.out_cur_idx;
+      state->parse_state.prev_name_length = static_cast<unsigned int>(length);
     }
     Append(state, str, length);
   }
 }
 
-// A convenient wrapper arount MaybeAppendWithLength().
-static bool MaybeAppend(State *state, const char * const str) {
-  if (state->append) {
-    int length = StrLen(str);
+// Appends a positive decimal number to the output if appending is enabled.
+static bool MaybeAppendDecimal(State* state, int val) {
+  // Max {32-64}-bit unsigned int is 20 digits.
+  constexpr size_t kMaxLength = 20;
+  char buf[kMaxLength];
+
+  // We can't use itoa or sprintf as neither is specified to be
+  // async-signal-safe.
+  if (state->parse_state.append) {
+    // We can't have a one-before-the-beginning pointer, so instead start with
+    // one-past-the-end and manipulate one character before the pointer.
+    char* p = &buf[kMaxLength];
+    do {  // val=0 is the only input that should write a leading zero digit.
+      *--p = static_cast<char>((val % 10) + '0');
+      val /= 10;
+    } while (p > buf && val != 0);
+
+    // 'p' landed on the last character we set.  How convenient.
+    Append(state, p, kMaxLength - static_cast<size_t>(p - buf));
+  }
+
+  return true;
+}
+
+// A convenient wrapper around MaybeAppendWithLength().
+// Returns true so that it can be placed in "if" conditions.
+static bool MaybeAppend(State* state, const char* const str) {
+  if (state->parse_state.append) {
+    size_t length = StrLen(str);
     MaybeAppendWithLength(state, str, length);
   }
   return true;
@@ -307,80 +510,83 @@
 
 // This function is used for handling nested names.
 static bool EnterNestedName(State *state) {
-  state->nest_level = 0;
+  state->parse_state.nest_level = 0;
   return true;
 }
 
 // This function is used for handling nested names.
-static bool LeaveNestedName(State* state, int prev_value) {
-  state->nest_level = prev_value;
+static bool LeaveNestedName(State* state, int16_t prev_value) {
+  state->parse_state.nest_level = prev_value;
   return true;
 }
 
 // Disable the append mode not to print function parameters, etc.
 static bool DisableAppend(State *state) {
-  state->append = false;
+  state->parse_state.append = false;
   return true;
 }
 
 // Restore the append mode to the previous state.
 static bool RestoreAppend(State *state, bool prev_value) {
-  state->append = prev_value;
+  state->parse_state.append = prev_value;
   return true;
 }
 
 // Increase the nest level for nested names.
 static void MaybeIncreaseNestLevel(State *state) {
-  if (state->nest_level > -1) {
-    ++state->nest_level;
+  if (state->parse_state.nest_level > -1) {
+    ++state->parse_state.nest_level;
   }
 }
 
 // Appends :: for nested names if necessary.
 static void MaybeAppendSeparator(State *state) {
-  if (state->nest_level >= 1) {
+  if (state->parse_state.nest_level >= 1) {
     MaybeAppend(state, "::");
   }
 }
 
 // Cancel the last separator if necessary.
 static void MaybeCancelLastSeparator(State *state) {
-  if (state->nest_level >= 1 && state->append &&
-      state->out_begin <= state->out_cur - 2) {
-    state->out_cur -= 2;
-    *state->out_cur = '\0';
+  if (state->parse_state.nest_level >= 1 && state->parse_state.append &&
+      state->parse_state.out_cur_idx >= 2) {
+    state->parse_state.out_cur_idx -= 2;
+    state->out[state->parse_state.out_cur_idx] = '\0';
   }
 }
 
-// Returns true if identifier pointed by "mangled_cur" is anonymous
-// namespace.
-static bool IdentifierIsAnonymousNamespace(State* state) {
-  const char anon_prefix[] = "_GLOBAL__N_";
-  return (state->number > sizeof(anon_prefix) - 1 &&  // Should be longer.
-          StrPrefix(state->mangled_cur, anon_prefix));
+// Returns true if the identifier of the given length pointed to by
+// "mangled_cur" is anonymous namespace.
+static bool IdentifierIsAnonymousNamespace(State* state, size_t length) {
+  // Returns true if "anon_prefix" is a proper prefix of "mangled_cur".
+  static const char anon_prefix[] = "_GLOBAL__N_";
+  return (length > (sizeof(anon_prefix) - 1) &&
+          StrPrefix(RemainingInput(state), anon_prefix));
 }
 
 // Forward declarations of our parsing functions.
 static bool ParseMangledName(State *state);
 static bool ParseEncoding(State *state);
 static bool ParseName(State *state);
-static bool ParseUnscopedName(State *state);
-static bool ParseUnscopedTemplateName(State *state);
+static bool ParseUnscopedName(State* state);
 static bool ParseNestedName(State *state);
 static bool ParsePrefix(State *state);
 static bool ParseUnqualifiedName(State *state);
 static bool ParseSourceName(State *state);
 static bool ParseLocalSourceName(State *state);
-static bool ParseNumber(State* state);
+static bool ParseUnnamedTypeName(State* state);
+static bool ParseNumber(State *state, int *number_out);
 static bool ParseFloatNumber(State *state);
 static bool ParseSeqId(State *state);
-static bool ParseIdentifier(State* state);
-static bool ParseOperatorName(State *state);
+static bool ParseIdentifier(State* state, size_t length);
+static bool ParseOperatorName(State* state, int* arity);
 static bool ParseSpecialName(State *state);
 static bool ParseCallOffset(State *state);
 static bool ParseNVOffset(State *state);
 static bool ParseVOffset(State *state);
+static bool ParseAbiTags(State* state);
 static bool ParseCtorDtorName(State *state);
+static bool ParseDecltype(State* state);
 static bool ParseType(State *state);
 static bool ParseCVQualifiers(State *state);
 static bool ParseBuiltinType(State *state);
@@ -393,11 +599,15 @@
 static bool ParseTemplateTemplateParam(State *state);
 static bool ParseTemplateArgs(State *state);
 static bool ParseTemplateArg(State *state);
+static bool ParseBaseUnresolvedName(State* state);
+static bool ParseUnresolvedName(State* state);
 static bool ParseExpression(State *state);
 static bool ParseExprPrimary(State *state);
+static bool ParseExprCastValue(State* state);
 static bool ParseLocalName(State *state);
+static bool ParseLocalNameSuffix(State* state);
 static bool ParseDiscriminator(State *state);
-static bool ParseSubstitution(State *state);
+static bool ParseSubstitution(State* state, bool accept_std);
 
 // Implementation note: the following code is a straightforward
 // translation of the Itanium C++ ABI defined in BNF with a couple of
@@ -409,11 +619,12 @@
 // - Reorder patterns to give greedier functions precedence
 //   We'll mark "Less greedy than" for these cases in the code
 //
-// Each parsing function changes the state and returns true on
-// success.  Otherwise, don't change the state and returns false.  To
-// ensure that the state isn't changed in the latter case, we save the
-// original state before we call more than one parsing functions
-// consecutively with &&, and restore the state if unsuccessful.  See
+// Each parsing function changes the parse state and returns true on
+// success, or returns false and doesn't change the parse state (note:
+// the parse-steps counter increases regardless of success or failure).
+// To ensure that the parse state isn't changed in the latter case, we
+// save the original state before we call multiple parsing functions
+// consecutively with &&, and restore it if unsuccessful.  See
 // ParseEncoding() as an example of this convention.  We follow the
 // convention throughout the code.
 //
@@ -427,34 +638,35 @@
 //
 // Reference:
 // - Itanium C++ ABI
-//   <http://www.codesourcery.com/cxx-abi/abi.html#mangling>
+//   <https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling>
 
 // <mangled-name> ::= _Z <encoding>
 static bool ParseMangledName(State *state) {
-  if (ParseTwoChar(state, "_Z") && ParseEncoding(state)) {
-    // Append trailing version suffix if any.
-    // ex. _Z3foo@@GLIBCXX_3.4
-    if (state->mangled_cur < state->mangled_end &&
-        state->mangled_cur[0] == '@') {
-      MaybeAppend(state, state->mangled_cur);
-      state->mangled_cur = state->mangled_end;
-    }
-    return true;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
   }
-  return false;
+  return ParseTwoCharToken(state, "_Z") && ParseEncoding(state);
 }
 
 // <encoding> ::= <(function) name> <bare-function-type>
 //            ::= <(data) name>
 //            ::= <special-name>
 static bool ParseEncoding(State *state) {
-  State copy = *state;
-  if (ParseName(state) && ParseBareFunctionType(state)) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  // Implementing the first two productions together as <name>
+  // [<bare-function-type>] avoids exponential blowup of backtracking.
+  //
+  // Since Optional(...) can't fail, there's no need to copy the state for
+  // backtracking.
+  if (ParseName(state) && Optional(ParseBareFunctionType(state))) {
     return true;
   }
-  *state = copy;
 
-  if (ParseName(state) || ParseSpecialName(state)) {
+  if (ParseSpecialName(state)) {
     return true;
   }
   return false;
@@ -465,56 +677,79 @@
 //        ::= <unscoped-name>
 //        ::= <local-name>
 static bool ParseName(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   if (ParseNestedName(state) || ParseLocalName(state)) {
     return true;
   }
 
-  State copy = *state;
-  if (ParseUnscopedTemplateName(state) &&
+  // We reorganize the productions to avoid re-parsing unscoped names.
+  // - Inline <unscoped-template-name> productions:
+  //   <name> ::= <substitution> <template-args>
+  //          ::= <unscoped-name> <template-args>
+  //          ::= <unscoped-name>
+  // - Merge the two productions that start with unscoped-name:
+  //   <name> ::= <unscoped-name> [<template-args>]
+
+  ParseState copy = state->parse_state;
+  // "std<...>" isn't a valid name.
+  if (ParseSubstitution(state, /*accept_std=*/false) &&
       ParseTemplateArgs(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  // Less greedy than <unscoped-template-name> <template-args>.
-  if (ParseUnscopedName(state)) {
-    return true;
-  }
-  return false;
+  // Note there's no need to restore state after this since only the first
+  // subparser can fail.
+  return ParseUnscopedName(state) && Optional(ParseTemplateArgs(state));
 }
 
 // <unscoped-name> ::= <unqualified-name>
 //                 ::= St <unqualified-name>
 static bool ParseUnscopedName(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   if (ParseUnqualifiedName(state)) {
     return true;
   }
 
-  State copy = *state;
-  if (ParseTwoChar(state, "St") && MaybeAppend(state, "std::") &&
+  ParseState copy = state->parse_state;
+  if (ParseTwoCharToken(state, "St") && MaybeAppend(state, "std::") &&
       ParseUnqualifiedName(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
-// <unscoped-template-name> ::= <unscoped-name>
-//                          ::= <substitution>
-static bool ParseUnscopedTemplateName(State *state) {
-  return ParseUnscopedName(state) || ParseSubstitution(state);
+// <ref-qualifer> ::= R // lvalue method reference qualifier
+//                ::= O // rvalue method reference qualifier
+static inline bool ParseRefQualifier(State* state) {
+  return ParseCharClass(state, "OR");
 }
 
-// <nested-name> ::= N [<CV-qualifiers>] <prefix> <unqualified-name> E
-//               ::= N [<CV-qualifiers>] <template-prefix> <template-args> E
+// <nested-name> ::= N [<CV-qualifiers>] [<ref-qualifier>] <prefix>
+//                   <unqualified-name> E
+//               ::= N [<CV-qualifiers>] [<ref-qualifier>] <template-prefix>
+//                   <template-args> E
 static bool ParseNestedName(State *state) {
-  State copy = *state;
-  if (ParseChar(state, 'N') && EnterNestedName(state) &&
-      Optional(ParseCVQualifiers(state)) && ParsePrefix(state) &&
-      LeaveNestedName(state, copy.nest_level) && ParseChar(state, 'E')) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'N') && EnterNestedName(state) &&
+      Optional(ParseCVQualifiers(state)) &&
+      Optional(ParseRefQualifier(state)) && ParsePrefix(state) &&
+      LeaveNestedName(state, copy.nest_level) &&
+      ParseOneCharToken(state, 'E')) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
@@ -530,12 +765,17 @@
 //                   ::= <template-param>
 //                   ::= <substitution>
 static bool ParsePrefix(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   bool has_something = false;
   while (true) {
     MaybeAppendSeparator(state);
     if (ParseTemplateParam(state) ||
-        ParseSubstitution(state) ||
-        ParseUnscopedName(state)) {
+        ParseSubstitution(state, /*accept_std=*/true) ||
+        ParseUnscopedName(state) ||
+        (ParseOneCharToken(state, 'M') && ParseUnnamedTypeName(state))) {
       has_something = true;
       MaybeIncreaseNestLevel(state);
       continue;
@@ -550,60 +790,158 @@
   return true;
 }
 
-// <unqualified-name> ::= <operator-name>
-//                    ::= <ctor-dtor-name>
-//                    ::= <source-name>
-//                    ::= <local-source-name>
+// <unqualified-name> ::= <operator-name> [<abi-tags>]
+//                    ::= <ctor-dtor-name> [<abi-tags>]
+//                    ::= <source-name> [<abi-tags>]
+//                    ::= <local-source-name> [<abi-tags>]
+//                    ::= <unnamed-type-name> [<abi-tags>]
+//
+// <local-source-name> is a GCC extension; see below.
 static bool ParseUnqualifiedName(State *state) {
-  return (ParseOperatorName(state) ||
-          ParseCtorDtorName(state) ||
-          ParseSourceName(state) ||
-          ParseLocalSourceName(state));
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (ParseOperatorName(state, nullptr) || ParseCtorDtorName(state) ||
+      ParseSourceName(state) || ParseLocalSourceName(state) ||
+      ParseUnnamedTypeName(state)) {
+    return ParseAbiTags(state);
+  }
+  return false;
+}
+
+// <abi-tags> ::= <abi-tag> [<abi-tags>]
+// <abi-tag>  ::= B <source-name>
+static bool ParseAbiTags(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+
+  while (ParseOneCharToken(state, 'B')) {
+    ParseState copy = state->parse_state;
+    MaybeAppend(state, "[abi:");
+
+    if (!ParseSourceName(state)) {
+      state->parse_state = copy;
+      return false;
+    }
+    MaybeAppend(state, "]");
+  }
+
+  return true;
 }
 
 // <source-name> ::= <positive length number> <identifier>
 static bool ParseSourceName(State *state) {
-  State copy = *state;
-  if (ParseNumber(state) && ParseIdentifier(state)) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  int length = -1;
+  if (ParseNumber(state, &length) &&
+      ParseIdentifier(state, static_cast<size_t>(length))) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <local-source-name> ::= L <source-name> [<discriminator>]
 //
 // References:
-//   http://gcc.gnu.org/bugzilla/show_bug.cgi?id=31775
-//   http://gcc.gnu.org/viewcvs?view=rev&revision=124467
+//   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=31775
+//   https://gcc.gnu.org/viewcvs?view=rev&revision=124467
 static bool ParseLocalSourceName(State *state) {
-  State copy = *state;
-  if (ParseChar(state, 'L') && ParseSourceName(state) &&
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'L') && ParseSourceName(state) &&
       Optional(ParseDiscriminator(state))) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
+  return false;
+}
+
+// <unnamed-type-name> ::= Ut [<(nonnegative) number>] _
+//                     ::= <closure-type-name>
+// <closure-type-name> ::= Ul <lambda-sig> E [<(nonnegative) number>] _
+// <lambda-sig>        ::= <(parameter) type>+
+static bool ParseUnnamedTypeName(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  // Type's 1-based index n is encoded as { "", n == 1; itoa(n-2), otherwise }.
+  // Optionally parse the encoded value into 'which' and add 2 to get the index.
+  int which = -1;
+
+  // Unnamed type local to function or class.
+  if (ParseTwoCharToken(state, "Ut") && Optional(ParseNumber(state, &which)) &&
+      which <= std::numeric_limits<int>::max() - 2 &&  // Don't overflow.
+      ParseOneCharToken(state, '_')) {
+    MaybeAppend(state, "{unnamed type#");
+    MaybeAppendDecimal(state, 2 + which);
+    MaybeAppend(state, "}");
+    return true;
+  }
+  state->parse_state = copy;
+
+  // Closure type.
+  which = -1;
+  if (ParseTwoCharToken(state, "Ul") && DisableAppend(state) &&
+      OneOrMore(ParseType, state) && RestoreAppend(state, copy.append) &&
+      ParseOneCharToken(state, 'E') && Optional(ParseNumber(state, &which)) &&
+      which <= std::numeric_limits<int>::max() - 2 &&  // Don't overflow.
+      ParseOneCharToken(state, '_')) {
+    MaybeAppend(state, "{lambda()#");
+    MaybeAppendDecimal(state, 2 + which);
+    MaybeAppend(state, "}");
+    return true;
+  }
+  state->parse_state = copy;
+
   return false;
 }
 
 // <number> ::= [n] <non-negative decimal integer>
-static bool ParseNumber(State* state) {
-  int sign = 1;
-  if (ParseChar(state, 'n')) {
-    sign = -1;
+// If "number_out" is non-null, then *number_out is set to the value of the
+// parsed number on success.
+static bool ParseNumber(State *state, int *number_out) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
   }
-  const char *p = state->mangled_cur;
-  int number = 0;
-  for (; p < state->mangled_end; ++p) {
-    if ((*p >= '0' && *p <= '9')) {
-      number = number * 10 + (*p - '0');
+  bool negative = false;
+  if (ParseOneCharToken(state, 'n')) {
+    negative = true;
+  }
+  const char* p = RemainingInput(state);
+  uint64_t number = 0;
+  for (; *p != '\0'; ++p) {
+    if (IsDigit(*p)) {
+      number = number * 10 + static_cast<uint64_t>(*p - '0');
     } else {
       break;
     }
   }
-  if (p != state->mangled_cur) {  // Conversion succeeded.
-    state->mangled_cur = p;
-    state->number = number * sign;
+  // Apply the sign with uint64_t arithmetic so overflows aren't UB.  Gives
+  // "incorrect" results for out-of-range inputs, but negative values only
+  // appear for literals, which aren't printed.
+  if (negative) {
+    number = ~number + 1;
+  }
+  if (p != RemainingInput(state)) {  // Conversion succeeded.
+    state->parse_state.mangled_idx += p - RemainingInput(state);
+    if (number_out != nullptr) {
+      // Note: possibly truncate "number".
+      *number_out = static_cast<int>(number);
+    }
     return true;
   }
   return false;
@@ -612,20 +950,18 @@
 // Floating-point literals are encoded using a fixed-length lowercase
 // hexadecimal string.
 static bool ParseFloatNumber(State *state) {
-  const char *p = state->mangled_cur;
-  int number = 0;
-  for (; p < state->mangled_end; ++p) {
-    if ((*p >= '0' && *p <= '9')) {
-      number = number * 16 + (*p - '0');
-    } else if (*p >= 'a' && *p <= 'f') {
-      number = number * 16 + (*p - 'a' + 10);
-    } else {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  const char* p = RemainingInput(state);
+  for (; *p != '\0'; ++p) {
+    if (!IsDigit(*p) && !(*p >= 'a' && *p <= 'f')) {
       break;
     }
   }
-  if (p != state->mangled_cur) {  // Conversion succeeded.
-    state->mangled_cur = p;
-    state->number = number;
+  if (p != RemainingInput(state)) {  // Conversion succeeded.
+    state->parse_state.mangled_idx += p - RemainingInput(state);
     return true;
   }
   return false;
@@ -634,80 +970,91 @@
 // The <seq-id> is a sequence number in base 36,
 // using digits and upper case letters
 static bool ParseSeqId(State *state) {
-  const char *p = state->mangled_cur;
-  int number = 0;
-  for (; p < state->mangled_end; ++p) {
-    if ((*p >= '0' && *p <= '9')) {
-      number = number * 36 + (*p - '0');
-    } else if (*p >= 'A' && *p <= 'Z') {
-      number = number * 36 + (*p - 'A' + 10);
-    } else {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  const char* p = RemainingInput(state);
+  for (; *p != '\0'; ++p) {
+    if (!IsDigit(*p) && !(*p >= 'A' && *p <= 'Z')) {
       break;
     }
   }
-  if (p != state->mangled_cur) {  // Conversion succeeded.
-    state->mangled_cur = p;
-    state->number = number;
+  if (p != RemainingInput(state)) {  // Conversion succeeded.
+    state->parse_state.mangled_idx += p - RemainingInput(state);
     return true;
   }
   return false;
 }
 
-// <identifier> ::= <unqualified source code identifier>
-static bool ParseIdentifier(State* state) {
-  if (state->number == -1 || RemainingLength(state) < state->number) {
+// <identifier> ::= <unqualified source code identifier> (of given length)
+static bool ParseIdentifier(State* state, size_t length) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
     return false;
   }
-  if (IdentifierIsAnonymousNamespace(state)) {
+  if (!AtLeastNumCharsRemaining(RemainingInput(state), length)) {
+    return false;
+  }
+  if (IdentifierIsAnonymousNamespace(state, length)) {
     MaybeAppend(state, "(anonymous namespace)");
   } else {
-    MaybeAppendWithLength(state, state->mangled_cur, state->number);
+    MaybeAppendWithLength(state, RemainingInput(state), length);
   }
-  state->mangled_cur += state->number;
-  state->number = -1;  // Reset the number.
+  state->parse_state.mangled_idx += length;
   return true;
 }
 
 // <operator-name> ::= nw, and other two letters cases
 //                 ::= cv <type>  # (cast)
 //                 ::= v  <digit> <source-name> # vendor extended operator
-static bool ParseOperatorName(State *state) {
-  if (RemainingLength(state) < 2) {
+static bool ParseOperatorName(State* state, int* arity) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (!AtLeastNumCharsRemaining(RemainingInput(state), 2)) {
     return false;
   }
   // First check with "cv" (cast) case.
-  State copy = *state;
-  if (ParseTwoChar(state, "cv") && MaybeAppend(state, "operator ") &&
+  ParseState copy = state->parse_state;
+  if (ParseTwoCharToken(state, "cv") && MaybeAppend(state, "operator ") &&
       EnterNestedName(state) && ParseType(state) &&
       LeaveNestedName(state, copy.nest_level)) {
+    if (arity != nullptr) {
+      *arity = 1;
+    }
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   // Then vendor extended operators.
-  if (ParseChar(state, 'v') && ParseCharClass(state, "0123456789") &&
+  if (ParseOneCharToken(state, 'v') && ParseDigit(state, arity) &&
       ParseSourceName(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   // Other operator names should start with a lower alphabet followed
   // by a lower/upper alphabet.
-  if (!(IsLower(state->mangled_cur[0]) &&
-        IsAlpha(state->mangled_cur[1]))) {
+  if (!(IsLower(RemainingInput(state)[0]) &&
+        IsAlpha(RemainingInput(state)[1]))) {
     return false;
   }
   // We may want to perform a binary search if we really need speed.
   const AbbrevPair *p;
-  for (p = kOperatorList; p->abbrev != NULL; ++p) {
-    if (state->mangled_cur[0] == p->abbrev[0] &&
-        state->mangled_cur[1] == p->abbrev[1]) {
+  for (p = kOperatorList; p->abbrev != nullptr; ++p) {
+    if (RemainingInput(state)[0] == p->abbrev[0] &&
+        RemainingInput(state)[1] == p->abbrev[1]) {
+      if (arity != nullptr) {
+        *arity = p->arity;
+      }
       MaybeAppend(state, "operator");
       if (IsLower(*p->real_name)) {  // new, delete, etc.
         MaybeAppend(state, " ");
       }
       MaybeAppend(state, p->real_name);
-      state->mangled_cur += 2;
+      state->parse_state.mangled_idx += 2;
       return true;
     }
   }
@@ -718,6 +1065,7 @@
 //                ::= TT <type>
 //                ::= TI <type>
 //                ::= TS <type>
+//                ::= TH <type>  # thread-local
 //                ::= Tc <call-offset> <call-offset> <(base) encoding>
 //                ::= GV <(object) name>
 //                ::= T <call-offset> <(base) encoding>
@@ -733,168 +1081,267 @@
 // Note: we don't care much about them since they don't appear in
 // stack traces.  The are special data.
 static bool ParseSpecialName(State *state) {
-  State copy = *state;
-  if (ParseChar(state, 'T') && ParseCharClass(state, "VTIS") &&
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "VTISH") &&
       ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseTwoChar(state, "Tc") && ParseCallOffset(state) &&
+  if (ParseTwoCharToken(state, "Tc") && ParseCallOffset(state) &&
       ParseCallOffset(state) && ParseEncoding(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseTwoChar(state, "GV") && ParseName(state)) {
+  if (ParseTwoCharToken(state, "GV") && ParseName(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseChar(state, 'T') && ParseCallOffset(state) && ParseEncoding(state)) {
+  if (ParseOneCharToken(state, 'T') && ParseCallOffset(state) &&
+      ParseEncoding(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   // G++ extensions
-  if (ParseTwoChar(state, "TC") && ParseType(state) && ParseNumber(state) &&
-      ParseChar(state, '_') && DisableAppend(state) && ParseType(state)) {
+  if (ParseTwoCharToken(state, "TC") && ParseType(state) &&
+      ParseNumber(state, nullptr) && ParseOneCharToken(state, '_') &&
+      DisableAppend(state) && ParseType(state)) {
     RestoreAppend(state, copy.append);
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseChar(state, 'T') && ParseCharClass(state, "FJ") &&
+  if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "FJ") &&
       ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseTwoChar(state, "GR") && ParseName(state)) {
+  if (ParseTwoCharToken(state, "GR") && ParseName(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseTwoChar(state, "GA") && ParseEncoding(state)) {
+  if (ParseTwoCharToken(state, "GA") && ParseEncoding(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseChar(state, 'T') && ParseCharClass(state, "hv") &&
+  if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "hv") &&
       ParseCallOffset(state) && ParseEncoding(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <call-offset> ::= h <nv-offset> _
 //               ::= v <v-offset> _
 static bool ParseCallOffset(State *state) {
-  State copy = *state;
-  if (ParseChar(state, 'h') && ParseNVOffset(state) && ParseChar(state, '_')) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'h') && ParseNVOffset(state) &&
+      ParseOneCharToken(state, '_')) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseChar(state, 'v') && ParseVOffset(state) && ParseChar(state, '_')) {
+  if (ParseOneCharToken(state, 'v') && ParseVOffset(state) &&
+      ParseOneCharToken(state, '_')) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   return false;
 }
 
 // <nv-offset> ::= <(offset) number>
 static bool ParseNVOffset(State *state) {
-  return ParseNumber(state);
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  return ParseNumber(state, nullptr);
 }
 
 // <v-offset>  ::= <(offset) number> _ <(virtual offset) number>
 static bool ParseVOffset(State *state) {
-  State copy = *state;
-  if (ParseNumber(state) && ParseChar(state, '_') && ParseNumber(state)) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseNumber(state, nullptr) && ParseOneCharToken(state, '_') &&
+      ParseNumber(state, nullptr)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
-// <ctor-dtor-name> ::= C1 | C2 | C3
+// <ctor-dtor-name> ::= C1 | C2 | C3 | CI1 <base-class-type> | CI2
+// <base-class-type>
 //                  ::= D0 | D1 | D2
+// # GCC extensions: "unified" constructor/destructor.  See
+// #
+// https://github.com/gcc-mirror/gcc/blob/7ad17b583c3643bd4557f29b8391ca7ef08391f5/gcc/cp/mangle.c#L1847
+//                  ::= C4 | D4
 static bool ParseCtorDtorName(State *state) {
-  State copy = *state;
-  if (ParseChar(state, 'C') && ParseCharClass(state, "123")) {
-    const char * const prev_name = state->prev_name;
-    const int prev_name_length = state->prev_name_length;
-    MaybeAppendWithLength(state, prev_name, prev_name_length);
-    return true;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
   }
-  *state = copy;
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'C')) {
+    if (ParseCharClass(state, "1234")) {
+      const char* const prev_name =
+          state->out + state->parse_state.prev_name_idx;
+      MaybeAppendWithLength(state, prev_name,
+                            state->parse_state.prev_name_length);
+      return true;
+    } else if (ParseOneCharToken(state, 'I') && ParseCharClass(state, "12") &&
+               ParseClassEnumType(state)) {
+      return true;
+    }
+  }
+  state->parse_state = copy;
 
-  if (ParseChar(state, 'D') && ParseCharClass(state, "012")) {
-    const char * const prev_name = state->prev_name;
-    const int prev_name_length = state->prev_name_length;
+  if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "0124")) {
+    const char* const prev_name = state->out + state->parse_state.prev_name_idx;
     MaybeAppend(state, "~");
-    MaybeAppendWithLength(state, prev_name, prev_name_length);
+    MaybeAppendWithLength(state, prev_name,
+                          state->parse_state.prev_name_length);
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
+  return false;
+}
+
+// <decltype> ::= Dt <expression> E  # decltype of an id-expression or class
+//                                   # member access (C++0x)
+//            ::= DT <expression> E  # decltype of an expression (C++0x)
+static bool ParseDecltype(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "tT") &&
+      ParseExpression(state) && ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
+
   return false;
 }
 
 // <type> ::= <CV-qualifiers> <type>
-//        ::= P <type>
-//        ::= R <type>
-//        ::= C <type>
-//        ::= G <type>
-//        ::= U <source-name> <type>
+//        ::= P <type>   # pointer-to
+//        ::= R <type>   # reference-to
+//        ::= O <type>   # rvalue reference-to (C++0x)
+//        ::= C <type>   # complex pair (C 2000)
+//        ::= G <type>   # imaginary (C 2000)
+//        ::= U <source-name> <type>  # vendor extended type qualifier
 //        ::= <builtin-type>
 //        ::= <function-type>
-//        ::= <class-enum-type>
+//        ::= <class-enum-type>  # note: just an alias for <name>
 //        ::= <array-type>
 //        ::= <pointer-to-member-type>
 //        ::= <template-template-param> <template-args>
 //        ::= <template-param>
+//        ::= <decltype>
 //        ::= <substitution>
+//        ::= Dp <type>          # pack expansion of (C++0x)
+//        ::= Dv <num-elems> _   # GNU vector extension
+//
 static bool ParseType(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+
   // We should check CV-qualifers, and PRGC things first.
-  State copy = *state;
-  if (ParseCVQualifiers(state) && ParseType(state)) {
+  //
+  // CV-qualifiers overlap with some operator names, but an operator name is not
+  // valid as a type.  To avoid an ambiguity that can lead to exponential time
+  // complexity, refuse to backtrack the CV-qualifiers.
+  //
+  // _Z4aoeuIrMvvE
+  //  => _Z 4aoeuI        rM  v     v   E
+  //         aoeu<operator%=, void, void>
+  //  => _Z 4aoeuI r Mv v              E
+  //         aoeu<void void::* restrict>
+  //
+  // By consuming the CV-qualifiers first, the former parse is disabled.
+  if (ParseCVQualifiers(state)) {
+    const bool result = ParseType(state);
+    if (!result) {
+      state->parse_state = copy;
+    }
+    return result;
+  }
+  state->parse_state = copy;
+
+  // Similarly, these tag characters can overlap with other <name>s resulting in
+  // two different parse prefixes that land on <template-args> in the same
+  // place, such as "C3r1xI...".  So, disable the "ctor-name = C3" parse by
+  // refusing to backtrack the tag characters.
+  if (ParseCharClass(state, "OPRCG")) {
+    const bool result = ParseType(state);
+    if (!result) {
+      state->parse_state = copy;
+    }
+    return result;
+  }
+  state->parse_state = copy;
+
+  if (ParseTwoCharToken(state, "Dp") && ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseCharClass(state, "PRCG") && ParseType(state)) {
+  if (ParseOneCharToken(state, 'U') && ParseSourceName(state) &&
+      ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseChar(state, 'U') && ParseSourceName(state) && ParseType(state)) {
-    return true;
-  }
-  *state = copy;
-
-  if (ParseBuiltinType(state) ||
-      ParseFunctionType(state) ||
-      ParseClassEnumType(state) ||
-      ParseArrayType(state) ||
-      ParsePointerToMemberType(state) ||
-      ParseSubstitution(state)) {
+  if (ParseBuiltinType(state) || ParseFunctionType(state) ||
+      ParseClassEnumType(state) || ParseArrayType(state) ||
+      ParsePointerToMemberType(state) || ParseDecltype(state) ||
+      // "std" on its own isn't a type.
+      ParseSubstitution(state, /*accept_std=*/false)) {
     return true;
   }
 
-  if (ParseTemplateTemplateParam(state) &&
-      ParseTemplateArgs(state)) {
+  if (ParseTemplateTemplateParam(state) && ParseTemplateArgs(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   // Less greedy than <template-template-param> <template-args>.
   if (ParseTemplateParam(state)) {
     return true;
   }
 
+  if (ParseTwoCharToken(state, "Dv") && ParseNumber(state, nullptr) &&
+      ParseOneCharToken(state, '_')) {
+    return true;
+  }
+  state->parse_state = copy;
+
   return false;
 }
 
@@ -902,200 +1349,540 @@
 // We don't allow empty <CV-qualifiers> to avoid infinite loop in
 // ParseType().
 static bool ParseCVQualifiers(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   int num_cv_qualifiers = 0;
-  num_cv_qualifiers += ParseChar(state, 'r');
-  num_cv_qualifiers += ParseChar(state, 'V');
-  num_cv_qualifiers += ParseChar(state, 'K');
+  num_cv_qualifiers += ParseOneCharToken(state, 'r');
+  num_cv_qualifiers += ParseOneCharToken(state, 'V');
+  num_cv_qualifiers += ParseOneCharToken(state, 'K');
   return num_cv_qualifiers > 0;
 }
 
-// <builtin-type> ::= v, etc.
+// <builtin-type> ::= v, etc.  # single-character builtin types
 //                ::= u <source-name>
+//                ::= Dd, etc.  # two-character builtin types
+//
+// Not supported:
+//                ::= DF <number> _ # _FloatN (N bits)
+//
 static bool ParseBuiltinType(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   const AbbrevPair *p;
-  for (p = kBuiltinTypeList; p->abbrev != NULL; ++p) {
-    if (state->mangled_cur[0] == p->abbrev[0]) {
+  for (p = kBuiltinTypeList; p->abbrev != nullptr; ++p) {
+    // Guaranteed only 1- or 2-character strings in kBuiltinTypeList.
+    if (p->abbrev[1] == '\0') {
+      if (ParseOneCharToken(state, p->abbrev[0])) {
+        MaybeAppend(state, p->real_name);
+        return true;
+      }
+    } else if (p->abbrev[2] == '\0' && ParseTwoCharToken(state, p->abbrev)) {
       MaybeAppend(state, p->real_name);
-      ++state->mangled_cur;
       return true;
     }
   }
 
-  State copy = *state;
-  if (ParseChar(state, 'u') && ParseSourceName(state)) {
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'u') && ParseSourceName(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
-// <function-type> ::= F [Y] <bare-function-type> E
-static bool ParseFunctionType(State *state) {
-  State copy = *state;
-  if (ParseChar(state, 'F') && Optional(ParseChar(state, 'Y')) &&
-      ParseBareFunctionType(state) && ParseChar(state, 'E')) {
+//  <exception-spec> ::= Do                # non-throwing
+//                                           exception-specification (e.g.,
+//                                           noexcept, throw())
+//                   ::= DO <expression> E # computed (instantiation-dependent)
+//                                           noexcept
+//                   ::= Dw <type>+ E      # dynamic exception specification
+//                                           with instantiation-dependent types
+static bool ParseExceptionSpec(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+
+  if (ParseTwoCharToken(state, "Do")) {
     return true;
   }
-  *state = copy;
+
+  ParseState copy = state->parse_state;
+  if (ParseTwoCharToken(state, "DO") && ParseExpression(state) &&
+      ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
+  if (ParseTwoCharToken(state, "Dw") && OneOrMore(ParseType, state) &&
+      ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  return false;
+}
+
+// <function-type> ::= [exception-spec] F [Y] <bare-function-type> [O] E
+static bool ParseFunctionType(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (Optional(ParseExceptionSpec(state)) && ParseOneCharToken(state, 'F') &&
+      Optional(ParseOneCharToken(state, 'Y')) && ParseBareFunctionType(state) &&
+      Optional(ParseOneCharToken(state, 'O')) &&
+      ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
   return false;
 }
 
 // <bare-function-type> ::= <(signature) type>+
 static bool ParseBareFunctionType(State *state) {
-  State copy = *state;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
   DisableAppend(state);
   if (OneOrMore(ParseType, state)) {
     RestoreAppend(state, copy.append);
     MaybeAppend(state, "()");
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <class-enum-type> ::= <name>
 static bool ParseClassEnumType(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   return ParseName(state);
 }
 
 // <array-type> ::= A <(positive dimension) number> _ <(element) type>
 //              ::= A [<(dimension) expression>] _ <(element) type>
 static bool ParseArrayType(State *state) {
-  State copy = *state;
-  if (ParseChar(state, 'A') && ParseNumber(state) && ParseChar(state, '_') &&
-      ParseType(state)) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'A') && ParseNumber(state, nullptr) &&
+      ParseOneCharToken(state, '_') && ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseChar(state, 'A') && Optional(ParseExpression(state)) &&
-      ParseChar(state, '_') && ParseType(state)) {
+  if (ParseOneCharToken(state, 'A') && Optional(ParseExpression(state)) &&
+      ParseOneCharToken(state, '_') && ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <pointer-to-member-type> ::= M <(class) type> <(member) type>
 static bool ParsePointerToMemberType(State *state) {
-  State copy = *state;
-  if (ParseChar(state, 'M') && ParseType(state) && ParseType(state)) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'M') && ParseType(state) && ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <template-param> ::= T_
 //                  ::= T <parameter-2 non-negative number> _
 static bool ParseTemplateParam(State *state) {
-  if (ParseTwoChar(state, "T_")) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (ParseTwoCharToken(state, "T_")) {
     MaybeAppend(state, "?");  // We don't support template substitutions.
     return true;
   }
 
-  State copy = *state;
-  if (ParseChar(state, 'T') && ParseNumber(state) && ParseChar(state, '_')) {
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'T') && ParseNumber(state, nullptr) &&
+      ParseOneCharToken(state, '_')) {
     MaybeAppend(state, "?");  // We don't support template substitutions.
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
-
 // <template-template-param> ::= <template-param>
 //                           ::= <substitution>
 static bool ParseTemplateTemplateParam(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   return (ParseTemplateParam(state) ||
-          ParseSubstitution(state));
+          // "std" on its own isn't a template.
+          ParseSubstitution(state, /*accept_std=*/false));
 }
 
 // <template-args> ::= I <template-arg>+ E
 static bool ParseTemplateArgs(State *state) {
-  State copy = *state;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
   DisableAppend(state);
-  if (ParseChar(state, 'I') && OneOrMore(ParseTemplateArg, state) &&
-      ParseChar(state, 'E')) {
+  if (ParseOneCharToken(state, 'I') && OneOrMore(ParseTemplateArg, state) &&
+      ParseOneCharToken(state, 'E')) {
     RestoreAppend(state, copy.append);
     MaybeAppend(state, "<>");
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <template-arg>  ::= <type>
 //                 ::= <expr-primary>
+//                 ::= J <template-arg>* E        # argument pack
 //                 ::= X <expression> E
 static bool ParseTemplateArg(State *state) {
-  if (ParseType(state) ||
-      ParseExprPrimary(state)) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'J') && ZeroOrMore(ParseTemplateArg, state) &&
+      ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  // There can be significant overlap between the following leading to
+  // exponential backtracking:
+  //
+  //   <expr-primary> ::= L <type> <expr-cast-value> E
+  //                 e.g. L 2xxIvE 1                 E
+  //   <type>         ==> <local-source-name> <template-args>
+  //                 e.g. L 2xx               IvE
+  //
+  // This means parsing an entire <type> twice, and <type> can contain
+  // <template-arg>, so this can generate exponential backtracking.  There is
+  // only overlap when the remaining input starts with "L <source-name>", so
+  // parse all cases that can start this way jointly to share the common prefix.
+  //
+  // We have:
+  //
+  //   <template-arg> ::= <type>
+  //                  ::= <expr-primary>
+  //
+  // First, drop all the productions of <type> that must start with something
+  // other than 'L'.  All that's left is <class-enum-type>; inline it.
+  //
+  //   <type> ::= <nested-name> # starts with 'N'
+  //          ::= <unscoped-name>
+  //          ::= <unscoped-template-name> <template-args>
+  //          ::= <local-name> # starts with 'Z'
+  //
+  // Drop and inline again:
+  //
+  //   <type> ::= <unscoped-name>
+  //          ::= <unscoped-name> <template-args>
+  //          ::= <substitution> <template-args> # starts with 'S'
+  //
+  // Merge the first two, inline <unscoped-name>, drop last:
+  //
+  //   <type> ::= <unqualified-name> [<template-args>]
+  //          ::= St <unqualified-name> [<template-args>] # starts with 'S'
+  //
+  // Drop and inline:
+  //
+  //   <type> ::= <operator-name> [<template-args>] # starts with lowercase
+  //          ::= <ctor-dtor-name> [<template-args>] # starts with 'C' or 'D'
+  //          ::= <source-name> [<template-args>] # starts with digit
+  //          ::= <local-source-name> [<template-args>]
+  //          ::= <unnamed-type-name> [<template-args>] # starts with 'U'
+  //
+  // One more time:
+  //
+  //   <type> ::= L <source-name> [<template-args>]
+  //
+  // Likewise with <expr-primary>:
+  //
+  //   <expr-primary> ::= L <type> <expr-cast-value> E
+  //                  ::= LZ <encoding> E # cannot overlap; drop
+  //                  ::= L <mangled_name> E # cannot overlap; drop
+  //
+  // By similar reasoning as shown above, the only <type>s starting with
+  // <source-name> are "<source-name> [<template-args>]".  Inline this.
+  //
+  //   <expr-primary> ::= L <source-name> [<template-args>] <expr-cast-value> E
+  //
+  // Now inline both of these into <template-arg>:
+  //
+  //   <template-arg> ::= L <source-name> [<template-args>]
+  //                  ::= L <source-name> [<template-args>] <expr-cast-value> E
+  //
+  // Merge them and we're done:
+  //   <template-arg>
+  //     ::= L <source-name> [<template-args>] [<expr-cast-value> E]
+  if (ParseLocalSourceName(state) && Optional(ParseTemplateArgs(state))) {
+    copy = state->parse_state;
+    if (ParseExprCastValue(state) && ParseOneCharToken(state, 'E')) {
+      return true;
+    }
+    state->parse_state = copy;
     return true;
   }
 
-  State copy = *state;
-  if (ParseChar(state, 'X') && ParseExpression(state) &&
-      ParseChar(state, 'E')) {
+  // Now that the overlapping cases can't reach this code, we can safely call
+  // both of these.
+  if (ParseType(state) || ParseExprPrimary(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
+
+  if (ParseOneCharToken(state, 'X') && ParseExpression(state) &&
+      ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
   return false;
 }
 
-// <expression> ::= <template-param>
-//              ::= <expr-primary>
-//              ::= <unary operator-name> <expression>
-//              ::= <binary operator-name> <expression> <expression>
-//              ::= <trinary operator-name> <expression> <expression>
-//                  <expression>
+// <unresolved-type> ::= <template-param> [<template-args>]
+//                   ::= <decltype>
+//                   ::= <substitution>
+static inline bool ParseUnresolvedType(State* state) {
+  // No ComplexityGuard because we don't copy the state in this stack frame.
+  return (ParseTemplateParam(state) && Optional(ParseTemplateArgs(state))) ||
+         ParseDecltype(state) || ParseSubstitution(state, /*accept_std=*/false);
+}
+
+// <simple-id> ::= <source-name> [<template-args>]
+static inline bool ParseSimpleId(State* state) {
+  // No ComplexityGuard because we don't copy the state in this stack frame.
+
+  // Note: <simple-id> cannot be followed by a parameter pack; see comment in
+  // ParseUnresolvedType.
+  return ParseSourceName(state) && Optional(ParseTemplateArgs(state));
+}
+
+// <base-unresolved-name> ::= <source-name> [<template-args>]
+//                        ::= on <operator-name> [<template-args>]
+//                        ::= dn <destructor-name>
+static bool ParseBaseUnresolvedName(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+
+  if (ParseSimpleId(state)) {
+    return true;
+  }
+
+  ParseState copy = state->parse_state;
+  if (ParseTwoCharToken(state, "on") && ParseOperatorName(state, nullptr) &&
+      Optional(ParseTemplateArgs(state))) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  if (ParseTwoCharToken(state, "dn") &&
+      (ParseUnresolvedType(state) || ParseSimpleId(state))) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  return false;
+}
+
+// <unresolved-name> ::= [gs] <base-unresolved-name>
+//                   ::= sr <unresolved-type> <base-unresolved-name>
+//                   ::= srN <unresolved-type> <unresolved-qualifier-level>+ E
+//                         <base-unresolved-name>
+//                   ::= [gs] sr <unresolved-qualifier-level>+ E
+//                         <base-unresolved-name>
+static bool ParseUnresolvedName(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+
+  ParseState copy = state->parse_state;
+  if (Optional(ParseTwoCharToken(state, "gs")) &&
+      ParseBaseUnresolvedName(state)) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  if (ParseTwoCharToken(state, "sr") && ParseUnresolvedType(state) &&
+      ParseBaseUnresolvedName(state)) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  if (ParseTwoCharToken(state, "sr") && ParseOneCharToken(state, 'N') &&
+      ParseUnresolvedType(state) &&
+      OneOrMore(/* <unresolved-qualifier-level> ::= */ ParseSimpleId, state) &&
+      ParseOneCharToken(state, 'E') && ParseBaseUnresolvedName(state)) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  if (Optional(ParseTwoCharToken(state, "gs")) &&
+      ParseTwoCharToken(state, "sr") &&
+      OneOrMore(/* <unresolved-qualifier-level> ::= */ ParseSimpleId, state) &&
+      ParseOneCharToken(state, 'E') && ParseBaseUnresolvedName(state)) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  return false;
+}
+
+// <expression> ::= <1-ary operator-name> <expression>
+//              ::= <2-ary operator-name> <expression> <expression>
+//              ::= <3-ary operator-name> <expression> <expression> <expression>
+//              ::= cl <expression>+ E
+//              ::= cp <simple-id> <expression>* E # Clang-specific.
+//              ::= cv <type> <expression>      # type (expression)
+//              ::= cv <type> _ <expression>* E # type (expr-list)
 //              ::= st <type>
+//              ::= <template-param>
+//              ::= <function-param>
+//              ::= <expr-primary>
+//              ::= dt <expression> <unresolved-name> # expr.name
+//              ::= pt <expression> <unresolved-name> # expr->name
+//              ::= sp <expression>         # argument pack expansion
 //              ::= sr <type> <unqualified-name> <template-args>
 //              ::= sr <type> <unqualified-name>
+// <function-param> ::= fp <(top-level) CV-qualifiers> _
+//                  ::= fp <(top-level) CV-qualifiers> <number> _
+//                  ::= fL <number> p <(top-level) CV-qualifiers> _
+//                  ::= fL <number> p <(top-level) CV-qualifiers> <number> _
 static bool ParseExpression(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   if (ParseTemplateParam(state) || ParseExprPrimary(state)) {
     return true;
   }
 
-  State copy = *state;
-  if (ParseOperatorName(state) &&
-      ParseExpression(state) &&
-      ParseExpression(state) &&
+  ParseState copy = state->parse_state;
+
+  // Object/function call expression.
+  if (ParseTwoCharToken(state, "cl") && OneOrMore(ParseExpression, state) &&
+      ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  // Clang-specific "cp <simple-id> <expression>* E"
+  //   https://clang.llvm.org/doxygen/ItaniumMangle_8cpp_source.html#l04338
+  if (ParseTwoCharToken(state, "cp") && ParseSimpleId(state) &&
+      ZeroOrMore(ParseExpression, state) && ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  // Function-param expression (level 0).
+  if (ParseTwoCharToken(state, "fp") && Optional(ParseCVQualifiers(state)) &&
+      Optional(ParseNumber(state, nullptr)) && ParseOneCharToken(state, '_')) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  // Function-param expression (level 1+).
+  if (ParseTwoCharToken(state, "fL") && Optional(ParseNumber(state, nullptr)) &&
+      ParseOneCharToken(state, 'p') && Optional(ParseCVQualifiers(state)) &&
+      Optional(ParseNumber(state, nullptr)) && ParseOneCharToken(state, '_')) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  // Parse the conversion expressions jointly to avoid re-parsing the <type> in
+  // their common prefix.  Parsed as:
+  // <expression> ::= cv <type> <conversion-args>
+  // <conversion-args> ::= _ <expression>* E
+  //                   ::= <expression>
+  //
+  // Also don't try ParseOperatorName after seeing "cv", since ParseOperatorName
+  // also needs to accept "cv <type>" in other contexts.
+  if (ParseTwoCharToken(state, "cv")) {
+    if (ParseType(state)) {
+      ParseState copy2 = state->parse_state;
+      if (ParseOneCharToken(state, '_') && ZeroOrMore(ParseExpression, state) &&
+          ParseOneCharToken(state, 'E')) {
+        return true;
+      }
+      state->parse_state = copy2;
+      if (ParseExpression(state)) {
+        return true;
+      }
+    }
+  } else {
+    // Parse unary, binary, and ternary operator expressions jointly, taking
+    // care not to re-parse subexpressions repeatedly. Parse like:
+    //   <expression> ::= <operator-name> <expression>
+    //                    [<one-to-two-expressions>]
+    //   <one-to-two-expressions> ::= <expression> [<expression>]
+    int arity = -1;
+    if (ParseOperatorName(state, &arity) &&
+        arity > 0 &&  // 0 arity => disabled.
+        (arity < 3 || ParseExpression(state)) &&
+        (arity < 2 || ParseExpression(state)) &&
+        (arity < 1 || ParseExpression(state))) {
+      return true;
+    }
+  }
+  state->parse_state = copy;
+
+  // sizeof type
+  if (ParseTwoCharToken(state, "st") && ParseType(state)) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  // Object and pointer member access expressions.
+  if ((ParseTwoCharToken(state, "dt") || ParseTwoCharToken(state, "pt")) &&
+      ParseExpression(state) && ParseType(state)) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  // Pointer-to-member access expressions.  This parses the same as a binary
+  // operator, but it's implemented separately because "ds" shouldn't be
+  // accepted in other contexts that parse an operator name.
+  if (ParseTwoCharToken(state, "ds") && ParseExpression(state) &&
       ParseExpression(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseOperatorName(state) &&
-      ParseExpression(state) &&
-      ParseExpression(state)) {
+  // Parameter pack expansion
+  if (ParseTwoCharToken(state, "sp") && ParseExpression(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseOperatorName(state) &&
-      ParseExpression(state)) {
-    return true;
-  }
-  *state = copy;
-
-  if (ParseTwoChar(state, "st") && ParseType(state)) {
-    return true;
-  }
-  *state = copy;
-
-  if (ParseTwoChar(state, "sr") && ParseType(state) &&
-      ParseUnqualifiedName(state) && ParseTemplateArgs(state)) {
-    return true;
-  }
-  *state = copy;
-
-  if (ParseTwoChar(state, "sr") && ParseType(state) &&
-      ParseUnqualifiedName(state)) {
-    return true;
-  }
-  *state = copy;
-  return false;
+  return ParseUnresolvedName(state);
 }
 
 // <expr-primary> ::= L <type> <(value) number> E
@@ -1103,106 +1890,259 @@
 //                ::= L <mangled-name> E
 //                // A bug in g++'s C++ ABI version 2 (-fabi-version=2).
 //                ::= LZ <encoding> E
+//
+// Warning, subtle: the "bug" LZ production above is ambiguous with the first
+// production where <type> starts with <local-name>, which can lead to
+// exponential backtracking in two scenarios:
+//
+// - When whatever follows the E in the <local-name> in the first production is
+//   not a name, we backtrack the whole <encoding> and re-parse the whole thing.
+//
+// - When whatever follows the <local-name> in the first production is not a
+//   number and this <expr-primary> may be followed by a name, we backtrack the
+//   <name> and re-parse it.
+//
+// Moreover this ambiguity isn't always resolved -- for example, the following
+// has two different parses:
+//
+//   _ZaaILZ4aoeuE1x1EvE
+//   => operator&&<aoeu, x, E, void>
+//   => operator&&<(aoeu::x)(1), void>
+//
+// To resolve this, we just do what GCC's demangler does, and refuse to parse
+// casts to <local-name> types.
 static bool ParseExprPrimary(State *state) {
-  State copy = *state;
-  if (ParseChar(state, 'L') && ParseType(state) && ParseNumber(state) &&
-      ParseChar(state, 'E')) {
-    return true;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
   }
-  *state = copy;
+  ParseState copy = state->parse_state;
 
-  if (ParseChar(state, 'L') && ParseType(state) && ParseFloatNumber(state) &&
-      ParseChar(state, 'E')) {
-    return true;
-  }
-  *state = copy;
+  // The "LZ" special case: if we see LZ, we commit to accept "LZ <encoding> E"
+  // or fail, no backtracking.
+  if (ParseTwoCharToken(state, "LZ")) {
+    if (ParseEncoding(state) && ParseOneCharToken(state, 'E')) {
+      return true;
+    }
 
-  if (ParseChar(state, 'L') && ParseMangledName(state) &&
-      ParseChar(state, 'E')) {
-    return true;
+    state->parse_state = copy;
+    return false;
   }
-  *state = copy;
 
-  if (ParseTwoChar(state, "LZ") && ParseEncoding(state) &&
-      ParseChar(state, 'E')) {
+  // The merged cast production.
+  if (ParseOneCharToken(state, 'L') && ParseType(state) &&
+      ParseExprCastValue(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
+
+  if (ParseOneCharToken(state, 'L') && ParseMangledName(state) &&
+      ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
 
   return false;
 }
 
-// <local-name> := Z <(function) encoding> E <(entity) name>
-//                 [<discriminator>]
-//              := Z <(function) encoding> E s [<discriminator>]
-static bool ParseLocalName(State *state) {
-  State copy = *state;
-  if (ParseChar(state, 'Z') && ParseEncoding(state) && ParseChar(state, 'E') &&
-      MaybeAppend(state, "::") && ParseName(state) &&
+// <number> or <float>, followed by 'E', as described above ParseExprPrimary.
+static bool ParseExprCastValue(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  // We have to be able to backtrack after accepting a number because we could
+  // have e.g. "7fffE", which will accept "7" as a number but then fail to find
+  // the 'E'.
+  ParseState copy = state->parse_state;
+  if (ParseNumber(state, nullptr) && ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  if (ParseFloatNumber(state) && ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  return false;
+}
+
+// <local-name> ::= Z <(function) encoding> E <(entity) name> [<discriminator>]
+//              ::= Z <(function) encoding> E s [<discriminator>]
+//
+// Parsing a common prefix of these two productions together avoids an
+// exponential blowup of backtracking.  Parse like:
+//   <local-name> := Z <encoding> E <local-name-suffix>
+//   <local-name-suffix> ::= s [<discriminator>]
+//                       ::= <name> [<discriminator>]
+
+static bool ParseLocalNameSuffix(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+
+  if (MaybeAppend(state, "::") && ParseName(state) &&
       Optional(ParseDiscriminator(state))) {
     return true;
   }
-  *state = copy;
 
-  if (ParseChar(state, 'Z') && ParseEncoding(state) &&
-      ParseTwoChar(state, "Es") && Optional(ParseDiscriminator(state))) {
+  // Since we're not going to overwrite the above "::" by re-parsing the
+  // <encoding> (whose trailing '\0' byte was in the byte now holding the
+  // first ':'), we have to rollback the "::" if the <name> parse failed.
+  if (state->parse_state.append) {
+    state->out[state->parse_state.out_cur_idx - 2] = '\0';
+  }
+
+  return ParseOneCharToken(state, 's') && Optional(ParseDiscriminator(state));
+}
+
+static bool ParseLocalName(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'Z') && ParseEncoding(state) &&
+      ParseOneCharToken(state, 'E') && ParseLocalNameSuffix(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <discriminator> := _ <(non-negative) number>
 static bool ParseDiscriminator(State *state) {
-  State copy = *state;
-  if (ParseChar(state, '_') && ParseNumber(state)) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, '_') && ParseNumber(state, nullptr)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <substitution> ::= S_
 //                ::= S <seq-id> _
 //                ::= St, etc.
-static bool ParseSubstitution(State *state) {
-  if (ParseTwoChar(state, "S_")) {
+//
+// "St" is special in that it's not valid as a standalone name, and it *is*
+// allowed to precede a name without being wrapped in "N...E".  This means that
+// if we accept it on its own, we can accept "St1a" and try to parse
+// template-args, then fail and backtrack, accept "St" on its own, then "1a" as
+// an unqualified name and re-parse the same template-args.  To block this
+// exponential backtracking, we disable it with 'accept_std=false' in
+// problematic contexts.
+static bool ParseSubstitution(State* state, bool accept_std) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (ParseTwoCharToken(state, "S_")) {
     MaybeAppend(state, "?");  // We don't support substitutions.
     return true;
   }
 
-  State copy = *state;
-  if (ParseChar(state, 'S') && ParseSeqId(state) && ParseChar(state, '_')) {
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'S') && ParseSeqId(state) &&
+      ParseOneCharToken(state, '_')) {
     MaybeAppend(state, "?");  // We don't support substitutions.
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   // Expand abbreviations like "St" => "std".
-  if (ParseChar(state, 'S')) {
+  if (ParseOneCharToken(state, 'S')) {
     const AbbrevPair *p;
-    for (p = kSubstitutionList; p->abbrev != NULL; ++p) {
-      if (state->mangled_cur[0] == p->abbrev[1]) {
+    for (p = kSubstitutionList; p->abbrev != nullptr; ++p) {
+      if (RemainingInput(state)[0] == p->abbrev[1] &&
+          (accept_std || p->abbrev[1] != 't')) {
         MaybeAppend(state, "std");
         if (p->real_name[0] != '\0') {
           MaybeAppend(state, "::");
           MaybeAppend(state, p->real_name);
         }
-        state->mangled_cur += 1;
+        ++state->parse_state.mangled_idx;
         return true;
       }
     }
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
+// Parse <mangled-name>, optionally followed by either a function-clone suffix
+// or version suffix.  Returns true only if all of "mangled_cur" was consumed.
+static bool ParseTopLevelMangledName(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (ParseMangledName(state)) {
+    if (RemainingInput(state)[0] != '\0') {
+      // Drop trailing function clone suffix, if any.
+      if (IsFunctionCloneSuffix(RemainingInput(state))) {
+        return true;
+      }
+      // Append trailing version suffix if any.
+      // ex. _Z3foo@@GLIBCXX_3.4
+      if (RemainingInput(state)[0] == '@') {
+        MaybeAppend(state, RemainingInput(state));
+        return true;
+      }
+      return false;  // Unconsumed suffix.
+    }
+    return true;
+  }
+  return false;
+}
+
+static bool Overflowed(const State* state) {
+  return state->parse_state.out_cur_idx >= state->out_end_idx;
+}
+#endif
+
 // The demangler entry point.
-bool Demangle(const char *mangled, char *out, int out_size) {
+bool Demangle(const char* mangled, char* out, size_t out_size) {
+#if defined(GLOG_OS_WINDOWS)
+#if defined(HAVE_DBGHELP)
+  // When built with incremental linking, the Windows debugger
+  // library provides a more complicated `Symbol->Name` with the
+  // Incremental Linking Table offset, which looks like
+  // `@ILT+1105(?func@Foo@@SAXH@Z)`. However, the demangler expects
+  // only the mangled symbol, `?func@Foo@@SAXH@Z`. Fortunately, the
+  // mangled symbol is guaranteed not to have parentheses,
+  // so we search for `(` and extract up to `)`.
+  //
+  // Since we may be in a signal handler here, we cannot use `std::string`.
+  char buffer[1024];  // Big enough for a sane symbol.
+  const char *lparen = strchr(mangled, '(');
+  if (lparen) {
+    // Extract the string `(?...)`
+    const char *rparen = strchr(lparen, ')');
+    size_t length = static_cast<size_t>(rparen - lparen) - 1;
+    strncpy(buffer, lparen + 1, length);
+    buffer[length] = '\0';
+    mangled = buffer;
+  } // Else the symbol wasn't inside a set of parentheses
+  // We use the ANSI version to ensure the string type is always `char *`.
+  return UnDecorateSymbolName(mangled, out, out_size, UNDNAME_COMPLETE);
+#else
+  (void)mangled;
+  (void)out;
+  (void)out_size;
+  return false;
+#endif
+#else
   State state;
   InitState(&state, mangled, out, out_size);
-  return (ParseMangledName(&state) && state.overflowed == false &&
-          RemainingLength(&state) == 0);
+  return ParseTopLevelMangledName(&state) && !Overflowed(&state) &&
+         state.parse_state.out_cur_idx > 0;
+#endif
 }
 
 _END_GOOGLE_NAMESPACE_
diff --git a/base/third_party/symbolize/demangle.h b/base/third_party/symbolize/demangle.h
index 9c75915..620019f 100644
--- a/base/third_party/symbolize/demangle.h
+++ b/base/third_party/symbolize/demangle.h
@@ -70,14 +70,21 @@
 #ifndef BASE_DEMANGLE_H_
 #define BASE_DEMANGLE_H_
 
+#include <stddef.h>
+
 #include "config.h"
+#include "glog/logging.h"
 
 _START_GOOGLE_NAMESPACE_
 
+#if defined(STARBOARD)
+#define GLOG_EXPORT
+#endif
+
 // Demangle "mangled".  On success, return true and write the
 // demangled symbol name to "out".  Otherwise, return false.
 // "out" is modified even if demangling is unsuccessful.
-bool Demangle(const char *mangled, char *out, int out_size);
+bool GLOG_EXPORT Demangle(const char* mangled, char* out, size_t out_size);
 
 _END_GOOGLE_NAMESPACE_
 
diff --git a/base/third_party/symbolize/glog/logging.h b/base/third_party/symbolize/glog/logging.h
index a42c306..b935e3e 100644
--- a/base/third_party/symbolize/glog/logging.h
+++ b/base/third_party/symbolize/glog/logging.h
@@ -1,5 +1,41 @@
-// Copyright (c) 2010 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.
+// Copyright (c) 2022, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: Ray Sidney
+//
+// This file contains #include information about logging-related stuff.
+// Pretty much everybody needs to #include this file so that they can
+// log various happenings.
+//
+#ifndef GLOG_LOGGING_H
+#define GLOG_LOGGING_H
 
-// Empty.
+// Not needed in Chrome.
+
+#endif  // GLOG_LOGGING_H
diff --git a/base/third_party/symbolize/glog/raw_logging.h b/base/third_party/symbolize/glog/raw_logging.h
index f5515c4..74619b9 100644
--- a/base/third_party/symbolize/glog/raw_logging.h
+++ b/base/third_party/symbolize/glog/raw_logging.h
@@ -1,6 +1,41 @@
-// Copyright (c) 2010 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.
+// Copyright (c) 2006, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: Maxim Lifantsev
+//
+// Thread-safe logging routines that do not allocate any memory or
+// acquire any locks, and can therefore be used by low-level memory
+// allocation and synchronization code.
 
-#define WARNING 1;
-#define RAW_LOG(severity, ...);  // Do nothing.
+#ifndef GLOG_RAW_LOGGING_H
+#define GLOG_RAW_LOGGING_H
+
+#define RAW_LOG(...)  // Do nothing.
+
+#endif  // GLOG_RAW_LOGGING_H
diff --git a/base/third_party/symbolize/patches/001-fix-up-includes.patch b/base/third_party/symbolize/patches/001-fix-up-includes.patch
new file mode 100644
index 0000000..3c6b828
--- /dev/null
+++ b/base/third_party/symbolize/patches/001-fix-up-includes.patch
@@ -0,0 +1,56 @@
+diff --git a/base/third_party/symbolize/demangle.h b/base/third_party/symbolize/demangle.h
+index f347b98148fb1..416f7ee153560 100644
+--- a/base/third_party/symbolize/demangle.h
++++ b/base/third_party/symbolize/demangle.h
+@@ -71,7 +71,7 @@
+ #define BASE_DEMANGLE_H_
+ 
+ #include "config.h"
+-#include <glog/logging.h>
++#include "glog/logging.h"
+ 
+ _START_GOOGLE_NAMESPACE_
+ 
+diff --git a/base/third_party/symbolize/symbolize.cc b/base/third_party/symbolize/symbolize.cc
+index f56e97c99332a..2cfd4c490cc94 100644
+--- a/base/third_party/symbolize/symbolize.cc
++++ b/base/third_party/symbolize/symbolize.cc
+@@ -132,7 +132,7 @@ _END_GOOGLE_NAMESPACE_
+ 
+ #include "symbolize.h"
+ #include "config.h"
+-#include <glog/raw_logging.h>
++#include "glog/raw_logging.h"
+ 
+ // Re-runs fn until it doesn't cause EINTR.
+ #define NO_INTR(fn)   do {} while ((fn) < 0 && errno == EINTR)
+diff --git a/base/third_party/symbolize/symbolize.h b/base/third_party/symbolize/symbolize.h
+index dcbb194c4b37e..5959e579ffc93 100644
+--- a/base/third_party/symbolize/symbolize.h
++++ b/base/third_party/symbolize/symbolize.h
+@@ -56,7 +56,7 @@
+ 
+ #include "utilities.h"
+ #include "config.h"
+-#include <glog/logging.h>
++#include "glog/logging.h"
+ 
+ #ifdef HAVE_SYMBOLIZE
+ 
+diff --git a/base/third_party/symbolize/utilities.h b/base/third_party/symbolize/utilities.h
+index 760c142c09e18..efa3a8d99856e 100644
+--- a/base/third_party/symbolize/utilities.h
++++ b/base/third_party/symbolize/utilities.h
+@@ -52,11 +52,9 @@
+ #define PRIXS __PRIS_PREFIX "X"
+ #define PRIoS __PRIS_PREFIX "o"
+ 
+-#include "base/mutex.h"  // This must go first so we get _XOPEN_SOURCE
+-
+ #include <string>
+ 
+-#include <glog/logging.h>
++#include "glog/logging.h"
+ 
+ #if defined(GLOG_OS_WINDOWS)
+ # include "port.h"
diff --git a/base/third_party/symbolize/patches/002-minimal-logging.patch b/base/third_party/symbolize/patches/002-minimal-logging.patch
new file mode 100644
index 0000000..2c37a43
--- /dev/null
+++ b/base/third_party/symbolize/patches/002-minimal-logging.patch
@@ -0,0 +1,2135 @@
+diff --git a/base/third_party/symbolize/glog/logging.h b/base/third_party/symbolize/glog/logging.h
+index 3c9253d829d7f..46869226024da 100644
+--- a/base/third_party/symbolize/glog/logging.h
++++ b/base/third_party/symbolize/glog/logging.h
+@@ -36,1979 +36,6 @@
+ #ifndef GLOG_LOGGING_H
+ #define GLOG_LOGGING_H
+ 
+-#if 1 && __cplusplus >= 201103L
+-#include <chrono>
+-#endif
+-
+-#include <cerrno>
+-#include <cstddef>
+-#include <cstdlib>
+-#include <cstring>
+-#include <ctime>
+-#include <iosfwd>
+-#include <ostream>
+-#include <sstream>
+-#include <string>
+-#if 1
+-# include <unistd.h>
+-#endif
+-#include <vector>
+-
+-#if defined(_MSC_VER)
+-#define GLOG_MSVC_PUSH_DISABLE_WARNING(n) __pragma(warning(push)) \
+-                                     __pragma(warning(disable:n))
+-#define GLOG_MSVC_POP_WARNING() __pragma(warning(pop))
+-#else
+-#define GLOG_MSVC_PUSH_DISABLE_WARNING(n)
+-#define GLOG_MSVC_POP_WARNING()
+-#endif
+-
+-#include <glog/platform.h>
+-
+-#if 1
+-#include <glog/export.h>
+-#endif
+-
+-// We care a lot about number of bits things take up.  Unfortunately,
+-// systems define their bit-specific ints in a lot of different ways.
+-// We use our own way, and have a typedef to get there.
+-// Note: these commands below may look like "#if 1" or "#if 0", but
+-// that's because they were constructed that way at ./configure time.
+-// Look at logging.h.in to see how they're calculated (based on your config).
+-#if 1
+-#include <stdint.h>             // the normal place uint16_t is defined
+-#endif
+-#if 1
+-#include <sys/types.h>          // the normal place u_int16_t is defined
+-#endif
+-#if 1
+-#include <inttypes.h>           // a third place for uint16_t or u_int16_t
+-#endif
+-
+-#if 0
+-#include <gflags/gflags.h>
+-#endif
+-
+-#if 1 && __cplusplus >= 201103L
+-#include <atomic>
+-#elif defined(GLOG_OS_WINDOWS)
+-#include <Windows.h>
+-#endif
+-
+-namespace google {
+-
+-#if 1      // the C99 format
+-typedef int32_t int32;
+-typedef uint32_t uint32;
+-typedef int64_t int64;
+-typedef uint64_t uint64;
+-#elif 1   // the BSD format
+-typedef int32_t int32;
+-typedef u_int32_t uint32;
+-typedef int64_t int64;
+-typedef u_int64_t uint64;
+-#elif 0    // the windows (vc7) format
+-typedef __int32 int32;
+-typedef unsigned __int32 uint32;
+-typedef __int64 int64;
+-typedef unsigned __int64 uint64;
+-#else
+-#error Do not know how to define a 32-bit integer quantity on your system
+-#endif
+-
+-#if !(1)
+-typedef ptrdiff_t ssize_t;
+-#endif
+-
+-#if !(1)
+-typedef int mode_t;
+-#endif
+-
+-typedef double WallTime;
+-
+-struct GLOG_EXPORT LogMessageTime {
+-  LogMessageTime();
+-  LogMessageTime(std::tm t);
+-  LogMessageTime(std::time_t timestamp, WallTime now);
+-
+-  const time_t& timestamp() const { return timestamp_; }
+-  const int& sec() const { return time_struct_.tm_sec; }
+-  const int32_t& usec() const { return usecs_; }
+-  const int&(min)() const { return time_struct_.tm_min; }
+-  const int& hour() const { return time_struct_.tm_hour; }
+-  const int& day() const { return time_struct_.tm_mday; }
+-  const int& month() const { return time_struct_.tm_mon; }
+-  const int& year() const { return time_struct_.tm_year; }
+-  const int& dayOfWeek() const { return time_struct_.tm_wday; }
+-  const int& dayInYear() const { return time_struct_.tm_yday; }
+-  const int& dst() const { return time_struct_.tm_isdst; }
+-  const long int& gmtoff() const { return gmtoffset_; }
+-  const std::tm& tm() const { return time_struct_; }
+-
+- private:
+-  void init(const std::tm& t, std::time_t timestamp, WallTime now);
+-  std::tm time_struct_;  // Time of creation of LogMessage
+-  time_t timestamp_;     // Time of creation of LogMessage in seconds
+-  int32_t usecs_;        // Time of creation of LogMessage - microseconds part
+-  long int gmtoffset_;
+-
+-  void CalcGmtOffset();
+-};
+-
+-#ifdef GLOG_CUSTOM_PREFIX_SUPPORT
+-struct LogMessageInfo {
+-  explicit LogMessageInfo(const char* const severity_,
+-                          const char* const filename_,
+-                          const int& line_number_,
+-                          const int& thread_id_,
+-                          const LogMessageTime& time_):
+-      severity(severity_), filename(filename_), line_number(line_number_),
+-      thread_id(thread_id_), time(time_)
+-  {}
+-
+-  const char* const severity;
+-  const char* const filename;
+-  const int &line_number;
+-  const int &thread_id;
+-  const LogMessageTime& time;
+-};
+-
+-typedef void(*CustomPrefixCallback)(std::ostream& s, const LogMessageInfo& l, void* data);
+-
+-#endif
+-
+-}
+-
+-
+-// The global value of GOOGLE_STRIP_LOG. All the messages logged to
+-// LOG(XXX) with severity less than GOOGLE_STRIP_LOG will not be displayed.
+-// If it can be determined at compile time that the message will not be
+-// printed, the statement will be compiled out.
+-//
+-// Example: to strip out all INFO and WARNING messages, use the value
+-// of 2 below. To make an exception for WARNING messages from a single
+-// file, add "#define GOOGLE_STRIP_LOG 1" to that file _before_ including
+-// base/logging.h
+-#ifndef GOOGLE_STRIP_LOG
+-#define GOOGLE_STRIP_LOG 0
+-#endif
+-
+-// GCC can be told that a certain branch is not likely to be taken (for
+-// instance, a CHECK failure), and use that information in static analysis.
+-// Giving it this information can help it optimize for the common case in
+-// the absence of better information (ie. -fprofile-arcs).
+-//
+-#ifndef GOOGLE_PREDICT_BRANCH_NOT_TAKEN
+-#if 1
+-#define GOOGLE_PREDICT_BRANCH_NOT_TAKEN(x) (__builtin_expect(x, 0))
+-#else
+-#define GOOGLE_PREDICT_BRANCH_NOT_TAKEN(x) x
+-#endif
+-#endif
+-
+-#ifndef GOOGLE_PREDICT_FALSE
+-#if 1
+-#define GOOGLE_PREDICT_FALSE(x) (__builtin_expect(x, 0))
+-#else
+-#define GOOGLE_PREDICT_FALSE(x) x
+-#endif
+-#endif
+-
+-#ifndef GOOGLE_PREDICT_TRUE
+-#if 1
+-#define GOOGLE_PREDICT_TRUE(x) (__builtin_expect(!!(x), 1))
+-#else
+-#define GOOGLE_PREDICT_TRUE(x) x
+-#endif
+-#endif
+-
+-
+-// Make a bunch of macros for logging.  The way to log things is to stream
+-// things to LOG(<a particular severity level>).  E.g.,
+-//
+-//   LOG(INFO) << "Found " << num_cookies << " cookies";
+-//
+-// You can capture log messages in a string, rather than reporting them
+-// immediately:
+-//
+-//   vector<string> errors;
+-//   LOG_STRING(ERROR, &errors) << "Couldn't parse cookie #" << cookie_num;
+-//
+-// This pushes back the new error onto 'errors'; if given a NULL pointer,
+-// it reports the error via LOG(ERROR).
+-//
+-// You can also do conditional logging:
+-//
+-//   LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";
+-//
+-// You can also do occasional logging (log every n'th occurrence of an
+-// event):
+-//
+-//   LOG_EVERY_N(INFO, 10) << "Got the " << google::COUNTER << "th cookie";
+-//
+-// The above will cause log messages to be output on the 1st, 11th, 21st, ...
+-// times it is executed.  Note that the special google::COUNTER value is used
+-// to identify which repetition is happening.
+-//
+-// You can also do occasional conditional logging (log every n'th
+-// occurrence of an event, when condition is satisfied):
+-//
+-//   LOG_IF_EVERY_N(INFO, (size > 1024), 10) << "Got the " << google::COUNTER
+-//                                           << "th big cookie";
+-//
+-// You can log messages the first N times your code executes a line. E.g.
+-//
+-//   LOG_FIRST_N(INFO, 20) << "Got the " << google::COUNTER << "th cookie";
+-//
+-// Outputs log messages for the first 20 times it is executed.
+-//
+-// Analogous SYSLOG, SYSLOG_IF, and SYSLOG_EVERY_N macros are available.
+-// These log to syslog as well as to the normal logs.  If you use these at
+-// all, you need to be aware that syslog can drastically reduce performance,
+-// especially if it is configured for remote logging!  Don't use these
+-// unless you fully understand this and have a concrete need to use them.
+-// Even then, try to minimize your use of them.
+-//
+-// There are also "debug mode" logging macros like the ones above:
+-//
+-//   DLOG(INFO) << "Found cookies";
+-//
+-//   DLOG_IF(INFO, num_cookies > 10) << "Got lots of cookies";
+-//
+-//   DLOG_EVERY_N(INFO, 10) << "Got the " << google::COUNTER << "th cookie";
+-//
+-// All "debug mode" logging is compiled away to nothing for non-debug mode
+-// compiles.
+-//
+-// We also have
+-//
+-//   LOG_ASSERT(assertion);
+-//   DLOG_ASSERT(assertion);
+-//
+-// which is syntactic sugar for {,D}LOG_IF(FATAL, assert fails) << assertion;
+-//
+-// There are "verbose level" logging macros.  They look like
+-//
+-//   VLOG(1) << "I'm printed when you run the program with --v=1 or more";
+-//   VLOG(2) << "I'm printed when you run the program with --v=2 or more";
+-//
+-// These always log at the INFO log level (when they log at all).
+-// The verbose logging can also be turned on module-by-module.  For instance,
+-//    --vmodule=mapreduce=2,file=1,gfs*=3 --v=0
+-// will cause:
+-//   a. VLOG(2) and lower messages to be printed from mapreduce.{h,cc}
+-//   b. VLOG(1) and lower messages to be printed from file.{h,cc}
+-//   c. VLOG(3) and lower messages to be printed from files prefixed with "gfs"
+-//   d. VLOG(0) and lower messages to be printed from elsewhere
+-//
+-// The wildcarding functionality shown by (c) supports both '*' (match
+-// 0 or more characters) and '?' (match any single character) wildcards.
+-//
+-// There's also VLOG_IS_ON(n) "verbose level" condition macro. To be used as
+-//
+-//   if (VLOG_IS_ON(2)) {
+-//     // do some logging preparation and logging
+-//     // that can't be accomplished with just VLOG(2) << ...;
+-//   }
+-//
+-// There are also VLOG_IF, VLOG_EVERY_N and VLOG_IF_EVERY_N "verbose level"
+-// condition macros for sample cases, when some extra computation and
+-// preparation for logs is not needed.
+-//   VLOG_IF(1, (size > 1024))
+-//      << "I'm printed when size is more than 1024 and when you run the "
+-//         "program with --v=1 or more";
+-//   VLOG_EVERY_N(1, 10)
+-//      << "I'm printed every 10th occurrence, and when you run the program "
+-//         "with --v=1 or more. Present occurence is " << google::COUNTER;
+-//   VLOG_IF_EVERY_N(1, (size > 1024), 10)
+-//      << "I'm printed on every 10th occurence of case when size is more "
+-//         " than 1024, when you run the program with --v=1 or more. ";
+-//         "Present occurence is " << google::COUNTER;
+-//
+-// The supported severity levels for macros that allow you to specify one
+-// are (in increasing order of severity) INFO, WARNING, ERROR, and FATAL.
+-// Note that messages of a given severity are logged not only in the
+-// logfile for that severity, but also in all logfiles of lower severity.
+-// E.g., a message of severity FATAL will be logged to the logfiles of
+-// severity FATAL, ERROR, WARNING, and INFO.
+-//
+-// There is also the special severity of DFATAL, which logs FATAL in
+-// debug mode, ERROR in normal mode.
+-//
+-// Very important: logging a message at the FATAL severity level causes
+-// the program to terminate (after the message is logged).
+-//
+-// Unless otherwise specified, logs will be written to the filename
+-// "<program name>.<hostname>.<user name>.log.<severity level>.", followed
+-// by the date, time, and pid (you can't prevent the date, time, and pid
+-// from being in the filename).
+-//
+-// The logging code takes two flags:
+-//     --v=#           set the verbose level
+-//     --logtostderr   log all the messages to stderr instead of to logfiles
+-
+-// LOG LINE PREFIX FORMAT
+-//
+-// Log lines have this form:
+-//
+-//     Lyyyymmdd hh:mm:ss.uuuuuu threadid file:line] msg...
+-//
+-// where the fields are defined as follows:
+-//
+-//   L                A single character, representing the log level
+-//                    (eg 'I' for INFO)
+-//   yyyy             The year
+-//   mm               The month (zero padded; ie May is '05')
+-//   dd               The day (zero padded)
+-//   hh:mm:ss.uuuuuu  Time in hours, minutes and fractional seconds
+-//   threadid         The space-padded thread ID as returned by GetTID()
+-//                    (this matches the PID on Linux)
+-//   file             The file name
+-//   line             The line number
+-//   msg              The user-supplied message
+-//
+-// Example:
+-//
+-//   I1103 11:57:31.739339 24395 google.cc:2341] Command line: ./some_prog
+-//   I1103 11:57:31.739403 24395 google.cc:2342] Process id 24395
+-//
+-// NOTE: although the microseconds are useful for comparing events on
+-// a single machine, clocks on different machines may not be well
+-// synchronized.  Hence, use caution when comparing the low bits of
+-// timestamps from different machines.
+-
+-#pragma push_macro("DECLARE_VARIABLE")
+-#pragma push_macro("DECLARE_bool")
+-#pragma push_macro("DECLARE_string")
+-#pragma push_macro("DECLARE_int32")
+-#pragma push_macro("DECLARE_uint32")
+-
+-#ifdef DECLARE_VARIABLE
+-#undef DECLARE_VARIABLE
+-#endif
+-
+-#ifdef DECLARE_bool
+-#undef DECLARE_bool
+-#endif
+-
+-#ifdef DECLARE_string
+-#undef DECLARE_string
+-#endif
+-
+-#ifdef DECLARE_int32
+-#undef DECLARE_int32
+-#endif
+-
+-#ifdef DECLARE_uint32
+-#undef DECLARE_uint32
+-#endif
+-
+-#ifndef DECLARE_VARIABLE
+-#define DECLARE_VARIABLE(type, shorttype, name, tn)                     \
+-  namespace fL##shorttype {                                             \
+-    extern GLOG_EXPORT type FLAGS_##name;                      \
+-  }                                                                     \
+-  using fL##shorttype::FLAGS_##name
+-
+-// bool specialization
+-#define DECLARE_bool(name) \
+-  DECLARE_VARIABLE(bool, B, name, bool)
+-
+-// int32 specialization
+-#define DECLARE_int32(name) \
+-  DECLARE_VARIABLE(google::int32, I, name, int32)
+-
+-#if !defined(DECLARE_uint32)
+-// uint32 specialization
+-#define DECLARE_uint32(name) \
+-  DECLARE_VARIABLE(google::uint32, U, name, uint32)
+-#endif // !defined(DECLARE_uint32) && !(0)
+-
+-// Special case for string, because we have to specify the namespace
+-// std::string, which doesn't play nicely with our FLAG__namespace hackery.
+-#define DECLARE_string(name)                                            \
+-  namespace fLS {                                                       \
+-    extern GLOG_EXPORT std::string& FLAGS_##name;              \
+-  }                                                                     \
+-  using fLS::FLAGS_##name
+-#endif
+-
+-// Set whether appending a timestamp to the log file name
+-DECLARE_bool(timestamp_in_logfile_name);
+-
+-// Set whether log messages go to stdout instead of logfiles
+-DECLARE_bool(logtostdout);
+-
+-// Set color messages logged to stdout (if supported by terminal).
+-DECLARE_bool(colorlogtostdout);
+-
+-// Set whether log messages go to stderr instead of logfiles
+-DECLARE_bool(logtostderr);
+-
+-// Set whether log messages go to stderr in addition to logfiles.
+-DECLARE_bool(alsologtostderr);
+-
+-// Set color messages logged to stderr (if supported by terminal).
+-DECLARE_bool(colorlogtostderr);
+-
+-// Log messages at a level >= this flag are automatically sent to
+-// stderr in addition to log files.
+-DECLARE_int32(stderrthreshold);
+-
+-// Set whether the log file header should be written upon creating a file.
+-DECLARE_bool(log_file_header);
+-
+-// Set whether the log prefix should be prepended to each line of output.
+-DECLARE_bool(log_prefix);
+-
+-// Set whether the year should be included in the log prefix.
+-DECLARE_bool(log_year_in_prefix);
+-
+-// Log messages at a level <= this flag are buffered.
+-// Log messages at a higher level are flushed immediately.
+-DECLARE_int32(logbuflevel);
+-
+-// Sets the maximum number of seconds which logs may be buffered for.
+-DECLARE_int32(logbufsecs);
+-
+-// Log suppression level: messages logged at a lower level than this
+-// are suppressed.
+-DECLARE_int32(minloglevel);
+-
+-// If specified, logfiles are written into this directory instead of the
+-// default logging directory.
+-DECLARE_string(log_dir);
+-
+-// Set the log file mode.
+-DECLARE_int32(logfile_mode);
+-
+-// Sets the path of the directory into which to put additional links
+-// to the log files.
+-DECLARE_string(log_link);
+-
+-DECLARE_int32(v);  // in vlog_is_on.cc
+-
+-DECLARE_string(vmodule); // also in vlog_is_on.cc
+-
+-// Sets the maximum log file size (in MB).
+-DECLARE_uint32(max_log_size);
+-
+-// Sets whether to avoid logging to the disk if the disk is full.
+-DECLARE_bool(stop_logging_if_full_disk);
+-
+-// Use UTC time for logging
+-DECLARE_bool(log_utc_time);
+-
+-// Log messages below the GOOGLE_STRIP_LOG level will be compiled away for
+-// security reasons. See LOG(severtiy) below.
+-
+-// A few definitions of macros that don't generate much code.  Since
+-// LOG(INFO) and its ilk are used all over our code, it's
+-// better to have compact code for these operations.
+-
+-#if GOOGLE_STRIP_LOG == 0
+-#define COMPACT_GOOGLE_LOG_INFO google::LogMessage( \
+-      __FILE__, __LINE__)
+-#define LOG_TO_STRING_INFO(message) google::LogMessage( \
+-      __FILE__, __LINE__, google::GLOG_INFO, message)
+-#else
+-#define COMPACT_GOOGLE_LOG_INFO google::NullStream()
+-#define LOG_TO_STRING_INFO(message) google::NullStream()
+-#endif
+-
+-#if GOOGLE_STRIP_LOG <= 1
+-#define COMPACT_GOOGLE_LOG_WARNING google::LogMessage( \
+-      __FILE__, __LINE__, google::GLOG_WARNING)
+-#define LOG_TO_STRING_WARNING(message) google::LogMessage( \
+-      __FILE__, __LINE__, google::GLOG_WARNING, message)
+-#else
+-#define COMPACT_GOOGLE_LOG_WARNING google::NullStream()
+-#define LOG_TO_STRING_WARNING(message) google::NullStream()
+-#endif
+-
+-#if GOOGLE_STRIP_LOG <= 2
+-#define COMPACT_GOOGLE_LOG_ERROR google::LogMessage( \
+-      __FILE__, __LINE__, google::GLOG_ERROR)
+-#define LOG_TO_STRING_ERROR(message) google::LogMessage( \
+-      __FILE__, __LINE__, google::GLOG_ERROR, message)
+-#else
+-#define COMPACT_GOOGLE_LOG_ERROR google::NullStream()
+-#define LOG_TO_STRING_ERROR(message) google::NullStream()
+-#endif
+-
+-#if GOOGLE_STRIP_LOG <= 3
+-#define COMPACT_GOOGLE_LOG_FATAL google::LogMessageFatal( \
+-      __FILE__, __LINE__)
+-#define LOG_TO_STRING_FATAL(message) google::LogMessage( \
+-      __FILE__, __LINE__, google::GLOG_FATAL, message)
+-#else
+-#define COMPACT_GOOGLE_LOG_FATAL google::NullStreamFatal()
+-#define LOG_TO_STRING_FATAL(message) google::NullStreamFatal()
+-#endif
+-
+-#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
+-#define DCHECK_IS_ON() 0
+-#else
+-#define DCHECK_IS_ON() 1
+-#endif
+-
+-// For DFATAL, we want to use LogMessage (as opposed to
+-// LogMessageFatal), to be consistent with the original behavior.
+-#if !DCHECK_IS_ON()
+-#define COMPACT_GOOGLE_LOG_DFATAL COMPACT_GOOGLE_LOG_ERROR
+-#elif GOOGLE_STRIP_LOG <= 3
+-#define COMPACT_GOOGLE_LOG_DFATAL google::LogMessage( \
+-      __FILE__, __LINE__, google::GLOG_FATAL)
+-#else
+-#define COMPACT_GOOGLE_LOG_DFATAL google::NullStreamFatal()
+-#endif
+-
+-#define GOOGLE_LOG_INFO(counter) google::LogMessage(__FILE__, __LINE__, google::GLOG_INFO, counter, &google::LogMessage::SendToLog)
+-#define SYSLOG_INFO(counter) \
+-  google::LogMessage(__FILE__, __LINE__, google::GLOG_INFO, counter, \
+-  &google::LogMessage::SendToSyslogAndLog)
+-#define GOOGLE_LOG_WARNING(counter)  \
+-  google::LogMessage(__FILE__, __LINE__, google::GLOG_WARNING, counter, \
+-  &google::LogMessage::SendToLog)
+-#define SYSLOG_WARNING(counter)  \
+-  google::LogMessage(__FILE__, __LINE__, google::GLOG_WARNING, counter, \
+-  &google::LogMessage::SendToSyslogAndLog)
+-#define GOOGLE_LOG_ERROR(counter)  \
+-  google::LogMessage(__FILE__, __LINE__, google::GLOG_ERROR, counter, \
+-  &google::LogMessage::SendToLog)
+-#define SYSLOG_ERROR(counter)  \
+-  google::LogMessage(__FILE__, __LINE__, google::GLOG_ERROR, counter, \
+-  &google::LogMessage::SendToSyslogAndLog)
+-#define GOOGLE_LOG_FATAL(counter) \
+-  google::LogMessage(__FILE__, __LINE__, google::GLOG_FATAL, counter, \
+-  &google::LogMessage::SendToLog)
+-#define SYSLOG_FATAL(counter) \
+-  google::LogMessage(__FILE__, __LINE__, google::GLOG_FATAL, counter, \
+-  &google::LogMessage::SendToSyslogAndLog)
+-#define GOOGLE_LOG_DFATAL(counter) \
+-  google::LogMessage(__FILE__, __LINE__, google::DFATAL_LEVEL, counter, \
+-  &google::LogMessage::SendToLog)
+-#define SYSLOG_DFATAL(counter) \
+-  google::LogMessage(__FILE__, __LINE__, google::DFATAL_LEVEL, counter, \
+-  &google::LogMessage::SendToSyslogAndLog)
+-
+-#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__) || defined(__CYGWIN32__)
+-// A very useful logging macro to log windows errors:
+-#define LOG_SYSRESULT(result) \
+-  if (FAILED(HRESULT_FROM_WIN32(result))) { \
+-    LPSTR message = NULL; \
+-    LPSTR msg = reinterpret_cast<LPSTR>(&message); \
+-    DWORD message_length = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | \
+-                         FORMAT_MESSAGE_FROM_SYSTEM | \
+-                         FORMAT_MESSAGE_IGNORE_INSERTS, \
+-                         0, result, 0, msg, 100, NULL); \
+-    if (message_length > 0) { \
+-      google::LogMessage(__FILE__, __LINE__, google::GLOG_ERROR, 0, \
+-          &google::LogMessage::SendToLog).stream() \
+-          << reinterpret_cast<const char*>(message); \
+-      LocalFree(message); \
+-    } \
+-  }
+-#endif
+-
+-// We use the preprocessor's merging operator, "##", so that, e.g.,
+-// LOG(INFO) becomes the token GOOGLE_LOG_INFO.  There's some funny
+-// subtle difference between ostream member streaming functions (e.g.,
+-// ostream::operator<<(int) and ostream non-member streaming functions
+-// (e.g., ::operator<<(ostream&, string&): it turns out that it's
+-// impossible to stream something like a string directly to an unnamed
+-// ostream. We employ a neat hack by calling the stream() member
+-// function of LogMessage which seems to avoid the problem.
+-#define LOG(severity) COMPACT_GOOGLE_LOG_ ## severity.stream()
+-#define SYSLOG(severity) SYSLOG_ ## severity(0).stream()
+-
+-namespace google {
+-
+-// They need the definitions of integer types.
+-#include <glog/log_severity.h>
+-#include <glog/vlog_is_on.h>
+-
+-// Initialize google's logging library. You will see the program name
+-// specified by argv0 in log outputs.
+-GLOG_EXPORT void InitGoogleLogging(const char* argv0);
+-
+-#ifdef GLOG_CUSTOM_PREFIX_SUPPORT
+-GLOG_EXPORT void InitGoogleLogging(const char* argv0,
+-                                   CustomPrefixCallback prefix_callback,
+-                                   void* prefix_callback_data = NULL);
+-#endif
+-
+-// Check if google's logging library has been initialized.
+-GLOG_EXPORT bool IsGoogleLoggingInitialized();
+-
+-// Shutdown google's logging library.
+-GLOG_EXPORT void ShutdownGoogleLogging();
+-
+-#if defined(__GNUC__)
+-typedef void (*logging_fail_func_t)() __attribute__((noreturn));
+-#else
+-typedef void (*logging_fail_func_t)();
+-#endif
+-
+-// Install a function which will be called after LOG(FATAL).
+-GLOG_EXPORT void InstallFailureFunction(logging_fail_func_t fail_func);
+-
+-// Enable/Disable old log cleaner.
+-GLOG_EXPORT void EnableLogCleaner(unsigned int overdue_days);
+-GLOG_EXPORT void DisableLogCleaner();
+-GLOG_EXPORT void SetApplicationFingerprint(const std::string& fingerprint);
+-
+-class LogSink;  // defined below
+-
+-// If a non-NULL sink pointer is given, we push this message to that sink.
+-// For LOG_TO_SINK we then do normal LOG(severity) logging as well.
+-// This is useful for capturing messages and passing/storing them
+-// somewhere more specific than the global log of the process.
+-// Argument types:
+-//   LogSink* sink;
+-//   LogSeverity severity;
+-// The cast is to disambiguate NULL arguments.
+-#define LOG_TO_SINK(sink, severity) \
+-  google::LogMessage(                                    \
+-      __FILE__, __LINE__,                                               \
+-      google::GLOG_ ## severity,                         \
+-      static_cast<google::LogSink*>(sink), true).stream()
+-#define LOG_TO_SINK_BUT_NOT_TO_LOGFILE(sink, severity)                  \
+-  google::LogMessage(                                    \
+-      __FILE__, __LINE__,                                               \
+-      google::GLOG_ ## severity,                         \
+-      static_cast<google::LogSink*>(sink), false).stream()
+-
+-// If a non-NULL string pointer is given, we write this message to that string.
+-// We then do normal LOG(severity) logging as well.
+-// This is useful for capturing messages and storing them somewhere more
+-// specific than the global log of the process.
+-// Argument types:
+-//   string* message;
+-//   LogSeverity severity;
+-// The cast is to disambiguate NULL arguments.
+-// NOTE: LOG(severity) expands to LogMessage().stream() for the specified
+-// severity.
+-#define LOG_TO_STRING(severity, message) \
+-  LOG_TO_STRING_##severity(static_cast<std::string*>(message)).stream()
+-
+-// If a non-NULL pointer is given, we push the message onto the end
+-// of a vector of strings; otherwise, we report it with LOG(severity).
+-// This is handy for capturing messages and perhaps passing them back
+-// to the caller, rather than reporting them immediately.
+-// Argument types:
+-//   LogSeverity severity;
+-//   vector<string> *outvec;
+-// The cast is to disambiguate NULL arguments.
+-#define LOG_STRING(severity, outvec) \
+-  LOG_TO_STRING_##severity(static_cast<std::vector<std::string>*>(outvec)).stream()
+-
+-#define LOG_IF(severity, condition) \
+-  static_cast<void>(0),             \
+-  !(condition) ? (void) 0 : google::LogMessageVoidify() & LOG(severity)
+-#define SYSLOG_IF(severity, condition) \
+-  static_cast<void>(0),                \
+-  !(condition) ? (void) 0 : google::LogMessageVoidify() & SYSLOG(severity)
+-
+-#define LOG_ASSERT(condition)  \
+-  LOG_IF(FATAL, !(condition)) << "Assert failed: " #condition
+-#define SYSLOG_ASSERT(condition) \
+-  SYSLOG_IF(FATAL, !(condition)) << "Assert failed: " #condition
+-
+-// CHECK dies with a fatal error if condition is not true.  It is *not*
+-// controlled by DCHECK_IS_ON(), so the check will be executed regardless of
+-// compilation mode.  Therefore, it is safe to do things like:
+-//    CHECK(fp->Write(x) == 4)
+-#define CHECK(condition)  \
+-      LOG_IF(FATAL, GOOGLE_PREDICT_BRANCH_NOT_TAKEN(!(condition))) \
+-             << "Check failed: " #condition " "
+-
+-// A container for a string pointer which can be evaluated to a bool -
+-// true iff the pointer is NULL.
+-struct CheckOpString {
+-  CheckOpString(std::string* str) : str_(str) { }
+-  // No destructor: if str_ is non-NULL, we're about to LOG(FATAL),
+-  // so there's no point in cleaning up str_.
+-  operator bool() const {
+-    return GOOGLE_PREDICT_BRANCH_NOT_TAKEN(str_ != NULL);
+-  }
+-  std::string* str_;
+-};
+-
+-// Function is overloaded for integral types to allow static const
+-// integrals declared in classes and not defined to be used as arguments to
+-// CHECK* macros. It's not encouraged though.
+-template <class T>
+-inline const T&       GetReferenceableValue(const T&           t) { return t; }
+-inline char           GetReferenceableValue(char               t) { return t; }
+-inline unsigned char  GetReferenceableValue(unsigned char      t) { return t; }
+-inline signed char    GetReferenceableValue(signed char        t) { return t; }
+-inline short          GetReferenceableValue(short              t) { return t; }
+-inline unsigned short GetReferenceableValue(unsigned short     t) { return t; }
+-inline int            GetReferenceableValue(int                t) { return t; }
+-inline unsigned int   GetReferenceableValue(unsigned int       t) { return t; }
+-inline long           GetReferenceableValue(long               t) { return t; }
+-inline unsigned long  GetReferenceableValue(unsigned long      t) { return t; }
+-#if __cplusplus >= 201103L
+-inline long long      GetReferenceableValue(long long          t) { return t; }
+-inline unsigned long long GetReferenceableValue(unsigned long long t) {
+-  return t;
+-}
+-#endif
+-
+-// This is a dummy class to define the following operator.
+-struct DummyClassToDefineOperator {};
+-
+-}
+-
+-// Define global operator<< to declare using ::operator<<.
+-// This declaration will allow use to use CHECK macros for user
+-// defined classes which have operator<< (e.g., stl_logging.h).
+-inline std::ostream& operator<<(
+-    std::ostream& out, const google::DummyClassToDefineOperator&) {
+-  return out;
+-}
+-
+-namespace google {
+-
+-// This formats a value for a failing CHECK_XX statement.  Ordinarily,
+-// it uses the definition for operator<<, with a few special cases below.
+-template <typename T>
+-inline void MakeCheckOpValueString(std::ostream* os, const T& v) {
+-  (*os) << v;
+-}
+-
+-// Overrides for char types provide readable values for unprintable
+-// characters.
+-template <> GLOG_EXPORT
+-void MakeCheckOpValueString(std::ostream* os, const char& v);
+-template <> GLOG_EXPORT
+-void MakeCheckOpValueString(std::ostream* os, const signed char& v);
+-template <> GLOG_EXPORT
+-void MakeCheckOpValueString(std::ostream* os, const unsigned char& v);
+-
+-// This is required because nullptr is only present in c++ 11 and later.
+-#if 1 && __cplusplus >= 201103L
+-// Provide printable value for nullptr_t
+-template <> GLOG_EXPORT
+-void MakeCheckOpValueString(std::ostream* os, const std::nullptr_t& v);
+-#endif
+-
+-// Build the error message string. Specify no inlining for code size.
+-template <typename T1, typename T2>
+-std::string* MakeCheckOpString(const T1& v1, const T2& v2, const char* exprtext)
+-    __attribute__((noinline));
+-
+-namespace base {
+-namespace internal {
+-
+-// If "s" is less than base_logging::INFO, returns base_logging::INFO.
+-// If "s" is greater than base_logging::FATAL, returns
+-// base_logging::ERROR.  Otherwise, returns "s".
+-LogSeverity NormalizeSeverity(LogSeverity s);
+-
+-}  // namespace internal
+-
+-// A helper class for formatting "expr (V1 vs. V2)" in a CHECK_XX
+-// statement.  See MakeCheckOpString for sample usage.  Other
+-// approaches were considered: use of a template method (e.g.,
+-// base::BuildCheckOpString(exprtext, base::Print<T1>, &v1,
+-// base::Print<T2>, &v2), however this approach has complications
+-// related to volatile arguments and function-pointer arguments).
+-class GLOG_EXPORT CheckOpMessageBuilder {
+- public:
+-  // Inserts "exprtext" and " (" to the stream.
+-  explicit CheckOpMessageBuilder(const char *exprtext);
+-  // Deletes "stream_".
+-  ~CheckOpMessageBuilder();
+-  // For inserting the first variable.
+-  std::ostream* ForVar1() { return stream_; }
+-  // For inserting the second variable (adds an intermediate " vs. ").
+-  std::ostream* ForVar2();
+-  // Get the result (inserts the closing ")").
+-  std::string* NewString();
+-
+- private:
+-  std::ostringstream *stream_;
+-};
+-
+-}  // namespace base
+-
+-template <typename T1, typename T2>
+-std::string* MakeCheckOpString(const T1& v1, const T2& v2, const char* exprtext) {
+-  base::CheckOpMessageBuilder comb(exprtext);
+-  MakeCheckOpValueString(comb.ForVar1(), v1);
+-  MakeCheckOpValueString(comb.ForVar2(), v2);
+-  return comb.NewString();
+-}
+-
+-// Helper functions for CHECK_OP macro.
+-// The (int, int) specialization works around the issue that the compiler
+-// will not instantiate the template version of the function on values of
+-// unnamed enum type - see comment below.
+-#define DEFINE_CHECK_OP_IMPL(name, op) \
+-  template <typename T1, typename T2> \
+-  inline std::string* name##Impl(const T1& v1, const T2& v2,    \
+-                            const char* exprtext) { \
+-    if (GOOGLE_PREDICT_TRUE(v1 op v2)) return NULL; \
+-    else return MakeCheckOpString(v1, v2, exprtext); \
+-  } \
+-  inline std::string* name##Impl(int v1, int v2, const char* exprtext) { \
+-    return name##Impl<int, int>(v1, v2, exprtext); \
+-  }
+-
+-// We use the full name Check_EQ, Check_NE, etc. in case the file including
+-// base/logging.h provides its own #defines for the simpler names EQ, NE, etc.
+-// This happens if, for example, those are used as token names in a
+-// yacc grammar.
+-DEFINE_CHECK_OP_IMPL(Check_EQ, ==)  // Compilation error with CHECK_EQ(NULL, x)?
+-DEFINE_CHECK_OP_IMPL(Check_NE, !=)  // Use CHECK(x == NULL) instead.
+-DEFINE_CHECK_OP_IMPL(Check_LE, <=)
+-DEFINE_CHECK_OP_IMPL(Check_LT, < )
+-DEFINE_CHECK_OP_IMPL(Check_GE, >=)
+-DEFINE_CHECK_OP_IMPL(Check_GT, > )
+-#undef DEFINE_CHECK_OP_IMPL
+-
+-// Helper macro for binary operators.
+-// Don't use this macro directly in your code, use CHECK_EQ et al below.
+-
+-#if defined(STATIC_ANALYSIS)
+-// Only for static analysis tool to know that it is equivalent to assert
+-#define CHECK_OP_LOG(name, op, val1, val2, log) CHECK((val1) op (val2))
+-#elif DCHECK_IS_ON()
+-// In debug mode, avoid constructing CheckOpStrings if possible,
+-// to reduce the overhead of CHECK statments by 2x.
+-// Real DCHECK-heavy tests have seen 1.5x speedups.
+-
+-// The meaning of "string" might be different between now and
+-// when this macro gets invoked (e.g., if someone is experimenting
+-// with other string implementations that get defined after this
+-// file is included).  Save the current meaning now and use it
+-// in the macro.
+-typedef std::string _Check_string;
+-#define CHECK_OP_LOG(name, op, val1, val2, log)                         \
+-  while (google::_Check_string* _result =                \
+-         google::Check##name##Impl(                      \
+-             google::GetReferenceableValue(val1),        \
+-             google::GetReferenceableValue(val2),        \
+-             #val1 " " #op " " #val2))                                  \
+-    log(__FILE__, __LINE__,                                             \
+-        google::CheckOpString(_result)).stream()
+-#else
+-// In optimized mode, use CheckOpString to hint to compiler that
+-// the while condition is unlikely.
+-#define CHECK_OP_LOG(name, op, val1, val2, log)                         \
+-  while (google::CheckOpString _result =                 \
+-         google::Check##name##Impl(                      \
+-             google::GetReferenceableValue(val1),        \
+-             google::GetReferenceableValue(val2),        \
+-             #val1 " " #op " " #val2))                                  \
+-    log(__FILE__, __LINE__, _result).stream()
+-#endif  // STATIC_ANALYSIS, DCHECK_IS_ON()
+-
+-#if GOOGLE_STRIP_LOG <= 3
+-#define CHECK_OP(name, op, val1, val2) \
+-  CHECK_OP_LOG(name, op, val1, val2, google::LogMessageFatal)
+-#else
+-#define CHECK_OP(name, op, val1, val2) \
+-  CHECK_OP_LOG(name, op, val1, val2, google::NullStreamFatal)
+-#endif // STRIP_LOG <= 3
+-
+-// Equality/Inequality checks - compare two values, and log a FATAL message
+-// including the two values when the result is not as expected.  The values
+-// must have operator<<(ostream, ...) defined.
+-//
+-// You may append to the error message like so:
+-//   CHECK_NE(1, 2) << ": The world must be ending!";
+-//
+-// We are very careful to ensure that each argument is evaluated exactly
+-// once, and that anything which is legal to pass as a function argument is
+-// legal here.  In particular, the arguments may be temporary expressions
+-// which will end up being destroyed at the end of the apparent statement,
+-// for example:
+-//   CHECK_EQ(string("abc")[1], 'b');
+-//
+-// WARNING: These don't compile correctly if one of the arguments is a pointer
+-// and the other is NULL. To work around this, simply static_cast NULL to the
+-// type of the desired pointer.
+-
+-#define CHECK_EQ(val1, val2) CHECK_OP(_EQ, ==, val1, val2)
+-#define CHECK_NE(val1, val2) CHECK_OP(_NE, !=, val1, val2)
+-#define CHECK_LE(val1, val2) CHECK_OP(_LE, <=, val1, val2)
+-#define CHECK_LT(val1, val2) CHECK_OP(_LT, < , val1, val2)
+-#define CHECK_GE(val1, val2) CHECK_OP(_GE, >=, val1, val2)
+-#define CHECK_GT(val1, val2) CHECK_OP(_GT, > , val1, val2)
+-
+-// Check that the input is non NULL.  This very useful in constructor
+-// initializer lists.
+-
+-#define CHECK_NOTNULL(val) \
+-  google::CheckNotNull(__FILE__, __LINE__, "'" #val "' Must be non NULL", (val))
+-
+-// Helper functions for string comparisons.
+-// To avoid bloat, the definitions are in logging.cc.
+-#define DECLARE_CHECK_STROP_IMPL(func, expected) \
+-  GLOG_EXPORT std::string* Check##func##expected##Impl( \
+-      const char* s1, const char* s2, const char* names);
+-DECLARE_CHECK_STROP_IMPL(strcmp, true)
+-DECLARE_CHECK_STROP_IMPL(strcmp, false)
+-DECLARE_CHECK_STROP_IMPL(strcasecmp, true)
+-DECLARE_CHECK_STROP_IMPL(strcasecmp, false)
+-#undef DECLARE_CHECK_STROP_IMPL
+-
+-// Helper macro for string comparisons.
+-// Don't use this macro directly in your code, use CHECK_STREQ et al below.
+-#define CHECK_STROP(func, op, expected, s1, s2) \
+-  while (google::CheckOpString _result = \
+-         google::Check##func##expected##Impl((s1), (s2), \
+-                                     #s1 " " #op " " #s2)) \
+-    LOG(FATAL) << *_result.str_
+-
+-
+-// String (char*) equality/inequality checks.
+-// CASE versions are case-insensitive.
+-//
+-// Note that "s1" and "s2" may be temporary strings which are destroyed
+-// by the compiler at the end of the current "full expression"
+-// (e.g. CHECK_STREQ(Foo().c_str(), Bar().c_str())).
+-
+-#define CHECK_STREQ(s1, s2) CHECK_STROP(strcmp, ==, true, s1, s2)
+-#define CHECK_STRNE(s1, s2) CHECK_STROP(strcmp, !=, false, s1, s2)
+-#define CHECK_STRCASEEQ(s1, s2) CHECK_STROP(strcasecmp, ==, true, s1, s2)
+-#define CHECK_STRCASENE(s1, s2) CHECK_STROP(strcasecmp, !=, false, s1, s2)
+-
+-#define CHECK_INDEX(I,A) CHECK(I < (sizeof(A)/sizeof(A[0])))
+-#define CHECK_BOUND(B,A) CHECK(B <= (sizeof(A)/sizeof(A[0])))
+-
+-#define CHECK_DOUBLE_EQ(val1, val2)              \
+-  do {                                           \
+-    CHECK_LE((val1), (val2)+0.000000000000001L); \
+-    CHECK_GE((val1), (val2)-0.000000000000001L); \
+-  } while (0)
+-
+-#define CHECK_NEAR(val1, val2, margin)           \
+-  do {                                           \
+-    CHECK_LE((val1), (val2)+(margin));           \
+-    CHECK_GE((val1), (val2)-(margin));           \
+-  } while (0)
+-
+-// perror()..googly style!
+-//
+-// PLOG() and PLOG_IF() and PCHECK() behave exactly like their LOG* and
+-// CHECK equivalents with the addition that they postpend a description
+-// of the current state of errno to their output lines.
+-
+-#define PLOG(severity) GOOGLE_PLOG(severity, 0).stream()
+-
+-#define GOOGLE_PLOG(severity, counter)  \
+-  google::ErrnoLogMessage( \
+-      __FILE__, __LINE__, google::GLOG_ ## severity, counter, \
+-      &google::LogMessage::SendToLog)
+-
+-#define PLOG_IF(severity, condition) \
+-  static_cast<void>(0),              \
+-  !(condition) ? (void) 0 : google::LogMessageVoidify() & PLOG(severity)
+-
+-// A CHECK() macro that postpends errno if the condition is false. E.g.
+-//
+-// if (poll(fds, nfds, timeout) == -1) { PCHECK(errno == EINTR); ... }
+-#define PCHECK(condition)  \
+-      PLOG_IF(FATAL, GOOGLE_PREDICT_BRANCH_NOT_TAKEN(!(condition))) \
+-              << "Check failed: " #condition " "
+-
+-// A CHECK() macro that lets you assert the success of a function that
+-// returns -1 and sets errno in case of an error. E.g.
+-//
+-// CHECK_ERR(mkdir(path, 0700));
+-//
+-// or
+-//
+-// int fd = open(filename, flags); CHECK_ERR(fd) << ": open " << filename;
+-#define CHECK_ERR(invocation)                                          \
+-PLOG_IF(FATAL, GOOGLE_PREDICT_BRANCH_NOT_TAKEN((invocation) == -1))    \
+-        << #invocation
+-
+-// Use macro expansion to create, for each use of LOG_EVERY_N(), static
+-// variables with the __LINE__ expansion as part of the variable name.
+-#define LOG_EVERY_N_VARNAME(base, line) LOG_EVERY_N_VARNAME_CONCAT(base, line)
+-#define LOG_EVERY_N_VARNAME_CONCAT(base, line) base ## line
+-
+-#define LOG_OCCURRENCES LOG_EVERY_N_VARNAME(occurrences_, __LINE__)
+-#define LOG_OCCURRENCES_MOD_N LOG_EVERY_N_VARNAME(occurrences_mod_n_, __LINE__)
+-
+-#if 1 && __cplusplus >= 201103L
+-#define GLOG_CONSTEXPR constexpr
+-#else
+-#define GLOG_CONSTEXPR const
+-#endif
+-
+-#define LOG_TIME_PERIOD LOG_EVERY_N_VARNAME(timePeriod_, __LINE__)
+-#define LOG_PREVIOUS_TIME_RAW LOG_EVERY_N_VARNAME(previousTimeRaw_, __LINE__)
+-#define LOG_TIME_DELTA LOG_EVERY_N_VARNAME(deltaTime_, __LINE__)
+-#define LOG_CURRENT_TIME LOG_EVERY_N_VARNAME(currentTime_, __LINE__)
+-#define LOG_PREVIOUS_TIME LOG_EVERY_N_VARNAME(previousTime_, __LINE__)
+-
+-#if defined(__has_feature)
+-#  if __has_feature(thread_sanitizer)
+-#    define GLOG_SANITIZE_THREAD 1
+-#  endif
+-#endif
+-
+-#if !defined(GLOG_SANITIZE_THREAD) && defined(__SANITIZE_THREAD__) && __SANITIZE_THREAD__
+-#  define GLOG_SANITIZE_THREAD 1
+-#endif
+-
+-#if defined(GLOG_SANITIZE_THREAD)
+-#define GLOG_IFDEF_THREAD_SANITIZER(X) X
+-#else
+-#define GLOG_IFDEF_THREAD_SANITIZER(X)
+-#endif
+-
+-#if defined(GLOG_SANITIZE_THREAD)
+-} // namespace google
+-
+-// We need to identify the static variables as "benign" races
+-// to avoid noisy reports from TSAN.
+-extern "C" void AnnotateBenignRaceSized(
+-  const char *file,
+-  int line,
+-  const volatile void *mem,
+-  size_t size,
+-  const char *description);
+-
+-namespace google {
+-#endif
+-
+-#if __cplusplus >= 201103L && 1 && 1 // Have <chrono> and <atomic>
+-#define SOME_KIND_OF_LOG_EVERY_T(severity, seconds) \
+-  GLOG_CONSTEXPR std::chrono::nanoseconds LOG_TIME_PERIOD = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::duration<double>(seconds)); \
+-  static std::atomic<google::int64> LOG_PREVIOUS_TIME_RAW; \
+-  GLOG_IFDEF_THREAD_SANITIZER( \
+-          AnnotateBenignRaceSized(__FILE__, __LINE__, &LOG_TIME_PERIOD, sizeof(google::int64), "")); \
+-  GLOG_IFDEF_THREAD_SANITIZER( \
+-          AnnotateBenignRaceSized(__FILE__, __LINE__, &LOG_PREVIOUS_TIME_RAW, sizeof(google::int64), "")); \
+-  const auto LOG_CURRENT_TIME = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()); \
+-  const auto LOG_PREVIOUS_TIME = LOG_PREVIOUS_TIME_RAW.load(std::memory_order_relaxed); \
+-  const auto LOG_TIME_DELTA = LOG_CURRENT_TIME - std::chrono::nanoseconds(LOG_PREVIOUS_TIME); \
+-  if (LOG_TIME_DELTA > LOG_TIME_PERIOD) \
+-    LOG_PREVIOUS_TIME_RAW.store(std::chrono::duration_cast<std::chrono::nanoseconds>(LOG_CURRENT_TIME).count(), std::memory_order_relaxed); \
+-  if (LOG_TIME_DELTA > LOG_TIME_PERIOD) google::LogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity).stream()
+-#elif defined(GLOG_OS_WINDOWS)
+-#define SOME_KIND_OF_LOG_EVERY_T(severity, seconds) \
+-  GLOG_CONSTEXPR LONGLONG LOG_TIME_PERIOD = (seconds) * LONGLONG(1000000000); \
+-  static LARGE_INTEGER LOG_PREVIOUS_TIME; \
+-  LONGLONG LOG_TIME_DELTA; \
+-  { \
+-    LARGE_INTEGER currTime; \
+-    LARGE_INTEGER freq; \
+-    QueryPerformanceCounter(&currTime); \
+-    QueryPerformanceFrequency(&freq); \
+-    InterlockedCompareExchange64(&LOG_PREVIOUS_TIME.QuadPart, currTime.QuadPart, 0); \
+-    LOG_TIME_DELTA = (currTime.QuadPart - LOG_PREVIOUS_TIME.QuadPart) * LONGLONG(1000000000) / freq.QuadPart; \
+-    if (LOG_TIME_DELTA > LOG_TIME_PERIOD) InterlockedExchange64(&LOG_PREVIOUS_TIME.QuadPart, currTime.QuadPart); \
+-  } \
+-  if (LOG_TIME_DELTA > LOG_TIME_PERIOD) \
+-    google::LogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity).stream()
+-#else
+-#define SOME_KIND_OF_LOG_EVERY_T(severity, seconds) \
+-  GLOG_CONSTEXPR google::int64 LOG_TIME_PERIOD(seconds * 1000000000); \
+-  static google::int64 LOG_PREVIOUS_TIME; \
+-  google::int64 LOG_TIME_DELTA = 0; \
+-  { \
+-    timespec currentTime = {}; \
+-    clock_gettime(CLOCK_MONOTONIC, &currentTime); \
+-    LOG_TIME_DELTA = (currentTime.tv_sec * 1000000000 + currentTime.tv_nsec) - LOG_PREVIOUS_TIME; \
+-  } \
+-  if (LOG_TIME_DELTA > LOG_TIME_PERIOD) __sync_add_and_fetch(&LOG_PREVIOUS_TIME, LOG_TIME_DELTA); \
+-  if (LOG_TIME_DELTA > LOG_TIME_PERIOD) google::LogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity).stream()
+-#endif
+-
+-#if 1 && __cplusplus >= 201103L
+-#define SOME_KIND_OF_LOG_EVERY_N(severity, n, what_to_do) \
+-  static std::atomic<int> LOG_OCCURRENCES(0), LOG_OCCURRENCES_MOD_N(0); \
+-  GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized(__FILE__, __LINE__, &LOG_OCCURRENCES, sizeof(int), "")); \
+-  GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized(__FILE__, __LINE__, &LOG_OCCURRENCES_MOD_N, sizeof(int), "")); \
+-  ++LOG_OCCURRENCES; \
+-  if (++LOG_OCCURRENCES_MOD_N > n) LOG_OCCURRENCES_MOD_N -= n; \
+-  if (LOG_OCCURRENCES_MOD_N == 1) \
+-    google::LogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity, LOG_OCCURRENCES, \
+-        &what_to_do).stream()
+-
+-#define SOME_KIND_OF_LOG_IF_EVERY_N(severity, condition, n, what_to_do) \
+-  static std::atomic<int> LOG_OCCURRENCES(0), LOG_OCCURRENCES_MOD_N(0); \
+-  GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized(__FILE__, __LINE__, &LOG_OCCURRENCES, sizeof(int), "")); \
+-  GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized(__FILE__, __LINE__, &LOG_OCCURRENCES_MOD_N, sizeof(int), "")); \
+-  ++LOG_OCCURRENCES; \
+-  if ((condition) && \
+-      ((LOG_OCCURRENCES_MOD_N=(LOG_OCCURRENCES_MOD_N + 1) % n) == (1 % n))) \
+-    google::LogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity, LOG_OCCURRENCES, \
+-                 &what_to_do).stream()
+-
+-#define SOME_KIND_OF_PLOG_EVERY_N(severity, n, what_to_do) \
+-  static std::atomic<int> LOG_OCCURRENCES(0), LOG_OCCURRENCES_MOD_N(0); \
+-  GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized(__FILE__, __LINE__, &LOG_OCCURRENCES, sizeof(int), "")); \
+-  GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized(__FILE__, __LINE__, &LOG_OCCURRENCES_MOD_N, sizeof(int), "")); \
+-  ++LOG_OCCURRENCES; \
+-  if (++LOG_OCCURRENCES_MOD_N > n) LOG_OCCURRENCES_MOD_N -= n; \
+-  if (LOG_OCCURRENCES_MOD_N == 1) \
+-    google::ErrnoLogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity, LOG_OCCURRENCES, \
+-        &what_to_do).stream()
+-
+-#define SOME_KIND_OF_LOG_FIRST_N(severity, n, what_to_do) \
+-  static std::atomic<int> LOG_OCCURRENCES(0); \
+-  GLOG_IFDEF_THREAD_SANITIZER(AnnotateBenignRaceSized(__FILE__, __LINE__, &LOG_OCCURRENCES, sizeof(int), "")); \
+-  if (LOG_OCCURRENCES <= n) \
+-    ++LOG_OCCURRENCES; \
+-  if (LOG_OCCURRENCES <= n) \
+-    google::LogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity, LOG_OCCURRENCES, \
+-        &what_to_do).stream()
+-
+-#elif defined(GLOG_OS_WINDOWS)
+-
+-#define SOME_KIND_OF_LOG_EVERY_N(severity, n, what_to_do) \
+-  static volatile unsigned LOG_OCCURRENCES = 0; \
+-  static volatile unsigned LOG_OCCURRENCES_MOD_N = 0; \
+-  InterlockedIncrement(&LOG_OCCURRENCES); \
+-  if (InterlockedIncrement(&LOG_OCCURRENCES_MOD_N) > n) \
+-    InterlockedExchangeSubtract(&LOG_OCCURRENCES_MOD_N, n); \
+-  if (LOG_OCCURRENCES_MOD_N == 1) \
+-    google::LogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity, LOG_OCCURRENCES, \
+-        &what_to_do).stream()
+-
+-#define SOME_KIND_OF_LOG_IF_EVERY_N(severity, condition, n, what_to_do) \
+-  static volatile unsigned LOG_OCCURRENCES = 0; \
+-  static volatile unsigned LOG_OCCURRENCES_MOD_N = 0; \
+-  InterlockedIncrement(&LOG_OCCURRENCES); \
+-  if ((condition) && \
+-    ((InterlockedIncrement(&LOG_OCCURRENCES_MOD_N), \
+-     (LOG_OCCURRENCES_MOD_N > n && InterlockedExchangeSubtract(&LOG_OCCURRENCES_MOD_N, n))), \
+-     LOG_OCCURRENCES_MOD_N == 1)) \
+-    google::LogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity, LOG_OCCURRENCES, \
+-                 &what_to_do).stream()
+-
+-#define SOME_KIND_OF_PLOG_EVERY_N(severity, n, what_to_do) \
+-  static volatile unsigned LOG_OCCURRENCES = 0; \
+-  static volatile unsigned LOG_OCCURRENCES_MOD_N = 0; \
+-  InterlockedIncrement(&LOG_OCCURRENCES); \
+-  if (InterlockedIncrement(&LOG_OCCURRENCES_MOD_N) > n) \
+-    InterlockedExchangeSubtract(&LOG_OCCURRENCES_MOD_N, n); \
+-  if (LOG_OCCURRENCES_MOD_N == 1) \
+-    google::ErrnoLogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity, LOG_OCCURRENCES, \
+-        &what_to_do).stream()
+-
+-#define SOME_KIND_OF_LOG_FIRST_N(severity, n, what_to_do) \
+-  static volatile unsigned LOG_OCCURRENCES = 0; \
+-  if (LOG_OCCURRENCES <= n) \
+-    InterlockedIncrement(&LOG_OCCURRENCES); \
+-  if (LOG_OCCURRENCES <= n) \
+-    google::LogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity, LOG_OCCURRENCES, \
+-        &what_to_do).stream()
+-
+-#else
+-
+-#define SOME_KIND_OF_LOG_EVERY_N(severity, n, what_to_do) \
+-  static int LOG_OCCURRENCES = 0, LOG_OCCURRENCES_MOD_N = 0; \
+-  __sync_add_and_fetch(&LOG_OCCURRENCES, 1); \
+-  if (__sync_add_and_fetch(&LOG_OCCURRENCES_MOD_N, 1) > n) \
+-    __sync_sub_and_fetch(&LOG_OCCURRENCES_MOD_N, n); \
+-  if (LOG_OCCURRENCES_MOD_N == 1) \
+-    google::LogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity, LOG_OCCURRENCES, \
+-        &what_to_do).stream()
+-
+-#define SOME_KIND_OF_LOG_IF_EVERY_N(severity, condition, n, what_to_do) \
+-  static int LOG_OCCURRENCES = 0, LOG_OCCURRENCES_MOD_N = 0; \
+-  __sync_add_and_fetch(&LOG_OCCURRENCES, 1); \
+-  if ((condition) && \
+-      (__sync_add_and_fetch(&LOG_OCCURRENCES_MOD_N, 1) || true) && \
+-      ((LOG_OCCURRENCES_MOD_N >= n && __sync_sub_and_fetch(&LOG_OCCURRENCES_MOD_N, n)) || true) && \
+-      LOG_OCCURRENCES_MOD_N == (1 % n)) \
+-    google::LogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity, LOG_OCCURRENCES, \
+-                 &what_to_do).stream()
+-
+-#define SOME_KIND_OF_PLOG_EVERY_N(severity, n, what_to_do) \
+-  static int LOG_OCCURRENCES = 0, LOG_OCCURRENCES_MOD_N = 0; \
+-  __sync_add_and_fetch(&LOG_OCCURRENCES, 1); \
+-  if (__sync_add_and_fetch(&LOG_OCCURRENCES_MOD_N, 1) > n) \
+-    __sync_sub_and_fetch(&LOG_OCCURRENCES_MOD_N, n); \
+-  if (LOG_OCCURRENCES_MOD_N == 1) \
+-    google::ErrnoLogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity, LOG_OCCURRENCES, \
+-        &what_to_do).stream()
+-
+-#define SOME_KIND_OF_LOG_FIRST_N(severity, n, what_to_do) \
+-  static int LOG_OCCURRENCES = 0; \
+-  if (LOG_OCCURRENCES <= n) \
+-    __sync_add_and_fetch(&LOG_OCCURRENCES, 1); \
+-  if (LOG_OCCURRENCES <= n) \
+-    google::LogMessage( \
+-        __FILE__, __LINE__, google::GLOG_ ## severity, LOG_OCCURRENCES, \
+-        &what_to_do).stream()
+-#endif
+-
+-namespace glog_internal_namespace_ {
+-template <bool>
+-struct CompileAssert {
+-};
+-struct CrashReason;
+-
+-// Returns true if FailureSignalHandler is installed.
+-// Needs to be exported since it's used by the signalhandler_unittest.
+-GLOG_EXPORT bool IsFailureSignalHandlerInstalled();
+-}  // namespace glog_internal_namespace_
+-
+-#define LOG_EVERY_N(severity, n)                                        \
+-  SOME_KIND_OF_LOG_EVERY_N(severity, (n), google::LogMessage::SendToLog)
+-
+-#define LOG_EVERY_T(severity, T) SOME_KIND_OF_LOG_EVERY_T(severity, (T))
+-
+-#define SYSLOG_EVERY_N(severity, n) \
+-  SOME_KIND_OF_LOG_EVERY_N(severity, (n), google::LogMessage::SendToSyslogAndLog)
+-
+-#define PLOG_EVERY_N(severity, n) \
+-  SOME_KIND_OF_PLOG_EVERY_N(severity, (n), google::LogMessage::SendToLog)
+-
+-#define LOG_FIRST_N(severity, n) \
+-  SOME_KIND_OF_LOG_FIRST_N(severity, (n), google::LogMessage::SendToLog)
+-
+-#define LOG_IF_EVERY_N(severity, condition, n) \
+-  SOME_KIND_OF_LOG_IF_EVERY_N(severity, (condition), (n), google::LogMessage::SendToLog)
+-
+-// We want the special COUNTER value available for LOG_EVERY_X()'ed messages
+-enum PRIVATE_Counter {COUNTER};
+-
+-#ifdef GLOG_NO_ABBREVIATED_SEVERITIES
+-// wingdi.h defines ERROR to be 0. When we call LOG(ERROR), it gets
+-// substituted with 0, and it expands to COMPACT_GOOGLE_LOG_0. To allow us
+-// to keep using this syntax, we define this macro to do the same thing
+-// as COMPACT_GOOGLE_LOG_ERROR.
+-#define COMPACT_GOOGLE_LOG_0 COMPACT_GOOGLE_LOG_ERROR
+-#define SYSLOG_0 SYSLOG_ERROR
+-#define LOG_TO_STRING_0 LOG_TO_STRING_ERROR
+-// Needed for LOG_IS_ON(ERROR).
+-const LogSeverity GLOG_0 = GLOG_ERROR;
+-#else
+-// Users may include windows.h after logging.h without
+-// GLOG_NO_ABBREVIATED_SEVERITIES nor WIN32_LEAN_AND_MEAN.
+-// For this case, we cannot detect if ERROR is defined before users
+-// actually use ERROR. Let's make an undefined symbol to warn users.
+-# define GLOG_ERROR_MSG ERROR_macro_is_defined_Define_GLOG_NO_ABBREVIATED_SEVERITIES_before_including_logging_h_See_the_document_for_detail
+-# define COMPACT_GOOGLE_LOG_0 GLOG_ERROR_MSG
+-# define SYSLOG_0 GLOG_ERROR_MSG
+-# define LOG_TO_STRING_0 GLOG_ERROR_MSG
+-# define GLOG_0 GLOG_ERROR_MSG
+-#endif
+-
+-// Plus some debug-logging macros that get compiled to nothing for production
+-
+-#if DCHECK_IS_ON()
+-
+-#define DLOG(severity) LOG(severity)
+-#define DVLOG(verboselevel) VLOG(verboselevel)
+-#define DLOG_IF(severity, condition) LOG_IF(severity, condition)
+-#define DLOG_EVERY_N(severity, n) LOG_EVERY_N(severity, n)
+-#define DLOG_IF_EVERY_N(severity, condition, n) \
+-  LOG_IF_EVERY_N(severity, condition, n)
+-#define DLOG_ASSERT(condition) LOG_ASSERT(condition)
+-
+-// debug-only checking.  executed if DCHECK_IS_ON().
+-#define DCHECK(condition) CHECK(condition)
+-#define DCHECK_EQ(val1, val2) CHECK_EQ(val1, val2)
+-#define DCHECK_NE(val1, val2) CHECK_NE(val1, val2)
+-#define DCHECK_LE(val1, val2) CHECK_LE(val1, val2)
+-#define DCHECK_LT(val1, val2) CHECK_LT(val1, val2)
+-#define DCHECK_GE(val1, val2) CHECK_GE(val1, val2)
+-#define DCHECK_GT(val1, val2) CHECK_GT(val1, val2)
+-#define DCHECK_NOTNULL(val) CHECK_NOTNULL(val)
+-#define DCHECK_STREQ(str1, str2) CHECK_STREQ(str1, str2)
+-#define DCHECK_STRCASEEQ(str1, str2) CHECK_STRCASEEQ(str1, str2)
+-#define DCHECK_STRNE(str1, str2) CHECK_STRNE(str1, str2)
+-#define DCHECK_STRCASENE(str1, str2) CHECK_STRCASENE(str1, str2)
+-
+-#else  // !DCHECK_IS_ON()
+-
+-#define DLOG(severity)  \
+-  static_cast<void>(0), \
+-  true ? (void) 0 : google::LogMessageVoidify() & LOG(severity)
+-
+-#define DVLOG(verboselevel)             \
+-  static_cast<void>(0),                 \
+-  (true || !VLOG_IS_ON(verboselevel)) ? \
+-      (void) 0 : google::LogMessageVoidify() & LOG(INFO)
+-
+-#define DLOG_IF(severity, condition) \
+-  static_cast<void>(0),              \
+-  (true || !(condition)) ? (void) 0 : google::LogMessageVoidify() & LOG(severity)
+-
+-#define DLOG_EVERY_N(severity, n) \
+-  static_cast<void>(0),           \
+-  true ? (void) 0 : google::LogMessageVoidify() & LOG(severity)
+-
+-#define DLOG_IF_EVERY_N(severity, condition, n) \
+-  static_cast<void>(0),                         \
+-  (true || !(condition))? (void) 0 : google::LogMessageVoidify() & LOG(severity)
+-
+-#define DLOG_ASSERT(condition) \
+-  static_cast<void>(0),        \
+-  true ? (void) 0 : LOG_ASSERT(condition)
+-
+-// MSVC warning C4127: conditional expression is constant
+-#define DCHECK(condition) \
+-  GLOG_MSVC_PUSH_DISABLE_WARNING(4127) \
+-  while (false) \
+-    GLOG_MSVC_POP_WARNING() CHECK(condition)
+-
+-#define DCHECK_EQ(val1, val2) \
+-  GLOG_MSVC_PUSH_DISABLE_WARNING(4127) \
+-  while (false) \
+-    GLOG_MSVC_POP_WARNING() CHECK_EQ(val1, val2)
+-
+-#define DCHECK_NE(val1, val2) \
+-  GLOG_MSVC_PUSH_DISABLE_WARNING(4127) \
+-  while (false) \
+-    GLOG_MSVC_POP_WARNING() CHECK_NE(val1, val2)
+-
+-#define DCHECK_LE(val1, val2) \
+-  GLOG_MSVC_PUSH_DISABLE_WARNING(4127) \
+-  while (false) \
+-    GLOG_MSVC_POP_WARNING() CHECK_LE(val1, val2)
+-
+-#define DCHECK_LT(val1, val2) \
+-  GLOG_MSVC_PUSH_DISABLE_WARNING(4127) \
+-  while (false) \
+-    GLOG_MSVC_POP_WARNING() CHECK_LT(val1, val2)
+-
+-#define DCHECK_GE(val1, val2) \
+-  GLOG_MSVC_PUSH_DISABLE_WARNING(4127) \
+-  while (false) \
+-    GLOG_MSVC_POP_WARNING() CHECK_GE(val1, val2)
+-
+-#define DCHECK_GT(val1, val2) \
+-  GLOG_MSVC_PUSH_DISABLE_WARNING(4127) \
+-  while (false) \
+-    GLOG_MSVC_POP_WARNING() CHECK_GT(val1, val2)
+-
+-// You may see warnings in release mode if you don't use the return
+-// value of DCHECK_NOTNULL. Please just use DCHECK for such cases.
+-#define DCHECK_NOTNULL(val) (val)
+-
+-#define DCHECK_STREQ(str1, str2) \
+-  GLOG_MSVC_PUSH_DISABLE_WARNING(4127) \
+-  while (false) \
+-    GLOG_MSVC_POP_WARNING() CHECK_STREQ(str1, str2)
+-
+-#define DCHECK_STRCASEEQ(str1, str2) \
+-  GLOG_MSVC_PUSH_DISABLE_WARNING(4127) \
+-  while (false) \
+-    GLOG_MSVC_POP_WARNING() CHECK_STRCASEEQ(str1, str2)
+-
+-#define DCHECK_STRNE(str1, str2) \
+-  GLOG_MSVC_PUSH_DISABLE_WARNING(4127) \
+-  while (false) \
+-    GLOG_MSVC_POP_WARNING() CHECK_STRNE(str1, str2)
+-
+-#define DCHECK_STRCASENE(str1, str2) \
+-  GLOG_MSVC_PUSH_DISABLE_WARNING(4127) \
+-  while (false) \
+-    GLOG_MSVC_POP_WARNING() CHECK_STRCASENE(str1, str2)
+-
+-#endif  // DCHECK_IS_ON()
+-
+-// Log only in verbose mode.
+-
+-#define VLOG(verboselevel) LOG_IF(INFO, VLOG_IS_ON(verboselevel))
+-
+-#define VLOG_IF(verboselevel, condition) \
+-  LOG_IF(INFO, (condition) && VLOG_IS_ON(verboselevel))
+-
+-#define VLOG_EVERY_N(verboselevel, n) \
+-  LOG_IF_EVERY_N(INFO, VLOG_IS_ON(verboselevel), n)
+-
+-#define VLOG_IF_EVERY_N(verboselevel, condition, n) \
+-  LOG_IF_EVERY_N(INFO, (condition) && VLOG_IS_ON(verboselevel), n)
+-
+-namespace base_logging {
+-
+-// LogMessage::LogStream is a std::ostream backed by this streambuf.
+-// This class ignores overflow and leaves two bytes at the end of the
+-// buffer to allow for a '\n' and '\0'.
+-class GLOG_EXPORT LogStreamBuf : public std::streambuf {
+- public:
+-  // REQUIREMENTS: "len" must be >= 2 to account for the '\n' and '\0'.
+-  LogStreamBuf(char *buf, int len) {
+-    setp(buf, buf + len - 2);
+-  }
+-
+-  // This effectively ignores overflow.
+-  int_type overflow(int_type ch) {
+-    return ch;
+-  }
+-
+-  // Legacy public ostrstream method.
+-  size_t pcount() const { return static_cast<size_t>(pptr() - pbase()); }
+-  char* pbase() const { return std::streambuf::pbase(); }
+-};
+-
+-}  // namespace base_logging
+-
+-//
+-// This class more or less represents a particular log message.  You
+-// create an instance of LogMessage and then stream stuff to it.
+-// When you finish streaming to it, ~LogMessage is called and the
+-// full message gets streamed to the appropriate destination.
+-//
+-// You shouldn't actually use LogMessage's constructor to log things,
+-// though.  You should use the LOG() macro (and variants thereof)
+-// above.
+-class GLOG_EXPORT LogMessage {
+-public:
+-  enum {
+-    // Passing kNoLogPrefix for the line number disables the
+-    // log-message prefix. Useful for using the LogMessage
+-    // infrastructure as a printing utility. See also the --log_prefix
+-    // flag for controlling the log-message prefix on an
+-    // application-wide basis.
+-    kNoLogPrefix = -1
+-  };
+-
+-  // LogStream inherit from non-DLL-exported class (std::ostrstream)
+-  // and VC++ produces a warning for this situation.
+-  // However, MSDN says "C4275 can be ignored in Microsoft Visual C++
+-  // 2005 if you are deriving from a type in the Standard C++ Library"
+-  // http://msdn.microsoft.com/en-us/library/3tdb471s(VS.80).aspx
+-  // Let's just ignore the warning.
+-GLOG_MSVC_PUSH_DISABLE_WARNING(4275)
+-  class GLOG_EXPORT LogStream : public std::ostream {
+-GLOG_MSVC_POP_WARNING()
+-  public:
+-    LogStream(char *buf, int len, int64 ctr)
+-        : std::ostream(NULL),
+-          streambuf_(buf, len),
+-          ctr_(ctr),
+-          self_(this) {
+-      rdbuf(&streambuf_);
+-    }
+-
+-    int64 ctr() const { return ctr_; }
+-    void set_ctr(int64 ctr) { ctr_ = ctr; }
+-    LogStream* self() const { return self_; }
+-
+-    // Legacy std::streambuf methods.
+-    size_t pcount() const { return streambuf_.pcount(); }
+-    char* pbase() const { return streambuf_.pbase(); }
+-    char* str() const { return pbase(); }
+-
+-  private:
+-    LogStream(const LogStream&);
+-    LogStream& operator=(const LogStream&);
+-    base_logging::LogStreamBuf streambuf_;
+-    int64 ctr_;  // Counter hack (for the LOG_EVERY_X() macro)
+-    LogStream *self_;  // Consistency check hack
+-  };
+-
+-public:
+-  // icc 8 requires this typedef to avoid an internal compiler error.
+-  typedef void (LogMessage::*SendMethod)();
+-
+-  LogMessage(const char* file, int line, LogSeverity severity, int64 ctr,
+-             SendMethod send_method);
+-
+-  // Two special constructors that generate reduced amounts of code at
+-  // LOG call sites for common cases.
+-
+-  // Used for LOG(INFO): Implied are:
+-  // severity = INFO, ctr = 0, send_method = &LogMessage::SendToLog.
+-  //
+-  // Using this constructor instead of the more complex constructor above
+-  // saves 19 bytes per call site.
+-  LogMessage(const char* file, int line);
+-
+-  // Used for LOG(severity) where severity != INFO.  Implied
+-  // are: ctr = 0, send_method = &LogMessage::SendToLog
+-  //
+-  // Using this constructor instead of the more complex constructor above
+-  // saves 17 bytes per call site.
+-  LogMessage(const char* file, int line, LogSeverity severity);
+-
+-  // Constructor to log this message to a specified sink (if not NULL).
+-  // Implied are: ctr = 0, send_method = &LogMessage::SendToSinkAndLog if
+-  // also_send_to_log is true, send_method = &LogMessage::SendToSink otherwise.
+-  LogMessage(const char* file, int line, LogSeverity severity, LogSink* sink,
+-             bool also_send_to_log);
+-
+-  // Constructor where we also give a vector<string> pointer
+-  // for storing the messages (if the pointer is not NULL).
+-  // Implied are: ctr = 0, send_method = &LogMessage::SaveOrSendToLog.
+-  LogMessage(const char* file, int line, LogSeverity severity,
+-             std::vector<std::string>* outvec);
+-
+-  // Constructor where we also give a string pointer for storing the
+-  // message (if the pointer is not NULL).  Implied are: ctr = 0,
+-  // send_method = &LogMessage::WriteToStringAndLog.
+-  LogMessage(const char* file, int line, LogSeverity severity,
+-             std::string* message);
+-
+-  // A special constructor used for check failures
+-  LogMessage(const char* file, int line, const CheckOpString& result);
+-
+-  ~LogMessage();
+-
+-  // Flush a buffered message to the sink set in the constructor.  Always
+-  // called by the destructor, it may also be called from elsewhere if
+-  // needed.  Only the first call is actioned; any later ones are ignored.
+-  void Flush();
+-
+-  // An arbitrary limit on the length of a single log message.  This
+-  // is so that streaming can be done more efficiently.
+-  static const size_t kMaxLogMessageLen;
+-
+-  // Theses should not be called directly outside of logging.*,
+-  // only passed as SendMethod arguments to other LogMessage methods:
+-  void SendToLog();  // Actually dispatch to the logs
+-  void SendToSyslogAndLog();  // Actually dispatch to syslog and the logs
+-
+-  // Call abort() or similar to perform LOG(FATAL) crash.
+-  static void __attribute__((noreturn)) Fail();
+-
+-  std::ostream& stream();
+-
+-  int preserved_errno() const;
+-
+-  // Must be called without the log_mutex held.  (L < log_mutex)
+-  static int64 num_messages(int severity);
+-
+-  const LogMessageTime& getLogMessageTime() const;
+-
+-  struct LogMessageData;
+-
+-private:
+-  // Fully internal SendMethod cases:
+-  void SendToSinkAndLog();  // Send to sink if provided and dispatch to the logs
+-  void SendToSink();  // Send to sink if provided, do nothing otherwise.
+-
+-  // Write to string if provided and dispatch to the logs.
+-  void WriteToStringAndLog();
+-
+-  void SaveOrSendToLog();  // Save to stringvec if provided, else to logs
+-
+-  void Init(const char* file, int line, LogSeverity severity,
+-            void (LogMessage::*send_method)());
+-
+-  // Used to fill in crash information during LOG(FATAL) failures.
+-  void RecordCrashReason(glog_internal_namespace_::CrashReason* reason);
+-
+-  // Counts of messages sent at each priority:
+-  static int64 num_messages_[NUM_SEVERITIES];  // under log_mutex
+-
+-  // We keep the data in a separate struct so that each instance of
+-  // LogMessage uses less stack space.
+-  LogMessageData* allocated_;
+-  LogMessageData* data_;
+-  LogMessageTime logmsgtime_;
+-
+-  friend class LogDestination;
+-
+-  LogMessage(const LogMessage&);
+-  void operator=(const LogMessage&);
+-};
+-
+-// This class happens to be thread-hostile because all instances share
+-// a single data buffer, but since it can only be created just before
+-// the process dies, we don't worry so much.
+-class GLOG_EXPORT LogMessageFatal : public LogMessage {
+- public:
+-  LogMessageFatal(const char* file, int line);
+-  LogMessageFatal(const char* file, int line, const CheckOpString& result);
+-  __attribute__((noreturn)) ~LogMessageFatal();
+-};
+-
+-// A non-macro interface to the log facility; (useful
+-// when the logging level is not a compile-time constant).
+-inline void LogAtLevel(int const severity, std::string const &msg) {
+-  LogMessage(__FILE__, __LINE__, severity).stream() << msg;
+-}
+-
+-// A macro alternative of LogAtLevel. New code may want to use this
+-// version since there are two advantages: 1. this version outputs the
+-// file name and the line number where this macro is put like other
+-// LOG macros, 2. this macro can be used as C++ stream.
+-#define LOG_AT_LEVEL(severity) google::LogMessage(__FILE__, __LINE__, severity).stream()
+-
+-// Check if it's compiled in C++11 mode.
+-//
+-// GXX_EXPERIMENTAL_CXX0X is defined by gcc and clang up to at least
+-// gcc-4.7 and clang-3.1 (2011-12-13).  __cplusplus was defined to 1
+-// in gcc before 4.7 (Crosstool 16) and clang before 3.1, but is
+-// defined according to the language version in effect thereafter.
+-// Microsoft Visual Studio 14 (2015) sets __cplusplus==199711 despite
+-// reasonably good C++11 support, so we set LANG_CXX for it and
+-// newer versions (_MSC_VER >= 1900).
+-#if (defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L || \
+-     (defined(_MSC_VER) && _MSC_VER >= 1900)) && !defined(__UCLIBCXX_MAJOR__)
+-// Helper for CHECK_NOTNULL().
+-//
+-// In C++11, all cases can be handled by a single function. Since the value
+-// category of the argument is preserved (also for rvalue references),
+-// member initializer lists like the one below will compile correctly:
+-//
+-//   Foo()
+-//     : x_(CHECK_NOTNULL(MethodReturningUniquePtr())) {}
+-template <typename T>
+-T CheckNotNull(const char* file, int line, const char* names, T&& t) {
+- if (t == nullptr) {
+-   LogMessageFatal(file, line, new std::string(names));
+- }
+- return std::forward<T>(t);
+-}
+-
+-#else
+-
+-// A small helper for CHECK_NOTNULL().
+-template <typename T>
+-T* CheckNotNull(const char *file, int line, const char *names, T* t) {
+-  if (t == NULL) {
+-    LogMessageFatal(file, line, new std::string(names));
+-  }
+-  return t;
+-}
+-#endif
+-
+-// Allow folks to put a counter in the LOG_EVERY_X()'ed messages. This
+-// only works if ostream is a LogStream. If the ostream is not a
+-// LogStream you'll get an assert saying as much at runtime.
+-GLOG_EXPORT std::ostream& operator<<(std::ostream &os,
+-                                              const PRIVATE_Counter&);
+-
+-
+-// Derived class for PLOG*() above.
+-class GLOG_EXPORT ErrnoLogMessage : public LogMessage {
+- public:
+-  ErrnoLogMessage(const char* file, int line, LogSeverity severity, int64 ctr,
+-                  void (LogMessage::*send_method)());
+-
+-  // Postpends ": strerror(errno) [errno]".
+-  ~ErrnoLogMessage();
+-
+- private:
+-  ErrnoLogMessage(const ErrnoLogMessage&);
+-  void operator=(const ErrnoLogMessage&);
+-};
+-
+-
+-// This class is used to explicitly ignore values in the conditional
+-// logging macros.  This avoids compiler warnings like "value computed
+-// is not used" and "statement has no effect".
+-
+-class GLOG_EXPORT LogMessageVoidify {
+- public:
+-  LogMessageVoidify() { }
+-  // This has to be an operator with a precedence lower than << but
+-  // higher than ?:
+-  void operator&(std::ostream&) { }
+-};
+-
+-
+-// Flushes all log files that contains messages that are at least of
+-// the specified severity level.  Thread-safe.
+-GLOG_EXPORT void FlushLogFiles(LogSeverity min_severity);
+-
+-// Flushes all log files that contains messages that are at least of
+-// the specified severity level. Thread-hostile because it ignores
+-// locking -- used for catastrophic failures.
+-GLOG_EXPORT void FlushLogFilesUnsafe(LogSeverity min_severity);
+-
+-//
+-// Set the destination to which a particular severity level of log
+-// messages is sent.  If base_filename is "", it means "don't log this
+-// severity".  Thread-safe.
+-//
+-GLOG_EXPORT void SetLogDestination(LogSeverity severity,
+-                                            const char* base_filename);
+-
+-//
+-// Set the basename of the symlink to the latest log file at a given
+-// severity.  If symlink_basename is empty, do not make a symlink.  If
+-// you don't call this function, the symlink basename is the
+-// invocation name of the program.  Thread-safe.
+-//
+-GLOG_EXPORT void SetLogSymlink(LogSeverity severity,
+-                                        const char* symlink_basename);
+-
+-//
+-// Used to send logs to some other kind of destination
+-// Users should subclass LogSink and override send to do whatever they want.
+-// Implementations must be thread-safe because a shared instance will
+-// be called from whichever thread ran the LOG(XXX) line.
+-class GLOG_EXPORT LogSink {
+- public:
+-  virtual ~LogSink();
+-
+-  // Sink's logging logic (message_len is such as to exclude '\n' at the end).
+-  // This method can't use LOG() or CHECK() as logging system mutex(s) are held
+-  // during this call.
+-  virtual void send(LogSeverity severity, const char* full_filename,
+-                    const char* base_filename, int line,
+-                    const LogMessageTime& logmsgtime, const char* message,
+-                    size_t message_len);
+-  // Provide an overload for compatibility purposes
+-  GLOG_DEPRECATED
+-  virtual void send(LogSeverity severity, const char* full_filename,
+-                    const char* base_filename, int line, const std::tm* t,
+-                    const char* message, size_t message_len);
+-
+-  // Redefine this to implement waiting for
+-  // the sink's logging logic to complete.
+-  // It will be called after each send() returns,
+-  // but before that LogMessage exits or crashes.
+-  // By default this function does nothing.
+-  // Using this function one can implement complex logic for send()
+-  // that itself involves logging; and do all this w/o causing deadlocks and
+-  // inconsistent rearrangement of log messages.
+-  // E.g. if a LogSink has thread-specific actions, the send() method
+-  // can simply add the message to a queue and wake up another thread that
+-  // handles real logging while itself making some LOG() calls;
+-  // WaitTillSent() can be implemented to wait for that logic to complete.
+-  // See our unittest for an example.
+-  virtual void WaitTillSent();
+-
+-  // Returns the normal text output of the log message.
+-  // Can be useful to implement send().
+-  static std::string ToString(LogSeverity severity, const char* file, int line,
+-                              const LogMessageTime &logmsgtime,
+-                              const char* message, size_t message_len);
+-};
+-
+-// Add or remove a LogSink as a consumer of logging data.  Thread-safe.
+-GLOG_EXPORT void AddLogSink(LogSink *destination);
+-GLOG_EXPORT void RemoveLogSink(LogSink *destination);
+-
+-//
+-// Specify an "extension" added to the filename specified via
+-// SetLogDestination.  This applies to all severity levels.  It's
+-// often used to append the port we're listening on to the logfile
+-// name.  Thread-safe.
+-//
+-GLOG_EXPORT void SetLogFilenameExtension(
+-    const char* filename_extension);
+-
+-//
+-// Make it so that all log messages of at least a particular severity
+-// are logged to stderr (in addition to logging to the usual log
+-// file(s)).  Thread-safe.
+-//
+-GLOG_EXPORT void SetStderrLogging(LogSeverity min_severity);
+-
+-//
+-// Make it so that all log messages go only to stderr.  Thread-safe.
+-//
+-GLOG_EXPORT void LogToStderr();
+-
+-//
+-// Make it so that all log messages of at least a particular severity are
+-// logged via email to a list of addresses (in addition to logging to the
+-// usual log file(s)).  The list of addresses is just a string containing
+-// the email addresses to send to (separated by spaces, say).  Thread-safe.
+-//
+-GLOG_EXPORT void SetEmailLogging(LogSeverity min_severity,
+-                                          const char* addresses);
+-
+-// A simple function that sends email. dest is a commma-separated
+-// list of addressess.  Thread-safe.
+-GLOG_EXPORT bool SendEmail(const char* dest, const char* subject,
+-                           const char* body);
+-
+-GLOG_EXPORT const std::vector<std::string>& GetLoggingDirectories();
+-
+-// For tests only:  Clear the internal [cached] list of logging directories to
+-// force a refresh the next time GetLoggingDirectories is called.
+-// Thread-hostile.
+-void TestOnly_ClearLoggingDirectoriesList();
+-
+-// Returns a set of existing temporary directories, which will be a
+-// subset of the directories returned by GetLoggingDirectories().
+-// Thread-safe.
+-GLOG_EXPORT void GetExistingTempDirectories(
+-    std::vector<std::string>* list);
+-
+-// Print any fatal message again -- useful to call from signal handler
+-// so that the last thing in the output is the fatal message.
+-// Thread-hostile, but a race is unlikely.
+-GLOG_EXPORT void ReprintFatalMessage();
+-
+-// Truncate a log file that may be the append-only output of multiple
+-// processes and hence can't simply be renamed/reopened (typically a
+-// stdout/stderr).  If the file "path" is > "limit" bytes, copy the
+-// last "keep" bytes to offset 0 and truncate the rest. Since we could
+-// be racing with other writers, this approach has the potential to
+-// lose very small amounts of data. For security, only follow symlinks
+-// if the path is /proc/self/fd/*
+-GLOG_EXPORT void TruncateLogFile(const char* path, uint64 limit, uint64 keep);
+-
+-// Truncate stdout and stderr if they are over the value specified by
+-// --max_log_size; keep the final 1MB.  This function has the same
+-// race condition as TruncateLogFile.
+-GLOG_EXPORT void TruncateStdoutStderr();
+-
+-// Return the string representation of the provided LogSeverity level.
+-// Thread-safe.
+-GLOG_EXPORT const char* GetLogSeverityName(LogSeverity severity);
+-
+-// ---------------------------------------------------------------------
+-// Implementation details that are not useful to most clients
+-// ---------------------------------------------------------------------
+-
+-// A Logger is the interface used by logging modules to emit entries
+-// to a log.  A typical implementation will dump formatted data to a
+-// sequence of files.  We also provide interfaces that will forward
+-// the data to another thread so that the invoker never blocks.
+-// Implementations should be thread-safe since the logging system
+-// will write to them from multiple threads.
+-
+-namespace base {
+-
+-class GLOG_EXPORT Logger {
+- public:
+-  virtual ~Logger();
+-
+-  // Writes "message[0,message_len-1]" corresponding to an event that
+-  // occurred at "timestamp".  If "force_flush" is true, the log file
+-  // is flushed immediately.
+-  //
+-  // The input message has already been formatted as deemed
+-  // appropriate by the higher level logging facility.  For example,
+-  // textual log messages already contain timestamps, and the
+-  // file:linenumber header.
+-  virtual void Write(bool force_flush,
+-                     time_t timestamp,
+-                     const char* message,
+-                     size_t message_len) = 0;
+-
+-  // Flush any buffered messages
+-  virtual void Flush() = 0;
+-
+-  // Get the current LOG file size.
+-  // The returned value is approximate since some
+-  // logged data may not have been flushed to disk yet.
+-  virtual uint32 LogSize() = 0;
+-};
+-
+-// Get the logger for the specified severity level.  The logger
+-// remains the property of the logging module and should not be
+-// deleted by the caller.  Thread-safe.
+-extern GLOG_EXPORT Logger* GetLogger(LogSeverity level);
+-
+-// Set the logger for the specified severity level.  The logger
+-// becomes the property of the logging module and should not
+-// be deleted by the caller.  Thread-safe.
+-extern GLOG_EXPORT void SetLogger(LogSeverity level, Logger* logger);
+-
+-}
+-
+-// glibc has traditionally implemented two incompatible versions of
+-// strerror_r(). There is a poorly defined convention for picking the
+-// version that we want, but it is not clear whether it even works with
+-// all versions of glibc.
+-// So, instead, we provide this wrapper that automatically detects the
+-// version that is in use, and then implements POSIX semantics.
+-// N.B. In addition to what POSIX says, we also guarantee that "buf" will
+-// be set to an empty string, if this function failed. This means, in most
+-// cases, you do not need to check the error code and you can directly
+-// use the value of "buf". It will never have an undefined value.
+-// DEPRECATED: Use StrError(int) instead.
+-GLOG_EXPORT int posix_strerror_r(int err, char *buf, size_t len);
+-
+-// A thread-safe replacement for strerror(). Returns a string describing the
+-// given POSIX error code.
+-GLOG_EXPORT std::string StrError(int err);
+-
+-// A class for which we define operator<<, which does nothing.
+-class GLOG_EXPORT NullStream : public LogMessage::LogStream {
+- public:
+-  // Initialize the LogStream so the messages can be written somewhere
+-  // (they'll never be actually displayed). This will be needed if a
+-  // NullStream& is implicitly converted to LogStream&, in which case
+-  // the overloaded NullStream::operator<< will not be invoked.
+-  NullStream() : LogMessage::LogStream(message_buffer_, 1, 0) { }
+-  NullStream(const char* /*file*/, int /*line*/,
+-             const CheckOpString& /*result*/) :
+-      LogMessage::LogStream(message_buffer_, 1, 0) { }
+-  NullStream &stream() { return *this; }
+- private:
+-  // A very short buffer for messages (which we discard anyway). This
+-  // will be needed if NullStream& converted to LogStream& (e.g. as a
+-  // result of a conditional expression).
+-  char message_buffer_[2];
+-};
+-
+-// Do nothing. This operator is inline, allowing the message to be
+-// compiled away. The message will not be compiled away if we do
+-// something like (flag ? LOG(INFO) : LOG(ERROR)) << message; when
+-// SKIP_LOG=WARNING. In those cases, NullStream will be implicitly
+-// converted to LogStream and the message will be computed and then
+-// quietly discarded.
+-template<class T>
+-inline NullStream& operator<<(NullStream &str, const T &) { return str; }
+-
+-// Similar to NullStream, but aborts the program (without stack
+-// trace), like LogMessageFatal.
+-class GLOG_EXPORT NullStreamFatal : public NullStream {
+- public:
+-  NullStreamFatal() { }
+-  NullStreamFatal(const char* file, int line, const CheckOpString& result) :
+-      NullStream(file, line, result) { }
+-#if defined(_MSC_VER)
+-#pragma warning(push)
+-#pragma warning(disable : 4722)
+-#endif // _MSC_VER
+-  __attribute__((noreturn)) ~NullStreamFatal() throw () { _exit(EXIT_FAILURE); }
+-#if defined(_MSC_VER)
+-#pragma warning(pop)
+-#endif // _MSC_VER
+-};
+-
+-// Install a signal handler that will dump signal information and a stack
+-// trace when the program crashes on certain signals.  We'll install the
+-// signal handler for the following signals.
+-//
+-// SIGSEGV, SIGILL, SIGFPE, SIGABRT, SIGBUS, and SIGTERM.
+-//
+-// By default, the signal handler will write the failure dump to the
+-// standard error.  You can customize the destination by installing your
+-// own writer function by InstallFailureWriter() below.
+-//
+-// Note on threading:
+-//
+-// The function should be called before threads are created, if you want
+-// to use the failure signal handler for all threads.  The stack trace
+-// will be shown only for the thread that receives the signal.  In other
+-// words, stack traces of other threads won't be shown.
+-GLOG_EXPORT void InstallFailureSignalHandler();
+-
+-// Installs a function that is used for writing the failure dump.  "data"
+-// is the pointer to the beginning of a message to be written, and "size"
+-// is the size of the message.  You should not expect the data is
+-// terminated with '\0'.
+-GLOG_EXPORT void InstallFailureWriter(
+-    void (*writer)(const char* data, size_t size));
+-
+-}
+-
+-#pragma pop_macro("DECLARE_VARIABLE")
+-#pragma pop_macro("DECLARE_bool")
+-#pragma pop_macro("DECLARE_string")
+-#pragma pop_macro("DECLARE_int32")
+-#pragma pop_macro("DECLARE_uint32")
++// Not needed in Chrome.
+ 
+ #endif // GLOG_LOGGING_H
+diff --git a/base/third_party/symbolize/glog/raw_logging.h b/base/third_party/symbolize/glog/raw_logging.h
+index eda5fb456aee1..74619b9a0be7d 100644
+--- a/base/third_party/symbolize/glog/raw_logging.h
++++ b/base/third_party/symbolize/glog/raw_logging.h
+@@ -36,144 +36,6 @@
+ #ifndef GLOG_RAW_LOGGING_H
+ #define GLOG_RAW_LOGGING_H
+ 
+-#include <ctime>
+-
+-namespace google {
+-
+-#include <glog/log_severity.h>
+-#include <glog/logging.h>
+-#include <glog/vlog_is_on.h>
+-
+-#if defined(__GNUC__)
+-#pragma GCC diagnostic push
+-#pragma GCC diagnostic ignored "-Wvariadic-macros"
+-#endif
+-
+-// This is similar to LOG(severity) << format... and VLOG(level) << format..,
+-// but
+-// * it is to be used ONLY by low-level modules that can't use normal LOG()
+-// * it is desiged to be a low-level logger that does not allocate any
+-//   memory and does not need any locks, hence:
+-// * it logs straight and ONLY to STDERR w/o buffering
+-// * it uses an explicit format and arguments list
+-// * it will silently chop off really long message strings
+-// Usage example:
+-//   RAW_LOG(ERROR, "Failed foo with %i: %s", status, error);
+-//   RAW_VLOG(3, "status is %i", status);
+-// These will print an almost standard log lines like this to stderr only:
+-//   E20200821 211317 file.cc:123] RAW: Failed foo with 22: bad_file
+-//   I20200821 211317 file.cc:142] RAW: status is 20
+-#define RAW_LOG(severity, ...) \
+-  do { \
+-    switch (google::GLOG_ ## severity) {  \
+-      case 0: \
+-        RAW_LOG_INFO(__VA_ARGS__); \
+-        break; \
+-      case 1: \
+-        RAW_LOG_WARNING(__VA_ARGS__); \
+-        break; \
+-      case 2: \
+-        RAW_LOG_ERROR(__VA_ARGS__); \
+-        break; \
+-      case 3: \
+-        RAW_LOG_FATAL(__VA_ARGS__); \
+-        break; \
+-      default: \
+-        break; \
+-    } \
+-  } while (0)
+-
+-// The following STRIP_LOG testing is performed in the header file so that it's
+-// possible to completely compile out the logging code and the log messages.
+-#if !defined(STRIP_LOG) || STRIP_LOG == 0
+-#define RAW_VLOG(verboselevel, ...) \
+-  do { \
+-    if (VLOG_IS_ON(verboselevel)) { \
+-      RAW_LOG_INFO(__VA_ARGS__); \
+-    } \
+-  } while (0)
+-#else
+-#define RAW_VLOG(verboselevel, ...) RawLogStub__(0, __VA_ARGS__)
+-#endif // STRIP_LOG == 0
+-
+-#if !defined(STRIP_LOG) || STRIP_LOG == 0
+-#define RAW_LOG_INFO(...) google::RawLog__(google::GLOG_INFO, \
+-                                   __FILE__, __LINE__, __VA_ARGS__)
+-#else
+-#define RAW_LOG_INFO(...) google::RawLogStub__(0, __VA_ARGS__)
+-#endif // STRIP_LOG == 0
+-
+-#if !defined(STRIP_LOG) || STRIP_LOG <= 1
+-#define RAW_LOG_WARNING(...) google::RawLog__(google::GLOG_WARNING,   \
+-                                      __FILE__, __LINE__, __VA_ARGS__)
+-#else
+-#define RAW_LOG_WARNING(...) google::RawLogStub__(0, __VA_ARGS__)
+-#endif // STRIP_LOG <= 1
+-
+-#if !defined(STRIP_LOG) || STRIP_LOG <= 2
+-#define RAW_LOG_ERROR(...) google::RawLog__(google::GLOG_ERROR,       \
+-                                    __FILE__, __LINE__, __VA_ARGS__)
+-#else
+-#define RAW_LOG_ERROR(...) google::RawLogStub__(0, __VA_ARGS__)
+-#endif // STRIP_LOG <= 2
+-
+-#if !defined(STRIP_LOG) || STRIP_LOG <= 3
+-#define RAW_LOG_FATAL(...) google::RawLog__(google::GLOG_FATAL,       \
+-                                    __FILE__, __LINE__, __VA_ARGS__)
+-#else
+-#define RAW_LOG_FATAL(...) \
+-  do { \
+-    google::RawLogStub__(0, __VA_ARGS__);        \
+-    exit(EXIT_FAILURE); \
+-  } while (0)
+-#endif // STRIP_LOG <= 3
+-
+-// Similar to CHECK(condition) << message,
+-// but for low-level modules: we use only RAW_LOG that does not allocate memory.
+-// We do not want to provide args list here to encourage this usage:
+-//   if (!cond)  RAW_LOG(FATAL, "foo ...", hard_to_compute_args);
+-// so that the args are not computed when not needed.
+-#define RAW_CHECK(condition, message)                                   \
+-  do {                                                                  \
+-    if (!(condition)) {                                                 \
+-      RAW_LOG(FATAL, "Check %s failed: %s", #condition, message);       \
+-    }                                                                   \
+-  } while (0)
+-
+-// Debug versions of RAW_LOG and RAW_CHECK
+-#ifndef NDEBUG
+-
+-#define RAW_DLOG(severity, ...) RAW_LOG(severity, __VA_ARGS__)
+-#define RAW_DCHECK(condition, message) RAW_CHECK(condition, message)
+-
+-#else  // NDEBUG
+-
+-#define RAW_DLOG(severity, ...)                                 \
+-  while (false)                                                 \
+-    RAW_LOG(severity, __VA_ARGS__)
+-#define RAW_DCHECK(condition, message) \
+-  while (false) \
+-    RAW_CHECK(condition, message)
+-
+-#endif  // NDEBUG
+-
+-#if defined(__GNUC__)
+-#pragma GCC diagnostic pop
+-#endif
+-
+-// Stub log function used to work around for unused variable warnings when
+-// building with STRIP_LOG > 0.
+-static inline void RawLogStub__(int /* ignored */, ...) {
+-}
+-
+-// Helper function to implement RAW_LOG and RAW_VLOG
+-// Logs format... at "severity" level, reporting it
+-// as called from file:line.
+-// This does not allocate memory or acquire locks.
+-GLOG_EXPORT void RawLog__(LogSeverity severity, const char* file, int line,
+-                          const char* format, ...)
+-    __attribute__((__format__(__printf__, 4, 5)));
+-
+-}
++#define RAW_LOG(...)  // Do nothing.
+ 
+ #endif  // GLOG_RAW_LOGGING_H
diff --git a/base/third_party/symbolize/patches/003-minimal-utilities.patch b/base/third_party/symbolize/patches/003-minimal-utilities.patch
new file mode 100644
index 0000000..53c65a43
--- /dev/null
+++ b/base/third_party/symbolize/patches/003-minimal-utilities.patch
@@ -0,0 +1,184 @@
+diff --git a/base/third_party/symbolize/utilities.h b/base/third_party/symbolize/utilities.h
+index efa3a8d99856e..8c61380fad159 100644
+--- a/base/third_party/symbolize/utilities.h
++++ b/base/third_party/symbolize/utilities.h
+@@ -34,102 +34,6 @@
+ #ifndef UTILITIES_H__
+ #define UTILITIES_H__
+ 
+-// printf macros for size_t, in the style of inttypes.h
+-#ifdef _LP64
+-#define __PRIS_PREFIX "z"
+-#else
+-#define __PRIS_PREFIX
+-#endif
+-
+-// Use these macros after a % in a printf format string
+-// to get correct 32/64 bit behavior, like this:
+-// size_t size = records.size();
+-// printf("%"PRIuS"\n", size);
+-
+-#define PRIdS __PRIS_PREFIX "d"
+-#define PRIxS __PRIS_PREFIX "x"
+-#define PRIuS __PRIS_PREFIX "u"
+-#define PRIXS __PRIS_PREFIX "X"
+-#define PRIoS __PRIS_PREFIX "o"
+-
+-#include <string>
+-
+-#include "glog/logging.h"
+-
+-#if defined(GLOG_OS_WINDOWS)
+-# include "port.h"
+-#endif
+-
+-#include "config.h"
+-
+-// There are three different ways we can try to get the stack trace:
+-//
+-// 1) The libunwind library.  This is still in development, and as a
+-//    separate library adds a new dependency, but doesn't need a frame
+-//    pointer.  It also doesn't call malloc.
+-//
+-// 2) Our hand-coded stack-unwinder.  This depends on a certain stack
+-//    layout, which is used by gcc (and those systems using a
+-//    gcc-compatible ABI) on x86 systems, at least since gcc 2.95.
+-//    It uses the frame pointer to do its work.
+-//
+-// 3) The gdb unwinder -- also the one used by the c++ exception code.
+-//    It's obviously well-tested, but has a fatal flaw: it can call
+-//    malloc() from the unwinder.  This is a problem because we're
+-//    trying to use the unwinder to instrument malloc().
+-//
+-// 4) The Windows API CaptureStackTrace.
+-//
+-// Note: if you add a new implementation here, make sure it works
+-// correctly when GetStackTrace() is called with max_depth == 0.
+-// Some code may do that.
+-
+-#if defined(HAVE_LIB_UNWIND)
+-# define STACKTRACE_H "stacktrace_libunwind-inl.h"
+-#elif defined(HAVE__UNWIND_BACKTRACE) && defined(HAVE__UNWIND_GETIP)
+-# define STACKTRACE_H "stacktrace_unwind-inl.h"
+-#elif !defined(NO_FRAME_POINTER)
+-# if defined(__i386__) && __GNUC__ >= 2
+-#  define STACKTRACE_H "stacktrace_x86-inl.h"
+-# elif (defined(__ppc__) || defined(__PPC__)) && __GNUC__ >= 2
+-#  define STACKTRACE_H "stacktrace_powerpc-inl.h"
+-# elif defined(GLOG_OS_WINDOWS)
+-#  define STACKTRACE_H "stacktrace_windows-inl.h"
+-# endif
+-#endif
+-
+-#if !defined(STACKTRACE_H) && defined(HAVE_EXECINFO_BACKTRACE)
+-# define STACKTRACE_H "stacktrace_generic-inl.h"
+-#endif
+-
+-#if defined(STACKTRACE_H)
+-# define HAVE_STACKTRACE
+-#endif
+-
+-#ifndef GLOG_NO_SYMBOLIZE_DETECTION
+-#ifndef HAVE_SYMBOLIZE
+-// defined by gcc
+-#if defined(__ELF__) && defined(GLOG_OS_LINUX)
+-# define HAVE_SYMBOLIZE
+-#elif defined(GLOG_OS_MACOSX) && defined(HAVE_DLADDR)
+-// Use dladdr to symbolize.
+-# define HAVE_SYMBOLIZE
+-#elif defined(GLOG_OS_WINDOWS)
+-// Use DbgHelp to symbolize
+-# define HAVE_SYMBOLIZE
+-#endif
+-#endif // !defined(HAVE_SYMBOLIZE)
+-#endif // !defined(GLOG_NO_SYMBOLIZE_DETECTION)
+-
+-#ifndef ARRAYSIZE
+-// There is a better way, but this is good enough for our purpose.
+-# define ARRAYSIZE(a) (sizeof(a) / sizeof(*(a)))
+-#endif
+-
+-_START_GOOGLE_NAMESPACE_
+-
+-namespace glog_internal_namespace_ {
+-
+ #ifdef HAVE___ATTRIBUTE__
+ # define ATTRIBUTE_NOINLINE __attribute__ ((noinline))
+ # define HAVE_ATTRIBUTE_NOINLINE
+@@ -140,76 +44,4 @@ namespace glog_internal_namespace_ {
+ # define ATTRIBUTE_NOINLINE
+ #endif
+ 
+-const char* ProgramInvocationShortName();
+-
+-int64 CycleClock_Now();
+-
+-int64 UsecToCycles(int64 usec);
+-WallTime WallTime_Now();
+-
+-int32 GetMainThreadPid();
+-bool PidHasChanged();
+-
+-pid_t GetTID();
+-
+-const std::string& MyUserName();
+-
+-// Get the part of filepath after the last path separator.
+-// (Doesn't modify filepath, contrary to basename() in libgen.h.)
+-const char* const_basename(const char* filepath);
+-
+-// Wrapper of __sync_val_compare_and_swap. If the GCC extension isn't
+-// defined, we try the CPU specific logics (we only support x86 and
+-// x86_64 for now) first, then use a naive implementation, which has a
+-// race condition.
+-template<typename T>
+-inline T sync_val_compare_and_swap(T* ptr, T oldval, T newval) {
+-#if defined(HAVE___SYNC_VAL_COMPARE_AND_SWAP)
+-  return __sync_val_compare_and_swap(ptr, oldval, newval);
+-#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+-  T ret;
+-  __asm__ __volatile__("lock; cmpxchg %1, (%2);"
+-                       :"=a"(ret)
+-                        // GCC may produces %sil or %dil for
+-                        // constraint "r", but some of apple's gas
+-                        // dosn't know the 8 bit registers.
+-                        // We use "q" to avoid these registers.
+-                       :"q"(newval), "q"(ptr), "a"(oldval)
+-                       :"memory", "cc");
+-  return ret;
+-#else
+-  T ret = *ptr;
+-  if (ret == oldval) {
+-    *ptr = newval;
+-  }
+-  return ret;
+-#endif
+-}
+-
+-void DumpStackTraceToString(std::string* stacktrace);
+-
+-struct CrashReason {
+-  CrashReason() : filename(0), line_number(0), message(0), depth(0) {}
+-
+-  const char* filename;
+-  int line_number;
+-  const char* message;
+-
+-  // We'll also store a bit of stack trace context at the time of crash as
+-  // it may not be available later on.
+-  void* stack[32];
+-  int depth;
+-};
+-
+-void SetCrashReason(const CrashReason* r);
+-
+-void InitGoogleLoggingUtilities(const char* argv0);
+-void ShutdownGoogleLoggingUtilities();
+-
+-}  // namespace glog_internal_namespace_
+-
+-_END_GOOGLE_NAMESPACE_
+-
+-using namespace GOOGLE_NAMESPACE::glog_internal_namespace_;
+-
+ #endif  // UTILITIES_H__
diff --git a/base/third_party/symbolize/patches/004-add-missing-symbolize-header.patch b/base/third_party/symbolize/patches/004-add-missing-symbolize-header.patch
new file mode 100644
index 0000000..8bbddc2
--- /dev/null
+++ b/base/third_party/symbolize/patches/004-add-missing-symbolize-header.patch
@@ -0,0 +1,13 @@
+diff --git a/base/third_party/symbolize/symbolize.cc b/base/third_party/symbolize/symbolize.cc
+index 2cfd4c490cc94..e2f99a647bf88 100644
+--- a/base/third_party/symbolize/symbolize.cc
++++ b/base/third_party/symbolize/symbolize.cc
+@@ -52,6 +52,8 @@
+ #include GLOG_BUILD_CONFIG_INCLUDE
+ #endif  // GLOG_BUILD_CONFIG_INCLUDE
+ 
++#include "symbolize.h"
++
+ #include "utilities.h"
+ 
+ #if defined(HAVE_SYMBOLIZE)
diff --git a/base/third_party/symbolize/patches/005-expose-file-helpers.patch b/base/third_party/symbolize/patches/005-expose-file-helpers.patch
new file mode 100644
index 0000000..fe768c5
--- /dev/null
+++ b/base/third_party/symbolize/patches/005-expose-file-helpers.patch
@@ -0,0 +1,135 @@
+diff --git a/base/third_party/symbolize/symbolize.cc b/base/third_party/symbolize/symbolize.cc
+index e2f99a647bf88..3c72fd1183f48 100644
+--- a/base/third_party/symbolize/symbolize.cc
++++ b/base/third_party/symbolize/symbolize.cc
+@@ -141,12 +141,16 @@ _END_GOOGLE_NAMESPACE_
+ 
+ _START_GOOGLE_NAMESPACE_
+ 
+-// Read up to "count" bytes from "offset" in the file pointed by file
+-// descriptor "fd" into the buffer starting at "buf" while handling short reads
+-// and EINTR.  On success, return the number of bytes read.  Otherwise, return
+-// -1.
+-static ssize_t ReadFromOffset(const int fd, void *buf, const size_t count,
+-                              const size_t offset) {
++FileDescriptor::~FileDescriptor() {
++  if (fd_ >= 0) {
++    close(fd_);
++  }
++}
++
++ssize_t ReadFromOffset(const int fd,
++                       void* buf,
++                       const size_t count,
++                       const size_t offset) {
+   SAFE_ASSERT(fd >= 0);
+   SAFE_ASSERT(count <= static_cast<size_t>(std::numeric_limits<ssize_t>::max()));
+   char *buf0 = reinterpret_cast<char *>(buf);
+@@ -371,22 +375,6 @@ static bool GetSymbolFromObjectFile(const int fd,
+ }
+ 
+ namespace {
+-// Thin wrapper around a file descriptor so that the file descriptor
+-// gets closed for sure.
+-struct FileDescriptor {
+-  const int fd_;
+-  explicit FileDescriptor(int fd) : fd_(fd) {}
+-  ~FileDescriptor() {
+-    if (fd_ >= 0) {
+-      close(fd_);
+-    }
+-  }
+-  int get() { return fd_; }
+-
+- private:
+-  FileDescriptor(const FileDescriptor &);
+-  void operator=(const FileDescriptor&);
+-};
+ 
+ // Helper class for reading lines from file.
+ //
+@@ -503,20 +491,11 @@ static char *GetHex(const char *start, const char *end, uint64_t *hex) {
+   return const_cast<char *>(p);
+ }
+ 
+-// Searches for the object file (from /proc/self/maps) that contains
+-// the specified pc.  If found, sets |start_address| to the start address
+-// of where this object file is mapped in memory, sets the module base
+-// address into |base_address|, copies the object file name into
+-// |out_file_name|, and attempts to open the object file.  If the object
+-// file is opened successfully, returns the file descriptor.  Otherwise,
+-// returns -1.  |out_file_name_size| is the size of the file name buffer
+-// (including the null-terminator).
+-static ATTRIBUTE_NOINLINE int
+-OpenObjectFileContainingPcAndGetStartAddress(uint64_t pc,
+-                                             uint64_t &start_address,
+-                                             uint64_t &base_address,
+-                                             char *out_file_name,
+-                                             size_t out_file_name_size) {
++int OpenObjectFileContainingPcAndGetStartAddress(uint64_t pc,
++                                                 uint64_t& start_address,
++                                                 uint64_t& base_address,
++                                                 char* out_file_name,
++                                                 size_t out_file_name_size) {
+   int object_fd;
+ 
+   int maps_fd;
+diff --git a/base/third_party/symbolize/symbolize.h b/base/third_party/symbolize/symbolize.h
+index 5959e579ffc93..11b24fbd06f5c 100644
+--- a/base/third_party/symbolize/symbolize.h
++++ b/base/third_party/symbolize/symbolize.h
+@@ -94,17 +94,54 @@
+ 
+ _START_GOOGLE_NAMESPACE_
+ 
++// Read up to "count" bytes from "offset" in the file pointed by file
++// descriptor "fd" into the buffer starting at "buf" while handling short reads
++// and EINTR.  On success, return the number of bytes read.  Otherwise, return
++// -1.
++ssize_t ReadFromOffset(const int fd,
++                       void* buf,
++                       const size_t count,
++                       const size_t offset);
++
+ // Gets the section header for the given name, if it exists. Returns true on
+ // success. Otherwise, returns false.
+ bool GetSectionHeaderByName(int fd, const char *name, size_t name_len,
+                             ElfW(Shdr) *out);
+ 
++// Searches for the object file (from /proc/self/maps) that contains
++// the specified pc.  If found, sets |start_address| to the start address
++// of where this object file is mapped in memory, sets the module base
++// address into |base_address|, copies the object file name into
++// |out_file_name|, and attempts to open the object file.  If the object
++// file is opened successfully, returns the file descriptor.  Otherwise,
++// returns -1.  |out_file_name_size| is the size of the file name buffer
++// (including the null-terminator).
++ATTRIBUTE_NOINLINE int OpenObjectFileContainingPcAndGetStartAddress(
++    uint64_t pc,
++    uint64_t& start_address,
++    uint64_t& base_address,
++    char* out_file_name,
++    size_t out_file_name_size);
++
+ _END_GOOGLE_NAMESPACE_
+ 
+ #endif  /* __ELF__ */
+ 
+ _START_GOOGLE_NAMESPACE_
+ 
++// Thin wrapper around a file descriptor so that the file descriptor
++// gets closed for sure.
++struct FileDescriptor {
++  const int fd_;
++  explicit FileDescriptor(int fd) : fd_(fd) {}
++  ~FileDescriptor();
++  int get() { return fd_; }
++
++ private:
++  FileDescriptor(const FileDescriptor &);
++  void operator=(const FileDescriptor&);
++};
++
+ // Restrictions on the callbacks that follow:
+ //  - The callbacks must not use heaps but only use stacks.
+ //  - The callbacks must be async-signal-safe.
diff --git a/base/third_party/symbolize/patches/006-use-sandbox-hook-for-open-object-file.patch b/base/third_party/symbolize/patches/006-use-sandbox-hook-for-open-object-file.patch
new file mode 100644
index 0000000..0081801
--- /dev/null
+++ b/base/third_party/symbolize/patches/006-use-sandbox-hook-for-open-object-file.patch
@@ -0,0 +1,70 @@
+diff --git a/base/third_party/symbolize/symbolize.cc b/base/third_party/symbolize/symbolize.cc
+index 3c72fd1183f48..20d4d4106d65b 100644
+--- a/base/third_party/symbolize/symbolize.cc
++++ b/base/third_party/symbolize/symbolize.cc
+@@ -491,11 +491,12 @@ static char *GetHex(const char *start, const char *end, uint64_t *hex) {
+   return const_cast<char *>(p);
+ }
+ 
+-int OpenObjectFileContainingPcAndGetStartAddress(uint64_t pc,
+-                                                 uint64_t& start_address,
+-                                                 uint64_t& base_address,
+-                                                 char* out_file_name,
+-                                                 size_t out_file_name_size) {
++static int OpenObjectFileContainingPcAndGetStartAddressNoHook(
++    uint64_t pc,
++    uint64_t& start_address,
++    uint64_t& base_address,
++    char* out_file_name,
++    size_t out_file_name_size) {
+   int object_fd;
+ 
+   int maps_fd;
+@@ -645,6 +646,20 @@ int OpenObjectFileContainingPcAndGetStartAddress(uint64_t pc,
+   }
+ }
+ 
++int OpenObjectFileContainingPcAndGetStartAddress(
++    uint64_t pc,
++    uint64_t& start_address,
++    uint64_t& base_address,
++    char* out_file_name,
++    size_t out_file_name_size) {
++  if (g_symbolize_open_object_file_callback) {
++    return g_symbolize_open_object_file_callback(
++        pc, start_address, base_address, out_file_name, out_file_name_size);
++  }
++  return OpenObjectFileContainingPcAndGetStartAddressNoHook(
++      pc, start_address, base_address, out_file_name, out_file_name_size);
++}
++
+ // POSIX doesn't define any async-signal safe function for converting
+ // an integer to ASCII. We'll have to define our own version.
+ // itoa_r() converts an (unsigned) integer to ASCII. It returns "buf", if the
+@@ -734,7 +749,6 @@ static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
+   uint64_t pc0 = reinterpret_cast<uintptr_t>(pc);
+   uint64_t start_address = 0;
+   uint64_t base_address = 0;
+-  int object_fd = -1;
+ 
+   if (out_size < 1) {
+     return false;
+@@ -742,16 +756,8 @@ static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
+   out[0] = '\0';
+   SafeAppendString("(", out, out_size);
+ 
+-  if (g_symbolize_open_object_file_callback) {
+-    object_fd = g_symbolize_open_object_file_callback(pc0, start_address,
+-                                                      base_address, out + 1,
+-                                                      out_size - 1);
+-  } else {
+-    object_fd = OpenObjectFileContainingPcAndGetStartAddress(pc0, start_address,
+-                                                             base_address,
+-                                                             out + 1,
+-                                                             out_size - 1);
+-  }
++  int object_fd = OpenObjectFileContainingPcAndGetStartAddress(
++      pc0, start_address, base_address, out + 1, out_size - 1);
+ 
+   FileDescriptor wrapped_object_fd(object_fd);
+ 
diff --git a/base/third_party/symbolize/patches/007-sys-types-h.patch b/base/third_party/symbolize/patches/007-sys-types-h.patch
new file mode 100644
index 0000000..cc47bd4
--- /dev/null
+++ b/base/third_party/symbolize/patches/007-sys-types-h.patch
@@ -0,0 +1,13 @@
+diff --git a/base/third_party/symbolize/symbolize.h b/base/third_party/symbolize/symbolize.h
+index 2a55c688aedfb..987569fdde67f 100644
+--- a/base/third_party/symbolize/symbolize.h
++++ b/base/third_party/symbolize/symbolize.h
+@@ -54,6 +54,8 @@
+ #ifndef BASE_SYMBOLIZE_H_
+ #define BASE_SYMBOLIZE_H_
+ 
++#include <sys/types.h>  // for ssize_t
++
+ #include "utilities.h"
+ #include "config.h"
+ #include "glog/logging.h"
diff --git a/base/third_party/symbolize/patches/008-include-cstdlib.patch b/base/third_party/symbolize/patches/008-include-cstdlib.patch
new file mode 100644
index 0000000..ffa9edc
--- /dev/null
+++ b/base/third_party/symbolize/patches/008-include-cstdlib.patch
@@ -0,0 +1,12 @@
+diff --git a/base/third_party/symbolize/symbolize.cc b/base/third_party/symbolize/symbolize.cc
+index 5287e5bb11007..a3b8399f411bf 100644
+--- a/base/third_party/symbolize/symbolize.cc
++++ b/base/third_party/symbolize/symbolize.cc
+@@ -59,6 +59,7 @@
+ #if defined(HAVE_SYMBOLIZE)
+ 
+ #include <cstring>
++#include <cstdlib>
+ 
+ #include <algorithm>
+ #include <limits>
diff --git a/base/third_party/symbolize/patches/009-clone-absl-demangle.patch b/base/third_party/symbolize/patches/009-clone-absl-demangle.patch
new file mode 100644
index 0000000..3adae98
--- /dev/null
+++ b/base/third_party/symbolize/patches/009-clone-absl-demangle.patch
@@ -0,0 +1,2388 @@
+diff --git a/base/third_party/symbolize/demangle.cc b/base/third_party/symbolize/demangle.cc
+index 9276c5b879a8c..2632646dd4072 100644
+--- a/base/third_party/symbolize/demangle.cc
++++ b/base/third_party/symbolize/demangle.cc
+@@ -34,13 +34,14 @@
+ //
+ // Note that we only have partial C++0x support yet.
+ 
+-#include <cstdio>  // for NULL
+-
+ #include "demangle.h"
+-#include "utilities.h"
+ 
+ #if defined(GLOG_OS_WINDOWS)
+ #include <dbghelp.h>
++#else
++#include <cstdint>
++#include <cstdio>
++#include <limits>
+ #endif
+ 
+ _START_GOOGLE_NAMESPACE_
+@@ -49,117 +50,199 @@ _START_GOOGLE_NAMESPACE_
+ typedef struct {
+   const char *abbrev;
+   const char *real_name;
++  // Number of arguments in <expression> context, or 0 if disallowed.
++  int arity;
+ } AbbrevPair;
+ 
+ // List of operators from Itanium C++ ABI.
+ static const AbbrevPair kOperatorList[] = {
+-  { "nw", "new" },
+-  { "na", "new[]" },
+-  { "dl", "delete" },
+-  { "da", "delete[]" },
+-  { "ps", "+" },
+-  { "ng", "-" },
+-  { "ad", "&" },
+-  { "de", "*" },
+-  { "co", "~" },
+-  { "pl", "+" },
+-  { "mi", "-" },
+-  { "ml", "*" },
+-  { "dv", "/" },
+-  { "rm", "%" },
+-  { "an", "&" },
+-  { "or", "|" },
+-  { "eo", "^" },
+-  { "aS", "=" },
+-  { "pL", "+=" },
+-  { "mI", "-=" },
+-  { "mL", "*=" },
+-  { "dV", "/=" },
+-  { "rM", "%=" },
+-  { "aN", "&=" },
+-  { "oR", "|=" },
+-  { "eO", "^=" },
+-  { "ls", "<<" },
+-  { "rs", ">>" },
+-  { "lS", "<<=" },
+-  { "rS", ">>=" },
+-  { "eq", "==" },
+-  { "ne", "!=" },
+-  { "lt", "<" },
+-  { "gt", ">" },
+-  { "le", "<=" },
+-  { "ge", ">=" },
+-  { "nt", "!" },
+-  { "aa", "&&" },
+-  { "oo", "||" },
+-  { "pp", "++" },
+-  { "mm", "--" },
+-  { "cm", "," },
+-  { "pm", "->*" },
+-  { "pt", "->" },
+-  { "cl", "()" },
+-  { "ix", "[]" },
+-  { "qu", "?" },
+-  { "st", "sizeof" },
+-  { "sz", "sizeof" },
+-  { NULL, NULL },
++    // New has special syntax (not currently supported).
++    {"nw", "new", 0},
++    {"na", "new[]", 0},
++
++    // Works except that the 'gs' prefix is not supported.
++    {"dl", "delete", 1},
++    {"da", "delete[]", 1},
++
++    {"ps", "+", 1},  // "positive"
++    {"ng", "-", 1},  // "negative"
++    {"ad", "&", 1},  // "address-of"
++    {"de", "*", 1},  // "dereference"
++    {"co", "~", 1},
++
++    {"pl", "+", 2},
++    {"mi", "-", 2},
++    {"ml", "*", 2},
++    {"dv", "/", 2},
++    {"rm", "%", 2},
++    {"an", "&", 2},
++    {"or", "|", 2},
++    {"eo", "^", 2},
++    {"aS", "=", 2},
++    {"pL", "+=", 2},
++    {"mI", "-=", 2},
++    {"mL", "*=", 2},
++    {"dV", "/=", 2},
++    {"rM", "%=", 2},
++    {"aN", "&=", 2},
++    {"oR", "|=", 2},
++    {"eO", "^=", 2},
++    {"ls", "<<", 2},
++    {"rs", ">>", 2},
++    {"lS", "<<=", 2},
++    {"rS", ">>=", 2},
++    {"eq", "==", 2},
++    {"ne", "!=", 2},
++    {"lt", "<", 2},
++    {"gt", ">", 2},
++    {"le", "<=", 2},
++    {"ge", ">=", 2},
++    {"nt", "!", 1},
++    {"aa", "&&", 2},
++    {"oo", "||", 2},
++    {"pp", "++", 1},
++    {"mm", "--", 1},
++    {"cm", ",", 2},
++    {"pm", "->*", 2},
++    {"pt", "->", 0},  // Special syntax
++    {"cl", "()", 0},  // Special syntax
++    {"ix", "[]", 2},
++    {"qu", "?", 3},
++    {"st", "sizeof", 0},  // Special syntax
++    {"sz", "sizeof", 1},  // Not a real operator name, but used in expressions.
++    {nullptr, nullptr, 0},
+ };
+ 
+ // List of builtin types from Itanium C++ ABI.
++//
++// Invariant: only one- or two-character type abbreviations here.
+ static const AbbrevPair kBuiltinTypeList[] = {
+-  { "v", "void" },
+-  { "w", "wchar_t" },
+-  { "b", "bool" },
+-  { "c", "char" },
+-  { "a", "signed char" },
+-  { "h", "unsigned char" },
+-  { "s", "short" },
+-  { "t", "unsigned short" },
+-  { "i", "int" },
+-  { "j", "unsigned int" },
+-  { "l", "long" },
+-  { "m", "unsigned long" },
+-  { "x", "long long" },
+-  { "y", "unsigned long long" },
+-  { "n", "__int128" },
+-  { "o", "unsigned __int128" },
+-  { "f", "float" },
+-  { "d", "double" },
+-  { "e", "long double" },
+-  { "g", "__float128" },
+-  { "z", "ellipsis" },
+-  { NULL, NULL }
++    {"v", "void", 0},
++    {"w", "wchar_t", 0},
++    {"b", "bool", 0},
++    {"c", "char", 0},
++    {"a", "signed char", 0},
++    {"h", "unsigned char", 0},
++    {"s", "short", 0},
++    {"t", "unsigned short", 0},
++    {"i", "int", 0},
++    {"j", "unsigned int", 0},
++    {"l", "long", 0},
++    {"m", "unsigned long", 0},
++    {"x", "long long", 0},
++    {"y", "unsigned long long", 0},
++    {"n", "__int128", 0},
++    {"o", "unsigned __int128", 0},
++    {"f", "float", 0},
++    {"d", "double", 0},
++    {"e", "long double", 0},
++    {"g", "__float128", 0},
++    {"z", "ellipsis", 0},
++
++    {"De", "decimal128", 0},      // IEEE 754r decimal floating point (128 bits)
++    {"Dd", "decimal64", 0},       // IEEE 754r decimal floating point (64 bits)
++    {"Dc", "decltype(auto)", 0},
++    {"Da", "auto", 0},
++    {"Dn", "std::nullptr_t", 0},  // i.e., decltype(nullptr)
++    {"Df", "decimal32", 0},       // IEEE 754r decimal floating point (32 bits)
++    {"Di", "char32_t", 0},
++    {"Du", "char8_t", 0},
++    {"Ds", "char16_t", 0},
++    {"Dh", "float16", 0},         // IEEE 754r half-precision float (16 bits)
++    {nullptr, nullptr, 0},
+ };
+ 
+ // List of substitutions Itanium C++ ABI.
+ static const AbbrevPair kSubstitutionList[] = {
+-  { "St", "" },
+-  { "Sa", "allocator" },
+-  { "Sb", "basic_string" },
+-  // std::basic_string<char, std::char_traits<char>,std::allocator<char> >
+-  { "Ss", "string"},
+-  // std::basic_istream<char, std::char_traits<char> >
+-  { "Si", "istream" },
+-  // std::basic_ostream<char, std::char_traits<char> >
+-  { "So", "ostream" },
+-  // std::basic_iostream<char, std::char_traits<char> >
+-  { "Sd", "iostream" },
+-  { NULL, NULL }
++    {"St", "", 0},
++    {"Sa", "allocator", 0},
++    {"Sb", "basic_string", 0},
++    // std::basic_string<char, std::char_traits<char>,std::allocator<char> >
++    {"Ss", "string", 0},
++    // std::basic_istream<char, std::char_traits<char> >
++    {"Si", "istream", 0},
++    // std::basic_ostream<char, std::char_traits<char> >
++    {"So", "ostream", 0},
++    // std::basic_iostream<char, std::char_traits<char> >
++    {"Sd", "iostream", 0},
++    {nullptr, nullptr, 0},
+ };
+ 
+-// State needed for demangling.
++// State needed for demangling.  This struct is copied in almost every stack
++// frame, so every byte counts.
++typedef struct {
++  int mangled_idx;                     // Cursor of mangled name.
++  int out_cur_idx;                     // Cursor of output string.
++  int prev_name_idx;                   // For constructors/destructors.
++  unsigned int prev_name_length : 16;  // For constructors/destructors.
++  signed int nest_level : 15;          // For nested names.
++  unsigned int append : 1;             // Append flag.
++  // Note: for some reason MSVC can't pack "bool append : 1" into the same int
++  // with the above two fields, so we use an int instead.  Amusingly it can pack
++  // "signed bool" as expected, but relying on that to continue to be a legal
++  // type seems ill-advised (as it's illegal in at least clang).
++} ParseState;
++
++static_assert(sizeof(ParseState) == 4 * sizeof(int),
++              "unexpected size of ParseState");
++
++// One-off state for demangling that's not subject to backtracking -- either
++// constant data, data that's intentionally immune to backtracking (steps), or
++// data that would never be changed by backtracking anyway (recursion_depth).
++//
++// Only one copy of this exists for each call to Demangle, so the size of this
++// struct is nearly inconsequential.
+ typedef struct {
+-  const char *mangled_cur;   // Cursor of mangled name.
+-  char *out_cur;             // Cursor of output string.
+-  const char *out_begin;     // Beginning of output string.
+-  const char *out_end;       // End of output string.
+-  const char *prev_name;     // For constructors/destructors.
+-  ssize_t prev_name_length;  // For constructors/destructors.
+-  short nest_level;          // For nested names.
+-  bool append;               // Append flag.
+-  bool overflowed;           // True if output gets overflowed.
++  const char *mangled_begin;  // Beginning of input string.
++  char *out;                  // Beginning of output string.
++  int out_end_idx;            // One past last allowed output character.
++  int recursion_depth;        // For stack exhaustion prevention.
++  int steps;               // Cap how much work we'll do, regardless of depth.
++  ParseState parse_state;  // Backtrackable state copied for most frames.
+ } State;
+ 
++namespace {
++// Prevent deep recursion / stack exhaustion.
++// Also prevent unbounded handling of complex inputs.
++class ComplexityGuard {
++ public:
++  explicit ComplexityGuard(State *state) : state_(state) {
++    ++state->recursion_depth;
++    ++state->steps;
++  }
++  ~ComplexityGuard() { --state_->recursion_depth; }
++
++  // 256 levels of recursion seems like a reasonable upper limit on depth.
++  // 128 is not enough to demagle synthetic tests from demangle_unittest.txt:
++  // "_ZaaZZZZ..." and "_ZaaZcvZcvZ..."
++  static constexpr int kRecursionDepthLimit = 256;
++
++  // We're trying to pick a charitable upper-limit on how many parse steps are
++  // necessary to handle something that a human could actually make use of.
++  // This is mostly in place as a bound on how much work we'll do if we are
++  // asked to demangle an mangled name from an untrusted source, so it should be
++  // much larger than the largest expected symbol, but much smaller than the
++  // amount of work we can do in, e.g., a second.
++  //
++  // Some real-world symbols from an arbitrary binary started failing between
++  // 2^12 and 2^13, so we multiply the latter by an extra factor of 16 to set
++  // the limit.
++  //
++  // Spending one second on 2^17 parse steps would require each step to take
++  // 7.6us, or ~30000 clock cycles, so it's safe to say this can be done in
++  // under a second.
++  static constexpr int kParseStepsLimit = 1 << 17;
++
++  bool IsTooComplex() const {
++    return state_->recursion_depth > kRecursionDepthLimit ||
++           state_->steps > kParseStepsLimit;
++  }
++
++ private:
++  State *state_;
++};
++}  // namespace
++
+ // We don't use strlen() in libc since it's not guaranteed to be async
+ // signal safe.
+ static size_t StrLen(const char *str) {
+@@ -172,8 +255,8 @@ static size_t StrLen(const char *str) {
+ }
+ 
+ // Returns true if "str" has at least "n" characters remaining.
+-static bool AtLeastNumCharsRemaining(const char *str, ssize_t n) {
+-  for (ssize_t i = 0; i < n; ++i) {
++static bool AtLeastNumCharsRemaining(const char *str, size_t n) {
++  for (size_t i = 0; i < n; ++i) {
+     if (str[i] == '\0') {
+       return false;
+     }
+@@ -184,32 +267,42 @@ static bool AtLeastNumCharsRemaining(const char *str, ssize_t n) {
+ // Returns true if "str" has "prefix" as a prefix.
+ static bool StrPrefix(const char *str, const char *prefix) {
+   size_t i = 0;
+-  while (str[i] != '\0' && prefix[i] != '\0' &&
+-         str[i] == prefix[i]) {
++  while (str[i] != '\0' && prefix[i] != '\0' && str[i] == prefix[i]) {
+     ++i;
+   }
+   return prefix[i] == '\0';  // Consumed everything in "prefix".
+ }
+ 
+-static void InitState(State *state, const char *mangled,
+-                      char *out, size_t out_size) {
+-  state->mangled_cur = mangled;
+-  state->out_cur = out;
+-  state->out_begin = out;
+-  state->out_end = out + out_size;
+-  state->prev_name  = NULL;
+-  state->prev_name_length = -1;
+-  state->nest_level = -1;
+-  state->append = true;
+-  state->overflowed = false;
++static void InitState(State* state,
++                      const char* mangled,
++                      char* out,
++                      size_t out_size) {
++  state->mangled_begin = mangled;
++  state->out = out;
++  state->out_end_idx = static_cast<int>(out_size);
++  state->recursion_depth = 0;
++  state->steps = 0;
++
++  state->parse_state.mangled_idx = 0;
++  state->parse_state.out_cur_idx = 0;
++  state->parse_state.prev_name_idx = 0;
++  state->parse_state.prev_name_length = 0;
++  state->parse_state.nest_level = -1;
++  state->parse_state.append = true;
++}
++
++static inline const char *RemainingInput(State *state) {
++  return &state->mangled_begin[state->parse_state.mangled_idx];
+ }
+ 
+-// Returns true and advances "mangled_cur" if we find "one_char_token"
+-// at "mangled_cur" position.  It is assumed that "one_char_token" does
++// Returns true and advances "mangled_idx" if we find "one_char_token"
++// at "mangled_idx" position.  It is assumed that "one_char_token" does
+ // not contain '\0'.
+ static bool ParseOneCharToken(State *state, const char one_char_token) {
+-  if (state->mangled_cur[0] == one_char_token) {
+-    ++state->mangled_cur;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  if (RemainingInput(state)[0] == one_char_token) {
++    ++state->parse_state.mangled_idx;
+     return true;
+   }
+   return false;
+@@ -219,9 +312,11 @@ static bool ParseOneCharToken(State *state, const char one_char_token) {
+ // at "mangled_cur" position.  It is assumed that "two_char_token" does
+ // not contain '\0'.
+ static bool ParseTwoCharToken(State *state, const char *two_char_token) {
+-  if (state->mangled_cur[0] == two_char_token[0] &&
+-      state->mangled_cur[1] == two_char_token[1]) {
+-    state->mangled_cur += 2;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  if (RemainingInput(state)[0] == two_char_token[0] &&
++      RemainingInput(state)[1] == two_char_token[1]) {
++    state->parse_state.mangled_idx += 2;
+     return true;
+   }
+   return false;
+@@ -230,21 +325,35 @@ static bool ParseTwoCharToken(State *state, const char *two_char_token) {
+ // Returns true and advances "mangled_cur" if we find any character in
+ // "char_class" at "mangled_cur" position.
+ static bool ParseCharClass(State *state, const char *char_class) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  if (RemainingInput(state)[0] == '\0') {
++    return false;
++  }
+   const char *p = char_class;
+   for (; *p != '\0'; ++p) {
+-    if (state->mangled_cur[0] == *p) {
+-      ++state->mangled_cur;
++    if (RemainingInput(state)[0] == *p) {
++      ++state->parse_state.mangled_idx;
+       return true;
+     }
+   }
+   return false;
+ }
+ 
+-// This function is used for handling an optional non-terminal.
+-static bool Optional(bool) {
+-  return true;
++static bool ParseDigit(State *state, int *digit) {
++  char c = RemainingInput(state)[0];
++  if (ParseCharClass(state, "0123456789")) {
++    if (digit != nullptr) {
++      *digit = c - '0';
++    }
++    return true;
++  }
++  return false;
+ }
+ 
++// This function is used for handling an optional non-terminal.
++static bool Optional(bool /*status*/) { return true; }
++
+ // This function is used for handling <non-terminal>+ syntax.
+ typedef bool (*ParseFunc)(State *);
+ static bool OneOrMore(ParseFunc parse_func, State *state) {
+@@ -266,146 +375,179 @@ static bool ZeroOrMore(ParseFunc parse_func, State *state) {
+   return true;
+ }
+ 
+-// Append "str" at "out_cur".  If there is an overflow, "overflowed"
+-// is set to true for later use.  The output string is ensured to
++// Append "str" at "out_cur_idx".  If there is an overflow, out_cur_idx is
++// set to out_end_idx+1.  The output string is ensured to
+ // always terminate with '\0' as long as there is no overflow.
+-static void Append(State *state, const char * const str, ssize_t length) {
+-  for (ssize_t i = 0; i < length; ++i) {
+-    if (state->out_cur + 1 < state->out_end) {  // +1 for '\0'
+-      *state->out_cur = str[i];
+-      ++state->out_cur;
++static void Append(State *state, const char *const str, const size_t length) {
++  for (size_t i = 0; i < length; ++i) {
++    if (state->parse_state.out_cur_idx + 1 <
++        state->out_end_idx) {  // +1 for '\0'
++      state->out[state->parse_state.out_cur_idx++] = str[i];
+     } else {
+-      state->overflowed = true;
++      // signal overflow
++      state->parse_state.out_cur_idx = state->out_end_idx + 1;
+       break;
+     }
+   }
+-  if (!state->overflowed) {
+-    *state->out_cur = '\0';  // Terminate it with '\0'
++  if (state->parse_state.out_cur_idx < state->out_end_idx) {
++    state->out[state->parse_state.out_cur_idx] =
++        '\0';  // Terminate it with '\0'
+   }
+ }
+ 
+ // We don't use equivalents in libc to avoid locale issues.
+-static bool IsLower(char c) {
+-  return c >= 'a' && c <= 'z';
+-}
++static bool IsLower(char c) { return c >= 'a' && c <= 'z'; }
+ 
+ static bool IsAlpha(char c) {
+   return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+ }
+ 
+-static bool IsDigit(char c) {
+-  return c >= '0' && c <= '9';
+-}
++static bool IsDigit(char c) { return c >= '0' && c <= '9'; }
+ 
+ // Returns true if "str" is a function clone suffix.  These suffixes are used
+-// by GCC 4.5.x and later versions to indicate functions which have been
+-// cloned during optimization.  We treat any sequence (.<alpha>+.<digit>+)+ as
+-// a function clone suffix.
++// by GCC 4.5.x and later versions (and our locally-modified version of GCC
++// 4.4.x) to indicate functions which have been cloned during optimization.
++// We treat any sequence (.<alpha>+.<digit>+)+ as a function clone suffix.
++// Additionally, '_' is allowed along with the alphanumeric sequence.
+ static bool IsFunctionCloneSuffix(const char *str) {
+   size_t i = 0;
+   while (str[i] != '\0') {
+-    // Consume a single .<alpha>+.<digit>+ sequence.
+-    if (str[i] != '.' || !IsAlpha(str[i + 1])) {
+-      return false;
++    bool parsed = false;
++    // Consume a single [.<alpha> | _]*[.<digit>]* sequence.
++    if (str[i] == '.' && (IsAlpha(str[i + 1]) || str[i + 1] == '_')) {
++      parsed = true;
++      i += 2;
++      while (IsAlpha(str[i]) || str[i] == '_') {
++        ++i;
++      }
+     }
+-    i += 2;
+-    while (IsAlpha(str[i])) {
+-      ++i;
++    if (str[i] == '.' && IsDigit(str[i + 1])) {
++      parsed = true;
++      i += 2;
++      while (IsDigit(str[i])) {
++        ++i;
++      }
+     }
+-    if (str[i] != '.' || !IsDigit(str[i + 1])) {
++    if (!parsed)
+       return false;
+-    }
+-    i += 2;
+-    while (IsDigit(str[i])) {
+-      ++i;
+-    }
+   }
+   return true;  // Consumed everything in "str".
+ }
+ 
++static bool EndsWith(State *state, const char chr) {
++  return state->parse_state.out_cur_idx > 0 &&
++         state->parse_state.out_cur_idx < state->out_end_idx &&
++         chr == state->out[state->parse_state.out_cur_idx - 1];
++}
++
+ // Append "str" with some tweaks, iff "append" state is true.
+-// Returns true so that it can be placed in "if" conditions.
+-static void MaybeAppendWithLength(State *state, const char * const str,
+-                                  ssize_t length) {
+-  if (state->append && length > 0) {
++static void MaybeAppendWithLength(State *state, const char *const str,
++                                  const size_t length) {
++  if (state->parse_state.append && length > 0) {
+     // Append a space if the output buffer ends with '<' and "str"
+     // starts with '<' to avoid <<<.
+-    if (str[0] == '<' && state->out_begin < state->out_cur  &&
+-        state->out_cur[-1] == '<') {
++    if (str[0] == '<' && EndsWith(state, '<')) {
+       Append(state, " ", 1);
+     }
+-    // Remember the last identifier name for ctors/dtors.
+-    if (IsAlpha(str[0]) || str[0] == '_') {
+-      state->prev_name = state->out_cur;
+-      state->prev_name_length = length;
++    // Remember the last identifier name for ctors/dtors,
++    // but only if we haven't yet overflown the buffer.
++    if (state->parse_state.out_cur_idx < state->out_end_idx &&
++        (IsAlpha(str[0]) || str[0] == '_')) {
++      state->parse_state.prev_name_idx = state->parse_state.out_cur_idx;
++      state->parse_state.prev_name_length = static_cast<unsigned int>(length);
+     }
+     Append(state, str, length);
+   }
+ }
+ 
+-// A convenient wrapper arount MaybeAppendWithLength().
+-static bool MaybeAppend(State *state, const char * const str) {
+-  if (state->append) {
++// Appends a positive decimal number to the output if appending is enabled.
++static bool MaybeAppendDecimal(State *state, int val) {
++  // Max {32-64}-bit unsigned int is 20 digits.
++  constexpr size_t kMaxLength = 20;
++  char buf[kMaxLength];
++
++  // We can't use itoa or sprintf as neither is specified to be
++  // async-signal-safe.
++  if (state->parse_state.append) {
++    // We can't have a one-before-the-beginning pointer, so instead start with
++    // one-past-the-end and manipulate one character before the pointer.
++    char *p = &buf[kMaxLength];
++    do {  // val=0 is the only input that should write a leading zero digit.
++      *--p = static_cast<char>((val % 10) + '0');
++      val /= 10;
++    } while (p > buf && val != 0);
++
++    // 'p' landed on the last character we set.  How convenient.
++    Append(state, p, kMaxLength - static_cast<size_t>(p - buf));
++  }
++
++  return true;
++}
++
++// A convenient wrapper around MaybeAppendWithLength().
++// Returns true so that it can be placed in "if" conditions.
++static bool MaybeAppend(State *state, const char *const str) {
++  if (state->parse_state.append) {
+     size_t length = StrLen(str);
+-    MaybeAppendWithLength(state, str, static_cast<ssize_t>(length));
++    MaybeAppendWithLength(state, str, length);
+   }
+   return true;
+ }
+ 
+ // This function is used for handling nested names.
+ static bool EnterNestedName(State *state) {
+-  state->nest_level = 0;
++  state->parse_state.nest_level = 0;
+   return true;
+ }
+ 
+ // This function is used for handling nested names.
+-static bool LeaveNestedName(State *state, short prev_value) {
+-  state->nest_level = prev_value;
++static bool LeaveNestedName(State *state, int16_t prev_value) {
++  state->parse_state.nest_level = prev_value;
+   return true;
+ }
+ 
+ // Disable the append mode not to print function parameters, etc.
+ static bool DisableAppend(State *state) {
+-  state->append = false;
++  state->parse_state.append = false;
+   return true;
+ }
+ 
+ // Restore the append mode to the previous state.
+ static bool RestoreAppend(State *state, bool prev_value) {
+-  state->append = prev_value;
++  state->parse_state.append = prev_value;
+   return true;
+ }
+ 
+ // Increase the nest level for nested names.
+ static void MaybeIncreaseNestLevel(State *state) {
+-  if (state->nest_level > -1) {
+-    ++state->nest_level;
++  if (state->parse_state.nest_level > -1) {
++    ++state->parse_state.nest_level;
+   }
+ }
+ 
+ // Appends :: for nested names if necessary.
+ static void MaybeAppendSeparator(State *state) {
+-  if (state->nest_level >= 1) {
++  if (state->parse_state.nest_level >= 1) {
+     MaybeAppend(state, "::");
+   }
+ }
+ 
+ // Cancel the last separator if necessary.
+ static void MaybeCancelLastSeparator(State *state) {
+-  if (state->nest_level >= 1 && state->append &&
+-      state->out_begin <= state->out_cur - 2) {
+-    state->out_cur -= 2;
+-    *state->out_cur = '\0';
++  if (state->parse_state.nest_level >= 1 && state->parse_state.append &&
++      state->parse_state.out_cur_idx >= 2) {
++    state->parse_state.out_cur_idx -= 2;
++    state->out[state->parse_state.out_cur_idx] = '\0';
+   }
+ }
+ 
+ // Returns true if the identifier of the given length pointed to by
+ // "mangled_cur" is anonymous namespace.
+-static bool IdentifierIsAnonymousNamespace(State *state, ssize_t length) {
++static bool IdentifierIsAnonymousNamespace(State *state, size_t length) {
++  // Returns true if "anon_prefix" is a proper prefix of "mangled_cur".
+   static const char anon_prefix[] = "_GLOBAL__N_";
+-  return (length > static_cast<ssize_t>(sizeof(anon_prefix)) -
+-                       1 &&  // Should be longer.
+-          StrPrefix(state->mangled_cur, anon_prefix));
++  return (length > (sizeof(anon_prefix) - 1) &&
++          StrPrefix(RemainingInput(state), anon_prefix));
+ }
+ 
+ // Forward declarations of our parsing functions.
+@@ -413,24 +555,24 @@ static bool ParseMangledName(State *state);
+ static bool ParseEncoding(State *state);
+ static bool ParseName(State *state);
+ static bool ParseUnscopedName(State *state);
+-static bool ParseUnscopedTemplateName(State *state);
+ static bool ParseNestedName(State *state);
+ static bool ParsePrefix(State *state);
+ static bool ParseUnqualifiedName(State *state);
+ static bool ParseSourceName(State *state);
+ static bool ParseLocalSourceName(State *state);
++static bool ParseUnnamedTypeName(State *state);
+ static bool ParseNumber(State *state, int *number_out);
+ static bool ParseFloatNumber(State *state);
+ static bool ParseSeqId(State *state);
+-static bool ParseIdentifier(State *state, ssize_t length);
+-static bool ParseAbiTags(State *state);
+-static bool ParseAbiTag(State *state);
+-static bool ParseOperatorName(State *state);
++static bool ParseIdentifier(State *state, size_t length);
++static bool ParseOperatorName(State *state, int *arity);
+ static bool ParseSpecialName(State *state);
+ static bool ParseCallOffset(State *state);
+ static bool ParseNVOffset(State *state);
+ static bool ParseVOffset(State *state);
++static bool ParseAbiTags(State *state);
+ static bool ParseCtorDtorName(State *state);
++static bool ParseDecltype(State *state);
+ static bool ParseType(State *state);
+ static bool ParseCVQualifiers(State *state);
+ static bool ParseBuiltinType(State *state);
+@@ -443,11 +585,15 @@ static bool ParseTemplateParam(State *state);
+ static bool ParseTemplateTemplateParam(State *state);
+ static bool ParseTemplateArgs(State *state);
+ static bool ParseTemplateArg(State *state);
++static bool ParseBaseUnresolvedName(State *state);
++static bool ParseUnresolvedName(State *state);
+ static bool ParseExpression(State *state);
+ static bool ParseExprPrimary(State *state);
++static bool ParseExprCastValue(State *state);
+ static bool ParseLocalName(State *state);
++static bool ParseLocalNameSuffix(State *state);
+ static bool ParseDiscriminator(State *state);
+-static bool ParseSubstitution(State *state);
++static bool ParseSubstitution(State *state, bool accept_std);
+ 
+ // Implementation note: the following code is a straightforward
+ // translation of the Itanium C++ ABI defined in BNF with a couple of
+@@ -459,11 +605,12 @@ static bool ParseSubstitution(State *state);
+ // - Reorder patterns to give greedier functions precedence
+ //   We'll mark "Less greedy than" for these cases in the code
+ //
+-// Each parsing function changes the state and returns true on
+-// success.  Otherwise, don't change the state and returns false.  To
+-// ensure that the state isn't changed in the latter case, we save the
+-// original state before we call more than one parsing functions
+-// consecutively with &&, and restore the state if unsuccessful.  See
++// Each parsing function changes the parse state and returns true on
++// success, or returns false and doesn't change the parse state (note:
++// the parse-steps counter increases regardless of success or failure).
++// To ensure that the parse state isn't changed in the latter case, we
++// save the original state before we call multiple parsing functions
++// consecutively with &&, and restore it if unsuccessful.  See
+ // ParseEncoding() as an example of this convention.  We follow the
+ // convention throughout the code.
+ //
+@@ -477,10 +624,12 @@ static bool ParseSubstitution(State *state);
+ //
+ // Reference:
+ // - Itanium C++ ABI
+-//   <http://www.codesourcery.com/cxx-abi/abi.html#mangling>
++//   <https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling>
+ 
+ // <mangled-name> ::= _Z <encoding>
+ static bool ParseMangledName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   return ParseTwoCharToken(state, "_Z") && ParseEncoding(state);
+ }
+ 
+@@ -488,13 +637,18 @@ static bool ParseMangledName(State *state) {
+ //            ::= <(data) name>
+ //            ::= <special-name>
+ static bool ParseEncoding(State *state) {
+-  State copy = *state;
+-  if (ParseName(state) && ParseBareFunctionType(state)) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  // Implementing the first two productions together as <name>
++  // [<bare-function-type>] avoids exponential blowup of backtracking.
++  //
++  // Since Optional(...) can't fail, there's no need to copy the state for
++  // backtracking.
++  if (ParseName(state) && Optional(ParseBareFunctionType(state))) {
+     return true;
+   }
+-  *state = copy;
+ 
+-  if (ParseName(state) || ParseSpecialName(state)) {
++  if (ParseSpecialName(state)) {
+     return true;
+   }
+   return false;
+@@ -505,60 +659,73 @@ static bool ParseEncoding(State *state) {
+ //        ::= <unscoped-name>
+ //        ::= <local-name>
+ static bool ParseName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   if (ParseNestedName(state) || ParseLocalName(state)) {
+     return true;
+   }
+ 
+-  State copy = *state;
+-  if (ParseUnscopedTemplateName(state) &&
++  // We reorganize the productions to avoid re-parsing unscoped names.
++  // - Inline <unscoped-template-name> productions:
++  //   <name> ::= <substitution> <template-args>
++  //          ::= <unscoped-name> <template-args>
++  //          ::= <unscoped-name>
++  // - Merge the two productions that start with unscoped-name:
++  //   <name> ::= <unscoped-name> [<template-args>]
++
++  ParseState copy = state->parse_state;
++  // "std<...>" isn't a valid name.
++  if (ParseSubstitution(state, /*accept_std=*/false) &&
+       ParseTemplateArgs(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  // Less greedy than <unscoped-template-name> <template-args>.
+-  if (ParseUnscopedName(state)) {
+-    return true;
+-  }
+-  return false;
++  // Note there's no need to restore state after this since only the first
++  // subparser can fail.
++  return ParseUnscopedName(state) && Optional(ParseTemplateArgs(state));
+ }
+ 
+ // <unscoped-name> ::= <unqualified-name>
+ //                 ::= St <unqualified-name>
+ static bool ParseUnscopedName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   if (ParseUnqualifiedName(state)) {
+     return true;
+   }
+ 
+-  State copy = *state;
+-  if (ParseTwoCharToken(state, "St") &&
+-      MaybeAppend(state, "std::") &&
++  ParseState copy = state->parse_state;
++  if (ParseTwoCharToken(state, "St") && MaybeAppend(state, "std::") &&
+       ParseUnqualifiedName(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+-// <unscoped-template-name> ::= <unscoped-name>
+-//                          ::= <substitution>
+-static bool ParseUnscopedTemplateName(State *state) {
+-  return ParseUnscopedName(state) || ParseSubstitution(state);
++// <ref-qualifer> ::= R // lvalue method reference qualifier
++//                ::= O // rvalue method reference qualifier
++static inline bool ParseRefQualifier(State *state) {
++  return ParseCharClass(state, "OR");
+ }
+ 
+-// <nested-name> ::= N [<CV-qualifiers>] <prefix> <unqualified-name> E
+-//               ::= N [<CV-qualifiers>] <template-prefix> <template-args> E
++// <nested-name> ::= N [<CV-qualifiers>] [<ref-qualifier>] <prefix>
++//                   <unqualified-name> E
++//               ::= N [<CV-qualifiers>] [<ref-qualifier>] <template-prefix>
++//                   <template-args> E
+ static bool ParseNestedName(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'N') &&
+-      EnterNestedName(state) &&
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'N') && EnterNestedName(state) &&
+       Optional(ParseCVQualifiers(state)) &&
+-      ParsePrefix(state) &&
++      Optional(ParseRefQualifier(state)) && ParsePrefix(state) &&
+       LeaveNestedName(state, copy.nest_level) &&
+       ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+@@ -574,12 +741,15 @@ static bool ParseNestedName(State *state) {
+ //                   ::= <template-param>
+ //                   ::= <substitution>
+ static bool ParsePrefix(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   bool has_something = false;
+   while (true) {
+     MaybeAppendSeparator(state);
+     if (ParseTemplateParam(state) ||
+-        ParseSubstitution(state) ||
+-        ParseUnscopedName(state)) {
++        ParseSubstitution(state, /*accept_std=*/true) ||
++        ParseUnscopedName(state) ||
++        (ParseOneCharToken(state, 'M') && ParseUnnamedTypeName(state))) {
+       has_something = true;
+       MaybeIncreaseNestLevel(state);
+       continue;
+@@ -594,40 +764,112 @@ static bool ParsePrefix(State *state) {
+   return true;
+ }
+ 
+-// <unqualified-name> ::= <operator-name>
+-//                    ::= <ctor-dtor-name>
++// <unqualified-name> ::= <operator-name> [<abi-tags>]
++//                    ::= <ctor-dtor-name> [<abi-tags>]
+ //                    ::= <source-name> [<abi-tags>]
+ //                    ::= <local-source-name> [<abi-tags>]
++//                    ::= <unnamed-type-name> [<abi-tags>]
++//
++// <local-source-name> is a GCC extension; see below.
+ static bool ParseUnqualifiedName(State *state) {
+-  return (ParseOperatorName(state) ||
+-          ParseCtorDtorName(state) ||
+-          (ParseSourceName(state) && Optional(ParseAbiTags(state))) ||
+-          (ParseLocalSourceName(state) && Optional(ParseAbiTags(state))));
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  if (ParseOperatorName(state, nullptr) || ParseCtorDtorName(state) ||
++      ParseSourceName(state) || ParseLocalSourceName(state) ||
++      ParseUnnamedTypeName(state)) {
++    return ParseAbiTags(state);
++  }
++  return false;
++}
++
++// <abi-tags> ::= <abi-tag> [<abi-tags>]
++// <abi-tag>  ::= B <source-name>
++static bool ParseAbiTags(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++
++  while (ParseOneCharToken(state, 'B')) {
++    ParseState copy = state->parse_state;
++    MaybeAppend(state, "[abi:");
++
++    if (!ParseSourceName(state)) {
++      state->parse_state = copy;
++      return false;
++    }
++    MaybeAppend(state, "]");
++  }
++
++  return true;
+ }
+ 
+ // <source-name> ::= <positive length number> <identifier>
+ static bool ParseSourceName(State *state) {
+-  State copy = *state;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
+   int length = -1;
+-  if (ParseNumber(state, &length) && ParseIdentifier(state, length)) {
++  if (ParseNumber(state, &length) &&
++      ParseIdentifier(state, static_cast<size_t>(length))) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <local-source-name> ::= L <source-name> [<discriminator>]
+ //
+ // References:
+-//   http://gcc.gnu.org/bugzilla/show_bug.cgi?id=31775
+-//   http://gcc.gnu.org/viewcvs?view=rev&revision=124467
++//   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=31775
++//   https://gcc.gnu.org/viewcvs?view=rev&revision=124467
+ static bool ParseLocalSourceName(State *state) {
+-  State copy = *state;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'L') && ParseSourceName(state) &&
+       Optional(ParseDiscriminator(state))) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
++  return false;
++}
++
++// <unnamed-type-name> ::= Ut [<(nonnegative) number>] _
++//                     ::= <closure-type-name>
++// <closure-type-name> ::= Ul <lambda-sig> E [<(nonnegative) number>] _
++// <lambda-sig>        ::= <(parameter) type>+
++static bool ParseUnnamedTypeName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  // Type's 1-based index n is encoded as { "", n == 1; itoa(n-2), otherwise }.
++  // Optionally parse the encoded value into 'which' and add 2 to get the index.
++  int which = -1;
++
++  // Unnamed type local to function or class.
++  if (ParseTwoCharToken(state, "Ut") && Optional(ParseNumber(state, &which)) &&
++      which <= std::numeric_limits<int>::max() - 2 &&  // Don't overflow.
++      ParseOneCharToken(state, '_')) {
++    MaybeAppend(state, "{unnamed type#");
++    MaybeAppendDecimal(state, 2 + which);
++    MaybeAppend(state, "}");
++    return true;
++  }
++  state->parse_state = copy;
++
++  // Closure type.
++  which = -1;
++  if (ParseTwoCharToken(state, "Ul") && DisableAppend(state) &&
++      OneOrMore(ParseType, state) && RestoreAppend(state, copy.append) &&
++      ParseOneCharToken(state, 'E') && Optional(ParseNumber(state, &which)) &&
++      which <= std::numeric_limits<int>::max() - 2 &&  // Don't overflow.
++      ParseOneCharToken(state, '_')) {
++    MaybeAppend(state, "{lambda()#");
++    MaybeAppendDecimal(state, 2 + which);
++    MaybeAppend(state, "}");
++    return true;
++  }
++  state->parse_state = copy;
++
+   return false;
+ }
+ 
+@@ -635,23 +877,32 @@ static bool ParseLocalSourceName(State *state) {
+ // If "number_out" is non-null, then *number_out is set to the value of the
+ // parsed number on success.
+ static bool ParseNumber(State *state, int *number_out) {
+-  int sign = 1;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  bool negative = false;
+   if (ParseOneCharToken(state, 'n')) {
+-    sign = -1;
++    negative = true;
+   }
+-  const char *p = state->mangled_cur;
+-  int number = 0;
+-  for (;*p != '\0'; ++p) {
++  const char *p = RemainingInput(state);
++  uint64_t number = 0;
++  for (; *p != '\0'; ++p) {
+     if (IsDigit(*p)) {
+-      number = number * 10 + (*p - '0');
++      number = number * 10 + static_cast<uint64_t>(*p - '0');
+     } else {
+       break;
+     }
+   }
+-  if (p != state->mangled_cur) {  // Conversion succeeded.
+-    state->mangled_cur = p;
+-    if (number_out != NULL) {
+-      *number_out = number * sign;
++  // Apply the sign with uint64_t arithmetic so overflows aren't UB.  Gives
++  // "incorrect" results for out-of-range inputs, but negative values only
++  // appear for literals, which aren't printed.
++  if (negative) {
++    number = ~number + 1;
++  }
++  if (p != RemainingInput(state)) {  // Conversion succeeded.
++    state->parse_state.mangled_idx += p - RemainingInput(state);
++    if (number_out != nullptr) {
++      // Note: possibly truncate "number".
++      *number_out = static_cast<int>(number);
+     }
+     return true;
+   }
+@@ -661,14 +912,16 @@ static bool ParseNumber(State *state, int *number_out) {
+ // Floating-point literals are encoded using a fixed-length lowercase
+ // hexadecimal string.
+ static bool ParseFloatNumber(State *state) {
+-  const char *p = state->mangled_cur;
+-  for (;*p != '\0'; ++p) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  const char *p = RemainingInput(state);
++  for (; *p != '\0'; ++p) {
+     if (!IsDigit(*p) && !(*p >= 'a' && *p <= 'f')) {
+       break;
+     }
+   }
+-  if (p != state->mangled_cur) {  // Conversion succeeded.
+-    state->mangled_cur = p;
++  if (p != RemainingInput(state)) {  // Conversion succeeded.
++    state->parse_state.mangled_idx += p - RemainingInput(state);
+     return true;
+   }
+   return false;
+@@ -677,93 +930,85 @@ static bool ParseFloatNumber(State *state) {
+ // The <seq-id> is a sequence number in base 36,
+ // using digits and upper case letters
+ static bool ParseSeqId(State *state) {
+-  const char *p = state->mangled_cur;
+-  for (;*p != '\0'; ++p) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  const char *p = RemainingInput(state);
++  for (; *p != '\0'; ++p) {
+     if (!IsDigit(*p) && !(*p >= 'A' && *p <= 'Z')) {
+       break;
+     }
+   }
+-  if (p != state->mangled_cur) {  // Conversion succeeded.
+-    state->mangled_cur = p;
++  if (p != RemainingInput(state)) {  // Conversion succeeded.
++    state->parse_state.mangled_idx += p - RemainingInput(state);
+     return true;
+   }
+   return false;
+ }
+ 
+ // <identifier> ::= <unqualified source code identifier> (of given length)
+-static bool ParseIdentifier(State *state, ssize_t length) {
+-  if (length == -1 ||
+-      !AtLeastNumCharsRemaining(state->mangled_cur, length)) {
++static bool ParseIdentifier(State *state, size_t length) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  if (!AtLeastNumCharsRemaining(RemainingInput(state), length)) {
+     return false;
+   }
+   if (IdentifierIsAnonymousNamespace(state, length)) {
+     MaybeAppend(state, "(anonymous namespace)");
+   } else {
+-    MaybeAppendWithLength(state, state->mangled_cur, length);
++    MaybeAppendWithLength(state, RemainingInput(state), length);
+   }
+-  state->mangled_cur += length;
++  state->parse_state.mangled_idx += length;
+   return true;
+ }
+ 
+-// <abi-tags> ::= <abi-tag> [<abi-tags>]
+-static bool ParseAbiTags(State *state) {
+-  State copy = *state;
+-  DisableAppend(state);
+-  if (OneOrMore(ParseAbiTag, state)) {
+-    RestoreAppend(state, copy.append);
+-    return true;
+-  }
+-  *state = copy;
+-  return false;
+-}
+-
+-// <abi-tag> ::= B <source-name>
+-static bool ParseAbiTag(State *state) {
+-  return ParseOneCharToken(state, 'B') && ParseSourceName(state);
+-}
+-
+ // <operator-name> ::= nw, and other two letters cases
+ //                 ::= cv <type>  # (cast)
+ //                 ::= v  <digit> <source-name> # vendor extended operator
+-static bool ParseOperatorName(State *state) {
+-  if (!AtLeastNumCharsRemaining(state->mangled_cur, 2)) {
++static bool ParseOperatorName(State *state, int *arity) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  if (!AtLeastNumCharsRemaining(RemainingInput(state), 2)) {
+     return false;
+   }
+   // First check with "cv" (cast) case.
+-  State copy = *state;
+-  if (ParseTwoCharToken(state, "cv") &&
+-      MaybeAppend(state, "operator ") &&
+-      EnterNestedName(state) &&
+-      ParseType(state) &&
++  ParseState copy = state->parse_state;
++  if (ParseTwoCharToken(state, "cv") && MaybeAppend(state, "operator ") &&
++      EnterNestedName(state) && ParseType(state) &&
+       LeaveNestedName(state, copy.nest_level)) {
++    if (arity != nullptr) {
++      *arity = 1;
++    }
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   // Then vendor extended operators.
+-  if (ParseOneCharToken(state, 'v') && ParseCharClass(state, "0123456789") &&
++  if (ParseOneCharToken(state, 'v') && ParseDigit(state, arity) &&
+       ParseSourceName(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   // Other operator names should start with a lower alphabet followed
+   // by a lower/upper alphabet.
+-  if (!(IsLower(state->mangled_cur[0]) &&
+-        IsAlpha(state->mangled_cur[1]))) {
++  if (!(IsLower(RemainingInput(state)[0]) &&
++        IsAlpha(RemainingInput(state)[1]))) {
+     return false;
+   }
+   // We may want to perform a binary search if we really need speed.
+   const AbbrevPair *p;
+-  for (p = kOperatorList; p->abbrev != NULL; ++p) {
+-    if (state->mangled_cur[0] == p->abbrev[0] &&
+-        state->mangled_cur[1] == p->abbrev[1]) {
++  for (p = kOperatorList; p->abbrev != nullptr; ++p) {
++    if (RemainingInput(state)[0] == p->abbrev[0] &&
++        RemainingInput(state)[1] == p->abbrev[1]) {
++      if (arity != nullptr) {
++        *arity = p->arity;
++      }
+       MaybeAppend(state, "operator");
+       if (IsLower(*p->real_name)) {  // new, delete, etc.
+         MaybeAppend(state, " ");
+       }
+       MaybeAppend(state, p->real_name);
+-      state->mangled_cur += 2;
++      state->parse_state.mangled_idx += 2;
+       return true;
+     }
+   }
+@@ -774,6 +1019,7 @@ static bool ParseOperatorName(State *state) {
+ //                ::= TT <type>
+ //                ::= TI <type>
+ //                ::= TS <type>
++//                ::= TH <type>  # thread-local
+ //                ::= Tc <call-offset> <call-offset> <(base) encoding>
+ //                ::= GV <(object) name>
+ //                ::= T <call-offset> <(base) encoding>
+@@ -789,123 +1035,156 @@ static bool ParseOperatorName(State *state) {
+ // Note: we don't care much about them since they don't appear in
+ // stack traces.  The are special data.
+ static bool ParseSpecialName(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'T') &&
+-      ParseCharClass(state, "VTIS") &&
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "VTISH") &&
+       ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseTwoCharToken(state, "Tc") && ParseCallOffset(state) &&
+       ParseCallOffset(state) && ParseEncoding(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseTwoCharToken(state, "GV") &&
+-      ParseName(state)) {
++  if (ParseTwoCharToken(state, "GV") && ParseName(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'T') && ParseCallOffset(state) &&
+       ParseEncoding(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   // G++ extensions
+   if (ParseTwoCharToken(state, "TC") && ParseType(state) &&
+-      ParseNumber(state, NULL) && ParseOneCharToken(state, '_') &&
+-      DisableAppend(state) &&
+-      ParseType(state)) {
++      ParseNumber(state, nullptr) && ParseOneCharToken(state, '_') &&
++      DisableAppend(state) && ParseType(state)) {
+     RestoreAppend(state, copy.append);
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "FJ") &&
+       ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseTwoCharToken(state, "GR") && ParseName(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseTwoCharToken(state, "GA") && ParseEncoding(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "hv") &&
+       ParseCallOffset(state) && ParseEncoding(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <call-offset> ::= h <nv-offset> _
+ //               ::= v <v-offset> _
+ static bool ParseCallOffset(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'h') &&
+-      ParseNVOffset(state) && ParseOneCharToken(state, '_')) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'h') && ParseNVOffset(state) &&
++      ParseOneCharToken(state, '_')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseOneCharToken(state, 'v') &&
+-      ParseVOffset(state) && ParseOneCharToken(state, '_')) {
++  if (ParseOneCharToken(state, 'v') && ParseVOffset(state) &&
++      ParseOneCharToken(state, '_')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   return false;
+ }
+ 
+ // <nv-offset> ::= <(offset) number>
+ static bool ParseNVOffset(State *state) {
+-  return ParseNumber(state, NULL);
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  return ParseNumber(state, nullptr);
+ }
+ 
+ // <v-offset>  ::= <(offset) number> _ <(virtual offset) number>
+ static bool ParseVOffset(State *state) {
+-  State copy = *state;
+-  if (ParseNumber(state, NULL) && ParseOneCharToken(state, '_') &&
+-      ParseNumber(state, NULL)) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseNumber(state, nullptr) && ParseOneCharToken(state, '_') &&
++      ParseNumber(state, nullptr)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+-// <ctor-dtor-name> ::= C1 | C2 | C3
++// <ctor-dtor-name> ::= C1 | C2 | C3 | CI1 <base-class-type> | CI2
++// <base-class-type>
+ //                  ::= D0 | D1 | D2
++// # GCC extensions: "unified" constructor/destructor.  See
++// #
++// https://github.com/gcc-mirror/gcc/blob/7ad17b583c3643bd4557f29b8391ca7ef08391f5/gcc/cp/mangle.c#L1847
++//                  ::= C4 | D4
+ static bool ParseCtorDtorName(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'C') &&
+-      ParseCharClass(state, "123")) {
+-    const char * const prev_name = state->prev_name;
+-    const ssize_t prev_name_length = state->prev_name_length;
+-    MaybeAppendWithLength(state, prev_name, prev_name_length);
+-    return true;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'C')) {
++    if (ParseCharClass(state, "1234")) {
++      const char *const prev_name =
++          state->out + state->parse_state.prev_name_idx;
++      MaybeAppendWithLength(state, prev_name,
++                            state->parse_state.prev_name_length);
++      return true;
++    } else if (ParseOneCharToken(state, 'I') && ParseCharClass(state, "12") &&
++               ParseClassEnumType(state)) {
++      return true;
++    }
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseOneCharToken(state, 'D') &&
+-      ParseCharClass(state, "012")) {
+-    const char * const prev_name = state->prev_name;
+-    const ssize_t prev_name_length = state->prev_name_length;
++  if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "0124")) {
++    const char *const prev_name = state->out + state->parse_state.prev_name_idx;
+     MaybeAppend(state, "~");
+-    MaybeAppendWithLength(state, prev_name, prev_name_length);
++    MaybeAppendWithLength(state, prev_name,
++                          state->parse_state.prev_name_length);
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
++  return false;
++}
++
++// <decltype> ::= Dt <expression> E  # decltype of an id-expression or class
++//                                   # member access (C++0x)
++//            ::= DT <expression> E  # decltype of an expression (C++0x)
++static bool ParseDecltype(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "tT") &&
++      ParseExpression(state) && ParseOneCharToken(state, 'E')) {
++    return true;
++  }
++  state->parse_state = copy;
++
+   return false;
+ }
+ 
+@@ -918,67 +1197,87 @@ static bool ParseCtorDtorName(State *state) {
+ //        ::= U <source-name> <type>  # vendor extended type qualifier
+ //        ::= <builtin-type>
+ //        ::= <function-type>
+-//        ::= <class-enum-type>
++//        ::= <class-enum-type>  # note: just an alias for <name>
+ //        ::= <array-type>
+ //        ::= <pointer-to-member-type>
+ //        ::= <template-template-param> <template-args>
+ //        ::= <template-param>
++//        ::= <decltype>
+ //        ::= <substitution>
+ //        ::= Dp <type>          # pack expansion of (C++0x)
+-//        ::= Dt <expression> E  # decltype of an id-expression or class
+-//                               # member access (C++0x)
+-//        ::= DT <expression> E  # decltype of an expression (C++0x)
++//        ::= Dv <num-elems> _   # GNU vector extension
+ //
+ static bool ParseType(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++
+   // We should check CV-qualifers, and PRGC things first.
+-  State copy = *state;
+-  if (ParseCVQualifiers(state) && ParseType(state)) {
+-    return true;
++  //
++  // CV-qualifiers overlap with some operator names, but an operator name is not
++  // valid as a type.  To avoid an ambiguity that can lead to exponential time
++  // complexity, refuse to backtrack the CV-qualifiers.
++  //
++  // _Z4aoeuIrMvvE
++  //  => _Z 4aoeuI        rM  v     v   E
++  //         aoeu<operator%=, void, void>
++  //  => _Z 4aoeuI r Mv v              E
++  //         aoeu<void void::* restrict>
++  //
++  // By consuming the CV-qualifiers first, the former parse is disabled.
++  if (ParseCVQualifiers(state)) {
++    const bool result = ParseType(state);
++    if (!result) state->parse_state = copy;
++    return result;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseCharClass(state, "OPRCG") && ParseType(state)) {
+-    return true;
++  // Similarly, these tag characters can overlap with other <name>s resulting in
++  // two different parse prefixes that land on <template-args> in the same
++  // place, such as "C3r1xI...".  So, disable the "ctor-name = C3" parse by
++  // refusing to backtrack the tag characters.
++  if (ParseCharClass(state, "OPRCG")) {
++    const bool result = ParseType(state);
++    if (!result) state->parse_state = copy;
++    return result;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseTwoCharToken(state, "Dp") && ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
+-
+-  if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "tT") &&
+-      ParseExpression(state) && ParseOneCharToken(state, 'E')) {
+-    return true;
+-  }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'U') && ParseSourceName(state) &&
+       ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseBuiltinType(state) ||
+-      ParseFunctionType(state) ||
+-      ParseClassEnumType(state) ||
+-      ParseArrayType(state) ||
+-      ParsePointerToMemberType(state) ||
+-      ParseSubstitution(state)) {
++  if (ParseBuiltinType(state) || ParseFunctionType(state) ||
++      ParseClassEnumType(state) || ParseArrayType(state) ||
++      ParsePointerToMemberType(state) || ParseDecltype(state) ||
++      // "std" on its own isn't a type.
++      ParseSubstitution(state, /*accept_std=*/false)) {
+     return true;
+   }
+ 
+-  if (ParseTemplateTemplateParam(state) &&
+-      ParseTemplateArgs(state)) {
++  if (ParseTemplateTemplateParam(state) && ParseTemplateArgs(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   // Less greedy than <template-template-param> <template-args>.
+   if (ParseTemplateParam(state)) {
+     return true;
+   }
+ 
++  if (ParseTwoCharToken(state, "Dv") && ParseNumber(state, nullptr) &&
++      ParseOneCharToken(state, '_')) {
++    return true;
++  }
++  state->parse_state = copy;
++
+   return false;
+ }
+ 
+@@ -986,6 +1285,8 @@ static bool ParseType(State *state) {
+ // We don't allow empty <CV-qualifiers> to avoid infinite loop in
+ // ParseType().
+ static bool ParseCVQualifiers(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   int num_cv_qualifiers = 0;
+   num_cv_qualifiers += ParseOneCharToken(state, 'r');
+   num_cv_qualifiers += ParseOneCharToken(state, 'V');
+@@ -993,208 +1294,499 @@ static bool ParseCVQualifiers(State *state) {
+   return num_cv_qualifiers > 0;
+ }
+ 
+-// <builtin-type> ::= v, etc.
++// <builtin-type> ::= v, etc.  # single-character builtin types
+ //                ::= u <source-name>
++//                ::= Dd, etc.  # two-character builtin types
++//
++// Not supported:
++//                ::= DF <number> _ # _FloatN (N bits)
++//
+ static bool ParseBuiltinType(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   const AbbrevPair *p;
+-  for (p = kBuiltinTypeList; p->abbrev != NULL; ++p) {
+-    if (state->mangled_cur[0] == p->abbrev[0]) {
++  for (p = kBuiltinTypeList; p->abbrev != nullptr; ++p) {
++    // Guaranteed only 1- or 2-character strings in kBuiltinTypeList.
++    if (p->abbrev[1] == '\0') {
++      if (ParseOneCharToken(state, p->abbrev[0])) {
++        MaybeAppend(state, p->real_name);
++        return true;
++      }
++    } else if (p->abbrev[2] == '\0' && ParseTwoCharToken(state, p->abbrev)) {
+       MaybeAppend(state, p->real_name);
+-      ++state->mangled_cur;
+       return true;
+     }
+   }
+ 
+-  State copy = *state;
++  ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'u') && ParseSourceName(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+-// <function-type> ::= F [Y] <bare-function-type> E
++//  <exception-spec> ::= Do                # non-throwing
++//                                           exception-specification (e.g.,
++//                                           noexcept, throw())
++//                   ::= DO <expression> E # computed (instantiation-dependent)
++//                                           noexcept
++//                   ::= Dw <type>+ E      # dynamic exception specification
++//                                           with instantiation-dependent types
++static bool ParseExceptionSpec(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++
++  if (ParseTwoCharToken(state, "Do")) return true;
++
++  ParseState copy = state->parse_state;
++  if (ParseTwoCharToken(state, "DO") && ParseExpression(state) &&
++      ParseOneCharToken(state, 'E')) {
++    return true;
++  }
++  state->parse_state = copy;
++  if (ParseTwoCharToken(state, "Dw") && OneOrMore(ParseType, state) &&
++      ParseOneCharToken(state, 'E')) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  return false;
++}
++
++// <function-type> ::= [exception-spec] F [Y] <bare-function-type> [O] E
+ static bool ParseFunctionType(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'F') &&
+-      Optional(ParseOneCharToken(state, 'Y')) &&
+-      ParseBareFunctionType(state) && ParseOneCharToken(state, 'E')) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (Optional(ParseExceptionSpec(state)) && ParseOneCharToken(state, 'F') &&
++      Optional(ParseOneCharToken(state, 'Y')) && ParseBareFunctionType(state) &&
++      Optional(ParseOneCharToken(state, 'O')) &&
++      ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <bare-function-type> ::= <(signature) type>+
+ static bool ParseBareFunctionType(State *state) {
+-  State copy = *state;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
+   DisableAppend(state);
+   if (OneOrMore(ParseType, state)) {
+     RestoreAppend(state, copy.append);
+     MaybeAppend(state, "()");
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <class-enum-type> ::= <name>
+ static bool ParseClassEnumType(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   return ParseName(state);
+ }
+ 
+ // <array-type> ::= A <(positive dimension) number> _ <(element) type>
+ //              ::= A [<(dimension) expression>] _ <(element) type>
+ static bool ParseArrayType(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'A') && ParseNumber(state, NULL) &&
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'A') && ParseNumber(state, nullptr) &&
+       ParseOneCharToken(state, '_') && ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'A') && Optional(ParseExpression(state)) &&
+       ParseOneCharToken(state, '_') && ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <pointer-to-member-type> ::= M <(class) type> <(member) type>
+ static bool ParsePointerToMemberType(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'M') && ParseType(state) &&
+-      ParseType(state)) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'M') && ParseType(state) && ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <template-param> ::= T_
+ //                  ::= T <parameter-2 non-negative number> _
+ static bool ParseTemplateParam(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   if (ParseTwoCharToken(state, "T_")) {
+     MaybeAppend(state, "?");  // We don't support template substitutions.
+     return true;
+   }
+ 
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'T') && ParseNumber(state, NULL) &&
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'T') && ParseNumber(state, nullptr) &&
+       ParseOneCharToken(state, '_')) {
+     MaybeAppend(state, "?");  // We don't support template substitutions.
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+-
+ // <template-template-param> ::= <template-param>
+ //                           ::= <substitution>
+ static bool ParseTemplateTemplateParam(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   return (ParseTemplateParam(state) ||
+-          ParseSubstitution(state));
++          // "std" on its own isn't a template.
++          ParseSubstitution(state, /*accept_std=*/false));
+ }
+ 
+ // <template-args> ::= I <template-arg>+ E
+ static bool ParseTemplateArgs(State *state) {
+-  State copy = *state;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
+   DisableAppend(state);
+-  if (ParseOneCharToken(state, 'I') &&
+-      OneOrMore(ParseTemplateArg, state) &&
++  if (ParseOneCharToken(state, 'I') && OneOrMore(ParseTemplateArg, state) &&
+       ParseOneCharToken(state, 'E')) {
+     RestoreAppend(state, copy.append);
+     MaybeAppend(state, "<>");
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <template-arg>  ::= <type>
+ //                 ::= <expr-primary>
+-//                 ::= I <template-arg>* E        # argument pack
+ //                 ::= J <template-arg>* E        # argument pack
+ //                 ::= X <expression> E
+ static bool ParseTemplateArg(State *state) {
+-  State copy = *state;
+-  if ((ParseOneCharToken(state, 'I') || ParseOneCharToken(state, 'J')) &&
+-      ZeroOrMore(ParseTemplateArg, state) &&
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'J') && ZeroOrMore(ParseTemplateArg, state) &&
+       ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
++
++  // There can be significant overlap between the following leading to
++  // exponential backtracking:
++  //
++  //   <expr-primary> ::= L <type> <expr-cast-value> E
++  //                 e.g. L 2xxIvE 1                 E
++  //   <type>         ==> <local-source-name> <template-args>
++  //                 e.g. L 2xx               IvE
++  //
++  // This means parsing an entire <type> twice, and <type> can contain
++  // <template-arg>, so this can generate exponential backtracking.  There is
++  // only overlap when the remaining input starts with "L <source-name>", so
++  // parse all cases that can start this way jointly to share the common prefix.
++  //
++  // We have:
++  //
++  //   <template-arg> ::= <type>
++  //                  ::= <expr-primary>
++  //
++  // First, drop all the productions of <type> that must start with something
++  // other than 'L'.  All that's left is <class-enum-type>; inline it.
++  //
++  //   <type> ::= <nested-name> # starts with 'N'
++  //          ::= <unscoped-name>
++  //          ::= <unscoped-template-name> <template-args>
++  //          ::= <local-name> # starts with 'Z'
++  //
++  // Drop and inline again:
++  //
++  //   <type> ::= <unscoped-name>
++  //          ::= <unscoped-name> <template-args>
++  //          ::= <substitution> <template-args> # starts with 'S'
++  //
++  // Merge the first two, inline <unscoped-name>, drop last:
++  //
++  //   <type> ::= <unqualified-name> [<template-args>]
++  //          ::= St <unqualified-name> [<template-args>] # starts with 'S'
++  //
++  // Drop and inline:
++  //
++  //   <type> ::= <operator-name> [<template-args>] # starts with lowercase
++  //          ::= <ctor-dtor-name> [<template-args>] # starts with 'C' or 'D'
++  //          ::= <source-name> [<template-args>] # starts with digit
++  //          ::= <local-source-name> [<template-args>]
++  //          ::= <unnamed-type-name> [<template-args>] # starts with 'U'
++  //
++  // One more time:
++  //
++  //   <type> ::= L <source-name> [<template-args>]
++  //
++  // Likewise with <expr-primary>:
++  //
++  //   <expr-primary> ::= L <type> <expr-cast-value> E
++  //                  ::= LZ <encoding> E # cannot overlap; drop
++  //                  ::= L <mangled_name> E # cannot overlap; drop
++  //
++  // By similar reasoning as shown above, the only <type>s starting with
++  // <source-name> are "<source-name> [<template-args>]".  Inline this.
++  //
++  //   <expr-primary> ::= L <source-name> [<template-args>] <expr-cast-value> E
++  //
++  // Now inline both of these into <template-arg>:
++  //
++  //   <template-arg> ::= L <source-name> [<template-args>]
++  //                  ::= L <source-name> [<template-args>] <expr-cast-value> E
++  //
++  // Merge them and we're done:
++  //   <template-arg>
++  //     ::= L <source-name> [<template-args>] [<expr-cast-value> E]
++  if (ParseLocalSourceName(state) && Optional(ParseTemplateArgs(state))) {
++    copy = state->parse_state;
++    if (ParseExprCastValue(state) && ParseOneCharToken(state, 'E')) {
++      return true;
++    }
++    state->parse_state = copy;
++    return true;
++  }
+ 
+-  if (ParseType(state) ||
+-      ParseExprPrimary(state)) {
++  // Now that the overlapping cases can't reach this code, we can safely call
++  // both of these.
++  if (ParseType(state) || ParseExprPrimary(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'X') && ParseExpression(state) &&
+       ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+-// <expression> ::= <template-param>
+-//              ::= <expr-primary>
+-//              ::= <unary operator-name> <expression>
+-//              ::= <binary operator-name> <expression> <expression>
+-//              ::= <trinary operator-name> <expression> <expression>
+-//                  <expression>
++// <unresolved-type> ::= <template-param> [<template-args>]
++//                   ::= <decltype>
++//                   ::= <substitution>
++static inline bool ParseUnresolvedType(State *state) {
++  // No ComplexityGuard because we don't copy the state in this stack frame.
++  return (ParseTemplateParam(state) && Optional(ParseTemplateArgs(state))) ||
++         ParseDecltype(state) || ParseSubstitution(state, /*accept_std=*/false);
++}
++
++// <simple-id> ::= <source-name> [<template-args>]
++static inline bool ParseSimpleId(State *state) {
++  // No ComplexityGuard because we don't copy the state in this stack frame.
++
++  // Note: <simple-id> cannot be followed by a parameter pack; see comment in
++  // ParseUnresolvedType.
++  return ParseSourceName(state) && Optional(ParseTemplateArgs(state));
++}
++
++// <base-unresolved-name> ::= <source-name> [<template-args>]
++//                        ::= on <operator-name> [<template-args>]
++//                        ::= dn <destructor-name>
++static bool ParseBaseUnresolvedName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++
++  if (ParseSimpleId(state)) {
++    return true;
++  }
++
++  ParseState copy = state->parse_state;
++  if (ParseTwoCharToken(state, "on") && ParseOperatorName(state, nullptr) &&
++      Optional(ParseTemplateArgs(state))) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  if (ParseTwoCharToken(state, "dn") &&
++      (ParseUnresolvedType(state) || ParseSimpleId(state))) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  return false;
++}
++
++// <unresolved-name> ::= [gs] <base-unresolved-name>
++//                   ::= sr <unresolved-type> <base-unresolved-name>
++//                   ::= srN <unresolved-type> <unresolved-qualifier-level>+ E
++//                         <base-unresolved-name>
++//                   ::= [gs] sr <unresolved-qualifier-level>+ E
++//                         <base-unresolved-name>
++static bool ParseUnresolvedName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++
++  ParseState copy = state->parse_state;
++  if (Optional(ParseTwoCharToken(state, "gs")) &&
++      ParseBaseUnresolvedName(state)) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  if (ParseTwoCharToken(state, "sr") && ParseUnresolvedType(state) &&
++      ParseBaseUnresolvedName(state)) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  if (ParseTwoCharToken(state, "sr") && ParseOneCharToken(state, 'N') &&
++      ParseUnresolvedType(state) &&
++      OneOrMore(/* <unresolved-qualifier-level> ::= */ ParseSimpleId, state) &&
++      ParseOneCharToken(state, 'E') && ParseBaseUnresolvedName(state)) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  if (Optional(ParseTwoCharToken(state, "gs")) &&
++      ParseTwoCharToken(state, "sr") &&
++      OneOrMore(/* <unresolved-qualifier-level> ::= */ ParseSimpleId, state) &&
++      ParseOneCharToken(state, 'E') && ParseBaseUnresolvedName(state)) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  return false;
++}
++
++// <expression> ::= <1-ary operator-name> <expression>
++//              ::= <2-ary operator-name> <expression> <expression>
++//              ::= <3-ary operator-name> <expression> <expression> <expression>
++//              ::= cl <expression>+ E
++//              ::= cp <simple-id> <expression>* E # Clang-specific.
++//              ::= cv <type> <expression>      # type (expression)
++//              ::= cv <type> _ <expression>* E # type (expr-list)
+ //              ::= st <type>
++//              ::= <template-param>
++//              ::= <function-param>
++//              ::= <expr-primary>
++//              ::= dt <expression> <unresolved-name> # expr.name
++//              ::= pt <expression> <unresolved-name> # expr->name
++//              ::= sp <expression>         # argument pack expansion
+ //              ::= sr <type> <unqualified-name> <template-args>
+ //              ::= sr <type> <unqualified-name>
++// <function-param> ::= fp <(top-level) CV-qualifiers> _
++//                  ::= fp <(top-level) CV-qualifiers> <number> _
++//                  ::= fL <number> p <(top-level) CV-qualifiers> _
++//                  ::= fL <number> p <(top-level) CV-qualifiers> <number> _
+ static bool ParseExpression(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   if (ParseTemplateParam(state) || ParseExprPrimary(state)) {
+     return true;
+   }
+ 
+-  State copy = *state;
+-  if (ParseOperatorName(state) &&
+-      ParseExpression(state) &&
+-      ParseExpression(state) &&
+-      ParseExpression(state)) {
++  ParseState copy = state->parse_state;
++
++  // Object/function call expression.
++  if (ParseTwoCharToken(state, "cl") && OneOrMore(ParseExpression, state) &&
++      ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseOperatorName(state) &&
+-      ParseExpression(state) &&
+-      ParseExpression(state)) {
++  // Clang-specific "cp <simple-id> <expression>* E"
++  //   https://clang.llvm.org/doxygen/ItaniumMangle_8cpp_source.html#l04338
++  if (ParseTwoCharToken(state, "cp") && ParseSimpleId(state) &&
++      ZeroOrMore(ParseExpression, state) && ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseOperatorName(state) &&
+-      ParseExpression(state)) {
++  // Function-param expression (level 0).
++  if (ParseTwoCharToken(state, "fp") && Optional(ParseCVQualifiers(state)) &&
++      Optional(ParseNumber(state, nullptr)) && ParseOneCharToken(state, '_')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
++  // Function-param expression (level 1+).
++  if (ParseTwoCharToken(state, "fL") && Optional(ParseNumber(state, nullptr)) &&
++      ParseOneCharToken(state, 'p') && Optional(ParseCVQualifiers(state)) &&
++      Optional(ParseNumber(state, nullptr)) && ParseOneCharToken(state, '_')) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  // Parse the conversion expressions jointly to avoid re-parsing the <type> in
++  // their common prefix.  Parsed as:
++  // <expression> ::= cv <type> <conversion-args>
++  // <conversion-args> ::= _ <expression>* E
++  //                   ::= <expression>
++  //
++  // Also don't try ParseOperatorName after seeing "cv", since ParseOperatorName
++  // also needs to accept "cv <type>" in other contexts.
++  if (ParseTwoCharToken(state, "cv")) {
++    if (ParseType(state)) {
++      ParseState copy2 = state->parse_state;
++      if (ParseOneCharToken(state, '_') && ZeroOrMore(ParseExpression, state) &&
++          ParseOneCharToken(state, 'E')) {
++        return true;
++      }
++      state->parse_state = copy2;
++      if (ParseExpression(state)) {
++        return true;
++      }
++    }
++  } else {
++    // Parse unary, binary, and ternary operator expressions jointly, taking
++    // care not to re-parse subexpressions repeatedly. Parse like:
++    //   <expression> ::= <operator-name> <expression>
++    //                    [<one-to-two-expressions>]
++    //   <one-to-two-expressions> ::= <expression> [<expression>]
++    int arity = -1;
++    if (ParseOperatorName(state, &arity) &&
++        arity > 0 &&  // 0 arity => disabled.
++        (arity < 3 || ParseExpression(state)) &&
++        (arity < 2 || ParseExpression(state)) &&
++        (arity < 1 || ParseExpression(state))) {
++      return true;
++    }
++  }
++  state->parse_state = copy;
++
++  // sizeof type
+   if (ParseTwoCharToken(state, "st") && ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseTwoCharToken(state, "sr") && ParseType(state) &&
+-      ParseUnqualifiedName(state) &&
+-      ParseTemplateArgs(state)) {
++  // Object and pointer member access expressions.
++  if ((ParseTwoCharToken(state, "dt") || ParseTwoCharToken(state, "pt")) &&
++      ParseExpression(state) && ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseTwoCharToken(state, "sr") && ParseType(state) &&
+-      ParseUnqualifiedName(state)) {
++  // Pointer-to-member access expressions.  This parses the same as a binary
++  // operator, but it's implemented separately because "ds" shouldn't be
++  // accepted in other contexts that parse an operator name.
++  if (ParseTwoCharToken(state, "ds") && ParseExpression(state) &&
++      ParseExpression(state)) {
+     return true;
+   }
+-  *state = copy;
+-  return false;
++  state->parse_state = copy;
++
++  // Parameter pack expansion
++  if (ParseTwoCharToken(state, "sp") && ParseExpression(state)) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  return ParseUnresolvedName(state);
+ }
+ 
+ // <expr-primary> ::= L <type> <(value) number> E
+@@ -1202,116 +1794,194 @@ static bool ParseExpression(State *state) {
+ //                ::= L <mangled-name> E
+ //                // A bug in g++'s C++ ABI version 2 (-fabi-version=2).
+ //                ::= LZ <encoding> E
++//
++// Warning, subtle: the "bug" LZ production above is ambiguous with the first
++// production where <type> starts with <local-name>, which can lead to
++// exponential backtracking in two scenarios:
++//
++// - When whatever follows the E in the <local-name> in the first production is
++//   not a name, we backtrack the whole <encoding> and re-parse the whole thing.
++//
++// - When whatever follows the <local-name> in the first production is not a
++//   number and this <expr-primary> may be followed by a name, we backtrack the
++//   <name> and re-parse it.
++//
++// Moreover this ambiguity isn't always resolved -- for example, the following
++// has two different parses:
++//
++//   _ZaaILZ4aoeuE1x1EvE
++//   => operator&&<aoeu, x, E, void>
++//   => operator&&<(aoeu::x)(1), void>
++//
++// To resolve this, we just do what GCC's demangler does, and refuse to parse
++// casts to <local-name> types.
+ static bool ParseExprPrimary(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'L') && ParseType(state) &&
+-      ParseNumber(state, NULL) &&
+-      ParseOneCharToken(state, 'E')) {
+-    return true;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++
++  // The "LZ" special case: if we see LZ, we commit to accept "LZ <encoding> E"
++  // or fail, no backtracking.
++  if (ParseTwoCharToken(state, "LZ")) {
++    if (ParseEncoding(state) && ParseOneCharToken(state, 'E')) {
++      return true;
++    }
++
++    state->parse_state = copy;
++    return false;
+   }
+-  *state = copy;
+ 
++  // The merged cast production.
+   if (ParseOneCharToken(state, 'L') && ParseType(state) &&
+-      ParseFloatNumber(state) &&
+-      ParseOneCharToken(state, 'E')) {
++      ParseExprCastValue(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'L') && ParseMangledName(state) &&
+       ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseTwoCharToken(state, "LZ") && ParseEncoding(state) &&
+-      ParseOneCharToken(state, 'E')) {
++  return false;
++}
++
++// <number> or <float>, followed by 'E', as described above ParseExprPrimary.
++static bool ParseExprCastValue(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  // We have to be able to backtrack after accepting a number because we could
++  // have e.g. "7fffE", which will accept "7" as a number but then fail to find
++  // the 'E'.
++  ParseState copy = state->parse_state;
++  if (ParseNumber(state, nullptr) && ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
++
++  if (ParseFloatNumber(state) && ParseOneCharToken(state, 'E')) {
++    return true;
++  }
++  state->parse_state = copy;
+ 
+   return false;
+ }
+ 
+-// <local-name> := Z <(function) encoding> E <(entity) name>
+-//                 [<discriminator>]
+-//              := Z <(function) encoding> E s [<discriminator>]
+-static bool ParseLocalName(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'Z') && ParseEncoding(state) &&
+-      ParseOneCharToken(state, 'E') && MaybeAppend(state, "::") &&
+-      ParseName(state) && Optional(ParseDiscriminator(state))) {
++// <local-name> ::= Z <(function) encoding> E <(entity) name> [<discriminator>]
++//              ::= Z <(function) encoding> E s [<discriminator>]
++//
++// Parsing a common prefix of these two productions together avoids an
++// exponential blowup of backtracking.  Parse like:
++//   <local-name> := Z <encoding> E <local-name-suffix>
++//   <local-name-suffix> ::= s [<discriminator>]
++//                       ::= <name> [<discriminator>]
++
++static bool ParseLocalNameSuffix(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++
++  if (MaybeAppend(state, "::") && ParseName(state) &&
++      Optional(ParseDiscriminator(state))) {
+     return true;
+   }
+-  *state = copy;
+ 
++  // Since we're not going to overwrite the above "::" by re-parsing the
++  // <encoding> (whose trailing '\0' byte was in the byte now holding the
++  // first ':'), we have to rollback the "::" if the <name> parse failed.
++  if (state->parse_state.append) {
++    state->out[state->parse_state.out_cur_idx - 2] = '\0';
++  }
++
++  return ParseOneCharToken(state, 's') && Optional(ParseDiscriminator(state));
++}
++
++static bool ParseLocalName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'Z') && ParseEncoding(state) &&
+-      ParseTwoCharToken(state, "Es") && Optional(ParseDiscriminator(state))) {
++      ParseOneCharToken(state, 'E') && ParseLocalNameSuffix(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <discriminator> := _ <(non-negative) number>
+ static bool ParseDiscriminator(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, '_') && ParseNumber(state, NULL)) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, '_') && ParseNumber(state, nullptr)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <substitution> ::= S_
+ //                ::= S <seq-id> _
+ //                ::= St, etc.
+-static bool ParseSubstitution(State *state) {
++//
++// "St" is special in that it's not valid as a standalone name, and it *is*
++// allowed to precede a name without being wrapped in "N...E".  This means that
++// if we accept it on its own, we can accept "St1a" and try to parse
++// template-args, then fail and backtrack, accept "St" on its own, then "1a" as
++// an unqualified name and re-parse the same template-args.  To block this
++// exponential backtracking, we disable it with 'accept_std=false' in
++// problematic contexts.
++static bool ParseSubstitution(State *state, bool accept_std) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   if (ParseTwoCharToken(state, "S_")) {
+     MaybeAppend(state, "?");  // We don't support substitutions.
+     return true;
+   }
+ 
+-  State copy = *state;
++  ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'S') && ParseSeqId(state) &&
+       ParseOneCharToken(state, '_')) {
+     MaybeAppend(state, "?");  // We don't support substitutions.
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   // Expand abbreviations like "St" => "std".
+   if (ParseOneCharToken(state, 'S')) {
+     const AbbrevPair *p;
+-    for (p = kSubstitutionList; p->abbrev != NULL; ++p) {
+-      if (state->mangled_cur[0] == p->abbrev[1]) {
++    for (p = kSubstitutionList; p->abbrev != nullptr; ++p) {
++      if (RemainingInput(state)[0] == p->abbrev[1] &&
++          (accept_std || p->abbrev[1] != 't')) {
+         MaybeAppend(state, "std");
+         if (p->real_name[0] != '\0') {
+           MaybeAppend(state, "::");
+           MaybeAppend(state, p->real_name);
+         }
+-        ++state->mangled_cur;
++        ++state->parse_state.mangled_idx;
+         return true;
+       }
+     }
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // Parse <mangled-name>, optionally followed by either a function-clone suffix
+ // or version suffix.  Returns true only if all of "mangled_cur" was consumed.
+ static bool ParseTopLevelMangledName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   if (ParseMangledName(state)) {
+-    if (state->mangled_cur[0] != '\0') {
++    if (RemainingInput(state)[0] != '\0') {
+       // Drop trailing function clone suffix, if any.
+-      if (IsFunctionCloneSuffix(state->mangled_cur)) {
++      if (IsFunctionCloneSuffix(RemainingInput(state))) {
+         return true;
+       }
+       // Append trailing version suffix if any.
+       // ex. _Z3foo@@GLIBCXX_3.4
+-      if (state->mangled_cur[0] == '@') {
+-        MaybeAppend(state, state->mangled_cur);
++      if (RemainingInput(state)[0] == '@') {
++        MaybeAppend(state, RemainingInput(state));
+         return true;
+       }
+       return false;  // Unconsumed suffix.
+@@ -1320,6 +1990,10 @@ static bool ParseTopLevelMangledName(State *state) {
+   }
+   return false;
+ }
++
++static bool Overflowed(const State *state) {
++  return state->parse_state.out_cur_idx >= state->out_end_idx;
++}
+ #endif
+ 
+ // The demangler entry point.
+@@ -1356,7 +2030,8 @@ bool Demangle(const char *mangled, char *out, size_t out_size) {
+ #else
+   State state;
+   InitState(&state, mangled, out, out_size);
+-  return ParseTopLevelMangledName(&state) && !state.overflowed;
++  return ParseTopLevelMangledName(&state) && !Overflowed(&state) &&
++         state.parse_state.out_cur_idx > 0;
+ #endif
+ }
+ 
+diff --git a/base/third_party/symbolize/demangle.h b/base/third_party/symbolize/demangle.h
+index 416f7ee153560..26e821a53c2cb 100644
+--- a/base/third_party/symbolize/demangle.h
++++ b/base/third_party/symbolize/demangle.h
+@@ -70,6 +70,8 @@
+ #ifndef BASE_DEMANGLE_H_
+ #define BASE_DEMANGLE_H_
+ 
++#include <stddef.h>
++
+ #include "config.h"
+ #include "glog/logging.h"
+ 
diff --git a/base/third_party/symbolize/patches/010-clang-format.patch b/base/third_party/symbolize/patches/010-clang-format.patch
new file mode 100644
index 0000000..3e786f0
--- /dev/null
+++ b/base/third_party/symbolize/patches/010-clang-format.patch
@@ -0,0 +1,1184 @@
+diff --git a/base/third_party/symbolize/demangle.cc b/base/third_party/symbolize/demangle.cc
+index 2632646dd4072..8db75f01071e2 100644
+--- a/base/third_party/symbolize/demangle.cc
++++ b/base/third_party/symbolize/demangle.cc
+@@ -139,8 +139,8 @@ static const AbbrevPair kBuiltinTypeList[] = {
+     {"g", "__float128", 0},
+     {"z", "ellipsis", 0},
+ 
+-    {"De", "decimal128", 0},      // IEEE 754r decimal floating point (128 bits)
+-    {"Dd", "decimal64", 0},       // IEEE 754r decimal floating point (64 bits)
++    {"De", "decimal128", 0},  // IEEE 754r decimal floating point (128 bits)
++    {"Dd", "decimal64", 0},   // IEEE 754r decimal floating point (64 bits)
+     {"Dc", "decltype(auto)", 0},
+     {"Da", "auto", 0},
+     {"Dn", "std::nullptr_t", 0},  // i.e., decltype(nullptr)
+@@ -148,7 +148,7 @@ static const AbbrevPair kBuiltinTypeList[] = {
+     {"Di", "char32_t", 0},
+     {"Du", "char8_t", 0},
+     {"Ds", "char16_t", 0},
+-    {"Dh", "float16", 0},         // IEEE 754r half-precision float (16 bits)
++    {"Dh", "float16", 0},  // IEEE 754r half-precision float (16 bits)
+     {nullptr, nullptr, 0},
+ };
+ 
+@@ -193,8 +193,8 @@ static_assert(sizeof(ParseState) == 4 * sizeof(int),
+ // Only one copy of this exists for each call to Demangle, so the size of this
+ // struct is nearly inconsequential.
+ typedef struct {
+-  const char *mangled_begin;  // Beginning of input string.
+-  char *out;                  // Beginning of output string.
++  const char* mangled_begin;  // Beginning of input string.
++  char* out;                  // Beginning of output string.
+   int out_end_idx;            // One past last allowed output character.
+   int recursion_depth;        // For stack exhaustion prevention.
+   int steps;               // Cap how much work we'll do, regardless of depth.
+@@ -206,7 +206,7 @@ namespace {
+ // Also prevent unbounded handling of complex inputs.
+ class ComplexityGuard {
+  public:
+-  explicit ComplexityGuard(State *state) : state_(state) {
++  explicit ComplexityGuard(State* state) : state_(state) {
+     ++state->recursion_depth;
+     ++state->steps;
+   }
+@@ -239,7 +239,7 @@ class ComplexityGuard {
+   }
+ 
+  private:
+-  State *state_;
++  State* state_;
+ };
+ }  // namespace
+ 
+@@ -255,7 +255,7 @@ static size_t StrLen(const char *str) {
+ }
+ 
+ // Returns true if "str" has at least "n" characters remaining.
+-static bool AtLeastNumCharsRemaining(const char *str, size_t n) {
++static bool AtLeastNumCharsRemaining(const char* str, size_t n) {
+   for (size_t i = 0; i < n; ++i) {
+     if (str[i] == '\0') {
+       return false;
+@@ -291,7 +291,7 @@ static void InitState(State* state,
+   state->parse_state.append = true;
+ }
+ 
+-static inline const char *RemainingInput(State *state) {
++static inline const char* RemainingInput(State* state) {
+   return &state->mangled_begin[state->parse_state.mangled_idx];
+ }
+ 
+@@ -300,7 +300,9 @@ static inline const char *RemainingInput(State *state) {
+ // not contain '\0'.
+ static bool ParseOneCharToken(State *state, const char one_char_token) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (RemainingInput(state)[0] == one_char_token) {
+     ++state->parse_state.mangled_idx;
+     return true;
+@@ -313,7 +315,9 @@ static bool ParseOneCharToken(State *state, const char one_char_token) {
+ // not contain '\0'.
+ static bool ParseTwoCharToken(State *state, const char *two_char_token) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (RemainingInput(state)[0] == two_char_token[0] &&
+       RemainingInput(state)[1] == two_char_token[1]) {
+     state->parse_state.mangled_idx += 2;
+@@ -326,7 +330,9 @@ static bool ParseTwoCharToken(State *state, const char *two_char_token) {
+ // "char_class" at "mangled_cur" position.
+ static bool ParseCharClass(State *state, const char *char_class) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (RemainingInput(state)[0] == '\0') {
+     return false;
+   }
+@@ -340,7 +346,7 @@ static bool ParseCharClass(State *state, const char *char_class) {
+   return false;
+ }
+ 
+-static bool ParseDigit(State *state, int *digit) {
++static bool ParseDigit(State* state, int* digit) {
+   char c = RemainingInput(state)[0];
+   if (ParseCharClass(state, "0123456789")) {
+     if (digit != nullptr) {
+@@ -352,7 +358,9 @@ static bool ParseDigit(State *state, int *digit) {
+ }
+ 
+ // This function is used for handling an optional non-terminal.
+-static bool Optional(bool /*status*/) { return true; }
++static bool Optional(bool /*status*/) {
++  return true;
++}
+ 
+ // This function is used for handling <non-terminal>+ syntax.
+ typedef bool (*ParseFunc)(State *);
+@@ -378,7 +386,7 @@ static bool ZeroOrMore(ParseFunc parse_func, State *state) {
+ // Append "str" at "out_cur_idx".  If there is an overflow, out_cur_idx is
+ // set to out_end_idx+1.  The output string is ensured to
+ // always terminate with '\0' as long as there is no overflow.
+-static void Append(State *state, const char *const str, const size_t length) {
++static void Append(State* state, const char* const str, const size_t length) {
+   for (size_t i = 0; i < length; ++i) {
+     if (state->parse_state.out_cur_idx + 1 <
+         state->out_end_idx) {  // +1 for '\0'
+@@ -396,13 +404,17 @@ static void Append(State *state, const char *const str, const size_t length) {
+ }
+ 
+ // We don't use equivalents in libc to avoid locale issues.
+-static bool IsLower(char c) { return c >= 'a' && c <= 'z'; }
++static bool IsLower(char c) {
++  return c >= 'a' && c <= 'z';
++}
+ 
+ static bool IsAlpha(char c) {
+   return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+ }
+ 
+-static bool IsDigit(char c) { return c >= '0' && c <= '9'; }
++static bool IsDigit(char c) {
++  return c >= '0' && c <= '9';
++}
+ 
+ // Returns true if "str" is a function clone suffix.  These suffixes are used
+ // by GCC 4.5.x and later versions (and our locally-modified version of GCC
+@@ -428,20 +440,22 @@ static bool IsFunctionCloneSuffix(const char *str) {
+         ++i;
+       }
+     }
+-    if (!parsed)
++    if (!parsed) {
+       return false;
++    }
+   }
+   return true;  // Consumed everything in "str".
+ }
+ 
+-static bool EndsWith(State *state, const char chr) {
++static bool EndsWith(State* state, const char chr) {
+   return state->parse_state.out_cur_idx > 0 &&
+          state->parse_state.out_cur_idx < state->out_end_idx &&
+          chr == state->out[state->parse_state.out_cur_idx - 1];
+ }
+ 
+ // Append "str" with some tweaks, iff "append" state is true.
+-static void MaybeAppendWithLength(State *state, const char *const str,
++static void MaybeAppendWithLength(State* state,
++                                  const char* const str,
+                                   const size_t length) {
+   if (state->parse_state.append && length > 0) {
+     // Append a space if the output buffer ends with '<' and "str"
+@@ -461,7 +475,7 @@ static void MaybeAppendWithLength(State *state, const char *const str,
+ }
+ 
+ // Appends a positive decimal number to the output if appending is enabled.
+-static bool MaybeAppendDecimal(State *state, int val) {
++static bool MaybeAppendDecimal(State* state, int val) {
+   // Max {32-64}-bit unsigned int is 20 digits.
+   constexpr size_t kMaxLength = 20;
+   char buf[kMaxLength];
+@@ -471,7 +485,7 @@ static bool MaybeAppendDecimal(State *state, int val) {
+   if (state->parse_state.append) {
+     // We can't have a one-before-the-beginning pointer, so instead start with
+     // one-past-the-end and manipulate one character before the pointer.
+-    char *p = &buf[kMaxLength];
++    char* p = &buf[kMaxLength];
+     do {  // val=0 is the only input that should write a leading zero digit.
+       *--p = static_cast<char>((val % 10) + '0');
+       val /= 10;
+@@ -486,7 +500,7 @@ static bool MaybeAppendDecimal(State *state, int val) {
+ 
+ // A convenient wrapper around MaybeAppendWithLength().
+ // Returns true so that it can be placed in "if" conditions.
+-static bool MaybeAppend(State *state, const char *const str) {
++static bool MaybeAppend(State* state, const char* const str) {
+   if (state->parse_state.append) {
+     size_t length = StrLen(str);
+     MaybeAppendWithLength(state, str, length);
+@@ -501,7 +515,7 @@ static bool EnterNestedName(State *state) {
+ }
+ 
+ // This function is used for handling nested names.
+-static bool LeaveNestedName(State *state, int16_t prev_value) {
++static bool LeaveNestedName(State* state, int16_t prev_value) {
+   state->parse_state.nest_level = prev_value;
+   return true;
+ }
+@@ -543,7 +557,7 @@ static void MaybeCancelLastSeparator(State *state) {
+ 
+ // Returns true if the identifier of the given length pointed to by
+ // "mangled_cur" is anonymous namespace.
+-static bool IdentifierIsAnonymousNamespace(State *state, size_t length) {
++static bool IdentifierIsAnonymousNamespace(State* state, size_t length) {
+   // Returns true if "anon_prefix" is a proper prefix of "mangled_cur".
+   static const char anon_prefix[] = "_GLOBAL__N_";
+   return (length > (sizeof(anon_prefix) - 1) &&
+@@ -554,25 +568,25 @@ static bool IdentifierIsAnonymousNamespace(State *state, size_t length) {
+ static bool ParseMangledName(State *state);
+ static bool ParseEncoding(State *state);
+ static bool ParseName(State *state);
+-static bool ParseUnscopedName(State *state);
++static bool ParseUnscopedName(State* state);
+ static bool ParseNestedName(State *state);
+ static bool ParsePrefix(State *state);
+ static bool ParseUnqualifiedName(State *state);
+ static bool ParseSourceName(State *state);
+ static bool ParseLocalSourceName(State *state);
+-static bool ParseUnnamedTypeName(State *state);
++static bool ParseUnnamedTypeName(State* state);
+ static bool ParseNumber(State *state, int *number_out);
+ static bool ParseFloatNumber(State *state);
+ static bool ParseSeqId(State *state);
+-static bool ParseIdentifier(State *state, size_t length);
+-static bool ParseOperatorName(State *state, int *arity);
++static bool ParseIdentifier(State* state, size_t length);
++static bool ParseOperatorName(State* state, int* arity);
+ static bool ParseSpecialName(State *state);
+ static bool ParseCallOffset(State *state);
+ static bool ParseNVOffset(State *state);
+ static bool ParseVOffset(State *state);
+-static bool ParseAbiTags(State *state);
++static bool ParseAbiTags(State* state);
+ static bool ParseCtorDtorName(State *state);
+-static bool ParseDecltype(State *state);
++static bool ParseDecltype(State* state);
+ static bool ParseType(State *state);
+ static bool ParseCVQualifiers(State *state);
+ static bool ParseBuiltinType(State *state);
+@@ -585,15 +599,15 @@ static bool ParseTemplateParam(State *state);
+ static bool ParseTemplateTemplateParam(State *state);
+ static bool ParseTemplateArgs(State *state);
+ static bool ParseTemplateArg(State *state);
+-static bool ParseBaseUnresolvedName(State *state);
+-static bool ParseUnresolvedName(State *state);
++static bool ParseBaseUnresolvedName(State* state);
++static bool ParseUnresolvedName(State* state);
+ static bool ParseExpression(State *state);
+ static bool ParseExprPrimary(State *state);
+-static bool ParseExprCastValue(State *state);
++static bool ParseExprCastValue(State* state);
+ static bool ParseLocalName(State *state);
+-static bool ParseLocalNameSuffix(State *state);
++static bool ParseLocalNameSuffix(State* state);
+ static bool ParseDiscriminator(State *state);
+-static bool ParseSubstitution(State *state, bool accept_std);
++static bool ParseSubstitution(State* state, bool accept_std);
+ 
+ // Implementation note: the following code is a straightforward
+ // translation of the Itanium C++ ABI defined in BNF with a couple of
+@@ -629,7 +643,9 @@ static bool ParseSubstitution(State *state, bool accept_std);
+ // <mangled-name> ::= _Z <encoding>
+ static bool ParseMangledName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   return ParseTwoCharToken(state, "_Z") && ParseEncoding(state);
+ }
+ 
+@@ -638,7 +654,9 @@ static bool ParseMangledName(State *state) {
+ //            ::= <special-name>
+ static bool ParseEncoding(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   // Implementing the first two productions together as <name>
+   // [<bare-function-type>] avoids exponential blowup of backtracking.
+   //
+@@ -660,7 +678,9 @@ static bool ParseEncoding(State *state) {
+ //        ::= <local-name>
+ static bool ParseName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseNestedName(state) || ParseLocalName(state)) {
+     return true;
+   }
+@@ -690,7 +710,9 @@ static bool ParseName(State *state) {
+ //                 ::= St <unqualified-name>
+ static bool ParseUnscopedName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseUnqualifiedName(state)) {
+     return true;
+   }
+@@ -706,7 +728,7 @@ static bool ParseUnscopedName(State *state) {
+ 
+ // <ref-qualifer> ::= R // lvalue method reference qualifier
+ //                ::= O // rvalue method reference qualifier
+-static inline bool ParseRefQualifier(State *state) {
++static inline bool ParseRefQualifier(State* state) {
+   return ParseCharClass(state, "OR");
+ }
+ 
+@@ -716,7 +738,9 @@ static inline bool ParseRefQualifier(State *state) {
+ //                   <template-args> E
+ static bool ParseNestedName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'N') && EnterNestedName(state) &&
+       Optional(ParseCVQualifiers(state)) &&
+@@ -742,7 +766,9 @@ static bool ParseNestedName(State *state) {
+ //                   ::= <substitution>
+ static bool ParsePrefix(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   bool has_something = false;
+   while (true) {
+     MaybeAppendSeparator(state);
+@@ -773,7 +799,9 @@ static bool ParsePrefix(State *state) {
+ // <local-source-name> is a GCC extension; see below.
+ static bool ParseUnqualifiedName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseOperatorName(state, nullptr) || ParseCtorDtorName(state) ||
+       ParseSourceName(state) || ParseLocalSourceName(state) ||
+       ParseUnnamedTypeName(state)) {
+@@ -784,9 +812,11 @@ static bool ParseUnqualifiedName(State *state) {
+ 
+ // <abi-tags> ::= <abi-tag> [<abi-tags>]
+ // <abi-tag>  ::= B <source-name>
+-static bool ParseAbiTags(State *state) {
++static bool ParseAbiTags(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+ 
+   while (ParseOneCharToken(state, 'B')) {
+     ParseState copy = state->parse_state;
+@@ -805,7 +835,9 @@ static bool ParseAbiTags(State *state) {
+ // <source-name> ::= <positive length number> <identifier>
+ static bool ParseSourceName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   int length = -1;
+   if (ParseNumber(state, &length) &&
+@@ -823,7 +855,9 @@ static bool ParseSourceName(State *state) {
+ //   https://gcc.gnu.org/viewcvs?view=rev&revision=124467
+ static bool ParseLocalSourceName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'L') && ParseSourceName(state) &&
+       Optional(ParseDiscriminator(state))) {
+@@ -837,9 +871,11 @@ static bool ParseLocalSourceName(State *state) {
+ //                     ::= <closure-type-name>
+ // <closure-type-name> ::= Ul <lambda-sig> E [<(nonnegative) number>] _
+ // <lambda-sig>        ::= <(parameter) type>+
+-static bool ParseUnnamedTypeName(State *state) {
++static bool ParseUnnamedTypeName(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   // Type's 1-based index n is encoded as { "", n == 1; itoa(n-2), otherwise }.
+   // Optionally parse the encoded value into 'which' and add 2 to get the index.
+@@ -878,12 +914,14 @@ static bool ParseUnnamedTypeName(State *state) {
+ // parsed number on success.
+ static bool ParseNumber(State *state, int *number_out) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   bool negative = false;
+   if (ParseOneCharToken(state, 'n')) {
+     negative = true;
+   }
+-  const char *p = RemainingInput(state);
++  const char* p = RemainingInput(state);
+   uint64_t number = 0;
+   for (; *p != '\0'; ++p) {
+     if (IsDigit(*p)) {
+@@ -913,8 +951,10 @@ static bool ParseNumber(State *state, int *number_out) {
+ // hexadecimal string.
+ static bool ParseFloatNumber(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
+-  const char *p = RemainingInput(state);
++  if (guard.IsTooComplex()) {
++    return false;
++  }
++  const char* p = RemainingInput(state);
+   for (; *p != '\0'; ++p) {
+     if (!IsDigit(*p) && !(*p >= 'a' && *p <= 'f')) {
+       break;
+@@ -931,8 +971,10 @@ static bool ParseFloatNumber(State *state) {
+ // using digits and upper case letters
+ static bool ParseSeqId(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
+-  const char *p = RemainingInput(state);
++  if (guard.IsTooComplex()) {
++    return false;
++  }
++  const char* p = RemainingInput(state);
+   for (; *p != '\0'; ++p) {
+     if (!IsDigit(*p) && !(*p >= 'A' && *p <= 'Z')) {
+       break;
+@@ -946,9 +988,11 @@ static bool ParseSeqId(State *state) {
+ }
+ 
+ // <identifier> ::= <unqualified source code identifier> (of given length)
+-static bool ParseIdentifier(State *state, size_t length) {
++static bool ParseIdentifier(State* state, size_t length) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (!AtLeastNumCharsRemaining(RemainingInput(state), length)) {
+     return false;
+   }
+@@ -964,9 +1008,11 @@ static bool ParseIdentifier(State *state, size_t length) {
+ // <operator-name> ::= nw, and other two letters cases
+ //                 ::= cv <type>  # (cast)
+ //                 ::= v  <digit> <source-name> # vendor extended operator
+-static bool ParseOperatorName(State *state, int *arity) {
++static bool ParseOperatorName(State* state, int* arity) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (!AtLeastNumCharsRemaining(RemainingInput(state), 2)) {
+     return false;
+   }
+@@ -1036,7 +1082,9 @@ static bool ParseOperatorName(State *state, int *arity) {
+ // stack traces.  The are special data.
+ static bool ParseSpecialName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "VTISH") &&
+       ParseType(state)) {
+@@ -1098,7 +1146,9 @@ static bool ParseSpecialName(State *state) {
+ //               ::= v <v-offset> _
+ static bool ParseCallOffset(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'h') && ParseNVOffset(state) &&
+       ParseOneCharToken(state, '_')) {
+@@ -1118,14 +1168,18 @@ static bool ParseCallOffset(State *state) {
+ // <nv-offset> ::= <(offset) number>
+ static bool ParseNVOffset(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   return ParseNumber(state, nullptr);
+ }
+ 
+ // <v-offset>  ::= <(offset) number> _ <(virtual offset) number>
+ static bool ParseVOffset(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseNumber(state, nullptr) && ParseOneCharToken(state, '_') &&
+       ParseNumber(state, nullptr)) {
+@@ -1144,11 +1198,13 @@ static bool ParseVOffset(State *state) {
+ //                  ::= C4 | D4
+ static bool ParseCtorDtorName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'C')) {
+     if (ParseCharClass(state, "1234")) {
+-      const char *const prev_name =
++      const char* const prev_name =
+           state->out + state->parse_state.prev_name_idx;
+       MaybeAppendWithLength(state, prev_name,
+                             state->parse_state.prev_name_length);
+@@ -1161,7 +1217,7 @@ static bool ParseCtorDtorName(State *state) {
+   state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "0124")) {
+-    const char *const prev_name = state->out + state->parse_state.prev_name_idx;
++    const char* const prev_name = state->out + state->parse_state.prev_name_idx;
+     MaybeAppend(state, "~");
+     MaybeAppendWithLength(state, prev_name,
+                           state->parse_state.prev_name_length);
+@@ -1174,9 +1230,11 @@ static bool ParseCtorDtorName(State *state) {
+ // <decltype> ::= Dt <expression> E  # decltype of an id-expression or class
+ //                                   # member access (C++0x)
+ //            ::= DT <expression> E  # decltype of an expression (C++0x)
+-static bool ParseDecltype(State *state) {
++static bool ParseDecltype(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+ 
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "tT") &&
+@@ -1209,7 +1267,9 @@ static bool ParseDecltype(State *state) {
+ //
+ static bool ParseType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+ 
+   // We should check CV-qualifers, and PRGC things first.
+@@ -1227,7 +1287,9 @@ static bool ParseType(State *state) {
+   // By consuming the CV-qualifiers first, the former parse is disabled.
+   if (ParseCVQualifiers(state)) {
+     const bool result = ParseType(state);
+-    if (!result) state->parse_state = copy;
++    if (!result) {
++      state->parse_state = copy;
++    }
+     return result;
+   }
+   state->parse_state = copy;
+@@ -1238,7 +1300,9 @@ static bool ParseType(State *state) {
+   // refusing to backtrack the tag characters.
+   if (ParseCharClass(state, "OPRCG")) {
+     const bool result = ParseType(state);
+-    if (!result) state->parse_state = copy;
++    if (!result) {
++      state->parse_state = copy;
++    }
+     return result;
+   }
+   state->parse_state = copy;
+@@ -1286,7 +1350,9 @@ static bool ParseType(State *state) {
+ // ParseType().
+ static bool ParseCVQualifiers(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   int num_cv_qualifiers = 0;
+   num_cv_qualifiers += ParseOneCharToken(state, 'r');
+   num_cv_qualifiers += ParseOneCharToken(state, 'V');
+@@ -1303,7 +1369,9 @@ static bool ParseCVQualifiers(State *state) {
+ //
+ static bool ParseBuiltinType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   const AbbrevPair *p;
+   for (p = kBuiltinTypeList; p->abbrev != nullptr; ++p) {
+     // Guaranteed only 1- or 2-character strings in kBuiltinTypeList.
+@@ -1333,11 +1401,15 @@ static bool ParseBuiltinType(State *state) {
+ //                                           noexcept
+ //                   ::= Dw <type>+ E      # dynamic exception specification
+ //                                           with instantiation-dependent types
+-static bool ParseExceptionSpec(State *state) {
++static bool ParseExceptionSpec(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+ 
+-  if (ParseTwoCharToken(state, "Do")) return true;
++  if (ParseTwoCharToken(state, "Do")) {
++    return true;
++  }
+ 
+   ParseState copy = state->parse_state;
+   if (ParseTwoCharToken(state, "DO") && ParseExpression(state) &&
+@@ -1357,7 +1429,9 @@ static bool ParseExceptionSpec(State *state) {
+ // <function-type> ::= [exception-spec] F [Y] <bare-function-type> [O] E
+ static bool ParseFunctionType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (Optional(ParseExceptionSpec(state)) && ParseOneCharToken(state, 'F') &&
+       Optional(ParseOneCharToken(state, 'Y')) && ParseBareFunctionType(state) &&
+@@ -1372,7 +1446,9 @@ static bool ParseFunctionType(State *state) {
+ // <bare-function-type> ::= <(signature) type>+
+ static bool ParseBareFunctionType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   DisableAppend(state);
+   if (OneOrMore(ParseType, state)) {
+@@ -1387,7 +1463,9 @@ static bool ParseBareFunctionType(State *state) {
+ // <class-enum-type> ::= <name>
+ static bool ParseClassEnumType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   return ParseName(state);
+ }
+ 
+@@ -1395,7 +1473,9 @@ static bool ParseClassEnumType(State *state) {
+ //              ::= A [<(dimension) expression>] _ <(element) type>
+ static bool ParseArrayType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'A') && ParseNumber(state, nullptr) &&
+       ParseOneCharToken(state, '_') && ParseType(state)) {
+@@ -1414,7 +1494,9 @@ static bool ParseArrayType(State *state) {
+ // <pointer-to-member-type> ::= M <(class) type> <(member) type>
+ static bool ParsePointerToMemberType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'M') && ParseType(state) && ParseType(state)) {
+     return true;
+@@ -1427,7 +1509,9 @@ static bool ParsePointerToMemberType(State *state) {
+ //                  ::= T <parameter-2 non-negative number> _
+ static bool ParseTemplateParam(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseTwoCharToken(state, "T_")) {
+     MaybeAppend(state, "?");  // We don't support template substitutions.
+     return true;
+@@ -1447,7 +1531,9 @@ static bool ParseTemplateParam(State *state) {
+ //                           ::= <substitution>
+ static bool ParseTemplateTemplateParam(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   return (ParseTemplateParam(state) ||
+           // "std" on its own isn't a template.
+           ParseSubstitution(state, /*accept_std=*/false));
+@@ -1456,7 +1542,9 @@ static bool ParseTemplateTemplateParam(State *state) {
+ // <template-args> ::= I <template-arg>+ E
+ static bool ParseTemplateArgs(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   DisableAppend(state);
+   if (ParseOneCharToken(state, 'I') && OneOrMore(ParseTemplateArg, state) &&
+@@ -1475,7 +1563,9 @@ static bool ParseTemplateArgs(State *state) {
+ //                 ::= X <expression> E
+ static bool ParseTemplateArg(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'J') && ZeroOrMore(ParseTemplateArg, state) &&
+       ParseOneCharToken(state, 'E')) {
+@@ -1578,14 +1668,14 @@ static bool ParseTemplateArg(State *state) {
+ // <unresolved-type> ::= <template-param> [<template-args>]
+ //                   ::= <decltype>
+ //                   ::= <substitution>
+-static inline bool ParseUnresolvedType(State *state) {
++static inline bool ParseUnresolvedType(State* state) {
+   // No ComplexityGuard because we don't copy the state in this stack frame.
+   return (ParseTemplateParam(state) && Optional(ParseTemplateArgs(state))) ||
+          ParseDecltype(state) || ParseSubstitution(state, /*accept_std=*/false);
+ }
+ 
+ // <simple-id> ::= <source-name> [<template-args>]
+-static inline bool ParseSimpleId(State *state) {
++static inline bool ParseSimpleId(State* state) {
+   // No ComplexityGuard because we don't copy the state in this stack frame.
+ 
+   // Note: <simple-id> cannot be followed by a parameter pack; see comment in
+@@ -1596,9 +1686,11 @@ static inline bool ParseSimpleId(State *state) {
+ // <base-unresolved-name> ::= <source-name> [<template-args>]
+ //                        ::= on <operator-name> [<template-args>]
+ //                        ::= dn <destructor-name>
+-static bool ParseBaseUnresolvedName(State *state) {
++static bool ParseBaseUnresolvedName(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+ 
+   if (ParseSimpleId(state)) {
+     return true;
+@@ -1626,9 +1718,11 @@ static bool ParseBaseUnresolvedName(State *state) {
+ //                         <base-unresolved-name>
+ //                   ::= [gs] sr <unresolved-qualifier-level>+ E
+ //                         <base-unresolved-name>
+-static bool ParseUnresolvedName(State *state) {
++static bool ParseUnresolvedName(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+ 
+   ParseState copy = state->parse_state;
+   if (Optional(ParseTwoCharToken(state, "gs")) &&
+@@ -1684,7 +1778,9 @@ static bool ParseUnresolvedName(State *state) {
+ //                  ::= fL <number> p <(top-level) CV-qualifiers> <number> _
+ static bool ParseExpression(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseTemplateParam(state) || ParseExprPrimary(state)) {
+     return true;
+   }
+@@ -1817,7 +1913,9 @@ static bool ParseExpression(State *state) {
+ // casts to <local-name> types.
+ static bool ParseExprPrimary(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+ 
+   // The "LZ" special case: if we see LZ, we commit to accept "LZ <encoding> E"
+@@ -1848,9 +1946,11 @@ static bool ParseExprPrimary(State *state) {
+ }
+ 
+ // <number> or <float>, followed by 'E', as described above ParseExprPrimary.
+-static bool ParseExprCastValue(State *state) {
++static bool ParseExprCastValue(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   // We have to be able to backtrack after accepting a number because we could
+   // have e.g. "7fffE", which will accept "7" as a number but then fail to find
+   // the 'E'.
+@@ -1877,9 +1977,11 @@ static bool ParseExprCastValue(State *state) {
+ //   <local-name-suffix> ::= s [<discriminator>]
+ //                       ::= <name> [<discriminator>]
+ 
+-static bool ParseLocalNameSuffix(State *state) {
++static bool ParseLocalNameSuffix(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+ 
+   if (MaybeAppend(state, "::") && ParseName(state) &&
+       Optional(ParseDiscriminator(state))) {
+@@ -1896,9 +1998,11 @@ static bool ParseLocalNameSuffix(State *state) {
+   return ParseOneCharToken(state, 's') && Optional(ParseDiscriminator(state));
+ }
+ 
+-static bool ParseLocalName(State *state) {
++static bool ParseLocalName(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'Z') && ParseEncoding(state) &&
+       ParseOneCharToken(state, 'E') && ParseLocalNameSuffix(state)) {
+@@ -1911,7 +2015,9 @@ static bool ParseLocalName(State *state) {
+ // <discriminator> := _ <(non-negative) number>
+ static bool ParseDiscriminator(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, '_') && ParseNumber(state, nullptr)) {
+     return true;
+@@ -1931,9 +2037,11 @@ static bool ParseDiscriminator(State *state) {
+ // an unqualified name and re-parse the same template-args.  To block this
+ // exponential backtracking, we disable it with 'accept_std=false' in
+ // problematic contexts.
+-static bool ParseSubstitution(State *state, bool accept_std) {
++static bool ParseSubstitution(State* state, bool accept_std) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseTwoCharToken(state, "S_")) {
+     MaybeAppend(state, "?");  // We don't support substitutions.
+     return true;
+@@ -1971,7 +2079,9 @@ static bool ParseSubstitution(State *state, bool accept_std) {
+ // or version suffix.  Returns true only if all of "mangled_cur" was consumed.
+ static bool ParseTopLevelMangledName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseMangledName(state)) {
+     if (RemainingInput(state)[0] != '\0') {
+       // Drop trailing function clone suffix, if any.
+@@ -1991,13 +2101,13 @@ static bool ParseTopLevelMangledName(State *state) {
+   return false;
+ }
+ 
+-static bool Overflowed(const State *state) {
++static bool Overflowed(const State* state) {
+   return state->parse_state.out_cur_idx >= state->out_end_idx;
+ }
+ #endif
+ 
+ // The demangler entry point.
+-bool Demangle(const char *mangled, char *out, size_t out_size) {
++bool Demangle(const char* mangled, char* out, size_t out_size) {
+ #if defined(GLOG_OS_WINDOWS)
+ #if defined(HAVE_DBGHELP)
+   // When built with incremental linking, the Windows debugger
+diff --git a/base/third_party/symbolize/demangle.h b/base/third_party/symbolize/demangle.h
+index 26e821a53c2cb..7d5cfaaabf0dd 100644
+--- a/base/third_party/symbolize/demangle.h
++++ b/base/third_party/symbolize/demangle.h
+@@ -80,7 +80,7 @@ _START_GOOGLE_NAMESPACE_
+ // Demangle "mangled".  On success, return true and write the
+ // demangled symbol name to "out".  Otherwise, return false.
+ // "out" is modified even if demangling is unsuccessful.
+-bool GLOG_EXPORT Demangle(const char *mangled, char *out, size_t out_size);
++bool GLOG_EXPORT Demangle(const char* mangled, char* out, size_t out_size);
+ 
+ _END_GOOGLE_NAMESPACE_
+ 
+diff --git a/base/third_party/symbolize/glog/logging.h b/base/third_party/symbolize/glog/logging.h
+index 46869226024da..b935e3ec9cded 100644
+--- a/base/third_party/symbolize/glog/logging.h
++++ b/base/third_party/symbolize/glog/logging.h
+@@ -38,4 +38,4 @@
+ 
+ // Not needed in Chrome.
+ 
+-#endif // GLOG_LOGGING_H
++#endif  // GLOG_LOGGING_H
+diff --git a/base/third_party/symbolize/symbolize.cc b/base/third_party/symbolize/symbolize.cc
+index b6ddc85d57185..a3b8399f411bf 100644
+--- a/base/third_party/symbolize/symbolize.cc
++++ b/base/third_party/symbolize/symbolize.cc
+@@ -97,7 +97,7 @@ void InstallSymbolizeOpenObjectFileCallback(
+ // where the input symbol is demangled in-place.
+ // To keep stack consumption low, we would like this function to not
+ // get inlined.
+-static ATTRIBUTE_NOINLINE void DemangleInplace(char *out, size_t out_size) {
++static ATTRIBUTE_NOINLINE void DemangleInplace(char* out, size_t out_size) {
+   char demangled[256];  // Big enough for sane demangled symbols.
+   if (Demangle(out, demangled, sizeof(demangled))) {
+     // Demangling succeeded. Copy to out if the space allows.
+@@ -121,17 +121,17 @@ _END_GOOGLE_NAMESPACE_
+ #else
+ #include <elf.h>
+ #endif
++#include <fcntl.h>
++#include <stdint.h>
++#include <sys/stat.h>
++#include <sys/types.h>
++#include <unistd.h>
+ #include <cerrno>
+ #include <climits>
+ #include <cstddef>
+ #include <cstdio>
+ #include <cstdlib>
+ #include <cstring>
+-#include <fcntl.h>
+-#include <stdint.h>
+-#include <sys/stat.h>
+-#include <sys/types.h>
+-#include <unistd.h>
+ 
+ #include "symbolize.h"
+ #include "config.h"
+@@ -153,7 +153,8 @@ ssize_t ReadFromOffset(const int fd,
+                        const size_t count,
+                        const size_t offset) {
+   SAFE_ASSERT(fd >= 0);
+-  SAFE_ASSERT(count <= static_cast<size_t>(std::numeric_limits<ssize_t>::max()));
++  SAFE_ASSERT(count <=
++              static_cast<size_t>(std::numeric_limits<ssize_t>::max()));
+   char *buf0 = reinterpret_cast<char *>(buf);
+   size_t num_bytes = 0;
+   while (num_bytes < count) {
+@@ -176,8 +177,10 @@ ssize_t ReadFromOffset(const int fd,
+ // pointed by "fd" into the buffer starting at "buf" while handling
+ // short reads and EINTR.  On success, return true. Otherwise, return
+ // false.
+-static bool ReadFromOffsetExact(const int fd, void *buf,
+-                                const size_t count, const size_t offset) {
++static bool ReadFromOffsetExact(const int fd,
++                                void* buf,
++                                const size_t count,
++                                const size_t offset) {
+   ssize_t len = ReadFromOffset(fd, buf, count, offset);
+   return static_cast<size_t>(len) == count;
+ }
+@@ -199,9 +202,11 @@ static int FileGetElfType(const int fd) {
+ // and return true.  Otherwise, return false.
+ // To keep stack consumption low, we would like this function to not get
+ // inlined.
+-static ATTRIBUTE_NOINLINE bool
+-GetSectionHeaderByType(const int fd, ElfW(Half) sh_num, const size_t sh_offset,
+-                       ElfW(Word) type, ElfW(Shdr) *out) {
++static ATTRIBUTE_NOINLINE bool GetSectionHeaderByType(const int fd,
++                                                      ElfW(Half) sh_num,
++                                                      const size_t sh_offset,
++                                                      ElfW(Word) type,
++                                                      ElfW(Shdr) * out) {
+   // Read at most 16 section headers at a time to save read calls.
+   ElfW(Shdr) buf[16];
+   for (size_t i = 0; i < sh_num;) {
+@@ -248,8 +253,8 @@ bool GetSectionHeaderByName(int fd, const char *name, size_t name_len,
+   }
+ 
+   for (size_t i = 0; i < elf_header.e_shnum; ++i) {
+-    size_t section_header_offset = (elf_header.e_shoff +
+-                                   elf_header.e_shentsize * i);
++    size_t section_header_offset =
++        (elf_header.e_shoff + elf_header.e_shentsize * i);
+     if (!ReadFromOffsetExact(fd, out, sizeof(*out), section_header_offset)) {
+       return false;
+     }
+@@ -281,10 +286,13 @@ bool GetSectionHeaderByName(int fd, const char *name, size_t name_len,
+ // to out.  Otherwise, return false.
+ // To keep stack consumption low, we would like this function to not get
+ // inlined.
+-static ATTRIBUTE_NOINLINE bool
+-FindSymbol(uint64_t pc, const int fd, char *out, size_t out_size,
+-           uint64_t symbol_offset, const ElfW(Shdr) *strtab,
+-           const ElfW(Shdr) *symtab) {
++static ATTRIBUTE_NOINLINE bool FindSymbol(uint64_t pc,
++                                          const int fd,
++                                          char* out,
++                                          size_t out_size,
++                                          uint64_t symbol_offset,
++                                          const ElfW(Shdr) * strtab,
++                                          const ElfW(Shdr) * symtab) {
+   if (symtab == NULL) {
+     return false;
+   }
+@@ -384,7 +392,7 @@ namespace {
+ // and snprintf().
+ class LineReader {
+  public:
+-  explicit LineReader(int fd, char *buf, size_t buf_len, size_t offset)
++  explicit LineReader(int fd, char* buf, size_t buf_len, size_t offset)
+       : fd_(fd),
+         buf_(buf),
+         buf_len_(buf_len),
+@@ -449,11 +457,12 @@ class LineReader {
+   }
+ 
+  private:
+-  LineReader(const LineReader &);
++  LineReader(const LineReader&);
+   void operator=(const LineReader&);
+ 
+   char *FindLineFeed() {
+-    return reinterpret_cast<char *>(memchr(bol_, '\n', static_cast<size_t>(eod_ - bol_)));
++    return reinterpret_cast<char*>(
++        memchr(bol_, '\n', static_cast<size_t>(eod_ - bol_)));
+   }
+ 
+   bool BufferIsEmpty() {
+@@ -483,7 +492,8 @@ static char *GetHex(const char *start, const char *end, uint64_t *hex) {
+     int ch = *p;
+     if ((ch >= '0' && ch <= '9') ||
+         (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) {
+-      *hex = (*hex << 4U) | (ch < 'A' ? static_cast<uint64_t>(ch - '0') : (ch & 0xF) + 9U);
++      *hex = (*hex << 4U) |
++             (ch < 'A' ? static_cast<uint64_t>(ch - '0') : (ch & 0xF) + 9U);
+     } else {  // Encountered the first non-hex character.
+       break;
+     }
+@@ -647,12 +657,11 @@ static int OpenObjectFileContainingPcAndGetStartAddressNoHook(
+   }
+ }
+ 
+-int OpenObjectFileContainingPcAndGetStartAddress(
+-    uint64_t pc,
+-    uint64_t& start_address,
+-    uint64_t& base_address,
+-    char* out_file_name,
+-    size_t out_file_name_size) {
++int OpenObjectFileContainingPcAndGetStartAddress(uint64_t pc,
++                                                 uint64_t& start_address,
++                                                 uint64_t& base_address,
++                                                 char* out_file_name,
++                                                 size_t out_file_name_size) {
+   if (g_symbolize_open_object_file_callback) {
+     return g_symbolize_open_object_file_callback(
+         pc, start_address, base_address, out_file_name, out_file_name_size);
+@@ -668,7 +677,11 @@ int OpenObjectFileContainingPcAndGetStartAddress(
+ // bytes. Output will be truncated as needed, and a NUL character is always
+ // appended.
+ // NOTE: code from sandbox/linux/seccomp-bpf/demo.cc.
+-static char *itoa_r(uintptr_t i, char *buf, size_t sz, unsigned base, size_t padding) {
++static char* itoa_r(uintptr_t i,
++                    char* buf,
++                    size_t sz,
++                    unsigned base,
++                    size_t padding) {
+   // Make sure we can write at least one NUL byte.
+   size_t n = 1;
+   if (n > sz) {
+@@ -745,7 +758,8 @@ static void SafeAppendHexNumber(uint64_t value, char* dest, size_t dest_size) {
+ // and "out" is used as its output.
+ // To keep stack consumption low, we would like this function to not
+ // get inlined.
+-static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
++static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void* pc,
++                                                    char* out,
+                                                     size_t out_size) {
+   uint64_t pc0 = reinterpret_cast<uintptr_t>(pc);
+   uint64_t start_address = 0;
+@@ -822,14 +836,16 @@ static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
+ 
+ _END_GOOGLE_NAMESPACE_
+ 
+-#elif (defined(GLOG_OS_MACOSX) || defined(GLOG_OS_EMSCRIPTEN)) && defined(HAVE_DLADDR)
++#elif (defined(GLOG_OS_MACOSX) || defined(GLOG_OS_EMSCRIPTEN)) && \
++    defined(HAVE_DLADDR)
+ 
+ #include <dlfcn.h>
+ #include <cstring>
+ 
+ _START_GOOGLE_NAMESPACE_
+ 
+-static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
++static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void* pc,
++                                                    char* out,
+                                                     size_t out_size) {
+   Dl_info info;
+   if (dladdr(pc, &info)) {
+@@ -883,7 +899,8 @@ private:
+   SymInitializer& operator=(const SymInitializer&);
+ };
+ 
+-static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
++static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void* pc,
++                                                    char* out,
+                                                     size_t out_size) {
+   const static SymInitializer symInitializer;
+   if (!symInitializer.ready) {
+@@ -918,7 +935,7 @@ _END_GOOGLE_NAMESPACE_
+ 
+ _START_GOOGLE_NAMESPACE_
+ 
+-bool Symbolize(void *pc, char *out, size_t out_size) {
++bool Symbolize(void* pc, char* out, size_t out_size) {
+   return SymbolizeAndDemangle(pc, out, out_size);
+ }
+ 
+diff --git a/base/third_party/symbolize/symbolize.h b/base/third_party/symbolize/symbolize.h
+index 64ff0509ce57d..987569fdde67f 100644
+--- a/base/third_party/symbolize/symbolize.h
++++ b/base/third_party/symbolize/symbolize.h
+@@ -127,7 +127,7 @@ ATTRIBUTE_NOINLINE int OpenObjectFileContainingPcAndGetStartAddress(
+ 
+ _END_GOOGLE_NAMESPACE_
+ 
+-#endif  /* __ELF__ */
++#endif /* __ELF__ */
+ 
+ _START_GOOGLE_NAMESPACE_
+ 
+@@ -140,7 +140,7 @@ struct FileDescriptor {
+   int get() { return fd_; }
+ 
+  private:
+-  FileDescriptor(const FileDescriptor &);
++  FileDescriptor(const FileDescriptor&);
+   void operator=(const FileDescriptor&);
+ };
+ 
+diff --git a/base/third_party/symbolize/utilities.h b/base/third_party/symbolize/utilities.h
+index 8c61380fad159..bb206a8020315 100644
+--- a/base/third_party/symbolize/utilities.h
++++ b/base/third_party/symbolize/utilities.h
+@@ -35,13 +35,13 @@
+ #define UTILITIES_H__
+ 
+ #ifdef HAVE___ATTRIBUTE__
+-# define ATTRIBUTE_NOINLINE __attribute__ ((noinline))
+-# define HAVE_ATTRIBUTE_NOINLINE
++#define ATTRIBUTE_NOINLINE __attribute__((noinline))
++#define HAVE_ATTRIBUTE_NOINLINE
+ #elif defined(GLOG_OS_WINDOWS)
+-# define ATTRIBUTE_NOINLINE __declspec(noinline)
+-# define HAVE_ATTRIBUTE_NOINLINE
++#define ATTRIBUTE_NOINLINE __declspec(noinline)
++#define HAVE_ATTRIBUTE_NOINLINE
+ #else
+-# define ATTRIBUTE_NOINLINE
++#define ATTRIBUTE_NOINLINE
+ #endif
+ 
+ #endif  // UTILITIES_H__
diff --git a/base/third_party/symbolize/symbolize.cc b/base/third_party/symbolize/symbolize.cc
index c13be34..a2794e9 100644
--- a/base/third_party/symbolize/symbolize.cc
+++ b/base/third_party/symbolize/symbolize.cc
@@ -45,28 +45,35 @@
 // some functions which are not guaranteed to be so, such as memchr()
 // and memmove().  We assume they are async-signal-safe.
 //
+// Additional header can be specified by the GLOG_BUILD_CONFIG_INCLUDE
+// macro to add platform specific defines (e.g. GLOG_OS_OPENBSD).
 
-// Note for Cobalt: Cobalt Starboard depends on the old version of Symbolize so
-// this file is from m27 Chromium. There are no Cobalt-introduced changes in
-// this file.
+#ifdef GLOG_BUILD_CONFIG_INCLUDE
+#include GLOG_BUILD_CONFIG_INCLUDE
+#endif  // GLOG_BUILD_CONFIG_INCLUDE
 
-#include "build/build_config.h"
+#include "symbolize.h"
 
 #include "utilities.h"
 
-#if SB_IS(EVERGREEN_COMPATIBLE)
-#include "starboard/common/log.h"
-#include "starboard/elf_loader/evergreen_info.h"
-#include "starboard/memory.h"
-#endif
-
 #if defined(HAVE_SYMBOLIZE)
 
+#include <cstring>
+#include <cstdlib>
+
+#include <algorithm>
 #include <limits>
 
 #include "symbolize.h"
 #include "demangle.h"
 
+#if defined(STARBOARD)
+#include "starboard/configuration.h"
+#include "starboard/common/log.h"
+#include "starboard/elf_loader/evergreen_info.h"
+#include "starboard/memory.h"
+#endif
+
 _START_GOOGLE_NAMESPACE_
 
 // We don't use assert() since it's not guaranteed to be
@@ -86,15 +93,22 @@
   g_symbolize_callback = callback;
 }
 
+static SymbolizeOpenObjectFileCallback g_symbolize_open_object_file_callback =
+    NULL;
+void InstallSymbolizeOpenObjectFileCallback(
+    SymbolizeOpenObjectFileCallback callback) {
+  g_symbolize_open_object_file_callback = callback;
+}
+
 // This function wraps the Demangle function to provide an interface
 // where the input symbol is demangled in-place.
 // To keep stack consumption low, we would like this function to not
 // get inlined.
-static ATTRIBUTE_NOINLINE void DemangleInplace(char *out, int out_size) {
+static ATTRIBUTE_NOINLINE void DemangleInplace(char* out, size_t out_size) {
   char demangled[256];  // Big enough for sane demangled symbols.
   if (Demangle(out, demangled, sizeof(demangled))) {
     // Demangling succeeded. Copy to out if the space allows.
-    int len = strlen(demangled);
+    size_t len = strlen(demangled);
     if (len + 1 <= out_size) {  // +1 for '\0'.
       SAFE_ASSERT(len < sizeof(demangled));
       memmove(out, demangled, len + 1);
@@ -106,23 +120,25 @@
 
 #if defined(__ELF__)
 
+#if defined(HAVE_DLFCN_H)
 #include <dlfcn.h>
-#if defined(OS_OPENBSD)
+#endif
+#if defined(GLOG_OS_OPENBSD)
 #include <sys/exec_elf.h>
 #else
 #include <elf.h>
 #endif
-#include <errno.h>
 #include <fcntl.h>
-#include <limits.h>
-#include <stddef.h>
 #include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <cerrno>
+#include <climits>
+#include <cstddef>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
 
 #include "symbolize.h"
 #include "config.h"
@@ -133,49 +149,47 @@
 
 _START_GOOGLE_NAMESPACE_
 
-// Read up to "count" bytes from file descriptor "fd" into the buffer
-// starting at "buf" while handling short reads and EINTR.  On
-// success, return the number of bytes read.  Otherwise, return -1.
-static ssize_t ReadPersistent(const int fd, void *buf, const size_t count) {
+FileDescriptor::~FileDescriptor() {
+  if (fd_ >= 0) {
+    close(fd_);
+  }
+}
+
+ssize_t ReadFromOffset(const int fd,
+                       void* buf,
+                       const size_t count,
+                       const size_t offset) {
   SAFE_ASSERT(fd >= 0);
-  SAFE_ASSERT(count <= std::numeric_limits<ssize_t>::max());
+  SAFE_ASSERT(count <=
+              static_cast<size_t>(std::numeric_limits<ssize_t>::max()));
   char *buf0 = reinterpret_cast<char *>(buf);
-  ssize_t num_bytes = 0;
+  size_t num_bytes = 0;
   while (num_bytes < count) {
     ssize_t len;
-    NO_INTR(len = read(fd, buf0 + num_bytes, count - num_bytes));
+    NO_INTR(len = pread(fd, buf0 + num_bytes, count - num_bytes,
+                        static_cast<off_t>(offset + num_bytes)));
     if (len < 0) {  // There was an error other than EINTR.
       return -1;
     }
     if (len == 0) {  // Reached EOF.
       break;
     }
-    num_bytes += len;
+    num_bytes += static_cast<size_t>(len);
   }
   SAFE_ASSERT(num_bytes <= count);
-  return num_bytes;
-}
-
-// Read up to "count" bytes from "offset" in the file pointed by file
-// descriptor "fd" into the buffer starting at "buf".  On success,
-// return the number of bytes read.  Otherwise, return -1.
-static ssize_t ReadFromOffset(const int fd, void *buf,
-                              const size_t count, const off_t offset) {
-  off_t off = lseek(fd, offset, SEEK_SET);
-  if (off == (off_t)-1) {
-    return -1;
-  }
-  return ReadPersistent(fd, buf, count);
+  return static_cast<ssize_t>(num_bytes);
 }
 
 // Try reading exactly "count" bytes from "offset" bytes in a file
 // pointed by "fd" into the buffer starting at "buf" while handling
 // short reads and EINTR.  On success, return true. Otherwise, return
 // false.
-static bool ReadFromOffsetExact(const int fd, void *buf,
-                                const size_t count, const off_t offset) {
+static bool ReadFromOffsetExact(const int fd,
+                                void* buf,
+                                const size_t count,
+                                const size_t offset) {
   ssize_t len = ReadFromOffset(fd, buf, count, offset);
-  return len == count;
+  return static_cast<size_t>(len) == count;
 }
 
 // Returns elf_header.e_type if the file pointed by fd is an ELF binary.
@@ -195,21 +209,26 @@
 // and return true.  Otherwise, return false.
 // To keep stack consumption low, we would like this function to not get
 // inlined.
-static ATTRIBUTE_NOINLINE bool
-GetSectionHeaderByType(const int fd, ElfW(Half) sh_num, const off_t sh_offset,
-                       ElfW(Word) type, ElfW(Shdr) *out) {
+static ATTRIBUTE_NOINLINE bool GetSectionHeaderByType(const int fd,
+                                                      ElfW(Half) sh_num,
+                                                      const size_t sh_offset,
+                                                      ElfW(Word) type,
+                                                      ElfW(Shdr) * out) {
   // Read at most 16 section headers at a time to save read calls.
   ElfW(Shdr) buf[16];
-  for (int i = 0; i < sh_num;) {
-    const ssize_t num_bytes_left = (sh_num - i) * sizeof(buf[0]);
-    const ssize_t num_bytes_to_read =
+  for (size_t i = 0; i < sh_num;) {
+    const size_t num_bytes_left = (sh_num - i) * sizeof(buf[0]);
+    const size_t num_bytes_to_read =
         (sizeof(buf) > num_bytes_left) ? num_bytes_left : sizeof(buf);
     const ssize_t len = ReadFromOffset(fd, buf, num_bytes_to_read,
                                        sh_offset + i * sizeof(buf[0]));
-    SAFE_ASSERT(len % sizeof(buf[0]) == 0);
-    const ssize_t num_headers_in_buf = len / sizeof(buf[0]);
+    if (len == -1) {
+      return false;
+    }
+    SAFE_ASSERT(static_cast<size_t>(len) % sizeof(buf[0]) == 0);
+    const size_t num_headers_in_buf = static_cast<size_t>(len) / sizeof(buf[0]);
     SAFE_ASSERT(num_headers_in_buf <= sizeof(buf) / sizeof(buf[0]));
-    for (int j = 0; j < num_headers_in_buf; ++j) {
+    for (size_t j = 0; j < num_headers_in_buf; ++j) {
       if (buf[j].sh_type == type) {
         *out = buf[j];
         return true;
@@ -233,15 +252,16 @@
   }
 
   ElfW(Shdr) shstrtab;
-  off_t shstrtab_offset = (elf_header.e_shoff +
-                           elf_header.e_shentsize * elf_header.e_shstrndx);
+  size_t shstrtab_offset =
+      (elf_header.e_shoff + static_cast<size_t>(elf_header.e_shentsize) *
+                                static_cast<size_t>(elf_header.e_shstrndx));
   if (!ReadFromOffsetExact(fd, &shstrtab, sizeof(shstrtab), shstrtab_offset)) {
     return false;
   }
 
-  for (int i = 0; i < elf_header.e_shnum; ++i) {
-    off_t section_header_offset = (elf_header.e_shoff +
-                                   elf_header.e_shentsize * i);
+  for (size_t i = 0; i < elf_header.e_shnum; ++i) {
+    size_t section_header_offset =
+        (elf_header.e_shoff + elf_header.e_shentsize * i);
     if (!ReadFromOffsetExact(fd, out, sizeof(*out), section_header_offset)) {
       return false;
     }
@@ -252,11 +272,11 @@
       // No point in even trying.
       return false;
     }
-    off_t name_offset = shstrtab.sh_offset + out->sh_name;
+    size_t name_offset = shstrtab.sh_offset + out->sh_name;
     ssize_t n_read = ReadFromOffset(fd, &header_name, name_len, name_offset);
     if (n_read == -1) {
       return false;
-    } else if (n_read != name_len) {
+    } else if (static_cast<size_t>(n_read) != name_len) {
       // Short read -- name could be at end of file.
       continue;
     }
@@ -273,33 +293,38 @@
 // to out.  Otherwise, return false.
 // To keep stack consumption low, we would like this function to not get
 // inlined.
-static ATTRIBUTE_NOINLINE bool
-FindSymbol(uint64_t pc, const int fd, char *out, int out_size,
-           uint64_t symbol_offset, const ElfW(Shdr) *strtab,
-           const ElfW(Shdr) *symtab) {
+static ATTRIBUTE_NOINLINE bool FindSymbol(uint64_t pc,
+                                          const int fd,
+                                          char* out,
+                                          size_t out_size,
+                                          uint64_t symbol_offset,
+                                          const ElfW(Shdr) * strtab,
+                                          const ElfW(Shdr) * symtab) {
   if (symtab == NULL) {
     return false;
   }
-  const int num_symbols = symtab->sh_size / symtab->sh_entsize;
-  for (int i = 0; i < num_symbols;) {
-    off_t offset = symtab->sh_offset + i * symtab->sh_entsize;
+  const size_t num_symbols = symtab->sh_size / symtab->sh_entsize;
+  for (unsigned i = 0; i < num_symbols;) {
+    size_t offset = symtab->sh_offset + i * symtab->sh_entsize;
 
     // If we are reading Elf64_Sym's, we want to limit this array to
     // 32 elements (to keep stack consumption low), otherwise we can
     // have a 64 element Elf32_Sym array.
-#if __WORDSIZE == 64
-#define NUM_SYMBOLS 32
+#if defined(__WORDSIZE) && __WORDSIZE == 64
+    const size_t NUM_SYMBOLS = 32U;
 #else
-#define NUM_SYMBOLS 64
+    const size_t NUM_SYMBOLS = 64U;
 #endif
 
     // Read at most NUM_SYMBOLS symbols at once to save read() calls.
     ElfW(Sym) buf[NUM_SYMBOLS];
-    const ssize_t len = ReadFromOffset(fd, &buf, sizeof(buf), offset);
-    SAFE_ASSERT(len % sizeof(buf[0]) == 0);
-    const ssize_t num_symbols_in_buf = len / sizeof(buf[0]);
-    SAFE_ASSERT(num_symbols_in_buf <= sizeof(buf) / sizeof(buf[0]));
-    for (int j = 0; j < num_symbols_in_buf; ++j) {
+    size_t num_symbols_to_read = std::min(NUM_SYMBOLS, num_symbols - i);
+    const ssize_t len =
+        ReadFromOffset(fd, &buf, sizeof(buf[0]) * num_symbols_to_read, offset);
+    SAFE_ASSERT(static_cast<size_t>(len) % sizeof(buf[0]) == 0);
+    const size_t num_symbols_in_buf = static_cast<size_t>(len) / sizeof(buf[0]);
+    SAFE_ASSERT(num_symbols_in_buf <= num_symbols_to_read);
+    for (unsigned j = 0; j < num_symbols_in_buf; ++j) {
       const ElfW(Sym)& symbol = buf[j];
       uint64_t start_address = symbol.st_value;
       start_address += symbol_offset;
@@ -310,6 +335,7 @@
         ssize_t len1 = ReadFromOffset(fd, out, out_size,
                                       strtab->sh_offset + symbol.st_name);
         if (len1 <= 0 || memchr(out, '\0', out_size) == NULL) {
+          memset(out, 0, out_size);
           return false;
         }
         return true;  // Obtained the symbol name.
@@ -327,69 +353,44 @@
 static bool GetSymbolFromObjectFile(const int fd,
                                     uint64_t pc,
                                     char* out,
-                                    int out_size,
-                                    uint64_t map_start_address) {
+                                    size_t out_size,
+                                    uint64_t base_address) {
   // Read the ELF header.
   ElfW(Ehdr) elf_header;
   if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) {
     return false;
   }
 
-  uint64_t symbol_offset = 0;
-  if (elf_header.e_type == ET_DYN) {  // DSO needs offset adjustment.
-    symbol_offset = map_start_address;
-  }
-
   ElfW(Shdr) symtab, strtab;
 
   // Consult a regular symbol table first.
-  if (!GetSectionHeaderByType(fd, elf_header.e_shnum, elf_header.e_shoff,
-                              SHT_SYMTAB, &symtab)) {
-    return false;
-  }
-  if (!ReadFromOffsetExact(
-          fd, &strtab, sizeof(strtab),
-          elf_header.e_shoff + symtab.sh_link * sizeof(symtab))) {
-    return false;
-  }
-  if (FindSymbol(pc, fd, out, out_size, symbol_offset, &strtab, &symtab)) {
-    return true;  // Found the symbol in a regular symbol table.
+  if (GetSectionHeaderByType(fd, elf_header.e_shnum, elf_header.e_shoff,
+                             SHT_SYMTAB, &symtab)) {
+    if (!ReadFromOffsetExact(fd, &strtab, sizeof(strtab), elf_header.e_shoff +
+                             symtab.sh_link * sizeof(symtab))) {
+      return false;
+    }
+    if (FindSymbol(pc, fd, out, out_size, base_address, &strtab, &symtab)) {
+      return true;  // Found the symbol in a regular symbol table.
+    }
   }
 
   // If the symbol is not found, then consult a dynamic symbol table.
-  if (!GetSectionHeaderByType(fd, elf_header.e_shnum, elf_header.e_shoff,
-                              SHT_DYNSYM, &symtab)) {
-    return false;
-  }
-  if (!ReadFromOffsetExact(
-          fd, &strtab, sizeof(strtab),
-          elf_header.e_shoff + symtab.sh_link * sizeof(symtab))) {
-    return false;
-  }
-  if (FindSymbol(pc, fd, out, out_size, symbol_offset, &strtab, &symtab)) {
-    return true;  // Found the symbol in a dynamic symbol table.
+  if (GetSectionHeaderByType(fd, elf_header.e_shnum, elf_header.e_shoff,
+                             SHT_DYNSYM, &symtab)) {
+    if (!ReadFromOffsetExact(fd, &strtab, sizeof(strtab), elf_header.e_shoff +
+                             symtab.sh_link * sizeof(symtab))) {
+      return false;
+    }
+    if (FindSymbol(pc, fd, out, out_size, base_address, &strtab, &symtab)) {
+      return true;  // Found the symbol in a dynamic symbol table.
+    }
   }
 
   return false;
 }
 
 namespace {
-// Thin wrapper around a file descriptor so that the file descriptor
-// gets closed for sure.
-struct FileDescriptor {
-  const int fd_;
-  explicit FileDescriptor(int fd) : fd_(fd) {}
-  ~FileDescriptor() {
-    if (fd_ >= 0) {
-      NO_INTR(close(fd_));
-    }
-  }
-  int get() { return fd_; }
-
- private:
-  explicit FileDescriptor(const FileDescriptor&);
-  void operator=(const FileDescriptor&);
-};
 
 // Helper class for reading lines from file.
 //
@@ -398,9 +399,14 @@
 // and snprintf().
 class LineReader {
  public:
-  explicit LineReader(int fd, char *buf, int buf_len) : fd_(fd),
-    buf_(buf), buf_len_(buf_len), bol_(buf), eol_(buf), eod_(buf) {
-  }
+  explicit LineReader(int fd, char* buf, size_t buf_len, size_t offset)
+      : fd_(fd),
+        buf_(buf),
+        buf_len_(buf_len),
+        offset_(offset),
+        bol_(buf),
+        eol_(buf),
+        eod_(buf) {}
 
   // Read '\n'-terminated line from file.  On success, modify "bol"
   // and "eol", then return true.  Otherwise, return false.
@@ -409,27 +415,29 @@
   // dropped.  It's an intentional behavior to make the code simple.
   bool ReadLine(const char **bol, const char **eol) {
     if (BufferIsEmpty()) {  // First time.
-      const ssize_t num_bytes = ReadPersistent(fd_, buf_, buf_len_);
+      const ssize_t num_bytes = ReadFromOffset(fd_, buf_, buf_len_, offset_);
       if (num_bytes <= 0) {  // EOF or error.
         return false;
       }
+      offset_ += static_cast<size_t>(num_bytes);
       eod_ = buf_ + num_bytes;
       bol_ = buf_;
     } else {
       bol_ = eol_ + 1;  // Advance to the next line in the buffer.
       SAFE_ASSERT(bol_ <= eod_);  // "bol_" can point to "eod_".
       if (!HasCompleteLine()) {
-        const int incomplete_line_length = eod_ - bol_;
+        const size_t incomplete_line_length = static_cast<size_t>(eod_ - bol_);
         // Move the trailing incomplete line to the beginning.
         memmove(buf_, bol_, incomplete_line_length);
         // Read text from file and append it.
         char * const append_pos = buf_ + incomplete_line_length;
-        const int capacity_left = buf_len_ - incomplete_line_length;
-        const ssize_t num_bytes = ReadPersistent(fd_, append_pos,
-                                                 capacity_left);
+        const size_t capacity_left = buf_len_ - incomplete_line_length;
+        const ssize_t num_bytes =
+            ReadFromOffset(fd_, append_pos, capacity_left, offset_);
         if (num_bytes <= 0) {  // EOF or error.
           return false;
         }
+        offset_ += static_cast<size_t>(num_bytes);
         eod_ = append_pos + num_bytes;
         bol_ = buf_;
       }
@@ -456,11 +464,12 @@
   }
 
  private:
-  explicit LineReader(const LineReader&);
+  LineReader(const LineReader&);
   void operator=(const LineReader&);
 
   char *FindLineFeed() {
-    return reinterpret_cast<char*>(memchr(bol_, '\n', eod_ - bol_));
+    return reinterpret_cast<char*>(
+        memchr(bol_, '\n', static_cast<size_t>(eod_ - bol_)));
   }
 
   bool BufferIsEmpty() {
@@ -473,7 +482,8 @@
 
   const int fd_;
   char * const buf_;
-  const int buf_len_;
+  const size_t buf_len_;
+  size_t offset_;
   char *bol_;
   char *eol_;
   const char *eod_;  // End of data in "buf_".
@@ -489,7 +499,8 @@
     int ch = *p;
     if ((ch >= '0' && ch <= '9') ||
         (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) {
-      *hex = (*hex << 4) | (ch < 'A' ? ch - '0' : (ch & 0xF) + 9);
+      *hex = (*hex << 4U) |
+             (ch < 'A' ? static_cast<uint64_t>(ch - '0') : (ch & 0xF) + 9U);
     } else {  // Encountered the first non-hex character.
       break;
     }
@@ -509,16 +520,14 @@
 }
 #endif
 
-// Search for the object file (from /proc/self/maps) that contains
-// the specified pc. If found, open this file and return the file handle,
-// and also set start_address to the start address of where this object
-// file is mapped to in memory. Otherwise, return -1.
-static ATTRIBUTE_NOINLINE int OpenObjectFileContainingPcAndGetStartAddress(
+static int OpenObjectFileContainingPcAndGetStartAddressNoHook(
     uint64_t pc,
-    uint64_t& start_address) {
+    uint64_t& start_address,
+    uint64_t& base_address,
+    char* out_file_name,
+    size_t out_file_name_size) {
   int object_fd;
 
-  // Open /proc/self/maps.
   int maps_fd;
   NO_INTR(maps_fd = open("/proc/self/maps", O_RDONLY));
   FileDescriptor wrapped_maps_fd(maps_fd);
@@ -526,11 +535,20 @@
     return -1;
   }
 
+  int mem_fd;
+  NO_INTR(mem_fd = open("/proc/self/mem", O_RDONLY));
+  FileDescriptor wrapped_mem_fd(mem_fd);
+  if (wrapped_mem_fd.get() < 0) {
+    return -1;
+  }
+
   // Iterate over maps and look for the map containing the pc.  Then
   // look into the symbol tables inside.
   char buf[1024];  // Big enough for line of sane /proc/self/maps
-  LineReader reader(wrapped_maps_fd.get(), buf, sizeof(buf));
+  unsigned num_maps = 0;
+  LineReader reader(wrapped_maps_fd.get(), buf, sizeof(buf), 0);
   while (true) {
+    num_maps++;
     const char *cursor;
     const char *eol;
     if (!reader.ReadLine(&cursor, &eol)) {  // EOF or malformed line.
@@ -559,11 +577,6 @@
     }
     ++cursor;  // Skip ' '.
 
-    // Check start and end addresses.
-    if (!(start_address <= pc && pc < end_address)) {
-      continue;  // We skip this map.  PC isn't in this map.
-    }
-
     // Read flags.  Skip flags until we encounter a space or eol.
     const char * const flags_start = cursor;
     while (cursor < eol && *cursor != ' ') {
@@ -574,20 +587,71 @@
       return -1;  // Malformed line.
     }
 
-    // Check flags.  We are only interested in "r-x" maps.
-    if (memcmp(flags_start, "r-x", 3) != 0) {  // Not a "r-x" map.
+    // Determine the base address by reading ELF headers in process memory.
+    ElfW(Ehdr) ehdr;
+    // Skip non-readable maps.
+    if (flags_start[0] == 'r' &&
+        ReadFromOffsetExact(mem_fd, &ehdr, sizeof(ElfW(Ehdr)), start_address) &&
+        memcmp(ehdr.e_ident, ELFMAG, SELFMAG) == 0) {
+      switch (ehdr.e_type) {
+        case ET_EXEC:
+          base_address = 0;
+          break;
+        case ET_DYN:
+          // Find the segment containing file offset 0. This will correspond
+          // to the ELF header that we just read. Normally this will have
+          // virtual address 0, but this is not guaranteed. We must subtract
+          // the virtual address from the address where the ELF header was
+          // mapped to get the base address.
+          //
+          // If we fail to find a segment for file offset 0, use the address
+          // of the ELF header as the base address.
+          base_address = start_address;
+          for (unsigned i = 0; i != ehdr.e_phnum; ++i) {
+            ElfW(Phdr) phdr;
+            if (ReadFromOffsetExact(
+                    mem_fd, &phdr, sizeof(phdr),
+                    start_address + ehdr.e_phoff + i * sizeof(phdr)) &&
+                phdr.p_type == PT_LOAD && phdr.p_offset == 0) {
+              base_address = start_address - phdr.p_vaddr;
+              break;
+            }
+          }
+          break;
+        default:
+          // ET_REL or ET_CORE. These aren't directly executable, so they don't
+          // affect the base address.
+          break;
+      }
+    }
+
+    // Check start and end addresses.
+    if (!(start_address <= pc && pc < end_address)) {
+      continue;  // We skip this map.  PC isn't in this map.
+    }
+
+   // Check flags.  We are only interested in "r*x" maps.
+    if (flags_start[0] != 'r' || flags_start[2] != 'x') {
       continue;  // We skip this map.
     }
     ++cursor;  // Skip ' '.
 
-    // Skip to file name.  "cursor" now points to file offset.  We need to
-    // skip at least three spaces for file offset, dev, and inode.
+    // Read file offset.
+    uint64_t file_offset;
+    cursor = GetHex(cursor, eol, &file_offset);
+    if (cursor == eol || *cursor != ' ') {
+      return -1;  // Malformed line.
+    }
+    ++cursor;  // Skip ' '.
+
+    // Skip to file name.  "cursor" now points to dev.  We need to
+    // skip at least two spaces for dev and inode.
     int num_spaces = 0;
     while (cursor < eol) {
       if (*cursor == ' ') {
         ++num_spaces;
-      } else if (num_spaces >= 3) {
-        // The first non-space character after  skipping three spaces
+      } else if (num_spaces >= 2) {
+        // The first non-space character after skipping two spaces
         // is the beginning of the file name.
         break;
       }
@@ -600,12 +664,110 @@
     // Finally, "cursor" now points to file name of our interest.
     NO_INTR(object_fd = open(cursor, O_RDONLY));
     if (object_fd < 0) {
+      // Failed to open object file.  Copy the object file name to
+      // |out_file_name|.
+      strncpy(out_file_name, cursor, out_file_name_size);
+      // Making sure |out_file_name| is always null-terminated.
+      out_file_name[out_file_name_size - 1] = '\0';
       return -1;
     }
     return object_fd;
   }
 }
 
+int OpenObjectFileContainingPcAndGetStartAddress(uint64_t pc,
+                                                 uint64_t& start_address,
+                                                 uint64_t& base_address,
+                                                 char* out_file_name,
+                                                 size_t out_file_name_size) {
+  if (g_symbolize_open_object_file_callback) {
+    return g_symbolize_open_object_file_callback(
+        pc, start_address, base_address, out_file_name, out_file_name_size);
+  }
+  return OpenObjectFileContainingPcAndGetStartAddressNoHook(
+      pc, start_address, base_address, out_file_name, out_file_name_size);
+}
+
+// POSIX doesn't define any async-signal safe function for converting
+// an integer to ASCII. We'll have to define our own version.
+// itoa_r() converts an (unsigned) integer to ASCII. It returns "buf", if the
+// conversion was successful or NULL otherwise. It never writes more than "sz"
+// bytes. Output will be truncated as needed, and a NUL character is always
+// appended.
+// NOTE: code from sandbox/linux/seccomp-bpf/demo.cc.
+static char* itoa_r(uintptr_t i,
+                    char* buf,
+                    size_t sz,
+                    unsigned base,
+                    size_t padding) {
+  // Make sure we can write at least one NUL byte.
+  size_t n = 1;
+  if (n > sz) {
+    return NULL;
+  }
+
+  if (base < 2 || base > 16) {
+    buf[0] = '\000';
+    return NULL;
+  }
+
+  char *start = buf;
+
+  // Loop until we have converted the entire number. Output at least one
+  // character (i.e. '0').
+  char *ptr = start;
+  do {
+    // Make sure there is still enough space left in our output buffer.
+    if (++n > sz) {
+      buf[0] = '\000';
+      return NULL;
+    }
+
+    // Output the next digit.
+    *ptr++ = "0123456789abcdef"[i % base];
+    i /= base;
+
+    if (padding > 0) {
+      padding--;
+    }
+  } while (i > 0 || padding > 0);
+
+  // Terminate the output with a NUL character.
+  *ptr = '\000';
+
+  // Conversion to ASCII actually resulted in the digits being in reverse
+  // order. We can't easily generate them in forward order, as we can't tell
+  // the number of characters needed until we are done converting.
+  // So, now, we reverse the string (except for the possible "-" sign).
+  while (--ptr > start) {
+    char ch = *ptr;
+    *ptr = *start;
+    *start++ = ch;
+  }
+  return buf;
+}
+
+// Safely appends string |source| to string |dest|.  Never writes past the
+// buffer size |dest_size| and guarantees that |dest| is null-terminated.
+static void SafeAppendString(const char* source, char* dest, size_t dest_size) {
+  size_t dest_string_length = strlen(dest);
+  SAFE_ASSERT(dest_string_length < dest_size);
+  dest += dest_string_length;
+  dest_size -= dest_string_length;
+  strncpy(dest, source, dest_size);
+  // Making sure |dest| is always null-terminated.
+  dest[dest_size - 1] = '\0';
+}
+
+// Converts a 64-bit value into a hex string, and safely appends it to |dest|.
+// Never writes past the buffer size |dest_size| and guarantees that |dest| is
+// null-terminated.
+static void SafeAppendHexNumber(uint64_t value, char* dest, size_t dest_size) {
+  // 64-bit numbers in hex can have up to 16 digits.
+  char buf[17] = {'\0'};
+  SafeAppendString(itoa_r(value, buf, sizeof(buf), 16, 0), dest, dest_size);
+}
+
 // The implementation of our symbolization routine.  If it
 // successfully finds the symbol containing "pc" and obtains the
 // symbol name, returns true and write the symbol name to "out".
@@ -614,11 +776,18 @@
 // and "out" is used as its output.
 // To keep stack consumption low, we would like this function to not
 // get inlined.
-static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
-                                                    int out_size) {
+static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void* pc,
+                                                    char* out,
+                                                    size_t out_size) {
   uint64_t pc0 = reinterpret_cast<uintptr_t>(pc);
   uint64_t start_address = 0;
+  uint64_t base_address = 0;
 
+  if (out_size < 1) {
+    return false;
+  }
+  out[0] = '\0';
+  SafeAppendString("(", out, out_size);
 #if SB_IS(EVERGREEN_COMPATIBLE)
   char* file_name = NULL;
   EvergreenInfo evergreen_info;
@@ -626,23 +795,44 @@
     if (IS_EVERGREEN_ADDRESS(pc, evergreen_info)) {
       file_name = evergreen_info.file_path_buf;
       start_address = evergreen_info.base_address;
+      base_address = evergreen_info.base_address;
     }
   }
   int object_fd = -1;
   if (file_name != NULL) {
     object_fd = OpenFile(file_name);
   } else {
-    object_fd =
-        OpenObjectFileContainingPcAndGetStartAddress(pc0, start_address);
+    object_fd = OpenObjectFileContainingPcAndGetStartAddress(
+      pc0, start_address, base_address, out + 1, out_size - 1);
+
   }
 #else
-  int object_fd =
-      OpenObjectFileContainingPcAndGetStartAddress(pc0, start_address);
+  int object_fd = OpenObjectFileContainingPcAndGetStartAddress(
+      pc0, start_address, base_address, out + 1, out_size - 1);
 #endif
-  if (object_fd == -1) {
+
+  FileDescriptor wrapped_object_fd(object_fd);
+
+#if defined(PRINT_UNSYMBOLIZED_STACK_TRACES)
+  {
+#else
+  // Check whether a file name was returned.
+  if (object_fd < 0) {
+#endif
+    if (out[1]) {
+      // The object file containing PC was determined successfully however the
+      // object file was not opened successfully.  This is still considered
+      // success because the object file name and offset are known and tools
+      // like asan_symbolize.py can be used for the symbolization.
+      out[out_size - 1] = '\0';  // Making sure |out| is always null-terminated.
+      SafeAppendString("+0x", out, out_size);
+      SafeAppendHexNumber(pc0 - base_address, out, out_size);
+      SafeAppendString(")", out, out_size);
+      return true;
+    }
+    // Failed to determine the object file containing PC.  Bail out.
     return false;
   }
-  FileDescriptor wrapped_object_fd(object_fd);
   int elf_type = FileGetElfType(wrapped_object_fd.get());
   if (elf_type == -1) {
     return false;
@@ -651,17 +841,28 @@
     // Run the call back if it's installed.
     // Note: relocation (and much of the rest of this code) will be
     // wrong for prelinked shared libraries and PIE executables.
-    uint64 relocation = (elf_type == ET_DYN) ? start_address : 0;
+    uint64_t relocation = (elf_type == ET_DYN) ? start_address : 0;
     int num_bytes_written = g_symbolize_callback(wrapped_object_fd.get(),
                                                  pc, out, out_size,
                                                  relocation);
     if (num_bytes_written > 0) {
-      out += num_bytes_written;
-      out_size -= num_bytes_written;
+      out += static_cast<size_t>(num_bytes_written);
+      out_size -= static_cast<size_t>(num_bytes_written);
     }
   }
-  if (!GetSymbolFromObjectFile(wrapped_object_fd.get(), pc0, out, out_size,
-                               start_address)) {
+  if (!GetSymbolFromObjectFile(wrapped_object_fd.get(), pc0,
+                               out, out_size, base_address)) {
+    if (out[1] && !g_symbolize_callback) {
+      // The object file containing PC was opened successfully however the
+      // symbol was not found. The object may have been stripped. This is still
+      // considered success because the object file name and offset are known
+      // and tools like asan_symbolize.py can be used for the symbolization.
+      out[out_size - 1] = '\0';  // Making sure |out| is always null-terminated.
+      SafeAppendString("+0x", out, out_size);
+      SafeAppendHexNumber(pc0 - base_address, out, out_size);
+      SafeAppendString(")", out, out_size);
+      return true;
+    }
     return false;
   }
 
@@ -672,22 +873,26 @@
 
 _END_GOOGLE_NAMESPACE_
 
-#elif defined(OS_MACOSX) && defined(HAVE_DLADDR)
+#elif (defined(GLOG_OS_MACOSX) || defined(GLOG_OS_EMSCRIPTEN)) && \
+    defined(HAVE_DLADDR)
 
 #include <dlfcn.h>
-#include <string.h>
+#include <cstring>
 
 _START_GOOGLE_NAMESPACE_
 
-static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
-                                                    int out_size) {
+static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void* pc,
+                                                    char* out,
+                                                    size_t out_size) {
   Dl_info info;
   if (dladdr(pc, &info)) {
-    if (strlen(info.dli_sname) < out_size) {
-      strcpy(out, info.dli_sname);
-      // Symbolization succeeded.  Now we try to demangle the symbol.
-      DemangleInplace(out, out_size);
-      return true;
+    if (info.dli_sname) {
+      if (strlen(info.dli_sname) < out_size) {
+        strcpy(out, info.dli_sname);
+        // Symbolization succeeded.  Now we try to demangle the symbol.
+        DemangleInplace(out, out_size);
+        return true;
+      }
     }
   }
   return false;
@@ -695,14 +900,79 @@
 
 _END_GOOGLE_NAMESPACE_
 
+#elif defined(GLOG_OS_WINDOWS) || defined(GLOG_OS_CYGWIN)
+
+#include <windows.h>
+#include <dbghelp.h>
+
+#ifdef _MSC_VER
+#pragma comment(lib, "dbghelp")
+#endif
+
+_START_GOOGLE_NAMESPACE_
+
+class SymInitializer {
+public:
+  HANDLE process;
+  bool ready;
+  SymInitializer() : process(NULL), ready(false) {
+    // Initialize the symbol handler.
+    // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680344(v=vs.85).aspx
+    process = GetCurrentProcess();
+    // Defer symbol loading.
+    // We do not request undecorated symbols with SYMOPT_UNDNAME
+    // because the mangling library calls UnDecorateSymbolName.
+    SymSetOptions(SYMOPT_DEFERRED_LOADS);
+    if (SymInitialize(process, NULL, true)) {
+      ready = true;
+    }
+  }
+  ~SymInitializer() {
+    SymCleanup(process);
+    // We do not need to close `HANDLE process` because it's a "pseudo handle."
+  }
+private:
+  SymInitializer(const SymInitializer&);
+  SymInitializer& operator=(const SymInitializer&);
+};
+
+static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void* pc,
+                                                    char* out,
+                                                    size_t out_size) {
+  const static SymInitializer symInitializer;
+  if (!symInitializer.ready) {
+    return false;
+  }
+  // Resolve symbol information from address.
+  // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680578(v=vs.85).aspx
+  char buf[sizeof(SYMBOL_INFO) + MAX_SYM_NAME];
+  SYMBOL_INFO *symbol = reinterpret_cast<SYMBOL_INFO *>(buf);
+  symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
+  symbol->MaxNameLen = MAX_SYM_NAME;
+  // We use the ANSI version to ensure the string type is always `char *`.
+  // This could break if a symbol has Unicode in it.
+  BOOL ret = SymFromAddr(symInitializer.process,
+                         reinterpret_cast<DWORD64>(pc), 0, symbol);
+  if (ret == 1 && static_cast<ssize_t>(symbol->NameLen) < out_size) {
+    // `NameLen` does not include the null terminating character.
+    strncpy(out, symbol->Name, static_cast<size_t>(symbol->NameLen) + 1);
+    out[static_cast<size_t>(symbol->NameLen)] = '\0';
+    // Symbolization succeeded.  Now we try to demangle the symbol.
+    DemangleInplace(out, out_size);
+    return true;
+  }
+  return false;
+}
+
+_END_GOOGLE_NAMESPACE_
+
 #else
 # error BUG: HAVE_SYMBOLIZE was wrongly set
 #endif
 
 _START_GOOGLE_NAMESPACE_
 
-bool Symbolize(void *pc, char *out, int out_size) {
-  SAFE_ASSERT(out_size >= 0);
+bool Symbolize(void* pc, char* out, size_t out_size) {
   return SymbolizeAndDemangle(pc, out, out_size);
 }
 
@@ -710,14 +980,14 @@
 
 #else  /* HAVE_SYMBOLIZE */
 
-#include <assert.h>
+#include <cassert>
 
 #include "config.h"
 
 _START_GOOGLE_NAMESPACE_
 
 // TODO: Support other environments.
-bool Symbolize(void *pc, char *out, int out_size) {
+bool Symbolize(void* /*pc*/, char* /*out*/, size_t /*out_size*/) {
   assert(0);
   return false;
 }
diff --git a/base/third_party/symbolize/symbolize.h b/base/third_party/symbolize/symbolize.h
index 609af8f..588f4ab 100644
--- a/base/third_party/symbolize/symbolize.h
+++ b/base/third_party/symbolize/symbolize.h
@@ -51,13 +51,15 @@
 // malloc() and other unsafe operations.  It should be both
 // thread-safe and async-signal-safe.
 
-// Note for Cobalt: Cobalt Starboard depends on the old version of Symbolize so
-// this file is from m27 Chromium. There are no Cobalt-introduced changes in
-// this file.
-
 #ifndef BASE_SYMBOLIZE_H_
 #define BASE_SYMBOLIZE_H_
 
+#if defined (STARBOARD)
+#define GLOG_EXPORT
+#endif
+
+#include <sys/types.h>  // for ssize_t
+
 #include "utilities.h"
 #include "config.h"
 #include "glog/logging.h"
@@ -98,17 +100,58 @@
 
 _START_GOOGLE_NAMESPACE_
 
+// Read up to "count" bytes from "offset" in the file pointed by file
+// descriptor "fd" into the buffer starting at "buf" while handling short reads
+// and EINTR.  On success, return the number of bytes read.  Otherwise, return
+// -1.
+ssize_t ReadFromOffset(const int fd,
+                       void* buf,
+                       const size_t count,
+                       const size_t offset);
+
 // Gets the section header for the given name, if it exists. Returns true on
 // success. Otherwise, returns false.
 bool GetSectionHeaderByName(int fd, const char *name, size_t name_len,
                             ElfW(Shdr) *out);
 
+// Searches for the object file (from /proc/self/maps) that contains
+// the specified pc.  If found, sets |start_address| to the start address
+// of where this object file is mapped in memory, sets the module base
+// address into |base_address|, copies the object file name into
+// |out_file_name|, and attempts to open the object file.  If the object
+// file is opened successfully, returns the file descriptor.  Otherwise,
+// returns -1.  |out_file_name_size| is the size of the file name buffer
+// (including the null-terminator).
+ATTRIBUTE_NOINLINE int OpenObjectFileContainingPcAndGetStartAddress(
+    uint64_t pc,
+    uint64_t& start_address,
+    uint64_t& base_address,
+    char* out_file_name,
+    size_t out_file_name_size);
+
 _END_GOOGLE_NAMESPACE_
 
-#endif  /* __ELF__ */
+#endif /* __ELF__ */
 
 _START_GOOGLE_NAMESPACE_
 
+// Thin wrapper around a file descriptor so that the file descriptor
+// gets closed for sure.
+struct FileDescriptor {
+  const int fd_;
+  explicit FileDescriptor(int fd) : fd_(fd) {}
+  ~FileDescriptor();
+  int get() { return fd_; }
+
+ private:
+  FileDescriptor(const FileDescriptor&);
+  void operator=(const FileDescriptor&);
+};
+
+// Restrictions on the callbacks that follow:
+//  - The callbacks must not use heaps but only use stacks.
+//  - The callbacks must be async-signal-safe.
+
 // Installs a callback function, which will be called right before a symbol name
 // is printed. The callback is intended to be used for showing a file name and a
 // line number preceding a symbol name.
@@ -120,9 +163,28 @@
                                  void* pc,
                                  char* out,
                                  size_t out_size,
-                                 uint64 relocation);
+                                 uint64_t relocation);
+GLOG_EXPORT
 void InstallSymbolizeCallback(SymbolizeCallback callback);
 
+// Installs a callback function, which will be called instead of
+// OpenObjectFileContainingPcAndGetStartAddress.  The callback is expected
+// to searches for the object file (from /proc/self/maps) that contains
+// the specified pc.  If found, sets |start_address| to the start address
+// of where this object file is mapped in memory, sets the module base
+// address into |base_address|, copies the object file name into
+// |out_file_name|, and attempts to open the object file.  If the object
+// file is opened successfully, returns the file descriptor.  Otherwise,
+// returns -1.  |out_file_name_size| is the size of the file name buffer
+// (including the null-terminator).
+typedef int (*SymbolizeOpenObjectFileCallback)(uint64_t pc,
+                                               uint64_t& start_address,
+                                               uint64_t& base_address,
+                                               char* out_file_name,
+                                               size_t out_file_name_size);
+void InstallSymbolizeOpenObjectFileCallback(
+    SymbolizeOpenObjectFileCallback callback);
+
 _END_GOOGLE_NAMESPACE_
 
 #endif
@@ -133,7 +195,7 @@
 // symbol name to "out".  The symbol name is demangled if possible
 // (supports symbols generated by GCC 3.x or newer).  Otherwise,
 // returns false.
-bool Symbolize(void *pc, char *out, int out_size);
+GLOG_EXPORT bool Symbolize(void* pc, char* out, size_t out_size);
 
 _END_GOOGLE_NAMESPACE_
 
diff --git a/base/third_party/symbolize/utilities.h b/base/third_party/symbolize/utilities.h
index 0bed526..bb206a8 100644
--- a/base/third_party/symbolize/utilities.h
+++ b/base/third_party/symbolize/utilities.h
@@ -1,11 +1,47 @@
-// Copyright (c) 2010 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.
+// Copyright (c) 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: Shinichiro Hamaji
+//
+// Define utilties for glog internal usage.
 
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-typedef uint64_t uint64;
-#define HAVE_SYMBOLIZE 1
-#define ATTRIBUTE_NOINLINE __attribute__ ((noinline))
+#ifndef UTILITIES_H__
+#define UTILITIES_H__
+
+#ifdef HAVE___ATTRIBUTE__
+#define ATTRIBUTE_NOINLINE __attribute__((noinline))
+#define HAVE_ATTRIBUTE_NOINLINE
+#elif defined(GLOG_OS_WINDOWS)
+#define ATTRIBUTE_NOINLINE __declspec(noinline)
+#define HAVE_ATTRIBUTE_NOINLINE
+#else
+#define ATTRIBUTE_NOINLINE
+#endif
+
+#endif  // UTILITIES_H__
diff --git a/base/threading/thread_task_runner_handle.cc b/base/threading/thread_task_runner_handle.cc
index 314b303..85b47f2 100644
--- a/base/threading/thread_task_runner_handle.cc
+++ b/base/threading/thread_task_runner_handle.cc
@@ -24,7 +24,7 @@
 }  // namespace
 
 // static
-scoped_refptr<SingleThreadTaskRunner> ThreadTaskRunnerHandle::Get() {
+const scoped_refptr<SingleThreadTaskRunner>& ThreadTaskRunnerHandle::Get() {
   ThreadTaskRunnerHandle* current = thread_task_runner_tls.Pointer()->Get();
   CHECK(current) << "Error: This caller requires a single-threaded context "
                     "(i.e. the current task needs to run from a "
diff --git a/base/threading/thread_task_runner_handle.h b/base/threading/thread_task_runner_handle.h
index f6b71d7..fc66fec 100644
--- a/base/threading/thread_task_runner_handle.h
+++ b/base/threading/thread_task_runner_handle.h
@@ -22,7 +22,7 @@
 class BASE_EXPORT ThreadTaskRunnerHandle {
  public:
   // Gets the SingleThreadTaskRunner for the current thread.
-  static scoped_refptr<SingleThreadTaskRunner> Get();
+  static const scoped_refptr<SingleThreadTaskRunner>& Get();
 
   // Returns true if the SingleThreadTaskRunner is already created for
   // the current thread.
diff --git a/build/config/win/visual_studio_version.gni b/build/config/win/visual_studio_version.gni
index 74828ec..13ca252 100644
--- a/build/config/win/visual_studio_version.gni
+++ b/build/config/win/visual_studio_version.gni
@@ -36,7 +36,12 @@
   if (is_docker_build) {
     _default_visual_studio_path = "C:/BuildTools"
   } else {
-    _default_visual_studio_path = "C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional"
+    if (use_visual_studio_2022) {
+      _vis_std_year = "2022"
+    } else {
+      _vis_std_year = "2017"
+    }
+    _default_visual_studio_path = "C:/Program Files (x86)/Microsoft Visual Studio/$_vis_std_year/Professional"
   }
 
   declare_args() {
diff --git a/cobalt/BUILD.gn b/cobalt/BUILD.gn
index 2c221a3..cecfe49 100644
--- a/cobalt/BUILD.gn
+++ b/cobalt/BUILD.gn
@@ -38,6 +38,7 @@
     "//cobalt/renderer/sandbox:renderer_sandbox",
     "//cobalt/renderer/sandbox:scaling_text_sandbox",
     "//cobalt/speech/sandbox:speech_sandbox",
+    "//cobalt/ui_navigation/scroll_engine:scroll_engine_tests",
     "//cobalt/web:web_test",
     "//cobalt/web_animations:web_animations_test",
     "//cobalt/webdriver:webdriver_test",
diff --git a/cobalt/audio/audio_buffer_source_node.cc b/cobalt/audio/audio_buffer_source_node.cc
index 3d1153a..bcc8ba7 100644
--- a/cobalt/audio/audio_buffer_source_node.cc
+++ b/cobalt/audio/audio_buffer_source_node.cc
@@ -20,6 +20,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/audio/audio_context.h"
 #include "cobalt/audio/audio_helpers.h"
 #include "cobalt/audio/audio_node_output.h"
@@ -35,7 +36,7 @@
 AudioBufferSourceNode::AudioBufferSourceNode(
     script::EnvironmentSettings* settings, AudioContext* context)
     : AudioNode(settings, context),
-      task_runner_(base::MessageLoop::current()->task_runner()),
+      task_runner_(base::ThreadTaskRunnerHandle::Get()),
       state_(kNone),
       read_index_(0),
       buffer_source_added_(false),
diff --git a/cobalt/audio/audio_context.cc b/cobalt/audio/audio_context.cc
index 0e7ae9d..45f8568 100644
--- a/cobalt/audio/audio_context.cc
+++ b/cobalt/audio/audio_context.cc
@@ -18,6 +18,7 @@
 #include <utility>
 
 #include "base/callback.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/environment_settings.h"
@@ -42,7 +43,7 @@
       ALLOW_THIS_IN_INITIALIZER_LIST(
           destination_(new AudioDestinationNode(settings, this))),
       next_callback_id_(0),
-      main_message_loop_(base::MessageLoop::current()->task_runner()) {
+      main_message_loop_(base::ThreadTaskRunnerHandle::Get()) {
   DCHECK(main_message_loop_);
 }
 
diff --git a/cobalt/base/fixed_size_lru_cache.h b/cobalt/base/fixed_size_lru_cache.h
index 341c6c0..a1d03d5 100644
--- a/cobalt/base/fixed_size_lru_cache.h
+++ b/cobalt/base/fixed_size_lru_cache.h
@@ -16,6 +16,7 @@
 
 #include <algorithm>
 #include <functional>
+#include <utility>
 
 #include "base/basictypes.h"
 #include "base/logging.h"
@@ -40,7 +41,7 @@
 
   // Functor that checks if two items have keys that match (based on a template
   // argument).
-  struct KeyEqual : public std::unary_function<KeyEqual, bool> {
+  struct KeyEqual {
     explicit KeyEqual(const Key& key) : to_check(key) {}
     bool operator()(const value_type& i) const {
       KeyCompare comparator;
diff --git a/cobalt/base/wrap_main_starboard.h b/cobalt/base/wrap_main_starboard.h
index 4d4bd74..db37f6c 100644
--- a/cobalt/base/wrap_main_starboard.h
+++ b/cobalt/base/wrap_main_starboard.h
@@ -53,14 +53,8 @@
       DCHECK(!g_loop);
       g_loop = new base::MessageLoopForUI();
       g_loop->Start();
-#if SB_API_VERSION >= 13
       preload_function(data->argument_count, data->argument_values, data->link,
                        base::Bind(&SbSystemRequestStop, 0), event->timestamp);
-#else   // SB_API_VERSION >= 13
-      preload_function(data->argument_count, data->argument_values, data->link,
-                       base::Bind(&SbSystemRequestStop, 0),
-                       SbTimeGetMonotonicNow());
-#endif  // SB_API_VERSION >= 13
       g_started = true;
       break;
     }
@@ -79,14 +73,8 @@
         g_loop = new base::MessageLoopForUI();
         g_loop->Start();
       }
-#if SB_API_VERSION >= 13
       start_function(data->argument_count, data->argument_values, data->link,
                      base::Bind(&SbSystemRequestStop, 0), event->timestamp);
-#else   // SB_API_VERSION >= 13
-      start_function(data->argument_count, data->argument_values, data->link,
-                     base::Bind(&SbSystemRequestStop, 0),
-                     SbTimeGetMonotonicNow());
-#endif  // SB_API_VERSION >= 13
       g_started = true;
       break;
     }
@@ -107,29 +95,18 @@
       g_at_exit = NULL;
       break;
     }
-#if SB_API_VERSION >= 13
     case kSbEventTypeBlur:
     case kSbEventTypeFocus:
     case kSbEventTypeConceal:
     case kSbEventTypeReveal:
     case kSbEventTypeFreeze:
     case kSbEventTypeUnfreeze:
-#else
-    case kSbEventTypePause:
-    case kSbEventTypeUnpause:
-    case kSbEventTypeSuspend:
-    case kSbEventTypeResume:
-#endif  // SB_API_VERSION >= 13
     case kSbEventTypeInput:
     case kSbEventTypeUser:
     case kSbEventTypeLink:
     case kSbEventTypeVerticalSync:
     case kSbEventTypeScheduled:
-#if SB_API_VERSION >= 13
     case kSbEventTypeAccessibilitySettingsChanged:
-#else
-    case kSbEventTypeAccessiblitySettingsChanged:
-#endif  // SB_API_VERSION >= 13
     case kSbEventTypeLowMemory:
     case kSbEventTypeWindowSizeChanged:
     case kSbEventTypeOnScreenKeyboardShown:
@@ -138,18 +115,10 @@
     case kSbEventTypeOnScreenKeyboardBlurred:
     case kSbEventTypeOnScreenKeyboardSuggestionsUpdated:
     case kSbEventTypeAccessibilityCaptionSettingsChanged:
-#if SB_API_VERSION >= 13
     case kSbEventTypeAccessibilityTextToSpeechSettingsChanged:
-#else
-    case kSbEventTypeAccessiblityTextToSpeechSettingsChanged:
-#endif  // SB_API_VERSION >= 13
-#if SB_API_VERSION >= 13
     case kSbEventTypeOsNetworkDisconnected:
     case kSbEventTypeOsNetworkConnected:
-#endif
-#if SB_API_VERSION >= 13
     case kSbEventDateTimeConfigurationChanged:
-#endif
       event_function(event);
       break;
   }
diff --git a/cobalt/bindings/code_generator_cobalt.py b/cobalt/bindings/code_generator_cobalt.py
index e82d1eb..6756410 100644
--- a/cobalt/bindings/code_generator_cobalt.py
+++ b/cobalt/bindings/code_generator_cobalt.py
@@ -77,8 +77,7 @@
       if (special in operation.specials and operation.arguments and
           str(operation.arguments[0].idl_type) == 'unsigned long'))
   assert len(special_operations) <= 1, (
-      'Multiple indexed %ss defined on interface: %s' %
-      (special, interface.name))
+      f'Multiple indexed {special}s defined on interface: {interface.name}')
   return special_operations[0] if special_operations else None
 
 
@@ -106,7 +105,7 @@
       if (special in operation.specials and operation.arguments and
           str(operation.arguments[0].idl_type) == 'DOMString'))
   assert len(special_operations) <= 1, (
-      'Multiple named %ss defined on interface: %s' % (special, interface.name))
+      f'Multiple named {special}s defined on interface: {interface.name}')
   return special_operations[0] if special_operations else None
 
 
@@ -186,9 +185,8 @@
   __metaclass__ = abc.ABCMeta
 
   def __init__(self, templates_dir, info_provider, cache_dir, output_dir):
-    super(CodeGeneratorCobalt,
-          self).__init__('CodeGeneratorCobalt', info_provider, cache_dir,
-                         output_dir)
+    super().__init__('CodeGeneratorCobalt', info_provider, cache_dir,
+                     output_dir)
     # CodeGeneratorBase inititalizes this with the v8 template path, so
     # reinitialize it with cobalt's template path
 
@@ -225,7 +223,7 @@
     if definition_name in definitions.enumerations:
       return self.generate_enum_code(definition_name,
                                      definitions.enumerations[definition_name])
-    raise ValueError('%s is not in IDL definitions' % definition_name)
+    raise ValueError(f'{definition_name} is not in IDL definitions')
 
   def generate_interface_code(self, definitions, interface_name, interface):
     interface_info = self.info_provider.interfaces_info[interface_name]
@@ -326,7 +324,7 @@
 
     return {
         'fully_qualified_name':
-            '%s::%s' % (namespace, dictionary_name),
+            f'{namespace}::{dictionary_name}',
         'include':
             self.path_builder.DictionaryHeaderIncludePath(dictionary_name),
         'conditional':
@@ -338,7 +336,7 @@
   def referenced_enum_context(self, enum_name):
     namespace = '::'.join(self.path_builder.NamespaceComponents(enum_name))
     return {
-        'fully_qualified_name': '%s::%s' % (namespace, enum_name),
+        'fully_qualified_name': f'{namespace}::{enum_name}',
         'include': self.path_builder.EnumHeaderIncludePath(enum_name),
         'conditional': None,
         'is_callback_interface': False,
@@ -381,7 +379,7 @@
         impl_name = interface_info.get('implemented_as') or interface_name
         referenced_classes.append({
             'fully_qualified_name':
-                '%s::%s' % (namespace, impl_name),
+                f'{namespace}::{impl_name}',
             'include':
                 self.path_builder.ImplementationHeaderPath(interface_name),
             'conditional':
@@ -392,8 +390,7 @@
         if include_bindings_class:
           referenced_classes.append({
               'fully_qualified_name':
-                  '%s::%s' %
-                  (namespace, self.path_builder.BindingsClass(impl_name)),
+                  f'{namespace}::{self.path_builder.BindingsClass(impl_name)}',
               'include':
                   self.path_builder.BindingsHeaderIncludePath(interface_name),
               'conditional':
@@ -442,7 +439,7 @@
       referenced_interface_names.add(dictionary.parent)
       parent_namespace = '::'.join(
           self.path_builder.NamespaceComponents(dictionary.parent))
-      context['parent'] = '%s::%s' % (parent_namespace, dictionary.parent)
+      context['parent'] = f'{parent_namespace}::{dictionary.parent}'
 
     referenced_class_contexts = self.referenced_class_contexts(
         referenced_interface_names, for_conversion)
@@ -626,7 +623,7 @@
     templates_dir = argv[2]
     dummy_filename = argv[3]
   except IndexError:
-    print('Usage: %s CACHE_DIR TEMPLATES_DIR DUMMY_FILENAME' % argv[0])
+    print(f'Usage: {argv[0]} CACHE_DIR TEMPLATES_DIR DUMMY_FILENAME')
     return 1
 
   # Delete all jinja2 .cache files, since they will get regenerated anyways.
@@ -659,7 +656,7 @@
   # Create a dummy file as output for the build system,
   # since filenames of individual cache files are unpredictable and opaque
   # (they are hashes of the template path, which varies based on environment)
-  with open(dummy_filename, 'w') as dummy_file:
+  with open(dummy_filename, 'w', encoding='utf-8') as dummy_file:
     pass  # |open| creates or touches the file
 
 
diff --git a/cobalt/bindings/contexts.py b/cobalt/bindings/contexts.py
index 10b02da..7cdbea0 100644
--- a/cobalt/bindings/contexts.py
+++ b/cobalt/bindings/contexts.py
@@ -70,8 +70,8 @@
   """Mapping to cobalt value filtering for dictionary acceptable values."""
   if is_any_type(idl_type) and not idl_literal.is_null:
     raise ValueError('Unsupported default value in dictionary: '
-                     '\'%s %s = %s\'. Only null default is supported.' %
-                     (idl_type, name, idl_literal))
+                     f"'{idl_type} {name} = {idl_literal}'. "
+                     'Only null default is supported.')
   return idl_literal_to_cobalt_literal(idl_type, idl_literal)
 
 
@@ -207,7 +207,7 @@
     flags.append('kConversionFlagObjectOnly')
 
   if flags:
-    return '(%s)' % ' | '.join(flags)
+    return f"({' | '.join(flags)})"
   else:
     return 'kNoConversionFlags'
 
@@ -233,7 +233,7 @@
             not idl_type.is_callback_interface), 'Callback types not supported.'
     element_cobalt_type = self.idl_type_to_cobalt_type(
         self.resolve_typedef(element_idl_type))
-    return '::cobalt::script::Sequence< %s >' % element_cobalt_type
+    return f'::cobalt::script::Sequence< {element_cobalt_type} >'
 
   def idl_promise_type_to_cobalt(self, idl_type):
     """Map IDL promise type to C++ promise type implementation."""
@@ -244,8 +244,8 @@
             not idl_type.is_callback_interface), 'Callback types not supported.'
     element_cobalt_type = self.idl_type_to_cobalt_type(
         self.resolve_typedef(result_idl_type))
-    result = 'std::unique_ptr<::cobalt::script::Promise< %s* > >' % (
-        element_cobalt_type)
+    result = ('std::unique_ptr<::cobalt::script::Promise'
+              f'< {element_cobalt_type}* > >')
     return result
 
   def idl_union_type_to_cobalt(self, idl_type):
@@ -269,11 +269,11 @@
       # Some member types need to be wrapped with ScriptValue::Handle.
       if is_any_type(flattened_type) or is_array_buffer_or_view_type(
           flattened_type):
-        cobalt_type = '::cobalt::script::Handle<{}>'.format(cobalt_type)
+        cobalt_type = f'::cobalt::script::Handle<{cobalt_type}>'
       cobalt_types.append(cobalt_type)
 
-    return '::cobalt::script::UnionType%d<%s >' % (len(cobalt_types),
-                                                   ', '.join(cobalt_types))
+    return (f'::cobalt::script::UnionType{len(cobalt_types)}<'
+            f"{', '.join(cobalt_types)} >")
 
   def get_implemented_interface_name(self, idl_type):
     interface_name = get_interface_name(idl_type)
@@ -291,11 +291,11 @@
     elif idl_type.is_string_type:
       cobalt_type = idl_string_type_to_cobalt(idl_type)
     elif idl_type.is_callback_interface:
-      cobalt_type = '::cobalt::script::CallbackInterfaceTraits<%s >' % (
-          self.get_implemented_interface_name(idl_type))
+      cobalt_type = ('::cobalt::script::CallbackInterfaceTraits<'
+                     f'{self.get_implemented_interface_name(idl_type)} >')
     elif idl_type.is_interface_type:
-      cobalt_type = 'scoped_refptr<%s>' % self.get_implemented_interface_name(
-          idl_type)
+      cobalt_type = ('scoped_refptr<'
+                     f'{self.get_implemented_interface_name(idl_type)}>')
     elif idl_type.is_union_type:
       cobalt_type = self.idl_union_type_to_cobalt(idl_type)
     elif idl_type.is_enum:
@@ -315,11 +315,11 @@
     elif is_promise_type(idl_type):
       cobalt_type = self.idl_promise_type_to_cobalt(idl_type)
     elif is_array_buffer_or_view_type(idl_type):
-      cobalt_type = '::cobalt::script::{}'.format(idl_type.base_type)
-    assert cobalt_type, 'Unsupported idl_type %s' % idl_type
+      cobalt_type = f'::cobalt::script::{idl_type.base_type}'
+    assert cobalt_type, f'Unsupported idl_type {idl_type}'
 
     if cobalt_type_is_optional(idl_type):
-      cobalt_type = 'base::Optional<%s >' % cobalt_type
+      cobalt_type = f'base::Optional<{cobalt_type} >'
 
     return cobalt_type
 
@@ -332,7 +332,7 @@
     else:
       cobalt_type = self.idl_type_to_cobalt_type(idl_type)
     if getattr(typed_object, 'is_variadic', False):
-      cobalt_type = 'std::vector<%s>' % cobalt_type
+      cobalt_type = f'std::vector<{cobalt_type}>'
     return cobalt_type
 
   def typed_object_to_arg_type(self, interface, typed_object):
@@ -344,11 +344,11 @@
         idl_type.is_callback_interface):
       return base_type + '*'
     if is_any_type(idl_type) or is_array_buffer_or_view_type(idl_type):
-      return 'const ::cobalt::script::ScriptValue<%s>*' % base_type
+      return f'const ::cobalt::script::ScriptValue<{base_type}>*'
     elif cobalt_type_is_optional(idl_type) or is_sequence_type(idl_type) or (
         idl_type.is_string_type or idl_type.is_interface_type or
         idl_type.is_union_type):
-      return 'const %s&' % base_type
+      return f'const {base_type}&'
     return base_type
 
   def argument_context(self, interface, argument):
@@ -466,10 +466,10 @@
     if operation.name:
       cobalt_name = capitalize_function_name(operation.name)
     elif is_indexed:
-      cobalt_name = 'AnonymousIndexed%s' % function_suffix[special_type]
+      cobalt_name = f'AnonymousIndexed{function_suffix[special_type]}'
     else:
       assert is_named
-      cobalt_name = 'AnonymousNamed%s' % function_suffix[special_type]
+      cobalt_name = f'AnonymousNamed{function_suffix[special_type]}'
 
     context = {
         'name': cobalt_name,
diff --git a/cobalt/bindings/flatten_idls.py b/cobalt/bindings/flatten_idls.py
index 2d463a1..c5e5faa 100644
--- a/cobalt/bindings/flatten_idls.py
+++ b/cobalt/bindings/flatten_idls.py
@@ -116,7 +116,7 @@
     """
     flattened_interfaces = list(flattened_interfaces)
     assert all((isinstance(i, cls) for i in flattened_interfaces))
-    with open(output_file, 'w') as f:
+    with open(output_file, 'w', encoding='utf-8') as f:
       pickle.dump(flattened_interfaces, f)
 
   @classmethod
@@ -128,7 +128,7 @@
     Returns:
       A list of FlattenedInterface objects.
     """
-    with open(pickle_file) as f:
+    with open(pickle_file, encoding='utf-8') as f:
       unpickled_list = pickle.load(f)
     assert all((isinstance(item, cls) for item in unpickled_list))
     return unpickled_list
@@ -153,7 +153,7 @@
   """Parse the list of idl_files and return a list of FlattenedInterfaces."""
   # Import idl_reader here rather than at the beginning of the file because the
   # module import path will be set based on an argument to this script.
-  import idl_reader  # pylint: disable=g-import-not-at-top
+  import idl_reader  # pylint: disable=import-outside-toplevel
 
   # Create an IdlReader that will parse the IDL and return the internal
   # representation of the interface.
@@ -187,7 +187,7 @@
         partial_interfaces[name].append(interface)
       else:
         assert name not in interfaces, ('Multiple IDL files found for '
-                                        'interface: %s') % name
+                                        f'interface: {name}')
         interfaces[name] = interface
 
   # Merge partial interfaces into their main interface.
@@ -243,13 +243,13 @@
   parser.add_argument(
       '--blink_scripts_dir',
       required=True,
-      help='Specify the directory from which blink\'s scripts should be imported.'
-  )
+      help=('Specify the directory from which blink\'s scripts should be '
+            'imported.'))
 
   options = parser.parse_args(argv)
 
   if not os.path.isdir(options.blink_scripts_dir):
-    raise RuntimeError('%s is not a directory' % options.blink_scripts_dir)
+    raise RuntimeError(f'{options.blink_scripts_dir} is not a directory')
 
   # Set the script directory dynamically. This ensures that Blink's IDLs will
   # be loaded using Blink's IDL parsing scripts (and Cobalt IDLs will be loaded
diff --git a/cobalt/bindings/idl_compiler_cobalt.py b/cobalt/bindings/idl_compiler_cobalt.py
index da34fe7..6b8c09d 100644
--- a/cobalt/bindings/idl_compiler_cobalt.py
+++ b/cobalt/bindings/idl_compiler_cobalt.py
@@ -52,9 +52,8 @@
   if options.output_directory is None:
     parser.error('Must specify output directory using --output-directory.')
   if len(args) != 1:
-    parser.error(
-        'Must specify exactly 1 input file as argument, but %d given.' %
-        len(args))
+    parser.error('Must specify exactly 1 input file as argument, '
+                 f'but {len(args)} given.')
   idl_filename = os.path.realpath(args[0])
   return options, idl_filename
 
diff --git a/cobalt/bindings/name_conversion.py b/cobalt/bindings/name_conversion.py
index 00673c8..17c17d5 100644
--- a/cobalt/bindings/name_conversion.py
+++ b/cobalt/bindings/name_conversion.py
@@ -36,7 +36,7 @@
 
 # Regular expression to capture all of the special tokens.
 special_token_re = re.compile(
-    '(%s)' % '|'.join(special_token_list), flags=re.IGNORECASE)
+    f"({'|'.join(special_token_list)})", flags=re.IGNORECASE)
 
 # Split tokens on non-alphanumeric characters (excluding underscores).
 enumeration_value_word_delimeter_re = re.compile(r'[^a-zA-Z0-9]')
@@ -46,8 +46,8 @@
   cobalt_name = titlecase_word_delimiter_re.sub('_', class_name).lower()
   for term in word_list:
     replacement = [
-        token for token in re.split('_?(%s)_?' %
-                                    term, cobalt_name, re.IGNORECASE) if token
+        token for token in re.split(f'_?({term})_?', cobalt_name, re.IGNORECASE)
+        if token
     ]
     cobalt_name = '_'.join(replacement)
   return cobalt_name
@@ -79,9 +79,9 @@
 
 
 def convert_to_cobalt_enumeration_value(enum_type, enum_value):
-  return 'k%s%s' % (enum_type, ''.join(
-      (token.capitalize()
-       for token in enumeration_value_word_delimeter_re.split(enum_value))))
+  tokens = enumeration_value_word_delimeter_re.split(enum_value)
+  capitalized_tokens_joined = ''.join(token.capitalize() for token in tokens)
+  return f'k{enum_type}{capitalized_tokens_joined}'
 
 
 def get_interface_name(idl_type):
diff --git a/cobalt/bindings/overload_context.py b/cobalt/bindings/overload_context.py
index e44d989..ae3faaa 100644
--- a/cobalt/bindings/overload_context.py
+++ b/cobalt/bindings/overload_context.py
@@ -51,7 +51,7 @@
         effective_overload_set_by_length(overloaded_methods))
 
     overload_context['length'] = min(
-        [length for length, _ in effective_overloads_by_length])
+        length for length, _ in effective_overloads_by_length)
 
     # The first four steps of the overload resolution algorithm involve
     # removing entries from the set S of all overloads where the number
diff --git a/cobalt/bindings/path_generator.py b/cobalt/bindings/path_generator.py
index ee91538..9041278 100644
--- a/cobalt/bindings/path_generator.py
+++ b/cobalt/bindings/path_generator.py
@@ -40,7 +40,7 @@
   @property
   def generated_conversion_header_path(self):
     return os.path.join(self.generated_root,
-                        '%s_gen_type_conversion.h' % self.engine_prefix)
+                        f'{self.engine_prefix}_gen_type_conversion.h')
 
   @property
   def generated_conversion_include_path(self):
@@ -59,7 +59,7 @@
       enum_info = self.info_provider.enumerations[interface_name]
       idl_path = enum_info['full_path']
     else:
-      raise KeyError('Unknown interface name %s' % interface_name)
+      raise KeyError(f'Unknown interface name {interface_name}')
 
     rel_idl_path = os.path.relpath(idl_path, self.interfaces_root)
     components = os.path.dirname(rel_idl_path).split(os.sep)
@@ -75,8 +75,7 @@
 
   def FullBindingsClassName(self, impl_name, interface_name):
     """Get the fully qualified name of the generated bindings class."""
-    return '%s::%s' % (self.Namespace(interface_name),
-                       self.BindingsClass(impl_name))
+    return f'{self.Namespace(interface_name)}::{self.BindingsClass(impl_name)}'
 
   def FullClassName(self, impl_name, interface_name=None):
     """Get the fully qualified name of the implementation class."""
@@ -107,7 +106,7 @@
         interface_info['full_path'],
         forward_slashes=True,
         output_directory=self.generated_root,
-        output_prefix='%s_' % self.engine_prefix,
+        output_prefix=f'{self.engine_prefix}_',
         output_extension='h',
         base_directory=os.path.dirname(self.interfaces_root))
 
@@ -118,7 +117,7 @@
         interface_info['full_path'],
         forward_slashes=True,
         output_directory=self.generated_root,
-        output_prefix='%s_' % self.engine_prefix,
+        output_prefix=f'{self.engine_prefix}_',
         output_extension='cc',
         base_directory=os.path.dirname(self.interfaces_root))
 
@@ -144,7 +143,7 @@
         interface_info['full_path'],
         forward_slashes=True,
         output_directory=self.generated_root,
-        output_prefix='%s_' % self.engine_prefix,
+        output_prefix=f'{self.engine_prefix}_',
         output_extension='cc',
         base_directory=os.path.dirname(self.interfaces_root))
 
@@ -170,6 +169,6 @@
         interface_info['full_path'],
         forward_slashes=True,
         output_directory=self.generated_root,
-        output_prefix='%s_' % self.engine_prefix,
+        output_prefix=f'{self.engine_prefix}_',
         output_extension='cc',
         base_directory=os.path.dirname(self.interfaces_root))
diff --git a/cobalt/bindings/testing/bindings_sandbox_main.cc b/cobalt/bindings/testing/bindings_sandbox_main.cc
index 83adb20..3e57a22 100644
--- a/cobalt/bindings/testing/bindings_sandbox_main.cc
+++ b/cobalt/bindings/testing/bindings_sandbox_main.cc
@@ -15,6 +15,7 @@
 #include <iostream>
 #include <string>
 
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/wrap_main.h"
 #include "cobalt/bindings/testing/window.h"
 #include "cobalt/script/javascript_engine.h"
@@ -35,7 +36,7 @@
 
   DCHECK(!g_javascript_runner);
   g_javascript_runner = new cobalt::script::StandaloneJavascriptRunner(
-      base::MessageLoop::current()->task_runner(), javascript_engine_options,
+      base::ThreadTaskRunnerHandle::Get(), javascript_engine_options,
       test_window);
   DCHECK(g_javascript_runner);
   g_javascript_runner->RunUntilDone(quit_closure);
diff --git a/cobalt/bindings/update_blink_idls.py b/cobalt/bindings/update_blink_idls.py
index 5bba317..823d360 100644
--- a/cobalt/bindings/update_blink_idls.py
+++ b/cobalt/bindings/update_blink_idls.py
@@ -15,7 +15,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
 """Generate IDL files for interfaces that are not implemented in Cobalt.
 
 This will do a shallow clone of the chromium repository and gather the set of
@@ -59,8 +58,10 @@
     branch: Name of a branch in Chromium's git repository.
     destination_dir: Directory into which Chromium repository will be cloned.
   """
-  clone_command = ['git', 'clone', '--depth', '1', '--branch', branch,
-                   _CHROMIUM_REPOSITORY_URL, '.']
+  clone_command = [
+      'git', 'clone', '--depth', '1', '--branch', branch,
+      _CHROMIUM_REPOSITORY_URL, '.'
+  ]
   subprocess.check_call(clone_command, cwd=destination_dir)
 
 
@@ -80,7 +81,7 @@
     output_dir: Directory into which the generated IDL file will be written.
   """
   output_idl_filename = os.path.join(output_dir, interface_name) + '.idl'
-  with open(output_idl_filename, 'w') as f:
+  with open(output_idl_filename, 'w', encoding='utf-8') as f:
     f.write(_UNIMPLEMENTED_INTERFACE_TEMPLATE.format(interface_name))
 
 
@@ -95,15 +96,16 @@
   """
   output_idl_filename = os.path.join(output_dir,
                                      interface.name) + '_unsupported.idl'
-  with open(output_idl_filename, 'w') as f:
-    f.write('partial interface %s {\n' % interface.name)
+  with open(output_idl_filename, 'w', encoding='utf-8') as f:
+    f.write(f'partial interface {interface.name} '
+            '{\n')
     for c in interface.constants:
       # Type doesn't matter so use long
-      f.write('  [NotSupported] const long %s;\n' % c)
+      f.write(f'  [NotSupported] const long {c};\n')
     for a in interface.attributes:
-      f.write('  [NotSupported] attribute long %s;\n' % a)
+      f.write(f'  [NotSupported] attribute long {a};\n')
     for o in interface.operations:
-      f.write('  [NotSupported] void %s();\n' % o)
+      f.write(f'  [NotSupported] void {o}();\n')
     f.write('}\n')
 
 
@@ -121,16 +123,16 @@
       help='Directory containing a chromium repository. If the --branch '
       'argument is set, the directory will be clobbered and the specified '
       'branch will be cloned into this directory.')
-  parser.add_argument('--output_dir',
-                      required=True,
-                      help='Directory into which IDL files will be placed. '
-                      'The current contents will be clobbered.')
+  parser.add_argument(
+      '--output_dir',
+      required=True,
+      help='Directory into which IDL files will be placed. '
+      'The current contents will be clobbered.')
 
   options = parser.parse_args(argv)
   logging_format = '%(asctime)s %(levelname)-8s %(message)s'
-  logging.basicConfig(level=logging.INFO,
-                      format=logging_format,
-                      datefmt='%m-%d %H:%M')
+  logging.basicConfig(
+      level=logging.INFO, format=logging_format, datefmt='%m-%d %H:%M')
 
   temp_dir = tempfile.mkdtemp()
 
@@ -153,25 +155,28 @@
     # Gather the blink IDLs
     logging.info('Gathering blink IDLs.')
     blink_pickle_file = os.path.join(temp_dir, 'blink_idl.pickle')
-    subprocess.check_call(
-        ['python', 'flatten_idls.py', '--directory', os.path.join(
-            chromium_dir, 'third_party/WebKit/Source/core'), '--directory',
-         os.path.join(chromium_dir, 'third_party/WebKit/Source/modules'),
-         '--ignore', '*/InspectorInstrumentation.idl', '--blink_scripts_dir',
-         os.path.join(chromium_dir,
-                      'third_party/WebKit/Source/bindings/scripts'),
-         '--output_path', blink_pickle_file])
+    subprocess.check_call([
+        'python', 'flatten_idls.py', '--directory',
+        os.path.join(chromium_dir,
+                     'third_party/WebKit/Source/core'), '--directory',
+        os.path.join(chromium_dir, 'third_party/WebKit/Source/modules'),
+        '--ignore', '*/InspectorInstrumentation.idl', '--blink_scripts_dir',
+        os.path.join(chromium_dir,
+                     'third_party/WebKit/Source/bindings/scripts'),
+        '--output_path', blink_pickle_file
+    ])
 
     # Gather Cobalt's IDLs
     logging.info('Gathering Cobalt IDLs.')
     cobalt_root = os.path.join(_SCRIPT_DIR, '../../../')
     cobalt_pickle_file = os.path.join(temp_dir, 'cobalt_idl.pickle')
-    subprocess.check_call(
-        ['python', 'flatten_idls.py', '--directory', os.path.join(
-            cobalt_root, 'cobalt'), '--ignore', '*/cobalt/bindings/*',
-         '--blink_scripts_dir', os.path.join(
-             cobalt_root, 'third_party/blink/Source/bindings/scripts'),
-         '--output_path', cobalt_pickle_file])
+    subprocess.check_call([
+        'python', 'flatten_idls.py', '--directory',
+        os.path.join(cobalt_root, 'cobalt'), '--ignore', '*/cobalt/bindings/*',
+        '--blink_scripts_dir',
+        os.path.join(cobalt_root, 'third_party/blink/Source/bindings/scripts'),
+        '--output_path', cobalt_pickle_file
+    ])
 
     # Unpickle the files.
     blink_interfaces = _LoadInterfaces(blink_pickle_file)
diff --git a/cobalt/bindings/v8c/_env.py b/cobalt/bindings/v8c/_env.py
index 332eabc..ea1f9b1 100644
--- a/cobalt/bindings/v8c/_env.py
+++ b/cobalt/bindings/v8c/_env.py
@@ -21,6 +21,6 @@
 
 _ENV = path.abspath(path.join(path.dirname(__file__), path.pardir, '_env.py'))
 if not path.exists(_ENV):
-  print('%s: Can\'t find repo root.\nMissing parent: %s' % (__file__, _ENV))
+  print(f'{__file__}: Can\'t find repo root.\nMissing parent: {_ENV}')
   sys.exit(1)
 load_source('', _ENV)
diff --git a/cobalt/bindings/v8c/code_generator_v8c.py b/cobalt/bindings/v8c/code_generator_v8c.py
index 3e11cf6..4c566eb 100644
--- a/cobalt/bindings/v8c/code_generator_v8c.py
+++ b/cobalt/bindings/v8c/code_generator_v8c.py
@@ -26,22 +26,22 @@
   """Implementation of ExpressionGenerator for V8."""
 
   def is_undefined(self, arg):
-    return '{}->IsUndefined()'.format(arg)
+    return f'{arg}->IsUndefined()'
 
   def is_undefined_or_null(self, arg):
-    return '{}->IsNullOrUndefined()'.format(arg)
+    return f'{arg}->IsNullOrUndefined()'
 
   def inherits_interface(self, interface_name, arg):
-    return ('{}->IsObject() ? '
+    return (f'{arg}->IsObject() ? '
             'wrapper_factory->DoesObjectImplementInterface(object, '
-            'base::GetTypeId<{}>()) : false').format(arg, interface_name)
+            f'base::GetTypeId<{interface_name}>()) : false')
 
   def is_number(self, arg):
-    return '{}->IsNumber()'.format(arg)
+    return f'{arg}->IsNumber()'.format(arg)
 
   def is_type(self, interface_name, arg):
-    return ('{}->IsObject() ? '
-            'object->Is{}(): false').format(arg, interface_name)
+    return (f'{arg}->IsObject() ? '
+            f'object->Is{interface_name}(): false')
 
 
 class CodeGeneratorV8c(CodeGeneratorCobalt):
@@ -52,7 +52,7 @@
   def __init__(self, *args, **kwargs):
     module_path, _ = os.path.split(os.path.realpath(__file__))
     templates_dir = os.path.normpath(os.path.join(module_path, 'templates'))
-    super(CodeGeneratorV8c, self).__init__(templates_dir, *args, **kwargs)
+    super().__init__(templates_dir, *args, **kwargs)
 
   def build_interface_context(self, interface, interface_info, definitions):
     # Due to a V8 internals quirks, named constructor attributes MUST come
@@ -60,13 +60,14 @@
     # as the "name" property on the function template value, overriding the
     # originally selected name from |FunctionTemplate::SetClassName|.  Efforts
     # to document/modify this behavior in V8 are underway.
-    context = super(CodeGeneratorV8c, self).build_interface_context(
-        interface, interface_info, definitions)
+    context = super().build_interface_context(interface, interface_info,
+                                              definitions)
     context['all_attributes_v8_order_quirk'] = sorted(
-        [a for a in context['attributes'] + context['static_attributes']],
+        context['attributes'] + context['static_attributes'],
         key=lambda a: not a.get('is_named_constructor_attribute', False))
     return context
 
+  # pylint: disable=invalid-overridden-method
   @property
   def generated_file_prefix(self):
     return 'v8c'
@@ -74,3 +75,5 @@
   @property
   def expression_generator(self):
     return CodeGeneratorV8c._expression_generator
+
+  # pylint: enable=invalid-overridden-method
diff --git a/cobalt/black_box_tests/black_box_cobalt_runner.py b/cobalt/black_box_tests/black_box_cobalt_runner.py
index 1f333e3..9e83f09 100644
--- a/cobalt/black_box_tests/black_box_cobalt_runner.py
+++ b/cobalt/black_box_tests/black_box_cobalt_runner.py
@@ -48,7 +48,9 @@
                url,
                log_file=None,
                target_params=None,
-               success_message=None):
+               success_message=None,
+               poll_until_wait_seconds=POLL_UNTIL_WAIT_SECONDS,
+               **kwargs):
     # For black box tests, don't log inline script warnings, we intend to
     # explicitly control timings for suspends and resumes, so we are not
     # concerned about a "suspend at the wrong time".
@@ -56,8 +58,16 @@
       target_params = []
     target_params.append('--silence_inline_script_warnings')
 
-    super().__init__(launcher_params, url, log_file, target_params,
-                     success_message)
+    super().__init__(
+        launcher_params,
+        url,
+        log_file,
+        target_params,
+        success_message,
+        poll_until_wait_seconds=poll_until_wait_seconds,
+        **kwargs)
+
+    self.poll_until_wait_seconds = poll_until_wait_seconds
 
   def PollUntilFoundOrTestsFailedWithReconnects(self, css_selector):
     """Polls until an element is found.
@@ -66,7 +76,7 @@
       css_selector: A CSS selector
     """
     start_time = time.time()
-    while time.time() - start_time < POLL_UNTIL_WAIT_SECONDS:
+    while time.time() - start_time < self.poll_until_wait_seconds:
       is_failed = False
       try:
         if self.FindElements(css_selector):
@@ -76,7 +86,8 @@
               selenium_exceptions.NoSuchElementException,
               selenium_exceptions.NoSuchWindowException,
               selenium_exceptions.WebDriverException) as e:
-        # If the page
+        # If the page is reloaded, the webdriver client status becomes
+        # stale and should be reconnected.
         logging.warning(e)
         self.ReconnectWebDriver()
         continue
diff --git a/cobalt/black_box_tests/black_box_tests.py b/cobalt/black_box_tests/black_box_tests.py
old mode 100644
new mode 100755
index 079b829..d8cea51
--- a/cobalt/black_box_tests/black_box_tests.py
+++ b/cobalt/black_box_tests/black_box_tests.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python3
 # Copyright 2017 The Cobalt Authors. All Rights Reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -41,6 +42,15 @@
     'raspi-0/devel',
 ]
 
+_EVERGREEN_COMPATIBLE_CONFIGS = [
+    # TODO(b/283788059): enable when there are GitHub jobs to run integration
+    # and Black Box Tests on evergreen-arm-hardfp.
+    #'evergreen-arm/devel',
+    # TODO(b/283144901): enable when the Starboard 16 binaries are released for
+    # Evergreen.
+    #'evergreen-x64/devel',
+]
+
 _PORT_SELECTION_RETRY_LIMIT = 10
 _PORT_SELECTION_RANGE = [5000, 7000]
 # List of blocked ports.
@@ -81,23 +91,34 @@
     'service_worker_persist_test',
     'soft_mic_platform_service_test',
     'text_encoding_test',
+    'wasm_basic_test',
     'web_debugger',
-    'web_platform_tests',
     'web_worker_test',
     'worker_csp_test',
     'worker_load_test',
 ]
+# These are very different and require a custom config + proxy
+_WPT_TESTS = [
+    'web_platform_tests',
+]
 # These tests can only be run on platforms whose app launcher can send deep
 # links.
 _TESTS_NEEDING_DEEP_LINK = [
     'deep_links',
 ]
+# These tests can only run on Evergreen-compatible platforms.
+_TESTS_EVERGREEN_END_TO_END = [
+    'evergreen_verify_qa_channel_update_test',
+]
 # Location of test files.
 _TEST_DIR_PATH = 'cobalt.black_box_tests.tests.'
+
+_LAUNCH_TARGET = 'cobalt'
+
 # Platform configuration and device information parameters.
 _launcher_params = None
 # Binding address used to create the test server.
-_binding_address = None
+_server_binding_address = None
 # Port used to create the web platform test http server.
 _wpt_http_port = None
 
@@ -115,6 +136,7 @@
   @classmethod
   def setUpClass(cls):
     super(BlackBoxTestCase, cls).setUpClass()
+    logging.info('\n\n\n%s\n\n', '=' * 40)
     logging.info('Running %s', cls.__name__)
 
   @classmethod
@@ -122,27 +144,28 @@
     super(BlackBoxTestCase, cls).tearDownClass()
     logging.info('Done %s', cls.__name__)
 
-  def CreateCobaltRunner(self, url=None, target_params=None):
+  def CreateCobaltRunner(self, url=None, target_params=None, **kwargs):
     all_target_params = list(target_params) if target_params else []
     if _launcher_params.target_params is not None:
       all_target_params += _launcher_params.target_params
-    new_runner = black_box_cobalt_runner.BlackBoxCobaltRunner(
+
+    return black_box_cobalt_runner.BlackBoxCobaltRunner(
         launcher_params=_launcher_params,
         url=url,
-        target_params=all_target_params)
-    return new_runner
+        target_params=all_target_params,
+        **kwargs)
 
   def GetBindingAddress(self):
-    return _binding_address
+    return _server_binding_address
 
   def GetWptHttpPort(self):
     return _wpt_http_port
 
 
-def LoadTests(launcher_params):
+def LoadTests(launcher_params, test_set):
   launcher = abstract_launcher.LauncherFactory(
       launcher_params.platform,
-      'cobalt',
+      _LAUNCH_TARGET,
       launcher_params.config,
       device_id=launcher_params.device_id,
       target_params=None,
@@ -152,13 +175,44 @@
       loader_config=launcher_params.loader_config,
       loader_out_directory=launcher_params.loader_out_directory)
 
-  test_targets = _TESTS_NO_SIGNAL
+  test_targets = []
 
-  if launcher.SupportsSuspendResume():
-    test_targets += _TESTS_NEEDING_SYSTEM_SIGNAL
+  if test_set in ['all', 'blackbox']:
+    test_targets = _TESTS_NO_SIGNAL
 
-  if launcher.SupportsDeepLink():
-    test_targets += _TESTS_NEEDING_DEEP_LINK
+    if launcher.SupportsSuspendResume():
+      test_targets += _TESTS_NEEDING_SYSTEM_SIGNAL
+
+    if launcher.SupportsDeepLink():
+      test_targets += _TESTS_NEEDING_DEEP_LINK
+
+  if test_set in ['all', 'wpt']:
+    test_targets += _WPT_TESTS
+
+  test_suite = unittest.TestSuite()
+  for test in test_targets:
+    test_suite.addTest(unittest.TestLoader().loadTestsFromModule(
+        importlib.import_module(_TEST_DIR_PATH + test)))
+  return test_suite
+
+
+def LoadEvergreenEndToEndTests(launcher_params):
+  launcher = abstract_launcher.LauncherFactory(  # pylint: disable=unused-variable
+      launcher_params.platform,
+      _LAUNCH_TARGET,
+      launcher_params.config,
+      device_id=launcher_params.device_id,
+      target_params=None,
+      output_file=None,
+      out_directory=launcher_params.out_directory,
+      loader_platform=launcher_params.loader_platform,
+      loader_config=launcher_params.loader_config,
+      loader_out_directory=launcher_params.loader_out_directory,
+      # The more lightweight elf_loader_sandbox can't be used since it has no
+      # knowledge of updates or installations.
+      loader_target='loader_app')
+
+  test_targets = _TESTS_EVERGREEN_END_TO_END
 
   test_suite = unittest.TestSuite()
   for test in test_targets:
@@ -170,50 +224,49 @@
 class BlackBoxTests(object):
   """Helper class to run all black box tests and return results."""
 
-  def __init__(self,
-               server_binding_address,
-               proxy_address=None,
-               proxy_port=None,
-               test_name=None,
-               wpt_http_port=None,
-               device_ips=None,
-               device_id=None):
+  def __init__(self, args):
+
+    self.args = args
+
+    #TODO(b/137905502): These globals should be refactored
     # Setup global variables used by test cases.
     global _launcher_params
     _launcher_params = command_line.CreateLauncherParams()
     # Keep other modules from seeing these args.
     sys.argv = sys.argv[:1]
-    global _binding_address
-    _binding_address = server_binding_address
+    global _server_binding_address
+    _server_binding_address = args.server_binding_address
+
     # Port used to create the web platform test http server. If not specified,
     # a random free port is used.
-    if wpt_http_port is None:
-      wpt_http_port = str(self.GetUnusedPort([server_binding_address]))
     global _wpt_http_port
-    _wpt_http_port = wpt_http_port
+    _wpt_http_port = args.wpt_http_port or str(
+        self.GetUnusedPort([_server_binding_address]))
+
+    # Proxy is only needed for WPT
+    self.use_proxy = args.test_set in ['all', 'wpt']
+
     # TODO: Remove generation of --dev_servers_listen_ip once executable will
     # be able to bind correctly with incomplete support of IPv6
-    if device_id and IsValidIpAddress(device_id):
+    if args.device_id and IsValidIpAddress(args.device_id):
       _launcher_params.target_params.append(
-          f'--dev_servers_listen_ip={device_id}')
-    elif IsValidIpAddress(server_binding_address):
+          f'--dev_servers_listen_ip={args.device_id}')
+    elif IsValidIpAddress(_server_binding_address):
       _launcher_params.target_params.append(
-          f'--dev_servers_listen_ip={server_binding_address}')
+          f'--dev_servers_listen_ip={_server_binding_address}')
     _launcher_params.target_params.append(
-        f'--web-platform-test-server=http://web-platform.test:{wpt_http_port}')
+        f'--web-platform-test-server=http://web-platform.test:{_wpt_http_port}')
 
     # Port used to create the proxy server. If not specified, a random free
     # port is used.
-    if proxy_port is None:
-      proxy_port = str(self.GetUnusedPort([server_binding_address]))
-    if proxy_address is None:
-      proxy_address = server_binding_address
-    _launcher_params.target_params.append(
-        f'--proxy={proxy_address}:{proxy_port}')
+    if self.use_proxy:
+      self.proxy_port = args.proxy_port or str(
+          self.GetUnusedPort([_server_binding_address]))
+      proxy_address = args.proxy_address or _server_binding_address
+      _launcher_params.target_params.append(
+          f'--proxy={proxy_address}:{self.proxy_port}')
 
-    self.proxy_port = proxy_port
-    self.test_name = test_name
-    self.device_ips = device_ips
+    self.device_ips = args.device_ips
 
     # Test domains used in web platform tests to be resolved to the server
     # binding address.
@@ -222,33 +275,61 @@
         'www2.web-platform.test', 'xn--n8j6ds53lwwkrqhv28a.web-platform.test',
         'xn--lve-6lad.web-platform.test'
     ]
-    self.host_resolve_map = {host: server_binding_address for host in hosts}
+    self.host_resolve_map = {host: _server_binding_address for host in hosts}
 
   def Run(self):
-    if self.proxy_port == '-1':
+    if self.use_proxy and self.proxy_port == '-1':
       return 1
 
-    if (f'{_launcher_params.platform}/{_launcher_params.config}'
-        in _DISABLED_BLACKBOXTEST_CONFIGS):
-      logging.warning('Blackbox tests disabled for platform:%s config:%s',
-                      _launcher_params.platform, _launcher_params.config)
+    run_cobalt_tests = True
+    run_evergreen_tests = False
+    launch_config = f'{_launcher_params.platform}/{_launcher_params.config}'
+    # TODO(b/135549281): Configuring this in Python is superfluous, the on/off
+    # flags can be in Github Actions code
+    if launch_config in _DISABLED_BLACKBOXTEST_CONFIGS:
+      run_cobalt_tests = False
+      logging.warning(
+          'Cobalt blackbox tests disabled for platform:%s config:%s',
+          _launcher_params.platform, _launcher_params.config)
+
+    if launch_config in _EVERGREEN_COMPATIBLE_CONFIGS:
+      run_evergreen_tests = self.args.test_set in ['all', 'evergreen']
+
+    if not (run_cobalt_tests or run_evergreen_tests):
       return 0
 
-    logging.info('Using proxy port: %s', self.proxy_port)
-
-    with ProxyServer(
-        port=self.proxy_port,
-        host_resolve_map=self.host_resolve_map,
-        client_ips=self.device_ips):
-      if self.test_name:
+    def LoadAndRunTests():
+      if self.args.test_name:
         suite = unittest.TestLoader().loadTestsFromName(_TEST_DIR_PATH +
-                                                        self.test_name)
+                                                        self.args.test_name)
+        return_code = not unittest.TextTestRunner(
+            verbosity=2, stream=sys.stdout).run(suite).wasSuccessful()
+        return return_code
       else:
-        suite = LoadTests(_launcher_params)
-      # Using verbosity=2 to log individual test function names and results.
-      return_code = not unittest.TextTestRunner(
-          verbosity=2, stream=sys.stdout).run(suite).wasSuccessful()
-      return return_code
+        cobalt_tests_return_code = 0
+        if run_cobalt_tests:
+          suite = LoadTests(_launcher_params, self.args.test_set)
+          # Using verbosity=2 to log individual test function names and results.
+          cobalt_tests_return_code = not unittest.TextTestRunner(
+              verbosity=2, stream=sys.stdout).run(suite).wasSuccessful()
+
+        evergreen_tests_return_code = 0
+        if run_evergreen_tests:
+          suite = LoadEvergreenEndToEndTests(_launcher_params)
+          evergreen_tests_return_code = not unittest.TextTestRunner(
+              verbosity=2, stream=sys.stdout).run(suite).wasSuccessful()
+
+        return cobalt_tests_return_code or evergreen_tests_return_code
+
+    if self.use_proxy:
+      logging.info('Using proxy port: %s', self.proxy_port)
+      with ProxyServer(
+          port=self.proxy_port,
+          host_resolve_map=self.host_resolve_map,
+          client_ips=self.args.device_ips):
+        return LoadAndRunTests()
+    else:
+      return LoadAndRunTests()
 
   def GetUnusedPort(self, addresses):
     """Find a free port on the list of addresses by pinging with sockets."""
@@ -346,14 +427,15 @@
       nargs='*',
       help=('IPs of test devices that will be allowed to connect. If not '
             'specified, all IPs will be allowed to connect.'))
+  parser.add_argument(
+      '--test_set',
+      choices=['all', 'wpt', 'blackbox', 'evergreen'],
+      default='all')
   args, _ = parser.parse_known_args()
 
   log_level.InitializeLogging(args)
 
-  test_object = BlackBoxTests(args.server_binding_address, args.proxy_address,
-                              args.proxy_port, args.test_name,
-                              args.wpt_http_port, args.device_ips,
-                              args.device_id)
+  test_object = BlackBoxTests(args)
   sys.exit(test_object.Run())
 
 
diff --git a/cobalt/black_box_tests/testdata/evergreen_test.html b/cobalt/black_box_tests/testdata/evergreen_test.html
new file mode 100644
index 0000000..b989250
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/evergreen_test.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!--
+Copyright 2023 The Cobalt Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html>
+
+<<head>
+  <title>Cobalt Evergreen Test</title>
+  <script src='black_box_js_test_utils.js'></script>
+</head>
+
+<body>
+  <script src='evergreen_test_script.js'></script>
+</body>>
+
+</html>
diff --git a/cobalt/black_box_tests/testdata/evergreen_test_script.js b/cobalt/black_box_tests/testdata/evergreen_test_script.js
new file mode 100644
index 0000000..cb03636
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/evergreen_test_script.js
@@ -0,0 +1,101 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+var changeChannelResult = null;
+var waitForStatusResult = null;
+
+function changeChannel() {
+  const currentChannel = window.h5vcc.updater.getUpdaterChannel();
+  const status = window.h5vcc.updater.getUpdateStatus();
+
+  if (currentChannel == "" || status == "") {
+    // An update check hasn't happened yet.
+    return;
+  }
+
+  if (status != "App is up to date" &&
+      status != "Update installed, pending restart") {
+    // An update is in progress.
+    return;
+  }
+
+  if (currentChannel == "prod") {
+    window.h5vcc.updater.setUpdaterChannel(targetChannel);
+    console.log('The channel was changed to ' + targetChannel);
+    clearInterval(changeChannelResult);
+    waitForStatusResult = setInterval(waitForStatus, 500, targetStatus);
+    return;
+  }
+
+  if (currentChannel == targetChannel) {
+    clearInterval(changeChannelResult);
+    waitForStatusResult = setInterval(waitForStatus, 500, targetStatus);
+    return;
+  }
+}
+
+function waitForStatus() {
+  const currentStatus = window.h5vcc.updater.getUpdateStatus();
+
+  if (currentStatus == targetStatus) {
+    console.log('The expected status was found: ' + targetStatus);
+    assertTrue(true);
+    clearInterval(waitForStatusResult);
+    endTest();
+    return;
+  }
+
+  return;
+}
+
+function endTest() {
+  onEndTest();
+  setupFinished();
+}
+
+var resetInstallations = false;
+var encodedStatus = null;
+var targetStatus = null;
+var targetChannel = null;
+
+var query = window.location.search;
+
+if (query) {
+  // Splits each parameter into an array after removing the prepended "?".
+  query = query.slice(1).split("&");
+}
+
+query.forEach(part => {
+  if (part.startsWith("resetInstallations=")) {
+    resetInstallations = (part.split("=")[1] === "true")
+  }
+
+  if (part.startsWith("status=")) {
+    encodedStatus = part.split("=")[1];
+    targetStatus = decodeURI(encodedStatus);
+  }
+
+  if (part.startsWith("channel=")) {
+    targetChannel = part.split("=")[1];
+  }
+});
+
+if (resetInstallations) {
+  window.h5vcc.updater.resetInstallations();
+  console.log('Installations have been reset');
+  assertTrue(true);
+  endTest();
+} else {
+  ChannelResult = setInterval(changeChannel, 500);
+}
diff --git a/cobalt/black_box_tests/testdata/wasm_basic_test.html b/cobalt/black_box_tests/testdata/wasm_basic_test.html
new file mode 100644
index 0000000..ccca569
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/wasm_basic_test.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<!--
+  Copyright 2023 The Cobalt Authors. All Rights Reserved.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<head>
+  <title>Cobalt WASM Basic Test</title>
+  <script src='black_box_js_test_utils.js'></script>
+</head>
+
+<body>
+  <script>
+const fail = msg => {
+  if (msg) {
+    console.error(msg);
+  }
+  notReached();
+};
+const timeoutId = setTimeout(fail, 3000);
+const success = () => {
+  clearTimeout(timeoutId);
+  onEndTest();
+};
+
+const Module = {};
+
+const noop = () => {};
+const wasmImports = {
+  emscripten_notify_memory_growth: () => {
+    if (!Module.asm || !Module.asm.memory) {
+      return;
+    }
+    Module.HEAPU8 = new Uint8Array(Module.asm.memory.buffer);
+  },
+  fd_close: noop,
+  fd_seek: noop,
+  fd_write: noop,
+  proc_exit: noop,
+};
+const importObject = {
+  env: wasmImports,
+  wasi_snapshot_preview1: wasmImports,
+};
+
+const stringFromHeap = stringPointer => {
+  const heap = Module.HEAPU8;
+  let endPointer = stringPointer;
+  // Find null terminating character.
+  while (heap[endPointer]) ++endPointer;
+  return (new TextDecoder('utf8')).decode(heap.subarray(stringPointer, endPointer));
+}
+
+fetch('wasm_basic_test.wasm')
+    .then(response => response.arrayBuffer())
+    .then(bufferSource => WebAssembly.instantiate(bufferSource, importObject))
+    .then(({instance}) => {
+      Module.asm = instance.exports;
+      wasmImports.emscripten_notify_memory_growth();
+      assertEqual(3, Module.asm.add(1, 2));
+      // Include space for null terminating byte.
+      const helloStringPointer = Module.asm.malloc(7);
+      Module.asm.getHello(helloStringPointer);
+      assertEqual("Hello.", stringFromHeap(helloStringPointer));
+      Module.asm.free(helloStringPointer);
+      success();
+    })
+    .catch(fail);
+
+setupFinished();
+  </script>
+</body>
diff --git a/cobalt/black_box_tests/testdata/wasm_basic_test.wasm b/cobalt/black_box_tests/testdata/wasm_basic_test.wasm
new file mode 100755
index 0000000..61e7da2
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/wasm_basic_test.wasm
Binary files differ
diff --git a/cobalt/black_box_tests/testdata/web_worker_test.html b/cobalt/black_box_tests/testdata/web_worker_test.html
index 9be8f5b..f4d264f 100644
--- a/cobalt/black_box_tests/testdata/web_worker_test.html
+++ b/cobalt/black_box_tests/testdata/web_worker_test.html
@@ -19,95 +19,99 @@
   functionality. This can be expanded as more functionality is implemented,
   but probably will be superseded by Web Platform Tests.
 -->
+
 <head>
-    <title>Cobalt Web Worker Test</title>
-    <script src='black_box_js_test_utils.js'></script>
+  <title>Cobalt Web Worker Test</title>
+  <script src='black_box_js_test_utils.js'></script>
 </head>
 
 <body>
-<script>
+  <script>
     var window_error_event_count = 0;
+    var worker_error_event_count = 0;
     window.onerror = function (message) {
-        window_error_event_count += 1;
-        assertIncludes('TypeError: self.Foo is not a function', message);
-        console.log('window got onerror', message);
+      window_error_event_count += 1;
+      console.error('window got onerror', message);
+      notReached();
     };
 
     var message_event_count = 0;
-    console.log('running');
 
-    // This is expected trigger a an error event on window.
+    // This is expected trigger an error event on the Worker object.
     var worker_with_error = new Worker('web_worker_test_with_syntax_error.js',
-        { name : 'worker_with_error'});
+      { name: 'worker_with_error' });
+    worker_with_error.onerror = function (event) {
+      worker_error_event_count += 1;
+      assertIncludes('TypeError: self.Foo is not a function', event.message);
+    };
 
-    var worker = new Worker('web_worker_test.js', { name : 'test_worker'});
-    console.log(worker);
-    worker.onmessage = function (event) {
-        message_event_count += 1;
-        console.log('window got message', message_event_count, 'from worker:', event.data);
-        switch (message_event_count) {
-            case 1:
-                assertEqual('web worker test loaded', event.data);
-                break;
-            case 2:
-                // The first script is expected to execute even though the next
-                // one has a syntax error.
-                assertEqual('Imported Script 1', event.data);
-                break;
-            case 3:
-                assertEqual('Imported Script Before Syntax Error', event.data);
-                break;
-            case 4:
-                assertEqual('Expected exception message 4: SyntaxError', event.data);
-                break;
-            case 5:
-                assertEqual('Expected exception message 5: SyntaxError', event.data);
-                worker.postMessage('import scripts now');
-                worker.postMessage('just some data');
-                worker.postMessage('a special message');
-                break;
-            case 6:
-                // Scripts loaded with importScripts are guaranteed to execute
-                // in order.
-                assertEqual('Imported Script 1', event.data);
-                break;
-            case 7:
-                assertEqual('Imported Script 2', event.data);
-                break;
-            case 8:
-                assertEqual('Imported Script 3', event.data);
-                break;
-            case 9:
-                assertEqual('worker received import scripts now', event.data);
-                break;
-            case 10:
-                assertEqual('IMPORT SCRIPTS NOW', event.data);
-                break;
-            case 11:
-                assertEqual('worker received just some data', event.data);
-                break;
-            case 12:
-                assertEqual('JUST SOME DATA', event.data);
-                break;
-            case 13:
-                assertEqual('worker received a special message', event.data);
-                break;
-            case 14:
-                assertEqual('A SPECIAL MESSAGE', event.data);
-                worker.terminate();
-                window.setTimeout(
-                    () => {
-                        assertEqual(14, message_event_count);
-                        assertEqual(1, window_error_event_count);
-                        onEndTest();
-                    }, 250);
-                break;
-        }
-    };
+    var worker = new Worker('web_worker_test.js', { name: 'test_worker' });
     worker.onerror = function (event) {
-        console.log('window got onerror');
-        notReached();
+      worker_error_event_count += 1;
+      console.error('worker got onerror', event.message);
+      notReached();
     };
-    console.log('end');
-</script>
+    worker.onmessage = function (event) {
+      message_event_count += 1;
+      switch (message_event_count) {
+        case 1:
+          assertEqual('web worker test loaded', event.data);
+          break;
+        case 2:
+          // The first script is expected to execute even though the next
+          // one has a syntax error.
+          assertEqual('Imported Script 1', event.data);
+          break;
+        case 3:
+          assertEqual('Imported Script Before Syntax Error', event.data);
+          break;
+        case 4:
+          assertEqual('Expected exception message 4: SyntaxError', event.data);
+          break;
+        case 5:
+          assertEqual('Expected exception message 5: SyntaxError', event.data);
+          worker.postMessage('import scripts now');
+          worker.postMessage('just some data');
+          worker.postMessage('a special message');
+          break;
+        case 6:
+          // Scripts loaded with importScripts are guaranteed to execute
+          // in order.
+          assertEqual('Imported Script 1', event.data);
+          break;
+        case 7:
+          assertEqual('Imported Script 2', event.data);
+          break;
+        case 8:
+          assertEqual('Imported Script 3', event.data);
+          break;
+        case 9:
+          assertEqual('worker received import scripts now', event.data);
+          break;
+        case 10:
+          assertEqual('IMPORT SCRIPTS NOW', event.data);
+          break;
+        case 11:
+          assertEqual('worker received just some data', event.data);
+          break;
+        case 12:
+          assertEqual('JUST SOME DATA', event.data);
+          break;
+        case 13:
+          assertEqual('worker received a special message', event.data);
+          break;
+        case 14:
+          assertEqual('A SPECIAL MESSAGE', event.data);
+          worker.terminate();
+          window.setTimeout(
+            () => {
+              assertEqual(14, message_event_count);
+              assertEqual(0, window_error_event_count);
+              assertEqual(1, worker_error_event_count);
+              onEndTest();
+            }, 250);
+          break;
+      }
+    };
+  </script>
 </body>
diff --git a/cobalt/black_box_tests/testdata/web_worker_test.js b/cobalt/black_box_tests/testdata/web_worker_test.js
index 8e029a6..ea5a527 100644
--- a/cobalt/black_box_tests/testdata/web_worker_test.js
+++ b/cobalt/black_box_tests/testdata/web_worker_test.js
@@ -16,10 +16,8 @@
 self.postMessage(data);
 self.onmessage = function (event) {
     let message = `worker received ${event.data}`;
-    console.log(message);
     if (event.data == 'import scripts now') {
         // These should load and execute synchronously.
-        console.log('Worker importing scripts.');
         self.importScripts('web_worker_test_importscripts_1.js',
             'web_worker_test_importscripts_2.js',
             'web_worker_test_importscripts_3.js');
@@ -38,7 +36,6 @@
         'web_worker_test_importscripts_3.js');
 } catch (e) {
     message = 'Expected exception message 4: ' + e;
-    console.log(message);
     self.postMessage(message);
 }
 
@@ -46,7 +43,6 @@
     self.importScripts('...:...');
 } catch (e) {
     message = 'Expected exception message 5: ' + e;
-    console.log(message);
     self.postMessage(message);
 }
 
diff --git a/cobalt/black_box_tests/testdata/web_worker_test_importscripts_1.js b/cobalt/black_box_tests/testdata/web_worker_test_importscripts_1.js
index 54c48cd..29aa77a 100644
--- a/cobalt/black_box_tests/testdata/web_worker_test_importscripts_1.js
+++ b/cobalt/black_box_tests/testdata/web_worker_test_importscripts_1.js
@@ -12,5 +12,4 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-console.log('worker test importscripts 1');
 self.postMessage('Imported Script 1');
diff --git a/cobalt/black_box_tests/testdata/web_worker_test_importscripts_2.js b/cobalt/black_box_tests/testdata/web_worker_test_importscripts_2.js
index a32f94b..ea539ba 100644
--- a/cobalt/black_box_tests/testdata/web_worker_test_importscripts_2.js
+++ b/cobalt/black_box_tests/testdata/web_worker_test_importscripts_2.js
@@ -12,5 +12,4 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-console.log('worker test importscripts 2');
 self.postMessage('Imported Script 2');
diff --git a/cobalt/black_box_tests/testdata/web_worker_test_importscripts_3.js b/cobalt/black_box_tests/testdata/web_worker_test_importscripts_3.js
index 3d59d1c..43e714c 100644
--- a/cobalt/black_box_tests/testdata/web_worker_test_importscripts_3.js
+++ b/cobalt/black_box_tests/testdata/web_worker_test_importscripts_3.js
@@ -12,5 +12,4 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-console.log('worker test importscripts 3');
 self.postMessage('Imported Script 3');
diff --git a/cobalt/black_box_tests/testdata/web_worker_test_importscripts_with_syntax_error.js b/cobalt/black_box_tests/testdata/web_worker_test_importscripts_with_syntax_error.js
index 49275c8..743ec51 100644
--- a/cobalt/black_box_tests/testdata/web_worker_test_importscripts_with_syntax_error.js
+++ b/cobalt/black_box_tests/testdata/web_worker_test_importscripts_with_syntax_error.js
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-console.log('worker test importscripts with syntax error');
 self.postMessage('Imported Script Before Syntax Error');
 self.Foo('Bar');
 self.postMessage('Imported Script After Syntax Error');
diff --git a/cobalt/black_box_tests/testdata/worker_csp_test.html b/cobalt/black_box_tests/testdata/worker_csp_test.html
index ba09f97..1b2d0b4 100644
--- a/cobalt/black_box_tests/testdata/worker_csp_test.html
+++ b/cobalt/black_box_tests/testdata/worker_csp_test.html
@@ -22,31 +22,37 @@
 
 <body>
   <script>
-  var worker;
-  var window_onerror_count = 0;
-  window.onerror = (message, filename, lineno, colno, error) => {
-    ++window_onerror_count;
-    // Note: Worker execution errors currently don't pass line or column
-    // number in the error message.
-    assertIncludes('SecurityError', message);
-    assertIncludes('worker_csp_test.js', filename);
-    assertEqual(1, window_onerror_count);
-    window.setTimeout(
-      () => {
-        worker.terminate();
-        onEndTest();
-      }, 250);
-  }
+    var worker;
+    var window_onerror_count = 0;
+    var worker_onerror_count = 0;
+    window.onerror = (message, filename, lineno, colno, error) => {
+      ++window_onerror_count;
+      console.log('window got onerror', message);
+      notReached();
+    }
 
-  // This worker attempts to do an XHR request that is blocked by CSP.
-  worker = new Worker('worker_csp_test.js');
-  worker.onmessage = function (event) {
-    notReached();
-  };
-  worker.onerror = function (event) {
-    // Note: The Worker's onerror handler (incorrectly) isn't called.
-    notReached();
-  };
+    // This worker attempts to do an XHR request that is blocked by CSP.
+    worker = new Worker('worker_csp_test.js');
+    worker.onmessage = function (event) {
+      console.log('worker got onmessage', event.data);
+      notReached();
+    };
+    worker.onerror = function (event) {
+      ++worker_onerror_count;
+      console.log('worker got onerror', event.message);
+      // Note: Worker execution errors currently don't pass line or column
+      // number in the error message.
+      assertIncludes('SecurityError', event.message);
+      assertIncludes('worker_csp_test.js', event.filename);
+      assertEqual(1, worker_onerror_count);
+      window.setTimeout(
+        () => {
+          assertEqual(1, worker_onerror_count);
+          assertEqual(0, window_onerror_count);
+          worker.terminate();
+          onEndTest();
+        }, 250);
+    };
 
   </script>
 </body>
diff --git a/cobalt/black_box_tests/testdata/worker_load_csp_test.html b/cobalt/black_box_tests/testdata/worker_load_csp_test.html
index f265f05..6defe4b 100644
--- a/cobalt/black_box_tests/testdata/worker_load_csp_test.html
+++ b/cobalt/black_box_tests/testdata/worker_load_csp_test.html
@@ -22,35 +22,39 @@
 
 <body>
   <script>
-  var window_onerror_count = 0;
-  window.onerror = (message, filename, lineno, colno, error) => {
-    ++window_onerror_count;
-    if (message.includes('blocked_worker.js')) {
-      assertIncludes('rejected by security policy', message);
-      assertIncludes('worker_load_csp_test.html', filename);
-    } else {
+    var window_onerror_count = 0;
+    var worker_onerror_count = 0;
+    window.onerror = (message, filename, lineno, colno, error) => {
+      ++window_onerror_count;
+      console.log('window got onerror', message);
       notReached();
     }
-    if (window_onerror_count == 1) {
-      window.setTimeout(
-        () => {
-          assertEqual(1, window_onerror_count);
-          onEndTest();
-        }, 250);
 
-    }
-  }
+    // This worker is blocked because the URL isn't allowed by CSP.
+    try {
+      var blocked_worker = new Worker('https://www.google.com/blocked_worker.js');
+      blocked_worker.onerror = function (event) {
+        ++worker_onerror_count;
+        console.log('worker got onerror', event.message);
+        if (event.message.includes('blocked_worker.js')) {
+          assertIncludes('rejected by security policy', event.message);
+          assertEqual('https://www.google.com/blocked_worker.js', event.filename);
+        } else {
+          notReached();
+        }
+        if (worker_onerror_count == 1) {
+          window.setTimeout(
+            () => {
+              assertEqual(1, worker_onerror_count);
+              assertEqual(0, window_onerror_count);
+              onEndTest();
+            }, 250);
 
-  // This worker is blocked because the URL isn't allowed by CSP.
-  try {
-    var blocked_worker = new Worker('https://www.google.com/blocked_worker.js');
-    blocked_worker.onerror = function (event) {
-      // Note: The Worker's onerror handler (incorrectly) isn't called.
+        }
+      };
+    } catch (error) {
+      // The error is thrown asynchronously after the Worker constructor.
       notReached();
-    };
-  } catch (error) {
-    // The error is thrown asynchronously after the Worker constructor.
-    notReached();
-  }
+    }
   </script>
 </body>
diff --git a/cobalt/black_box_tests/testdata/worker_load_test.html b/cobalt/black_box_tests/testdata/worker_load_test.html
index 7f13191..dc4aeac 100644
--- a/cobalt/black_box_tests/testdata/worker_load_test.html
+++ b/cobalt/black_box_tests/testdata/worker_load_test.html
@@ -22,45 +22,48 @@
 
 <body>
   <script>
-  var window_onerror_count = 0;
-  window.onerror = (message, filename, lineno, colno, error) => {
-    ++window_onerror_count;
-    console.log(message);
-    if (message.includes('nonexisting_worker.js')) {
-      assertIncludes('aborted or failed with code 404', message);
-      assertIncludes('worker_load_test.html', filename);
-    } else {
+    var window_onerror_count = 0;
+    var worker_onerror_count = 0;
+    window.onerror = (message, filename, lineno, colno, error) => {
+      ++window_onerror_count;
+      console.log('window got onerror', message);
       notReached();
     }
-    if (window_onerror_count == 1) {
-      window.setTimeout(
-        () => {
-          assertEqual(1, window_onerror_count);
-          onEndTest();
-        }, 250);
 
-    }
-  }
-
-  // This worker is blocked because the URL can't resolve.
-  try {
-    var blocked_worker = new Worker('..:/blocked_worker.js');
-    notReached();
-  } catch(error) {
-    console.log(error);
-    assertEqual('SyntaxError', error.name);
-  }
-
-  // This worker is blocked because the script does not exist.
-  try {
-    var nonexisting_worker = new Worker('nonexisting_worker.js');
-    nonexisting_worker.onerror = function (event) {
-      // Note: The Worker's onerror handler (incorrectly) isn't called.
+    // This worker is blocked because the URL can't resolve.
+    try {
+      var blocked_worker = new Worker('..:/blocked_worker.js');
       notReached();
-    };
-  } catch (error) {
-    // The error is thrown asynchronously after the Worker constructor.
-    notReached();
-  }
+    } catch (error) {
+      console.log(error);
+      assertEqual('SyntaxError', error.name);
+    }
+
+    // This worker is blocked because the script does not exist.
+    try {
+      var nonexisting_worker = new Worker('nonexisting_worker.js');
+      nonexisting_worker.onerror = function (event) {
+        ++worker_onerror_count;
+        console.log(event.message);
+        if (event.message.includes('nonexisting_worker.js')) {
+          assertIncludes('aborted or failed with code 404', event.message);
+          assertIncludes('/testdata/nonexisting_worker.js', event.filename);
+        } else {
+          notReached();
+        }
+        if (worker_onerror_count == 1) {
+          window.setTimeout(
+            () => {
+              assertEqual(1, worker_onerror_count);
+              assertEqual(0, window_onerror_count);
+              onEndTest();
+            }, 250);
+
+        }
+      };
+    } catch (error) {
+      // The error is thrown asynchronously after the Worker constructor.
+      notReached();
+    }
   </script>
 </body>
diff --git a/cobalt/black_box_tests/tests/compression_test.py b/cobalt/black_box_tests/tests/compression_test.py
index 505cc45..27ea24a 100644
--- a/cobalt/black_box_tests/tests/compression_test.py
+++ b/cobalt/black_box_tests/tests/compression_test.py
@@ -33,7 +33,7 @@
     return encode_deflate(raw_content)
   elif encoding_type == 'br':
     return encode_brotli(raw_content)
-  raise ValueError('Unknown encoding type used [{}].'.format(encoding_type))
+  raise ValueError(f'Unknown encoding type used [{encoding_type}].')
 
 
 def encode_gzip(raw_content):
diff --git a/cobalt/black_box_tests/tests/evergreen_verify_qa_channel_update_test.py b/cobalt/black_box_tests/tests/evergreen_verify_qa_channel_update_test.py
new file mode 100644
index 0000000..12e422b
--- /dev/null
+++ b/cobalt/black_box_tests/tests/evergreen_verify_qa_channel_update_test.py
@@ -0,0 +1,68 @@
+# Copyright 2023 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests successful update to Cobalt binary available on the test channel."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+
+
+class EvergreenVerifyQaChannelUpdateTest(black_box_tests.BlackBoxTestCase):
+
+  def test_evergreen_verify_qa_channel_update(self):
+    with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+      url = server.GetURL(
+          file_name='testdata/evergreen_test.html?resetInstallations=true')
+      # Resetting the installations doesn't require an update check.
+      with self.CreateCobaltRunner(
+          url=url,
+          target_params=['--update_check_delay_seconds=300'],
+          loader_target='loader_app') as runner:
+        runner.WaitForJSTestsSetup()
+        self.assertTrue(runner.JSTestsSucceeded())
+
+      url = server.GetURL(
+          file_name='testdata/evergreen_test.html?channel=test&status=Update installed, pending restart'  # pylint: disable=line-too-long
+          .replace(' ', '%20'))
+      # 100 seconds provides enough time for the initial update delay, the prod
+      # channel update, and the target channel update.
+      with self.CreateCobaltRunner(
+          url=url, poll_until_wait_seconds=100,
+          loader_target='loader_app') as runner:
+        runner.WaitForJSTestsSetup()
+        self.assertTrue(runner.JSTestsSucceeded())
+
+      url = server.GetURL(
+          file_name='testdata/evergreen_test.html?channel=test&status=App is up to date'  # pylint: disable=line-too-long
+          .replace(' ', '%20'))
+      # 60 seconds provides enough time for the initial update delay and target
+      # channel update check.
+      with self.CreateCobaltRunner(
+          url=url, poll_until_wait_seconds=60,
+          loader_target='loader_app') as runner:
+        runner.WaitForJSTestsSetup()
+        self.assertTrue(runner.JSTestsSucceeded())
+
+      url = server.GetURL(
+          file_name='testdata/evergreen_test.html?resetInstallations=true')
+      # Resetting the installations doesn't require an update check.
+      with self.CreateCobaltRunner(
+          url=url,
+          target_params=['--update_check_delay_seconds=300'],
+          loader_target='loader_app') as runner:
+        runner.WaitForJSTestsSetup()
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/black_box_tests/tests/override_ua_parameters.py b/cobalt/black_box_tests/tests/override_ua_parameters.py
index 3dec188..bc9eb3f 100644
--- a/cobalt/black_box_tests/tests/override_ua_parameters.py
+++ b/cobalt/black_box_tests/tests/override_ua_parameters.py
@@ -43,9 +43,9 @@
         'foo.bar.baz.qux/21.2.1.41.0'
 
     if not ua_request_header == expected_ua_request_header:
-      raise ValueError('UA string in HTTP request header does not match with '\
-          'UA params overrides specified in command line\n'\
-          'UA string in HTTP request header:%s' % (ua_request_header))
+      raise ValueError('UA string in HTTP request header does not match with '
+                       'UA params overrides specified in command line\n'
+                       f'UA string in HTTP request header:{ua_request_header}')
 
     return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
 
diff --git a/cobalt/black_box_tests/tests/wasm_basic_test.py b/cobalt/black_box_tests/tests/wasm_basic_test.py
new file mode 100644
index 0000000..2515b53
--- /dev/null
+++ b/cobalt/black_box_tests/tests/wasm_basic_test.py
@@ -0,0 +1,48 @@
+# Copyright 2023 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests Cobalt can load and use WebAssembly."""
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+import logging
+
+PLATFORMS_SUPPORTED = [
+    'stub',
+    'linux-x64x11',
+    'linux-x64x11-egl',
+    'linux-x64x11-gcc-6-3',
+    'linux-x64x11-skia',
+    'android-arm',
+    'android-arm64',
+    'android-arm64-vulkan',
+    'android-x86',
+    'raspi-2',
+    'raspi-2-skia',
+    'win-win32',
+    'linux-x64x11-clang-crosstool',
+]
+
+
+class WasmBasicTest(black_box_tests.BlackBoxTestCase):
+
+  def test_wasm_basic(self):
+    if self.launcher_params.platform not in PLATFORMS_SUPPORTED:
+      logging.warning('Blackbox tests disabled for platform:%s',
+                      self.launcher_params.platform)
+      return
+
+    with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+      url = server.GetURL(file_name='testdata/wasm_basic_test.html')
+      with self.CreateCobaltRunner(url=url) as runner:
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/black_box_tests/tests/web_debugger.py b/cobalt/black_box_tests/tests/web_debugger.py
index 5d2d209..f55a037 100644
--- a/cobalt/black_box_tests/tests/web_debugger.py
+++ b/cobalt/black_box_tests/tests/web_debugger.py
@@ -28,6 +28,7 @@
 
 from cobalt.black_box_tests import black_box_tests
 from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+from starboard.tools import config
 
 sys.path.append(
     os.path.join(
@@ -43,16 +44,15 @@
   """Exception when an error response is received for a command."""
 
   def __init__(self, error):
-    code = '[{}] '.format(error['code']) if 'code' in error else ''
-    super(DebuggerCommandError, self).__init__(code + error['message'])
+    code = f"[{error['code']}] " if 'code' in error else ''
+    super().__init__(code + error['message'])
 
 
 class DebuggerEventError(Exception):
   """Exception when an unexpected event is received."""
 
   def __init__(self, expected, actual):
-    super(DebuggerEventError,
-          self).__init__('Waiting for {} but got {}'.format(expected, actual))
+    super().__init__(f'Waiting for {expected} but got {actual}')
 
 
 class JavaScriptError(Exception):
@@ -63,7 +63,7 @@
     ex = exception_details.get('exception', {})
     fallback = ex.get('className', 'Unknown error') + ' (No description)'
     msg = ex.get('description', fallback)
-    super(JavaScriptError, self).__init__(msg)
+    super().__init__(msg)
 
 
 class DebuggerConnection(object):
@@ -130,10 +130,10 @@
     while command_id not in self.responses:
       try:
         self._receive_message()
-      except websocket.WebSocketTimeoutException:
+      except websocket.WebSocketTimeoutException as e:
         method = self.commands[command_id]['method']
         raise DebuggerCommandError(
-            {'message': 'Timeout waiting for response to ' + method})
+            {'message': 'Timeout waiting for response to ' + method}) from e
     self.commands.pop(command_id)
     return self.responses.pop(command_id)
 
@@ -147,7 +147,7 @@
     # "Debugger.scriptParsed" events get sent as artifacts of the debugger
     # backend implementation running its own injected code, so they are ignored
     # unless that's what we're waiting for.
-    allow_script_parsed = (method == 'Debugger.scriptParsed')
+    allow_script_parsed = method == 'Debugger.scriptParsed'
 
     # Pop already-received events from the event queue.
     def _next_event():
@@ -165,8 +165,8 @@
         break
       try:
         self._receive_message()
-      except websocket.WebSocketTimeoutException:
-        raise DebuggerEventError(method, 'None (timeout)')
+      except websocket.WebSocketTimeoutException as e:
+        raise DebuggerEventError(method, 'None (timeout)') from e
     if method != event['method']:
       raise DebuggerEventError(method, event['method'])
     return event
@@ -216,13 +216,13 @@
   """Test interaction with the web debugger over a WebSocket."""
 
   def set_up_with(self, cm):
-    val = cm.__enter__()
+    val = cm.__enter__()  # pylint: disable=unnecessary-dunder-call
     self.addCleanup(cm.__exit__, None, None, None)
     return val
 
   def setUp(self):
-    cobalt_vars = self.cobalt_config.GetVariables(self.launcher_params.config)
-    if not cobalt_vars['enable_debugger']:
+    is_gold_config = self.launcher_params.config == config.Config.GOLD
+    if is_gold_config:
       self.skipTest('DevTools is disabled on this platform')
 
     self.server = self.set_up_with(
@@ -600,7 +600,7 @@
     # (replace all children of <div#B>)
     inner_html = "<div id='D'>\\n</div>"
     self.debugger.evaluate_js('b = document.getElementById("B");'
-                              'b.innerHTML = "%s"' % inner_html)
+                              f'b.innerHTML = "{inner_html}"')
     removed_event = self.debugger.wait_event('DOM.childNodeRemoved')
     self.assertEqual(moved_span_b1['nodeId'], removed_event['params']['nodeId'])
     inserted_event = self.debugger.wait_event('DOM.childNodeInserted')
@@ -896,6 +896,7 @@
         [
             'asyncBreak',
             'promiseThen',
+            'promiseTimeout',
         ],
         [
             'waitPromise',
@@ -909,6 +910,7 @@
         [
             'asyncBreak',
             'asyncAwait',
+            'promiseTimeout',
         ],
         [
             'asyncAwait',
diff --git a/cobalt/black_box_tests/tests/web_platform_tests.py b/cobalt/black_box_tests/tests/web_platform_tests.py
index d5e1933..dc11142 100644
--- a/cobalt/black_box_tests/tests/web_platform_tests.py
+++ b/cobalt/black_box_tests/tests/web_platform_tests.py
@@ -70,8 +70,7 @@
 
       if used_filters:
         if 'gtest_filter' not in ' '.join(self.launcher_params.target_params):
-          target_params.append('--gtest_filter=-{}'.format(
-              ':'.join(used_filters)))
+          target_params.append(f"--gtest_filter=-{':'.join(used_filters)}")
 
       if self.launcher_params.target_params:
         target_params += self.launcher_params.target_params
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn
index 72217bd..0b66445 100644
--- a/cobalt/browser/BUILD.gn
+++ b/cobalt/browser/BUILD.gn
@@ -114,6 +114,8 @@
     "application.h",
     "browser_module.cc",
     "browser_module.h",
+    "client_hint_headers.cc",
+    "client_hint_headers.h",
     "device_authentication.cc",
     "device_authentication.h",
     "lifecycle_observer.h",
@@ -231,6 +233,7 @@
   has_pedantic_warnings = true
 
   sources = [
+    "client_hint_headers_test.cc",
     "device_authentication_test.cc",
     "memory_settings/auto_mem_settings_test.cc",
     "memory_settings/auto_mem_test.cc",
@@ -412,14 +415,3 @@
 cache_templates("cached_jinja_templates") {
   output_dir = _bindings_scripts_output_dir
 }
-
-target(final_executable_type, "snapshot_app_stats") {
-  sources = [ "snapshot_app_stats.cc" ]
-  deps = [
-    ":browser",
-    ":browser_switches",
-    ":cobalt",
-    "//cobalt/base",
-    "//third_party/protobuf:protobuf_lite",
-  ]
-}
diff --git a/cobalt/browser/application.cc b/cobalt/browser/application.cc
index 4156bdd..3f5f413 100644
--- a/cobalt/browser/application.cc
+++ b/cobalt/browser/application.cc
@@ -55,6 +55,7 @@
 #include "cobalt/base/window_on_offline_event.h"
 #include "cobalt/base/window_on_online_event.h"
 #include "cobalt/base/window_size_changed_event.h"
+#include "cobalt/browser/client_hint_headers.h"
 #include "cobalt/browser/device_authentication.h"
 #include "cobalt/browser/memory_settings/auto_mem_settings.h"
 #include "cobalt/browser/memory_tracker/tool.h"
@@ -72,6 +73,8 @@
 #include "cobalt/system_window/input_event.h"
 #include "cobalt/trace_event/scoped_trace_to_file.h"
 #include "cobalt/watchdog/watchdog.h"
+#include "starboard/common/device_type.h"
+#include "starboard/common/system_property.h"
 #include "starboard/configuration.h"
 #include "starboard/event.h"
 #include "starboard/extension/crash_handler.h"
@@ -120,10 +123,17 @@
   // Default to INADDR_ANY
   std::string listen_ip(ip_v6 ? "::" : "0.0.0.0");
 
+#if SB_API_VERSION < 15
   // Desktop PCs default to loopback.
   if (SbSystemGetDeviceType() == kSbSystemDeviceTypeDesktopPC) {
     listen_ip = ip_v6 ? "::1" : "127.0.0.1";
   }
+#else
+  if (starboard::GetSystemPropertyString(kSbSystemPropertyDeviceType) ==
+      starboard::kSystemDeviceTypeDesktopPC) {
+    listen_ip = ip_v6 ? "::1" : "127.0.0.1";
+  }
+#endif
 
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
@@ -506,6 +516,7 @@
 struct SecurityFlags {
   csp::CSPHeaderPolicy csp_header_policy;
   network::HTTPSRequirement https_requirement;
+  network::CORSPolicy cors_policy;
 };
 
 // |non_trivial_static_fields| will be lazily created on the first time it's
@@ -521,7 +532,7 @@
     "available trackers.";
 #endif  // defined(ENABLE_DEBUGGER) && defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
 
-void AddCrashHandlerAnnotations() {
+void AddCrashHandlerAnnotations(const UserAgentPlatformInfo& platform_info) {
   auto crash_handler_extension =
       static_cast<const CobaltExtensionCrashHandlerApi*>(
           SbSystemGetExtension(kCobaltExtensionCrashHandlerName));
@@ -530,7 +541,6 @@
     return;
   }
 
-  auto platform_info = cobalt::browser::GetUserAgentPlatformInfoFromSystem();
   std::string user_agent =
       cobalt::browser::CreateUserAgentString(platform_info);
   std::string version = "";
@@ -589,6 +599,28 @@
   }
 }
 
+void AddCrashHandlerApplicationState(base::ApplicationState state) {
+  auto crash_handler_extension =
+      static_cast<const CobaltExtensionCrashHandlerApi*>(
+          SbSystemGetExtension(kCobaltExtensionCrashHandlerName));
+  if (!crash_handler_extension) {
+    DLOG(INFO) << "No crash handler extension, not sending application state.";
+    return;
+  }
+
+  std::string application_state = std::string(GetApplicationStateString(state));
+  application_state.push_back('\0');
+
+  if (crash_handler_extension->version > 1) {
+    if (crash_handler_extension->SetString("application_state",
+                                           application_state.c_str())) {
+      DLOG(INFO) << "Sent application state to crash handler.";
+      return;
+    }
+  }
+  DLOG(ERROR) << "Could not send application state to crash handler.";
+}
+
 }  // namespace
 
 // Helper stub to disable histogram tracking in StatisticsRecorder
@@ -779,7 +811,8 @@
   // User can specify an extra search path entry for files loaded via file://.
   options.web_module_options.web_options.extra_web_file_dir =
       GetExtraWebFileDir();
-  SecurityFlags security_flags{csp::kCSPRequired, network::kHTTPSRequired};
+  SecurityFlags security_flags{csp::kCSPRequired, network::kHTTPSRequired,
+                               network::kCORSRequired};
   // Set callback to be notified when a navigation occurs that destroys the
   // underlying WebModule.
   options.web_module_created_callback =
@@ -808,9 +841,14 @@
     security_flags.csp_header_policy = csp::kCSPOptional;
   }
 
+  if (command_line->HasSwitch(browser::switches::kAllowAllCrossOrigin)) {
+    security_flags.cors_policy = network::kCORSOptional;
+  }
+
   if (command_line->HasSwitch(browser::switches::kProd)) {
     security_flags.https_requirement = network::kHTTPSRequired;
     security_flags.csp_header_policy = csp::kCSPRequired;
+    security_flags.cors_policy = network::kCORSRequired;
   }
 
   if (command_line->HasSwitch(switches::kVideoPlaybackRateMultiplier)) {
@@ -851,7 +889,12 @@
   security_flags.csp_header_policy = csp::kCSPRequired;
 #endif  // defined(COBALT_FORCE_CSP)
 
+#if defined(COBALT_FORCE_CORS)
+  security_flags.cors_policy = network::kCORSRequired;
+#endif  // defined(COBALT_FORCE_CORS)
+
   network_module_options.https_requirement = security_flags.https_requirement;
+  network_module_options.cors_policy = security_flags.cors_policy;
   options.web_module_options.csp_header_policy =
       security_flags.csp_header_policy;
   options.web_module_options.csp_enforcement_type = web::kCspEnforcementEnable;
@@ -867,11 +910,13 @@
 
   storage_manager_.reset(new storage::StorageManager(storage_manager_options));
 
+  cobalt::browser::UserAgentPlatformInfo platform_info;
+
   network_module_.reset(new network::NetworkModule(
-      CreateUserAgentString(GetUserAgentPlatformInfoFromSystem()),
+      CreateUserAgentString(platform_info), GetClientHintHeaders(platform_info),
       storage_manager_.get(), &event_dispatcher_, network_module_options));
 
-  AddCrashHandlerAnnotations();
+  AddCrashHandlerAnnotations(platform_info);
 
 #if SB_IS(EVERGREEN)
   if (SbSystemGetExtension(kCobaltExtensionInstallationManagerName) &&
@@ -948,14 +993,12 @@
       base::Bind(&Application::OnWindowOnOfflineEvent, base::Unretained(this));
   event_dispatcher_.AddEventCallback(base::WindowOnOfflineEvent::TypeId(),
                                      on_window_on_offline_event_callback_);
-#if SB_API_VERSION >= 13
   on_date_time_configuration_changed_event_callback_ =
       base::Bind(&Application::OnDateTimeConfigurationChangedEvent,
                  base::Unretained(this));
   event_dispatcher_.AddEventCallback(
       base::DateTimeConfigurationChangedEvent::TypeId(),
       on_date_time_configuration_changed_event_callback_);
-#endif
 
 #if defined(ENABLE_WEBDRIVER)
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
@@ -1038,11 +1081,9 @@
   event_dispatcher_.RemoveEventCallback(
       base::AccessibilityCaptionSettingsChangedEvent::TypeId(),
       on_caption_settings_changed_event_callback_);
-#if SB_API_VERSION >= 13
   event_dispatcher_.RemoveEventCallback(
       base::DateTimeConfigurationChangedEvent::TypeId(),
       on_date_time_configuration_changed_event_callback_);
-#endif
 }
 
 void Application::Start(SbTimeMonotonic timestamp) {
@@ -1083,7 +1124,6 @@
 
   // Create a Cobalt event from the Starboard event, if recognized.
   switch (starboard_event->type) {
-#if SB_API_VERSION >= 13
     case kSbEventTypeBlur:
     case kSbEventTypeFocus:
     case kSbEventTypeConceal:
@@ -1093,15 +1133,6 @@
     case kSbEventTypeLowMemory:
       OnApplicationEvent(starboard_event->type, starboard_event->timestamp);
       break;
-#else
-    case kSbEventTypePause:
-    case kSbEventTypeUnpause:
-    case kSbEventTypeSuspend:
-    case kSbEventTypeResume:
-    case kSbEventTypeLowMemory:
-      OnApplicationEvent(starboard_event->type, SbTimeGetMonotonicNow());
-      break;
-#endif  // SB_API_VERSION >= 13
     case kSbEventTypeWindowSizeChanged:
       DispatchEventInternal(new base::WindowSizeChangedEvent(
           static_cast<SbEventWindowSizeChangedData*>(starboard_event->data)
@@ -1132,47 +1163,30 @@
           *static_cast<int*>(starboard_event->data)));
       break;
     case kSbEventTypeLink: {
-#if SB_API_VERSION >= 13
       DispatchDeepLink(static_cast<const char*>(starboard_event->data),
                        starboard_event->timestamp);
-#else   // SB_API_VERSION >= 13
-      DispatchDeepLink(static_cast<const char*>(starboard_event->data),
-                       SbTimeGetMonotonicNow());
-#endif  // SB_API_VERSION >= 13
       break;
     }
-#if SB_API_VERSION >= 13
     case kSbEventTypeAccessibilitySettingsChanged:
-#else
-    case kSbEventTypeAccessiblitySettingsChanged:
-#endif  // SB_API_VERSION >= 13
       DispatchEventInternal(new base::AccessibilitySettingsChangedEvent());
       break;
     case kSbEventTypeAccessibilityCaptionSettingsChanged:
       DispatchEventInternal(
           new base::AccessibilityCaptionSettingsChangedEvent());
       break;
-#if SB_API_VERSION >= 13
     case kSbEventTypeAccessibilityTextToSpeechSettingsChanged:
-#else
-    case kSbEventTypeAccessiblityTextToSpeechSettingsChanged:
-#endif  // SB_API_VERSION >= 13
       DispatchEventInternal(
           new base::AccessibilityTextToSpeechSettingsChangedEvent());
       break;
-#if SB_API_VERSION >= 13
     case kSbEventTypeOsNetworkDisconnected:
       DispatchEventInternal(new base::WindowOnOfflineEvent());
       break;
     case kSbEventTypeOsNetworkConnected:
       DispatchEventInternal(new base::WindowOnOnlineEvent());
       break;
-#endif
-#if SB_API_VERSION >= 13
     case kSbEventDateTimeConfigurationChanged:
       DispatchEventInternal(new base::DateTimeConfigurationChangedEvent());
       break;
-#endif
     // Explicitly list unhandled cases here so that the compiler can give a
     // warning when a value is added, but not handled.
     case kSbEventTypeInput:
@@ -1198,6 +1212,7 @@
     case kSbEventTypeStop:
       LOG(INFO) << "Got quit event.";
       if (watchdog) watchdog->UpdateState(base::kApplicationStateStopped);
+      AddCrashHandlerApplicationState(base::kApplicationStateStopped);
       Quit();
       LOG(INFO) << "Finished quitting.";
       break;
@@ -1207,7 +1222,6 @@
       browser_module_->Focus(timestamp);
       LOG(INFO) << "Finished starting.";
       break;
-#if SB_API_VERSION >= 13
     case kSbEventTypeBlur:
       LOG(INFO) << "Got blur event.";
       browser_module_->Blur(timestamp);
@@ -1245,37 +1259,6 @@
 #endif
       LOG(INFO) << "Finished unfreezing.";
       break;
-#else
-    case kSbEventTypePause:
-      LOG(INFO) << "Got pause event.";
-      browser_module_->Blur(timestamp);
-      LOG(INFO) << "Finished pausing.";
-      break;
-    case kSbEventTypeUnpause:
-      LOG(INFO) << "Got unpause event.";
-      browser_module_->Focus(timestamp);
-      LOG(INFO) << "Finished unpausing.";
-      break;
-    case kSbEventTypeSuspend:
-      LOG(INFO) << "Got suspend event.";
-      browser_module_->Conceal(timestamp);
-      browser_module_->Freeze(timestamp);
-#if SB_IS(EVERGREEN)
-      if (updater_module_) updater_module_->Suspend();
-#endif
-      LOG(INFO) << "Finished suspending.";
-      break;
-    case kSbEventTypeResume:
-      DCHECK(SbSystemSupportsResume());
-      LOG(INFO) << "Got resume event.";
-      browser_module_->Unfreeze(timestamp);
-      browser_module_->Reveal(timestamp);
-#if SB_IS(EVERGREEN)
-      if (updater_module_) updater_module_->Resume();
-#endif
-      LOG(INFO) << "Finished resuming.";
-      break;
-#endif  // SB_API_VERSION >= 13
     case kSbEventTypeLowMemory:
       DLOG(INFO) << "Got low memory event.";
       browser_module_->ReduceMemory();
@@ -1285,37 +1268,26 @@
     case kSbEventTypePreload:
     case kSbEventTypeWindowSizeChanged:
     case kSbEventTypeAccessibilityCaptionSettingsChanged:
-#if SB_API_VERSION >= 13
     case kSbEventTypeAccessibilityTextToSpeechSettingsChanged:
-#else
-    case kSbEventTypeAccessiblityTextToSpeechSettingsChanged:
-#endif  // SB_API_VERSION >= 13
     case kSbEventTypeOnScreenKeyboardBlurred:
     case kSbEventTypeOnScreenKeyboardFocused:
     case kSbEventTypeOnScreenKeyboardHidden:
     case kSbEventTypeOnScreenKeyboardShown:
     case kSbEventTypeOnScreenKeyboardSuggestionsUpdated:
-#if SB_API_VERSION >= 13
     case kSbEventTypeAccessibilitySettingsChanged:
-#else
-    case kSbEventTypeAccessiblitySettingsChanged:
-#endif  // SB_API_VERSION >= 13
     case kSbEventTypeInput:
     case kSbEventTypeLink:
     case kSbEventTypeScheduled:
     case kSbEventTypeUser:
     case kSbEventTypeVerticalSync:
-#if SB_API_VERSION >= 13
     case kSbEventTypeOsNetworkDisconnected:
     case kSbEventTypeOsNetworkConnected:
-#endif
-#if SB_API_VERSION >= 13
     case kSbEventDateTimeConfigurationChanged:
-#endif
       NOTREACHED() << "Unexpected event type: " << event_type;
       return;
   }
   if (watchdog) watchdog->UpdateState(browser_module_->GetApplicationState());
+  AddCrashHandlerApplicationState(browser_module_->GetApplicationState());
 }
 
 void Application::OnWindowSizeChangedEvent(const base::Event* event) {
@@ -1394,7 +1366,6 @@
       base::polymorphic_downcast<const base::WindowOnOfflineEvent*>(event));
 }
 
-#if SB_API_VERSION >= 13
 void Application::OnDateTimeConfigurationChangedEvent(
     const base::Event* event) {
   TRACE_EVENT0("cobalt::browser",
@@ -1403,7 +1374,6 @@
       base::polymorphic_downcast<
           const base::DateTimeConfigurationChangedEvent*>(event));
 }
-#endif
 
 void Application::MainWebModuleCreated(WebModule* web_module) {
   TRACE_EVENT0("cobalt::browser", "Application::MainWebModuleCreated()");
@@ -1552,26 +1522,20 @@
   DispatchEventInternal(new base::DeepLinkEvent(
       deep_link, base::Bind(&Application::OnDeepLinkConsumedCallback,
                             base::Unretained(this), deep_link)));
-#if SB_API_VERSION >= 13
   if (browser_module_) {
     browser_module_->SetDeepLinkTimestamp(timestamp);
   }
-#endif  // SB_API_VERSION >= 13
 }
 
 void Application::DispatchDeepLinkIfNotConsumed() {
   std::string deep_link;
-#if SB_API_VERSION >= 13
   SbTimeMonotonic timestamp;
-#endif  // SB_API_VERSION >= 13
   // This block exists to ensure that the lock is held while accessing
   // unconsumed_deep_link_.
   {
     base::AutoLock auto_lock(unconsumed_deep_link_lock_);
     deep_link = unconsumed_deep_link_;
-#if SB_API_VERSION >= 13
     timestamp = deep_link_timestamp_;
-#endif  // SB_API_VERSION >= 13
   }
 
   if (!deep_link.empty()) {
@@ -1580,11 +1544,9 @@
         deep_link, base::Bind(&Application::OnDeepLinkConsumedCallback,
                               base::Unretained(this), deep_link)));
   }
-#if SB_API_VERSION >= 13
   if (browser_module_) {
     browser_module_->SetDeepLinkTimestamp(timestamp);
   }
-#endif  // SB_API_VERSION >= 13
 }
 
 }  // namespace browser
diff --git a/cobalt/browser/application.h b/cobalt/browser/application.h
index 96480b7..04a08ca 100644
--- a/cobalt/browser/application.h
+++ b/cobalt/browser/application.h
@@ -85,9 +85,7 @@
   void OnWindowOnOnlineEvent(const base::Event* event);
   void OnWindowOnOfflineEvent(const base::Event* event);
 
-#if SB_API_VERSION >= 13
   void OnDateTimeConfigurationChangedEvent(const base::Event* event);
-#endif
 
   // Called when a navigation occurs in the BrowserModule.
   void MainWebModuleCreated(WebModule* web_module);
@@ -123,13 +121,9 @@
   base::EventCallback on_screen_keyboard_blurred_event_callback_;
   base::EventCallback on_screen_keyboard_suggestions_updated_event_callback_;
   base::EventCallback on_caption_settings_changed_event_callback_;
-#if SB_API_VERSION >= SB_NETWORK_EVENT_VERSION
   base::EventCallback on_window_on_online_event_callback_;
   base::EventCallback on_window_on_offline_event_callback_;
-#endif
-#if SB_API_VERSION >= 13
   base::EventCallback on_date_time_configuration_changed_event_callback_;
-#endif
 
   // Thread checkers to ensure that callbacks for network and application events
   // always occur on the same thread.
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc
index e4e1481..a4f3934 100644
--- a/cobalt/browser/browser_module.cc
+++ b/cobalt/browser/browser_module.cc
@@ -280,7 +280,6 @@
 #endif
       on_error_retry_count_(0),
       waiting_for_error_retry_(false),
-      will_quit_(false),
       application_state_(initial_application_state),
       main_web_module_generation_(0),
       next_timeline_id_(1),
@@ -691,6 +690,13 @@
 
   on_load_event_time_ = base::TimeTicks::Now().ToInternalValue();
 
+#if SB_IS(EVERGREEN)
+  if (updater_module_) {
+    // Mark the Evergreen update successful if this is Evergreen-Full.
+    updater_module_->MarkSuccessful();
+  }
+#endif
+
   web_module_loaded_.Signal();
 
   options_.persistent_settings->ValidatePersistentSettings();
@@ -915,11 +921,7 @@
     return;
   }
 #endif
-#if SB_API_VERSION >= 13
   SbSystemRequestConceal();
-#else
-  SbSystemRequestSuspend();
-#endif  // SB_API_VERSION >= 13
 }
 
 void BrowserModule::OnWindowSizeChanged(const ViewportSize& viewport_size) {
@@ -996,7 +998,6 @@
   }
 }
 
-#if SB_API_VERSION >= 13
 void BrowserModule::OnDateTimeConfigurationChanged(
     const base::DateTimeConfigurationChangedEvent* event) {
   DCHECK_EQ(base::MessageLoop::current(), self_message_loop_);
@@ -1005,7 +1006,6 @@
     web_module_->UpdateDateTimeConfiguration();
   }
 }
-#endif
 
 #if defined(ENABLE_DEBUGGER)
 void BrowserModule::OnFuzzerToggle(const std::string& message) {
@@ -1309,12 +1309,7 @@
     }
   } else if (event.ctrl_key() && event.key_code() == dom::keycode::kS) {
     if (type == base::Tokens::keydown()) {
-#if SB_API_VERSION >= 13
       SbSystemRequestConceal();
-#else
-      // Ctrl+S suspends Cobalt.
-      SbSystemRequestSuspend();
-#endif  // SB_API_VERSION >= 13
     }
     return false;
   }
@@ -1614,12 +1609,8 @@
   InstantiateRendererModule();
 
   if (media_module_) {
-#if SB_API_VERSION >= 13
     media_module_->UpdateSystemWindowAndResourceProvider(system_window_.get(),
                                                          GetResourceProvider());
-#else
-    media_module_->Resume(GetResourceProvider());
-#endif  // SB_API_VERSION >= 13
   } else {
     options_.media_module_options.allow_resume_after_suspend =
         SbSystemSupportsResume();
@@ -1758,12 +1749,10 @@
 
   // Suspend media module and update resource provider.
   if (media_module_) {
-#if SB_API_VERSION >= 13
     // This needs to be done before destroying the renderer module as it
     // may use the renderer module to release assets during the update.
     media_module_->UpdateSystemWindowAndResourceProvider(NULL,
                                                          GetResourceProvider());
-#endif  // SB_API_VERSION >= 13
   }
 
   if (renderer_module_) {
@@ -1772,13 +1761,11 @@
     DestroyRendererModule();
   }
 
-#if SB_API_VERSION >= 13
   // Reset system window after renderer module destroyed.
   if (media_module_) {
     input_device_manager_.reset();
     system_window_.reset();
   }
-#endif  // SB_API_VERSION >= 13
 }
 
 void BrowserModule::FreezeInternal(SbTimeMonotonic timestamp) {
@@ -1815,11 +1802,9 @@
 
 void BrowserModule::UnfreezeInternal(SbTimeMonotonic timestamp) {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::UnfreezeInternal()");
-// Set the Stub resource provider to media module and to web module
-// at Concealed state.
-#if SB_API_VERSION >= 13
+  // Set the Stub resource provider to media module and to web module
+  // at Concealed state.
   if (media_module_) media_module_->Resume(GetResourceProvider());
-#endif  // SB_API_VERSION >= 13
 
   FOR_EACH_OBSERVER(LifecycleObserver, lifecycle_observers_,
                     Unfreeze(GetResourceProvider(), timestamp));
@@ -1847,10 +1832,8 @@
 #endif  // defined(ENABLE_DEBUGGER)
       web_module_ready_to_freeze &&
       application_state_ == base::kApplicationStateConcealed) {
-#if SB_API_VERSION >= 13
     DLOG(INFO) << "System request to freeze the app.";
     SbSystemRequestFreeze();
-#endif  // SB_API_VERSION >= 13
   }
 }
 
diff --git a/cobalt/browser/browser_module.h b/cobalt/browser/browser_module.h
index 05aece7..4b01a90 100644
--- a/cobalt/browser/browser_module.h
+++ b/cobalt/browser/browser_module.h
@@ -221,10 +221,8 @@
   void OnWindowOnOnlineEvent(const base::Event* event);
   void OnWindowOnOfflineEvent(const base::Event* event);
 
-#if SB_API_VERSION >= 13
   void OnDateTimeConfigurationChanged(
       const base::DateTimeConfigurationChangedEvent* event);
-#endif
 
   bool IsWebModuleLoaded() { return web_module_loaded_.IsSignaled(); }
 
@@ -688,15 +686,6 @@
   // resume until the error retry occurs.
   bool waiting_for_error_retry_;
 
-  // Set when the application is about to quit. May be set from a thread other
-  // than the one hosting this object, and read from another.
-  bool will_quit_;
-
-  // The |will_quit_| flag may be set from one thread (e.g. not the one hosting
-  // this object) and read from another. This lock is used to
-  // ensure synchronous access.
-  base::Lock quit_lock_;
-
   // The current application state.
   base::ApplicationState application_state_;
 
diff --git a/cobalt/browser/client_hint_headers.cc b/cobalt/browser/client_hint_headers.cc
new file mode 100644
index 0000000..8dc7dcc
--- /dev/null
+++ b/cobalt/browser/client_hint_headers.cc
@@ -0,0 +1,55 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/browser/client_hint_headers.h"
+
+#include <string>
+#include <vector>
+
+#include "base/strings/strcat.h"
+
+namespace cobalt {
+namespace browser {
+namespace {
+
+// Note, we intentionally will prefix all headers with 'Co' for Cobalt.
+const char kCHPrefix[] = "Sec-CH-UA-Co-";
+
+// Helper function to check for empty values and add header prefix.
+void AddHeader(std::vector<std::string>& request_headers,
+               const std::string& header, const std::string& value) {
+  if (!value.empty()) {
+    request_headers.push_back(base::StrCat({kCHPrefix, header, ":", value}));
+  }
+}
+
+}  // namespace
+
+// This function is expected to be deterministic and non-dependent on global
+// variables and state.  If global state should be referenced, it should be done
+// so during the creation of |platform_info| instead.
+std::vector<std::string> GetClientHintHeaders(
+    const UserAgentPlatformInfo& platform_info) {
+  std::vector<std::string> headers;
+
+  AddHeader(headers, "Firmware-Version-Details",
+            platform_info.firmware_version_details());
+
+  AddHeader(headers, "OS-Experience", platform_info.os_experience());
+
+  return headers;
+}
+
+}  // namespace browser
+}  // namespace cobalt
diff --git a/cobalt/browser/client_hint_headers.h b/cobalt/browser/client_hint_headers.h
new file mode 100644
index 0000000..6e77039
--- /dev/null
+++ b/cobalt/browser/client_hint_headers.h
@@ -0,0 +1,34 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_BROWSER_CLIENT_HINT_HEADERS_H_
+#define COBALT_BROWSER_CLIENT_HINT_HEADERS_H_
+
+#include <string>
+#include <vector>
+
+#include "cobalt/browser/user_agent_platform_info.h"
+
+namespace cobalt {
+namespace browser {
+
+// This function is deterministic and non-dependent on global variables and
+// state.  It is dependent only on |platform_info|.
+std::vector<std::string> GetClientHintHeaders(
+    const UserAgentPlatformInfo& platform_info);
+
+}  // namespace browser
+}  // namespace cobalt
+
+#endif  // COBALT_BROWSER_CLIENT_HINT_HEADERS_H_
diff --git a/cobalt/browser/client_hint_headers_test.cc b/cobalt/browser/client_hint_headers_test.cc
new file mode 100644
index 0000000..f96626a
--- /dev/null
+++ b/cobalt/browser/client_hint_headers_test.cc
@@ -0,0 +1,43 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/browser/client_hint_headers.h"
+
+#include "cobalt/browser/user_agent_platform_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace browser {
+
+namespace {
+
+using ::testing::UnorderedElementsAre;
+
+TEST(ClientHintHeadersTest, GetClientHintHeaders) {
+  UserAgentPlatformInfo platform_info;
+  platform_info.set_firmware_version_details("abc/def:123.456/xy-z");
+  platform_info.set_os_experience("Amati");
+
+  std::vector<std::string> headers = GetClientHintHeaders(platform_info);
+  EXPECT_THAT(headers,
+              UnorderedElementsAre(
+                  "Sec-CH-UA-Co-Firmware-Version-Details:abc/def:123.456/xy-z",
+                  "Sec-CH-UA-Co-OS-Experience:Amati"));
+}
+
+}  // namespace
+
+}  // namespace browser
+}  // namespace cobalt
diff --git a/cobalt/browser/device_authentication.cc b/cobalt/browser/device_authentication.cc
index 050857e..bf01d14 100644
--- a/cobalt/browser/device_authentication.cc
+++ b/cobalt/browser/device_authentication.cc
@@ -32,25 +32,6 @@
 
 constexpr size_t kSHA256DigestSize = 32;
 
-#if SB_API_VERSION < 13
-bool ComputeSignatureWithSystemPropertySecret(const std::string& message,
-                                              uint8_t* signature) {
-  const size_t kBase64EncodedCertificationSecretLength = 1023;
-  char base_64_secret_property[kBase64EncodedCertificationSecretLength + 1] = {
-      0};
-  bool result = SbSystemGetProperty(
-      kSbSystemPropertyBase64EncodedCertificationSecret,
-      base_64_secret_property, kBase64EncodedCertificationSecretLength);
-  if (!result) {
-    return false;
-  }
-
-  ComputeHMACSHA256SignatureWithProvidedKey(message, base_64_secret_property,
-                                            signature, kSHA256DigestSize);
-  return true;
-}
-#endif  // SB_API_VERSION < 13
-
 bool ComputeSignatureFromSignAPI(const std::string& message,
                                  uint8_t* signature) {
   return SbSystemSignWithCertificationSecretKey(
@@ -68,10 +49,6 @@
   if (ComputeSignatureFromSignAPI(message, signature)) {
     DLOG(INFO) << "Using certification signature provided by "
                << "SbSystemSignWithCertificationSecretKey().";
-#if SB_API_VERSION < 13
-  } else if (ComputeSignatureWithSystemPropertySecret(message, signature)) {
-    DLOG(INFO) << "Using certification key from SbSystemGetProperty().";
-#endif  // SB_API_VERSION < 13
   } else {
     return std::string();
   }
diff --git a/cobalt/browser/lifecycle_console_commands.cc b/cobalt/browser/lifecycle_console_commands.cc
index 6e8253c..3784e29 100644
--- a/cobalt/browser/lifecycle_console_commands.cc
+++ b/cobalt/browser/lifecycle_console_commands.cc
@@ -50,7 +50,6 @@
     "ending the process (peacefully).";
 
 namespace {
-#if SB_API_VERSION >= 13
 // This is temporary that will be changed in later CLs, for mapping Starboard
 // Concealed state support onto Cobalt without Concealed state support to be
 // able to test the former.
@@ -63,17 +62,6 @@
             << "reveal Cobalt using a platform-specific method.";
   SbSystemRequestConceal();
 }
-#else
-void OnPause(const std::string& message) { SbSystemRequestPause(); }
-
-void OnUnpause(const std::string& message) { SbSystemRequestUnpause(); }
-
-void OnSuspend(const std::string& message) {
-  LOG(INFO) << "Suspending Cobalt through the console, but you will need to "
-            << "resume Cobalt using a platform-specific method.";
-  SbSystemRequestSuspend();
-}
-#endif  // SB_API_VERSION >= 13
 
 void OnQuit(const std::string& /*message*/) { SbSystemRequestStop(0); }
 }  // namespace
diff --git a/cobalt/browser/main.cc b/cobalt/browser/main.cc
index 371613c..7f87bc0 100644
--- a/cobalt/browser/main.cc
+++ b/cobalt/browser/main.cc
@@ -63,9 +63,8 @@
   }
   LOG(INFO) << "Concealing application.";
   DCHECK(!g_application);
-  g_application = new cobalt::browser::Application(quit_closure,
-                                                   true /*should_preload*/,
-                                                   timestamp);
+  g_application = new cobalt::browser::Application(
+      quit_closure, true /*should_preload*/, timestamp);
   DCHECK(g_application);
 }
 
@@ -77,22 +76,10 @@
     return;
   }
   LOG(INFO) << "Starting application.";
-#if SB_API_VERSION >= 13
   DCHECK(!g_application);
-  g_application = new cobalt::browser::Application(quit_closure,
-                                                   false /*not_preload*/,
-                                                   timestamp);
+  g_application = new cobalt::browser::Application(
+      quit_closure, false /*not_preload*/, timestamp);
   DCHECK(g_application);
-#else
-  if (!g_application) {
-    g_application = new cobalt::browser::Application(quit_closure,
-                                                     false /*should_preload*/,
-                                                     timestamp);
-    DCHECK(g_application);
-  } else {
-    g_application->Start(timestamp);
-  }
-#endif  // SB_API_VERSION >= 13
 }
 
 void StopApplication() {
diff --git a/cobalt/browser/snapshot_app_stats.cc b/cobalt/browser/snapshot_app_stats.cc
deleted file mode 100644
index e52b721..0000000
--- a/cobalt/browser/snapshot_app_stats.cc
+++ /dev/null
@@ -1,167 +0,0 @@
-// Copyright 2015 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cstdio>
-#include <map>
-
-#include "base/command_line.h"
-#include "base/threading/thread.h"
-#include "base/time/time.h"
-#include "cobalt/base/c_val.h"
-#include "cobalt/base/wrap_main.h"
-#include "cobalt/browser/application.h"
-#include "cobalt/browser/switches.h"
-
-namespace {
-
-// How many seconds to wait after starting the application before taking the
-// snapshot of CVal values.
-const int kSecondsToWait = 20;
-
-// The list of CVals we are interested in reporting.
-//
-// Note that if you add an item to this list, you should also add a column
-// to the stats tracking table in the database. Please see the document titled
-// "Adding a SnapshotAppStats column" on the Cobalt intranet home page for
-// examples on how to add a new column.
-
-// The naming convention for CVal columns in the database are the same as the
-// name of the CVal except with the dots removed and the CVal name converted to
-// camel case.  For example, "DOM.TokenLists" would become "dOMTokenLists".
-//
-// A document that explains how to modify the database in more detail can be
-// found called "Dashboard TDD" on the Cobalt intranet home page.
-
-const char* g_cvals_to_snapshot[] = {
-    "Count.DOM.Nodes",
-    "Count.DOM.TokenLists",
-    "Count.XHR",
-    "Memory.ArrayBuffer",
-    "Memory.CPU.Exe",
-    "Memory.CPU.Used",
-    "Memory.GraphicsPS3.Fixed.Size",
-    "Memory.JS",
-    "Memory.MainWebModule.ImageCache.Size",
-    "Memory.MainWebModule.RemoteTypefaceCache.Size",
-    "Memory.Media.AudioDecoder",
-    "Memory.Media.MediaSource.CPU.Fixed.Capacity",
-    "Memory.Media.VideoDecoder",
-    "Memory.XHR",
-};
-
-PRINTF_FORMAT(1, 2) void Output(const char* fmt, ...) {
-  va_list ap;
-  va_start(ap, fmt);
-
-  std::vfprintf(stdout, fmt, ap);
-
-  va_end(ap);
-
-  std::fflush(stdout);
-}
-
-typedef std::map<std::string, std::string> CValsMap;
-
-// Returns all CVals along with their values.
-CValsMap GetAllCValValues() {
-  base::CValManager* cvm = base::CValManager::GetInstance();
-  std::set<std::string> cvals = cvm->GetOrderedCValNames();
-  CValsMap cvals_with_values;
-
-  for (std::set<std::string>::iterator iter = cvals.begin();
-       iter != cvals.end(); ++iter) {
-    base::Optional<std::string> cval_value = cvm->GetValueAsString(*iter);
-    if (cval_value) {
-      cvals_with_values[*iter] = *cval_value;
-    }
-  }
-
-  return cvals_with_values;
-}
-
-// Grab a snapshot of all current CVals and their values and then output them
-// so that they can be analyzed by humans and inserted into a database where
-// the values can be graphed.
-void DoStatsSnapshot(cobalt::browser::Application* application) {
-  Output("---Benchmark Results Start---\n");
-  Output("{\n");
-  Output("  \"LiveYouTubeAfter%dSecondsStatsSnapshot\": {\n", kSecondsToWait);
-
-  CValsMap cval_values = GetAllCValValues();
-  bool have_printed_results = false;
-  for (size_t i = 0; i < arraysize(g_cvals_to_snapshot); ++i) {
-    CValsMap::iterator found = cval_values.find(g_cvals_to_snapshot[i]);
-    if (found != cval_values.end()) {
-      if (have_printed_results) {
-        Output(",\n");
-      } else {
-        have_printed_results = true;
-      }
-      Output("    \"%s\": %s", found->first.c_str(), found->second.c_str());
-    }
-  }
-
-  Output("\n");
-
-  Output("  }\n");
-  Output("}\n");
-  Output("---Benchmark Results End---\n");
-
-  application->Quit();
-}
-
-}  // namespace
-
-namespace {
-
-cobalt::browser::Application* g_application = NULL;
-base::Thread* g_snapshot_thread = NULL;
-
-void StartApplication(int argc, char* argv[], const char* link,
-                      const base::Closure& quit_closure,
-                      SbTimeMonotonic timestamp) {
-  logging::SetMinLogLevel(100);
-
-  // Use null storage for our savegame so that we don't persist state from
-  // one run to another, which makes the measurements more deterministic (e.g.
-  // we will not consistently register for experiments via cookies).
-  base::CommandLine::ForCurrentProcess()->AppendSwitch(
-      cobalt::browser::switches::kNullSavegame);
-  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-      cobalt::browser::switches::kDebugConsoleMode, "off");
-
-  // Create the application object just like is done in the Cobalt main app.
-  g_application =
-      new cobalt::browser::Application(quit_closure, false /*should_preload*/,
-                                       timestamp);
-
-  // Create a thread to start a timer for kSecondsToWait seconds after which
-  // we will take a snapshot of the CVals at that time and then quit the
-  // application.
-  g_snapshot_thread = new base::Thread("StatsSnapshot");
-  g_snapshot_thread->Start();
-  g_snapshot_thread->message_loop()->task_runner()->PostDelayedTask(
-      FROM_HERE, base::Bind(&DoStatsSnapshot, g_application),
-      base::TimeDelta::FromSeconds(kSecondsToWait));
-}
-
-void StopApplication() {
-  g_snapshot_thread->Stop();
-  delete g_application;
-  g_application = NULL;
-}
-
-}  // namespace
-
-COBALT_WRAP_BASE_MAIN(StartApplication, StopApplication);
diff --git a/cobalt/browser/splash_screen.cc b/cobalt/browser/splash_screen.cc
index ea764a6..62e8e4b 100644
--- a/cobalt/browser/splash_screen.cc
+++ b/cobalt/browser/splash_screen.cc
@@ -20,6 +20,7 @@
 #include "base/callback.h"
 #include "base/cancelable_callback.h"
 #include "base/threading/platform_thread.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "cobalt/browser/splash_screen_cache.h"
 #include "cobalt/dom/window.h"
@@ -132,7 +133,7 @@
   DCHECK(!ShutdownSignaled()) << "Shutdown() should be called at most once.";
 
   if (!on_splash_screen_shutdown_complete_.callback().is_null()) {
-    base::MessageLoop::current()->task_runner()->PostDelayedTask(
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
         FROM_HERE,
         base::Bind(on_splash_screen_shutdown_complete_.callback(),
                    base::TimeDelta()),
diff --git a/cobalt/browser/suspend_fuzzer.cc b/cobalt/browser/suspend_fuzzer.cc
index 4c71c2c..eeebfba 100644
--- a/cobalt/browser/suspend_fuzzer.cc
+++ b/cobalt/browser/suspend_fuzzer.cc
@@ -14,6 +14,8 @@
 
 #include "cobalt/browser/suspend_fuzzer.h"
 
+#include "base/threading/thread_task_runner_handle.h"
+
 namespace cobalt {
 namespace browser {
 
@@ -31,13 +33,8 @@
 
 }  // namespace
 
-#if SB_API_VERSION >= 13
 SuspendFuzzer::SuspendFuzzer()
     : thread_("suspend_fuzzer"), step_type_(kShouldRequestFreeze) {
-#else
-SuspendFuzzer::SuspendFuzzer()
-    : thread_("suspend_fuzzer"), step_type_(kShouldRequestSuspend) {
-#endif  // SB_API_VERSION >= 13
   thread_.Start();
   thread_.message_loop()->task_runner()->PostDelayedTask(
       FROM_HERE, base::Bind(&SuspendFuzzer::DoStep, base::Unretained(this)),
@@ -48,7 +45,6 @@
 
 void SuspendFuzzer::DoStep() {
   DCHECK(base::MessageLoop::current() == thread_.message_loop());
-#if SB_API_VERSION >= 13
   if (step_type_ == kShouldRequestFreeze) {
     SB_DLOG(INFO) << "suspend_fuzzer: Requesting freeze.";
     SbSystemRequestFreeze();
@@ -60,21 +56,8 @@
   } else {
     NOTREACHED();
   }
-#else
-  if (step_type_ == kShouldRequestSuspend) {
-    SB_DLOG(INFO) << "suspend_fuzzer: Requesting suspend.";
-    SbSystemRequestSuspend();
-    step_type_ = kShouldRequestSuspend;
-  } else if (step_type_ == kShouldRequestUnpause) {
-    SB_DLOG(INFO) << "suspend_fuzzer: Requesting unpause.";
-    SbSystemRequestUnpause();
-    step_type_ = kShouldRequestUnpause;
-  } else {
-    NOTREACHED();
-  }
-#endif  // SB_API_VERSION >= 13
 
-  base::MessageLoop::current()->task_runner()->PostDelayedTask(
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE, base::Bind(&SuspendFuzzer::DoStep, base::Unretained(this)),
       kInterval);
 }
diff --git a/cobalt/browser/suspend_fuzzer.h b/cobalt/browser/suspend_fuzzer.h
index d11ecd9..19b5053 100644
--- a/cobalt/browser/suspend_fuzzer.h
+++ b/cobalt/browser/suspend_fuzzer.h
@@ -37,13 +37,8 @@
   base::Thread thread_;
 
   enum StepType {
-#if SB_API_VERSION >= 13
     kShouldRequestFreeze,
     kShouldRequestFocus,
-#else
-    kShouldRequestSuspend,
-    kShouldRequestUnpause,
-#endif  // SB_API_VERSION >= 13
   } step_type_;
 };
 
diff --git a/cobalt/browser/switches.cc b/cobalt/browser/switches.cc
index e746329..e455eeb 100644
--- a/cobalt/browser/switches.cc
+++ b/cobalt/browser/switches.cc
@@ -147,6 +147,11 @@
     "Forbid Cobalt to start without receiving csp headers which is enabled by "
     "default in production.";
 
+const char kAllowAllCrossOrigin[] = "allow_all_cross_origin";
+const char kAllowAllCrossOriginHelp[] =
+    "Disables SOP checks and allows all cross-origin connections in "
+    "development builds.";
+
 const char kRequireHTTPSLocation[] = "require_https";
 const char kRequireHTTPSLocationHelp[] =
     "Ask Cobalt to only accept https url which is enabled by default in "
@@ -458,6 +463,7 @@
         {kMinCompatibilityVersion, kMinCompatibilityVersionHelp},
         {kNullSavegame, kNullSavegameHelp}, {kProd, kProdHelp},
         {kRequireCSP, kRequireCSPHelp},
+        {kAllowAllCrossOrigin, kAllowAllCrossOriginHelp},
         {kRequireHTTPSLocation, kRequireHTTPSLocationHelp},
         {kShutdownAfter, kShutdownAfterHelp},
         {kStubImageDecoder, kStubImageDecoderHelp},
diff --git a/cobalt/browser/switches.h b/cobalt/browser/switches.h
index 859b52c..c1401d7 100644
--- a/cobalt/browser/switches.h
+++ b/cobalt/browser/switches.h
@@ -36,6 +36,8 @@
 extern const char kWaitForWebDebuggerHelp[];
 #endif  // ENABLE_DEBUGGER
 
+extern const char kAllowAllCrossOrigin[];
+extern const char kAllowAllCrossOriginHelp[];
 extern const char kDisableImageAnimations[];
 extern const char kDisableImageAnimationsHelp[];
 extern const char kForceDeterministicRendering[];
diff --git a/cobalt/browser/user_agent_platform_info.cc b/cobalt/browser/user_agent_platform_info.cc
index e89f79e..30b4b39 100644
--- a/cobalt/browser/user_agent_platform_info.cc
+++ b/cobalt/browser/user_agent_platform_info.cc
@@ -27,6 +27,8 @@
 #include "cobalt_build_id.h"  // NOLINT(build/include_subdir)
 #include "starboard/common/log.h"
 #include "starboard/common/string.h"
+#include "starboard/common/system_property.h"
+#include "starboard/extension/platform_info.h"
 #if SB_IS(EVERGREEN)
 #include "starboard/extension/installation_manager.h"
 #endif  // SB_IS(EVERGREEN)
@@ -35,6 +37,8 @@
 #include "cobalt/updater/utils.h"
 #endif
 
+using starboard::kSystemPropertyMaxLength;
+
 namespace cobalt {
 namespace browser {
 
@@ -106,6 +110,8 @@
 
 namespace {
 
+#if SB_API_VERSION < 15
+
 struct DeviceTypeName {
   SbSystemDeviceType device_type;
   char device_type_string[10];
@@ -145,6 +151,7 @@
   return kSbSystemDeviceTypeUnknown;
 }
 #endif
+#endif  // SB_API_VERSION < 15
 
 static bool isAsciiAlphaDigit(int c) {
   return base::IsAsciiAlpha(c) || base::IsAsciiDigit(c);
@@ -216,7 +223,6 @@
   info.set_starboard_version(
       base::StringPrintf("Starboard/%d", SB_API_VERSION));
 
-  const size_t kSystemPropertyMaxLength = 1024;
   char value[kSystemPropertyMaxLength];
   bool result;
 
@@ -265,6 +271,22 @@
   }
 #endif
 
+  // Additional Platform Info
+  auto platform_info_extension =
+      static_cast<const CobaltExtensionPlatformInfoApi*>(
+          SbSystemGetExtension(kCobaltExtensionPlatformInfoName));
+  if (platform_info_extension &&
+      strcmp(platform_info_extension->name, kCobaltExtensionPlatformInfoName) ==
+          0 &&
+      platform_info_extension->version >= 1) {
+    result = platform_info_extension->GetFirmwareVersionDetails(
+        value, kSystemPropertyMaxLength);
+    if (result) {
+      info.set_firmware_version_details(value);
+    }
+    info.set_os_experience(platform_info_extension->GetOsExperience());
+  }
+
   info.set_cobalt_version(COBALT_VERSION);
   info.set_cobalt_build_version_number(COBALT_BUILD_VERSION_NUMBER);
 
@@ -286,8 +308,15 @@
     info.set_aux_field(value);
   }
 
+#if SB_API_VERSION >= 15
+  result = SbSystemGetProperty(kSbSystemPropertyDeviceType, value,
+                               kSystemPropertyMaxLength);
+  SB_DCHECK(result);
+  info.set_device_type(value);
+#else
   // Fill platform info if it is a hardware TV device.
   info.set_device_type(SbSystemGetDeviceType());
+#endif
 
   // Chipset model number
   result = SbSystemGetProperty(kSbSystemPropertyChipsetModelNumber, value,
@@ -354,7 +383,11 @@
           info.set_original_design_manufacturer(input.second);
           LOG(INFO) << "Set original design manufacturer to " << input.second;
         } else if (!input.first.compare("device_type")) {
+#if SB_API_VERSION < 15
           info.set_device_type(GetDeviceType(input.second));
+#else
+          info.set_device_type(input.second);
+#endif
           LOG(INFO) << "Set device type to " << input.second;
         } else if (!input.first.compare("chipset_model_number")) {
           info.set_chipset_model_number(input.second);
@@ -389,6 +422,12 @@
         } else if (!input.first.compare("evergreen_version")) {
           info.set_evergreen_version(input.second);
           LOG(INFO) << "Set evergreen version to " << input.second;
+        } else if (!input.first.compare("firmware_version_details")) {
+          info.set_firmware_version_details(input.second);
+          LOG(INFO) << "Set firmware version details to " << input.second;
+        } else if (!input.first.compare("os_experience")) {
+          info.set_os_experience(input.second);
+          LOG(INFO) << "Set os experience to " << input.second;
         } else if (!input.first.compare("cobalt_version")) {
           info.set_cobalt_version(input.second);
           LOG(INFO) << "Set cobalt type to " << input.second;
@@ -429,10 +468,16 @@
         Sanitize(original_design_manufacturer, isAsciiAlphaDigit);
   }
 }
+
+#if SB_API_VERSION < 15
 void UserAgentPlatformInfo::set_device_type(SbSystemDeviceType device_type) {
   device_type_ = device_type;
   device_type_string_ = CreateDeviceTypeString(device_type_);
 }
+#endif
+void UserAgentPlatformInfo::set_device_type(const std::string& device_type) {
+  device_type_string_ = device_type;
+}
 
 void UserAgentPlatformInfo::set_chipset_model_number(
     base::Optional<std::string> chipset_model_number) {
@@ -497,6 +542,17 @@
   evergreen_version_ = Sanitize(evergreen_version, isTCHAR);
 }
 
+void UserAgentPlatformInfo::set_firmware_version_details(
+    const std::string& firmware_version_details) {
+  firmware_version_details_ =
+      Sanitize(firmware_version_details, isVCHARorSpace);
+}
+
+void UserAgentPlatformInfo::set_os_experience(
+    const std::string& os_experience) {
+  os_experience_ = Sanitize(os_experience, isTCHAR);
+}
+
 void UserAgentPlatformInfo::set_cobalt_version(
     const std::string& cobalt_version) {
   cobalt_version_ = Sanitize(cobalt_version, isTCHAR);
diff --git a/cobalt/browser/user_agent_platform_info.h b/cobalt/browser/user_agent_platform_info.h
index d71c7fe..26b0b64 100644
--- a/cobalt/browser/user_agent_platform_info.h
+++ b/cobalt/browser/user_agent_platform_info.h
@@ -43,7 +43,11 @@
   base::Optional<std::string> original_design_manufacturer() const override {
     return original_design_manufacturer_;
   }
+
+#if SB_API_VERSION < 15
   SbSystemDeviceType device_type() const override { return device_type_; }
+#endif
+
   const std::string& device_type_string() const override {
     return device_type_string_;
   }
@@ -72,6 +76,10 @@
   const std::string& evergreen_version() const override {
     return evergreen_version_;
   }
+  const std::string& firmware_version_details() const override {
+    return firmware_version_details_;
+  }
+  const std::string& os_experience() const override { return os_experience_; }
   const std::string& cobalt_version() const override { return cobalt_version_; }
   const std::string& cobalt_build_version_number() const override {
     return cobalt_build_version_number_;
@@ -86,7 +94,10 @@
   void set_os_name_and_version(const std::string& os_name_and_version);
   void set_original_design_manufacturer(
       base::Optional<std::string> original_design_manufacturer);
+#if SB_API_VERSION < 15
   void set_device_type(SbSystemDeviceType device_type);
+#endif
+  void set_device_type(const std::string& device_type);
   void set_chipset_model_number(
       base::Optional<std::string> chipset_model_number);
   void set_model_year(base::Optional<std::string> model_year);
@@ -100,6 +111,9 @@
   void set_evergreen_type(const std::string& evergreen_type);
   void set_evergreen_file_type(const std::string& evergreen_file_type);
   void set_evergreen_version(const std::string& evergreen_version);
+  void set_firmware_version_details(
+      const std::string& firmware_version_details);
+  void set_os_experience(const std::string& os_experience);
   void set_cobalt_version(const std::string& cobalt_version);
   void set_cobalt_build_version_number(
       const std::string& cobalt_build_version_number);
@@ -109,7 +123,9 @@
   std::string starboard_version_;
   std::string os_name_and_version_;
   base::Optional<std::string> original_design_manufacturer_;
+#if SB_API_VERSION < 15
   SbSystemDeviceType device_type_ = kSbSystemDeviceTypeUnknown;
+#endif
   std::string device_type_string_;
   base::Optional<std::string> chipset_model_number_;
   base::Optional<std::string> model_year_;
@@ -122,6 +138,8 @@
   std::string evergreen_type_;
   std::string evergreen_file_type_;
   std::string evergreen_version_;
+  std::string firmware_version_details_;  // Only via Client Hints
+  std::string os_experience_;             // Only via Client Hints
 
   std::string cobalt_version_;
   std::string cobalt_build_version_number_;
diff --git a/cobalt/browser/user_agent_string_test.cc b/cobalt/browser/user_agent_string_test.cc
index 27e232b..ab6a09b 100644
--- a/cobalt/browser/user_agent_string_test.cc
+++ b/cobalt/browser/user_agent_string_test.cc
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "cobalt/browser/user_agent_string.h"
+
 #include <map>
 
 #include "cobalt/browser/user_agent_platform_info.h"
-#include "cobalt/browser/user_agent_string.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
@@ -28,7 +29,7 @@
   platform_info.set_starboard_version("");
   platform_info.set_os_name_and_version("");
   platform_info.set_original_design_manufacturer("");
-  platform_info.set_device_type(kSbSystemDeviceTypeUnknown);
+  platform_info.set_device_type("UNKNOWN");
   platform_info.set_chipset_model_number("");
   platform_info.set_model_year("");
   platform_info.set_firmware_version("");
@@ -306,7 +307,7 @@
   UserAgentPlatformInfo platform_info =
       CreateOnlyOSNameAndVersionPlatformInfo();
   platform_info.set_original_design_manufacturer("Aperture_Science_Innovators");
-  platform_info.set_device_type(kSbSystemDeviceTypeOverTheTopBox);
+  platform_info.set_device_type("OTT");
   platform_info.set_chipset_model_number("P-body/Orange_Atlas/Blue");
   platform_info.set_model_year("2013");
   platform_info.set_firmware_version("0,01");
@@ -323,7 +324,7 @@
 TEST(UserAgentStringFactoryTest, WithOnlyBrandModelAndDeviceType) {
   UserAgentPlatformInfo platform_info =
       CreateOnlyOSNameAndVersionPlatformInfo();
-  platform_info.set_device_type(kSbSystemDeviceTypeOverTheTopBox);
+  platform_info.set_device_type("OTT");
   platform_info.set_brand("Aperture Science");
   platform_info.set_model("GLaDOS");
   std::string user_agent_string = CreateUserAgentString(platform_info);
@@ -336,7 +337,7 @@
 TEST(UserAgentStringFactoryTest, WithStarboardVersion) {
   UserAgentPlatformInfo platform_info = CreateEmptyPlatformInfo();
   platform_info.set_starboard_version("Starboard/6");
-  platform_info.set_device_type(kSbSystemDeviceTypeOverTheTopBox);
+  platform_info.set_device_type("OTT");
   std::string user_agent_string = CreateUserAgentString(platform_info);
 
   const char* tv_info_str =
@@ -429,7 +430,8 @@
   GetUserAgentInputMap(user_agent_input, user_agent_input_map);
 
   std::map<std::string, std::string> expected_user_agent_input_map{
-      {"aux_field", ""}, {"device_type", "GAME"},
+      {"aux_field", ""},
+      {"device_type", "GAME"},
   };
   EXPECT_TRUE(user_agent_input_map == expected_user_agent_input_map);
 }
diff --git a/cobalt/browser/web_module.cc b/cobalt/browser/web_module.cc
index f746098..33ca0be 100644
--- a/cobalt/browser/web_module.cc
+++ b/cobalt/browser/web_module.cc
@@ -28,6 +28,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/c_val.h"
 #include "cobalt/base/debugger_hooks.h"
@@ -615,7 +616,7 @@
     // Post a task that blocks the message loop and waits for the web debugger.
     // This must be posted before the the window's task to load the document.
     waiting_for_web_debugger_->store(true);
-    base::MessageLoop::current()->task_runner()->PostTask(
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::Bind(&WebModule::Impl::WaitForWebDebugger,
                               base::Unretained(this)));
   }
@@ -949,8 +950,7 @@
   LayoutResults layout_results_with_callback(
       layout_results.render_tree, layout_results.layout_time,
       base::Bind(&WebModule::Impl::OnRenderTreeRasterized,
-                 base::Unretained(this),
-                 base::MessageLoop::current()->task_runner(),
+                 base::Unretained(this), base::ThreadTaskRunnerHandle::Get(),
                  last_render_tree_produced_time_));
 
 #if defined(ENABLE_DEBUGGER)
@@ -1027,7 +1027,7 @@
       base::Bind(&WebModule::Impl::global_environment, base::Unretained(this)),
       base::Bind(&WebModule::Impl::InjectKeyboardEvent, base::Unretained(this)),
       base::Bind(&WebModule::Impl::InjectPointerEvent, base::Unretained(this)),
-      base::MessageLoop::current()->task_runner()));
+      base::ThreadTaskRunnerHandle::Get()));
 }
 #endif  // defined(ENABLE_WEBDRIVER)
 
diff --git a/cobalt/build/build_id.py b/cobalt/build/build_id.py
index 1abe051..1c6791f 100755
--- a/cobalt/build/build_id.py
+++ b/cobalt/build/build_id.py
@@ -13,12 +13,9 @@
 # limitations under the License.
 """Generate a Cobalt build ID header."""
 
-import datetime
-import os
 import sys
-import time
 
-template = """
+BUILD_ID_HEADER_TEMPLATE = """
 #ifndef _COBALT_BUILD_ID_H_
 #define _COBALT_BUILD_ID_H_
 
@@ -32,7 +29,7 @@
 
 
 def BuildId(output_path, version_number):
-  """Write a Cobalt build_id header file with time and version info.
+  """Write a Cobalt build_id header file version info.
 
   Args:
     output_path: Location of the build id header to write.
@@ -40,21 +37,9 @@
   Returns:
     0 on success.
   """
-  username = os.environ.get('USERNAME', os.environ.get('USER'))
-  if not username:
-    username = 'unknown'
-  timestamp = time.time()
-  date_rep = datetime.datetime.fromtimestamp(timestamp).strftime('%c')
-
-  with open(output_path, 'w') as f:
-    f.write(
-        template.format(
-            date_rep=date_rep,
-            timestamp=int(timestamp),
-            version_number=version_number,
-            username=username))
-  return 0
+  with open(output_path, 'w', encoding='utf-8') as f:
+    f.write(BUILD_ID_HEADER_TEMPLATE.format(version_number=version_number))
 
 
 if __name__ == '__main__':
-  sys.exit(BuildId(sys.argv[1], sys.argv[2]))
+  BuildId(sys.argv[1], sys.argv[2])
diff --git a/cobalt/build/build_number.py b/cobalt/build/build_number.py
deleted file mode 100644
index 7ec534e..0000000
--- a/cobalt/build/build_number.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# Copyright 2012 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Utilities for use by gyp_cobalt and other build tools."""
-
-import json
-import logging
-import os
-import subprocess
-from six.moves import urllib
-from cobalt.tools import paths
-
-_SUBREPO_PATHS = ['starboard/keyboxes']
-_VERSION_SERVER_URL = 'https://carbon-airlock-95823.appspot.com/build_version/generate'  # pylint:disable=line-too-long
-_XSSI_PREFIX = ")]}'\n"
-
-# The path to the build.id file that preserves a build ID.
-BUILD_ID_PATH = os.path.join(paths.BUILD_ROOT, 'build.id')
-
-
-def CheckRevInfo(key, cwd=None):
-  git_get_remote_args = ['git', 'config', '--get', 'remote.origin.url']
-  remote = subprocess.check_output(
-      git_get_remote_args, cwd=cwd).strip().decode('utf-8')
-
-  if remote.endswith('.git'):
-    remote = remote[:-len('.git')]
-
-  git_get_revision_args = ['git', 'rev-parse', 'HEAD']
-  revision = subprocess.check_output(
-      git_get_revision_args, cwd=cwd).strip().decode('utf-8')
-  return {key: '{}@{}'.format(remote, revision)}
-
-
-def GetRevinfo():
-  """Get absolute state of all git repos."""
-  # First make sure we can add the cobalt_src repo.
-  try:
-    repos = CheckRevInfo('.', cwd=paths.REPOSITORY_ROOT)
-  except subprocess.CalledProcessError as e:
-    logging.warning('Failed to get revision information: %s', e)
-    return {}
-
-  for rel_path in _SUBREPO_PATHS:
-    path = os.path.join(paths.REPOSITORY_ROOT, rel_path)
-    try:
-      repos.update(CheckRevInfo(rel_path, cwd=path))
-    except subprocess.CalledProcessError as e:
-      logging.warning('Failed to get revision information for subrepo %s: %s',
-                      rel_path, e)
-      continue
-    except OSError as e:
-      logging.info('%s. Subrepository %s not found.', e, rel_path)
-      continue
-
-  return repos
-
-
-# We leave this function in for backwards compatibility.
-# New callers should use GetOrGenerateNewBuildNumber.
-def GetBuildNumber(version_server=_VERSION_SERVER_URL):
-  return GetOrGenerateNewBuildNumber(version_server)
-
-
-def GetOrGenerateNewBuildNumber(version_server=_VERSION_SERVER_URL):
-  """Send a request to the build version server for a build number."""
-
-  if os.path.isfile(BUILD_ID_PATH):
-    with open(BUILD_ID_PATH, 'r') as build_id_file:
-      build_number = int(build_id_file.read().replace('\n', ''))
-      logging.info('Retrieving build number from %s', BUILD_ID_PATH)
-      return build_number
-
-  revinfo = GetRevinfo()
-  json_deps = json.dumps(revinfo)
-  username = os.environ.get('USERNAME', os.environ.get('USER'))
-
-  post_data = {'deps': json_deps}
-  if username:
-    post_data['user'] = username
-
-  logging.debug('Post data is %s', post_data)
-  request = urllib.request.Request(version_server)
-  # TODO: retry on timeout.
-  try:
-    response = urllib.request.urlopen(  # pylint: disable=consider-using-with
-        request,
-        data=urllib.parse.urlencode(post_data).encode('utf-8'))
-    data = response.read().decode('utf-8')
-    if data.find(_XSSI_PREFIX) == 0:
-      data = data[len(_XSSI_PREFIX):]
-    results = json.loads(data)
-    build_number = results.get('build_number', 0)
-    return build_number
-  except urllib.error.HTTPError as e:
-    logging.warning('Failed to retrieve build number: %s', e)
-    return 0
-  except urllib.error.URLError as e:
-    logging.warning('Could not connect to %s: %s', version_server, e)
-    return 0
diff --git a/cobalt/build/cobalt_configuration.py b/cobalt/build/cobalt_configuration.py
index a8c09b1..adaa361 100644
--- a/cobalt/build/cobalt_configuration.py
+++ b/cobalt/build/cobalt_configuration.py
@@ -13,12 +13,7 @@
 # limitations under the License.
 """Base cobalt configuration for GYP."""
 
-import os
-
-from cobalt.tools import paths
-import cobalt.tools.webdriver_benchmark_config as wb_config
 from starboard.build import application_configuration
-from starboard.tools.config import Config
 
 # The canonical Cobalt application name.
 APPLICATION_NAME = 'cobalt'
@@ -30,40 +25,6 @@
   Cobalt per-platform configurations, if defined, must subclass from this class.
   """
 
-  def GetVariables(self, config_name):
-
-    # Use env var to optimize build speed on CI
-    try:
-      # Force to int, so it's easy to pass in an override.
-      use_fastbuild = int(os.environ.get('IS_CI', 0))
-    except (ValueError, TypeError):
-      use_fastbuild = 0
-
-    try:
-      build_in_docker = int(os.environ.get('IS_DOCKER', 0))
-    except (ValueError, TypeError):
-      build_in_docker = 0
-
-    variables = {
-        # This is used to omit large debuginfo in files on CI environment
-        'cobalt_fastbuild': use_fastbuild,
-        'cobalt_docker_build': build_in_docker,
-
-        # This is here rather than cobalt_configuration.gypi so that it's
-        # available for browser_bindings_gen.gyp.
-        'enable_debugger': 0 if config_name == Config.GOLD else 1,
-
-        # Cobalt uses OpenSSL on all platforms.
-        'use_openssl': 1,
-    }
-    return variables
-
-  def GetPostIncludes(self):
-    # Insert cobalt_configuration.gypi into the post includes list.
-    includes = super(CobaltConfiguration, self).GetPostIncludes()
-    includes[:0] = [os.path.join(paths.BUILD_ROOT, 'cobalt_configuration.gypi')]
-    return includes
-
   def GetWebPlatformTestFilters(self):
     """Gets all tests to be excluded from a black box test run."""
 
@@ -175,6 +136,7 @@
         'poem_unittests',
         'render_tree_test',
         'renderer_test',
+        'scroll_engine_tests',
         'storage_test',
         'text_encoding_test',
         'web_test',
@@ -185,25 +147,3 @@
         'xhr_test',
         'zip_unittests',
     ]
-
-  def GetDefaultTargetBuildFile(self):
-    return os.path.join(paths.BUILD_ROOT, 'all.gyp')
-
-  def WebdriverBenchmarksEnabled(self):
-    """Determines if webdriver benchmarks are enabled or not.
-
-    Returns:
-      True if webdriver benchmarks can run on this platform, False if not.
-    """
-    return False
-
-  def GetDefaultSampleSize(self):
-    return wb_config.STANDARD_SIZE
-
-  def GetWebdriverBenchmarksTargetParams(self):
-    """Gets command line params to pass to the Cobalt executable."""
-    return []
-
-  def GetWebdriverBenchmarksParams(self):
-    """Gets command line params to pass to the webdriver benchmark script."""
-    return []
diff --git a/cobalt/build/generate_data_header.py b/cobalt/build/generate_data_header.py
index 04fe02e..10dad4e 100755
--- a/cobalt/build/generate_data_header.py
+++ b/cobalt/build/generate_data_header.py
@@ -62,7 +62,7 @@
       return ord(x) if is_py2 else x
 
     output_string = ',\n'.join([
-        ', '.join(['0x%02x' % GetByte(y)
+        ', '.join([f'0x{GetByte(y):02x}'
                    for y in x])
         for x in Chunks(file_contents, 13)
     ])
@@ -95,7 +95,7 @@
     elif os.path.isfile(path):
       file_list.append(InputFile(path, os.path.dirname(path)))
     else:
-      raise ValueError('%s is not a file or directory.' % path)
+      raise ValueError(f'{path} is not a file or directory.')
   return file_list
 
 
@@ -104,7 +104,7 @@
 
   for input_file in input_files:
     input_file_variable_name = input_file.GetVariableName()
-    output_file.write('const unsigned char %s[] =\n' % input_file_variable_name)
+    output_file.write(f'const unsigned char {input_file_variable_name}[] =\n')
     WriteFileDataToHeader(input_file.path, output_file)
 
 
@@ -127,16 +127,16 @@
   for input_file in input_files:
     # The lookup key will be the file path relative to the input directory
     input_file_variable_name = input_file.GetVariableName()
-    output_file.write('  out_map["%s"] = FileContents(%s, sizeof(%s));\n' %
-                      (input_file.GetRelativePath(), input_file_variable_name,
-                       input_file_variable_name))
+    output_file.write(f'  out_map["{input_file.GetRelativePath()}"] = '
+                      f'FileContents({input_file_variable_name}, '
+                      f'sizeof({input_file_variable_name}));\n')
 
   output_file.write('}\n\n')
 
 
 def WriteHeader(namespace, output_file_name, files_to_concatenate):
   """Writes an embedded resource header to the given output filename."""
-  with open(output_file_name, 'w') as output_file:
+  with open(output_file_name, 'w', encoding='utf-8') as output_file:
     include_guard = '_COBALT_GENERATED_' + namespace.upper() + '_H_'
     output_file.write('// Copyright 2014 The Cobalt Authors. '
                       'All Rights Reserved.\n'
@@ -159,7 +159,7 @@
 
 if __name__ == '__main__':
   if len(sys.argv) < 4:
-    print('usage:\n %s <namespace> <output-file> <inputs...> \n' % sys.argv[0])
+    print(f'usage:\n {sys.argv[0]} <namespace> <output-file> <inputs...> \n')
     print(__doc__)
     sys.exit(1)
   main(sys.argv[1], sys.argv[2], sys.argv[3:])
diff --git a/cobalt/build/get_build_id.py b/cobalt/build/get_build_id.py
index 14f4f0d..5e5c717 100755
--- a/cobalt/build/get_build_id.py
+++ b/cobalt/build/get_build_id.py
@@ -15,21 +15,49 @@
 """Prints out the Cobalt Build ID."""
 
 import os
-from cobalt.build.build_number import GetOrGenerateNewBuildNumber
+import re
+import subprocess
+
+_FILE_DIR = os.path.dirname(__file__)
+COMMIT_COUNT_BUILD_NUMBER_OFFSET = 1000000
+
+# Matches numbers > 1000000. The pattern is basic so git log --grep is able to
+# interpret it.
+GIT_BUILD_NUMBER_PATTERN = r'[1-9]' + r'[0-9]' * 6 + r'[0-9]*'
+BUILD_NUMBER_TAG_PATTERN = r'^BUILD_NUMBER={}$'
+
+# git log --grep can't handle capture groups.
+BUILD_NUBER_PATTERN_WITH_CAPTURE = f'({GIT_BUILD_NUMBER_PATTERN})'
 
 
-def main():
-  # Note $BUILD_ID_SERVER_URL will always be set in CI.
-  build_id_server_url = os.environ.get('BUILD_ID_SERVER_URL')
-  if build_id_server_url:
-    build_num = GetOrGenerateNewBuildNumber(version_server=build_id_server_url)
-    if build_num == 0:
-      raise ValueError('The build number received was zero.')
-    print(build_num)
-  else:
-    # No need to generate a build id for local builds.
-    print('0')
+def get_build_number_from_commits(cwd=_FILE_DIR):
+  full_pattern = BUILD_NUMBER_TAG_PATTERN.format(GIT_BUILD_NUMBER_PATTERN)
+  output = subprocess.check_output(
+      ['git', 'log', '--grep', full_pattern, '-1', '--pretty=%b'],
+      cwd=cwd).decode()
+
+  full_pattern_with_capture = re.compile(
+      BUILD_NUMBER_TAG_PATTERN.format(BUILD_NUBER_PATTERN_WITH_CAPTURE),
+      flags=re.MULTILINE)
+  match = full_pattern_with_capture.search(output)
+  return match.group(1) if match else None
+
+
+def get_build_number_from_commit_count(cwd=_FILE_DIR):
+  output = subprocess.check_output(['git', 'rev-list', '--count', 'HEAD'],
+                                   cwd=cwd)
+  build_number = int(output.strip().decode('utf-8'))
+  return build_number + COMMIT_COUNT_BUILD_NUMBER_OFFSET
+
+
+def main(cwd=_FILE_DIR):
+  build_number = get_build_number_from_commits(cwd=cwd)
+
+  if not build_number:
+    build_number = get_build_number_from_commit_count(cwd=cwd)
+
+  return build_number
 
 
 if __name__ == '__main__':
-  main()
+  print(main())
diff --git a/cobalt/build/get_build_id_test.py b/cobalt/build/get_build_id_test.py
new file mode 100644
index 0000000..f9ea8c3
--- /dev/null
+++ b/cobalt/build/get_build_id_test.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+# Copyright 2023 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Tests the get_build_id module."""
+
+import os
+import subprocess
+import tempfile
+import unittest
+
+from cobalt.build import get_build_id
+
+_TEST_BUILD_NUMBER = 1234 + get_build_id.COMMIT_COUNT_BUILD_NUMBER_OFFSET
+
+
+# TODO(b/282040638): fix and re-enabled this
+@unittest.skipIf(os.name == 'nt', 'Broken on Windows')
+class GetBuildIdTest(unittest.TestCase):
+
+  def setUp(self):
+    self.test_dir = tempfile.TemporaryDirectory()  # pylint: disable=consider-using-with
+    self.original_cwd = os.getcwd()
+    os.chdir(self.test_dir.name)
+    subprocess.check_call(['git', 'init'])
+    subprocess.check_call(['git', 'config', 'user.name', 'pytest'])
+    subprocess.check_call(['git', 'config', 'user.email', 'pytest@pytest.com'])
+
+  def tearDown(self):
+    os.chdir(self.original_cwd)
+    self.test_dir.cleanup()
+
+  def make_commit(self, message='Temporary commit'):
+    with tempfile.NamedTemporaryFile('w', dir=self.test_dir.name) as temp_file:
+      subprocess.check_call(['git', 'add', temp_file.name])
+    subprocess.check_call(['git', 'commit', '-m', message])
+
+  def make_commit_with_build_number(self, build_number=_TEST_BUILD_NUMBER):
+    message = f'Subject line\n\nBUILD_NUMBER={build_number}'
+    self.make_commit(message)
+
+  def testSanity(self):
+    self.make_commit()
+    head_rev = subprocess.check_output(['git', 'rev-parse', 'HEAD'])
+    self.assertNotEqual(head_rev.strip().decode('utf-8'), '')
+
+  def testGetBuildNumberFromCommitsSunnyDay(self):
+    self.make_commit_with_build_number()
+    build_number = get_build_id.get_build_number_from_commits(
+        cwd=self.test_dir.name)
+    self.assertEqual(int(build_number), _TEST_BUILD_NUMBER)
+
+  def testGetBuildNumberFromCommitsSunnyDayGetMostRecent(self):
+    num_commits = 5
+    for i in range(num_commits):
+      self.make_commit_with_build_number(
+          get_build_id.COMMIT_COUNT_BUILD_NUMBER_OFFSET + i)
+    build_number = get_build_id.get_build_number_from_commits(
+        cwd=self.test_dir.name)
+    self.assertEqual(
+        int(build_number),
+        num_commits + get_build_id.COMMIT_COUNT_BUILD_NUMBER_OFFSET - 1)
+
+  def testGetBuildNumberFromCommitsRainyDayInvalidBuildNumber(self):
+    self.make_commit()
+    self.make_commit(f'BUILD_NUMBER={_TEST_BUILD_NUMBER}')
+    build_number = get_build_id.get_build_number_from_commits(
+        cwd=self.test_dir.name)
+    self.assertIsNone(build_number)
+
+  def testGetBuildNumberFromCommitCountSunnyDay(self):
+    num_commits = 5
+    for _ in range(num_commits):
+      self.make_commit()
+    build_number = get_build_id.get_build_number_from_commit_count(
+        cwd=self.test_dir.name)
+    self.assertEqual(
+        build_number,
+        num_commits + get_build_id.COMMIT_COUNT_BUILD_NUMBER_OFFSET)
+
+  def testCommitsOutrankCommitCount(self):
+    self.make_commit()
+    self.make_commit_with_build_number()
+    self.make_commit()
+    build_number = get_build_id.main(cwd=self.test_dir.name)
+    self.assertEqual(int(build_number), _TEST_BUILD_NUMBER)
+
+  def testFallbackToCommitCount(self):
+    num_commits = 5
+    for _ in range(num_commits):
+      self.make_commit()
+    build_number = get_build_id.main(cwd=self.test_dir.name)
+    self.assertEqual(
+        build_number,
+        num_commits + get_build_id.COMMIT_COUNT_BUILD_NUMBER_OFFSET)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/cobalt/build/gn.py b/cobalt/build/gn.py
index 973ec4c..7ff3bc4 100755
--- a/cobalt/build/gn.py
+++ b/cobalt/build/gn.py
@@ -37,15 +37,14 @@
 
   if overwrite_args or not os.path.exists(dst_args_gn_file):
     shutil.copy(src_args_gn_file, dst_args_gn_file)
-    with open(dst_args_gn_file, 'a') as f:
+    with open(dst_args_gn_file, 'a', encoding='utf-8') as f:
       f.write(f'build_type = "{build_type}"\n')
   else:
     print(f'{dst_args_gn_file} already exists.' +
           ' Running ninja will regenerate build files automatically.')
 
   gn_command = [
-      'gn', '--script-executable={}'.format(sys.executable), 'gen',
-      out_directory
+      'gn', f'--script-executable={sys.executable}', 'gen', out_directory
   ] + gn_gen_args
   print(' '.join(gn_command))
   subprocess.check_call(gn_command)
diff --git a/cobalt/build/save_build_id.py b/cobalt/build/save_build_id.py
index cdd0d01..a6f3b5d 100755
--- a/cobalt/build/save_build_id.py
+++ b/cobalt/build/save_build_id.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright 2016 The Cobalt Authors. All Rights Reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,10 +20,11 @@
 import sys
 import textwrap
 
-from cobalt.build import build_number
+from cobalt.build import get_build_id
+from cobalt.tools import paths
 
+_BUILD_ID_PATH = os.path.join(paths.BUILD_ROOT, 'build.id')
 _SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
-_BUILD_ID_PATH = build_number.BUILD_ID_PATH
 
 # Return values used by main().
 RETVAL_SUCCESS = 0
@@ -63,20 +64,15 @@
     return 0
 
   if not options.build_id:
-    build_id_server_url = os.environ.get('BUILD_ID_SERVER_URL')
-    if build_id_server_url:
-      options.build_id = build_number.GetOrGenerateNewBuildNumber(
-          version_server=build_id_server_url)
-    else:
-      options.build_id = build_number.GetOrGenerateNewBuildNumber()
+    options.build_id = get_build_id.main()
 
   if not options.build_id:
     logging.error('Unable to retrieve build id.')
     return RETVAL_ERROR
 
   try:
-    with open(_BUILD_ID_PATH, 'w') as build_id_file:
-      build_id_file.write('{0}'.format(options.build_id))
+    with open(_BUILD_ID_PATH, 'w', encoding='utf-8') as build_id_file:
+      build_id_file.write(f'{options.build_id}')
   except RuntimeError as e:
     logging.error(e)
     return RETVAL_ERROR
diff --git a/cobalt/build/sync_to_build_id.py b/cobalt/build/sync_to_build_id.py
deleted file mode 100755
index d96e4d0..0000000
--- a/cobalt/build/sync_to_build_id.py
+++ /dev/null
@@ -1,187 +0,0 @@
-#!/usr/bin/python2
-"""Syncs to a given Cobalt build id.
-
-Syncs current gclient instance to a given build id, as
-generated by "build_id.py" and stored on carbon-airlock-95823.
-"""
-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import argparse
-import json
-import os
-import shutil
-import subprocess
-import sys
-import requests
-
-_BUILD_ID_QUERY_URL = (
-    "https://carbon-airlock-95823.appspot.com/build_version/search")
-_BUILD_ID_QUERY_PARAMETER_NAME = "build_number"
-
-
-class SubprocessFailedException(Exception):
-  """Exception for non-zero subprocess exits."""
-
-  def __init__(self, command):
-    super(SubprocessFailedException, self).__init__()  # pylint: disable=super-with-arguments
-    self.command = command
-
-  def __str__(self):
-    return "Subprocess failed '{0}'".format(self.command)
-
-
-def _RunGitCommand(gitargs, **kwargs):
-  """Runs a git command with "gitargs", returning the output splitlines().
-
-  Args:
-    gitargs: Commandline args that follow 'git'.
-    **kwargs: Keyword args for Popen.
-
-  Returns:
-    All of stdout, as an array of lines.
-
-  Raises:
-    SubprocessFailedException: if the exit code is nonzero.
-
-  """
-  result_tuple = _RunGitCommandReturnExitCode(gitargs, **kwargs)
-  if result_tuple[0] != 0:
-    raise SubprocessFailedException(" ".join(["git"] + gitargs))
-  return result_tuple[1]
-
-
-def _RunGitCommandReturnExitCode(gitargs, **kwargs):
-  """Runs a git command with "gitargs", returning the exit code and output.
-
-  Args:
-    gitargs: Commandline args that follow 'git'.
-    **kwargs: Keyword args for Popen.
-
-  Returns:
-    Tuple of (exit code, all of stdout as an array of lines).
-
-  """
-  popen_args = ["git"] + gitargs
-  with subprocess.Popen(popen_args, stdout=subprocess.PIPE, **kwargs) as p:
-    output = p.stdout.read().splitlines()
-    return p.wait(), output
-
-
-def main():
-  dev_null = open(os.devnull, "w")  # pylint: disable=consider-using-with
-  arg_parser = argparse.ArgumentParser(
-      description="Syncs to a given Cobalt build id")
-  arg_parser.add_argument("buildid", nargs=1)
-  arg_parser.add_argument(
-      "--force",
-      default=False,
-      action="store_true",
-      help="Deletes directories that don't match the requested format.")
-  args = arg_parser.parse_args()
-  r = requests.get(
-      _BUILD_ID_QUERY_URL,
-      params={_BUILD_ID_QUERY_PARAMETER_NAME: args.buildid[0]})
-  if not r.ok:
-    print(
-        "HTTP request failed\n{0} {1}\n{2}".format(r.status_code, r.reason,
-                                                   r.text),
-        file=sys.stderr)
-    return 1
-  # The response starts with a security-related close expression line
-  outer_json = json.loads(r.text.splitlines()[1])
-  hashes = json.loads(outer_json["deps"])
-  git_root = os.getcwd()
-
-  for relpath, rep_hash in hashes.items():
-    path = os.path.normpath(os.path.join(git_root, relpath))
-    if not os.path.exists(path):
-      # No warning in this case, we will attempt to clone the repository in
-      # the next pass through the repos.
-      continue
-    is_dirty = (
-        bool(
-            _RunGitCommandReturnExitCode(["diff", "--no-ext-diff", "--quiet"],
-                                         cwd=path,
-                                         stderr=dev_null)[0]) or
-        bool(
-            _RunGitCommandReturnExitCode(
-                ["diff", "--no-ext-diff", "--quiet", "--cached"],
-                cwd=path,
-                stderr=dev_null)[0]))
-
-    if is_dirty:
-      print("{0} is dirty, please resolve".format(relpath))
-      return 1
-
-    (requested_repo, _) = rep_hash.split("@")
-    remote_url = _RunGitCommand(["config", "--get", "remote.origin.url"],
-                                cwd=path)[0].strip().decode("utf-8")
-    if requested_repo.endswith(".git"):
-      if remote_url + ".git" == requested_repo:
-        print(("WARNING: You are syncing to {0} instead of {1}. While these "
-               "point to the same repo, the differing extension will cause "
-               "different build ids to be generated. If you need the same "
-               "id, you'll need to specifically clone {0} (note the .git "
-               "extension).").format(requested_repo, remote_url))
-        remote_url += ".git"
-
-    if remote_url != requested_repo:
-      if args.force and path != git_root:
-        shutil.rmtree(path)
-      else:
-        print(("{0} exists but does not point to the requested repo for that "
-               "path, {1}. Either replace that directory manually or run this "
-               "script with --force. --force will not try to remove the top "
-               "level repository.").format(path, requested_repo))
-        return 1
-
-  for relpath, rep_hash in hashes.items():
-    path = os.path.normpath(os.path.join(git_root, relpath))
-
-    # repo_hash has a repo path prefix like this:
-    # 'https://chromium.googlesource.com/chromium/llvm-project/libcxx.git
-    # @48198f9110397fff47fe7c37cbfa296be7d44d3d'
-    (requested_repo, requested_hash) = rep_hash.split("@")
-
-    if not os.path.exists(path):
-      print("Missing path {0}, cloning from {1}.".format(path, requested_repo))
-      try:
-        # The clone command will create all missing directories leading to the
-        # path.  If the clone is successful, we continue on as usual and let
-        # the subsequent logic here checkout the appropriate git hash.
-        _RunGitCommand(["clone", "-q", requested_repo, path])
-      except SubprocessFailedException:
-        print("There was an error cloning the repository.")
-        continue
-
-    current_hash = _RunGitCommand(["rev-parse", "HEAD"], cwd=path)[0]
-
-    if requested_hash == current_hash:
-      continue
-    symbolic_ref = None
-    try:
-      symbolic_ref = _RunGitCommand(["symbolic-ref", "--short", "-q", "HEAD"],
-                                    cwd=path,
-                                    stderr=dev_null)[0]
-    except SubprocessFailedException:
-      pass
-
-    user_visible_commit = symbolic_ref if symbolic_ref else current_hash[0:7]
-
-    print("{0} was at {1} now {2}".format(path, user_visible_commit,
-                                          requested_hash[0:7]))
-
-    _RunGitCommand(["checkout", "-q", "--detach", requested_hash], cwd=path)
-
-  return 0
-
-
-if __name__ == "__main__":
-  try:
-    sys.exit(main())
-  except SubprocessFailedException as ex:
-    print(str(ex), file=sys.stderr)
-    sys.exit(1)
diff --git a/cobalt/codereview.settings b/cobalt/codereview.settings
new file mode 100644
index 0000000..a4b341a
--- /dev/null
+++ b/cobalt/codereview.settings
@@ -0,0 +1,4 @@
+# This file is used by gcl to get repository specific information.
+GERRIT_HOST: lbshell-internal-review.googlesource.com
+GERRIT_AUTODETECT_BRANCH: true
+CODE_REVIEW_SERVER: lbshell-internal-review.googlesource.com
diff --git a/cobalt/content/fonts/config/common/fonts.xml b/cobalt/content/fonts/config/common/fonts.xml
index abba022..2ddeaec 100644
--- a/cobalt/content/fonts/config/common/fonts.xml
+++ b/cobalt/content/fonts/config/common/fonts.xml
@@ -184,7 +184,7 @@
             <font weight="400" style="normal">NotoSansArmenian-Regular.woff2</font>
             <font weight="700" style="normal">NotoSansArmenian-Bold.woff2</font>
         </family>
-        <family lang="und-Geor,und-Geok" pages="0,5,16,45,254">
+        <family lang="und-Geor,und-Geok" pages="0,5,16,28,45,254">
             <font weight="400" style="normal">NotoSansGeorgian-Regular.woff2</font>
             <font weight="700" style="normal">NotoSansGeorgian-Bold.woff2</font>
         </family>
diff --git a/cobalt/content/fonts/font_files/NotoSansGeorgian-Bold.woff2 b/cobalt/content/fonts/font_files/NotoSansGeorgian-Bold.woff2
index a7f9336..03a41c8 100644
--- a/cobalt/content/fonts/font_files/NotoSansGeorgian-Bold.woff2
+++ b/cobalt/content/fonts/font_files/NotoSansGeorgian-Bold.woff2
Binary files differ
diff --git a/cobalt/content/fonts/font_files/NotoSansGeorgian-Regular.woff2 b/cobalt/content/fonts/font_files/NotoSansGeorgian-Regular.woff2
index fb2fe49..68e26b4 100644
--- a/cobalt/content/fonts/font_files/NotoSansGeorgian-Regular.woff2
+++ b/cobalt/content/fonts/font_files/NotoSansGeorgian-Regular.woff2
Binary files differ
diff --git a/cobalt/content/fonts/scripts/filter_fonts.py b/cobalt/content/fonts/scripts/filter_fonts.py
index 74486da..4eca57e 100755
--- a/cobalt/content/fonts/scripts/filter_fonts.py
+++ b/cobalt/content/fonts/scripts/filter_fonts.py
@@ -80,9 +80,8 @@
   elif category == '4':
     return True
   else:
-    raise ValueError(
-        'Package category for "%s" must be between 0 and 4 (is: %s)' %
-        (package_name, category))
+    raise ValueError(f'Package category for "{package_name}" must be between '
+                     f'0 and 4 (is: {category})')
 
 
 def FilterFonts(filter_function, node, families, fonts):
@@ -190,7 +189,7 @@
   FilterFonts(filter_function, fonts_doc, kept_families, kept_fonts)
 
   if options.output_xml:
-    with open(options.output_xml, 'w') as f:
+    with open(options.output_xml, 'w', encoding='utf-8') as f:
       f.write(fonts_doc.toprettyxml(indent='  '))
 
   if options.fonts_dir:
diff --git a/cobalt/css_parser/BUILD.gn b/cobalt/css_parser/BUILD.gn
index 93c7458..d6a668c 100644
--- a/cobalt/css_parser/BUILD.gn
+++ b/cobalt/css_parser/BUILD.gn
@@ -97,6 +97,11 @@
     "//nb",
     "//starboard/common",
   ]
+
+  if (is_clang_16) {
+    # grammar_impl_generated.h variable 'yynerrs' set but not used
+    cflags_cc = [ "-Wno-unused-but-set-variable" ]
+  }
 }
 
 target(gtest_target_type, "css_parser_test") {
diff --git a/cobalt/debug/backend/debug_dispatcher.cc b/cobalt/debug/backend/debug_dispatcher.cc
index e6643c6..0e7a137 100644
--- a/cobalt/debug/backend/debug_dispatcher.cc
+++ b/cobalt/debug/backend/debug_dispatcher.cc
@@ -15,10 +15,12 @@
 #include "cobalt/debug/backend/debug_dispatcher.h"
 
 #include <string>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/logging.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
 #include "cobalt/debug/debug_client.h"
 
@@ -30,7 +32,7 @@
                                  DebugScriptRunner* script_runner)
     : script_debugger_(script_debugger),
       script_runner_(script_runner),
-      task_runner_(base::MessageLoop::current()->task_runner()),
+      task_runner_(base::ThreadTaskRunnerHandle::Get()),
       is_paused_(false),
       // No manual reset, not initially signaled.
       command_added_while_paused_(
diff --git a/cobalt/debug/command.h b/cobalt/debug/command.h
index 566d58a..fd63ddb 100644
--- a/cobalt/debug/command.h
+++ b/cobalt/debug/command.h
@@ -18,6 +18,7 @@
 
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/debug/debug_client.h"
 #include "cobalt/debug/json_object.h"
 
@@ -49,7 +50,7 @@
         domain_(method_, 0, method_.find('.')),
         json_params_(json_params),
         callback_(response_callback),
-        task_runner_(base::MessageLoop::current()->task_runner()),
+        task_runner_(base::ThreadTaskRunnerHandle::Get()),
         response_sent_(false) {
     DCHECK(!method_.empty());
     DCHECK(!domain_.empty());
diff --git a/cobalt/debug/console/debug_hub.cc b/cobalt/debug/console/debug_hub.cc
index 41f3ce1..f87f175 100644
--- a/cobalt/debug/console/debug_hub.cc
+++ b/cobalt/debug/console/debug_hub.cc
@@ -16,11 +16,13 @@
 
 #include <memory>
 #include <set>
+#include <utility>
 
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/json/json_writer.h"
 #include "base/path_service.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
 #include "cobalt/base/c_val.h"
 #include "cobalt/base/cobalt_paths.h"
@@ -164,8 +166,7 @@
 void DebugHub::RunResponseCallback(
     const scoped_refptr<ResponseCallbackInfo>& callback_info,
     base::Optional<std::string> response) const {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(),
-            callback_info->task_runner);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), callback_info->task_runner);
   callback_info->callback.value().Run(std::move(response));
 }
 
diff --git a/cobalt/debug/console/debug_hub.h b/cobalt/debug/console/debug_hub.h
index da32546..ef5da93 100644
--- a/cobalt/debug/console/debug_hub.h
+++ b/cobalt/debug/console/debug_hub.h
@@ -15,12 +15,14 @@
 #ifndef COBALT_DEBUG_CONSOLE_DEBUG_HUB_H_
 #define COBALT_DEBUG_CONSOLE_DEBUG_HUB_H_
 
+#include <memory>
 #include <string>
 
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop.h"
 #include "base/optional.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/debug/console/console_command.h"
 #include "cobalt/debug/console/debug_console_mode.h"
 #include "cobalt/debug/console/debugger_event_target.h"
@@ -63,7 +65,7 @@
     ResponseCallbackInfo(DebugHub* const debugger,
                          const ResponseCallbackArg& cb)
         : callback(debugger, cb),
-          task_runner(base::MessageLoop::current()->task_runner()) {}
+          task_runner(base::ThreadTaskRunnerHandle::Get()) {}
     ResponseCallbackArg::Reference callback;
     scoped_refptr<base::SingleThreadTaskRunner> task_runner;
     friend class base::RefCountedThreadSafe<ResponseCallbackInfo>;
@@ -97,7 +99,7 @@
   const script::Sequence<ConsoleCommand> console_commands() const;
 
   // Sends a console command to be handled in the context of the debug WebModule
-  // by a registered hander. This lets the JavaScript debug console trigger
+  // by a registered handler. This lets the JavaScript debug console trigger
   // actions in the app.
   void SendConsoleCommand(const std::string& command,
                           const std::string& message);
diff --git a/cobalt/debug/console/debugger_event_target.cc b/cobalt/debug/console/debugger_event_target.cc
index 3b65b28..831b357 100644
--- a/cobalt/debug/console/debugger_event_target.cc
+++ b/cobalt/debug/console/debugger_event_target.cc
@@ -14,6 +14,7 @@
 
 #include "cobalt/debug/console/debugger_event_target.h"
 
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/source_location.h"
 
 namespace cobalt {
@@ -48,14 +49,14 @@
 void DebuggerEventTarget::AddListener(
     const DebuggerEventTarget::DebuggerEventCallbackArg& callback) {
   base::AutoLock auto_lock(lock_);
-  listeners_.insert(new ListenerInfo(
-      this, callback, base::MessageLoop::current()->task_runner()));
+  listeners_.insert(
+      new ListenerInfo(this, callback, base::ThreadTaskRunnerHandle::Get()));
 }
 
 void DebuggerEventTarget::NotifyListener(
     const DebuggerEventTarget::ListenerInfo* listener,
     const std::string& method, const base::Optional<std::string>& json_params) {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), listener->task_runner);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), listener->task_runner);
   listener->callback.value().Run(method, json_params);
 }
 
diff --git a/cobalt/demos/content/BUILD.gn b/cobalt/demos/content/BUILD.gn
index e9f5d09..1d6ec9a 100644
--- a/cobalt/demos/content/BUILD.gn
+++ b/cobalt/demos/content/BUILD.gn
@@ -71,8 +71,6 @@
     "media-element-demo/tsconfig.json",
     "media-element-demo/webpack.config.js",
     "media-query/media-query-test.html",
-    "mtm-demo/mtm.html",
-    "mtm-demo/normal.html",
     "opacity-transitions-demo/opacity-transitions-demo.html",
     "page-visibility-demo/page-visibility-demo.html",
     "performance-api-demo/performance-lifecycle-timing-demo.html",
@@ -98,6 +96,7 @@
     "splash_screen/redirected.html",
     "splash_screen/render_postponed.html",
     "system-caption-settings/index.html",
+    "text-encoding-workaround/text-encoding-workaround.html",
     "timer-demo/timer-demo.html",
     "transitions-demo/transitions-demo.html",
     "transparent-animated-webp-demo/bottleflip_loader.webp",
@@ -111,6 +110,10 @@
 
   if (is_internal_build) {
     sources += [
+      "//internal/cobalt/demos/content/mtm-demo/README.txt",
+      "//internal/cobalt/demos/content/mtm-demo/mtm.html",
+      "//internal/cobalt/demos/content/mtm-demo/normal.html",
+      "//internal/cobalt/demos/content/mtm-demo/progressive.mp4",
       "media-element-demo/public/assets/ac3.mp4",
       "media-element-demo/public/assets/dash-audio.mp4",
       "media-element-demo/public/assets/dash-video-1080p.mp4",
@@ -121,9 +124,6 @@
       "media-element-demo/public/assets/hvc1_720p.mp4",
       "media-element-demo/public/assets/hvc1_hdr_480p.mp4",
       "media-element-demo/public/assets/progressive.mp4",
-      "mtm-demo/README.txt",
-      "mtm-demo/progressive.mp4",
-      "text-encoding-workaround/text-encoding-workaround.html",
     ]
   }
 
diff --git a/cobalt/demos/content/media-element-demo/package.json b/cobalt/demos/content/media-element-demo/package.json
index bed93af..d6e26e2 100644
--- a/cobalt/demos/content/media-element-demo/package.json
+++ b/cobalt/demos/content/media-element-demo/package.json
@@ -5,7 +5,7 @@
   "scripts": {
     "start": "npm run watch & npm run server",
     "build": "webpack",
-    "server": "http-server dist",
+    "server": "http-server -c-1 dist",
     "watch": "webpack --watch"
   },
   "author": "",
diff --git a/cobalt/demos/content/media-element-demo/src/components/player.ts b/cobalt/demos/content/media-element-demo/src/components/player.ts
index 7bd9c17..60e3a5c 100644
--- a/cobalt/demos/content/media-element-demo/src/components/player.ts
+++ b/cobalt/demos/content/media-element-demo/src/components/player.ts
@@ -25,6 +25,18 @@
   /** The <video> element. */
   private videoEl!: HTMLVideoElement;
 
+  /** The video SourceBuffer */
+  private videoSourceBuffer!: SourceBuffer;
+
+  /** The audio SourceBuffer */
+  private audioSourceBuffer!: SourceBuffer;
+
+  /** max(videoSourceBuffer.writeHead - videoEl.currentTime) */
+  private maxVideoWriteHeadDistance!: number;
+
+  /** max(audioSourceBuffer.writeHead - videoEl.currentTime) */
+  private maxAudioWriteHeadDistance!: number;
+
   /** The element displaying video download buffer info. */
   private videoDownloadBufferInfo!: Element;
 
@@ -53,6 +65,8 @@
     super(props);
     this.videos = convertToMediaArray(props.video);
     this.audios = convertToMediaArray(props.audio);
+    this.maxVideoWriteHeadDistance = 0;
+    this.maxAudioWriteHeadDistance = 0;
   }
 
   /** @override */
@@ -93,12 +107,37 @@
   }
 
   private renderVideoInfo() {
+    var h5vccAudioConnectors = '';
+    try {
+      h5vccAudioConnectors = this.videoEl.h5vccAudioConnectors;
+    } catch (error) {}
     renderComponent(
         VideoInfo, {
           duration: this.videoEl.duration,
           currentTime: this.videoEl.currentTime,
+          audioConnectors: h5vccAudioConnectors,
         },
         this.videoInfo);
+    if (this.videoSourceBuffer) {
+      this.maxVideoWriteHeadDistance =
+          Math.max(this.maxVideoWriteHeadDistance,
+                   this.videoSourceBuffer.writeHead - this.videoEl.currentTime);
+      renderComponent(
+        SourceBufferInfo,
+          {name: 'Video', sourceBuffer: this.videoSourceBuffer,
+           maxWriteHeadDistance: this.maxVideoWriteHeadDistance},
+         this.videoSourceBufferInfo);
+      }
+    if (this.audioSourceBuffer) {
+      this.maxAudioWriteHeadDistance =
+          Math.max(this.maxAudioWriteHeadDistance,
+                   this.audioSourceBuffer.writeHead - this.videoEl.currentTime);
+      renderComponent(
+        SourceBufferInfo,
+        {name: 'Audio', sourceBuffer: this.audioSourceBuffer,
+         maxWriteHeadDistance: this.maxAudioWriteHeadDistance},
+        this.audioSourceBufferInfo);
+    }
   }
 
   private async play() {
@@ -149,7 +188,7 @@
 
   /**
    * Plays all videos as adaptive videos.
-   * TODO: dynmaically calculate the source buffer MIME.
+   * TODO: dynamically calculate the source buffer MIME.
    */
   private playAdaptiveVideo() {
     const ms = new MediaSource();
@@ -158,12 +197,7 @@
       if (this.videos.length > 0) {
         const videoSourceBuffer =
             ms.addSourceBuffer('video/mp4; codecs="avc1.640028"');
-        videoSourceBuffer.addEventListener('updateend', () => {
-          renderComponent(
-              SourceBufferInfo,
-              {name: 'Video', sourceBuffer: videoSourceBuffer},
-              this.videoSourceBufferInfo);
-        });
+        this.videoSourceBuffer = videoSourceBuffer;
         const downloadBuffer = new DownloadBuffer(this.videos);
         downloadBuffer.register((reportMap) => {
           renderComponent(
@@ -176,12 +210,7 @@
       if (this.audios.length > 0) {
         const audioSourceBuffer =
             ms.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"');
-        audioSourceBuffer.addEventListener('updateend', () => {
-          renderComponent(
-              SourceBufferInfo,
-              {name: 'Audio', sourceBuffer: audioSourceBuffer},
-              this.audioSourceBufferInfo);
-        });
+        this.audioSourceBuffer = audioSourceBuffer;
         const downloadBuffer = new DownloadBuffer(this.audios);
         downloadBuffer.register(
             (reportMap) => {renderComponent(
diff --git a/cobalt/demos/content/media-element-demo/src/components/source_buffer_info.ts b/cobalt/demos/content/media-element-demo/src/components/source_buffer_info.ts
index 92343f3..6747f66 100644
--- a/cobalt/demos/content/media-element-demo/src/components/source_buffer_info.ts
+++ b/cobalt/demos/content/media-element-demo/src/components/source_buffer_info.ts
@@ -1,9 +1,12 @@
 interface Props {
   sourceBuffer: SourceBuffer;
   name: string;
+  maxWriteHeadDistance: number;
 }
 
 /** A component that displays the source buffer info. */
-export function SourceBufferInfo({sourceBuffer, name}: Props) {
-  return `<div>${name} buffered: ${sourceBuffer.buffered.end(0)} sec</div>`;
+export function SourceBufferInfo({sourceBuffer, name, maxWriteHeadDistance}: Props) {
+  return `<div>${name} buffered: ${sourceBuffer.buffered.end(0)} sec` +
+      `, writeHead: ${sourceBuffer.writeHead} sec` +
+      `, maxWriteHeadDistance: ${maxWriteHeadDistance} sec</div>`;
 }
diff --git a/cobalt/demos/content/media-element-demo/src/components/video_info.ts b/cobalt/demos/content/media-element-demo/src/components/video_info.ts
index 3f8306f..68012c8 100644
--- a/cobalt/demos/content/media-element-demo/src/components/video_info.ts
+++ b/cobalt/demos/content/media-element-demo/src/components/video_info.ts
@@ -1,9 +1,13 @@
 interface Props {
   duration: number;
   currentTime: number;
+  audioConnectors: string;
 }
 
 /** A component that displays video info. */
-export function VideoInfo({duration, currentTime}: Props) {
+export function VideoInfo({duration, currentTime, audioConnectors}: Props) {
+  if (audioConnectors) {
+    return `<div>${currentTime} / ${duration} / audioConnectors: ${audioConnectors}</div>`;
+  }
   return `<div>${currentTime} / ${duration}</div>`;
 }
diff --git a/cobalt/demos/content/mtm-demo/mtm.html b/cobalt/demos/content/mtm-demo/mtm.html
deleted file mode 100644
index eeee431..0000000
--- a/cobalt/demos/content/mtm-demo/mtm.html
+++ /dev/null
@@ -1,138 +0,0 @@
-<!DOCTYPE html>
-<html>
-
-<head>
-  <title>Map-To-Mesh Demo</title>
-
-  <style>
-    #v {
-      width: 100%;
-      height: 100%;
-      filter: map-to-mesh(equirectangular, 100deg 60deg,
-                          matrix3d(1, 0, 0, 0,
-                                   0, 1, 0, 0,
-                                   0, 0, 1, 0,
-                                   0, 0, 0, 1));
-    }
-
-    .instructions {
-      position: absolute;
-      left: 0;
-      background-color: white;
-      color: black;
-    }
-
-    @keyframes blink {
-      from {background-color: white;}
-      to {background-color: black;}
-    }
-
-    .spinner {
-      position: absolute;
-      left: 0;
-      bottom: 0;
-      animation: blink 1s infinite alternate;
-      width: 20px;
-      height: 20px;
-    }
-
-    #fps {
-      position: absolute;
-      left: 50px;
-      bottom: 0;
-      background-color: white;
-      color: black;
-    }
-  </style>
-
-  <script>
-    var degreesPerSecond = 90;
-    // The following mappings are done in this order:
-    // Up, Down, Left, Right
-
-    // Direction keys
-    camera3D.createKeyMapping(38, camera3D.DOM_CAMERA_PITCH, degreesPerSecond);
-    camera3D.createKeyMapping(40, camera3D.DOM_CAMERA_PITCH, -degreesPerSecond);
-    camera3D.createKeyMapping(37, camera3D.DOM_CAMERA_YAW, degreesPerSecond);
-    camera3D.createKeyMapping(39, camera3D.DOM_CAMERA_YAW, -degreesPerSecond);
-
-    // DPAD
-    camera3D.createKeyMapping(
-        0x800C, camera3D.DOM_CAMERA_PITCH, degreesPerSecond);
-    camera3D.createKeyMapping(
-        0x800D, camera3D.DOM_CAMERA_PITCH, -degreesPerSecond);
-    camera3D.createKeyMapping(
-        0x800E, camera3D.DOM_CAMERA_YAW, degreesPerSecond);
-    camera3D.createKeyMapping(
-        0x800F, camera3D.DOM_CAMERA_YAW, -degreesPerSecond);
-
-    // Left joystick
-    camera3D.createKeyMapping(
-        0x8011, camera3D.DOM_CAMERA_PITCH, degreesPerSecond);
-    camera3D.createKeyMapping(
-        0x8012, camera3D.DOM_CAMERA_PITCH, -degreesPerSecond);
-    camera3D.createKeyMapping(
-        0x8013, camera3D.DOM_CAMERA_YAW, degreesPerSecond);
-    camera3D.createKeyMapping(
-        0x8014, camera3D.DOM_CAMERA_YAW, -degreesPerSecond);
-
-    // Right joystick
-    camera3D.createKeyMapping(
-        0x8015, camera3D.DOM_CAMERA_PITCH, degreesPerSecond);
-    camera3D.createKeyMapping(
-        0x8016, camera3D.DOM_CAMERA_PITCH, -degreesPerSecond);
-    camera3D.createKeyMapping(
-        0x8017, camera3D.DOM_CAMERA_YAW, degreesPerSecond);
-    camera3D.createKeyMapping(
-        0x8018, camera3D.DOM_CAMERA_YAW, -degreesPerSecond);
-
-    // Update the frame rate counter at a regular interval.
-    function UpdateFPS() {
-      if ('h5vcc' in window && 'cVal' in window.h5vcc) {
-        // Query Cobalt for the average amount of time between the start of
-        // each frame.  Translate that into a framerate and then update a
-        // framerate counter on the window.
-        var average_frame_time_in_us = window.h5vcc.cVal.getValue(
-            'Renderer.Rasterize.DurationInterval.Avg');
-        if (!average_frame_time_in_us || average_frame_time_in_us <= 0) {
-          // In older versions of Cobalt use a different name for the framerate
-          // counter, so try falling back to that if the first fails.
-          average_frame_time_in_us = window.h5vcc.cVal.getValue(
-              'Renderer.Rasterize.Duration.Avg');
-        }
-
-        if (average_frame_time_in_us && average_frame_time_in_us > 0) {
-          // Convert frame time into frame rate (by taking the inverse).
-          // We also multiply by 1000000 to convert from microseconds to
-          // seconds.
-          var average_frames_per_second =
-              Math.round(1000000.0 / average_frame_time_in_us);
-
-          // Update the display with our calculated frame rate.
-          var fps_counter = document.getElementById('fps');
-          fps_counter.innerHTML = 'FPS: ' + average_frames_per_second;
-        }
-      }
-      window.setTimeout(UpdateFPS, 1000);
-    }
-    window.setTimeout(UpdateFPS, 1000);
-  </script>
-</head>
-
-<body>
-  <video autoplay loop id="v" src="progressive.mp4"></video>
-  <div class="instructions">
-    Use either the keyboard keys, direction keys, or analog joystick/thumbstick
-    to look around.
-  </div>
-  <!-- The spinner is required in order to get around an implementation detail
-       of the 'Renderer.Rasterize.DurationInterval.Avg' cval that we rely on
-       for measuring the framerate.  In a nutshell, that cval is only updated
-       when a CSS animation is active, but not when a video is playing, even
-       though both of these things result in a continual re-rasterization of
-       the UI/video. -->
-  <div class="spinner"></div>
-  <div id="fps"></div>
-</body>
-
-</html>
diff --git a/cobalt/demos/content/mtm-demo/normal.html b/cobalt/demos/content/mtm-demo/normal.html
deleted file mode 100644
index 3d516f6..0000000
--- a/cobalt/demos/content/mtm-demo/normal.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <title>Normal Demo</title>
-  <style>
-    body {
-      background-color: rgb(255, 255, 255);
-      color: #0047ab;
-      font-size: 100px;
-    }
-    .vid {
-      margin: 100px;
-      border: 10px solid blue;
-      width: 960px;
-      height: 540px;
-    }
-  </style>
-</head>
-<body>
-  <div>Normal Demo</div>
-  <video autoplay loop id="v" class="vid" src="progressive.mp4"></video>
-</body>
-</html>
diff --git a/cobalt/demos/content/text-encoding-workaround/text-encoding-workaround.html b/cobalt/demos/content/text-encoding-workaround/text-encoding-workaround.html
new file mode 100644
index 0000000..799fcc5
--- /dev/null
+++ b/cobalt/demos/content/text-encoding-workaround/text-encoding-workaround.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<head>
+    <title>Text Encoding Workaround</title>
+    <style>
+        div {
+            background-color: rgb(200, 200, 200);
+            height: 50px;
+        }
+    </style>
+</head>
+<body>
+    <div id="text-decoded"> </div>
+    <div id="text-encoded"> </div>
+    <div id="large-string-encode"> </div>
+    <div id="large-string-decode"> </div>
+    <script>
+        /*
+        Text encoding is not natively supported by Cobalt.
+        There is work in progress to support it in Cobalt 22.lts.
+
+        In the mean time, a workaround is presented here, that could be
+        used to encode/decode to/from UTF-8.
+
+        The full IDL definition can be found at:
+        https://cobalt.googlesource.com/cobalt/+/refs/heads/21.lts.1+/src/cobalt/fetch/fetch_internal.idl,
+        ant it is available in 19.lts.1+ versions.
+
+        **WARNING:** As mentioned in the IDL file, the utility was not
+        meant to be public and should not be used outside of the fetch
+        implementation.
+
+        **WARNING:** Even when this is a native implementation it might still
+        not be ideal to handle very large strings due to its performance.
+
+        **WARNING:** DecodeFromUTF8 throws a simple TypeError exception
+        when the data provided is not a valid utf-8.
+        */
+
+        // Using FetchInternal.decodeFromUTF8
+        var arr = new Uint8Array(
+            [ 228, 189, 160, 229, 165, 189, 239, 188, 140, 228,
+              184, 150, 231, 149, 140,  33,  32, 208, 159, 209,
+              128, 208, 184, 208, 178, 208, 181, 209, 130,  32,
+              208, 188, 208, 184, 209, 128,  33,  32,  72, 101,
+              108, 108, 111,  32, 119, 111, 114, 108, 100, 33]);
+        var utf8_string = FetchInternal.decodeFromUTF8(arr);
+        document.getElementById("text-decoded").innerHTML = utf8_string;
+
+        // Using FetchInternal.encodeToUTF8
+        var str = "你好,世界! Привет мир! Hello world!";
+        var u8_arr = FetchInternal.encodeToUTF8(str);
+        document.getElementById("text-encoded").innerHTML = u8_arr.toString();
+
+        // Trying very large strings;
+        // Note: This section takes a while for this to be completed.
+        var kSize = 1048576; // 8 x 1024 x 128
+        var char = "c";
+        var large_str = char.repeat(kSize);
+        var u8_large_arr = FetchInternal.encodeToUTF8(large_str);
+        document.getElementById("large-string-encode").innerHTML = u8_large_arr.toString();
+        var large_decoded_str = FetchInternal.decodeFromUTF8(u8_large_arr);
+        document.getElementById("large-string-decode").innerHTML = large_decoded_str;
+    </script>
+</body>
+</html>
diff --git a/cobalt/doc/cvals.md b/cobalt/doc/cvals.md
new file mode 100644
index 0000000..47eb55a
--- /dev/null
+++ b/cobalt/doc/cvals.md
@@ -0,0 +1,433 @@
+# Cobalt CVals
+
+## Overview
+
+CVals are globally accessible, thread-safe, key-value pairs within Cobalt, which
+are primarily used for monitoring state and tracking performance and memory.
+Each CVal is defined with a unique key and registered with the global
+CValManager. The CValManager can subsequently be queried for the current value
+of the key, which is returned as a string. CVals come in two varieties:
+
+*   **PublicCVals** - active in all builds.
+*   **DebugCVals** - only enabled in non-Gold builds.
+
+## Usage
+
+### Debug Console
+
+The debug console can be toggled between three modes (off, on, and hud) by
+pressing Ctrl-O. It displays the current values of registered CVals, updating
+them at 60 Hz. The initially displayed values are determined by
+`DEFAULT_ACTIVE_SET` in `debug_values/console_values.js`. However, this is
+modifiable in the debug console hud.
+
+The registered CVals can be viewed by entering `debug.cvalList()` into the debug
+console. Additional CVals can be registered via
+`debug.cvalAdd(substringToMatch)` and can be removed via
+`debug.cvalRemove(substringToMatch)`. The current registered list can be saved
+as a new set via `debug.cvalSave(set_key)`. At that point, the saved set can
+later be loaded as the active set via the command, `debug.cvalLoad(set_key)`.
+
+A full list of the commands available via the debug console is shown by entering
+`debug.help()`.
+
+### JavaScript
+
+CVals are exposed to JavaScript via the H5vccCVal object. This provides an
+interface for requesting all of the keys, which are returned as an array, and
+for retrieving a specific CVal value, which is returned as an optional string,
+via its CVal key. While the H5vccCVal object is usable in any build, only public
+CVals are queryable in Gold builds.
+
+Here are examples of its usage:
+
+```
+  > h5vcc.cVal.keys()
+  < H5vccCValKeyList[151]
+
+  > h5vcc.cVal.keys().length
+  < 151
+
+  > h5vcc.cVal.keys().item(8)
+  < "Count.MainWebModule.Layout.Box"
+
+  > h5vcc.cVal.getValue("Count.MainWebModule.Layout.Box")
+  < "463"
+```
+
+## Tracking Categories
+
+### Cobalt
+
+#### PublicCVals
+
+*   **Cobalt.Lifetime** - The total number of milliseconds that Cobalt has been
+    running.
+
+#### DebugCVals
+
+*   **Cobalt.Server.DevTools** - The IP address and port of the DevTools server,
+    if it is running.
+*   **Cobalt.Server.WebDriver** - The IP address and port of the Webdriver
+    server, if it is running.
+
+### Count
+
+#### PublicCVals
+
+*   **Count.DOM.EventListeners** - The total number of EventListeners in
+    existence globally. This includes ones that are pending garbage collection.
+*   **Count.DOM.Nodes** - The total number of Nodes in existence globally. This
+    includes ones that are pending garbage collection.
+*   **Count.MainWebModule.DOM.HtmlElement** - The total number of HtmlElements
+    in the MainWebModule. This includes elements that are not in the document
+    and ones that are pending garbage collection.
+*   **Count.MainWebModule.DOM.HtmlElement.Document** - The total number of
+    HtmlElements that are in the MainWebModule’s document.
+*   **Count.MainWebModule.DOM.HtmlScriptElement.Execute** - The total number of
+    HtmlScriptElement executions that have run since Cobalt started.
+*   **Count.MainWebModule.Layout.Box** - The total number of Boxes that are in
+    the MainWebModule’s current layout.
+
+#### DebugCVals
+
+*   **Count.DOM.ActiveJavaScriptEvents** - The number of JavaScript events that
+    are currently running.
+*   **Count.DOM.Attrs** - The total number of Attrs in existence globally. This
+    includes ones that are pending garbage collection.
+*   **Count.DOM.StringMaps** - The total number of StringMaps in existence
+    globally. This includes ones that are pending garbage collection.
+*   **Count.DOM.TokenLists** - The total number of TokenLists in existence
+    globally. This includes ones that are pending garbage collection.
+*   **Count.DOM.HtmlCollections** - The total number of HtmlCollections in
+    existence globally. This includes ones that are pending garbage collection.
+*   **Count.DOM.NodeLists** - The total number of NodeLists in existence
+    globally. This includes ones that are pending garbage collection.
+*   **Count.DOM.NodeMaps** - The total number of NodeMaps in existence globally.
+    This includes ones that are pending garbage collection.
+*   **Count.MainWebModule.[RESOURCE_CACHE_TYPE].PendingCallbacks** - The number
+    of loading completed resources that have pending callbacks.
+*   **Count.MainWebModule.[RESOURCE_CACHE_TYPE].Resource.Requested** - The total
+    number of resources that have ever been requested.
+*   **Count.MainWebModule.[RESOURCE_CACHE_TYPE].Resource.Loaded** - The total
+    number of resources that have ever been successfully loaded.
+*   **Count.MainWebModule.[RESOURCE_CACHE_TYPE].Resource.Loading** - The number
+    of resources that are currently loading.
+*   **Count.Renderer.Rasterize.NewRenderTree** - The total number of new render
+    trees that have been rasterized.
+*   **Count.VersionCompatibilityViolation** - The total number of Cobalt version
+    compatibility violations encountered.
+*   **Count.XHR** - The total number of xhr::XMLHttpRequest in existence
+    globally.
+
+### Event
+
+The Event category currently consists of counts and durations (in microseconds)
+for input events, which are tracked from when the event is first injected into
+the window, until a render tree is generated from it. Each event type is tracked
+separately; current event types are: *KeyDown*, *KeyUp*, *PointerDown*,
+*PointerUp*.
+
+#### PublicCVals
+
+*   **Event.MainWebModule.[EVENT_TYPE].ProducedRenderTree** - Whether or not the
+    event produced a render tree.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].DOM.HtmlElement** - The total
+    number of HTML elements after the event produces its first render tree.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].DOM.HtmlElement.Created** - The
+    number of HTML elements created during the event.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].DOM.HtmlElement.Destroyed** - The
+    number of HTML elements destroyed during the event. NOTE: This number will
+    only be non-zero if GC occurs during the event.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].DOM.HtmlElement.Document** - The
+    number of HTML elements in the document after the event produces its first
+    render tree.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].DOM.HtmlElement.Document.Added** -
+    The number of HTML elements added to the document during the event.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].DOM.HtmlElement.Document.Removed** -
+    The number of HTML elements removed from the document during the event.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].DOM.HtmlElement.UpdateMatchingRules** -
+    The number of HTML elements that had their matching rules updated during the
+    event.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].DOM.HtmlElement.UpdateComputedStyle** -
+    The number of HTML elements that had their computed style updated during the
+    event.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].DOM.HtmlElement.GenerateHtmlElementComputedStyle** -
+    The number of HTML elements that had their computed style fully generated
+    during the event.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].DOM.HtmlElement.GeneratePseudoElementComputedStyle** -
+    The number of pseudo elements that had their computed style fully generated
+    during the event.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].Layout.Box** - The total number of
+    Layout boxes after the event produces its first render tree.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].Layout.Box.Created** - The number
+    of Layout boxes that were created during the event.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].Layout.Box.Destroyed** - The number
+    of Layout boxes that were destroyed during the event.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].Layout.Box.UpdateSize** - The
+    number of times UpdateSize() is called during the event.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].Layout.Box.RenderAndAnimate** - The
+    number of times RenderAndAnimate() is called during the event.
+*   **Event.Count.MainWebModule.[EVENT_TYPE].Layout.Box.UpdateCrossReferences** -
+    The number of times UpdateCrossReferences() is called during the event.
+*   **Event.Duration.MainWebModule.[EVENT_TYPE]** - The total duration of the
+    event from the keypress being injected until the first render tree is
+    rendered. If the event does not trigger a re-layout, then it only includes
+    the event injection time.
+*   **Event.Duration.MainWebModule.[EVENT_TYPE].DOM.InjectEvent** - The time
+    taken to inject the event into the window object. This mainly consists of
+    JavaScript time.
+*   **Event.Duration.MainWebModule.[EVENT_TYPE].DOM.RunAnimationFrameCallbacks** -
+    The time taken to run animation frame callbacks during the event. This
+    mainly consists of JavaScript time.
+*   **Event.Duration.MainWebModule.[EVENT_TYPE].DOM.UpdateComputedStyle** - The
+    time taken to update the computed styles of all HTML elements (which also
+    includes updating their matching rules). This will track closely with the
+    event DOM counts.
+*   **Event.Duration.MainWebModule.[EVENT_TYPE].Layout.BoxTree** - The time
+    taken to fully lay out the box tree.
+*   **Event.Duration.MainWebModule.[EVENT_TYPE].Layout.BoxTree.BoxGeneration** -
+    The time taken to generate the boxes within the box tree. This will track
+    closely with the number of boxes created during the event. This is included
+    within the BoxTree time.
+*   **Event.Duration.MainWebModule.[EVENT_TYPE].Layout.BoxTree.UpdateUsedSizes** -
+    The time taken to update the sizes of the boxes within the box tree, which
+    is when all of the boxes are laid out. This is included within the BoxTree
+    time.
+*   **Event.Duration.MainWebModule.[EVENT_TYPE].Layout.RenderAndAnimate** - The
+    time taken to generate the render tree produced by the event.
+*   **Event.Duration.MainWebModule.[EVENT_TYPE].Renderer.Rasterize** - The time
+    taken to rasterize the event’s render tree after it is submitted to the
+    renderer.
+*   **Event.Duration.MainWebModule.DOM.VideoStartDelay** - The delay from the
+    start of the event until the start of a video. NOTE1: This is not set until
+    after the event completes, so it is not included in the value dictionary.
+    NOTE2: This is not tracked by event type, so each new event will reset this
+    value.
+*   **Event.Time.MainWebModule.[EVENT_TYPE].Start** - Time when the event
+    started.
+
+#### DebugCVals
+
+*   **Event.MainWebModule.IsProcessing** - Whether or not an event is currently
+    being processed.
+*   **Event.MainWebModule.[EVENT_TYPE].ValueDictionary** - All counts and
+    durations for this event as a JSON string. This is used by the
+    tv_testcase_event_recorder to minimize the number of CVal requests it makes
+    to retrieve all of the data for an event.
+
+### MainWebModule
+
+#### DebugCVals
+
+*   **MainWebModule.IsRenderTreeRasterizationPending** - Whether or not a render
+    tree has been produced but not yet rasterized.
+*   **MainWebModule.Layout.IsRenderTreePending** - Whether or not the layout is
+    scheduled to produce a new render tree.
+
+### Media
+
+#### PublicCVals
+
+We have various SbPlayer metrics available which need to be enabled first using:
+`h5vcc.settings.set("Media.EnableMetrics", 1)`
+
+*   **Media.SbPlayerCreateTime.Minimum**
+*   **Media.SbPlayerCreateTime.Median**
+*   **Media.SbPlayerCreateTime.Maximum**
+*   **Media.SbPlayerDestructionTime.Minimum**
+*   **Media.SbPlayerDestructionTime.Median**
+*   **Media.SbPlayerDestructionTime.Maximum**
+*   **Media.SbPlayerWriteSamplesTime.Minimum**
+*   **Media.SbPlayerWriteSamplesTime.Median**
+*   **Media.SbPlayerWriteSamplesTime.Maximum**
+
+#### DebugCVals
+
+Media Pipeline exposes many State values, however we support multiple pipelines
+at once, so we have to query for the current pipeline number to access them.
+This can be done with `h5vcc.cVal.keys().filter(key =>
+key.startsWith("Media.Pipeline.") && key.endsWith("MaxVideoCapabilities") &&
+h5vcc.cVal.getValue(key).length === 0)` (If a Pipeline has MaxVideoCapabilities,
+it implies that the Pipeline is a 10x player, i.e. a secondary small player, so
+if MaxVideoCapabilities.length is 0, then it must be the primary pipeline.)
+This will give back an answer like `Media.Pipeline.3.MaxVideoCapabilities` so
+the main pipeline in this example is `3`. With the current pipeline number you
+can then access (Switching out pipeline for the one returned from previous
+query):
+
+*   **Media.Pipeline.3.Started**
+*   **Media.Pipeline.3.Suspended**
+*   **Media.Pipeline.3.Stopped**
+*   **Media.Pipeline.3.Ended**
+*   **Media.Pipeline.3.PlayerState**
+*   **Media.Pipeline.3.Volume**
+*   **Media.Pipeline.3.PlaybackRate**
+*   **Media.Pipeline.3.Duration**
+*   **Media.Pipeline.3.LastMediaTime**
+*   **Media.Pipeline.3.MaxVideoCapabilities**
+*   **Media.Pipeline.3.SeekTime**
+*   **Media.Pipeline.3.FirstWrittenAudioTimestamp**
+*   **Media.Pipeline.3.FirstWrittenVideoTimestamp**
+*   **Media.Pipeline.3.LastWrittenAudioTimestamp**
+*   **Media.Pipeline.3.LastWrittenVideoTimestamp**
+*   **Media.Pipeline.3.VideoWidth**
+*   **Media.Pipeline.3.VideoHeight**
+*   **Media.Pipeline.3.IsAudioEosWritten**
+*   **Media.Pipeline.3.IsVideoEosWritten**
+*   **Media.Pipeline.3.PipelineStatus**
+*   **Media.Pipeline.3.CurrentCodec**
+*   **Media.Pipeline.3.ErrorMessage**
+
+### Memory
+
+#### PublicCVals
+
+*   **Memory.CPU.Free** - The total CPU memory (in bytes) potentially available
+    to this application minus the amount being used by the application.
+*   **Memory.CPU.Used** - The total physical CPU memory (in bytes) used by this
+    application.
+*   **Memory.GPU.Free** - The total GPU memory (in bytes) potentially available
+    to this application minus the amount being used by the application. NOTE: On
+    many platforms, GPU memory information is unavailable.
+*   **Memory.GPU.Used** - The total physical CPU memory (in bytes) used by this
+    application. NOTE: On many platforms, GPU memory information is unavailable.
+*   **Memory.JS** - The total memory being used by JavaScript.
+*   **Memory.Font.LocalTypefaceCache.Capacity** - The capacity in bytes of the
+    font cache for use with local typefaces. This is a hard cap that can never
+    be exceeded.
+*   **Memory.Font.LocalTypefaceCache.Size** - The current size in bytes of the
+    font cache for use with local typefaces.
+*   **Memory.MainWebModule.DOM.HtmlScriptElement.Execute** - The total size in
+    bytes of all scripts executed by HtmlScriptElements since Cobalt started.
+*   **Memory.MainWebModule.[RESOURCE_CACHE_TYPE].Capacity** - The capacity in
+    bytes of the resource cache specified by RESOURCE_CACHE_TYPE. When the
+    resource cache exceeds the capacity, unused resources being purged from it.
+*   **Memory.MainWebModule.[RESOURCE_CACHE_TYPE].Resource.Loaded** - The total
+    size in bytes of all resources ever loaded by the resource cache specified
+    by RESOURCE_CACHE_TYPE.
+*   **Memory.MainWebModule.[RESOURCE_CACHE_TYPE].Size** - The total number of
+    bytes currently used by the resource cache specified by RESOURCE_CACHE_TYPE.
+
+#### DebugCVals
+
+*   **Memory.XHR** - The total memory allocated to xhr::XMLHttpRequest objects.
+*   **Memory.CachedSoftwareRasterizer.CacheUsage** - Total memory occupied by
+    cached software-rasterized surfaces.
+*   **Memory.CachedSoftwareRasterizer.FrameCacheUsage** - Total memory occupied
+    by cache software-rasterizer surfaces that were referenced this frame.
+
+### Renderer
+
+#### PublicCVals
+
+*   **Renderer.HasActiveAnimations** - Whether or not the current render tree
+    has animations.
+*   **Renderer.Rasterize.DurationInterval.\*** - Tracks the duration of
+    intervals between rasterization calls during all animations and updates its
+    stats with a new set of entries every 60 calls. Given that it only updates
+    every 60 samples, it typically includes multiple animations. This provides
+    an accurate view of the framerate over those samples and is the value used
+    with the FPS overlay.
+    *   **Renderer.Rasterize.DurationInterval.Cnt** - The number of intervals
+        included in the stats. Should always be 60.
+    *   **Renderer.Rasterize.DurationInterval.Avg** - The average duration of
+        the intervals between the animation rasterizations included in the set.
+    *   **Renderer.Rasterize.DurationInterval.Min** - The minimum duration of
+        the intervals between the animation rasterizations included in the set.
+    *   **Renderer.Rasterize.DurationInterval.Max** - The maximum duration of
+        the intervals between the animation rasterizations included in the set.
+    *   **Renderer.Rasterize.DurationInterval.Pct.25th** - The 25th percentile
+        duration of the intervals between the animation rasterizations included
+        in the set.
+    *   **Renderer.Rasterize.DurationInterval.Pct.50th** - The 50th percentile
+        duration of the intervals between the animation rasterizations included
+        in the set.
+    *   **Renderer.Rasterize.DurationInterval.Pct.75th** - The 75th percentile
+        duration of the intervals between the animation rasterizations included
+        in the set.
+    *   **Renderer.Rasterize.DurationInterval.Pct.95th** - The 95th percentile
+        duration of the intervals between the animation rasterizations included
+        in the set.
+    *   **Renderer.Rasterize.DurationInterval.Std** - The standard deviation of
+        the intervals between the animation rasterizations included in the set.
+*   **Renderer.Rasterize.AnimationsInterval.\*** - Tracks the duration of
+    intervals between rasterization calls during a single animation and updates
+    its stats when the animation ends. The stats include all of the animation’s
+    rasterization intervals. This provides an accurate view of the framerate
+    during the animation.
+    *   **Renderer.Rasterize.AnimationsInterval.Cnt** - The number of intervals
+        included in the stats. Accounts for all rasterizations that occurred
+        during the animation.
+    *   **Renderer.Rasterize.AnimationsInterval.Avg** - The average duration of
+        the intervals between the animation rasterizations included in the set.
+    *   **Renderer.Rasterize.AnimationsInterval.Min** - The minimum duration of
+        the intervals between the animation rasterizations included in the set.
+    *   **Renderer.Rasterize.AnimationsInterval.Max** - The maximum duration of
+        the intervals between the animation rasterizations included in the set.
+    *   **Renderer.Rasterize.AnimationsInterval.Pct.25th** - The 25th percentile
+        duration of the intervals between the animation rasterizations included
+        in the set.
+    *   **Renderer.Rasterize.AnimationsInterval.Pct.50th** - The 50th percentile
+        duration of the intervals between the animation rasterizations included
+        in the set.
+    *   **Renderer.Rasterize.AnimationsInterval.Pct.75th** - The 75th percentile
+        duration of the intervals between the animation rasterizations included
+        in the set.
+    *   **Renderer.Rasterize.AnimationsInterval.Pct.95th** - The 95th percentile
+        duration of the intervals between the animation rasterizations included
+        in the set.
+    *   **Renderer.Rasterize.AnimationsInterval.Std** - The standard deviation
+        of the intervals between the animation rasterizations included in the
+        set.
+
+#### DebugCVals
+
+*   **Renderer.SubmissionQueueSize** - The current size of the renderer
+    submission queue. Each item in the queue contains a render tree and
+    associated animations.
+*   **Renderer.ToSubmissionTime** - The current difference in milliseconds
+    between the layout's clock and the renderer's clock.
+*   **Renderer.Rasterize.Animations.\*** - Tracks the duration of each
+    rasterization call during a single animation and updates its stats when the
+    animation ends. The stats are drawn from all of the animation’s
+    rasterizations. Given that this only tracks the time spent in the
+    rasterizer, it does not provide as accurate a picture of the framerate as
+    DurationInterval and AnimationsInterval.
+    *   **Renderer.Rasterize.Animations.Cnt** - The number of rasterization
+        durations included in the stats. Accounts for all rasterizations that
+        occurred during the animation.
+    *   **Renderer.Rasterize.Animations.Avg** - The average duration of the
+        rasterizations included in the set.
+    *   **Renderer.Rasterize.Animations.Min** - The minimum duration of the
+        rasterizations included in the set.
+    *   **Renderer.Rasterize.Animations.Max** - The maximum duration of the
+        rasterizations included in the set.
+    *   **Renderer.Rasterize.Animations.Pct.25th** - The 25th percentile
+        duration of the rasterizations included in the set.
+    *   **Renderer.Rasterize.Animations.Pct.50th** - The 50th percentile
+        duration of the rasterizations included in the set.
+    *   **Renderer.Rasterize.Animations.Pct.75th** - The 75th percentile
+        duration of the rasterizations included in the set.
+    *   **Renderer.Rasterize.Animations.Pct.95th** - The 95th percentile
+        duration of the rasterizations included in the set.
+    *   **Renderer.Rasterize.Animations.Std** - The standard deviation of the
+        rasterization durations included in the set.
+
+### Time
+
+#### PublicCVals
+
+*   **Time.Cobalt.Start** - Time when Cobalt was launched.
+*   **Time.Browser.Navigate** - Time when the BrowserModule’s last Navigate
+    occurred.
+*   **Time.Browser.OnLoadEvent** - Time when the BrowserModule’s last
+    OnLoadEvent occurred.
+*   **Time.MainWebModule.DOM.HtmlScriptElement.Execute** - Time when an
+    HtmlScriptElement was last executed.
+*   **Time.Renderer.Rasterize.Animations.Start** - Time when the Renderer last
+    started playing animations.
+*   **Time.Renderer.Rasterize.Animations.End** - Time when the Renderer last
+    stopped playing animations.
+*   **Time.Renderer.Rasterize.NewRenderTree** - Time when the most recent render
+    tree was first rasterized.
diff --git a/cobalt/doc/device_authentication.md b/cobalt/doc/device_authentication.md
index 297f82b..6ef84a5 100644
--- a/cobalt/doc/device_authentication.md
+++ b/cobalt/doc/device_authentication.md
@@ -35,7 +35,7 @@
 since it enables implementations where the key exists only in secure hardware
 and never enters the system's main memory.  A reference implementation, which
 depends on BoringSSL exists at
-[starboard/linux/x64x11/internal/system_sign_with_certification_secret_key.cc](../../starboard/linux/x64x11/internal/system_sign_with_certification_secret_key.cc).
+[internal/starboard/linux/x64x11/internal/system_sign_with_certification_secret_key.cc](../../internal/starboard/linux/x64x11/internal/system_sign_with_certification_secret_key.cc).
 
 ### Cobalt signing
 
diff --git a/cobalt/doc/docker_build.md b/cobalt/doc/docker_build.md
index 4ba59a9..a65e17d 100644
--- a/cobalt/doc/docker_build.md
+++ b/cobalt/doc/docker_build.md
@@ -54,7 +54,19 @@
 
 ## Pre-built images
 
-Note: Pre-built images from a public container registry are not yet available.
+Pre-built images are available at https://github.com/orgs/youtube/packages?repo_name=cobalt
+
+For example, a container for building Android platform from main branch can be pulled as follows:
+
+```
+docker pull ghcr.io/youtube/cobalt/cobalt-build-android:main
+```
+
+Similarly, from LTS branch for Evergreen platform:
+
+```
+docker pull ghcr.io/youtube/cobalt/cobalt-build-evergreen:23.lts
+```
 
 ## Troubleshooting
 
@@ -63,4 +75,4 @@
 
   `docker-compose run linux-x64x11 /bin/bash`
 
-and try to build cobalt [with the usual `gyp / ninja` flow](../../README.md#building-and-running-the-code).
+and try to build cobalt [with the usual `gn / ninja` flow](../../README.md#building-and-running-the-code).
diff --git a/cobalt/doc/voice_search.md b/cobalt/doc/voice_search.md
index d4bd6ec..6a72bee 100644
--- a/cobalt/doc/voice_search.md
+++ b/cobalt/doc/voice_search.md
@@ -1,15 +1,10 @@
 # Enabling voice search in Cobalt
 
-Cobalt enables voice search through either:
+Cobalt enables voice search through a subset of the
+[MediaRecorder Web API](https://www.w3.org/TR/mediastream-recording/#mediarecorder-api)
 
-1. A subset of the [MediaRecorder Web API](https://www.w3.org/TR/mediastream-recording/#mediarecorder-api)
-2. A subset of the [Speech Recognition Web API](https://w3c.github.io/speech-api/#speechreco-section)
-
-Only one or the other can be used, and we recommend that the MediaRecorder API
-is followed, as the Speech Recognition API is deprecated as of Starboard 13.
-
-In both approaches, in order to check whether to enable voice control or not,
-web apps will call the [MediaDevices.enumerateDevices()](https://www.w3.org/TR/mediacapture-streams/#dom-mediadevices-enumeratedevices%28%29)
+In order to check whether to enable voice control or not, web apps will call the
+[MediaDevices.enumerateDevices()](https://www.w3.org/TR/mediacapture-streams/#dom-mediadevices-enumeratedevices%28%29)
 Web API function within which Cobalt will in turn call a subset of the
 [Starboard SbMicrophone API](../../starboard/microphone.h).
 
@@ -19,48 +14,7 @@
 ## MediaRecorder API
 
 To enable the MediaRecorder API in Cobalt, the complete
-[SbMicrophone API](../../starboard/microphone.h) must be implemented, and
-`SbSpeechRecognizerIsSupported()` must return `false`.
-
-## Speech Recognition API - Deprecated
-
-**The Speech Recognition API is deprecated as of Starboard 13.**
-
-In order to provide support for using this API, platforms must implement the
-[Starboard SbSpeechRecognizer API](../../starboard/speech_recognizer.h) as well
-as a subset of the [SbMicrophone API](../../starboard/microphone.h).
-
-### Specific instructions to enable voice search
-
-1. Implement `SbSpeechRecognizerIsSupported()` to return `true`, and implement
-   the [SbSpeechRecognizer API](../../starboard/speech_recognizer.h).
-2. Implement the following subset of the
-   [SbMicrophone API](../../starboard/microphone.h):
-    - `SbMicrophoneGetAvailable()`
-    - `SbMicrophoneCreate()`
-    - `SbMicrophoneDestroy()`
-
-   In particular, SbMicrophoneCreate() must return a valid microphone.  It is
-   okay to stub out the other functions, e.g. have `SbMicrophoneOpen()`
-   return `false`.
-3. The YouTube app will display the mic icon on the search page when it detects
-   valid microphone input devices using `MediaDevices.enumerateDevices()`.
-4. With `SbSpeechRecognizerIsSupported()` implemented to return `true`, Cobalt
-   will use the platform's
-   [Starboard SbSpeechRecognizer API](../../starboard/speech_recognizer.h)
-   implementation, and it will not actually read directly from the microphone
-   via the [Starboard SbMicrophone API](../../starboard/microphone.h).
-
-### Differences from versions of Cobalt <= 11
-
-In previous versions of Cobalt, there was no way to dynamically disable
-speech support besides modifying common Cobalt code to dynamically stub out the
-Speech Recognition API when the platform does not support microphone input.
-This is no longer necessary, web apps should now rely on
-`MediaDevices.enumerateDevices()` to determine whether voice support is enabled
-or not.
-
-### Speech Recognition API is deprecated in Starboard 13 ###
+[SbMicrophone API](../../starboard/microphone.h) must be implemented.
 
 Web applications are expected to use the MediaRecorder API. This in turn relies
 on the SbMicrophone API as detailed above.
diff --git a/cobalt/dom/document.cc b/cobalt/dom/document.cc
index c35472c..b3d1ca0 100644
--- a/cobalt/dom/document.cc
+++ b/cobalt/dom/document.cc
@@ -23,6 +23,7 @@
 #include "base/compiler_specific.h"
 #include "base/message_loop/message_loop.h"
 #include "base/strings/string_util.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/token.h"
 #include "cobalt/base/tokens.h"
@@ -603,7 +604,7 @@
     DCHECK(base::MessageLoop::current());
     should_dispatch_load_event_ = false;
 
-    base::MessageLoop::current()->task_runner()->PostTask(
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::Bind(&Document::DispatchOnLoadEvent,
                               base::AsWeakPtr<Document>(this)));
 
diff --git a/cobalt/dom/event_queue.cc b/cobalt/dom/event_queue.cc
index 1bd5282..f12e2aa 100644
--- a/cobalt/dom/event_queue.cc
+++ b/cobalt/dom/event_queue.cc
@@ -16,13 +16,14 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
+#include "base/threading/thread_task_runner_handle.h"
 
 namespace cobalt {
 namespace dom {
 
 EventQueue::EventQueue(web::EventTarget* event_target)
     : event_target_(event_target),
-      message_loop_(base::MessageLoop::current()->task_runner()) {
+      message_loop_(base::ThreadTaskRunnerHandle::Get()) {
   DCHECK(event_target_);
   DCHECK(message_loop_);
 }
diff --git a/cobalt/dom/html_element.cc b/cobalt/dom/html_element.cc
index 36ad437..01ea1eb 100644
--- a/cobalt/dom/html_element.cc
+++ b/cobalt/dom/html_element.cc
@@ -22,6 +22,7 @@
 #include "base/lazy_instance.h"
 #include "base/message_loop/message_loop_task_runner.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/console_log.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/cssom/absolute_url_value.h"
@@ -2220,13 +2221,13 @@
     ui_nav_item_ = new ui_navigation::NavItem(
         *ui_nav_item_type,
         base::Bind(
-            &UiNavCallbackHelper, base::MessageLoop::current()->task_runner(),
+            &UiNavCallbackHelper, base::ThreadTaskRunnerHandle::Get(),
             base::Bind(&HTMLElement::OnUiNavBlur, base::AsWeakPtr(this))),
         base::Bind(
-            &UiNavCallbackHelper, base::MessageLoop::current()->task_runner(),
+            &UiNavCallbackHelper, base::ThreadTaskRunnerHandle::Get(),
             base::Bind(&HTMLElement::OnUiNavFocus, base::AsWeakPtr(this))),
         base::Bind(
-            &UiNavCallbackHelper, base::MessageLoop::current()->task_runner(),
+            &UiNavCallbackHelper, base::ThreadTaskRunnerHandle::Get(),
             base::Bind(&HTMLElement::OnUiNavScroll, base::AsWeakPtr(this))));
     ui_nav_item_->SetDir(ui_nav_item_dir);
     if (ui_nav_focus_duration_) {
diff --git a/cobalt/dom/html_link_element.cc b/cobalt/dom/html_link_element.cc
index d01d0e0..efd1019 100644
--- a/cobalt/dom/html_link_element.cc
+++ b/cobalt/dom/html_link_element.cc
@@ -21,6 +21,7 @@
 
 #include "base/bind.h"
 #include "base/strings/string_tokenizer.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/cssom/css_parser.h"
 #include "cobalt/cssom/css_style_sheet.h"
@@ -268,7 +269,7 @@
   // GetLoadTimingInfo and create resource timing before loader released.
   GetLoadTimingInfoAndCreateResourceTiming();
 
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::Bind(&HTMLLinkElement::ReleaseLoader, this));
 
   if (!error) return;
diff --git a/cobalt/dom/html_media_element.cc b/cobalt/dom/html_media_element.cc
index aaa4675..c3376ea 100644
--- a/cobalt/dom/html_media_element.cc
+++ b/cobalt/dom/html_media_element.cc
@@ -19,6 +19,7 @@
 #include <limits>
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "base/bind.h"
 #include "base/compiler_specific.h"
@@ -26,6 +27,8 @@
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/instance_counter.h"
 #include "cobalt/base/tokens.h"
@@ -640,6 +643,24 @@
   event_queue_.Enqueue(event);
 }
 
+std::string HTMLMediaElement::h5vcc_audio_connectors(
+    script::ExceptionState* exception_state) const {
+#if SB_API_VERSION >= 15
+  if (!player_) {
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
+    return std::string();
+  }
+
+  std::vector<std::string> configs = player_->GetAudioConnectors();
+  return base::JoinString(configs, ";");
+#else   // SB_API_VERSION >= 15
+  web::DOMException::Raise(web::DOMException::kNotSupportedErr,
+                           exception_state);
+  return std::string();
+#endif  // SB_API_VERSION >= 15
+}
+
 void HTMLMediaElement::CreateMediaPlayer() {
   TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::CreateMediaPlayer()");
   LOG(INFO) << "Create media player.";
@@ -890,7 +911,7 @@
     request_mode_ = GetRequestMode(GetAttribute("crossOrigin"));
     DCHECK(node_document()->location());
     std::unique_ptr<DataSource> data_source(new media::URLFetcherDataSource(
-        base::MessageLoop::current()->task_runner(), url, csp_callback,
+        base::ThreadTaskRunnerHandle::Get(), url, csp_callback,
         html_element_context()->fetcher_factory()->network_module(),
         request_mode_, node_document()->location()->GetOriginAsObject()));
     player_->LoadProgressive(url, std::move(data_source));
diff --git a/cobalt/dom/html_media_element.h b/cobalt/dom/html_media_element.h
index 4276fd8..612e21c 100644
--- a/cobalt/dom/html_media_element.h
+++ b/cobalt/dom/html_media_element.h
@@ -146,6 +146,12 @@
   // function won't modify the target of the |event| passed in.
   void ScheduleEvent(const scoped_refptr<web::Event>& event);
 
+  // Returns semicolon separated names of audio connectors, like
+  // "hdmi;bluetooth".
+  // TODO(b/267678497): The current interface is tentative, to be refined.
+  std::string h5vcc_audio_connectors(
+      script::ExceptionState* exception_state) const;
+
   // Set max video capabilities.
   void SetMaxVideoCapabilities(const std::string& max_video_capabilities,
                                script::ExceptionState* exception_state);
diff --git a/cobalt/dom/html_media_element.idl b/cobalt/dom/html_media_element.idl
index 4a1a509..401060a 100644
--- a/cobalt/dom/html_media_element.idl
+++ b/cobalt/dom/html_media_element.idl
@@ -61,4 +61,10 @@
   attribute boolean controls;
   [RaisesException] attribute double volume;
   attribute boolean muted;
+
+  // non standard, semicolon separated names of audio connectors, like
+  // "hdmi;bluetooth".  It raises `NotSupportedError` on apps doesn't support
+  // this feature, or `InvalidStateError` if there isn't an active playback.
+  // TODO(b/267678497): The current interface is tentative, to be refined.
+  [RaisesException] readonly attribute DOMString h5vccAudioConnectors;
 };
diff --git a/cobalt/dom/html_script_element.cc b/cobalt/dom/html_script_element.cc
index 1a1f7ba..fa4153e 100644
--- a/cobalt/dom/html_script_element.cc
+++ b/cobalt/dom/html_script_element.cc
@@ -21,6 +21,7 @@
 #include "base/bind.h"
 #include "base/compiler_specific.h"
 #include "base/strings/string_util.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/console_log.h"
 #include "cobalt/base/tokens.h"
@@ -607,7 +608,7 @@
   }
 
   // Post a task to release the loader.
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::Bind(&HTMLScriptElement::ReleaseLoader, this));
 }
 
diff --git a/cobalt/dom/keyboard_event.cc b/cobalt/dom/keyboard_event.cc
index ca4fd9c..892dad9 100644
--- a/cobalt/dom/keyboard_event.cc
+++ b/cobalt/dom/keyboard_event.cc
@@ -324,11 +324,11 @@
     case keycode::kBrowserHome:
       return "BrowserHome";
     case keycode::kVolumeMute:
-      return "VolumeMute";
+      return "AudioVolumeMute";
     case keycode::kVolumeDown:
-      return "VolumeMute";
+      return "AudioVolumeDown";
     case keycode::kVolumeUp:
-      return "VolumeMute";
+      return "AudioVolumeUp";
     case keycode::kMediaNextTrack:
       return "MediaNextTrack";
     case keycode::kMediaPrevTrack:
diff --git a/cobalt/dom/lottie_player.cc b/cobalt/dom/lottie_player.cc
index 4adb4a8..10e2397 100644
--- a/cobalt/dom/lottie_player.cc
+++ b/cobalt/dom/lottie_player.cc
@@ -19,6 +19,7 @@
 #include <utility>
 
 #include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/dom/document.h"
@@ -41,7 +42,7 @@
     : HTMLElement(document, base::Token(kTagName)),
       autoplaying_(true),
       ALLOW_THIS_IN_INITIALIZER_LIST(event_queue_(this)),
-      callback_task_runner_(base::MessageLoop::current()->task_runner()) {
+      callback_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
   SetAnimationEventCallbacks();
 }
 
diff --git a/cobalt/dom/media_source.cc b/cobalt/dom/media_source.cc
index 8871b83..31ffc95 100644
--- a/cobalt/dom/media_source.cc
+++ b/cobalt/dom/media_source.cc
@@ -53,6 +53,7 @@
 #include "base/guid.h"
 #include "base/logging.h"
 #include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/tokens.h"
@@ -423,7 +424,7 @@
     offload_algorithm_runner_.reset(
         new OffloadAlgorithmRunner<SourceBufferAlgorithm>(
             algorithm_process_thread_->message_loop()->task_runner(),
-            base::MessageLoop::current()->task_runner()));
+            base::ThreadTaskRunnerHandle::Get()));
   } else {
     LOG(INFO) << "Algorithm offloading disabled.";
   }
diff --git a/cobalt/dom/media_source.h b/cobalt/dom/media_source.h
index 0daa77a..9af3f55 100644
--- a/cobalt/dom/media_source.h
+++ b/cobalt/dom/media_source.h
@@ -51,6 +51,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/token.h"
 #include "cobalt/dom/audio_track.h"
 #include "cobalt/dom/event_queue.h"
diff --git a/cobalt/dom/mutation_observer_task_manager.cc b/cobalt/dom/mutation_observer_task_manager.cc
index 5453873..9c5196a 100644
--- a/cobalt/dom/mutation_observer_task_manager.cc
+++ b/cobalt/dom/mutation_observer_task_manager.cc
@@ -16,6 +16,7 @@
 
 #include "base/callback.h"
 #include "base/message_loop/message_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/dom/mutation_observer.h"
 
@@ -55,7 +56,7 @@
   // 2. Set mutation observer compound microtask queued flag.
   task_posted_ = true;
   // 3. Queue a compound microtask to notify mutation observers.
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::Bind(&MutationObserverTaskManager::NotifyMutationObservers,
                  base::Unretained(this)));
diff --git a/cobalt/dom/screenshot_manager.cc b/cobalt/dom/screenshot_manager.cc
index 0483259..b8f3eeb 100644
--- a/cobalt/dom/screenshot_manager.cc
+++ b/cobalt/dom/screenshot_manager.cc
@@ -17,6 +17,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "cobalt/dom/screenshot.h"
 #include "cobalt/render_tree/node.h"
@@ -45,10 +46,9 @@
 
   // We want to ScreenshotManager::FillScreenshot, on this thread.
   base::Callback<void(std::unique_ptr<uint8[]>, const math::Size&)>
-      fill_screenshot = base::Bind(&ScreenshotManager::FillScreenshot,
-                                   base::Unretained(this), next_ticket_id_,
-                                   base::MessageLoop::current()->task_runner(),
-                                   desired_format);
+      fill_screenshot = base::Bind(
+          &ScreenshotManager::FillScreenshot, base::Unretained(this),
+          next_ticket_id_, base::ThreadTaskRunnerHandle::Get(), desired_format);
   bool was_emplaced =
       ticket_to_screenshot_promise_map_
           .emplace(next_ticket_id_, std::move(promise_reference))
diff --git a/cobalt/dom/serialized_algorithm_runner.h b/cobalt/dom/serialized_algorithm_runner.h
index db625ad..0d8122a 100644
--- a/cobalt/dom/serialized_algorithm_runner.h
+++ b/cobalt/dom/serialized_algorithm_runner.h
@@ -15,6 +15,7 @@
 #ifndef COBALT_DOM_SERIALIZED_ALGORITHM_RUNNER_H_
 #define COBALT_DOM_SERIALIZED_ALGORITHM_RUNNER_H_
 
+#include <algorithm>
 #include <memory>
 #include <utility>
 
@@ -24,8 +25,10 @@
 #include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop.h"
 #include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "starboard/common/mutex.h"
+#include "starboard/time.h"
 
 namespace cobalt {
 namespace dom {
@@ -84,11 +87,31 @@
                              const starboard::Mutex& mutex)
           : synchronization_required_(synchronization_required), mutex_(mutex) {
         if (synchronization_required_) {
-          mutex_.Acquire();
+          // Crash if we are trying to re-acquire again on the same thread.
+          CHECK_NE(acquired_thread_id_, SbThreadGetId());
+
+          SbTime start = SbTimeGetMonotonicNow();
+          SbTime wait_interval = kSbTimeMillisecond;
+          constexpr SbTime kMaxWaitInterval = kSbTimeMillisecond * 16;
+
+          for (;;) {
+            if (mutex_.AcquireTry()) {
+              break;
+            }
+            SbThreadSleep(wait_interval);
+            // Double the wait interval upon every failure, but cap it at
+            // kMaxWaitInterval.
+            wait_interval = std::min(wait_interval * 2, kMaxWaitInterval);
+            // Crash if we've been waiting for too long.
+            CHECK_LT(SbTimeGetMonotonicNow() - start, kSbTimeSecond);
+          }
+          acquired_thread_id_ = SbThreadGetId();
         }
       }
       ~ScopedLockWhenRequired() {
         if (synchronization_required_) {
+          CHECK_EQ(acquired_thread_id_, SbThreadGetId());
+          acquired_thread_id_ = kSbThreadInvalidId;
           mutex_.Release();
         }
       }
@@ -96,6 +119,7 @@
      private:
       const bool synchronization_required_;
       const starboard::Mutex& mutex_;
+      SbThreadId acquired_thread_id_ = kSbThreadInvalidId;
     };
 
     Handle(bool synchronization_required,
@@ -270,7 +294,7 @@
     return;
   }
 
-  auto task_runner = base::MessageLoop::current()->task_runner();
+  auto task_runner = base::ThreadTaskRunnerHandle::Get();
   task_runner->PostTask(FROM_HERE,
                         base::BindOnce(&DefaultAlgorithmRunner::Process,
                                        base::Unretained(this), handle));
@@ -282,7 +306,7 @@
   DCHECK(handle);
   TRACE_EVENT0("cobalt::dom", "DefaultAlgorithmRunner::Process()");
 
-  auto task_runner = base::MessageLoop::current()->task_runner();
+  auto task_runner = base::ThreadTaskRunnerHandle::Get();
 
   bool finished = false;
   handle->Process(&finished);
diff --git a/cobalt/dom/source_buffer.cc b/cobalt/dom/source_buffer.cc
index 267677b..039303f 100644
--- a/cobalt/dom/source_buffer.cc
+++ b/cobalt/dom/source_buffer.cc
@@ -453,6 +453,17 @@
   track_defaults_ = track_defaults;
 }
 
+double SourceBuffer::write_head(script::ExceptionState* exception_state) const {
+  if (media_source_ == NULL) {
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
+    return 0.0;
+  }
+
+  DCHECK(chunk_demuxer_);
+  return chunk_demuxer_->GetWriteHead(id_).InSecondsF();
+}
+
 void SourceBuffer::OnRemovedFromMediaSource() {
   if (media_source_ == NULL) {
     return;
diff --git a/cobalt/dom/source_buffer.h b/cobalt/dom/source_buffer.h
index c611177..5869760 100644
--- a/cobalt/dom/source_buffer.h
+++ b/cobalt/dom/source_buffer.h
@@ -55,6 +55,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/optional.h"
 #include "base/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/timer/timer.h"
 #include "cobalt/base/token.h"
 #include "cobalt/dom/audio_track_list.h"
@@ -134,6 +135,9 @@
 
   // Custom, not in any spec.
   //
+  // Return the highest presentation timestamp written to SbPlayer.
+  double write_head(script::ExceptionState* exception_state) const;
+
   void OnRemovedFromMediaSource();
   double GetHighestPresentationTimestamp() const;
 
@@ -160,7 +164,7 @@
 
    private:
     scoped_refptr<base::SingleThreadTaskRunner> task_runner_ =
-        base::MessageLoop::current()->task_runner();
+        base::ThreadTaskRunnerHandle::Get();
     // The access to |source_buffer_| always happens on |task_runner_|, and
     // needn't be explicitly synchronized by a mutex.
     SourceBuffer* source_buffer_;
diff --git a/cobalt/dom/source_buffer.idl b/cobalt/dom/source_buffer.idl
index 39a9248..0d81b03 100644
--- a/cobalt/dom/source_buffer.idl
+++ b/cobalt/dom/source_buffer.idl
@@ -33,4 +33,10 @@
   [RaisesException] void abort();
   [RaisesException] void remove(double start, unrestricted double end);
   [RaisesException] attribute TrackDefaultList trackDefaults;
+
+  // Non standard interface (b/267678497).
+  // Returns the highest presentation timestamp written to SbPlayer, raises
+  // `InvalidStateError` if the SourceBuffer object has been removed from the
+  // MediaSource object.
+  [RaisesException] readonly attribute double writeHead;
 };
diff --git a/cobalt/dom/user_agent_data_test.cc b/cobalt/dom/user_agent_data_test.cc
index 376fcda..cd6886f 100644
--- a/cobalt/dom/user_agent_data_test.cc
+++ b/cobalt/dom/user_agent_data_test.cc
@@ -87,7 +87,7 @@
   platform_info_->set_starboard_version("");
   platform_info_->set_os_name_and_version("");
   platform_info_->set_original_design_manufacturer("");
-  platform_info_->set_device_type(kSbSystemDeviceTypeUnknown);
+  platform_info_->set_device_type("UNKNOWN");
   platform_info_->set_chipset_model_number("");
   platform_info_->set_model_year("");
   platform_info_->set_firmware_version("");
diff --git a/cobalt/dom/window.cc b/cobalt/dom/window.cc
index 1644cdc..8ff1bd9 100644
--- a/cobalt/dom/window.cc
+++ b/cobalt/dom/window.cc
@@ -19,6 +19,7 @@
 
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/tokens.h"
@@ -196,7 +197,7 @@
   // Document load start is deferred from this constructor so that we can be
   // guaranteed that this Window object is fully constructed before document
   // loading begins.
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::Bind(&Window::StartDocumentLoad, base::Unretained(this),
                             fetcher_factory, settings->creation_url(),
                             dom_parser, load_complete_callback));
diff --git a/cobalt/h5vcc/dial/dial_server.cc b/cobalt/h5vcc/dial/dial_server.cc
index f68f570..9bb528d 100644
--- a/cobalt/h5vcc/dial/dial_server.cc
+++ b/cobalt/h5vcc/dial/dial_server.cc
@@ -14,6 +14,8 @@
 
 #include "cobalt/h5vcc/dial/dial_server.h"
 
+#include "base/threading/thread_task_runner_handle.h"
+
 #if defined(DIAL_SERVER)
 
 #include <memory>
@@ -162,7 +164,7 @@
     const base::WeakPtr<DialServer>& dial_server,
     const std::string& service_name)
     : dial_server_(dial_server), service_name_(service_name) {
-  task_runner_ = base::MessageLoop::current()->task_runner();
+  task_runner_ = base::ThreadTaskRunnerHandle::Get();
 }
 
 DialServer::ServiceHandler::~ServiceHandler() {}
@@ -172,7 +174,7 @@
     const CompletionCB& completion_cb) {
   // This gets called on the DialService/Network thread.
   // Post it to the WebModule thread.
-  DCHECK_NE(base::MessageLoop::current()->task_runner(), task_runner_);
+  DCHECK_NE(base::ThreadTaskRunnerHandle::Get(), task_runner_);
   task_runner_->PostTask(
       FROM_HERE, base::Bind(&ServiceHandler::OnHandleRequest, this, path,
                             request, completion_cb));
@@ -181,7 +183,7 @@
 void DialServer::ServiceHandler::OnHandleRequest(
     const std::string& path, const net::HttpServerRequestInfo& request,
     const CompletionCB& completion_cb) {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), task_runner_);
   if (!dial_server_) {
     completion_cb.Run(std::unique_ptr<net::HttpServerResponseInfo>());
     return;
diff --git a/cobalt/h5vcc/h5vcc.cc b/cobalt/h5vcc/h5vcc.cc
index 48c2ed5..fdc0893 100644
--- a/cobalt/h5vcc/h5vcc.cc
+++ b/cobalt/h5vcc/h5vcc.cc
@@ -37,7 +37,8 @@
 #if SB_IS(EVERGREEN)
                         settings.updater_module,
 #endif
-                        settings.user_agent_data, settings.global_environment);
+                        settings.user_agent_data, settings.global_environment,
+                        settings.persistent_settings);
   storage_ =
       new H5vccStorage(settings.network_module, settings.persistent_settings);
   trace_event_ = new H5vccTraceEvent();
diff --git a/cobalt/h5vcc/h5vcc_accessibility.cc b/cobalt/h5vcc/h5vcc_accessibility.cc
index 2463943..f248a0c 100644
--- a/cobalt/h5vcc/h5vcc_accessibility.cc
+++ b/cobalt/h5vcc/h5vcc_accessibility.cc
@@ -16,6 +16,7 @@
 
 #include "base/command_line.h"
 #include "base/message_loop/message_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/accessibility_settings_changed_event.h"
 #include "cobalt/base/accessibility_text_to_speech_settings_changed_event.h"
 #include "cobalt/browser/switches.h"
@@ -42,7 +43,7 @@
 
 H5vccAccessibility::H5vccAccessibility(base::EventDispatcher* event_dispatcher)
     : event_dispatcher_(event_dispatcher) {
-  task_runner_ = base::MessageLoop::current()->task_runner();
+  task_runner_ = base::ThreadTaskRunnerHandle::Get();
   on_application_event_callback_ = base::Bind(
       &H5vccAccessibility::OnApplicationEvent, base::Unretained(this));
   event_dispatcher_->AddEventCallback(
@@ -99,29 +100,28 @@
 
 void H5vccAccessibility::AddTextToSpeechListener(
     const H5vccAccessibilityCallbackHolder& holder) {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), task_runner_);
   text_to_speech_listener_.reset(
       new H5vccAccessibilityCallbackReference(this, holder));
 }
 
 void H5vccAccessibility::AddHighContrastTextListener(
     const H5vccAccessibilityCallbackHolder& holder) {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), task_runner_);
   high_contrast_text_listener_.reset(
       new H5vccAccessibilityCallbackReference(this, holder));
 }
 
 void H5vccAccessibility::OnApplicationEvent(const base::Event* event) {
   // This method should be called from the application event thread.
-  DCHECK_NE(base::MessageLoop::current()->task_runner(), task_runner_);
+  DCHECK_NE(base::ThreadTaskRunnerHandle::Get(), task_runner_);
   task_runner_->PostTask(
       FROM_HERE, base::Bind(&H5vccAccessibility::InternalOnApplicationEvent,
-                            base::Unretained(this),
-                            event->GetTypeId()));
+                            base::Unretained(this), event->GetTypeId()));
 }
 
 void H5vccAccessibility::InternalOnApplicationEvent(base::TypeId type) {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), task_runner_);
   if (type == base::AccessibilitySettingsChangedEvent::TypeId() &&
       high_contrast_text_listener_) {
     high_contrast_text_listener_->value().Run();
diff --git a/cobalt/h5vcc/h5vcc_event_listener_container.h b/cobalt/h5vcc/h5vcc_event_listener_container.h
index 7da01c0..f2a0876 100644
--- a/cobalt/h5vcc/h5vcc_event_listener_container.h
+++ b/cobalt/h5vcc/h5vcc_event_listener_container.h
@@ -22,6 +22,7 @@
 #include "base/location.h"
 #include "base/message_loop/message_loop.h"
 #include "base/synchronization/lock.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/wrappable.h"
@@ -48,12 +49,12 @@
   struct Listener {
     Listener(script::Wrappable* owner, const CallbackHolderType& cb)
         : callback(owner, cb),
-          task_runner(base::MessageLoop::current()->task_runner()) {}
+          task_runner(base::ThreadTaskRunnerHandle::Get()) {}
 
     // Notifies listener. Must be called on the same message loop the
     // listener registered its callback from.
     void Notify(GetArgumentCallback on_notify) {
-      DCHECK_EQ(base::MessageLoop::current()->task_runner(), task_runner);
+      DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), task_runner);
       CallbackArgType arg = on_notify.Run();
       callback.value().Run(arg);
     }
@@ -128,10 +129,10 @@
 // Explicit template specialization for the no callback argument case, where
 // we don't need to call the |GetArgumentCallback| callback.
 template <>
-inline void
-    H5vccEventListenerContainer<void, script::CallbackFunction<void()> >::
-        Listener::Notify(GetArgumentCallback) {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), task_runner);
+inline void H5vccEventListenerContainer<
+    void,
+    script::CallbackFunction<void()> >::Listener::Notify(GetArgumentCallback) {
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), task_runner);
   callback.value().Run();
 }
 
diff --git a/cobalt/h5vcc/h5vcc_platform_service.cc b/cobalt/h5vcc/h5vcc_platform_service.cc
index 27f7510..aaa07e6 100644
--- a/cobalt/h5vcc/h5vcc_platform_service.cc
+++ b/cobalt/h5vcc/h5vcc_platform_service.cc
@@ -17,6 +17,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/environment_settings.h"
@@ -66,7 +67,7 @@
     : environment_(environment),
       platform_service_api_(platform_service_api),
       receive_callback_(this, receive_callback),
-      main_message_loop_(base::MessageLoop::current()->task_runner()),
+      main_message_loop_(base::ThreadTaskRunnerHandle::Get()),
       ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
       ALLOW_THIS_IN_INITIALIZER_LIST(
           weak_this_(weak_ptr_factory_.GetWeakPtr())) {
diff --git a/cobalt/h5vcc/h5vcc_settings.cc b/cobalt/h5vcc/h5vcc_settings.cc
index ca8a59c..574852d 100644
--- a/cobalt/h5vcc/h5vcc_settings.cc
+++ b/cobalt/h5vcc/h5vcc_settings.cc
@@ -16,6 +16,10 @@
 
 #include <string.h>
 
+#include <memory>
+
+#include "cobalt/network/network_module.h"
+
 namespace cobalt {
 namespace h5vcc {
 
@@ -25,8 +29,8 @@
 // option 1 disables all video codecs except av1
 // option 2 disables all video codecs except vp9
 constexpr std::array<const char*, 3> kDisableCodecCombinations{
-    "av01;hev1;hvc1;vp09;vp8.vp9", "avc1;avc3;hev1;hvc1;vp09;vp8;vp9",
-    "av01;avc1;avc3;hev1;hvc1;vp8"};
+    {"av01;hev1;hvc1;vp09;vp8.vp9", "avc1;avc3;hev1;hvc1;vp09;vp8;vp9",
+     "av01;avc1;avc3;hev1;hvc1;vp8"}};
 };  // namespace
 
 H5vccSettings::H5vccSettings(
@@ -38,7 +42,8 @@
     cobalt::updater::UpdaterModule* updater_module,
 #endif
     web::NavigatorUAData* user_agent_data,
-    script::GlobalEnvironment* global_environment)
+    script::GlobalEnvironment* global_environment,
+    persistent_storage::PersistentSettings* persistent_settings)
     : set_web_setting_func_(set_web_setting_func),
       media_module_(media_module),
       can_play_type_handler_(can_play_type_handler),
@@ -47,13 +52,15 @@
       updater_module_(updater_module),
 #endif
       user_agent_data_(user_agent_data),
-      global_environment_(global_environment) {
+      global_environment_(global_environment),
+      persistent_settings_(persistent_settings) {
 }
 
 bool H5vccSettings::Set(const std::string& name, int32 value) const {
   const char kMediaPrefix[] = "Media.";
   const char kDisableMediaCodec[] = "DisableMediaCodec";
   const char kNavigatorUAData[] = "NavigatorUAData";
+  const char kClientHintHeaders[] = "ClientHintHeaders";
   const char kQUIC[] = "QUIC";
 
 #if SB_IS(EVERGREEN)
@@ -82,6 +89,17 @@
     return true;
   }
 
+  if (name.compare(kClientHintHeaders) == 0) {
+    if (!persistent_settings_) {
+      return false;
+    } else {
+      persistent_settings_->SetPersistentSetting(
+          network::kClientHintHeadersEnabledPersistentSettingsKey,
+          std::make_unique<base::Value>(value != 0));
+      return true;
+    }
+  }
+
   if (name.compare(kQUIC) == 0) {
     if (!network_module_) {
       return false;
diff --git a/cobalt/h5vcc/h5vcc_settings.h b/cobalt/h5vcc/h5vcc_settings.h
index e6e5b4a..34e7a44 100644
--- a/cobalt/h5vcc/h5vcc_settings.h
+++ b/cobalt/h5vcc/h5vcc_settings.h
@@ -19,6 +19,7 @@
 
 #include "cobalt/media/media_module.h"
 #include "cobalt/network/network_module.h"
+#include "cobalt/persistent_storage/persistent_settings.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/wrappable.h"
 #include "cobalt/web/navigator_ua_data.h"
@@ -46,7 +47,8 @@
                 cobalt::updater::UpdaterModule* updater_module,
 #endif
                 web::NavigatorUAData* user_agent_data,
-                script::GlobalEnvironment* global_environment);
+                script::GlobalEnvironment* global_environment,
+                persistent_storage::PersistentSettings* persistent_settings);
 
   // Returns true when the setting is set successfully or if the setting has
   // already been set to the expected value.  Returns false when the setting is
@@ -65,6 +67,7 @@
 #endif
   web::NavigatorUAData* user_agent_data_;
   script::GlobalEnvironment* global_environment_;
+  persistent_storage::PersistentSettings* persistent_settings_;
 
   DISALLOW_COPY_AND_ASSIGN(H5vccSettings);
 };
diff --git a/cobalt/h5vcc/h5vcc_storage.cc b/cobalt/h5vcc/h5vcc_storage.cc
index 6c83852..0bb660c 100644
--- a/cobalt/h5vcc/h5vcc_storage.cc
+++ b/cobalt/h5vcc/h5vcc_storage.cc
@@ -119,7 +119,7 @@
   net::CookieStore* cookie_store =
       network_module_->url_request_context()->cookie_store();
   auto* cookie_monster = static_cast<net::CookieMonster*>(cookie_store);
-  network_module_->task_runner()->PostBlockingTask(
+  network_module_->task_runner()->PostTask(
       FROM_HERE,
       base::Bind(&net::CookieMonster::DeleteAllMatchingInfoAsync,
                  base::Unretained(cookie_monster), net::CookieDeletionInfo(),
diff --git a/cobalt/h5vcc/h5vcc_system.cc b/cobalt/h5vcc/h5vcc_system.cc
index b28e941..89c9d9a 100644
--- a/cobalt/h5vcc/h5vcc_system.cc
+++ b/cobalt/h5vcc/h5vcc_system.cc
@@ -18,8 +18,11 @@
 #include "cobalt/configuration/configuration.h"
 #include "cobalt/version.h"
 #include "cobalt_build_id.h"  // NOLINT(build/include_subdir)
+#include "starboard/common/system_property.h"
 #include "starboard/system.h"
 
+using starboard::kSystemPropertyMaxLength;
+
 namespace cobalt {
 namespace h5vcc {
 
@@ -55,7 +58,6 @@
 std::string H5vccSystem::advertising_id() const {
   std::string result;
 #if SB_API_VERSION >= 14
-  const size_t kSystemPropertyMaxLength = 1024;
   char property[kSystemPropertyMaxLength] = {0};
   if (!SbSystemGetProperty(kSbSystemPropertyAdvertisingId, property,
                            SB_ARRAY_SIZE_INT(property))) {
@@ -69,7 +71,6 @@
 bool H5vccSystem::limit_ad_tracking() const {
   bool result = false;
 #if SB_API_VERSION >= 14
-  const size_t kSystemPropertyMaxLength = 1024;
   char property[kSystemPropertyMaxLength] = {0};
   if (!SbSystemGetProperty(kSbSystemPropertyLimitAdTracking, property,
                            SB_ARRAY_SIZE_INT(property))) {
diff --git a/cobalt/input/BUILD.gn b/cobalt/input/BUILD.gn
index d97732f..912eb83 100644
--- a/cobalt/input/BUILD.gn
+++ b/cobalt/input/BUILD.gn
@@ -35,8 +35,8 @@
 
   if (enable_vr) {
     sources += [
-      "private/camera_3d_vr.cc",
-      "private/camera_3d_vr.h",
+      "//internal/cobalt/input/private/camera_3d_vr.cc",
+      "//internal/cobalt/input/private/camera_3d_vr.h",
     ]
   } else {
     sources += [
diff --git a/cobalt/input/input_device_manager_fuzzer.cc b/cobalt/input/input_device_manager_fuzzer.cc
index 02dcf1f..866b764 100644
--- a/cobalt/input/input_device_manager_fuzzer.cc
+++ b/cobalt/input/input_device_manager_fuzzer.cc
@@ -18,6 +18,7 @@
 #include "base/bind.h"
 #include "base/message_loop/message_loop.h"
 #include "base/rand_util.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/keyboard_event.h"
 #include "cobalt/dom/keycode.h"
@@ -121,7 +122,7 @@
   keyboard_event_callback_.Run(base::Tokens::keydown(), event_init);
   keyboard_event_callback_.Run(base::Tokens::keyup(), event_init);
 
-  base::MessageLoop::current()->task_runner()->PostDelayedTask(
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE,
       base::Bind(&InputDeviceManagerFuzzer::OnNextEvent,
                  base::Unretained(this)),
diff --git a/cobalt/input/keypress_generator_filter.cc b/cobalt/input/keypress_generator_filter.cc
index 75b35a3..3844a27 100644
--- a/cobalt/input/keypress_generator_filter.cc
+++ b/cobalt/input/keypress_generator_filter.cc
@@ -55,14 +55,10 @@
   int char_code = dom::KeyboardEvent::ComputeCharCode(orig_event.key_code(),
                                                       orig_event.shift_key());
 
-  if (char_code > 0) {
-    dom::KeyboardEventInit event(orig_event);
-    event.set_char_code(char_code);
-    DispatchKeyboardEvent(base::Tokens::keypress(), event);
-    return true;
-  }
-
-  return false;
+  dom::KeyboardEventInit event(orig_event);
+  event.set_char_code(char_code);
+  DispatchKeyboardEvent(base::Tokens::keypress(), event);
+  return true;
 }
 
 }  // namespace input
diff --git a/cobalt/layout/layout_manager.cc b/cobalt/layout/layout_manager.cc
index 6592f0e..500d060 100644
--- a/cobalt/layout/layout_manager.cc
+++ b/cobalt/layout/layout_manager.cc
@@ -21,6 +21,7 @@
 
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/timer/timer.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/cssom/cascade_precedence.h"
@@ -231,7 +232,7 @@
     DirtyLayout();
 
     // Run the |DoLayoutAndProduceRenderTree| task after onload event finished.
-    base::MessageLoop::current()->task_runner()->PostTask(
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::Bind(&LayoutManager::Impl::DoLayoutAndProduceRenderTree,
                    base::Unretained(this)));
diff --git a/cobalt/layout_tests/layout_snapshot.cc b/cobalt/layout_tests/layout_snapshot.cc
index e137530..a600395 100644
--- a/cobalt/layout_tests/layout_snapshot.cc
+++ b/cobalt/layout_tests/layout_snapshot.cc
@@ -20,6 +20,8 @@
 #include "base/path_service.h"
 #include "base/run_loop.h"
 #include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "cobalt/browser/client_hint_headers.h"
 #include "cobalt/browser/user_agent_string.h"
 #include "cobalt/browser/web_module.h"
 #include "cobalt/cssom/viewport_size.h"
@@ -37,8 +39,8 @@
 
 namespace {
 void Quit(base::RunLoop* run_loop) {
-  base::MessageLoop::current()->task_runner()->PostTask(
-      FROM_HERE, run_loop->QuitClosure());
+  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                run_loop->QuitClosure());
 }
 
 // Called when layout completes and results have been produced.  We use this
@@ -75,10 +77,10 @@
   // don't interfere.
   net_options.https_requirement = network::kHTTPSOptional;
   web::WebSettingsImpl web_settings;
+  browser::UserAgentPlatformInfo platform_info;
   network::NetworkModule network_module(
-      browser::CreateUserAgentString(
-          browser::GetUserAgentPlatformInfoFromSystem()),
-      NULL, NULL, net_options);
+      browser::CreateUserAgentString(platform_info),
+      browser::GetClientHintHeaders(platform_info), NULL, NULL, net_options);
 
   // Use 128M of image cache to minimize the effect of image loading.
   const size_t kImageCacheCapacity = 128 * 1024 * 1024;
diff --git a/cobalt/layout_tests/layout_tests.cc b/cobalt/layout_tests/layout_tests.cc
index 0e2f11f..5c9d0f1 100644
--- a/cobalt/layout_tests/layout_tests.cc
+++ b/cobalt/layout_tests/layout_tests.cc
@@ -20,6 +20,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/path_service.h"
 #include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/cobalt_paths.h"
 #include "cobalt/cssom/viewport_size.h"
 #include "cobalt/layout_tests/layout_snapshot.h"
@@ -137,8 +138,7 @@
 
   browser::WebModule::LayoutResults layout_results = SnapshotURL(
       test_info.url, viewport_size, pixel_tester.GetResourceProvider(),
-      base::Bind(&ScreenshotFunction,
-                 base::MessageLoop::current()->task_runner(),
+      base::Bind(&ScreenshotFunction, base::ThreadTaskRunnerHandle::Get(),
                  base::Unretained(&pixel_tester)));
 
   scoped_refptr<render_tree::animations::AnimateNode> animate_node =
diff --git a/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt b/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt
index 8072c2b..1132c15 100644
--- a/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt
+++ b/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt
@@ -2,8 +2,6 @@
 
 service-worker/clients-matchall-on-evaluation.https.html, PASS
 service-worker/fetch-event-add-async.https.html, PASS
-service-worker/import-scripts-cross-origin.https.html, PASS
-service-worker/import-scripts-mime-types.https.html, PASS
 service-worker/import-scripts-resource-map.https.html, PASS
 service-worker/import-scripts-updated-flag.https.html, PASS
 service-worker/register-default-scope.https.html, PASS
@@ -11,30 +9,36 @@
 service-worker/registration-security-error.https.html, PASS
 service-worker/registration-script-url.https.html, PASS
 service-worker/rejections.https.html, PASS
-service-worker/serviceworkerobject-scripturl.https.html, PASS
 service-worker/service-worker-csp-default.https.html, PASS
 service-worker/service-worker-csp-connect.https.html, PASS
-service-worker/service-worker-header.https.html, PASS
 service-worker/service-worker-csp-script.https.html, PASS
 service-worker/Service-Worker-Allowed-header.https.html, PASS
 service-worker/skip-waiting-without-client.https.html, PASS
 service-worker/uncontrolled-page.https.html, PASS
 service-worker/unregister.https.html, PASS
-service-worker/update-missing-import-scripts.https.html, PASS
-service-worker/update-result.https.html, PASS
+service-worker/update-no-cache-request-headers.https.html, PASS
 
-# Tests pass with memory leakage issue.
-service-worker/update-no-cache-request-headers.https.html, DISABLE
+# b/274011216 flaky test
+service-worker/update-result.https.html, DISABLE
+
+# TODO(b/275914032): Flaky test.
+service-worker/update-missing-import-scripts.https.html, DISABLE
+
+# b/275643772 MIME type check is flaky
+service-worker/import-scripts-mime-types.https.html, DISABLE
 
 # b/234788479 Implement waiting for update worker state tasks in Install algorithm.
 service-worker/activation-after-registration.https.html, DISABLE
 service-worker/activate-event-after-install-state-change.https.html, DISABLE
+service-worker/import-scripts-cross-origin.https.html, DISABLE
 service-worker/import-scripts-redirect.https.html, DISABLE
 service-worker/multiple-update.https.html, DISABLE
 service-worker/register-wait-forever-in-install-worker.https.html, DISABLE
 service-worker/registration-service-worker-attributes.https.html, DISABLE
+service-worker/service-worker-header.https.html, DISABLE
 service-worker/state.https.html, DISABLE
 service-worker/synced-state.https.html, DISABLE
+service-worker/serviceworkerobject-scripturl.https.html, DISABLE
 
 # "Module" type of dedicated worker is supported in Cobalt
 service-worker/dedicated-worker-service-worker-interception.https.html, DISABLE
diff --git a/cobalt/layout_tests/testdata/web-platform-tests/workers/web_platform_tests.txt b/cobalt/layout_tests/testdata/web-platform-tests/workers/web_platform_tests.txt
index 48654c3..9b36ff3 100644
--- a/cobalt/layout_tests/testdata/web-platform-tests/workers/web_platform_tests.txt
+++ b/cobalt/layout_tests/testdata/web-platform-tests/workers/web_platform_tests.txt
@@ -5,11 +5,9 @@
 # features that are expected to currently work.
 
 Worker_basic.htm, PASS
+Worker_cross_origin_security_err.htm, PASS
 Worker_dispatchEvent_ErrorEvent.htm, PASS
 
-# b/225037465
-Worker_cross_origin_security_err.htm, DISABLE
-
 # b/275741116
 Worker_ErrorEvent_bubbles_cancelable.htm, DISABLE
 Worker_ErrorEvent_filename.htm, DISABLE
diff --git a/cobalt/layout_tests/web_platform_tests.cc b/cobalt/layout_tests/web_platform_tests.cc
index ac6e3fd..05c2376 100644
--- a/cobalt/layout_tests/web_platform_tests.cc
+++ b/cobalt/layout_tests/web_platform_tests.cc
@@ -24,6 +24,7 @@
 #include "base/run_loop.h"
 #include "base/strings/string_util.h"
 #include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
 #include "cobalt/browser/service_worker_registry.h"
 #include "cobalt/browser/user_agent_platform_info.h"
@@ -150,8 +151,8 @@
 };
 
 void Quit(base::RunLoop* run_loop) {
-  base::MessageLoop::current()->task_runner()->PostTask(
-      FROM_HERE, run_loop->QuitClosure());
+  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                run_loop->QuitClosure());
 }
 
 // Called upon window.close(), which indicates that the test has finished.
@@ -213,33 +214,36 @@
 
   web::CspDelegateFactory::GetInstance()->OverrideCreator(
       web::kCspEnforcementEnable, CspDelegatePermissive::Create);
+
+  std::unique_ptr<browser::UserAgentPlatformInfo> platform_info(
+      new browser::UserAgentPlatformInfo());
+  std::unique_ptr<browser::ServiceWorkerRegistry> service_worker_registry(
+      new browser::ServiceWorkerRegistry(&web_settings, &network_module,
+                                         platform_info.get(), url));
+
+  browser::WebModule::Options web_module_options;
   // Use test runner mode to allow the content itself to dictate when it is
   // ready for layout should be performed.  See cobalt/dom/test_runner.h.
-  browser::WebModule::Options web_module_options;
   web_module_options.layout_trigger = layout::LayoutManager::kTestRunnerMode;
+
   // We assume that we won't suspend/resume while running the tests, and so
   // we take advantage of the convenience of inline script tags.
   web_module_options.enable_inline_script_warnings = false;
 
   web_module_options.web_options.web_settings = &web_settings;
   web_module_options.web_options.network_module = &network_module;
+  web_module_options.web_options.service_worker_jobs =
+      service_worker_registry->service_worker_jobs();
+  web_module_options.web_options.platform_info = platform_info.get();
 
   // Prepare a slot for our results to be placed when ready.
   base::Optional<browser::WebModule::LayoutResults> results;
   base::RunLoop run_loop;
 
-  // Create the WebModule and wait for a layout to occur.
-  browser::WebModule web_module("RunWebPlatformTest");
-
-  // Create Service Worker Registry
-  browser::ServiceWorkerRegistry* service_worker_registry =
-      new browser::ServiceWorkerRegistry(&web_settings, &network_module,
-                                         new browser::UserAgentPlatformInfo(),
-                                         url);
-  web_module_options.web_options.service_worker_jobs =
-      service_worker_registry->service_worker_jobs();
-
-  web_module.Run(
+  // Run the WebModule and wait for a layout to occur.
+  std::unique_ptr<browser::WebModule> web_module(
+      new browser::WebModule("RunWebPlatformTest"));
+  web_module->Run(
       url, base::kApplicationStateStarted, nullptr /* scroll_engine */,
       base::Bind(&WebModuleOnRenderTreeProducedCallback, &results),
       base::Bind(&WebModuleErrorCallback, &run_loop,
@@ -249,12 +253,16 @@
       can_play_type_handler.get(), media_module.get(), kDefaultViewportSize,
       &resource_provider, 60.0f, web_module_options);
   run_loop.Run();
+
   const std::string extract_results =
       "document.getElementById(\"__testharness__results__\").textContent;";
   std::string output;
-  web_module.ExecuteJavascript(extract_results,
-                               base::SourceLocation(__FILE__, __LINE__, 1),
-                               &output, got_results);
+  web_module->ExecuteJavascript(extract_results,
+                                base::SourceLocation(__FILE__, __LINE__, 1),
+                                &output, got_results);
+  // Ensure that the WebModule stops before stopping the ServiceWorkerRegistry.
+  web_module.reset();
+  service_worker_registry.reset();
   return output;
 }
 
diff --git a/cobalt/loader/about_fetcher.cc b/cobalt/loader/about_fetcher.cc
index 33d32d8..0f43428 100644
--- a/cobalt/loader/about_fetcher.cc
+++ b/cobalt/loader/about_fetcher.cc
@@ -16,6 +16,7 @@
 
 #include "base/bind.h"
 #include "base/message_loop/message_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
 
 namespace cobalt {
 namespace loader {
@@ -23,7 +24,7 @@
 AboutFetcher::AboutFetcher(Handler* handler)
     : Fetcher(handler),
       ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::Bind(&AboutFetcher::Fetch, weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/cobalt/loader/cors_preflight.cc b/cobalt/loader/cors_preflight.cc
index 53e501d..93f96d2 100644
--- a/cobalt/loader/cors_preflight.cc
+++ b/cobalt/loader/cors_preflight.cc
@@ -280,6 +280,7 @@
   url_fetcher_ = net::URLFetcher::Create(url_, net::URLFetcher::OPTIONS, this);
   url_fetcher_->SetRequestContext(
       network_module_->url_request_context_getter().get());
+  network_module_->AddClientHintHeaders(*url_fetcher_);
   url_fetcher_->AddExtraRequestHeader(kOriginheadername + origin_);
   // 3. Let headers be the names of request's header list's headers,
   //    excluding CORS-safelisted request-headers and duplicates, sorted
@@ -331,7 +332,8 @@
     net::HttpResponseHeaders* response_headers = source->GetResponseHeaders();
     std::string methods, headernames;
     // If status is not ok status, return network error
-    if (!CORSCheck(*response_headers, origin_, credentials_mode_is_include_) ||
+    if (!CORSCheck(*response_headers, origin_, credentials_mode_is_include_,
+                   cors_policy_) ||
         source->GetResponseCode() < 200 || source->GetResponseCode() > 299) {
       error_callback_.Run();
       return;
@@ -406,7 +408,14 @@
 // https://fetch.spec.whatwg.org/#concept-cors-check
 bool CORSPreflight::CORSCheck(const net::HttpResponseHeaders& response_headers,
                               const std::string& serialized_origin,
-                              bool credentials_mode_is_include) {
+                              bool credentials_mode_is_include,
+                              network::CORSPolicy cors_policy) {
+#ifndef COBALT_FORCE_CORS
+  if (cors_policy == network::kCORSOptional) {
+    DLOG(WARNING) << "Cors check disabled, allowing request without checking.";
+    return true;
+  }
+#endif
   // 1. Let origin be the result of extracting header list values given `Access-
   //    Control-Allow-Origin` and response's header list.
   std::string allowed_origin, empty_container, allow_credentials;
diff --git a/cobalt/loader/cors_preflight.h b/cobalt/loader/cors_preflight.h
index 8afa99d..a7476c8 100644
--- a/cobalt/loader/cors_preflight.h
+++ b/cobalt/loader/cors_preflight.h
@@ -27,6 +27,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/threading/thread_checker.h"
 #include "cobalt/loader/cors_preflight_cache.h"
+#include "cobalt/network/network_delegate.h"
 #include "cobalt/network/network_module.h"
 #include "net/http/http_request_headers.h"
 #include "net/http/http_response_headers.h"
@@ -57,6 +58,9 @@
   void set_headers(const net::HttpRequestHeaders& headers) {
     headers_ = headers;
   }
+  void set_cors_policy(const network::CORSPolicy cors_policy) {
+    cors_policy_ = cors_policy;
+  }
   // Determine if CORS Preflight is needed by a cross origin request
   bool IsPreflightNeeded();
   // The send method can be called after initializing this class.
@@ -65,9 +69,10 @@
   bool Send();
   // CORS Check is done on response to ensure simple CORS request is
   // allowed by the server.
-  static bool CORSCheck(const net::HttpResponseHeaders& response_headers,
-                        const std::string& serialized_origin,
-                        bool credentials_mode_is_include);
+  static bool CORSCheck(
+      const net::HttpResponseHeaders& response_headers,
+      const std::string& serialized_origin, bool credentials_mode_is_include,
+      network::CORSPolicy cors_policy = network::kCORSRequired);
   // Checks if a header(a name-value pair) is a CORS-Safelisted request-header.
   static bool IsSafeRequestHeader(const std::string& name,
                                   const std::string& value);
@@ -88,6 +93,7 @@
 
   bool credentials_mode_is_include_;
   bool force_preflight_;
+  network::CORSPolicy cors_policy_ = network::kCORSRequired;
   GURL url_;
   net::URLFetcher::RequestType method_;
   const network::NetworkModule* network_module_;
diff --git a/cobalt/loader/error_fetcher.cc b/cobalt/loader/error_fetcher.cc
index 41aa2e4..cc18c76 100644
--- a/cobalt/loader/error_fetcher.cc
+++ b/cobalt/loader/error_fetcher.cc
@@ -16,6 +16,7 @@
 
 #include "base/bind.h"
 #include "base/message_loop/message_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
 
 namespace cobalt {
 namespace loader {
@@ -24,7 +25,7 @@
     : Fetcher(handler),
       error_message_(error_message),
       ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::Bind(&ErrorFetcher::Fetch, weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/cobalt/loader/fetch_interceptor_coordinator.cc b/cobalt/loader/fetch_interceptor_coordinator.cc
index a7599fc..e4d2af9 100644
--- a/cobalt/loader/fetch_interceptor_coordinator.cc
+++ b/cobalt/loader/fetch_interceptor_coordinator.cc
@@ -39,7 +39,7 @@
 void FetchInterceptorCoordinator::TryIntercept(
     const GURL& url, bool main_resource,
     const net::HttpRequestHeaders& request_headers,
-    scoped_refptr<base::SingleThreadTaskRunner> callback_task_runner,
+    scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
     base::OnceCallback<void(std::unique_ptr<std::string>)> callback,
     base::OnceCallback<void(const net::LoadTimingInfo&)>
         report_load_timing_info,
diff --git a/cobalt/loader/fetch_interceptor_coordinator.h b/cobalt/loader/fetch_interceptor_coordinator.h
index b266807..82ec85f 100644
--- a/cobalt/loader/fetch_interceptor_coordinator.h
+++ b/cobalt/loader/fetch_interceptor_coordinator.h
@@ -19,7 +19,7 @@
 #include <string>
 
 #include "base/bind.h"
-#include "base/single_thread_task_runner.h"
+#include "base/sequenced_task_runner.h"
 #include "net/base/load_timing_info.h"
 #include "net/http/http_request_headers.h"
 #include "url/gurl.h"
@@ -37,7 +37,7 @@
   virtual void StartFetch(
       const GURL& url, bool main_resource,
       const net::HttpRequestHeaders& request_headers,
-      scoped_refptr<base::SingleThreadTaskRunner> callback_task_runner,
+      scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
       base::OnceCallback<void(std::unique_ptr<std::string>)> callback,
       base::OnceCallback<void(const net::LoadTimingInfo&)>
           report_load_timing_info,
@@ -58,7 +58,7 @@
   void TryIntercept(
       const GURL& url, bool main_resource,
       const net::HttpRequestHeaders& request_headers,
-      scoped_refptr<base::SingleThreadTaskRunner> callback_task_runner,
+      scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
       base::OnceCallback<void(std::unique_ptr<std::string>)> callback,
       base::OnceCallback<void(const net::LoadTimingInfo&)>
           report_load_timing_info,
diff --git a/cobalt/loader/fetcher_cache.cc b/cobalt/loader/fetcher_cache.cc
index 8d6b707..1e6adb5 100644
--- a/cobalt/loader/fetcher_cache.cc
+++ b/cobalt/loader/fetcher_cache.cc
@@ -33,7 +33,7 @@
       const std::string& url,
       const scoped_refptr<net::HttpResponseHeaders>& headers,
       const Origin& last_url_origin, bool did_fail_from_transient_error,
-      std::string* data)>
+      std::string data)>
       SuccessCallback;
 
   CachedFetcherHandler(const std::string& url, Fetcher::Handler* handler,
@@ -52,6 +52,7 @@
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(!wrapping_fetcher_);
     DCHECK(wrapping_fetcher);
+
     wrapping_fetcher_ = wrapping_fetcher;
   }
 
@@ -63,6 +64,7 @@
     // TODO: Respect HttpResponseHeaders::GetMaxAgeValue().
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(wrapping_fetcher_);
+
     auto response = handler_->OnResponseStarted(wrapping_fetcher_, headers);
     if (response == kLoadResponseContinue && headers) {
       headers_ = headers;
@@ -77,6 +79,7 @@
   void OnReceived(Fetcher*, const char* data, size_t size) override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(wrapping_fetcher_);
+
     data_.insert(data_.end(), data, data + size);
     handler_->OnReceived(wrapping_fetcher_, data, size);
   }
@@ -84,6 +87,7 @@
   void OnReceivedPassed(Fetcher*, std::unique_ptr<std::string> data) override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(wrapping_fetcher_);
+
     data_.insert(data_.end(), data->begin(), data->end());
     handler_->OnReceivedPassed(wrapping_fetcher_, std::move(data));
   }
@@ -91,15 +95,17 @@
   void OnDone(Fetcher*) override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(wrapping_fetcher_);
+
     handler_->OnDone(wrapping_fetcher_);
     on_success_callback_.Run(
         url_, headers_, wrapping_fetcher_->last_url_origin(),
-        wrapping_fetcher_->did_fail_from_transient_error(), &data_);
+        wrapping_fetcher_->did_fail_from_transient_error(), std::move(data_));
   }
 
   void OnError(Fetcher*, const std::string& error) override {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
     DCHECK(wrapping_fetcher_);
+
     handler_->OnError(wrapping_fetcher_, error);
   }
 
@@ -184,13 +190,12 @@
  public:
   CacheEntry(const scoped_refptr<net::HttpResponseHeaders>& headers,
              const Origin& last_url_origin, bool did_fail_from_transient_error,
-             std::string* data)
+             std::string data)
       : headers_(headers),
         last_url_origin_(last_url_origin),
-        did_fail_from_transient_error_(did_fail_from_transient_error) {
-    DCHECK(data);
-    data_.swap(*data);
-  }
+        did_fail_from_transient_error_(did_fail_from_transient_error),
+        data_(std::move(data)),
+        capacity_(data_.capacity()) {}
 
   const scoped_refptr<net::HttpResponseHeaders>& headers() const {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -213,16 +218,24 @@
   }
   size_t capacity() const {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-    return data_.capacity();
+    CHECK_EQ(capacity_, data_.capacity());
+    return capacity_;
   }
 
  private:
+  CacheEntry(const CacheEntry&) = delete;
+  CacheEntry& operator=(const CacheEntry&) = delete;
+
   THREAD_CHECKER(thread_checker_);
 
   scoped_refptr<net::HttpResponseHeaders> headers_;
-  Origin last_url_origin_;
-  bool did_fail_from_transient_error_;
-  std::string data_;
+  const Origin last_url_origin_;
+  const bool did_fail_from_transient_error_;
+  const std::string data_;
+
+  // TODO(b/270993319): For debugging cache integrity issues in production only,
+  //                    remove after identifying the root cause.
+  const size_t capacity_;
 };
 
 FetcherCache::FetcherCache(const char* name, size_t capacity)
@@ -236,12 +249,15 @@
 
 FetcherCache::~FetcherCache() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  CHECK_EQ(thread_id_, SbThreadGetId());
+  CHECK(destroy_soon_called_);
 
   while (!cache_entries_.empty()) {
     delete cache_entries_.begin()->second;
-    cache_entries_.erase(cache_entries_.begin());
+    cache_entries_.pop_front();
   }
 
+  total_size_ = 0;
   memory_size_in_bytes_ = 0;
   count_resources_cached_ = 0;
 }
@@ -251,12 +267,13 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(!real_fetcher_creator.is_null());
 
-  return base::Bind(&FetcherCache::CreateCachedFetcher, base::Unretained(this),
-                    url, real_fetcher_creator);
+  return base::Bind(&FetcherCache::CreateCachedFetcher, this, url,
+                    real_fetcher_creator);
 }
 
 void FetcherCache::NotifyResourceRequested(const std::string& url) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  CHECK_EQ(thread_id_, SbThreadGetId());
 
   auto iter = cache_entries_.find(url);
   if (iter != cache_entries_.end()) {
@@ -266,6 +283,14 @@
   }
 }
 
+void FetcherCache::DestroySoon() {
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+  CHECK(HasOneRef());
+#endif  //! defined(COBALT_BUILD_TYPE_GOLD)
+
+  destroy_soon_called_ = true;
+}
+
 std::unique_ptr<Fetcher> FetcherCache::CreateCachedFetcher(
     const GURL& url, const Loader::FetcherCreator& real_fetcher_creator,
     Fetcher::Handler* handler) {
@@ -273,6 +298,11 @@
   DCHECK(!real_fetcher_creator.is_null());
   DCHECK(handler);
 
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+  CHECK(!destroy_soon_called_);
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+  CHECK_EQ(thread_id_, SbThreadGetId());
+
   auto iterator = cache_entries_.find(url.spec());
   if (iterator != cache_entries_.end()) {
     auto entry = iterator->second;
@@ -285,8 +315,7 @@
   }
 
   std::unique_ptr<CachedFetcherHandler> cached_handler(new CachedFetcherHandler(
-      url.spec(), handler,
-      base::Bind(&FetcherCache::OnFetchSuccess, base::Unretained(this))));
+      url.spec(), handler, base::Bind(&FetcherCache::OnFetchSuccess, this)));
   return std::unique_ptr<Fetcher>(
       new OngoingFetcher(std::move(cached_handler), real_fetcher_creator));
 }
@@ -295,32 +324,44 @@
     const std::string& url,
     const scoped_refptr<net::HttpResponseHeaders>& headers,
     const Origin& last_url_origin, bool did_fail_from_transient_error,
-    std::string* data) {
+    std::string data) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  DCHECK(data);
 
-  if (data->size() <= capacity_) {
-    auto entry = new CacheEntry(headers, last_url_origin,
-                                did_fail_from_transient_error, data);
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+  CHECK(!destroy_soon_called_);
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+  CHECK_EQ(thread_id_, SbThreadGetId());
 
-    bool inserted = cache_entries_.insert(std::make_pair(url, entry)).second;
-    if (!inserted) {
-      // The resource is already cached.
-      delete entry;
-      return;
-    }
-
-    total_size_ += entry->capacity();
-    while (total_size_ > capacity_) {
-      DCHECK(!cache_entries_.empty());
-      total_size_ -= cache_entries_.begin()->second->capacity();
-      delete cache_entries_.begin()->second;
-      cache_entries_.erase(cache_entries_.begin());
-      --count_resources_cached_;
-    }
-    ++count_resources_cached_;
-    memory_size_in_bytes_ = total_size_;
+  if (data.capacity() > capacity_) {
+    return;
   }
+
+  auto entry = new CacheEntry(headers, last_url_origin,
+                              did_fail_from_transient_error, std::move(data));
+
+  bool inserted = cache_entries_.insert(std::make_pair(url, entry)).second;
+  if (!inserted) {
+    // The resource is already cached.
+    delete entry;
+    return;
+  }
+
+  total_size_ += entry->capacity();
+  ++count_resources_cached_;
+
+  while (total_size_ > capacity_) {
+    // TODO(b/270993319): For debugging cache integrity issues in production
+    //                    only, remove after identifying the root cause.
+    CHECK(!cache_entries_.empty());
+    CHECK_GE(total_size_, cache_entries_.begin()->second->capacity());
+
+    total_size_ -= cache_entries_.begin()->second->capacity();
+    delete cache_entries_.begin()->second;
+    cache_entries_.pop_front();
+    --count_resources_cached_;
+  }
+
+  memory_size_in_bytes_ = total_size_;
 }
 
 }  // namespace loader
diff --git a/cobalt/loader/fetcher_cache.h b/cobalt/loader/fetcher_cache.h
index 32a4466..8f6e2b0 100644
--- a/cobalt/loader/fetcher_cache.h
+++ b/cobalt/loader/fetcher_cache.h
@@ -15,6 +15,7 @@
 #ifndef COBALT_LOADER_FETCHER_CACHE_H_
 #define COBALT_LOADER_FETCHER_CACHE_H_
 
+#include <atomic>
 #include <memory>
 #include <string>
 
@@ -25,6 +26,7 @@
 #include "cobalt/loader/loader.h"
 #include "net/base/linked_hash_map.h"
 #include "net/http/http_response_headers.h"
+#include "starboard/thread.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -32,7 +34,7 @@
 namespace loader {
 
 // Manages a cache for data fetched by Fetchers.
-class FetcherCache {
+class FetcherCache : public base::RefCountedThreadSafe<FetcherCache> {
  public:
   FetcherCache(const char* name, size_t capacity);
   ~FetcherCache();
@@ -41,6 +43,13 @@
       const GURL& url, const Loader::FetcherCreator& real_fetcher_creator);
   void NotifyResourceRequested(const std::string& url);
 
+  // To signal the imminent destruction of this object.  If everything is
+  // working as expected, there shouldn't be any other reference of this object,
+  // and all usages of this object should be completed.
+  // TODO(b/270993319): For debugging cache integrity issues in production only,
+  //                    remove after identifying the root cause.
+  void DestroySoon();
+
  private:
   class CacheEntry;
 
@@ -50,10 +59,15 @@
   void OnFetchSuccess(const std::string& url,
                       const scoped_refptr<net::HttpResponseHeaders>& headers,
                       const Origin& last_url_origin,
-                      bool did_fail_from_transient_error, std::string* data);
+                      bool did_fail_from_transient_error, std::string data);
 
   THREAD_CHECKER(thread_checker_);
 
+  // TODO(b/270993319): For debugging cache integrity issues in production only,
+  //                    remove after identifying the root cause.
+  const SbThreadId thread_id_ = SbThreadGetId();
+  std::atomic_bool destroy_soon_called_{false};
+
   const size_t capacity_;
   size_t total_size_ = 0;
 
diff --git a/cobalt/loader/fetcher_factory_test.cc b/cobalt/loader/fetcher_factory_test.cc
index 1f579db..bae9441 100644
--- a/cobalt/loader/fetcher_factory_test.cc
+++ b/cobalt/loader/fetcher_factory_test.cc
@@ -19,6 +19,7 @@
 
 #include "base/optional.h"
 #include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/loader/file_fetcher.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -36,14 +37,14 @@
   }
   void OnDone(Fetcher* fetcher) override {
     CheckSameFetcher(fetcher);
-    base::MessageLoop::current()->task_runner()->PostTask(
-        FROM_HERE, run_loop_->QuitClosure());
+    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                  run_loop_->QuitClosure());
   }
   void OnError(Fetcher* fetcher, const std::string& error_message) override {
     CheckSameFetcher(fetcher);
     error_message_ = error_message;
-    base::MessageLoop::current()->task_runner()->PostTask(
-        FROM_HERE, run_loop_->QuitClosure());
+    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                  run_loop_->QuitClosure());
   }
 
   Fetcher* fetcher() const { return fetcher_; }
diff --git a/cobalt/loader/fetcher_test.h b/cobalt/loader/fetcher_test.h
index 8a14c4f..2f878c1 100644
--- a/cobalt/loader/fetcher_test.h
+++ b/cobalt/loader/fetcher_test.h
@@ -15,16 +15,16 @@
 #ifndef COBALT_LOADER_FETCHER_TEST_H_
 #define COBALT_LOADER_FETCHER_TEST_H_
 
-#include "cobalt/loader/fetcher.h"
-
 #include <string>
 
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "cobalt/loader/fetcher.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
-using ::testing::Invoke;
 using ::testing::_;
+using ::testing::Invoke;
 
 namespace cobalt {
 namespace loader {
@@ -41,13 +41,13 @@
   }
   void OnDone(Fetcher* fetcher) override {
     CheckFetcher(fetcher);
-    base::MessageLoop::current()->task_runner()->PostTask(
-        FROM_HERE, run_loop_->QuitClosure());
+    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                  run_loop_->QuitClosure());
   }
   void OnError(Fetcher* fetcher, const std::string& error) override {
     CheckFetcher(fetcher);
-    base::MessageLoop::current()->task_runner()->PostTask(
-        FROM_HERE, run_loop_->QuitClosure());
+    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                  run_loop_->QuitClosure());
   }
 
   const std::string& data() const { return data_; }
diff --git a/cobalt/loader/file_fetcher.cc b/cobalt/loader/file_fetcher.cc
index 11e62bf..752d051 100644
--- a/cobalt/loader/file_fetcher.cc
+++ b/cobalt/loader/file_fetcher.cc
@@ -18,6 +18,7 @@
 #include "base/files/file_util.h"
 #include "base/location.h"
 #include "base/path_service.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/cobalt_paths.h"
 
 namespace cobalt {
@@ -105,7 +106,7 @@
 FileFetcher::~FileFetcher() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  if (task_runner_ != base::MessageLoop::current()->task_runner()) {
+  if (task_runner_ != base::ThreadTaskRunnerHandle::Get()) {
     // In case we are currently in the middle of a fetch (in which case it will
     // be aborted), invalidate the weak pointers to this FileFetcher object to
     // ensure that we do not process any responses from pending file I/O, which
diff --git a/cobalt/loader/file_fetcher.h b/cobalt/loader/file_fetcher.h
index 097faa1..68dee66 100644
--- a/cobalt/loader/file_fetcher.h
+++ b/cobalt/loader/file_fetcher.h
@@ -27,8 +27,8 @@
 #include "base/files/platform_file.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-#include "base/message_loop/message_loop.h"
 #include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/loader/fetcher.h"
 
 namespace cobalt {
@@ -45,7 +45,7 @@
         : buffer_size(kDefaultBufferSize),
           start_offset(0),
           bytes_to_read(std::numeric_limits<int64>::max()),
-          message_loop_proxy(base::MessageLoop::current()->task_runner()) {}
+          message_loop_proxy(base::ThreadTaskRunnerHandle::Get()) {}
 
     int32 buffer_size;
     int64 start_offset;
diff --git a/cobalt/loader/loader.cc b/cobalt/loader/loader.cc
index 2e768ec..f33aaf9 100644
--- a/cobalt/loader/loader.cc
+++ b/cobalt/loader/loader.cc
@@ -12,14 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <memory>
-
 #include "cobalt/loader/loader.h"
 
+#include <memory>
+#include <utility>
+
 #include "base/bind.h"
 #include "base/compiler_specific.h"
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
 
 namespace cobalt {
 namespace loader {
@@ -161,8 +163,8 @@
   fetcher_ = fetcher_creator_.Run(fetcher_handler_to_decoder_adaptor_.get());
 
   if (fetcher_) {
-    fetcher_->SetLoadTimingInfoCallback(base::Bind(&Loader::set_load_timing_info,
-                                                   base::Unretained(this)));
+    fetcher_->SetLoadTimingInfoCallback(
+        base::Bind(&Loader::set_load_timing_info, base::Unretained(this)));
   }
 
   // Post the error callback on the current message loop in case the loader is
@@ -171,7 +173,7 @@
     fetcher_creator_error_closure_.Reset(
         base::Bind(base::Bind(&Loader::LoadComplete, base::Unretained(this)),
                    std::string("Fetcher was not created.")));
-    base::MessageLoop::current()->task_runner()->PostTask(
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, fetcher_creator_error_closure_.callback());
   }
 }
@@ -180,9 +182,7 @@
   load_timing_info_ = timing_info;
 }
 
-net::LoadTimingInfo Loader::get_load_timing_info() {
-  return load_timing_info_;
-}
+net::LoadTimingInfo Loader::get_load_timing_info() { return load_timing_info_; }
 
 }  // namespace loader
 }  // namespace cobalt
diff --git a/cobalt/loader/loader_factory.cc b/cobalt/loader/loader_factory.cc
index 0cb575e..fcbe2d2 100644
--- a/cobalt/loader/loader_factory.cc
+++ b/cobalt/loader/loader_factory.cc
@@ -31,7 +31,13 @@
       debugger_hooks_(debugger_hooks),
       resource_provider_(resource_provider) {
   if (encoded_image_cache_capacity > 0) {
-    fetcher_cache_.reset(new FetcherCache(name, encoded_image_cache_capacity));
+    fetcher_cache_ = new FetcherCache(name, encoded_image_cache_capacity);
+  }
+}
+
+LoaderFactory::~LoaderFactory() {
+  if (fetcher_cache_) {
+    fetcher_cache_->DestroySoon();
   }
 }
 
diff --git a/cobalt/loader/loader_factory.h b/cobalt/loader/loader_factory.h
index e3f80ff..7602602 100644
--- a/cobalt/loader/loader_factory.h
+++ b/cobalt/loader/loader_factory.h
@@ -47,6 +47,7 @@
                 const base::DebuggerHooks& debugger_hooks,
                 size_t encoded_image_cache_capacity,
                 base::ThreadPriority loader_thread_priority);
+  ~LoaderFactory();
 
   // Creates a loader that fetches and decodes an image.
   std::unique_ptr<Loader> CreateImageLoader(
@@ -108,7 +109,7 @@
   // Used to cache the fetched raw data.  Note that currently the cache is only
   // used to cache Image data.  We may introduce more caches once we want to
   // cache fetched data for other resource types.
-  std::unique_ptr<FetcherCache> fetcher_cache_;
+  scoped_refptr<FetcherCache> fetcher_cache_;
 
   // Used with CLOG to report errors with the image source.
   const base::DebuggerHooks& debugger_hooks_;
diff --git a/cobalt/loader/loader_test.cc b/cobalt/loader/loader_test.cc
index 424f402..fb9f281 100644
--- a/cobalt/loader/loader_test.cc
+++ b/cobalt/loader/loader_test.cc
@@ -12,25 +12,26 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <memory>
-
 #include "cobalt/loader/loader.h"
 
+#include <memory>
+
 #include "base/bind.h"
 #include "base/files/file_util.h"
 #include "base/message_loop/message_loop.h"
 #include "base/optional.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/loader/file_fetcher.h"
 #include "cobalt/loader/text_decoder.h"
 #include "cobalt/web/url_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using ::testing::_;
 using ::testing::InSequence;
 using ::testing::Invoke;
-using ::testing::_;
 
 namespace cobalt {
 namespace loader {
@@ -46,8 +47,8 @@
 
   void OnDone(const Origin&, std::unique_ptr<std::string> text) {
     text_ = *text;
-    base::MessageLoop::current()->task_runner()->PostTask(
-        FROM_HERE, run_loop_->QuitClosure());
+    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                  run_loop_->QuitClosure());
   }
 
   std::string text() { return text_; }
@@ -67,8 +68,8 @@
 
     DLOG(ERROR) << *text;
     if (run_loop_)
-      base::MessageLoop::current()->task_runner()->PostTask(
-          FROM_HERE, run_loop_->QuitClosure());
+      base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                    run_loop_->QuitClosure());
   }
 
  private:
diff --git a/cobalt/loader/net_fetcher.cc b/cobalt/loader/net_fetcher.cc
index 7f43139..d825fc4 100644
--- a/cobalt/loader/net_fetcher.cc
+++ b/cobalt/loader/net_fetcher.cc
@@ -20,6 +20,7 @@
 
 #include "base/memory/ptr_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/loader/cors_preflight.h"
@@ -104,7 +105,7 @@
       request_cross_origin_(false),
       origin_(origin),
       request_script_(options.resource_type == disk_cache::kUncompiledScript),
-      task_runner_(base::MessageLoop::current()->task_runner()),
+      task_runner_(base::ThreadTaskRunnerHandle::Get()),
       skip_fetch_intercept_(options.skip_fetch_intercept),
       will_destroy_current_message_loop_(false),
       main_resource_(main_resource) {
@@ -134,12 +135,13 @@
         net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SEND_AUTH_DATA;
     url_fetcher_->SetLoadFlags(kDisableCookiesLoadFlags);
   }
+  network_module->AddClientHintHeaders(*url_fetcher_);
 
   // Delay the actual start until this function is complete. Otherwise we might
   // call handler's callbacks at an unexpected time- e.g. receiving OnError()
   // while a loader is still being constructed.
-  base::MessageLoop::current()->task_runner()->PostTask(
-      FROM_HERE, start_callback_.callback());
+  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
+                                                start_callback_.callback());
   base::MessageLoop::current()->AddDestructionObserver(this);
 }
 
@@ -178,7 +180,7 @@
   if (will_destroy_current_message_loop_.load()) {
     return;
   }
-  if (task_runner_ != base::MessageLoop::current()->task_runner()) {
+  if (task_runner_ != base::ThreadTaskRunnerHandle::Get()) {
     task_runner_->PostTask(
         FROM_HERE, base::BindOnce(&NetFetcher::OnFetchIntercepted,
                                   base::Unretained(this), std::move(body)));
diff --git a/cobalt/media/base/drm_system.cc b/cobalt/media/base/drm_system.cc
index f92f1a7..f3d0a1f 100644
--- a/cobalt/media/base/drm_system.cc
+++ b/cobalt/media/base/drm_system.cc
@@ -21,6 +21,7 @@
 #include "base/compiler_specific.h"
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/instance_counter.h"
 
 namespace cobalt {
@@ -85,7 +86,7 @@
           key_system, this, OnSessionUpdateRequestGeneratedFunc,
           OnSessionUpdatedFunc, OnSessionKeyStatusesChangedFunc,
           OnServerCertificateUpdatedFunc, OnSessionClosedFunc)),
-      message_loop_(base::MessageLoop::current()->task_runner()),
+      message_loop_(base::ThreadTaskRunnerHandle::Get()),
       ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
       weak_this_(weak_ptr_factory_.GetWeakPtr()) {
   ON_INSTANCE_CREATED(DrmSystem);
diff --git a/cobalt/media/base/pipeline.h b/cobalt/media/base/pipeline.h
index ccb3a1b..b930107 100644
--- a/cobalt/media/base/pipeline.h
+++ b/cobalt/media/base/pipeline.h
@@ -98,6 +98,9 @@
       const GetDecodeTargetGraphicsContextProviderFunc&
           get_decode_target_graphics_context_provider_func,
       bool allow_resume_after_suspend, bool allow_batched_sample_write,
+#if SB_API_VERSION >= 15
+      SbTime audio_write_duration_local, SbTime audio_write_duration_remote,
+#endif  // SB_API_VERSION >= 15
       MediaLog* media_log, DecodeTargetProvider* decode_target_provider);
 
   virtual ~Pipeline() {}
@@ -217,6 +220,9 @@
   // be 0.
   virtual void GetNaturalVideoSize(gfx::Size* out_size) const = 0;
 
+  // Gets the names of audio connectors used by the audio output.
+  virtual std::vector<std::string> GetAudioConnectors() const = 0;
+
   // Return true if loading progress has been made since the last time this
   // method was called.
   virtual bool DidLoadingProgress() const = 0;
diff --git a/cobalt/media/base/sbplayer_bridge.cc b/cobalt/media/base/sbplayer_bridge.cc
index 7194560..1fb4c81 100644
--- a/cobalt/media/base/sbplayer_bridge.cc
+++ b/cobalt/media/base/sbplayer_bridge.cc
@@ -65,11 +65,11 @@
                    SbMediaAudioSampleInfo* sample_info) {
   DCHECK(sample_info);
 
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
   sample_info->stream_info = stream_info;
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
   *sample_info = stream_info;
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION}
+#endif  // SB_API_VERSION >= 15}
 }
 
 void SetStreamInfo(
@@ -89,11 +89,11 @@
                    SbMediaVideoSampleInfo* sample_info) {
   DCHECK(sample_info);
 
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
   sample_info->stream_info = stream_info;
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
   *sample_info = stream_info;
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION}
+#endif  // SB_API_VERSION >= 15}
 }
 
 void SetDiscardPadding(
@@ -110,10 +110,10 @@
     SbMediaAudioSampleInfo* sample_info) {
   DCHECK(sample_info);
 
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
   sample_info->discarded_duration_from_front = discard_padding.first.ToSbTime();
   sample_info->discarded_duration_from_back = discard_padding.second.ToSbTime();
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION}
+#endif  // SB_API_VERSION >= 15}
 }
 
 }  // namespace
@@ -451,6 +451,37 @@
   GetInfo_Locked(video_frames_decoded, video_frames_dropped, media_time);
 }
 
+#if SB_API_VERSION >= 15
+std::vector<SbMediaAudioConfiguration>
+SbPlayerBridge::GetAudioConfigurations() {
+  base::AutoLock auto_lock(lock_);
+
+  if (!SbPlayerIsValid(player_)) {
+    return std::vector<SbMediaAudioConfiguration>();
+  }
+
+  std::vector<SbMediaAudioConfiguration> configurations;
+
+  // Set a limit to avoid infinite loop.
+  constexpr int kMaxAudioConfigurations = 32;
+
+  for (int i = 0; i < kMaxAudioConfigurations; ++i) {
+    SbMediaAudioConfiguration configuration;
+    if (!sbplayer_interface_->GetAudioConfiguration(player_, i,
+                                                    &configuration)) {
+      break;
+    }
+
+    configurations.push_back(configuration);
+  }
+
+  LOG_IF(WARNING, configurations.empty())
+      << "Failed to find any audio configurations.";
+
+  return configurations;
+}
+#endif  // SB_API_VERSION >= 15
+
 #if SB_HAS(PLAYER_WITH_URL)
 void SbPlayerBridge::GetUrlPlayerBufferedTimeRanges(
     base::TimeDelta* buffer_start_time, base::TimeDelta* buffer_length_time) {
@@ -491,11 +522,11 @@
 
   DCHECK(SbPlayerIsValid(player_));
 
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
   SbPlayerInfo out_player_info;
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
   SbPlayerInfo2 out_player_info;
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#endif  // SB_API_VERSION >= 15
   sbplayer_interface_->GetInfo(player_, &out_player_info);
 
   video_stream_info_.frame_width = out_player_info.frame_width;
@@ -514,11 +545,11 @@
 
   DCHECK(SbPlayerIsValid(player_));
 
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
   SbPlayerInfo info;
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
   SbPlayerInfo2 info;
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#endif  // SB_API_VERSION >= 15
   sbplayer_interface_->GetInfo(player_, &info);
   if (info.duration == SB_PLAYER_NO_DURATION) {
     // URL-based player may not have loaded asset yet, so map no duration to 0.
@@ -536,11 +567,11 @@
 
   DCHECK(SbPlayerIsValid(player_));
 
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
   SbPlayerInfo info;
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
   SbPlayerInfo2 info;
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#endif  // SB_API_VERSION >= 15
   sbplayer_interface_->GetInfo(player_, &info);
   return base::TimeDelta::FromMicroseconds(info.start_date);
 }
@@ -700,22 +731,22 @@
 
   SbPlayerCreationParam creation_param = {};
   creation_param.drm_system = drm_system_;
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
   creation_param.audio_stream_info = audio_stream_info_;
   creation_param.video_stream_info = video_stream_info_;
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
   creation_param.audio_sample_info = audio_stream_info_;
   creation_param.video_sample_info = video_stream_info_;
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#endif  // SB_API_VERSION >= 15
 
   // TODO: This is temporary for supporting background media playback.
   //       Need to be removed with media refactor.
   if (!is_visible) {
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
     creation_param.video_stream_info.codec = kSbMediaVideoCodecNone;
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
     creation_param.video_sample_info.codec = kSbMediaVideoCodecNone;
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#endif  // SB_API_VERSION >= 15
   }
   creation_param.output_mode = output_mode_;
   DCHECK_EQ(sbplayer_interface_->GetPreferredOutputMode(&creation_param),
@@ -932,11 +963,11 @@
 
   DCHECK(SbPlayerIsValid(player_));
 
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
   SbPlayerInfo info;
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
   SbPlayerInfo2 info;
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#endif  // SB_API_VERSION >= 15
   sbplayer_interface_->GetInfo(player_, &info);
 
   if (media_time) {
@@ -1183,13 +1214,13 @@
     bool prefer_decode_to_texture) const {
   SbPlayerCreationParam creation_param = {};
   creation_param.drm_system = drm_system_;
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
   creation_param.audio_stream_info = audio_stream_info_;
   creation_param.video_stream_info = video_stream_info_;
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
   creation_param.audio_sample_info = audio_stream_info_;
   creation_param.video_sample_info = video_stream_info_;
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#endif  // SB_API_VERSION >= 15
 
   // Try to choose |kSbPlayerOutputModeDecodeToTexture| when
   // |prefer_decode_to_texture| is true.
diff --git a/cobalt/media/base/sbplayer_bridge.h b/cobalt/media/base/sbplayer_bridge.h
index 3ea0b76..ca7163e 100644
--- a/cobalt/media/base/sbplayer_bridge.h
+++ b/cobalt/media/base/sbplayer_bridge.h
@@ -115,6 +115,9 @@
   void SetPlaybackRate(double playback_rate);
   void GetInfo(uint32* video_frames_decoded, uint32* video_frames_dropped,
                base::TimeDelta* media_time);
+#if SB_API_VERSION >= 15
+  std::vector<SbMediaAudioConfiguration> GetAudioConfigurations();
+#endif  // SB_API_VERSION >= 15
 
 #if SB_HAS(PLAYER_WITH_URL)
   void GetUrlPlayerBufferedTimeRanges(base::TimeDelta* buffer_start_time,
diff --git a/cobalt/media/base/sbplayer_interface.cc b/cobalt/media/base/sbplayer_interface.cc
index a3777f0..df33c90 100644
--- a/cobalt/media/base/sbplayer_interface.cc
+++ b/cobalt/media/base/sbplayer_interface.cc
@@ -61,11 +61,11 @@
 
 void DefaultSbPlayerInterface::Seek(SbPlayer player, SbTime seek_to_timestamp,
                                     int ticket) {
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
   SbPlayerSeek(player, seek_to_timestamp, ticket);
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
   SbPlayerSeek2(player, seek_to_timestamp, ticket);
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#endif  // SB_API_VERSION >= 15
 }
 
 bool DefaultSbPlayerInterface::IsEnhancedAudioExtensionEnabled() const {
@@ -76,13 +76,13 @@
     SbPlayer player, SbMediaType sample_type,
     const SbPlayerSampleInfo* sample_infos, int number_of_sample_infos) {
   DCHECK(!IsEnhancedAudioExtensionEnabled());
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
   SbPlayerWriteSamples(player, sample_type, sample_infos,
                        number_of_sample_infos);
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
   SbPlayerWriteSample2(player, sample_type, sample_infos,
                        number_of_sample_infos);
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#endif  // SB_API_VERSION >= 15
 }
 
 void DefaultSbPlayerInterface::WriteSamples(
@@ -119,13 +119,13 @@
 }
 
 void DefaultSbPlayerInterface::GetInfo(SbPlayer player,
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
                                        SbPlayerInfo* out_player_info) {
   SbPlayerGetInfo(player, out_player_info);
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
                                        SbPlayerInfo2* out_player_info2) {
   SbPlayerGetInfo2(player, out_player_info2);
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#endif  // SB_API_VERSION >= 15
 }
 
 SbDecodeTarget DefaultSbPlayerInterface::GetCurrentFrame(SbPlayer player) {
@@ -159,5 +159,15 @@
 }
 #endif  // SB_HAS(PLAYER_WITH_URL)
 
+#if SB_API_VERSION >= 15
+
+bool DefaultSbPlayerInterface::GetAudioConfiguration(
+    SbPlayer player, int index,
+    SbMediaAudioConfiguration* out_audio_configuration) {
+  return SbPlayerGetAudioConfiguration(player, index, out_audio_configuration);
+}
+
+#endif  // SB_API_VERSION >= 15
+
 }  // namespace media
 }  // namespace cobalt
diff --git a/cobalt/media/base/sbplayer_interface.h b/cobalt/media/base/sbplayer_interface.h
index 893f1a2..d9a3174 100644
--- a/cobalt/media/base/sbplayer_interface.h
+++ b/cobalt/media/base/sbplayer_interface.h
@@ -59,11 +59,11 @@
   virtual bool SetPlaybackRate(SbPlayer player, double playback_rate) = 0;
   virtual void SetVolume(SbPlayer player, double volume) = 0;
 
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
   virtual void GetInfo(SbPlayer player, SbPlayerInfo* out_player_info) = 0;
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
   virtual void GetInfo(SbPlayer player, SbPlayerInfo2* out_player_info2) = 0;
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#endif  // SB_API_VERSION >= 15
   virtual SbDecodeTarget GetCurrentFrame(SbPlayer player) = 0;
 
 #if SB_HAS(PLAYER_WITH_URL)
@@ -81,6 +81,12 @@
       SbPlayer player, SbUrlPlayerExtraInfo* out_url_player_info) = 0;
 #endif  // SB_HAS(PLAYER_WITH_URL)
 
+#if SB_API_VERSION >= 15
+  virtual bool GetAudioConfiguration(
+      SbPlayer player, int index,
+      SbMediaAudioConfiguration* out_audio_configuration) = 0;
+#endif  // SB_API_VERSION >= 15
+
   // disabled by default, but can be enabled via h5vcc setting.
   void EnableCValStats(bool should_enable) {
     cval_stats_.Enable(should_enable);
@@ -118,11 +124,11 @@
                  int height) override;
   bool SetPlaybackRate(SbPlayer player, double playback_rate) override;
   void SetVolume(SbPlayer player, double volume) override;
-#if SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#if SB_API_VERSION >= 15
   void GetInfo(SbPlayer player, SbPlayerInfo* out_player_info) override;
-#else   // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#else   // SB_API_VERSION >= 15
   void GetInfo(SbPlayer player, SbPlayerInfo2* out_player_info2) override;
-#endif  // SB_API_VERSION >= SB_MEDIA_ENHANCED_AUDIO_API_VERSION
+#endif  // SB_API_VERSION >= 15
   SbDecodeTarget GetCurrentFrame(SbPlayer player) override;
 
 #if SB_HAS(PLAYER_WITH_URL)
@@ -138,6 +144,12 @@
       SbPlayer player, SbUrlPlayerExtraInfo* out_url_player_info) override;
 #endif  // SB_HAS(PLAYER_WITH_URL)
 
+#if SB_API_VERSION >= 15
+  bool GetAudioConfiguration(
+      SbPlayer player, int index,
+      SbMediaAudioConfiguration* out_audio_configuration) override;
+#endif  // SB_API_VERSION >= 15
+
  private:
   void (*enhanced_audio_player_write_samples_)(
       SbPlayer player, SbMediaType sample_type,
diff --git a/cobalt/media/base/sbplayer_pipeline.cc b/cobalt/media/base/sbplayer_pipeline.cc
index 78d7b6b..9523a9e 100644
--- a/cobalt/media/base/sbplayer_pipeline.cc
+++ b/cobalt/media/base/sbplayer_pipeline.cc
@@ -36,6 +36,7 @@
 #include "cobalt/media/base/playback_statistics.h"
 #include "cobalt/media/base/sbplayer_bridge.h"
 #include "cobalt/media/base/sbplayer_set_bounds_helper.h"
+#include "starboard/common/media.h"
 #include "starboard/common/string.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/time.h"
@@ -66,6 +67,7 @@
 using ::media::PipelineStatistics;
 using ::media::PipelineStatusCallback;
 using ::media::VideoDecoderConfig;
+using ::starboard::GetMediaAudioConnectorName;
 
 static const int kRetryDelayAtSuspendInMilliseconds = 100;
 
@@ -90,6 +92,37 @@
 #endif  // SB_HAS(PLAYER_WITH_URL)
 };
 
+#if SB_API_VERSION >= 15
+bool HasRemoteAudioOutputs(
+    const std::vector<SbMediaAudioConfiguration>& configurations) {
+  for (auto&& configuration : configurations) {
+    const auto connector = configuration.connector;
+    switch (connector) {
+      case kSbMediaAudioConnectorUnknown:
+      case kSbMediaAudioConnectorAnalog:
+      case kSbMediaAudioConnectorBuiltIn:
+      case kSbMediaAudioConnectorHdmi:
+      case kSbMediaAudioConnectorSpdif:
+      case kSbMediaAudioConnectorUsb:
+        LOG(INFO) << "Encountered local audio connector: "
+                  << GetMediaAudioConnectorName(connector);
+        break;
+      case kSbMediaAudioConnectorBluetooth:
+      case kSbMediaAudioConnectorRemoteWired:
+      case kSbMediaAudioConnectorRemoteWireless:
+      case kSbMediaAudioConnectorRemoteOther:
+        LOG(INFO) << "Encountered remote audio connector: "
+                  << GetMediaAudioConnectorName(connector);
+        return true;
+    }
+  }
+
+  LOG(INFO) << "No remote audio outputs found.";
+
+  return false;
+}
+#endif  // SB_API_VERSION >= 15
+
 // SbPlayerPipeline is a PipelineBase implementation that uses the SbPlayer
 // interface internally.
 class MEDIA_EXPORT SbPlayerPipeline : public Pipeline,
@@ -103,6 +136,9 @@
       const GetDecodeTargetGraphicsContextProviderFunc&
           get_decode_target_graphics_context_provider_func,
       bool allow_resume_after_suspend, bool allow_batched_sample_write,
+#if SB_API_VERSION >= 15
+      SbTime audio_write_duration_local, SbTime audio_write_duration_remote,
+#endif  // SB_API_VERSION >= 15
       MediaLog* media_log, DecodeTargetProvider* decode_target_provider);
   ~SbPlayerPipeline() override;
 
@@ -148,6 +184,7 @@
   TimeDelta GetMediaStartDate() const override;
 #endif  // SB_HAS(PLAYER_WITH_URL)
   void GetNaturalVideoSize(gfx::Size* out_size) const override;
+  std::vector<std::string> GetAudioConnectors() const override;
 
   bool DidLoadingProgress() const override;
   PipelineStatistics GetStatistics() const override;
@@ -322,19 +359,31 @@
 
   DecodeTargetProvider* decode_target_provider_;
 
+#if SB_API_VERSION >= 15
+  const SbTime audio_write_duration_local_;
+  const SbTime audio_write_duration_remote_;
+
+  // The two variables below should always contain the same value.  They are
+  // kept as separate variables so we can keep the existing implementation as
+  // is, which simplifies the implementation across multiple Starboard versions.
+  SbTime audio_write_duration_ = 0;
+  SbTime audio_write_duration_for_preroll_ = audio_write_duration_;
+#else   // SB_API_VERSION >= 15
   // Read audio from the stream if |timestamp_of_last_written_audio_| is less
-  // than |seek_time_| + |kAudioPrerollLimit|, this effectively allows 10
-  // seconds of audio to be written to the SbPlayer after playback startup or
-  // seek.
-  static const SbTime kAudioPrerollLimit = 10 * kSbTimeSecond;
-  // Don't read audio from the stream more than |kAudioLimit| ahead of the
-  // current media time during playing.
-  static const SbTime kAudioLimit = kSbTimeSecond;
+  // than |seek_time_| + |audio_write_duration_for_preroll_|, this effectively
+  // allows 10 seconds of audio to be written to the SbPlayer after playback
+  // startup or seek.
+  SbTime audio_write_duration_for_preroll_ = 10 * kSbTimeSecond;
+  // Don't read audio from the stream more than |audio_write_duration_| ahead of
+  // the current media time during playing.
+  SbTime audio_write_duration_ = kSbTimeSecond;
+#endif  // SB_API_VERSION >= 15
   // Only call GetMediaTime() from OnNeedData if it has been
   // |kMediaTimeCheckInterval| since the last call to GetMediaTime().
   static const SbTime kMediaTimeCheckInterval = 0.1 * kSbTimeSecond;
   // Timestamp for the last written audio.
   SbTime timestamp_of_last_written_audio_ = 0;
+
   // Last media time reported by GetMediaTime().
   base::CVal<SbTime> last_media_time_;
   // Time when we last checked the media time.
@@ -359,6 +408,9 @@
     const GetDecodeTargetGraphicsContextProviderFunc&
         get_decode_target_graphics_context_provider_func,
     bool allow_resume_after_suspend, bool allow_batched_sample_write,
+#if SB_API_VERSION >= 15
+    SbTime audio_write_duration_local, SbTime audio_write_duration_remote,
+#endif  // SB_API_VERSION >= 15
     MediaLog* media_log, DecodeTargetProvider* decode_target_provider)
     : pipeline_identifier_(
           base::StringPrintf("%X", g_pipeline_identifier_counter++)),
@@ -400,6 +452,10 @@
                     kSbPlayerStateInitialized,
                     "The underlying SbPlayer state of the media pipeline."),
       decode_target_provider_(decode_target_provider),
+#if SB_API_VERSION >= 15
+      audio_write_duration_local_(audio_write_duration_local),
+      audio_write_duration_remote_(audio_write_duration_remote),
+#endif  // SB_API_VERSION >= 15
       last_media_time_(base::StringPrintf("Media.Pipeline.%s.LastMediaTime",
                                           pipeline_identifier_.c_str()),
                        0, "Last media time reported by the underlying player."),
@@ -408,7 +464,12 @@
                              pipeline_identifier_.c_str()),
           "", "The max video capabilities required for the media pipeline."),
       playback_statistics_(pipeline_identifier_) {
-  SbMediaSetAudioWriteDuration(kAudioLimit);
+#if SB_API_VERSION < 15
+  SbMediaSetAudioWriteDuration(audio_write_duration_);
+  LOG(INFO) << "Setting audio write duration to " << audio_write_duration_
+            << ", the duration during preroll is "
+            << audio_write_duration_for_preroll_;
+#endif  // SB_API_VERSION < 15
 }
 
 SbPlayerPipeline::~SbPlayerPipeline() { DCHECK(!player_bridge_); }
@@ -790,6 +851,26 @@
   *out_size = natural_size_;
 }
 
+std::vector<std::string> SbPlayerPipeline::GetAudioConnectors() const {
+#if SB_API_VERSION >= 15
+  base::AutoLock auto_lock(lock_);
+  if (!player_bridge_) {
+    return std::vector<std::string>();
+  }
+
+  std::vector<std::string> connectors;
+
+  auto configurations = player_bridge_->GetAudioConfigurations();
+  for (auto&& configuration : configurations) {
+    connectors.push_back(GetMediaAudioConnectorName(configuration.connector));
+  }
+
+  return connectors;
+#else   // SB_API_VERSION >= 15
+  return std::vector<std::string>();
+#endif  // SB_API_VERSION >= 15
+}
+
 bool SbPlayerPipeline::DidLoadingProgress() const {
   base::AutoLock auto_lock(lock_);
   bool ret = did_loading_progress_;
@@ -1045,6 +1126,18 @@
         *decode_to_texture_output_mode_, decode_target_provider_,
         max_video_capabilities_, pipeline_identifier_));
     if (player_bridge_->IsValid()) {
+#if SB_API_VERSION >= 15
+      // TODO(b/267678497): When `player_bridge_->GetAudioConfigurations()`
+      // returns no audio configurations, update the write durations again
+      // before the SbPlayer reaches `kSbPlayerStatePresenting`.
+      audio_write_duration_for_preroll_ = audio_write_duration_ =
+          HasRemoteAudioOutputs(player_bridge_->GetAudioConfigurations())
+              ? audio_write_duration_remote_
+              : audio_write_duration_local_;
+      LOG(INFO) << "SbPlayerBridge created, with audio write duration at "
+                << audio_write_duration_for_preroll_;
+#endif  // SB_API_VERSION >= 15
+
       SetPlaybackRateTask(playback_rate_);
       SetVolumeTask(volume_);
     } else {
@@ -1278,17 +1371,18 @@
       GetMediaTime();
     }
 
-    // Delay reading audio more than |kAudioLimit| ahead of playback after the
-    // player has received enough audio for preroll, taking into account that
-    // our estimate of playback time might be behind by
+    // Delay reading audio more than |audio_write_duration_| ahead of playback
+    // after the player has received enough audio for preroll, taking into
+    // account that our estimate of playback time might be behind by
     // |kMediaTimeCheckInterval|.
     if (timestamp_of_last_written_audio_ - seek_time_.ToSbTime() >
-        kAudioPrerollLimit) {
+        audio_write_duration_for_preroll_) {
       // The estimated time ahead of playback may be negative if no audio has
       // been written.
       SbTime time_ahead_of_playback =
           timestamp_of_last_written_audio_ - last_media_time_;
-      if (time_ahead_of_playback > (kAudioLimit + kMediaTimeCheckInterval)) {
+      if (time_ahead_of_playback >
+          (audio_write_duration_ + kMediaTimeCheckInterval)) {
         task_runner_->PostDelayedTask(
             FROM_HERE,
             base::Bind(&SbPlayerPipeline::DelayedNeedData, this, max_buffers),
@@ -1358,6 +1452,14 @@
         playback_statistics_.OnPresenting(
             video_stream_->video_decoder_config());
       }
+#if SB_API_VERSION >= 15
+      audio_write_duration_for_preroll_ = audio_write_duration_ =
+          HasRemoteAudioOutputs(player_bridge_->GetAudioConfigurations())
+              ? audio_write_duration_remote_
+              : audio_write_duration_local_;
+      LOG(INFO) << "SbPlayerBridge reaches kSbPlayerStatePresenting, with audio"
+                << " write duration at " << audio_write_duration_;
+#endif  // SB_API_VERSION >= 15
       break;
     }
     case kSbPlayerStateEndOfStream:
@@ -1612,12 +1714,18 @@
     const GetDecodeTargetGraphicsContextProviderFunc&
         get_decode_target_graphics_context_provider_func,
     bool allow_resume_after_suspend, bool allow_batched_sample_write,
+#if SB_API_VERSION >= 15
+    SbTime audio_write_duration_local, SbTime audio_write_duration_remote,
+#endif  // SB_API_VERSION >= 15
     MediaLog* media_log, DecodeTargetProvider* decode_target_provider) {
-  return new SbPlayerPipeline(interface, window, task_runner,
-                              get_decode_target_graphics_context_provider_func,
-                              allow_resume_after_suspend,
-                              allow_batched_sample_write, media_log,
-                              decode_target_provider);
+  return new SbPlayerPipeline(
+      interface, window, task_runner,
+      get_decode_target_graphics_context_provider_func,
+      allow_resume_after_suspend, allow_batched_sample_write,
+#if SB_API_VERSION >= 15
+      audio_write_duration_local, audio_write_duration_remote,
+#endif  // SB_API_VERSION >= 15
+      media_log, decode_target_provider);
 }
 
 }  // namespace media
diff --git a/cobalt/media/media_module.cc b/cobalt/media/media_module.cc
index 95965a5..00db9fe 100644
--- a/cobalt/media/media_module.cc
+++ b/cobalt/media/media_module.cc
@@ -194,6 +194,18 @@
     LOG(INFO) << (value ? "Enabling" : "Disabling")
               << " media metrics collection.";
     return true;
+#if SB_API_VERSION >= 15
+  } else if (name == "AudioWriteDurationLocal" && value > 0) {
+    audio_write_duration_local_ = value;
+    LOG(INFO) << "Set AudioWriteDurationLocal to "
+              << audio_write_duration_local_;
+    return true;
+  } else if (name == "AudioWriteDurationRemote" && value > 0) {
+    audio_write_duration_remote_ = value;
+    LOG(INFO) << "Set AudioWriteDurationRemote to "
+              << audio_write_duration_remote_;
+    return true;
+#endif  // SB_API_VERSION >= 15
   }
   return false;
 }
@@ -211,7 +223,11 @@
       base::Bind(&MediaModule::GetSbDecodeTargetGraphicsContextProvider,
                  base::Unretained(this)),
       client, this, options_.allow_resume_after_suspend,
-      allow_batched_sample_write_, &media_log_));
+      allow_batched_sample_write_,
+#if SB_API_VERSION >= 15
+      audio_write_duration_local_, audio_write_duration_remote_,
+#endif  // SB_API_VERSION >= 15
+      &media_log_));
 }
 
 void MediaModule::Suspend() {
diff --git a/cobalt/media/media_module.h b/cobalt/media/media_module.h
index 6ca47b8..59a753e 100644
--- a/cobalt/media/media_module.h
+++ b/cobalt/media/media_module.h
@@ -35,6 +35,7 @@
 #include "cobalt/render_tree/resource_provider.h"
 #include "cobalt/system_window/system_window.h"
 #include "starboard/common/mutex.h"
+#include "starboard/player.h"
 #include "third_party/chromium/media/base/media_log.h"
 
 namespace cobalt {
@@ -123,6 +124,11 @@
 
   bool allow_batched_sample_write_ = false;
 
+#if SB_API_VERSION >= 15
+  SbTime audio_write_duration_local_ = kSbPlayerWriteDurationLocal;
+  SbTime audio_write_duration_remote_ = kSbPlayerWriteDurationRemote;
+#endif  // SB_API_VERSION >= 15
+
   DecoderBufferAllocator decoder_buffer_allocator_;
 };
 
diff --git a/cobalt/media/player/web_media_player.h b/cobalt/media/player/web_media_player.h
index 1322473..459ec6e 100644
--- a/cobalt/media/player/web_media_player.h
+++ b/cobalt/media/player/web_media_player.h
@@ -130,6 +130,9 @@
   virtual int GetNaturalWidth() const = 0;
   virtual int GetNaturalHeight() const = 0;
 
+  // Names of audio connectors used by the playback.
+  virtual std::vector<std::string> GetAudioConnectors() const = 0;
+
   // Getters of playback state.
   virtual bool IsPaused() const = 0;
   virtual bool IsSeeking() const = 0;
diff --git a/cobalt/media/player/web_media_player_impl.cc b/cobalt/media/player/web_media_player_impl.cc
index 8073366..b17f82e 100644
--- a/cobalt/media/player/web_media_player_impl.cc
+++ b/cobalt/media/player/web_media_player_impl.cc
@@ -118,6 +118,9 @@
         get_decode_target_graphics_context_provider_func,
     WebMediaPlayerClient* client, WebMediaPlayerDelegate* delegate,
     bool allow_resume_after_suspend, bool allow_batched_sample_write,
+#if SB_API_VERSION >= 15
+    SbTime audio_write_duration_local, SbTime audio_write_duration_remote,
+#endif  // SB_API_VERSION >= 15
     ::media::MediaLog* const media_log)
     : pipeline_thread_("media_pipeline"),
       network_state_(WebMediaPlayer::kNetworkStateEmpty),
@@ -146,6 +149,9 @@
       Pipeline::Create(interface, window, pipeline_thread_.task_runner(),
                        get_decode_target_graphics_context_provider_func,
                        allow_resume_after_suspend_, allow_batched_sample_write_,
+#if SB_API_VERSION >= 15
+                       audio_write_duration_local, audio_write_duration_remote,
+#endif  // SB_API_VERSION >= 15
                        media_log_, decode_target_provider_.get());
 
   // Also we want to be notified of |main_loop_| destruction.
@@ -428,6 +434,11 @@
   return size.height();
 }
 
+std::vector<std::string> WebMediaPlayerImpl::GetAudioConnectors() const {
+  DCHECK_EQ(main_loop_, base::MessageLoop::current());
+  return pipeline_->GetAudioConnectors();
+}
+
 bool WebMediaPlayerImpl::IsPaused() const {
   DCHECK_EQ(main_loop_, base::MessageLoop::current());
 
diff --git a/cobalt/media/player/web_media_player_impl.h b/cobalt/media/player/web_media_player_impl.h
index b64f318..4f0c6a9 100644
--- a/cobalt/media/player/web_media_player_impl.h
+++ b/cobalt/media/player/web_media_player_impl.h
@@ -110,6 +110,10 @@
                      WebMediaPlayerDelegate* delegate,
                      bool allow_resume_after_suspend,
                      bool allow_batched_sample_write,
+#if SB_API_VERSION >= 15
+                     SbTime audio_write_duration_local,
+                     SbTime audio_write_duration_remote,
+#endif  // SB_API_VERSION >= 15
                      ::media::MediaLog* const media_log);
   ~WebMediaPlayerImpl() override;
 
@@ -146,6 +150,9 @@
   int GetNaturalWidth() const override;
   int GetNaturalHeight() const override;
 
+  // Names of audio connectors used by the playback.
+  std::vector<std::string> GetAudioConnectors() const override;
+
   // Getters of playback state.
   bool IsPaused() const override;
   bool IsSeeking() const override;
diff --git a/cobalt/media/progressive/demuxer_fuzzer.cc b/cobalt/media/progressive/demuxer_fuzzer.cc
index d7be774..5e1755a 100644
--- a/cobalt/media/progressive/demuxer_fuzzer.cc
+++ b/cobalt/media/progressive/demuxer_fuzzer.cc
@@ -19,6 +19,7 @@
 #include "base/compiler_specific.h"
 #include "base/files/file_path.h"
 #include "base/memory/ref_counted.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/wrap_main.h"
 #include "cobalt/media/base/bind_to_loop.h"
 #include "cobalt/media/base/pipeline_status.h"
@@ -45,9 +46,8 @@
  public:
   explicit DemuxerFuzzer(const std::vector<uint8>& content)
       : error_occurred_(false), eos_count_(0), stopped_(false) {
-    demuxer_ =
-        new ProgressiveDemuxer(base::MessageLoop::current()->task_runner(),
-                               new InMemoryDataSource(content));
+    demuxer_ = new ProgressiveDemuxer(base::ThreadTaskRunnerHandle::Get(),
+                                      new InMemoryDataSource(content));
   }
 
   void Fuzz() {
diff --git a/cobalt/media/progressive/progressive_demuxer.cc b/cobalt/media/progressive/progressive_demuxer.cc
index 3a30c8d..932b45e 100644
--- a/cobalt/media/progressive/progressive_demuxer.cc
+++ b/cobalt/media/progressive/progressive_demuxer.cc
@@ -24,6 +24,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/task_runner_util.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/media/base/data_source.h"
@@ -409,7 +410,7 @@
 }
 
 void ProgressiveDemuxer::Download(scoped_refptr<DecoderBuffer> buffer) {
-  DCHECK(blocking_thread_.task_runner()->BelongsToCurrentThread());
+  DCHECK(base::ThreadTaskRunnerHandle::Get()->BelongsToCurrentThread());
   // We need a requested_au_ or to have canceled this request and
   // are buffering to a new location for this to make sense
   DCHECK(requested_au_);
@@ -464,7 +465,7 @@
   // Notify host of each disjoint range.
   host_->OnBufferedTimeRangesChanged(buffered);
 
-  blocking_thread_.task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::Bind(&ProgressiveDemuxer::IssueNextRequest,
                             base::Unretained(this)));
 }
@@ -516,7 +517,7 @@
   // We cannot call Request() directly even if this function is also run on
   // |blocking_thread_| as otherwise it is possible that this function is
   // running in a tight loop and seek or stop request has no chance to kick in.
-  blocking_thread_.task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::Bind(&ProgressiveDemuxer::Request, base::Unretained(this), type));
 }
diff --git a/cobalt/media/progressive/progressive_parser.cc b/cobalt/media/progressive/progressive_parser.cc
index 771e386..a7b5b52 100644
--- a/cobalt/media/progressive/progressive_parser.cc
+++ b/cobalt/media/progressive/progressive_parser.cc
@@ -35,8 +35,8 @@
   DCHECK(media_log);
   *parser = NULL;
 
-  // download first 16 bytes of stream to determine file type and extract basic
-  // container-specific stream configuration information
+  // download first kInitialHeaderSize bytes of stream to determine file type
+  // and extract basic container-specific stream configuration information
   uint8 header[kInitialHeaderSize];
   int bytes_read = reader->BlockingRead(0, kInitialHeaderSize, header);
   if (bytes_read != kInitialHeaderSize) {
diff --git a/cobalt/media/sandbox/web_media_player_helper.cc b/cobalt/media/sandbox/web_media_player_helper.cc
index ab5c080..c2364a5 100644
--- a/cobalt/media/sandbox/web_media_player_helper.cc
+++ b/cobalt/media/sandbox/web_media_player_helper.cc
@@ -17,6 +17,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/media/file_data_source.h"
 #include "cobalt/media/url_fetcher_data_source.h"
 #include "third_party/chromium/media/cobalt/ui/gfx/geometry/rect.h"
@@ -88,9 +89,9 @@
     player_->LoadProgressive(video_url, std::move(data_source));
   } else {
     std::unique_ptr<DataSource> data_source(new URLFetcherDataSource(
-        base::MessageLoop::current()->task_runner(), video_url,
-        csp::SecurityCallback(), fetcher_factory->network_module(),
-        loader::kNoCORSMode, loader::Origin()));
+        base::ThreadTaskRunnerHandle::Get(), video_url, csp::SecurityCallback(),
+        fetcher_factory->network_module(), loader::kNoCORSMode,
+        loader::Origin()));
     player_->LoadProgressive(video_url, std::move(data_source));
   }
 
diff --git a/cobalt/media/url_fetcher_data_source.cc b/cobalt/media/url_fetcher_data_source.cc
index 4f25520..a52e1fb 100644
--- a/cobalt/media/url_fetcher_data_source.cc
+++ b/cobalt/media/url_fetcher_data_source.cc
@@ -340,6 +340,7 @@
       std::move(net::URLFetcher::Create(url_, net::URLFetcher::GET, this));
   fetcher_->SetRequestContext(
       network_module_->url_request_context_getter().get());
+  network_module_->AddClientHintHeaders(*fetcher_);
   std::unique_ptr<loader::URLFetcherStringWriter> download_data_writer(
       new loader::URLFetcherStringWriter());
   fetcher_->SaveResponseWithWriter(std::move(download_data_writer));
diff --git a/cobalt/media_capture/media_devices.cc b/cobalt/media_capture/media_devices.cc
index e754755..7a75b18 100644
--- a/cobalt/media_capture/media_devices.cc
+++ b/cobalt/media_capture/media_devices.cc
@@ -18,6 +18,7 @@
 #include <string>
 #include <utility>
 
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/media_capture/media_device_info.h"
 #include "cobalt/media_stream/media_stream.h"
@@ -175,7 +176,7 @@
 
 void MediaDevices::OnMicrophoneStopped() {
   if (javascript_message_loop_->task_runner() !=
-      base::MessageLoop::current()->task_runner()) {
+      base::ThreadTaskRunnerHandle::Get()) {
     javascript_message_loop_->task_runner()->PostTask(
         FROM_HERE, base::Bind(&MediaDevices::OnMicrophoneStopped, weak_this_));
     return;
@@ -195,7 +196,7 @@
 
 void MediaDevices::OnMicrophoneSuccess() {
   if (javascript_message_loop_->task_runner() !=
-      base::MessageLoop::current()->task_runner()) {
+      base::ThreadTaskRunnerHandle::Get()) {
     javascript_message_loop_->task_runner()->PostTask(
         FROM_HERE, base::Bind(&MediaDevices::OnMicrophoneSuccess, this));
     return;
diff --git a/cobalt/media_capture/media_recorder.cc b/cobalt/media_capture/media_recorder.cc
index 7e868a5..05d7851 100644
--- a/cobalt/media_capture/media_recorder.cc
+++ b/cobalt/media_capture/media_recorder.cc
@@ -25,6 +25,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util_starboard.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/media_capture/blob_event.h"
@@ -232,7 +233,7 @@
     : web::EventTarget(settings),
       settings_(settings),
       stream_(stream),
-      javascript_message_loop_(base::MessageLoop::current()->task_runner()),
+      javascript_message_loop_(base::ThreadTaskRunnerHandle::Get()),
       ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
       weak_this_(weak_ptr_factory_.GetWeakPtr()) {
   DCHECK(settings);
diff --git a/cobalt/media_integration_tests/endurance/endurance_test.py b/cobalt/media_integration_tests/endurance/endurance_test.py
index 213ae65..a5492b98d 100644
--- a/cobalt/media_integration_tests/endurance/endurance_test.py
+++ b/cobalt/media_integration_tests/endurance/endurance_test.py
@@ -40,7 +40,7 @@
   """
 
   def __init__(self, *args, **kwargs):
-    super(EnduranceTest, self).__init__(*args, **kwargs)
+    super().__init__(*args, **kwargs)
 
     parser = argparse.ArgumentParser()
     parser.add_argument(
@@ -129,19 +129,19 @@
 
   def GenerateErrorString(self, err_msg):
     return (
-        '%s (running time: %f, player identifier: "%s", is playing: %r, '
-        'playback start time: %f, last media time: %f (updated at %f), '
-        'last written audio timestamp: %d (updated at %f), '
-        'last written video timestamp: %d (updated at %f), '
-        'audio eos written at %f, video eos written at %f, '
-        'playback ended at %f).' %
-        (err_msg, time.time() - self.start_time, self.player_identifier,
-         self.playback_is_playing, self.playback_start_time,
-         self.last_media_time, self.last_media_time_update_time,
-         self.last_written_audio_timestamp, self.last_written_audio_update_time,
-         self.last_written_video_timestamp, self.last_written_video_update_time,
-         self.audio_eos_written_time, self.video_eos_written_time,
-         self.playback_end_time))
+        f'{err_msg} (running time: {time.time() - self.start_time}, '
+        f'player identifier: "{self.player_identifier}", '
+        f'is playing: {self.playback_is_playing}, '
+        f'playback start time: {self.playback_start_time}, '
+        f'last media time: {self.last_media_time} '
+        f'(updated at {self.last_media_time_update_time}), '
+        f'last written audio timestamp: {self.last_written_audio_timestamp} '
+        f'(updated at {self.last_written_audio_update_time}), '
+        f'last written video timestamp: {self.last_written_video_timestamp} '
+        f'(updated at {self.last_written_video_update_time}), '
+        f'audio eos written at {self.audio_eos_written_time}, '
+        f'video eos written at {self.video_eos_written_time}, '
+        f'playback ended at {self.playback_end_time}).')
 
   def SendRandomAction(self, app):
 
@@ -195,8 +195,8 @@
               current_running_time - self.last_state_check_time <
               PLAYER_INITIALIZATION_WAITING_TIMEOUT,
               self.GenerateErrorString(
-                  'Timed out waiting for player initialization (waited: %f).' %
-                  (current_running_time - self.last_state_check_time)))
+                  'Timed out waiting for player initialization (waited: '
+                  f'{current_running_time - self.last_state_check_time}).'))
           continue
         self.last_state_check_time = current_running_time
         # Skip to next playback if it has been played for long time.
@@ -215,8 +215,8 @@
                 current_running_time - self.last_media_time_update_time <
                 MEDIA_TIME_UPDATE_WAITING_TIMEOUT,
                 self.GenerateErrorString(
-                    'Timed out waiting for media time update (waited: %f).' %
-                    (current_running_time - self.last_media_time_update_time)))
+                    'Timed out waiting for media time update (waited: '
+                    f'{current_running_time - self.last_media_time_update_time}).'))  # pylint: disable=line-too-long
           # Check written audio timestamp.
           if (self.last_written_audio_update_time > 0 and
               self.audio_eos_written_time == -1):
@@ -224,9 +224,8 @@
                 current_running_time - self.last_written_audio_update_time <
                 WRITTEN_INPUT_WAITING_TIMEOUT,
                 self.GenerateErrorString(
-                    'Timed out waiting for new audio input (waited: %f).' %
-                    (current_running_time -
-                     self.last_written_audio_update_time)))
+                    'Timed out waiting for new audio input (waited: '
+                    f'{current_running_time - self.last_written_audio_update_time}).'))  # pylint: disable=line-too-long
           # Check written video timestamp.
           if (self.last_written_video_update_time > 0 and
               self.video_eos_written_time == -1):
@@ -234,9 +233,8 @@
                 current_running_time - self.last_written_video_update_time <
                 WRITTEN_INPUT_WAITING_TIMEOUT,
                 self.GenerateErrorString(
-                    'Timed out waiting for new video input (waited: %f).' %
-                    (current_running_time -
-                     self.last_written_video_update_time)))
+                    'Timed out waiting for new video input (waited: '
+                    f'{current_running_time -self.last_written_video_update_time}).'))  # pylint: disable=line-too-long
           # Check if the playback ends properly.
           if (self.audio_eos_written_time > 0 and
               self.video_eos_written_time > 0 and self.playback_end_time > 0):
@@ -244,8 +242,8 @@
                 current_running_time - self.playback_end_time <
                 PLAYBACK_END_WAITING_TIMEOUT,
                 self.GenerateErrorString(
-                    'Timed out waiting for playback to end (waited: %f).' %
-                    (current_running_time - self.playback_end_time,)))
+                    'Timed out waiting for playback to end (waited: '
+                    f'{current_running_time - self.playback_end_time}).'))
 
         # Send random actions.
         if (self.needs_random_action and
diff --git a/cobalt/media_integration_tests/performance/codec_capability.py b/cobalt/media_integration_tests/performance/codec_capability.py
index 4178d07..c843b17 100644
--- a/cobalt/media_integration_tests/performance/codec_capability.py
+++ b/cobalt/media_integration_tests/performance/codec_capability.py
@@ -32,8 +32,8 @@
       for res_name, _ in reversed(MimeStrings.RESOLUTIONS.items()):
         if app.IsMediaTypeSupported(
             MimeStrings.create_video_mime_string(codec_mime, res_name)):
-          return '[%s, %s]' % (codec_name, res_name)
-    return '[%s, n/a]' % (codec_name)
+          return f'[{codec_name}, {res_name}]'
+    return f'[{codec_name}, n/a]'
 
   # Returns a string which shows the max supported channels, or "n/a" if the
   # codec is not supported.
@@ -43,8 +43,8 @@
       for channels in [6, 4, 2]:
         if app.IsMediaTypeSupported(
             MimeStrings.create_audio_mime_string(codec_mime, channels)):
-          return '[%s, %s]' % (codec_name, channels)
-    return '[%s, n/a]' % (codec_name)
+          return f'[{codec_name}, {channels}]'
+    return f'[{codec_name}, n/a]'
 
   def test_video_codec_capability(self):
     app = self.CreateCobaltApp(PlaybackUrls.DEFAULT)
diff --git a/cobalt/media_integration_tests/performance/dropped_frames.py b/cobalt/media_integration_tests/performance/dropped_frames.py
index bcd0215..1b2a2b3 100644
--- a/cobalt/media_integration_tests/performance/dropped_frames.py
+++ b/cobalt/media_integration_tests/performance/dropped_frames.py
@@ -26,7 +26,7 @@
   """
 
   def __init__(self, *args, **kwargs):
-    super(DroppedFrameTest, self).__init__(*args, **kwargs)
+    super().__init__(*args, **kwargs)
 
     parser = argparse.ArgumentParser()
     parser.add_argument('--test_times', default=5, type=int)
diff --git a/cobalt/media_integration_tests/performance/start_latency.py b/cobalt/media_integration_tests/performance/start_latency.py
index cb3f644..54df143 100644
--- a/cobalt/media_integration_tests/performance/start_latency.py
+++ b/cobalt/media_integration_tests/performance/start_latency.py
@@ -28,7 +28,7 @@
   """
 
   def __init__(self, *args, **kwargs):
-    super(StartLatencyTest, self).__init__(*args, **kwargs)
+    super().__init__(*args, **kwargs)
 
     parser = argparse.ArgumentParser()
     parser.add_argument('--test_times', default=5, type=int)
@@ -145,12 +145,12 @@
 ]
 
 for name, playback_url, mime_str in TEST_PARAMETERS:
-  TestCase.CreateTest(StartLatencyTest, 'first_start_latency_%s' % (name),
+  TestCase.CreateTest(StartLatencyTest, f'first_start_latency_{name}',
                       StartLatencyTest.run_first_start_latency_test, name,
                       playback_url, mime_str)
-  TestCase.CreateTest(StartLatencyTest, 'play_pause_latency_%s' % (name),
+  TestCase.CreateTest(StartLatencyTest, f'play_pause_latency_{name}',
                       StartLatencyTest.run_play_pause_latency_test, name,
                       playback_url, mime_str)
-  TestCase.CreateTest(StartLatencyTest, 'fastforward_latency_%s' % (name),
+  TestCase.CreateTest(StartLatencyTest, f'fastforward_latency_{name}',
                       StartLatencyTest.run_fastforward_latency_test, name,
                       playback_url, mime_str)
diff --git a/cobalt/media_integration_tests/test_app.py b/cobalt/media_integration_tests/test_app.py
index ba65c1d..60e1042 100644
--- a/cobalt/media_integration_tests/test_app.py
+++ b/cobalt/media_integration_tests/test_app.py
@@ -30,7 +30,7 @@
 WAIT_UNTIL_ADS_END_DEFAULT_TIMEOUT_SECONDS = 120
 WAIT_UNTIL_MEDIA_TIME_REACHED_DEFAULT_TIMEOUT_SECONDS = 30
 
-ACCOUNT_SELECTOR_ADD_ACCOUNT_TEXT = u'Add account'
+ACCOUNT_SELECTOR_ADD_ACCOUNT_TEXT = 'Add account'
 
 
 def GetValueFromQueryResult(query_result, key, default):
@@ -61,13 +61,13 @@
       try:
         # Try to parse numbers and booleans.
         parsed_value = json.loads(value)
-      except ValueError:
-        raise RuntimeError('Failed to parse query result.')
+      except ValueError as e:
+        raise RuntimeError('Failed to parse query result.') from e
 
       return parsed_value
 
-  raise NotImplementedError('Convertion from (%s) to (%s) is not supported.' %
-                            (value_type, default_value_type))
+  raise NotImplementedError(f'Convertion from ({value_type}) to '
+                            f'({default_value_type}) is not supported.')
 
 
 class AdditionalKeys():
@@ -75,12 +75,12 @@
     Set of special keys codes for media control, corresponding to cobalt
     webdriver AdditionalSpecialKey.
   """
-  MEDIA_NEXT_TRACK = u'\uf000'
-  MEDIA_PREV_TRACK = u'\uf001'
-  MEDIA_STOP = u'\uf002'
-  MEDIA_PLAY_PAUSE = u'\uf003'
-  MEDIA_REWIND = u'\uf004'
-  MEDIA_FAST_FORWARD = u'\uf005'
+  MEDIA_NEXT_TRACK = '\uf000'
+  MEDIA_PREV_TRACK = '\uf001'
+  MEDIA_STOP = '\uf002'
+  MEDIA_PLAY_PAUSE = '\uf003'
+  MEDIA_REWIND = '\uf004'
+  MEDIA_FAST_FORWARD = '\uf005'
 
 
 class Features():
@@ -164,7 +164,7 @@
 
   def __init__(self, query_result=None):
     # If there's no player existing, the query return unicode code "null".
-    if (not query_result is None and not query_result == u'null' and
+    if (not query_result is None and not query_result == 'null' and
         not isinstance(query_result, dict)):
       raise NotImplementedError
 
@@ -311,7 +311,7 @@
     if string == 'playing':
       return 2
     raise NotImplementedError(
-        '"%s" is not a valid media session playback state.' % string)
+        f'"{string}" is not a valid media session playback state.')
 
 
 class MediaSessionState():
@@ -364,7 +364,7 @@
 
   def __init__(self, query_result=None):
     # If there's no player existing, the query return unicode code "null".
-    if (not query_result is None and not query_result == u'null' and
+    if (not query_result is None and not query_result == 'null' and
         not isinstance(query_result, dict)):
       raise NotImplementedError
 
@@ -543,7 +543,8 @@
     try:
       result = self.runner.webdriver.execute_script(script)
     except Exception as e:  # pylint: disable=broad-except
-      raise RuntimeError('Fail to execute script with error (%s).' % (str(e)))
+      raise RuntimeError('Fail to execute script with error '
+                         f'({str(e)}).') from e
     return result
 
   def _OnNewLogLine(self, line):
@@ -682,8 +683,8 @@
       time.sleep(WAIT_INTERVAL_SECONDS)
     execute_interval = time.time() - start_time
     if execute_interval > timeout:
-      raise RuntimeError('WaitUntilReachState timed out after (%f) seconds.' %
-                         (execute_interval))
+      raise RuntimeError('WaitUntilReachState timed out after '
+                         f'({execute_interval}) seconds.')
 
   # The result is an array of overlay header text contents.
   _OVERLAY_QUERY_JS_CODE = """
@@ -791,9 +792,9 @@
 
     execute_interval = time.time() - start_time
     if execute_interval > timeout:
-      raise RuntimeError(
-          'WaitUntilReachState timed out after (%f) seconds, ads_state: (%d).' %
-          (execute_interval, ads_state))
+      raise RuntimeError('WaitUntilReachState timed out after '
+                         f'({execute_interval}) seconds, '
+                         f'ads_state: ({ads_state}).')
 
   def WaitUntilMediaTimeReached(
       self,
@@ -822,8 +823,7 @@
                              adjusted_timeout)
 
   def IsMediaTypeSupported(self, mime):
-    return self.ExecuteScript('return MediaSource.isTypeSupported("%s");' %
-                              (mime))
+    return self.ExecuteScript(f'return MediaSource.isTypeSupported("{mime}");')
 
   def PlayOrPause(self):
     self.SendKeys(AdditionalKeys.MEDIA_PLAY_PAUSE)
diff --git a/cobalt/media_integration_tests/test_case.py b/cobalt/media_integration_tests/test_case.py
index d756418..bcdc1ae 100644
--- a/cobalt/media_integration_tests/test_case.py
+++ b/cobalt/media_integration_tests/test_case.py
@@ -37,7 +37,7 @@
   """The base class for media integration test cases."""
 
   def __init__(self, *args, **kwargs):
-    super(TestCase, self).__init__(*args, **kwargs)
+    super().__init__(*args, **kwargs)
     self.launcher_params = _launcher_params
     self.supported_features = _supported_features
 
@@ -60,6 +60,6 @@
 
   @staticmethod
   def CreateTest(test_class, test_name, test_function, *args):
-    test_method = lambda self: test_function(self, *args)
-    test_method.__name__ = 'test_%s' % test_name
+    test_method = lambda self: test_function(self, *args)  # pylint: disable=unnecessary-lambda-assignment
+    test_method.__name__ = f'test_{test_name}'
     setattr(test_class, test_method.__name__, test_method)
diff --git a/cobalt/media_integration_tests/test_util.py b/cobalt/media_integration_tests/test_util.py
index 81b8e2a..e22cd78 100644
--- a/cobalt/media_integration_tests/test_util.py
+++ b/cobalt/media_integration_tests/test_util.py
@@ -46,11 +46,11 @@
 
   @staticmethod
   def create_video_mime_string(codec, resolution):
-    return '%s; %s' % (codec, MimeStrings.RESOLUTIONS[resolution])
+    return f'{codec}; {MimeStrings.RESOLUTIONS[resolution]}'
 
   @staticmethod
   def create_audio_mime_string(codec, channels):
-    return '%s; channels=%d' % (codec, channels)
+    return f'{codec}; channels={channels}'
 
 
 class PlaybackUrls():
diff --git a/cobalt/media_session/media_session.cc b/cobalt/media_session/media_session.cc
index d0d412c..1a21cb8 100644
--- a/cobalt/media_session/media_session.cc
+++ b/cobalt/media_session/media_session.cc
@@ -14,6 +14,7 @@
 
 #include "cobalt/media_session/media_session.h"
 
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/media_session/media_session_client.h"
 
 namespace cobalt {
@@ -21,7 +22,7 @@
 
 MediaSession::MediaSession()
     : playback_state_(kMediaSessionPlaybackStateNone),
-      task_runner_(base::MessageLoop::current()->task_runner()),
+      task_runner_(base::ThreadTaskRunnerHandle::Get()),
       is_change_task_queued_(false),
       last_position_updated_time_(0) {}
 
@@ -40,7 +41,7 @@
 MediaSession::MediaSession(MediaSessionClient* client)
     : media_session_client_(client),
       playback_state_(kMediaSessionPlaybackStateNone),
-      task_runner_(base::MessageLoop::current()->task_runner()),
+      task_runner_(base::ThreadTaskRunnerHandle::Get()),
       is_change_task_queued_(false),
       last_position_updated_time_(0) {}
 
diff --git a/cobalt/media_stream/microphone_audio_source.cc b/cobalt/media_stream/microphone_audio_source.cc
index 74eff37..3ed0be9 100644
--- a/cobalt/media_stream/microphone_audio_source.cc
+++ b/cobalt/media_stream/microphone_audio_source.cc
@@ -17,6 +17,7 @@
 #include <memory>
 #include <string>
 
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/media_stream/audio_parameters.h"
 #include "cobalt/speech/microphone.h"
 #include "cobalt/speech/microphone_fake.h"
@@ -81,8 +82,7 @@
     // Furthermore, it is an error to destruct the microphone manager
     // without stopping it, so these callbacks are not to be called
     // during the destruction of the object.
-    : javascript_thread_task_runner_(
-          base::MessageLoop::current()->task_runner()),
+    : javascript_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
       successful_open_callback_(successful_open),
       completion_callback_(completion),
@@ -106,8 +106,7 @@
 }
 
 void MicrophoneAudioSource::OnDataCompletion() {
-  if (javascript_thread_task_runner_ !=
-      base::MessageLoop::current()->task_runner()) {
+  if (javascript_thread_task_runner_ != base::ThreadTaskRunnerHandle::Get()) {
     javascript_thread_task_runner_->PostTask(
         FROM_HERE, base::Bind(&MicrophoneAudioSource::OnDataCompletion,
                               weak_ptr_factory_.GetWeakPtr()));
@@ -123,8 +122,7 @@
 }
 
 void MicrophoneAudioSource::OnMicrophoneOpen() {
-  if (javascript_thread_task_runner_ !=
-      base::MessageLoop::current()->task_runner()) {
+  if (javascript_thread_task_runner_ != base::ThreadTaskRunnerHandle::Get()) {
     javascript_thread_task_runner_->PostTask(
         FROM_HERE, base::Bind(&MicrophoneAudioSource::OnMicrophoneOpen,
                               weak_ptr_factory_.GetWeakPtr()));
@@ -139,8 +137,7 @@
 void MicrophoneAudioSource::OnMicrophoneError(
     speech::MicrophoneManager::MicrophoneError error,
     std::string error_message) {
-  if (javascript_thread_task_runner_ !=
-      base::MessageLoop::current()->task_runner()) {
+  if (javascript_thread_task_runner_ != base::ThreadTaskRunnerHandle::Get()) {
     javascript_thread_task_runner_->PostTask(
         FROM_HERE,
         base::Bind(&MicrophoneAudioSource::OnMicrophoneError,
diff --git a/cobalt/network/BUILD.gn b/cobalt/network/BUILD.gn
index bbfa1cb..adf0975 100644
--- a/cobalt/network/BUILD.gn
+++ b/cobalt/network/BUILD.gn
@@ -58,6 +58,10 @@
     "//url",
   ]
 
+  if (!is_gold) {
+    deps += [ "//cobalt/debug:console_command_manager" ]
+  }
+
   if (enable_in_app_dial) {
     deps += [
       # DialService depends on http server.
diff --git a/cobalt/network/net_poster.cc b/cobalt/network/net_poster.cc
index c40b95e..c2d1a1b 100644
--- a/cobalt/network/net_poster.cc
+++ b/cobalt/network/net_poster.cc
@@ -16,7 +16,10 @@
 
 #include <algorithm>
 #include <memory>
+#include <utility>
+#include <vector>
 
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/network/network_module.h"
 #include "net/base/net_errors.h"
 
@@ -30,8 +33,7 @@
 
 void NetPoster::Send(const GURL& url, const std::string& content_type,
                      const std::string& data) {
-  if (network_module_->task_runner() !=
-      base::MessageLoop::current()->task_runner()) {
+  if (network_module_->task_runner() != base::ThreadTaskRunnerHandle::Get()) {
     network_module_->task_runner()->PostTask(
         FROM_HERE, base::Bind(&NetPoster::Send, base::Unretained(this), url,
                               content_type, data));
@@ -47,6 +49,7 @@
   url_fetcher->SetStopOnRedirect(true);
   url_fetcher->SetRequestContext(
       network_module_->url_request_context_getter().get());
+  network_module_->AddClientHintHeaders(*url_fetcher);
 
   if (data.size()) {
     url_fetcher->SetUploadData(content_type, data);
@@ -58,7 +61,7 @@
 void NetPoster::OnURLFetchComplete(const net::URLFetcher* source) {
   // Make sure the thread that created the fetcher is the same one that deletes
   // it. Otherwise we have unsafe access to the fetchers_ list.
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(),
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(),
             network_module_->task_runner());
   net::URLRequestStatus status = source->GetStatus();
   if (!status.is_success()) {
diff --git a/cobalt/network/network_delegate.cc b/cobalt/network/network_delegate.cc
index a4c1158..6e7234e 100644
--- a/cobalt/network/network_delegate.cc
+++ b/cobalt/network/network_delegate.cc
@@ -26,8 +26,10 @@
 namespace network {
 
 NetworkDelegate::NetworkDelegate(net::StaticCookiePolicy::Type cookie_policy,
-                                 network::HTTPSRequirement https_requirement)
+                                 network::HTTPSRequirement https_requirement,
+                                 network::CORSPolicy cors_policy)
     : cookie_policy_(cookie_policy),
+      cors_policy_(cors_policy),
       cookies_enabled_(true),
       https_requirement_(https_requirement) {}
 
diff --git a/cobalt/network/network_delegate.h b/cobalt/network/network_delegate.h
index f44478b..8fbeb8f 100644
--- a/cobalt/network/network_delegate.h
+++ b/cobalt/network/network_delegate.h
@@ -15,6 +15,7 @@
 #ifndef COBALT_NETWORK_NETWORK_DELEGATE_H_
 #define COBALT_NETWORK_NETWORK_DELEGATE_H_
 
+#include <set>
 #include <string>
 
 #include "net/base/static_cookie_policy.h"
@@ -30,6 +31,11 @@
   kHTTPSOptional,
 };
 
+enum CORSPolicy {
+  kCORSRequired,
+  kCORSOptional,
+};
+
 // A NetworkDelegate receives callbacks when network events occur.
 // Each override can specify custom behavior or just add additional logging.
 // We do nothing for most events, but our network delegate
@@ -37,13 +43,16 @@
 class NetworkDelegate : public net::NetworkDelegate {
  public:
   NetworkDelegate(net::StaticCookiePolicy::Type cookie_policy,
-                  network::HTTPSRequirement https_requirement);
+                  network::HTTPSRequirement https_requirement,
+                  network::CORSPolicy cors_policy);
   ~NetworkDelegate() override;
 
   // For debugging, we allow blocking all cookies.
   void set_cookies_enabled(bool enabled) { cookies_enabled_ = enabled; }
   bool cookies_enabled() const { return cookies_enabled_; }
 
+  network::CORSPolicy cors_policy() const { return cors_policy_; }
+
  protected:
   // net::NetworkDelegate implementation.
   int OnBeforeURLRequest(net::URLRequest* request,
@@ -88,10 +97,10 @@
   bool OnCanAccessFile(const net::URLRequest& request,
                        const base::FilePath& original_path,
                        const base::FilePath& absolute_path) const override;
-  virtual bool OnCanEnablePrivacyMode(
-      const GURL& url, const GURL& site_for_cookies) const override;
-  virtual bool OnAreExperimentalCookieFeaturesEnabled() const override;
-  virtual bool OnCancelURLRequestWithPolicyViolatingReferrerHeader(
+  bool OnCanEnablePrivacyMode(const GURL& url,
+                              const GURL& site_for_cookies) const override;
+  bool OnAreExperimentalCookieFeaturesEnabled() const override;
+  bool OnCancelURLRequestWithPolicyViolatingReferrerHeader(
       const net::URLRequest& request, const GURL& target_url,
       const GURL& referrer_url) const override;
 
@@ -100,24 +109,23 @@
   // Reporting is a central mechanism for sending out-of-band error reports
   // to origins from various other components (e.g. HTTP Public Key Pinning,
   // Interventions, or Content Security Policy could potentially use it).
-  virtual bool OnCanQueueReportingReport(
-      const url::Origin& origin) const override;
+  bool OnCanQueueReportingReport(const url::Origin& origin) const override;
 
-  virtual void OnCanSendReportingReports(
-      std::set<url::Origin> origins,
-      base::OnceCallback<void(std::set<url::Origin>)> result_callback)
-      const override;
+  void OnCanSendReportingReports(std::set<url::Origin> origins,
+                                 base::OnceCallback<void(std::set<url::Origin>)>
+                                     result_callback) const override;
 
-  virtual bool OnCanSetReportingClient(const url::Origin& origin,
-                                       const GURL& endpoint) const override;
+  bool OnCanSetReportingClient(const url::Origin& origin,
+                               const GURL& endpoint) const override;
 
-  virtual bool OnCanUseReportingClient(const url::Origin& origin,
-                                       const GURL& endpoint) const override;
+  bool OnCanUseReportingClient(const url::Origin& origin,
+                               const GURL& endpoint) const override;
 
   net::StaticCookiePolicy::Type ComputeCookiePolicy() const;
 
  private:
   net::StaticCookiePolicy::Type cookie_policy_;
+  network::CORSPolicy cors_policy_;
   bool cookies_enabled_;
   network::HTTPSRequirement https_requirement_;
 
diff --git a/cobalt/network/network_module.cc b/cobalt/network/network_module.cc
index f192961..b358c11 100644
--- a/cobalt/network/network_module.cc
+++ b/cobalt/network/network_module.cc
@@ -41,11 +41,14 @@
   Initialize("Null user agent string.", NULL);
 }
 
-NetworkModule::NetworkModule(const std::string& user_agent_string,
-                             storage::StorageManager* storage_manager,
-                             base::EventDispatcher* event_dispatcher,
-                             const Options& options)
-    : storage_manager_(storage_manager), options_(options) {
+NetworkModule::NetworkModule(
+    const std::string& user_agent_string,
+    const std::vector<std::string>& client_hint_headers,
+    storage::StorageManager* storage_manager,
+    base::EventDispatcher* event_dispatcher, const Options& options)
+    : client_hint_headers_(client_hint_headers),
+      storage_manager_(storage_manager),
+      options_(options) {
   Initialize(user_agent_string, event_dispatcher);
 }
 
@@ -170,7 +173,7 @@
 }
 
 void NetworkModule::OnCreate(base::WaitableEvent* creation_event) {
-  DCHECK(task_runner()->BelongsToCurrentThread());
+  DCHECK(task_runner()->RunsTasksInCurrentSequence());
 
   net::NetLog* net_log = NULL;
 #if defined(ENABLE_NETWORK_LOGGING)
@@ -180,8 +183,9 @@
       new URLRequestContext(storage_manager_, options_.custom_proxy, net_log,
                             options_.ignore_certificate_errors, task_runner(),
                             options_.persistent_settings));
-  network_delegate_.reset(
-      new NetworkDelegate(options_.cookie_policy, options_.https_requirement));
+  network_delegate_.reset(new NetworkDelegate(options_.cookie_policy,
+                                              options_.https_requirement,
+                                              options_.cors_policy));
   url_request_context_->set_http_user_agent_settings(
       http_user_agent_settings_.get());
   url_request_context_->set_network_delegate(network_delegate_.get());
@@ -197,5 +201,16 @@
   creation_event->Signal();
 }
 
+void NetworkModule::AddClientHintHeaders(net::URLFetcher& url_fetcher) const {
+  // Check if persistent setting is enabled before adding the headers.
+  if (options_.persistent_settings != nullptr &&
+      options_.persistent_settings->GetPersistentSettingAsBool(
+          kClientHintHeadersEnabledPersistentSettingsKey, false)) {
+    for (const auto& header : client_hint_headers_) {
+      url_fetcher.AddExtraRequestHeader(header);
+    }
+  }
+}
+
 }  // namespace network
 }  // namespace cobalt
diff --git a/cobalt/network/network_module.h b/cobalt/network/network_module.h
index ee04620..1162cac 100644
--- a/cobalt/network/network_module.h
+++ b/cobalt/network/network_module.h
@@ -17,9 +17,10 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/message_loop/message_loop.h"
-#include "base/single_thread_task_runner.h"
+#include "base/sequenced_task_runner.h"
 #include "base/threading/thread.h"
 #include "cobalt/base/event_dispatcher.h"
 #include "cobalt/network/cobalt_net_log.h"
@@ -50,6 +51,9 @@
 
 namespace network {
 
+const char kClientHintHeadersEnabledPersistentSettingsKey[] =
+    "clientHintHeadersEnabled";
+
 class NetworkSystem;
 // NetworkModule wraps various networking-related components such as
 // a URL request context. This is owned by BrowserModule.
@@ -60,12 +64,14 @@
         : cookie_policy(net::StaticCookiePolicy::BLOCK_ALL_THIRD_PARTY_COOKIES),
           ignore_certificate_errors(false),
           https_requirement(network::kHTTPSRequired),
+          cors_policy(network::kCORSRequired),
           preferred_language("en-US"),
           max_network_delay(0),
           persistent_settings(nullptr) {}
     net::StaticCookiePolicy::Type cookie_policy;
     bool ignore_certificate_errors;
     HTTPSRequirement https_requirement;
+    network::CORSPolicy cors_policy;
     std::string preferred_language;
     std::string custom_proxy;
     SbTime max_network_delay;
@@ -77,6 +83,7 @@
 
   // Constructor for production use.
   NetworkModule(const std::string& user_agent_string,
+                const std::vector<std::string>& client_hint_headers,
                 storage::StorageManager* storage_manager,
                 base::EventDispatcher* event_dispatcher,
                 const Options& options = Options());
@@ -94,7 +101,7 @@
   scoped_refptr<URLRequestContextGetter> url_request_context_getter() const {
     return url_request_context_getter_;
   }
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner() const {
+  scoped_refptr<base::SequencedTaskRunner> task_runner() const {
     return thread_->task_runner();
   }
   storage::StorageManager* storage_manager() const { return storage_manager_; }
@@ -109,12 +116,17 @@
 
   void SetEnableQuic(bool enable_quic);
 
+  // Adds the Client Hint Headers to the provided URLFetcher.
+  // It is conditional on kClientHintHeadersEnabledPersistentSettingsKey != 0.
+  void AddClientHintHeaders(net::URLFetcher& url_fetcher) const;
+
  private:
   void Initialize(const std::string& user_agent_string,
                   base::EventDispatcher* event_dispatcher);
   void OnCreate(base::WaitableEvent* creation_event);
   std::unique_ptr<network_bridge::NetPoster> CreateNetPoster();
 
+  std::vector<std::string> client_hint_headers_;
   storage::StorageManager* storage_manager_;
   std::unique_ptr<base::Thread> thread_;
   std::unique_ptr<URLRequestContext> url_request_context_;
diff --git a/cobalt/network/persistent_cookie_store.cc b/cobalt/network/persistent_cookie_store.cc
index c607ddf..e0772eb 100644
--- a/cobalt/network/persistent_cookie_store.cc
+++ b/cobalt/network/persistent_cookie_store.cc
@@ -15,6 +15,7 @@
 #include "cobalt/network/persistent_cookie_store.h"
 
 #include <memory>
+#include <utility>
 #include <vector>
 
 #include "base/bind.h"
@@ -30,7 +31,7 @@
 
 void CookieStorageInit(
     const PersistentCookieStore::LoadedCallback& loaded_callback,
-    scoped_refptr<base::SingleThreadTaskRunner> loaded_callback_task_runner,
+    scoped_refptr<base::SequencedTaskRunner> loaded_callback_task_runner,
     const storage::MemoryStore& memory_store) {
   TRACE_EVENT0("cobalt::network", "PersistentCookieStore::CookieStorageInit()");
 
@@ -79,7 +80,7 @@
 
 void SendEmptyCookieList(
     const PersistentCookieStore::LoadedCallback& loaded_callback,
-    scoped_refptr<base::SingleThreadTaskRunner> loaded_callback_task_runner,
+    scoped_refptr<base::SequencedTaskRunner> loaded_callback_task_runner,
     const storage::MemoryStore& memory_store) {
   loaded_callback_task_runner->PostTask(
       FROM_HERE,
@@ -96,7 +97,7 @@
 
 PersistentCookieStore::PersistentCookieStore(
     storage::StorageManager* storage,
-    scoped_refptr<base::SingleThreadTaskRunner> network_task_runner)
+    scoped_refptr<base::SequencedTaskRunner> network_task_runner)
     : storage_(storage), loaded_callback_task_runner_(network_task_runner) {}
 
 PersistentCookieStore::~PersistentCookieStore() {}
diff --git a/cobalt/network/persistent_cookie_store.h b/cobalt/network/persistent_cookie_store.h
index bbc428c..065b082 100644
--- a/cobalt/network/persistent_cookie_store.h
+++ b/cobalt/network/persistent_cookie_store.h
@@ -18,7 +18,7 @@
 #include <string>
 #include <vector>
 
-#include "base/single_thread_task_runner.h"
+#include "base/sequenced_task_runner.h"
 #include "cobalt/storage/storage_manager.h"
 #include "net/cookies/cookie_monster.h"
 
@@ -29,7 +29,7 @@
  public:
   explicit PersistentCookieStore(
       storage::StorageManager* storage,
-      scoped_refptr<base::SingleThreadTaskRunner> network_task_runner);
+      scoped_refptr<base::SequencedTaskRunner> network_task_runner);
   ~PersistentCookieStore() override;
 
   // net::CookieMonster::PersistentCookieStore methods
@@ -52,7 +52,7 @@
   storage::StorageManager* storage_;
   // This is required because for example cookie store callbacks can only be
   // executed on the network thread.
-  scoped_refptr<base::SingleThreadTaskRunner> loaded_callback_task_runner_;
+  scoped_refptr<base::SequencedTaskRunner> loaded_callback_task_runner_;
 
   DISALLOW_COPY_AND_ASSIGN(PersistentCookieStore);
 };
diff --git a/cobalt/network/persistent_cookie_store_test.cc b/cobalt/network/persistent_cookie_store_test.cc
index a05cde9..b01c257 100644
--- a/cobalt/network/persistent_cookie_store_test.cc
+++ b/cobalt/network/persistent_cookie_store_test.cc
@@ -23,6 +23,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/path_service.h"
 #include "base/strings/stringprintf.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "cobalt/base/cobalt_paths.h"
 #include "cobalt/storage/savegame.h"
@@ -112,7 +113,7 @@
 
     storage_manager_.reset(new storage::StorageManager(options));
     cookie_store_ = new PersistentCookieStore(
-        storage_manager_.get(), base::MessageLoop::current()->task_runner());
+        storage_manager_.get(), base::ThreadTaskRunnerHandle::Get());
   }
 
   ~PersistentCookieStoreTest() {
diff --git a/cobalt/network/url_request_context.cc b/cobalt/network/url_request_context.cc
index eb46dcf..7467b98 100644
--- a/cobalt/network/url_request_context.cc
+++ b/cobalt/network/url_request_context.cc
@@ -70,7 +70,7 @@
 URLRequestContext::URLRequestContext(
     storage::StorageManager* storage_manager, const std::string& custom_proxy,
     net::NetLog* net_log, bool ignore_certificate_errors,
-    scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
+    scoped_refptr<base::SequencedTaskRunner> network_task_runner,
     persistent_storage::PersistentSettings* persistent_settings)
     : ALLOW_THIS_IN_INITIALIZER_LIST(storage_(this))
 #if defined(ENABLE_DEBUGGER)
@@ -230,7 +230,7 @@
 }
 
 void URLRequestContext::SetEnableQuic(bool enable_quic) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   storage_.http_network_session()->SetEnableQuic(enable_quic);
 }
 
diff --git a/cobalt/network/url_request_context.h b/cobalt/network/url_request_context.h
index 8f673c3..6e80479 100644
--- a/cobalt/network/url_request_context.h
+++ b/cobalt/network/url_request_context.h
@@ -19,7 +19,7 @@
 
 #include "base/basictypes.h"
 #include "base/macros.h"
-#include "base/threading/thread_checker.h"
+#include "base/sequence_checker.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
 #include "net/cookies/cookie_monster.h"
 #include "net/log/net_log.h"
@@ -43,7 +43,7 @@
   URLRequestContext(
       storage::StorageManager* storage_manager, const std::string& custom_proxy,
       net::NetLog* net_log, bool ignore_certificate_errors,
-      scoped_refptr<base::SingleThreadTaskRunner> network_task_runner,
+      scoped_refptr<base::SequencedTaskRunner> network_task_runner,
       persistent_storage::PersistentSettings* persistent_settings);
   ~URLRequestContext() override;
 
@@ -54,7 +54,7 @@
   bool using_http_cache();
 
  private:
-  THREAD_CHECKER(thread_checker_);
+  SEQUENCE_CHECKER(sequence_checker_);
   net::URLRequestContextStorage storage_;
   scoped_refptr<net::CookieMonster::PersistentCookieStore>
       persistent_cookie_store_;
diff --git a/cobalt/network/url_request_context_getter.cc b/cobalt/network/url_request_context_getter.cc
index 8db25cc..9bc3791 100644
--- a/cobalt/network/url_request_context_getter.cc
+++ b/cobalt/network/url_request_context_getter.cc
@@ -32,7 +32,7 @@
   return url_request_context_;
 }
 
-scoped_refptr<base::SingleThreadTaskRunner>
+scoped_refptr<base::SequencedTaskRunner>
 URLRequestContextGetter::GetNetworkTaskRunner() const {
   return network_task_runner_;
 }
diff --git a/cobalt/network/url_request_context_getter.h b/cobalt/network/url_request_context_getter.h
index 981db33..0695da5 100644
--- a/cobalt/network/url_request_context_getter.h
+++ b/cobalt/network/url_request_context_getter.h
@@ -30,7 +30,7 @@
 
   // Implementation for net::UrlRequestContextGetter.
   net::URLRequestContext* GetURLRequestContext() override;
-  scoped_refptr<base::SingleThreadTaskRunner> GetNetworkTaskRunner()
+  scoped_refptr<base::SequencedTaskRunner> GetNetworkTaskRunner()
       const override;
 
  protected:
@@ -38,7 +38,7 @@
 
  private:
   URLRequestContext* url_request_context_;
-  scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
+  scoped_refptr<base::SequencedTaskRunner> network_task_runner_;
   DISALLOW_COPY_AND_ASSIGN(URLRequestContextGetter);
 };
 
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color.glsl
deleted file mode 100644
index f611e34..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color.glsl
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-varying vec4 v_color;
-
-void main() {
-  gl_FragColor = v_color;
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_blur.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_blur.glsl
deleted file mode 100644
index e7d1c62..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_blur.glsl
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-
-uniform vec4 u_color;
-uniform vec4 u_blur_rect;
-uniform vec2 u_scale_add;
-
-varying vec2 v_offset;
-
-// Calculate the normalized gaussian integral from (pos.x * k, pos.y * k)
-// where k = sqrt(2) * sigma and pos.x <= pos.y. This is just a 1D filter --
-// the pos.x and pos.y values are expected to be on the same axis.
-float GaussianIntegral(vec2 pos) {
-  // Approximation of the error function.
-  // For x >= 0,
-  //   erf(x) = 1 - 1 / (1 + k1 * x + k2 * x^2 + k3 * x^3 + k4 * x^4)^4
-  //   where k1 = 0.278393, k2 = 0.230389, k3 = 0.000972, k4 = 0.078108.
-  // For y < 0,
-  //   erf(y) = -erf(-y).
-  vec2 s = sign(pos);
-  vec2 a = abs(pos);
-  vec2 t = 1.0 +
-           (0.278393 + (0.230389 + (0.000972 + 0.078108 * a) * a) * a) * a;
-  vec2 t2 = t * t;
-  vec2 erf = s - s / (t2 * t2);
-
-  // erf(x) = the integral of the normalized gaussian from [-x * k, x * k],
-  // where k = sqrt(2) * sigma. Find the integral from (pos.x * k, pos.y * k).
-  return dot(erf, vec2(-0.5, 0.5));
-}
-
-void main() {
-  // Get the integral over the interval occupied by the rectangle. Both
-  // v_offset and u_blur_rect are already scaled for the integral function.
-  float integral = GaussianIntegral(u_blur_rect.xz - v_offset.xx) *
-                   GaussianIntegral(u_blur_rect.yw - v_offset.yy);
-  float blur_scale = integral * u_scale_add.x + u_scale_add.y;
-  gl_FragColor = u_color * blur_scale;
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_blur_rrects.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_blur_rrects.glsl
deleted file mode 100644
index 77d9ab0..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_blur_rrects.glsl
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-
-// The blur rounded rect is split into top and bottom halves.
-// The "start" values represent (left_start.xy, right_start.xy).
-// The "scale" values represent (left_radius.x, 1 / left_radius.y,
-//   right_radius.x, 1 / right_radius.y). The sign of the scale value helps
-//   to translate between position and corner offset values, where the corner
-//   offset is positive if the position is inside the rounded corner.
-uniform vec4 u_blur_start_top;
-uniform vec4 u_blur_start_bottom;
-uniform vec4 u_blur_scale_top;
-uniform vec4 u_blur_scale_bottom;
-
-// The blur extent specifies (blur_size, min_rect_y, max_rect_y, center_rect_y).
-uniform vec4 u_blur_extent;
-
-// The scale_add uniform is used to switch the shader between generating
-// outset shadows and inset shadows. It impacts the shadow gradient and
-// scissor behavior. Use (1, 0) to get an outset shadow with the provided
-// scissor rect behaving as an exclusive scissor, and (-1, 1) to get an
-// inset shadow with scissor rect behaving as an inclusive scissor.
-uniform vec2 u_scale_add;
-
-uniform vec4 u_color;
-
-varying vec2 v_offset;
-varying vec4 v_rcorner;
-
-// Return 0 if the given position is inside the rounded corner, or scale
-// towards 1 as it goes outside a 1-pixel anti-aliasing border.
-// |rcorner| is a vec4 representing (scaled.xy, 1 / radius.xy) with scaled.xy
-//   representing the offset of the current position in terms of radius.xy
-//   (i.e. offset.xy / radius.xy). The scaled.xy values can be negative if the
-//   current position is outside the corner start.
-float IsOutsideRCorner(vec4 rcorner) {
-  // Estimate the distance to an implicit function using
-  //   dist = f(x,y) / length(gradient(f(x,y)))
-  // For an ellipse, f(x,y) = x^2 / a^2 + y^2 / b^2 - 1.
-  highp vec2 scaled = max(rcorner.xy, 0.0);
-  highp float implicit = dot(scaled, scaled) - 1.0;
-
-  // NOTE: To accommodate large radius values using mediump floats, rcorner.zw
-  //   was scaled by kRCornerGradientScale in the vertex attribute data.
-  //   Multiply inv_gradient by kRCornerGradientScale to undo that scaling.
-  const highp float kRCornerGradientScale = 16.0;
-  highp vec2 gradient = 2.0 * scaled * rcorner.zw;
-  highp float inv_gradient = kRCornerGradientScale *
-      inversesqrt(max(dot(gradient, gradient), 0.0001));
-
-  return clamp(0.5 + implicit * inv_gradient, 0.0, 1.0);
-}
-
-// Calculate the normalized gaussian integral from (pos.x * k, pos.y * k)
-// where k = sqrt(2) * sigma and pos.x <= pos.y. This is just a 1D filter --
-// the pos.x and pos.y values are expected to be on the same axis.
-float GaussianIntegral(vec2 pos) {
-  // Approximation of the error function.
-  // For x >= 0,
-  //   erf(x) = 1 - 1 / (1 + k1 * x + k2 * x^2 + k3 * x^3 + k4 * x^4)^4
-  //   where k1 = 0.278393, k2 = 0.230389, k3 = 0.000972, k4 = 0.078108.
-  // For y < 0,
-  //   erf(y) = -erf(-y).
-  vec2 s = sign(pos);
-  vec2 a = abs(pos);
-  vec2 t = 1.0 +
-           (0.278393 + (0.230389 + (0.000972 + 0.078108 * a) * a) * a) * a;
-  vec2 t2 = t * t;
-  vec2 erf = s - s / (t2 * t2);
-
-  // erf(x) = the integral of the normalized gaussian from [-x * k, x * k],
-  // where k = sqrt(2) * sigma. Find the integral from (pos.x * k, pos.y * k).
-  return dot(erf, vec2(-0.5, 0.5));
-}
-
-float GetXBlur(float x, float y) {
-  // Solve for X of the rounded corners at the given Y based on the equation
-  // for an ellipse: x^2 / a^2 + y^2 / b^2 = 1.
-  vec4 corner_start =
-      (y < u_blur_extent.w) ? u_blur_start_top : u_blur_start_bottom;
-  vec4 corner_scale =
-      (y < u_blur_extent.w) ? u_blur_scale_top : u_blur_scale_bottom;
-  vec2 scaled = clamp((y - corner_start.yw) * corner_scale.yw, 0.0, 1.0);
-  vec2 root = sqrt(1.0 - scaled * scaled);
-  vec2 extent_x = corner_start.xz + corner_scale.xz * root;
-
-  // Get the integral over the interval occupied by the rectangle.
-  return GaussianIntegral(extent_x - x);
-}
-
-float GetBlur(vec2 pos) {
-  // Approximate the 2D gaussian filter using numerical integration. Sample
-  // points between the y extents of the rectangle.
-  float low = clamp(pos.y - u_blur_extent.x, u_blur_extent.y, u_blur_extent.z);
-  float high = clamp(pos.y + u_blur_extent.x, u_blur_extent.y, u_blur_extent.z);
-
-  // Use the Gauss–Legendre quadrature with 6 points to numerically integrate.
-  // Using fewer samples will show artifacts with elliptical corners that are
-  // likely to be used.
-  const vec3 kStepScale1 = vec3(-0.932470, -0.661209, -0.238619);
-  const vec3 kStepScale2 = vec3( 0.932470,  0.661209,  0.238619);
-  const vec3 kWeight = vec3(0.171324, 0.360762, 0.467914);
-
-  float half_size = (high - low) * 0.5;
-  float middle = (high + low) * 0.5;
-  vec3 weight = half_size * kWeight;
-  vec3 pos1 = middle + half_size * kStepScale1;
-  vec3 pos2 = middle + half_size * kStepScale2;
-  vec3 offset1 = pos1 - pos.yyy;
-  vec3 offset2 = pos2 - pos.yyy;
-
-  // The integral along the x-axis is computed. The integral along the y-axis
-  // is roughly approximated. To get the 2D filter, multiply the two integrals.
-  // Visual artifacts appear when the computed integrals along the x-axis
-  // change rapidly between samples (e.g. elliptical corners that are much
-  // wider than they are tall).
-  vec3 xblur1 = vec3(GetXBlur(pos.x, pos1.x),
-                     GetXBlur(pos.x, pos1.y),
-                     GetXBlur(pos.x, pos1.z));
-  vec3 xblur2 = vec3(GetXBlur(pos.x, pos2.x),
-                     GetXBlur(pos.x, pos2.y),
-                     GetXBlur(pos.x, pos2.z));
-  vec3 yblur1 = exp(-offset1 * offset1) * weight;
-  vec3 yblur2 = exp(-offset2 * offset2) * weight;
-
-  // Since each yblur value should be normalized by kNormalizeGaussian, just
-  // scale the sum by it.
-  const float kNormalizeGaussian = 0.564189584;  // 1 / sqrt(pi)
-  return (dot(xblur1, yblur1) + dot(xblur2, yblur2)) * kNormalizeGaussian;
-}
-
-void main() {
-  float scissor_scale =
-      IsOutsideRCorner(v_rcorner) * u_scale_add.x + u_scale_add.y;
-  float blur_scale = GetBlur(v_offset) * u_scale_add.x + u_scale_add.y;
-  gl_FragColor = u_color * (blur_scale * scissor_scale);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_include.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_include.glsl
deleted file mode 100644
index ddeb45d..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_include.glsl
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision highp float;
-uniform vec4 u_include;   // include scissor (x_min, y_min, x_max, y_max)
-varying vec2 v_offset;
-varying vec4 v_color;
-
-void main() {
-  vec2 include = step(u_include.xy, v_offset) * step(v_offset, u_include.zw);
-  gl_FragColor = v_color * include.x * include.y;
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_texcoord.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_texcoord.glsl
deleted file mode 100644
index 5ff73f6..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_texcoord.glsl
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-varying vec4 v_color;
-varying vec2 v_texcoord;
-
-uniform vec4 u_texcoord_clamp_rgba;
-uniform sampler2D u_texture_rgba;
-
-#pragma array u_texcoord_clamp(u_texcoord_clamp_rgba);
-#pragma array u_texture(u_texture_rgba);
-
-vec4 GetRgba() {
-  return texture2D(u_texture_rgba,
-      clamp(v_texcoord, u_texcoord_clamp_rgba.xy, u_texcoord_clamp_rgba.zw));
-}
-
-void main() {
-  gl_FragColor = v_color * GetRgba();
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_texcoord_yuv3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_texcoord_yuv3.glsl
deleted file mode 100644
index ff766ea..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_color_texcoord_yuv3.glsl
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-varying vec4 v_color;
-varying vec2 v_texcoord;
-
-uniform vec4 u_texcoord_clamp_y;
-uniform vec4 u_texcoord_clamp_u;
-uniform vec4 u_texcoord_clamp_v;
-uniform sampler2D u_texture_y;
-uniform sampler2D u_texture_u;
-uniform sampler2D u_texture_v;
-uniform mat4 u_color_transform_matrix;
-
-#pragma array u_texcoord_clamp(u_texcoord_clamp_y, u_texcoord_clamp_u, u_texcoord_clamp_v);
-#pragma array u_texture(u_texture_y, u_texture_u, u_texture_v);
-
-vec4 GetRgba() {
-  float y = texture2D(u_texture_y,
-                      clamp(v_texcoord, u_texcoord_clamp_y.xy, u_texcoord_clamp_y.zw)).a;
-  float u = texture2D(u_texture_u,
-                      clamp(v_texcoord, u_texcoord_clamp_u.xy, u_texcoord_clamp_u.zw)).a;
-  float v = texture2D(u_texture_v,
-                      clamp(v_texcoord, u_texcoord_clamp_v.xy, u_texcoord_clamp_v.zw)).a;
-  vec4 rgba = u_color_transform_matrix * vec4(y, u, v, 1);
-
-  return clamp(rgba, vec4(0, 0, 0, 0), vec4(1, 1, 1, 1));
-}
-
-void main() {
-  gl_FragColor = v_color * GetRgba();
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_opacity_texcoord1d.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_opacity_texcoord1d.glsl
deleted file mode 100644
index f41f1ca..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_opacity_texcoord1d.glsl
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-uniform sampler2D u_texture;
-uniform vec4 u_texcoord_transform;  // (u-scale, u-add, u-max, v-center)
-uniform float u_opacity;
-varying vec2 v_offset;
-
-void main() {
-  vec2 texcoord = vec2(
-      min(length(v_offset) * u_texcoord_transform.x + u_texcoord_transform.y,
-          u_texcoord_transform.z),
-      u_texcoord_transform.w);
-  gl_FragColor = texture2D(u_texture, texcoord) * u_opacity;
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_rcorner2_color.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_rcorner2_color.glsl
deleted file mode 100644
index e245d3a..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_rcorner2_color.glsl
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-precision mediump float;
-
-uniform vec4 u_color;
-varying vec4 v_rcorner_inner;
-varying vec4 v_rcorner_outer;
-
-// Return 0 if the given position is inside the rounded corner, or scale
-// towards 1 as it goes outside a 1-pixel anti-aliasing border.
-// |rcorner| is a vec4 representing (scaled.xy, 1 / radius.xy) with scaled.xy
-//   representing the offset of the current position in terms of radius.xy
-//   (i.e. offset.xy / radius.xy). The scaled.xy values can be negative if the
-//   current position is outside the corner start.
-float IsOutsideRCorner(vec4 rcorner) {
-  // Estimate the distance to an implicit function using
-  //   dist = f(x,y) / length(gradient(f(x,y)))
-  // For an ellipse, f(x,y) = x^2 / a^2 + y^2 / b^2 - 1.
-  highp vec2 scaled = max(rcorner.xy, 0.0);
-  highp float implicit = dot(scaled, scaled) - 1.0;
-
-  // NOTE: To accommodate large radius values using mediump floats, rcorner.zw
-  //   was scaled by kRCornerGradientScale in the vertex attribute data.
-  //   Multiply inv_gradient by kRCornerGradientScale to undo that scaling.
-  const highp float kRCornerGradientScale = 16.0;
-  highp vec2 gradient = 2.0 * scaled * rcorner.zw;
-  highp float inv_gradient = kRCornerGradientScale *
-      inversesqrt(max(dot(gradient, gradient), 0.0001));
-
-  return clamp(0.5 + implicit * inv_gradient, 0.0, 1.0);
-}
-
-void main() {
-  float inner_scale = IsOutsideRCorner(v_rcorner_inner);
-  float outer_scale = 1.0 - IsOutsideRCorner(v_rcorner_outer);
-  gl_FragColor = u_color * (inner_scale * outer_scale);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_rcorner_color.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_rcorner_color.glsl
deleted file mode 100644
index b818cf9..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_rcorner_color.glsl
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-
-uniform vec4 u_color;
-varying vec4 v_rcorner;
-
-// Return 0 if the given position is inside the rounded corner, or scale
-// towards 1 as it goes outside a 1-pixel anti-aliasing border.
-// |rcorner| is a vec4 representing (scaled.xy, 1 / radius.xy) with scaled.xy
-//   representing the offset of the current position in terms of radius.xy
-//   (i.e. offset.xy / radius.xy). The scaled.xy values can be negative if the
-//   current position is outside the corner start.
-float IsOutsideRCorner(vec4 rcorner) {
-  // Estimate the distance to an implicit function using
-  //   dist = f(x,y) / length(gradient(f(x,y)))
-  // For an ellipse, f(x,y) = x^2 / a^2 + y^2 / b^2 - 1.
-  highp vec2 scaled = max(rcorner.xy, 0.0);
-  highp float implicit = dot(scaled, scaled) - 1.0;
-
-  // NOTE: To accommodate large radius values using mediump floats, rcorner.zw
-  //   was scaled by kRCornerGradientScale in the vertex attribute data.
-  //   Multiply inv_gradient by kRCornerGradientScale to undo that scaling.
-  const highp float kRCornerGradientScale = 16.0;
-  highp vec2 gradient = 2.0 * scaled * rcorner.zw;
-  highp float inv_gradient = kRCornerGradientScale *
-      inversesqrt(max(dot(gradient, gradient), 0.0001));
-
-  return clamp(0.5 + implicit * inv_gradient, 0.0, 1.0);
-}
-
-void main() {
-  float scale = IsOutsideRCorner(v_rcorner);
-  gl_FragColor = u_color * (1.0 - scale);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_rcorner_texcoord_color.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_rcorner_texcoord_color.glsl
deleted file mode 100644
index 5db8675..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_rcorner_texcoord_color.glsl
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-
-uniform vec4 u_color;
-
-varying vec4 v_rcorner;
-varying vec2 v_texcoord;
-
-// Return 0 if the given position is inside the rounded corner, or scale
-// towards 1 as it goes outside a 1-pixel anti-aliasing border.
-// |rcorner| is a vec4 representing (scaled.xy, 1 / radius.xy) with scaled.xy
-//   representing the offset of the current position in terms of radius.xy
-//   (i.e. offset.xy / radius.xy). The scaled.xy values can be negative if the
-//   current position is outside the corner start.
-float IsOutsideRCorner(vec4 rcorner) {
-  // Estimate the distance to an implicit function using
-  //   dist = f(x,y) / length(gradient(f(x,y)))
-  // For an ellipse, f(x,y) = x^2 / a^2 + y^2 / b^2 - 1.
-  highp vec2 scaled = max(rcorner.xy, 0.0);
-  highp float implicit = dot(scaled, scaled) - 1.0;
-
-  // NOTE: To accommodate large radius values using mediump floats, rcorner.zw
-  //   was scaled by kRCornerGradientScale in the vertex attribute data.
-  //   Multiply inv_gradient by kRCornerGradientScale to undo that scaling.
-  const highp float kRCornerGradientScale = 16.0;
-  highp vec2 gradient = 2.0 * scaled * rcorner.zw;
-  highp float inv_gradient = kRCornerGradientScale *
-      inversesqrt(max(dot(gradient, gradient), 0.0001));
-
-  return clamp(0.5 + implicit * inv_gradient, 0.0, 1.0);
-}
-
-uniform vec4 u_texcoord_clamp_rgba;
-uniform sampler2D u_texture_rgba;
-
-#pragma array u_texcoord_clamp(u_texcoord_clamp_rgba);
-#pragma array u_texture(u_texture_rgba);
-
-vec4 GetRgba() {
-  return texture2D(u_texture_rgba,
-      clamp(v_texcoord, u_texcoord_clamp_rgba.xy, u_texcoord_clamp_rgba.zw));
-}
-
-void main() {
-  float scale = IsOutsideRCorner(v_rcorner);
-  gl_FragColor = u_color * (1.0 - scale) * GetRgba();
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_rcorner_texcoord_color_yuv3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_rcorner_texcoord_color_yuv3.glsl
deleted file mode 100644
index 146664e..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_rcorner_texcoord_color_yuv3.glsl
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-
-uniform vec4 u_color;
-
-varying vec4 v_rcorner;
-varying vec2 v_texcoord;
-
-// Return 0 if the given position is inside the rounded corner, or scale
-// towards 1 as it goes outside a 1-pixel anti-aliasing border.
-// |rcorner| is a vec4 representing (scaled.xy, 1 / radius.xy) with scaled.xy
-//   representing the offset of the current position in terms of radius.xy
-//   (i.e. offset.xy / radius.xy). The scaled.xy values can be negative if the
-//   current position is outside the corner start.
-float IsOutsideRCorner(vec4 rcorner) {
-  // Estimate the distance to an implicit function using
-  //   dist = f(x,y) / length(gradient(f(x,y)))
-  // For an ellipse, f(x,y) = x^2 / a^2 + y^2 / b^2 - 1.
-  highp vec2 scaled = max(rcorner.xy, 0.0);
-  highp float implicit = dot(scaled, scaled) - 1.0;
-
-  // NOTE: To accommodate large radius values using mediump floats, rcorner.zw
-  //   was scaled by kRCornerGradientScale in the vertex attribute data.
-  //   Multiply inv_gradient by kRCornerGradientScale to undo that scaling.
-  const highp float kRCornerGradientScale = 16.0;
-  highp vec2 gradient = 2.0 * scaled * rcorner.zw;
-  highp float inv_gradient = kRCornerGradientScale *
-      inversesqrt(max(dot(gradient, gradient), 0.0001));
-
-  return clamp(0.5 + implicit * inv_gradient, 0.0, 1.0);
-}
-
-uniform vec4 u_texcoord_clamp_y;
-uniform vec4 u_texcoord_clamp_u;
-uniform vec4 u_texcoord_clamp_v;
-uniform sampler2D u_texture_y;
-uniform sampler2D u_texture_u;
-uniform sampler2D u_texture_v;
-uniform mat4 u_color_transform_matrix;
-
-#pragma array u_texcoord_clamp(u_texcoord_clamp_y, u_texcoord_clamp_u, u_texcoord_clamp_v);
-#pragma array u_texture(u_texture_y, u_texture_u, u_texture_v);
-
-vec4 GetRgba() {
-  float y = texture2D(u_texture_y,
-                      clamp(v_texcoord, u_texcoord_clamp_y.xy, u_texcoord_clamp_y.zw)).a;
-  float u = texture2D(u_texture_u,
-                      clamp(v_texcoord, u_texcoord_clamp_u.xy, u_texcoord_clamp_u.zw)).a;
-  float v = texture2D(u_texture_v,
-                      clamp(v_texcoord, u_texcoord_clamp_v.xy, u_texcoord_clamp_v.zw)).a;
-  vec4 rgba = u_color_transform_matrix * vec4(y, u, v, 1);
-
-  return clamp(rgba, vec4(0, 0, 0, 0), vec4(1, 1, 1, 1));
-}
-
-void main() {
-  float scale = IsOutsideRCorner(v_rcorner);
-  gl_FragColor = u_color * (1.0 - scale) * GetRgba();
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_texcoord.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_texcoord.glsl
deleted file mode 100644
index 20e2228..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_texcoord.glsl
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-varying vec2 v_texcoord;
-
-uniform sampler2D u_texture_rgba;
-
-#pragma array u_texture(u_texture_rgba);
-
-vec4 GetRgba() {
-  return texture2D(u_texture_rgba, v_texcoord);
-}
-
-void main() {
-  gl_FragColor = GetRgba();
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_texcoord_yuv3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_texcoord_yuv3.glsl
deleted file mode 100644
index 40bc9fa..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_directgles_texcoord_yuv3.glsl
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-precision mediump float;
-varying vec2 v_texcoord;
-
-uniform sampler2D u_texture_y;
-uniform sampler2D u_texture_u;
-uniform sampler2D u_texture_v;
-uniform mat4 u_color_transform_matrix;
-
-#pragma array u_texture(u_texture_y, u_texture_u, u_texture_v);
-
-vec4 GetRgba() {
-  float y = texture2D(u_texture_y, v_texcoord).a;
-  float u = texture2D(u_texture_u, v_texcoord).a;
-  float v = texture2D(u_texture_v, v_texcoord).a;
-  vec4 rgba = u_color_transform_matrix * vec4(y, u, v, 1);
-
-  return clamp(rgba, vec4(0, 0, 0, 0), vec4(1, 1, 1, 1));
-}
-
-void main() {
-  gl_FragColor = GetRgba();
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_ellipse_with_texture_and_some_alpha.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_ellipse_with_texture_and_some_alpha.glsl
deleted file mode 100644
index c37000f..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_ellipse_with_texture_and_some_alpha.glsl
+++ /dev/null
@@ -1,41 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-uniform vec4 uinnerRect_Stage2;
-uniform vec4 uinvRadiiLTRB_Stage2;
-uniform highp sampler2D uTextureSampler_0_Stage1;
-varying mediump vec4 vcolor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-void main() {
-highp     vec2 _sktmpCoord = gl_FragCoord.xy;
-highp     vec4 sk_FragCoord = vec4(_sktmpCoord.x, u_skRTHeight - _sktmpCoord.y, 1.0, 1.0);
-    vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    vec4 output_Stage1;
-    {
-        vec4 child;
-        {
-            child = texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0).xyzw;
-        }
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    vec4 output_Stage2;
-    {
-        vec2 dxy0 = uinnerRect_Stage2.xy - sk_FragCoord.xy;
-        vec2 dxy1 = sk_FragCoord.xy - uinnerRect_Stage2.zw;
-        vec2 dxy = max(max(dxy0, dxy1), 0.0);
-        vec2 Z = max(max(dxy0 * uinvRadiiLTRB_Stage2.xy, dxy1 * uinvRadiiLTRB_Stage2.zw), 0.0);
-        float implicit = dot(Z, dxy) - 1.0;
-        float grad_dot = 4.0 * dot(Z, Z);
-        grad_dot = max(grad_dot, 0.0001);
-        float approx_dist = implicit * inversesqrt(grad_dot);
-        float alpha = clamp(0.5 - approx_dist, 0.0, 1.0);
-        output_Stage2 = vec4(alpha);
-    }
-    {
-        gl_FragColor = output_Stage1 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_position_and_texcoord.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_position_and_texcoord.glsl
deleted file mode 100644
index ca7ad4e..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_position_and_texcoord.glsl
+++ /dev/null
@@ -1,7 +0,0 @@
-precision mediump float;

-varying vec2 v_tex_coord;

-uniform sampler2D tex;

-

-void main() {

-  gl_FragColor = texture2D(tex, v_tex_coord);

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold.glsl
deleted file mode 100644
index 6950864..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold.glsl
+++ /dev/null
@@ -1,72 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-varying highp vec4 vinCircleEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = length(vTransformedCoords_0_Stage0);
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp vec4 circleEdge;
-        circleEdge = vinCircleEdge_Stage0;
-        outputColor_Stage0 = vinColor_Stage0;
-        highp float d = length(circleEdge.xy);
-        mediump float distanceToOuterEdge = circleEdge.z * (1.0 - d);
-        mediump float edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_2.glsl
deleted file mode 100644
index 1be401c..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_2.glsl
+++ /dev/null
@@ -1,75 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-varying highp vec4 vinCircleEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    {
-        child.xyz *= child.w;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp vec4 circleEdge;
-        circleEdge = vinCircleEdge_Stage0;
-        outputColor_Stage0 = vinColor_Stage0;
-        highp float d = length(circleEdge.xy);
-        mediump float distanceToOuterEdge = circleEdge.z * (1.0 - d);
-        mediump float edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_3.glsl
deleted file mode 100644
index 02f89e1..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_threshold_3.glsl
+++ /dev/null
@@ -1,75 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-varying highp vec4 vinCircleEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp vec4 circleEdge;
-        circleEdge = vinCircleEdge_Stage0;
-        outputColor_Stage0 = vinColor_Stage0;
-        highp float d = length(circleEdge.xy);
-        mediump float distanceToOuterEdge = circleEdge.z * (1.0 - d);
-        mediump float edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);
-        mediump float distanceToInnerEdge = circleEdge.z * (d - circleEdge.w);
-        mediump float innerAlpha = clamp(distanceToInnerEdge, 0.0, 1.0);
-        edgeAlpha *= innerAlpha;
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_thresholds.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_thresholds.glsl
deleted file mode 100644
index 11db997..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_circle_color_scale_thresholds.glsl
+++ /dev/null
@@ -1,93 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying highp vec4 vinCircleEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage1_c0_c0_c1_c0;
-                bias = ubias6_7_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    {
-        _sample1992.xyz *= _sample1992.w;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp vec4 circleEdge;
-        circleEdge = vinCircleEdge_Stage0;
-        highp float d = length(circleEdge.xy);
-        mediump float distanceToOuterEdge = circleEdge.z * (1.0 - d);
-        mediump float edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);
-        mediump float distanceToInnerEdge = circleEdge.z * (d - circleEdge.w);
-        mediump float innerAlpha = clamp(distanceToInnerEdge, 0.0, 1.0);
-        edgeAlpha *= innerAlpha;
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold.glsl
deleted file mode 100644
index e1600ec..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold.glsl
+++ /dev/null
@@ -1,70 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uColor_Stage0;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump float vinCoverage_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = length(vTransformedCoords_0_Stage0);
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    {
-        _sample1992.xyz *= _sample1992.w;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        mediump float alpha = 1.0;
-        alpha = vinCoverage_Stage0;
-        outputCoverage_Stage0 = vec4(alpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_2.glsl
deleted file mode 100644
index e7447a2..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_2.glsl
+++ /dev/null
@@ -1,67 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uColor_Stage0;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump float vinCoverage_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        mediump float alpha = 1.0;
-        alpha = vinCoverage_Stage0;
-        outputCoverage_Stage0 = vec4(alpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_3.glsl
deleted file mode 100644
index 1ee4967..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_threshold_3.glsl
+++ /dev/null
@@ -1,66 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-varying highp float vcoverage_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp float coverage = vcoverage_Stage0;
-        outputCoverage_Stage0 = vec4(coverage);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds.glsl
deleted file mode 100644
index 7bf9ef3..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds.glsl
+++ /dev/null
@@ -1,83 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-varying highp float vcoverage_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage1_c0_c0_c1_c0;
-                bias = ubias6_7_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp float coverage = vcoverage_Stage0;
-        outputCoverage_Stage0 = vec4(coverage);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_2.glsl
deleted file mode 100644
index e6a6613..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_2.glsl
+++ /dev/null
@@ -1,69 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-varying highp float vcoverage_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    {
-        _sample1992.xyz *= _sample1992.w;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp float coverage = vcoverage_Stage0;
-        outputCoverage_Stage0 = vec4(coverage);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_3.glsl
deleted file mode 100644
index a5049c7..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_3.glsl
+++ /dev/null
@@ -1,93 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uColor_Stage0;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale8_9_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias8_9_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump float vinCoverage_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.w) {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage1_c0_c0_c1_c0;
-                bias = ubias6_7_Stage1_c0_c0_c1_c0;
-            }
-        }
-    } else {
-        {
-            {
-                scale = uscale8_9_Stage1_c0_c0_c1_c0;
-                bias = ubias8_9_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        mediump float alpha = 1.0;
-        alpha = vinCoverage_Stage0;
-        outputCoverage_Stage0 = vec4(alpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_4.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_4.glsl
deleted file mode 100644
index 7a83670..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_coverage_scale_thresholds_4.glsl
+++ /dev/null
@@ -1,98 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uColor_Stage0;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale8_9_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale10_11_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias8_9_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias10_11_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump float vinCoverage_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = length(vTransformedCoords_0_Stage0);
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.w) {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage1_c0_c0_c1_c0;
-                bias = ubias6_7_Stage1_c0_c0_c1_c0;
-            }
-        }
-    } else {
-        {
-            if (t < uthresholds9_13_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale8_9_Stage1_c0_c0_c1_c0;
-                bias = ubias8_9_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale10_11_Stage1_c0_c0_c1_c0;
-                bias = ubias10_11_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        mediump float alpha = 1.0;
-        alpha = vinCoverage_Stage0;
-        outputCoverage_Stage0 = vec4(alpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_decal_scaletranslate_texdom_texturew_threshold.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_decal_scaletranslate_texdom_texturew_threshold.glsl
deleted file mode 100644
index e338bcf..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_decal_scaletranslate_texdom_texturew_threshold.glsl
+++ /dev/null
@@ -1,89 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uscaleAndTranslate_Stage2;
-uniform mediump vec4 uTexDom_Stage2;
-uniform mediump vec3 uDecalParams_Stage2;
-uniform sampler2D uTextureSampler_0_Stage2;
-varying mediump vec4 vcolor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    {
-        child.xyz *= child.w;
-    }
-    return child;
-}
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage2.xy + uscaleAndTranslate_Stage2.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage2.xy, uTexDom_Stage2.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage2, clampedCoord).wwww;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage2.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage2.y);
-            if (err > uDecalParams_Stage2.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage2.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage2 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    {
-        gl_FragColor = output_Stage1 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold.glsl
deleted file mode 100644
index 667a564..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold.glsl
+++ /dev/null
@@ -1,76 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_2.glsl
deleted file mode 100644
index 35a3010..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_2.glsl
+++ /dev/null
@@ -1,78 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_3.glsl
deleted file mode 100644
index 1f47fae..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_threshold_3.glsl
+++ /dev/null
@@ -1,79 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    {
-        _sample1992.xyz *= _sample1992.w;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds.glsl
deleted file mode 100644
index b7223a3..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds.glsl
+++ /dev/null
@@ -1,88 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds_2.glsl
deleted file mode 100644
index ba5cadd..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_quad_scale_thresholds_2.glsl
+++ /dev/null
@@ -1,93 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage1_c0_c0_c1_c0;
-                bias = ubias6_7_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold.glsl
deleted file mode 100644
index 8bcc27a..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold.glsl
+++ /dev/null
@@ -1,73 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage0;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump vec4 texColor;
-        {
-            texColor = texture2D(uTextureSampler_0_Stage0, vTextureCoords_Stage0).wwww;
-        }
-        outputCoverage_Stage0 = texColor;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_2.glsl
deleted file mode 100644
index 2ac7fcc..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_2.glsl
+++ /dev/null
@@ -1,74 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage0;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    {
-        _sample1992.xyz *= _sample1992.w;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        mediump vec4 texColor;
-        {
-            texColor = texture2D(uTextureSampler_0_Stage0, vTextureCoords_Stage0).wwww;
-        }
-        outputCoverage_Stage0 = texColor;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_3.glsl
deleted file mode 100644
index 1cb8137..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_threshold_3.glsl
+++ /dev/null
@@ -1,71 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage0;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        mediump vec4 texColor;
-        {
-            texColor = texture2D(uTextureSampler_0_Stage0, vTextureCoords_Stage0).wwww;
-        }
-        outputCoverage_Stage0 = texColor;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_thresholds.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_thresholds.glsl
deleted file mode 100644
index ba8e732..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texindex_texturew_thresholds.glsl
+++ /dev/null
@@ -1,86 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage0;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    {
-        _sample1992.xyz *= _sample1992.w;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        mediump vec4 texColor;
-        {
-            texColor = texture2D(uTextureSampler_0_Stage0, vTextureCoords_Stage0).wwww;
-        }
-        outputCoverage_Stage0 = texColor;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texture_threshold.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texture_threshold.glsl
deleted file mode 100644
index 4522b68..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texture_threshold.glsl
+++ /dev/null
@@ -1,89 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage2_c1_c0_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage2_c1_c0_c0_c0;
-uniform mediump float ubias_Stage2_c1_c0_c0_c0_c0_c0;
-uniform mediump float uscale_Stage2_c1_c0_c0_c0_c0_c0;
-uniform highp vec4 uscale01_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage2_c1_c0_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage2_c1_c0_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying highp vec2 vTransformedCoords_1_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    _sample1992 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return _sample1992;
-}
-mediump vec4 stage_Stage2_c1_c0_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c1_c0_c0_c0;
-    mediump float angle;
-    {
-        angle = atan(-vTransformedCoords_1_Stage0.y, -vTransformedCoords_1_Stage0.x);
-    }
-    mediump float t = ((angle * 0.15915493667125702 + 0.5) + ubias_Stage2_c1_c0_c0_c0_c0_c0) * uscale_Stage2_c1_c0_c0_c0_c0_c0;
-    _sample1099_c1_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c1_c0_c0_c0;
-}
-mediump vec4 stage_Stage2_c1_c0_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c1_c0_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage2_c1_c0_c0_c0_c1_c0) {
-        scale = uscale01_Stage2_c1_c0_c0_c0_c1_c0;
-        bias = ubias01_Stage2_c1_c0_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage2_c1_c0_c0_c0_c1_c0;
-        bias = ubias23_Stage2_c1_c0_c0_c0_c1_c0;
-    }
-    _sample1767_c1_c0_c0_c0 = t * scale + bias;
-    return _sample1767_c1_c0_c0_c0;
-}
-mediump vec4 stage_Stage2_c1_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 child_c1_c0;
-    mediump vec4 _sample1099_c1_c0_c0_c0;
-    _sample1099_c1_c0_c0_c0 = stage_Stage2_c1_c0_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c1_c0_c0_c0;
-    if (t.x < 0.0) {
-        child_c1_c0 = uleftBorderColor_Stage2_c1_c0_c0_c0;
-    } else if (t.x > 1.0) {
-        child_c1_c0 = urightBorderColor_Stage2_c1_c0_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c1_c0_c0_c0;
-        _sample1767_c1_c0_c0_c0 = stage_Stage2_c1_c0_c0_c0_c1_c0(t);
-        child_c1_c0 = _sample1767_c1_c0_c0_c0;
-    }
-    {
-        child_c1_c0.xyz *= child_c1_c0.w;
-    }
-    return child_c1_c0;
-}
-mediump vec4 stage_Stage2_c1_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 child_c1_c0;
-    child_c1_c0 = stage_Stage2_c1_c0_c0_c0(vec4(1.0));
-    child = child_c1_c0 * _input.w;
-    return child;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec4 child;
-        child = stage_Stage2_c1_c0(vec4(1.0));
-        output_Stage2 = vec4(child.w);
-    }
-    {
-        gl_FragColor = output_Stage1 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texturew_threshold.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texturew_threshold.glsl
deleted file mode 100644
index 8ea773f..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_texturew_threshold.glsl
+++ /dev/null
@@ -1,69 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage2;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying highp vec2 vTransformedCoords_1_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    {
-        _sample1992.xyz *= _sample1992.w;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        output_Stage2 = texture2D(uTextureSampler_0_Stage2, vTransformedCoords_1_Stage0).wwww;
-    }
-    {
-        gl_FragColor = output_Stage1 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold.glsl
deleted file mode 100644
index 2cec90c..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold.glsl
+++ /dev/null
@@ -1,67 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    {
-        child.xyz *= child.w;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold_2.glsl
deleted file mode 100644
index 8335e68..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_threshold_2.glsl
+++ /dev/null
@@ -1,60 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c1_c0;
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds.glsl
deleted file mode 100644
index 7685597..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds.glsl
+++ /dev/null
@@ -1,84 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying mediump vec4 vcolor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage1_c0_c0_c1_c0;
-                bias = ubias6_7_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    {
-        child.xyz *= child.w;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_2.glsl
deleted file mode 100644
index a1f1060..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_2.glsl
+++ /dev/null
@@ -1,84 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage1_c0_c0_c1_c0;
-                bias = ubias6_7_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    {
-        child.xyz *= child.w;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_3.glsl
deleted file mode 100644
index ecc4cb4..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_3.glsl
+++ /dev/null
@@ -1,81 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage1_c0_c0_c1_c0;
-                bias = ubias6_7_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_4.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_4.glsl
deleted file mode 100644
index 71ce693..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_4.glsl
+++ /dev/null
@@ -1,91 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale8_9_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale10_11_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias8_9_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias10_11_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.w) {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage1_c0_c0_c1_c0;
-                bias = ubias6_7_Stage1_c0_c0_c1_c0;
-            }
-        }
-    } else {
-        {
-            if (t < uthresholds9_13_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale8_9_Stage1_c0_c0_c1_c0;
-                bias = ubias8_9_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale10_11_Stage1_c0_c0_c1_c0;
-                bias = ubias10_11_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_5.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_5.glsl
deleted file mode 100644
index 4fb093a..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_5.glsl
+++ /dev/null
@@ -1,77 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage1_c0_c0_c1_c0;
-                bias = ubias6_7_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_6.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_6.glsl
deleted file mode 100644
index bffd97d..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_6.glsl
+++ /dev/null
@@ -1,94 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale8_9_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale10_11_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias8_9_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias10_11_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.w) {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage1_c0_c0_c1_c0;
-                bias = ubias6_7_Stage1_c0_c0_c1_c0;
-            }
-        }
-    } else {
-        {
-            if (t < uthresholds9_13_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale8_9_Stage1_c0_c0_c1_c0;
-                bias = ubias8_9_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale10_11_Stage1_c0_c0_c1_c0;
-                bias = ubias10_11_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    {
-        _sample1992.xyz *= _sample1992.w;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_7.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_7.glsl
deleted file mode 100644
index 9324401..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_7.glsl
+++ /dev/null
@@ -1,80 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage1_c0_c0_c1_c0;
-                bias = ubias6_7_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    {
-        _sample1992.xyz *= _sample1992.w;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_8.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_8.glsl
deleted file mode 100644
index 39b4e76..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_8.glsl
+++ /dev/null
@@ -1,86 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage1_c0_c0_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c0_c0_c1_c0;
-            }
-        } else {
-            {
-                scale = uscale4_5_Stage1_c0_c0_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 child_c0_c0;
-    mediump vec4 _sample1099_c0_c0_c0_c0;
-    _sample1099_c0_c0_c0_c0 = stage_Stage1_c0_c0_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0_c0_c0;
-    if (t.x < 0.0) {
-        child_c0_c0 = uleftBorderColor_Stage1_c0_c0_c0_c0;
-    } else if (t.x > 1.0) {
-        child_c0_c0 = urightBorderColor_Stage1_c0_c0_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0_c0_c0;
-        _sample1767_c0_c0_c0_c0 = stage_Stage1_c0_c0_c0_c0_c1_c0(t);
-        child_c0_c0 = _sample1767_c0_c0_c0_c0;
-    }
-    {
-        child_c0_c0.xyz *= child_c0_c0.w;
-    }
-    return child_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 child_c0_c0;
-    child_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    child = child_c0_c0 * _input.w;
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = vec4(child.w);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_9.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_9.glsl
deleted file mode 100644
index 0e27646..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_border_color_scale_thresholds_9.glsl
+++ /dev/null
@@ -1,95 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale8_9_Stage1_c0_c0_c1_c0;
-uniform highp vec4 uscale10_11_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias8_9_Stage1_c0_c0_c1_c0;
-uniform highp vec4 ubias10_11_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.w) {
-        if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage1_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage1_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage1_c0_c0_c1_c0;
-                bias = ubias6_7_Stage1_c0_c0_c1_c0;
-            }
-        }
-    } else {
-        {
-            if (t < uthresholds9_13_Stage1_c0_c0_c1_c0.x) {
-                scale = uscale8_9_Stage1_c0_c0_c1_c0;
-                bias = ubias8_9_Stage1_c0_c0_c1_c0;
-            } else {
-                scale = uscale10_11_Stage1_c0_c0_c1_c0;
-                bias = ubias10_11_Stage1_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1767_c0_c0 = t * scale + bias;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold.glsl
deleted file mode 100644
index 482238d..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold.glsl
+++ /dev/null
@@ -1,89 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 uscale01_Stage1_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c1_c0_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c1_c0_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying highp vec2 vTransformedCoords_1_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 child_c0_c0;
-    child_c0_c0 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return child_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 xfer_src;
-    mediump vec4 child_c0_c0;
-    child_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    xfer_src = child_c0_c0 * _input.w;
-    return xfer_src;
-}
-mediump vec4 stage_Stage1_c1_c0_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample453_c1_c0_c0_c0;
-    mediump float t = vTransformedCoords_1_Stage0.x + 9.999999747378752e-06;
-    _sample453_c1_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample453_c1_c0_c0_c0;
-}
-mediump vec4 stage_Stage1_c1_c0_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1464_c1_c0_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c1_c0_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c1_c0_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c1_c0_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c1_c0_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c1_c0_c0_c0_c1_c0;
-    }
-    _sample1464_c1_c0_c0_c0 = t * scale + bias;
-    return _sample1464_c1_c0_c0_c0;
-}
-mediump vec4 stage_Stage1_c1_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992_c1_c0;
-    mediump vec4 _sample453_c1_c0_c0_c0;
-    _sample453_c1_c0_c0_c0 = stage_Stage1_c1_c0_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample453_c1_c0_c0_c0;
-    {
-        {
-            t.x = fract(t.x);
-        }
-        mediump vec4 _sample1464_c1_c0_c0_c0;
-        _sample1464_c1_c0_c0_c0 = stage_Stage1_c1_c0_c0_c0_c1_c0(t);
-        _sample1992_c1_c0 = _sample1464_c1_c0_c0_c0;
-    }
-    {
-        _sample1992_c1_c0.xyz *= _sample1992_c1_c0.w;
-    }
-    return _sample1992_c1_c0;
-}
-mediump vec4 stage_Stage1_c1_c0(mediump vec4 _input) {
-    mediump vec4 xfer_dst;
-    mediump vec4 _sample1992_c1_c0;
-    _sample1992_c1_c0 = stage_Stage1_c1_c0_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-    xfer_dst = _sample1992_c1_c0;
-    return xfer_dst;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 inputColor = vec4(outputColor_Stage0.xyz, 1.0);
-        mediump vec4 xfer_src;
-        xfer_src = stage_Stage1_c0_c0(inputColor);
-        mediump vec4 xfer_dst;
-        xfer_dst = stage_Stage1_c1_c0(inputColor);
-        output_Stage1 = xfer_src * xfer_dst.w;
-        output_Stage1 *= outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_2.glsl
deleted file mode 100644
index 563d103..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_2.glsl
+++ /dev/null
@@ -1,89 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 uscale01_Stage1_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c1_c0_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c1_c0_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying highp vec2 vTransformedCoords_1_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 child_c0_c0;
-    child_c0_c0 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return child_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 xfer_src;
-    mediump vec4 child_c0_c0;
-    child_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    xfer_src = child_c0_c0 * _input.w;
-    return xfer_src;
-}
-mediump vec4 stage_Stage1_c1_c0_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample453_c1_c0_c0_c0;
-    mediump float t = vTransformedCoords_1_Stage0.x + 9.999999747378752e-06;
-    _sample453_c1_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample453_c1_c0_c0_c0;
-}
-mediump vec4 stage_Stage1_c1_c0_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1464_c1_c0_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c1_c0_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c1_c0_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c1_c0_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c1_c0_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c1_c0_c0_c0_c1_c0;
-    }
-    _sample1464_c1_c0_c0_c0 = t * scale + bias;
-    return _sample1464_c1_c0_c0_c0;
-}
-mediump vec4 stage_Stage1_c1_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992_c1_c0;
-    mediump vec4 _sample453_c1_c0_c0_c0;
-    _sample453_c1_c0_c0_c0 = stage_Stage1_c1_c0_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample453_c1_c0_c0_c0;
-    {
-        {
-            t.x = fract(t.x);
-        }
-        mediump vec4 _sample1464_c1_c0_c0_c0;
-        _sample1464_c1_c0_c0_c0 = stage_Stage1_c1_c0_c0_c0_c1_c0(t);
-        _sample1992_c1_c0 = _sample1464_c1_c0_c0_c0;
-    }
-    {
-        _sample1992_c1_c0.xyz *= _sample1992_c1_c0.w;
-    }
-    return _sample1992_c1_c0;
-}
-mediump vec4 stage_Stage1_c1_c0(mediump vec4 _input) {
-    mediump vec4 xfer_dst;
-    mediump vec4 _sample1992_c1_c0;
-    _sample1992_c1_c0 = stage_Stage1_c1_c0_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-    xfer_dst = _sample1992_c1_c0;
-    return xfer_dst;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 inputColor = vec4(outputColor_Stage0.xyz, 1.0);
-        mediump vec4 xfer_src;
-        xfer_src = stage_Stage1_c0_c0(inputColor);
-        mediump vec4 xfer_dst;
-        xfer_dst = stage_Stage1_c1_c0(inputColor);
-        output_Stage1 = xfer_src * (1.0 - xfer_dst.w);
-        output_Stage1 *= outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_3.glsl
deleted file mode 100644
index 37c926c..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_3.glsl
+++ /dev/null
@@ -1,80 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 uscale01_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage2_c1_c0_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage2_c1_c0_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying highp vec2 vTransformedCoords_1_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    _sample1992 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return _sample1992;
-}
-mediump vec4 stage_Stage2_c1_c0_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample453_c1_c0_c0_c0;
-    mediump float t = vTransformedCoords_1_Stage0.x + 9.999999747378752e-06;
-    _sample453_c1_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample453_c1_c0_c0_c0;
-}
-mediump vec4 stage_Stage2_c1_c0_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1464_c1_c0_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage2_c1_c0_c0_c0_c1_c0) {
-        scale = uscale01_Stage2_c1_c0_c0_c0_c1_c0;
-        bias = ubias01_Stage2_c1_c0_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage2_c1_c0_c0_c0_c1_c0;
-        bias = ubias23_Stage2_c1_c0_c0_c0_c1_c0;
-    }
-    _sample1464_c1_c0_c0_c0 = t * scale + bias;
-    return _sample1464_c1_c0_c0_c0;
-}
-mediump vec4 stage_Stage2_c1_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 child_c1_c0;
-    mediump vec4 _sample453_c1_c0_c0_c0;
-    _sample453_c1_c0_c0_c0 = stage_Stage2_c1_c0_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample453_c1_c0_c0_c0;
-    {
-        {
-            t.x = fract(t.x);
-        }
-        mediump vec4 _sample1464_c1_c0_c0_c0;
-        _sample1464_c1_c0_c0_c0 = stage_Stage2_c1_c0_c0_c0_c1_c0(t);
-        child_c1_c0 = _sample1464_c1_c0_c0_c0;
-    }
-    {
-        child_c1_c0.xyz *= child_c1_c0.w;
-    }
-    return child_c1_c0;
-}
-mediump vec4 stage_Stage2_c1_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 child_c1_c0;
-    child_c1_c0 = stage_Stage2_c1_c0_c0_c0(vec4(1.0));
-    child = child_c1_c0 * _input.w;
-    return child;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec4 child;
-        child = stage_Stage2_c1_c0(vec4(1.0));
-        output_Stage2 = vec4(child.w);
-    }
-    {
-        gl_FragColor = output_Stage1 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_4.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_4.glsl
deleted file mode 100644
index aa9cd37..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_threshold_4.glsl
+++ /dev/null
@@ -1,71 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 uscale01_Stage1_c0_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias01_Stage1_c0_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale23_Stage1_c0_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias23_Stage1_c0_c0_c0_c0_c1_c0;
-uniform mediump float uthreshold_Stage1_c0_c0_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample453_c0_c0_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample453_c0_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample453_c0_c0_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1464_c0_c0_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    if (t < uthreshold_Stage1_c0_c0_c0_c0_c1_c0) {
-        scale = uscale01_Stage1_c0_c0_c0_c0_c1_c0;
-        bias = ubias01_Stage1_c0_c0_c0_c0_c1_c0;
-    } else {
-        scale = uscale23_Stage1_c0_c0_c0_c0_c1_c0;
-        bias = ubias23_Stage1_c0_c0_c0_c0_c1_c0;
-    }
-    _sample1464_c0_c0_c0_c0 = t * scale + bias;
-    return _sample1464_c0_c0_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 child_c0_c0;
-    mediump vec4 _sample453_c0_c0_c0_c0;
-    _sample453_c0_c0_c0_c0 = stage_Stage1_c0_c0_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample453_c0_c0_c0_c0;
-    {
-        {
-            t.x = fract(t.x);
-        }
-        mediump vec4 _sample1464_c0_c0_c0_c0;
-        _sample1464_c0_c0_c0_c0 = stage_Stage1_c0_c0_c0_c0_c1_c0(t);
-        child_c0_c0 = _sample1464_c0_c0_c0_c0;
-    }
-    {
-        child_c0_c0.xyz *= child_c0_c0.w;
-    }
-    return child_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 child_c0_c0;
-    child_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    child = child_c0_c0 * _input.w;
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = vec4(child.w);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds.glsl
deleted file mode 100644
index 5eedd12..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds.glsl
+++ /dev/null
@@ -1,92 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 uscale0_1_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage2_c1_c0_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage2_c1_c0_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage2_c1_c0_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying highp vec2 vTransformedCoords_1_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    _sample1992 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return _sample1992;
-}
-mediump vec4 stage_Stage2_c1_c0_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample453_c1_c0_c0_c0;
-    mediump float t = vTransformedCoords_1_Stage0.x + 9.999999747378752e-06;
-    _sample453_c1_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample453_c1_c0_c0_c0;
-}
-mediump vec4 stage_Stage2_c1_c0_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1464_c1_c0_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage2_c1_c0_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage2_c1_c0_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage2_c1_c0_c0_c0_c1_c0;
-                bias = ubias0_1_Stage2_c1_c0_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage2_c1_c0_c0_c0_c1_c0;
-                bias = ubias2_3_Stage2_c1_c0_c0_c0_c1_c0;
-            }
-        } else {
-            {
-                scale = uscale4_5_Stage2_c1_c0_c0_c0_c1_c0;
-                bias = ubias4_5_Stage2_c1_c0_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1464_c1_c0_c0_c0 = t * scale + bias;
-    return _sample1464_c1_c0_c0_c0;
-}
-mediump vec4 stage_Stage2_c1_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 child_c1_c0;
-    mediump vec4 _sample453_c1_c0_c0_c0;
-    _sample453_c1_c0_c0_c0 = stage_Stage2_c1_c0_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample453_c1_c0_c0_c0;
-    {
-        {
-            t.x = fract(t.x);
-        }
-        mediump vec4 _sample1464_c1_c0_c0_c0;
-        _sample1464_c1_c0_c0_c0 = stage_Stage2_c1_c0_c0_c0_c1_c0(t);
-        child_c1_c0 = _sample1464_c1_c0_c0_c0;
-    }
-    {
-        child_c1_c0.xyz *= child_c1_c0.w;
-    }
-    return child_c1_c0;
-}
-mediump vec4 stage_Stage2_c1_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 child_c1_c0;
-    child_c1_c0 = stage_Stage2_c1_c0_c0_c0(vec4(1.0));
-    child = child_c1_c0 * _input.w;
-    return child;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec4 child;
-        child = stage_Stage2_c1_c0(vec4(1.0));
-        output_Stage2 = vec4(child.w);
-    }
-    {
-        gl_FragColor = output_Stage1 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds_2.glsl
deleted file mode 100644
index ddacd34..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_texture_thresholds_2.glsl
+++ /dev/null
@@ -1,97 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 uscale0_1_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale6_7_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage2_c1_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias6_7_Stage2_c1_c0_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage2_c1_c0_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage2_c1_c0_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying highp vec2 vTransformedCoords_1_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    _sample1992 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return _sample1992;
-}
-mediump vec4 stage_Stage2_c1_c0_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample453_c1_c0_c0_c0;
-    mediump float t = vTransformedCoords_1_Stage0.x + 9.999999747378752e-06;
-    _sample453_c1_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample453_c1_c0_c0_c0;
-}
-mediump vec4 stage_Stage2_c1_c0_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1464_c1_c0_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage2_c1_c0_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage2_c1_c0_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage2_c1_c0_c0_c0_c1_c0;
-                bias = ubias0_1_Stage2_c1_c0_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage2_c1_c0_c0_c0_c1_c0;
-                bias = ubias2_3_Stage2_c1_c0_c0_c0_c1_c0;
-            }
-        } else {
-            if (t < uthresholds1_7_Stage2_c1_c0_c0_c0_c1_c0.z) {
-                scale = uscale4_5_Stage2_c1_c0_c0_c0_c1_c0;
-                bias = ubias4_5_Stage2_c1_c0_c0_c0_c1_c0;
-            } else {
-                scale = uscale6_7_Stage2_c1_c0_c0_c0_c1_c0;
-                bias = ubias6_7_Stage2_c1_c0_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1464_c1_c0_c0_c0 = t * scale + bias;
-    return _sample1464_c1_c0_c0_c0;
-}
-mediump vec4 stage_Stage2_c1_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 child_c1_c0;
-    mediump vec4 _sample453_c1_c0_c0_c0;
-    _sample453_c1_c0_c0_c0 = stage_Stage2_c1_c0_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample453_c1_c0_c0_c0;
-    {
-        {
-            t.x = fract(t.x);
-        }
-        mediump vec4 _sample1464_c1_c0_c0_c0;
-        _sample1464_c1_c0_c0_c0 = stage_Stage2_c1_c0_c0_c0_c1_c0(t);
-        child_c1_c0 = _sample1464_c1_c0_c0_c0;
-    }
-    {
-        child_c1_c0.xyz *= child_c1_c0.w;
-    }
-    return child_c1_c0;
-}
-mediump vec4 stage_Stage2_c1_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 child_c1_c0;
-    child_c1_c0 = stage_Stage2_c1_c0_c0_c0(vec4(1.0));
-    child = child_c1_c0 * _input.w;
-    return child;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec4 child;
-        child = stage_Stage2_c1_c0(vec4(1.0));
-        output_Stage2 = vec4(child.w);
-    }
-    {
-        gl_FragColor = output_Stage1 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_thresholds.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_thresholds.glsl
deleted file mode 100644
index 8dd97f4..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bias_color_scale_thresholds.glsl
+++ /dev/null
@@ -1,83 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 uscale0_1_Stage1_c0_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale2_3_Stage1_c0_c0_c0_c0_c1_c0;
-uniform highp vec4 uscale4_5_Stage1_c0_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias0_1_Stage1_c0_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias2_3_Stage1_c0_c0_c0_c0_c1_c0;
-uniform highp vec4 ubias4_5_Stage1_c0_c0_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds1_7_Stage1_c0_c0_c0_c0_c1_c0;
-uniform mediump vec4 uthresholds9_13_Stage1_c0_c0_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample453_c0_c0_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample453_c0_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample453_c0_c0_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1464_c0_c0_c0_c0;
-    mediump float t = _input.x;
-    highp vec4 scale, bias;
-    {
-        if (t < uthresholds1_7_Stage1_c0_c0_c0_c0_c1_c0.y) {
-            if (t < uthresholds1_7_Stage1_c0_c0_c0_c0_c1_c0.x) {
-                scale = uscale0_1_Stage1_c0_c0_c0_c0_c1_c0;
-                bias = ubias0_1_Stage1_c0_c0_c0_c0_c1_c0;
-            } else {
-                scale = uscale2_3_Stage1_c0_c0_c0_c0_c1_c0;
-                bias = ubias2_3_Stage1_c0_c0_c0_c0_c1_c0;
-            }
-        } else {
-            {
-                scale = uscale4_5_Stage1_c0_c0_c0_c0_c1_c0;
-                bias = ubias4_5_Stage1_c0_c0_c0_c0_c1_c0;
-            }
-        }
-    }
-    _sample1464_c0_c0_c0_c0 = t * scale + bias;
-    return _sample1464_c0_c0_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 child_c0_c0;
-    mediump vec4 _sample453_c0_c0_c0_c0;
-    _sample453_c0_c0_c0_c0 = stage_Stage1_c0_c0_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample453_c0_c0_c0_c0;
-    {
-        {
-            t.x = fract(t.x);
-        }
-        mediump vec4 _sample1464_c0_c0_c0_c0;
-        _sample1464_c0_c0_c0_c0 = stage_Stage1_c0_c0_c0_c0_c1_c0(t);
-        child_c0_c0 = _sample1464_c0_c0_c0_c0;
-    }
-    {
-        child_c0_c0.xyz *= child_c0_c0.w;
-    }
-    return child_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 child_c0_c0;
-    child_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    child = child_c0_c0 * _input.w;
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = vec4(child.w);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_circle_color.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_circle_color.glsl
deleted file mode 100644
index 77e95c3..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_circle_color.glsl
+++ /dev/null
@@ -1,61 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform mediump vec4 ustart_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uend_Stage1_c0_c0_c1_c0;
-varying highp vec4 vinCircleEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = length(vTransformedCoords_0_Stage0);
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    _sample1767_c0_c0 = (1.0 - t) * ustart_Stage1_c0_c0_c1_c0 + t * uend_Stage1_c0_c0_c1_c0;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp vec4 circleEdge;
-        circleEdge = vinCircleEdge_Stage0;
-        outputColor_Stage0 = vinColor_Stage0;
-        highp float d = length(circleEdge.xy);
-        mediump float distanceToOuterEdge = circleEdge.z * (1.0 - d);
-        mediump float edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color.glsl
deleted file mode 100644
index d0e6ce0..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color.glsl
+++ /dev/null
@@ -1,49 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform mediump vec4 ustart_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uend_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    _sample1767_c0_c0 = (1.0 - t) * ustart_Stage1_c0_c0_c1_c0 + t * uend_Stage1_c0_c0_c1_c0;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_2.glsl
deleted file mode 100644
index 8f365d1..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_2.glsl
+++ /dev/null
@@ -1,49 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform mediump vec4 ustart_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uend_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = length(vTransformedCoords_0_Stage0);
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    _sample1767_c0_c0 = (1.0 - t) * ustart_Stage1_c0_c0_c1_c0 + t * uend_Stage1_c0_c0_c1_c0;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_3.glsl
deleted file mode 100644
index 4cf129e..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_3.glsl
+++ /dev/null
@@ -1,53 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform mediump vec4 ustart_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uend_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = length(vTransformedCoords_0_Stage0);
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    _sample1767_c0_c0 = (1.0 - t) * ustart_Stage1_c0_c0_c1_c0 + t * uend_Stage1_c0_c0_c1_c0;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage.glsl
deleted file mode 100644
index 6adacd9..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage.glsl
+++ /dev/null
@@ -1,55 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform mediump vec4 ustart_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uend_Stage1_c0_c0_c1_c0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-varying highp float vcoverage_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    _sample1767_c0_c0 = (1.0 - t) * ustart_Stage1_c0_c0_c1_c0 + t * uend_Stage1_c0_c0_c1_c0;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp float coverage = vcoverage_Stage0;
-        outputCoverage_Stage0 = vec4(coverage);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage_texture.glsl
deleted file mode 100644
index 07a5ac3..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_coverage_texture.glsl
+++ /dev/null
@@ -1,54 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-varying highp float vcoverage_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump vec2 coord = vec2(_input.x, 0.5);
-    _sample1767_c0_c0 = texture2D(uTextureSampler_0_Stage1, coord);
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp float coverage = vcoverage_Stage0;
-        outputCoverage_Stage0 = vec4(coverage);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_decal_ellipse_scaletranslate_texdom_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_decal_ellipse_scaletranslate_texdom_texturew.glsl
deleted file mode 100644
index 70771b9..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_decal_ellipse_scaletranslate_texdom_texturew.glsl
+++ /dev/null
@@ -1,84 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform mediump vec4 ustart_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uend_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uscaleAndTranslate_Stage2;
-uniform mediump vec4 uTexDom_Stage2;
-uniform mediump vec3 uDecalParams_Stage2;
-uniform sampler2D uTextureSampler_0_Stage2;
-varying highp vec3 vEllipseOffsets_Stage0;
-varying highp vec4 vEllipseRadii_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = length(vTransformedCoords_0_Stage0);
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    _sample1767_c0_c0 = (1.0 - t) * ustart_Stage1_c0_c0_c1_c0 + t * uend_Stage1_c0_c0_c1_c0;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp vec2 offset = vEllipseOffsets_Stage0.xy;
-        highp float test = dot(offset, offset) - 1.0;
-        highp vec2 grad = (2.0 * offset) * (vEllipseOffsets_Stage0.z * vEllipseRadii_Stage0.xy);
-        highp float grad_dot = dot(grad, grad);
-        grad_dot = max(grad_dot, 6.103600026108325e-05);
-        highp float invlen = vEllipseOffsets_Stage0.z * inversesqrt(grad_dot);
-        highp float edgeAlpha = clamp(0.5 - test * invlen, 0.0, 1.0);
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage2.xy + uscaleAndTranslate_Stage2.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage2.xy, uTexDom_Stage2.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage2, clampedCoord).wwww * outputCoverage_Stage0;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage2.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage2.y);
-            if (err > uDecalParams_Stage2.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage2.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage2 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    {
-        gl_FragColor = output_Stage1 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture.glsl
deleted file mode 100644
index 3c26f70..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture.glsl
+++ /dev/null
@@ -1,64 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump vec2 coord = vec2(_input.x, 0.5);
-    _sample1767_c0_c0 = texture2D(uTextureSampler_0_Stage1, coord);
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture_2.glsl
deleted file mode 100644
index 298ccc0..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_quad_texture_2.glsl
+++ /dev/null
@@ -1,66 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump vec2 coord = vec2(_input.x, 0.5);
-    _sample1767_c0_c0 = texture2D(uTextureSampler_0_Stage1, coord);
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texindex_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texindex_texturew.glsl
deleted file mode 100644
index 92cba89..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texindex_texturew.glsl
+++ /dev/null
@@ -1,61 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform sampler2D uTextureSampler_0_Stage0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump vec2 coord = vec2(_input.x, 0.5);
-    _sample1767_c0_c0 = texture2D(uTextureSampler_0_Stage1, coord);
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump vec4 texColor;
-        {
-            texColor = texture2D(uTextureSampler_0_Stage0, vTextureCoords_Stage0).wwww;
-        }
-        outputCoverage_Stage0 = texColor;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture.glsl
deleted file mode 100644
index 6f7f457..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture.glsl
+++ /dev/null
@@ -1,48 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump vec2 coord = vec2(_input.x, 0.5);
-    _sample1767_c0_c0 = texture2D(uTextureSampler_0_Stage1, coord);
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture_2.glsl
deleted file mode 100644
index edebf92..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texture_2.glsl
+++ /dev/null
@@ -1,52 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump vec2 coord = vec2(_input.x, 0.5);
-    _sample1767_c0_c0 = texture2D(uTextureSampler_0_Stage1, coord);
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texturew.glsl
deleted file mode 100644
index df8be8f..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texturew.glsl
+++ /dev/null
@@ -1,55 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform mediump vec4 ustart_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uend_Stage1_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage2;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying highp vec2 vTransformedCoords_1_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = length(vTransformedCoords_0_Stage0);
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    _sample1767_c0_c0 = (1.0 - t) * ustart_Stage1_c0_c0_c1_c0 + t * uend_Stage1_c0_c0_c1_c0;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        output_Stage2 = texture2D(uTextureSampler_0_Stage2, vTransformedCoords_1_Stage0).wwww;
-    }
-    {
-        gl_FragColor = output_Stage1 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texturew_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texturew_2.glsl
deleted file mode 100644
index 78b3466..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texturew_2.glsl
+++ /dev/null
@@ -1,55 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform mediump vec4 ustart_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uend_Stage1_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage2;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying highp vec2 vTransformedCoords_1_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    _sample1767_c0_c0 = (1.0 - t) * ustart_Stage1_c0_c0_c1_c0 + t * uend_Stage1_c0_c0_c1_c0;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        _sample1992 = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        _sample1992 = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        _sample1992 = _sample1767_c0_c0;
-    }
-    return _sample1992;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        output_Stage2 = texture2D(uTextureSampler_0_Stage2, vTransformedCoords_1_Stage0).wwww;
-    }
-    {
-        gl_FragColor = output_Stage1 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texturew_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texturew_3.glsl
deleted file mode 100644
index 4b409d6..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_border_color_texturew_3.glsl
+++ /dev/null
@@ -1,59 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uleftBorderColor_Stage1_c0_c0;
-uniform mediump vec4 urightBorderColor_Stage1_c0_c0;
-uniform mediump vec4 ustart_Stage1_c0_c0_c1_c0;
-uniform mediump vec4 uend_Stage1_c0_c0_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage2;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying highp vec2 vTransformedCoords_1_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1099_c0_c0;
-    mediump float t = length(vTransformedCoords_0_Stage0);
-    _sample1099_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample1099_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1767_c0_c0;
-    mediump float t = _input.x;
-    _sample1767_c0_c0 = (1.0 - t) * ustart_Stage1_c0_c0_c1_c0 + t * uend_Stage1_c0_c0_c1_c0;
-    return _sample1767_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 _sample1099_c0_c0;
-    _sample1099_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample1099_c0_c0;
-    if (t.x < 0.0) {
-        child = uleftBorderColor_Stage1_c0_c0;
-    } else if (t.x > 1.0) {
-        child = urightBorderColor_Stage1_c0_c0;
-    } else {
-        mediump vec4 _sample1767_c0_c0;
-        _sample1767_c0_c0 = stage_Stage1_c0_c0_c1_c0(t);
-        child = _sample1767_c0_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    mediump vec4 output_Stage2;
-    {
-        output_Stage2 = texture2D(uTextureSampler_0_Stage2, vTransformedCoords_1_Stage0).wwww;
-    }
-    {
-        gl_FragColor = output_Stage1 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bounds_increment_kernel_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bounds_increment_kernel_texture.glsl
deleted file mode 100644
index 42c9f0f..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bounds_increment_kernel_texture.glsl
+++ /dev/null
@@ -1,145 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform mediump vec2 uImageIncrement_Stage1;

-uniform mediump vec2 uBounds_Stage1;

-uniform mediump vec4 uKernel_Stage1[7];

-uniform sampler2D uTextureSampler_0_Stage1;

-varying highp vec2 vTransformedCoords_0_Stage0;

-void main() {

-    mediump vec4 output_Stage1;

-    {

-        output_Stage1 = vec4(0.0, 0.0, 0.0, 0.0);

-        highp vec2 coord = vTransformedCoords_0_Stage0 - 12.0 * uImageIncrement_Stage1;

-        highp vec2 coordSampled = vec2(0.0, 0.0);

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].x;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].y;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].z;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].w;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].x;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].y;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].z;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].w;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].x;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].y;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].z;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].w;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].x;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].y;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].z;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].w;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].x;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].y;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].z;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].w;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].x;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].y;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].z;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].w;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.x >= uBounds_Stage1.x && coord.x <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[6].x;

-        }

-        coord += uImageIncrement_Stage1;

-    }

-    {

-        gl_FragColor = output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bounds_increment_kernel_texture_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bounds_increment_kernel_texture_2.glsl
deleted file mode 100644
index f3743d7..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bounds_increment_kernel_texture_2.glsl
+++ /dev/null
@@ -1,145 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform mediump vec2 uImageIncrement_Stage1;

-uniform mediump vec2 uBounds_Stage1;

-uniform mediump vec4 uKernel_Stage1[7];

-uniform sampler2D uTextureSampler_0_Stage1;

-varying highp vec2 vTransformedCoords_0_Stage0;

-void main() {

-    mediump vec4 output_Stage1;

-    {

-        output_Stage1 = vec4(0.0, 0.0, 0.0, 0.0);

-        highp vec2 coord = vTransformedCoords_0_Stage0 - 12.0 * uImageIncrement_Stage1;

-        highp vec2 coordSampled = vec2(0.0, 0.0);

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].x;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].y;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].z;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].w;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].x;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].y;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].z;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].w;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].x;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].y;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].z;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].w;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].x;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].y;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].z;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].w;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].x;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].y;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].z;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].w;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].x;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].y;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].z;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].w;

-        }

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        if (coord.y >= uBounds_Stage1.x && coord.y <= uBounds_Stage1.y) {

-            output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[6].x;

-        }

-        coord += uImageIncrement_Stage1;

-    }

-    {

-        gl_FragColor = output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bounds_increment_kernel_texture_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bounds_increment_kernel_texture_3.glsl
deleted file mode 100644
index 4285cdf..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bounds_increment_kernel_texture_3.glsl
+++ /dev/null
@@ -1,120 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec2 uImageIncrement_Stage1;
-uniform mediump vec2 uBounds_Stage1;
-uniform mediump vec4 uKernel_Stage1[7];
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        output_Stage1 = vec4(0.0, 0.0, 0.0, 0.0);
-        highp vec2 coord = vTransformedCoords_0_Stage0 - 12.0 * uImageIncrement_Stage1;
-        highp vec2 coordSampled = vec2(0.0, 0.0);
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].x;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].y;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].z;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].w;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].x;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].y;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].z;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].w;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].x;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].y;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].z;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].w;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].x;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].y;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].z;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].w;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].x;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].y;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].z;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].w;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].x;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].y;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].z;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].w;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.y = clamp(coord.y, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[6].x;
-        coord += uImageIncrement_Stage1;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bounds_increment_kernel_texture_4.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bounds_increment_kernel_texture_4.glsl
deleted file mode 100644
index 768ef3a..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_bounds_increment_kernel_texture_4.glsl
+++ /dev/null
@@ -1,120 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec2 uImageIncrement_Stage1;
-uniform mediump vec2 uBounds_Stage1;
-uniform mediump vec4 uKernel_Stage1[7];
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        output_Stage1 = vec4(0.0, 0.0, 0.0, 0.0);
-        highp vec2 coord = vTransformedCoords_0_Stage0 - 12.0 * uImageIncrement_Stage1;
-        highp vec2 coordSampled = vec2(0.0, 0.0);
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].x;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].y;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].z;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].w;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].x;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].y;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].z;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].w;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].x;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].y;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].z;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].w;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].x;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].y;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].z;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].w;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].x;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].y;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].z;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].w;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].x;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].y;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].z;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].w;
-        coord += uImageIncrement_Stage1;
-        coordSampled = coord;
-        coordSampled.x = clamp(coord.x, uBounds_Stage1.x, uBounds_Stage1.y);
-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[6].x;
-        coord += uImageIncrement_Stage1;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_color.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_color.glsl
deleted file mode 100644
index 4ded4d6..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_color.glsl
+++ /dev/null
@@ -1,25 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-varying highp vec4 vinCircleEdge_Stage0;

-varying mediump vec4 vinColor_Stage0;

-void main() {

-    mediump vec4 outputColor_Stage0;

-    mediump vec4 outputCoverage_Stage0;

-    {

-        highp vec4 circleEdge;

-        circleEdge = vinCircleEdge_Stage0;

-        outputColor_Stage0 = vinColor_Stage0;

-        highp float d = length(circleEdge.xy);

-        mediump float distanceToOuterEdge = circleEdge.z * (1.0 - d);

-        mediump float edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);

-        mediump float distanceToInnerEdge = circleEdge.z * (d - circleEdge.w);

-        mediump float innerAlpha = clamp(distanceToInnerEdge, 0.0, 1.0);

-        edgeAlpha *= innerAlpha;

-        outputCoverage_Stage0 = vec4(edgeAlpha);

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_color_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_color_2.glsl
deleted file mode 100644
index a91964d..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_color_2.glsl
+++ /dev/null
@@ -1,22 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-varying highp vec4 vinCircleEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp vec4 circleEdge;
-        circleEdge = vinCircleEdge_Stage0;
-        outputColor_Stage0 = vinColor_Stage0;
-        highp float d = length(circleEdge.xy);
-        mediump float distanceToOuterEdge = circleEdge.z * (1.0 - d);
-        mediump float edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_color_edges.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_color_edges.glsl
deleted file mode 100644
index 450d975..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_color_edges.glsl
+++ /dev/null
@@ -1,43 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage1[4];
-varying highp vec4 vinCircleEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp vec4 circleEdge;
-        circleEdge = vinCircleEdge_Stage0;
-        outputColor_Stage0 = vinColor_Stage0;
-        highp float d = length(circleEdge.xy);
-        mediump float distanceToOuterEdge = circleEdge.z * (1.0 - d);
-        mediump float edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage1 = outputCoverage_Stage0 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_color_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_color_texture.glsl
deleted file mode 100644
index eff113e..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_color_texture.glsl
+++ /dev/null
@@ -1,35 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec2 uDstTextureUpperLeft_Stage1;
-uniform mediump vec2 uDstTextureCoordScale_Stage1;
-uniform sampler2D uDstTextureSampler_Stage1;
-varying highp vec4 vinCircleEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp vec4 circleEdge;
-        circleEdge = vinCircleEdge_Stage0;
-        outputColor_Stage0 = vinColor_Stage0;
-        highp float d = length(circleEdge.xy);
-        mediump float distanceToOuterEdge = circleEdge.z * (1.0 - d);
-        mediump float edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    {
-        if (all(lessThanEqual(outputCoverage_Stage0.xyz, vec3(0.0)))) {
-            discard;
-        }
-        mediump vec2 _dstTexCoord = (sk_FragCoord.xy - uDstTextureUpperLeft_Stage1) * uDstTextureCoordScale_Stage1;
-        _dstTexCoord.y = 1.0 - _dstTexCoord.y;
-        mediump vec4 _dstColor = texture2D(uDstTextureSampler_Stage1, _dstTexCoord);
-        gl_FragColor.w = outputColor_Stage0.w + (1.0 - outputColor_Stage0.w) * _dstColor.w;
-        gl_FragColor.xyz = ((1.0 - outputColor_Stage0.w) * _dstColor.xyz + (1.0 - _dstColor.w) * outputColor_Stage0.xyz) + outputColor_Stage0.xyz * _dstColor.xyz;
-        gl_FragColor = outputCoverage_Stage0 * gl_FragColor + (vec4(1.0) - outputCoverage_Stage0) * _dstColor;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_texture.glsl
deleted file mode 100644
index 87b7c3e..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_circle_texture.glsl
+++ /dev/null
@@ -1,31 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 ucircle_Stage1;
-uniform sampler2D uTextureSampler_0_Stage0;
-varying highp vec2 vlocalCoord_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vec4(1.0);
-        highp vec2 texCoord;
-        texCoord = vlocalCoord_Stage0;
-        outputColor_Stage0 = texture2D(uTextureSampler_0_Stage0, texCoord);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float d;
-        {
-            d = (1.0 - length((ucircle_Stage1.xy - sk_FragCoord.xy) * ucircle_Stage1.w)) * ucircle_Stage1.z;
-        }
-        {
-            output_Stage1 = vec4(clamp(d, 0.0, 1.0));
-        }
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color.glsl
deleted file mode 100644
index 7ad14e0..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color.glsl
+++ /dev/null
@@ -1,14 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-varying mediump vec4 vcolor_Stage0;

-void main() {

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = vcolor_Stage0;

-    }

-    {

-        gl_FragColor = outputColor_Stage0;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_2.glsl
deleted file mode 100644
index 0211cf4..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_2.glsl
+++ /dev/null
@@ -1,14 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform mediump vec4 uColor_Stage0;

-void main() {

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = uColor_Stage0;

-    }

-    {

-        gl_FragColor = outputColor_Stage0;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage.glsl
deleted file mode 100644
index 7272bcb..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage.glsl
+++ /dev/null
@@ -1,19 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uColor_Stage0;
-varying mediump float vinCoverage_Stage0;
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = uColor_Stage0;
-        mediump float alpha = 1.0;
-        alpha = vinCoverage_Stage0;
-        outputCoverage_Stage0 = vec4(alpha);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_decal_hairquad_scaletranslate_texdom_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_decal_hairquad_scaletranslate_texdom_texturew.glsl
deleted file mode 100644
index cb09752..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_decal_hairquad_scaletranslate_texdom_texturew.glsl
+++ /dev/null
@@ -1,48 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uColor_Stage0;
-uniform mediump float uCoverage_Stage0;
-uniform mediump vec4 uscaleAndTranslate_Stage1;
-uniform mediump vec4 uTexDom_Stage1;
-uniform mediump vec3 uDecalParams_Stage1;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying mediump vec4 vHairQuadEdge_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = uColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vHairQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vHairQuadEdge_Stage0.xy);
-        mediump vec2 gF = vec2((2.0 * vHairQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vHairQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-        edgeAlpha = vHairQuadEdge_Stage0.x * vHairQuadEdge_Stage0.x - vHairQuadEdge_Stage0.y;
-        edgeAlpha = sqrt((edgeAlpha * edgeAlpha) / dot(gF, gF));
-        edgeAlpha = max(1.0 - edgeAlpha, 0.0);
-        outputCoverage_Stage0 = vec4(uCoverage_Stage0 * edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage1.xy + uscaleAndTranslate_Stage1.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1.xy, uTexDom_Stage1.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord).wwww * outputCoverage_Stage0;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage1.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage1.y);
-            if (err > uDecalParams_Stage1.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage1.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage1 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_decal_scaletranslate_texdom_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_decal_scaletranslate_texdom_texturew.glsl
deleted file mode 100644
index d8d359e..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_decal_scaletranslate_texdom_texturew.glsl
+++ /dev/null
@@ -1,41 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uColor_Stage0;
-uniform mediump vec4 uscaleAndTranslate_Stage1;
-uniform mediump vec4 uTexDom_Stage1;
-uniform mediump vec3 uDecalParams_Stage1;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying mediump float vinCoverage_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = uColor_Stage0;
-        mediump float alpha = 1.0;
-        alpha = vinCoverage_Stage0;
-        outputCoverage_Stage0 = vec4(alpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage1.xy + uscaleAndTranslate_Stage1.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1.xy, uTexDom_Stage1.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord).wwww * outputCoverage_Stage0;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage1.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage1.y);
-            if (err > uDecalParams_Stage1.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage1.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage1 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_edges.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_edges.glsl
deleted file mode 100644
index 2a9dfce..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_edges.glsl
+++ /dev/null
@@ -1,40 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uColor_Stage0;
-uniform mediump vec3 uedges_Stage1[4];
-varying mediump float vinCoverage_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = uColor_Stage0;
-        mediump float alpha = 1.0;
-        alpha = vinCoverage_Stage0;
-        outputCoverage_Stage0 = vec4(alpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage1 = outputCoverage_Stage0 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_edges_geomdomain.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_edges_geomdomain.glsl
deleted file mode 100644
index 8b100d2..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_edges_geomdomain.glsl
+++ /dev/null
@@ -1,47 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage1[4];
-varying mediump vec4 vcolor_Stage0;
-varying highp float vcoverage_Stage0;
-varying highp vec4 vgeomDomain_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-        highp float coverage = vcoverage_Stage0 * sk_FragCoord.w;
-        highp vec4 geoDomain;
-        geoDomain = vgeomDomain_Stage0;
-        if (coverage < 0.5) {
-            highp vec4 dists4 = clamp(vec4(1.0, 1.0, -1.0, -1.0) * (sk_FragCoord.xyxy - geoDomain), 0.0, 1.0);
-            highp vec2 dists2 = dists4.xy * dists4.zw;
-            coverage = min(coverage, dists2.x * dists2.y);
-        }
-        outputCoverage_Stage0 = vec4(coverage);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage1 = outputCoverage_Stage0 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_edges_hairquad.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_edges_hairquad.glsl
deleted file mode 100644
index 43b519a..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_edges_hairquad.glsl
+++ /dev/null
@@ -1,47 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uColor_Stage0;
-uniform mediump float uCoverage_Stage0;
-uniform mediump vec3 uedges_Stage1[4];
-varying mediump vec4 vHairQuadEdge_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = uColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vHairQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vHairQuadEdge_Stage0.xy);
-        mediump vec2 gF = vec2((2.0 * vHairQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vHairQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-        edgeAlpha = vHairQuadEdge_Stage0.x * vHairQuadEdge_Stage0.x - vHairQuadEdge_Stage0.y;
-        edgeAlpha = sqrt((edgeAlpha * edgeAlpha) / dot(gF, gF));
-        edgeAlpha = max(1.0 - edgeAlpha, 0.0);
-        outputCoverage_Stage0 = vec4(uCoverage_Stage0 * edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage1 = outputCoverage_Stage0 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_geomdomain.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_geomdomain.glsl
deleted file mode 100644
index 50d9a4e..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_geomdomain.glsl
+++ /dev/null
@@ -1,28 +0,0 @@
-#version 100

-

-uniform highp float u_skRTHeight;

-precision mediump float;

-precision mediump sampler2D;

-varying mediump vec4 vcolor_Stage0;

-varying highp float vcoverage_Stage0;

-varying highp vec4 vgeomDomain_Stage0;

-void main() {

-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);

-    mediump vec4 outputColor_Stage0;

-    mediump vec4 outputCoverage_Stage0;

-    {

-        outputColor_Stage0 = vcolor_Stage0;

-        highp float coverage = vcoverage_Stage0;

-        highp vec4 geoDomain;

-        geoDomain = vgeomDomain_Stage0;

-        if (coverage < 0.5) {

-            highp vec4 dists4 = clamp(vec4(1.0, 1.0, -1.0, -1.0) * (sk_FragCoord.xyxy - geoDomain), 0.0, 1.0);

-            highp vec2 dists2 = dists4.xy * dists4.zw;

-            coverage = min(coverage, dists2.x * dists2.y);

-        }

-        outputCoverage_Stage0 = vec4(coverage);

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_geomdomain_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_geomdomain_2.glsl
deleted file mode 100644
index ef5504c..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_geomdomain_2.glsl
+++ /dev/null
@@ -1,28 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-varying mediump vec4 vcolor_Stage0;
-varying highp float vcoverage_Stage0;
-varying highp vec4 vgeomDomain_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-        highp float coverage = vcoverage_Stage0 * sk_FragCoord.w;
-        highp vec4 geoDomain;
-        geoDomain = vgeomDomain_Stage0;
-        if (coverage < 0.5) {
-            highp vec4 dists4 = clamp(vec4(1.0, 1.0, -1.0, -1.0) * (sk_FragCoord.xyxy - geoDomain), 0.0, 1.0);
-            highp vec2 dists2 = dists4.xy * dists4.zw;
-            coverage = min(coverage, dists2.x * dists2.y);
-        }
-        outputCoverage_Stage0 = vec4(coverage);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_hairquad.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_hairquad.glsl
deleted file mode 100644
index 894f33a..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_hairquad.glsl
+++ /dev/null
@@ -1,26 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uColor_Stage0;
-uniform mediump float uCoverage_Stage0;
-varying mediump vec4 vHairQuadEdge_Stage0;
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = uColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vHairQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vHairQuadEdge_Stage0.xy);
-        mediump vec2 gF = vec2((2.0 * vHairQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vHairQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-        edgeAlpha = vHairQuadEdge_Stage0.x * vHairQuadEdge_Stage0.x - vHairQuadEdge_Stage0.y;
-        edgeAlpha = sqrt((edgeAlpha * edgeAlpha) / dot(gF, gF));
-        edgeAlpha = max(1.0 - edgeAlpha, 0.0);
-        outputCoverage_Stage0 = vec4(uCoverage_Stage0 * edgeAlpha);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_texdom_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_texdom_texture.glsl
deleted file mode 100644
index 76979ee..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_texdom_texture.glsl
+++ /dev/null
@@ -1,49 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uTexDom_Stage1_c0_c0;
-uniform mediump vec4 ucolor_Stage2_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-varying highp float vcoverage_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    {
-        highp vec2 origCoord = vTransformedCoords_0_Stage0;
-        highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1_c0_c0.xy, uTexDom_Stage1_c0_c0.zw);
-        mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord) * _input;
-        _sample1992 = inside;
-    }
-    return _sample1992;
-}
-mediump vec4 stage_Stage2_c1_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    {
-        child = ucolor_Stage2_c1_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp float coverage = vcoverage_Stage0;
-        outputCoverage_Stage0 = vec4(coverage);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec4 child;
-        child = stage_Stage2_c1_c0(vec4(1.0));
-        output_Stage2 = child * output_Stage1.w;
-    }
-    {
-        gl_FragColor = output_Stage2 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_texture.glsl
deleted file mode 100644
index b4fbafc..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_coverage_texture.glsl
+++ /dev/null
@@ -1,29 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-varying highp float vcoverage_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    _sample1992 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return _sample1992;
-}
-void main() {
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp float coverage = vcoverage_Stage0;
-        outputCoverage_Stage0 = vec4(coverage);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        gl_FragColor = output_Stage1 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_edges_quad_scaletranslate_texdom_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_edges_quad_scaletranslate_texdom_texturew.glsl
deleted file mode 100644
index 7bc3642..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_edges_quad_scaletranslate_texdom_texturew.glsl
+++ /dev/null
@@ -1,69 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uscaleAndTranslate_Stage1;
-uniform mediump vec4 uTexDom_Stage1;
-uniform mediump vec3 uDecalParams_Stage1;
-uniform mediump vec3 uedges_Stage2[4];
-uniform sampler2D uTextureSampler_0_Stage1;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage1.xy + uscaleAndTranslate_Stage1.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1.xy, uTexDom_Stage1.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord).wwww * outputCoverage_Stage0;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage1.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage1.y);
-            if (err > uDecalParams_Stage1.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage1.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage1 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage2[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage2 = output_Stage1 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_edges_scaletranslate_texdom_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_edges_scaletranslate_texdom_texturew.glsl
deleted file mode 100644
index 8f976a2..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_edges_scaletranslate_texdom_texturew.glsl
+++ /dev/null
@@ -1,52 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uscaleAndTranslate_Stage1;
-uniform mediump vec4 uTexDom_Stage1;
-uniform mediump vec3 uDecalParams_Stage1;
-uniform mediump vec3 uedges_Stage2[3];
-uniform sampler2D uTextureSampler_0_Stage1;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage1.xy + uscaleAndTranslate_Stage1.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1.xy, uTexDom_Stage1.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord).wwww;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage1.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage1.y);
-            if (err > uDecalParams_Stage1.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage1.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage1 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage2[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage2 = output_Stage1 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_edges_scaletranslate_texdom_texturew_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_edges_scaletranslate_texdom_texturew_2.glsl
deleted file mode 100644
index ede7578..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_edges_scaletranslate_texdom_texturew_2.glsl
+++ /dev/null
@@ -1,61 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uscaleAndTranslate_Stage2;
-uniform mediump vec4 uTexDom_Stage2;
-uniform mediump vec3 uDecalParams_Stage2;
-uniform mediump vec3 uedges_Stage3[4];
-uniform sampler2D uTextureSampler_0_Stage1;
-uniform sampler2D uTextureSampler_0_Stage2;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        output_Stage1 = texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0).wwww;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage2.xy + uscaleAndTranslate_Stage2.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage2.xy, uTexDom_Stage2.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage2, clampedCoord).wwww * output_Stage1;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage2.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage2.y);
-            if (err > uDecalParams_Stage2.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage2.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage2 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    mediump vec4 output_Stage3;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage3[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage3[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage3[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage3[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage3 = output_Stage2 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage3;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_edges_scaletranslate_texdom_texturew_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_edges_scaletranslate_texdom_texturew_3.glsl
deleted file mode 100644
index 53738e5..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_edges_scaletranslate_texdom_texturew_3.glsl
+++ /dev/null
@@ -1,55 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uscaleAndTranslate_Stage1;
-uniform mediump vec4 uTexDom_Stage1;
-uniform mediump vec3 uDecalParams_Stage1;
-uniform mediump vec3 uedges_Stage2[4];
-uniform sampler2D uTextureSampler_0_Stage1;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage1.xy + uscaleAndTranslate_Stage1.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1.xy, uTexDom_Stage1.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord).wwww;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage1.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage1.y);
-            if (err > uDecalParams_Stage1.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage1.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage1 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage2[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage2 = output_Stage1 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_ellipse_scaletranslate_texdom_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_ellipse_scaletranslate_texdom_texture.glsl
deleted file mode 100644
index b1749e3..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_ellipse_scaletranslate_texdom_texture.glsl
+++ /dev/null
@@ -1,55 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uscaleAndTranslate_Stage1;
-uniform mediump vec4 uTexDom_Stage1;
-uniform mediump vec3 uDecalParams_Stage1;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec3 vEllipseOffsets_Stage0;
-varying highp vec4 vEllipseRadii_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        highp vec2 offset = vEllipseOffsets_Stage0.xy;
-        offset *= vEllipseRadii_Stage0.xy;
-        highp float test = dot(offset, offset) - 1.0;
-        highp vec2 grad = (2.0 * offset) * (vEllipseOffsets_Stage0.z * vEllipseRadii_Stage0.xy);
-        highp float grad_dot = dot(grad, grad);
-        grad_dot = max(grad_dot, 6.103600026108325e-05);
-        highp float invlen = vEllipseOffsets_Stage0.z * inversesqrt(grad_dot);
-        highp float edgeAlpha = clamp(0.5 - test * invlen, 0.0, 1.0);
-        offset = vEllipseOffsets_Stage0.xy * vEllipseRadii_Stage0.zw;
-        test = dot(offset, offset) - 1.0;
-        grad = (2.0 * offset) * (vEllipseOffsets_Stage0.z * vEllipseRadii_Stage0.zw);
-        grad_dot = dot(grad, grad);
-        grad_dot = max(grad_dot, 6.103600026108325e-05);
-        invlen = vEllipseOffsets_Stage0.z * inversesqrt(grad_dot);
-        edgeAlpha *= clamp(0.5 + test * invlen, 0.0, 1.0);
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage1.xy + uscaleAndTranslate_Stage1.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1.xy, uTexDom_Stage1.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord).wwww * outputCoverage_Stage0;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage1.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage1.y);
-            if (err > uDecalParams_Stage1.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage1.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage1 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_quad_scaletranslate_texdom_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_quad_scaletranslate_texdom_texturew.glsl
deleted file mode 100644
index a8c8198..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_quad_scaletranslate_texdom_texturew.glsl
+++ /dev/null
@@ -1,50 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uscaleAndTranslate_Stage1;
-uniform mediump vec4 uTexDom_Stage1;
-uniform mediump vec3 uDecalParams_Stage1;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage1.xy + uscaleAndTranslate_Stage1.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1.xy, uTexDom_Stage1.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord).wwww * outputCoverage_Stage0;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage1.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage1.y);
-            if (err > uDecalParams_Stage1.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage1.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage1 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_scaletranslate_texdom_texindex_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_scaletranslate_texdom_texindex_texturew.glsl
deleted file mode 100644
index 32b5458..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_scaletranslate_texdom_texindex_texturew.glsl
+++ /dev/null
@@ -1,45 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uscaleAndTranslate_Stage1;
-uniform mediump vec4 uTexDom_Stage1;
-uniform mediump vec3 uDecalParams_Stage1;
-uniform sampler2D uTextureSampler_0_Stage0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump vec4 texColor;
-        {
-            texColor = texture2D(uTextureSampler_0_Stage0, vTextureCoords_Stage0).wwww;
-        }
-        outputCoverage_Stage0 = texColor;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage1.xy + uscaleAndTranslate_Stage1.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1.xy, uTexDom_Stage1.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord).wwww * outputCoverage_Stage0;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage1.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage1.y);
-            if (err > uDecalParams_Stage1.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage1.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage1 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_scaletranslate_texdom_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_scaletranslate_texdom_texturew.glsl
deleted file mode 100644
index eaca17b..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_scaletranslate_texdom_texturew.glsl
+++ /dev/null
@@ -1,36 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uscaleAndTranslate_Stage1;
-uniform mediump vec4 uTexDom_Stage1;
-uniform mediump vec3 uDecalParams_Stage1;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage1.xy + uscaleAndTranslate_Stage1.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1.xy, uTexDom_Stage1.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord).wwww;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage1.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage1.y);
-            if (err > uDecalParams_Stage1.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage1.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage1 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_scaletranslate_texdom_texturew_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_scaletranslate_texdom_texturew_2.glsl
deleted file mode 100644
index 4092ef8..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_scaletranslate_texdom_texturew_2.glsl
+++ /dev/null
@@ -1,45 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uscaleAndTranslate_Stage2;
-uniform mediump vec4 uTexDom_Stage2;
-uniform mediump vec3 uDecalParams_Stage2;
-uniform sampler2D uTextureSampler_0_Stage1;
-uniform sampler2D uTextureSampler_0_Stage2;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    _sample1992 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return _sample1992;
-}
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage2.xy + uscaleAndTranslate_Stage2.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage2.xy, uTexDom_Stage2.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage2, clampedCoord).wwww;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage2.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage2.y);
-            if (err > uDecalParams_Stage2.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage2.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage2 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    {
-        gl_FragColor = output_Stage1 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_scaletranslate_texdom_texturew_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_scaletranslate_texdom_texturew_3.glsl
deleted file mode 100644
index 2845edf..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_decal_scaletranslate_texdom_texturew_3.glsl
+++ /dev/null
@@ -1,42 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uscaleAndTranslate_Stage2;
-uniform mediump vec4 uTexDom_Stage2;
-uniform mediump vec3 uDecalParams_Stage2;
-uniform sampler2D uTextureSampler_0_Stage1;
-uniform sampler2D uTextureSampler_0_Stage2;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        output_Stage1 = texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0).wwww;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage2.xy + uscaleAndTranslate_Stage2.zw;
-        {
-            highp vec2 origCoord = coords;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage2.xy, uTexDom_Stage2.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage2, clampedCoord).wwww * output_Stage1;
-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage2.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage2.y);
-            if (err > uDecalParams_Stage2.z) {
-                err = 1.0;
-            } else if (uDecalParams_Stage2.z < 1.0) {
-                err = 0.0;
-            }
-            output_Stage2 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);
-        }
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges.glsl
deleted file mode 100644
index ec0b725..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges.glsl
+++ /dev/null
@@ -1,32 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage1[3];
-varying mediump vec4 vcolor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage1 = vec4(alpha);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_2.glsl
deleted file mode 100644
index 40b5af2..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_2.glsl
+++ /dev/null
@@ -1,35 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage1[4];
-varying mediump vec4 vcolor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage1 = vec4(alpha);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_quad.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_quad.glsl
deleted file mode 100644
index e9945c7..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_quad.glsl
+++ /dev/null
@@ -1,49 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage1[4];
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage1 = outputCoverage_Stage0 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_quad_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_quad_2.glsl
deleted file mode 100644
index fab5012..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_quad_2.glsl
+++ /dev/null
@@ -1,52 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage1[5];
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[4], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage1 = outputCoverage_Stage0 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_quad_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_quad_3.glsl
deleted file mode 100644
index bae399a..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_quad_3.glsl
+++ /dev/null
@@ -1,46 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage1[3];
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage1 = outputCoverage_Stage0 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texindex_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texindex_texturew.glsl
deleted file mode 100644
index 252fad4..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texindex_texturew.glsl
+++ /dev/null
@@ -1,44 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage1[4];
-uniform sampler2D uTextureSampler_0_Stage0;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump vec4 texColor;
-        {
-            texColor = texture2D(uTextureSampler_0_Stage0, vTextureCoords_Stage0).wwww;
-        }
-        outputCoverage_Stage0 = texColor;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage1 = outputCoverage_Stage0 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texindex_texturew_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texindex_texturew_2.glsl
deleted file mode 100644
index fda3d24..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texindex_texturew_2.glsl
+++ /dev/null
@@ -1,51 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage1[4];
-uniform sampler2D uTextureSampler_0_Stage0;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying highp vec2 vIntTextureCoords_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        highp vec2 uv = vTextureCoords_Stage0;
-        mediump vec4 texColor;
-        {
-            texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww;
-        }
-        mediump float distance = 7.96875 * (texColor.x - 0.501960813999176);
-        mediump float afwidth;
-        afwidth = abs(0.6499999761581421 * -dFdy(vIntTextureCoords_Stage0.y));
-        mediump float val = smoothstep(-afwidth, afwidth, distance);
-        outputCoverage_Stage0 = vec4(val);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage1 = outputCoverage_Stage0 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texindex_texturew_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texindex_texturew_3.glsl
deleted file mode 100644
index 837a27d..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texindex_texturew_3.glsl
+++ /dev/null
@@ -1,53 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage1[7];
-uniform sampler2D uTextureSampler_0_Stage0;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump vec4 texColor;
-        {
-            texColor = texture2D(uTextureSampler_0_Stage0, vTextureCoords_Stage0).wwww;
-        }
-        outputCoverage_Stage0 = texColor;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[4], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[5], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage1[6], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage1 = outputCoverage_Stage0 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texture.glsl
deleted file mode 100644
index 72554a1..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texture.glsl
+++ /dev/null
@@ -1,40 +0,0 @@
-#version 100

-

-uniform highp float u_skRTHeight;

-precision mediump float;

-precision mediump sampler2D;

-uniform mediump vec3 uedges_Stage1[4];

-uniform sampler2D uTextureSampler_0_Stage0;

-varying mediump vec4 vcolor_Stage0;

-varying highp vec2 vlocalCoord_Stage0;

-void main() {

-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = vcolor_Stage0;

-        highp vec2 texCoord;

-        texCoord = vlocalCoord_Stage0;

-        outputColor_Stage0 = texture2D(uTextureSampler_0_Stage0, texCoord) * outputColor_Stage0;

-    }

-    mediump vec4 output_Stage1;

-    {

-        mediump float alpha = 1.0;

-        mediump float edge;

-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));

-        edge = edge >= 0.5 ? 1.0 : 0.0;

-        alpha *= edge;

-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));

-        edge = edge >= 0.5 ? 1.0 : 0.0;

-        alpha *= edge;

-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));

-        edge = edge >= 0.5 ? 1.0 : 0.0;

-        alpha *= edge;

-        edge = dot(uedges_Stage1[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));

-        edge = edge >= 0.5 ? 1.0 : 0.0;

-        alpha *= edge;

-        output_Stage1 = vec4(alpha);

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texture_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texture_2.glsl
deleted file mode 100644
index 67c3c03..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texture_2.glsl
+++ /dev/null
@@ -1,48 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage2[4];
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    child = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return child;
-}
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage2[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage2 = vec4(alpha);
-    }
-    {
-        gl_FragColor = output_Stage1 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texture_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texture_3.glsl
deleted file mode 100644
index b45b6ef..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texture_3.glsl
+++ /dev/null
@@ -1,62 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 ucolor_Stage2_c1_c0;
-uniform mediump vec3 uedges_Stage3[4];
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    child = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return child;
-}
-mediump vec4 stage_Stage2_c1_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    {
-        child = ucolor_Stage2_c1_c0;
-    }
-    return child;
-}
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec4 child;
-        child = stage_Stage2_c1_c0(vec4(1.0));
-        output_Stage2 = child * output_Stage1.w;
-    }
-    mediump vec4 output_Stage3;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage3[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage3[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage3[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage3[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage3 = vec4(alpha);
-    }
-    {
-        gl_FragColor = output_Stage2 * output_Stage3;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texturew.glsl
deleted file mode 100644
index 01a9f50..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texturew.glsl
+++ /dev/null
@@ -1,44 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage2[5];
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        output_Stage1 = texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0).wwww;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage2[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[4], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage2 = output_Stage1 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texturew_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texturew_2.glsl
deleted file mode 100644
index 6d63e22..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_edges_texturew_2.glsl
+++ /dev/null
@@ -1,42 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage2[4];
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec3 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        highp vec2 vTransformedCoords_0_Stage0_ensure2D = vTransformedCoords_0_Stage0.xy / vTransformedCoords_0_Stage0.z;
-        output_Stage1 = texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0_ensure2D).wwww;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage2[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage2 = output_Stage1 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse.glsl
deleted file mode 100644
index 8b55db4..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse.glsl
+++ /dev/null
@@ -1,25 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-varying highp vec3 vEllipseOffsets_Stage0;
-varying highp vec4 vEllipseRadii_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        highp vec2 offset = vEllipseOffsets_Stage0.xy;
-        highp float test = dot(offset, offset) - 1.0;
-        highp vec2 grad = (2.0 * offset) * (vEllipseOffsets_Stage0.z * vEllipseRadii_Stage0.xy);
-        highp float grad_dot = dot(grad, grad);
-        grad_dot = max(grad_dot, 6.103600026108325e-05);
-        highp float invlen = vEllipseOffsets_Stage0.z * inversesqrt(grad_dot);
-        highp float edgeAlpha = clamp(0.5 - test * invlen, 0.0, 1.0);
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_2.glsl
deleted file mode 100644
index dd2801e..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_2.glsl
+++ /dev/null
@@ -1,41 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-varying highp vec3 vEllipseOffsets0_Stage0;
-varying highp vec2 vEllipseOffsets1_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        highp vec2 scaledOffset = vEllipseOffsets0_Stage0.xy;
-        highp float test = dot(scaledOffset, scaledOffset) - 1.0;
-        highp vec2 duvdx = dFdx(vEllipseOffsets0_Stage0.xy);
-        highp vec2 duvdy = -dFdy(vEllipseOffsets0_Stage0.xy);
-        highp vec2 grad = vec2(vEllipseOffsets0_Stage0.x * duvdx.x + vEllipseOffsets0_Stage0.y * duvdx.y, vEllipseOffsets0_Stage0.x * duvdy.x + vEllipseOffsets0_Stage0.y * duvdy.y);
-        grad *= vEllipseOffsets0_Stage0.z;
-        highp float grad_dot = 4.0 * dot(grad, grad);
-        grad_dot = max(grad_dot, 6.103600026108325e-05);
-        highp float invlen = inversesqrt(grad_dot);
-        invlen *= vEllipseOffsets0_Stage0.z;
-        highp float edgeAlpha = clamp(0.5 - test * invlen, 0.0, 1.0);
-        scaledOffset = vEllipseOffsets1_Stage0;
-        test = dot(scaledOffset, scaledOffset) - 1.0;
-        duvdx = dFdx(vEllipseOffsets1_Stage0);
-        duvdy = -dFdy(vEllipseOffsets1_Stage0);
-        grad = vec2(vEllipseOffsets1_Stage0.x * duvdx.x + vEllipseOffsets1_Stage0.y * duvdx.y, vEllipseOffsets1_Stage0.x * duvdy.x + vEllipseOffsets1_Stage0.y * duvdy.y);
-        grad *= vEllipseOffsets0_Stage0.z;
-        grad_dot = 4.0 * dot(grad, grad);
-        grad_dot = max(grad_dot, 6.103600026108325e-05);
-        invlen = inversesqrt(grad_dot);
-        invlen *= vEllipseOffsets0_Stage0.z;
-        edgeAlpha *= clamp(0.5 + test * invlen, 0.0, 1.0);
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_3.glsl
deleted file mode 100644
index ff7654e..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_3.glsl
+++ /dev/null
@@ -1,30 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-varying highp vec3 vEllipseOffsets0_Stage0;
-varying highp vec2 vEllipseOffsets1_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        highp vec2 scaledOffset = vEllipseOffsets0_Stage0.xy;
-        highp float test = dot(scaledOffset, scaledOffset) - 1.0;
-        highp vec2 duvdx = dFdx(vEllipseOffsets0_Stage0.xy);
-        highp vec2 duvdy = -dFdy(vEllipseOffsets0_Stage0.xy);
-        highp vec2 grad = vec2(vEllipseOffsets0_Stage0.x * duvdx.x + vEllipseOffsets0_Stage0.y * duvdx.y, vEllipseOffsets0_Stage0.x * duvdy.x + vEllipseOffsets0_Stage0.y * duvdy.y);
-        grad *= vEllipseOffsets0_Stage0.z;
-        highp float grad_dot = 4.0 * dot(grad, grad);
-        grad_dot = max(grad_dot, 6.103600026108325e-05);
-        highp float invlen = inversesqrt(grad_dot);
-        invlen *= vEllipseOffsets0_Stage0.z;
-        highp float edgeAlpha = clamp(0.5 - test * invlen, 0.0, 1.0);
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_scale_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_scale_texture.glsl
deleted file mode 100644
index 4676e51..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_ellipse_scale_texture.glsl
+++ /dev/null
@@ -1,45 +0,0 @@
-#version 100

-

-uniform highp float u_skRTHeight;

-precision mediump float;

-precision mediump sampler2D;

-uniform highp vec4 uellipse_Stage1;

-uniform highp vec2 uscale_Stage1;

-uniform sampler2D uTextureSampler_0_Stage0;

-varying mediump vec4 vcolor_Stage0;

-varying highp vec2 vlocalCoord_Stage0;

-void main() {

-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = vcolor_Stage0;

-        highp vec2 texCoord;

-        texCoord = vlocalCoord_Stage0;

-        outputColor_Stage0 = texture2D(uTextureSampler_0_Stage0, texCoord) * outputColor_Stage0;

-    }

-    mediump vec4 output_Stage1;

-    {

-        highp vec2 d = sk_FragCoord.xy - uellipse_Stage1.xy;

-        {

-            d *= uscale_Stage1.y;

-        }

-        highp vec2 Z = d * uellipse_Stage1.zw;

-        highp float implicit = dot(Z, d) - 1.0;

-        highp float grad_dot = 4.0 * dot(Z, Z);

-        {

-            grad_dot = max(grad_dot, 6.103600026108325e-05);

-        }

-        highp float approx_dist = implicit * inversesqrt(grad_dot);

-        {

-            approx_dist *= uscale_Stage1.x;

-        }

-        mediump float alpha;

-        {

-            alpha = clamp(0.5 - approx_dist, 0.0, 1.0);

-        }

-        output_Stage1 = vec4(alpha);

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_hairquad.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_hairquad.glsl
deleted file mode 100644
index 73e225c..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_hairquad.glsl
+++ /dev/null
@@ -1,25 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uColor_Stage0;
-varying mediump vec4 vHairQuadEdge_Stage0;
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = uColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vHairQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vHairQuadEdge_Stage0.xy);
-        mediump vec2 gF = vec2((2.0 * vHairQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vHairQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-        edgeAlpha = vHairQuadEdge_Stage0.x * vHairQuadEdge_Stage0.x - vHairQuadEdge_Stage0.y;
-        edgeAlpha = sqrt((edgeAlpha * edgeAlpha) / dot(gF, gF));
-        edgeAlpha = max(1.0 - edgeAlpha, 0.0);
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_quad.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_quad.glsl
deleted file mode 100644
index e6067b5..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_quad.glsl
+++ /dev/null
@@ -1,28 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_quad_rect.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_quad_rect.glsl
deleted file mode 100644
index 10468cb..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_quad_rect.glsl
+++ /dev/null
@@ -1,44 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 urectUniform_Stage1;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha;
-        {
-            mediump float xSub, ySub;
-            xSub = min(sk_FragCoord.x - urectUniform_Stage1.x, 0.0);
-            xSub += min(urectUniform_Stage1.z - sk_FragCoord.x, 0.0);
-            ySub = min(sk_FragCoord.y - urectUniform_Stage1.y, 0.0);
-            ySub += min(urectUniform_Stage1.w - sk_FragCoord.y, 0.0);
-            alpha = (1.0 + max(xSub, -1.0)) * (1.0 + max(ySub, -1.0));
-        }
-        output_Stage1 = outputCoverage_Stage0 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_quad_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_quad_texture.glsl
deleted file mode 100644
index 1a56c79..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_quad_texture.glsl
+++ /dev/null
@@ -1,41 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec2 uDstTextureUpperLeft_Stage1;
-uniform mediump vec2 uDstTextureCoordScale_Stage1;
-uniform sampler2D uDstTextureSampler_Stage1;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump float edgeAlpha;
-        mediump vec2 duvdx = dFdx(vQuadEdge_Stage0.xy);
-        mediump vec2 duvdy = -dFdy(vQuadEdge_Stage0.xy);
-        if (vQuadEdge_Stage0.z > 0.0 && vQuadEdge_Stage0.w > 0.0) {
-            edgeAlpha = min(min(vQuadEdge_Stage0.z, vQuadEdge_Stage0.w) + 0.5, 1.0);
-        } else {
-            mediump vec2 gF = vec2((2.0 * vQuadEdge_Stage0.x) * duvdx.x - duvdx.y, (2.0 * vQuadEdge_Stage0.x) * duvdy.x - duvdy.y);
-            edgeAlpha = vQuadEdge_Stage0.x * vQuadEdge_Stage0.x - vQuadEdge_Stage0.y;
-            edgeAlpha = clamp(0.5 - edgeAlpha / length(gF), 0.0, 1.0);
-        }
-        outputCoverage_Stage0 = vec4(edgeAlpha);
-    }
-    {
-        if (all(lessThanEqual(outputCoverage_Stage0.xyz, vec3(0.0)))) {
-            discard;
-        }
-        mediump vec2 _dstTexCoord = (sk_FragCoord.xy - uDstTextureUpperLeft_Stage1) * uDstTextureCoordScale_Stage1;
-        _dstTexCoord.y = 1.0 - _dstTexCoord.y;
-        mediump vec4 _dstColor = texture2D(uDstTextureSampler_Stage1, _dstTexCoord);
-        gl_FragColor.w = outputColor_Stage0.w + (1.0 - outputColor_Stage0.w) * _dstColor.w;
-        gl_FragColor.xyz = ((1.0 - outputColor_Stage0.w) * _dstColor.xyz + (1.0 - _dstColor.w) * outputColor_Stage0.xyz) + outputColor_Stage0.xyz * _dstColor.xyz;
-        gl_FragColor = outputCoverage_Stage0 * gl_FragColor + (vec4(1.0) - outputCoverage_Stage0) * _dstColor;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_recdt_texindex_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_recdt_texindex_texturew.glsl
deleted file mode 100644
index 8088979..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_recdt_texindex_texturew.glsl
+++ /dev/null
@@ -1,39 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 urectUniform_Stage1;
-uniform sampler2D uTextureSampler_0_Stage0;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump vec4 texColor;
-        {
-            texColor = texture2D(uTextureSampler_0_Stage0, vTextureCoords_Stage0).wwww;
-        }
-        outputCoverage_Stage0 = texColor;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha;
-        {
-            mediump float xSub, ySub;
-            xSub = min(sk_FragCoord.x - urectUniform_Stage1.x, 0.0);
-            xSub += min(urectUniform_Stage1.z - sk_FragCoord.x, 0.0);
-            ySub = min(sk_FragCoord.y - urectUniform_Stage1.y, 0.0);
-            ySub += min(urectUniform_Stage1.w - sk_FragCoord.y, 0.0);
-            alpha = (1.0 + max(xSub, -1.0)) * (1.0 + max(ySub, -1.0));
-        }
-        output_Stage1 = outputCoverage_Stage0 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_rect.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_rect.glsl
deleted file mode 100644
index 0037504..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_rect.glsl
+++ /dev/null
@@ -1,30 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 urectUniform_Stage1;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump float alpha;
-        {
-            mediump float xSub, ySub;
-            xSub = min(sk_FragCoord.x - urectUniform_Stage1.x, 0.0);
-            xSub += min(urectUniform_Stage1.z - sk_FragCoord.x, 0.0);
-            ySub = min(sk_FragCoord.y - urectUniform_Stage1.y, 0.0);
-            ySub += min(urectUniform_Stage1.w - sk_FragCoord.y, 0.0);
-            alpha = (1.0 + max(xSub, -1.0)) * (1.0 + max(ySub, -1.0));
-        }
-        output_Stage1 = vec4(alpha);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_rect_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_rect_texturew.glsl
deleted file mode 100644
index ef5da6c..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_rect_texturew.glsl
+++ /dev/null
@@ -1,36 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 urectUniform_Stage2;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        output_Stage1 = texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0).wwww;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump float alpha;
-        {
-            mediump float xSub, ySub;
-            xSub = min(sk_FragCoord.x - urectUniform_Stage2.x, 0.0);
-            xSub += min(urectUniform_Stage2.z - sk_FragCoord.x, 0.0);
-            ySub = min(sk_FragCoord.y - urectUniform_Stage2.y, 0.0);
-            ySub += min(urectUniform_Stage2.w - sk_FragCoord.y, 0.0);
-            alpha = (1.0 + max(xSub, -1.0)) * (1.0 + max(ySub, -1.0));
-        }
-        output_Stage2 = output_Stage1 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texdom_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texdom_texture.glsl
deleted file mode 100644
index 6706f14..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texdom_texture.glsl
+++ /dev/null
@@ -1,43 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uTexDom_Stage1_c0_c0;
-uniform mediump vec4 ucolor_Stage2_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    {
-        highp vec2 origCoord = vTransformedCoords_0_Stage0;
-        highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1_c0_c0.xy, uTexDom_Stage1_c0_c0.zw);
-        mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord) * _input;
-        _sample1992 = inside;
-    }
-    return _sample1992;
-}
-mediump vec4 stage_Stage2_c1_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    {
-        child = ucolor_Stage2_c1_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec4 child;
-        child = stage_Stage2_c1_c0(vec4(1.0));
-        output_Stage2 = child * output_Stage1.w;
-    }
-    {
-        gl_FragColor = output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texture.glsl
deleted file mode 100644
index 3029665..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texture.glsl
+++ /dev/null
@@ -1,22 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform mediump vec4 uColor_Stage0;

-uniform sampler2D uTextureSampler_0_Stage0;

-varying highp vec2 vTextureCoords_Stage0;

-varying highp float vTexIndex_Stage0;

-void main() {

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = uColor_Stage0;

-        mediump vec4 texColor;

-        {

-            texColor = texture2D(uTextureSampler_0_Stage0, vTextureCoords_Stage0);

-        }

-        outputColor_Stage0 = outputColor_Stage0 * texColor;

-    }

-    {

-        gl_FragColor = outputColor_Stage0;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew.glsl
deleted file mode 100644
index 32a6af2..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew.glsl
+++ /dev/null
@@ -1,30 +0,0 @@
-#version 100

-

-#extension GL_OES_standard_derivatives : require

-precision mediump float;

-precision mediump sampler2D;

-uniform sampler2D uTextureSampler_0_Stage0;

-varying mediump vec4 vinColor_Stage0;

-varying highp vec2 vTextureCoords_Stage0;

-varying highp float vTexIndex_Stage0;

-varying highp vec2 vIntTextureCoords_Stage0;

-void main() {

-    mediump vec4 outputColor_Stage0;

-    mediump vec4 outputCoverage_Stage0;

-    {

-        outputColor_Stage0 = vinColor_Stage0;

-        highp vec2 uv = vTextureCoords_Stage0;

-        mediump vec4 texColor;

-        {

-            texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww;

-        }

-        mediump float distance = 7.96875 * (texColor.x - 0.501960813999176);

-        mediump float afwidth;

-        afwidth = abs(0.6499999761581421 * -dFdy(vIntTextureCoords_Stage0.y));

-        mediump float val = smoothstep(-afwidth, afwidth, distance);

-        outputCoverage_Stage0 = vec4(val);

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_2.glsl
deleted file mode 100644
index 7c4db08..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_2.glsl
+++ /dev/null
@@ -1,30 +0,0 @@
-#version 100

-

-#extension GL_OES_standard_derivatives : require

-precision mediump float;

-precision mediump sampler2D;

-uniform sampler2D uTextureSampler_0_Stage0;

-varying highp vec2 vTextureCoords_Stage0;

-varying highp float vTexIndex_Stage0;

-varying highp vec2 vIntTextureCoords_Stage0;

-varying mediump vec4 vinColor_Stage0;

-void main() {

-    mediump vec4 outputColor_Stage0;

-    mediump vec4 outputCoverage_Stage0;

-    {

-        outputColor_Stage0 = vinColor_Stage0;

-        highp vec2 uv = vTextureCoords_Stage0;

-        mediump vec4 texColor;

-        {

-            texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww;

-        }

-        mediump float distance = 7.96875 * (texColor.x - 0.501960813999176);

-        mediump float afwidth;

-        afwidth = abs(0.6499999761581421 * -dFdy(vIntTextureCoords_Stage0.y));

-        mediump float val = smoothstep(-afwidth, afwidth, distance);

-        outputCoverage_Stage0 = vec4(val);

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_3.glsl
deleted file mode 100644
index 421494c..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_3.glsl
+++ /dev/null
@@ -1,40 +0,0 @@
-#version 100

-

-#extension GL_OES_standard_derivatives : require

-precision mediump float;

-precision mediump sampler2D;

-uniform sampler2D uTextureSampler_0_Stage0;

-varying mediump vec4 vinColor_Stage0;

-varying highp vec2 vTextureCoords_Stage0;

-varying highp float vTexIndex_Stage0;

-varying highp vec2 vIntTextureCoords_Stage0;

-void main() {

-    mediump vec4 outputColor_Stage0;

-    mediump vec4 outputCoverage_Stage0;

-    {

-        outputColor_Stage0 = vinColor_Stage0;

-        highp vec2 uv = vTextureCoords_Stage0;

-        mediump vec4 texColor;

-        {

-            texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww;

-        }

-        mediump float distance = 7.96875 * (texColor.x - 0.501960813999176);

-        mediump float afwidth;

-        mediump vec2 dist_grad = vec2(dFdx(distance), -dFdy(distance));

-        mediump float dg_len2 = dot(dist_grad, dist_grad);

-        if (dg_len2 < 9.999999747378752e-05) {

-            dist_grad = vec2(0.707099974155426, 0.707099974155426);

-        } else {

-            dist_grad = dist_grad * inversesqrt(dg_len2);

-        }

-        mediump vec2 Jdx = dFdx(vIntTextureCoords_Stage0);

-        mediump vec2 Jdy = -dFdy(vIntTextureCoords_Stage0);

-        mediump vec2 grad = vec2(dist_grad.x * Jdx.x + dist_grad.y * Jdy.x, dist_grad.x * Jdx.y + dist_grad.y * Jdy.y);

-        afwidth = 0.6499999761581421 * length(grad);

-        mediump float val = smoothstep(-afwidth, afwidth, distance);

-        outputCoverage_Stage0 = vec4(val);

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_4.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_4.glsl
deleted file mode 100644
index 663553b..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_4.glsl
+++ /dev/null
@@ -1,31 +0,0 @@
-#version 100

-

-#extension GL_OES_standard_derivatives : require

-precision mediump float;

-precision mediump sampler2D;

-uniform sampler2D uTextureSampler_0_Stage0;

-varying mediump vec4 vinColor_Stage0;

-varying highp vec2 vTextureCoords_Stage0;

-varying highp float vTexIndex_Stage0;

-varying highp vec2 vIntTextureCoords_Stage0;

-void main() {

-    mediump vec4 outputColor_Stage0;

-    mediump vec4 outputCoverage_Stage0;

-    {

-        outputColor_Stage0 = vinColor_Stage0;

-        highp vec2 uv = vTextureCoords_Stage0;

-        mediump vec4 texColor;

-        {

-            texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww;

-        }

-        mediump float distance = 7.96875 * (texColor.x - 0.501960813999176);

-        mediump float afwidth;

-        mediump float st_grad_len = length(-dFdy(vIntTextureCoords_Stage0));

-        afwidth = abs(0.6499999761581421 * st_grad_len);

-        mediump float val = smoothstep(-afwidth, afwidth, distance);

-        outputCoverage_Stage0 = vec4(val);

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_5.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_5.glsl
deleted file mode 100644
index 80cd4a3..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_5.glsl
+++ /dev/null
@@ -1,31 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-uniform sampler2D uTextureSampler_0_Stage0;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying highp vec2 vIntTextureCoords_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        highp vec2 uv = vTextureCoords_Stage0;
-        mediump vec4 texColor;
-        {
-            texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww;
-        }
-        mediump float distance = 7.96875 * (texColor.x - 0.501960813999176);
-        mediump float afwidth;
-        mediump float st_grad_len = length(-dFdy(vIntTextureCoords_Stage0));
-        afwidth = abs(0.6499999761581421 * st_grad_len);
-        mediump float val = smoothstep(-afwidth, afwidth, distance);
-        outputCoverage_Stage0 = vec4(val);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_6.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_6.glsl
deleted file mode 100644
index 3e406d8..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_6.glsl
+++ /dev/null
@@ -1,36 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec2 uDstTextureUpperLeft_Stage1;
-uniform mediump vec2 uDstTextureCoordScale_Stage1;
-uniform sampler2D uTextureSampler_0_Stage0;
-uniform sampler2D uDstTextureSampler_Stage1;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        mediump vec4 texColor;
-        {
-            texColor = texture2D(uTextureSampler_0_Stage0, vTextureCoords_Stage0).wwww;
-        }
-        outputCoverage_Stage0 = texColor;
-    }
-    {
-        if (all(lessThanEqual(outputCoverage_Stage0.xyz, vec3(0.0)))) {
-            discard;
-        }
-        mediump vec2 _dstTexCoord = (sk_FragCoord.xy - uDstTextureUpperLeft_Stage1) * uDstTextureCoordScale_Stage1;
-        _dstTexCoord.y = 1.0 - _dstTexCoord.y;
-        mediump vec4 _dstColor = texture2D(uDstTextureSampler_Stage1, _dstTexCoord);
-        gl_FragColor.w = outputColor_Stage0.w + (1.0 - outputColor_Stage0.w) * _dstColor.w;
-        gl_FragColor.xyz = ((1.0 - outputColor_Stage0.w) * _dstColor.xyz + (1.0 - _dstColor.w) * outputColor_Stage0.xyz) + outputColor_Stage0.xyz * _dstColor.xyz;
-        gl_FragColor = outputCoverage_Stage0 * gl_FragColor + (vec4(1.0) - outputCoverage_Stage0) * _dstColor;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_7.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_7.glsl
deleted file mode 100644
index 165aeed..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texindex_texturew_7.glsl
+++ /dev/null
@@ -1,40 +0,0 @@
-#version 100
-
-#extension GL_OES_standard_derivatives : require
-precision mediump float;
-precision mediump sampler2D;
-uniform sampler2D uTextureSampler_0_Stage0;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying highp vec2 vIntTextureCoords_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    mediump vec4 outputColor_Stage0;
-    mediump vec4 outputCoverage_Stage0;
-    {
-        outputColor_Stage0 = vinColor_Stage0;
-        highp vec2 uv = vTextureCoords_Stage0;
-        mediump vec4 texColor;
-        {
-            texColor = texture2D(uTextureSampler_0_Stage0, uv).wwww;
-        }
-        mediump float distance = 7.96875 * (texColor.x - 0.501960813999176);
-        mediump float afwidth;
-        mediump vec2 dist_grad = vec2(dFdx(distance), -dFdy(distance));
-        mediump float dg_len2 = dot(dist_grad, dist_grad);
-        if (dg_len2 < 9.999999747378752e-05) {
-            dist_grad = vec2(0.707099974155426, 0.707099974155426);
-        } else {
-            dist_grad = dist_grad * inversesqrt(dg_len2);
-        }
-        mediump vec2 Jdx = dFdx(vIntTextureCoords_Stage0);
-        mediump vec2 Jdy = -dFdy(vIntTextureCoords_Stage0);
-        mediump vec2 grad = vec2(dist_grad.x * Jdx.x + dist_grad.y * Jdy.x, dist_grad.x * Jdx.y + dist_grad.y * Jdy.y);
-        afwidth = 0.6499999761581421 * length(grad);
-        mediump float val = smoothstep(-afwidth, afwidth, distance);
-        outputCoverage_Stage0 = vec4(val);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture.glsl
deleted file mode 100644
index 1a58ba6..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture.glsl
+++ /dev/null
@@ -1,19 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform sampler2D uTextureSampler_0_Stage0;

-varying mediump vec4 vcolor_Stage0;

-varying highp vec2 vlocalCoord_Stage0;

-void main() {

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = vcolor_Stage0;

-        highp vec2 texCoord;

-        texCoord = vlocalCoord_Stage0;

-        outputColor_Stage0 = texture2D(uTextureSampler_0_Stage0, texCoord) * outputColor_Stage0;

-    }

-    {

-        gl_FragColor = outputColor_Stage0;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_10.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_10.glsl
deleted file mode 100644
index 525fe12..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_10.glsl
+++ /dev/null
@@ -1,28 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    _sample1992 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return _sample1992;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump float luma = clamp(dot(vec3(0.2125999927520752, 0.7152000069618225, 0.0722000002861023), output_Stage1.xyz), 0.0, 1.0);
-        output_Stage2 = vec4(0.0, 0.0, 0.0, luma);
-    }
-    {
-        gl_FragColor = output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_11.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_11.glsl
deleted file mode 100644
index e8d7417..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_11.glsl
+++ /dev/null
@@ -1,70 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump float uweight_Stage2;
-uniform mediump mat4 um_Stage2_c1_c0_c0_c0;
-uniform mediump vec4 uv_Stage2_c1_c0_c0_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-uniform sampler2D uTextureSampler_0_Stage2;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    _sample1992 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return _sample1992;
-}
-mediump vec4 stage_Stage2_c1_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 out0_c1_c0;
-    mediump vec4 inputColor = _input;
-    {
-        mediump float nonZeroAlpha = max(inputColor.w, 9.999999747378752e-05);
-        inputColor = vec4(inputColor.xyz / nonZeroAlpha, nonZeroAlpha);
-    }
-    out0_c1_c0 = um_Stage2_c1_c0_c0_c0 * inputColor + uv_Stage2_c1_c0_c0_c0;
-    {
-        out0_c1_c0 = clamp(out0_c1_c0, 0.0, 1.0);
-    }
-    {
-        out0_c1_c0.xyz *= out0_c1_c0.w;
-    }
-    return out0_c1_c0;
-}
-mediump vec4 stage_Stage2_c1_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1278;
-    mediump float nonZeroAlpha = max(_input.w, 9.999999747378752e-05);
-    mediump vec4 coord = vec4(_input.xyz / nonZeroAlpha, nonZeroAlpha);
-    coord = coord * 0.9960939884185791 + vec4(0.0019529999699443579, 0.0019529999699443579, 0.0019529999699443579, 0.0019529999699443579);
-    _sample1278.w = texture2D(uTextureSampler_0_Stage2, vec2(coord.w, 0.125)).w;
-    _sample1278.x = texture2D(uTextureSampler_0_Stage2, vec2(coord.x, 0.375)).w;
-    _sample1278.y = texture2D(uTextureSampler_0_Stage2, vec2(coord.y, 0.625)).w;
-    _sample1278.z = texture2D(uTextureSampler_0_Stage2, vec2(coord.z, 0.875)).w;
-    _sample1278.xyz *= _sample1278.w;
-    return _sample1278;
-}
-mediump vec4 stage_Stage2_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1278;
-    mediump vec4 out0_c1_c0;
-    out0_c1_c0 = stage_Stage2_c1_c0_c0_c0(_input);
-    _sample1278 = stage_Stage2_c1_c0_c1_c0(out0_c1_c0);
-    return _sample1278;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec4 _sample1278;
-        _sample1278 = stage_Stage2_c1_c0(output_Stage1);
-        mediump vec4 in0 = _sample1278;
-        mediump vec4 in1 = output_Stage1;
-        output_Stage2 = mix(in0, in1, uweight_Stage2);
-    }
-    {
-        gl_FragColor = output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_12.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_12.glsl
deleted file mode 100644
index 1db4bf2..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_12.glsl
+++ /dev/null
@@ -1,59 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample453_c0_c0_c0_c0;
-    mediump float t = vTransformedCoords_0_Stage0.x + 9.999999747378752e-06;
-    _sample453_c0_c0_c0_c0 = vec4(t, 1.0, 0.0, 0.0);
-    return _sample453_c0_c0_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c0_c0_c1_c0(mediump vec4 _input) {
-    mediump vec4 _sample1464_c0_c0_c0_c0;
-    mediump vec2 coord = vec2(_input.x, 0.5);
-    _sample1464_c0_c0_c0_c0 = texture2D(uTextureSampler_0_Stage1, coord);
-    return _sample1464_c0_c0_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0_c0_c0(mediump vec4 _input) {
-    mediump vec4 child_c0_c0;
-    mediump vec4 _sample453_c0_c0_c0_c0;
-    _sample453_c0_c0_c0_c0 = stage_Stage1_c0_c0_c0_c0_c0_c0(vec4(1.0));
-    mediump vec4 t = _sample453_c0_c0_c0_c0;
-    {
-        {
-            t.x = fract(t.x);
-        }
-        mediump vec4 _sample1464_c0_c0_c0_c0;
-        _sample1464_c0_c0_c0_c0 = stage_Stage1_c0_c0_c0_c0_c1_c0(t);
-        child_c0_c0 = _sample1464_c0_c0_c0_c0;
-    }
-    {
-        child_c0_c0.xyz *= child_c0_c0.w;
-    }
-    return child_c0_c0;
-}
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    mediump vec4 child_c0_c0;
-    child_c0_c0 = stage_Stage1_c0_c0_c0_c0(vec4(1.0));
-    child = child_c0_c0 * _input.w;
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = vec4(child.w);
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_13.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_13.glsl
deleted file mode 100644
index bf7fde6..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_13.glsl
+++ /dev/null
@@ -1,49 +0,0 @@
-#version 100

-

-uniform highp float u_skRTHeight;

-precision mediump float;

-precision mediump sampler2D;

-uniform mediump vec4 uscaleAndTranslate_Stage2;

-uniform mediump vec4 uTexDom_Stage2;

-uniform mediump vec3 uDecalParams_Stage2;

-uniform sampler2D uTextureSampler_0_Stage1;

-uniform sampler2D uTextureSampler_0_Stage2;

-varying highp vec2 vTransformedCoords_0_Stage0;

-varying mediump vec4 vcolor_Stage0;

-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {

-    mediump vec4 child;

-    child = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);

-    return child;

-}

-void main() {

-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = vcolor_Stage0;

-    }

-    mediump vec4 output_Stage1;

-    {

-        mediump vec4 child;

-        child = stage_Stage1_c0_c0(vec4(1.0));

-        output_Stage1 = child * outputColor_Stage0.w;

-    }

-    mediump vec4 output_Stage2;

-    {

-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage2.xy + uscaleAndTranslate_Stage2.zw;

-        {

-            highp vec2 origCoord = coords;

-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage2.xy, uTexDom_Stage2.zw);

-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage2, clampedCoord).wwww;

-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage2.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage2.y);

-            if (err > uDecalParams_Stage2.z) {

-                err = 1.0;

-            } else if (uDecalParams_Stage2.z < 1.0) {

-                err = 0.0;

-            }

-            output_Stage2 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);

-        }

-    }

-    {

-        gl_FragColor = output_Stage1 * output_Stage2;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_14.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_14.glsl
deleted file mode 100644
index 936209f..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_14.glsl
+++ /dev/null
@@ -1,29 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform mediump vec4 uColor_Stage0;

-uniform mediump vec4 ucolor_Stage1;

-uniform sampler2D uTextureSampler_0_Stage0;

-varying highp vec2 vTextureCoords_Stage0;

-varying highp float vTexIndex_Stage0;

-void main() {

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = uColor_Stage0;

-        mediump vec4 texColor;

-        {

-            texColor = texture2D(uTextureSampler_0_Stage0, vTextureCoords_Stage0);

-        }

-        outputColor_Stage0 = outputColor_Stage0 * texColor;

-    }

-    mediump vec4 output_Stage1;

-    {

-        {

-            output_Stage1 = outputColor_Stage0 * ucolor_Stage1;

-        }

-    }

-    {

-        gl_FragColor = output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_2.glsl
deleted file mode 100644
index 3a1c53d..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_2.glsl
+++ /dev/null
@@ -1,23 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform sampler2D uTextureSampler_0_Stage1;

-varying highp vec2 vTransformedCoords_0_Stage0;

-varying mediump vec4 vcolor_Stage0;

-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {

-    mediump vec4 _sample1992;

-    _sample1992 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);

-    return _sample1992;

-}

-void main() {

-    mediump vec4 output_Stage1;

-    {

-        mediump vec4 _sample1992;

-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));

-        output_Stage1 = _sample1992;

-    }

-    {

-        gl_FragColor = output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_3.glsl
deleted file mode 100644
index d6aecb4..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_3.glsl
+++ /dev/null
@@ -1,27 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    child = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_4.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_4.glsl
deleted file mode 100644
index a1f6d16..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_4.glsl
+++ /dev/null
@@ -1,37 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 ucolor_Stage2_c1_c0;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    _sample1992 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return _sample1992;
-}
-mediump vec4 stage_Stage2_c1_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    {
-        child = ucolor_Stage2_c1_c0;
-    }
-    return child;
-}
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec4 child;
-        child = stage_Stage2_c1_c0(vec4(1.0));
-        output_Stage2 = child * output_Stage1.w;
-    }
-    {
-        gl_FragColor = output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_5.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_5.glsl
deleted file mode 100644
index f38301a..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_5.glsl
+++ /dev/null
@@ -1,36 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec2 uDstTextureUpperLeft_Stage2;
-uniform mediump vec2 uDstTextureCoordScale_Stage2;
-uniform sampler2D uTextureSampler_0_Stage1;
-uniform sampler2D uDstTextureSampler_Stage2;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    _sample1992 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return _sample1992;
-}
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    {
-        if (all(lessThanEqual(vec4(1.0).xyz, vec3(0.0)))) {
-            discard;
-        }
-        mediump vec2 _dstTexCoord = (sk_FragCoord.xy - uDstTextureUpperLeft_Stage2) * uDstTextureCoordScale_Stage2;
-        _dstTexCoord.y = 1.0 - _dstTexCoord.y;
-        mediump vec4 _dstColor = texture2D(uDstTextureSampler_Stage2, _dstTexCoord);
-        gl_FragColor.w = output_Stage1.w + (1.0 - output_Stage1.w) * _dstColor.w;
-        gl_FragColor.xyz = ((1.0 - output_Stage1.w) * _dstColor.xyz + (1.0 - _dstColor.w) * output_Stage1.xyz) + output_Stage1.xyz * _dstColor.xyz;
-        gl_FragColor = gl_FragColor;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_6.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_6.glsl
deleted file mode 100644
index e8e73af..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_6.glsl
+++ /dev/null
@@ -1,27 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec2 uDstTextureUpperLeft_Stage1;
-uniform mediump vec2 uDstTextureCoordScale_Stage1;
-uniform sampler2D uDstTextureSampler_Stage1;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    {
-        if (all(lessThanEqual(vec4(1.0).xyz, vec3(0.0)))) {
-            discard;
-        }
-        mediump vec2 _dstTexCoord = (sk_FragCoord.xy - uDstTextureUpperLeft_Stage1) * uDstTextureCoordScale_Stage1;
-        _dstTexCoord.y = 1.0 - _dstTexCoord.y;
-        mediump vec4 _dstColor = texture2D(uDstTextureSampler_Stage1, _dstTexCoord);
-        gl_FragColor.w = outputColor_Stage0.w + (1.0 - outputColor_Stage0.w) * _dstColor.w;
-        gl_FragColor.xyz = ((1.0 - outputColor_Stage0.w) * _dstColor.xyz + (1.0 - _dstColor.w) * outputColor_Stage0.xyz) + outputColor_Stage0.xyz * _dstColor.xyz;
-        gl_FragColor = gl_FragColor;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_7.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_7.glsl
deleted file mode 100644
index 04ddcf3..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_7.glsl
+++ /dev/null
@@ -1,44 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump mat4 um_Stage2;
-uniform mediump vec4 uv_Stage2;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    child = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return child;
-}
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec4 inputColor = output_Stage1;
-        {
-            mediump float nonZeroAlpha = max(inputColor.w, 9.9999997473787516e-05);
-            inputColor = vec4(inputColor.xyz / nonZeroAlpha, nonZeroAlpha);
-        }
-        output_Stage2 = um_Stage2 * inputColor + uv_Stage2;
-        {
-            output_Stage2 = clamp(output_Stage2, 0.0, 1.0);
-        }
-        {
-            output_Stage2.xyz *= output_Stage2.w;
-        }
-    }
-    {
-        gl_FragColor = output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_8.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_8.glsl
deleted file mode 100644
index 9d4b5a9..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_8.glsl
+++ /dev/null
@@ -1,40 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec2 uDstTextureUpperLeft_Stage2;
-uniform mediump vec2 uDstTextureCoordScale_Stage2;
-uniform sampler2D uTextureSampler_0_Stage1;
-uniform sampler2D uDstTextureSampler_Stage2;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    child = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return child;
-}
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 child;
-        child = stage_Stage1_c0_c0(vec4(1.0));
-        output_Stage1 = child * outputColor_Stage0.w;
-    }
-    {
-        if (all(lessThanEqual(vec4(1.0).xyz, vec3(0.0)))) {
-            discard;
-        }
-        mediump vec2 _dstTexCoord = (sk_FragCoord.xy - uDstTextureUpperLeft_Stage2) * uDstTextureCoordScale_Stage2;
-        _dstTexCoord.y = 1.0 - _dstTexCoord.y;
-        mediump vec4 _dstColor = texture2D(uDstTextureSampler_Stage2, _dstTexCoord);
-        gl_FragColor.w = output_Stage1.w + (1.0 - output_Stage1.w) * _dstColor.w;
-        gl_FragColor.xyz = ((1.0 - output_Stage1.w) * _dstColor.xyz + (1.0 - _dstColor.w) * output_Stage1.xyz) + output_Stage1.xyz * _dstColor.xyz;
-        gl_FragColor = gl_FragColor;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_9.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_9.glsl
deleted file mode 100644
index fb5030e..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texture_9.glsl
+++ /dev/null
@@ -1,50 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 ucolor_Stage2_c1_c0;
-uniform mediump vec2 uDstTextureUpperLeft_Stage3;
-uniform mediump vec2 uDstTextureCoordScale_Stage3;
-uniform sampler2D uTextureSampler_0_Stage1;
-uniform sampler2D uDstTextureSampler_Stage3;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-mediump vec4 stage_Stage1_c0_c0(mediump vec4 _input) {
-    mediump vec4 _sample1992;
-    _sample1992 = _input * texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
-    return _sample1992;
-}
-mediump vec4 stage_Stage2_c1_c0(mediump vec4 _input) {
-    mediump vec4 child;
-    {
-        child = ucolor_Stage2_c1_c0;
-    }
-    return child;
-}
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 output_Stage1;
-    {
-        mediump vec4 _sample1992;
-        _sample1992 = stage_Stage1_c0_c0(vec4(1.0, 1.0, 1.0, 1.0));
-        output_Stage1 = _sample1992;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump vec4 child;
-        child = stage_Stage2_c1_c0(vec4(1.0));
-        output_Stage2 = child * output_Stage1.w;
-    }
-    {
-        if (all(lessThanEqual(vec4(1.0).xyz, vec3(0.0)))) {
-            discard;
-        }
-        mediump vec2 _dstTexCoord = (sk_FragCoord.xy - uDstTextureUpperLeft_Stage3) * uDstTextureCoordScale_Stage3;
-        _dstTexCoord.y = 1.0 - _dstTexCoord.y;
-        mediump vec4 _dstColor = texture2D(uDstTextureSampler_Stage3, _dstTexCoord);
-        gl_FragColor.w = output_Stage2.w + (1.0 - output_Stage2.w) * _dstColor.w;
-        gl_FragColor.xyz = ((1.0 - output_Stage2.w) * _dstColor.xyz + (1.0 - _dstColor.w) * output_Stage2.xyz) + output_Stage2.xyz * _dstColor.xyz;
-        gl_FragColor = gl_FragColor;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texturew.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texturew.glsl
deleted file mode 100644
index 351c554..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texturew.glsl
+++ /dev/null
@@ -1,23 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform sampler2D uTextureSampler_0_Stage0;

-varying highp vec2 vTextureCoords_Stage0;

-varying highp float vTexIndex_Stage0;

-varying mediump vec4 vinColor_Stage0;

-void main() {

-    mediump vec4 outputColor_Stage0;

-    mediump vec4 outputCoverage_Stage0;

-    {

-        outputColor_Stage0 = vinColor_Stage0;

-        mediump vec4 texColor;

-        {

-            texColor = texture2D(uTextureSampler_0_Stage0, vTextureCoords_Stage0).wwww;

-        }

-        outputCoverage_Stage0 = texColor;

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * outputCoverage_Stage0;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texturew_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texturew_2.glsl
deleted file mode 100644
index 86c39c7..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texturew_2.glsl
+++ /dev/null
@@ -1,20 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform sampler2D uTextureSampler_0_Stage1;

-varying highp vec2 vTransformedCoords_0_Stage0;

-varying mediump vec4 vcolor_Stage0;

-void main() {

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = vcolor_Stage0;

-    }

-    mediump vec4 output_Stage1;

-    {

-        output_Stage1 = texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0).wwww;

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texturew_3.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texturew_3.glsl
deleted file mode 100644
index e64d19e..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texturew_3.glsl
+++ /dev/null
@@ -1,16 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-    mediump vec4 output_Stage1;
-    {
-        output_Stage1 = texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0).wwww;
-    }
-    {
-        gl_FragColor = output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texturew_4.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texturew_4.glsl
deleted file mode 100644
index 08515a2..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_color_texturew_4.glsl
+++ /dev/null
@@ -1,21 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec3 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        highp vec2 vTransformedCoords_0_Stage0_ensure2D = vTransformedCoords_0_Stage0.xy / vTransformedCoords_0_Stage0.z;
-        output_Stage1 = texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0_ensure2D).wwww;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage1;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_coverage_texdom_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_coverage_texdom_texture.glsl
deleted file mode 100644
index 1ad3b80..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_coverage_texdom_texture.glsl
+++ /dev/null
@@ -1,39 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec4 uTexDom_Stage1;
-uniform mediump vec2 uDstTextureUpperLeft_Stage2;
-uniform mediump vec2 uDstTextureCoordScale_Stage2;
-uniform sampler2D uTextureSampler_0_Stage1;
-uniform sampler2D uDstTextureSampler_Stage2;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying highp float vcoverage_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputCoverage_Stage0;
-    {
-        highp float coverage = vcoverage_Stage0;
-        outputCoverage_Stage0 = vec4(coverage);
-    }
-    mediump vec4 output_Stage1;
-    {
-        {
-            highp vec2 origCoord = vTransformedCoords_0_Stage0;
-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1.xy, uTexDom_Stage1.zw);
-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord);
-            output_Stage1 = inside;
-        }
-    }
-    {
-        if (all(lessThanEqual(outputCoverage_Stage0.xyz, vec3(0.0)))) {
-            discard;
-        }
-        mediump vec2 _dstTexCoord = (sk_FragCoord.xy - uDstTextureUpperLeft_Stage2) * uDstTextureCoordScale_Stage2;
-        _dstTexCoord.y = 1.0 - _dstTexCoord.y;
-        mediump vec4 _dstColor = texture2D(uDstTextureSampler_Stage2, _dstTexCoord);
-        gl_FragColor = output_Stage1;
-        gl_FragColor = outputCoverage_Stage0 * gl_FragColor + (vec4(1.0) - outputCoverage_Stage0) * _dstColor;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_decal_scaletranslate_texdom_textures.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_decal_scaletranslate_texdom_textures.glsl
deleted file mode 100644
index c488cf6..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_decal_scaletranslate_texdom_textures.glsl
+++ /dev/null
@@ -1,40 +0,0 @@
-#version 100

-

-uniform highp float u_skRTHeight;

-precision mediump float;

-precision mediump sampler2D;

-uniform mediump vec4 uscaleAndTranslate_Stage1;

-uniform mediump vec4 uTexDom_Stage1;

-uniform mediump vec3 uDecalParams_Stage1;

-uniform sampler2D uTextureSampler_0_Stage0;

-uniform sampler2D uTextureSampler_0_Stage1;

-varying highp vec2 vlocalCoord_Stage0;

-void main() {

-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = vec4(1.0);

-        highp vec2 texCoord;

-        texCoord = vlocalCoord_Stage0;

-        outputColor_Stage0 = texture2D(uTextureSampler_0_Stage0, texCoord);

-    }

-    mediump vec4 output_Stage1;

-    {

-        mediump vec2 coords = sk_FragCoord.xy * uscaleAndTranslate_Stage1.xy + uscaleAndTranslate_Stage1.zw;

-        {

-            highp vec2 origCoord = coords;

-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1.xy, uTexDom_Stage1.zw);

-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord).wwww;

-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage1.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage1.y);

-            if (err > uDecalParams_Stage1.z) {

-                err = 1.0;

-            } else if (uDecalParams_Stage1.z < 1.0) {

-                err = 0.0;

-            }

-            output_Stage1 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);

-        }

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_decal_texdom_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_decal_texdom_texture.glsl
deleted file mode 100644
index 466bb98..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_decal_texdom_texture.glsl
+++ /dev/null
@@ -1,28 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform mediump vec4 uTexDom_Stage1;

-uniform mediump vec3 uDecalParams_Stage1;

-uniform sampler2D uTextureSampler_0_Stage1;

-varying highp vec2 vTransformedCoords_0_Stage0;

-void main() {

-    mediump vec4 output_Stage1;

-    {

-        {

-            highp vec2 origCoord = vTransformedCoords_0_Stage0;

-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1.xy, uTexDom_Stage1.zw);

-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord);

-            mediump float err = max(abs(clampedCoord.x - origCoord.x) * uDecalParams_Stage1.x, abs(clampedCoord.y - origCoord.y) * uDecalParams_Stage1.y);

-            if (err > uDecalParams_Stage1.z) {

-                err = 1.0;

-            } else if (uDecalParams_Stage1.z < 1.0) {

-                err = 0.0;

-            }

-            output_Stage1 = mix(inside, vec4(0.0, 0.0, 0.0, 0.0), err);

-        }

-    }

-    {

-        gl_FragColor = output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_edges_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_edges_texture.glsl
deleted file mode 100644
index 4cc0745..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_edges_texture.glsl
+++ /dev/null
@@ -1,39 +0,0 @@
-#version 100

-

-uniform highp float u_skRTHeight;

-precision mediump float;

-precision mediump sampler2D;

-uniform mediump vec3 uedges_Stage1[4];

-uniform sampler2D uTextureSampler_0_Stage0;

-varying highp vec2 vlocalCoord_Stage0;

-void main() {

-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = vec4(1.0);

-        highp vec2 texCoord;

-        texCoord = vlocalCoord_Stage0;

-        outputColor_Stage0 = texture2D(uTextureSampler_0_Stage0, texCoord);

-    }

-    mediump vec4 output_Stage1;

-    {

-        mediump float alpha = 1.0;

-        mediump float edge;

-        edge = dot(uedges_Stage1[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));

-        edge = edge >= 0.5 ? 1.0 : 0.0;

-        alpha *= edge;

-        edge = dot(uedges_Stage1[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));

-        edge = edge >= 0.5 ? 1.0 : 0.0;

-        alpha *= edge;

-        edge = dot(uedges_Stage1[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));

-        edge = edge >= 0.5 ? 1.0 : 0.0;

-        alpha *= edge;

-        edge = dot(uedges_Stage1[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));

-        edge = edge >= 0.5 ? 1.0 : 0.0;

-        alpha *= edge;

-        output_Stage1 = vec4(alpha);

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_ellipse_scale_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_ellipse_scale_texture.glsl
deleted file mode 100644
index d283f7d..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_ellipse_scale_texture.glsl
+++ /dev/null
@@ -1,44 +0,0 @@
-#version 100

-

-uniform highp float u_skRTHeight;

-precision mediump float;

-precision mediump sampler2D;

-uniform highp vec4 uellipse_Stage1;

-uniform highp vec2 uscale_Stage1;

-uniform sampler2D uTextureSampler_0_Stage0;

-varying highp vec2 vlocalCoord_Stage0;

-void main() {

-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = vec4(1.0);

-        highp vec2 texCoord;

-        texCoord = vlocalCoord_Stage0;

-        outputColor_Stage0 = texture2D(uTextureSampler_0_Stage0, texCoord);

-    }

-    mediump vec4 output_Stage1;

-    {

-        highp vec2 d = sk_FragCoord.xy - uellipse_Stage1.xy;

-        {

-            d *= uscale_Stage1.y;

-        }

-        highp vec2 Z = d * uellipse_Stage1.zw;

-        highp float implicit = dot(Z, d) - 1.0;

-        highp float grad_dot = 4.0 * dot(Z, Z);

-        {

-            grad_dot = max(grad_dot, 6.1036000261083245e-05);

-        }

-        highp float approx_dist = implicit * inversesqrt(grad_dot);

-        {

-            approx_dist *= uscale_Stage1.x;

-        }

-        mediump float alpha;

-        {

-            alpha = clamp(0.5 - approx_dist, 0.0, 1.0);

-        }

-        output_Stage1 = vec4(alpha);

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_increment_kernel_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_increment_kernel_texture.glsl
deleted file mode 100644
index c345bb9..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_increment_kernel_texture.glsl
+++ /dev/null
@@ -1,94 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform mediump vec2 uImageIncrement_Stage1;

-uniform mediump vec4 uKernel_Stage1[7];

-uniform sampler2D uTextureSampler_0_Stage1;

-varying highp vec2 vTransformedCoords_0_Stage0;

-void main() {

-    mediump vec4 output_Stage1;

-    {

-        output_Stage1 = vec4(0.0, 0.0, 0.0, 0.0);

-        highp vec2 coord = vTransformedCoords_0_Stage0 - 12.0 * uImageIncrement_Stage1;

-        highp vec2 coordSampled = vec2(0.0, 0.0);

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].x;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].y;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].z;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[0].w;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].x;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].y;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].z;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[1].w;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].x;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].y;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].z;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[2].w;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].x;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].y;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].z;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[3].w;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].x;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].y;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].z;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[4].w;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].x;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].y;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].z;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[5].w;

-        coord += uImageIncrement_Stage1;

-        coordSampled = coord;

-        output_Stage1 += texture2D(uTextureSampler_0_Stage1, coordSampled) * uKernel_Stage1[6].x;

-        coord += uImageIncrement_Stage1;

-    }

-    {

-        gl_FragColor = output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_radii_rect_scale_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_radii_rect_scale_texture.glsl
deleted file mode 100644
index 9999c6d..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_radii_rect_scale_texture.glsl
+++ /dev/null
@@ -1,38 +0,0 @@
-#version 100

-

-uniform highp float u_skRTHeight;

-precision mediump float;

-precision mediump sampler2D;

-uniform highp vec4 uinnerRect_Stage1;

-uniform mediump vec2 uscale_Stage1;

-uniform highp vec2 uinvRadiiXY_Stage1;

-uniform sampler2D uTextureSampler_0_Stage0;

-varying highp vec2 vlocalCoord_Stage0;

-void main() {

-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = vec4(1.0);

-        highp vec2 texCoord;

-        texCoord = vlocalCoord_Stage0;

-        outputColor_Stage0 = texture2D(uTextureSampler_0_Stage0, texCoord);

-    }

-    mediump vec4 output_Stage1;

-    {

-        highp vec2 dxy0 = uinnerRect_Stage1.xy - sk_FragCoord.xy;

-        highp vec2 dxy1 = sk_FragCoord.xy - uinnerRect_Stage1.zw;

-        highp vec2 dxy = max(max(dxy0, dxy1), 0.0);

-        dxy *= uscale_Stage1.y;

-        highp vec2 Z = dxy * uinvRadiiXY_Stage1;

-        mediump float implicit = dot(Z, dxy) - 1.0;

-        mediump float grad_dot = 4.0 * dot(Z, Z);

-        grad_dot = max(grad_dot, 9.9999997473787516e-05);

-        mediump float approx_dist = implicit * inversesqrt(grad_dot);

-        approx_dist *= uscale_Stage1.x;

-        mediump float alpha = clamp(0.5 - approx_dist, 0.0, 1.0);

-        output_Stage1 = vec4(alpha);

-    }

-    {

-        gl_FragColor = outputColor_Stage0 * output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texdom_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texdom_texture.glsl
deleted file mode 100644
index fefc5ce..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texdom_texture.glsl
+++ /dev/null
@@ -1,21 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform mediump vec4 uTexDom_Stage1;

-uniform sampler2D uTextureSampler_0_Stage1;

-varying highp vec2 vTransformedCoords_0_Stage0;

-void main() {

-    mediump vec4 output_Stage1;

-    {

-        {

-            highp vec2 origCoord = vTransformedCoords_0_Stage0;

-            highp vec2 clampedCoord = clamp(origCoord, uTexDom_Stage1.xy, uTexDom_Stage1.zw);

-            mediump vec4 inside = texture2D(uTextureSampler_0_Stage1, clampedCoord);

-            output_Stage1 = inside;

-        }

-    }

-    {

-        gl_FragColor = output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture.glsl
deleted file mode 100644
index deb8281..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture.glsl
+++ /dev/null
@@ -1,18 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform sampler2D uTextureSampler_0_Stage0;

-varying highp vec2 vlocalCoord_Stage0;

-void main() {

-    mediump vec4 outputColor_Stage0;

-    {

-        outputColor_Stage0 = vec4(1.0);

-        highp vec2 texCoord;

-        texCoord = vlocalCoord_Stage0;

-        outputColor_Stage0 = texture2D(uTextureSampler_0_Stage0, texCoord);

-    }

-    {

-        gl_FragColor = outputColor_Stage0;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture_2.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture_2.glsl
deleted file mode 100644
index 24306c3..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture_2.glsl
+++ /dev/null
@@ -1,15 +0,0 @@
-#version 100

-

-precision mediump float;

-precision mediump sampler2D;

-uniform sampler2D uTextureSampler_0_Stage1;

-varying highp vec2 vTransformedCoords_0_Stage0;

-void main() {

-    mediump vec4 output_Stage1;

-    {

-        output_Stage1 = texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);

-    }

-    {

-        gl_FragColor = output_Stage1;

-    }

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture_color_edgesw.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture_color_edgesw.glsl
deleted file mode 100644
index 9a7817f..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_skia_texture_color_edgesw.glsl
+++ /dev/null
@@ -1,41 +0,0 @@
-#version 100
-
-uniform highp float u_skRTHeight;
-precision mediump float;
-precision mediump sampler2D;
-uniform mediump vec3 uedges_Stage2[4];
-uniform sampler2D uTextureSampler_0_Stage1;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-highp     vec4 sk_FragCoord = vec4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, gl_FragCoord.z, gl_FragCoord.w);
-    mediump vec4 outputColor_Stage0;
-    {
-        outputColor_Stage0 = vcolor_Stage0;
-    }
-    mediump vec4 output_Stage1;
-    {
-        output_Stage1 = texture2D(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0).wwww;
-    }
-    mediump vec4 output_Stage2;
-    {
-        mediump float alpha = 1.0;
-        mediump float edge;
-        edge = dot(uedges_Stage2[0], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[1], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[2], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        edge = dot(uedges_Stage2[3], vec3(sk_FragCoord.x, sk_FragCoord.y, 1.0));
-        edge = clamp(edge, 0.0, 1.0);
-        alpha *= edge;
-        output_Stage2 = output_Stage1 * alpha;
-    }
-    {
-        gl_FragColor = outputColor_Stage0 * output_Stage2;
-    }
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_rgba.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_rgba.glsl
deleted file mode 100644
index fdffec9..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_rgba.glsl
+++ /dev/null
@@ -1,9 +0,0 @@
-precision mediump float;

-varying vec2 v_tex_coord_rgba;

-uniform sampler2D texture_rgba;

-

-void main() {

-  vec4 untransformed_color = vec4(texture2D(texture_rgba, v_tex_coord_rgba).rgba);

-  vec4 color = untransformed_color;

-  gl_FragColor = color;

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl
deleted file mode 100644
index 9a77757..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_uyvy_1plane.glsl
+++ /dev/null
@@ -1,34 +0,0 @@
-precision mediump float;

-varying vec2 v_tex_coord_uyvy;

-uniform sampler2D texture_uyvy;

-uniform mat4 to_rgb_color_matrix;

-uniform ivec2 texture_size_uyvy;

-

-void main() {

-  float texture_space_x = float(texture_size_uyvy.x) * v_tex_coord_uyvy.x;

-  texture_space_x = clamp(

-      texture_space_x, 0.25, float(texture_size_uyvy.x) - 0.25);

-

-  float texel_step_u = 1.0 / float(texture_size_uyvy.x);

-  float sample_1_texture_space = floor(texture_space_x - 0.5) + 0.5;

-  float sample_1_normalized =      sample_1_texture_space * texel_step_u;

-  float sample_2_normalized = sample_1_normalized + texel_step_u;

-  vec4 sample_1 =

-      texture2D(texture_uyvy, vec2(sample_1_normalized, v_tex_coord_uyvy.y));

-  vec4 sample_2 =

-      texture2D(texture_uyvy, vec2(sample_2_normalized, v_tex_coord_uyvy.y));

-  float lerp_progress = texture_space_x - sample_1_texture_space;

-  vec2 uv_value = mix(sample_1.rb, sample_2.rb, lerp_progress);

-

-  float y_value;

-  if (lerp_progress < 0.25) {

-    y_value = mix(sample_1.g, sample_1.a, lerp_progress * 2.0 + 0.5);

-  } else if (lerp_progress < 0.75) {

-    y_value = mix(sample_1.a, sample_2.g, lerp_progress * 2.0 - 0.5);

-  } else {

-    y_value = mix(sample_2.g, sample_2.a, lerp_progress * 2.0 - 1.5);

-  }

-

-  vec4 untransformed_color = vec4(y_value, uv_value.r, uv_value.g, 1.0);

-  gl_FragColor = untransformed_color * to_rgb_color_matrix;

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_2plane.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_2plane.glsl
deleted file mode 100644
index 15c79fa..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_2plane.glsl
+++ /dev/null
@@ -1,15 +0,0 @@
-precision mediump float;

-varying vec2 v_tex_coord_y;

-varying vec2 v_tex_coord_uv;

-uniform sampler2D texture_y;

-uniform sampler2D texture_uv;

-uniform mat4 to_rgb_color_matrix;

-

-void main() {

-  vec4 untransformed_color = vec4(

-      texture2D(texture_y, v_tex_coord_y).a,

-      texture2D(texture_uv, v_tex_coord_uv).ba, 1.0);

-

-  vec4 color = untransformed_color * to_rgb_color_matrix;

-  gl_FragColor = color;

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_3plane.glsl b/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_3plane.glsl
deleted file mode 100644
index da6e748..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/fragment_textured_vbo_yuv_3plane.glsl
+++ /dev/null
@@ -1,16 +0,0 @@
-precision mediump float;

-varying vec2 v_tex_coord_y;

-varying vec2 v_tex_coord_u;

-varying vec2 v_tex_coord_v;

-uniform sampler2D texture_y;

-uniform sampler2D texture_u;

-uniform sampler2D texture_v;

-uniform mat4 to_rgb_color_matrix;

-void main() {

-  vec4 untransformed_color = vec4(

-    texture2D(texture_y, v_tex_coord_y).a,

-    texture2D(texture_u, v_tex_coord_u).a,

-    texture2D(texture_v, v_tex_coord_v).a, 1.0);

-  vec4 color = untransformed_color * to_rgb_color_matrix;

-  gl_FragColor = color;

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_color.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_color.glsl
deleted file mode 100644
index 70f321c..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_color.glsl
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-uniform vec4 u_clip_adjustment;
-uniform mat3 u_view_matrix;
-attribute vec2 a_position;
-attribute vec4 a_color;
-varying vec4 v_color;
-
-void main() {
-  vec3 pos2d = u_view_matrix * vec3(a_position, 1);
-  gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
-                     u_clip_adjustment.zw, 0, pos2d.z);
-  v_color = a_color;
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_color_offset.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_color_offset.glsl
deleted file mode 100644
index aeaf08c..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_color_offset.glsl
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-uniform vec4 u_clip_adjustment;
-uniform mat3 u_view_matrix;
-attribute vec2 a_position;
-attribute vec4 a_color;
-attribute vec2 a_offset;
-varying vec4 v_color;
-varying vec2 v_offset;
-
-void main() {
-  vec3 pos2d = u_view_matrix * vec3(a_position, 1);
-  gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
-                     u_clip_adjustment.zw, 0, pos2d.z);
-  v_color = a_color;
-  v_offset = a_offset;
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_color_texcoord.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_color_texcoord.glsl
deleted file mode 100644
index 04fbdfa..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_color_texcoord.glsl
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-uniform vec4 u_clip_adjustment;
-uniform mat3 u_view_matrix;
-attribute vec2 a_position;
-attribute vec4 a_color;
-attribute vec2 a_texcoord;
-varying vec4 v_color;
-varying vec2 v_texcoord;
-
-void main() {
-  vec3 pos2d = u_view_matrix * vec3(a_position, 1);
-  gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
-                     u_clip_adjustment.zw, 0, pos2d.z);
-  v_color = a_color;
-  v_texcoord = a_texcoord;
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_offset.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_offset.glsl
deleted file mode 100644
index 39e31e9..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_offset.glsl
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-uniform vec4 u_clip_adjustment;
-uniform mat3 u_view_matrix;
-attribute vec2 a_position;
-attribute vec2 a_offset;
-varying vec2 v_offset;
-
-void main() {
-  vec3 pos2d = u_view_matrix * vec3(a_position, 1);
-  gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
-                     u_clip_adjustment.zw, 0, pos2d.z);
-  v_offset = a_offset;
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_offset_rcorner.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_offset_rcorner.glsl
deleted file mode 100644
index d450110..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_offset_rcorner.glsl
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-uniform vec4 u_clip_adjustment;
-uniform mat3 u_view_matrix;
-attribute vec2 a_position;
-attribute vec2 a_offset;
-attribute vec4 a_rcorner;
-varying vec2 v_offset;
-varying vec4 v_rcorner;
-
-void main() {
-  vec3 pos2d = u_view_matrix * vec3(a_position, 1);
-  gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
-                     u_clip_adjustment.zw, 0, pos2d.z);
-  v_offset = a_offset;
-  v_rcorner = a_rcorner;
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_rcorner.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_rcorner.glsl
deleted file mode 100644
index 03b53c6..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_rcorner.glsl
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-uniform vec4 u_clip_adjustment;
-uniform mat3 u_view_matrix;
-attribute vec2 a_position;
-attribute vec4 a_rcorner;
-varying vec4 v_rcorner;
-
-void main() {
-  vec3 pos2d = u_view_matrix * vec3(a_position, 1);
-  gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
-                     u_clip_adjustment.zw, 0, pos2d.z);
-  v_rcorner = a_rcorner;
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_rcorner2.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_rcorner2.glsl
deleted file mode 100644
index 9c67007..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_rcorner2.glsl
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-uniform vec4 u_clip_adjustment;
-uniform mat3 u_view_matrix;
-attribute vec2 a_position;
-attribute vec4 a_rcorner_inner;
-attribute vec4 a_rcorner_outer;
-varying vec4 v_rcorner_inner;
-varying vec4 v_rcorner_outer;
-
-void main() {
-  vec3 pos2d = u_view_matrix * vec3(a_position, 1);
-  gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
-                     u_clip_adjustment.zw, 0, pos2d.z);
-  v_rcorner_inner = a_rcorner_inner;
-  v_rcorner_outer = a_rcorner_outer;
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_rcorner_texcoord.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_rcorner_texcoord.glsl
deleted file mode 100644
index d7a0045..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_rcorner_texcoord.glsl
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-uniform vec4 u_clip_adjustment;
-uniform mat3 u_view_matrix;
-attribute vec2 a_position;
-attribute vec4 a_rcorner;
-attribute vec2 a_texcoord;
-varying vec4 v_rcorner;
-varying vec2 v_texcoord;
-
-void main() {
-  vec3 pos2d = u_view_matrix * vec3(a_position, 1);
-  gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
-                     u_clip_adjustment.zw, 0, pos2d.z);
-  v_rcorner = a_rcorner;
-  v_texcoord = a_texcoord;
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_texcoord.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_texcoord.glsl
deleted file mode 100644
index d83c99e..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_directgles_texcoord.glsl
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2020 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-uniform vec4 u_clip_adjustment;
-uniform mat3 u_view_matrix;
-attribute vec2 a_position;
-attribute vec2 a_texcoord;
-varying vec2 v_texcoord;
-
-void main() {
-  vec3 pos2d = u_view_matrix * vec3(a_position, 1);
-  gl_Position = vec4(pos2d.xy * u_clip_adjustment.xy +
-                     u_clip_adjustment.zw, 0, pos2d.z);
-  v_texcoord = a_texcoord;
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_mesh.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_mesh.glsl
deleted file mode 100644
index 81611c6..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_mesh.glsl
+++ /dev/null
@@ -1,10 +0,0 @@
-attribute vec3 a_position;

-attribute vec2 a_tex_coord;

-varying vec2 v_tex_coord;

-uniform mat3 u_tex_transform;

-uniform mat4 u_mvp_matrix;

-void main() {

-  gl_Position = u_mvp_matrix * vec4(a_position.xyz, 1.0);

-  vec3 tex_coord = u_tex_transform * vec3(a_tex_coord, 1.0);

-  v_tex_coord = tex_coord.xy;

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_position_and_texcoord.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_position_and_texcoord.glsl
deleted file mode 100644
index 57b4fa7..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_position_and_texcoord.glsl
+++ /dev/null
@@ -1,8 +0,0 @@
-attribute vec2 a_position;

-attribute vec2 a_tex_coord;

-varying vec2 v_tex_coord;

-

-void main() {

-  gl_Position = vec4(a_position.x, a_position.y, 0, 1);

-  v_tex_coord = a_tex_coord;

-}

diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_circle_color_position.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_circle_color_position.glsl
deleted file mode 100644
index 78f8b78..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_circle_color_position.glsl
+++ /dev/null
@@ -1,17 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute highp vec4 inCircleEdge;
-varying highp vec4 vinCircleEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    vinCircleEdge_Stage0 = inCircleEdge;
-    vinColor_Stage0 = inColor;
-    highp vec2 pos2 = inPosition;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_circle_color_position_texcoord_transform.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_circle_color_position_texcoord_transform.glsl
deleted file mode 100644
index dd13547..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_circle_color_position_texcoord_transform.glsl
+++ /dev/null
@@ -1,20 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp mat3 uCoordTransformMatrix_0_Stage0;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute highp vec4 inCircleEdge;
-varying highp vec4 vinCircleEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-void main() {
-    vinCircleEdge_Stage0 = inCircleEdge;
-    vinColor_Stage0 = inColor;
-    highp vec2 pos2 = inPosition;
-    vTransformedCoords_0_Stage0 = (uCoordTransformMatrix_0_Stage0 * vec3(inPosition, 1.0)).xy;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_geomdomain.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_geomdomain.glsl
deleted file mode 100644
index 42fa747..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_geomdomain.glsl
+++ /dev/null
@@ -1,19 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-attribute highp vec4 positionWithCoverage;
-attribute mediump vec4 color;
-attribute highp vec4 geomDomain;
-varying mediump vec4 vcolor_Stage0;
-varying highp float vcoverage_Stage0;
-varying highp vec4 vgeomDomain_Stage0;
-void main() {
-    highp vec3 position = positionWithCoverage.xyz;
-    vcolor_Stage0 = color;
-    vcoverage_Stage0 = positionWithCoverage.w * positionWithCoverage.z;
-    vgeomDomain_Stage0 = geomDomain;
-    gl_Position = vec4(position.x, position.y, 0.0, position.z);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_geomdomain_position.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_geomdomain_position.glsl
deleted file mode 100644
index f0524bb..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_geomdomain_position.glsl
+++ /dev/null
@@ -1,20 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-attribute highp vec2 position;
-attribute highp float coverage;
-attribute mediump vec4 color;
-attribute highp vec4 geomDomain;
-varying mediump vec4 vcolor_Stage0;
-varying highp float vcoverage_Stage0;
-varying highp vec4 vgeomDomain_Stage0;
-void main() {
-    highp vec2 position = position;
-    vcolor_Stage0 = color;
-    vcoverage_Stage0 = coverage;
-    vgeomDomain_Stage0 = geomDomain;
-    gl_Position = vec4(position.x, position.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_position.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_position.glsl
deleted file mode 100644
index bfd2202..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_position.glsl
+++ /dev/null
@@ -1,17 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform mediump vec4 uColor_Stage0;
-attribute highp vec2 inPosition;
-attribute mediump float inCoverage;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-    mediump vec4 color = uColor_Stage0;
-    color = color * inCoverage;
-    vcolor_Stage0 = color;
-    highp vec2 pos2 = inPosition;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_position_2.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_position_2.glsl
deleted file mode 100644
index 563fa5c..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_position_2.glsl
+++ /dev/null
@@ -1,17 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute mediump float inCoverage;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-    mediump vec4 color = inColor;
-    color = color * inCoverage;
-    vcolor_Stage0 = color;
-    highp vec2 pos2 = inPosition;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_position_texcoord_transform.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_position_texcoord_transform.glsl
deleted file mode 100644
index 7bd02d0..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_position_texcoord_transform.glsl
+++ /dev/null
@@ -1,21 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp mat3 uCoordTransformMatrix_0_Stage0;
-attribute highp vec2 position;
-attribute highp float coverage;
-attribute mediump vec4 color;
-attribute highp vec2 localCoord;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-varying highp float vcoverage_Stage0;
-void main() {
-    highp vec2 position = position;
-    vTransformedCoords_0_Stage0 = (uCoordTransformMatrix_0_Stage0 * vec3(localCoord, 1.0)).xy;
-    vcolor_Stage0 = color;
-    vcoverage_Stage0 = coverage;
-    gl_Position = vec4(position.x, position.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_position_texcoord_transform_2.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_position_texcoord_transform_2.glsl
deleted file mode 100644
index ebb9b3e..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_coverage_position_texcoord_transform_2.glsl
+++ /dev/null
@@ -1,20 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform mediump vec4 uColor_Stage0;
-uniform highp mat3 uCoordTransformMatrix_0_Stage0;
-attribute highp vec2 inPosition;
-attribute mediump float inCoverage;
-varying mediump vec4 vcolor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-void main() {
-    mediump vec4 color = uColor_Stage0;
-    color = color * inCoverage;
-    vcolor_Stage0 = color;
-    highp vec2 pos2 = inPosition;
-    vTransformedCoords_0_Stage0 = (uCoordTransformMatrix_0_Stage0 * vec3(inPosition, 1.0)).xy;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_ellipse_position.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_ellipse_position.glsl
deleted file mode 100644
index bf8aa7d..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_ellipse_position.glsl
+++ /dev/null
@@ -1,20 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute highp vec3 inEllipseOffset;
-attribute highp vec4 inEllipseRadii;
-varying highp vec3 vEllipseOffsets_Stage0;
-varying highp vec4 vEllipseRadii_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    vEllipseOffsets_Stage0 = inEllipseOffset;
-    vEllipseRadii_Stage0 = inEllipseRadii;
-    vinColor_Stage0 = inColor;
-    highp vec2 pos2 = inPosition;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_ellipse_position_2.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_ellipse_position_2.glsl
deleted file mode 100644
index d31eeb5..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_ellipse_position_2.glsl
+++ /dev/null
@@ -1,21 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp mat3 uViewM_Stage0;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute highp vec3 inEllipseOffsets0;
-attribute highp vec2 inEllipseOffsets1;
-varying highp vec3 vEllipseOffsets0_Stage0;
-varying highp vec2 vEllipseOffsets1_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    vEllipseOffsets0_Stage0 = inEllipseOffsets0;
-    vEllipseOffsets1_Stage0 = inEllipseOffsets1;
-    vinColor_Stage0 = inColor;
-    highp vec2 pos2 = (uViewM_Stage0 * vec3(inPosition, 1.0)).xy;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_ellipse_position_3.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_ellipse_position_3.glsl
deleted file mode 100644
index f59a37c..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_ellipse_position_3.glsl
+++ /dev/null
@@ -1,21 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp mat3 uViewM_Stage0;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute highp vec3 inEllipseOffsets0;
-attribute highp vec2 inEllipseOffsets1;
-varying highp vec3 vEllipseOffsets0_Stage0;
-varying highp vec2 vEllipseOffsets1_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    vEllipseOffsets0_Stage0 = inEllipseOffsets0;
-    vEllipseOffsets1_Stage0 = inEllipseOffsets1;
-    vinColor_Stage0 = inColor;
-    highp vec3 pos3 = uViewM_Stage0 * vec3(inPosition, 1.0);
-    gl_Position = vec4(pos3.x, pos3.y, 0.0, pos3.z);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_ellipse_position_texcoord_transform.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_ellipse_position_texcoord_transform.glsl
deleted file mode 100644
index 3ae93c0..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_ellipse_position_texcoord_transform.glsl
+++ /dev/null
@@ -1,23 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp mat3 uCoordTransformMatrix_0_Stage0;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute highp vec3 inEllipseOffset;
-attribute highp vec4 inEllipseRadii;
-varying highp vec3 vEllipseOffsets_Stage0;
-varying highp vec4 vEllipseRadii_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-void main() {
-    vEllipseOffsets_Stage0 = inEllipseOffset;
-    vEllipseRadii_Stage0 = inEllipseRadii;
-    vinColor_Stage0 = inColor;
-    highp vec2 pos2 = inPosition;
-    vTransformedCoords_0_Stage0 = (uCoordTransformMatrix_0_Stage0 * vec3(inPosition, 1.0)).xy;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position.glsl
deleted file mode 100644
index c68b57a..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position.glsl
+++ /dev/null
@@ -1,13 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-attribute highp vec2 position;
-attribute mediump vec4 color;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-    vcolor_Stage0 = color;
-    gl_Position = vec4(position.x, position.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_2.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_2.glsl
deleted file mode 100644
index 2f4b372..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_2.glsl
+++ /dev/null
@@ -1,15 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-    mediump vec4 color = inColor;
-    vcolor_Stage0 = color;
-    highp vec2 pos2 = inPosition;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_quad.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_quad.glsl
deleted file mode 100644
index e4b5524..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_quad.glsl
+++ /dev/null
@@ -1,17 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute mediump vec4 inQuadEdge;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    vQuadEdge_Stage0 = inQuadEdge;
-    vinColor_Stage0 = inColor;
-    highp vec2 pos2 = inPosition;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_quad_texcoord_transform.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_quad_texcoord_transform.glsl
deleted file mode 100644
index fd968cb..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_quad_texcoord_transform.glsl
+++ /dev/null
@@ -1,20 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp mat3 uCoordTransformMatrix_0_Stage0;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute mediump vec4 inQuadEdge;
-varying mediump vec4 vQuadEdge_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-void main() {
-    vQuadEdge_Stage0 = inQuadEdge;
-    vinColor_Stage0 = inColor;
-    highp vec2 pos2 = inPosition;
-    vTransformedCoords_0_Stage0 = (uCoordTransformMatrix_0_Stage0 * vec3(inPosition, 1.0)).xy;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord.glsl
deleted file mode 100644
index c15bd44..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord.glsl
+++ /dev/null
@@ -1,25 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp vec2 uAtlasSizeInv_Stage0;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute highp vec2 inTextureCoords;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying highp vec2 vIntTextureCoords_Stage0;
-void main() {
-    vinColor_Stage0 = inColor;
-    highp vec2 indexTexCoords = vec2(inTextureCoords.x, inTextureCoords.y);
-    highp vec2 unormTexCoords = floor(0.5 * indexTexCoords);
-    highp vec2 diff = indexTexCoords - 2.0 * unormTexCoords;
-    highp float texIdx = 2.0 * diff.x + diff.y;
-    vTextureCoords_Stage0 = unormTexCoords * uAtlasSizeInv_Stage0;
-    vTexIndex_Stage0 = texIdx;
-    vIntTextureCoords_Stage0 = unormTexCoords;
-    gl_Position = vec4(inPosition.x, inPosition.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_2.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_2.glsl
deleted file mode 100644
index 61a2b23..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_2.glsl
+++ /dev/null
@@ -1,23 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp vec2 uAtlasSizeInv_Stage0;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute highp vec2 inTextureCoords;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    highp vec2 indexTexCoords = vec2(inTextureCoords.x, inTextureCoords.y);
-    highp vec2 unormTexCoords = floor(0.5 * indexTexCoords);
-    highp vec2 diff = indexTexCoords - 2.0 * unormTexCoords;
-    highp float texIdx = 2.0 * diff.x + diff.y;
-    vTextureCoords_Stage0 = unormTexCoords * uAtlasSizeInv_Stage0;
-    vTexIndex_Stage0 = texIdx;
-    vinColor_Stage0 = inColor;
-    gl_Position = vec4(inPosition.x, inPosition.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_3.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_3.glsl
deleted file mode 100644
index 619a5db..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_3.glsl
+++ /dev/null
@@ -1,17 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp mat3 uCoordTransformMatrix_0_Stage0;
-attribute highp vec2 position;
-attribute mediump vec4 color;
-attribute highp vec2 localCoord;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-    vTransformedCoords_0_Stage0 = (uCoordTransformMatrix_0_Stage0 * vec3(localCoord, 1.0)).xy;
-    vcolor_Stage0 = color;
-    gl_Position = vec4(position.x, position.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_4.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_4.glsl
deleted file mode 100644
index 39d3803..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_4.glsl
+++ /dev/null
@@ -1,16 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-attribute highp vec2 position;
-attribute mediump vec4 color;
-attribute highp vec2 localCoord;
-varying mediump vec4 vcolor_Stage0;
-varying highp vec2 vlocalCoord_Stage0;
-void main() {
-    vcolor_Stage0 = color;
-    vlocalCoord_Stage0 = localCoord;
-    gl_Position = vec4(position.x, position.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_5.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_5.glsl
deleted file mode 100644
index aa505f6..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_5.glsl
+++ /dev/null
@@ -1,26 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp vec2 uAtlasSizeInv_Stage0;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute highp vec2 inTextureCoords;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying highp vec2 vIntTextureCoords_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    highp vec2 indexTexCoords = vec2(inTextureCoords.x, inTextureCoords.y);
-    highp vec2 unormTexCoords = floor(0.5 * indexTexCoords);
-    highp vec2 diff = indexTexCoords - 2.0 * unormTexCoords;
-    highp float texIdx = 2.0 * diff.x + diff.y;
-    vTextureCoords_Stage0 = unormTexCoords * uAtlasSizeInv_Stage0;
-    vTexIndex_Stage0 = texIdx;
-    vIntTextureCoords_Stage0 = unormTexCoords;
-    vinColor_Stage0 = inColor;
-    highp vec2 pos2 = inPosition;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_texindex.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_texindex.glsl
deleted file mode 100644
index 15c7252..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_texindex.glsl
+++ /dev/null
@@ -1,27 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp vec2 uAtlasSizeInv_Stage0;
-uniform highp mat3 uViewM_Stage0;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute highp vec2 inTextureCoords;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying highp vec2 vIntTextureCoords_Stage0;
-varying mediump vec4 vinColor_Stage0;
-void main() {
-    highp vec2 indexTexCoords = vec2(inTextureCoords.x, inTextureCoords.y);
-    highp vec2 unormTexCoords = floor(0.5 * indexTexCoords);
-    highp vec2 diff = indexTexCoords - 2.0 * unormTexCoords;
-    highp float texIdx = 2.0 * diff.x + diff.y;
-    vTextureCoords_Stage0 = unormTexCoords * uAtlasSizeInv_Stage0;
-    vTexIndex_Stage0 = texIdx;
-    vIntTextureCoords_Stage0 = unormTexCoords;
-    vinColor_Stage0 = inColor;
-    highp vec3 pos3 = uViewM_Stage0 * vec3(inPosition, 1.0);
-    gl_Position = vec4(pos3.x, pos3.y, 0.0, pos3.z);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_texindex_transform.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_texindex_transform.glsl
deleted file mode 100644
index 82387e0..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_texindex_transform.glsl
+++ /dev/null
@@ -1,26 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp vec2 uAtlasSizeInv_Stage0;
-uniform highp mat3 uCoordTransformMatrix_0_Stage0;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inColor;
-attribute highp vec2 inTextureCoords;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-varying mediump vec4 vinColor_Stage0;
-varying highp vec2 vTransformedCoords_0_Stage0;
-void main() {
-    highp vec2 indexTexCoords = vec2(inTextureCoords.x, inTextureCoords.y);
-    highp vec2 unormTexCoords = floor(0.5 * indexTexCoords);
-    highp vec2 diff = indexTexCoords - 2.0 * unormTexCoords;
-    highp float texIdx = 2.0 * diff.x + diff.y;
-    vTextureCoords_Stage0 = unormTexCoords * uAtlasSizeInv_Stage0;
-    vTexIndex_Stage0 = texIdx;
-    vinColor_Stage0 = inColor;
-    vTransformedCoords_0_Stage0 = (uCoordTransformMatrix_0_Stage0 * vec3(inPosition, 1.0)).xy;
-    gl_Position = vec4(inPosition.x, inPosition.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_transform.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_transform.glsl
deleted file mode 100644
index f5358bf..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_transform.glsl
+++ /dev/null
@@ -1,20 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp mat3 uCoordTransformMatrix_0_Stage0;
-uniform highp mat3 uCoordTransformMatrix_1_Stage0;
-attribute highp vec2 position;
-attribute mediump vec4 color;
-attribute highp vec2 localCoord;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying highp vec2 vTransformedCoords_1_Stage0;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-    vTransformedCoords_0_Stage0 = (uCoordTransformMatrix_0_Stage0 * vec3(localCoord, 1.0)).xy;
-    vTransformedCoords_1_Stage0 = (uCoordTransformMatrix_1_Stage0 * vec3(localCoord, 1.0)).xy;
-    vcolor_Stage0 = color;
-    gl_Position = vec4(position.x, position.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_transform_2.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_transform_2.glsl
deleted file mode 100644
index 0684998..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_color_position_texcoord_transform_2.glsl
+++ /dev/null
@@ -1,17 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp mat3 uCoordTransformMatrix_0_Stage0;
-attribute highp vec2 position;
-attribute mediump vec4 color;
-attribute highp vec3 localCoord;
-varying highp vec3 vTransformedCoords_0_Stage0;
-varying mediump vec4 vcolor_Stage0;
-void main() {
-    vTransformedCoords_0_Stage0 = uCoordTransformMatrix_0_Stage0 * localCoord;
-    vcolor_Stage0 = color;
-    gl_Position = vec4(position.x, position.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_coverage_position.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_coverage_position.glsl
deleted file mode 100644
index 7124ac4..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_coverage_position.glsl
+++ /dev/null
@@ -1,14 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-attribute highp vec2 inPosition;
-attribute mediump float inCoverage;
-varying mediump float vinCoverage_Stage0;
-void main() {
-    highp vec2 pos2 = inPosition;
-    vinCoverage_Stage0 = inCoverage;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_coverage_position_texcoord_transform.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_coverage_position_texcoord_transform.glsl
deleted file mode 100644
index db58f8f..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_coverage_position_texcoord_transform.glsl
+++ /dev/null
@@ -1,17 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp mat3 uCoordTransformMatrix_0_Stage0;
-attribute highp vec2 inPosition;
-attribute mediump float inCoverage;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying mediump float vinCoverage_Stage0;
-void main() {
-    highp vec2 pos2 = inPosition;
-    vTransformedCoords_0_Stage0 = (uCoordTransformMatrix_0_Stage0 * vec3(inPosition, 1.0)).xy;
-    vinCoverage_Stage0 = inCoverage;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_coverage_position_texcoord_transform_2.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_coverage_position_texcoord_transform_2.glsl
deleted file mode 100644
index 2881e74..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_coverage_position_texcoord_transform_2.glsl
+++ /dev/null
@@ -1,18 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp mat3 uCoordTransformMatrix_0_Stage0;
-attribute highp vec2 position;
-attribute highp float coverage;
-attribute highp vec2 localCoord;
-varying highp vec2 vTransformedCoords_0_Stage0;
-varying highp float vcoverage_Stage0;
-void main() {
-    highp vec2 position = position;
-    vTransformedCoords_0_Stage0 = (uCoordTransformMatrix_0_Stage0 * vec3(localCoord, 1.0)).xy;
-    vcoverage_Stage0 = coverage;
-    gl_Position = vec4(position.x, position.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_hairquad_position.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_hairquad_position.glsl
deleted file mode 100644
index f21c1ab..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_hairquad_position.glsl
+++ /dev/null
@@ -1,14 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-attribute highp vec2 inPosition;
-attribute mediump vec4 inHairQuadEdge;
-varying mediump vec4 vHairQuadEdge_Stage0;
-void main() {
-    vHairQuadEdge_Stage0 = inHairQuadEdge;
-    highp vec2 pos2 = inPosition;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_position.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_position.glsl
deleted file mode 100644
index 6a5e9ca..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_position.glsl
+++ /dev/null
@@ -1,12 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp mat3 uViewM_Stage0;
-attribute highp vec2 inPosition;
-void main() {
-    highp vec2 pos2 = (uViewM_Stage0 * vec3(inPosition, 1.0)).xy;
-    gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_position_texcoord.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_position_texcoord.glsl
deleted file mode 100644
index 0782274..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_position_texcoord.glsl
+++ /dev/null
@@ -1,13 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-attribute highp vec2 position;
-attribute highp vec2 localCoord;
-varying highp vec2 vlocalCoord_Stage0;
-void main() {
-    vlocalCoord_Stage0 = localCoord;
-    gl_Position = vec4(position.x, position.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_position_texcoord_2.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_position_texcoord_2.glsl
deleted file mode 100644
index cbb8078..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_position_texcoord_2.glsl
+++ /dev/null
@@ -1,20 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp vec2 uAtlasSizeInv_Stage0;
-attribute highp vec2 inPosition;
-attribute highp vec2 inTextureCoords;
-varying highp vec2 vTextureCoords_Stage0;
-varying highp float vTexIndex_Stage0;
-void main() {
-    highp vec2 indexTexCoords = vec2(inTextureCoords.x, inTextureCoords.y);
-    highp vec2 unormTexCoords = floor(0.5 * indexTexCoords);
-    highp vec2 diff = indexTexCoords - 2.0 * unormTexCoords;
-    highp float texIdx = 2.0 * diff.x + diff.y;
-    vTextureCoords_Stage0 = unormTexCoords * uAtlasSizeInv_Stage0;
-    vTexIndex_Stage0 = texIdx;
-    gl_Position = vec4(inPosition.x, inPosition.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_position_texcoord_transform.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_skia_position_texcoord_transform.glsl
deleted file mode 100644
index d60d1d2..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_skia_position_texcoord_transform.glsl
+++ /dev/null
@@ -1,14 +0,0 @@
-#version 100
-
-precision mediump float;
-precision mediump sampler2D;
-uniform highp vec4 sk_RTAdjust;
-uniform highp mat3 uCoordTransformMatrix_0_Stage0;
-attribute highp vec2 position;
-attribute highp vec2 localCoord;
-varying highp vec2 vTransformedCoords_0_Stage0;
-void main() {
-    vTransformedCoords_0_Stage0 = (uCoordTransformMatrix_0_Stage0 * vec3(localCoord, 1.0)).xy;
-    gl_Position = vec4(position.x, position.y, 0.0, 1.0);
-    gl_Position = vec4(gl_Position.xy * sk_RTAdjust.xz + gl_Position.ww * sk_RTAdjust.yw, 0.0, gl_Position.w);
-}
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_textured_vbo_rgba.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_textured_vbo_rgba.glsl
deleted file mode 100644
index 5797b87..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_textured_vbo_rgba.glsl
+++ /dev/null
@@ -1,11 +0,0 @@
-attribute vec3 a_position;

-attribute vec2 a_tex_coord;

-varying vec2 v_tex_coord_rgba;

-uniform vec4 scale_translate_rgba;

-uniform mat4 model_view_projection_transform;

-

-void main() {

-  gl_Position = model_view_projection_transform * vec4(a_position.xyz, 1.0);

-  v_tex_coord_rgba =

-      a_tex_coord * scale_translate_rgba.xy + scale_translate_rgba.zw;

-}
\ No newline at end of file
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_textured_vbo_uyvy_1plane.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_textured_vbo_uyvy_1plane.glsl
deleted file mode 100644
index 3c0ebfa..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_textured_vbo_uyvy_1plane.glsl
+++ /dev/null
@@ -1,10 +0,0 @@
-attribute vec3 a_position;

-attribute vec2 a_tex_coord;

-varying vec2 v_tex_coord_uyvy;

-uniform vec4 scale_translate_uyvy;

-uniform mat4 model_view_projection_transform;

-

-void main() {

-  gl_Position = model_view_projection_transform * vec4(a_position.xyz, 1.0);

-  v_tex_coord_uyvy = a_tex_coord * scale_translate_uyvy.xy + scale_translate_uyvy.zw;

-}
\ No newline at end of file
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_textured_vbo_yuv_2plane.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_textured_vbo_yuv_2plane.glsl
deleted file mode 100644
index 8ebde52..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_textured_vbo_yuv_2plane.glsl
+++ /dev/null
@@ -1,13 +0,0 @@
-attribute vec3 a_position;

-attribute vec2 a_tex_coord;

-varying vec2 v_tex_coord_y;

-varying vec2 v_tex_coord_uv;

-uniform vec4 scale_translate_y;

-uniform vec4 scale_translate_uv;

-uniform mat4 model_view_projection_transform;

-

-void main() {

-  gl_Position = model_view_projection_transform * vec4(a_position.xyz, 1.0);

-  v_tex_coord_y = a_tex_coord * scale_translate_y.xy + scale_translate_y.zw;

-  v_tex_coord_uv = a_tex_coord * scale_translate_uv.xy + scale_translate_uv.zw;

-}
\ No newline at end of file
diff --git a/cobalt/renderer/glimp_shaders/glsl/vertex_textured_vbo_yuv_3plane.glsl b/cobalt/renderer/glimp_shaders/glsl/vertex_textured_vbo_yuv_3plane.glsl
deleted file mode 100644
index c95ad67..0000000
--- a/cobalt/renderer/glimp_shaders/glsl/vertex_textured_vbo_yuv_3plane.glsl
+++ /dev/null
@@ -1,16 +0,0 @@
-attribute vec3 a_position;

-attribute vec2 a_tex_coord;

-varying vec2 v_tex_coord_y;

-varying vec2 v_tex_coord_u;

-varying vec2 v_tex_coord_v;

-uniform vec4 scale_translate_y;

-uniform vec4 scale_translate_u;

-uniform vec4 scale_translate_v;

-uniform mat4 model_view_projection_transform;

-

-void main() {

-  gl_Position = model_view_projection_transform * vec4(a_position.xyz, 1.0);

-  v_tex_coord_y = a_tex_coord * scale_translate_y.xy + scale_translate_y.zw;

-  v_tex_coord_u = a_tex_coord * scale_translate_u.xy + scale_translate_u.zw;

-  v_tex_coord_v = a_tex_coord * scale_translate_v.xy + scale_translate_v.zw;

-}
\ No newline at end of file
diff --git a/cobalt/renderer/rasterizer/egl/shaders/generate_shader_impl.py b/cobalt/renderer/rasterizer/egl/shaders/generate_shader_impl.py
index b5d3ba8..7450945 100644
--- a/cobalt/renderer/rasterizer/egl/shaders/generate_shader_impl.py
+++ b/cobalt/renderer/rasterizer/egl/shaders/generate_shader_impl.py
@@ -127,7 +127,7 @@
   data_definition_string = '{\n'
   for output_line_data in GetChunk(file_contents, chunk_size):
     data_definition_string += (
-        '  ' + ' '.join(['0x%02x,' % ord(y) for y in output_line_data]) + '\n')
+        '  ' + ' '.join([f'0x{ord(y):02x},' for y in output_line_data]) + '\n')
   data_definition_string += '};\n'
   return data_definition_string
 
@@ -137,7 +137,7 @@
   source_definition_string = ''
   for filename in files:
     class_name = GetShaderClassName(filename)
-    source_definition_string += '\nconst char %s::kSource[] = ' % class_name
+    source_definition_string += f'\nconst char {class_name}::kSource[] = '
     source_definition_string += GetDataDefinitionStringForFile(filename)
   return source_definition_string
 
@@ -169,7 +169,7 @@
   """Generate the actual C++ source file."""
   header_filename = os.path.basename(header_filename)
   current_year = datetime.datetime.now().year
-  with open(source_filename, 'w') as output_file:
+  with open(source_filename, 'w', encoding='utf-8') as output_file:
     output_file.write(
         SOURCE_FILE_TEMPLATE.format(
             year=current_year,
@@ -239,7 +239,7 @@
   """Return a string representing C++ methods for the given attributes."""
   methods = ''
   for index, name in enumerate(attributes):
-    methods += '\nGLuint {0}() const {{ return {1}; }}'.format(name, index)
+    methods += f'\nGLuint {name}() const {{ return {index}; }}'
   return methods
 
 
@@ -248,11 +248,10 @@
   methods = ''
   for name in uniforms:
     base, count = ParseUniformName(name)
-    methods += '\nGLuint {0}() const {{ return {0}_; }}'.format(base)
+    methods += f'\nGLuint {base}() const {{ return {base}_; }}'
     if count:
       methods += (
-          '\nstatic constexpr GLsizei {0}_count() {{ return {1}; }}'.format(
-              base, count))
+          f'\nstatic constexpr GLsizei {base}_count() {{ return {count}; }}')
   return methods
 
 
@@ -261,8 +260,7 @@
   methods = ''
   for index, name in enumerate(samplers):
     methods += (
-        '\nGLenum {0}_texunit() const {{ return GL_TEXTURE{1}; }}'.format(
-            name, index))
+        f'\nGLenum {name}_texunit() const {{ return GL_TEXTURE{index}; }}')
   return methods
 
 
@@ -274,24 +272,22 @@
     if not elements:
       continue
 
-    method = '\nGLuint {0}(int index) const {{\n'.format(array_name)
+    method = f'\nGLuint {array_name}(int index) const {{\n'
 
     for index in range(len(elements)):
-      method += '  if (index == {0}) return {1}();\n'.format(
-          index, elements[index])
-    method += '  NOTREACHED();\n  return {0}();\n}}'.format(elements[0])
+      method += f'  if (index == {index}) return {elements[index]}();\n'
+    method += f'  NOTREACHED();\n  return {elements[0]}();\n}}'
 
     methods += (method)
 
     if samplers.count(elements[0]) == 0:
       continue
 
-    method = '\nGLenum {0}_texunit(int index) const {{\n'.format(array_name)
+    method = f'\nGLenum {array_name}_texunit(int index) const {{\n'
     # Generate *_texunit() for samplers.
     for index in range(len(elements)):
-      method += '  if (index == {0}) return {1}_texunit();\n'.format(
-          index, elements[index])
-    method += '  NOTREACHED();\n  return {0}_texunit();\n}}'.format(elements[0])
+      method += f'  if (index == {index}) return {elements[index]}_texunit();\n'
+    method += f'  NOTREACHED();\n  return {elements[0]}_texunit();\n}}'
 
     methods += (method)
   return methods
@@ -301,7 +297,7 @@
   """Returns a string representing C++ statements to process during prelink."""
   statements = ''
   for name in attributes:
-    statements += '\nBindAttribLocation(program, {0}(), "{0}");'.format(name)
+    statements += f'\nBindAttribLocation(program, {name}(), "{name}");'
   return statements
 
 
@@ -310,7 +306,7 @@
   statements = ''
   for name in uniforms:
     base, unused_count = ParseUniformName(name)
-    statements += '\n{0}_ = GetUniformLocation(program, "{0}");'.format(base)
+    statements += f'\n{base}_ = GetUniformLocation(program, "{base}");'
   return statements
 
 
@@ -319,7 +315,7 @@
   statements = ''
   for name in samplers:
     statements += (
-        '\nSetTextureUnitForUniformSampler({0}(), {0}_texunit());'.format(name))
+        f'\nSetTextureUnitForUniformSampler({name}(), {name}_texunit());')
   return statements
 
 
@@ -328,7 +324,7 @@
   variables = ''
   for name in variable_names:
     base, unused_count = ParseUniformName(name)
-    variables += '\nGLuint {0}_;'.format(base)
+    variables += f'\nGLuint {base}_;'
   return variables
 
 
@@ -413,9 +409,9 @@
 
 def GenerateHeaderFile(output_filename, all_shaders):
   """Generate the actual C++ header file."""
-  include_guard = 'GENERATED_%s_H_' % GetBasename(output_filename).upper()
+  include_guard = f'GENERATED_{GetBasename(output_filename).upper()}_H_'
   current_year = datetime.datetime.now().year
-  with open(output_filename, 'w') as output_file:
+  with open(output_filename, 'w', encoding='utf-8') as output_file:
     output_file.write(
         HEADER_FILE_TEMPLATE.format(
             year=current_year,
@@ -425,7 +421,7 @@
 
 def main(output_header_filename, output_source_filename, shader_files_file):
   all_shader_files = []
-  with open(shader_files_file, 'r') as input_file:
+  with open(shader_files_file, 'r', encoding='utf-8') as input_file:
     shader_files = input_file.read().splitlines()
     for filename in shader_files:
       # Ignore *.inc files. These are include files and not shader files.
diff --git a/cobalt/renderer/rasterizer/skia/hardware_mesh.cc b/cobalt/renderer/rasterizer/skia/hardware_mesh.cc
index 39d09af..215cbce 100644
--- a/cobalt/renderer/rasterizer/skia/hardware_mesh.cc
+++ b/cobalt/renderer/rasterizer/skia/hardware_mesh.cc
@@ -20,6 +20,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/threading/thread_task_runner_handle.h"
 #include "starboard/configuration.h"
 
 namespace cobalt {
@@ -42,7 +43,7 @@
 const VertexBufferObject* HardwareMesh::GetVBO() const {
   if (!vbo_) {
     if (base::MessageLoop::current()) {
-      rasterizer_task_runner_ = base::MessageLoop::current()->task_runner();
+      rasterizer_task_runner_ = base::ThreadTaskRunnerHandle::Get();
     }
     vbo_.reset(new VertexBufferObject(std::move(vertices_), draw_mode_));
   }
diff --git a/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc b/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
index 6b7a91e..83f3dc4 100644
--- a/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
+++ b/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
@@ -18,6 +18,7 @@
 #include <utility>
 
 #include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/renderer/backend/egl/graphics_system.h"
@@ -60,7 +61,7 @@
           purge_skia_font_caches_on_destruction),
       max_texture_size_(gr_context->maxTextureSize()) {
   if (base::MessageLoop::current()) {
-    rasterizer_task_runner_ = base::MessageLoop::current()->task_runner();
+    rasterizer_task_runner_ = base::ThreadTaskRunnerHandle::Get();
   }
 
   // Initialize the font manager now to ensure that it doesn't get initialized
@@ -239,7 +240,9 @@
       return render_tree::kMultiPlaneImageFormatYUV3Plane10BitCompactedBT2020;
     }
 #endif  // SB_API_VERSION >= 14
-    default: { NOTREACHED(); }
+    default: {
+      NOTREACHED();
+    }
   }
   return render_tree::kMultiPlaneImageFormatYUV2PlaneBT709;
 }
diff --git a/cobalt/renderer/sandbox/renderer_sandbox_main.cc b/cobalt/renderer/sandbox/renderer_sandbox_main.cc
index 5f14c46..7e9a845 100644
--- a/cobalt/renderer/sandbox/renderer_sandbox_main.cc
+++ b/cobalt/renderer/sandbox/renderer_sandbox_main.cc
@@ -17,6 +17,7 @@
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/wrap_main.h"
 #include "cobalt/math/size.h"
@@ -88,7 +89,7 @@
   g_renderer_sandbox = new RendererSandbox();
   DCHECK(g_renderer_sandbox);
 
-  base::MessageLoop::current()->task_runner()->PostDelayedTask(
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE, quit_closure, base::TimeDelta::FromSeconds(30));
 }
 
diff --git a/cobalt/script/v8c/BUILD.gn b/cobalt/script/v8c/BUILD.gn
index 74add0b..3ecb6e0 100644
--- a/cobalt/script/v8c/BUILD.gn
+++ b/cobalt/script/v8c/BUILD.gn
@@ -36,14 +36,19 @@
 }
 
 config("common_public_config") {
+  defines = []
   if ((current_cpu == "arm64" || current_cpu == "x64") &&
       v8_enable_pointer_compression_override) {
-    defines = [
+    defines += [
       # enables pointer compression on 64 bit platforms for Cobalt.
       "V8_COMPRESS_POINTERS",
       "V8_31BIT_SMIS_ON_64BIT_ARCH",
     ]
   }
+
+  if (v8_enable_webassembly) {
+    defines += [ "V8_ENABLE_WEBASSEMBLY" ]
+  }
 }
 
 config("engine_all_dependent_config") {
diff --git a/cobalt/script/v8c/isolate_fellowship.cc b/cobalt/script/v8c/isolate_fellowship.cc
index 6496c4c..0af59d5 100644
--- a/cobalt/script/v8c/isolate_fellowship.cc
+++ b/cobalt/script/v8c/isolate_fellowship.cc
@@ -43,17 +43,22 @@
 // v8 instance is created.
 void V8FlagsInit() {
   std::vector<std::string> kV8CommandLineFlags = {
-    "--optimize_for_size",
-    // Starboard disallow rwx memory access.
-    "--write_protect_code_memory",
-    // Cobalt's TraceMembers and
-    // ScriptValue::*Reference do not currently
-    // support incremental tracing.
-    "--noincremental_marking_wrappers",
-    "--noexpose_wasm",
-    "--novalidate_asm",
+      "--optimize_for_size",
+      // Starboard disallow rwx memory access.
+      "--write_protect_code_memory",
+      // Cobalt's TraceMembers and
+      // ScriptValue::*Reference do not currently
+      // support incremental tracing.
+      "--noincremental_marking_wrappers",
+      "--novalidate_asm",
   };
 
+#ifdef V8_ENABLE_WEBASSEMBLY
+  kV8CommandLineFlags.push_back("--expose_wasm");
+#else
+  kV8CommandLineFlags.push_back("--noexpose_wasm");
+#endif
+
   if (!configuration::Configuration::GetInstance()->CobaltEnableJit()) {
     kV8CommandLineFlags.push_back("--jitless");
   }
@@ -63,8 +68,7 @@
   }
 
   for (auto flag_str : kV8CommandLineFlags) {
-    v8::V8::SetFlagsFromString(flag_str.c_str(),
-                               strlen(flag_str.c_str()));
+    v8::V8::SetFlagsFromString(flag_str.c_str(), strlen(flag_str.c_str()));
   }
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
@@ -104,7 +108,6 @@
   DCHECK(array_buffer_allocator);
   delete array_buffer_allocator;
   array_buffer_allocator = nullptr;
-
 }
 
 }  // namespace v8c
diff --git a/cobalt/script/v8c/v8c.cc b/cobalt/script/v8c/v8c.cc
index 5ff424f..ed69e68 100644
--- a/cobalt/script/v8c/v8c.cc
+++ b/cobalt/script/v8c/v8c.cc
@@ -17,6 +17,7 @@
 #include "base/command_line.h"
 #include "base/files/file_path.h"
 #include "base/strings/string_util.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/wrap_main.h"
 #include "cobalt/script/source_code.h"
 #include "cobalt/script/standalone_javascript_runner.h"
@@ -79,7 +80,7 @@
                       SbTimeMonotonic timestamp) {
   DCHECK(!g_javascript_runner);
   g_javascript_runner = new cobalt::script::StandaloneJavascriptRunner(
-      base::MessageLoop::current()->task_runner());
+      base::ThreadTaskRunnerHandle::Get());
   DCHECK(g_javascript_runner);
 
   GlobalEnvironment* global_environment =
diff --git a/cobalt/script/v8c/v8c_global_environment.cc b/cobalt/script/v8c/v8c_global_environment.cc
index 0fd5937..2a56a4d 100644
--- a/cobalt/script/v8c/v8c_global_environment.cc
+++ b/cobalt/script/v8c/v8c_global_environment.cc
@@ -22,6 +22,7 @@
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/cache/cache.h"
+#include "cobalt/configuration/configuration.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/script/v8c/embedded_resources.h"
 #include "cobalt/script/v8c/entry_scope.h"
@@ -102,7 +103,11 @@
 
   isolate_->SetAllowWasmCodeGenerationCallback(
       [](v8::Local<v8::Context> context, v8::Local<v8::String> source) {
+#ifdef V8_ENABLE_WEBASSEMBLY
+        return true;
+#else
         return false;
+#endif
       });
 
   isolate_->AddMessageListenerWithErrorLevel(
diff --git a/cobalt/script/v8c/v8c_script_debugger.cc b/cobalt/script/v8c/v8c_script_debugger.cc
index 2778aec..197f747 100644
--- a/cobalt/script/v8c/v8c_script_debugger.cc
+++ b/cobalt/script/v8c/v8c_script_debugger.cc
@@ -17,9 +17,11 @@
 #include <memory>
 #include <sstream>
 #include <string>
+#include <utility>
 
 #include "base/logging.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/script/v8c/conversion_helpers.h"
@@ -45,14 +47,16 @@
     span<uint8_t> env = state->tokenizer()->GetEnvelope();
     auto res = T::fromBinary(env.data(), env.size());
     if (!res) {
-      // TODO(caseq): properly plumb an error rather than returning a bogus code.
+      // TODO(caseq): properly plumb an error rather than returning a bogus
+      // code.
       state->RegisterError(Error::MESSAGE_MUST_BE_AN_OBJECT);
       return false;
     }
     *value = std::move(res);
     return true;
   }
-  static void Serialize(const std::unique_ptr<T>& value, std::vector<uint8_t>* bytes) {
+  static void Serialize(const std::unique_ptr<T>& value,
+                        std::vector<uint8_t>* bytes) {
     // Use virtual method, so that outgoing protocol objects could be retained
     // by a pointer to ProtocolObject.
     value->AppendSerialized(bytes);
@@ -61,16 +65,15 @@
 
 template <typename T>
 struct ProtocolTypeTraits<
-    T,
-    typename std::enable_if<
-        std::is_base_of<v8_inspector::protocol::Exported, T>::value>::type> {
+    T, typename std::enable_if<
+           std::is_base_of<v8_inspector::protocol::Exported, T>::value>::type> {
   static void Serialize(const T& value, std::vector<uint8_t>* bytes) {
     // Use virtual method, so that outgoing protocol objects could be retained
     // by a pointer to ProtocolObject.
     value.AppendSerialized(bytes);
   }
 };
-}
+}  // namespace v8_crdtp
 
 namespace cobalt {
 namespace script {
@@ -78,7 +81,9 @@
 
 namespace {
 constexpr const char* kInspectorDomains[] = {
-    "Runtime", "Debugger", "Profiler",
+    "Runtime",
+    "Debugger",
+    "Profiler",
 };
 
 constexpr int kContextGroupId = 1;
@@ -160,8 +165,7 @@
   // TODO: there might be an opportunity to utilize the already encoded json to
   // reduce network traffic size on the wire.
   v8_crdtp::Status status = v8_crdtp::json::ConvertCBORToJSON(
-      v8_crdtp::span<uint8_t>(state.data(), state.size()),
-      &state_str);
+      v8_crdtp::span<uint8_t>(state.data(), state.size()), &state_str);
   CHECK(status.ok()) << status.Message();
   return state_str;
 }
@@ -246,8 +250,7 @@
   remote_object->AppendSerialized(&out);
   std::string remote_object_str;
   v8_crdtp::Status status = v8_crdtp::json::ConvertCBORToJSON(
-      v8_crdtp::span<uint8_t>(out.data(), out.size()),
-      &remote_object_str);
+      v8_crdtp::span<uint8_t>(out.data(), out.size()), &remote_object_str);
   CHECK(status.ok()) << status.Message();
   return remote_object_str;
 }
@@ -278,7 +281,7 @@
   V8cValueHandleHolder* retval = holder.get();
   // Keep the scoped_ptr alive in a no-op task so the holder stays valid until
   // the bindings code gets the v8::Value out of it through the raw pointer.
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::Bind([](std::unique_ptr<V8cValueHandleHolder>) {},
                             base::Passed(&holder)));
   return retval;
diff --git a/cobalt/site/docs/codelabs/starboard_extensions/codelab.md b/cobalt/site/docs/codelabs/starboard_extensions/codelab.md
index 7cac353..09e2870 100644
--- a/cobalt/site/docs/codelabs/starboard_extensions/codelab.md
+++ b/cobalt/site/docs/codelabs/starboard_extensions/codelab.md
@@ -378,18 +378,18 @@
 
 <details>
     <summary style="display:list-item">`git diff
-    starboard/linux/shared/starboard_platform.gypi`</summary>
+    starboard/linux/shared/BUILD.gn`</summary>
 
 ```
-@@ -38,6 +38,8 @@
-       '<(DEPTH)/starboard/linux/shared/netlink.cc',
-       '<(DEPTH)/starboard/linux/shared/netlink.h',
-       '<(DEPTH)/starboard/linux/shared/player_components_factory.cc',
-+      '<(DEPTH)/starboard/linux/shared/pleasantry.cc',
-+      '<(DEPTH)/starboard/linux/shared/pleasantry.h',
-       '<(DEPTH)/starboard/linux/shared/routes.cc',
-       '<(DEPTH)/starboard/linux/shared/routes.h',
-       '<(DEPTH)/starboard/linux/shared/system_get_connection_type.cc',
+@@ -71,6 +71,8 @@ static_library("starboard_platform_sources") {
+     "//starboard/linux/shared/netlink.cc",
+     "//starboard/linux/shared/netlink.h",
+     "//starboard/linux/shared/player_components_factory.cc",
++    "//starboard/linux/shared/pleasantry.cc",
++    "//starboard/linux/shared/pleasantry.h",
+     "//starboard/linux/shared/routes.cc",
+     "//starboard/linux/shared/routes.h",
+     "//starboard/linux/shared/soft_mic_platform_service.cc",
 ```
 
 </details>
@@ -399,14 +399,15 @@
     starboard/linux/shared/system_get_extensions.cc`</summary>
 
 ```
-@@ -16,12 +16,14 @@
-
- #include "starboard/extension/configuration.h"
- #include "starboard/extension/crash_handler.h"
+@@ -22,6 +22,7 @@
+ #include "starboard/extension/free_space.h"
+ #include "starboard/extension/memory_mapped_file.h"
+ #include "starboard/extension/platform_service.h"
 +#include "starboard/extension/pleasantry.h"
- #include "starboard/common/string.h"
- #include "starboard/shared/starboard/crash_handler.h"
- #if SB_IS(EVERGREEN_COMPATIBLE)
+ #include "starboard/linux/shared/soft_mic_platform_service.h"
+ #include "starboard/shared/enhanced_audio/enhanced_audio.h"
+ #include "starboard/shared/ffmpeg/ffmpeg_demuxer.h"
+@@ -33,6 +34,7 @@
  #include "starboard/elf_loader/evergreen_config.h"
  #endif
  #include "starboard/linux/shared/configuration.h"
@@ -414,15 +415,16 @@
 
  const void* SbSystemGetExtension(const char* name) {
  #if SB_IS(EVERGREEN_COMPATIBLE)
-@@ -41,5 +43,8 @@ const void* SbSystemGetExtension(const char* name) {
-   if (strcmp(name, kStarboardExtensionCrashHandlerName) == 0) {
-     return starboard::common::GetCrashHandlerApi();
+@@ -74,5 +76,8 @@ const void* SbSystemGetExtension(const char* name) {
+     return use_ffmpeg_demuxer ? starboard::shared::ffmpeg::GetFFmpegDemuxerApi()
+                               : NULL;
    }
 +  if (strcmp(name, kStarboardExtensionPleasantryName) == 0) {
 +    return starboard::shared::GetPleasantryApi();
 +  }
    return NULL;
  }
+
 ```
 
 </details>
@@ -432,12 +434,11 @@
     </summary>
 
 ```
-@@ -18,7 +18,9 @@
- #include "cobalt/base/wrap_main.h"
+@@ -19,6 +19,8 @@
  #include "cobalt/browser/application.h"
  #include "cobalt/browser/switches.h"
-+#include "starboard/extension/pleasantry.h"
  #include "cobalt/version.h"
++#include "starboard/extension/pleasantry.h"
 +#include "starboard/system.h"
 
  namespace {
@@ -605,12 +606,11 @@
     </summary>
 
 ```
-@@ -18,7 +18,9 @@
- #include "cobalt/base/wrap_main.h"
+@@ -19,6 +19,8 @@
  #include "cobalt/browser/application.h"
  #include "cobalt/browser/switches.h"
-+#include "starboard/extension/pleasantry.h"
  #include "cobalt/version.h"
++#include "starboard/extension/pleasantry.h"
 +#include "starboard/system.h"
 
  namespace {
@@ -630,11 +630,13 @@
  void PreloadApplication(int argc, char** argv, const char* link,
                          const base::Closure& quit_closure,
                          SbTimeMonotonic timestamp) {
-@@ -77,6 +87,12 @@ void StartApplication(int argc, char** argv, const char* link,
+@@ -77,6 +87,14 @@ void StartApplication(int argc, char** argv, const char* link,
      return;
    }
    LOG(INFO) << "Starting application.";
-+  const StarboardExtensionPleasantryApi* pleasantry_extension = GetPleasantryApi();
++  const StarboardExtensionPleasantryApi* pleasantry_extension =
++      static_cast<const StarboardExtensionPleasantryApi*>(
++          SbSystemGetExtension(kStarboardExtensionPleasantryName));
 +  if (pleasantry_extension &&
 +      strcmp(pleasantry_extension->name, kStarboardExtensionPleasantryName) == 0 &&
 +      pleasantry_extension->version >= 1) {
@@ -643,7 +645,7 @@
  #if SB_API_VERSION >= 13
    DCHECK(!g_application);
    g_application = new cobalt::browser::Application(quit_closure,
-@@ -96,7 +112,14 @@ void StartApplication(int argc, char** argv, const char* link,
+@@ -96,7 +114,14 @@ void StartApplication(int argc, char** argv, const char* link,
  }
 
  void StopApplication() {
@@ -664,7 +666,7 @@
 </details>
 
 `starboard/linux/shared/pleasantry.h`,
-`starboard/linux/shared/starboard_platform.gypi`, and
+`starboard/linux/shared/BUILD.gn`, and
 `starboard/linux/shared/system_get_extensions.cc` should be unchanged from the
 Exercise 1 solution.
 
@@ -710,8 +712,8 @@
 `starboard/extension/extension_test.cc`.
 
 Once you've written your test you can execute it to confirm that it passes.
-`starboard/extension/extension.gyp` configures an `extension_test` target to be
-built from our `extension_test.cc` source file. We can build that target for our
+`starboard/extension/BUILD.gn` configures an `extension_test` target to be built
+from our `extension_test.cc` source file. We can build that target for our
 platform and then run the executable to run the tests.
 
 ```
@@ -775,15 +777,9 @@
 
 **If you'd like to contribute an actual Starboard Extension to Cobalt in order to
 add some useful functionality for your platform, we encourage you to start a
-discussion with the Cobalt team before you begin coding.** To do so, please
-[file a feature request](https://issuetracker.google.com/issues/new?component=181120)
-for the extension and include the following information:
-
-*   The name of the Starboard Extension.
-*   A description of the extension.
-*   Why a Starboard Extension is the right tool, instead of some alternative.
-*   The fact that you'd like to contribute the extension (i.e., write the code)
-    rather than rely on the Cobalt team to prioritize, plan, and implement it.
+discussion with the Cobalt team before you begin coding.** To do so, please file
+a feature request for the extension
+[using this template](https://issuetracker.google.com/issues/new?component=181120&template=1820891).
 
 Please file this feature request with the appropriate priority and the Cobalt
 team will review the proposal accordingly. If the Cobalt team approves of the
diff --git a/cobalt/site/docs/development/setup-linux.md b/cobalt/site/docs/development/setup-linux.md
index 78ecf3f..0acdd64 100644
--- a/cobalt/site/docs/development/setup-linux.md
+++ b/cobalt/site/docs/development/setup-linux.md
@@ -19,7 +19,7 @@
 
     ```
     $ sudo apt update && sudo apt install -qqy --no-install-recommends \
-        pkgconf ninja-build bison yasm binutils clang libgles2-mesa-dev \
+        pkgconf ninja-build bison nasm yasm binutils clang libgles2-mesa-dev \
         mesa-common-dev libpulse-dev libavresample-dev libasound2-dev \
         libxrender-dev libxcomposite-dev libxml2-dev curl git \
         python3.8-venv libxi-dev
diff --git a/cobalt/site/docs/development/setup-rdk.md b/cobalt/site/docs/development/setup-rdk.md
new file mode 100644
index 0000000..6a16461
--- /dev/null
+++ b/cobalt/site/docs/development/setup-rdk.md
@@ -0,0 +1,8 @@
+---
+layout: doc
+title: "Set up your environment - RDK"
+---
+
+The detailed documentation for building and using RDK-V reference port is
+provided by the RDK project. Please see details in
+[starboard/contrib/RDK](starboard/contrib/RDK/)
diff --git a/cobalt/site/docs/gen/starboard/doc/building.md b/cobalt/site/docs/gen/starboard/doc/building.md
index 0281e71..99fecd9 100644
--- a/cobalt/site/docs/gen/starboard/doc/building.md
+++ b/cobalt/site/docs/gen/starboard/doc/building.md
@@ -75,9 +75,7 @@
 
    Define a subclass of
    `starboard.build.application_configuration.ApplicationConfiguration` and
-   override any desired methods. In particular, you probably at least want to
-   override the `GetDefaultTargetBuildFile()` method to point at your root
-   `.gyp` file from step 1.
+   override any desired methods.
 
 3. Register your Application in your `starboard_configuration.py` file in your
    source tree root.
diff --git a/cobalt/site/docs/reference/starboard/gn-configuration.md b/cobalt/site/docs/reference/starboard/gn-configuration.md
index 2fff0ac..a0bbde6 100644
--- a/cobalt/site/docs/reference/starboard/gn-configuration.md
+++ b/cobalt/site/docs/reference/starboard/gn-configuration.md
@@ -28,7 +28,6 @@
 | **`sb_enable_cpp17_audit`**<br><br> Enables an NPLB audit of C++17 support.<br><br>The default value is `true`. |
 | **`sb_enable_lib`**<br><br> Enables embedding Cobalt as a shared library within another app. This requires a 'lib' starboard implementation for the corresponding platform.<br><br>The default value is `false`. |
 | **`sb_enable_opus_sse`**<br><br> Enables optimizations on SSE compatible platforms.<br><br>The default value is `true`. |
-| **`sb_evergreen_compatible_enable_lite`**<br><br> Whether to adopt Evergreen Lite on the Evergreen compatible platform.<br><br>The default value is `false`. |
 | **`sb_evergreen_compatible_package`**<br><br> Whether to generate the whole package containing both Loader app and Cobalt core on the Evergreen compatible platform.<br><br>The default value is `false`. |
 | **`sb_evergreen_compatible_use_libunwind`**<br><br> Whether to use the libunwind library on Evergreen compatible platform.<br><br>The default value is `false`. |
 | **`sb_filter_based_player`**<br><br> Used to indicate that the player is filter based.<br><br>The default value is `true`. |
diff --git a/cobalt/site/docs/reference/starboard/modules/memory.md b/cobalt/site/docs/reference/starboard/modules/memory.md
index e09dbc3..2fbf7dc 100644
--- a/cobalt/site/docs/reference/starboard/modules/memory.md
+++ b/cobalt/site/docs/reference/starboard/modules/memory.md
@@ -233,19 +233,6 @@
 void SbMemoryFreeAligned(void *memory)
 ```
 
-### SbMemoryGetStackBounds ###
-
-Gets the stack bounds for the current thread.
-
-`out_high`: The highest addressable byte + 1 for the current thread. `out_low`:
-The lowest addressable byte for the current thread.
-
-#### Declaration ####
-
-```
-void SbMemoryGetStackBounds(void **out_high, void **out_low)
-```
-
 ### SbMemoryMap ###
 
 Allocates `size_bytes` worth of physical memory pages and maps them into an
@@ -345,4 +332,3 @@
 ```
 bool SbMemoryUnmap(void *virtual_address, int64_t size_bytes)
 ```
-
diff --git a/cobalt/speech/BUILD.gn b/cobalt/speech/BUILD.gn
index 199796a..3f4cc72 100644
--- a/cobalt/speech/BUILD.gn
+++ b/cobalt/speech/BUILD.gn
@@ -86,8 +86,6 @@
     "speech_synthesis_utterance.cc",
     "speech_synthesis_utterance.h",
     "speech_synthesis_voice.h",
-    "starboard_speech_recognizer.cc",
-    "starboard_speech_recognizer.h",
   ]
 
   configs += [ ":speech_config" ]
diff --git a/cobalt/speech/google_speech_service.cc b/cobalt/speech/google_speech_service.cc
index 1cb7e36..1ee9e4b 100644
--- a/cobalt/speech/google_speech_service.cc
+++ b/cobalt/speech/google_speech_service.cc
@@ -23,15 +23,16 @@
 //
 // Talk with your Google representative about how to get speech-api quota.
 
-#include <memory>
-
 #include "cobalt/speech/google_speech_service.h"
 
+#include <memory>
+
 #include "base/bind.h"
 #include "base/rand_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/language.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/network/network_module.h"
@@ -193,7 +194,7 @@
       ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
       ALLOW_THIS_IN_INITIALIZER_LIST(
           weak_this_(weak_ptr_factory_.GetWeakPtr())),
-      wrappables_task_runner_(base::MessageLoop::current()->task_runner()) {
+      wrappables_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
   thread_.StartWithOptions(
       base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
 }
diff --git a/cobalt/speech/sandbox/speech_sandbox_main.cc b/cobalt/speech/sandbox/speech_sandbox_main.cc
index 55e0481..cc67b41 100644
--- a/cobalt/speech/sandbox/speech_sandbox_main.cc
+++ b/cobalt/speech/sandbox/speech_sandbox_main.cc
@@ -15,6 +15,7 @@
 #include "base/files/file_path.h"
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/wrap_main.h"
 #include "cobalt/speech/sandbox/speech_sandbox.h"
 #include "url/gurl.h"
@@ -48,7 +49,7 @@
       base::FilePath(FILE_PATH_LITERAL("speech_sandbox_trace.json")));
 
   if (timeout != 0) {
-    base::MessageLoop::current()->task_runner()->PostDelayedTask(
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
         FROM_HERE, quit_closure, base::TimeDelta::FromSeconds(timeout));
   }
 }
diff --git a/cobalt/speech/speech_configuration.h b/cobalt/speech/speech_configuration.h
index 6d9cd90..a7a1b49 100644
--- a/cobalt/speech/speech_configuration.h
+++ b/cobalt/speech/speech_configuration.h
@@ -20,8 +20,4 @@
 
 #define SB_USE_SB_MICROPHONE 1
 
-#if SB_API_VERSION == 12 || (SB_HAS(SPEECH_RECOGNIZER) && SB_API_VERSION < 13)
-#define SB_USE_SB_SPEECH_RECOGNIZER 1
-#endif  // SB_API_VERSION == 12 || SB_HAS(SPEECH_RECOGNIZER)
-
 #endif  // COBALT_SPEECH_SPEECH_CONFIGURATION_H_
diff --git a/cobalt/speech/speech_recognition_manager.cc b/cobalt/speech/speech_recognition_manager.cc
index 218346a..6043ffc 100644
--- a/cobalt/speech/speech_recognition_manager.cc
+++ b/cobalt/speech/speech_recognition_manager.cc
@@ -15,13 +15,11 @@
 #include "cobalt/speech/speech_recognition_manager.h"
 
 #include "base/bind.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "cobalt/speech/cobalt_speech_recognizer.h"
 #include "cobalt/speech/speech_configuration.h"
 #include "cobalt/speech/speech_recognition_error.h"
 #include "cobalt/web/dom_exception.h"
-#if defined(SB_USE_SB_SPEECH_RECOGNIZER)
-#include "cobalt/speech/starboard_speech_recognizer.h"
-#endif
-#include "cobalt/speech/cobalt_speech_recognizer.h"
 
 namespace cobalt {
 namespace speech {
@@ -31,17 +29,9 @@
     const Microphone::Options& microphone_options)
     : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
       weak_this_(weak_ptr_factory_.GetWeakPtr()),
-      main_message_loop_task_runner_(
-          base::MessageLoop::current()->task_runner()),
+      main_message_loop_task_runner_(base::ThreadTaskRunnerHandle::Get()),
       event_callback_(event_callback),
       state_(kStopped) {
-#if defined(SB_USE_SB_SPEECH_RECOGNIZER)
-  if (StarboardSpeechRecognizer::IsSupported()) {
-    recognizer_.reset(new StarboardSpeechRecognizer(base::Bind(
-        &SpeechRecognitionManager::OnEventAvailable, base::Unretained(this))));
-    return;
-  }
-#endif  // defined(SB_USE_SB_SPEECH_RECOGNIZER)
   if (GoogleSpeechService::GetSpeechAPIKey()) {
     recognizer_.reset(new CobaltSpeechRecognizer(
         network_module, microphone_options,
diff --git a/cobalt/speech/starboard_speech_recognizer.cc b/cobalt/speech/starboard_speech_recognizer.cc
deleted file mode 100644
index cc37dcc..0000000
--- a/cobalt/speech/starboard_speech_recognizer.cc
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright 2017 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "cobalt/speech/starboard_speech_recognizer.h"
-
-#if defined(SB_USE_SB_SPEECH_RECOGNIZER)
-
-#include <utility>
-
-#include "cobalt/base/tokens.h"
-#include "cobalt/speech/speech_recognition_error.h"
-#include "cobalt/speech/speech_recognition_event.h"
-#include "starboard/common/log.h"
-#include "starboard/types.h"
-
-namespace cobalt {
-namespace speech {
-
-// static
-bool StarboardSpeechRecognizer::IsSupported() {
-#if SB_API_VERSION >= 13
-  return false;
-#else
-  return SbSpeechRecognizerIsSupported();
-#endif
-}
-
-// static
-void StarboardSpeechRecognizer::OnSpeechDetected(void* context, bool detected) {
-  StarboardSpeechRecognizer* recognizer =
-      static_cast<StarboardSpeechRecognizer*>(context);
-  recognizer->message_loop_->task_runner()->PostTask(
-      FROM_HERE,
-      base::Bind(&StarboardSpeechRecognizer::OnRecognizerSpeechDetected,
-                 recognizer->weak_factory_.GetWeakPtr(), detected));
-}
-
-// static
-void StarboardSpeechRecognizer::OnError(void* context,
-                                        SbSpeechRecognizerError error) {
-  StarboardSpeechRecognizer* recognizer =
-      static_cast<StarboardSpeechRecognizer*>(context);
-  recognizer->message_loop_->task_runner()->PostTask(
-      FROM_HERE, base::Bind(&StarboardSpeechRecognizer::OnRecognizerError,
-                            recognizer->weak_factory_.GetWeakPtr(), error));
-}
-
-// static
-void StarboardSpeechRecognizer::OnResults(void* context,
-                                          SbSpeechResult* results,
-                                          int results_size, bool is_final) {
-  StarboardSpeechRecognizer* recognizer =
-      static_cast<StarboardSpeechRecognizer*>(context);
-
-  std::vector<SpeechRecognitionAlternative::Data> results_copy;
-  results_copy.reserve(results_size);
-  for (int i = 0; i < results_size; ++i) {
-    results_copy.emplace_back(SpeechRecognitionAlternative::Data{
-        results[i].transcript, results[i].confidence});
-  }
-
-  recognizer->message_loop_->task_runner()->PostTask(
-      FROM_HERE, base::BindOnce(&StarboardSpeechRecognizer::OnRecognizerResults,
-                                recognizer->weak_factory_.GetWeakPtr(),
-                                std::move(results_copy), is_final));
-}
-
-StarboardSpeechRecognizer::StarboardSpeechRecognizer(
-    const EventCallback& event_callback)
-    : SpeechRecognizer(event_callback),
-      message_loop_(base::MessageLoop::current()),
-      weak_factory_(this) {
-  SbSpeechRecognizerHandler handler = {&OnSpeechDetected, &OnError, &OnResults,
-                                       this};
-  speech_recognizer_ = SbSpeechRecognizerCreate(&handler);
-
-  if (!SbSpeechRecognizerIsValid(speech_recognizer_)) {
-    scoped_refptr<web::Event> error_event(new SpeechRecognitionError(
-        kSpeechRecognitionErrorCodeServiceNotAllowed, ""));
-    RunEventCallback(error_event);
-  }
-}
-
-StarboardSpeechRecognizer::~StarboardSpeechRecognizer() {
-  if (SbSpeechRecognizerIsValid(speech_recognizer_)) {
-    SbSpeechRecognizerDestroy(speech_recognizer_);
-  }
-}
-
-void StarboardSpeechRecognizer::Start(const SpeechRecognitionConfig& config) {
-  SB_DCHECK(config.max_alternatives < INT_MAX);
-  SbSpeechConfiguration configuration = {
-      config.continuous, config.interim_results,
-      static_cast<int>(config.max_alternatives)};
-  if (SbSpeechRecognizerIsValid(speech_recognizer_)) {
-    SbSpeechRecognizerStart(speech_recognizer_, &configuration);
-  }
-}
-
-void StarboardSpeechRecognizer::Stop() {
-  if (SbSpeechRecognizerIsValid(speech_recognizer_)) {
-    SbSpeechRecognizerStop(speech_recognizer_);
-  }
-  // Clear the final results.
-  final_results_.clear();
-}
-
-void StarboardSpeechRecognizer::OnRecognizerSpeechDetected(bool detected) {
-  scoped_refptr<web::Event> event(new web::Event(
-      detected ? base::Tokens::soundstart() : base::Tokens::soundend()));
-  RunEventCallback(event);
-}
-
-void StarboardSpeechRecognizer::OnRecognizerError(
-    SbSpeechRecognizerError error) {
-  scoped_refptr<web::Event> error_event;
-  switch (error) {
-    case kSbNoSpeechError:
-      error_event =
-          new SpeechRecognitionError(kSpeechRecognitionErrorCodeNoSpeech, "");
-      break;
-    case kSbAborted:
-      error_event =
-          new SpeechRecognitionError(kSpeechRecognitionErrorCodeAborted, "");
-      break;
-    case kSbAudioCaptureError:
-      error_event = new SpeechRecognitionError(
-          kSpeechRecognitionErrorCodeAudioCapture, "");
-      break;
-    case kSbNetworkError:
-      error_event =
-          new SpeechRecognitionError(kSpeechRecognitionErrorCodeNetwork, "");
-      break;
-    case kSbNotAllowed:
-      error_event =
-          new SpeechRecognitionError(kSpeechRecognitionErrorCodeNotAllowed, "");
-      break;
-    case kSbServiceNotAllowed:
-      error_event = new SpeechRecognitionError(
-          kSpeechRecognitionErrorCodeServiceNotAllowed, "");
-      break;
-    case kSbBadGrammar:
-      error_event =
-          new SpeechRecognitionError(kSpeechRecognitionErrorCodeBadGrammar, "");
-      break;
-    case kSbLanguageNotSupported:
-      error_event = new SpeechRecognitionError(
-          kSpeechRecognitionErrorCodeLanguageNotSupported, "");
-      break;
-  }
-  SB_DCHECK(error_event);
-  RunEventCallback(error_event);
-}
-
-void StarboardSpeechRecognizer::OnRecognizerResults(
-    std::vector<SpeechRecognitionAlternative::Data>&& results, bool is_final) {
-  SpeechRecognitionResultList::SpeechRecognitionResults recognition_results;
-  SpeechRecognitionResult::SpeechRecognitionAlternatives alternatives;
-  for (auto& result : results) {
-    scoped_refptr<SpeechRecognitionAlternative> alternative(
-        new SpeechRecognitionAlternative(std::move(result)));
-    alternatives.push_back(alternative);
-  }
-  scoped_refptr<SpeechRecognitionResult> recognition_result(
-      new SpeechRecognitionResult(alternatives, is_final));
-  recognition_results.push_back(recognition_result);
-
-  // Gather all results for the SpeechRecognitionEvent, including all final
-  // results we've previously accumulated, plus all (final or not) results that
-  // we just received.
-  SpeechRecognitionResults success_results;
-  size_t total_size = final_results_.size() + recognition_results.size();
-  success_results.reserve(total_size);
-  success_results = final_results_;
-  success_results.insert(success_results.end(), recognition_results.begin(),
-                         recognition_results.end());
-
-  size_t result_index = final_results_.size();
-  // Update final results list with final results that we just received, so we
-  // have them for the next event.
-  for (size_t i = 0; i < recognition_results.size(); ++i) {
-    if (recognition_results[i]->is_final()) {
-      final_results_.push_back(recognition_results[i]);
-    }
-  }
-
-  scoped_refptr<SpeechRecognitionResultList> recognition_list(
-      new SpeechRecognitionResultList(success_results));
-  scoped_refptr<SpeechRecognitionEvent> recognition_event(
-      new SpeechRecognitionEvent(SpeechRecognitionEvent::kResult,
-                                 static_cast<uint32>(result_index),
-                                 recognition_list));
-  RunEventCallback(recognition_event);
-}
-
-}  // namespace speech
-}  // namespace cobalt
-
-#endif  // defined(SB_USE_SB_SPEECH_RECOGNIZER)
diff --git a/cobalt/speech/starboard_speech_recognizer.h b/cobalt/speech/starboard_speech_recognizer.h
deleted file mode 100644
index 80b4ca0..0000000
--- a/cobalt/speech/starboard_speech_recognizer.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2017 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef COBALT_SPEECH_STARBOARD_SPEECH_RECOGNIZER_H_
-#define COBALT_SPEECH_STARBOARD_SPEECH_RECOGNIZER_H_
-
-#include <vector>
-
-#include "base/message_loop/message_loop.h"
-#include "cobalt/speech/speech_configuration.h"
-#include "cobalt/speech/speech_recognition_result_list.h"
-#include "cobalt/speech/speech_recognizer.h"
-
-#if defined(SB_USE_SB_SPEECH_RECOGNIZER)
-
-#include "starboard/speech_recognizer.h"
-
-namespace cobalt {
-namespace speech {
-
-// Controls |SbSpeechRecognizer| to access the device's recognition service and
-// receives the speech recognition results from there.
-class StarboardSpeechRecognizer : public SpeechRecognizer {
- public:
-  typedef SpeechRecognitionResultList::SpeechRecognitionResults
-      SpeechRecognitionResults;
-
-  explicit StarboardSpeechRecognizer(const EventCallback& event_callback);
-  ~StarboardSpeechRecognizer();
-
-  static bool IsSupported();
-
-  void Start(const SpeechRecognitionConfig& config) override;
-  void Stop() override;
-
- private:
-  static void OnSpeechDetected(void* context, bool detected);
-  void OnRecognizerSpeechDetected(bool detected);
-  static void OnError(void* context, SbSpeechRecognizerError error);
-  void OnRecognizerError(SbSpeechRecognizerError error);
-  static void OnResults(void* context, SbSpeechResult* results,
-                        int results_size, bool is_final);
-  void OnRecognizerResults(
-      std::vector<SpeechRecognitionAlternative::Data>&& results, bool is_final);
-
-  SbSpeechRecognizer speech_recognizer_;
-
-  // Used for accumulating final results.
-  SpeechRecognitionResults final_results_;
-
-  // Track the message loop that created this object so that our callbacks can
-  // post back to it.
-  base::MessageLoop* message_loop_;
-
-  // We have our callbacks post events back to us using weak pointers, in case
-  // this object is destroyed while those tasks are in flight.  Note that it
-  // is impossible for the callbacks to be called after this object is
-  // destroyed, since SbSpeechRecognizerDestroy() ensures this.
-  base::WeakPtrFactory<StarboardSpeechRecognizer> weak_factory_;
-};
-
-}  // namespace speech
-}  // namespace cobalt
-
-#endif  // defined(SB_USE_SB_SPEECH_RECOGNIZER)
-
-#endif  // COBALT_SPEECH_STARBOARD_SPEECH_RECOGNIZER_H_
diff --git a/cobalt/system_window/system_window.cc b/cobalt/system_window/system_window.cc
index f2edc9b..bedd522 100644
--- a/cobalt/system_window/system_window.cc
+++ b/cobalt/system_window/system_window.cc
@@ -121,15 +121,7 @@
 
   // Use the current time unless it was overridden.
   SbTimeMonotonic timestamp = 0;
-#if SB_API_VERSION >= 13
   timestamp = event->timestamp;
-#else   // SB_API_VERSION >= 13
-  bool use_input_timestamp =
-      SbSystemHasCapability(kSbSystemCapabilitySetsInputTimestamp);
-  if (use_input_timestamp) {
-    timestamp = data.timestamp;
-  }
-#endif  // SB_API_VERSION >= 13
   if (timestamp == 0) {
     timestamp = SbTimeGetMonotonicNow();
   }
diff --git a/cobalt/tools/automated_testing/c_val_names.py b/cobalt/tools/automated_testing/c_val_names.py
index a394cad..57aad1c 100644
--- a/cobalt/tools/automated_testing/c_val_names.py
+++ b/cobalt/tools/automated_testing/c_val_names.py
@@ -1,4 +1,17 @@
 """Provides names for Cobalt CVals that are used by webdriver-related tests."""
+# Copyright 2018 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 
 from __future__ import absolute_import
 from __future__ import division
@@ -62,7 +75,7 @@
 
 
 def event_value_dictionary(event_type):
-  return 'Event.MainWebModule.{}.ValueDictionary'.format(event_type)
+  return f'Event.MainWebModule.{event_type}.ValueDictionary'
 
 
 def is_render_tree_generation_pending():
diff --git a/cobalt/tools/automated_testing/cobalt_runner.py b/cobalt/tools/automated_testing/cobalt_runner.py
index 3366638..82db23a 100644
--- a/cobalt/tools/automated_testing/cobalt_runner.py
+++ b/cobalt/tools/automated_testing/cobalt_runner.py
@@ -98,7 +98,9 @@
                log_file=None,
                target_params=None,
                success_message=None,
-               log_handler=None):
+               log_handler=None,
+               poll_until_wait_seconds=POLL_UNTIL_WAIT_SECONDS,
+               **kwargs):
     """CobaltRunner constructor.
 
     Args:
@@ -110,6 +112,8 @@
         with.
       success_message:  Optional success message to be printed on successful
         exit.
+      pull_until_wait_seconds:  Seconds to wait while polling for an event.
+      **kwargs:  Additional parameters to be passed to the launcher.
     """
 
     # Tracks if test execution started successfully
@@ -129,6 +133,8 @@
 
     self.launcher_params = launcher_params
     self.log_handler = log_handler
+    self.poll_until_wait_seconds = poll_until_wait_seconds
+    self.kwargs = kwargs
 
     if log_file:
       self.log_file = open(log_file, encoding='utf-8')  # pylint: disable=consider-using-with
@@ -265,7 +271,8 @@
         out_directory=self.launcher_params.out_directory,
         loader_platform=self.launcher_params.loader_platform,
         loader_config=self.launcher_params.loader_config,
-        loader_out_directory=self.launcher_params.loader_out_directory)
+        loader_out_directory=self.launcher_params.loader_out_directory,
+        **self.kwargs)
 
     self.runner_thread = threading.Thread(target=self._RunLauncher)
     self.runner_thread.start()
@@ -475,7 +482,7 @@
     """
     start_time = time.time()
     while (not self.FindElements(css_selector) and
-           (time.time() - start_time < POLL_UNTIL_WAIT_SECONDS)):
+           (time.time() - start_time < self.poll_until_wait_seconds)):
       time.sleep(0.5)
     if expected_num:
       self.FindElements(css_selector, expected_num)
diff --git a/cobalt/tools/automated_testing/webdriver_utils.py b/cobalt/tools/automated_testing/webdriver_utils.py
index 14ddf38..b799cc5 100644
--- a/cobalt/tools/automated_testing/webdriver_utils.py
+++ b/cobalt/tools/automated_testing/webdriver_utils.py
@@ -1,4 +1,17 @@
 """This module provides webdriver-based utility functions."""
+# Copyright 2018 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 
 from __future__ import absolute_import
 from __future__ import division
@@ -25,7 +38,7 @@
   else:
     module_path = 'selenium'
   # As of this writing, Google uses selenium 3.0.0b2 internally, so
-  # thats what we will target here as well.
+  # that's what we will target here as well.
   try:
     module = importlib.import_module(module_path)
     if submodule is None:
@@ -33,9 +46,8 @@
       if not module.__version__.startswith('3.'):
         raise ImportError('Not version 3.x.x')
   except ImportError:
-    sys.stderr.write('Could not import {}\n'
+    sys.stderr.write(f'Could not import {module_path}\n'
                      'Please install selenium >= 3.0.0b2.\n'
-                     'Commonly: \"sudo pip install \'selenium>=3.0.0b2\'\"\n'
-                     .format(module_path))
+                     'Commonly: \"sudo pip install \'selenium>=3.0.0b2\'\"\n')
     sys.exit(1)
   return module
diff --git a/cobalt/tools/buildbot/run_black_box_tests.py b/cobalt/tools/buildbot/run_black_box_tests.py
new file mode 100644
index 0000000..439cd2b
--- /dev/null
+++ b/cobalt/tools/buildbot/run_black_box_tests.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+#
+# Copyright 2023 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Compatibility layer for buildbot."""
+
+import sys
+from internal.cobalt.tools.buildbot.run_black_box_tests import main
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/cobalt/tools/buildbot/run_evergreen_tests.py b/cobalt/tools/buildbot/run_evergreen_tests.py
new file mode 100644
index 0000000..9707a31
--- /dev/null
+++ b/cobalt/tools/buildbot/run_evergreen_tests.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+#
+# Copyright 2023 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Compatibility layer for buildbot."""
+
+import sys
+from internal.cobalt.tools.buildbot.run_evergreen_tests import main
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/cobalt/tools/buildbot/run_py_tests.py b/cobalt/tools/buildbot/run_py_tests.py
new file mode 100644
index 0000000..bde3767
--- /dev/null
+++ b/cobalt/tools/buildbot/run_py_tests.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+#
+# Copyright 2023 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Compatibility layer for buildbot."""
+
+from internal.cobalt.tools.buildbot.run_py_tests import main
+
+if __name__ == '__main__':
+  main()
diff --git a/cobalt/tools/buildbot/run_unit_tests.py b/cobalt/tools/buildbot/run_unit_tests.py
new file mode 100644
index 0000000..d09fa41
--- /dev/null
+++ b/cobalt/tools/buildbot/run_unit_tests.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+#
+# Copyright 2023 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Compatibility layer for buildbot."""
+
+import logging
+import sys
+from internal.cobalt.tools.buildbot.run_unit_tests import main
+
+if __name__ == '__main__':
+  try:
+    sys.exit(int(main()))
+  except Exception as err:  # pylint: disable=broad-except
+    logging.exception('Exception happened during main: %s', str(err))
+    sys.exit(1)
diff --git a/cobalt/tools/collectd/cobalt.py b/cobalt/tools/collectd/cobalt.py
index dd38f9e..b0c7343 100644
--- a/cobalt/tools/collectd/cobalt.py
+++ b/cobalt/tools/collectd/cobalt.py
@@ -11,11 +11,12 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+# pylint: disable=redefined-outer-name,redefined-builtin,broad-except,unused-argument
+'''Collectd module for Cobalt'''
 
 import json
 import time
 import argparse
-import random
 import websocket
 
 try:
@@ -27,14 +28,13 @@
 
 
 def config(conf):
-  print('config called {!r}'.format(conf))
+  print(f'config called {conf!r}')
   conf_['collectd_conf'] = conf
-  collectd.info('config called {!r}'.format(conf))
+  collectd.info(f'config called {conf!r}')
 
 
 def reconnect():
-  ws = websocket.create_connection('ws://{}:{}'.format(conf_['host'],
-                                                       conf_['port']))
+  ws = websocket.create_connection(f"ws://{conf_['host']}:{conf_['port']}")
   ws.settimeout(3)
   setattr(ws, 'message_id', 1)
   conf_['ws'] = ws
@@ -43,7 +43,7 @@
 def init():
   conf = conf_['collectd_conf']
   for child in conf.children:
-    collectd.info('conf.child key {} values {}'.format(child.key, child.values))
+    collectd.info(f'conf.child key {child.key} values {child.values}')
     if child.key.lower() == 'address':
       host, port = child.values[0].split(':')
       conf_['host'] = host
@@ -67,7 +67,7 @@
       if 'result' in parsed_message and parsed_message['id'] == result_id:
         matching_result = parsed_message
         break
-    except:
+    except Exception:
       break
   return (matching_result, messages)
 
@@ -101,13 +101,13 @@
     # collectd.warning('key {} value: {}'.format(key, value))
     report_value('cobalt', collectd_type, key, value)
   except TypeError:
-    collectd.warning('Failed to collect: {} {}'.format(key, val))
+    collectd.warning(f'Failed to collect: {key} {val}')
 
 
 # Tuple of a Cobalt Cval, and data type
-# Collectd frontends like CGP group values to graphs by the reported data type. The types here are
-# simple gauges grouped by their rough estimated orders of magnitude, so that all plots are still
-# somewhat discernible.
+# Collectd frontends like CGP group values to graphs by the reported data type.
+# The types here are simple gauges grouped by their rough estimated orders of
+# magnitude, so that all plots are still somewhat discernible.
 tracked_stats = [('Cobalt.Lifetime', 'lifetime'),
                  ('Count.DOM.ActiveJavaScriptEvents', 'gauge'),
                  ('Count.DOM.Attrs', 'gauge_10k'),
@@ -152,24 +152,28 @@
   collectd.register_init(init)
   collectd.register_read(read)
 
-
 # Debugcode to verify plugin connection
 if __name__ == '__main__':
+
   class Bunch:
-      def __init__(self, **kwds):
-          self.__dict__.update(kwds)
-  def debugprint(*args,**kwargs):
-    print('{!r} {!r}'.format(args,kwargs))
+
+    def __init__(self, **kwds):
+      self.__dict__.update(kwds)
+
+  def debugprint(*args, **kwargs):
+    print(f'{args!r} {kwargs!r}')
+
   def debugvalues(**kwargs):
     debugprint(**kwargs)
-    return Bunch(dispatch=debugprint,**kwargs)
-  collectd = Bunch(info=debugprint,warning=debugprint,Values=debugvalues)
+    return Bunch(dispatch=debugprint, **kwargs)
+
+  collectd = Bunch(info=debugprint, warning=debugprint, Values=debugvalues)
   parser = argparse.ArgumentParser()
   parser.add_argument('--host', default='localhost')
   parser.add_argument('--port', type=int, default=9222)
   args = parser.parse_args()
-  conf = Bunch(children=[Bunch( key='address',
-    values=['{}:{}'.format(args.host,args.port)])])
-  conf_['collectd_conf']  = conf
+  conf = Bunch(
+      children=[Bunch(key='address', values=[f'{args.host}:{args.port}'])])
+  conf_['collectd_conf'] = conf
   init()
   cobalt_read()
diff --git a/cobalt/tools/verify-trace-members/verify-trace-members.py b/cobalt/tools/verify-trace-members/verify-trace-members.py
index 6692482..3643891 100755
--- a/cobalt/tools/verify-trace-members/verify-trace-members.py
+++ b/cobalt/tools/verify-trace-members/verify-trace-members.py
@@ -13,6 +13,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+# pylint: disable=invalid-name
 """A wrapper around the native-trace-members binary
 
 This script is responsible for:
@@ -63,7 +64,7 @@
     break
 
 if not found_dir:
-  logging.error('At least one of {} must exist.'.format(LINUX_OUT_DIRS))
+  logging.error('At least one of %s must exist.', LINUX_OUT_DIRS)
   sys.exit(1)
 
 
@@ -72,9 +73,10 @@
 
   to build the target "all".
   """
-  p = subprocess.Popen(['ninja', '-t', 'commands', 'all'],
-                       stdout=subprocess.PIPE,
-                       stderr=subprocess.PIPE)
+  p = subprocess.Popen(  # pylint: disable=consider-using-with
+      ['ninja', '-t', 'commands', 'all'],
+      stdout=subprocess.PIPE,
+      stderr=subprocess.PIPE)
   stdout, stderr = p.communicate()
   assert len(stderr) == 0
   assert p.returncode == 0
@@ -194,12 +196,14 @@
     # TODO: Accept list of files as arguments.
     if not source_file.startswith('../../cobalt/dom/'):
       continue
-    p = subprocess.Popen(
-        tool_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    p = subprocess.Popen(  # pylint: disable=consider-using-with
+        tool_command,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE)
     stdout, stderr = p.communicate()
     if len(stderr.decode()) != 0 or p.returncode != 0:
       logging.error(stderr)
-      exit(1)
+      exit(1)  # pylint: disable=consider-using-sys-exit
     for line in stdout.decode().splitlines():
       # Load and then dump to JSON to standardize w.r.t. to whitespace/order, so
       # we can use the stringified object itself as a key into a set.
@@ -239,24 +243,22 @@
     if message_type == 'needsTraceMembersDeclaration':
       parent_class_friendly = suggestion['parentClassFriendly']
       field_name = suggestion['fieldName']
-      print('{} needs to declare TraceMembers because of field {}'.format(
-          parent_class_friendly, field_name))
+      print(f'{parent_class_friendly} needs to declare TraceMembers because '
+            f'of field {field_name}')
       print('  void TraceMembers(script::Tracer* tracer) override;')
     elif message_type == 'needsTracerTraceField':
       parent_class_friendly = suggestion['parentClassFriendly']
       field_name = suggestion['fieldName']
-      print('{} needs to trace field {}'.format(parent_class_friendly,
-                                                field_name))
-      print('  tracer->Trace({});'.format(field_name))
+      print(f'{parent_class_friendly} needs to trace field {field_name}')
+      print(f'  tracer->Trace({field_name});')
     elif message_type == 'needsCallBaseTraceMembers':
       parent_class_friendly = suggestion['parentClassFriendly']
       base_names = suggestion['baseNames']
-      print(
-          '{} needs to call base class TraceMembers in its TraceMembers'.format(
-              parent_class_friendly))
+      print(f'{parent_class_friendly} needs to call base class TraceMembers '
+            'in its TraceMembers')
       print('Something like (this is probably over-qualified):')
       for base_name in base_names:
-        print('  {}::TraceMembers(tracer);'.format(base_name))
+        print(f'  {base_name}::TraceMembers(tracer);')
     else:
       assert False
 
diff --git a/cobalt/tools/webdriver_benchmark_config.py b/cobalt/tools/webdriver_benchmark_config.py
deleted file mode 100644
index f551f35..0000000
--- a/cobalt/tools/webdriver_benchmark_config.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Copyright 2017 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Class for configuring Webdriver Benchmarks."""
-
-# WEBDRIVER SCRIPT CONFIGURATION PARAMETERS
-MINIMAL_SIZE = 'minimal'
-REDUCED_SIZE = 'reduced'
-STANDARD_SIZE = 'standard'
-SAMPLE_SIZES = [MINIMAL_SIZE, REDUCED_SIZE, STANDARD_SIZE]
-
-DISABLE_VIDEOS = '--disable_videos'
-
-# COBALT COMMAND LINE PARAMETERS
-DISABLE_SPLASH_SCREEN_ON_RELOADS = '--disable_splash_screen_on_reloads'
diff --git a/cobalt/trace_event/scoped_trace_to_file.cc b/cobalt/trace_event/scoped_trace_to_file.cc
index ffbe948..453b013 100644
--- a/cobalt/trace_event/scoped_trace_to_file.cc
+++ b/cobalt/trace_event/scoped_trace_to_file.cc
@@ -12,15 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <memory>
-
 #include "cobalt/trace_event/scoped_trace_to_file.h"
 
+#include <memory>
+
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
 #include "base/path_service.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/cobalt_paths.h"
 #include "cobalt/trace_event/json_file_outputter.h"
@@ -71,7 +72,7 @@
 
 void TraceToFileForDuration(const base::FilePath& output_path_relative_to_logs,
                             const base::TimeDelta& duration) {
-  base::MessageLoop::current()->task_runner()->PostDelayedTask(
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE,
       base::Bind(&EndTimedTrace,
                  base::Passed(base::WrapUnique(
diff --git a/cobalt/ui_navigation/interface.cc b/cobalt/ui_navigation/interface.cc
index da1f51f..2dd0fb0 100644
--- a/cobalt/ui_navigation/interface.cc
+++ b/cobalt/ui_navigation/interface.cc
@@ -157,13 +157,8 @@
     interface.set_item_container_item = sb_ui_interface.set_item_container_item;
     interface.set_item_content_offset = sb_ui_interface.set_item_content_offset;
     interface.get_item_content_offset = sb_ui_interface.get_item_content_offset;
-#if SB_API_VERSION >= 13
     interface.set_item_focus_duration = sb_ui_interface.set_item_focus_duration;
     interface.do_batch_update = sb_ui_interface.do_batch_update;
-#else
-    interface.set_item_focus_duration = &SetItemFocusDuration;
-    interface.do_batch_update = &DoBatchUpdate;
-#endif
     return interface;
   }
 
diff --git a/cobalt/ui_navigation/nav_item.cc b/cobalt/ui_navigation/nav_item.cc
index 21874ac..d946463 100644
--- a/cobalt/ui_navigation/nav_item.cc
+++ b/cobalt/ui_navigation/nav_item.cc
@@ -186,11 +186,9 @@
 void NavItem::UnfocusAll() {
   starboard::ScopedSpinLock lock(&g_pending_updates_lock);
   g_pending_focus = kNativeItemInvalid;
-#if SB_API_VERSION >= 13
   g_pending_updates->emplace_back(
       kNativeItemInvalid,
       base::Bind(GetInterface().set_focus, kNativeItemInvalid));
-#endif
 }
 
 void NavItem::SetEnabled(bool enabled) {
diff --git a/cobalt/ui_navigation/scroll_engine/BUILD.gn b/cobalt/ui_navigation/scroll_engine/BUILD.gn
index 0c3cd62..980b3fd 100644
--- a/cobalt/ui_navigation/scroll_engine/BUILD.gn
+++ b/cobalt/ui_navigation/scroll_engine/BUILD.gn
@@ -28,3 +28,15 @@
     "//cobalt/ui_navigation",
   ]
 }
+
+target(gtest_target_type, "scroll_engine_tests") {
+  testonly = true
+  sources = [ "free_scrolling_nav_item_test.cc" ]
+  deps = [
+    ":scroll_engine",
+    "//cobalt/base",
+    "//cobalt/test:run_all_unittests",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
diff --git a/cobalt/ui_navigation/scroll_engine/free_scrolling_nav_item.cc b/cobalt/ui_navigation/scroll_engine/free_scrolling_nav_item.cc
index 29eb3ae..dd03104 100644
--- a/cobalt/ui_navigation/scroll_engine/free_scrolling_nav_item.cc
+++ b/cobalt/ui_navigation/scroll_engine/free_scrolling_nav_item.cc
@@ -31,7 +31,7 @@
       initial_offset_(initial_offset),
       target_offset_(target_offset),
       animation_duration_(animation_duration) {
-  initial_change_ = base::Time::Now();
+  initial_change_ = clock_->Now();
 
   // Constants are derived from the ease-in-out curve definition here:
   // https://www.w3.org/TR/2023/CRD-css-easing-1-20230213/#typedef-cubic-bezier-easing-function
@@ -45,7 +45,7 @@
     return 1.f;
   }
 
-  auto now = base::Time::Now();
+  auto now = clock_->Now();
   auto time_delta = now - initial_change_;
   auto fraction_of_progress =
       time_delta.InMillisecondsF() / animation_duration_.InMillisecondsF();
diff --git a/cobalt/ui_navigation/scroll_engine/free_scrolling_nav_item.h b/cobalt/ui_navigation/scroll_engine/free_scrolling_nav_item.h
index cbd8592..05005e0 100644
--- a/cobalt/ui_navigation/scroll_engine/free_scrolling_nav_item.h
+++ b/cobalt/ui_navigation/scroll_engine/free_scrolling_nav_item.h
@@ -15,6 +15,9 @@
 #ifndef COBALT_UI_NAVIGATION_SCROLL_ENGINE_FREE_SCROLLING_NAV_ITEM_H_
 #define COBALT_UI_NAVIGATION_SCROLL_ENGINE_FREE_SCROLLING_NAV_ITEM_H_
 
+#include "base/time/clock.h"
+#include "base/time/default_clock.h"
+#include "base/time/time.h"
 #include "cobalt/cssom/timing_function.h"
 #include "cobalt/math/vector2d_f.h"
 #include "cobalt/ui_navigation/nav_item.h"
@@ -37,6 +40,11 @@
   bool AnimationIsComplete();
   math::Vector2dF GetCurrentOffset();
 
+  void set_clock_for_testing(const base::Clock* clock) { clock_ = clock; }
+  void set_initial_change_for_testing(const base::Time initial_change) {
+    initial_change_ = initial_change;
+  }
+
  private:
   scoped_refptr<NavItem> nav_item_;
   math::Vector2dF initial_offset_;
@@ -44,6 +52,7 @@
   scoped_refptr<cssom::CubicBezierTimingFunction> animation_function_;
   base::TimeDelta animation_duration_;
   base::Time initial_change_;
+  const base::Clock* clock_ = base::DefaultClock::GetInstance();
 };
 
 }  // namespace scroll_engine
diff --git a/cobalt/ui_navigation/scroll_engine/free_scrolling_nav_item_test.cc b/cobalt/ui_navigation/scroll_engine/free_scrolling_nav_item_test.cc
new file mode 100644
index 0000000..f405a2d
--- /dev/null
+++ b/cobalt/ui_navigation/scroll_engine/free_scrolling_nav_item_test.cc
@@ -0,0 +1,80 @@
+// Copyright 2023 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/ui_navigation/scroll_engine/free_scrolling_nav_item.h"
+
+#include "base/test/simple_test_clock.h"
+#include "base/time/time.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+
+namespace cobalt {
+namespace ui_navigation {
+namespace scroll_engine {
+
+namespace {
+
+constexpr base::TimeDelta kTestDuration = base::TimeDelta::FromSeconds(5);
+
+class FreeScrollingNavItemTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    free_scrolling_nav_item.set_clock_for_testing(&test_clock);
+    free_scrolling_nav_item.set_initial_change_for_testing(test_clock.Now());
+  }
+
+  scoped_refptr<NavItem> mock_nav_item_;
+  math::Vector2dF initial_offset_{0.f, 0.f};
+  math::Vector2dF target_offset_{10.f, 10.f};
+  base::TimeDelta animation_duration_{kTestDuration};
+  float animation_slope_{0.5f};
+  base::SimpleTestClock test_clock;
+  FreeScrollingNavItem free_scrolling_nav_item{
+      mock_nav_item_, initial_offset_, target_offset_, animation_duration_,
+      animation_slope_};
+};
+
+}  // namespace
+
+TEST_F(FreeScrollingNavItemTest, GetFractionOfCurrentProgressTest) {
+  EXPECT_EQ(free_scrolling_nav_item.GetFractionOfCurrentProgress(), 0.f);
+  test_clock.Advance(kTestDuration / 2);
+  EXPECT_EQ(free_scrolling_nav_item.GetFractionOfCurrentProgress(), 0.5f);
+  test_clock.Advance(kTestDuration / 2);
+  EXPECT_EQ(free_scrolling_nav_item.GetFractionOfCurrentProgress(), 1.f);
+  test_clock.Advance(kTestDuration / 2);
+  EXPECT_EQ(free_scrolling_nav_item.GetFractionOfCurrentProgress(), 1.f);
+}
+
+TEST_F(FreeScrollingNavItemTest, AnimationIsCompleteTest) {
+  EXPECT_FALSE(free_scrolling_nav_item.AnimationIsComplete());
+  test_clock.Advance(kTestDuration / 2);
+  EXPECT_FALSE(free_scrolling_nav_item.AnimationIsComplete());
+  test_clock.Advance(kTestDuration / 2);
+  EXPECT_TRUE(free_scrolling_nav_item.AnimationIsComplete());
+}
+
+TEST_F(FreeScrollingNavItemTest, GetCurrentOffsetTest) {
+  EXPECT_EQ(free_scrolling_nav_item.GetCurrentOffset(), initial_offset_);
+  test_clock.Advance(kTestDuration);
+  EXPECT_EQ(free_scrolling_nav_item.GetCurrentOffset(), target_offset_);
+  test_clock.Advance(kTestDuration);
+  EXPECT_EQ(free_scrolling_nav_item.GetCurrentOffset(), target_offset_);
+}
+
+}  // namespace scroll_engine
+}  // namespace ui_navigation
+}  // namespace cobalt
diff --git a/cobalt/updater/configurator.cc b/cobalt/updater/configurator.cc
index 027a68a..4fbcd1b 100644
--- a/cobalt/updater/configurator.cc
+++ b/cobalt/updater/configurator.cc
@@ -22,9 +22,12 @@
 #include "components/update_client/patcher.h"
 #include "components/update_client/protocol_handler.h"
 #include "components/update_client/unzipper.h"
+#include "starboard/common/system_property.h"
 #include "starboard/system.h"
 #include "url/gurl.h"
 
+using starboard::kSystemPropertyMaxLength;
+
 namespace {
 
 // Default time constants.
@@ -35,7 +38,6 @@
 const char kOmahaCobaltAppID[] = "{6D4E53F3-CC64-4CB8-B6BD-AB0B8F300E1C}";
 
 std::string GetDeviceProperty(SbSystemPropertyId id) {
-  const size_t kSystemPropertyMaxLength = 1024;
   char value[kSystemPropertyMaxLength];
   bool result;
   result = SbSystemGetProperty(id, value, kSystemPropertyMaxLength);
diff --git a/cobalt/updater/network_fetcher.cc b/cobalt/updater/network_fetcher.cc
index c70b636..2f0019e 100644
--- a/cobalt/updater/network_fetcher.cc
+++ b/cobalt/updater/network_fetcher.cc
@@ -189,6 +189,7 @@
 
   url_fetcher_->SetRequestContext(
       network_module_->url_request_context_getter().get());
+  network_module_->AddClientHintHeaders(*url_fetcher_);
 
   // Request mode is kCORSModeOmitCredentials.
   const uint32 kDisableCookiesAndCacheLoadFlags =
diff --git a/cobalt/updater/one_app_only_sandbox.cc b/cobalt/updater/one_app_only_sandbox.cc
index 33e6478..0ded6a7 100644
--- a/cobalt/updater/one_app_only_sandbox.cc
+++ b/cobalt/updater/one_app_only_sandbox.cc
@@ -92,20 +92,10 @@
     return;
   }
   LOG(INFO) << "Starting application.";
-#if SB_API_VERSION >= 13
   DCHECK(!g_application);
   g_application = new cobalt::browser::Application(
       quit_closure, false /*not_preload*/, timestamp);
   DCHECK(g_application);
-#else
-  if (!g_application) {
-    g_application = new cobalt::browser::Application(
-        quit_closure, false /*should_preload*/, timestamp);
-    DCHECK(g_application);
-  } else {
-    g_application->Start(timestamp);
-  }
-#endif  // SB_API_VERSION >= 13
 }
 
 void StopApplication() {
diff --git a/cobalt/updater/updater_module.cc b/cobalt/updater/updater_module.cc
index 2305b02..98cbb29 100644
--- a/cobalt/updater/updater_module.cc
+++ b/cobalt/updater/updater_module.cc
@@ -147,11 +147,6 @@
   updater_thread_->task_runner()->PostTask(
       FROM_HERE,
       base::Bind(&UpdaterModule::Initialize, base::Unretained(this)));
-
-  // Mark the current installation as successful.
-  updater_thread_->task_runner()->PostTask(
-      FROM_HERE,
-      base::Bind(&UpdaterModule::MarkSuccessful, base::Unretained(this)));
 }
 
 UpdaterModule::~UpdaterModule() {
@@ -238,8 +233,14 @@
 }
 
 void UpdaterModule::MarkSuccessful() {
+  updater_thread_->task_runner()->PostTask(
+      FROM_HERE,
+      base::Bind(&UpdaterModule::MarkSuccessfulImpl, base::Unretained(this)));
+}
+
+void UpdaterModule::MarkSuccessfulImpl() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  LOG(INFO) << "UpdaterModule::MarkSuccessful";
+  LOG(INFO) << "UpdaterModule::MarkSuccessfulImpl";
 
   auto installation_manager =
       static_cast<const CobaltExtensionInstallationManagerApi*>(
diff --git a/cobalt/updater/updater_module.h b/cobalt/updater/updater_module.h
index f88ed46..7bb9136 100644
--- a/cobalt/updater/updater_module.h
+++ b/cobalt/updater/updater_module.h
@@ -156,6 +156,8 @@
   bool GetUseCompressedUpdates() const;
   void SetUseCompressedUpdates(bool use_compressed_updates);
 
+  void MarkSuccessful();
+
  private:
   std::unique_ptr<base::Thread> updater_thread_;
   scoped_refptr<update_client::UpdateClient> update_client_;
@@ -173,7 +175,7 @@
 
   void Initialize();
   void Finalize();
-  void MarkSuccessful();
+  void MarkSuccessfulImpl();
   void Update();
 };
 
diff --git a/cobalt/web/agent.cc b/cobalt/web/agent.cc
index 9914d4d..4341600 100644
--- a/cobalt/web/agent.cc
+++ b/cobalt/web/agent.cc
@@ -20,6 +20,7 @@
 
 #include "base/observer_list.h"
 #include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/startup_timer.h"
 #include "cobalt/loader/fetcher_factory.h"
@@ -290,7 +291,7 @@
   // are added after the global object is created.
   if (!options.injected_global_object_attributes.empty()) {
     DCHECK(base::MessageLoop::current());
-    base::MessageLoop::current()->task_runner()->PostTask(
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::Bind(&Impl::InjectGlobalObjectAttributes, base::Unretained(this),
                    options.injected_global_object_attributes));
diff --git a/cobalt/web/cobalt_ua_data_values.idl b/cobalt/web/cobalt_ua_data_values.idl
index d8832df..399651a 100644
--- a/cobalt/web/cobalt_ua_data_values.idl
+++ b/cobalt/web/cobalt_ua_data_values.idl
@@ -22,6 +22,8 @@
   DOMString evergreenType;
   DOMString evergreenFileType;
   DOMString evergreenVersion;
+  DOMString firmwareVersionDetails;
+  DOMString osExperience;
   DOMString starboardVersion;
   DOMString originalDesignManufacturer;
   DOMString deviceType;
diff --git a/cobalt/web/cobalt_ua_data_values_interface.cc b/cobalt/web/cobalt_ua_data_values_interface.cc
index d1dd39a..47e23dd 100644
--- a/cobalt/web/cobalt_ua_data_values_interface.cc
+++ b/cobalt/web/cobalt_ua_data_values_interface.cc
@@ -67,6 +67,12 @@
   if (init_dict.has_evergreen_version()) {
     evergreen_version_ = init_dict.evergreen_version();
   }
+  if (init_dict.has_firmware_version_details()) {
+    firmware_version_details_ = init_dict.firmware_version_details();
+  }
+  if (init_dict.has_os_experience()) {
+    os_experience_ = init_dict.os_experience();
+  }
   if (init_dict.has_starboard_version()) {
     starboard_version_ = init_dict.starboard_version();
   }
diff --git a/cobalt/web/cobalt_ua_data_values_interface.h b/cobalt/web/cobalt_ua_data_values_interface.h
index 15612a1..81dcc70 100644
--- a/cobalt/web/cobalt_ua_data_values_interface.h
+++ b/cobalt/web/cobalt_ua_data_values_interface.h
@@ -48,6 +48,10 @@
     return evergreen_file_type_;
   }
   const std::string& evergreen_version() const { return evergreen_version_; }
+  const std::string& firmware_version_details() const {
+    return firmware_version_details_;
+  }
+  const std::string& os_experience() const { return os_experience_; }
   const std::string& starboard_version() const { return starboard_version_; }
   const std::string& original_design_manufacturer() const {
     return original_design_manufacturer_;
@@ -79,6 +83,8 @@
   std::string evergreen_type_;
   std::string evergreen_file_type_;
   std::string evergreen_version_;
+  std::string firmware_version_details_;
+  std::string os_experience_;
   std::string starboard_version_;
   std::string original_design_manufacturer_;
   std::string device_type_;
diff --git a/cobalt/web/cobalt_ua_data_values_interface.idl b/cobalt/web/cobalt_ua_data_values_interface.idl
index 6243b22..fc670c2 100644
--- a/cobalt/web/cobalt_ua_data_values_interface.idl
+++ b/cobalt/web/cobalt_ua_data_values_interface.idl
@@ -32,6 +32,8 @@
   readonly attribute DOMString evergreenType;
   readonly attribute DOMString evergreenFileType;
   readonly attribute DOMString evergreenVersion;
+  readonly attribute DOMString firmwareVersionDetails;
+  readonly attribute DOMString osExperience;
   readonly attribute DOMString starboardVersion;
   readonly attribute DOMString originalDesignManufacturer;
   readonly attribute DOMString deviceType;
diff --git a/cobalt/web/csp_delegate.cc b/cobalt/web/csp_delegate.cc
index 3fe54b5..92d0520 100644
--- a/cobalt/web/csp_delegate.cc
+++ b/cobalt/web/csp_delegate.cc
@@ -80,8 +80,8 @@
     if (csp_header_policy_ == csp::kCSPRequired || should_allow) {
       return should_allow;
     } else {
-      DLOG(WARNING) << "Page must include Content-Security-Policy header, it "
-                       "will fail to load in production builds of Cobalt!";
+      LOG(WARNING) << "Page must include Content-Security-Policy header, it "
+                      "will fail to load in production builds of Cobalt!";
     }
   }
 
@@ -185,7 +185,7 @@
   } else {
     // Didn't find Content-Security-Policy header.
     if (!headers.content_security_policy_report_only().empty()) {
-      DLOG(INFO)
+      LOG(INFO)
           << "Content-Security-Policy-Report-Only headers were "
              "received, but Content-Security-Policy headers are required.";
     }
diff --git a/cobalt/web/event_target.cc b/cobalt/web/event_target.cc
index eaea80d..f270d18 100644
--- a/cobalt/web/event_target.cc
+++ b/cobalt/web/event_target.cc
@@ -21,6 +21,7 @@
 #include "base/bind_helpers.h"
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/script/environment_settings.h"
@@ -159,7 +160,7 @@
   if (!base::MessageLoop::current()) {
     return;
   }
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       location,
       base::Bind(base::IgnoreResult(&EventTarget::DispatchEventAndRunCallback),
                  base::AsWeakPtr<EventTarget>(this), event, callback));
@@ -171,7 +172,7 @@
   if (!base::MessageLoop::current()) {
     return;
   }
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       location,
       base::Bind(
           base::IgnoreResult(&EventTarget::DispatchEventNameAndRunCallback),
diff --git a/cobalt/web/navigator_base.cc b/cobalt/web/navigator_base.cc
index 4922cb8..e4f9f07 100644
--- a/cobalt/web/navigator_base.cc
+++ b/cobalt/web/navigator_base.cc
@@ -55,13 +55,7 @@
   return user_agent_data_;
 }
 
-bool NavigatorBase::on_line() const {
-#if SB_API_VERSION >= 13
-  return !SbSystemNetworkIsDisconnected();
-#else
-  return true;
-#endif
-}
+bool NavigatorBase::on_line() const { return !SbSystemNetworkIsDisconnected(); }
 
 }  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/web/navigator_ua_data.cc b/cobalt/web/navigator_ua_data.cc
index 52ac278..4ca973c 100644
--- a/cobalt/web/navigator_ua_data.cc
+++ b/cobalt/web/navigator_ua_data.cc
@@ -60,6 +60,9 @@
       platform_info->evergreen_file_type());
   all_high_entropy_values_.set_evergreen_version(
       platform_info->evergreen_version());
+  all_high_entropy_values_.set_firmware_version_details(
+      platform_info->firmware_version_details());
+  all_high_entropy_values_.set_os_experience(platform_info->os_experience());
   all_high_entropy_values_.set_starboard_version(
       platform_info->starboard_version());
   all_high_entropy_values_.set_original_design_manufacturer(
diff --git a/cobalt/web/testing/mock_user_agent_platform_info.h b/cobalt/web/testing/mock_user_agent_platform_info.h
index 4f87cfd..1ef4301 100644
--- a/cobalt/web/testing/mock_user_agent_platform_info.h
+++ b/cobalt/web/testing/mock_user_agent_platform_info.h
@@ -40,9 +40,11 @@
   base::Optional<std::string> original_design_manufacturer() const override {
     return optional_empty_string_;
   }
+#if SB_API_VERSION < 15
   SbSystemDeviceType device_type() const override {
     return kSbSystemDeviceTypeUnknown;
   }
+#endif
   const std::string& device_type_string() const override {
     return empty_string_;
   }
@@ -73,6 +75,10 @@
   const std::string& evergreen_version() const override {
     return empty_string_;
   }
+  const std::string& firmware_version_details() const override {
+    return empty_string_;
+  }
+  const std::string& os_experience() const override { return empty_string_; }
   const std::string& cobalt_version() const override { return empty_string_; }
   const std::string& cobalt_build_version_number() const override {
     return empty_string_;
diff --git a/cobalt/web/user_agent_platform_info.h b/cobalt/web/user_agent_platform_info.h
index 4d7e51a..6ee6e9c 100644
--- a/cobalt/web/user_agent_platform_info.h
+++ b/cobalt/web/user_agent_platform_info.h
@@ -32,7 +32,9 @@
   virtual const std::string& starboard_version() const = 0;
   virtual const std::string& os_name_and_version() const = 0;
   virtual base::Optional<std::string> original_design_manufacturer() const = 0;
+#if SB_API_VERSION < 15
   virtual SbSystemDeviceType device_type() const = 0;
+#endif
   virtual const std::string& device_type_string() const = 0;
   virtual base::Optional<std::string> chipset_model_number() const = 0;
   virtual base::Optional<std::string> model_year() const = 0;
@@ -45,6 +47,8 @@
   virtual const std::string& evergreen_type() const = 0;
   virtual const std::string& evergreen_file_type() const = 0;
   virtual const std::string& evergreen_version() const = 0;
+  virtual const std::string& firmware_version_details() const = 0;
+  virtual const std::string& os_experience() const = 0;
 
   virtual const std::string& cobalt_version() const = 0;
   virtual const std::string& cobalt_build_version_number() const = 0;
diff --git a/cobalt/webdriver/BUILD.gn b/cobalt/webdriver/BUILD.gn
index e275c91..bce97f0 100644
--- a/cobalt/webdriver/BUILD.gn
+++ b/cobalt/webdriver/BUILD.gn
@@ -31,6 +31,7 @@
     "//cobalt/script",
     "//net",
     "//net:http_server",
+    "//starboard/common",
     "//third_party/icu:icuuc",
     "//url",
   ]
diff --git a/cobalt/webdriver/algorithms.cc b/cobalt/webdriver/algorithms.cc
index e4c6a5b..155e605 100644
--- a/cobalt/webdriver/algorithms.cc
+++ b/cobalt/webdriver/algorithms.cc
@@ -455,7 +455,7 @@
 }
 
 // There is a spec for "displayedness" available:
-//   https://w3c.github.io/webdriver/webdriver-spec.html#element-displayedness
+//   https://www.w3.org/TR/2015/WD-webdriver-20150808/#element-displayedness
 // However, the algorithm described in the spec does not match existing
 // implementations of WebDriver.
 // IsDisplayed will match the existing implementations, using the implementation
diff --git a/cobalt/webdriver/algorithms.h b/cobalt/webdriver/algorithms.h
index d75d3b2..df7f2cc 100644
--- a/cobalt/webdriver/algorithms.h
+++ b/cobalt/webdriver/algorithms.h
@@ -24,13 +24,13 @@
 namespace algorithms {
 
 // Implementation of getElementText algorithm.
-// https://w3c.github.io/webdriver/webdriver-spec.html#getelementtext
+// https://www.w3.org/TR/2015/WD-webdriver-20150808/#getelementtext
 // The spec is not totally clear and, according to comments on the spec, does
 // not exactly match the behavior of existing WebDriver implementations. This
 // implementation will follow the de-facto standards where they differ.
 std::string GetElementText(dom::Element* element);
 
-// https://w3c.github.io/webdriver/webdriver-spec.html#element-displayedness
+// https://www.w3.org/TR/2015/WD-webdriver-20150808/#element-displayedness
 // The spec does not exactly match the behavior of existing WebDriver
 // implementations. Consistency with existing implementations will be preferred
 // over strict conformance to the draft spec.
diff --git a/cobalt/webdriver/dispatcher.h b/cobalt/webdriver/dispatcher.h
index 56796ba..ae29bb1 100644
--- a/cobalt/webdriver/dispatcher.h
+++ b/cobalt/webdriver/dispatcher.h
@@ -76,8 +76,8 @@
     };
     // Send the result of the execution of a registered WebDriver command to be
     // sent as a response as described in the spec:
-    // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Responses
-    // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Failed-Commands
+    // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#responses
+    // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#failed-commands
     virtual void SendResult(
         const base::Optional<protocol::SessionId>& session_id,
         protocol::Response::StatusCode status_code,
@@ -95,7 +95,7 @@
     // Some forms of Invalid Requests are detected in the CommandCallback by
     // checking the path variables and command parameters. Invalid requests are
     // described here:
-    // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Invalid-Requests
+    // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#invalid-requests
     //
     // TODO: Invalid requests should be handled before calling the
     //   CommandCallback.
diff --git a/cobalt/webdriver/element_driver.cc b/cobalt/webdriver/element_driver.cc
index 0f36d4b..3cbc361 100644
--- a/cobalt/webdriver/element_driver.cc
+++ b/cobalt/webdriver/element_driver.cc
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <memory>
-
 #include "cobalt/webdriver/element_driver.h"
 
+#include <memory>
+
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/cssom/property_value.h"
 #include "cobalt/cssom/viewport_size.h"
 #include "cobalt/dom/document.h"
@@ -225,14 +226,14 @@
 }
 
 dom::Element* ElementDriver::GetWeakElement() {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), element_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), element_task_runner_);
   return element_.get();
 }
 
 util::CommandResult<void> ElementDriver::SendKeysInternal(
     std::unique_ptr<Keyboard::KeyboardEventVector> events) {
   typedef util::CommandResult<void> CommandResult;
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), element_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), element_task_runner_);
   if (!element_) {
     return CommandResult(protocol::Response::kStaleElementReference);
   }
@@ -257,7 +258,7 @@
 util::CommandResult<void> ElementDriver::SendClickInternal(
     const protocol::Button& button) {
   typedef util::CommandResult<void> CommandResult;
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), element_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), element_task_runner_);
   if (!element_) {
     return CommandResult(protocol::Response::kStaleElementReference);
   }
@@ -266,14 +267,14 @@
     return CommandResult(protocol::Response::kElementNotVisible);
   }
   // Click on an element.
-  //   https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidelementidclick
+  //   https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidelementidclick
   // The Element Click clicks the in-view center point of the element
-  //   https://w3c.github.io/webdriver/webdriver-spec.html#dfn-element-click
+  //   https://www.w3.org/TR/2015/WD-webdriver-20150808/#click
 
   // An element's in-view center point is the origin position of the rectangle
   // that is the intersection between the element's first DOM client rectangle
   // and the initial viewport.
-  //   https://w3c.github.io/webdriver/webdriver-spec.html#dfn-in-view-center-point
+  //   https://www.w3.org/TR/2017/WD-webdriver-20170125/#dfn-in-view-center-point
   scoped_refptr<dom::DOMRectList> dom_rects = element_->GetClientRects();
   if (dom_rects->length() == 0) {
     return CommandResult(protocol::Response::kElementNotVisible);
@@ -325,7 +326,7 @@
 util::CommandResult<std::string> ElementDriver::RequestScreenshotInternal(
     Screenshot::GetScreenshotFunction get_screenshot_function) {
   typedef util::CommandResult<std::string> CommandResult;
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), element_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), element_task_runner_);
   if (!element_) {
     return CommandResult(protocol::Response::kStaleElementReference);
   }
@@ -337,7 +338,7 @@
 template <typename T>
 util::CommandResult<T> ElementDriver::FindElementsInternal(
     const protocol::SearchStrategy& strategy) {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), element_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), element_task_runner_);
   typedef util::CommandResult<T> CommandResult;
   if (!element_) {
     return CommandResult(protocol::Response::kStaleElementReference);
@@ -348,7 +349,7 @@
 
 util::CommandResult<bool> ElementDriver::EqualsInternal(
     const ElementDriver* other_element_driver) {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), element_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), element_task_runner_);
   typedef util::CommandResult<bool> CommandResult;
   base::WeakPtr<dom::Element> other_element = other_element_driver->element_;
   if (!element_ || !other_element) {
diff --git a/cobalt/webdriver/element_driver.h b/cobalt/webdriver/element_driver.h
index 5a29e5d..471f129 100644
--- a/cobalt/webdriver/element_driver.h
+++ b/cobalt/webdriver/element_driver.h
@@ -46,7 +46,7 @@
 
 // ElementDriver could be considered a WebElement as described in the WebDriver
 // spec.
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#webelement
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#webelement
 // Commands that interact with a WebElement, such as:
 //     /session/:sessionId/element/:id/some_command
 // will map to a method on this class.
diff --git a/cobalt/webdriver/execute_test.cc b/cobalt/webdriver/execute_test.cc
index 516430c..32d969b 100644
--- a/cobalt/webdriver/execute_test.cc
+++ b/cobalt/webdriver/execute_test.cc
@@ -18,6 +18,7 @@
 
 #include "base/json/json_reader.h"
 #include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/testing/stub_window.h"
 #include "cobalt/script/global_environment.h"
@@ -147,7 +148,7 @@
   // Let the message loop run for 200ms to allow enough time for the async
   // script to fire the callback.
   base::RunLoop run_loop;
-  base::MessageLoop::current()->task_runner()->PostDelayedTask(
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE, run_loop.QuitClosure(),
       base::TimeDelta::FromMilliseconds(200));
   run_loop.Run();
@@ -172,7 +173,7 @@
   // Let the message loop run for 200ms to allow enough time for the async
   // timeout to fire.
   base::RunLoop run_loop;
-  base::MessageLoop::current()->task_runner()->PostDelayedTask(
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE, run_loop.QuitClosure(),
       base::TimeDelta::FromMilliseconds(200));
   run_loop.Run();
diff --git a/cobalt/webdriver/protocol/button.h b/cobalt/webdriver/protocol/button.h
index 9866ea5..50db380 100644
--- a/cobalt/webdriver/protocol/button.h
+++ b/cobalt/webdriver/protocol/button.h
@@ -27,9 +27,9 @@
 
 // Represents the JSON parameters passed to the click, buttondown, and buttonup
 // WebDriver commands.
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidelementidclick
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidbuttondown
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidbuttonup
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidelementidclick
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidbuttondown
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidbuttonup
 class Button {
  public:
   static std::unique_ptr<base::Value> ToValue(const Button& button);
diff --git a/cobalt/webdriver/protocol/capabilities.h b/cobalt/webdriver/protocol/capabilities.h
index 54ca1b0..87893de 100644
--- a/cobalt/webdriver/protocol/capabilities.h
+++ b/cobalt/webdriver/protocol/capabilities.h
@@ -45,7 +45,7 @@
  private:
   Capabilities() {}
   // The capabilities listed here:
-  // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Capabilities-JSON-Object
+  // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#capabilities-json-object
 
   base::Optional<std::string> browser_name_;
   base::Optional<std::string> version_;
diff --git a/cobalt/webdriver/protocol/cookie.h b/cobalt/webdriver/protocol/cookie.h
index 4d1357d..70f69cb 100644
--- a/cobalt/webdriver/protocol/cookie.h
+++ b/cobalt/webdriver/protocol/cookie.h
@@ -27,7 +27,7 @@
 namespace webdriver {
 namespace protocol {
 
-// https://w3c.github.io/webdriver/webdriver-spec.html#cookies
+// https://www.w3.org/TR/2015/WD-webdriver-20150808/#cookies
 class Cookie {
  public:
   static std::unique_ptr<base::Value> ToValue(const Cookie& cookie);
diff --git a/cobalt/webdriver/protocol/element_id.h b/cobalt/webdriver/protocol/element_id.h
index 94b75c3..ec322a6 100644
--- a/cobalt/webdriver/protocol/element_id.h
+++ b/cobalt/webdriver/protocol/element_id.h
@@ -31,7 +31,7 @@
   static const char kElementKey[];
 
   // Convert the ElementId to a WebElement JSON object:
-  // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#WebElement-JSON-Object
+  // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#webelement-json-object
   static std::unique_ptr<base::Value> ToValue(const ElementId& element_id);
   static base::Optional<ElementId> FromValue(const base::Value* value);
 
diff --git a/cobalt/webdriver/protocol/frame_id.h b/cobalt/webdriver/protocol/frame_id.h
index 4e2e7ce..3cdaeb7 100644
--- a/cobalt/webdriver/protocol/frame_id.h
+++ b/cobalt/webdriver/protocol/frame_id.h
@@ -22,7 +22,7 @@
 namespace webdriver {
 namespace protocol {
 
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidframe
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidframe
 // Since Cobalt doesn't support multiple frames, the only valid value for this
 // command is to request switching to the top-level browsing context, which
 // is always active.
diff --git a/cobalt/webdriver/protocol/log_entry.cc b/cobalt/webdriver/protocol/log_entry.cc
index 297c084..d0682c1 100644
--- a/cobalt/webdriver/protocol/log_entry.cc
+++ b/cobalt/webdriver/protocol/log_entry.cc
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <memory>
-
 #include "cobalt/webdriver/protocol/log_entry.h"
 
+#include <memory>
+
 #include "base/logging.h"
 
 namespace cobalt {
@@ -48,7 +48,7 @@
   std::unique_ptr<base::DictionaryValue> log_entry_value(
       new base::DictionaryValue());
   // Format of the Log Entry object can be found here:
-  // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Log-Entry-JSON-Object
+  // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#log-entry-json-object
   // timestamp is in milliseconds since the Unix Epoch.
   log_entry_value->SetInteger("timestamp",
                               log_entry.timestamp_.InMilliseconds());
diff --git a/cobalt/webdriver/protocol/log_entry.h b/cobalt/webdriver/protocol/log_entry.h
index 94d01bd..777b66f 100644
--- a/cobalt/webdriver/protocol/log_entry.h
+++ b/cobalt/webdriver/protocol/log_entry.h
@@ -26,7 +26,7 @@
 namespace protocol {
 
 // Log entry object:
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Log-Entry-JSON-Object
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#log-entry-json-object
 class LogEntry {
  public:
   enum LogLevel {
diff --git a/cobalt/webdriver/protocol/moveto.h b/cobalt/webdriver/protocol/moveto.h
index c1be6dc..0819a1f 100644
--- a/cobalt/webdriver/protocol/moveto.h
+++ b/cobalt/webdriver/protocol/moveto.h
@@ -26,7 +26,7 @@
 namespace protocol {
 
 // Represents the JSON parameters passed to the moveto WebDriver command.
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidmoveto
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidmoveto
 class Moveto {
  public:
   static std::unique_ptr<base::Value> ToValue(const Moveto& moveto);
diff --git a/cobalt/webdriver/protocol/response.h b/cobalt/webdriver/protocol/response.h
index 0244c14..1995ed2 100644
--- a/cobalt/webdriver/protocol/response.h
+++ b/cobalt/webdriver/protocol/response.h
@@ -29,7 +29,7 @@
 class Response {
  public:
   // WebDriver Response Status Codes:
-  // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Response-Status-Codes
+  // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#response-status-codes
   enum StatusCode {
     // The command executed successfully.
     kSuccess = 0,
@@ -79,13 +79,13 @@
 
   // Create a JSON object that will be used as the response body for a failed
   // command:
-  // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Failed-Commands
+  // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#failed-commands
   // TODO: Add support for screenshot, stack trace, etc.
   static std::unique_ptr<base::Value> CreateErrorResponse(
       const std::string& message);
 
   // Create a JSON object that will be used as the response body for a command:
-  // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Responses
+  // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#responses
   static std::unique_ptr<base::Value> CreateResponse(
       const base::Optional<protocol::SessionId>& session_id,
       StatusCode status_code,
diff --git a/cobalt/webdriver/protocol/script.h b/cobalt/webdriver/protocol/script.h
index 78256fc..e1ad598 100644
--- a/cobalt/webdriver/protocol/script.h
+++ b/cobalt/webdriver/protocol/script.h
@@ -27,8 +27,8 @@
 
 // Represents the JSON parameters passed to the execute and execute_async
 // WebDriver commands.
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidexecute
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidexecute-async
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidexecute
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidexecute_async
 class Script {
  public:
   static base::Optional<Script> FromValue(const base::Value* script);
diff --git a/cobalt/webdriver/protocol/server_status.cc b/cobalt/webdriver/protocol/server_status.cc
index ed5d409..639074d 100644
--- a/cobalt/webdriver/protocol/server_status.cc
+++ b/cobalt/webdriver/protocol/server_status.cc
@@ -12,18 +12,21 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <memory>
-
 #include "cobalt/webdriver/protocol/server_status.h"
 
+#include <memory>
+#include <utility>
+
 #include "cobalt/version.h"
+#include "starboard/common/system_property.h"
+
+using starboard::kSystemPropertyMaxLength;
 
 namespace cobalt {
 namespace webdriver {
 namespace protocol {
 
 ServerStatus::ServerStatus() {
-  const size_t kSystemPropertyMaxLength = 1024;
   char value[kSystemPropertyMaxLength];
   bool result;
 
diff --git a/cobalt/webdriver/protocol/server_status.h b/cobalt/webdriver/protocol/server_status.h
index f100f41..708f61f 100644
--- a/cobalt/webdriver/protocol/server_status.h
+++ b/cobalt/webdriver/protocol/server_status.h
@@ -27,7 +27,7 @@
 
 // Represents the JSON object that describes the WebDriver server's current
 // status in response to the /status command:
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#status
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#status
 class ServerStatus {
  public:
   ServerStatus();
diff --git a/cobalt/webdriver/protocol/session_id.h b/cobalt/webdriver/protocol/session_id.h
index 8fc7f45..b262940 100644
--- a/cobalt/webdriver/protocol/session_id.h
+++ b/cobalt/webdriver/protocol/session_id.h
@@ -25,7 +25,7 @@
 namespace protocol {
 
 // sessionId is mentioned in the spec describing WebDriver responses:
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Responses
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#responses
 //
 class SessionId {
  public:
diff --git a/cobalt/webdriver/server.cc b/cobalt/webdriver/server.cc
index f7bff66..06093eb 100644
--- a/cobalt/webdriver/server.cc
+++ b/cobalt/webdriver/server.cc
@@ -16,12 +16,14 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/bind.h"
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
 #include "base/strings/string_util.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
@@ -72,11 +74,11 @@
 class ResponseHandlerImpl : public WebDriverServer::ResponseHandler {
  public:
   ResponseHandlerImpl(net::HttpServer* server, int connection_id)
-      : task_runner_(base::MessageLoop::current()->task_runner()),
+      : task_runner_(base::ThreadTaskRunnerHandle::Get()),
         server_(server),
         connection_id_(connection_id) {}
 
-  // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Responses
+  // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#responses
   void Success(std::unique_ptr<base::Value> value) override {
     DCHECK(value);
     std::string data;
@@ -94,7 +96,7 @@
   // Failed commands map to a valid WebDriver command and contain the expected
   // parameters, but otherwise failed to execute for some reason. This should
   // send a 500 Internal Server Error.
-  // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Error-Handling
+  // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#error-handling
   void FailedCommand(std::unique_ptr<base::Value> value) override {
     DCHECK(value);
     std::string data;
@@ -103,7 +105,7 @@
   }
 
   // A number of cases for invalid requests are explained here:
-  // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Invalid-Requests
+  // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#invalid-requests
   // The response type should be text/plain and the message body is an error
   // message
 
@@ -170,7 +172,7 @@
                     const std::string& content_type,
                     base::Optional<net::HttpServerResponseInfo> response_info =
                         base::Optional<net::HttpServerResponseInfo>()) {
-    if (base::MessageLoop::current()->task_runner() == task_runner_) {
+    if (base::ThreadTaskRunnerHandle::Get() == task_runner_) {
       SendToServer(server_, connection_id_, status, message, content_type,
                    response_info);
     } else {
diff --git a/cobalt/webdriver/server.h b/cobalt/webdriver/server.h
index ffc9372..d93ae6d 100644
--- a/cobalt/webdriver/server.h
+++ b/cobalt/webdriver/server.h
@@ -54,18 +54,18 @@
   class ResponseHandler {
    public:
     // Called after a successful WebDriver command.
-    // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Responses
+    // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#responses
     virtual void Success(std::unique_ptr<base::Value>) = 0;
     // |content_type| specifies the type of the data using HTTP mime types.
     virtual void SuccessData(const std::string& content_type, const char* data,
                              int len) = 0;
 
     // Called after a failed WebDriver command
-    // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Failed-Commands
+    // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#failed-commands
     virtual void FailedCommand(std::unique_ptr<base::Value>) = 0;
 
     // Called after an invalid request.
-    // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Invalid-Requests
+    // https://www.selenium.dev/documentation/legacy/json_wire_protocol/#invalid-requests
     virtual void UnknownCommand(const std::string& path) = 0;
     virtual void UnimplementedCommand(const std::string& path) = 0;
     virtual void VariableResourceNotFound(const std::string& variable_name) = 0;
diff --git a/cobalt/webdriver/testdata/simple_test.py b/cobalt/webdriver/testdata/simple_test.py
index bf532b0..5b5a9a1 100755
--- a/cobalt/webdriver/testdata/simple_test.py
+++ b/cobalt/webdriver/testdata/simple_test.py
@@ -21,7 +21,7 @@
 import requests
 
 # This is a simple script for Webdriver Wire Protocol communication.
-#   https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
+#   https://www.selenium.dev/documentation/legacy/json_wire_protocol
 
 WEBDRIVER_HOST = 'http://localhost:4444'
 
@@ -30,7 +30,7 @@
 DELETE = 'DELETE'
 
 # WebDriver Response Status Codes:
-# https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#Response-Status-Codes
+# https://www.selenium.dev/documentation/legacy/json_wire_protocol/#response-status-codes
 
 RESPONSE_STATUS_CODES = {
     0: 'Success',
@@ -72,24 +72,29 @@
   Returns:
     The dictionary returned by the WebDriver server
   """
-  url = '%s/%s' % (WEBDRIVER_HOST, path)
+  url = f'{WEBDRIVER_HOST}/{path}'
   headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'}
   if request_type == GET:
-    request = requests.get(url, data=json.dumps(parameters), headers=headers)
+    request = requests.get(
+        url, data=json.dumps(parameters), headers=headers, timeout=10)
   if request_type == POST:
-    request = requests.post(url, data=json.dumps(parameters), headers=headers)
+    request = requests.post(
+        url, data=json.dumps(parameters), headers=headers, timeout=10)
   if request_type == DELETE:
-    request = requests.delete(url, data=json.dumps(parameters), headers=headers)
+    request = requests.delete(
+        url, data=json.dumps(parameters), headers=headers, timeout=10)
   result = request.text if request.headers[
       'content-type'] == 'text/plain' else request.json()
   if request.status_code == 200:
     return result
   else:
-    print('*** Error %d %s: \"%s\"' %
-          (request.status_code, RESPONSE_STATUS_CODES[result['status']]
-           if isinstance(result, dict) else 'unknown',
-           result['value']['message'] if isinstance(result, dict) else result))
-    print('*** Error %d: %s' % (request.status_code, result))
+    print(
+        f'*** Error {request.status_code} '
+        f"{RESPONSE_STATUS_CODES[result['status']] if isinstance(result, dict) else 'unknown'}"  # pylint: disable=line-too-long
+        ': \"'
+        f"{result['value']['message'] if isinstance(result, dict) else result}"
+        '\"')
+    print(f'*** Error {request.status_code}: {result}')
   return None
 
 
@@ -106,9 +111,8 @@
     The dictionary returned by the WebDriver server
   """
   if path:
-    return Request(request_type, 'session/%s/%s' % (session_id, path),
-                   parameters)
-  return Request(request_type, 'session/%s' % (session_id), parameters)
+    return Request(request_type, f'session/{session_id}/{path}', parameters)
+  return Request(request_type, f'session/{session_id}', parameters)
 
 
 def ElementRequest(session_id,
@@ -117,8 +121,7 @@
                    path=None,
                    parameters=None):
   return SessionRequest(session_id, request_type,
-                        'element/%s/%s' % (element_id[u'ELEMENT'], path),
-                        parameters)
+                        f"element/{element_id['ELEMENT']}/{path}", parameters)
 
 
 def GetSessionID():
@@ -129,7 +132,7 @@
   """
   request = Request(POST, 'session', {'desiredCapabilities': {}})
   if request:
-    session_id = request[u'sessionId']
+    session_id = request['sessionId']
   else:
     # If creating a new session id fails, use an already existing session.
     request = Request(GET, 'sessions')
@@ -159,7 +162,7 @@
   """
   request = SessionRequest(session_id, GET, 'screenshot')
   if request:
-    with open(filename, 'w') as f:
+    with open(filename, 'w', encoding='utf-8') as f:
       f.write(binascii.a2b_base64(request['value']))
       f.close()
 
@@ -173,7 +176,7 @@
   """
   request = ElementRequest(session_id, element_id, GET, 'screenshot')
   if request:
-    with open(filename, 'w') as f:
+    with open(filename, 'w', encoding='utf-8') as f:
       f.write(binascii.a2b_base64(request['value']))
       f.close()
 
@@ -184,22 +187,22 @@
 
 def Moveto(session_id, element, xoffset, yoffset):
   return SessionRequest(session_id, POST, 'moveto', {
-      u'element': element,
-      u'xoffset': xoffset,
-      u'yoffset': yoffset
+      'element': element,
+      'xoffset': xoffset,
+      'yoffset': yoffset
   })
 
 
 def Click(session_id, button):
-  return SessionRequest(session_id, POST, 'click', {u'button': button})
+  return SessionRequest(session_id, POST, 'click', {'button': button})
 
 
 def Buttondown(session_id, button):
-  return SessionRequest(session_id, POST, 'buttondown', {u'button': button})
+  return SessionRequest(session_id, POST, 'buttondown', {'button': button})
 
 
 def Buttonup(session_id, button):
-  return SessionRequest(session_id, POST, 'buttonup', {u'button': button})
+  return SessionRequest(session_id, POST, 'buttonup', {'button': button})
 
 
 def ElementName(session_id, element_id):
@@ -212,17 +215,17 @@
 
 def ElementClick(session_id, element_id, button):
   return ElementRequest(session_id, element_id, POST, 'click',
-                        {u'button': button})
+                        {'button': button})
 
 
 def ElementKeys(session_id, element_id, keys):
-  return ElementRequest(session_id, element_id, POST, 'value', {u'value': keys})
+  return ElementRequest(session_id, element_id, POST, 'value', {'value': keys})
 
 
 def ElementFind(session_id, using, value):
   result = SessionRequest(session_id, POST, 'element', {
-      u'using': using,
-      u'value': value
+      'using': using,
+      'value': value
   })
   return None if result is None else result['value']
 
@@ -233,17 +236,17 @@
   session_id = GetSessionID()
   try:
     active_element = GetActiveElement(session_id)
-    print('active_element : %s' % active_element)
+    print(f'active_element : {active_element}')
 
     for xoffset in range(0, 1900, 20):
-      print('Moveto: %s' % Moveto(session_id, active_element, xoffset, 200))
+      print(f'Moveto: {Moveto(session_id, active_element, xoffset, 200)}')
       time.sleep(0.05)
 
     selected_element = ElementFind(session_id, 'class name',
                                    'ytlr-tile-renderer--focused')
-    print('selected_element : %s' % selected_element)
+    print(f'selected_element : {selected_element}')
 
-    print('ElementClick: %s' % ElementClick(session_id, selected_element, 0))
+    print(f'ElementClick: {ElementClick(session_id, selected_element, 0)}')
 
   except KeyboardInterrupt:
     print('Bye')
@@ -258,17 +261,16 @@
   try:
     selected_element = ElementFind(session_id, 'class name',
                                    'ytlr-tile-renderer--focused')
-    print('Selected List element : %s' % selected_element)
+    print(f'Selected List element : {selected_element}')
 
     # Write screenshots for the selected element, until interrupted.
     while True:
       selected_element = ElementFind(session_id, 'class name',
                                      'ytlr-tile-renderer--focused')
-      print('Selected List element : %s' % selected_element)
+      print(f'Selected List element : {selected_element}')
       if selected_element is not None:
-        print('GetElementScreenShot: %s' % GetElementScreenShot(
-            session_id, selected_element,
-            'element-' + selected_element['ELEMENT'] + '.png'))
+        print('GetElementScreenShot: '
+              f"{GetElementScreenShot(session_id, selected_element, 'element-' + selected_element['ELEMENT'] + '.png')}")  # pylint: disable=line-too-long
 
   except KeyboardInterrupt:
     print('Bye')
@@ -282,21 +284,21 @@
   session_id = GetSessionID()
   try:
     initial_active_element = GetActiveElement(session_id)
-    print('initial active_element : %s' % initial_active_element)
+    print(f'initial active_element : {initial_active_element}')
 
     initial_selected_element = ElementFind(session_id, 'class name',
                                            'ytlr-tile-renderer--focused')
-    print('Selected List element : %s' % initial_selected_element)
+    print(f'Selected List element : {initial_selected_element}')
     while True:
       active_element = GetActiveElement(session_id)
-      print('active_element : %s' % active_element)
+      print(f'active_element : {active_element}')
 
       if initial_active_element != active_element:
         break
 
       selected_element = ElementFind(session_id, 'class name',
                                      'ytlr-tile-renderer--focused')
-      print('Selected List element : %s' % selected_element)
+      print(f'Selected List element : {selected_element}')
 
       if initial_selected_element != selected_element:
         break
diff --git a/cobalt/webdriver/util/call_on_message_loop.h b/cobalt/webdriver/util/call_on_message_loop.h
index 90957a0..2d89963 100644
--- a/cobalt/webdriver/util/call_on_message_loop.h
+++ b/cobalt/webdriver/util/call_on_message_loop.h
@@ -22,6 +22,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop.h"
 #include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_task_runner_handle.h"
 
 namespace cobalt {
 namespace webdriver {
@@ -37,7 +38,7 @@
       : completed_event_(base::WaitableEvent::ResetPolicy::MANUAL,
                          base::WaitableEvent::InitialState::NOT_SIGNALED),
         success_(false) {
-    DCHECK_NE(base::MessageLoop::current()->task_runner(), task_runner);
+    DCHECK_NE(base::ThreadTaskRunnerHandle::Get(), task_runner);
     std::unique_ptr<DeletionSignaler> dt(
         new DeletionSignaler(&completed_event_));
     // Note that while base::MessageLoopProxy::PostTask returns false
diff --git a/cobalt/webdriver/web_driver_module.cc b/cobalt/webdriver/web_driver_module.cc
index 3a4d360..8b1edfc 100644
--- a/cobalt/webdriver/web_driver_module.cc
+++ b/cobalt/webdriver/web_driver_module.cc
@@ -294,11 +294,18 @@
       base::StringPrintf("/session/%s/execute", kSessionIdVariable),
       current_window_command_factory->GetCommandHandler(
           base::Bind(&WindowDriver::Execute)));
+  // https://www.w3.org/TR/2015/WD-webdriver-20150808/#execute-async-script
   webdriver_dispatcher_->RegisterCommand(
       WebDriverServer::kPost,
       base::StringPrintf("/session/%s/execute_async", kSessionIdVariable),
       current_window_command_factory->GetCommandHandler(
           base::Bind(&WindowDriver::ExecuteAsync)));
+  // https://www.w3.org/TR/2015/WD-webdriver-20150827/#execute-async-script
+  webdriver_dispatcher_->RegisterCommand(
+      WebDriverServer::kPost,
+      base::StringPrintf("/session/%s/execute/async", kSessionIdVariable),
+      current_window_command_factory->GetCommandHandler(
+          base::Bind(&WindowDriver::ExecuteAsync)));
   webdriver_dispatcher_->RegisterCommand(
       WebDriverServer::kPost,
       base::StringPrintf("/session/%s/element", kSessionIdVariable),
@@ -514,7 +521,7 @@
   return NULL;
 }
 
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#status
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#status
 void WebDriverModule::GetServerStatus(
     const base::Value* parameters,
     const WebDriverDispatcher::PathVariableMap* path_variables,
@@ -524,7 +531,7 @@
                              protocol::ServerStatus::ToValue(status_));
 }
 
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessions
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessions
 void WebDriverModule::GetActiveSessions(
     const base::Value* parameters,
     const WebDriverDispatcher::PathVariableMap* path_variables,
@@ -538,7 +545,7 @@
                              util::internal::ToValue(sessions));
 }
 
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#get-sessionsessionid
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionid
 void WebDriverModule::CreateSession(
     const base::Value* parameters,
     const WebDriverDispatcher::PathVariableMap* path_variables,
@@ -564,7 +571,7 @@
                                  result_handler.get());
 }
 
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#delete-sessionsessionid
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessions
 void WebDriverModule::DeleteSession(
     const base::Value* parameters,
     const WebDriverDispatcher::PathVariableMap* path_variables,
@@ -627,7 +634,7 @@
   }
 }
 
-// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidscreenshot
+// https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidscreenshot
 void WebDriverModule::RequestScreenshot(
     const base::Value* parameters,
     const WebDriverDispatcher::PathVariableMap* path_variables,
diff --git a/cobalt/webdriver/window_driver.cc b/cobalt/webdriver/window_driver.cc
index da4efbd..43f213a 100644
--- a/cobalt/webdriver/window_driver.cc
+++ b/cobalt/webdriver/window_driver.cc
@@ -18,6 +18,7 @@
 #include <utility>
 
 #include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/dom_rect.h"
 #include "cobalt/dom/location.h"
@@ -164,7 +165,7 @@
         &result);
     return success ? result : NULL;
   }
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
   ElementDriverMap::iterator it = element_drivers_.find(element_id.id());
   if (it != element_drivers_.end()) {
     return it->second;
@@ -398,7 +399,7 @@
 
 protocol::ElementId WindowDriver::ElementToId(
     const scoped_refptr<dom::Element>& element) {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
   for (auto i : element_drivers_) {
     // Note: The element_task_runner_ is the same as the window_task_runner_.
     auto weak_element = i.second->GetWeakElement();
@@ -412,14 +413,14 @@
 
 scoped_refptr<dom::Element> WindowDriver::IdToElement(
     const protocol::ElementId& id) {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
   return base::WrapRefCounted(
       GetElementDriver(protocol::ElementId(id))->GetWeakElement());
 }
 
 protocol::ElementId WindowDriver::CreateNewElementDriver(
     const base::WeakPtr<dom::Element>& weak_element) {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
 
   protocol::ElementId element_id(
       base::StringPrintf("element-%d", next_element_id_++));
@@ -442,7 +443,7 @@
 template <typename T>
 util::CommandResult<T> WindowDriver::FindElementsInternal(
     const protocol::SearchStrategy& strategy) {
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
   typedef util::CommandResult<T> CommandResult;
   if (!window_) {
     return CommandResult(protocol::Response::kNoSuchWindow);
@@ -456,7 +457,7 @@
     base::Optional<base::TimeDelta> async_timeout,
     ScriptExecutorResult::ResultHandler* async_handler) {
   typedef util::CommandResult<protocol::ScriptResult> CommandResult;
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
   if (!window_) {
     return CommandResult(protocol::Response::kNoSuchWindow);
   }
@@ -494,7 +495,7 @@
 util::CommandResult<void> WindowDriver::SendKeysInternal(
     std::unique_ptr<Keyboard::KeyboardEventVector> events) {
   typedef util::CommandResult<void> CommandResult;
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
   if (!window_) {
     return CommandResult(protocol::Response::kNoSuchWindow);
   }
@@ -508,7 +509,7 @@
 
 util::CommandResult<void> WindowDriver::NavigateInternal(const GURL& url) {
   typedef util::CommandResult<void> CommandResult;
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
   if (!window_) {
     return CommandResult(protocol::Response::kNoSuchWindow);
   }
@@ -519,7 +520,7 @@
 util::CommandResult<void> WindowDriver::AddCookieInternal(
     const protocol::Cookie& cookie) {
   typedef util::CommandResult<void> CommandResult;
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
   if (!window_) {
     return CommandResult(protocol::Response::kNoSuchWindow);
   }
@@ -568,7 +569,7 @@
 util::CommandResult<void> WindowDriver::MouseMoveToInternal(
     const protocol::Moveto& moveto) {
   typedef util::CommandResult<void> CommandResult;
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
   if (!window_) {
     return CommandResult(protocol::Response::kNoSuchWindow);
   }
@@ -577,7 +578,7 @@
   // specified, the move is relative to the current mouse cursor. If an element
   // is provided but no offset, the mouse will be moved to the center of the
   // element.
-  //   https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidmoveto
+  //   https://www.selenium.dev/documentation/legacy/json_wire_protocol/#sessionsessionidmoveto
   float x = 0;
   float y = 0;
   scoped_refptr<dom::Element> element;
@@ -666,7 +667,7 @@
 util::CommandResult<void> WindowDriver::MouseButtonDownInternal(
     const protocol::Button& button) {
   typedef util::CommandResult<void> CommandResult;
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
   if (!window_) {
     return CommandResult(protocol::Response::kNoSuchWindow);
   }
@@ -679,7 +680,7 @@
 util::CommandResult<void> WindowDriver::MouseButtonUpInternal(
     const protocol::Button& button) {
   typedef util::CommandResult<void> CommandResult;
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
   if (!window_) {
     return CommandResult(protocol::Response::kNoSuchWindow);
   }
@@ -692,7 +693,7 @@
 util::CommandResult<void> WindowDriver::SendClickInternal(
     const protocol::Button& button) {
   typedef util::CommandResult<void> CommandResult;
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
   if (!window_) {
     return CommandResult(protocol::Response::kNoSuchWindow);
   }
@@ -706,7 +707,7 @@
 util::CommandResult<protocol::ElementId>
 WindowDriver::GetActiveElementInternal() {
   typedef util::CommandResult<protocol::ElementId> CommandResult;
-  DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+  DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
   if (!window_) {
     return CommandResult(protocol::Response::kNoSuchWindow);
   }
diff --git a/cobalt/webdriver/window_driver.h b/cobalt/webdriver/window_driver.h
index d72f8c9..03e0bf8 100644
--- a/cobalt/webdriver/window_driver.h
+++ b/cobalt/webdriver/window_driver.h
@@ -29,6 +29,7 @@
 #include "base/stl_util.h"
 #include "base/synchronization/lock.h"
 #include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/dom/keyboard_event.h"
 #include "cobalt/dom/pointer_event.h"
 #include "cobalt/dom/pointer_event_init.h"
@@ -117,7 +118,7 @@
       const protocol::ElementId& id) override;
 
   dom::Window* GetWeak() {
-    DCHECK_EQ(base::MessageLoop::current()->task_runner(), window_task_runner_);
+    DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), window_task_runner_);
     return window_.get();
   }
 
diff --git a/cobalt/websocket/web_socket_impl.cc b/cobalt/websocket/web_socket_impl.cc
index 39b54d2..1d7d262 100644
--- a/cobalt/websocket/web_socket_impl.cc
+++ b/cobalt/websocket/web_socket_impl.cc
@@ -17,6 +17,7 @@
 #include <algorithm>
 #include <cstdint>
 #include <memory>
+#include <utility>
 
 #include "base/basictypes.h"
 #include "base/bind_helpers.h"
@@ -24,6 +25,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/websocket/web_socket.h"
 #include "net/http/http_util.h"
@@ -36,11 +38,11 @@
                              WebSocket *delegate)
     : network_module_(network_module), delegate_(delegate) {
   DCHECK(base::MessageLoop::current());
-  owner_task_runner_ = base::MessageLoop::current()->task_runner();
+  owner_task_runner_ = base::ThreadTaskRunnerHandle::Get();
 }
 
 void WebSocketImpl::ResetWebSocketEventDelegate() {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   delegate_ = NULL;
 
   delegate_task_runner_->PostTask(
@@ -56,7 +58,7 @@
     return;
   }
   DCHECK(network_module_->url_request_context_getter());
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   origin_ = origin;
 
   DLOG(INFO) << "Connecting to websocket at " << url.spec();
@@ -87,7 +89,7 @@
 void WebSocketImpl::DoConnect(
     scoped_refptr<cobalt::network::URLRequestContextGetter> context,
     const GURL &url, base::WaitableEvent *channel_created_event) {
-  DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+  DCHECK(delegate_task_runner_->RunsTasksInCurrentSequence());
   DCHECK(url.is_valid());
   DCHECK(channel_created_event);
   DCHECK(context->GetURLRequestContext());
@@ -120,7 +122,7 @@
   if (!websocket_channel_) {
     return;
   }
-  DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+  DCHECK(delegate_task_runner_->RunsTasksInCurrentSequence());
 
   auto channel_state = websocket_channel_->StartClosingHandshake(
       close_info.code, close_info.reason);
@@ -131,7 +133,7 @@
 }
 
 void WebSocketImpl::ResetChannel() {
-  DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+  DCHECK(delegate_task_runner_->RunsTasksInCurrentSequence());
   websocket_channel_.reset();
 }
 
@@ -169,7 +171,7 @@
 
 void WebSocketImpl::OnWebSocketConnected(
     const std::string &selected_subprotocol) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (delegate_) {
     delegate_->OnConnected(selected_subprotocol);
@@ -178,7 +180,7 @@
 
 void WebSocketImpl::OnWebSocketDisconnected(bool was_clean, uint16 code,
                                             const std::string &reason) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (delegate_) {
     delegate_->OnDisconnected(was_clean, code, reason);
   }
@@ -186,14 +188,14 @@
 
 void WebSocketImpl::OnWebSocketReceivedData(
     bool is_text_frame, scoped_refptr<net::IOBufferWithSize> data) {
-  if (!owner_task_runner_->BelongsToCurrentThread()) {
+  if (!owner_task_runner_->RunsTasksInCurrentSequence()) {
     owner_task_runner_->PostTask(
         FROM_HERE, base::Bind(&WebSocketImpl::OnWebSocketReceivedData, this,
                               is_text_frame, data));
     return;
   }
 
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (delegate_) {
     delegate_->OnReceivedData(is_text_frame, data);
   }
@@ -201,7 +203,7 @@
 
 void WebSocketImpl::OnClose(bool was_clean, int error_code,
                             const std::string &close_reason) {
-  DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+  DCHECK(delegate_task_runner_->RunsTasksInCurrentSequence());
 
   std::uint16_t close_code = static_cast<std::uint16_t>(error_code);
 
@@ -242,7 +244,7 @@
   scoped_refptr<net::IOBuffer> io_buffer(new net::IOBuffer(length));
   memcpy(io_buffer->data(), data, length);
 
-  if (delegate_task_runner_->BelongsToCurrentThread()) {
+  if (delegate_task_runner_->RunsTasksInCurrentSequence()) {
     SendOnDelegateThread(op_code, std::move(io_buffer), length);
   } else {
     base::Closure do_send_closure(
@@ -258,7 +260,7 @@
 void WebSocketImpl::SendOnDelegateThread(
     const net::WebSocketFrameHeader::OpCode op_code,
     scoped_refptr<net::IOBuffer> io_buffer, std::size_t length) {
-  DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+  DCHECK(delegate_task_runner_->RunsTasksInCurrentSequence());
 
   if (!websocket_channel_) {
     DLOG(WARNING) << "Attempt to send over a closed channel.";
@@ -270,7 +272,7 @@
 }
 
 void WebSocketImpl::ProcessSendQueue() {
-  DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+  DCHECK(delegate_task_runner_->RunsTasksInCurrentSequence());
   while (current_quota_ > 0 && !send_queue_.empty()) {
     SendQueueMessage message = send_queue_.front();
     size_t current_message_length = message.length - sent_size_of_top_message_;
@@ -281,8 +283,8 @@
       scoped_refptr<net::IOBuffer> new_io_buffer(
           new net::IOBuffer(static_cast<size_t>(current_quota_)));
       memcpy(new_io_buffer->data(),
-                   message.io_buffer->data() + sent_size_of_top_message_,
-                   current_quota_);
+             message.io_buffer->data() + sent_size_of_top_message_,
+             current_quota_);
       sent_size_of_top_message_ += current_quota_;
       message.io_buffer = new_io_buffer;
       current_message_length = current_quota_;
diff --git a/cobalt/websocket/web_socket_impl.h b/cobalt/websocket/web_socket_impl.h
index 6c65620..f2842a0 100644
--- a/cobalt/websocket/web_socket_impl.h
+++ b/cobalt/websocket/web_socket_impl.h
@@ -23,8 +23,8 @@
 #include "base/compiler_specific.h"
 #include "base/memory/ref_counted.h"
 #include "base/optional.h"
-#include "base/single_thread_task_runner.h"
-#include "base/threading/thread_checker.h"
+#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/websocket/buffered_amount_tracker.h"
 #include "cobalt/websocket/cobalt_web_socket_event_handler.h"
@@ -123,7 +123,7 @@
 
   void ResetChannel();
 
-  THREAD_CHECKER(thread_checker_);
+  SEQUENCE_CHECKER(sequence_checker_);
 
   std::vector<std::string> desired_sub_protocols_;
   network::NetworkModule* network_module_;
@@ -143,8 +143,8 @@
   std::queue<SendQueueMessage> send_queue_;
   size_t sent_size_of_top_message_ = 0;
 
-  scoped_refptr<base::SingleThreadTaskRunner> delegate_task_runner_;
-  scoped_refptr<base::SingleThreadTaskRunner> owner_task_runner_;
+  scoped_refptr<base::SequencedTaskRunner> delegate_task_runner_;
+  scoped_refptr<base::SequencedTaskRunner> owner_task_runner_;
 
   ~WebSocketImpl();
   friend class base::RefCountedThreadSafe<WebSocketImpl>;
diff --git a/cobalt/websocket/web_socket_impl_test.cc b/cobalt/websocket/web_socket_impl_test.cc
index d7ddb7b..a750436 100644
--- a/cobalt/websocket/web_socket_impl_test.cc
+++ b/cobalt/websocket/web_socket_impl_test.cc
@@ -107,7 +107,7 @@
   }
 
   std::unique_ptr<web::testing::StubWebContext> web_context_;
-  scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
+  scoped_refptr<base::SequencedTaskRunner> network_task_runner_;
   scoped_refptr<WebSocketImpl> websocket_impl_;
   MockWebSocketChannel* mock_channel_;
   StrictMock<MockExceptionState> exception_state_;
diff --git a/cobalt/worker/clients.cc b/cobalt/worker/clients.cc
index c7c7f1a..c78e12a 100644
--- a/cobalt/worker/clients.cc
+++ b/cobalt/worker/clients.cc
@@ -22,6 +22,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop.h"
 #include "base/task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/script/environment_settings.h"
@@ -139,7 +140,7 @@
            service_worker->state() != kServiceWorkerStateActivating);
     // Perform the rest of the steps in a task, because the promise has to be
     // returned before we can safely reject it.
-    base::MessageLoop::current()->task_runner()->PostTask(
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(
             [](std::unique_ptr<script::ValuePromiseVoid::Reference>
diff --git a/cobalt/worker/dedicated_worker.cc b/cobalt/worker/dedicated_worker.cc
index 43f2b0d..624dbec 100644
--- a/cobalt/worker/dedicated_worker.cc
+++ b/cobalt/worker/dedicated_worker.cc
@@ -94,6 +94,7 @@
   //    1. Run a worker given worker, worker URL, outside settings, outside
   //    port, and options.
   options.outside_context = environment_settings()->context();
+  options.outside_event_target = this;
   options.outside_port = outside_port_.get();
   options.options = worker_options_;
   options.web_options.service_worker_jobs =
diff --git a/cobalt/worker/fetch_event.cc b/cobalt/worker/fetch_event.cc
index 99497fb..57c9227 100644
--- a/cobalt/worker/fetch_event.cc
+++ b/cobalt/worker/fetch_event.cc
@@ -17,6 +17,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/threading/thread_task_runner_handle.h"
 #include "cobalt/script/v8c/conversion_helpers.h"
 #include "cobalt/script/v8c/v8c_value_handle.h"
 #include "cobalt/web/cache_utils.h"
@@ -29,13 +30,13 @@
                        const std::string& type,
                        const FetchEventInit& event_init_dict)
     : FetchEvent(environment_settings, base::Token(type), event_init_dict,
-                 base::MessageLoop::current()->task_runner(),
-                 RespondWithCallback(), ReportLoadTimingInfo()) {}
+                 base::ThreadTaskRunnerHandle::Get(), RespondWithCallback(),
+                 ReportLoadTimingInfo()) {}
 
 FetchEvent::FetchEvent(
     script::EnvironmentSettings* environment_settings, base::Token type,
     const FetchEventInit& event_init_dict,
-    scoped_refptr<base::SingleThreadTaskRunner> callback_task_runner,
+    scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
     RespondWithCallback respond_with_callback,
     ReportLoadTimingInfo report_load_timing_info)
     : ExtendableEvent(environment_settings, type, event_init_dict),
@@ -89,8 +90,7 @@
       ->PostTask(
           FROM_HERE,
           base::BindOnce(
-              [](scoped_refptr<base::SingleThreadTaskRunner>
-                     callback_task_runner,
+              [](scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
                  RespondWithCallback respond_with_callback, std::string body,
                  base::MessageLoop* loop, base::OnceClosure callback) {
                 callback_task_runner->PostTask(
diff --git a/cobalt/worker/fetch_event.h b/cobalt/worker/fetch_event.h
index 2b129c0..fe42571 100644
--- a/cobalt/worker/fetch_event.h
+++ b/cobalt/worker/fetch_event.h
@@ -41,7 +41,7 @@
              const FetchEventInit& event_init_dict);
   FetchEvent(script::EnvironmentSettings*, base::Token type,
              const FetchEventInit& event_init_dict,
-             scoped_refptr<base::SingleThreadTaskRunner> callback_task_runner,
+             scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
              RespondWithCallback respond_with_callback,
              ReportLoadTimingInfo report_load_timing_info);
   ~FetchEvent() override = default;
@@ -67,7 +67,7 @@
   void RespondWithDone();
 
   script::EnvironmentSettings* environment_settings_;
-  scoped_refptr<base::SingleThreadTaskRunner> callback_task_runner_;
+  scoped_refptr<base::SequencedTaskRunner> callback_task_runner_;
   RespondWithCallback respond_with_callback_;
   ReportLoadTimingInfo report_load_timing_info_;
   std::unique_ptr<script::ValueHandleHolder::Reference> request_;
diff --git a/cobalt/worker/service_worker_container.cc b/cobalt/worker/service_worker_container.cc
index 9350212..b392f5c 100644
--- a/cobalt/worker/service_worker_container.cc
+++ b/cobalt/worker/service_worker_container.cc
@@ -21,6 +21,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/optional.h"
 #include "base/task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/script/promise.h"
@@ -152,7 +153,7 @@
   }
   // 6. Invoke Start Register with scopeURL, scriptURL, p, client, client’s
   //    creation URL, options["type"], and options["updateViaCache"].
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::BindOnce(&ServiceWorkerJobs::StartRegister,
                      base::Unretained(client->service_worker_jobs()), scope_url,
@@ -182,7 +183,7 @@
       new script::ValuePromiseWrappable::Reference(
           environment_settings()->context()->GetWindowOrWorkerGlobalScope(),
           promise));
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::BindOnce(&ServiceWorkerContainer::GetRegistrationTask,
                                 base::Unretained(this), url,
                                 std::move(promise_reference)));
@@ -255,7 +256,7 @@
       promise_reference(new script::ValuePromiseSequenceWrappable::Reference(
           environment_settings()->context()->GetWindowOrWorkerGlobalScope(),
           promise));
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::BindOnce(&ServiceWorkerContainer::GetRegistrationsTask,
                      base::Unretained(this), std::move(promise_reference)));
diff --git a/cobalt/worker/service_worker_global_scope.cc b/cobalt/worker/service_worker_global_scope.cc
index 1598b81..06b7dae 100644
--- a/cobalt/worker/service_worker_global_scope.cc
+++ b/cobalt/worker/service_worker_global_scope.cc
@@ -20,7 +20,7 @@
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
-#include "base/single_thread_task_runner.h"
+#include "base/sequenced_task_runner.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/exception_state.h"
@@ -196,7 +196,7 @@
 void ServiceWorkerGlobalScope::StartFetch(
     const GURL& url, bool main_resource,
     const net::HttpRequestHeaders& request_headers,
-    scoped_refptr<base::SingleThreadTaskRunner> callback_task_runner,
+    scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
     base::OnceCallback<void(std::unique_ptr<std::string>)> callback,
     base::OnceCallback<void(const net::LoadTimingInfo&)>
         report_load_timing_info,
diff --git a/cobalt/worker/service_worker_global_scope.h b/cobalt/worker/service_worker_global_scope.h
index a32ba66..5da2e11 100644
--- a/cobalt/worker/service_worker_global_scope.h
+++ b/cobalt/worker/service_worker_global_scope.h
@@ -77,7 +77,7 @@
   void StartFetch(
       const GURL& url, bool main_resource,
       const net::HttpRequestHeaders& request_headers,
-      scoped_refptr<base::SingleThreadTaskRunner> callback_task_runner,
+      scoped_refptr<base::SequencedTaskRunner> callback_task_runner,
       base::OnceCallback<void(std::unique_ptr<std::string>)> callback,
       base::OnceCallback<void(const net::LoadTimingInfo&)>
           report_load_timing_info,
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc
index 6b332d0..8288cf9 100644
--- a/cobalt/worker/service_worker_jobs.cc
+++ b/cobalt/worker/service_worker_jobs.cc
@@ -31,6 +31,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/synchronization/lock.h"
 #include "base/task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/tokens.h"
@@ -1000,7 +1001,7 @@
 
   // Post a task for the remaining steps, to let tasks posted by
   // RunServiceWorker, such as for registering the web context, execute first.
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE, base::Bind(&ServiceWorkerJobs::UpdateOnRunServiceWorker,
                             base::Unretained(this), std::move(state),
                             std::move(worker), run_result_is_success));
diff --git a/cobalt/worker/service_worker_object.cc b/cobalt/worker/service_worker_object.cc
index b16feb6..9fcbbd1 100644
--- a/cobalt/worker/service_worker_object.cc
+++ b/cobalt/worker/service_worker_object.cc
@@ -233,8 +233,8 @@
   if (!service_worker_global_scope->csp_delegate()->OnReceiveHeaders(
           csp_headers)) {
     // https://www.w3.org/TR/service-workers/#content-security-policy
-    DLOG(WARNING) << "Warning: No Content Security Header received for the "
-                     "service worker.";
+    LOG(WARNING) << "Warning: No Content Security Header received for the "
+                    "service worker.";
   }
   web_context_->SetupFinished();
   // 8.11. If serviceWorker is an active worker, and there are any tasks queued
@@ -298,8 +298,8 @@
       // set of event types to handle remains an empty set. The user agents are
       // encouraged to show a warning that the event listeners must be added on
       // the very first evaluation of the worker script.
-      DLOG(WARNING) << "ServiceWorkerGlobalScope's event listeners must be "
-                       "added on the first evaluation of the worker script.";
+      LOG(WARNING) << "ServiceWorkerGlobalScope's event listeners must be "
+                      "added on the first evaluation of the worker script.";
     }
     event_types.clear();
   }
diff --git a/cobalt/worker/service_worker_persistent_settings.cc b/cobalt/worker/service_worker_persistent_settings.cc
index ba47191..41a689d 100644
--- a/cobalt/worker/service_worker_persistent_settings.cc
+++ b/cobalt/worker/service_worker_persistent_settings.cc
@@ -90,9 +90,6 @@
       ServiceWorkerConsts::kSettingsJson));
   persistent_settings_->ValidatePersistentSettings();
   DCHECK(persistent_settings_);
-
-  cache_.reset(cobalt::cache::Cache::GetInstance());
-  DCHECK(cache_);
 }
 
 void ServiceWorkerPersistentSettings::ReadServiceWorkerRegistrationMapSettings(
@@ -243,9 +240,10 @@
     if (script_url_value.is_string()) {
       auto script_url_string = script_url_value.GetString();
       auto script_url = GURL(script_url_string);
-      std::unique_ptr<std::vector<uint8_t>> data = cache_->Retrieve(
-          disk_cache::ResourceType::kServiceWorkerScript,
-          web::cache_utils::GetKey(key_string + script_url_string));
+      std::unique_ptr<std::vector<uint8_t>> data =
+          cobalt::cache::Cache::GetInstance()->Retrieve(
+              disk_cache::ResourceType::kServiceWorkerScript,
+              web::cache_utils::GetKey(key_string + script_url_string));
       if (data == nullptr) {
         return false;
       }
@@ -382,7 +380,7 @@
     // Use Cache::Store to persist the script resource.
     std::string resource = *(script_resource.second.content.get());
     std::vector<uint8_t> data(resource.begin(), resource.end());
-    cache_->Store(
+    cobalt::cache::Cache::GetInstance()->Store(
         disk_cache::ResourceType::kServiceWorkerScript,
         web::cache_utils::GetKey(registration_key_string + script_url_string),
         data,
@@ -448,7 +446,7 @@
       auto script_url_value = std::move(script_urls_list[i]);
       if (script_url_value.is_string()) {
         auto script_url_string = script_url_value.GetString();
-        cache_->Delete(
+        cobalt::cache::Cache::GetInstance()->Delete(
             disk_cache::ResourceType::kServiceWorkerScript,
             web::cache_utils::GetKey(key_string + script_url_string));
       }
diff --git a/cobalt/worker/service_worker_persistent_settings.h b/cobalt/worker/service_worker_persistent_settings.h
index efda66f..1d4fd2e 100644
--- a/cobalt/worker/service_worker_persistent_settings.h
+++ b/cobalt/worker/service_worker_persistent_settings.h
@@ -94,8 +94,6 @@
       persistent_settings_;
 
   std::set<std::string> key_set_;
-
-  std::unique_ptr<cobalt::cache::Cache> cache_;
 };
 
 }  // namespace worker
diff --git a/cobalt/worker/service_worker_registration.cc b/cobalt/worker/service_worker_registration.cc
index 4e30763..adba128 100644
--- a/cobalt/worker/service_worker_registration.cc
+++ b/cobalt/worker/service_worker_registration.cc
@@ -19,6 +19,7 @@
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
 #include "base/task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/web/context.h"
@@ -55,7 +56,7 @@
           promise));
   // Perform the rest of the steps in a task, because the promise has to be
   // returned before we can safely reject or resolve it.
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::BindOnce(&ServiceWorkerRegistration::UpdateTask,
                      base::Unretained(this), std::move(promise_reference)));
@@ -142,7 +143,7 @@
 
   // Perform the rest of the steps in a task, so that unregister doesn't race
   // past any previously submitted update requests.
-  base::MessageLoop::current()->task_runner()->PostTask(
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::BindOnce(
           [](worker::ServiceWorkerJobs* jobs, const url::Origin& storage_key,
diff --git a/cobalt/worker/worker.cc b/cobalt/worker/worker.cc
index 5e9d507..13fa24b 100644
--- a/cobalt/worker/worker.cc
+++ b/cobalt/worker/worker.cc
@@ -73,7 +73,6 @@
   worker_global_scope_ = nullptr;
   message_port_ = nullptr;
   content_.reset();
-  error_.reset();
 }
 
 Worker::~Worker() { Abort(); }
@@ -192,6 +191,23 @@
       base::Bind(&Worker::OnLoadingComplete, base::Unretained(this)));
 }
 
+void Worker::SendErrorEventToOutside(const std::string& message) {
+  LOG(WARNING) << "Worker loading failed : " << message;
+  options_.outside_context->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::Bind(
+          [](base::WeakPtr<web::EventTarget> event_target,
+             const std::string& message, const std::string& filename) {
+            web::ErrorEventInit error;
+            error.set_message(message);
+            error.set_filename(filename);
+            event_target->DispatchEvent(new web::ErrorEvent(
+                event_target->environment_settings(), error));
+          },
+          base::AsWeakPtr(options_.outside_event_target), message,
+          web_context_->environment_settings()->creation_url().spec()));
+}
+
 void Worker::OnContentProduced(const loader::Origin& last_url_origin,
                                std::unique_ptr<std::string> content) {
   // Algorithm for 'run a worker'
@@ -209,35 +225,13 @@
 void Worker::OnLoadingComplete(const base::Optional<std::string>& error) {
   // Algorithm for 'run a worker'
   //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#run-a-worker
-  error_ = error;
   //     If the algorithm asynchronously completes with null or with a script
   //     whose error to rethrow is non-null, then:
-  if (error_ || !content_) {
+  if (error || !content_) {
     //     1. Queue a global task on the DOM manipulation task source given
     //        worker's relevant global object to fire an event named error at
     //        worker.
-    options_.outside_context->message_loop()->task_runner()->PostTask(
-        FROM_HERE,
-        base::BindOnce(
-            [](web::WindowOrWorkerGlobalScope* global_scope,
-               const base::Optional<std::string>& message,
-               const base::SourceLocation& location) {
-              web::ErrorEventInit error;
-              error.set_message(message.value_or("No content for worker."));
-              error.set_filename(location.file_path);
-              error.set_lineno(location.line_number);
-              error.set_colno(location.column_number);
-              global_scope->DispatchEvent(new web::ErrorEvent(
-                  global_scope->environment_settings(), error));
-            },
-            base::Unretained(
-                options_.outside_context->GetWindowOrWorkerGlobalScope()),
-            error, options_.construction_location));
-    if (error_) {
-      LOG(WARNING) << "Script loading failed : " << *error;
-    } else {
-      LOG(WARNING) << "Script loading failed : no content received.";
-    }
+    SendErrorEventToOutside(error.value_or("No content for worker."));
     //     2. Run the environment discarding steps for inside settings.
     //     3. Return.
     return;
@@ -285,19 +279,7 @@
   std::string retval = web_context_->script_runner()->Execute(
       content, script_location, mute_errors, &succeeded);
   if (!succeeded) {
-    options_.outside_context->message_loop()->task_runner()->PostTask(
-        FROM_HERE,
-        base::BindOnce(
-            [](web::Context* context, const std::string& message,
-               const std::string& filename) {
-              web::ErrorEventInit error;
-              error.set_message(message);
-              error.set_filename(filename);
-              context->GetWindowOrWorkerGlobalScope()->DispatchEvent(
-                  new web::ErrorEvent(context->environment_settings(), error));
-            },
-            options_.outside_context, retval,
-            web_context_->environment_settings()->creation_url().spec()));
+    SendErrorEventToOutside(retval);
   }
 
   // 24. Enable outside port's port message queue.
diff --git a/cobalt/worker/worker.h b/cobalt/worker/worker.h
index 9d8d779..d37d462 100644
--- a/cobalt/worker/worker.h
+++ b/cobalt/worker/worker.h
@@ -69,6 +69,7 @@
     //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#dom-worker
     GURL url;
     web::Context* outside_context = nullptr;
+    web::EventTarget* outside_event_target;
     web::MessagePort* outside_port = nullptr;
     WorkerOptions options;
   };
@@ -97,6 +98,9 @@
   // thread.
   void Initialize(web::Context* context);
 
+  // Send an error event to the outside object.
+  void SendErrorEventToOutside(const std::string& message);
+
   void OnContentProduced(const loader::Origin& last_url_origin,
                          std::unique_ptr<std::string> content);
   void OnLoadingComplete(const base::Optional<std::string>& error);
@@ -135,9 +139,6 @@
   // Content of the script. Released after Execute is called.
   std::unique_ptr<std::string> content_;
 
-  // If the script failed, contains the error message.
-  base::Optional<std::string> error_;
-
   // The execution ready flag.
   //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#concept-environment-execution-ready-flag
   base::WaitableEvent execution_ready_ = {
diff --git a/cobalt/worker/worker_global_scope.cc b/cobalt/worker/worker_global_scope.cc
index 33c7b86..0f2372b 100644
--- a/cobalt/worker/worker_global_scope.cc
+++ b/cobalt/worker/worker_global_scope.cc
@@ -154,7 +154,7 @@
     if (!SbAtomicNoBarrier_Increment(&number_of_loads_, -1)) {
       // Clear the loader factory after this callback
       // completes.
-      base::MessageLoop::current()->task_runner()->PostTask(
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
           FROM_HERE, base::BindOnce(
                          [](base::WaitableEvent* load_finished_) {
                            load_finished_->Signal();
diff --git a/cobalt/xhr/BUILD.gn b/cobalt/xhr/BUILD.gn
index 17ad212..c2bd6fc 100644
--- a/cobalt/xhr/BUILD.gn
+++ b/cobalt/xhr/BUILD.gn
@@ -41,6 +41,7 @@
     "//cobalt/base",
     "//cobalt/dom_parser",
     "//cobalt/loader",
+    "//cobalt/network",
     "//cobalt/script",
     "//cobalt/web",
     "//nb",
diff --git a/cobalt/xhr/xml_http_request.cc b/cobalt/xhr/xml_http_request.cc
index edd864a..8de549a 100644
--- a/cobalt/xhr/xml_http_request.cc
+++ b/cobalt/xhr/xml_http_request.cc
@@ -23,6 +23,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/source_location.h"
@@ -36,6 +37,7 @@
 #include "cobalt/loader/fetch_interceptor_coordinator.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/url_fetcher_string_writer.h"
+#include "cobalt/network/network_module.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/web/context.h"
@@ -344,6 +346,12 @@
 XMLHttpRequestImpl::XMLHttpRequestImpl(XMLHttpRequest* xhr)
     : error_(false),
       is_cross_origin_(false),
+      cors_policy_(xhr->environment_settings()
+                       ->context()
+                       ->fetcher_factory()
+                       ->network_module()
+                       ->network_delegate()
+                       ->cors_policy()),
       is_data_url_(false),
       is_redirect_(false),
       method_(net::URLFetcher::GET),
@@ -366,7 +374,7 @@
       sent_(false),
       settings_(xhr->environment_settings()),
       stop_timeout_(false),
-      task_runner_(base::MessageLoop::current()->task_runner()),
+      task_runner_(base::ThreadTaskRunnerHandle::Get()),
       timeout_ms_(0),
       upload_complete_(false) {
   DCHECK(environment_settings());
@@ -534,7 +542,7 @@
   if (will_destroy_current_message_loop_.load()) {
     return;
   }
-  if (task_runner_ != base::MessageLoop::current()->task_runner()) {
+  if (task_runner_ != base::ThreadTaskRunnerHandle::Get()) {
     task_runner_->PostTask(FROM_HERE,
                            base::BindOnce(&XMLHttpRequestImpl::SendIntercepted,
                                           AsWeakPtr(), std::move(response)));
@@ -602,7 +610,7 @@
   if (will_destroy_current_message_loop_.load()) {
     return;
   }
-  if (task_runner_ != base::MessageLoop::current()->task_runner()) {
+  if (task_runner_ != base::ThreadTaskRunnerHandle::Get()) {
     task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(&XMLHttpRequestImpl::SendFallback,
@@ -918,7 +926,7 @@
   if (is_cross_origin_) {
     if (!loader::CORSPreflight::CORSCheck(*http_response_headers_,
                                           origin_.SerializedOrigin(),
-                                          with_credentials_)) {
+                                          with_credentials_, cors_policy_)) {
       HandleRequestError(XMLHttpRequest::kNetworkError);
       return;
     }
@@ -1177,7 +1185,7 @@
   // CORS check for the received response
   if (is_cross_origin_) {
     if (!loader::CORSPreflight::CORSCheck(headers, origin_.SerializedOrigin(),
-                                          with_credentials_)) {
+                                          with_credentials_, cors_policy_)) {
       HandleRequestError(XMLHttpRequest::kNetworkError);
       return;
     }
@@ -1416,6 +1424,7 @@
   // Don't retry, let the caller deal with it.
   url_fetcher_->SetAutomaticallyRetryOn5xx(false);
   url_fetcher_->SetExtraRequestHeaders(request_headers_.ToString());
+  network_module->AddClientHintHeaders(*url_fetcher_);
 
   // We want to do cors check and preflight during redirects
   url_fetcher_->SetStopOnRedirect(true);
@@ -1452,6 +1461,7 @@
             ->GetWindowOrWorkerGlobalScope()
             ->get_preflight_cache()));
     corspreflight_->set_headers(request_headers_);
+    corspreflight_->set_cors_policy(cors_policy_);
     // For cross-origin requests, don't send or save auth data / cookies unless
     // withCredentials was set.
     // To make a cross-origin request, add origin, referrer source, credentials,
@@ -1526,7 +1536,7 @@
 void XMLHttpRequestImpl::StartURLFetcher(const SbTime max_artificial_delay,
                                          const int url_fetcher_generation) {
   if (max_artificial_delay > 0) {
-    base::MessageLoop::current()->task_runner()->PostDelayedTask(
+    base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
         FROM_HERE,
         base::Bind(&XMLHttpRequestImpl::StartURLFetcher, base::Unretained(this),
                    0, url_fetcher_generation_),
diff --git a/cobalt/xhr/xml_http_request.h b/cobalt/xhr/xml_http_request.h
index 7b83f57..5a5275e 100644
--- a/cobalt/xhr/xml_http_request.h
+++ b/cobalt/xhr/xml_http_request.h
@@ -337,6 +337,7 @@
   // All members requiring initialization are grouped below.
   bool error_;
   bool is_cross_origin_;
+  network::CORSPolicy cors_policy_;
   bool is_data_url_;
   bool is_redirect_;
   net::URLFetcher::RequestType method_;
@@ -444,7 +445,7 @@
   bool sent_;
   web::EnvironmentSettings* const settings_;
   bool stop_timeout_;
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
   uint32 timeout_ms_;
   bool upload_complete_;
 
diff --git a/components/crash/core/common/BUILD.gn b/components/crash/core/common/BUILD.gn
new file mode 100644
index 0000000..e5d44d3
--- /dev/null
+++ b/components/crash/core/common/BUILD.gn
@@ -0,0 +1,154 @@
+# 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("//build/buildflag_header.gni")
+
+declare_args() {
+  # If set to true, this will stub out and disable the entire crash key system.
+  use_crash_key_stubs = is_fuchsia
+}
+
+group("common") {
+  public_deps = [
+    ":crash_key",
+    ":crash_key_utils",
+  ]
+
+  if (is_mac || is_ios) {
+    public_deps += [ ":zombies" ]
+  }
+}
+
+use_crashpad_annotation = (is_mac || is_win) && !use_crash_key_stubs
+
+buildflag_header("crash_buildflags") {
+  header = "crash_buildflags.h"
+  flags = [
+    "USE_CRASHPAD_ANNOTATION=$use_crashpad_annotation",
+    "USE_CRASH_KEY_STUBS=$use_crash_key_stubs",
+  ]
+}
+
+# Crashpad's annotation system can store data on a per-module basis (i.e.,
+# in different shared libraries in the component build) without issue. The
+# Breakpad implementation uses a static global variable, so ensure there is
+# only one instance of the symbol in the component build by making this
+# target a component.
+if (use_crash_key_stubs || use_crashpad_annotation) {
+  crash_key_target_type = "static_library"
+} else {
+  crash_key_target_type = "component"
+}
+target(crash_key_target_type, "crash_key") {
+  sources = [
+    "crash_export.h",
+    "crash_key.cc",
+    "crash_key.h",
+    "crash_key_base_support.cc",
+    "crash_key_base_support.h",
+  ]
+
+  defines = []
+
+  # This target is not always a component, depending on the implementation.
+  # When it is not a component, annotating functions with the standard
+  # CRASH_EXPORT macro causes linking errors on Windows (clients of this target
+  # expect it to be dllimport but it is linked statically). Instead, provide a
+  # wrapper macro CRASH_KEY_EXPORT that only evaluates to CRASH_EXPORT if this
+  # target is really a component.
+  if (crash_key_target_type == "component") {
+    defines += [
+      "CRASH_KEY_EXPORT=CRASH_EXPORT",
+      "CRASH_CORE_COMMON_IMPLEMENTATION",
+    ]
+  }
+
+  deps = [
+    ":crash_buildflags",
+    "//base",
+  ]
+
+  if (use_crash_key_stubs || use_cobalt_customizations) {
+    sources += [ "crash_key_stubs.cc" ]
+  } else if (use_crashpad_annotation) {
+    sources += [ "crash_key_crashpad.cc" ]
+    deps += [ "//third_party/crashpad/crashpad/client" ]
+  } else {
+    include_dirs = [ "//third_party/breakpad/breakpad/src" ]
+
+    if (is_ios) {
+      sources += [ "crash_key_breakpad_ios.mm" ]
+
+      configs += [ "//build/config/compiler:enable_arc" ]
+    } else {
+      sources += [
+        "crash_key_breakpad.cc",
+        "crash_key_internal.h",
+      ]
+    }
+
+    deps += [ "//third_party/breakpad:client" ]
+  }
+}
+
+static_library("crash_key_utils") {
+  visibility = [ ":*" ]
+
+  sources = [
+    "crash_keys.cc",
+    "crash_keys.h",
+  ]
+
+  deps = [
+    ":crash_key",
+    "//base",
+  ]
+}
+
+if (is_mac || is_ios) {
+  component("zombies") {
+    visibility = [ ":common" ]
+
+    sources = [
+      "objc_zombie.h",
+      "objc_zombie.mm",
+    ]
+
+    defines = [ "CRASH_CORE_COMMON_IMPLEMENTATION" ]
+
+    deps = [
+      ":crash_key",
+      "//base",
+    ]
+
+    libs = [ "Foundation.framework" ]
+  }
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "crash_key_unittest.cc",
+    "crash_keys_unittest.cc",
+  ]
+
+  deps = [
+    ":common",
+    "//base",
+    "//testing/gtest",
+  ]
+
+  if (is_mac || is_ios) {
+    sources += [ "objc_zombie_unittest.mm" ]
+  }
+
+  if (!is_mac && !is_win && !is_fuchsia) {
+    include_dirs = [ "//third_party/breakpad/breakpad/src/" ]
+    sources += [ "crash_key_breakpad_unittest.cc" ]
+  }
+
+  if (is_fuchsia) {
+    sources -= [ "crash_key_unittest.cc" ]
+  }
+}
diff --git a/components/crash/core/common/DEPS b/components/crash/core/common/DEPS
new file mode 100644
index 0000000..ef81573
--- /dev/null
+++ b/components/crash/core/common/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+third_party/breakpad/breakpad/src/client/ios/Breakpad.h",
+  "+third_party/breakpad/breakpad/src/client/ios/BreakpadController.h",
+]
diff --git a/components/crash/core/common/METADATA b/components/crash/core/common/METADATA
new file mode 100644
index 0000000..554e14d
--- /dev/null
+++ b/components/crash/core/common/METADATA
@@ -0,0 +1,21 @@
+name: "common"
+description:
+  "Filtered subtree at components/crash/core/common."
+
+third_party {
+  url {
+    type: LOCAL_SOURCE
+    value: "https://cobalt.googlesource.com/components/crash/core/common_filtered_mirror"
+  }
+  url {
+    type: GIT
+    value: "https://github.com/chromium/chromium"
+  }
+  # Closest commit hash to m70.
+  version: "15915dc41b2c3f0ff00a744d60a6f6cf25f74a8c"
+  last_upgrade_date {
+    year: 2018
+    month: 03
+    day: 30
+  }
+}
diff --git a/components/crash/core/common/crash_export.h b/components/crash/core/common/crash_export.h
new file mode 100644
index 0000000..84fac02
--- /dev/null
+++ b/components/crash/core/common/crash_export.h
@@ -0,0 +1,34 @@
+// 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.
+
+#ifndef COMPONENTS_CRASH_CORE_COMMON_CRASH_EXPORT_H_
+#define COMPONENTS_CRASH_CORE_COMMON_CRASH_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(CRASH_CORE_COMMON_IMPLEMENTATION)
+#define CRASH_EXPORT __declspec(dllexport)
+#else
+#define CRASH_EXPORT __declspec(dllimport)
+#endif  // defined(CRASH_CORE_COMMON_IMPLEMENTATION)
+
+#else  // defined(WIN32)
+#if defined(CRASH_CORE_COMMON_IMPLEMENTATION)
+#define CRASH_EXPORT __attribute__((visibility("default")))
+#else
+#define CRASH_EXPORT
+#endif  // defined(CRASH_CORE_COMMON_IMPLEMENTATION)
+#endif
+
+#else  // defined(COMPONENT_BUILD)
+#define CRASH_EXPORT
+#endif
+
+// See BUILD.gn :crash_key target for the declaration.
+#if !defined(CRASH_KEY_EXPORT)
+#define CRASH_KEY_EXPORT
+#endif
+
+#endif  // COMPONENTS_CRASH_CORE_COMMON_CRASH_EXPORT_H_
diff --git a/components/crash/core/common/crash_key.cc b/components/crash/core/common/crash_key.cc
new file mode 100644
index 0000000..1e2952d
--- /dev/null
+++ b/components/crash/core/common/crash_key.cc
@@ -0,0 +1,35 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/crash/core/common/crash_key.h"
+
+#include "base/format_macros.h"
+#include "base/strings/stringprintf.h"
+
+namespace crash_reporter {
+namespace internal {
+
+std::string FormatStackTrace(const base::debug::StackTrace& trace,
+                             size_t max_length) {
+  size_t count = 0;
+  const void* const* addresses = trace.Addresses(&count);
+
+  std::string value;
+  for (size_t i = 0; i < count; ++i) {
+    std::string address = base::StringPrintf(
+        "0x%" PRIx64, reinterpret_cast<uint64_t>(addresses[i]));
+    if (value.size() + address.size() > max_length)
+      break;
+    value += address + " ";
+  }
+
+  if (!value.empty() && value.back() == ' ') {
+    value.resize(value.size() - 1);
+  }
+
+  return value;
+}
+
+}  // namespace internal
+}  // namespace crash_reporter
diff --git a/components/crash/core/common/crash_key.h b/components/crash/core/common/crash_key.h
new file mode 100644
index 0000000..c96108a
--- /dev/null
+++ b/components/crash/core/common/crash_key.h
@@ -0,0 +1,229 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CRASH_CORE_COMMON_CRASH_KEY_H_
+#define COMPONENTS_CRASH_CORE_COMMON_CRASH_KEY_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/debug/stack_trace.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "build/build_config.h"
+#include "components/crash/core/common/crash_buildflags.h"
+#include "components/crash/core/common/crash_export.h"
+
+// The crash key interface exposed by this file is the same as the Crashpad
+// Annotation interface. Because not all platforms use Crashpad yet, a
+// source-compatible interface is provided on top of the older Breakpad
+// storage mechanism.
+#if BUILDFLAG(USE_CRASHPAD_ANNOTATION)
+#include "third_party/crashpad/crashpad/client/annotation.h"  // nogncheck
+#endif
+
+namespace crash_reporter {
+
+class CrashKeyBreakpadTest;
+
+// A CrashKeyString stores a name-value pair that will be recorded within a
+// crash report.
+//
+// The crash key name must be a constant string expression, and the value
+// should be unique and identifying. The maximum size for the value is
+// specified as the template argument, and values greater than this are
+// truncated. When specifying a value size, space should be left for the
+// `NUL` byte. Crash keys should be declared with static storage duration.
+//
+// Examples:
+// \code
+//    // This crash key is only set in one function:
+//    void DidNavigate(const GURL& gurl) {
+//      static crash_reporter::CrashKeyString<256> url_key("url");
+//      url_key.Set(gurl.ToString());
+//    }
+//
+//    // This crash key can be set/cleared across different functions:
+//    namespace {
+//    crash_reporter::CrashKeyString<32> g_operation_id("operation-req-id");
+//    }
+//
+//    void OnStartingOperation(const std::string& request_id) {
+//      g_operation_id.Set(request_id);
+//    }
+//
+//    void OnEndingOperation() {
+//      g_operation_id.Clear()
+//    }
+// \endcode
+#if BUILDFLAG(USE_CRASHPAD_ANNOTATION)
+
+template <crashpad::Annotation::ValueSizeType MaxLength>
+using CrashKeyString = crashpad::StringAnnotation<MaxLength>;
+
+#else  // Crashpad-compatible crash key interface:
+
+namespace internal {
+
+constexpr size_t kCrashKeyStorageNumEntries = 200;
+constexpr size_t kCrashKeyStorageValueSize = 128;
+
+// Base implementation of a CrashKeyString for non-Crashpad clients. A separate
+// base class is used to avoid inlining complex logic into the public template
+// API.
+class CRASH_KEY_EXPORT CrashKeyStringImpl {
+ public:
+  constexpr explicit CrashKeyStringImpl(const char name[],
+                                        size_t* index_array,
+                                        size_t index_array_count)
+      : name_(name),
+        index_array_(index_array),
+        index_array_count_(index_array_count) {}
+
+  void Set(base::StringPiece value);
+  void Clear();
+
+  bool is_set() const;
+
+ private:
+  friend class crash_reporter::CrashKeyBreakpadTest;
+
+  // The name of the crash key.
+  const char* const name_;
+
+  // If the crash key is set, this is the index into the storage that can be
+  // used to set/clear the key without requiring a linear scan of the storage
+  // table. This will be |num_entries| if unset.
+  size_t* index_array_;
+  size_t index_array_count_;
+
+  DISALLOW_COPY_AND_ASSIGN(CrashKeyStringImpl);
+};
+
+// This type creates a C array that is initialized with a specific default
+// value, rather than the standard zero-initialized default.
+template <typename T,
+          size_t TotalSize,
+          T DefaultValue,
+          size_t Count,
+          T... Values>
+struct InitializedArrayImpl {
+  using Type = typename InitializedArrayImpl<T,
+                                             TotalSize,
+                                             DefaultValue,
+                                             Count - 1,
+                                             DefaultValue,
+                                             Values...>::Type;
+};
+
+template <typename T, size_t TotalSize, T DefaultValue, T... Values>
+struct InitializedArrayImpl<T, TotalSize, DefaultValue, 0, Values...> {
+  using Type = InitializedArrayImpl<T, TotalSize, DefaultValue, 0, Values...>;
+  T data[TotalSize]{Values...};
+};
+
+template <typename T, size_t ArraySize, T DefaultValue>
+using InitializedArray =
+    typename InitializedArrayImpl<T, ArraySize, DefaultValue, ArraySize>::Type;
+
+}  // namespace internal
+
+template <uint32_t MaxLength>
+class CrashKeyString : public internal::CrashKeyStringImpl {
+ public:
+  constexpr static size_t chunk_count =
+      (MaxLength / internal::kCrashKeyStorageValueSize) + 1;
+
+  // A constructor tag that can be used to initialize a C array of crash keys.
+  enum class Tag { kArray };
+
+  constexpr explicit CrashKeyString(const char name[])
+      : internal::CrashKeyStringImpl(name, indexes_.data, chunk_count) {}
+
+  constexpr CrashKeyString(const char name[], Tag tag) : CrashKeyString(name) {}
+
+ private:
+  // Indexes into the TransitionalCrashKeyStorage for when a value is set.
+  // See the comment in CrashKeyStringImpl for details.
+  // An unset index in the storage is represented by a sentinel value, which
+  // is the total number of entries. This will initialize the array with
+  // that sentinel value as a compile-time expression.
+  internal::InitializedArray<size_t,
+                             chunk_count,
+                             internal::kCrashKeyStorageNumEntries>
+      indexes_;
+
+  DISALLOW_COPY_AND_ASSIGN(CrashKeyString);
+};
+
+#endif
+
+// This scoper clears the specified annotation's value when it goes out of
+// scope.
+//
+// Example:
+//    void DoSomething(const std::string& data) {
+//      static crash_reporter::CrashKeyString<32> crash_key("DoSomething-data");
+//      crash_reporter::ScopedCrashKeyString auto_clear(&crash_key, data);
+//
+//      DoSomethignImpl(data);
+//    }
+class ScopedCrashKeyString {
+ public:
+#if BUILDFLAG(USE_CRASHPAD_ANNOTATION)
+  using CrashKeyType = crashpad::Annotation;
+#else
+  using CrashKeyType = internal::CrashKeyStringImpl;
+#endif
+
+  template <class T>
+  ScopedCrashKeyString(T* crash_key, base::StringPiece value)
+      : crash_key_(crash_key) {
+    crash_key->Set(value);
+  }
+
+  ~ScopedCrashKeyString() { crash_key_->Clear(); }
+
+ private:
+  CrashKeyType* const crash_key_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedCrashKeyString);
+};
+
+namespace internal {
+// Formats a stack trace into a string whose length will not exceed
+// |max_length|. This function ensures no addresses are truncated when
+// being formatted.
+CRASH_KEY_EXPORT std::string FormatStackTrace(
+    const base::debug::StackTrace& trace,
+    size_t max_length);
+}  // namespace internal
+
+// Formats a base::debug::StackTrace as a string of space-separated hexadecimal
+// numbers and stores it in a CrashKeyString.
+// TODO(rsesek): When all clients use Crashpad, traces should become a first-
+// class Annotation type rather than being forced through string conversion.
+template <uint32_t Size>
+void SetCrashKeyStringToStackTrace(CrashKeyString<Size>* key,
+                                   const base::debug::StackTrace& trace) {
+  std::string trace_string = internal::FormatStackTrace(trace, Size);
+  key->Set(trace_string);
+}
+
+// Initializes the crash key subsystem if it is required.
+CRASH_KEY_EXPORT void InitializeCrashKeys();
+
+#if defined(UNIT_TEST) || defined(CRASH_CORE_COMMON_IMPLEMENTATION)
+// Returns a value for the crash key named |key_name|. For Crashpad-based
+// clients, this returns the first instance found of the name.
+CRASH_KEY_EXPORT std::string GetCrashKeyValue(const std::string& key_name);
+
+// Resets crash key state and, depending on the platform, de-initializes
+// the system.
+CRASH_KEY_EXPORT void ResetCrashKeysForTesting();
+#endif
+
+}  // namespace crash_reporter
+
+#endif  // COMPONENTS_CRASH_CORE_COMMON_CRASH_KEY_H_
diff --git a/components/crash/core/common/crash_key_base_support.cc b/components/crash/core/common/crash_key_base_support.cc
new file mode 100644
index 0000000..f9f2cd6
--- /dev/null
+++ b/components/crash/core/common/crash_key_base_support.cc
@@ -0,0 +1,76 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/crash/core/common/crash_key_base_support.h"
+
+#include <memory>
+
+#include "base/debug/crash_logging.h"
+#include "components/crash/core/common/crash_key.h"
+
+namespace crash_reporter {
+
+namespace {
+
+// This stores the value for a crash key allocated through the //base API.
+template <uint32_t ValueSize>
+struct BaseCrashKeyString : public base::debug::CrashKeyString {
+  BaseCrashKeyString(const char name[], base::debug::CrashKeySize size)
+      : base::debug::CrashKeyString(name, size), impl(name) {
+    DCHECK_EQ(static_cast<uint32_t>(size), ValueSize);
+  }
+  crash_reporter::CrashKeyString<ValueSize> impl;
+};
+
+#define SIZE_CLASS_OPERATION(size_class, operation_prefix, operation_suffix) \
+  switch (size_class) {                                                      \
+    case base::debug::CrashKeySize::Size32:                                  \
+      operation_prefix BaseCrashKeyString<32> operation_suffix;              \
+      break;                                                                 \
+    case base::debug::CrashKeySize::Size64:                                  \
+      operation_prefix BaseCrashKeyString<64> operation_suffix;              \
+      break;                                                                 \
+    case base::debug::CrashKeySize::Size256:                                 \
+      operation_prefix BaseCrashKeyString<256> operation_suffix;             \
+      break;                                                                 \
+  }
+
+class CrashKeyBaseSupport : public base::debug::CrashKeyImplementation {
+ public:
+  CrashKeyBaseSupport() = default;
+
+  ~CrashKeyBaseSupport() override = default;
+
+  base::debug::CrashKeyString* Allocate(
+      const char name[],
+      base::debug::CrashKeySize size) override {
+    SIZE_CLASS_OPERATION(size, return new, (name, size));
+    return nullptr;
+  }
+
+  void Set(base::debug::CrashKeyString* crash_key,
+           base::StringPiece value) override {
+    SIZE_CLASS_OPERATION(crash_key->size,
+                         reinterpret_cast<, *>(crash_key)->impl.Set(value));
+  }
+
+  void Clear(base::debug::CrashKeyString* crash_key) override {
+    SIZE_CLASS_OPERATION(crash_key->size,
+                         reinterpret_cast<, *>(crash_key)->impl.Clear());
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CrashKeyBaseSupport);
+};
+
+#undef SIZE_CLASS_OPERATION
+
+}  // namespace
+
+void InitializeCrashKeyBaseSupport() {
+  base::debug::SetCrashKeyImplementation(
+      std::make_unique<CrashKeyBaseSupport>());
+}
+
+}  // namespace crash_reporter
diff --git a/components/crash/core/common/crash_key_base_support.h b/components/crash/core/common/crash_key_base_support.h
new file mode 100644
index 0000000..fcb25ab
--- /dev/null
+++ b/components/crash/core/common/crash_key_base_support.h
@@ -0,0 +1,16 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CRASH_CORE_COMMON_CRASH_KEY_BASE_SUPPORT_H_
+#define COMPONENTS_CRASH_CORE_COMMON_CRASH_KEY_BASE_SUPPORT_H_
+
+namespace crash_reporter {
+
+// This initializes //base to support crash keys via the interface in
+// base/debug/crash_logging.h.
+void InitializeCrashKeyBaseSupport();
+
+}  // namespace crash_reporter
+
+#endif  // COMPONENTS_CRASH_CORE_COMMON_CRASH_KEY_BASE_SUPPORT_H_
diff --git a/components/crash/core/common/crash_key_breakpad.cc b/components/crash/core/common/crash_key_breakpad.cc
new file mode 100644
index 0000000..0351e01
--- /dev/null
+++ b/components/crash/core/common/crash_key_breakpad.cc
@@ -0,0 +1,152 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// NOTE: This file is only compiled when Crashpad is not used as the crash
+// reproter.
+
+#include "components/crash/core/common/crash_key.h"
+
+#include "base/debug/crash_logging.h"
+#include "base/format_macros.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "components/crash/core/common/crash_key_base_support.h"
+#include "components/crash/core/common/crash_key_internal.h"
+
+#if defined(OS_MACOSX) || defined(OS_IOS) || defined(OS_WIN)
+#error "This file should not be used when Crashpad is available, nor on iOS."
+#endif
+
+namespace crash_reporter {
+namespace internal {
+
+namespace {
+
+// String used to format chunked key names. The __1 through __N syntax is
+// recognized by the crash collector, which will then stitch the numbered
+// parts back into a single string value.
+const char kChunkFormatString[] = "%s__%" PRIuS;
+
+static TransitionalCrashKeyStorage* g_storage = nullptr;
+
+constexpr size_t kUnsetStorageSlotSentinel =
+    TransitionalCrashKeyStorage::num_entries;
+
+}  // namespace
+
+TransitionalCrashKeyStorage* GetCrashKeyStorage() {
+  if (!g_storage) {
+    g_storage = new internal::TransitionalCrashKeyStorage();
+  }
+  return g_storage;
+}
+
+void ResetCrashKeyStorageForTesting() {
+  auto* storage = g_storage;
+  g_storage = nullptr;
+  delete storage;
+}
+
+void CrashKeyStringImpl::Set(base::StringPiece value) {
+  const size_t kValueMaxLength = index_array_count_ * kCrashKeyStorageValueSize;
+
+  TransitionalCrashKeyStorage* storage = GetCrashKeyStorage();
+
+  value = value.substr(0, kValueMaxLength);
+
+  // If there is only one slot for the value, then handle it directly.
+  if (index_array_count_ == 1) {
+    std::string value_string = value.as_string();
+    if (is_set()) {
+      storage->SetValueAtIndex(index_array_[0], value_string.c_str());
+    } else {
+      index_array_[0] = storage->SetKeyValue(name_, value_string.c_str());
+    }
+    return;
+  }
+
+  // If the value fits in a single slot, the name of the key should not
+  // end with the __1 suffix of the chunked format.
+  if (value.length() < kCrashKeyStorageValueSize - 1) {
+    if (index_array_[1] != kUnsetStorageSlotSentinel) {
+      // If switching from chunked to non-chunked, clear all the values.
+      Clear();
+      index_array_[0] = storage->SetKeyValue(name_, value.data());
+    } else if (index_array_[0] != kUnsetStorageSlotSentinel) {
+      // The single entry was previously set.
+      storage->SetValueAtIndex(index_array_[0], value.data());
+    } else {
+      // This key was not previously set.
+      index_array_[0] = storage->SetKeyValue(name_, value.data());
+    }
+    return;
+  }
+
+  // If the key was previously set, but only using one slot, then the chunk
+  // name will change (from |name| to |name__1|).
+  if (index_array_[0] != kUnsetStorageSlotSentinel &&
+      index_array_[1] == kUnsetStorageSlotSentinel) {
+    storage->RemoveAtIndex(index_array_[0]);
+    index_array_[0] = kUnsetStorageSlotSentinel;
+  }
+
+  // Otherwise, break the value into chunks labeled name__1 through name__N,
+  // where N is |index_array_count_|.
+  size_t offset = 0;
+  for (size_t i = 0; i < index_array_count_; ++i) {
+    if (offset < value.length()) {
+      // The storage NUL-terminates the value, so ensure that a byte is
+      // not lost when setting individaul chunks.
+      base::StringPiece chunk =
+          value.substr(offset, kCrashKeyStorageValueSize - 1);
+      offset += chunk.length();
+
+      if (index_array_[i] == kUnsetStorageSlotSentinel) {
+        std::string chunk_name =
+            base::StringPrintf(kChunkFormatString, name_, i + 1);
+        index_array_[i] =
+            storage->SetKeyValue(chunk_name.c_str(), chunk.data());
+      } else {
+        storage->SetValueAtIndex(index_array_[i], chunk.data());
+      }
+    } else {
+      storage->RemoveAtIndex(index_array_[i]);
+      index_array_[i] = kUnsetStorageSlotSentinel;
+    }
+  }
+}
+
+void CrashKeyStringImpl::Clear() {
+  for (size_t i = 0; i < index_array_count_; ++i) {
+    GetCrashKeyStorage()->RemoveAtIndex(index_array_[i]);
+    index_array_[i] = kUnsetStorageSlotSentinel;
+  }
+}
+
+bool CrashKeyStringImpl::is_set() const {
+  return index_array_[0] != kUnsetStorageSlotSentinel;
+}
+
+}  // namespace internal
+
+void InitializeCrashKeys() {
+  internal::GetCrashKeyStorage();
+  InitializeCrashKeyBaseSupport();
+}
+
+std::string GetCrashKeyValue(const std::string& key_name) {
+  const char* value =
+      internal::GetCrashKeyStorage()->GetValueForKey(key_name.c_str());
+  if (value)
+    return value;
+  return std::string();
+}
+
+void ResetCrashKeysForTesting() {
+  internal::ResetCrashKeyStorageForTesting();
+  base::debug::SetCrashKeyImplementation(nullptr);
+}
+
+}  // namespace crash_reporter
diff --git a/components/crash/core/common/crash_key_breakpad_ios.mm b/components/crash/core/common/crash_key_breakpad_ios.mm
new file mode 100644
index 0000000..288453b
--- /dev/null
+++ b/components/crash/core/common/crash_key_breakpad_ios.mm
@@ -0,0 +1,94 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/crash/core/common/crash_key.h"
+
+#include <dispatch/dispatch.h>
+
+#include "base/strings/sys_string_conversions.h"
+#include "components/crash/core/common/crash_key_base_support.h"
+#import "third_party/breakpad/breakpad/src/client/ios/Breakpad.h"
+#import "third_party/breakpad/breakpad/src/client/ios/BreakpadController.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// The iOS Breakpad implementation internally uses a LongStringDictionary,
+// which performs the same chunking done by crash_key_breakpad.cc. This class
+// implementation therefore just wraps the iOS Breakpad interface.
+
+namespace crash_reporter {
+namespace internal {
+
+namespace {
+
+// Accessing the BreakpadRef is done on an async queue, so serialize the
+// access to the current thread, as the CrashKeyString API is sync. This
+// matches //ios/chrome/browser/crash_report/breakpad_helper.mm.
+void WithBreakpadRefSync(void (^block)(BreakpadRef ref)) {
+  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
+
+  [[BreakpadController sharedInstance] withBreakpadRef:^(BreakpadRef ref) {
+    block(ref);
+    dispatch_semaphore_signal(semaphore);
+  }];
+  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
+}
+
+}  // namespace
+
+void CrashKeyStringImpl::Set(base::StringPiece value) {
+  NSString* key = base::SysUTF8ToNSString(name_);
+  NSString* value_ns = base::SysUTF8ToNSString(value.as_string());
+
+  WithBreakpadRefSync(^(BreakpadRef ref) {
+    BreakpadAddUploadParameter(ref, key, value_ns);
+  });
+}
+
+void CrashKeyStringImpl::Clear() {
+  NSString* key = base::SysUTF8ToNSString(name_);
+
+  WithBreakpadRefSync(^(BreakpadRef ref) {
+    BreakpadRemoveUploadParameter(ref, key);
+  });
+}
+
+bool CrashKeyStringImpl::is_set() const {
+  __block bool is_set = false;
+  NSString* key = base::SysUTF8ToNSString(
+      std::string(BREAKPAD_SERVER_PARAMETER_PREFIX) + name_);
+
+  WithBreakpadRefSync(^(BreakpadRef ref) {
+    is_set = BreakpadKeyValue(ref, key) != nil;
+  });
+
+  return is_set;
+}
+
+}  // namespace internal
+
+void InitializeCrashKeys() {
+  InitializeCrashKeyBaseSupport();
+}
+
+std::string GetCrashKeyValue(const std::string& key_name) {
+  __block NSString* value;
+  NSString* key = base::SysUTF8ToNSString(
+      std::string(BREAKPAD_SERVER_PARAMETER_PREFIX) + key_name);
+
+  internal::WithBreakpadRefSync(^(BreakpadRef ref) {
+    value = BreakpadKeyValue(ref, key);
+  });
+
+  return base::SysNSStringToUTF8(value);
+}
+
+void ResetCrashKeysForTesting() {
+  // There's no way to do this on iOS without tearing down the
+  // BreakpadController.
+}
+
+}  // namespace crash_reporter
diff --git a/components/crash/core/common/crash_key_breakpad_unittest.cc b/components/crash/core/common/crash_key_breakpad_unittest.cc
new file mode 100644
index 0000000..e077766
--- /dev/null
+++ b/components/crash/core/common/crash_key_breakpad_unittest.cc
@@ -0,0 +1,198 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/crash/core/common/crash_key.h"
+
+#include "components/crash/core/common/crash_key_internal.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace crash_reporter {
+
+class CrashKeyBreakpadTest : public testing::Test {
+ public:
+  void SetUp() override {
+    InitializeCrashKeys();
+    ASSERT_TRUE(internal::GetCrashKeyStorage());
+  }
+
+  void TearDown() override { internal::ResetCrashKeyStorageForTesting(); }
+
+  internal::TransitionalCrashKeyStorage* storage() {
+    return internal::GetCrashKeyStorage();
+  }
+
+  size_t* GetIndexArray(internal::CrashKeyStringImpl* key) {
+    return key->index_array_;
+  }
+  size_t GetIndexArrayCount(internal::CrashKeyStringImpl* key) {
+    return key->index_array_count_;
+  }
+};
+
+TEST_F(CrashKeyBreakpadTest, ConstantAssertions) {
+  // Tests in this file generate and validate data based on constants
+  // having specific values. This test asserts those assumptions.
+  EXPECT_EQ(128u, internal::kCrashKeyStorageValueSize);
+}
+
+TEST_F(CrashKeyBreakpadTest, Allocation) {
+  const size_t kSentinel = internal::kCrashKeyStorageNumEntries;
+
+  static CrashKeyString<32> key1("short");
+  ASSERT_EQ(1u, GetIndexArrayCount(&key1));
+  auto* indexes = GetIndexArray(&key1);
+  EXPECT_EQ(kSentinel, indexes[0]);
+
+  // An extra index slot is created for lengths equal to the value size.
+  static CrashKeyString<128> key2("extra");
+  ASSERT_EQ(2u, GetIndexArrayCount(&key2));
+  indexes = GetIndexArray(&key2);
+  EXPECT_EQ(kSentinel, indexes[0]);
+  EXPECT_EQ(kSentinel, indexes[1]);
+
+  static CrashKeyString<395> key3("large");
+  ASSERT_EQ(4u, GetIndexArrayCount(&key3));
+  indexes = GetIndexArray(&key3);
+  EXPECT_EQ(kSentinel, indexes[0]);
+  EXPECT_EQ(kSentinel, indexes[1]);
+  EXPECT_EQ(kSentinel, indexes[2]);
+  EXPECT_EQ(kSentinel, indexes[3]);
+}
+
+TEST_F(CrashKeyBreakpadTest, SetClearSingle) {
+  static CrashKeyString<32> key("test-key");
+
+  EXPECT_FALSE(storage()->GetValueForKey("test-key"));
+  EXPECT_EQ(0u, storage()->GetCount());
+
+  key.Set("value");
+
+  ASSERT_EQ(1u, storage()->GetCount());
+  EXPECT_STREQ("value", storage()->GetValueForKey("test-key"));
+
+  key.Set("value 2");
+
+  ASSERT_EQ(1u, storage()->GetCount());
+  EXPECT_STREQ("value 2", storage()->GetValueForKey("test-key"));
+
+  key.Clear();
+
+  EXPECT_FALSE(storage()->GetValueForKey("test-key"));
+  EXPECT_EQ(0u, storage()->GetCount());
+}
+
+TEST_F(CrashKeyBreakpadTest, SetChunked) {
+  std::string chunk1(128, 'A');
+  std::string chunk2(128, 'B');
+  std::string chunk3(128, 'C');
+
+  static CrashKeyString<400> key("chunky");
+
+  EXPECT_EQ(0u, storage()->GetCount());
+
+  key.Set((chunk1 + chunk2 + chunk3).c_str());
+
+  ASSERT_EQ(4u, storage()->GetCount());
+
+  // Since chunk1 through chunk3 are the same size as a storage slot,
+  // and the storage NUL-terminates the value, ensure no bytes are
+  // lost when chunking.
+  EXPECT_EQ(std::string(127, 'A'), storage()->GetValueForKey("chunky__1"));
+  EXPECT_EQ(std::string("A") + std::string(126, 'B'),
+            storage()->GetValueForKey("chunky__2"));
+  EXPECT_EQ(std::string(2, 'B') + std::string(125, 'C'),
+            storage()->GetValueForKey("chunky__3"));
+  EXPECT_EQ(std::string(3, 'C'), storage()->GetValueForKey("chunky__4"));
+
+  std::string chunk4(240, 'D');
+
+  key.Set(chunk4.c_str());
+
+  ASSERT_EQ(2u, storage()->GetCount());
+
+  EXPECT_EQ(std::string(127, 'D'), storage()->GetValueForKey("chunky__1"));
+  EXPECT_EQ(std::string(240 - 127, 'D'),
+            storage()->GetValueForKey("chunky__2"));
+  EXPECT_FALSE(storage()->GetValueForKey("chunky__3"));
+
+  key.Clear();
+
+  EXPECT_EQ(0u, storage()->GetCount());
+}
+
+TEST_F(CrashKeyBreakpadTest, SetTwoChunked) {
+  static CrashKeyString<600> key1("big");
+  static CrashKeyString<256> key2("small");
+
+  EXPECT_EQ(0u, storage()->GetCount());
+
+  key1.Set(std::string(200, '1').c_str());
+
+  ASSERT_EQ(2u, storage()->GetCount());
+
+  EXPECT_EQ(std::string(127, '1'), storage()->GetValueForKey("big__1"));
+  EXPECT_EQ(std::string(73, '1'), storage()->GetValueForKey("big__2"));
+
+  key2.Set(std::string(256, '2').c_str());
+
+  ASSERT_EQ(5u, storage()->GetCount());
+
+  EXPECT_EQ(std::string(127, '1'), storage()->GetValueForKey("big__1"));
+  EXPECT_EQ(std::string(73, '1'), storage()->GetValueForKey("big__2"));
+  EXPECT_EQ(std::string(127, '2'), storage()->GetValueForKey("small__1"));
+  EXPECT_EQ(std::string(127, '2'), storage()->GetValueForKey("small__2"));
+  EXPECT_EQ(std::string(2, '2'), storage()->GetValueForKey("small__3"));
+
+  key1.Set(std::string(510, '3').c_str());
+
+  ASSERT_EQ(8u, storage()->GetCount());
+
+  EXPECT_EQ(std::string(127, '3'), storage()->GetValueForKey("big__1"));
+  EXPECT_EQ(std::string(127, '3'), storage()->GetValueForKey("big__2"));
+  EXPECT_EQ(std::string(127, '3'), storage()->GetValueForKey("big__3"));
+  EXPECT_EQ(std::string(127, '3'), storage()->GetValueForKey("big__4"));
+  EXPECT_EQ(std::string(2, '3'), storage()->GetValueForKey("big__5"));
+  EXPECT_EQ(std::string(127, '2'), storage()->GetValueForKey("small__1"));
+  EXPECT_EQ(std::string(127, '2'), storage()->GetValueForKey("small__2"));
+  EXPECT_EQ(std::string(2, '2'), storage()->GetValueForKey("small__3"));
+
+  key2.Clear();
+
+  ASSERT_EQ(5u, storage()->GetCount());
+
+  EXPECT_EQ(std::string(127, '3'), storage()->GetValueForKey("big__1"));
+  EXPECT_EQ(std::string(127, '3'), storage()->GetValueForKey("big__2"));
+  EXPECT_EQ(std::string(127, '3'), storage()->GetValueForKey("big__3"));
+  EXPECT_EQ(std::string(127, '3'), storage()->GetValueForKey("big__4"));
+  EXPECT_EQ(std::string(2, '3'), storage()->GetValueForKey("big__5"));
+}
+
+TEST_F(CrashKeyBreakpadTest, ChunkSingleEntry) {
+  static CrashKeyString<200> crash_key("split");
+
+  EXPECT_EQ(0u, storage()->GetCount());
+
+  crash_key.Set("test");
+
+  ASSERT_EQ(1u, storage()->GetCount());
+  EXPECT_STREQ("test", storage()->GetValueForKey("split"));
+
+  crash_key.Set(std::string(127, 'z') + "bloop");
+
+  ASSERT_EQ(2u, storage()->GetCount());
+  EXPECT_EQ(std::string(127, 'z'), storage()->GetValueForKey("split__1"));
+  EXPECT_STREQ("bloop", storage()->GetValueForKey("split__2"));
+
+  crash_key.Set("abcdefg");
+
+  ASSERT_EQ(1u, storage()->GetCount());
+  EXPECT_STREQ("abcdefg", storage()->GetValueForKey("split"));
+
+  crash_key.Set("hijklmnop");
+
+  ASSERT_EQ(1u, storage()->GetCount());
+  EXPECT_STREQ("hijklmnop", storage()->GetValueForKey("split"));
+}
+
+}  // namespace crash_reporter
diff --git a/components/crash/core/common/crash_key_crashpad.cc b/components/crash/core/common/crash_key_crashpad.cc
new file mode 100644
index 0000000..929392e
--- /dev/null
+++ b/components/crash/core/common/crash_key_crashpad.cc
@@ -0,0 +1,51 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// NOTE: This file is only compiled when Crashpad is used as the crash
+// reproter.
+
+#include "components/crash/core/common/crash_key.h"
+
+#include "base/debug/crash_logging.h"
+#include "components/crash/core/common/crash_key_base_support.h"
+#include "third_party/crashpad/crashpad/client/annotation_list.h"
+#include "third_party/crashpad/crashpad/client/crashpad_info.h"
+
+namespace crash_reporter {
+
+void InitializeCrashKeys() {
+  crashpad::AnnotationList::Register();
+  InitializeCrashKeyBaseSupport();
+}
+
+// Returns a value for the crash key named |key_name|. For Crashpad-based
+// clients, this returns the first instance found of the name.
+std::string GetCrashKeyValue(const std::string& key_name) {
+  auto* annotation_list = crashpad::AnnotationList::Get();
+  if (annotation_list) {
+    for (crashpad::Annotation* annotation : *annotation_list) {
+      if (key_name == annotation->name()) {
+        return std::string(static_cast<const char*>(annotation->value()),
+                           annotation->size());
+      }
+    }
+  }
+
+  return std::string();
+}
+
+void ResetCrashKeysForTesting() {
+  // The AnnotationList should not be deleted because the static Annotation
+  // object data still reference the link nodes.
+  auto* annotation_list = crashpad::AnnotationList::Get();
+  if (annotation_list) {
+    for (crashpad::Annotation* annotation : *annotation_list) {
+      annotation->Clear();
+    }
+  }
+
+  base::debug::SetCrashKeyImplementation(nullptr);
+}
+
+}  // namespace crash_reporter
diff --git a/components/crash/core/common/crash_key_internal.h b/components/crash/core/common/crash_key_internal.h
new file mode 100644
index 0000000..817b16a
--- /dev/null
+++ b/components/crash/core/common/crash_key_internal.h
@@ -0,0 +1,26 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CRASH_CORE_COMMON_CRASH_KEY_INTERNAL_H_
+#define COMPONENTS_CRASH_CORE_COMMON_CRASH_KEY_INTERNAL_H_
+
+#include "components/crash/core/common/crash_export.h"
+#include "components/crash/core/common/crash_key.h"
+#include "third_party/breakpad/breakpad/src/common/simple_string_dictionary.h"
+
+namespace crash_reporter {
+namespace internal {
+
+using TransitionalCrashKeyStorage = google_breakpad::
+    NonAllocatingMap<40, kCrashKeyStorageValueSize, kCrashKeyStorageNumEntries>;
+
+// Accesses the underlying storage for crash keys for non-Crashpad clients.
+CRASH_KEY_EXPORT TransitionalCrashKeyStorage* GetCrashKeyStorage();
+
+CRASH_KEY_EXPORT void ResetCrashKeyStorageForTesting();
+
+}  // namespace internal
+}  // namespace crash_reporter
+
+#endif  // COMPONENTS_CRASH_CORE_COMMON_CRASH_KEY_INTERNAL_H_
diff --git a/components/crash/core/common/crash_key_stubs.cc b/components/crash/core/common/crash_key_stubs.cc
new file mode 100644
index 0000000..5663eb8
--- /dev/null
+++ b/components/crash/core/common/crash_key_stubs.cc
@@ -0,0 +1,37 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is only used for OS_FUCHSIA, since there is no crash reporter
+// for that platform.
+
+#include "build/build_config.h"
+#include "components/crash/core/common/crash_key.h"
+
+#if !defined(STARBOARD) && !BUILDFLAG(USE_CRASH_KEY_STUBS)
+#error "This file should only be compiled when using stubs."
+#endif
+
+namespace crash_reporter {
+
+namespace internal {
+
+void CrashKeyStringImpl::Set(base::StringPiece value) {}
+
+void CrashKeyStringImpl::Clear() {}
+
+bool CrashKeyStringImpl::is_set() const {
+  return false;
+}
+
+}  // namespace internal
+
+void InitializeCrashKeys() {}
+
+std::string GetCrashKeyValue(const std::string& key_name) {
+  return std::string();
+}
+
+void ResetCrashKeysForTesting() {}
+
+}  // namespace crash_reporter
diff --git a/components/crash/core/common/crash_key_unittest.cc b/components/crash/core/common/crash_key_unittest.cc
new file mode 100644
index 0000000..466eb25
--- /dev/null
+++ b/components/crash/core/common/crash_key_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/crash/core/common/crash_key.h"
+
+#include "base/debug/crash_logging.h"
+#include "base/debug/stack_trace.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace crash_reporter {
+namespace {
+
+class CrashKeyStringTest : public testing::Test {
+ public:
+  void SetUp() override { InitializeCrashKeys(); }
+};
+
+TEST_F(CrashKeyStringTest, ScopedCrashKeyString) {
+  static CrashKeyString<32> key("test-scope");
+
+  EXPECT_FALSE(key.is_set());
+
+  {
+    ScopedCrashKeyString scoper(&key, "value");
+    EXPECT_TRUE(key.is_set());
+  }
+
+  EXPECT_FALSE(key.is_set());
+}
+
+TEST_F(CrashKeyStringTest, FormatStackTrace) {
+  const uintptr_t addresses[] = {
+      0x0badbeef, 0x77778888, 0xabc, 0x000ddeeff, 0x12345678,
+  };
+  base::debug::StackTrace trace(reinterpret_cast<const void* const*>(addresses),
+                                arraysize(addresses));
+
+  std::string too_small = internal::FormatStackTrace(trace, 3);
+  EXPECT_EQ(0u, too_small.size());
+
+  std::string one_value = internal::FormatStackTrace(trace, 16);
+  EXPECT_EQ("0xbadbeef", one_value);
+
+  std::string three_values = internal::FormatStackTrace(trace, 30);
+  EXPECT_EQ("0xbadbeef 0x77778888 0xabc", three_values);
+
+  std::string all_values = internal::FormatStackTrace(trace, 128);
+  EXPECT_EQ("0xbadbeef 0x77778888 0xabc 0xddeeff 0x12345678", all_values);
+}
+
+#if defined(ARCH_CPU_64_BITS)
+TEST_F(CrashKeyStringTest, FormatStackTrace64) {
+  const uintptr_t addresses[] = {
+      0xbaaaabaaaaba, 0x1000000000000000,
+  };
+  base::debug::StackTrace trace(reinterpret_cast<const void* const*>(addresses),
+                                arraysize(addresses));
+
+  std::string too_small = internal::FormatStackTrace(trace, 8);
+  EXPECT_EQ(0u, too_small.size());
+
+  std::string one_value = internal::FormatStackTrace(trace, 20);
+  EXPECT_EQ("0xbaaaabaaaaba", one_value);
+
+  std::string all_values = internal::FormatStackTrace(trace, 35);
+  EXPECT_EQ("0xbaaaabaaaaba 0x1000000000000000", all_values);
+}
+#endif
+
+// In certain build configurations, StackTrace will produce an
+// empty result, which will cause the test to fail.
+#if !defined(OFFICIAL_BUILD) && !defined(NO_UNWIND_TABLES)
+TEST_F(CrashKeyStringTest, SetStackTrace) {
+  static CrashKeyString<1024> key("test-trace");
+
+  EXPECT_FALSE(key.is_set());
+
+  SetCrashKeyStringToStackTrace(&key, base::debug::StackTrace());
+
+  EXPECT_TRUE(key.is_set());
+}
+#endif
+
+TEST_F(CrashKeyStringTest, BaseSupport) {
+  static base::debug::CrashKeyString* crash_key =
+      base::debug::AllocateCrashKeyString("base-support",
+                                          base::debug::CrashKeySize::Size64);
+
+  EXPECT_TRUE(crash_key);
+
+  base::debug::SetCrashKeyString(crash_key, "this is a test");
+
+  base::debug::ClearCrashKeyString(crash_key);
+
+  base::debug::SetCrashKeyString(crash_key, std::string(128, 'b'));
+  base::debug::SetCrashKeyString(crash_key, std::string(64, 'a'));
+}
+
+TEST_F(CrashKeyStringTest, CArrayInitializer) {
+  static CrashKeyString<8> keys[] = {
+      {"test-1", CrashKeyString<8>::Tag::kArray},
+      {"test-2", CrashKeyString<8>::Tag::kArray},
+      {"test-3", CrashKeyString<8>::Tag::kArray},
+  };
+
+  EXPECT_FALSE(keys[0].is_set());
+  EXPECT_FALSE(keys[1].is_set());
+  EXPECT_FALSE(keys[2].is_set());
+
+  keys[1].Set("test");
+
+  EXPECT_FALSE(keys[0].is_set());
+  EXPECT_TRUE(keys[1].is_set());
+  EXPECT_FALSE(keys[2].is_set());
+}
+
+}  // namespace
+}  // namespace crash_reporter
diff --git a/components/crash/core/common/crash_keys.cc b/components/crash/core/common/crash_keys.cc
new file mode 100644
index 0000000..bbb0af7
--- /dev/null
+++ b/components/crash/core/common/crash_keys.cc
@@ -0,0 +1,147 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/crash/core/common/crash_keys.h"
+
+#include <deque>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/format_macros.h"
+#include "base/no_destructor.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "components/crash/core/common/crash_key.h"
+
+namespace crash_keys {
+
+namespace {
+
+#if defined(OS_MACOSX) || defined(OS_WIN)
+// When using Crashpad, the crash reporting client ID is the responsibility of
+// Crashpad. It is not set directly by Chrome. To make the metrics client ID
+// available on the server, it's stored in a distinct key.
+const char kMetricsClientId[] = "metrics_client_id";
+#else
+// When using Breakpad instead of Crashpad, the crash reporting client ID is the
+// same as the metrics client ID.
+const char kMetricsClientId[] = "guid";
+#endif
+
+crash_reporter::CrashKeyString<40> client_id_key(kMetricsClientId);
+
+}  // namespace
+
+void SetMetricsClientIdFromGUID(const std::string& metrics_client_guid) {
+  std::string stripped_guid(metrics_client_guid);
+  // Remove all instance of '-' char from the GUID. So BCD-WXY becomes BCDWXY.
+  base::ReplaceSubstringsAfterOffset(
+      &stripped_guid, 0, "-", base::StringPiece());
+  if (stripped_guid.empty())
+    return;
+
+  client_id_key.Set(stripped_guid);
+}
+
+void ClearMetricsClientId() {
+#if defined(OS_MACOSX) || defined(OS_WIN)
+  // Crashpad always monitors for crashes, but doesn't upload them when
+  // crash reporting is disabled. The preference to upload crash reports is
+  // linked to the preference for metrics reporting. When metrics reporting is
+  // disabled, don't put the metrics client ID into crash dumps. This way, crash
+  // reports that are saved but not uploaded will not have a metrics client ID
+  // from the time that metrics reporting was disabled even if they are uploaded
+  // by user action at a later date.
+  //
+  // Breakpad cannot be enabled or disabled without an application restart, and
+  // it needs to use the metrics client ID as its stable crash client ID, so
+  // leave its client ID intact even when metrics reporting is disabled while
+  // the application is running.
+  client_id_key.Clear();
+#endif
+}
+
+using SwitchesCrashKeys = std::deque<crash_reporter::CrashKeyString<64>>;
+SwitchesCrashKeys& GetSwitchesCrashKeys() {
+  static base::NoDestructor<SwitchesCrashKeys> switches_keys;
+  return *switches_keys;
+}
+
+static crash_reporter::CrashKeyString<4> num_switches_key("num-switches");
+
+void SetSwitchesFromCommandLine(const base::CommandLine& command_line,
+                                SwitchFilterFunction skip_filter) {
+  const base::CommandLine::StringVector& argv = command_line.argv();
+
+  // Set the number of switches in case of uninteresting switches in
+  // command_line.
+  num_switches_key.Set(base::NumberToString(argv.size() - 1));
+
+  size_t key_i = 0;
+
+  // Go through the argv, skipping the exec path. Stop if there are too many
+  // switches to hold in crash keys.
+  for (size_t i = 1; i < argv.size(); ++i) {
+#if defined(OS_WIN)
+    std::string switch_str = base::WideToUTF8(argv[i]);
+#else
+    std::string switch_str = argv[i];
+#endif
+
+    // Skip uninteresting switches.
+    if (skip_filter && (*skip_filter)(switch_str))
+      continue;
+
+    if (key_i >= GetSwitchesCrashKeys().size()) {
+      static base::NoDestructor<std::deque<std::string>> crash_keys_names;
+      crash_keys_names->emplace_back(
+          base::StringPrintf("switch-%" PRIuS, key_i + 1));
+      GetSwitchesCrashKeys().emplace_back(crash_keys_names->back().c_str());
+    }
+    GetSwitchesCrashKeys()[key_i++].Set(switch_str);
+  }
+
+  // Clear any remaining switches.
+  for (; key_i < GetSwitchesCrashKeys().size(); ++key_i)
+    GetSwitchesCrashKeys()[key_i].Clear();
+}
+
+void ResetCommandLineForTesting() {
+  num_switches_key.Clear();
+  for (auto& key : GetSwitchesCrashKeys()) {
+    key.Clear();
+  }
+}
+
+using PrinterInfoKey = crash_reporter::CrashKeyString<64>;
+static PrinterInfoKey printer_info_keys[] = {
+    {"prn-info-1", PrinterInfoKey::Tag::kArray},
+    {"prn-info-2", PrinterInfoKey::Tag::kArray},
+    {"prn-info-3", PrinterInfoKey::Tag::kArray},
+    {"prn-info-4", PrinterInfoKey::Tag::kArray},
+};
+
+ScopedPrinterInfo::ScopedPrinterInfo(base::StringPiece data) {
+  std::vector<base::StringPiece> info = base::SplitStringPiece(
+      data, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  for (size_t i = 0; i < arraysize(printer_info_keys); ++i) {
+    if (i < info.size())
+      printer_info_keys[i].Set(info[i]);
+    else
+      printer_info_keys[i].Clear();
+  }
+}
+
+ScopedPrinterInfo::~ScopedPrinterInfo() {
+  for (auto& crash_key : printer_info_keys) {
+    crash_key.Clear();
+  }
+}
+
+}  // namespace crash_keys
diff --git a/components/crash/core/common/crash_keys.h b/components/crash/core/common/crash_keys.h
new file mode 100644
index 0000000..20eb442
--- /dev/null
+++ b/components/crash/core/common/crash_keys.h
@@ -0,0 +1,50 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CRASH_CORE_COMMON_CRASH_KEYS_H_
+#define COMPONENTS_CRASH_CORE_COMMON_CRASH_KEYS_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+
+namespace base {
+class CommandLine;
+}  // namespace base
+
+namespace crash_keys {
+
+// Sets the ID (which may either be a full GUID or a GUID that was already
+// stripped from its dashes -- in either case this method will strip remaining
+// dashes before setting the crash key).
+void SetMetricsClientIdFromGUID(const std::string& metrics_client_guid);
+void ClearMetricsClientId();
+
+// A function returning true if |flag| is a switch that should be filtered out
+// of crash keys.
+using SwitchFilterFunction = bool (*)(const std::string& flag);
+
+// Sets the "num-switches" key and a set of keys named using kSwitchFormat based
+// on the given |command_line|. If |skip_filter| is not null, ignore any switch
+// for which it returns true.
+void SetSwitchesFromCommandLine(const base::CommandLine& command_line,
+                                SwitchFilterFunction skip_filter);
+
+// Clears all the CommandLine-related crash keys.
+void ResetCommandLineForTesting();
+
+// Sets the printer info. Data should be separated by ';' up to 4 substrings.
+// Each substring will be truncated if necessary.
+class ScopedPrinterInfo {
+ public:
+  explicit ScopedPrinterInfo(base::StringPiece data);
+  ~ScopedPrinterInfo();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedPrinterInfo);
+};
+
+}  // namespace crash_keys
+
+#endif  // COMPONENTS_CRASH_CORE_COMMON_CRASH_KEYS_H_
diff --git a/components/crash/core/common/crash_keys_unittest.cc b/components/crash/core/common/crash_keys_unittest.cc
new file mode 100644
index 0000000..603cc36
--- /dev/null
+++ b/components/crash/core/common/crash_keys_unittest.cc
@@ -0,0 +1,113 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/crash/core/common/crash_keys.h"
+
+#include <string>
+
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/debug/crash_logging.h"
+#include "base/format_macros.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "components/crash/core/common/crash_key.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using crash_reporter::GetCrashKeyValue;
+
+// The number of switch-N keys declared in SetSwitchesFromCommandLine().
+constexpr int kSwitchesMaxCount = 15;
+
+class CrashKeysTest : public testing::Test {
+ public:
+  void SetUp() override {
+    ResetData();
+    crash_reporter::InitializeCrashKeys();
+  }
+
+  void TearDown() override {
+    ResetData();
+  }
+
+ private:
+  void ResetData() {
+    crash_keys::ResetCommandLineForTesting();
+    crash_reporter::ResetCrashKeysForTesting();
+  }
+};
+
+TEST_F(CrashKeysTest, Switches) {
+  // Set three switches.
+  {
+    base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+    for (size_t i = 1; i <= 3; ++i)
+      command_line.AppendSwitch(base::StringPrintf("--flag-%" PRIuS, i));
+    crash_keys::SetSwitchesFromCommandLine(command_line, nullptr);
+    EXPECT_EQ("--flag-1", GetCrashKeyValue("switch-1"));
+    EXPECT_EQ("--flag-2", GetCrashKeyValue("switch-2"));
+    EXPECT_EQ("--flag-3", GetCrashKeyValue("switch-3"));
+    EXPECT_TRUE(GetCrashKeyValue("switch-4").empty());
+  }
+
+  // Set more than 15 switches.
+  {
+    base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+    const size_t kMax = kSwitchesMaxCount + 2;
+    EXPECT_GT(kMax, static_cast<size_t>(15));
+    for (size_t i = 1; i <= kMax; ++i)
+      command_line.AppendSwitch(base::StringPrintf("--many-%" PRIuS, i));
+    crash_keys::SetSwitchesFromCommandLine(command_line, nullptr);
+    EXPECT_EQ("--many-1", GetCrashKeyValue("switch-1"));
+    EXPECT_EQ("--many-9", GetCrashKeyValue("switch-9"));
+    EXPECT_EQ("--many-15", GetCrashKeyValue("switch-15"));
+    EXPECT_FALSE(GetCrashKeyValue("switch-16").empty());
+    EXPECT_FALSE(GetCrashKeyValue("switch-17").empty());
+  }
+
+  // Set fewer to ensure that old ones are erased.
+  {
+    base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+    for (int i = 1; i <= 5; ++i)
+      command_line.AppendSwitch(base::StringPrintf("--fewer-%d", i));
+    crash_keys::SetSwitchesFromCommandLine(command_line, nullptr);
+    EXPECT_EQ("--fewer-1", GetCrashKeyValue("switch-1"));
+    EXPECT_EQ("--fewer-2", GetCrashKeyValue("switch-2"));
+    EXPECT_EQ("--fewer-3", GetCrashKeyValue("switch-3"));
+    EXPECT_EQ("--fewer-4", GetCrashKeyValue("switch-4"));
+    EXPECT_EQ("--fewer-5", GetCrashKeyValue("switch-5"));
+    for (int i = 6; i < 20; ++i)
+      EXPECT_TRUE(GetCrashKeyValue(base::StringPrintf("switch-%d", i)).empty());
+  }
+}
+
+namespace {
+
+bool IsBoringFlag(const std::string& flag) {
+  return flag.compare("--boring") == 0;
+}
+
+}  // namespace
+
+TEST_F(CrashKeysTest, FilterFlags) {
+  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+  command_line.AppendSwitch("--not-boring-1");
+  command_line.AppendSwitch("--boring");
+
+  // Include the max number of non-boring switches, to make sure that only the
+  // switches actually included in the crash keys are counted.
+  for (size_t i = 2; i <= kSwitchesMaxCount; ++i)
+    command_line.AppendSwitch(base::StringPrintf("--not-boring-%" PRIuS, i));
+
+  crash_keys::SetSwitchesFromCommandLine(command_line, &IsBoringFlag);
+
+  // If the boring keys are filtered out, every single key should now be
+  // not-boring.
+  for (int i = 1; i <= kSwitchesMaxCount; ++i) {
+    std::string switch_name = base::StringPrintf("switch-%d", i);
+    std::string switch_value = base::StringPrintf("--not-boring-%d", i);
+    EXPECT_EQ(switch_value, GetCrashKeyValue(switch_name))
+        << "switch_name is " << switch_name;
+  }
+}
diff --git a/components/crash/core/common/objc_zombie.h b/components/crash/core/common/objc_zombie.h
new file mode 100644
index 0000000..f1aa8e3
--- /dev/null
+++ b/components/crash/core/common/objc_zombie.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_CRASH_CORE_COMMON_OBJC_ZOMBIE_H_
+#define COMPONENTS_CRASH_CORE_COMMON_OBJC_ZOMBIE_H_
+
+#include <stddef.h>
+
+#include "build/build_config.h"
+#include "components/crash/core/common/crash_export.h"
+
+// You should think twice every single time you use anything from this
+// namespace.
+namespace ObjcEvilDoers {
+
+// Enable zombie object debugging. This implements a variant of Apple's
+// NSZombieEnabled which can help expose use-after-free errors where messages
+// are sent to freed Objective-C objects in production builds.
+//
+// Returns NO if it fails to enable.
+//
+// When |zombieAllObjects| is YES, all objects inheriting from
+// NSObject become zombies on -dealloc.  If NO, -shouldBecomeCrZombie
+// is queried to determine whether to make the object a zombie.
+//
+// |zombieCount| controls how many zombies to store before freeing the
+// oldest.  Set to 0 to free objects immediately after making them
+// zombies.
+bool CRASH_EXPORT ZombieEnable(bool zombieAllObjects, size_t zombieCount);
+
+// Disable zombies.
+void CRASH_EXPORT ZombieDisable();
+
+}  // namespace ObjcEvilDoers
+
+#if defined(OS_MACOSX)
+#if defined(__OBJC__)
+
+#import <Foundation/Foundation.h>
+
+@interface NSObject (CrZombie)
+- (BOOL)shouldBecomeCrZombie;
+@end
+
+#endif  // __OBJC__
+#endif  // OS_MACOSX
+
+#endif  // COMPONENTS_CRASH_CORE_COMMON_OBJC_ZOMBIE_H_
diff --git a/components/crash/core/common/objc_zombie.mm b/components/crash/core/common/objc_zombie.mm
new file mode 100644
index 0000000..9af0cee
--- /dev/null
+++ b/components/crash/core/common/objc_zombie.mm
@@ -0,0 +1,434 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "components/crash/core/common/objc_zombie.h"
+
+#include <AvailabilityMacros.h>
+#include <string.h>
+
+#include <execinfo.h>
+#import <objc/runtime.h>
+
+#include <algorithm>
+
+#include "base/debug/stack_trace.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
+#include "build/build_config.h"
+#include "components/crash/core/common/crash_key.h"
+
+// Deallocated objects are re-classed as |CrZombie|.  No superclass
+// because then the class would have to override many/most of the
+// inherited methods (|NSObject| is like a category magnet!).
+// Without the __attribute__, clang's -Wobjc-root-class warns on the missing
+// superclass.
+__attribute__((objc_root_class))
+@interface CrZombie  {
+  Class isa;
+}
+@end
+
+// Objects with enough space are made into "fat" zombies, which
+// directly remember which class they were until reallocated.
+@interface CrFatZombie : CrZombie {
+ @public
+  Class wasa;
+}
+@end
+
+namespace {
+
+// The depth of backtrace to store with zombies.  This directly influences
+// the amount of memory required to track zombies, so should be kept as
+// small as is useful.  Unfortunately, too small and it won't poke through
+// deep autorelease and event loop stacks.
+// NOTE(shess): Breakpad currently restricts values to 255 bytes.  The
+// trace is hex-encoded with "0x" prefix and " " separators, meaning
+// the maximum number of 32-bit items which can be encoded is 23.
+const size_t kBacktraceDepth = 20;
+
+// The original implementation for |-[NSObject dealloc]|.
+IMP g_originalDeallocIMP = NULL;
+
+// Classes which freed objects become.  |g_fatZombieSize| is the
+// minimum object size which can be made into a fat zombie (which can
+// remember which class it was before free, even after falling off the
+// treadmill).
+Class g_zombieClass = Nil;  // cached [CrZombie class]
+Class g_fatZombieClass = Nil;  // cached [CrFatZombie class]
+size_t g_fatZombieSize = 0;
+
+// Whether to zombie all freed objects, or only those which return YES
+// from |-shouldBecomeCrZombie|.
+BOOL g_zombieAllObjects = NO;
+
+// Protects |g_zombieCount|, |g_zombieIndex|, and |g_zombies|.
+base::Lock& GetLock() {
+  static auto* lock = new base::Lock();
+  return *lock;
+}
+
+// How many zombies to keep before freeing, and the current head of
+// the circular buffer.
+size_t g_zombieCount = 0;
+size_t g_zombieIndex = 0;
+
+typedef struct {
+  id object;   // The zombied object.
+  Class wasa;  // Value of |object->isa| before we replaced it.
+  void* trace[kBacktraceDepth];  // Backtrace at point of deallocation.
+  size_t traceDepth;             // Actual depth of trace[].
+} ZombieRecord;
+
+ZombieRecord* g_zombies = NULL;
+
+// Replacement |-dealloc| which turns objects into zombies and places
+// them into |g_zombies| to be freed later.
+void ZombieDealloc(id self, SEL _cmd) {
+  // This code should only be called when it is implementing |-dealloc|.
+  DCHECK_EQ(_cmd, @selector(dealloc));
+
+  // Use the original |-dealloc| if the object doesn't wish to be
+  // zombied.
+  if (!g_zombieAllObjects && ![self shouldBecomeCrZombie]) {
+    g_originalDeallocIMP(self, _cmd);
+    return;
+  }
+
+  Class wasa = object_getClass(self);
+  const size_t size = class_getInstanceSize(wasa);
+
+  // Destroy the instance by calling C++ destructors and clearing it
+  // to something unlikely to work well if someone references it.
+  // NOTE(shess): |object_dispose()| will call this again when the
+  // zombie falls off the treadmill!  But by then |isa| will be a
+  // class without C++ destructors or associative references, so it
+  // won't hurt anything.
+  objc_destructInstance(self);
+  memset(self, '!', size);
+
+  // If the instance is big enough, make it into a fat zombie and have
+  // it remember the old |isa|.  Otherwise make it a regular zombie.
+  // Setting |isa| rather than using |object_setClass()| because that
+  // function is implemented with a memory barrier.  The runtime's
+  // |_internal_object_dispose()| (in objc-class.m) does this, so it
+  // should be safe (messaging free'd objects shouldn't be expected to
+  // be thread-safe in the first place).
+#pragma clang diagnostic push  // clang warns about direct access to isa.
+#pragma clang diagnostic ignored "-Wdeprecated-objc-isa-usage"
+  if (size >= g_fatZombieSize) {
+    self->isa = g_fatZombieClass;
+    static_cast<CrFatZombie*>(self)->wasa = wasa;
+  } else {
+    self->isa = g_zombieClass;
+  }
+#pragma clang diagnostic pop
+
+  // The new record to swap into |g_zombies|.  If |g_zombieCount| is
+  // zero, then |self| will be freed immediately.
+  ZombieRecord zombieToFree = {self, wasa};
+  zombieToFree.traceDepth =
+      std::max(backtrace(zombieToFree.trace, kBacktraceDepth), 0);
+
+  // Don't involve the lock when creating zombies without a treadmill.
+  if (g_zombieCount > 0) {
+    base::AutoLock pin(GetLock());
+
+    // Check the count again in a thread-safe manner.
+    if (g_zombieCount > 0) {
+      // Put the current object on the treadmill and keep the previous
+      // occupant.
+      std::swap(zombieToFree, g_zombies[g_zombieIndex]);
+
+      // Bump the index forward.
+      g_zombieIndex = (g_zombieIndex + 1) % g_zombieCount;
+    }
+  }
+
+  // Do the free out here to prevent any chance of deadlock.
+  if (zombieToFree.object)
+    object_dispose(zombieToFree.object);
+}
+
+// Search the treadmill for |object| and fill in |*record| if found.
+// Returns YES if found.
+BOOL GetZombieRecord(id object, ZombieRecord* record) {
+  // Holding the lock is reasonable because this should be fast, and
+  // the process is going to crash presently anyhow.
+  base::AutoLock pin(GetLock());
+  for (size_t i = 0; i < g_zombieCount; ++i) {
+    if (g_zombies[i].object == object) {
+      *record = g_zombies[i];
+      return YES;
+    }
+  }
+  return NO;
+}
+
+// Dump the symbols.  This is pulled out into a function to make it
+// easy to use DCHECK to dump only in debug builds.
+BOOL DumpDeallocTrace(const void* const* array, int size) {
+  // Async-signal safe version of fputs, consistent with StackTrace::Print().
+  const char message[] = "Backtrace from -dealloc:\n";
+  ignore_result(HANDLE_EINTR(write(STDERR_FILENO, message, strlen(message))));
+  base::debug::StackTrace(array, size).Print();
+
+  return YES;
+}
+
+// Log a message to a freed object.  |wasa| is the object's original
+// class.  |aSelector| is the selector which the calling code was
+// attempting to send.  |viaSelector| is the selector of the
+// dispatch-related method which is being invoked to send |aSelector|
+// (for instance, -respondsToSelector:).
+void ZombieObjectCrash(id object, SEL aSelector, SEL viaSelector) {
+  ZombieRecord record;
+  BOOL found = GetZombieRecord(object, &record);
+
+  // The object's class can be in the zombie record, but if that is
+  // not available it can also be in the object itself (in most cases).
+  Class wasa = Nil;
+  if (found) {
+    wasa = record.wasa;
+  } else if (object_getClass(object) == g_fatZombieClass) {
+    wasa = static_cast<CrFatZombie*>(object)->wasa;
+  }
+  const char* wasaName = (wasa ? class_getName(wasa) : "<unknown>");
+
+  std::string aString = base::StringPrintf("Zombie <%s: %p> received -%s",
+      wasaName, object, sel_getName(aSelector));
+  if (viaSelector != NULL) {
+    const char* viaName = sel_getName(viaSelector);
+    base::StringAppendF(&aString, " (via -%s)", viaName);
+  }
+
+  // Set a value for breakpad to report.
+  static crash_reporter::CrashKeyString<256> zombie_key("zombie");
+  zombie_key.Set(aString);
+
+  // Encode trace into a breakpad key.
+  static crash_reporter::CrashKeyString<1024> zombie_trace_key(
+      "zombie_dealloc_bt");
+  if (found) {
+    crash_reporter::SetCrashKeyStringToStackTrace(
+        &zombie_trace_key,
+        base::debug::StackTrace(record.trace, record.traceDepth));
+  }
+
+  // Log -dealloc backtrace in debug builds then crash with a useful
+  // stack trace.
+  if (found && record.traceDepth) {
+    DCHECK(DumpDeallocTrace(record.trace, record.traceDepth));
+  } else {
+    DLOG(WARNING) << "Unable to generate backtrace from -dealloc.";
+  }
+  DLOG(FATAL) << aString;
+
+  // This is how about:crash is implemented.  Using instead of
+  // |base::debug::BreakDebugger()| or |LOG(FATAL)| to make the top of
+  // stack more immediately obvious in crash dumps.
+  int* zero = NULL;
+  *zero = 0;
+}
+
+// Initialize our globals, returning YES on success.
+BOOL ZombieInit() {
+  static BOOL initialized = NO;
+  if (initialized)
+    return YES;
+
+  Class rootClass = [NSObject class];
+  g_originalDeallocIMP =
+      class_getMethodImplementation(rootClass, @selector(dealloc));
+  // objc_getClass() so CrZombie doesn't need +class.
+  g_zombieClass = objc_getClass("CrZombie");
+  g_fatZombieClass = objc_getClass("CrFatZombie");
+  g_fatZombieSize = class_getInstanceSize(g_fatZombieClass);
+
+  if (!g_originalDeallocIMP || !g_zombieClass || !g_fatZombieClass)
+    return NO;
+
+  initialized = YES;
+  return YES;
+}
+
+}  // namespace
+
+@implementation CrZombie
+
+// The Objective-C runtime needs to be able to call this successfully.
++ (void)initialize {
+}
+
+// Any method not explicitly defined will end up here, forcing a
+// crash.
+- (id)forwardingTargetForSelector:(SEL)aSelector {
+  ZombieObjectCrash(self, aSelector, NULL);
+  return nil;
+}
+
+// Override a few methods often used for dynamic dispatch to log the
+// message the caller is attempting to send, rather than the utility
+// method being used to send it.
+- (BOOL)respondsToSelector:(SEL)aSelector {
+  ZombieObjectCrash(self, aSelector, _cmd);
+  return NO;
+}
+
+- (id)performSelector:(SEL)aSelector {
+  ZombieObjectCrash(self, aSelector, _cmd);
+  return nil;
+}
+
+- (id)performSelector:(SEL)aSelector withObject:(id)anObject {
+  ZombieObjectCrash(self, aSelector, _cmd);
+  return nil;
+}
+
+- (id)performSelector:(SEL)aSelector
+           withObject:(id)anObject
+           withObject:(id)anotherObject {
+  ZombieObjectCrash(self, aSelector, _cmd);
+  return nil;
+}
+
+- (void)performSelector:(SEL)aSelector
+             withObject:(id)anArgument
+             afterDelay:(NSTimeInterval)delay {
+  ZombieObjectCrash(self, aSelector, _cmd);
+}
+
+@end
+
+@implementation CrFatZombie
+
+// This implementation intentionally left empty.
+
+@end
+
+@implementation NSObject (CrZombie)
+
+- (BOOL)shouldBecomeCrZombie {
+  return NO;
+}
+
+@end
+
+namespace ObjcEvilDoers {
+
+bool ZombieEnable(bool zombieAllObjects,
+                  size_t zombieCount) {
+  // Only allow enable/disable on the main thread, just to keep things
+  // simple.
+  DCHECK([NSThread isMainThread]);
+
+  if (!ZombieInit())
+    return false;
+
+  g_zombieAllObjects = zombieAllObjects;
+
+  // Replace the implementation of -[NSObject dealloc].
+  Method m = class_getInstanceMethod([NSObject class], @selector(dealloc));
+  if (!m)
+    return false;
+
+  const IMP prevDeallocIMP = method_setImplementation(m, (IMP)ZombieDealloc);
+  DCHECK(prevDeallocIMP == g_originalDeallocIMP ||
+         prevDeallocIMP == (IMP)ZombieDealloc);
+
+  // Grab the current set of zombies.  This is thread-safe because
+  // only the main thread can change these.
+  const size_t oldCount = g_zombieCount;
+  ZombieRecord* oldZombies = g_zombies;
+
+  {
+    base::AutoLock pin(GetLock());
+
+    // Save the old index in case zombies need to be transferred.
+    size_t oldIndex = g_zombieIndex;
+
+    // Create the new zombie treadmill, disabling zombies in case of
+    // failure.
+    g_zombieIndex = 0;
+    g_zombieCount = zombieCount;
+    g_zombies = NULL;
+    if (g_zombieCount) {
+      g_zombies =
+          static_cast<ZombieRecord*>(calloc(g_zombieCount, sizeof(*g_zombies)));
+      if (!g_zombies) {
+        NOTREACHED();
+        g_zombies = oldZombies;
+        g_zombieCount = oldCount;
+        g_zombieIndex = oldIndex;
+        ZombieDisable();
+        return false;
+      }
+    }
+
+    // If the count is changing, allow some of the zombies to continue
+    // shambling forward.
+    const size_t sharedCount = std::min(oldCount, zombieCount);
+    if (sharedCount) {
+      // Get index of the first shared zombie.
+      oldIndex = (oldIndex + oldCount - sharedCount) % oldCount;
+
+      for (; g_zombieIndex < sharedCount; ++ g_zombieIndex) {
+        DCHECK_LT(g_zombieIndex, g_zombieCount);
+        DCHECK_LT(oldIndex, oldCount);
+        std::swap(g_zombies[g_zombieIndex], oldZombies[oldIndex]);
+        oldIndex = (oldIndex + 1) % oldCount;
+      }
+      g_zombieIndex %= g_zombieCount;
+    }
+  }
+
+  // Free the old treadmill and any remaining zombies.
+  if (oldZombies) {
+    for (size_t i = 0; i < oldCount; ++i) {
+      if (oldZombies[i].object)
+        object_dispose(oldZombies[i].object);
+    }
+    free(oldZombies);
+  }
+
+  return true;
+}
+
+void ZombieDisable() {
+  // Only allow enable/disable on the main thread, just to keep things
+  // simple.
+  DCHECK([NSThread isMainThread]);
+
+  // |ZombieInit()| was never called.
+  if (!g_originalDeallocIMP)
+    return;
+
+  // Put back the original implementation of -[NSObject dealloc].
+  Method m = class_getInstanceMethod([NSObject class], @selector(dealloc));
+  DCHECK(m);
+  method_setImplementation(m, g_originalDeallocIMP);
+
+  // Can safely grab this because it only happens on the main thread.
+  const size_t oldCount = g_zombieCount;
+  ZombieRecord* oldZombies = g_zombies;
+
+  {
+    base::AutoLock pin(GetLock());  // In case any -dealloc are in progress.
+    g_zombieCount = 0;
+    g_zombies = NULL;
+  }
+
+  // Free any remaining zombies.
+  if (oldZombies) {
+    for (size_t i = 0; i < oldCount; ++i) {
+      if (oldZombies[i].object)
+        object_dispose(oldZombies[i].object);
+    }
+    free(oldZombies);
+  }
+}
+
+}  // namespace ObjcEvilDoers
diff --git a/components/crash/core/common/objc_zombie_unittest.mm b/components/crash/core/common/objc_zombie_unittest.mm
new file mode 100644
index 0000000..812cff8
--- /dev/null
+++ b/components/crash/core/common/objc_zombie_unittest.mm
@@ -0,0 +1,98 @@
+// Copyright (c) 2011 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 <Foundation/Foundation.h>
+#include <objc/runtime.h>
+
+#include "base/logging.h"
+#import "base/mac/scoped_nsobject.h"
+#import "components/crash/core/common/objc_zombie.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+@interface ZombieCxxDestructTest : NSObject
+{
+  base::scoped_nsobject<id> aRef_;
+}
+- (id)initWith:(id)anObject;
+@end
+
+@implementation ZombieCxxDestructTest
+- (id)initWith:(id)anObject {
+  self = [super init];
+  if (self) {
+    aRef_.reset([anObject retain]);
+  }
+  return self;
+}
+@end
+
+@interface ZombieAssociatedObjectTest : NSObject
+- (id)initWithAssociatedObject:(id)anObject;
+@end
+
+@implementation ZombieAssociatedObjectTest
+
+- (id)initWithAssociatedObject:(id)anObject {
+  if ((self = [super init])) {
+    // The address of the variable itself is the unique key, the
+    // contents don't matter.
+    static char kAssociatedObjectKey = 'x';
+    objc_setAssociatedObject(
+        self, &kAssociatedObjectKey, anObject, OBJC_ASSOCIATION_RETAIN);
+  }
+  return self;
+}
+
+@end
+
+namespace {
+
+// Verify that the C++ destructors run when the last reference to the
+// object is released.
+// NOTE(shess): To test the negative, comment out the |g_objectDestruct()|
+// call in |ZombieDealloc()|.
+TEST(ObjcZombieTest, CxxDestructors) {
+  base::scoped_nsobject<id> anObject([[NSObject alloc] init]);
+  EXPECT_EQ(1u, [anObject retainCount]);
+
+  ASSERT_TRUE(ObjcEvilDoers::ZombieEnable(YES, 100));
+
+  base::scoped_nsobject<ZombieCxxDestructTest> soonInfected(
+      [[ZombieCxxDestructTest alloc] initWith:anObject]);
+  EXPECT_EQ(2u, [anObject retainCount]);
+
+  // When |soonInfected| becomes a zombie, the C++ destructors should
+  // run and release a reference to |anObject|.
+  soonInfected.reset();
+  EXPECT_EQ(1u, [anObject retainCount]);
+
+  // The local reference should remain (C++ destructors aren't re-run).
+  ObjcEvilDoers::ZombieDisable();
+  EXPECT_EQ(1u, [anObject retainCount]);
+}
+
+// Verify that the associated objects are released when the object is
+// released.
+TEST(ObjcZombieTest, AssociatedObjectsReleased) {
+  base::scoped_nsobject<id> anObject([[NSObject alloc] init]);
+  EXPECT_EQ(1u, [anObject retainCount]);
+
+  ASSERT_TRUE(ObjcEvilDoers::ZombieEnable(YES, 100));
+
+  base::scoped_nsobject<ZombieAssociatedObjectTest> soonInfected(
+      [[ZombieAssociatedObjectTest alloc] initWithAssociatedObject:anObject]);
+  EXPECT_EQ(2u, [anObject retainCount]);
+
+  // When |soonInfected| becomes a zombie, the associated object
+  // should be released.
+  soonInfected.reset();
+  EXPECT_EQ(1u, [anObject retainCount]);
+
+  // The local reference should remain (associated objects not re-released).
+  ObjcEvilDoers::ZombieDisable();
+  EXPECT_EQ(1u, [anObject retainCount]);
+}
+
+}  // namespace
diff --git a/components/metrics/BUILD.gn b/components/metrics/BUILD.gn
new file mode 100644
index 0000000..e17fd20
--- /dev/null
+++ b/components/metrics/BUILD.gn
@@ -0,0 +1,473 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# TODO(b/283275484): Remove this once test.gni is stubbed for Cobalt.
+if (!use_cobalt_customizations) {
+  import("//testing/test.gni")
+}
+
+static_library("metrics") {
+  sources = [
+    "call_stack_profile_metrics_provider.cc",
+    "call_stack_profile_metrics_provider.h",
+    "clean_exit_beacon.cc",
+    "clean_exit_beacon.h",
+    "client_info.cc",
+    "client_info.h",
+    "cloned_install_detector.cc",
+    "cloned_install_detector.h",
+    "daily_event.cc",
+    "daily_event.h",
+    "data_use_tracker.cc",
+    "data_use_tracker.h",
+    "delegating_provider.cc",
+    "delegating_provider.h",
+    "drive_metrics_provider.cc",
+    "drive_metrics_provider.h",
+    "drive_metrics_provider_android.cc",
+    "drive_metrics_provider_ios.mm",
+    "drive_metrics_provider_linux.cc",
+    "drive_metrics_provider_mac.mm",
+    "drive_metrics_provider_win.cc",
+    "enabled_state_provider.cc",
+    "enabled_state_provider.h",
+    "environment_recorder.cc",
+    "environment_recorder.h",
+    "execution_phase.cc",
+    "execution_phase.h",
+    "expired_histogram_util.cc",
+    "expired_histogram_util.h",
+    "expired_histograms_checker.cc",
+    "expired_histograms_checker.h",
+    "field_trials_provider.cc",
+    "field_trials_provider.h",
+    "file_metrics_provider.cc",
+    "file_metrics_provider.h",
+    "histogram_encoder.cc",
+    "histogram_encoder.h",
+    "log_decoder.cc",
+    "log_decoder.h",
+    "log_store.h",
+    "machine_id_provider.h",
+    "machine_id_provider_stub.cc",
+    "machine_id_provider_win.cc",
+    "metrics_log.cc",
+    "metrics_log.h",
+    "metrics_log_manager.cc",
+    "metrics_log_manager.h",
+    "metrics_log_store.cc",
+    "metrics_log_store.h",
+    "metrics_log_uploader.h",
+    "metrics_pref_names.cc",
+    "metrics_pref_names.h",
+    "metrics_provider.cc",
+    "metrics_provider.h",
+    "metrics_reporting_default_state.cc",
+    "metrics_reporting_default_state.h",
+    "metrics_reporting_service.cc",
+    "metrics_reporting_service.h",
+    "metrics_rotation_scheduler.cc",
+    "metrics_rotation_scheduler.h",
+    "metrics_scheduler.cc",
+    "metrics_scheduler.h",
+    "metrics_service.cc",
+    "metrics_service.h",
+    "metrics_service_accessor.cc",
+    "metrics_service_accessor.h",
+    "metrics_service_client.cc",
+    "metrics_service_client.h",
+    "metrics_state_manager.cc",
+    "metrics_state_manager.h",
+    "metrics_switches.cc",
+    "metrics_switches.h",
+    "metrics_upload_scheduler.cc",
+    "metrics_upload_scheduler.h",
+    "persisted_logs.cc",
+    "persisted_logs.h",
+    "persisted_logs_metrics.h",
+    "persisted_logs_metrics_impl.cc",
+    "persisted_logs_metrics_impl.h",
+    "persistent_system_profile.cc",
+    "persistent_system_profile.h",
+    "reporting_service.cc",
+    "reporting_service.h",
+    "stability_metrics_helper.cc",
+    "stability_metrics_helper.h",
+    "stability_metrics_provider.cc",
+    "stability_metrics_provider.h",
+    "system_memory_stats_recorder.h",
+    "system_memory_stats_recorder_linux.cc",
+    "system_memory_stats_recorder_win.cc",
+    "system_session_analyzer_win.cc",
+    "system_session_analyzer_win.h",
+    "url_constants.cc",
+    "url_constants.h",
+    "version_utils.cc",
+    "version_utils.h",
+  ]
+
+  public_deps = [
+    "//third_party/metrics_proto",
+  ]
+
+  deps = [
+    "//base",
+    "//base:base_static",
+    "//components/prefs",
+    "//components/variations",
+    "//components/version_info:version_info",
+    "//extensions/buildflags",
+    "//third_party/zlib/google:compression_utils",
+  ]
+
+  if (use_cobalt_customizations) {
+    sources -= [
+      # All code should be OS agnostic above SB.
+      "drive_metrics_provider_android.cc",
+      "drive_metrics_provider_ios.mm",
+      "drive_metrics_provider_linux.cc",
+      "drive_metrics_provider_mac.mm",
+      "drive_metrics_provider_win.cc",
+      # Too many non-existent SB functionalities (e.g., base::TouchFile,
+      # MemoryMapped files, etc).
+      "file_metrics_provider.cc",
+      "file_metrics_provider.h",
+       # All code should be OS agnostic above SB.
+      "machine_id_provider_win.cc",
+      "system_memory_stats_recorder_linux.cc",
+      "system_memory_stats_recorder_win.cc",
+      "system_session_analyzer_win.cc",
+      "system_session_analyzer_win.h",
+    ]
+  }
+
+  if (is_chromeos) {
+    deps += [ ":serialization" ]
+  }
+
+  if (is_mac) {
+    libs = [
+      # The below are all needed for drive_metrics_provider_mac.mm.
+      "CoreFoundation.framework",
+      "DiskArbitration.framework",
+      "Foundation.framework",
+      "IOKit.framework",
+    ]
+  }
+
+  if (is_win) {
+    sources -= [ "machine_id_provider_stub.cc" ]
+    deps += [ "//components/browser_watcher:stability_client" ]
+    libs = [ "wevtapi.lib" ]
+  }
+
+  if (is_fuchsia) {
+    sources += [ "drive_metrics_provider_fuchsia.cc" ]
+  }
+}
+
+if (!use_cobalt_customizations) {
+  # The component metrics provider is a separate target because it depends upon
+  # (the large) component_updater code, and is not needed for some entities that
+  # depend on :metrics.
+  static_library("component_metrics") {
+    sources = [
+      "component_metrics_provider.cc",
+      "component_metrics_provider.h",
+    ]
+
+    public_deps = [
+      "//third_party/metrics_proto",
+    ]
+
+    deps = [
+      ":metrics",
+      "//base",
+      "//components/component_updater",
+    ]
+  }
+}
+
+if (!is_ios && !use_cobalt_customizations) {
+  static_library("gpu") {
+    sources = [
+      "gpu/gpu_metrics_provider.cc",
+      "gpu/gpu_metrics_provider.h",
+    ]
+
+    public_deps = [
+      ":metrics",
+    ]
+    deps = [
+      "//base",
+      "//content/public/browser",
+      "//gpu/config",
+    ]
+  }
+}
+
+if (!use_cobalt_customizations) {
+  static_library("net") {
+    sources = [
+      "net/cellular_logic_helper.cc",
+      "net/cellular_logic_helper.h",
+      "net/net_metrics_log_uploader.cc",
+      "net/net_metrics_log_uploader.h",
+      "net/network_metrics_provider.cc",
+      "net/network_metrics_provider.h",
+      "net/wifi_access_point_info_provider.cc",
+      "net/wifi_access_point_info_provider.h",
+    ]
+
+    public_deps = [
+      ":metrics",
+    ]
+    allow_circular_includes_from = [ ":metrics" ]
+
+    deps = [
+      "//base",
+      "//components/data_use_measurement/core",
+      "//components/encrypted_messages:encrypted_message_proto",
+      "//components/encrypted_messages:encrypted_messages",
+      "//components/variations",
+      "//net",
+      "//services/network/public/cpp:cpp",
+      "//third_party/metrics_proto",
+      "//third_party/zlib/google:compression_utils",
+      "//url",
+    ]
+
+    if (is_chromeos) {
+      sources += [
+        "net/wifi_access_point_info_provider_chromeos.cc",
+        "net/wifi_access_point_info_provider_chromeos.h",
+      ]
+      deps += [ "//chromeos" ]
+    }
+  }
+
+  static_library("ui") {
+    sources = [
+      "ui/screen_info_metrics_provider.cc",
+      "ui/screen_info_metrics_provider.h",
+    ]
+
+    public_deps = [
+      ":metrics",
+    ]
+    deps = [
+      "//base",
+      "//ui/display",
+      "//ui/gfx",
+      "//ui/gfx/geometry",
+    ]
+  }
+
+  static_library("single_sample_metrics") {
+    sources = [
+      "single_sample_metrics.cc",
+      "single_sample_metrics.h",
+      "single_sample_metrics_factory_impl.cc",
+      "single_sample_metrics_factory_impl.h",
+    ]
+
+    deps = [
+      "//mojo/public/cpp/bindings",
+      "//services/service_manager/public/cpp",
+      "//services/service_manager/public/mojom",
+    ]
+
+    public_deps = [
+      "//components/metrics/public/interfaces:single_sample_metrics_mojo_bindings",
+    ]
+  }
+  
+
+  source_set("call_stack_profile_params") {
+    public = [
+      "call_stack_profile_encoding.h",
+      "call_stack_profile_params.h",
+    ]
+    sources = [
+      "call_stack_profile_encoding.cc",
+    ]
+
+    deps = [
+      "//base:base",
+      "//third_party/metrics_proto",
+    ]
+  }
+
+  # Dependency for child processes that use the CallStackProfileBuilder.
+  source_set("child_call_stack_profile_builder") {
+    public = [
+      "call_stack_profile_builder.h",
+      "child_call_stack_profile_collector.h",
+    ]
+    sources = [
+      "call_stack_profile_builder.cc",
+      "child_call_stack_profile_collector.cc",
+    ]
+    public_deps = [
+      ":call_stack_profile_params",
+    ]
+    deps = [
+      "//base",
+      "//components/metrics/public/interfaces:call_stack_mojo_bindings",
+      "//third_party/metrics_proto",
+    ]
+
+    # This target must not depend on :metrics because that code is intended solely
+    # for use in the browser process.
+    assert_no_deps = [ ":metrics" ]
+  }
+
+  # Dependency for browser process use of the CallStackProfileBuilder.
+  source_set("call_stack_profile_builder") {
+    deps = [
+      ":metrics",
+    ]
+    public_deps = [
+      ":child_call_stack_profile_builder",
+    ]
+  }
+
+  # The browser process mojo service for collecting profiles from child
+  # processes.
+  source_set("call_stack_profile_collector") {
+    sources = [
+      "call_stack_profile_collector.cc",
+      "call_stack_profile_collector.h",
+    ]
+    deps = [
+      ":call_stack_profile_params",
+      ":metrics",
+      "//components/metrics/public/interfaces:call_stack_mojo_bindings",
+    ]
+  }
+}
+
+static_library("test_support") {
+  testonly = true
+  sources = [
+    "test_enabled_state_provider.cc",
+    "test_enabled_state_provider.h",
+    "test_metrics_log_uploader.cc",
+    "test_metrics_log_uploader.h",
+    "test_metrics_provider.cc",
+    "test_metrics_provider.h",
+    "test_metrics_service_client.cc",
+    "test_metrics_service_client.h",
+  ]
+
+  public_deps = [
+    ":metrics",
+  ]
+  deps = [
+    "//base",
+  ]
+}
+
+if (is_linux) {
+  static_library("serialization") {
+    sources = [
+      "serialization/metric_sample.cc",
+      "serialization/metric_sample.h",
+      "serialization/serialization_utils.cc",
+      "serialization/serialization_utils.h",
+    ]
+    deps = [
+      "//base",
+    ]
+  }
+}
+# TODO(b/283275474): Re-enable as many of these tests as possible.
+if (!use_cobalt_customizations) {
+  source_set("unit_tests") {
+    testonly = true
+    sources = [
+      "call_stack_profile_builder_unittest.cc",
+      "call_stack_profile_metrics_provider_unittest.cc",
+      "child_call_stack_profile_collector_unittest.cc",
+      "cloned_install_detector_unittest.cc",
+      "component_metrics_provider_unittest.cc",
+      "daily_event_unittest.cc",
+      "data_use_tracker_unittest.cc",
+      "drive_metrics_provider_unittest.cc",
+      "environment_recorder_unittest.cc",
+      "expired_histograms_checker_unittest.cc",
+      "field_trials_provider_unittest.cc",
+      "file_metrics_provider_unittest.cc",
+      "histogram_encoder_unittest.cc",
+      "machine_id_provider_win_unittest.cc",
+      "metrics_log_manager_unittest.cc",
+      "metrics_log_store_unittest.cc",
+      "metrics_log_unittest.cc",
+      "metrics_service_unittest.cc",
+      "metrics_state_manager_unittest.cc",
+      "net/net_metrics_log_uploader_unittest.cc",
+      "net/network_metrics_provider_unittest.cc",
+      "persisted_logs_unittest.cc",
+      "persistent_system_profile_unittest.cc",
+      "reporting_service_unittest.cc",
+      "single_sample_metrics_factory_impl_unittest.cc",
+      "stability_metrics_helper_unittest.cc",
+      "stability_metrics_provider_unittest.cc",
+      "system_session_analyzer_win_unittest.cc",
+      "ui/screen_info_metrics_provider_unittest.cc",
+    ]
+
+    deps = [
+      ":call_stack_profile_builder",
+      ":component_metrics",
+      ":metrics",
+      ":net",
+      ":single_sample_metrics",
+      ":test_support",
+      ":ui",
+      "//base/test:test_support",
+      "//components/component_updater:test_support",
+      "//components/encrypted_messages:encrypted_message_proto",
+      "//components/metrics/public/cpp:call_stack_unit_tests",
+      "//components/prefs:test_support",
+      "//components/variations",
+      "//extensions/buildflags",
+      "//mojo/public/cpp/bindings",
+      "//net:test_support",
+      "//services/network:test_support",
+      "//services/network/public/cpp:cpp",
+      "//services/service_manager/public/cpp",
+      "//testing/gtest",
+      "//third_party/zlib/google:compression_utils",
+      "//ui/gfx/geometry",
+    ]
+
+    if (is_linux) {
+      sources += [ "serialization/serialization_utils_unittest.cc" ]
+      deps += [ ":serialization" ]
+    }
+
+    if (is_chromeos) {
+      deps += [ "//chromeos" ]
+    }
+
+    # iOS is not supported by the profiler and the ios-simulator bot chokes on
+    # these tests.
+    if (is_ios) {
+      sources -= [ "child_call_stack_profile_collector_unittest.cc" ]
+      deps -= [ "//components/metrics/public/cpp:call_stack_unit_tests" ]
+    }
+  }
+
+  # Convenience testing target
+  test("metrics_unittests") {
+    sources = [
+      "//components/test/run_all_unittests.cc",
+    ]
+    deps = [
+      ":unit_tests",
+      "//components/test:test_support",
+    ]
+  }
+}
diff --git a/components/metrics/DEPS b/components/metrics/DEPS
new file mode 100644
index 0000000..1457333
--- /dev/null
+++ b/components/metrics/DEPS
@@ -0,0 +1,19 @@
+# This component is shared with the Chrome OS build, so it's important to limit
+# dependencies to a minimal set.
+include_rules = [
+  "-components",
+  "+components/browser_watcher",
+  "+components/component_updater",
+  "+components/compression",
+  "+components/metrics",
+  "+components/prefs",
+  "+components/variations",
+  "+components/version_info",
+  "+content/public/test",
+  "+extensions/buildflags",
+  "+mojo/public/cpp",
+  "+services/service_manager/public/cpp",
+  "+third_party/metrics_proto",
+  "+third_party/zlib/google",
+  "-net",
+]
diff --git a/components/metrics/METADATA b/components/metrics/METADATA
new file mode 100644
index 0000000..207ab02
--- /dev/null
+++ b/components/metrics/METADATA
@@ -0,0 +1,16 @@
+name: "metrics"
+description:
+  "Subtree at components/metrics."
+
+third_party {
+  url {
+    type: GIT
+    value: "https://chromium.googlesource.com/chromium/src/components/metrics"
+  }
+  version: "734293972a4b3f6d949e489ac02145538fb59c70"
+  last_upgrade_date {
+    year: 2018
+    month: 08
+    day: 30
+  }
+}
diff --git a/components/metrics/OWNERS b/components/metrics/OWNERS
new file mode 100644
index 0000000..68c3f9c
--- /dev/null
+++ b/components/metrics/OWNERS
@@ -0,0 +1,5 @@
+file://base/metrics/OWNERS
+
+per-file *call_stack_profile*=wittman@chromium.org
+
+# COMPONENT: Internals>Metrics
diff --git a/components/metrics/README b/components/metrics/README
new file mode 100644
index 0000000..5a2abbb
--- /dev/null
+++ b/components/metrics/README
@@ -0,0 +1,23 @@
+This component contains the base classes for the metrics service and only
+depends on //base. It is used by ChromeOS as the base for a standalone service
+that will upload the metrics when ChromeOS is not installed (headless install).
+
+This is the first step towards the componentization of metrics that will happen
+later this spring.
+
+A proposed structure for the metrics component is:
+//components/metrics/base,
+  Depends on base only. Contains the protobuf definitions.
+//components/metrics/core
+  Depends on everything iOS depends on
+//components/metrics/content
+  Depends on content
+
+Ideally, the component would abstract the network stack and have a clean
+separation between the metrics upload logic (protbuf generation, retry, etc...),
+the chrome part (gathering histogram from all the threads, populating the
+log with hardware characteristics, plugin state, etc.).
+
+It is a plus if the code currently in the component (i.e., the code that can
+depend only on //base) stays in a single directory as it would be easier
+for ChromeOS to pull it :).
diff --git a/components/metrics/call_stack_profile_builder.cc b/components/metrics/call_stack_profile_builder.cc
new file mode 100644
index 0000000..7b2890f
--- /dev/null
+++ b/components/metrics/call_stack_profile_builder.cc
@@ -0,0 +1,346 @@
+// 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.
+
+#include "components/metrics/call_stack_profile_builder.h"
+
+#include <string>
+#include <utility>
+
+#include "base/atomicops.h"
+#include "base/files/file_path.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/no_destructor.h"
+#include "base/stl_util.h"
+#include "components/metrics/call_stack_profile_encoding.h"
+
+namespace metrics {
+
+namespace {
+
+// Only used by child processes.
+base::LazyInstance<ChildCallStackProfileCollector>::Leaky
+    g_child_call_stack_profile_collector = LAZY_INSTANCE_INITIALIZER;
+
+base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
+GetBrowserProcessReceiverCallbackInstance() {
+  static base::NoDestructor<
+      base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>>
+      instance;
+  return *instance;
+}
+
+// Identifies an unknown module.
+const size_t kUnknownModuleIndex = static_cast<size_t>(-1);
+
+// This global variables holds the current system state and is recorded with
+// every captured sample, done on a separate thread which is why updates to
+// this must be atomic. A PostTask to move the the updates to that thread
+// would skew the timing and a lock could result in deadlock if the thread
+// making a change was also being profiled and got stopped.
+static base::subtle::Atomic32 g_process_milestones = 0;
+
+void ChangeAtomicFlags(base::subtle::Atomic32* flags,
+                       base::subtle::Atomic32 set,
+                       base::subtle::Atomic32 clear) {
+  DCHECK(set != 0 || clear != 0);
+  DCHECK_EQ(0, set & clear);
+
+  base::subtle::Atomic32 bits = base::subtle::NoBarrier_Load(flags);
+  while (true) {
+    base::subtle::Atomic32 existing = base::subtle::NoBarrier_CompareAndSwap(
+        flags, bits, (bits | set) & ~clear);
+    if (existing == bits)
+      break;
+    bits = existing;
+  }
+}
+
+// Provide a mapping from the C++ "enum" definition of various process mile-
+// stones to the equivalent protobuf "enum" definition. This table-lookup
+// conversion allows for the implementation to evolve and still be compatible
+// with the protobuf -- even if there are ever more than 32 defined proto
+// values, though never more than 32 could be in-use in a given C++ version
+// of the code.
+const ProcessPhase kProtoPhases[CallStackProfileBuilder::MILESTONES_MAX_VALUE] =
+    {
+        ProcessPhase::MAIN_LOOP_START,
+        ProcessPhase::MAIN_NAVIGATION_START,
+        ProcessPhase::MAIN_NAVIGATION_FINISHED,
+        ProcessPhase::FIRST_NONEMPTY_PAINT,
+
+        ProcessPhase::SHUTDOWN_START,
+};
+
+// These functions are used to encode protobufs. --------------------------
+
+// The protobuf expects the MD5 checksum prefix of the module name.
+uint64_t HashModuleFilename(const base::FilePath& filename) {
+  const base::FilePath::StringType basename = filename.BaseName().value();
+  // Copy the bytes in basename into a string buffer.
+  size_t basename_length_in_bytes =
+      basename.size() * sizeof(base::FilePath::CharType);
+  std::string name_bytes(basename_length_in_bytes, '\0');
+  memcpy(&name_bytes[0], &basename[0], basename_length_in_bytes);
+  return base::HashMetricName(name_bytes);
+}
+
+// Transcode |sample| into |proto_sample|, using base addresses in |modules| to
+// compute module instruction pointer offsets.
+void CopySampleToProto(const CallStackProfileBuilder::Sample& sample,
+                       const std::vector<base::ModuleCache::Module>& modules,
+                       CallStackProfile::Sample* proto_sample) {
+  for (const auto& frame : sample.frames) {
+    CallStackProfile::Location* location = proto_sample->add_frame();
+    // A frame may not have a valid module. If so, we can't compute the
+    // instruction pointer offset, and we don't want to send bare pointers,
+    // so leave call_stack_entry empty.
+    if (frame.module_index == kUnknownModuleIndex)
+      continue;
+    int64_t module_offset =
+        reinterpret_cast<const char*>(frame.instruction_pointer) -
+        reinterpret_cast<const char*>(modules[frame.module_index].base_address);
+    DCHECK_GE(module_offset, 0);
+    location->set_address(static_cast<uint64_t>(module_offset));
+    location->set_module_id_index(frame.module_index);
+  }
+}
+
+// Transcode Sample annotations into protobuf fields. The C++ code uses a
+// bit- field with each bit corresponding to an entry in an enumeration
+// while the protobuf uses a repeated field of individual values. Conversion
+// tables allow for arbitrary mapping, though no more than 32 in any given
+// version of the code.
+void CopyAnnotationsToProto(uint32_t new_milestones,
+                            CallStackProfile::Sample* sample_proto) {
+  for (size_t bit = 0; new_milestones != 0 && bit < sizeof(new_milestones) * 8;
+       ++bit) {
+    const uint32_t flag = 1U << bit;
+    if (new_milestones & flag) {
+      if (bit >= base::size(kProtoPhases)) {
+        NOTREACHED();
+        continue;
+      }
+      sample_proto->add_process_phase(kProtoPhases[bit]);
+      new_milestones ^= flag;  // Bit is set so XOR will clear it.
+    }
+  }
+}
+
+}  // namespace
+
+// CallStackProfileBuilder::Frame ---------------------------------------------
+
+CallStackProfileBuilder::Frame::Frame(uintptr_t instruction_pointer,
+                                      size_t module_index)
+    : instruction_pointer(instruction_pointer), module_index(module_index) {}
+
+CallStackProfileBuilder::Frame::~Frame() = default;
+
+CallStackProfileBuilder::Frame::Frame()
+    : instruction_pointer(0), module_index(kUnknownModuleIndex) {}
+
+// CallStackProfileBuilder::Sample --------------------------------------------
+
+CallStackProfileBuilder::Sample::Sample() = default;
+
+CallStackProfileBuilder::Sample::Sample(const Sample& sample) = default;
+
+CallStackProfileBuilder::Sample::~Sample() = default;
+
+CallStackProfileBuilder::Sample::Sample(const Frame& frame) {
+  frames.push_back(std::move(frame));
+}
+
+CallStackProfileBuilder::Sample::Sample(const std::vector<Frame>& frames)
+    : frames(frames) {}
+
+CallStackProfileBuilder::CallStackProfileBuilder(
+    const CallStackProfileParams& profile_params,
+    base::OnceClosure completed_callback)
+    : profile_params_(profile_params),
+      profile_start_time_(base::TimeTicks::Now()) {
+  completed_callback_ = std::move(completed_callback);
+}
+
+CallStackProfileBuilder::~CallStackProfileBuilder() = default;
+
+void CallStackProfileBuilder::RecordAnnotations() {
+  // The code inside this method must not do anything that could acquire a
+  // mutex, including allocating memory (which includes LOG messages) because
+  // that mutex could be held by a stopped thread, thus resulting in deadlock.
+  sample_.process_milestones =
+      base::subtle::NoBarrier_Load(&g_process_milestones);
+}
+
+void CallStackProfileBuilder::OnSampleCompleted(
+    std::vector<base::StackSamplingProfiler::Frame> frames) {
+  // Assemble sample_ from |frames| first.
+  for (const auto& frame : frames) {
+    const base::ModuleCache::Module& module(frame.module);
+    if (!module.is_valid) {
+      sample_.frames.emplace_back(frame.instruction_pointer,
+                                  kUnknownModuleIndex);
+      continue;
+    }
+
+    // Dedup modules and cache them in modules_.
+    auto loc = module_index_.find(module.base_address);
+    if (loc == module_index_.end()) {
+      modules_.push_back(module);
+      size_t index = modules_.size() - 1;
+      loc = module_index_.insert(std::make_pair(module.base_address, index))
+                .first;
+    }
+    sample_.frames.emplace_back(frame.instruction_pointer, loc->second);
+  }
+
+  // Write CallStackProfile::Sample protocol buffer message based on sample_.
+  int existing_sample_index = -1;
+  auto location = sample_index_.find(sample_);
+  if (location != sample_index_.end())
+    existing_sample_index = location->second;
+
+  if (existing_sample_index != -1) {
+    CallStackProfile::Sample* sample_proto =
+        proto_profile_.mutable_deprecated_sample(existing_sample_index);
+    sample_proto->set_count(sample_proto->count() + 1);
+    return;
+  }
+
+  CallStackProfile::Sample* sample_proto =
+      proto_profile_.add_deprecated_sample();
+  CopySampleToProto(sample_, modules_, sample_proto);
+  sample_proto->set_count(1);
+  CopyAnnotationsToProto(sample_.process_milestones & ~milestones_,
+                         sample_proto);
+  milestones_ = sample_.process_milestones;
+
+  sample_index_.insert(std::make_pair(
+      sample_, static_cast<int>(proto_profile_.deprecated_sample_size()) - 1));
+
+  sample_ = Sample();
+}
+
+// Build a SampledProfile in the protocol buffer message format from the
+// collected sampling data. The message is then passed to
+// CallStackProfileMetricsProvider or ChildCallStackProfileCollector.
+
+// A SampledProfile message (third_party/metrics_proto/sampled_profile.proto)
+// contains a CallStackProfile message
+// (third_party/metrics_proto/call_stack_profile.proto) and associated profile
+// parameters (process/thread/trigger event). A CallStackProfile message
+// contains a set of Sample messages and ModuleIdentifier messages, and other
+// sampling information. One Sample corresponds to a single recorded stack, and
+// the ModuleIdentifiers record those modules associated with the recorded stack
+// frames.
+void CallStackProfileBuilder::OnProfileCompleted(
+    base::TimeDelta profile_duration,
+    base::TimeDelta sampling_period) {
+  proto_profile_.set_profile_duration_ms(profile_duration.InMilliseconds());
+  proto_profile_.set_sampling_period_ms(sampling_period.InMilliseconds());
+
+  for (const auto& module : modules_) {
+    CallStackProfile::ModuleIdentifier* module_id =
+        proto_profile_.add_module_id();
+    module_id->set_build_id(module.id);
+    module_id->set_name_md5_prefix(HashModuleFilename(module.filename));
+  }
+
+  // Clear the caches etc.
+  modules_.clear();
+  module_index_.clear();
+  sample_index_.clear();
+
+  // Assemble the SampledProfile protocol buffer message and run the associated
+  // callback to pass it.
+  SampledProfile sampled_profile;
+  CallStackProfile* proto_profile =
+      sampled_profile.mutable_call_stack_profile();
+  *proto_profile = std::move(proto_profile_);
+
+  sampled_profile.set_process(
+      ToExecutionContextProcess(profile_params_.process));
+  sampled_profile.set_thread(ToExecutionContextThread(profile_params_.thread));
+  sampled_profile.set_trigger_event(
+      ToSampledProfileTriggerEvent(profile_params_.trigger));
+
+  PassProfilesToMetricsProvider(std::move(sampled_profile));
+
+  // Run the completed callback if there is one.
+  if (!completed_callback_.is_null())
+    std::move(completed_callback_).Run();
+}
+
+// static
+void CallStackProfileBuilder::SetBrowserProcessReceiverCallback(
+    const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
+        callback) {
+  GetBrowserProcessReceiverCallbackInstance() = callback;
+}
+
+void CallStackProfileBuilder::PassProfilesToMetricsProvider(
+    SampledProfile sampled_profile) {
+  if (profile_params_.process == CallStackProfileParams::BROWSER_PROCESS) {
+    GetBrowserProcessReceiverCallbackInstance().Run(profile_start_time_,
+                                                    std::move(sampled_profile));
+  } else {
+    g_child_call_stack_profile_collector.Get()
+        .ChildCallStackProfileCollector::Collect(profile_start_time_,
+                                                 std::move(sampled_profile));
+  }
+}
+
+// static
+void CallStackProfileBuilder::SetProcessMilestone(int milestone) {
+  DCHECK_LE(0, milestone);
+  DCHECK_GT(static_cast<int>(sizeof(g_process_milestones) * 8), milestone);
+  DCHECK_EQ(0, base::subtle::NoBarrier_Load(&g_process_milestones) &
+                   (1 << milestone));
+  ChangeAtomicFlags(&g_process_milestones, 1 << milestone, 0);
+}
+
+// static
+void CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
+    metrics::mojom::CallStackProfileCollectorPtr browser_interface) {
+  g_child_call_stack_profile_collector.Get().SetParentProfileCollector(
+      std::move(browser_interface));
+}
+
+// These operators permit types to be compared and used in a map of Samples.
+
+bool operator==(const CallStackProfileBuilder::Sample& a,
+                const CallStackProfileBuilder::Sample& b) {
+  return a.process_milestones == b.process_milestones && a.frames == b.frames;
+}
+
+bool operator!=(const CallStackProfileBuilder::Sample& a,
+                const CallStackProfileBuilder::Sample& b) {
+  return !(a == b);
+}
+
+bool operator<(const CallStackProfileBuilder::Sample& a,
+               const CallStackProfileBuilder::Sample& b) {
+  if (a.process_milestones != b.process_milestones)
+    return a.process_milestones < b.process_milestones;
+
+  return a.frames < b.frames;
+}
+
+bool operator==(const CallStackProfileBuilder::Frame& a,
+                const CallStackProfileBuilder::Frame& b) {
+  return a.instruction_pointer == b.instruction_pointer &&
+         a.module_index == b.module_index;
+}
+
+bool operator<(const CallStackProfileBuilder::Frame& a,
+               const CallStackProfileBuilder::Frame& b) {
+  if (a.module_index != b.module_index)
+    return a.module_index < b.module_index;
+
+  return a.instruction_pointer < b.instruction_pointer;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/call_stack_profile_builder.h b/components/metrics/call_stack_profile_builder.h
new file mode 100644
index 0000000..deb2fea
--- /dev/null
+++ b/components/metrics/call_stack_profile_builder.h
@@ -0,0 +1,158 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_BUILDER_H_
+#define COMPONENTS_METRICS_CALL_STACK_PROFILE_BUILDER_H_
+
+#include <map>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/profiler/stack_sampling_profiler.h"
+#include "base/sampling_heap_profiler/module_cache.h"
+#include "base/time/time.h"
+#include "components/metrics/call_stack_profile_params.h"
+#include "components/metrics/child_call_stack_profile_collector.h"
+#include "third_party/metrics_proto/sampled_profile.pb.h"
+
+namespace metrics {
+
+class SampledProfile;
+
+// An instance of the class is meant to be passed to base::StackSamplingProfiler
+// to collect profiles. The profiles collected are uploaded via the metrics log.
+class CallStackProfileBuilder
+    : public base::StackSamplingProfiler::ProfileBuilder {
+ public:
+  // Frame represents an individual sampled stack frame with module information.
+  struct Frame {
+    Frame(uintptr_t instruction_pointer, size_t module_index);
+    ~Frame();
+
+    // Default constructor to satisfy IPC macros. Do not use explicitly.
+    Frame();
+
+    // The sampled instruction pointer within the function.
+    uintptr_t instruction_pointer;
+
+    // Index of the module in the associated vector of mofules. We don't
+    // represent module state directly here to save space.
+    size_t module_index;
+  };
+
+  // Sample represents a set of stack frames with some extra information.
+  struct Sample {
+    Sample();
+    Sample(const Sample& sample);
+    ~Sample();
+
+    // These constructors are used only during testing.
+    Sample(const Frame& frame);
+    Sample(const std::vector<Frame>& frames);
+
+    // The entire stack frame when the sample is taken.
+    std::vector<Frame> frames;
+
+    // A bit-field indicating which process milestones have passed. This can be
+    // used to tell where in the process lifetime the samples are taken. Just
+    // as a "lifetime" can only move forward, these bits mark the milestones of
+    // the processes life as they occur. Bits can be set but never reset. The
+    // actual definition of the individual bits is left to the user of this
+    // module.
+    uint32_t process_milestones = 0;
+  };
+
+  // These milestones of a process lifetime can be passed as process "mile-
+  // stones" to CallStackProfileBuilder::SetProcessMilestone(). Be sure to
+  // update the translation constants at the top of the .cc file when this is
+  // changed.
+  enum Milestones : int {
+    MAIN_LOOP_START,
+    MAIN_NAVIGATION_START,
+    MAIN_NAVIGATION_FINISHED,
+    FIRST_NONEMPTY_PAINT,
+
+    SHUTDOWN_START,
+
+    MILESTONES_MAX_VALUE
+  };
+
+  // |completed_callback| is made when sampling a profile completes. Other
+  // threads, including the UI thread, may block on callback completion so this
+  // should run as quickly as possible.
+  //
+  // IMPORTANT NOTE: The callback is invoked on a thread the profiler
+  // constructs, rather than on the thread used to construct the profiler, and
+  // thus the callback must be callable on any thread.
+  CallStackProfileBuilder(
+      const CallStackProfileParams& profile_params,
+      base::OnceClosure completed_callback = base::OnceClosure());
+
+  ~CallStackProfileBuilder() override;
+
+  // base::StackSamplingProfiler::ProfileBuilder:
+  void RecordAnnotations() override;
+  void OnSampleCompleted(
+      std::vector<base::StackSamplingProfiler::Frame> frames) override;
+  void OnProfileCompleted(base::TimeDelta profile_duration,
+                          base::TimeDelta sampling_period) override;
+
+  // Sets the callback to use for reporting browser process profiles. This
+  // indirection is required to avoid a dependency on unnecessary metrics code
+  // in child processes.
+  static void SetBrowserProcessReceiverCallback(
+      const base::RepeatingCallback<void(base::TimeTicks, SampledProfile)>&
+          callback);
+
+  // Sets the current system state that is recorded with each captured stack
+  // frame. This is thread-safe so can be called from anywhere. The parameter
+  // value should be from an enumeration of the appropriate type with values
+  // ranging from 0 to 31, inclusive. This sets bits within Sample field of
+  // |process_milestones|. The actual meanings of these bits are defined
+  // (globally) by the caller(s).
+  static void SetProcessMilestone(int milestone);
+
+  // Sets the CallStackProfileCollector interface from |browser_interface|.
+  // This function must be called within child processes.
+  static void SetParentProfileCollectorForChildProcess(
+      metrics::mojom::CallStackProfileCollectorPtr browser_interface);
+
+ protected:
+  // Test seam.
+  virtual void PassProfilesToMetricsProvider(SampledProfile sampled_profile);
+
+ private:
+  // The collected stack samples in proto buffer message format.
+  CallStackProfile proto_profile_;
+
+  // The current sample being recorded.
+  Sample sample_;
+
+  // The indexes of samples, indexed by the sample.
+  std::map<Sample, int> sample_index_;
+
+  // The indexes of modules, indexed by module's base_address.
+  std::map<uintptr_t, size_t> module_index_;
+
+  // The distinct modules in the current profile.
+  std::vector<base::ModuleCache::Module> modules_;
+
+  // The process milestones of a previous sample.
+  uint32_t milestones_ = 0;
+
+  // Callback made when sampling a profile completes.
+  base::OnceClosure completed_callback_;
+
+  // The parameters associated with the sampled profile.
+  const CallStackProfileParams profile_params_;
+
+  // The start time of a profile collection.
+  const base::TimeTicks profile_start_time_;
+
+  DISALLOW_COPY_AND_ASSIGN(CallStackProfileBuilder);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_CALL_STACK_PROFILE_BUILDER_H_
diff --git a/components/metrics/call_stack_profile_builder_unittest.cc b/components/metrics/call_stack_profile_builder_unittest.cc
new file mode 100644
index 0000000..89549c0
--- /dev/null
+++ b/components/metrics/call_stack_profile_builder_unittest.cc
@@ -0,0 +1,363 @@
+// 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.
+
+#include "components/metrics/call_stack_profile_builder.h"
+
+#include "base/files/file_path.h"
+#include "base/sampling_heap_profiler/module_cache.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/mock_callback.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/metrics/call_stack_profile_params.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/sampled_profile.pb.h"
+
+using Frame = base::StackSamplingProfiler::Frame;
+using Module = base::ModuleCache::Module;
+
+namespace metrics {
+
+namespace {
+
+constexpr CallStackProfileParams kProfileParams = {
+    CallStackProfileParams::BROWSER_PROCESS,
+    CallStackProfileParams::MAIN_THREAD,
+    CallStackProfileParams::PROCESS_STARTUP};
+
+class TestingCallStackProfileBuilder : public CallStackProfileBuilder {
+ public:
+  TestingCallStackProfileBuilder(
+      const CallStackProfileParams& profile_params,
+      base::OnceClosure completed_callback = base::OnceClosure());
+
+  ~TestingCallStackProfileBuilder() override;
+
+  const SampledProfile& sampled_profile() { return sampled_profile_; }
+
+ protected:
+  // Overridden for testing.
+  void PassProfilesToMetricsProvider(SampledProfile sampled_profile) override;
+
+ private:
+  // The completed profile.
+  SampledProfile sampled_profile_;
+};
+
+TestingCallStackProfileBuilder::TestingCallStackProfileBuilder(
+    const CallStackProfileParams& profile_params,
+    base::OnceClosure completed_callback)
+    : CallStackProfileBuilder(profile_params, std::move(completed_callback)) {}
+
+TestingCallStackProfileBuilder::~TestingCallStackProfileBuilder() = default;
+
+void TestingCallStackProfileBuilder::PassProfilesToMetricsProvider(
+    SampledProfile sampled_profile) {
+  sampled_profile_ = std::move(sampled_profile);
+}
+
+}  // namespace
+
+TEST(CallStackProfileBuilderTest, SetProcessMilestone) {
+  auto profile_builder =
+      std::make_unique<TestingCallStackProfileBuilder>(kProfileParams);
+
+  // The default milestone is 0.
+  profile_builder->RecordAnnotations();
+  profile_builder->OnSampleCompleted(std::vector<Frame>());
+
+  CallStackProfileBuilder::SetProcessMilestone(1);
+  profile_builder->RecordAnnotations();
+  profile_builder->OnSampleCompleted(std::vector<Frame>());
+
+  profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
+
+  const SampledProfile& proto = profile_builder->sampled_profile();
+
+  ASSERT_TRUE(proto.has_call_stack_profile());
+  const CallStackProfile& profile = proto.call_stack_profile();
+
+  ASSERT_EQ(2, profile.deprecated_sample_size());
+
+  uint32_t process_milestones = 0;
+  for (int i = 0; i < profile.deprecated_sample(0).process_phase().size(); ++i)
+    process_milestones |=
+        1U << profile.deprecated_sample(0).process_phase().Get(i);
+  EXPECT_EQ(0U, process_milestones);
+
+  process_milestones = 0;
+  for (int i = 0; i < profile.deprecated_sample(1).process_phase().size(); ++i)
+    process_milestones |=
+        1U << profile.deprecated_sample(1).process_phase().Get(i);
+  EXPECT_EQ(1U << 1, process_milestones);
+}
+
+TEST(CallStackProfileBuilderTest, ProfilingCompleted) {
+  // Set up a mock completed callback which will be run once.
+  base::MockCallback<base::OnceClosure> mock_closure;
+  EXPECT_CALL(mock_closure, Run()).Times(1);
+
+  auto profile_builder = std::make_unique<TestingCallStackProfileBuilder>(
+      kProfileParams, mock_closure.Get());
+
+#if defined(OS_WIN)
+  uint64_t module_md5 = 0x46C3E4166659AC02ULL;
+  base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe");
+#else
+  uint64_t module_md5 = 0x554838A8451AC36CULL;
+  base::FilePath module_path("/some/path/to/chrome");
+#endif
+
+  const uintptr_t module_base_address1 = 0x1000;
+  Module module1 = {module_base_address1, "1", module_path};
+  Frame frame1 = {module_base_address1 + 0x10, module1};
+
+  const uintptr_t module_base_address2 = 0x1100;
+  Module module2 = {module_base_address2, "2", module_path};
+  Frame frame2 = {module_base_address2 + 0x10, module2};
+
+  const uintptr_t module_base_address3 = 0x1010;
+  Module module3 = {module_base_address3, "3", module_path};
+  Frame frame3 = {module_base_address3 + 0x10, module3};
+
+  std::vector<Frame> frames1 = {frame1, frame2};
+  std::vector<Frame> frames2 = {frame3};
+
+  profile_builder->OnSampleCompleted(frames1);
+  profile_builder->OnSampleCompleted(frames2);
+  profile_builder->OnProfileCompleted(base::TimeDelta::FromMilliseconds(500),
+                                      base::TimeDelta::FromMilliseconds(100));
+
+  const SampledProfile& proto = profile_builder->sampled_profile();
+
+  ASSERT_TRUE(proto.has_process());
+  ASSERT_EQ(BROWSER_PROCESS, proto.process());
+  ASSERT_TRUE(proto.has_thread());
+  ASSERT_EQ(MAIN_THREAD, proto.thread());
+  ASSERT_TRUE(proto.has_trigger_event());
+  ASSERT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event());
+
+  ASSERT_TRUE(proto.has_call_stack_profile());
+  const CallStackProfile& profile = proto.call_stack_profile();
+
+  ASSERT_EQ(2, profile.deprecated_sample_size());
+  ASSERT_EQ(2, profile.deprecated_sample(0).frame_size());
+  ASSERT_TRUE(profile.deprecated_sample(0).frame(0).has_module_id_index());
+  EXPECT_EQ(0, profile.deprecated_sample(0).frame(0).module_id_index());
+  ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_module_id_index());
+  EXPECT_EQ(1, profile.deprecated_sample(0).frame(1).module_id_index());
+  ASSERT_EQ(1, profile.deprecated_sample(1).frame_size());
+  ASSERT_TRUE(profile.deprecated_sample(1).frame(0).has_module_id_index());
+  EXPECT_EQ(2, profile.deprecated_sample(1).frame(0).module_id_index());
+
+  ASSERT_EQ(3, profile.module_id().size());
+  ASSERT_TRUE(profile.module_id(0).has_build_id());
+  ASSERT_EQ("1", profile.module_id(0).build_id());
+  ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix());
+  ASSERT_EQ(module_md5, profile.module_id(0).name_md5_prefix());
+  ASSERT_TRUE(profile.module_id(1).has_build_id());
+  ASSERT_EQ("2", profile.module_id(1).build_id());
+  ASSERT_TRUE(profile.module_id(1).has_name_md5_prefix());
+  ASSERT_EQ(module_md5, profile.module_id(1).name_md5_prefix());
+  ASSERT_TRUE(profile.module_id(2).has_build_id());
+  ASSERT_EQ("3", profile.module_id(2).build_id());
+  ASSERT_TRUE(profile.module_id(2).has_name_md5_prefix());
+  ASSERT_EQ(module_md5, profile.module_id(2).name_md5_prefix());
+
+  ASSERT_TRUE(profile.has_profile_duration_ms());
+  EXPECT_EQ(500, profile.profile_duration_ms());
+  ASSERT_TRUE(profile.has_sampling_period_ms());
+  EXPECT_EQ(100, profile.sampling_period_ms());
+}
+
+TEST(CallStackProfileBuilderTest, SamplesDeduped) {
+  auto profile_builder =
+      std::make_unique<TestingCallStackProfileBuilder>(kProfileParams);
+
+#if defined(OS_WIN)
+  base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe");
+#else
+  base::FilePath module_path("/some/path/to/chrome");
+#endif
+
+  const uintptr_t module_base_address1 = 0x1000;
+  Module module1 = {module_base_address1, "1", module_path};
+  Frame frame1 = {module_base_address1 + 0x10, module1};
+
+  const uintptr_t module_base_address2 = 0x1100;
+  Module module2 = {module_base_address2, "2", module_path};
+  Frame frame2 = {module_base_address2 + 0x10, module2};
+
+  std::vector<Frame> frames = {frame1, frame2};
+
+  // Two samples are completed with the same frames. They also have the same
+  // process milestone therefore they are deduped to one.
+  CallStackProfileBuilder::SetProcessMilestone(0);
+
+  profile_builder->RecordAnnotations();
+  profile_builder->OnSampleCompleted(frames);
+
+  profile_builder->RecordAnnotations();
+  profile_builder->OnSampleCompleted(frames);
+
+  profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
+
+  const SampledProfile& proto = profile_builder->sampled_profile();
+
+  ASSERT_TRUE(proto.has_process());
+  ASSERT_EQ(BROWSER_PROCESS, proto.process());
+  ASSERT_TRUE(proto.has_thread());
+  ASSERT_EQ(MAIN_THREAD, proto.thread());
+  ASSERT_TRUE(proto.has_trigger_event());
+  ASSERT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event());
+
+  ASSERT_TRUE(proto.has_call_stack_profile());
+  ASSERT_EQ(1, proto.call_stack_profile().deprecated_sample_size());
+}
+
+TEST(CallStackProfileBuilderTest, SamplesNotDeduped) {
+  auto profile_builder =
+      std::make_unique<TestingCallStackProfileBuilder>(kProfileParams);
+
+#if defined(OS_WIN)
+  base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe");
+#else
+  base::FilePath module_path("/some/path/to/chrome");
+#endif
+
+  const uintptr_t module_base_address1 = 0x1000;
+  Module module1 = {module_base_address1, "1", module_path};
+  Frame frame1 = {module_base_address1 + 0x10, module1};
+
+  const uintptr_t module_base_address2 = 0x1100;
+  Module module2 = {module_base_address2, "2", module_path};
+  Frame frame2 = {module_base_address2 + 0x10, module2};
+
+  std::vector<Frame> frames = {frame1, frame2};
+
+  // Two samples are completed with the same frames but different process
+  // milestones. They are considered as different samples threfore not deduped.
+  CallStackProfileBuilder::SetProcessMilestone(2);
+  profile_builder->RecordAnnotations();
+  profile_builder->OnSampleCompleted(frames);
+
+  CallStackProfileBuilder::SetProcessMilestone(4);
+  profile_builder->RecordAnnotations();
+  profile_builder->OnSampleCompleted(frames);
+
+  profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
+
+  const SampledProfile& proto = profile_builder->sampled_profile();
+
+  ASSERT_TRUE(proto.has_process());
+  ASSERT_EQ(BROWSER_PROCESS, proto.process());
+  ASSERT_TRUE(proto.has_thread());
+  ASSERT_EQ(MAIN_THREAD, proto.thread());
+  ASSERT_TRUE(proto.has_trigger_event());
+  ASSERT_EQ(SampledProfile::PROCESS_STARTUP, proto.trigger_event());
+
+  ASSERT_TRUE(proto.has_call_stack_profile());
+  ASSERT_EQ(2, proto.call_stack_profile().deprecated_sample_size());
+}
+
+TEST(CallStackProfileBuilderTest, Modules) {
+  auto profile_builder =
+      std::make_unique<TestingCallStackProfileBuilder>(kProfileParams);
+
+  const uintptr_t module_base_address1 = 0x1000;
+  Module module1;  // module1 has no information hence invalid.
+  Frame frame1 = {module_base_address1 + 0x10, module1};
+
+  const uintptr_t module_base_address2 = 0x1100;
+#if defined(OS_WIN)
+  uint64_t module_md5 = 0x46C3E4166659AC02ULL;
+  base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe");
+#else
+  uint64_t module_md5 = 0x554838A8451AC36CULL;
+  base::FilePath module_path("/some/path/to/chrome");
+#endif
+  Module module2 = {module_base_address2, "2", module_path};
+  Frame frame2 = {module_base_address2 + 0x10, module2};
+
+  std::vector<Frame> frames = {frame1, frame2};
+
+  profile_builder->OnSampleCompleted(frames);
+  profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
+
+  const SampledProfile& proto = profile_builder->sampled_profile();
+
+  ASSERT_TRUE(proto.has_call_stack_profile());
+  const CallStackProfile& profile = proto.call_stack_profile();
+
+  ASSERT_EQ(1, profile.deprecated_sample_size());
+  ASSERT_EQ(2, profile.deprecated_sample(0).frame_size());
+
+  ASSERT_FALSE(profile.deprecated_sample(0).frame(0).has_module_id_index());
+  ASSERT_FALSE(profile.deprecated_sample(0).frame(0).has_address());
+
+  ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_module_id_index());
+  EXPECT_EQ(0, profile.deprecated_sample(0).frame(1).module_id_index());
+  ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_address());
+  EXPECT_EQ(0x10ULL, profile.deprecated_sample(0).frame(1).address());
+
+  ASSERT_EQ(1, profile.module_id().size());
+  ASSERT_TRUE(profile.module_id(0).has_build_id());
+  ASSERT_EQ("2", profile.module_id(0).build_id());
+  ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix());
+  ASSERT_EQ(module_md5, profile.module_id(0).name_md5_prefix());
+}
+
+TEST(CallStackProfileBuilderTest, DedupModules) {
+  auto profile_builder =
+      std::make_unique<TestingCallStackProfileBuilder>(kProfileParams);
+
+  const uintptr_t module_base_address = 0x1000;
+
+#if defined(OS_WIN)
+  uint64_t module_md5 = 0x46C3E4166659AC02ULL;
+  base::FilePath module_path(L"c:\\some\\path\\to\\chrome.exe");
+#else
+  uint64_t module_md5 = 0x554838A8451AC36CULL;
+  base::FilePath module_path("/some/path/to/chrome");
+#endif
+
+  Module module1 = {module_base_address, "1", module_path};
+  Frame frame1 = {module_base_address + 0x10, module1};
+
+  Module module2 = {module_base_address, "1", module_path};
+  Frame frame2 = {module_base_address + 0x20, module2};
+
+  std::vector<Frame> frames = {frame1, frame2};
+
+  profile_builder->OnSampleCompleted(frames);
+  profile_builder->OnProfileCompleted(base::TimeDelta(), base::TimeDelta());
+
+  const SampledProfile& proto = profile_builder->sampled_profile();
+
+  ASSERT_TRUE(proto.has_call_stack_profile());
+  const CallStackProfile& profile = proto.call_stack_profile();
+
+  ASSERT_EQ(1, profile.deprecated_sample_size());
+  ASSERT_EQ(2, profile.deprecated_sample(0).frame_size());
+
+  // Since module1 and module2 have the same base address, they are considered
+  // the same module and therefore deduped.
+  ASSERT_TRUE(profile.deprecated_sample(0).frame(0).has_module_id_index());
+  EXPECT_EQ(0, profile.deprecated_sample(0).frame(0).module_id_index());
+  ASSERT_TRUE(profile.deprecated_sample(0).frame(0).has_address());
+  EXPECT_EQ(0x10ULL, profile.deprecated_sample(0).frame(0).address());
+
+  ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_module_id_index());
+  EXPECT_EQ(0, profile.deprecated_sample(0).frame(1).module_id_index());
+  ASSERT_TRUE(profile.deprecated_sample(0).frame(1).has_address());
+  EXPECT_EQ(0x20ULL, profile.deprecated_sample(0).frame(1).address());
+
+  ASSERT_EQ(1, profile.module_id().size());
+  ASSERT_TRUE(profile.module_id(0).has_build_id());
+  ASSERT_EQ("1", profile.module_id(0).build_id());
+  ASSERT_TRUE(profile.module_id(0).has_name_md5_prefix());
+  ASSERT_EQ(module_md5, profile.module_id(0).name_md5_prefix());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/call_stack_profile_collector.cc b/components/metrics/call_stack_profile_collector.cc
new file mode 100644
index 0000000..3863331
--- /dev/null
+++ b/components/metrics/call_stack_profile_collector.cc
@@ -0,0 +1,40 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/call_stack_profile_collector.h"
+
+#include <memory>
+#include <utility>
+
+#include "components/metrics/call_stack_profile_encoding.h"
+#include "components/metrics/call_stack_profile_metrics_provider.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+
+namespace metrics {
+
+CallStackProfileCollector::CallStackProfileCollector(
+    CallStackProfileParams::Process expected_process)
+    : expected_process_(expected_process) {}
+
+CallStackProfileCollector::~CallStackProfileCollector() {}
+
+// static
+void CallStackProfileCollector::Create(
+    CallStackProfileParams::Process expected_process,
+    mojom::CallStackProfileCollectorRequest request) {
+  mojo::MakeStrongBinding(
+      std::make_unique<CallStackProfileCollector>(expected_process),
+      std::move(request));
+}
+
+void CallStackProfileCollector::Collect(base::TimeTicks start_timestamp,
+                                        SampledProfile profile) {
+  if (profile.process() != ToExecutionContextProcess(expected_process_))
+    return;
+
+  CallStackProfileMetricsProvider::ReceiveCompletedProfile(start_timestamp,
+                                                           std::move(profile));
+}
+
+}  // namespace metrics
diff --git a/components/metrics/call_stack_profile_collector.h b/components/metrics/call_stack_profile_collector.h
new file mode 100644
index 0000000..0ffb94d
--- /dev/null
+++ b/components/metrics/call_stack_profile_collector.h
@@ -0,0 +1,40 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_COLLECTOR_H_
+#define COMPONENTS_METRICS_CALL_STACK_PROFILE_COLLECTOR_H_
+
+#include "base/macros.h"
+#include "components/metrics/call_stack_profile_params.h"
+#include "components/metrics/public/interfaces/call_stack_profile_collector.mojom.h"
+#include "third_party/metrics_proto/sampled_profile.pb.h"
+
+namespace metrics {
+
+class CallStackProfileCollector : public mojom::CallStackProfileCollector {
+ public:
+  explicit CallStackProfileCollector(
+      CallStackProfileParams::Process expected_process);
+  ~CallStackProfileCollector() override;
+
+  // Create a collector to receive profiles from |expected_process|.
+  static void Create(CallStackProfileParams::Process expected_process,
+                     mojom::CallStackProfileCollectorRequest request);
+
+  // mojom::CallStackProfileCollector:
+  void Collect(base::TimeTicks start_timestamp,
+               SampledProfile profile) override;
+
+ private:
+  // Profile params are validated to come from this process. Profiles with a
+  // different process declared in the params are considered untrustworthy and
+  // ignored.
+  const CallStackProfileParams::Process expected_process_;
+
+  DISALLOW_COPY_AND_ASSIGN(CallStackProfileCollector);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_CALL_STACK_PROFILE_COLLECTOR_H_
diff --git a/components/metrics/call_stack_profile_encoding.cc b/components/metrics/call_stack_profile_encoding.cc
new file mode 100644
index 0000000..5d6a111
--- /dev/null
+++ b/components/metrics/call_stack_profile_encoding.cc
@@ -0,0 +1,67 @@
+// 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.
+
+#include "components/metrics/call_stack_profile_encoding.h"
+
+namespace metrics {
+
+Process ToExecutionContextProcess(CallStackProfileParams::Process process) {
+  switch (process) {
+    case CallStackProfileParams::UNKNOWN_PROCESS:
+      return UNKNOWN_PROCESS;
+    case CallStackProfileParams::BROWSER_PROCESS:
+      return BROWSER_PROCESS;
+    case CallStackProfileParams::RENDERER_PROCESS:
+      return RENDERER_PROCESS;
+    case CallStackProfileParams::GPU_PROCESS:
+      return GPU_PROCESS;
+    case CallStackProfileParams::UTILITY_PROCESS:
+      return UTILITY_PROCESS;
+    case CallStackProfileParams::ZYGOTE_PROCESS:
+      return ZYGOTE_PROCESS;
+    case CallStackProfileParams::SANDBOX_HELPER_PROCESS:
+      return SANDBOX_HELPER_PROCESS;
+    case CallStackProfileParams::PPAPI_PLUGIN_PROCESS:
+      return PPAPI_PLUGIN_PROCESS;
+    case CallStackProfileParams::PPAPI_BROKER_PROCESS:
+      return PPAPI_BROKER_PROCESS;
+  }
+  NOTREACHED();
+  return UNKNOWN_PROCESS;
+}
+
+Thread ToExecutionContextThread(CallStackProfileParams::Thread thread) {
+  switch (thread) {
+    case CallStackProfileParams::UNKNOWN_THREAD:
+      return UNKNOWN_THREAD;
+    case CallStackProfileParams::MAIN_THREAD:
+      return MAIN_THREAD;
+    case CallStackProfileParams::IO_THREAD:
+      return IO_THREAD;
+    case CallStackProfileParams::COMPOSITOR_THREAD:
+      return COMPOSITOR_THREAD;
+  }
+  NOTREACHED();
+  return UNKNOWN_THREAD;
+}
+
+SampledProfile::TriggerEvent ToSampledProfileTriggerEvent(
+    CallStackProfileParams::Trigger trigger) {
+  switch (trigger) {
+    case CallStackProfileParams::UNKNOWN:
+      return SampledProfile::UNKNOWN_TRIGGER_EVENT;
+    case CallStackProfileParams::PROCESS_STARTUP:
+      return SampledProfile::PROCESS_STARTUP;
+    case CallStackProfileParams::JANKY_TASK:
+      return SampledProfile::JANKY_TASK;
+    case CallStackProfileParams::THREAD_HUNG:
+      return SampledProfile::THREAD_HUNG;
+    case CallStackProfileParams::PERIODIC_COLLECTION:
+      return SampledProfile::PERIODIC_COLLECTION;
+  }
+  NOTREACHED();
+  return SampledProfile::UNKNOWN_TRIGGER_EVENT;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/call_stack_profile_encoding.h b/components/metrics/call_stack_profile_encoding.h
new file mode 100644
index 0000000..c71ace7
--- /dev/null
+++ b/components/metrics/call_stack_profile_encoding.h
@@ -0,0 +1,28 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_ENCODING_H_
+#define COMPONENTS_METRICS_CALL_STACK_PROFILE_ENCODING_H_
+
+#include "components/metrics/call_stack_profile_params.h"
+#include "third_party/metrics_proto/sampled_profile.pb.h"
+
+namespace metrics {
+
+// Translates CallStackProfileParams's process to the corresponding execution
+// context Process.
+Process ToExecutionContextProcess(CallStackProfileParams::Process process);
+
+// Translates CallStackProfileParams's thread to the corresponding
+// SampledProfile Thread.
+Thread ToExecutionContextThread(CallStackProfileParams::Thread thread);
+
+// Translates CallStackProfileParams's trigger to the corresponding
+// SampledProfile TriggerEvent.
+SampledProfile::TriggerEvent ToSampledProfileTriggerEvent(
+    CallStackProfileParams::Trigger trigger);
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_CALL_STACK_PROFILE_ENCODING_H_
diff --git a/components/metrics/call_stack_profile_metrics_provider.cc b/components/metrics/call_stack_profile_metrics_provider.cc
new file mode 100644
index 0000000..63160d1
--- /dev/null
+++ b/components/metrics/call_stack_profile_metrics_provider.cc
@@ -0,0 +1,233 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/call_stack_profile_metrics_provider.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
+
+namespace metrics {
+
+namespace {
+
+// Cap the number of pending profiles to avoid excessive memory usage when
+// profile uploads are delayed (e.g. due to being offline). 1250 profiles
+// corresponds to 80MB of storage. Capping at this threshold loses approximately
+// 0.5% of profiles on canary and dev.
+// TODO(chengx): Remove this threshold after moving to a more memory-efficient
+// profile representation.
+const size_t kMaxPendingProfiles = 1250;
+
+// ProfileState --------------------------------------------------------------
+
+// A set of profiles and the start time of the collection associated with them.
+struct ProfileState {
+  ProfileState(base::TimeTicks start_timestamp, SampledProfile profile);
+  ProfileState(ProfileState&&);
+  ProfileState& operator=(ProfileState&&);
+
+  // The time at which the profile collection was started.
+  base::TimeTicks start_timestamp;
+
+  // The call stack profile message collected by the profiler.
+  SampledProfile profile;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ProfileState);
+};
+
+ProfileState::ProfileState(base::TimeTicks start_timestamp,
+                           SampledProfile profile)
+    : start_timestamp(start_timestamp), profile(std::move(profile)) {}
+
+ProfileState::ProfileState(ProfileState&&) = default;
+
+// Some versions of GCC need this for push_back to work with std::move.
+ProfileState& ProfileState::operator=(ProfileState&&) = default;
+
+// PendingProfiles ------------------------------------------------------------
+
+// Singleton class responsible for retaining profiles received from
+// CallStackProfileBuilder. These are then sent to UMA on the invocation of
+// CallStackProfileMetricsProvider::ProvideCurrentSessionData(). We need to
+// store the profiles outside of a CallStackProfileMetricsProvider instance
+// since callers may start profiling before the CallStackProfileMetricsProvider
+// is created.
+//
+// Member functions on this class may be called on any thread.
+class PendingProfiles {
+ public:
+  static PendingProfiles* GetInstance();
+
+  void Swap(std::vector<ProfileState>* profiles);
+
+  // Enables the collection of profiles by CollectProfilesIfCollectionEnabled if
+  // |enabled| is true. Otherwise, clears current profiles and ignores profiles
+  // provided to future invocations of CollectProfilesIfCollectionEnabled.
+  void SetCollectionEnabled(bool enabled);
+
+  // True if profiles are being collected.
+  bool IsCollectionEnabled() const;
+
+  // Adds |profile| to the list of profiles if collection is enabled; it is
+  // not const& because it must be passed with std::move.
+  void CollectProfilesIfCollectionEnabled(ProfileState profile);
+
+  // Allows testing against the initial state multiple times.
+  void ResetToDefaultStateForTesting();
+
+ private:
+  friend struct base::DefaultSingletonTraits<PendingProfiles>;
+
+  PendingProfiles();
+  ~PendingProfiles() = default;
+
+  mutable base::Lock lock_;
+
+  // If true, profiles provided to CollectProfilesIfCollectionEnabled should be
+  // collected. Otherwise they will be ignored.
+  bool collection_enabled_;
+
+  // The last time collection was disabled. Used to determine if collection was
+  // disabled at any point since a profile was started.
+  base::TimeTicks last_collection_disable_time_;
+
+  // The last time collection was enabled. Used to determine if collection was
+  // enabled at any point since a profile was started.
+  base::TimeTicks last_collection_enable_time_;
+
+  // The set of completed profiles that should be reported.
+  std::vector<ProfileState> profiles_;
+
+  DISALLOW_COPY_AND_ASSIGN(PendingProfiles);
+};
+
+// static
+PendingProfiles* PendingProfiles::GetInstance() {
+  // Leaky for performance rather than correctness reasons.
+  return base::Singleton<PendingProfiles,
+                         base::LeakySingletonTraits<PendingProfiles>>::get();
+}
+
+void PendingProfiles::Swap(std::vector<ProfileState>* profiles) {
+  base::AutoLock scoped_lock(lock_);
+  profiles_.swap(*profiles);
+}
+
+void PendingProfiles::SetCollectionEnabled(bool enabled) {
+  base::AutoLock scoped_lock(lock_);
+
+  collection_enabled_ = enabled;
+
+  if (!collection_enabled_) {
+    profiles_.clear();
+    last_collection_disable_time_ = base::TimeTicks::Now();
+  } else {
+    last_collection_enable_time_ = base::TimeTicks::Now();
+  }
+}
+
+bool PendingProfiles::IsCollectionEnabled() const {
+  base::AutoLock scoped_lock(lock_);
+  return collection_enabled_;
+}
+
+void PendingProfiles::CollectProfilesIfCollectionEnabled(ProfileState profile) {
+  base::AutoLock scoped_lock(lock_);
+
+  // Scenario 1: stop collection if it is disabled.
+  if (!collection_enabled_)
+    return;
+
+  // Scenario 2: stop collection if it is disabled after the start of collection
+  // for this profile.
+  if (!last_collection_disable_time_.is_null() &&
+      last_collection_disable_time_ >= profile.start_timestamp) {
+    return;
+  }
+
+  // Scenario 3: stop collection if it is disabled before the start of
+  // collection and re-enabled after the start. Note that this is different from
+  // scenario 1 where re-enabling never happens.
+  if (!last_collection_disable_time_.is_null() &&
+      !last_collection_enable_time_.is_null() &&
+      last_collection_enable_time_ >= profile.start_timestamp) {
+    return;
+  }
+
+  if (profiles_.size() < kMaxPendingProfiles)
+    profiles_.push_back(std::move(profile));
+}
+
+void PendingProfiles::ResetToDefaultStateForTesting() {
+  base::AutoLock scoped_lock(lock_);
+
+  collection_enabled_ = true;
+  last_collection_disable_time_ = base::TimeTicks();
+  last_collection_enable_time_ = base::TimeTicks();
+  profiles_.clear();
+}
+
+// |collection_enabled_| is initialized to true to collect any profiles that are
+// generated prior to creation of the CallStackProfileMetricsProvider. The
+// ultimate disposition of these pre-creation collected profiles will be
+// determined by the initial recording state provided to
+// CallStackProfileMetricsProvider.
+PendingProfiles::PendingProfiles() : collection_enabled_(true) {}
+
+}  // namespace
+
+// CallStackProfileMetricsProvider --------------------------------------------
+
+const base::Feature CallStackProfileMetricsProvider::kEnableReporting = {
+    "SamplingProfilerReporting", base::FEATURE_DISABLED_BY_DEFAULT};
+
+CallStackProfileMetricsProvider::CallStackProfileMetricsProvider() {}
+
+CallStackProfileMetricsProvider::~CallStackProfileMetricsProvider() {}
+
+// static
+void CallStackProfileMetricsProvider::ReceiveCompletedProfile(
+    base::TimeTicks profile_start_time,
+    SampledProfile profile) {
+  PendingProfiles::GetInstance()->CollectProfilesIfCollectionEnabled(
+      ProfileState(profile_start_time, std::move(profile)));
+}
+
+void CallStackProfileMetricsProvider::OnRecordingEnabled() {
+  PendingProfiles::GetInstance()->SetCollectionEnabled(
+      base::FeatureList::IsEnabled(kEnableReporting));
+}
+
+void CallStackProfileMetricsProvider::OnRecordingDisabled() {
+  PendingProfiles::GetInstance()->SetCollectionEnabled(false);
+}
+
+void CallStackProfileMetricsProvider::ProvideCurrentSessionData(
+    ChromeUserMetricsExtension* uma_proto) {
+  std::vector<ProfileState> pending_profiles;
+  PendingProfiles::GetInstance()->Swap(&pending_profiles);
+
+  DCHECK(base::FeatureList::IsEnabled(kEnableReporting) ||
+         pending_profiles.empty());
+
+  for (const auto& profile_state : pending_profiles) {
+    SampledProfile* sampled_profile = uma_proto->add_sampled_profile();
+    *sampled_profile = std::move(profile_state.profile);
+  }
+}
+
+// static
+void CallStackProfileMetricsProvider::ResetStaticStateForTesting() {
+  PendingProfiles::GetInstance()->ResetToDefaultStateForTesting();
+}
+
+}  // namespace metrics
diff --git a/components/metrics/call_stack_profile_metrics_provider.h b/components/metrics/call_stack_profile_metrics_provider.h
new file mode 100644
index 0000000..e977040
--- /dev/null
+++ b/components/metrics/call_stack_profile_metrics_provider.h
@@ -0,0 +1,49 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_
+
+#include "base/feature_list.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_provider.h"
+#include "third_party/metrics_proto/sampled_profile.pb.h"
+
+namespace metrics {
+
+class ChromeUserMetricsExtension;
+
+// Performs metrics logging for the stack sampling profiler.
+class CallStackProfileMetricsProvider : public MetricsProvider {
+ public:
+  CallStackProfileMetricsProvider();
+  ~CallStackProfileMetricsProvider() override;
+
+  // Will be invoked on either the main thread or the profiler's thread.
+  // Provides the profile to PendingProfiles to append, if the collecting state
+  // allows. |profile| is not const& because it must be passed with std::move.
+  static void ReceiveCompletedProfile(base::TimeTicks profile_start_time,
+                                      SampledProfile profile);
+
+  // MetricsProvider:
+  void OnRecordingEnabled() override;
+  void OnRecordingDisabled() override;
+  void ProvideCurrentSessionData(
+      ChromeUserMetricsExtension* uma_proto) override;
+
+ protected:
+  // base::Feature for reporting profiles. Provided here for test use.
+  static const base::Feature kEnableReporting;
+
+  // Reset the static state to the defaults after startup.
+  static void ResetStaticStateForTesting();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CallStackProfileMetricsProvider);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_CALL_STACK_PROFILE_METRICS_PROVIDER_H_
diff --git a/components/metrics/call_stack_profile_metrics_provider_unittest.cc b/components/metrics/call_stack_profile_metrics_provider_unittest.cc
new file mode 100644
index 0000000..b326252
--- /dev/null
+++ b/components/metrics/call_stack_profile_metrics_provider_unittest.cc
@@ -0,0 +1,122 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/call_stack_profile_metrics_provider.h"
+
+#include <utility>
+
+#include "base/macros.h"
+#include "base/test/scoped_feature_list.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
+
+namespace metrics {
+
+// This test fixture enables the feature that
+// CallStackProfileMetricsProvider depends on to report a profile.
+class CallStackProfileMetricsProviderTest : public testing::Test {
+ public:
+  CallStackProfileMetricsProviderTest() {
+    scoped_feature_list_.InitAndEnableFeature(TestState::kEnableReporting);
+    TestState::ResetStaticStateForTesting();
+  }
+
+  ~CallStackProfileMetricsProviderTest() override {}
+
+ private:
+  // Exposes the feature from the CallStackProfileMetricsProvider.
+  class TestState : public CallStackProfileMetricsProvider {
+   public:
+    using CallStackProfileMetricsProvider::kEnableReporting;
+    using CallStackProfileMetricsProvider::ResetStaticStateForTesting;
+  };
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(CallStackProfileMetricsProviderTest);
+};
+
+// Checks that the pending profile is passed to ProvideCurrentSessionData.
+TEST_F(CallStackProfileMetricsProviderTest, ProvideCurrentSessionData) {
+  CallStackProfileMetricsProvider provider;
+  provider.OnRecordingEnabled();
+  CallStackProfileMetricsProvider::ReceiveCompletedProfile(
+      base::TimeTicks::Now(), SampledProfile());
+  ChromeUserMetricsExtension uma_proto;
+  provider.ProvideCurrentSessionData(&uma_proto);
+  ASSERT_EQ(1, uma_proto.sampled_profile().size());
+}
+
+// Checks that the pending profile is provided to ProvideCurrentSessionData
+// when collected before CallStackProfileMetricsProvider is instantiated.
+TEST_F(CallStackProfileMetricsProviderTest,
+       ProfileProvidedWhenCollectedBeforeInstantiation) {
+  CallStackProfileMetricsProvider::ReceiveCompletedProfile(
+      base::TimeTicks::Now(), SampledProfile());
+  CallStackProfileMetricsProvider provider;
+  provider.OnRecordingEnabled();
+  ChromeUserMetricsExtension uma_proto;
+  provider.ProvideCurrentSessionData(&uma_proto);
+  EXPECT_EQ(1, uma_proto.sampled_profile_size());
+}
+
+// Checks that the pending profile is not provided to ProvideCurrentSessionData
+// while recording is disabled.
+TEST_F(CallStackProfileMetricsProviderTest, ProfileNotProvidedWhileDisabled) {
+  CallStackProfileMetricsProvider provider;
+  provider.OnRecordingDisabled();
+  CallStackProfileMetricsProvider::ReceiveCompletedProfile(
+      base::TimeTicks::Now(), SampledProfile());
+  ChromeUserMetricsExtension uma_proto;
+  provider.ProvideCurrentSessionData(&uma_proto);
+  EXPECT_EQ(0, uma_proto.sampled_profile_size());
+}
+
+// Checks that the pending profile is not provided to ProvideCurrentSessionData
+// if recording is disabled while profiling.
+TEST_F(CallStackProfileMetricsProviderTest,
+       ProfileNotProvidedAfterChangeToDisabled) {
+  CallStackProfileMetricsProvider provider;
+  provider.OnRecordingEnabled();
+  base::TimeTicks profile_start_time = base::TimeTicks::Now();
+  provider.OnRecordingDisabled();
+  CallStackProfileMetricsProvider::ReceiveCompletedProfile(profile_start_time,
+                                                           SampledProfile());
+  ChromeUserMetricsExtension uma_proto;
+  provider.ProvideCurrentSessionData(&uma_proto);
+  EXPECT_EQ(0, uma_proto.sampled_profile_size());
+}
+
+// Checks that the pending profile is not provided to ProvideCurrentSessionData
+// if recording is enabled, but then disabled and reenabled while profiling.
+TEST_F(CallStackProfileMetricsProviderTest,
+       ProfileNotProvidedAfterChangeToDisabledThenEnabled) {
+  CallStackProfileMetricsProvider provider;
+  provider.OnRecordingEnabled();
+  base::TimeTicks profile_start_time = base::TimeTicks::Now();
+  provider.OnRecordingDisabled();
+  provider.OnRecordingEnabled();
+  CallStackProfileMetricsProvider::ReceiveCompletedProfile(profile_start_time,
+                                                           SampledProfile());
+  ChromeUserMetricsExtension uma_proto;
+  provider.ProvideCurrentSessionData(&uma_proto);
+  EXPECT_EQ(0, uma_proto.sampled_profile_size());
+}
+
+// Checks that the pending profile is provided to ProvideCurrentSessionData
+// if recording is disabled, but then enabled while profiling.
+TEST_F(CallStackProfileMetricsProviderTest,
+       ProfileNotProvidedAfterChangeFromDisabled) {
+  CallStackProfileMetricsProvider provider;
+  provider.OnRecordingDisabled();
+  base::TimeTicks profile_start_time = base::TimeTicks::Now();
+  provider.OnRecordingEnabled();
+  CallStackProfileMetricsProvider::ReceiveCompletedProfile(profile_start_time,
+                                                           SampledProfile());
+  ChromeUserMetricsExtension uma_proto;
+  provider.ProvideCurrentSessionData(&uma_proto);
+  EXPECT_EQ(0, uma_proto.sampled_profile_size());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/call_stack_profile_params.h b/components/metrics/call_stack_profile_params.h
new file mode 100644
index 0000000..5e78ef1
--- /dev/null
+++ b/components/metrics/call_stack_profile_params.h
@@ -0,0 +1,71 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_CALL_STACK_PROFILE_PARAMS_H_
+#define COMPONENTS_METRICS_CALL_STACK_PROFILE_PARAMS_H_
+
+#include "base/time/time.h"
+
+namespace metrics {
+
+// Parameters to pass back to the metrics provider.
+struct CallStackProfileParams {
+  // The process in which the collection occurred.
+  enum Process {
+    UNKNOWN_PROCESS,
+    BROWSER_PROCESS,
+    RENDERER_PROCESS,
+    GPU_PROCESS,
+    UTILITY_PROCESS,
+    ZYGOTE_PROCESS,
+    SANDBOX_HELPER_PROCESS,
+    PPAPI_PLUGIN_PROCESS,
+    PPAPI_BROKER_PROCESS
+  };
+
+  // The thread from which the collection occurred.
+  enum Thread {
+    UNKNOWN_THREAD,
+
+    // Each process has a 'main thread'. In the Browser process, the 'main
+    // thread' is also often called the 'UI thread'.
+    MAIN_THREAD,
+    IO_THREAD,
+
+    // Compositor thread (can be in both renderer and gpu processes).
+    COMPOSITOR_THREAD,
+  };
+
+  // The event that triggered the profile collection.
+  enum Trigger {
+    UNKNOWN,
+    PROCESS_STARTUP,
+    JANKY_TASK,
+    THREAD_HUNG,
+    PERIODIC_COLLECTION,
+    TRIGGER_LAST = PERIODIC_COLLECTION
+  };
+
+  // The default constructor is required for mojo and should not be used
+  // otherwise. A valid trigger should always be specified.
+  constexpr CallStackProfileParams()
+      : CallStackProfileParams(UNKNOWN_PROCESS, UNKNOWN_THREAD, UNKNOWN) {}
+  constexpr CallStackProfileParams(Process process,
+                                   Thread thread,
+                                   Trigger trigger)
+      : process(process), thread(thread), trigger(trigger) {}
+
+  // The collection process.
+  Process process;
+
+  // The collection thread.
+  Thread thread;
+
+  // The triggering event.
+  Trigger trigger;
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_CALL_STACK_PROFILE_PARAMS_H_
diff --git a/components/metrics/child_call_stack_profile_collector.cc b/components/metrics/child_call_stack_profile_collector.cc
new file mode 100644
index 0000000..0ae6d47
--- /dev/null
+++ b/components/metrics/child_call_stack_profile_collector.cc
@@ -0,0 +1,81 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/child_call_stack_profile_collector.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+
+namespace metrics {
+
+ChildCallStackProfileCollector::ProfileState::ProfileState() = default;
+ChildCallStackProfileCollector::ProfileState::ProfileState(ProfileState&&) =
+    default;
+
+ChildCallStackProfileCollector::ProfileState::ProfileState(
+    base::TimeTicks start_timestamp,
+    SampledProfile profile)
+    : start_timestamp(start_timestamp), profile(std::move(profile)) {}
+
+ChildCallStackProfileCollector::ProfileState::~ProfileState() = default;
+
+// Some versions of GCC need this for push_back to work with std::move.
+ChildCallStackProfileCollector::ProfileState&
+ChildCallStackProfileCollector::ProfileState::operator=(ProfileState&&) =
+    default;
+
+ChildCallStackProfileCollector::ChildCallStackProfileCollector() {}
+
+ChildCallStackProfileCollector::~ChildCallStackProfileCollector() {}
+
+void ChildCallStackProfileCollector::SetParentProfileCollector(
+    metrics::mojom::CallStackProfileCollectorPtr parent_collector) {
+  base::AutoLock alock(lock_);
+  // This function should only invoked once, during the mode of operation when
+  // retaining profiles after construction.
+  DCHECK(retain_profiles_);
+  retain_profiles_ = false;
+  task_runner_ = base::ThreadTaskRunnerHandle::Get();
+  // This should only be set one time per child process.
+  DCHECK(!parent_collector_);
+  parent_collector_ = std::move(parent_collector);
+  if (parent_collector_) {
+    for (ProfileState& state : profiles_) {
+      parent_collector_->Collect(state.start_timestamp,
+                                 std::move(state.profile));
+    }
+  }
+  profiles_.clear();
+}
+
+void ChildCallStackProfileCollector::Collect(base::TimeTicks start_timestamp,
+                                             SampledProfile profile) {
+  base::AutoLock alock(lock_);
+  if (task_runner_ &&
+      // The profiler thread does not have a task runner. Attempting to
+      // invoke Get() on it results in a DCHECK.
+      (!base::ThreadTaskRunnerHandle::IsSet() ||
+       base::ThreadTaskRunnerHandle::Get() != task_runner_)) {
+    // Post back to the thread that owns the the parent interface.
+    task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&ChildCallStackProfileCollector::Collect,
+                                  // This class has lazy instance lifetime.
+                                  base::Unretained(this), start_timestamp,
+                                  std::move(profile)));
+    return;
+  }
+
+  if (parent_collector_) {
+    parent_collector_->Collect(start_timestamp, std::move(profile));
+  } else if (retain_profiles_) {
+    profiles_.push_back(ProfileState(start_timestamp, std::move(profile)));
+  }
+}
+
+}  // namespace metrics
diff --git a/components/metrics/child_call_stack_profile_collector.h b/components/metrics/child_call_stack_profile_collector.h
new file mode 100644
index 0000000..27815d8
--- /dev/null
+++ b/components/metrics/child_call_stack_profile_collector.h
@@ -0,0 +1,115 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_CHILD_CALL_STACK_PROFILE_COLLECTOR_H_
+#define COMPONENTS_METRICS_CHILD_CALL_STACK_PROFILE_COLLECTOR_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/synchronization/lock.h"
+#include "components/metrics/public/interfaces/call_stack_profile_collector.mojom.h"
+#include "third_party/metrics_proto/sampled_profile.pb.h"
+
+namespace service_manager {
+class InterfaceProvider;
+}
+
+namespace metrics {
+
+// ChildCallStackProfileCollector collects stacks at startup, caching them
+// internally until a CallStackProfileCollector interface is available. If a
+// CallStackProfileCollector is provided via the InterfaceProvider supplied to
+// SetParentProfileCollector, the cached stacks are sent via that interface. All
+// future stacks received via callbacks supplied by GetProfilerCallback are sent
+// via that interface as well.
+//
+// If no CallStackProfileCollector is provided via InterfaceProvider, any cached
+// stacks and all future stacks received via callbacks supplied by
+// GetProfilerCallback are flushed. In typical usage this should not happen
+// because the browser is expected to always supply a CallStackProfileCollector.
+//
+// This class is only necessary if a CallStackProfileCollector is not available
+// at the time the profiler is created. Otherwise the CallStackProfileCollector
+// can be used directly.
+//
+// To use, create as a leaky lazy instance:
+//
+//   base::LazyInstance<metrics::ChildCallStackProfileCollector>::Leaky
+//       g_call_stack_profile_collector = LAZY_INSTANCE_INITIALIZER;
+//
+// Then, invoke Collect() in CallStackProfileBuilder::OnProfileCompleted() to
+// collect a profile.
+//
+// When the mojo InterfaceProvider becomes available, provide it via
+// SetParentProfileCollector().
+class ChildCallStackProfileCollector {
+ public:
+  ChildCallStackProfileCollector();
+  ~ChildCallStackProfileCollector();
+
+  // Sets the CallStackProfileCollector interface from |parent_collector|. This
+  // function MUST be invoked exactly once, regardless of whether
+  // |parent_collector| is null, as it flushes pending data in either case.
+  void SetParentProfileCollector(
+      metrics::mojom::CallStackProfileCollectorPtr parent_collector);
+
+  // Collects |profile| whose collection start time is |start_timestamp|.
+  void Collect(base::TimeTicks start_timestamp, SampledProfile profile);
+
+ private:
+  friend class ChildCallStackProfileCollectorTest;
+
+  // Bundles together a collected profile and the collection state for
+  // storage, pending availability of the parent mojo interface. |profile|
+  // is not const& because it must be passed with std::move.
+  struct ProfileState {
+    ProfileState();
+    ProfileState(ProfileState&&);
+    ProfileState(base::TimeTicks start_timestamp, SampledProfile profile);
+    ~ProfileState();
+
+    ProfileState& operator=(ProfileState&&);
+
+    base::TimeTicks start_timestamp;
+
+    // The sampled profile.
+    SampledProfile profile;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(ProfileState);
+  };
+
+  // This object may be accessed on any thread, including the profiler
+  // thread. The expected use case for the object is to be created and have
+  // GetProfilerCallback before the message loop starts, which prevents the use
+  // of PostTask and the like for inter-thread communication.
+  base::Lock lock_;
+
+  // Whether to retain the profile when the interface is not set. Remains true
+  // until the invocation of SetParentProfileCollector(), at which point it is
+  // false for the rest of the object lifetime.
+  bool retain_profiles_ = true;
+
+  // The task runner associated with the parent interface.
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+  // The interface to use to collect the stack profiles provided to this
+  // object. Initially null until SetParentProfileCollector() is invoked, at
+  // which point it may either become set or remain null. If set, stacks are
+  // collected via the interface, otherwise they are ignored.
+  mojom::CallStackProfileCollectorPtr parent_collector_;
+
+  // Profiles being cached by this object, pending a parent interface to be
+  // supplied.
+  std::vector<ProfileState> profiles_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChildCallStackProfileCollector);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_CHILD_CALL_STACK_PROFILE_COLLECTOR_H_
diff --git a/components/metrics/child_call_stack_profile_collector_unittest.cc b/components/metrics/child_call_stack_profile_collector_unittest.cc
new file mode 100644
index 0000000..24cc30e
--- /dev/null
+++ b/components/metrics/child_call_stack_profile_collector_unittest.cc
@@ -0,0 +1,112 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/child_call_stack_profile_collector.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+class ChildCallStackProfileCollectorTest : public testing::Test {
+ protected:
+  class Receiver : public mojom::CallStackProfileCollector {
+   public:
+    explicit Receiver(mojom::CallStackProfileCollectorRequest request)
+        : binding_(this, std::move(request)) {}
+    ~Receiver() override {}
+
+    void Collect(base::TimeTicks start_timestamp,
+                 SampledProfile profile) override {
+      this->profiles.push_back(ChildCallStackProfileCollector::ProfileState(
+          start_timestamp, std::move(profile)));
+    }
+
+    std::vector<ChildCallStackProfileCollector::ProfileState> profiles;
+
+   private:
+    mojo::Binding<mojom::CallStackProfileCollector> binding_;
+
+    DISALLOW_COPY_AND_ASSIGN(Receiver);
+  };
+
+  ChildCallStackProfileCollectorTest()
+      : receiver_impl_(new Receiver(MakeRequest(&receiver_))) {}
+
+  void CollectEmptyProfile() {
+    child_collector_.Collect(base::TimeTicks::Now(), SampledProfile());
+  }
+
+  const std::vector<ChildCallStackProfileCollector::ProfileState>& profiles()
+      const {
+    return child_collector_.profiles_;
+  }
+
+  base::MessageLoop loop_;
+  mojom::CallStackProfileCollectorPtr receiver_;
+  std::unique_ptr<Receiver> receiver_impl_;
+  ChildCallStackProfileCollector child_collector_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ChildCallStackProfileCollectorTest);
+};
+
+// Test the behavior when an interface is provided.
+TEST_F(ChildCallStackProfileCollectorTest, InterfaceProvided) {
+  EXPECT_EQ(0u, profiles().size());
+
+  // Add a profile before providing the interface.
+  CollectEmptyProfile();
+  ASSERT_EQ(1u, profiles().size());
+  base::TimeTicks start_timestamp = profiles()[0].start_timestamp;
+  EXPECT_GE(base::TimeDelta::FromMilliseconds(10),
+            base::TimeTicks::Now() - start_timestamp);
+
+  // Set the interface. The profiles should be passed to it.
+  child_collector_.SetParentProfileCollector(std::move(receiver_));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(0u, profiles().size());
+  ASSERT_EQ(1u, receiver_impl_->profiles.size());
+  EXPECT_EQ(start_timestamp, receiver_impl_->profiles[0].start_timestamp);
+
+  // Add a profile after providing the interface. It should also be passed.
+  receiver_impl_->profiles.clear();
+  CollectEmptyProfile();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(0u, profiles().size());
+  ASSERT_EQ(1u, receiver_impl_->profiles.size());
+  EXPECT_GE(base::TimeDelta::FromMilliseconds(10),
+            (base::TimeTicks::Now() -
+             receiver_impl_->profiles[0].start_timestamp));
+}
+
+TEST_F(ChildCallStackProfileCollectorTest, InterfaceNotProvided) {
+  EXPECT_EQ(0u, profiles().size());
+
+  // Add a profile before providing a null interface.
+  CollectEmptyProfile();
+  ASSERT_EQ(1u, profiles().size());
+  EXPECT_GE(base::TimeDelta::FromMilliseconds(10),
+            base::TimeTicks::Now() - profiles()[0].start_timestamp);
+
+  // Set the null interface. The profile should be flushed.
+  child_collector_.SetParentProfileCollector(
+      mojom::CallStackProfileCollectorPtr());
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(0u, profiles().size());
+
+  // Add a profile after providing a null interface. They should also be
+  // flushed.
+  CollectEmptyProfile();
+  EXPECT_EQ(0u, profiles().size());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/clean_exit_beacon.cc b/components/metrics/clean_exit_beacon.cc
new file mode 100644
index 0000000..74aefd8
--- /dev/null
+++ b/components/metrics/clean_exit_beacon.cc
@@ -0,0 +1,98 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/clean_exit_beacon.h"
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/registry.h"
+#endif
+
+namespace metrics {
+
+CleanExitBeacon::CleanExitBeacon(const base::string16& backup_registry_key,
+                                 PrefService* local_state)
+    : local_state_(local_state),
+      initial_value_(local_state->GetBoolean(prefs::kStabilityExitedCleanly)),
+      initial_browser_last_live_timestamp_(
+          local_state->GetTime(prefs::kStabilityBrowserLastLiveTimeStamp)),
+      backup_registry_key_(backup_registry_key) {
+  DCHECK_NE(PrefService::INITIALIZATION_STATUS_WAITING,
+            local_state_->GetInitializationStatus());
+
+#if defined(OS_WIN)
+  // An enumeration of all possible permutations of the the beacon state in the
+  // registry and in Local State.
+  enum {
+    DIRTY_DIRTY,
+    DIRTY_CLEAN,
+    CLEAN_DIRTY,
+    CLEAN_CLEAN,
+    MISSING_DIRTY,
+    MISSING_CLEAN,
+    NUM_CONSISTENCY_ENUMS
+  } consistency = DIRTY_DIRTY;
+
+  base::win::RegKey regkey;
+  DWORD value = 0u;
+  if (regkey.Open(HKEY_CURRENT_USER,
+                  backup_registry_key_.c_str(),
+                  KEY_ALL_ACCESS) == ERROR_SUCCESS &&
+      regkey.ReadValueDW(
+          base::ASCIIToUTF16(prefs::kStabilityExitedCleanly).c_str(), &value) ==
+          ERROR_SUCCESS) {
+    if (value)
+      consistency = initial_value_ ? CLEAN_CLEAN : CLEAN_DIRTY;
+    else
+      consistency = initial_value_ ? DIRTY_CLEAN : DIRTY_DIRTY;
+  } else {
+    consistency = initial_value_ ? MISSING_CLEAN : MISSING_DIRTY;
+  }
+
+  UMA_HISTOGRAM_ENUMERATION(
+      "UMA.CleanExitBeaconConsistency", consistency, NUM_CONSISTENCY_ENUMS);
+#endif
+}
+
+CleanExitBeacon::~CleanExitBeacon() {
+}
+
+// static
+void CleanExitBeacon::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterBooleanPref(prefs::kStabilityExitedCleanly, true);
+
+  registry->RegisterTimePref(prefs::kStabilityBrowserLastLiveTimeStamp,
+                             base::Time(), PrefRegistry::LOSSY_PREF);
+}
+
+void CleanExitBeacon::WriteBeaconValue(bool value) {
+  UpdateLastLiveTimestamp();
+  local_state_->SetBoolean(prefs::kStabilityExitedCleanly, value);
+
+#if defined(OS_WIN)
+  base::win::RegKey regkey;
+  if (regkey.Create(HKEY_CURRENT_USER,
+                    backup_registry_key_.c_str(),
+                    KEY_ALL_ACCESS) == ERROR_SUCCESS) {
+    regkey.WriteValue(
+        base::ASCIIToUTF16(prefs::kStabilityExitedCleanly).c_str(),
+        value ? 1u : 0u);
+  }
+#endif
+}
+
+void CleanExitBeacon::UpdateLastLiveTimestamp() {
+  local_state_->SetTime(prefs::kStabilityBrowserLastLiveTimeStamp,
+                        base::Time::Now());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/clean_exit_beacon.h b/components/metrics/clean_exit_beacon.h
new file mode 100644
index 0000000..0043596
--- /dev/null
+++ b/components/metrics/clean_exit_beacon.h
@@ -0,0 +1,63 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CLEAN_EXIT_BEACON_H_
+#define COMPONENTS_METRICS_CLEAN_EXIT_BEACON_H_
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace metrics {
+
+// Reads and updates a beacon used to detect whether the previous browser
+// process exited cleanly.
+class CleanExitBeacon {
+ public:
+  // Instantiates a CleanExitBeacon whose value is stored in |local_state|.
+  // |local_state| must be fully initialized.
+  // On Windows, |backup_registry_key| is used to store a backup of the beacon.
+  // It is ignored on other platforms.
+  CleanExitBeacon(
+      const base::string16& backup_registry_key,
+      PrefService* local_state);
+
+  ~CleanExitBeacon();
+
+  // Returns the original value of the beacon.
+  bool exited_cleanly() const { return initial_value_; }
+
+  // Returns the original value of the last live timestamp.
+  base::Time browser_last_live_timestamp() const {
+    return initial_browser_last_live_timestamp_;
+  }
+
+  // Writes the provided beacon value and updates the last live timestamp.
+  void WriteBeaconValue(bool exited_cleanly);
+
+  // Updates the last live timestamp.
+  void UpdateLastLiveTimestamp();
+
+  // Registers local state prefs used by this class.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+  PrefService* const local_state_;
+  const bool initial_value_;
+
+  // This is the value of the last live timestamp from local state at the
+  // time of construction. It notes a timestamp from the previous browser
+  // session when the browser was known to be alive.
+  const base::Time initial_browser_last_live_timestamp_;
+  const base::string16 backup_registry_key_;
+
+  DISALLOW_COPY_AND_ASSIGN(CleanExitBeacon);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_CLEAN_EXIT_BEACON_H_
diff --git a/components/metrics/client_info.cc b/components/metrics/client_info.cc
new file mode 100644
index 0000000..57ad394
--- /dev/null
+++ b/components/metrics/client_info.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/client_info.h"
+
+namespace metrics {
+
+ClientInfo::ClientInfo() : installation_date(0), reporting_enabled_date(0) {}
+
+ClientInfo::~ClientInfo() {}
+
+}  // namespace metrics
diff --git a/components/metrics/client_info.h b/components/metrics/client_info.h
new file mode 100644
index 0000000..7dcf5d8
--- /dev/null
+++ b/components/metrics/client_info.h
@@ -0,0 +1,36 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CLIENT_INFO_H_
+#define COMPONENTS_METRICS_CLIENT_INFO_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace metrics {
+
+// A data object used to pass data from outside the metrics component into the
+// metrics component.
+struct ClientInfo {
+ public:
+  ClientInfo();
+  ~ClientInfo();
+
+  // The metrics ID of this client: represented as a GUID string.
+  std::string client_id;
+
+  // The installation date: represented as an epoch time in seconds.
+  int64_t installation_date;
+
+  // The date on which metrics reporting was enabled: represented as an epoch
+  // time in seconds.
+  int64_t reporting_enabled_date;
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_CLIENT_INFO_H_
diff --git a/components/metrics/cloned_install_detector.cc b/components/metrics/cloned_install_detector.cc
new file mode 100644
index 0000000..3839744
--- /dev/null
+++ b/components/metrics/cloned_install_detector.cc
@@ -0,0 +1,98 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/cloned_install_detector.h"
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task/post_task.h"
+#include "base/task_runner_util.h"
+#include "components/metrics/machine_id_provider.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace metrics {
+
+namespace {
+
+uint32_t HashRawId(const std::string& value) {
+  uint64_t hash = base::HashMetricName(value);
+
+  // Only use 24 bits from the 64-bit hash.
+  return hash & ((1 << 24) - 1);
+}
+
+// State of the generated machine id in relation to the previously stored value.
+// Note: UMA histogram enum - don't re-order or remove entries
+enum MachineIdState {
+  ID_GENERATION_FAILED,
+  ID_NO_STORED_VALUE,
+  ID_CHANGED,
+  ID_UNCHANGED,
+  ID_ENUM_SIZE
+};
+
+// Logs the state of generating a machine id and comparing it to a stored value.
+void LogMachineIdState(MachineIdState state) {
+  UMA_HISTOGRAM_ENUMERATION("UMA.MachineIdState", state, ID_ENUM_SIZE);
+}
+
+}  // namespace
+
+ClonedInstallDetector::ClonedInstallDetector() : weak_ptr_factory_(this) {}
+
+ClonedInstallDetector::~ClonedInstallDetector() {
+}
+
+void ClonedInstallDetector::CheckForClonedInstall(PrefService* local_state) {
+  base::PostTaskWithTraitsAndReplyWithResult(
+      FROM_HERE,
+      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+      base::Bind(&MachineIdProvider::GetMachineId),
+      base::Bind(&ClonedInstallDetector::SaveMachineId,
+                 weak_ptr_factory_.GetWeakPtr(), local_state));
+}
+
+void ClonedInstallDetector::SaveMachineId(PrefService* local_state,
+                                          const std::string& raw_id) {
+  if (raw_id.empty()) {
+    LogMachineIdState(ID_GENERATION_FAILED);
+    local_state->ClearPref(prefs::kMetricsMachineId);
+    return;
+  }
+
+  int hashed_id = HashRawId(raw_id);
+
+  MachineIdState id_state = ID_NO_STORED_VALUE;
+  if (local_state->HasPrefPath(prefs::kMetricsMachineId)) {
+    if (local_state->GetInteger(prefs::kMetricsMachineId) != hashed_id) {
+      id_state = ID_CHANGED;
+      // TODO(jwd): Use a callback to set the reset pref. That way
+      // ClonedInstallDetector doesn't need to know about this pref.
+      local_state->SetBoolean(prefs::kMetricsResetIds, true);
+    } else {
+      id_state = ID_UNCHANGED;
+    }
+  }
+
+  LogMachineIdState(id_state);
+
+  local_state->SetInteger(prefs::kMetricsMachineId, hashed_id);
+}
+
+// static
+void ClonedInstallDetector::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterIntegerPref(prefs::kMetricsMachineId, 0);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/cloned_install_detector.h b/components/metrics/cloned_install_detector.h
new file mode 100644
index 0000000..6a728ae
--- /dev/null
+++ b/components/metrics/cloned_install_detector.h
@@ -0,0 +1,50 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_
+#define COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace metrics {
+
+// A class for detecting if an install is cloned. It does this by detecting
+// when the hardware running Chrome changes.
+class ClonedInstallDetector {
+ public:
+  ClonedInstallDetector();
+  virtual ~ClonedInstallDetector();
+
+  // Posts a task to |task_runner| to generate a machine ID and store it to a
+  // local state pref. If the newly generated ID is different than the
+  // previously stored one, then the install is considered cloned. The ID is a
+  // 24-bit value based off of machine characteristics. This value should never
+  // be sent over the network.
+  // TODO(jwd): Implement change detection.
+  void CheckForClonedInstall(PrefService* local_state);
+
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(ClonedInstallDetectorTest, SaveId);
+  FRIEND_TEST_ALL_PREFIXES(ClonedInstallDetectorTest, DetectClone);
+
+  // Converts raw_id into a 24-bit hash and stores the hash in |local_state|.
+  // |raw_id| is not a const ref because it's passed from a cross-thread post
+  // task.
+  void SaveMachineId(PrefService* local_state, const std::string& raw_id);
+
+  base::WeakPtrFactory<ClonedInstallDetector> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(ClonedInstallDetector);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_CLONED_INSTALL_DETECTOR_H_
diff --git a/components/metrics/cloned_install_detector_unittest.cc b/components/metrics/cloned_install_detector_unittest.cc
new file mode 100644
index 0000000..2e2ebd7
--- /dev/null
+++ b/components/metrics/cloned_install_detector_unittest.cc
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/cloned_install_detector.h"
+
+#include "components/metrics/machine_id_provider.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+const std::string kTestRawId = "test";
+// Hashed machine id for |kTestRawId|.
+const int kTestHashedId = 2216819;
+
+}  // namespace
+
+// TODO(jwd): Change these test to test the full flow and histogram outputs. It
+// should also remove the need to make the test a friend of
+// ClonedInstallDetector.
+TEST(ClonedInstallDetectorTest, SaveId) {
+  TestingPrefServiceSimple prefs;
+  ClonedInstallDetector::RegisterPrefs(prefs.registry());
+
+  ClonedInstallDetector detector;
+  detector.SaveMachineId(&prefs, kTestRawId);
+
+  EXPECT_EQ(kTestHashedId, prefs.GetInteger(prefs::kMetricsMachineId));
+}
+
+TEST(ClonedInstallDetectorTest, DetectClone) {
+  TestingPrefServiceSimple prefs;
+  MetricsStateManager::RegisterPrefs(prefs.registry());
+
+  // Save a machine id that will cause a clone to be detected.
+  prefs.SetInteger(prefs::kMetricsMachineId, kTestHashedId + 1);
+
+  ClonedInstallDetector detector;
+  detector.SaveMachineId(&prefs, kTestRawId);
+
+  EXPECT_TRUE(prefs.GetBoolean(prefs::kMetricsResetIds));
+}
+
+}  // namespace metrics
diff --git a/components/metrics/component_metrics_provider.cc b/components/metrics/component_metrics_provider.cc
new file mode 100644
index 0000000..5ec24e2
--- /dev/null
+++ b/components/metrics/component_metrics_provider.cc
@@ -0,0 +1,109 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/component_metrics_provider.h"
+
+#include <map>
+#include <string>
+#include "base/strings/string_number_conversions.h"
+#include "components/component_updater/component_updater_service.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace metrics {
+
+namespace {
+
+SystemProfileProto_ComponentId CrxIdToComponentId(const std::string& app_id) {
+  const static std::map<std::string, SystemProfileProto_ComponentId>
+      component_map = {
+          {"khaoiebndkojlmppeemjhbpbandiljpe",
+           SystemProfileProto_ComponentId_FILE_TYPE_POLICIES},
+          {"kfoklmclfodeliojeaekpoflbkkhojea",
+           SystemProfileProto_ComponentId_ORIGIN_TRIALS},
+          {"llkgjffcdpffmhiakmfcdcblohccpfmo",
+           SystemProfileProto_ComponentId_ORIGIN_TRIALS},  // Alternate ID
+          {"mimojjlkmoijpicakmndhoigimigcmbb",
+           SystemProfileProto_ComponentId_PEPPER_FLASH},
+          {"ckjlcfmdbdglblbjglepgnoekdnkoklc",
+           SystemProfileProto_ComponentId_PEPPER_FLASH_CHROMEOS},
+          {"hnimpnehoodheedghdeeijklkeaacbdc",
+           SystemProfileProto_ComponentId_PNACL},
+          {"npdjjkjlcidkjlamlmmdelcjbcpdjocm",
+           SystemProfileProto_ComponentId_RECOVERY},
+          {"giekcmmlnklenlaomppkphknjmnnpneh",
+           SystemProfileProto_ComponentId_SSL_ERROR_ASSISTANT},
+          {"ojjgnpkioondelmggbekfhllhdaimnho",
+           SystemProfileProto_ComponentId_STH_SET},
+          {"hfnkpimlhhgieaddgfemjhofmfblmnib",
+           SystemProfileProto_ComponentId_CRL_SET},
+          {"gcmjkmgdlgnkkcocmoeiminaijmmjnii",
+           SystemProfileProto_ComponentId_SUBRESOURCE_FILTER},
+          {"gkmgaooipdjhmangpemjhigmamcehddo",
+           SystemProfileProto_ComponentId_SW_REPORTER},
+          {"oimompecagnajdejgnnjijobebaeigek",
+           SystemProfileProto_ComponentId_WIDEVINE_CDM},
+          {"bjbdkfoakgmkndalgpadobhgbhhoanho",
+           SystemProfileProto_ComponentId_EPSON_INKJET_PRINTER_ESCPR},
+          {"ojnjgapiepgciobpecnafnoeaegllfld",
+           SystemProfileProto_ComponentId_CROS_TERMINA},
+          {"gncenodapghbnkfkoognegdnjoeegmkp",
+           SystemProfileProto_ComponentId_STAR_CUPS_DRIVER},
+          {"gelhpeofhffbaeegmemklllhfdifagmb",
+           SystemProfileProto_ComponentId_SPEECH_SYNTHESIS_SV_SE},
+          {"lmelglejhemejginpboagddgdfbepgmp",
+           SystemProfileProto_ComponentId_OPTIMIZATION_HINTS},
+          {"fookoiellkocclipolgaceabajejjcnp",
+           SystemProfileProto_ComponentId_DOWNLOADABLE_STRINGS},
+          {"cjfkbpdpjpdldhclahpfgnlhpodlpnba",
+           SystemProfileProto_ComponentId_VR_ASSETS},
+          {"gjpajnddmedjmcklfflllocelehklffm",
+           SystemProfileProto_ComponentId_RTANALYTICS_LIGHT},
+          {"mjdmdobabdmfcbaakcaadileafkmifen",
+           SystemProfileProto_ComponentId_RTANALYTICS_FULL},
+          {"fhbeibbmaepakgdkkmjgldjajgpkkhfj",
+           SystemProfileProto_ComponentId_CELLULAR}};
+  const auto result = component_map.find(app_id);
+  if (result == component_map.end())
+    return SystemProfileProto_ComponentId_UNKNOWN;
+  return result->second;
+}
+
+// Extract the first 32 bits of a fingerprint string, excluding the fingerprint
+// format specifier - see the fingerprint format specification at
+// https://github.com/google/omaha/blob/master/doc/ServerProtocolV3.md
+uint32_t Trim(const std::string& fp) {
+  const auto len_prefix = fp.find(".");
+  if (len_prefix == std::string::npos)
+    return 0;
+  uint32_t result = 0;
+  if (base::HexStringToUInt(fp.substr(len_prefix + 1, 8), &result))
+    return result;
+  return 0;
+}
+
+}  // namespace
+
+ComponentMetricsProvider::ComponentMetricsProvider(
+    component_updater::ComponentUpdateService* component_update_service)
+    : component_update_service_(component_update_service) {}
+
+ComponentMetricsProvider::~ComponentMetricsProvider() = default;
+
+void ComponentMetricsProvider::ProvideSystemProfileMetrics(
+    SystemProfileProto* system_profile) {
+  for (const auto& component : component_update_service_->GetComponents()) {
+    const auto id = CrxIdToComponentId(component.id);
+    // Ignore any unknown components - in practice these are the
+    // SupervisedUserWhitelists, which we do not want to transmit to UMA or
+    // Crash.
+    if (id == SystemProfileProto_ComponentId_UNKNOWN)
+      continue;
+    auto* proto = system_profile->add_chrome_component();
+    proto->set_component_id(id);
+    proto->set_version(component.version.GetString());
+    proto->set_omaha_fingerprint(Trim(component.fingerprint));
+  }
+}
+
+}  // namespace metrics
diff --git a/components/metrics/component_metrics_provider.h b/components/metrics/component_metrics_provider.h
new file mode 100644
index 0000000..7899b97
--- /dev/null
+++ b/components/metrics/component_metrics_provider.h
@@ -0,0 +1,37 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_COMPONENT_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_COMPONENT_METRICS_PROVIDER_H_
+
+#include "components/metrics/metrics_provider.h"
+
+namespace component_updater {
+class ComponentUpdateService;
+}
+
+namespace metrics {
+
+class SystemProfileProto;
+
+// Stores and loads system information to prefs for stability logs.
+class ComponentMetricsProvider : public MetricsProvider {
+ public:
+  explicit ComponentMetricsProvider(
+      component_updater::ComponentUpdateService* component_update_service);
+  ~ComponentMetricsProvider() override;
+
+  // MetricsProvider:
+  void ProvideSystemProfileMetrics(
+      SystemProfileProto* system_profile_proto) override;
+
+ private:
+  component_updater::ComponentUpdateService* component_update_service_;
+
+  DISALLOW_COPY_AND_ASSIGN(ComponentMetricsProvider);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_COMPONENT_METRICS_PROVIDER_H_
diff --git a/components/metrics/component_metrics_provider_unittest.cc b/components/metrics/component_metrics_provider_unittest.cc
new file mode 100644
index 0000000..2fc9cae
--- /dev/null
+++ b/components/metrics/component_metrics_provider_unittest.cc
@@ -0,0 +1,58 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/component_metrics_provider.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/version.h"
+#include "components/component_updater/mock_component_updater_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace metrics {
+
+class ComponentMetricsProviderTest : public testing::Test {
+ public:
+  ComponentMetricsProviderTest() {}
+  ~ComponentMetricsProviderTest() override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ComponentMetricsProviderTest);
+};
+
+using component_updater::ComponentInfo;
+
+TEST_F(ComponentMetricsProviderTest, ProvideComponentMetrics) {
+  std::vector<ComponentInfo> components = {
+      ComponentInfo(
+          "hfnkpimlhhgieaddgfemjhofmfblmnib",
+          "1.0846414bf2025bbc067b6fa5b61b16eda2269d8712b8fec0973b4c71fdc65ca0",
+          base::ASCIIToUTF16("name1"), base::Version("1.2.3.4")),
+      ComponentInfo(
+          "oimompecagnajdejgnnjijobebaeigek",
+          "1.adc9207a4a88ee98bf9ddf0330f35818386f1adc006bc8eee94dc59d43c0f5d6",
+          base::ASCIIToUTF16("name2"), base::Version("5.6.7.8")),
+      ComponentInfo(
+          "thiscomponentfilteredfromresults",
+          "1.b5268dc93e08d68d0be26bd8fbbb15c7b7f805cc06b4abd9d49381bc178e78cf",
+          base::ASCIIToUTF16("name3"), base::Version("9.9.9.9"))};
+  component_updater::MockComponentUpdateService service;
+  EXPECT_CALL(service, GetComponents()).WillOnce(testing::Return(components));
+  ComponentMetricsProvider component_provider(&service);
+  SystemProfileProto system_profile;
+  component_provider.ProvideSystemProfileMetrics(&system_profile);
+
+  EXPECT_EQ(2, system_profile.chrome_component_size());
+  EXPECT_EQ(SystemProfileProto_ComponentId_CRL_SET,
+            system_profile.chrome_component(0).component_id());
+  EXPECT_EQ("1.2.3.4", system_profile.chrome_component(0).version());
+  EXPECT_EQ(138821963u, system_profile.chrome_component(0).omaha_fingerprint());
+  EXPECT_EQ(SystemProfileProto_ComponentId_WIDEVINE_CDM,
+            system_profile.chrome_component(1).component_id());
+  EXPECT_EQ("5.6.7.8", system_profile.chrome_component(1).version());
+  EXPECT_EQ(2915639418u,
+            system_profile.chrome_component(1).omaha_fingerprint());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/daily_event.cc b/components/metrics/daily_event.cc
new file mode 100644
index 0000000..befc2eb
--- /dev/null
+++ b/components/metrics/daily_event.cc
@@ -0,0 +1,96 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/daily_event.h"
+
+#include <utility>
+
+#include "base/metrics/histogram.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace metrics {
+
+namespace {
+
+void RecordIntervalTypeHistogram(const std::string& histogram_name,
+                                 DailyEvent::IntervalType type) {
+  const int num_types = static_cast<int>(DailyEvent::IntervalType::NUM_TYPES);
+  base::Histogram::FactoryGet(histogram_name, 1, num_types, num_types + 1,
+                              base::HistogramBase::kUmaTargetedHistogramFlag)
+      ->Add(static_cast<int>(type));
+}
+
+}  // namespace
+
+DailyEvent::Observer::Observer() {
+}
+
+DailyEvent::Observer::~Observer() {
+}
+
+DailyEvent::DailyEvent(PrefService* pref_service,
+                       const char* pref_name,
+                       const std::string& histogram_name)
+    : pref_service_(pref_service),
+      pref_name_(pref_name),
+      histogram_name_(histogram_name) {
+}
+
+DailyEvent::~DailyEvent() {
+}
+
+// static
+void DailyEvent::RegisterPref(PrefRegistrySimple* registry,
+                              const char* pref_name) {
+  registry->RegisterInt64Pref(pref_name, 0);
+}
+
+void DailyEvent::AddObserver(std::unique_ptr<DailyEvent::Observer> observer) {
+  DVLOG(2) << "DailyEvent observer added.";
+  DCHECK(last_fired_.is_null());
+  observers_.push_back(std::move(observer));
+}
+
+void DailyEvent::CheckInterval() {
+  base::Time now = base::Time::Now();
+  if (last_fired_.is_null()) {
+    // The first time we call CheckInterval, we read the time stored in prefs.
+    last_fired_ = base::Time() + base::TimeDelta::FromMicroseconds(
+                                     pref_service_->GetInt64(pref_name_));
+
+    DVLOG(1) << "DailyEvent time loaded: " << last_fired_;
+    if (last_fired_.is_null()) {
+      DVLOG(1) << "DailyEvent first run.";
+      RecordIntervalTypeHistogram(histogram_name_, IntervalType::FIRST_RUN);
+      OnInterval(now, IntervalType::FIRST_RUN);
+      return;
+    }
+  }
+  int days_elapsed = (now - last_fired_).InDays();
+  if (days_elapsed >= 1) {
+    DVLOG(1) << "DailyEvent day elapsed.";
+    RecordIntervalTypeHistogram(histogram_name_, IntervalType::DAY_ELAPSED);
+    OnInterval(now, IntervalType::DAY_ELAPSED);
+  } else if (days_elapsed <= -1) {
+    // The "last fired" time is more than a day in the future, so the clock
+    // must have been changed.
+    DVLOG(1) << "DailyEvent clock change detected.";
+    RecordIntervalTypeHistogram(histogram_name_, IntervalType::CLOCK_CHANGED);
+    OnInterval(now, IntervalType::CLOCK_CHANGED);
+  }
+}
+
+void DailyEvent::OnInterval(base::Time now, IntervalType type) {
+  DCHECK(!now.is_null());
+  last_fired_ = now;
+  pref_service_->SetInt64(pref_name_,
+                          last_fired_.since_origin().InMicroseconds());
+
+  for (auto it = observers_.begin(); it != observers_.end(); ++it) {
+    (*it)->OnDailyEvent(type);
+  }
+}
+
+}  // namespace metrics
diff --git a/components/metrics/daily_event.h b/components/metrics/daily_event.h
new file mode 100644
index 0000000..326cad4
--- /dev/null
+++ b/components/metrics/daily_event.h
@@ -0,0 +1,102 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_DAILY_EVENT_H_
+#define COMPONENTS_METRICS_DAILY_EVENT_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace metrics {
+
+// DailyEvent is used for throttling an event to about once per day, even if
+// the program is restarted more frequently.  It is based on local machine
+// time, so it could be fired more often if the clock is changed.
+//
+// The service using the DailyEvent should first provide all of the Observers
+// for the interval, and then arrange for CheckInterval() to be called
+// periodically to test if the event should be fired.
+class DailyEvent {
+ public:
+  // Different reasons that Observer::OnDailyEvent() is called.
+  // This enum is used for histograms and must not be renumbered.
+  enum class IntervalType {
+    FIRST_RUN,
+    DAY_ELAPSED,
+    CLOCK_CHANGED,
+    NUM_TYPES,
+  };
+
+  // Observer receives notifications from a DailyEvent.
+  // Observers must be added before the DailyEvent begins checking time,
+  // and will be owned by the DailyEvent.
+  class Observer {
+   public:
+    Observer();
+    virtual ~Observer();
+
+    // Called when the daily event is fired.
+    virtual void OnDailyEvent(IntervalType type) = 0;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(Observer);
+  };
+
+  // Constructs DailyEvent monitor which stores the time it last fired in the
+  // preference |pref_name|. |pref_name| should be registered by calling
+  // RegisterPref before using this object.
+  // Caller is responsible for ensuring that |pref_service| and |pref_name|
+  // outlive the DailyEvent.
+  // |histogram_name| is the name of the UMA metric which record when this
+  // interval fires, and should be registered in histograms.xml
+  DailyEvent(PrefService* pref_service,
+             const char* pref_name,
+             const std::string& histogram_name);
+  ~DailyEvent();
+
+  // Adds a observer to be notified when a day elapses. All observers should
+  // be registered before the the DailyEvent starts checking time.
+  void AddObserver(std::unique_ptr<Observer> observer);
+
+  // Checks if a day has elapsed. If it has, OnDailyEvent will be called on
+  // all observers.
+  void CheckInterval();
+
+  // Registers the preference used by this interval.
+  static void RegisterPref(PrefRegistrySimple* registry, const char* pref_name);
+
+ private:
+  // Handles an interval elapsing because of |type|.
+  void OnInterval(base::Time now, IntervalType type);
+
+  // A weak pointer to the PrefService object to read and write preferences
+  // from. Calling code should ensure this object continues to exist for the
+  // lifetime of the DailyEvent object.
+  PrefService* pref_service_;
+
+  // The name of the preference to store the last fired time in.
+  // Calling code should ensure this outlives the DailyEvent.
+  const char* pref_name_;
+
+  // The name of the histogram to record intervals.
+  std::string histogram_name_;
+
+  // A list of observers.
+  std::vector<std::unique_ptr<Observer>> observers_;
+
+  // The time that the daily event was last fired.
+  base::Time last_fired_;
+
+  DISALLOW_COPY_AND_ASSIGN(DailyEvent);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_DAILY_EVENT_H_
diff --git a/components/metrics/daily_event_unittest.cc b/components/metrics/daily_event_unittest.cc
new file mode 100644
index 0000000..f9a6f52
--- /dev/null
+++ b/components/metrics/daily_event_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/daily_event.h"
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/optional.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+const char kTestPrefName[] = "TestPref";
+const char kTestMetricName[] = "TestMetric";
+
+class TestDailyObserver : public DailyEvent::Observer {
+ public:
+  TestDailyObserver() = default;
+
+  bool fired() const { return type_.has_value(); }
+  DailyEvent::IntervalType type() const { return type_.value(); }
+
+  void OnDailyEvent(DailyEvent::IntervalType type) override { type_ = type; }
+
+  void Reset() { type_ = {}; }
+
+ private:
+  // Last-received type, or unset if OnDailyEvent() hasn't been called.
+  base::Optional<DailyEvent::IntervalType> type_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestDailyObserver);
+};
+
+class DailyEventTest : public testing::Test {
+ public:
+  DailyEventTest() : event_(&prefs_, kTestPrefName, kTestMetricName) {
+    DailyEvent::RegisterPref(prefs_.registry(), kTestPrefName);
+    observer_ = new TestDailyObserver();
+    event_.AddObserver(base::WrapUnique(observer_));
+  }
+
+ protected:
+  TestingPrefServiceSimple prefs_;
+  TestDailyObserver* observer_;
+  DailyEvent event_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DailyEventTest);
+};
+
+}  // namespace
+
+// The event should fire if the preference is not available.
+TEST_F(DailyEventTest, TestNewFires) {
+  event_.CheckInterval();
+  ASSERT_TRUE(observer_->fired());
+  EXPECT_EQ(DailyEvent::IntervalType::FIRST_RUN, observer_->type());
+}
+
+// The event should fire if the preference is more than a day old.
+TEST_F(DailyEventTest, TestOldFires) {
+  base::Time last_time = base::Time::Now() - base::TimeDelta::FromHours(25);
+  prefs_.SetInt64(kTestPrefName, last_time.since_origin().InMicroseconds());
+  event_.CheckInterval();
+  ASSERT_TRUE(observer_->fired());
+  EXPECT_EQ(DailyEvent::IntervalType::DAY_ELAPSED, observer_->type());
+}
+
+// The event should fire if the preference is more than a day in the future.
+TEST_F(DailyEventTest, TestFutureFires) {
+  base::Time last_time = base::Time::Now() + base::TimeDelta::FromHours(25);
+  prefs_.SetInt64(kTestPrefName, last_time.since_origin().InMicroseconds());
+  event_.CheckInterval();
+  ASSERT_TRUE(observer_->fired());
+  EXPECT_EQ(DailyEvent::IntervalType::CLOCK_CHANGED, observer_->type());
+}
+
+// The event should not fire if the preference is more recent than a day.
+TEST_F(DailyEventTest, TestRecentNotFired) {
+  base::Time last_time = base::Time::Now() - base::TimeDelta::FromMinutes(2);
+  prefs_.SetInt64(kTestPrefName, last_time.since_origin().InMicroseconds());
+  event_.CheckInterval();
+  EXPECT_FALSE(observer_->fired());
+}
+
+// The event should not fire if the preference is less than a day in the future.
+TEST_F(DailyEventTest, TestSoonNotFired) {
+  base::Time last_time = base::Time::Now() + base::TimeDelta::FromMinutes(2);
+  prefs_.SetInt64(kTestPrefName, last_time.since_origin().InMicroseconds());
+  event_.CheckInterval();
+  EXPECT_FALSE(observer_->fired());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/data_use_tracker.cc b/components/metrics/data_use_tracker.cc
new file mode 100644
index 0000000..61be02f
--- /dev/null
+++ b/components/metrics/data_use_tracker.cc
@@ -0,0 +1,185 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/data_use_tracker.h"
+
+#include <string>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/variations/variations_associated_data.h"
+
+namespace metrics {
+
+namespace {
+
+// Default weekly quota and allowed UMA ratio for UMA log uploads for Android.
+// These defaults will not be used for non-Android as |DataUseTracker| will not
+// be initialized. Default values can be overridden by variation params.
+const int kDefaultUMAWeeklyQuotaBytes = 204800;
+const double kDefaultUMARatio = 0.05;
+
+}  // namespace
+
+DataUseTracker::DataUseTracker(PrefService* local_state)
+    : local_state_(local_state) {}
+
+DataUseTracker::~DataUseTracker() {}
+
+// static
+std::unique_ptr<DataUseTracker> DataUseTracker::Create(
+    PrefService* local_state) {
+  std::unique_ptr<DataUseTracker> data_use_tracker;
+#if defined(OS_ANDROID)
+  data_use_tracker.reset(new DataUseTracker(local_state));
+#endif
+  return data_use_tracker;
+}
+
+// static
+void DataUseTracker::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterDictionaryPref(metrics::prefs::kUserCellDataUse);
+  registry->RegisterDictionaryPref(metrics::prefs::kUmaCellDataUse);
+}
+
+void DataUseTracker::UpdateMetricsUsagePrefs(const std::string& service_name,
+                                             int message_size,
+                                             bool is_cellular) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!is_cellular)
+    return;
+
+  UpdateUsagePref(prefs::kUserCellDataUse, message_size);
+  // TODO(holte): Consider adding seperate tracking for UKM.
+  if (service_name == "UMA" || service_name == "UKM")
+    UpdateUsagePref(prefs::kUmaCellDataUse, message_size);
+}
+
+bool DataUseTracker::ShouldUploadLogOnCellular(int log_bytes) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  RemoveExpiredEntries();
+
+  int uma_weekly_quota_bytes;
+  if (!GetUmaWeeklyQuota(&uma_weekly_quota_bytes))
+    return true;
+
+  int uma_total_data_use = ComputeTotalDataUse(prefs::kUmaCellDataUse);
+  int new_uma_total_data_use = log_bytes + uma_total_data_use;
+  // If the new log doesn't increase the total UMA traffic to be above the
+  // allowed quota then the log should be uploaded.
+  if (new_uma_total_data_use <= uma_weekly_quota_bytes)
+    return true;
+
+  double uma_ratio;
+  if (!GetUmaRatio(&uma_ratio))
+    return true;
+
+  int user_total_data_use = ComputeTotalDataUse(prefs::kUserCellDataUse);
+  // If after adding the new log the uma ratio is still under the allowed ratio
+  // then the log should be uploaded and vice versa.
+  return new_uma_total_data_use /
+             static_cast<double>(log_bytes + user_total_data_use) <=
+         uma_ratio;
+}
+
+void DataUseTracker::UpdateUsagePref(const std::string& pref_name,
+                                     int message_size) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  DictionaryPrefUpdate pref_updater(local_state_, pref_name);
+  int todays_traffic = 0;
+  std::string todays_key = GetCurrentMeasurementDateAsString();
+
+  const base::DictionaryValue* user_pref_dict =
+      local_state_->GetDictionary(pref_name);
+  user_pref_dict->GetInteger(todays_key, &todays_traffic);
+  pref_updater->SetInteger(todays_key, todays_traffic + message_size);
+}
+
+void DataUseTracker::RemoveExpiredEntries() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  RemoveExpiredEntriesForPref(prefs::kUmaCellDataUse);
+  RemoveExpiredEntriesForPref(prefs::kUserCellDataUse);
+}
+
+void DataUseTracker::RemoveExpiredEntriesForPref(const std::string& pref_name) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const base::DictionaryValue* user_pref_dict =
+      local_state_->GetDictionary(pref_name);
+  const base::Time current_date = GetCurrentMeasurementDate();
+  const base::Time week_ago = current_date - base::TimeDelta::FromDays(7);
+
+  base::DictionaryValue user_pref_new_dict;
+  for (base::DictionaryValue::Iterator it(*user_pref_dict); !it.IsAtEnd();
+       it.Advance()) {
+    base::Time key_date;
+    if (base::Time::FromUTCString(it.key().c_str(), &key_date) &&
+        key_date > week_ago)
+      user_pref_new_dict.Set(it.key(), it.value().CreateDeepCopy());
+  }
+  local_state_->Set(pref_name, user_pref_new_dict);
+}
+
+// Note: We compute total data use regardless of what is the current date. In
+// scenario when user travels back in time zone and current date becomes earlier
+// than latest registered date in perf, we still count that in total use as user
+// actually used that data.
+int DataUseTracker::ComputeTotalDataUse(const std::string& pref_name) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  int total_data_use = 0;
+  const base::DictionaryValue* pref_dict =
+      local_state_->GetDictionary(pref_name);
+  for (base::DictionaryValue::Iterator it(*pref_dict); !it.IsAtEnd();
+       it.Advance()) {
+    int value = 0;
+    it.value().GetAsInteger(&value);
+    total_data_use += value;
+  }
+  return total_data_use;
+}
+
+bool DataUseTracker::GetUmaWeeklyQuota(int* uma_weekly_quota_bytes) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  std::string param_value_str = variations::GetVariationParamValue(
+      "UMA_EnableCellularLogUpload", "Uma_Quota");
+  if (param_value_str.empty())
+    *uma_weekly_quota_bytes = kDefaultUMAWeeklyQuotaBytes;
+  else
+    base::StringToInt(param_value_str, uma_weekly_quota_bytes);
+  return true;
+}
+
+bool DataUseTracker::GetUmaRatio(double* ratio) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  std::string param_value_str = variations::GetVariationParamValue(
+      "UMA_EnableCellularLogUpload", "Uma_Ratio");
+  if (param_value_str.empty())
+    *ratio = kDefaultUMARatio;
+  else
+    base::StringToDouble(param_value_str, ratio);
+  return true;
+}
+
+base::Time DataUseTracker::GetCurrentMeasurementDate() const {
+  return base::Time::Now().LocalMidnight();
+}
+
+std::string DataUseTracker::GetCurrentMeasurementDateAsString() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  base::Time::Exploded today_exploded;
+  GetCurrentMeasurementDate().LocalExplode(&today_exploded);
+  return base::StringPrintf("%04d-%02d-%02d", today_exploded.year,
+                            today_exploded.month, today_exploded.day_of_month);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/data_use_tracker.h b/components/metrics/data_use_tracker.h
new file mode 100644
index 0000000..104223e
--- /dev/null
+++ b/components/metrics/data_use_tracker.h
@@ -0,0 +1,88 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_DATA_USE_TRACKER_H_
+#define COMPONENTS_METRICS_DATA_USE_TRACKER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace metrics {
+
+typedef base::Callback<void(const std::string&, int, bool)>
+    UpdateUsagePrefCallbackType;
+
+// Records the data use of user traffic and UMA traffic in user prefs. Taking
+// into account those prefs it can verify whether certain UMA log upload is
+// allowed.
+class DataUseTracker {
+ public:
+  explicit DataUseTracker(PrefService* local_state);
+  virtual ~DataUseTracker();
+
+  // Returns an instance of |DataUseTracker| with provided |local_state| if
+  // users data use should be tracked and null pointer otherwise.
+  static std::unique_ptr<DataUseTracker> Create(PrefService* local_state);
+
+  // Registers data use prefs using provided |registry|.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+  // Updates data usage tracking prefs with the specified values.
+  void UpdateMetricsUsagePrefs(const std::string& service_name,
+                               int message_size,
+                               bool is_cellular);
+
+  // Returns whether a log with provided |log_bytes| can be uploaded according
+  // to data use ratio and UMA quota provided by variations.
+  bool ShouldUploadLogOnCellular(int log_bytes);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckUpdateUsagePref);
+  FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckRemoveExpiredEntries);
+  FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckComputeTotalDataUse);
+  FRIEND_TEST_ALL_PREFIXES(DataUseTrackerTest, CheckCanUploadUMALog);
+
+  // Updates provided |pref_name| for a current date with the given message
+  // size.
+  void UpdateUsagePref(const std::string& pref_name, int message_size);
+
+  // Removes entries from the all data use  prefs.
+  void RemoveExpiredEntries();
+
+  // Removes entries from the given |pref_name| if they are more than 7 days
+  // old.
+  void RemoveExpiredEntriesForPref(const std::string& pref_name);
+
+  // Computes data usage according to all the entries in the given dictionary
+  // pref.
+  int ComputeTotalDataUse(const std::string& pref_name);
+
+  // Returns the weekly allowed quota for UMA data use.
+  virtual bool GetUmaWeeklyQuota(int* uma_weekly_quota_bytes) const;
+
+  // Returns the allowed ratio for UMA data use over overall data use.
+  virtual bool GetUmaRatio(double* ratio) const;
+
+  // Returns the current date for measurement.
+  virtual base::Time GetCurrentMeasurementDate() const;
+
+  // Returns the current date as a string with a proper formatting.
+  virtual std::string GetCurrentMeasurementDateAsString() const;
+
+  PrefService* local_state_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(DataUseTracker);
+};
+
+}  // namespace metrics
+#endif  // COMPONENTS_METRICS_DATA_USE_TRACKER_H_
diff --git a/components/metrics/data_use_tracker_unittest.cc b/components/metrics/data_use_tracker_unittest.cc
new file mode 100644
index 0000000..a271b5d
--- /dev/null
+++ b/components/metrics/data_use_tracker_unittest.cc
@@ -0,0 +1,203 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/data_use_tracker.h"
+
+#include "base/strings/stringprintf.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+const char kTodayStr[] = "2016-03-16";
+const char kYesterdayStr[] = "2016-03-15";
+const char kExpiredDateStr1[] = "2016-03-09";
+const char kExpiredDateStr2[] = "2016-03-01";
+
+class TestDataUsePrefService : public TestingPrefServiceSimple {
+ public:
+  TestDataUsePrefService() { DataUseTracker::RegisterPrefs(registry()); }
+
+  void ClearDataUsePrefs() {
+    ClearPref(metrics::prefs::kUserCellDataUse);
+    ClearPref(metrics::prefs::kUmaCellDataUse);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestDataUsePrefService);
+};
+
+class FakeDataUseTracker : public DataUseTracker {
+ public:
+  FakeDataUseTracker(PrefService* local_state) : DataUseTracker(local_state) {}
+
+  bool GetUmaWeeklyQuota(int* uma_weekly_quota_bytes) const override {
+    *uma_weekly_quota_bytes = 200;
+    return true;
+  }
+
+  bool GetUmaRatio(double* ratio) const override {
+    *ratio = 0.05;
+    return true;
+  }
+
+  base::Time GetCurrentMeasurementDate() const override {
+    base::Time today_for_test;
+    EXPECT_TRUE(base::Time::FromUTCString(kTodayStr, &today_for_test));
+    return today_for_test;
+  }
+
+  std::string GetCurrentMeasurementDateAsString() const override {
+    return kTodayStr;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FakeDataUseTracker);
+};
+
+// Sets up data usage prefs with mock values so that UMA traffic is above the
+// allowed ratio.
+void SetPrefTestValuesOverRatio(PrefService* local_state) {
+  base::DictionaryValue user_pref_dict;
+  user_pref_dict.SetInteger(kTodayStr, 2 * 100);
+  user_pref_dict.SetInteger(kYesterdayStr, 2 * 100);
+  user_pref_dict.SetInteger(kExpiredDateStr1, 2 * 100);
+  user_pref_dict.SetInteger(kExpiredDateStr2, 2 * 100);
+  local_state->Set(prefs::kUserCellDataUse, user_pref_dict);
+
+  base::DictionaryValue uma_pref_dict;
+  uma_pref_dict.SetInteger(kTodayStr, 50);
+  uma_pref_dict.SetInteger(kYesterdayStr, 50);
+  uma_pref_dict.SetInteger(kExpiredDateStr1, 50);
+  uma_pref_dict.SetInteger(kExpiredDateStr2, 50);
+  local_state->Set(prefs::kUmaCellDataUse, uma_pref_dict);
+}
+
+// Sets up data usage prefs with mock values which can be valid.
+void SetPrefTestValuesValidRatio(PrefService* local_state) {
+  base::DictionaryValue user_pref_dict;
+  user_pref_dict.SetInteger(kTodayStr, 100 * 100);
+  user_pref_dict.SetInteger(kYesterdayStr, 100 * 100);
+  user_pref_dict.SetInteger(kExpiredDateStr1, 100 * 100);
+  user_pref_dict.SetInteger(kExpiredDateStr2, 100 * 100);
+  local_state->Set(prefs::kUserCellDataUse, user_pref_dict);
+
+  // Should be 4% of user traffic
+  base::DictionaryValue uma_pref_dict;
+  uma_pref_dict.SetInteger(kTodayStr, 4 * 100);
+  uma_pref_dict.SetInteger(kYesterdayStr, 4 * 100);
+  uma_pref_dict.SetInteger(kExpiredDateStr1, 4 * 100);
+  uma_pref_dict.SetInteger(kExpiredDateStr2, 4 * 100);
+  local_state->Set(prefs::kUmaCellDataUse, uma_pref_dict);
+}
+
+}  // namespace
+
+TEST(DataUseTrackerTest, CheckUpdateUsagePref) {
+  TestDataUsePrefService local_state;
+  FakeDataUseTracker data_use_tracker(&local_state);
+  local_state.ClearDataUsePrefs();
+
+  int user_pref_value = 0;
+  int uma_pref_value = 0;
+
+  data_use_tracker.UpdateMetricsUsagePrefs("", 2 * 100, true);
+  local_state.GetDictionary(prefs::kUserCellDataUse)
+      ->GetInteger(kTodayStr, &user_pref_value);
+  EXPECT_EQ(2 * 100, user_pref_value);
+  local_state.GetDictionary(prefs::kUmaCellDataUse)
+      ->GetInteger(kTodayStr, &uma_pref_value);
+  EXPECT_EQ(0, uma_pref_value);
+
+  data_use_tracker.UpdateMetricsUsagePrefs("UMA", 100, true);
+  local_state.GetDictionary(prefs::kUserCellDataUse)
+      ->GetInteger(kTodayStr, &user_pref_value);
+  EXPECT_EQ(3 * 100, user_pref_value);
+  local_state.GetDictionary(prefs::kUmaCellDataUse)
+      ->GetInteger(kTodayStr, &uma_pref_value);
+  EXPECT_EQ(100, uma_pref_value);
+}
+
+TEST(DataUseTrackerTest, CheckRemoveExpiredEntries) {
+  TestDataUsePrefService local_state;
+  FakeDataUseTracker data_use_tracker(&local_state);
+  local_state.ClearDataUsePrefs();
+  SetPrefTestValuesOverRatio(&local_state);
+  data_use_tracker.RemoveExpiredEntries();
+
+  int user_pref_value = 0;
+  int uma_pref_value = 0;
+
+  local_state.GetDictionary(prefs::kUserCellDataUse)
+      ->GetInteger(kExpiredDateStr1, &user_pref_value);
+  EXPECT_EQ(0, user_pref_value);
+  local_state.GetDictionary(prefs::kUmaCellDataUse)
+      ->GetInteger(kExpiredDateStr1, &uma_pref_value);
+  EXPECT_EQ(0, uma_pref_value);
+
+  local_state.GetDictionary(prefs::kUserCellDataUse)
+      ->GetInteger(kExpiredDateStr2, &user_pref_value);
+  EXPECT_EQ(0, user_pref_value);
+  local_state.GetDictionary(prefs::kUmaCellDataUse)
+      ->GetInteger(kExpiredDateStr2, &uma_pref_value);
+  EXPECT_EQ(0, uma_pref_value);
+
+  local_state.GetDictionary(prefs::kUserCellDataUse)
+      ->GetInteger(kTodayStr, &user_pref_value);
+  EXPECT_EQ(2 * 100, user_pref_value);
+  local_state.GetDictionary(prefs::kUmaCellDataUse)
+      ->GetInteger(kTodayStr, &uma_pref_value);
+  EXPECT_EQ(50, uma_pref_value);
+
+  local_state.GetDictionary(prefs::kUserCellDataUse)
+      ->GetInteger(kYesterdayStr, &user_pref_value);
+  EXPECT_EQ(2 * 100, user_pref_value);
+  local_state.GetDictionary(prefs::kUmaCellDataUse)
+      ->GetInteger(kYesterdayStr, &uma_pref_value);
+  EXPECT_EQ(50, uma_pref_value);
+}
+
+TEST(DataUseTrackerTest, CheckComputeTotalDataUse) {
+  TestDataUsePrefService local_state;
+  FakeDataUseTracker data_use_tracker(&local_state);
+  local_state.ClearDataUsePrefs();
+  SetPrefTestValuesOverRatio(&local_state);
+
+  int user_data_use =
+      data_use_tracker.ComputeTotalDataUse(prefs::kUserCellDataUse);
+  EXPECT_EQ(8 * 100, user_data_use);
+  int uma_data_use =
+      data_use_tracker.ComputeTotalDataUse(prefs::kUmaCellDataUse);
+  EXPECT_EQ(4 * 50, uma_data_use);
+}
+
+TEST(DataUseTrackerTest, CheckShouldUploadLogOnCellular) {
+  TestDataUsePrefService local_state;
+  FakeDataUseTracker data_use_tracker(&local_state);
+  local_state.ClearDataUsePrefs();
+  SetPrefTestValuesOverRatio(&local_state);
+
+  bool can_upload = data_use_tracker.ShouldUploadLogOnCellular(50);
+  EXPECT_TRUE(can_upload);
+  can_upload = data_use_tracker.ShouldUploadLogOnCellular(100);
+  EXPECT_TRUE(can_upload);
+  can_upload = data_use_tracker.ShouldUploadLogOnCellular(150);
+  EXPECT_FALSE(can_upload);
+
+  local_state.ClearDataUsePrefs();
+  SetPrefTestValuesValidRatio(&local_state);
+  can_upload = data_use_tracker.ShouldUploadLogOnCellular(100);
+  EXPECT_TRUE(can_upload);
+  // this is about 0.49%
+  can_upload = data_use_tracker.ShouldUploadLogOnCellular(200);
+  EXPECT_TRUE(can_upload);
+  can_upload = data_use_tracker.ShouldUploadLogOnCellular(300);
+  EXPECT_FALSE(can_upload);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/delegating_provider.cc b/components/metrics/delegating_provider.cc
new file mode 100644
index 0000000..2bb2013
--- /dev/null
+++ b/components/metrics/delegating_provider.cc
@@ -0,0 +1,112 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/delegating_provider.h"
+
+#include "base/barrier_closure.h"
+
+namespace metrics {
+
+DelegatingProvider::DelegatingProvider() = default;
+
+DelegatingProvider::~DelegatingProvider() = default;
+
+void DelegatingProvider::RegisterMetricsProvider(
+    std::unique_ptr<MetricsProvider> provider) {
+  metrics_providers_.push_back(std::move(provider));
+}
+
+const std::vector<std::unique_ptr<MetricsProvider>>&
+DelegatingProvider::GetProviders() {
+  return metrics_providers_;
+}
+
+void DelegatingProvider::Init() {
+  for (auto& provider : metrics_providers_)
+    provider->Init();
+}
+
+void DelegatingProvider::AsyncInit(const base::Closure& done_callback) {
+  base::Closure barrier =
+      base::BarrierClosure(metrics_providers_.size(), done_callback);
+  for (auto& provider : metrics_providers_) {
+    provider->AsyncInit(barrier);
+  }
+}
+
+void DelegatingProvider::OnDidCreateMetricsLog() {
+  for (auto& provider : metrics_providers_)
+    provider->OnDidCreateMetricsLog();
+}
+
+void DelegatingProvider::OnRecordingEnabled() {
+  for (auto& provider : metrics_providers_)
+    provider->OnRecordingEnabled();
+}
+
+void DelegatingProvider::OnRecordingDisabled() {
+  for (auto& provider : metrics_providers_)
+    provider->OnRecordingDisabled();
+}
+
+void DelegatingProvider::OnAppEnterBackground() {
+  for (auto& provider : metrics_providers_)
+    provider->OnAppEnterBackground();
+}
+
+bool DelegatingProvider::ProvideIndependentMetrics(
+    SystemProfileProto* system_profile_proto,
+    base::HistogramSnapshotManager* snapshot_manager) {
+  // These are collected seperately for each provider.
+  NOTREACHED();
+  return false;
+}
+
+void DelegatingProvider::ProvideSystemProfileMetrics(
+    SystemProfileProto* system_profile_proto) {
+  for (auto& provider : metrics_providers_)
+    provider->ProvideSystemProfileMetrics(system_profile_proto);
+}
+
+bool DelegatingProvider::HasPreviousSessionData() {
+  // All providers are queried (rather than stopping after the first "true"
+  // response) in case they do any kind of setup work in preparation for
+  // the later call to RecordInitialHistogramSnapshots().
+  bool has_stability_metrics = false;
+  for (auto& provider : metrics_providers_)
+    has_stability_metrics |= provider->HasPreviousSessionData();
+
+  return has_stability_metrics;
+}
+
+void DelegatingProvider::ProvidePreviousSessionData(
+    ChromeUserMetricsExtension* uma_proto) {
+  for (const auto& provider : metrics_providers_)
+    provider->ProvidePreviousSessionData(uma_proto);
+}
+
+void DelegatingProvider::ProvideCurrentSessionData(
+    ChromeUserMetricsExtension* uma_proto) {
+  for (const auto& provider : metrics_providers_)
+    provider->ProvideCurrentSessionData(uma_proto);
+}
+
+void DelegatingProvider::ClearSavedStabilityMetrics() {
+  for (auto& provider : metrics_providers_)
+    provider->ClearSavedStabilityMetrics();
+}
+
+void DelegatingProvider::RecordHistogramSnapshots(
+    base::HistogramSnapshotManager* snapshot_manager) {
+  for (auto& provider : metrics_providers_)
+    provider->RecordHistogramSnapshots(snapshot_manager);
+}
+
+void DelegatingProvider::RecordInitialHistogramSnapshots(
+    base::HistogramSnapshotManager* snapshot_manager) {
+  for (auto& provider : metrics_providers_)
+    provider->RecordInitialHistogramSnapshots(snapshot_manager);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/delegating_provider.h b/components/metrics/delegating_provider.h
new file mode 100644
index 0000000..55c7144
--- /dev/null
+++ b/components/metrics/delegating_provider.h
@@ -0,0 +1,60 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_DELEGATING_PROVIDER_H_
+#define COMPONENTS_METRICS_DELEGATING_PROVIDER_H_
+
+#include <memory>
+#include <vector>
+
+#include "components/metrics/metrics_provider.h"
+
+namespace metrics {
+
+// A MetricsProvider which manages a set of other MetricsProviders.
+// Calls to this providers methods are forwarded to all of the registered
+// metrics providers, allowing the group to be handled as a single provider.
+class DelegatingProvider final : public MetricsProvider {
+ public:
+  DelegatingProvider();
+  ~DelegatingProvider() override;
+
+  // Registers an additional MetricsProvider to forward calls to.
+  void RegisterMetricsProvider(std::unique_ptr<MetricsProvider> delegate);
+
+  // Gets the list of registered providers.
+  const std::vector<std::unique_ptr<MetricsProvider>>& GetProviders();
+
+  // MetricsProvider:
+  void Init() override;
+  void AsyncInit(const base::Closure& done_callback) override;
+  void OnDidCreateMetricsLog() override;
+  void OnRecordingEnabled() override;
+  void OnRecordingDisabled() override;
+  void OnAppEnterBackground() override;
+  bool ProvideIndependentMetrics(
+      SystemProfileProto* system_profile_proto,
+      base::HistogramSnapshotManager* snapshot_manager) override;
+  void ProvideSystemProfileMetrics(
+      SystemProfileProto* system_profile_proto) override;
+  bool HasPreviousSessionData() override;
+  void ProvidePreviousSessionData(
+      ChromeUserMetricsExtension* uma_proto) override;
+  void ProvideCurrentSessionData(
+      ChromeUserMetricsExtension* uma_proto) override;
+  void ClearSavedStabilityMetrics() override;
+  void RecordHistogramSnapshots(
+      base::HistogramSnapshotManager* snapshot_manager) override;
+  void RecordInitialHistogramSnapshots(
+      base::HistogramSnapshotManager* snapshot_manager) override;
+
+ private:
+  std::vector<std::unique_ptr<MetricsProvider>> metrics_providers_;
+
+  DISALLOW_COPY_AND_ASSIGN(DelegatingProvider);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_DELEGATING_PROVIDER_H_
diff --git a/components/metrics/drive_metrics_provider.cc b/components/metrics/drive_metrics_provider.cc
new file mode 100644
index 0000000..bb92915
--- /dev/null
+++ b/components/metrics/drive_metrics_provider.cc
@@ -0,0 +1,100 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/path_service.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "base/time/time.h"
+
+namespace metrics {
+
+DriveMetricsProvider::DriveMetricsProvider(int local_state_path_key)
+    : local_state_path_key_(local_state_path_key), weak_ptr_factory_(this) {}
+
+DriveMetricsProvider::~DriveMetricsProvider() {}
+
+void DriveMetricsProvider::ProvideSystemProfileMetrics(
+    metrics::SystemProfileProto* system_profile_proto) {
+  auto* hardware = system_profile_proto->mutable_hardware();
+  FillDriveMetrics(metrics_.app_drive, hardware->mutable_app_drive());
+  FillDriveMetrics(metrics_.user_data_drive,
+                   hardware->mutable_user_data_drive());
+}
+
+void DriveMetricsProvider::AsyncInit(const base::Closure& done_callback) {
+  base::PostTaskWithTraitsAndReplyWithResult(
+      FROM_HERE,
+      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
+      base::Bind(&DriveMetricsProvider::GetDriveMetricsOnBackgroundThread,
+                 local_state_path_key_),
+      base::Bind(&DriveMetricsProvider::GotDriveMetrics,
+                 weak_ptr_factory_.GetWeakPtr(), done_callback));
+}
+
+DriveMetricsProvider::SeekPenaltyResponse::SeekPenaltyResponse()
+    : success(false) {}
+
+// static
+DriveMetricsProvider::DriveMetrics
+DriveMetricsProvider::GetDriveMetricsOnBackgroundThread(
+    int local_state_path_key) {
+  base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::WILL_BLOCK);
+
+  DriveMetricsProvider::DriveMetrics metrics;
+  QuerySeekPenalty(base::FILE_EXE, &metrics.app_drive);
+  QuerySeekPenalty(local_state_path_key, &metrics.user_data_drive);
+  return metrics;
+}
+
+// static
+void DriveMetricsProvider::QuerySeekPenalty(
+    int path_service_key,
+    DriveMetricsProvider::SeekPenaltyResponse* response) {
+  DCHECK(response);
+
+  base::FilePath path;
+  if (!base::PathService::Get(path_service_key, &path))
+    return;
+
+  base::TimeTicks start = base::TimeTicks::Now();
+
+  response->success = HasSeekPenalty(path, &response->has_seek_penalty);
+
+  UMA_HISTOGRAM_TIMES("Hardware.Drive.HasSeekPenalty_Time",
+                      base::TimeTicks::Now() - start);
+  UMA_HISTOGRAM_BOOLEAN("Hardware.Drive.HasSeekPenalty_Success",
+                        response->success);
+  if (response->success) {
+    UMA_HISTOGRAM_BOOLEAN("Hardware.Drive.HasSeekPenalty",
+                          response->has_seek_penalty);
+  }
+}
+
+void DriveMetricsProvider::GotDriveMetrics(
+    const base::Closure& done_callback,
+    const DriveMetricsProvider::DriveMetrics& metrics) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  metrics_ = metrics;
+  done_callback.Run();
+}
+
+void DriveMetricsProvider::FillDriveMetrics(
+    const DriveMetricsProvider::SeekPenaltyResponse& response,
+    metrics::SystemProfileProto::Hardware::Drive* drive) {
+  if (response.success)
+    drive->set_has_seek_penalty(response.has_seek_penalty);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/drive_metrics_provider.h b/components/metrics/drive_metrics_provider.h
new file mode 100644
index 0000000..1da73ce
--- /dev/null
+++ b/components/metrics/drive_metrics_provider.h
@@ -0,0 +1,90 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_DRIVE_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_DRIVE_METRICS_PROVIDER_H_
+
+#include "base/callback_forward.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "components/metrics/metrics_provider.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace metrics {
+
+// Provides metrics about the local drives on a user's computer. Currently only
+// checks to see if they incur a seek-time penalty (e.g. if they're SSDs).
+class DriveMetricsProvider : public metrics::MetricsProvider {
+ public:
+  explicit DriveMetricsProvider(int local_state_path_key);
+  ~DriveMetricsProvider() override;
+
+  // metrics::MetricsDataProvider:
+  void AsyncInit(const base::Closure& done_callback) override;
+  void ProvideSystemProfileMetrics(
+      metrics::SystemProfileProto* system_profile_proto) override;
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(DriveMetricsProviderTest, HasSeekPenalty);
+
+  // A response to querying a drive as to whether it incurs a seek penalty.
+  // |has_seek_penalty| is set if |success| is true.
+  struct SeekPenaltyResponse {
+    SeekPenaltyResponse();
+    bool success;
+    bool has_seek_penalty;
+  };
+
+  struct DriveMetrics {
+    SeekPenaltyResponse app_drive;
+    SeekPenaltyResponse user_data_drive;
+  };
+
+  // Determine whether the device that services |path| has a seek penalty.
+  // Returns false if it couldn't be determined (e.g., |path| doesn't exist).
+  static bool HasSeekPenalty(const base::FilePath& path,
+                             bool* has_seek_penalty);
+
+  // Gather metrics about various drives. Should be run on a background thread.
+  static DriveMetrics GetDriveMetricsOnBackgroundThread(
+      int local_state_path_key);
+
+  // Tries to determine whether there is a penalty for seeking on the drive that
+  // hosts |path_service_key| (for example: the drive that holds "Local State").
+  static void QuerySeekPenalty(int path_service_key,
+                               SeekPenaltyResponse* response);
+
+  // Called when metrics are done being gathered asynchronously.
+  // |done_callback| is the callback that should be called once all metrics are
+  // gathered.
+  void GotDriveMetrics(const base::Closure& done_callback,
+                       const DriveMetrics& metrics);
+
+  // Fills |drive| with information from successful |response|s.
+  void FillDriveMetrics(const SeekPenaltyResponse& response,
+                        metrics::SystemProfileProto::Hardware::Drive* drive);
+
+  // The key to give to base::PathService to obtain the path to local state
+  // (supplied by the embedder).
+  int local_state_path_key_;
+
+  // Information gathered about various important drives.
+  DriveMetrics metrics_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  base::WeakPtrFactory<DriveMetricsProvider> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(DriveMetricsProvider);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_DRIVE_METRICS_PROVIDER_H_
diff --git a/components/metrics/drive_metrics_provider_android.cc b/components/metrics/drive_metrics_provider_android.cc
new file mode 100644
index 0000000..a653dd6
--- /dev/null
+++ b/components/metrics/drive_metrics_provider_android.cc
@@ -0,0 +1,16 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+namespace metrics {
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+                                          bool* has_seek_penalty) {
+  *has_seek_penalty = false;
+  return true;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/drive_metrics_provider_fuchsia.cc b/components/metrics/drive_metrics_provider_fuchsia.cc
new file mode 100644
index 0000000..165bc2d
--- /dev/null
+++ b/components/metrics/drive_metrics_provider_fuchsia.cc
@@ -0,0 +1,16 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+namespace metrics {
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+                                          bool* has_seek_penalty) {
+  *has_seek_penalty = false;
+  return true;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/drive_metrics_provider_ios.mm b/components/metrics/drive_metrics_provider_ios.mm
new file mode 100644
index 0000000..a653dd6
--- /dev/null
+++ b/components/metrics/drive_metrics_provider_ios.mm
@@ -0,0 +1,16 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+namespace metrics {
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+                                          bool* has_seek_penalty) {
+  *has_seek_penalty = false;
+  return true;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/drive_metrics_provider_linux.cc b/components/metrics/drive_metrics_provider_linux.cc
new file mode 100644
index 0000000..149405e
--- /dev/null
+++ b/components/metrics/drive_metrics_provider_linux.cc
@@ -0,0 +1,53 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include <linux/kdev_t.h>  // For MAJOR()/MINOR().
+#include <sys/stat.h>
+#include <string>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+
+namespace metrics {
+
+namespace {
+
+// See http://www.kernel.org/doc/Documentation/devices.txt for more info.
+const int kFirstScsiMajorNumber = 8;
+const int kPartitionsPerScsiDevice = 16;
+const char kRotationalFormat[] = "/sys/block/sd%c/queue/rotational";
+
+}  // namespace
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+                                          bool* has_seek_penalty) {
+  base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+  if (!file.IsValid())
+    return false;
+
+  struct stat path_stat;
+  int error = fstat(file.GetPlatformFile(), &path_stat);
+  if (error < 0 || MAJOR(path_stat.st_dev) != kFirstScsiMajorNumber) {
+    // TODO(dbeam): support more SCSI major numbers (e.g. /dev/sdq+) and LVM?
+    return false;
+  }
+
+  char sdX = 'a' + MINOR(path_stat.st_dev) / kPartitionsPerScsiDevice;
+  std::string rotational_path = base::StringPrintf(kRotationalFormat, sdX);
+  std::string rotates;
+  if (!base::ReadFileToString(base::FilePath(rotational_path), &rotates))
+    return false;
+
+  *has_seek_penalty = rotates.substr(0, 1) == "1";
+  return true;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/drive_metrics_provider_mac.mm b/components/metrics/drive_metrics_provider_mac.mm
new file mode 100644
index 0000000..84dbe68
--- /dev/null
+++ b/components/metrics/drive_metrics_provider_mac.mm
@@ -0,0 +1,77 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <DiskArbitration/DiskArbitration.h>
+#import <Foundation/Foundation.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "base/files/file_path.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_ioobject.h"
+
+namespace metrics {
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+                                          bool* has_seek_penalty) {
+  struct stat path_stat;
+  if (stat(path.value().c_str(), &path_stat) < 0)
+    return false;
+
+  const char* dev_name = devname(path_stat.st_dev, S_IFBLK);
+  if (!dev_name)
+    return false;
+
+  std::string bsd_name("/dev/");
+  bsd_name.append(dev_name);
+
+  base::ScopedCFTypeRef<DASessionRef> session(
+      DASessionCreate(kCFAllocatorDefault));
+  if (!session)
+    return false;
+
+  base::ScopedCFTypeRef<DADiskRef> disk(
+      DADiskCreateFromBSDName(kCFAllocatorDefault, session, bsd_name.c_str()));
+  if (!disk)
+    return false;
+
+  base::mac::ScopedIOObject<io_object_t> io_media(DADiskCopyIOMedia(disk));
+  base::ScopedCFTypeRef<CFDictionaryRef> characteristics(
+      static_cast<CFDictionaryRef>(IORegistryEntrySearchCFProperty(
+          io_media, kIOServicePlane, CFSTR(kIOPropertyDeviceCharacteristicsKey),
+          kCFAllocatorDefault,
+          kIORegistryIterateRecursively | kIORegistryIterateParents)));
+  if (!characteristics)
+    return false;
+
+  CFStringRef type_ref = base::mac::GetValueFromDictionary<CFStringRef>(
+      characteristics, CFSTR(kIOPropertyMediumTypeKey));
+  if (!type_ref)
+    return false;
+
+  NSString* type = base::mac::CFToNSCast(type_ref);
+  if ([type isEqualToString:@kIOPropertyMediumTypeRotationalKey]) {
+    *has_seek_penalty = true;
+    return true;
+  }
+  if ([type isEqualToString:@kIOPropertyMediumTypeSolidStateKey]) {
+    *has_seek_penalty = false;
+    return true;
+  }
+
+  // TODO(dbeam): should I look for these Rotational/Solid State keys in
+  // |characteristics|? What if I find device characteristic but there's no
+  // type? Assume rotational?
+  return false;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/drive_metrics_provider_unittest.cc b/components/metrics/drive_metrics_provider_unittest.cc
new file mode 100644
index 0000000..142faf7
--- /dev/null
+++ b/components/metrics/drive_metrics_provider_unittest.cc
@@ -0,0 +1,20 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+TEST(DriveMetricsProviderTest, HasSeekPenalty) {
+  base::FilePath tmp_path;
+  ASSERT_TRUE(base::GetTempDir(&tmp_path));
+  bool unused;
+  DriveMetricsProvider::HasSeekPenalty(tmp_path, &unused);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/drive_metrics_provider_win.cc b/components/metrics/drive_metrics_provider_win.cc
new file mode 100644
index 0000000..6c1334e
--- /dev/null
+++ b/components/metrics/drive_metrics_provider_win.cc
@@ -0,0 +1,46 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/drive_metrics_provider.h"
+
+#include <windows.h>
+#include <winioctl.h>
+#include <vector>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/strings/stringprintf.h"
+
+namespace metrics {
+
+// static
+bool DriveMetricsProvider::HasSeekPenalty(const base::FilePath& path,
+                                          bool* has_seek_penalty) {
+  std::vector<base::FilePath::StringType> components;
+  path.GetComponents(&components);
+
+  base::File volume(base::FilePath(L"\\\\.\\" + components[0]),
+                    base::File::FLAG_OPEN);
+  if (!volume.IsValid())
+    return false;
+
+  STORAGE_PROPERTY_QUERY query = {};
+  query.QueryType = PropertyStandardQuery;
+  query.PropertyId = StorageDeviceSeekPenaltyProperty;
+
+  DEVICE_SEEK_PENALTY_DESCRIPTOR result;
+  DWORD bytes_returned;
+
+  BOOL success = DeviceIoControl(
+      volume.GetPlatformFile(), IOCTL_STORAGE_QUERY_PROPERTY, &query,
+      sizeof(query), &result, sizeof(result), &bytes_returned, nullptr);
+
+  if (success == FALSE || bytes_returned < sizeof(result))
+    return false;
+
+  *has_seek_penalty = result.IncursSeekPenalty != FALSE;
+  return true;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/enabled_state_provider.cc b/components/metrics/enabled_state_provider.cc
new file mode 100644
index 0000000..31f2964
--- /dev/null
+++ b/components/metrics/enabled_state_provider.cc
@@ -0,0 +1,13 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/enabled_state_provider.h"
+
+namespace metrics {
+
+bool EnabledStateProvider::IsReportingEnabled() const {
+  return IsConsentGiven();
+}
+
+}  // namespace metrics
diff --git a/components/metrics/enabled_state_provider.h b/components/metrics/enabled_state_provider.h
new file mode 100644
index 0000000..266194d
--- /dev/null
+++ b/components/metrics/enabled_state_provider.h
@@ -0,0 +1,25 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_ENABLED_STATE_PROVIDER_H_
+#define COMPONENTS_METRICS_ENABLED_STATE_PROVIDER_H_
+
+namespace metrics {
+
+// An interface that provides whether metrics should be reported.
+class EnabledStateProvider {
+ public:
+  virtual ~EnabledStateProvider() {}
+
+  // Indicates that the user has provided consent to collect and report metrics.
+  virtual bool IsConsentGiven() const = 0;
+
+  // Should collection and reporting be enabled. This should depend on consent
+  // being given.
+  virtual bool IsReportingEnabled() const;
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_ENABLED_STATE_PROVIDER_H_
diff --git a/components/metrics/environment_recorder.cc b/components/metrics/environment_recorder.cc
new file mode 100644
index 0000000..6391f61
--- /dev/null
+++ b/components/metrics/environment_recorder.cc
@@ -0,0 +1,96 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/environment_recorder.h"
+
+#include "base/base64.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace metrics {
+
+namespace {
+
+// Computes a SHA-1 hash of |data| and returns it as a hex string.
+std::string ComputeSHA1(const std::string& data) {
+  const std::string sha1 = base::SHA1HashString(data);
+  return base::HexEncode(sha1.data(), sha1.size());
+}
+
+}  // namespace
+
+EnvironmentRecorder::EnvironmentRecorder(PrefService* local_state)
+    : local_state_(local_state) {}
+
+EnvironmentRecorder::~EnvironmentRecorder() = default;
+
+std::string EnvironmentRecorder::SerializeAndRecordEnvironmentToPrefs(
+    const SystemProfileProto& system_profile) {
+  std::string serialized_system_profile;
+  std::string base64_system_profile;
+  if (system_profile.SerializeToString(&serialized_system_profile)) {
+    // Persist the system profile to disk. In the event of an unclean shutdown,
+    // it will be used as part of the initial stability report.
+    base::Base64Encode(serialized_system_profile, &base64_system_profile);
+    local_state_->SetString(prefs::kStabilitySavedSystemProfile,
+                            base64_system_profile);
+    local_state_->SetString(prefs::kStabilitySavedSystemProfileHash,
+                            ComputeSHA1(serialized_system_profile));
+  }
+
+  return serialized_system_profile;
+}
+
+bool EnvironmentRecorder::LoadEnvironmentFromPrefs(
+    SystemProfileProto* system_profile) {
+  DCHECK(system_profile);
+
+  const std::string base64_system_profile =
+      local_state_->GetString(prefs::kStabilitySavedSystemProfile);
+  if (base64_system_profile.empty())
+    return false;
+  const std::string system_profile_hash =
+      local_state_->GetString(prefs::kStabilitySavedSystemProfileHash);
+
+  std::string serialized_system_profile;
+  return base::Base64Decode(base64_system_profile,
+                            &serialized_system_profile) &&
+         ComputeSHA1(serialized_system_profile) == system_profile_hash &&
+         system_profile->ParseFromString(serialized_system_profile);
+}
+
+void EnvironmentRecorder::ClearEnvironmentFromPrefs() {
+  local_state_->ClearPref(prefs::kStabilitySavedSystemProfile);
+  local_state_->ClearPref(prefs::kStabilitySavedSystemProfileHash);
+}
+
+int64_t EnvironmentRecorder::GetLastBuildtime() {
+  return local_state_->GetInt64(prefs::kStabilityStatsBuildTime);
+}
+
+std::string EnvironmentRecorder::GetLastVersion() {
+  return local_state_->GetString(prefs::kStabilityStatsVersion);
+}
+
+void EnvironmentRecorder::SetBuildtimeAndVersion(int64_t buildtime,
+                                                 const std::string& version) {
+  local_state_->SetInt64(prefs::kStabilityStatsBuildTime, buildtime);
+  local_state_->SetString(prefs::kStabilityStatsVersion, version);
+}
+
+// static
+void EnvironmentRecorder::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterStringPref(prefs::kStabilitySavedSystemProfile,
+                               std::string());
+  registry->RegisterStringPref(prefs::kStabilitySavedSystemProfileHash,
+                               std::string());
+  registry->RegisterStringPref(prefs::kStabilityStatsVersion, std::string());
+  registry->RegisterInt64Pref(prefs::kStabilityStatsBuildTime, 0);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/environment_recorder.h b/components/metrics/environment_recorder.h
new file mode 100644
index 0000000..0042d2e
--- /dev/null
+++ b/components/metrics/environment_recorder.h
@@ -0,0 +1,59 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_ENVIRONMENT_RECORDER_H_
+#define COMPONENTS_METRICS_ENVIRONMENT_RECORDER_H_
+
+#include <string>
+
+#include "base/macros.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace metrics {
+
+class SystemProfileProto;
+
+// Stores system profile information to prefs for creating stability logs
+// in the next launch of chrome, and reads data from previous launches.
+class EnvironmentRecorder {
+ public:
+  explicit EnvironmentRecorder(PrefService* local_state);
+  ~EnvironmentRecorder();
+
+  // Serializes the system profile and records it in prefs for the next
+  // session.  Returns the uncompressed serialized proto for passing to crash
+  // reports, or the empty string if the proto can't be serialized.
+  std::string SerializeAndRecordEnvironmentToPrefs(
+      const SystemProfileProto& system_profile);
+
+  // Loads the system_profile data stored in a previous chrome session, and
+  // stores it in the |system_profile| object.
+  // Returns true iff a system profile was successfully read.
+  bool LoadEnvironmentFromPrefs(SystemProfileProto* system_profile);
+
+  // Deletes system profile data from prefs.
+  void ClearEnvironmentFromPrefs();
+
+  // Stores the buildtime of the current binary and version in prefs.
+  void SetBuildtimeAndVersion(int64_t buildtime, const std::string& version);
+
+  // Gets the buildtime stored in prefs.
+  int64_t GetLastBuildtime();
+
+  // Gets the version stored in prefs.
+  std::string GetLastVersion();
+
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+  PrefService* local_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(EnvironmentRecorder);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_ENVIRONMENT_RECORDER_H_
diff --git a/components/metrics/environment_recorder_unittest.cc b/components/metrics/environment_recorder_unittest.cc
new file mode 100644
index 0000000..14c6cb7
--- /dev/null
+++ b/components/metrics/environment_recorder_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/environment_recorder.h"
+
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace metrics {
+
+class EnvironmentRecorderTest : public testing::Test {
+ public:
+  EnvironmentRecorderTest() {
+    EnvironmentRecorder::RegisterPrefs(prefs_.registry());
+  }
+
+  ~EnvironmentRecorderTest() override {}
+
+ protected:
+  TestingPrefServiceSimple prefs_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(EnvironmentRecorderTest);
+};
+
+TEST_F(EnvironmentRecorderTest, LoadEnvironmentFromPrefs) {
+  const char* kSystemProfilePref = prefs::kStabilitySavedSystemProfile;
+  const char* kSystemProfileHashPref = prefs::kStabilitySavedSystemProfileHash;
+
+  // The pref value is empty, so loading it from prefs should fail.
+  {
+    EnvironmentRecorder recorder(&prefs_);
+    SystemProfileProto system_profile;
+    EXPECT_FALSE(recorder.LoadEnvironmentFromPrefs(&system_profile));
+    EXPECT_FALSE(system_profile.has_app_version());
+  }
+
+  // Do a RecordEnvironment() call and check whether the pref is recorded.
+  {
+    EnvironmentRecorder recorder(&prefs_);
+    SystemProfileProto system_profile;
+    system_profile.set_app_version("bogus version");
+    std::string serialized_profile =
+        recorder.SerializeAndRecordEnvironmentToPrefs(system_profile);
+    EXPECT_FALSE(serialized_profile.empty());
+    EXPECT_FALSE(prefs_.GetString(kSystemProfilePref).empty());
+    EXPECT_FALSE(prefs_.GetString(kSystemProfileHashPref).empty());
+  }
+
+  // Load it and check that it has the right value.
+  {
+    EnvironmentRecorder recorder(&prefs_);
+    SystemProfileProto system_profile;
+    EXPECT_TRUE(recorder.LoadEnvironmentFromPrefs(&system_profile));
+    EXPECT_EQ("bogus version", system_profile.app_version());
+    // Ensure that the call did not clear the prefs.
+    EXPECT_FALSE(prefs_.GetString(kSystemProfilePref).empty());
+    EXPECT_FALSE(prefs_.GetString(kSystemProfileHashPref).empty());
+  }
+
+  // Ensure that a non-matching hash results in the pref being invalid.
+  {
+    // Set the hash to a bad value.
+    prefs_.SetString(kSystemProfileHashPref, "deadbeef");
+    EnvironmentRecorder recorder(&prefs_);
+    SystemProfileProto system_profile;
+    EXPECT_FALSE(recorder.LoadEnvironmentFromPrefs(&system_profile));
+    EXPECT_FALSE(system_profile.has_app_version());
+  }
+}
+
+}  // namespace metrics
diff --git a/components/metrics/execution_phase.cc b/components/metrics/execution_phase.cc
new file mode 100644
index 0000000..992ef82
--- /dev/null
+++ b/components/metrics/execution_phase.cc
@@ -0,0 +1,69 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/execution_phase.h"
+
+#include "build/build_config.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+#if defined(OS_WIN)
+#include "components/browser_watcher/stability_data_names.h"
+#include "components/browser_watcher/stability_debugging.h"
+#endif  // defined(OS_WIN)
+
+namespace metrics {
+
+ExecutionPhaseManager::ExecutionPhaseManager(PrefService* local_state)
+    : local_state_(local_state) {}
+
+ExecutionPhaseManager::~ExecutionPhaseManager() {}
+
+// static
+void ExecutionPhaseManager::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterIntegerPref(
+      prefs::kStabilityExecutionPhase,
+      static_cast<int>(ExecutionPhase::UNINITIALIZED_PHASE));
+}
+
+// static
+ExecutionPhase ExecutionPhaseManager::execution_phase_ =
+    ExecutionPhase::UNINITIALIZED_PHASE;
+
+void ExecutionPhaseManager::SetExecutionPhase(ExecutionPhase execution_phase) {
+  DCHECK(execution_phase != ExecutionPhase::START_METRICS_RECORDING ||
+         execution_phase_ == ExecutionPhase::UNINITIALIZED_PHASE);
+  execution_phase_ = execution_phase;
+  local_state_->SetInteger(prefs::kStabilityExecutionPhase,
+                           static_cast<int>(execution_phase_));
+#if defined(OS_WIN)
+  browser_watcher::SetStabilityDataInt(
+      browser_watcher::kStabilityExecutionPhase,
+      static_cast<int>(execution_phase_));
+#endif  // defined(OS_WIN)
+}
+
+ExecutionPhase ExecutionPhaseManager::GetExecutionPhase() {
+  // TODO(rtenneti): On windows, consider saving/getting execution_phase from
+  // the registry.
+  return static_cast<ExecutionPhase>(
+      local_state_->GetInteger(prefs::kStabilityExecutionPhase));
+}
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+void ExecutionPhaseManager::OnAppEnterBackground() {
+  // Note: the in-memory ExecutionPhaseManager::execution_phase_ is not updated.
+  local_state_->SetInteger(prefs::kStabilityExecutionPhase,
+                           static_cast<int>(ExecutionPhase::SHUTDOWN_COMPLETE));
+}
+
+void ExecutionPhaseManager::OnAppEnterForeground() {
+  // Restore prefs value altered by OnEnterBackground.
+  local_state_->SetInteger(prefs::kStabilityExecutionPhase,
+                           static_cast<int>(execution_phase_));
+}
+#endif  // defined(OS_ANDROID) || defined(OS_IOS)
+
+}  // namespace metrics
diff --git a/components/metrics/execution_phase.h b/components/metrics/execution_phase.h
new file mode 100644
index 0000000..b158923
--- /dev/null
+++ b/components/metrics/execution_phase.h
@@ -0,0 +1,55 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_EXECUTION_PHASE_H_
+#define COMPONENTS_METRICS_EXECUTION_PHASE_H_
+
+#include "base/macros.h"
+#include "build/build_config.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace metrics {
+
+enum class ExecutionPhase {
+  UNINITIALIZED_PHASE = 0,
+  START_METRICS_RECORDING = 100,
+  CREATE_PROFILE = 200,
+  STARTUP_TIMEBOMB_ARM = 300,
+  THREAD_WATCHER_START = 400,
+  MAIN_MESSAGE_LOOP_RUN = 500,
+  SHUTDOWN_TIMEBOMB_ARM = 600,
+  SHUTDOWN_COMPLETE = 700,
+};
+
+// Helper class for managing ExecutionPhase state in prefs and memory.
+// It's safe to construct temporary objects to perform these operations.
+class ExecutionPhaseManager {
+ public:
+  explicit ExecutionPhaseManager(PrefService* local_state);
+  ~ExecutionPhaseManager();
+
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+  void SetExecutionPhase(ExecutionPhase execution_phase);
+  ExecutionPhase GetExecutionPhase();
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+  void OnAppEnterBackground();
+  void OnAppEnterForeground();
+#endif  // defined(OS_ANDROID) || defined(OS_IOS)
+
+ private:
+  // Execution phase the browser is in.
+  static ExecutionPhase execution_phase_;
+
+  PrefService* local_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExecutionPhaseManager);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_EXECUTION_PHASE_H_
diff --git a/components/metrics/expired_histogram_util.cc b/components/metrics/expired_histogram_util.cc
new file mode 100644
index 0000000..17844e7
--- /dev/null
+++ b/components/metrics/expired_histogram_util.cc
@@ -0,0 +1,34 @@
+// 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.
+
+#include "components/metrics/expired_histogram_util.h"
+
+#include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/statistics_recorder.h"
+#include "components/metrics/expired_histograms_checker.h"
+
+namespace metrics {
+namespace {
+
+const base::Feature kExpiredHistogramLogicFeature{
+    "ExpiredHistogramLogic", base::FEATURE_DISABLED_BY_DEFAULT};
+
+const base::FeatureParam<std::string> kWhitelistParam{
+    &kExpiredHistogramLogicFeature, "whitelist", ""};
+
+}  // namespace
+
+void EnableExpiryChecker(const uint64_t* expired_histograms_hashes,
+                         size_t num_expired_histograms) {
+  DCHECK(base::FeatureList::GetInstance());
+  if (base::FeatureList::IsEnabled(kExpiredHistogramLogicFeature)) {
+    base::StatisticsRecorder::SetRecordChecker(
+        std::make_unique<ExpiredHistogramsChecker>(expired_histograms_hashes,
+                                                   num_expired_histograms,
+                                                   kWhitelistParam.Get()));
+  }
+}
+
+}  // namespace metrics
\ No newline at end of file
diff --git a/components/metrics/expired_histogram_util.h b/components/metrics/expired_histogram_util.h
new file mode 100644
index 0000000..cf6c455
--- /dev/null
+++ b/components/metrics/expired_histogram_util.h
@@ -0,0 +1,21 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_EXPIRED_HISTOGRAM_UTIL_H_
+#define COMPONENTS_METRICS_EXPIRED_HISTOGRAM_UTIL_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace metrics {
+
+// Enables histogram expiry checker if it is enabled by field trial. Histogram
+// expiry is disbaled by default so that unit tests don't fail unexpectedly when
+// a histogram expires.
+void EnableExpiryChecker(const uint64_t* expired_histograms_hashes,
+                         size_t num_expired_histograms);
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_EXPIRED_HISTOGRAM_UTIL_H_
\ No newline at end of file
diff --git a/components/metrics/expired_histograms_checker.cc b/components/metrics/expired_histograms_checker.cc
new file mode 100644
index 0000000..d1d3ecb
--- /dev/null
+++ b/components/metrics/expired_histograms_checker.cc
@@ -0,0 +1,41 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/expired_histograms_checker.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/metrics/metrics_hashes.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/stl_util.h"
+#include "base/strings/string_split.h"
+
+namespace metrics {
+
+ExpiredHistogramsChecker::ExpiredHistogramsChecker(
+    const uint64_t* array,
+    size_t size,
+    const std::string& whitelist_str)
+    : array_(array), size_(size) {
+  InitWhitelist(whitelist_str);
+}
+
+ExpiredHistogramsChecker::~ExpiredHistogramsChecker() {}
+
+bool ExpiredHistogramsChecker::ShouldRecord(uint64_t histogram_hash) const {
+  // If histogram is whitelisted then it should always be recorded.
+  if (base::ContainsKey(whitelist_, histogram_hash))
+    return true;
+  return !std::binary_search(array_, array_ + size_, histogram_hash);
+}
+
+void ExpiredHistogramsChecker::InitWhitelist(const std::string& whitelist_str) {
+  std::vector<base::StringPiece> whitelist_names = base::SplitStringPiece(
+      whitelist_str, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  for (base::StringPiece name : whitelist_names)
+    whitelist_.insert(base::HashMetricName(name));
+}
+
+}  // namespace metrics
diff --git a/components/metrics/expired_histograms_checker.h b/components/metrics/expired_histograms_checker.h
new file mode 100644
index 0000000..b3ca4a7
--- /dev/null
+++ b/components/metrics/expired_histograms_checker.h
@@ -0,0 +1,51 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_EXPIRED_HISTOGRAMS_CHECKER_H_
+#define COMPONENTS_METRICS_EXPIRED_HISTOGRAMS_CHECKER_H_
+
+#include <stdint.h>
+#include <set>
+
+#include "base/macros.h"
+#include "base/metrics/record_histogram_checker.h"
+#include "base/strings/string_piece.h"
+
+namespace metrics {
+
+// ExpiredHistogramsChecker implements RecordHistogramChecker interface
+// to avoid recording expired metrics.
+class ExpiredHistogramsChecker final : public base::RecordHistogramChecker {
+ public:
+  // Takes sorted in nondecreasing order array of histogram hashes, its size and
+  // list of whitelisted histogram names concatenated as a comma-separated
+  // string.
+  ExpiredHistogramsChecker(const uint64_t* array,
+                           size_t size,
+                           const std::string& whitelist_str);
+  ~ExpiredHistogramsChecker() override;
+
+  // Checks if the given |histogram_hash| corresponds to an expired histogram.
+  bool ShouldRecord(uint64_t histogram_hash) const override;
+
+ private:
+  // Initializes the |whitelist_| array of histogram hashes that should be
+  // recorded regardless of their expiration.
+  void InitWhitelist(const std::string& whitelist_str);
+
+  // Array of expired histogram hashes.
+  const uint64_t* const array_;
+
+  // Size of the |array_|.
+  const size_t size_;
+
+  // List of expired histogram hashes that should be recorded.
+  std::set<uint64_t> whitelist_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExpiredHistogramsChecker);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_EXPIRED_HISTOGRAMS_CHECKER_H_
diff --git a/components/metrics/expired_histograms_checker_unittest.cc b/components/metrics/expired_histograms_checker_unittest.cc
new file mode 100644
index 0000000..13aa77d
--- /dev/null
+++ b/components/metrics/expired_histograms_checker_unittest.cc
@@ -0,0 +1,40 @@
+// 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.
+
+#include "components/metrics/expired_histograms_checker.h"
+
+#include "base/metrics/metrics_hashes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+TEST(ExpiredHistogramsCheckerTests, BasicTest) {
+  uint64_t expired_hashes[] = {1, 2, 3};
+  size_t size = 3;
+  std::string whitelist_str = "";
+  ExpiredHistogramsChecker checker(expired_hashes, size, whitelist_str);
+
+  EXPECT_TRUE(checker.ShouldRecord(0));
+  EXPECT_FALSE(checker.ShouldRecord(3));
+}
+
+TEST(ExpiredHistogramsCheckerTests, WhitelistTest) {
+  std::string hist1 = "hist1";
+  std::string hist2 = "hist2";
+  std::string hist3 = "hist3";
+  std::string hist4 = "hist4";
+
+  uint64_t expired_hashes[] = {base::HashMetricName(hist1),
+                               base::HashMetricName(hist2)};
+  size_t size = 2;
+  std::string whitelist_str = hist2 + "," + hist4;
+  ExpiredHistogramsChecker checker(expired_hashes, size, whitelist_str);
+
+  EXPECT_FALSE(checker.ShouldRecord(base::HashMetricName(hist1)));
+  EXPECT_TRUE(checker.ShouldRecord(base::HashMetricName(hist2)));
+  EXPECT_TRUE(checker.ShouldRecord(base::HashMetricName(hist3)));
+  EXPECT_TRUE(checker.ShouldRecord(base::HashMetricName(hist4)));
+}
+
+}  // namespace metrics
\ No newline at end of file
diff --git a/components/metrics/field_trials_provider.cc b/components/metrics/field_trials_provider.cc
new file mode 100644
index 0000000..ea272f5
--- /dev/null
+++ b/components/metrics/field_trials_provider.cc
@@ -0,0 +1,65 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/field_trials_provider.h"
+
+#include "base/strings/string_piece.h"
+#include "components/variations/active_field_trials.h"
+#include "components/variations/synthetic_trial_registry.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace variations {
+
+namespace {
+
+void WriteFieldTrials(const std::vector<ActiveGroupId>& field_trial_ids,
+                      metrics::SystemProfileProto* system_profile) {
+  for (const ActiveGroupId& id : field_trial_ids) {
+    metrics::SystemProfileProto::FieldTrial* field_trial =
+        system_profile->add_field_trial();
+    field_trial->set_name_id(id.name);
+    field_trial->set_group_id(id.group);
+  }
+}
+
+}  // namespace
+
+FieldTrialsProvider::FieldTrialsProvider(SyntheticTrialRegistry* registry,
+                                         base::StringPiece suffix)
+    : registry_(registry), suffix_(suffix) {}
+FieldTrialsProvider::~FieldTrialsProvider() = default;
+
+void FieldTrialsProvider::GetFieldTrialIds(
+    std::vector<ActiveGroupId>* field_trial_ids) const {
+  // We use the default field trial suffixing (no suffix).
+  variations::GetFieldTrialActiveGroupIds(suffix_, field_trial_ids);
+}
+
+void FieldTrialsProvider::OnDidCreateMetricsLog() {
+  if (registry_) {
+    creation_times_.push_back(base::TimeTicks::Now());
+  }
+}
+
+void FieldTrialsProvider::ProvideSystemProfileMetrics(
+    metrics::SystemProfileProto* system_profile_proto) {
+  std::vector<ActiveGroupId> field_trial_ids;
+  GetFieldTrialIds(&field_trial_ids);
+  WriteFieldTrials(field_trial_ids, system_profile_proto);
+
+  if (registry_) {
+    base::TimeTicks creation_time;
+    // Should always be true, but don't crash even if there is a bug.
+    if (!creation_times_.empty()) {
+      creation_time = creation_times_.back();
+      creation_times_.pop_back();
+    }
+    std::vector<ActiveGroupId> synthetic_trials;
+    registry_->GetSyntheticFieldTrialsOlderThan(creation_time,
+                                                &synthetic_trials);
+    WriteFieldTrials(synthetic_trials, system_profile_proto);
+  }
+}
+
+}  // namespace variations
diff --git a/components/metrics/field_trials_provider.h b/components/metrics/field_trials_provider.h
new file mode 100644
index 0000000..d93988e
--- /dev/null
+++ b/components/metrics/field_trials_provider.h
@@ -0,0 +1,55 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_FIELD_TRIALS_PROVIDER_H_
+#define COMPONENTS_METRICS_FIELD_TRIALS_PROVIDER_H_
+
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_provider.h"
+
+// TODO(crbug/507665): Once MetricsProvider/SystemProfileProto are moved into
+// //services/metrics, then //components/variations can depend on them, and
+// this should be moved there.
+namespace variations {
+
+class SyntheticTrialRegistry;
+struct ActiveGroupId;
+
+class FieldTrialsProvider : public metrics::MetricsProvider {
+ public:
+  // |registry| must outlive this metrics provider.
+  FieldTrialsProvider(SyntheticTrialRegistry* registry,
+                      base::StringPiece suffix);
+  ~FieldTrialsProvider() override;
+
+  // metrics::MetricsProvider:
+  void OnDidCreateMetricsLog() override;
+  void ProvideSystemProfileMetrics(
+      metrics::SystemProfileProto* system_profile_proto) override;
+
+ private:
+  // Overrideable for testing.
+  virtual void GetFieldTrialIds(
+      std::vector<ActiveGroupId>* field_trial_ids) const;
+
+  SyntheticTrialRegistry* registry_;
+
+  // Suffix used for the field trial names before they are hashed for uploads.
+  std::string suffix_;
+
+  // A stack of log creation times.
+  // While the initial metrics log exists, there will be two logs open.
+  // Use a stack so that we use the right creation time for the first ongoing
+  // log.
+  // TODO(crbug/746098): Simplify InitialMetricsLog logic so this is not
+  // necessary.
+  std::vector<base::TimeTicks> creation_times_;
+};
+
+}  // namespace variations
+
+#endif  // COMPONENTS_METRICS_FIELD_TRIALS_PROVIDER_H_
diff --git a/components/metrics/field_trials_provider_unittest.cc b/components/metrics/field_trials_provider_unittest.cc
new file mode 100644
index 0000000..2f6c8ef
--- /dev/null
+++ b/components/metrics/field_trials_provider_unittest.cc
@@ -0,0 +1,103 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/field_trials_provider.h"
+
+#include "components/variations/active_field_trials.h"
+#include "components/variations/synthetic_trial_registry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace variations {
+
+namespace {
+
+const ActiveGroupId kFieldTrialIds[] = {{37, 43}, {13, 47}, {23, 17}};
+const ActiveGroupId kSyntheticTrials[] = {{55, 15}, {66, 16}};
+
+class TestProvider : public FieldTrialsProvider {
+ public:
+  TestProvider(SyntheticTrialRegistry* registry, base::StringPiece suffix)
+      : FieldTrialsProvider(registry, suffix) {}
+  ~TestProvider() override {}
+
+  void GetFieldTrialIds(
+      std::vector<ActiveGroupId>* field_trial_ids) const override {
+    ASSERT_TRUE(field_trial_ids->empty());
+    for (const ActiveGroupId& id : kFieldTrialIds) {
+      field_trial_ids->push_back(id);
+    }
+  }
+};
+
+// Check that the values in |system_values| correspond to the test data
+// defined at the top of this file.
+void CheckSystemProfile(const metrics::SystemProfileProto& system_profile) {
+  ASSERT_EQ(arraysize(kFieldTrialIds) + arraysize(kSyntheticTrials),
+            static_cast<size_t>(system_profile.field_trial_size()));
+  for (size_t i = 0; i < arraysize(kFieldTrialIds); ++i) {
+    const metrics::SystemProfileProto::FieldTrial& field_trial =
+        system_profile.field_trial(i);
+    EXPECT_EQ(kFieldTrialIds[i].name, field_trial.name_id());
+    EXPECT_EQ(kFieldTrialIds[i].group, field_trial.group_id());
+  }
+  // Verify the right data is present for the synthetic trials.
+  for (size_t i = 0; i < arraysize(kSyntheticTrials); ++i) {
+    const metrics::SystemProfileProto::FieldTrial& field_trial =
+        system_profile.field_trial(i + arraysize(kFieldTrialIds));
+    EXPECT_EQ(kSyntheticTrials[i].name, field_trial.name_id());
+    EXPECT_EQ(kSyntheticTrials[i].group, field_trial.group_id());
+  }
+}
+
+}  // namespace
+
+class FieldTrialsProviderTest : public ::testing::Test {
+ public:
+  FieldTrialsProviderTest() {}
+  ~FieldTrialsProviderTest() override {}
+
+ protected:
+  // Register trials which should get recorded.
+  void RegisterExpectedSyntheticTrials() {
+    for (const ActiveGroupId& id : kSyntheticTrials) {
+      registry_.RegisterSyntheticFieldTrial(
+          SyntheticTrialGroup(id.name, id.group));
+    }
+  }
+  // Register trial which shouldn't get recorded.
+  void RegisterExtraSyntheticTrial() {
+    registry_.RegisterSyntheticFieldTrial(SyntheticTrialGroup(100, 1000));
+  }
+
+  // Waits until base::TimeTicks::Now() no longer equals |value|. This should
+  // take between 1-15ms per the documented resolution of base::TimeTicks.
+  void WaitUntilTimeChanges(const base::TimeTicks& value) {
+    while (base::TimeTicks::Now() == value) {
+      base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1));
+    }
+  }
+
+  SyntheticTrialRegistry registry_;
+};
+
+TEST_F(FieldTrialsProviderTest, ProvideSyntheticTrials) {
+  TestProvider provider(&registry_, base::StringPiece());
+
+  RegisterExpectedSyntheticTrials();
+  // Make sure these trials are older than the log.
+  WaitUntilTimeChanges(base::TimeTicks::Now());
+
+  provider.OnDidCreateMetricsLog();
+  // Make sure that the log is older than the trials that should be excluded.
+  WaitUntilTimeChanges(base::TimeTicks::Now());
+
+  RegisterExtraSyntheticTrial();
+
+  metrics::SystemProfileProto proto;
+  provider.ProvideSystemProfileMetrics(&proto);
+  CheckSystemProfile(proto);
+}
+
+}  // namespace variations
diff --git a/components/metrics/file_metrics_provider.cc b/components/metrics/file_metrics_provider.cc
new file mode 100644
index 0000000..a94bb07
--- /dev/null
+++ b/components/metrics/file_metrics_provider.cc
@@ -0,0 +1,837 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/file_metrics_provider.h"
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/containers/flat_map.h"
+#include "base/files/file.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/persistent_memory_allocator.h"
+#include "base/strings/string_piece.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/task_runner.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_service.h"
+#include "components/metrics/persistent_system_profile.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace metrics {
+
+namespace {
+
+// These structures provide values used to define how files are opened and
+// accessed. It obviates the need for multiple code-paths within several of
+// the methods.
+struct SourceOptions {
+  // The flags to be used to open a file on disk.
+  int file_open_flags;
+
+  // The access mode to be used when mapping a file into memory.
+  base::MemoryMappedFile::Access memory_mapped_access;
+
+  // Indicates if the file is to be accessed read-only.
+  bool is_read_only;
+};
+
+enum : int {
+  // Opening a file typically requires at least these flags.
+  STD_OPEN = base::File::FLAG_OPEN | base::File::FLAG_READ,
+};
+
+constexpr SourceOptions kSourceOptions[] = {
+  // SOURCE_HISTOGRAMS_ATOMIC_FILE
+  {
+    // Ensure that no other process reads this at the same time.
+    STD_OPEN | base::File::FLAG_EXCLUSIVE_READ,
+    base::MemoryMappedFile::READ_ONLY,
+    true
+  },
+  // SOURCE_HISTOGRAMS_ATOMIC_DIR
+  {
+    // Ensure that no other process reads this at the same time.
+    STD_OPEN | base::File::FLAG_EXCLUSIVE_READ,
+    base::MemoryMappedFile::READ_ONLY,
+    true
+  },
+  // SOURCE_HISTOGRAMS_ACTIVE_FILE
+  {
+    // Allow writing (updated "logged" values) to the file.
+    STD_OPEN | base::File::FLAG_WRITE,
+    base::MemoryMappedFile::READ_WRITE,
+    false
+  }
+};
+
+enum EmbeddedProfileResult : int {
+  EMBEDDED_PROFILE_ATTEMPT,
+  EMBEDDED_PROFILE_FOUND,
+  EMBEDDED_PROFILE_FALLBACK,
+  EMBEDDED_PROFILE_DROPPED,
+  EMBEDDED_PROFILE_ACTION_MAX
+};
+
+void RecordEmbeddedProfileResult(EmbeddedProfileResult result) {
+  UMA_HISTOGRAM_ENUMERATION("UMA.FileMetricsProvider.EmbeddedProfileResult",
+                            result, EMBEDDED_PROFILE_ACTION_MAX);
+}
+
+void DeleteFileWhenPossible(const base::FilePath& path) {
+  // Open (with delete) and then immediately close the file by going out of
+  // scope. This is the only cross-platform safe way to delete a file that may
+  // be open elsewhere, a distinct possibility given the asynchronous nature
+  // of the delete task.
+  base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ |
+                            base::File::FLAG_DELETE_ON_CLOSE);
+}
+
+// A task runner to use for testing.
+base::TaskRunner* g_task_runner_for_testing = nullptr;
+
+// Returns a task runner appropriate for running background tasks that perform
+// file I/O.
+scoped_refptr<base::TaskRunner> CreateBackgroundTaskRunner() {
+  if (g_task_runner_for_testing)
+    return scoped_refptr<base::TaskRunner>(g_task_runner_for_testing);
+
+  return base::CreateTaskRunnerWithTraits(
+      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
+}
+
+}  // namespace
+
+// This structure stores all the information about the sources being monitored
+// and their current reporting state.
+struct FileMetricsProvider::SourceInfo {
+  SourceInfo(const Params& params)
+      : type(params.type),
+        association(params.association),
+        prefs_key(params.prefs_key),
+        filter(params.filter),
+        max_age(params.max_age),
+        max_dir_kib(params.max_dir_kib),
+        max_dir_files(params.max_dir_files) {
+    switch (type) {
+      case SOURCE_HISTOGRAMS_ACTIVE_FILE:
+        DCHECK(prefs_key.empty());
+        FALLTHROUGH;
+      case SOURCE_HISTOGRAMS_ATOMIC_FILE:
+        path = params.path;
+        break;
+      case SOURCE_HISTOGRAMS_ATOMIC_DIR:
+        directory = params.path;
+        break;
+    }
+  }
+  ~SourceInfo() {}
+
+  struct FoundFile {
+    base::FilePath path;
+    base::FileEnumerator::FileInfo info;
+  };
+  using FoundFiles = base::flat_map<base::Time, FoundFile>;
+
+  // How to access this source (file/dir, atomic/active).
+  const SourceType type;
+
+  // With what run this source is associated.
+  const SourceAssociation association;
+
+  // Where on disk the directory is located. This will only be populated when
+  // a directory is being monitored.
+  base::FilePath directory;
+
+  // The files found in the above directory, ordered by last-modified.
+  std::unique_ptr<FoundFiles> found_files;
+
+  // Where on disk the file is located. If a directory is being monitored,
+  // this will be updated for whatever file is being read.
+  base::FilePath path;
+
+  // Name used inside prefs to persistent metadata.
+  std::string prefs_key;
+
+  // The filter callback for determining what to do with found files.
+  FilterCallback filter;
+
+  // The maximum allowed age of a file.
+  base::TimeDelta max_age;
+
+  // The maximum allowed bytes in a directory.
+  size_t max_dir_kib;
+
+  // The maximum allowed files in a directory.
+  size_t max_dir_files;
+
+  // The last-seen time of this source to detect change.
+  base::Time last_seen;
+
+  // Indicates if the data has been read out or not.
+  bool read_complete = false;
+
+  // Once a file has been recognized as needing to be read, it is mapped
+  // into memory and assigned to an |allocator| object.
+  std::unique_ptr<base::PersistentHistogramAllocator> allocator;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SourceInfo);
+};
+
+FileMetricsProvider::Params::Params(const base::FilePath& path,
+                                    SourceType type,
+                                    SourceAssociation association,
+                                    base::StringPiece prefs_key)
+    : path(path), type(type), association(association), prefs_key(prefs_key) {}
+
+FileMetricsProvider::Params::~Params() {}
+
+FileMetricsProvider::FileMetricsProvider(PrefService* local_state)
+    : task_runner_(CreateBackgroundTaskRunner()),
+      pref_service_(local_state),
+      weak_factory_(this) {
+  base::StatisticsRecorder::RegisterHistogramProvider(
+      weak_factory_.GetWeakPtr());
+}
+
+FileMetricsProvider::~FileMetricsProvider() {}
+
+void FileMetricsProvider::RegisterSource(const Params& params) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Ensure that kSourceOptions has been filled for this type.
+  DCHECK_GT(arraysize(kSourceOptions), static_cast<size_t>(params.type));
+
+  std::unique_ptr<SourceInfo> source(new SourceInfo(params));
+
+  // |prefs_key| may be empty if the caller does not wish to persist the
+  // state across instances of the program.
+  if (pref_service_ && !params.prefs_key.empty()) {
+    source->last_seen = base::Time::FromInternalValue(
+        pref_service_->GetInt64(metrics::prefs::kMetricsLastSeenPrefix +
+                                source->prefs_key));
+  }
+
+  switch (params.association) {
+    case ASSOCIATE_CURRENT_RUN:
+    case ASSOCIATE_INTERNAL_PROFILE:
+      sources_to_check_.push_back(std::move(source));
+      break;
+    case ASSOCIATE_PREVIOUS_RUN:
+    case ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN:
+      DCHECK_EQ(SOURCE_HISTOGRAMS_ATOMIC_FILE, source->type);
+      sources_for_previous_run_.push_back(std::move(source));
+      break;
+  }
+}
+
+// static
+void FileMetricsProvider::RegisterPrefs(PrefRegistrySimple* prefs,
+                                        const base::StringPiece prefs_key) {
+  prefs->RegisterInt64Pref(metrics::prefs::kMetricsLastSeenPrefix +
+                           prefs_key.as_string(), 0);
+}
+
+// static
+void FileMetricsProvider::SetTaskRunnerForTesting(
+    const scoped_refptr<base::TaskRunner>& task_runner) {
+  DCHECK(!g_task_runner_for_testing || !task_runner);
+  g_task_runner_for_testing = task_runner.get();
+}
+
+// static
+void FileMetricsProvider::RecordAccessResult(AccessResult result) {
+  UMA_HISTOGRAM_ENUMERATION("UMA.FileMetricsProvider.AccessResult", result,
+                            ACCESS_RESULT_MAX);
+}
+
+// static
+bool FileMetricsProvider::LocateNextFileInDirectory(SourceInfo* source) {
+  DCHECK_EQ(SOURCE_HISTOGRAMS_ATOMIC_DIR, source->type);
+  DCHECK(!source->directory.empty());
+
+  // Cumulative directory stats. These will remain zero if the directory isn't
+  // scanned but that's okay since any work they would cause to be done below
+  // would have been done during the first call where the directory was fully
+  // scanned.
+  size_t total_size_kib = 0;  // Using KiB allows 4TiB even on 32-bit builds.
+  size_t file_count = 0;
+
+  base::Time now_time = base::Time::Now();
+  if (!source->found_files) {
+    source->found_files = std::make_unique<SourceInfo::FoundFiles>();
+    base::FileEnumerator file_iter(source->directory, /*recursive=*/false,
+                                   base::FileEnumerator::FILES);
+    SourceInfo::FoundFile found_file;
+
+    // Open the directory and find all the files, remembering the last-modified
+    // time of each.
+    for (found_file.path = file_iter.Next(); !found_file.path.empty();
+         found_file.path = file_iter.Next()) {
+      found_file.info = file_iter.GetInfo();
+
+      // Ignore directories.
+      if (found_file.info.IsDirectory())
+        continue;
+
+      // Ignore temporary files.
+      base::FilePath::CharType first_character =
+          found_file.path.BaseName().value().front();
+      if (first_character == FILE_PATH_LITERAL('.') ||
+          first_character == FILE_PATH_LITERAL('_')) {
+        continue;
+      }
+
+      // Ignore non-PMA (Persistent Memory Allocator) files.
+      if (found_file.path.Extension() !=
+          base::PersistentMemoryAllocator::kFileExtension) {
+        continue;
+      }
+
+      // Process real files.
+      total_size_kib += found_file.info.GetSize() >> 10;
+      base::Time modified = found_file.info.GetLastModifiedTime();
+      if (modified > source->last_seen) {
+        // This file hasn't been read. Remember it (unless from the future).
+        if (modified <= now_time)
+          source->found_files->emplace(modified, std::move(found_file));
+        ++file_count;
+      } else {
+        // This file has been read. Try to delete it. Ignore any errors because
+        // the file may be un-removeable by this process. It could, for example,
+        // have been created by a privileged process like setup.exe. Even if it
+        // is not removed, it will continue to be ignored bacuse of the older
+        // modification time.
+        base::DeleteFile(found_file.path, /*recursive=*/false);
+      }
+    }
+  }
+
+  // Filter files from the front until one is found for processing.
+  bool have_file = false;
+  while (!source->found_files->empty()) {
+    SourceInfo::FoundFile found =
+        std::move(source->found_files->begin()->second);
+    source->found_files->erase(source->found_files->begin());
+
+    bool too_many =
+        source->max_dir_files > 0 && file_count > source->max_dir_files;
+    bool too_big =
+        source->max_dir_kib > 0 && total_size_kib > source->max_dir_kib;
+    bool too_old =
+        source->max_age != base::TimeDelta() &&
+        now_time - found.info.GetLastModifiedTime() > source->max_age;
+    if (too_many || too_big || too_old) {
+      base::DeleteFile(found.path, /*recursive=*/false);
+      --file_count;
+      total_size_kib -= found.info.GetSize() >> 10;
+      RecordAccessResult(too_many ? ACCESS_RESULT_TOO_MANY_FILES
+                                  : too_big ? ACCESS_RESULT_TOO_MANY_BYTES
+                                            : ACCESS_RESULT_TOO_OLD);
+      continue;
+    }
+
+    AccessResult result = HandleFilterSource(source, found.path);
+    if (result == ACCESS_RESULT_SUCCESS) {
+      source->path = std::move(found.path);
+      have_file = true;
+      break;
+    }
+
+    // Record the result. Success will be recorded by the caller.
+    if (result != ACCESS_RESULT_THIS_PID)
+      RecordAccessResult(result);
+  }
+
+  return have_file;
+}
+
+// static
+void FileMetricsProvider::FinishedWithSource(SourceInfo* source,
+                                             AccessResult result) {
+  // Different source types require different post-processing.
+  switch (source->type) {
+    case SOURCE_HISTOGRAMS_ATOMIC_FILE:
+    case SOURCE_HISTOGRAMS_ATOMIC_DIR:
+      // Done with this file so delete the allocator and its owned file.
+      source->allocator.reset();
+      // Remove the file if has been recorded. This prevents them from
+      // accumulating or also being recorded by different instances of
+      // the browser.
+      if (result == ACCESS_RESULT_SUCCESS ||
+          result == ACCESS_RESULT_NOT_MODIFIED ||
+          result == ACCESS_RESULT_MEMORY_DELETED ||
+          result == ACCESS_RESULT_TOO_OLD) {
+        DeleteFileWhenPossible(source->path);
+      }
+      break;
+    case SOURCE_HISTOGRAMS_ACTIVE_FILE:
+      // Keep the allocator open so it doesn't have to be re-mapped each
+      // time. This also allows the contents to be merged on-demand.
+      break;
+  }
+}
+
+// static
+void FileMetricsProvider::CheckAndMergeMetricSourcesOnTaskRunner(
+    SourceInfoList* sources) {
+  // This method has all state information passed in |sources| and is intended
+  // to run on a worker thread rather than the UI thread.
+  for (std::unique_ptr<SourceInfo>& source : *sources) {
+    AccessResult result;
+    do {
+      result = CheckAndMapMetricSource(source.get());
+
+      // Some results are not reported in order to keep the dashboard clean.
+      if (result != ACCESS_RESULT_DOESNT_EXIST &&
+          result != ACCESS_RESULT_NOT_MODIFIED &&
+          result != ACCESS_RESULT_THIS_PID) {
+        RecordAccessResult(result);
+      }
+
+      // If there are no files (or no more files) in this source, stop now.
+      if (result == ACCESS_RESULT_DOESNT_EXIST)
+        break;
+
+      // Mapping was successful. Merge it.
+      if (result == ACCESS_RESULT_SUCCESS) {
+        // Metrics associated with internal profiles have to be fetched directly
+        // so just keep the mapping for use by the main thread.
+        if (source->association == ASSOCIATE_INTERNAL_PROFILE)
+          break;
+
+        MergeHistogramDeltasFromSource(source.get());
+        DCHECK(source->read_complete);
+      }
+
+      // All done with this source.
+      FinishedWithSource(source.get(), result);
+
+      // If it's a directory, keep trying until a file is successfully opened.
+      // When there are no more files, ACCESS_RESULT_DOESNT_EXIST will be
+      // returned and the loop will exit above.
+    } while (result != ACCESS_RESULT_SUCCESS && !source->directory.empty());
+
+    // If the set of known files is empty, clear the object so the next run
+    // will do a fresh scan of the directory.
+    if (source->found_files && source->found_files->empty())
+      source->found_files.reset();
+  }
+}
+
+// This method has all state information passed in |source| and is intended
+// to run on a worker thread rather than the UI thread.
+// static
+FileMetricsProvider::AccessResult FileMetricsProvider::CheckAndMapMetricSource(
+    SourceInfo* source) {
+  // If source was read, clean up after it.
+  if (source->read_complete)
+    FinishedWithSource(source, ACCESS_RESULT_SUCCESS);
+  source->read_complete = false;
+  DCHECK(!source->allocator);
+
+  // If the source is a directory, look for files within it.
+  if (!source->directory.empty() && !LocateNextFileInDirectory(source))
+    return ACCESS_RESULT_DOESNT_EXIST;
+
+  // Do basic validation on the file metadata.
+  base::File::Info info;
+  if (!base::GetFileInfo(source->path, &info))
+    return ACCESS_RESULT_DOESNT_EXIST;
+
+  if (info.is_directory || info.size == 0)
+    return ACCESS_RESULT_INVALID_FILE;
+
+  if (source->last_seen >= info.last_modified)
+    return ACCESS_RESULT_NOT_MODIFIED;
+  if (source->max_age != base::TimeDelta() &&
+      base::Time::Now() - info.last_modified > source->max_age) {
+    return ACCESS_RESULT_TOO_OLD;
+  }
+
+  // Non-directory files still need to be filtered.
+  if (source->directory.empty()) {
+    AccessResult result = HandleFilterSource(source, source->path);
+    if (result != ACCESS_RESULT_SUCCESS)
+      return result;
+  }
+
+  // A new file of metrics has been found.
+  base::File file(source->path, kSourceOptions[source->type].file_open_flags);
+  if (!file.IsValid())
+    return ACCESS_RESULT_NO_OPEN;
+
+  std::unique_ptr<base::MemoryMappedFile> mapped(new base::MemoryMappedFile());
+  if (!mapped->Initialize(std::move(file),
+                          kSourceOptions[source->type].memory_mapped_access)) {
+    return ACCESS_RESULT_SYSTEM_MAP_FAILURE;
+  }
+
+  // Ensure any problems below don't occur repeatedly.
+  source->last_seen = info.last_modified;
+
+  // Test the validity of the file contents.
+  const bool read_only = kSourceOptions[source->type].is_read_only;
+  if (!base::FilePersistentMemoryAllocator::IsFileAcceptable(*mapped,
+                                                             read_only)) {
+    return ACCESS_RESULT_INVALID_CONTENTS;
+  }
+
+  // Map the file and validate it.
+  std::unique_ptr<base::PersistentMemoryAllocator> memory_allocator =
+      std::make_unique<base::FilePersistentMemoryAllocator>(
+          std::move(mapped), 0, 0, base::StringPiece(), read_only);
+  if (memory_allocator->GetMemoryState() ==
+      base::PersistentMemoryAllocator::MEMORY_DELETED) {
+    return ACCESS_RESULT_MEMORY_DELETED;
+  }
+  if (memory_allocator->IsCorrupt())
+    return ACCESS_RESULT_DATA_CORRUPTION;
+
+  // Create an allocator for the mapped file. Ownership passes to the allocator.
+  source->allocator = std::make_unique<base::PersistentHistogramAllocator>(
+      std::move(memory_allocator));
+
+  // Check that an "independent" file has the necessary information present.
+  if (source->association == ASSOCIATE_INTERNAL_PROFILE &&
+      !PersistentSystemProfile::GetSystemProfile(
+          *source->allocator->memory_allocator(), nullptr)) {
+    return ACCESS_RESULT_NO_PROFILE;
+  }
+
+  return ACCESS_RESULT_SUCCESS;
+}
+
+// static
+void FileMetricsProvider::MergeHistogramDeltasFromSource(SourceInfo* source) {
+  DCHECK(source->allocator);
+  SCOPED_UMA_HISTOGRAM_TIMER("UMA.FileMetricsProvider.SnapshotTime.File");
+  base::PersistentHistogramAllocator::Iterator histogram_iter(
+      source->allocator.get());
+
+  const bool read_only = kSourceOptions[source->type].is_read_only;
+  int histogram_count = 0;
+  while (true) {
+    std::unique_ptr<base::HistogramBase> histogram = histogram_iter.GetNext();
+    if (!histogram)
+      break;
+    if (read_only) {
+      source->allocator->MergeHistogramFinalDeltaToStatisticsRecorder(
+          histogram.get());
+    } else {
+      source->allocator->MergeHistogramDeltaToStatisticsRecorder(
+          histogram.get());
+    }
+    ++histogram_count;
+  }
+
+  source->read_complete = true;
+  DVLOG(1) << "Reported " << histogram_count << " histograms from "
+           << source->path.value();
+}
+
+// static
+void FileMetricsProvider::RecordHistogramSnapshotsFromSource(
+    base::HistogramSnapshotManager* snapshot_manager,
+    SourceInfo* source) {
+  DCHECK_NE(SOURCE_HISTOGRAMS_ACTIVE_FILE, source->type);
+
+  base::PersistentHistogramAllocator::Iterator histogram_iter(
+      source->allocator.get());
+
+  int histogram_count = 0;
+  while (true) {
+    std::unique_ptr<base::HistogramBase> histogram = histogram_iter.GetNext();
+    if (!histogram)
+      break;
+    snapshot_manager->PrepareFinalDelta(histogram.get());
+    ++histogram_count;
+  }
+
+  source->read_complete = true;
+  DVLOG(1) << "Reported " << histogram_count << " histograms from "
+           << source->path.value();
+}
+
+FileMetricsProvider::AccessResult FileMetricsProvider::HandleFilterSource(
+    SourceInfo* source,
+    const base::FilePath& path) {
+  if (!source->filter)
+    return ACCESS_RESULT_SUCCESS;
+
+  // Alternatively, pass a Params object to the filter like what was originally
+  // used to configure the source.
+  // Params params(path, source->type, source->association, source->prefs_key);
+  FilterAction action = source->filter.Run(path);
+  switch (action) {
+    case FILTER_PROCESS_FILE:
+      // Process the file.
+      return ACCESS_RESULT_SUCCESS;
+
+    case FILTER_ACTIVE_THIS_PID:
+    // Even the file for the current process has to be touched or its stamp
+    // will be less than "last processed" and thus skipped on future runs,
+    // even those done by new instances of the browser if a pref key is
+    // provided so that the last-uploaded stamp is recorded.
+    case FILTER_TRY_LATER: {
+      // Touch the file with the current timestamp making it (presumably) the
+      // newest file in the directory.
+      base::Time now = base::Time::Now();
+      base::TouchFile(path, /*accessed=*/now, /*modified=*/now);
+      if (action == FILTER_ACTIVE_THIS_PID)
+        return ACCESS_RESULT_THIS_PID;
+      return ACCESS_RESULT_FILTER_TRY_LATER;
+    }
+
+    case FILTER_SKIP_FILE:
+      switch (source->type) {
+        case SOURCE_HISTOGRAMS_ATOMIC_FILE:
+        case SOURCE_HISTOGRAMS_ATOMIC_DIR:
+          // Only "atomic" files are deleted (best-effort).
+          DeleteFileWhenPossible(path);
+          break;
+        case SOURCE_HISTOGRAMS_ACTIVE_FILE:
+          // File will presumably get modified elsewhere and thus tried again.
+          break;
+      }
+      return ACCESS_RESULT_FILTER_SKIP_FILE;
+  }
+
+  // Code never gets here but some compilers don't realize that and so complain
+  // that "not all control paths return a value".
+  NOTREACHED();
+  return ACCESS_RESULT_SUCCESS;
+}
+
+void FileMetricsProvider::ScheduleSourcesCheck() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (sources_to_check_.empty())
+    return;
+
+  // Create an independent list of sources for checking. This will be Owned()
+  // by the reply call given to the task-runner, to be deleted when that call
+  // has returned. It is also passed Unretained() to the task itself, safe
+  // because that must complete before the reply runs.
+  SourceInfoList* check_list = new SourceInfoList();
+  std::swap(sources_to_check_, *check_list);
+  task_runner_->PostTaskAndReply(
+      FROM_HERE,
+      base::BindOnce(
+          &FileMetricsProvider::CheckAndMergeMetricSourcesOnTaskRunner,
+          base::Unretained(check_list)),
+      base::BindOnce(&FileMetricsProvider::RecordSourcesChecked,
+                     weak_factory_.GetWeakPtr(), base::Owned(check_list)));
+}
+
+void FileMetricsProvider::RecordSourcesChecked(SourceInfoList* checked) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Sources that still have an allocator at this point are read/write "active"
+  // files that may need their contents merged on-demand. If there is no
+  // allocator (not a read/write file) but a read was done on the task-runner,
+  // try again immediately to see if more is available (in a directory of
+  // files). Otherwise, remember the source for checking again at a later time.
+  bool did_read = false;
+  for (auto iter = checked->begin(); iter != checked->end();) {
+    auto temp = iter++;
+    SourceInfo* source = temp->get();
+    if (source->read_complete) {
+      RecordSourceAsRead(source);
+      did_read = true;
+    }
+    if (source->allocator) {
+      if (source->association == ASSOCIATE_INTERNAL_PROFILE) {
+        sources_with_profile_.splice(sources_with_profile_.end(), *checked,
+                                     temp);
+      } else {
+        sources_mapped_.splice(sources_mapped_.end(), *checked, temp);
+      }
+    } else {
+      sources_to_check_.splice(sources_to_check_.end(), *checked, temp);
+    }
+  }
+
+  // If a read was done, schedule another one immediately. In the case of a
+  // directory of files, this ensures that all entries get processed. It's
+  // done here instead of as a loop in CheckAndMergeMetricSourcesOnTaskRunner
+  // so that (a) it gives the disk a rest and (b) testing of individual reads
+  // is possible.
+  if (did_read)
+    ScheduleSourcesCheck();
+}
+
+void FileMetricsProvider::DeleteFileAsync(const base::FilePath& path) {
+  task_runner_->PostTask(FROM_HERE,
+                         base::BindOnce(DeleteFileWhenPossible, path));
+}
+
+void FileMetricsProvider::RecordSourceAsRead(SourceInfo* source) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Persistently record the "last seen" timestamp of the source file to
+  // ensure that the file is never read again unless it is modified again.
+  if (pref_service_ && !source->prefs_key.empty()) {
+    pref_service_->SetInt64(
+        metrics::prefs::kMetricsLastSeenPrefix + source->prefs_key,
+        source->last_seen.ToInternalValue());
+  }
+}
+
+void FileMetricsProvider::OnDidCreateMetricsLog() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Schedule a check to see if there are new metrics to load. If so, they will
+  // be reported during the next collection run after this one. The check is run
+  // off of a MayBlock() TaskRunner so as to not cause delays on the main UI
+  // thread (which is currently where metric collection is done).
+  ScheduleSourcesCheck();
+
+  // Clear any data for initial metrics since they're always reported
+  // before the first call to this method. It couldn't be released after
+  // being reported in RecordInitialHistogramSnapshots because the data
+  // will continue to be used by the caller after that method returns. Once
+  // here, though, all actions to be done on the data have been completed.
+  for (const std::unique_ptr<SourceInfo>& source : sources_for_previous_run_)
+    DeleteFileAsync(source->path);
+  sources_for_previous_run_.clear();
+}
+
+bool FileMetricsProvider::ProvideIndependentMetrics(
+    SystemProfileProto* system_profile_proto,
+    base::HistogramSnapshotManager* snapshot_manager) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  while (!sources_with_profile_.empty()) {
+    SourceInfo* source = sources_with_profile_.begin()->get();
+    DCHECK(source->allocator);
+
+    bool success = false;
+    RecordEmbeddedProfileResult(EMBEDDED_PROFILE_ATTEMPT);
+    if (PersistentSystemProfile::GetSystemProfile(
+            *source->allocator->memory_allocator(), system_profile_proto)) {
+      RecordHistogramSnapshotsFromSource(snapshot_manager, source);
+      success = true;
+      RecordEmbeddedProfileResult(EMBEDDED_PROFILE_FOUND);
+    } else {
+      RecordEmbeddedProfileResult(EMBEDDED_PROFILE_DROPPED);
+
+      // TODO(bcwhite): Remove these once crbug/695880 is resolved.
+
+      int histogram_count = 0;
+      base::PersistentHistogramAllocator::Iterator histogram_iter(
+          source->allocator.get());
+      while (histogram_iter.GetNext()) {
+        ++histogram_count;
+      }
+      UMA_HISTOGRAM_COUNTS_10000(
+          "UMA.FileMetricsProvider.EmbeddedProfile.DroppedHistogramCount",
+          histogram_count);
+    }
+
+    // Regardless of whether this source was successfully recorded, it is never
+    // read again.
+    source->read_complete = true;
+    RecordSourceAsRead(source);
+    sources_to_check_.splice(sources_to_check_.end(), sources_with_profile_,
+                             sources_with_profile_.begin());
+    ScheduleSourcesCheck();
+
+    if (success)
+      return true;
+  }
+
+  return false;
+}
+
+bool FileMetricsProvider::HasPreviousSessionData() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Check all sources for previous run to see if they need to be read.
+  for (auto iter = sources_for_previous_run_.begin();
+       iter != sources_for_previous_run_.end();) {
+    SCOPED_UMA_HISTOGRAM_TIMER("UMA.FileMetricsProvider.InitialCheckTime.File");
+
+    auto temp = iter++;
+    SourceInfo* source = temp->get();
+
+    // This would normally be done on a background I/O thread but there
+    // hasn't been a chance to run any at the time this method is called.
+    // Do the check in-line.
+    AccessResult result = CheckAndMapMetricSource(source);
+    UMA_HISTOGRAM_ENUMERATION("UMA.FileMetricsProvider.InitialAccessResult",
+                              result, ACCESS_RESULT_MAX);
+
+    // If it couldn't be accessed, remove it from the list. There is only ever
+    // one chance to record it so no point keeping it around for later. Also
+    // mark it as having been read since uploading it with a future browser
+    // run would associate it with the then-previous run which would no longer
+    // be the run from which it came.
+    if (result != ACCESS_RESULT_SUCCESS) {
+      DCHECK(!source->allocator);
+      RecordSourceAsRead(source);
+      DeleteFileAsync(source->path);
+      sources_for_previous_run_.erase(temp);
+      continue;
+    }
+
+    DCHECK(source->allocator);
+
+    // If the source should be associated with an existing internal profile,
+    // move it to |sources_with_profile_| for later upload.
+    if (source->association == ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN) {
+      if (PersistentSystemProfile::HasSystemProfile(
+              *source->allocator->memory_allocator())) {
+        RecordEmbeddedProfileResult(EMBEDDED_PROFILE_ATTEMPT);
+        RecordEmbeddedProfileResult(EMBEDDED_PROFILE_FALLBACK);
+        sources_with_profile_.splice(sources_with_profile_.end(),
+                                     sources_for_previous_run_, temp);
+      }
+    }
+  }
+
+  return !sources_for_previous_run_.empty();
+}
+
+void FileMetricsProvider::RecordInitialHistogramSnapshots(
+    base::HistogramSnapshotManager* snapshot_manager) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  for (const std::unique_ptr<SourceInfo>& source : sources_for_previous_run_) {
+    SCOPED_UMA_HISTOGRAM_TIMER(
+        "UMA.FileMetricsProvider.InitialSnapshotTime.File");
+
+    // The source needs to have an allocator attached to it in order to read
+    // histograms out of it.
+    DCHECK(!source->read_complete);
+    DCHECK(source->allocator);
+
+    // Dump all histograms contained within the source to the snapshot-manager.
+    RecordHistogramSnapshotsFromSource(snapshot_manager, source.get());
+
+    // Update the last-seen time so it isn't read again unless it changes.
+    RecordSourceAsRead(source.get());
+  }
+}
+
+void FileMetricsProvider::MergeHistogramDeltas() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  for (std::unique_ptr<SourceInfo>& source : sources_mapped_) {
+    MergeHistogramDeltasFromSource(source.get());
+  }
+}
+
+}  // namespace metrics
diff --git a/components/metrics/file_metrics_provider.h b/components/metrics/file_metrics_provider.h
new file mode 100644
index 0000000..7465d93
--- /dev/null
+++ b/components/metrics/file_metrics_provider.h
@@ -0,0 +1,323 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_
+
+#include <list>
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_provider.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace base {
+class TaskRunner;
+}
+
+namespace metrics {
+
+// FileMetricsProvider gathers and logs histograms written to files on disk.
+// Any number of files can be registered and will be polled once per upload
+// cycle (at startup and periodically thereafter -- about every 30 minutes
+// for desktop) for data to send.
+class FileMetricsProvider : public MetricsProvider,
+                            public base::StatisticsRecorder::HistogramProvider {
+ public:
+  struct Params;
+
+  enum SourceType {
+    // "Atomic" files are a collection of histograms that are written
+    // completely in a single atomic operation (typically a write followed
+    // by an atomic rename) and the file is never updated again except to
+    // be replaced by a completely new set of histograms. This is the only
+    // option that can be used if the file is not writeable by *this*
+    // process. Once the file has been read, an attempt will be made to
+    // delete it thus providing some measure of safety should different
+    // instantiations (such as by different users of a system-level install)
+    // try to read it. In case the delete operation fails, this class
+    // persistently tracks the last-modified time of the file so it will
+    // not be read a second time.
+    SOURCE_HISTOGRAMS_ATOMIC_FILE,
+
+    // A directory of atomic PMA files. This handles a directory in which
+    // files of metrics are atomically added. Only files ending with ".pma"
+    // will be read. They are read according to their last-modified time and
+    // never read more that once (unless they change). Only one file will
+    // be read per reporting cycle. Filenames that start with a dot (.) or
+    // an underscore (_) are ignored so temporary files (perhaps created by
+    // the ImportantFileWriter) will not get read. Files that have been
+    // read will be attempted to be deleted; should those files not be
+    // deletable by this process, it is the reponsibility of the producer
+    // to keep the directory pruned in some manner. Added files must have a
+    // timestamp later (not the same or earlier) than the newest file that
+    // already exists or it may be assumed to have been already uploaded.
+    SOURCE_HISTOGRAMS_ATOMIC_DIR,
+
+    // "Active" files may be open by one or more other processes and updated
+    // at any time with new samples or new histograms. Such files may also be
+    // inactive for any period of time only to be opened again and have new
+    // data written to them. The file should probably never be deleted because
+    // there would be no guarantee that the data has been reported.
+    // TODO(bcwhite): Enable when read/write mem-mapped files are supported.
+    SOURCE_HISTOGRAMS_ACTIVE_FILE,
+  };
+
+  enum SourceAssociation {
+    // Associates the metrics in the file with the current run of the browser.
+    // The reporting will take place as part of the normal logging of
+    // histograms.
+    ASSOCIATE_CURRENT_RUN,
+
+    // Associates the metrics in the file with the previous run of the browesr.
+    // The reporting will take place as part of the "stability" histograms.
+    // This is important when metrics are dumped as part of a crash of the
+    // previous run. This can only be used with FILE_HISTOGRAMS_ATOMIC.
+    ASSOCIATE_PREVIOUS_RUN,
+
+    // Associates the metrics in the file with the a profile embedded in the
+    // same file. The reporting will take place at a convenient time after
+    // startup when the browser is otherwise idle. If there is no embedded
+    // system profile, these metrics will be lost.
+    ASSOCIATE_INTERNAL_PROFILE,
+
+    // Like above but fall back to ASSOCIATE_PREVIOUS_RUN if there is no
+    // embedded profile. This has a small cost during startup as that is
+    // when previous-run metrics are sent so the file has be checked at
+    // that time even though actual transfer will be delayed if an
+    // embedded profile is found.
+    ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN,
+  };
+
+  enum FilterAction {
+    // Process this file normally.
+    FILTER_PROCESS_FILE,
+
+    // This file is the active metrics file for the current process.  Don't
+    // do anything with it. This is effectively "try later" but isn't
+    // added to the results histogram because the file has to be ignored
+    // throughout the life of the browser and that skews the distribution.
+    FILTER_ACTIVE_THIS_PID,
+
+    // Try again. This could happen within milliseconds or minutes but no other
+    // files from the same source will get processed in between. The process
+    // must have permission to "touch" the file and alter its last-modified
+    // time because files are always processed in order of those stamps.
+    FILTER_TRY_LATER,
+
+    // Skip this file. This file will not be processed until it has changed
+    // (i.e. had its last-modifided time updated). If it is "atomic", an
+    // attempt will be made to delete it.
+    FILTER_SKIP_FILE,
+  };
+
+  // A "filter" can be defined to determine what to do on a per-file basis.
+  // This is called only after a file has been found to be the next one to
+  // be processed so it's okay if filter calls are relatively expensive.
+  // Calls are made on a background thread of low-priority and capable of
+  // doing I/O.
+  using FilterCallback =
+      base::RepeatingCallback<FilterAction(const base::FilePath& path)>;
+
+  // Parameters for RegisterSource, defined as a structure to allow new
+  // ones to be added (with default values) that doesn't require changes
+  // to all call sites.
+  struct Params {
+    Params(const base::FilePath& path,
+           SourceType type,
+           SourceAssociation association,
+           base::StringPiece prefs_key = base::StringPiece());
+
+    ~Params();
+
+    // The standard parameters, set during construction.
+    const base::FilePath path;
+    const SourceType type;
+    const SourceAssociation association;
+    const base::StringPiece prefs_key;
+
+    // Other parameters that can be set after construction.
+    FilterCallback filter;       // Run-time check for what to do with file.
+    base::TimeDelta max_age;     // Maximum age of a file (0=unlimited).
+    size_t max_dir_kib = 0;      // Maximum bytes in a directory (0=inf).
+    size_t max_dir_files = 100;  // Maximum files in a directory (0=inf).
+  };
+
+  explicit FileMetricsProvider(PrefService* local_state);
+  ~FileMetricsProvider() override;
+
+  // Indicates a file or directory to be monitored and how the file or files
+  // within that directory are used. Because some metadata may need to persist
+  // across process restarts, preferences entries are used based on the
+  // |prefs_key| name. Call RegisterPrefs() with the same name to create the
+  // necessary keys in advance. Set |prefs_key| empty (nullptr will work) if
+  // no persistence is required. ACTIVE files shouldn't have a pref key as
+  // they update internal state about what has been previously sent.
+  void RegisterSource(const Params& params);
+
+  // Registers all necessary preferences for maintaining persistent state
+  // about a monitored file across process restarts. The |prefs_key| is
+  // typically the filename.
+  static void RegisterPrefs(PrefRegistrySimple* prefs,
+                            const base::StringPiece prefs_key);
+
+  // Sets the task runner to use for testing.
+  static void SetTaskRunnerForTesting(
+      const scoped_refptr<base::TaskRunner>& task_runner);
+
+ private:
+  friend class FileMetricsProviderTest;
+
+  // The different results that can occur accessing a file.
+  enum AccessResult {
+    // File was successfully mapped.
+    ACCESS_RESULT_SUCCESS,
+
+    // File does not exist.
+    ACCESS_RESULT_DOESNT_EXIST,
+
+    // File exists but not modified since last read.
+    ACCESS_RESULT_NOT_MODIFIED,
+
+    // File is not valid: is a directory or zero-size.
+    ACCESS_RESULT_INVALID_FILE,
+
+    // System could not map file into memory.
+    ACCESS_RESULT_SYSTEM_MAP_FAILURE,
+
+    // File had invalid contents.
+    ACCESS_RESULT_INVALID_CONTENTS,
+
+    // File could not be opened.
+    ACCESS_RESULT_NO_OPEN,
+
+    // File contents were internally deleted.
+    ACCESS_RESULT_MEMORY_DELETED,
+
+    // File is scheduled to be tried again later.
+    ACCESS_RESULT_FILTER_TRY_LATER,
+
+    // File was skipped according to filtering rules.
+    ACCESS_RESULT_FILTER_SKIP_FILE,
+
+    // File was skipped because it exceeds the maximum age.
+    ACCESS_RESULT_TOO_OLD,
+
+    // File was skipped because too many files in directory.
+    ACCESS_RESULT_TOO_MANY_FILES,
+
+    // File was skipped because too many bytes in directory.
+    ACCESS_RESULT_TOO_MANY_BYTES,
+
+    // The file was skipped because it's being written by this process.
+    ACCESS_RESULT_THIS_PID,
+
+    // The file had no embedded system profile.
+    ACCESS_RESULT_NO_PROFILE,
+
+    // The file had internal data corruption.
+    ACCESS_RESULT_DATA_CORRUPTION,
+
+    ACCESS_RESULT_MAX
+  };
+
+  // Information about sources being monitored; defined and used exclusively
+  // inside the .cc file.
+  struct SourceInfo;
+  using SourceInfoList = std::list<std::unique_ptr<SourceInfo>>;
+
+  // Records an access result in a histogram.
+  static void RecordAccessResult(AccessResult result);
+
+  // Looks for the next file to read within a directory. Returns true if a
+  // file was found. This is part of CheckAndMapNewMetricSourcesOnTaskRunner
+  // and so runs on an thread capable of I/O. The |source| structure will
+  // be internally updated to indicate the next file to be read.
+  static bool LocateNextFileInDirectory(SourceInfo* source);
+
+  // Handles the completion of a source.
+  static void FinishedWithSource(SourceInfo* source, AccessResult result);
+
+  // Checks a list of sources (on a task-runner allowed to do I/O) and merge
+  // any data found within them.
+  static void CheckAndMergeMetricSourcesOnTaskRunner(SourceInfoList* sources);
+
+  // Checks a single source and maps it into memory.
+  static AccessResult CheckAndMapMetricSource(SourceInfo* source);
+
+  // Merges all of the histograms from a |source| to the StatisticsRecorder.
+  static void MergeHistogramDeltasFromSource(SourceInfo* source);
+
+  // Records all histograms from a given source via a snapshot-manager.
+  static void RecordHistogramSnapshotsFromSource(
+      base::HistogramSnapshotManager* snapshot_manager,
+      SourceInfo* source);
+
+  // Calls source filter (if any) and returns the desired action.
+  static AccessResult HandleFilterSource(SourceInfo* source,
+                                         const base::FilePath& path);
+
+  // Creates a task to check all monitored sources for updates.
+  void ScheduleSourcesCheck();
+
+  // Takes a list of sources checked by an external task and determines what
+  // to do with each.
+  void RecordSourcesChecked(SourceInfoList* checked);
+
+  // Schedules the deletion of a file in the background using the task-runner.
+  void DeleteFileAsync(const base::FilePath& path);
+
+  // Updates the persistent state information to show a source as being read.
+  void RecordSourceAsRead(SourceInfo* source);
+
+  // metrics::MetricsProvider:
+  void OnDidCreateMetricsLog() override;
+  bool ProvideIndependentMetrics(
+      SystemProfileProto* system_profile_proto,
+      base::HistogramSnapshotManager* snapshot_manager) override;
+  bool HasPreviousSessionData() override;
+  void RecordInitialHistogramSnapshots(
+      base::HistogramSnapshotManager* snapshot_manager) override;
+
+  // base::StatisticsRecorder::HistogramProvider:
+  void MergeHistogramDeltas() override;
+
+  // A task-runner capable of performing I/O.
+  scoped_refptr<base::TaskRunner> task_runner_;
+
+  // A list of sources not currently active that need to be checked for changes.
+  SourceInfoList sources_to_check_;
+
+  // A list of currently active sources to be merged when required.
+  SourceInfoList sources_mapped_;
+
+  // A list of currently active sources to be merged when required.
+  SourceInfoList sources_with_profile_;
+
+  // A list of sources for a previous run. These are held separately because
+  // they are not subject to the periodic background checking that handles
+  // metrics for the current run.
+  SourceInfoList sources_for_previous_run_;
+
+  // The preferences-service used to store persistent state about sources.
+  PrefService* pref_service_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  base::WeakPtrFactory<FileMetricsProvider> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileMetricsProvider);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_FILE_METRICS_PROVIDER_H_
diff --git a/components/metrics/file_metrics_provider_unittest.cc b/components/metrics/file_metrics_provider_unittest.cc
new file mode 100644
index 0000000..ca5ca9a
--- /dev/null
+++ b/components/metrics/file_metrics_provider_unittest.cc
@@ -0,0 +1,1069 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/file_metrics_provider.h"
+
+#include <functional>
+
+#include "base/files/file_util.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_flattener.h"
+#include "base/metrics/histogram_snapshot_manager.h"
+#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/persistent_memory_allocator.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/persistent_system_profile.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace {
+const char kMetricsName[] = "TestMetrics";
+const char kMetricsFilename[] = "file.metrics";
+}  // namespace
+
+namespace metrics {
+
+class HistogramFlattenerDeltaRecorder : public base::HistogramFlattener {
+ public:
+  HistogramFlattenerDeltaRecorder() {}
+
+  void RecordDelta(const base::HistogramBase& histogram,
+                   const base::HistogramSamples& snapshot) override {
+    // Only remember locally created histograms; they have exactly 2 chars.
+    if (strlen(histogram.histogram_name()) == 2)
+      recorded_delta_histogram_names_.push_back(histogram.histogram_name());
+  }
+
+  std::vector<std::string> GetRecordedDeltaHistogramNames() {
+    return recorded_delta_histogram_names_;
+  }
+
+ private:
+  std::vector<std::string> recorded_delta_histogram_names_;
+
+  DISALLOW_COPY_AND_ASSIGN(HistogramFlattenerDeltaRecorder);
+};
+
+
+class FileMetricsProviderTest : public testing::TestWithParam<bool> {
+ protected:
+  const size_t kSmallFileSize = 64 << 10;  // 64 KiB
+  const size_t kLargeFileSize =  2 << 20;  //  2 MiB
+
+  enum : int { kMaxCreateHistograms = 10 };
+
+  FileMetricsProviderTest()
+      : create_large_files_(GetParam()),
+        task_runner_(new base::TestSimpleTaskRunner()),
+        thread_task_runner_handle_(task_runner_),
+        statistics_recorder_(
+            base::StatisticsRecorder::CreateTemporaryForTesting()),
+        prefs_(new TestingPrefServiceSimple) {
+    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+    FileMetricsProvider::RegisterPrefs(prefs_->registry(), kMetricsName);
+    FileMetricsProvider::SetTaskRunnerForTesting(task_runner_);
+  }
+
+  ~FileMetricsProviderTest() override {
+    // Clear out any final remaining tasks.
+    task_runner_->RunUntilIdle();
+    FileMetricsProvider::SetTaskRunnerForTesting(nullptr);
+    DCHECK_EQ(0U, filter_actions_remaining_);
+    // If a global histogram allocator exists at this point then it likely
+    // acquired histograms that will continue to point to the released
+    // memory and potentially cause use-after-free memory corruption.
+    DCHECK(!base::GlobalHistogramAllocator::Get());
+  }
+
+  TestingPrefServiceSimple* prefs() { return prefs_.get(); }
+  base::FilePath temp_dir() { return temp_dir_.GetPath(); }
+  base::FilePath metrics_file() {
+    return temp_dir_.GetPath().AppendASCII(kMetricsFilename);
+  }
+
+  FileMetricsProvider* provider() {
+    if (!provider_)
+      provider_.reset(new FileMetricsProvider(prefs()));
+    return provider_.get();
+  }
+
+  void OnDidCreateMetricsLog() {
+    provider()->OnDidCreateMetricsLog();
+  }
+
+  bool HasPreviousSessionData() { return provider()->HasPreviousSessionData(); }
+
+  void MergeHistogramDeltas() {
+    provider()->MergeHistogramDeltas();
+  }
+
+  bool ProvideIndependentMetrics(
+      SystemProfileProto* profile_proto,
+      base::HistogramSnapshotManager* snapshot_manager) {
+    return provider()->ProvideIndependentMetrics(profile_proto,
+                                                 snapshot_manager);
+  }
+
+  void RecordInitialHistogramSnapshots(
+      base::HistogramSnapshotManager* snapshot_manager) {
+    provider()->RecordInitialHistogramSnapshots(snapshot_manager);
+  }
+
+  size_t GetSnapshotHistogramCount() {
+    // Merge the data from the allocator into the StatisticsRecorder.
+    provider()->MergeHistogramDeltas();
+
+    // Flatten what is known to see what has changed since the last time.
+    HistogramFlattenerDeltaRecorder flattener;
+    base::HistogramSnapshotManager snapshot_manager(&flattener);
+    // "true" to the begin() includes histograms held in persistent storage.
+    base::StatisticsRecorder::PrepareDeltas(true, base::Histogram::kNoFlags,
+                                            base::Histogram::kNoFlags,
+                                            &snapshot_manager);
+    return flattener.GetRecordedDeltaHistogramNames().size();
+  }
+
+  size_t GetIndependentHistogramCount() {
+    HistogramFlattenerDeltaRecorder flattener;
+    base::HistogramSnapshotManager snapshot_manager(&flattener);
+    SystemProfileProto profile_proto;
+    if (!provider()->ProvideIndependentMetrics(&profile_proto,
+                                               &snapshot_manager)) {
+      return 0;
+    }
+    return flattener.GetRecordedDeltaHistogramNames().size();
+  }
+
+  void CreateGlobalHistograms(int histogram_count) {
+    DCHECK_GT(kMaxCreateHistograms, histogram_count);
+
+    // Create both sparse and normal histograms in the allocator.
+    created_histograms_[0] = base::SparseHistogram::FactoryGet("h0", 0);
+    created_histograms_[0]->Add(0);
+    for (int i = 1; i < histogram_count; ++i) {
+      created_histograms_[i] = base::Histogram::FactoryGet(
+          base::StringPrintf("h%d", i), 1, 100, 10, 0);
+      created_histograms_[i]->Add(i);
+    }
+  }
+
+  void RunTasks() {
+    // Run pending tasks twice: Once for IPC calls, once for replies. Don't
+    // use RunUntilIdle() because that can do more work than desired.
+    task_runner_->RunPendingTasks();
+    task_runner_->RunPendingTasks();
+  }
+
+  void WriteMetricsFile(const base::FilePath& path,
+                        base::PersistentHistogramAllocator* metrics) {
+    base::File writer(path,
+                      base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+    // Use DCHECK so the stack-trace will indicate where this was called.
+    DCHECK(writer.IsValid()) << path.value();
+    size_t file_size = create_large_files_ ? metrics->size() : metrics->used();
+    int written = writer.Write(0, (const char*)metrics->data(), file_size);
+    DCHECK_EQ(static_cast<int>(file_size), written);
+  }
+
+  void WriteMetricsFileAtTime(const base::FilePath& path,
+                              base::PersistentHistogramAllocator* metrics,
+                              base::Time write_time) {
+    WriteMetricsFile(path, metrics);
+    base::TouchFile(path, write_time, write_time);
+  }
+
+  std::unique_ptr<base::PersistentHistogramAllocator>
+  CreateMetricsFileWithHistograms(
+      const base::FilePath& file_path,
+      base::Time write_time,
+      int histogram_count,
+      const std::function<void(base::PersistentHistogramAllocator*)>&
+          callback) {
+    base::GlobalHistogramAllocator::CreateWithLocalMemory(
+        create_large_files_ ? kLargeFileSize : kSmallFileSize,
+        0, kMetricsName);
+
+    CreateGlobalHistograms(histogram_count);
+
+    std::unique_ptr<base::PersistentHistogramAllocator> histogram_allocator =
+        base::GlobalHistogramAllocator::ReleaseForTesting();
+    callback(histogram_allocator.get());
+
+    WriteMetricsFileAtTime(file_path, histogram_allocator.get(), write_time);
+    return histogram_allocator;
+  }
+
+  std::unique_ptr<base::PersistentHistogramAllocator>
+  CreateMetricsFileWithHistograms(int histogram_count) {
+    return CreateMetricsFileWithHistograms(
+        metrics_file(), base::Time::Now(), histogram_count,
+        [](base::PersistentHistogramAllocator* allocator) {});
+  }
+
+  base::HistogramBase* GetCreatedHistogram(int index) {
+    DCHECK_GT(kMaxCreateHistograms, index);
+    return created_histograms_[index];
+  }
+
+  void SetFilterActions(FileMetricsProvider::Params* params,
+                        const FileMetricsProvider::FilterAction* actions,
+                        size_t count) {
+    filter_actions_ = actions;
+    filter_actions_remaining_ = count;
+    params->filter = base::Bind(&FileMetricsProviderTest::FilterSourcePath,
+                                base::Unretained(this));
+  }
+
+  const bool create_large_files_;
+
+ private:
+  FileMetricsProvider::FilterAction FilterSourcePath(
+      const base::FilePath& path) {
+    DCHECK_LT(0U, filter_actions_remaining_);
+    --filter_actions_remaining_;
+    return *filter_actions_++;
+  }
+
+  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+  base::ThreadTaskRunnerHandle thread_task_runner_handle_;
+
+  std::unique_ptr<base::StatisticsRecorder> statistics_recorder_;
+  base::ScopedTempDir temp_dir_;
+  std::unique_ptr<TestingPrefServiceSimple> prefs_;
+  std::unique_ptr<FileMetricsProvider> provider_;
+  base::HistogramBase* created_histograms_[kMaxCreateHistograms];
+
+  const FileMetricsProvider::FilterAction* filter_actions_ = nullptr;
+  size_t filter_actions_remaining_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(FileMetricsProviderTest);
+};
+
+// Run all test cases with both small and large files.
+INSTANTIATE_TEST_CASE_P(SmallAndLargeFiles,
+                        FileMetricsProviderTest,
+                        testing::Bool());
+
+
+TEST_P(FileMetricsProviderTest, AccessMetrics) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+
+  base::Time metrics_time = base::Time::Now() - base::TimeDelta::FromMinutes(5);
+  std::unique_ptr<base::PersistentHistogramAllocator> histogram_allocator =
+      CreateMetricsFileWithHistograms(2);
+  ASSERT_TRUE(PathExists(metrics_file()));
+  base::TouchFile(metrics_file(), metrics_time, metrics_time);
+
+  // Register the file and allow the "checker" task to run.
+  provider()->RegisterSource(FileMetricsProvider::Params(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
+      FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName));
+
+  // Record embedded snapshots via snapshot-manager.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(2U, GetSnapshotHistogramCount());
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+
+  // Make sure a second call to the snapshot-recorder doesn't break anything.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(0U, GetSnapshotHistogramCount());
+
+  // File should have been deleted but recreate it to test behavior should
+  // the file not be deleteable by this process.
+  WriteMetricsFileAtTime(metrics_file(), histogram_allocator.get(),
+                         metrics_time);
+
+  // Second full run on the same file should produce nothing.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(0U, GetSnapshotHistogramCount());
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+
+  // Recreate the file to indicate that it is "new" and must be recorded.
+  metrics_time = metrics_time + base::TimeDelta::FromMinutes(1);
+  WriteMetricsFileAtTime(metrics_file(), histogram_allocator.get(),
+                         metrics_time);
+
+  // This run should again have "new" histograms.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(2U, GetSnapshotHistogramCount());
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+}
+
+TEST_P(FileMetricsProviderTest, AccessTimeLimitedFile) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+
+  base::Time metrics_time = base::Time::Now() - base::TimeDelta::FromHours(5);
+  std::unique_ptr<base::PersistentHistogramAllocator> histogram_allocator =
+      CreateMetricsFileWithHistograms(2);
+  ASSERT_TRUE(PathExists(metrics_file()));
+  base::TouchFile(metrics_file(), metrics_time, metrics_time);
+
+  // Register the file and allow the "checker" task to run.
+  FileMetricsProvider::Params params(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
+      FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName);
+  params.max_age = base::TimeDelta::FromHours(1);
+  provider()->RegisterSource(params);
+
+  // Attempt to access the file should return nothing.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(0U, GetSnapshotHistogramCount());
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+}
+
+TEST_P(FileMetricsProviderTest, FilterDelaysFile) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+
+  base::Time now_time = base::Time::Now();
+  base::Time metrics_time = now_time - base::TimeDelta::FromMinutes(5);
+  std::unique_ptr<base::PersistentHistogramAllocator> histogram_allocator =
+      CreateMetricsFileWithHistograms(2);
+  ASSERT_TRUE(PathExists(metrics_file()));
+  base::TouchFile(metrics_file(), metrics_time, metrics_time);
+  base::File::Info fileinfo;
+  ASSERT_TRUE(base::GetFileInfo(metrics_file(), &fileinfo));
+  EXPECT_GT(base::Time::Now(), fileinfo.last_modified);
+
+  // Register the file and allow the "checker" task to run.
+  FileMetricsProvider::Params params(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
+      FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName);
+  const FileMetricsProvider::FilterAction actions[] = {
+      FileMetricsProvider::FILTER_TRY_LATER,
+      FileMetricsProvider::FILTER_PROCESS_FILE};
+  SetFilterActions(&params, actions, arraysize(actions));
+  provider()->RegisterSource(params);
+
+  // Processing the file should touch it but yield no results. File timestamp
+  // accuracy is limited so compare the touched time to a couple seconds past.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(0U, GetSnapshotHistogramCount());
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+  ASSERT_TRUE(base::GetFileInfo(metrics_file(), &fileinfo));
+  EXPECT_LT(metrics_time, fileinfo.last_modified);
+  EXPECT_LE(now_time - base::TimeDelta::FromSeconds(2), fileinfo.last_modified);
+
+  // Second full run on the same file should process the file.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(2U, GetSnapshotHistogramCount());
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+}
+
+TEST_P(FileMetricsProviderTest, FilterSkipsFile) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+
+  base::Time now_time = base::Time::Now();
+  base::Time metrics_time = now_time - base::TimeDelta::FromMinutes(5);
+  std::unique_ptr<base::PersistentHistogramAllocator> histogram_allocator =
+      CreateMetricsFileWithHistograms(2);
+  ASSERT_TRUE(PathExists(metrics_file()));
+  base::TouchFile(metrics_file(), metrics_time, metrics_time);
+  base::File::Info fileinfo;
+  ASSERT_TRUE(base::GetFileInfo(metrics_file(), &fileinfo));
+  EXPECT_GT(base::Time::Now(), fileinfo.last_modified);
+
+  // Register the file and allow the "checker" task to run.
+  FileMetricsProvider::Params params(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
+      FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName);
+  const FileMetricsProvider::FilterAction actions[] = {
+      FileMetricsProvider::FILTER_SKIP_FILE};
+  SetFilterActions(&params, actions, arraysize(actions));
+  provider()->RegisterSource(params);
+
+  // Processing the file should delete it.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(0U, GetSnapshotHistogramCount());
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+}
+
+TEST_P(FileMetricsProviderTest, AccessDirectory) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+
+  base::GlobalHistogramAllocator::CreateWithLocalMemory(
+      64 << 10, 0, kMetricsName);
+  base::GlobalHistogramAllocator* allocator =
+      base::GlobalHistogramAllocator::Get();
+  base::HistogramBase* histogram;
+
+  // Create files starting with a timestamp a few minutes back.
+  base::Time base_time = base::Time::Now() - base::TimeDelta::FromMinutes(10);
+
+  // Create some files in an odd order. The files are "touched" back in time to
+  // ensure that each file has a later timestamp on disk than the previous one.
+  base::ScopedTempDir metrics_files;
+  EXPECT_TRUE(metrics_files.CreateUniqueTempDir());
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII(".foo.pma"),
+                         allocator, base_time);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("_bar.pma"),
+                         allocator, base_time);
+
+  histogram = base::Histogram::FactoryGet("h1", 1, 100, 10, 0);
+  histogram->Add(1);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("a1.pma"),
+                         allocator,
+                         base_time + base::TimeDelta::FromMinutes(1));
+
+  histogram = base::Histogram::FactoryGet("h2", 1, 100, 10, 0);
+  histogram->Add(2);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("c2.pma"),
+                         allocator,
+                         base_time + base::TimeDelta::FromMinutes(2));
+
+  histogram = base::Histogram::FactoryGet("h3", 1, 100, 10, 0);
+  histogram->Add(3);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("b3.pma"),
+                         allocator,
+                         base_time + base::TimeDelta::FromMinutes(3));
+
+  histogram = base::Histogram::FactoryGet("h4", 1, 100, 10, 0);
+  histogram->Add(3);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("d4.pma"),
+                         allocator,
+                         base_time + base::TimeDelta::FromMinutes(4));
+
+  base::TouchFile(metrics_files.GetPath().AppendASCII("b3.pma"),
+                  base_time + base::TimeDelta::FromMinutes(5),
+                  base_time + base::TimeDelta::FromMinutes(5));
+
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("baz"), allocator,
+                         base_time + base::TimeDelta::FromMinutes(6));
+
+  // The global allocator has to be detached here so that no metrics created
+  // by code called below get stored in it as that would make for potential
+  // use-after-free operations if that code is called again.
+  base::GlobalHistogramAllocator::ReleaseForTesting();
+
+  // Register the file and allow the "checker" task to run.
+  provider()->RegisterSource(FileMetricsProvider::Params(
+      metrics_files.GetPath(),
+      FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR,
+      FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName));
+
+  // Files could come out in the order: a1, c2, d4, b3. They are recognizeable
+  // by the number of histograms contained within each.
+  const uint32_t expect_order[] = {1, 2, 4, 3, 0};
+  for (size_t i = 0; i < arraysize(expect_order); ++i) {
+    // Record embedded snapshots via snapshot-manager.
+    OnDidCreateMetricsLog();
+    RunTasks();
+    EXPECT_EQ(expect_order[i], GetSnapshotHistogramCount()) << i;
+  }
+
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("a1.pma")));
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("c2.pma")));
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("b3.pma")));
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("d4.pma")));
+  EXPECT_TRUE(
+      base::PathExists(metrics_files.GetPath().AppendASCII(".foo.pma")));
+  EXPECT_TRUE(
+      base::PathExists(metrics_files.GetPath().AppendASCII("_bar.pma")));
+  EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("baz")));
+}
+
+TEST_P(FileMetricsProviderTest, AccessDirectoryWithInvalidFiles) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+
+  // Create files starting with a timestamp a few minutes back.
+  base::Time base_time = base::Time::Now() - base::TimeDelta::FromMinutes(10);
+
+  base::ScopedTempDir metrics_files;
+  EXPECT_TRUE(metrics_files.CreateUniqueTempDir());
+
+  CreateMetricsFileWithHistograms(
+      metrics_files.GetPath().AppendASCII("h1.pma"),
+      base_time + base::TimeDelta::FromMinutes(1), 1,
+      [](base::PersistentHistogramAllocator* allocator) {
+        allocator->memory_allocator()->SetMemoryState(
+            base::PersistentMemoryAllocator::MEMORY_DELETED);
+      });
+
+  CreateMetricsFileWithHistograms(
+      metrics_files.GetPath().AppendASCII("h2.pma"),
+      base_time + base::TimeDelta::FromMinutes(2), 2,
+      [](base::PersistentHistogramAllocator* allocator) {
+        SystemProfileProto profile_proto;
+        SystemProfileProto::FieldTrial* trial = profile_proto.add_field_trial();
+        trial->set_name_id(123);
+        trial->set_group_id(456);
+
+        PersistentSystemProfile persistent_profile;
+        persistent_profile.RegisterPersistentAllocator(
+            allocator->memory_allocator());
+        persistent_profile.SetSystemProfile(profile_proto, true);
+      });
+
+  CreateMetricsFileWithHistograms(
+      metrics_files.GetPath().AppendASCII("h3.pma"),
+      base_time + base::TimeDelta::FromMinutes(3), 3,
+      [](base::PersistentHistogramAllocator* allocator) {
+        allocator->memory_allocator()->SetMemoryState(
+            base::PersistentMemoryAllocator::MEMORY_DELETED);
+      });
+
+  {
+    base::File empty(metrics_files.GetPath().AppendASCII("h4.pma"),
+                     base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+  }
+  base::TouchFile(metrics_files.GetPath().AppendASCII("h4.pma"),
+                  base_time + base::TimeDelta::FromMinutes(4),
+                  base_time + base::TimeDelta::FromMinutes(4));
+
+  // Register the file and allow the "checker" task to run.
+  provider()->RegisterSource(FileMetricsProvider::Params(
+      metrics_files.GetPath(),
+      FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR,
+      FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE, kMetricsName));
+
+  // No files yet.
+  EXPECT_EQ(0U, GetIndependentHistogramCount());
+  EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h1.pma")));
+  EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h2.pma")));
+  EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h3.pma")));
+  EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h4.pma")));
+
+  // H1 should be skipped and H2 available.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(2U, GetIndependentHistogramCount());
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("h1.pma")));
+  EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h2.pma")));
+  EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h3.pma")));
+  EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h4.pma")));
+
+  // Nothing else should be found but the last (valid but empty) file will
+  // stick around to be processed later (should it get expanded).
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(0U, GetIndependentHistogramCount());
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("h2.pma")));
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("h3.pma")));
+  EXPECT_TRUE(base::PathExists(metrics_files.GetPath().AppendASCII("h4.pma")));
+}
+
+TEST_P(FileMetricsProviderTest, AccessTimeLimitedDirectory) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+
+  base::GlobalHistogramAllocator::CreateWithLocalMemory(64 << 10, 0,
+                                                        kMetricsName);
+  base::GlobalHistogramAllocator* allocator =
+      base::GlobalHistogramAllocator::Get();
+  base::HistogramBase* histogram;
+
+  // Create one old file and one new file.
+  base::ScopedTempDir metrics_files;
+  EXPECT_TRUE(metrics_files.CreateUniqueTempDir());
+  histogram = base::Histogram::FactoryGet("h1", 1, 100, 10, 0);
+  histogram->Add(1);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("a1.pma"),
+                         allocator,
+                         base::Time::Now() - base::TimeDelta::FromHours(1));
+
+  histogram = base::Histogram::FactoryGet("h2", 1, 100, 10, 0);
+  histogram->Add(2);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("b2.pma"),
+                         allocator, base::Time::Now());
+
+  // The global allocator has to be detached here so that no metrics created
+  // by code called below get stored in it as that would make for potential
+  // use-after-free operations if that code is called again.
+  base::GlobalHistogramAllocator::ReleaseForTesting();
+
+  // Register the file and allow the "checker" task to run.
+  FileMetricsProvider::Params params(
+      metrics_files.GetPath(),
+      FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR,
+      FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName);
+  params.max_age = base::TimeDelta::FromMinutes(30);
+  provider()->RegisterSource(params);
+
+  // Only b2, with 2 histograms, should be read.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(2U, GetSnapshotHistogramCount());
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(0U, GetSnapshotHistogramCount());
+
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("a1.pma")));
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("b2.pma")));
+}
+
+TEST_P(FileMetricsProviderTest, AccessCountLimitedDirectory) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+
+  base::GlobalHistogramAllocator::CreateWithLocalMemory(64 << 10, 0,
+                                                        kMetricsName);
+  base::GlobalHistogramAllocator* allocator =
+      base::GlobalHistogramAllocator::Get();
+  base::HistogramBase* histogram;
+
+  // Create one old file and one new file.
+  base::ScopedTempDir metrics_files;
+  EXPECT_TRUE(metrics_files.CreateUniqueTempDir());
+  histogram = base::Histogram::FactoryGet("h1", 1, 100, 10, 0);
+  histogram->Add(1);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("a1.pma"),
+                         allocator,
+                         base::Time::Now() - base::TimeDelta::FromHours(1));
+
+  histogram = base::Histogram::FactoryGet("h2", 1, 100, 10, 0);
+  histogram->Add(2);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("b2.pma"),
+                         allocator, base::Time::Now());
+
+  // The global allocator has to be detached here so that no metrics created
+  // by code called below get stored in it as that would make for potential
+  // use-after-free operations if that code is called again.
+  base::GlobalHistogramAllocator::ReleaseForTesting();
+
+  // Register the file and allow the "checker" task to run.
+  FileMetricsProvider::Params params(
+      metrics_files.GetPath(),
+      FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR,
+      FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName);
+  params.max_dir_files = 1;
+  provider()->RegisterSource(params);
+
+  // Only b2, with 2 histograms, should be read.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(2U, GetSnapshotHistogramCount());
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(0U, GetSnapshotHistogramCount());
+
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("a1.pma")));
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("b2.pma")));
+}
+
+TEST_P(FileMetricsProviderTest, AccessSizeLimitedDirectory) {
+  // This only works with large files that are big enough to count.
+  if (!create_large_files_)
+    return;
+
+  ASSERT_FALSE(PathExists(metrics_file()));
+
+  size_t file_size_kib = 64;
+  base::GlobalHistogramAllocator::CreateWithLocalMemory(file_size_kib << 10, 0,
+                                                        kMetricsName);
+  base::GlobalHistogramAllocator* allocator =
+      base::GlobalHistogramAllocator::Get();
+  base::HistogramBase* histogram;
+
+  // Create one old file and one new file.
+  base::ScopedTempDir metrics_files;
+  EXPECT_TRUE(metrics_files.CreateUniqueTempDir());
+  histogram = base::Histogram::FactoryGet("h1", 1, 100, 10, 0);
+  histogram->Add(1);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("a1.pma"),
+                         allocator,
+                         base::Time::Now() - base::TimeDelta::FromHours(1));
+
+  histogram = base::Histogram::FactoryGet("h2", 1, 100, 10, 0);
+  histogram->Add(2);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("b2.pma"),
+                         allocator, base::Time::Now());
+
+  // The global allocator has to be detached here so that no metrics created
+  // by code called below get stored in it as that would make for potential
+  // use-after-free operations if that code is called again.
+  base::GlobalHistogramAllocator::ReleaseForTesting();
+
+  // Register the file and allow the "checker" task to run.
+  FileMetricsProvider::Params params(
+      metrics_files.GetPath(),
+      FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR,
+      FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName);
+  params.max_dir_kib = file_size_kib + 1;
+  provider()->RegisterSource(params);
+
+  // Only b2, with 2 histograms, should be read.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(2U, GetSnapshotHistogramCount());
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(0U, GetSnapshotHistogramCount());
+
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("a1.pma")));
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("b2.pma")));
+}
+
+TEST_P(FileMetricsProviderTest, AccessFilteredDirectory) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+
+  base::GlobalHistogramAllocator::CreateWithLocalMemory(64 << 10, 0,
+                                                        kMetricsName);
+  base::GlobalHistogramAllocator* allocator =
+      base::GlobalHistogramAllocator::Get();
+  base::HistogramBase* histogram;
+
+  // Create files starting with a timestamp a few minutes back.
+  base::Time base_time = base::Time::Now() - base::TimeDelta::FromMinutes(10);
+
+  // Create some files in an odd order. The files are "touched" back in time to
+  // ensure that each file has a later timestamp on disk than the previous one.
+  base::ScopedTempDir metrics_files;
+  EXPECT_TRUE(metrics_files.CreateUniqueTempDir());
+
+  histogram = base::Histogram::FactoryGet("h1", 1, 100, 10, 0);
+  histogram->Add(1);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("a1.pma"),
+                         allocator,
+                         base_time + base::TimeDelta::FromMinutes(1));
+
+  histogram = base::Histogram::FactoryGet("h2", 1, 100, 10, 0);
+  histogram->Add(2);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("c2.pma"),
+                         allocator,
+                         base_time + base::TimeDelta::FromMinutes(2));
+
+  histogram = base::Histogram::FactoryGet("h3", 1, 100, 10, 0);
+  histogram->Add(3);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("b3.pma"),
+                         allocator,
+                         base_time + base::TimeDelta::FromMinutes(3));
+
+  histogram = base::Histogram::FactoryGet("h4", 1, 100, 10, 0);
+  histogram->Add(3);
+  WriteMetricsFileAtTime(metrics_files.GetPath().AppendASCII("d4.pma"),
+                         allocator,
+                         base_time + base::TimeDelta::FromMinutes(4));
+
+  base::TouchFile(metrics_files.GetPath().AppendASCII("b3.pma"),
+                  base_time + base::TimeDelta::FromMinutes(5),
+                  base_time + base::TimeDelta::FromMinutes(5));
+
+  // The global allocator has to be detached here so that no metrics created
+  // by code called below get stored in it as that would make for potential
+  // use-after-free operations if that code is called again.
+  base::GlobalHistogramAllocator::ReleaseForTesting();
+
+  // Register the file and allow the "checker" task to run.
+  FileMetricsProvider::Params params(
+      metrics_files.GetPath(),
+      FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR,
+      FileMetricsProvider::ASSOCIATE_CURRENT_RUN, kMetricsName);
+  const FileMetricsProvider::FilterAction actions[] = {
+      FileMetricsProvider::FILTER_PROCESS_FILE,   // a1
+      FileMetricsProvider::FILTER_TRY_LATER,      // c2
+      FileMetricsProvider::FILTER_SKIP_FILE,      // d4
+      FileMetricsProvider::FILTER_PROCESS_FILE,   // b3
+      FileMetricsProvider::FILTER_PROCESS_FILE};  // c2 (again)
+  SetFilterActions(&params, actions, arraysize(actions));
+  provider()->RegisterSource(params);
+
+  // Files could come out in the order: a1, b3, c2. They are recognizeable
+  // by the number of histograms contained within each.
+  const uint32_t expect_order[] = {1, 3, 2, 0};
+  for (size_t i = 0; i < arraysize(expect_order); ++i) {
+    // Record embedded snapshots via snapshot-manager.
+    OnDidCreateMetricsLog();
+    RunTasks();
+    EXPECT_EQ(expect_order[i], GetSnapshotHistogramCount()) << i;
+  }
+
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("a1.pma")));
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("c2.pma")));
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("b3.pma")));
+  EXPECT_FALSE(base::PathExists(metrics_files.GetPath().AppendASCII("d4.pma")));
+}
+
+TEST_P(FileMetricsProviderTest, AccessReadWriteMetrics) {
+  // Create a global histogram allocator that maps to a file.
+  ASSERT_FALSE(PathExists(metrics_file()));
+  base::GlobalHistogramAllocator::CreateWithFile(
+      metrics_file(),
+      create_large_files_ ? kLargeFileSize : kSmallFileSize,
+      0, kMetricsName);
+  CreateGlobalHistograms(2);
+  ASSERT_TRUE(PathExists(metrics_file()));
+  base::HistogramBase* h0 = GetCreatedHistogram(0);
+  base::HistogramBase* h1 = GetCreatedHistogram(1);
+  DCHECK(h0);
+  DCHECK(h1);
+  std::unique_ptr<base::PersistentHistogramAllocator> histogram_allocator =
+      base::GlobalHistogramAllocator::ReleaseForTesting();
+
+  // Register the file and allow the "checker" task to run.
+  provider()->RegisterSource(FileMetricsProvider::Params(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ACTIVE_FILE,
+      FileMetricsProvider::ASSOCIATE_CURRENT_RUN));
+
+  // Record embedded snapshots via snapshot-manager.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(2U, GetSnapshotHistogramCount());
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+
+  // Make sure a second call to the snapshot-recorder doesn't break anything.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(0U, GetSnapshotHistogramCount());
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+
+  // Change a histogram and ensure that it's counted.
+  h0->Add(0);
+  EXPECT_EQ(1U, GetSnapshotHistogramCount());
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+
+  // Change the other histogram and verify.
+  h1->Add(11);
+  EXPECT_EQ(1U, GetSnapshotHistogramCount());
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+}
+
+TEST_P(FileMetricsProviderTest, AccessInitialMetrics) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+  CreateMetricsFileWithHistograms(2);
+
+  // Register the file and allow the "checker" task to run.
+  ASSERT_TRUE(PathExists(metrics_file()));
+  provider()->RegisterSource(FileMetricsProvider::Params(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
+      FileMetricsProvider::ASSOCIATE_PREVIOUS_RUN, kMetricsName));
+
+  // Record embedded snapshots via snapshot-manager.
+  ASSERT_TRUE(HasPreviousSessionData());
+  RunTasks();
+  {
+    HistogramFlattenerDeltaRecorder flattener;
+    base::HistogramSnapshotManager snapshot_manager(&flattener);
+    RecordInitialHistogramSnapshots(&snapshot_manager);
+    EXPECT_EQ(2U, flattener.GetRecordedDeltaHistogramNames().size());
+  }
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+
+  // A run for normal histograms should produce nothing.
+  CreateMetricsFileWithHistograms(2);
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_EQ(0U, GetSnapshotHistogramCount());
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+}
+
+TEST_P(FileMetricsProviderTest, AccessEmbeddedProfileMetricsWithoutProfile) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+  CreateMetricsFileWithHistograms(2);
+
+  // Register the file and allow the "checker" task to run.
+  ASSERT_TRUE(PathExists(metrics_file()));
+  provider()->RegisterSource(FileMetricsProvider::Params(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
+      FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE, kMetricsName));
+
+  // Record embedded snapshots via snapshot-manager.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  {
+    HistogramFlattenerDeltaRecorder flattener;
+    base::HistogramSnapshotManager snapshot_manager(&flattener);
+    SystemProfileProto profile;
+
+    // A read of metrics with internal profiles should return nothing.
+    EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager));
+  }
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+}
+
+TEST_P(FileMetricsProviderTest, AccessEmbeddedProfileMetricsWithProfile) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+  CreateMetricsFileWithHistograms(
+      metrics_file(), base::Time::Now(), 2,
+      [](base::PersistentHistogramAllocator* allocator) {
+        SystemProfileProto profile_proto;
+        SystemProfileProto::FieldTrial* trial = profile_proto.add_field_trial();
+        trial->set_name_id(123);
+        trial->set_group_id(456);
+
+        PersistentSystemProfile persistent_profile;
+        persistent_profile.RegisterPersistentAllocator(
+            allocator->memory_allocator());
+        persistent_profile.SetSystemProfile(profile_proto, true);
+      });
+
+  // Register the file and allow the "checker" task to run.
+  ASSERT_TRUE(PathExists(metrics_file()));
+  provider()->RegisterSource(FileMetricsProvider::Params(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
+      FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE, kMetricsName));
+
+  // Record embedded snapshots via snapshot-manager.
+  OnDidCreateMetricsLog();
+  RunTasks();
+  {
+    HistogramFlattenerDeltaRecorder flattener;
+    base::HistogramSnapshotManager snapshot_manager(&flattener);
+    RecordInitialHistogramSnapshots(&snapshot_manager);
+    EXPECT_EQ(0U, flattener.GetRecordedDeltaHistogramNames().size());
+
+    // A read of metrics with internal profiles should return one result.
+    SystemProfileProto profile;
+    EXPECT_TRUE(ProvideIndependentMetrics(&profile, &snapshot_manager));
+    EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager));
+  }
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+}
+
+TEST_P(FileMetricsProviderTest, AccessEmbeddedFallbackMetricsWithoutProfile) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+  CreateMetricsFileWithHistograms(2);
+
+  // Register the file and allow the "checker" task to run.
+  ASSERT_TRUE(PathExists(metrics_file()));
+  provider()->RegisterSource(FileMetricsProvider::Params(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
+      FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN,
+      kMetricsName));
+
+  // Record embedded snapshots via snapshot-manager.
+  ASSERT_TRUE(HasPreviousSessionData());
+  RunTasks();
+  {
+    HistogramFlattenerDeltaRecorder flattener;
+    base::HistogramSnapshotManager snapshot_manager(&flattener);
+    RecordInitialHistogramSnapshots(&snapshot_manager);
+    EXPECT_EQ(2U, flattener.GetRecordedDeltaHistogramNames().size());
+
+    // A read of metrics with internal profiles should return nothing.
+    SystemProfileProto profile;
+    EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager));
+  }
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+}
+
+TEST_P(FileMetricsProviderTest, AccessEmbeddedFallbackMetricsWithProfile) {
+  ASSERT_FALSE(PathExists(metrics_file()));
+  CreateMetricsFileWithHistograms(
+      metrics_file(), base::Time::Now(), 2,
+      [](base::PersistentHistogramAllocator* allocator) {
+        SystemProfileProto profile_proto;
+        SystemProfileProto::FieldTrial* trial = profile_proto.add_field_trial();
+        trial->set_name_id(123);
+        trial->set_group_id(456);
+
+        PersistentSystemProfile persistent_profile;
+        persistent_profile.RegisterPersistentAllocator(
+            allocator->memory_allocator());
+        persistent_profile.SetSystemProfile(profile_proto, true);
+      });
+
+  // Register the file and allow the "checker" task to run.
+  ASSERT_TRUE(PathExists(metrics_file()));
+  provider()->RegisterSource(FileMetricsProvider::Params(
+      metrics_file(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_FILE,
+      FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE_OR_PREVIOUS_RUN,
+      kMetricsName));
+
+  // Record embedded snapshots via snapshot-manager.
+  EXPECT_FALSE(HasPreviousSessionData());
+  RunTasks();
+  {
+    HistogramFlattenerDeltaRecorder flattener;
+    base::HistogramSnapshotManager snapshot_manager(&flattener);
+    RecordInitialHistogramSnapshots(&snapshot_manager);
+    EXPECT_EQ(0U, flattener.GetRecordedDeltaHistogramNames().size());
+
+    // A read of metrics with internal profiles should return one result.
+    SystemProfileProto profile;
+    EXPECT_TRUE(ProvideIndependentMetrics(&profile, &snapshot_manager));
+    EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager));
+  }
+  EXPECT_TRUE(base::PathExists(metrics_file()));
+  OnDidCreateMetricsLog();
+  RunTasks();
+  EXPECT_FALSE(base::PathExists(metrics_file()));
+}
+
+TEST_P(FileMetricsProviderTest, AccessEmbeddedProfileMetricsFromDir) {
+  const int file_count = 3;
+  base::Time file_base_time = base::Time::Now();
+  std::vector<base::FilePath> file_names;
+  for (int i = 0; i < file_count; ++i) {
+    CreateMetricsFileWithHistograms(
+        metrics_file(), base::Time::Now(), 2,
+        [](base::PersistentHistogramAllocator* allocator) {
+          SystemProfileProto profile_proto;
+          SystemProfileProto::FieldTrial* trial =
+              profile_proto.add_field_trial();
+          trial->set_name_id(123);
+          trial->set_group_id(456);
+
+          PersistentSystemProfile persistent_profile;
+          persistent_profile.RegisterPersistentAllocator(
+              allocator->memory_allocator());
+          persistent_profile.SetSystemProfile(profile_proto, true);
+        });
+    ASSERT_TRUE(PathExists(metrics_file()));
+    char new_name[] = "hX";
+    new_name[1] = '1' + i;
+    base::FilePath file_name = temp_dir().AppendASCII(new_name).AddExtension(
+        base::PersistentMemoryAllocator::kFileExtension);
+    base::Time file_time =
+        file_base_time - base::TimeDelta::FromMinutes(file_count - i);
+    base::TouchFile(metrics_file(), file_time, file_time);
+    base::Move(metrics_file(), file_name);
+    file_names.push_back(std::move(file_name));
+  }
+
+  // Register the file and allow the "checker" task to run.
+  provider()->RegisterSource(FileMetricsProvider::Params(
+      temp_dir(), FileMetricsProvider::SOURCE_HISTOGRAMS_ATOMIC_DIR,
+      FileMetricsProvider::ASSOCIATE_INTERNAL_PROFILE));
+
+  OnDidCreateMetricsLog();
+  RunTasks();
+
+  // A read of metrics with internal profiles should return one result.
+  HistogramFlattenerDeltaRecorder flattener;
+  base::HistogramSnapshotManager snapshot_manager(&flattener);
+  SystemProfileProto profile;
+  for (int i = 0; i < file_count; ++i) {
+    EXPECT_TRUE(ProvideIndependentMetrics(&profile, &snapshot_manager)) << i;
+    RunTasks();
+  }
+  EXPECT_FALSE(ProvideIndependentMetrics(&profile, &snapshot_manager));
+
+  OnDidCreateMetricsLog();
+  RunTasks();
+  for (const auto& file_name : file_names)
+    EXPECT_FALSE(base::PathExists(file_name));
+}
+
+}  // namespace metrics
diff --git a/components/metrics/generate_expired_histograms_array.gni b/components/metrics/generate_expired_histograms_array.gni
new file mode 100644
index 0000000..b452246
--- /dev/null
+++ b/components/metrics/generate_expired_histograms_array.gni
@@ -0,0 +1,52 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Runs the resources map generation script other the given header files to
+# produce an output file and a source_set to build it.
+#
+# Parameters:
+#   inputs:
+#     List of file name to read. Each file should be a .xml file with
+#     histogram descriptions.
+#
+#   namespace (optional):
+#     Namespace in which the generated code should be scoped. If left empty,
+#     the code will be in the global namespace.
+#
+#   header_filename:
+#     Name of the generated header file.
+#
+#   major_branch_date_filepath:
+#     A path to the file with the base date.
+#
+#   milestone_filepath:
+#     A path to the file with the milestone information.
+#
+template("generate_expired_histograms_array") {
+  action(target_name) {
+    header_filename = "$target_gen_dir/" + invoker.header_filename
+
+    script = "//tools/metrics/histograms/generate_expired_histograms_array.py"
+    outputs = [
+      header_filename,
+    ]
+
+    inputs = invoker.inputs
+    major_branch_date_filepath = invoker.major_branch_date_filepath
+    milestone_filepath = invoker.milestone_filepath
+
+    args = []
+
+    if (defined(invoker.namespace) && invoker.namespace != "") {
+      args += [ "-n" + invoker.namespace ]
+    }
+
+    args += [
+              "-o" + rebase_path(root_gen_dir, root_build_dir),
+              "-H" + rebase_path(header_filename, root_gen_dir),
+              "-d" + rebase_path(major_branch_date_filepath, root_build_dir),
+              "-m" + rebase_path(milestone_filepath, root_build_dir),
+            ] + rebase_path(inputs, root_build_dir)
+  }
+}
diff --git a/components/metrics/gpu/DEPS b/components/metrics/gpu/DEPS
new file mode 100644
index 0000000..c2ff8a0
--- /dev/null
+++ b/components/metrics/gpu/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+content/public/browser",
+  "+gpu/config",
+]
diff --git a/components/metrics/gpu/gpu_metrics_provider.cc b/components/metrics/gpu/gpu_metrics_provider.cc
new file mode 100644
index 0000000..53c15fb
--- /dev/null
+++ b/components/metrics/gpu/gpu_metrics_provider.cc
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/gpu/gpu_metrics_provider.h"
+
+#include "content/public/browser/gpu_data_manager.h"
+#include "gpu/config/gpu_info.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace metrics {
+
+GPUMetricsProvider::GPUMetricsProvider() {
+}
+
+GPUMetricsProvider::~GPUMetricsProvider() {
+}
+
+void GPUMetricsProvider::ProvideSystemProfileMetrics(
+    SystemProfileProto* system_profile_proto) {
+  SystemProfileProto::Hardware* hardware =
+      system_profile_proto->mutable_hardware();
+
+  const gpu::GPUInfo& gpu_info =
+      content::GpuDataManager::GetInstance()->GetGPUInfo();
+  const gpu::GPUInfo::GPUDevice& active_gpu = gpu_info.active_gpu();
+  SystemProfileProto::Hardware::Graphics* gpu =
+      hardware->mutable_gpu();
+  gpu->set_vendor_id(active_gpu.vendor_id);
+  gpu->set_device_id(active_gpu.device_id);
+  gpu->set_driver_version(active_gpu.driver_version);
+  gpu->set_driver_date(active_gpu.driver_date);
+  gpu->set_gl_vendor(gpu_info.gl_vendor);
+  gpu->set_gl_renderer(gpu_info.gl_renderer);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/gpu/gpu_metrics_provider.h b/components/metrics/gpu/gpu_metrics_provider.h
new file mode 100644
index 0000000..581c765
--- /dev/null
+++ b/components/metrics/gpu/gpu_metrics_provider.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_GPU_GPU_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_GPU_GPU_METRICS_PROVIDER_H_
+
+#include "base/macros.h"
+#include "components/metrics/metrics_provider.h"
+
+namespace metrics {
+
+// GPUMetricsProvider provides GPU-related metrics.
+class GPUMetricsProvider : public MetricsProvider {
+ public:
+  GPUMetricsProvider();
+  ~GPUMetricsProvider() override;
+
+  // MetricsProvider:
+  void ProvideSystemProfileMetrics(
+      SystemProfileProto* system_profile_proto) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(GPUMetricsProvider);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_GPU_GPU_METRICS_PROVIDER_H_
diff --git a/components/metrics/histogram_encoder.cc b/components/metrics/histogram_encoder.cc
new file mode 100644
index 0000000..4d7d945
--- /dev/null
+++ b/components/metrics/histogram_encoder.cc
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/histogram_encoder.h"
+
+#include <memory>
+#include <string>
+
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/metrics_hashes.h"
+
+using base::SampleCountIterator;
+
+namespace metrics {
+
+void EncodeHistogramDelta(const std::string& histogram_name,
+                          const base::HistogramSamples& snapshot,
+                          ChromeUserMetricsExtension* uma_proto) {
+  DCHECK_NE(0, snapshot.TotalCount());
+  DCHECK(uma_proto);
+
+  // We will ignore the MAX_INT/infinite value in the last element of range[].
+
+  HistogramEventProto* histogram_proto = uma_proto->add_histogram_event();
+  histogram_proto->set_name_hash(base::HashMetricName(histogram_name));
+  if (snapshot.sum() != 0)
+    histogram_proto->set_sum(snapshot.sum());
+
+  for (std::unique_ptr<SampleCountIterator> it = snapshot.Iterator();
+       !it->Done(); it->Next()) {
+    base::Histogram::Sample min;
+    int64_t max;
+    base::Histogram::Count count;
+    it->Get(&min, &max, &count);
+    HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket();
+    bucket->set_min(min);
+    bucket->set_max(max);
+    // Note: The default for count is 1 in the proto, so omit it in that case.
+    if (count != 1)
+      bucket->set_count(count);
+  }
+
+  // Omit fields to save space (see rules in histogram_event.proto comments).
+  for (int i = 0; i < histogram_proto->bucket_size(); ++i) {
+    HistogramEventProto::Bucket* bucket = histogram_proto->mutable_bucket(i);
+    if (i + 1 < histogram_proto->bucket_size() &&
+        bucket->max() == histogram_proto->bucket(i + 1).min()) {
+      bucket->clear_max();
+    } else if (bucket->max() == bucket->min() + 1) {
+      bucket->clear_min();
+    }
+  }
+}
+
+}  // namespace metrics
diff --git a/components/metrics/histogram_encoder.h b/components/metrics/histogram_encoder.h
new file mode 100644
index 0000000..332e907
--- /dev/null
+++ b/components/metrics/histogram_encoder.h
@@ -0,0 +1,29 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines an utility function that records any changes in a given
+// histogram for transmission.
+
+#ifndef COMPONENTS_METRICS_HISTOGRAM_ENCODER_H_
+#define COMPONENTS_METRICS_HISTOGRAM_ENCODER_H_
+
+#include <string>
+
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
+
+namespace base {
+class HistogramSamples;
+}
+
+namespace metrics {
+
+// Record any changes (histogram deltas of counts from |snapshot|) into
+// |uma_proto| for the given histogram (|histogram_name|).
+void EncodeHistogramDelta(const std::string& histogram_name,
+                          const base::HistogramSamples& snapshot,
+                          ChromeUserMetricsExtension* uma_proto);
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_HISTOGRAM_ENCODER_H_
diff --git a/components/metrics/histogram_encoder_unittest.cc b/components/metrics/histogram_encoder_unittest.cc
new file mode 100644
index 0000000..dfe7f84
--- /dev/null
+++ b/components/metrics/histogram_encoder_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/histogram_encoder.h"
+
+#include <string>
+
+#include "base/metrics/bucket_ranges.h"
+#include "base/metrics/sample_vector.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+TEST(HistogramEncoder, HistogramBucketFields) {
+  // Create buckets: 1-5, 5-7, 7-8, 8-9, 9-10, 10-11, 11-12.
+  base::BucketRanges ranges(8);
+  ranges.set_range(0, 1);
+  ranges.set_range(1, 5);
+  ranges.set_range(2, 7);
+  ranges.set_range(3, 8);
+  ranges.set_range(4, 9);
+  ranges.set_range(5, 10);
+  ranges.set_range(6, 11);
+  ranges.set_range(7, 12);
+
+  base::SampleVector samples(1, &ranges);
+  samples.Accumulate(3, 1);   // Bucket 1-5.
+  samples.Accumulate(6, 1);   // Bucket 5-7.
+  samples.Accumulate(8, 1);   // Bucket 8-9. (7-8 skipped)
+  samples.Accumulate(10, 1);  // Bucket 10-11. (9-10 skipped)
+  samples.Accumulate(11, 1);  // Bucket 11-12.
+
+  ChromeUserMetricsExtension uma_proto;
+  EncodeHistogramDelta("Test", samples, &uma_proto);
+
+  const HistogramEventProto& histogram_proto =
+      uma_proto.histogram_event(uma_proto.histogram_event_size() - 1);
+
+  // Buckets with samples: 1-5, 5-7, 8-9, 10-11, 11-12.
+  // Should become: 1-/, 5-7, /-9, 10-/, /-12.
+  ASSERT_EQ(5, histogram_proto.bucket_size());
+
+  // 1-5 becomes 1-/ (max is same as next min).
+  EXPECT_TRUE(histogram_proto.bucket(0).has_min());
+  EXPECT_FALSE(histogram_proto.bucket(0).has_max());
+  EXPECT_EQ(1, histogram_proto.bucket(0).min());
+
+  // 5-7 stays 5-7 (no optimization possible).
+  EXPECT_TRUE(histogram_proto.bucket(1).has_min());
+  EXPECT_TRUE(histogram_proto.bucket(1).has_max());
+  EXPECT_EQ(5, histogram_proto.bucket(1).min());
+  EXPECT_EQ(7, histogram_proto.bucket(1).max());
+
+  // 8-9 becomes /-9 (min is same as max - 1).
+  EXPECT_FALSE(histogram_proto.bucket(2).has_min());
+  EXPECT_TRUE(histogram_proto.bucket(2).has_max());
+  EXPECT_EQ(9, histogram_proto.bucket(2).max());
+
+  // 10-11 becomes 10-/ (both optimizations apply, omit max is prioritized).
+  EXPECT_TRUE(histogram_proto.bucket(3).has_min());
+  EXPECT_FALSE(histogram_proto.bucket(3).has_max());
+  EXPECT_EQ(10, histogram_proto.bucket(3).min());
+
+  // 11-12 becomes /-12 (last record must keep max, min is same as max - 1).
+  EXPECT_FALSE(histogram_proto.bucket(4).has_min());
+  EXPECT_TRUE(histogram_proto.bucket(4).has_max());
+  EXPECT_EQ(12, histogram_proto.bucket(4).max());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/log_decoder.cc b/components/metrics/log_decoder.cc
new file mode 100644
index 0000000..1754c27
--- /dev/null
+++ b/components/metrics/log_decoder.cc
@@ -0,0 +1,16 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/log_decoder.h"
+
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace metrics {
+
+bool DecodeLogData(const std::string& compressed_log_data,
+                   std::string* log_data) {
+  return compression::GzipUncompress(compressed_log_data, log_data);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/log_decoder.h b/components/metrics/log_decoder.h
new file mode 100644
index 0000000..c85037f
--- /dev/null
+++ b/components/metrics/log_decoder.h
@@ -0,0 +1,21 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LOG_DECODER_H_
+#define COMPONENTS_METRICS_LOG_DECODER_H_
+
+#include <string>
+
+namespace metrics {
+
+// Other modules can call this function instead of directly calling gzip. This
+// prevents other modules from having to depend on zlib, or being aware of
+// metrics' use of gzip compression, which is a metrics implementation detail.
+// Returns true on success, false on failure.
+bool DecodeLogData(const std::string& compressed_log_data,
+                   std::string* log_data);
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_LOG_DECODER_H_
diff --git a/components/metrics/log_store.h b/components/metrics/log_store.h
new file mode 100644
index 0000000..9dd1a62
--- /dev/null
+++ b/components/metrics/log_store.h
@@ -0,0 +1,49 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_LOG_STORE_H_
+#define COMPONENTS_METRICS_LOG_STORE_H_
+
+#include <string>
+
+namespace metrics {
+
+// Interface for local storage of serialized logs to be reported.
+// It allows consumers to check if there are logs to consume, consume them one
+// at a time by staging and discarding logs, and persist/load the whole set.
+class LogStore {
+ public:
+  // Returns true if there are any logs waiting to be uploaded.
+  virtual bool has_unsent_logs() const = 0;
+
+  // Returns true if there is a log that needs to be, or is being, uploaded.
+  virtual bool has_staged_log() const = 0;
+
+  // The text of the staged log, as a serialized protobuf.
+  // Will trigger a DCHECK if there is no staged log.
+  virtual const std::string& staged_log() const = 0;
+
+  // The SHA1 hash of the staged log.
+  // Will trigger a DCHECK if there is no staged log.
+  virtual const std::string& staged_log_hash() const = 0;
+
+  // Populates staged_log() with the next stored log to send.
+  // The order in which logs are staged is up to the implementor.
+  // The staged_log must remain the same even if additional logs are added.
+  // Should only be called if has_unsent_logs() is true.
+  virtual void StageNextLog() = 0;
+
+  // Discards the staged log.
+  virtual void DiscardStagedLog() = 0;
+
+  // Saves any unsent logs to persistent storage.
+  virtual void PersistUnsentLogs() const = 0;
+
+  // Loads unsent logs from persistent storage.
+  virtual void LoadPersistedUnsentLogs() = 0;
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_LOG_STORE_H_
diff --git a/components/metrics/machine_id_provider.h b/components/metrics/machine_id_provider.h
new file mode 100644
index 0000000..b7a2704
--- /dev/null
+++ b/components/metrics/machine_id_provider.h
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_
+#define COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace metrics {
+
+// Provides machine characteristics used as a machine id. The implementation is
+// platform specific. GetMachineId() must be called on a thread which allows
+// I/O. GetMachineId() must not be called if HasId() returns false on this
+// platform.
+class MachineIdProvider {
+ public:
+  // Returns true if this platform provides a non-empty GetMachineId(). This is
+  // useful to avoid an async call to GetMachineId() on platforms with no
+  // implementation.
+  static bool HasId();
+
+  // Get a string containing machine characteristics, to be used as a machine
+  // id. The implementation is platform specific, with a default implementation
+  // returning an empty string.
+  // The return value should not be stored to disk or transmitted.
+  static std::string GetMachineId();
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(MachineIdProvider);
+};
+
+}  //  namespace metrics
+
+#endif  // COMPONENTS_METRICS_MACHINE_ID_PROVIDER_H_
diff --git a/components/metrics/machine_id_provider_stub.cc b/components/metrics/machine_id_provider_stub.cc
new file mode 100644
index 0000000..d747209
--- /dev/null
+++ b/components/metrics/machine_id_provider_stub.cc
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/machine_id_provider.h"
+
+#include "base/logging.h"
+
+namespace metrics {
+
+// static
+bool MachineIdProvider::HasId() {
+  return false;
+}
+
+// static
+std::string MachineIdProvider::GetMachineId() {
+  NOTREACHED();
+  return std::string();
+}
+
+}  //  namespace metrics
diff --git a/components/metrics/machine_id_provider_win.cc b/components/metrics/machine_id_provider_win.cc
new file mode 100644
index 0000000..eb7b0d7
--- /dev/null
+++ b/components/metrics/machine_id_provider_win.cc
@@ -0,0 +1,99 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/machine_id_provider.h"
+
+#include <windows.h>
+#include <stdint.h>
+#include <winioctl.h>
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "base/win/scoped_handle.h"
+
+namespace metrics {
+
+// static
+bool MachineIdProvider::HasId() {
+  return true;
+}
+
+// On windows, the machine id is based on the serial number of the drive Chrome
+// is running from.
+// static
+std::string MachineIdProvider::GetMachineId() {
+  base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK);
+
+  // Use the program's path to get the drive used for the machine id. This means
+  // that whenever the underlying drive changes, it's considered a new machine.
+  // This is fine as we do not support migrating Chrome installs to new drives.
+  base::FilePath executable_path;
+
+  if (!base::PathService::Get(base::FILE_EXE, &executable_path)) {
+    NOTREACHED();
+    return std::string();
+  }
+
+  std::vector<base::FilePath::StringType> path_components;
+  executable_path.GetComponents(&path_components);
+  if (path_components.empty()) {
+    NOTREACHED();
+    return std::string();
+  }
+  base::FilePath::StringType drive_name = L"\\\\.\\" + path_components[0];
+
+  base::win::ScopedHandle drive_handle(
+      CreateFile(drive_name.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
+                 nullptr, OPEN_EXISTING, 0, nullptr));
+
+  STORAGE_PROPERTY_QUERY query = {};
+  query.PropertyId = StorageDeviceProperty;
+  query.QueryType = PropertyStandardQuery;
+
+  // Perform an initial query to get the number of bytes being returned.
+  DWORD bytes_returned;
+  STORAGE_DESCRIPTOR_HEADER header = {};
+  BOOL status = DeviceIoControl(
+      drive_handle.Get(), IOCTL_STORAGE_QUERY_PROPERTY, &query,
+      sizeof(STORAGE_PROPERTY_QUERY), &header,
+      sizeof(STORAGE_DESCRIPTOR_HEADER), &bytes_returned, nullptr);
+
+  if (!status)
+    return std::string();
+
+  // Query for the actual serial number.
+  std::vector<int8_t> output_buf(header.Size);
+  status =
+      DeviceIoControl(drive_handle.Get(), IOCTL_STORAGE_QUERY_PROPERTY, &query,
+                      sizeof(STORAGE_PROPERTY_QUERY), &output_buf[0],
+                      output_buf.size(), &bytes_returned, nullptr);
+
+  if (!status)
+    return std::string();
+
+  const STORAGE_DEVICE_DESCRIPTOR* device_descriptor =
+      reinterpret_cast<STORAGE_DEVICE_DESCRIPTOR*>(&output_buf[0]);
+
+  // The serial number is stored in the |output_buf| as a null-terminated
+  // string starting at the specified offset.
+  const DWORD offset = device_descriptor->SerialNumberOffset;
+  if (offset >= output_buf.size())
+    return std::string();
+
+  // Make sure that the null-terminator exists.
+  const std::vector<int8_t>::iterator serial_number_begin =
+      output_buf.begin() + offset;
+  const std::vector<int8_t>::iterator null_location =
+      std::find(serial_number_begin, output_buf.end(), '\0');
+  if (null_location == output_buf.end())
+    return std::string();
+
+  const char* serial_number =
+      reinterpret_cast<const char*>(&output_buf[offset]);
+
+  return std::string(serial_number);
+}
+}  //  namespace metrics
diff --git a/components/metrics/machine_id_provider_win_unittest.cc b/components/metrics/machine_id_provider_win_unittest.cc
new file mode 100644
index 0000000..6bcd0c4
--- /dev/null
+++ b/components/metrics/machine_id_provider_win_unittest.cc
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/machine_id_provider.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+TEST(MachineIdProviderTest, GetId) {
+  EXPECT_TRUE(MachineIdProvider::HasId());
+
+  const std::string id1 = MachineIdProvider::GetMachineId();
+  EXPECT_NE(std::string(), id1);
+
+  const std::string id2 = MachineIdProvider::GetMachineId();
+  EXPECT_EQ(id1, id2);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_log.cc b/components/metrics/metrics_log.cc
new file mode 100644
index 0000000..39ad9b1
--- /dev/null
+++ b/components/metrics/metrics_log.cc
@@ -0,0 +1,350 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <string>
+
+#include "base/build_time.h"
+#include "base/cpu.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_flattener.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/histogram_snapshot_manager.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/metrics/delegating_provider.h"
+#include "components/metrics/environment_recorder.h"
+#include "components/metrics/histogram_encoder.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/metrics_service_client.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "third_party/metrics_proto/histogram_event.pb.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+#include "third_party/metrics_proto/user_action_event.pb.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/build_info.h"
+#endif
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include "base/win/current_module.h"
+#endif
+
+using base::SampleCountIterator;
+
+namespace metrics {
+
+namespace {
+
+// A simple class to write histogram data to a log.
+class IndependentFlattener : public base::HistogramFlattener {
+ public:
+  explicit IndependentFlattener(MetricsLog* log) : log_(log) {}
+
+  // base::HistogramFlattener:
+  void RecordDelta(const base::HistogramBase& histogram,
+                   const base::HistogramSamples& snapshot) override {
+    log_->RecordHistogramDelta(histogram.histogram_name(), snapshot);
+  }
+
+ private:
+  MetricsLog* const log_;
+
+  DISALLOW_COPY_AND_ASSIGN(IndependentFlattener);
+};
+
+// Any id less than 16 bytes is considered to be a testing id.
+bool IsTestingID(const std::string& id) {
+  return id.size() < 16;
+}
+
+}  // namespace
+
+MetricsLog::MetricsLog(const std::string& client_id,
+                       int session_id,
+                       LogType log_type,
+                       MetricsServiceClient* client)
+    : closed_(false),
+      log_type_(log_type),
+      client_(client),
+      creation_time_(base::TimeTicks::Now()),
+      has_environment_(false) {
+  if (IsTestingID(client_id))
+    uma_proto_.set_client_id(0);
+  else
+    uma_proto_.set_client_id(Hash(client_id));
+
+  uma_proto_.set_session_id(session_id);
+
+  const int32_t product = client_->GetProduct();
+  // Only set the product if it differs from the default value.
+  if (product != uma_proto_.product())
+    uma_proto_.set_product(product);
+
+  SystemProfileProto* system_profile = uma_proto()->mutable_system_profile();
+  RecordCoreSystemProfile(client_, system_profile);
+}
+
+MetricsLog::~MetricsLog() {
+}
+
+// static
+void MetricsLog::RegisterPrefs(PrefRegistrySimple* registry) {
+  EnvironmentRecorder::RegisterPrefs(registry);
+}
+
+// static
+uint64_t MetricsLog::Hash(const std::string& value) {
+  uint64_t hash = base::HashMetricName(value);
+
+  // The following log is VERY helpful when folks add some named histogram into
+  // the code, but forgot to update the descriptive list of histograms.  When
+  // that happens, all we get to see (server side) is a hash of the histogram
+  // name.  We can then use this logging to find out what histogram name was
+  // being hashed to a given MD5 value by just running the version of Chromium
+  // in question with --enable-logging.
+  DVLOG(1) << "Metrics: Hash numeric [" << value << "]=[" << hash << "]";
+
+  return hash;
+}
+
+// static
+int64_t MetricsLog::GetBuildTime() {
+  static int64_t integral_build_time = 0;
+  if (!integral_build_time)
+    integral_build_time = static_cast<int64_t>(base::GetBuildTime().ToTimeT());
+  return integral_build_time;
+}
+
+// static
+int64_t MetricsLog::GetCurrentTime() {
+  return (base::TimeTicks::Now() - base::TimeTicks()).InSeconds();
+}
+
+void MetricsLog::RecordUserAction(const std::string& key) {
+  DCHECK(!closed_);
+
+  UserActionEventProto* user_action = uma_proto_.add_user_action_event();
+  user_action->set_name_hash(Hash(key));
+  user_action->set_time_sec(GetCurrentTime());
+}
+
+void MetricsLog::RecordCoreSystemProfile(MetricsServiceClient* client,
+                                         SystemProfileProto* system_profile) {
+  system_profile->set_build_timestamp(metrics::MetricsLog::GetBuildTime());
+  system_profile->set_app_version(client->GetVersionString());
+  system_profile->set_channel(client->GetChannel());
+  system_profile->set_application_locale(client->GetApplicationLocale());
+
+#if defined(ADDRESS_SANITIZER)
+  system_profile->set_is_asan_build(true);
+#endif
+
+  metrics::SystemProfileProto::Hardware* hardware =
+      system_profile->mutable_hardware();
+#if !defined(OS_IOS) && !defined(STARBOARD)
+  // On iOS, OperatingSystemArchitecture() returns values like iPad4,4 which is
+  // not the actual CPU architecture. Don't set it until the API is fixed. See
+  // crbug.com/370104 for details.
+  hardware->set_cpu_architecture(base::SysInfo::OperatingSystemArchitecture());
+#endif
+  hardware->set_system_ram_mb(base::SysInfo::AmountOfPhysicalMemoryMB());
+  hardware->set_hardware_class(base::SysInfo::HardwareModelName());
+#if defined(OS_WIN)
+  hardware->set_dll_base(reinterpret_cast<uint64_t>(CURRENT_MODULE()));
+#endif
+
+  metrics::SystemProfileProto::OS* os = system_profile->mutable_os();
+// TODO(b/283256747): Remove when base::SysInfo is Starboardized.
+#if !defined(STARBOARD)
+  os->set_name(base::SysInfo::OperatingSystemName());
+  os->set_version(base::SysInfo::OperatingSystemVersion());
+#endif
+#if defined(OS_CHROMEOS)
+  os->set_kernel_version(base::SysInfo::KernelVersion());
+#elif defined(OS_ANDROID)
+  os->set_build_fingerprint(
+      base::android::BuildInfo::GetInstance()->android_build_fp());
+  std::string package_name = client->GetAppPackageName();
+  if (!package_name.empty() && package_name != "com.android.chrome")
+    system_profile->set_app_package_name(package_name);
+#endif
+}
+
+void MetricsLog::RecordHistogramDelta(const std::string& histogram_name,
+                                      const base::HistogramSamples& snapshot) {
+  DCHECK(!closed_);
+  EncodeHistogramDelta(histogram_name, snapshot, &uma_proto_);
+}
+
+void MetricsLog::RecordPreviousSessionData(
+    DelegatingProvider* delegating_provider) {
+  delegating_provider->ProvidePreviousSessionData(uma_proto());
+}
+
+void MetricsLog::RecordCurrentSessionData(
+    DelegatingProvider* delegating_provider,
+    base::TimeDelta incremental_uptime,
+    base::TimeDelta uptime) {
+  DCHECK(!closed_);
+  DCHECK(has_environment_);
+
+  // Record recent delta for critical stability metrics.  We can't wait for a
+  // restart to gather these, as that delay biases our observation away from
+  // users that run happily for a looooong time.  We send increments with each
+  // uma log upload, just as we send histogram data.
+  WriteRealtimeStabilityAttributes(incremental_uptime, uptime);
+
+  delegating_provider->ProvideCurrentSessionData(uma_proto());
+}
+
+void MetricsLog::WriteMetricsEnableDefault(EnableMetricsDefault metrics_default,
+                                           SystemProfileProto* system_profile) {
+  if (client_->IsReportingPolicyManaged()) {
+    // If it's managed, then it must be reporting, otherwise we wouldn't be
+    // sending metrics.
+    system_profile->set_uma_default_state(
+        SystemProfileProto_UmaDefaultState_POLICY_FORCED_ENABLED);
+    return;
+  }
+
+  switch (metrics_default) {
+    case EnableMetricsDefault::DEFAULT_UNKNOWN:
+      // Don't set the field if it's unknown.
+      break;
+    case EnableMetricsDefault::OPT_IN:
+      system_profile->set_uma_default_state(
+          SystemProfileProto_UmaDefaultState_OPT_IN);
+      break;
+    case EnableMetricsDefault::OPT_OUT:
+      system_profile->set_uma_default_state(
+          SystemProfileProto_UmaDefaultState_OPT_OUT);
+  }
+}
+
+void MetricsLog::WriteRealtimeStabilityAttributes(
+    base::TimeDelta incremental_uptime,
+    base::TimeDelta uptime) {
+  // Update the stats which are critical for real-time stability monitoring.
+  // Since these are "optional," only list ones that are non-zero, as the counts
+  // are aggregated (summed) server side.
+
+  SystemProfileProto::Stability* stability =
+      uma_proto()->mutable_system_profile()->mutable_stability();
+
+  const uint64_t incremental_uptime_sec = incremental_uptime.InSeconds();
+  if (incremental_uptime_sec)
+    stability->set_incremental_uptime_sec(incremental_uptime_sec);
+  const uint64_t uptime_sec = uptime.InSeconds();
+  if (uptime_sec)
+    stability->set_uptime_sec(uptime_sec);
+}
+
+const SystemProfileProto& MetricsLog::RecordEnvironment(
+    DelegatingProvider* delegating_provider) {
+  DCHECK(!has_environment_);
+  has_environment_ = true;
+
+  SystemProfileProto* system_profile = uma_proto()->mutable_system_profile();
+
+  WriteMetricsEnableDefault(client_->GetMetricsReportingDefaultState(),
+                            system_profile);
+
+  std::string brand_code;
+  if (client_->GetBrand(&brand_code))
+    system_profile->set_brand_code(brand_code);
+
+// TODO(b/283255893): Remove when base::CPU is Starboardized.
+#if !defined(STARBOARD)
+  SystemProfileProto::Hardware::CPU* cpu =
+      system_profile->mutable_hardware()->mutable_cpu();
+  base::CPU cpu_info;
+  cpu->set_vendor_name(cpu_info.vendor_name());
+  cpu->set_signature(cpu_info.signature());
+  cpu->set_num_cores(base::SysInfo::NumberOfProcessors());
+#endif
+
+  delegating_provider->ProvideSystemProfileMetrics(system_profile);
+
+  return *system_profile;
+}
+
+bool MetricsLog::LoadIndependentMetrics(MetricsProvider* metrics_provider) {
+  SystemProfileProto* system_profile = uma_proto()->mutable_system_profile();
+  IndependentFlattener flattener(this);
+  base::HistogramSnapshotManager snapshot_manager(&flattener);
+
+  return metrics_provider->ProvideIndependentMetrics(system_profile,
+                                                     &snapshot_manager);
+}
+
+bool MetricsLog::LoadSavedEnvironmentFromPrefs(PrefService* local_state,
+                                               std::string* app_version) {
+  DCHECK(!has_environment_);
+  has_environment_ = true;
+  app_version->clear();
+
+  SystemProfileProto* system_profile = uma_proto()->mutable_system_profile();
+  EnvironmentRecorder recorder(local_state);
+  bool success = recorder.LoadEnvironmentFromPrefs(system_profile);
+  if (success)
+    *app_version = system_profile->app_version();
+  return success;
+}
+
+void MetricsLog::CloseLog() {
+  DCHECK(!closed_);
+  closed_ = true;
+}
+
+void MetricsLog::TruncateEvents() {
+  DCHECK(!closed_);
+  if (uma_proto_.user_action_event_size() > internal::kUserActionEventLimit) {
+    UMA_HISTOGRAM_COUNTS_100000("UMA.TruncatedEvents.UserAction",
+                                uma_proto_.user_action_event_size());
+    for (int i = internal::kUserActionEventLimit;
+         i < uma_proto_.user_action_event_size(); ++i) {
+      // No histograms.xml entry is added for this histogram because it uses an
+      // enum that is generated from actions.xml in our processing pipelines.
+      // Instead, a histogram description will also be produced in our
+      // pipelines.
+      base::UmaHistogramSparse(
+          "UMA.TruncatedEvents.UserAction.Type",
+          // Truncate the unsigned 64-bit hash to 31 bits, to make it a suitable
+          // histogram sample.
+          uma_proto_.user_action_event(i).name_hash() & 0x7fffffff);
+    }
+    uma_proto_.mutable_user_action_event()->DeleteSubrange(
+        internal::kUserActionEventLimit,
+        uma_proto_.user_action_event_size() - internal::kUserActionEventLimit);
+  }
+
+  if (uma_proto_.omnibox_event_size() > internal::kOmniboxEventLimit) {
+    UMA_HISTOGRAM_COUNTS_100000("UMA.TruncatedEvents.Omnibox",
+                                uma_proto_.omnibox_event_size());
+    uma_proto_.mutable_omnibox_event()->DeleteSubrange(
+        internal::kOmniboxEventLimit,
+        uma_proto_.omnibox_event_size() - internal::kOmniboxEventLimit);
+  }
+}
+
+void MetricsLog::GetEncodedLog(std::string* encoded_log) {
+  DCHECK(closed_);
+  uma_proto_.SerializeToString(encoded_log);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_log.h b/components/metrics/metrics_log.h
new file mode 100644
index 0000000..4cedd66
--- /dev/null
+++ b/components/metrics/metrics_log.h
@@ -0,0 +1,186 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines a set of user experience metrics data recorded by
+// the MetricsService.  This is the unit of data that is sent to the server.
+
+#ifndef COMPONENTS_METRICS_METRICS_LOG_H_
+#define COMPONENTS_METRICS_METRICS_LOG_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_service_client.h"
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
+
+class PrefService;
+
+namespace base {
+class HistogramSamples;
+}
+
+namespace metrics {
+
+class MetricsProvider;
+class MetricsServiceClient;
+class DelegatingProvider;
+
+namespace internal {
+// Maximum number of events before truncation.
+constexpr int kOmniboxEventLimit = 5000;
+constexpr int kUserActionEventLimit = 5000;
+}  // namespace internal
+
+class MetricsLog {
+ public:
+  enum LogType {
+    INITIAL_STABILITY_LOG,  // The initial log containing stability stats.
+    ONGOING_LOG,            // Subsequent logs in a session.
+    INDEPENDENT_LOG,        // An independent log from a previous session.
+  };
+
+  // Creates a new metrics log of the specified type.
+  // |client_id| is the identifier for this profile on this installation
+  // |session_id| is an integer that's incremented on each application launch
+  // |client| is used to interact with the embedder.
+  // |local_state| is the PrefService that this instance should use.
+  // Note: |this| instance does not take ownership of the |client|, but rather
+  // stores a weak pointer to it. The caller should ensure that the |client| is
+  // valid for the lifetime of this class.
+  MetricsLog(const std::string& client_id,
+             int session_id,
+             LogType log_type,
+             MetricsServiceClient* client);
+  virtual ~MetricsLog();
+
+  // Registers local state prefs used by this class.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+  // Computes the MD5 hash of the given string, and returns the first 8 bytes of
+  // the hash.
+  static uint64_t Hash(const std::string& value);
+
+  // Get the GMT buildtime for the current binary, expressed in seconds since
+  // January 1, 1970 GMT.
+  // The value is used to identify when a new build is run, so that previous
+  // reliability stats, from other builds, can be abandoned.
+  static int64_t GetBuildTime();
+
+  // Convenience function to return the current time at a resolution in seconds.
+  // This wraps base::TimeTicks, and hence provides an abstract time that is
+  // always incrementing for use in measuring time durations.
+  static int64_t GetCurrentTime();
+
+  // Record core profile settings into the SystemProfileProto.
+  static void RecordCoreSystemProfile(MetricsServiceClient* client,
+                                      SystemProfileProto* system_profile);
+
+  // Records a user-initiated action.
+  void RecordUserAction(const std::string& key);
+
+  // Record any changes in a given histogram for transmission.
+  void RecordHistogramDelta(const std::string& histogram_name,
+                            const base::HistogramSamples& snapshot);
+
+  // TODO(rkaplow): I think this can be a little refactored as it currently
+  // records a pretty arbitrary set of things.
+  // Records the current operating environment, including metrics provided by
+  // the specified |delegating_provider|. The current environment is
+  // returned as a SystemProfileProto.
+  const SystemProfileProto& RecordEnvironment(
+      DelegatingProvider* delegating_provider);
+
+  // Loads a saved system profile and the associated metrics into the log.
+  // Returns true on success. Keep calling it with fresh logs until it returns
+  // false.
+  bool LoadIndependentMetrics(MetricsProvider* metrics_provider);
+
+  // Loads the environment proto that was saved by the last RecordEnvironment()
+  // call from prefs. On success, returns true and |app_version| contains the
+  // recovered version. Otherwise (if there was no saved environment in prefs
+  // or it could not be decoded), returns false and |app_version| is empty.
+  bool LoadSavedEnvironmentFromPrefs(PrefService* local_state,
+                                     std::string* app_version);
+
+  // Record data from providers about the previous session into the log.
+  void RecordPreviousSessionData(DelegatingProvider* delegating_provider);
+
+  // Record data from providers about the current session into the log.
+  void RecordCurrentSessionData(DelegatingProvider* delegating_provider,
+                                base::TimeDelta incremental_uptime,
+                                base::TimeDelta uptime);
+
+  // Stop writing to this record and generate the encoded representation.
+  // None of the Record* methods can be called after this is called.
+  void CloseLog();
+
+  // Truncate some of the fields within the log that we want to restrict in
+  // size due to bandwidth concerns.
+  void TruncateEvents();
+
+  // Fills |encoded_log| with the serialized protobuf representation of the
+  // record.  Must only be called after CloseLog() has been called.
+  void GetEncodedLog(std::string* encoded_log);
+
+  const base::TimeTicks& creation_time() const {
+    return creation_time_;
+  }
+
+  LogType log_type() const { return log_type_; }
+
+ protected:
+  // Exposed for the sake of mocking/accessing in test code.
+
+  ChromeUserMetricsExtension* uma_proto() { return &uma_proto_; }
+
+  // Exposed to allow subclass to access to export the uma_proto. Can be used
+  // by external components to export logs to Chrome.
+  const ChromeUserMetricsExtension* uma_proto() const {
+    return &uma_proto_;
+  }
+
+ private:
+  // Write the default state of the enable metrics checkbox.
+  void WriteMetricsEnableDefault(EnableMetricsDefault metrics_default,
+                                 SystemProfileProto* system_profile);
+
+  // Within the stability group, write attributes that need to be updated asap
+  // and can't be delayed until the user decides to restart chromium.
+  // Delaying these stats would bias metrics away from happy long lived
+  // chromium processes (ones that don't crash, and keep on running).
+  void WriteRealtimeStabilityAttributes(base::TimeDelta incremental_uptime,
+                                        base::TimeDelta uptime);
+
+  // closed_ is true when record has been packed up for sending, and should
+  // no longer be written to.  It is only used for sanity checking.
+  bool closed_;
+
+  // The type of the log, i.e. initial or ongoing.
+  const LogType log_type_;
+
+  // Stores the protocol buffer representation for this log.
+  ChromeUserMetricsExtension uma_proto_;
+
+  // Used to interact with the embedder. Weak pointer; must outlive |this|
+  // instance.
+  MetricsServiceClient* const client_;
+
+  // The time when the current log was created.
+  const base::TimeTicks creation_time_;
+
+  // True if the environment has already been filled in by a call to
+  // RecordEnvironment() or LoadSavedEnvironmentFromPrefs().
+  bool has_environment_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsLog);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_LOG_H_
diff --git a/components/metrics/metrics_log_manager.cc b/components/metrics/metrics_log_manager.cc
new file mode 100644
index 0000000..d90aa7b
--- /dev/null
+++ b/components/metrics/metrics_log_manager.cc
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log_manager.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/strings/string_util.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_log_store.h"
+#include "components/metrics/metrics_pref_names.h"
+
+namespace metrics {
+
+MetricsLogManager::MetricsLogManager() {}
+
+MetricsLogManager::~MetricsLogManager() {}
+
+void MetricsLogManager::BeginLoggingWithLog(std::unique_ptr<MetricsLog> log) {
+  DCHECK(!current_log_);
+  current_log_ = std::move(log);
+}
+
+void MetricsLogManager::FinishCurrentLog(MetricsLogStore* log_store) {
+  DCHECK(current_log_);
+  current_log_->CloseLog();
+  std::string log_data;
+  current_log_->GetEncodedLog(&log_data);
+  if (!log_data.empty())
+    log_store->StoreLog(log_data, current_log_->log_type());
+  current_log_.reset();
+}
+
+void MetricsLogManager::DiscardCurrentLog() {
+  current_log_->CloseLog();
+  current_log_.reset();
+}
+
+void MetricsLogManager::PauseCurrentLog() {
+  DCHECK(!paused_log_);
+  paused_log_ = std::move(current_log_);
+}
+
+void MetricsLogManager::ResumePausedLog() {
+  DCHECK(!current_log_);
+  current_log_ = std::move(paused_log_);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_log_manager.h b/components/metrics/metrics_log_manager.h
new file mode 100644
index 0000000..3227617
--- /dev/null
+++ b/components/metrics/metrics_log_manager.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_
+#define COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "components/metrics/metrics_log.h"
+
+namespace metrics {
+
+class MetricsLogStore;
+
+// Manages all the log objects used by a MetricsService implementation. Keeps
+// track of an in-progress log and a paused log.
+class MetricsLogManager {
+ public:
+  MetricsLogManager();
+  ~MetricsLogManager();
+
+  // Makes |log| the current_log. This should only be called if there is not a
+  // current log.
+  void BeginLoggingWithLog(std::unique_ptr<MetricsLog> log);
+
+  // Returns the in-progress log.
+  MetricsLog* current_log() { return current_log_.get(); }
+
+  // Closes |current_log_|, compresses it, and stores it in the |log_store| for
+  // later, leaving |current_log_| NULL.
+  void FinishCurrentLog(MetricsLogStore* log_store);
+
+  // Closes and discards |current_log|.
+  void DiscardCurrentLog();
+
+  // Sets current_log to NULL, but saves the current log for future use with
+  // ResumePausedLog(). Only one log may be paused at a time.
+  // TODO(stuartmorgan): Pause/resume support is really a workaround for a
+  // design issue in initial log writing; that should be fixed, and pause/resume
+  // removed.
+  void PauseCurrentLog();
+
+  // Restores the previously paused log (if any) to current_log().
+  // This should only be called if there is not a current log.
+  void ResumePausedLog();
+
+ private:
+  // The log that we are still appending to.
+  std::unique_ptr<MetricsLog> current_log_;
+
+  // A paused, previously-current log.
+  std::unique_ptr<MetricsLog> paused_log_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsLogManager);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_
diff --git a/components/metrics/metrics_log_manager_unittest.cc b/components/metrics/metrics_log_manager_unittest.cc
new file mode 100644
index 0000000..4e3004e
--- /dev/null
+++ b/components/metrics/metrics_log_manager_unittest.cc
@@ -0,0 +1,127 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log_manager.h"
+
+#include <stddef.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/memory/ptr_util.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_log_store.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/persisted_logs_metrics_impl.h"
+#include "components/metrics/test_metrics_service_client.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+class MetricsLogManagerTest : public testing::Test {
+ public:
+  MetricsLogManagerTest() : log_store_(&pref_service_, 0) {
+    MetricsLogStore::RegisterPrefs(pref_service_.registry());
+    log_store()->LoadPersistedUnsentLogs();
+  }
+  ~MetricsLogManagerTest() override {}
+
+  MetricsLogStore* log_store() { return &log_store_; }
+
+  MetricsLog* CreateLog(MetricsLog::LogType log_type) {
+    return new MetricsLog("id", 0, log_type, &client_);
+  }
+
+ private:
+  TestMetricsServiceClient client_;
+  TestingPrefServiceSimple pref_service_;
+  MetricsLogStore log_store_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsLogManagerTest);
+};
+
+}  // namespace
+
+TEST_F(MetricsLogManagerTest, StandardFlow) {
+  MetricsLogManager log_manager;
+
+  // Make sure a new manager has a clean slate.
+  EXPECT_EQ(nullptr, log_manager.current_log());
+
+  // Check that the normal flow works.
+  MetricsLog* initial_log = CreateLog(MetricsLog::INITIAL_STABILITY_LOG);
+  log_manager.BeginLoggingWithLog(base::WrapUnique(initial_log));
+  EXPECT_EQ(initial_log, log_manager.current_log());
+
+  EXPECT_FALSE(log_store()->has_unsent_logs());
+  log_manager.FinishCurrentLog(log_store());
+  EXPECT_EQ(nullptr, log_manager.current_log());
+  EXPECT_TRUE(log_store()->has_unsent_logs());
+
+  MetricsLog* second_log = CreateLog(MetricsLog::ONGOING_LOG);
+  log_manager.BeginLoggingWithLog(base::WrapUnique(second_log));
+  EXPECT_EQ(second_log, log_manager.current_log());
+}
+
+TEST_F(MetricsLogManagerTest, AbandonedLog) {
+  MetricsLogManager log_manager;
+
+  MetricsLog* dummy_log = CreateLog(MetricsLog::INITIAL_STABILITY_LOG);
+  log_manager.BeginLoggingWithLog(base::WrapUnique(dummy_log));
+  EXPECT_EQ(dummy_log, log_manager.current_log());
+
+  log_manager.DiscardCurrentLog();
+  EXPECT_EQ(nullptr, log_manager.current_log());
+}
+
+// Make sure that interjecting logs updates the "current" log correctly.
+TEST_F(MetricsLogManagerTest, InterjectedLog) {
+  MetricsLogManager log_manager;
+
+  MetricsLog* ongoing_log = CreateLog(MetricsLog::ONGOING_LOG);
+  MetricsLog* temp_log = CreateLog(MetricsLog::INITIAL_STABILITY_LOG);
+
+  log_manager.BeginLoggingWithLog(base::WrapUnique(ongoing_log));
+  EXPECT_EQ(ongoing_log, log_manager.current_log());
+
+  log_manager.PauseCurrentLog();
+  EXPECT_EQ(nullptr, log_manager.current_log());
+
+  log_manager.BeginLoggingWithLog(base::WrapUnique(temp_log));
+  EXPECT_EQ(temp_log, log_manager.current_log());
+  log_manager.FinishCurrentLog(log_store());
+  EXPECT_EQ(nullptr, log_manager.current_log());
+
+  log_manager.ResumePausedLog();
+  EXPECT_EQ(ongoing_log, log_manager.current_log());
+}
+
+// Make sure that when one log is interjected by another, that finishing them
+// creates logs of the correct type.
+TEST_F(MetricsLogManagerTest, InterjectedLogPreservesType) {
+  MetricsLogManager log_manager;
+
+  log_manager.BeginLoggingWithLog(
+      base::WrapUnique(CreateLog(MetricsLog::ONGOING_LOG)));
+  log_manager.PauseCurrentLog();
+  log_manager.BeginLoggingWithLog(
+      base::WrapUnique(CreateLog(MetricsLog::INITIAL_STABILITY_LOG)));
+  log_manager.FinishCurrentLog(log_store());
+  log_manager.ResumePausedLog();
+  // Finishing the interjecting inital log should have stored an initial log.
+  EXPECT_EQ(1U, log_store()->initial_log_count());
+  EXPECT_EQ(0U, log_store()->ongoing_log_count());
+
+  // Finishing the interjected ongoing log should store an ongoing log.
+  log_manager.FinishCurrentLog(log_store());
+  EXPECT_EQ(1U, log_store()->initial_log_count());
+  EXPECT_EQ(1U, log_store()->ongoing_log_count());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_log_store.cc b/components/metrics/metrics_log_store.cc
new file mode 100644
index 0000000..30cbe6c
--- /dev/null
+++ b/components/metrics/metrics_log_store.cc
@@ -0,0 +1,129 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log_store.h"
+
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/persisted_logs_metrics_impl.h"
+#include "components/prefs/pref_registry_simple.h"
+
+namespace metrics {
+
+namespace {
+
+// The number of "initial" logs to save, and hope to send during a future Chrome
+// session.  Initial logs contain crash stats, and are pretty small.
+const size_t kInitialLogsPersistLimit = 20;
+
+// The number of ongoing logs to save persistently, and hope to
+// send during a this or future sessions.  Note that each log may be pretty
+// large, as presumably the related "initial" log wasn't sent (probably nothing
+// was, as the user was probably off-line).  As a result, the log probably kept
+// accumulating while the "initial" log was stalled, and couldn't be sent.  As a
+// result, we don't want to save too many of these mega-logs.
+// A "standard shutdown" will create a small log, including just the data that
+// was not yet been transmitted, and that is normal (to have exactly one
+// ongoing_log_ at startup).
+const size_t kOngoingLogsPersistLimit = 8;
+
+// The number of bytes of logs to save of each type (initial/ongoing).
+// This ensures that a reasonable amount of history will be stored even if there
+// is a long series of very small logs.
+const size_t kStorageByteLimitPerLogType = 300 * 1000;  // ~300kB
+
+}  // namespace
+
+// static
+void MetricsLogStore::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterListPref(prefs::kMetricsInitialLogs);
+  registry->RegisterListPref(prefs::kMetricsOngoingLogs);
+}
+
+MetricsLogStore::MetricsLogStore(PrefService* local_state,
+                                 size_t max_ongoing_log_size)
+    : unsent_logs_loaded_(false),
+      initial_log_queue_(std::unique_ptr<PersistedLogsMetricsImpl>(
+                             new PersistedLogsMetricsImpl()),
+                         local_state,
+                         prefs::kMetricsInitialLogs,
+                         kInitialLogsPersistLimit,
+                         kStorageByteLimitPerLogType,
+                         0),
+      ongoing_log_queue_(std::unique_ptr<PersistedLogsMetricsImpl>(
+                             new PersistedLogsMetricsImpl()),
+                         local_state,
+                         prefs::kMetricsOngoingLogs,
+                         kOngoingLogsPersistLimit,
+                         kStorageByteLimitPerLogType,
+                         max_ongoing_log_size) {}
+
+MetricsLogStore::~MetricsLogStore() {}
+
+void MetricsLogStore::LoadPersistedUnsentLogs() {
+  initial_log_queue_.LoadPersistedUnsentLogs();
+  ongoing_log_queue_.LoadPersistedUnsentLogs();
+  unsent_logs_loaded_ = true;
+}
+
+void MetricsLogStore::StoreLog(const std::string& log_data,
+                               MetricsLog::LogType log_type) {
+  switch (log_type) {
+    case MetricsLog::INITIAL_STABILITY_LOG:
+      initial_log_queue_.StoreLog(log_data);
+      break;
+    case MetricsLog::ONGOING_LOG:
+    case MetricsLog::INDEPENDENT_LOG:
+      ongoing_log_queue_.StoreLog(log_data);
+      break;
+  }
+}
+
+bool MetricsLogStore::has_unsent_logs() const {
+  return initial_log_queue_.has_unsent_logs() ||
+         ongoing_log_queue_.has_unsent_logs();
+}
+
+bool MetricsLogStore::has_staged_log() const {
+  return initial_log_queue_.has_staged_log() ||
+         ongoing_log_queue_.has_staged_log();
+}
+
+const std::string& MetricsLogStore::staged_log() const {
+  return initial_log_queue_.has_staged_log() ? initial_log_queue_.staged_log()
+                                             : ongoing_log_queue_.staged_log();
+}
+
+const std::string& MetricsLogStore::staged_log_hash() const {
+  return initial_log_queue_.has_staged_log()
+             ? initial_log_queue_.staged_log_hash()
+             : ongoing_log_queue_.staged_log_hash();
+}
+
+void MetricsLogStore::StageNextLog() {
+  DCHECK(!has_staged_log());
+  if (initial_log_queue_.has_unsent_logs())
+    initial_log_queue_.StageNextLog();
+  else
+    ongoing_log_queue_.StageNextLog();
+}
+
+void MetricsLogStore::DiscardStagedLog() {
+  DCHECK(has_staged_log());
+  if (initial_log_queue_.has_staged_log())
+    initial_log_queue_.DiscardStagedLog();
+  else
+    ongoing_log_queue_.DiscardStagedLog();
+  DCHECK(!has_staged_log());
+}
+
+void MetricsLogStore::PersistUnsentLogs() const {
+  DCHECK(unsent_logs_loaded_);
+  if (!unsent_logs_loaded_)
+    return;
+
+  initial_log_queue_.PersistUnsentLogs();
+  ongoing_log_queue_.PersistUnsentLogs();
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_log_store.h b/components/metrics/metrics_log_store.h
new file mode 100644
index 0000000..b9658d6
--- /dev/null
+++ b/components/metrics/metrics_log_store.h
@@ -0,0 +1,67 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_LOG_STORE_H_
+#define COMPONENTS_METRICS_METRICS_LOG_STORE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/metrics/log_store.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/persisted_logs.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace metrics {
+
+// A LogStore implementation for storing UMA logs.
+// This implementation keeps track of two types of logs, initial and ongoing,
+// each stored in PersistedLogs.  It prioritizes staging initial logs over
+// ongoing logs.
+class MetricsLogStore : public LogStore {
+ public:
+  // Constructs a MetricsLogStore that persists data into |local_state|.
+  // If max_log_size is non-zero, it will not persist ongoing logs larger than
+  // |max_ongoing_log_size| bytes.
+  MetricsLogStore(PrefService* local_state, size_t max_ongoing_log_size);
+  ~MetricsLogStore();
+
+  // Registers local state prefs used by this class.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+  // Saves |log_data| as the given type.
+  void StoreLog(const std::string& log_data, MetricsLog::LogType log_type);
+
+  // LogStore:
+  bool has_unsent_logs() const override;
+  bool has_staged_log() const override;
+  const std::string& staged_log() const override;
+  const std::string& staged_log_hash() const override;
+  void StageNextLog() override;
+  void DiscardStagedLog() override;
+  void PersistUnsentLogs() const override;
+  void LoadPersistedUnsentLogs() override;
+
+  // Inspection methods for tests.
+  size_t ongoing_log_count() const { return ongoing_log_queue_.size(); }
+  size_t initial_log_count() const { return initial_log_queue_.size(); }
+
+ private:
+  // Tracks whether unsent logs (if any) have been loaded from the serializer.
+  bool unsent_logs_loaded_;
+
+  // Logs stored with the INITIAL_STABILITY_LOG type that haven't been sent yet.
+  // These logs will be staged first when staging new logs.
+  PersistedLogs initial_log_queue_;
+  // Logs stored with the ONGOING_LOG type that haven't been sent yet.
+  PersistedLogs ongoing_log_queue_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsLogStore);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_LOG_STORE_H_
diff --git a/components/metrics/metrics_log_store_unittest.cc b/components/metrics/metrics_log_store_unittest.cc
new file mode 100644
index 0000000..50b78c7
--- /dev/null
+++ b/components/metrics/metrics_log_store_unittest.cc
@@ -0,0 +1,194 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log_store.h"
+
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/test_metrics_service_client.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+class MetricsLogStoreTest : public testing::Test {
+ public:
+  MetricsLogStoreTest() {
+    MetricsLogStore::RegisterPrefs(pref_service_.registry());
+  }
+  ~MetricsLogStoreTest() override {}
+
+  MetricsLog* CreateLog(MetricsLog::LogType log_type) {
+    return new MetricsLog("id", 0, log_type, &client_);
+  }
+
+  // Returns the stored number of logs of the given type.
+  size_t TypeCount(MetricsLog::LogType log_type) {
+    const char* pref = log_type == MetricsLog::INITIAL_STABILITY_LOG
+                           ? prefs::kMetricsInitialLogs
+                           : prefs::kMetricsOngoingLogs;
+    return pref_service_.GetList(pref)->GetSize();
+  }
+
+  TestMetricsServiceClient client_;
+  TestingPrefServiceSimple pref_service_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MetricsLogStoreTest);
+};
+
+}  // namespace
+
+TEST_F(MetricsLogStoreTest, StandardFlow) {
+  MetricsLogStore log_store(&pref_service_, 0);
+  log_store.LoadPersistedUnsentLogs();
+
+  // Make sure a new manager has a clean slate.
+  EXPECT_FALSE(log_store.has_staged_log());
+  EXPECT_FALSE(log_store.has_unsent_logs());
+
+  log_store.StoreLog("a", MetricsLog::ONGOING_LOG);
+  EXPECT_TRUE(log_store.has_unsent_logs());
+  EXPECT_FALSE(log_store.has_staged_log());
+
+  log_store.StageNextLog();
+  EXPECT_TRUE(log_store.has_staged_log());
+  EXPECT_FALSE(log_store.staged_log().empty());
+
+  log_store.DiscardStagedLog();
+  EXPECT_FALSE(log_store.has_staged_log());
+  EXPECT_FALSE(log_store.has_unsent_logs());
+}
+
+TEST_F(MetricsLogStoreTest, StoreAndLoad) {
+  // Set up some in-progress logging in a scoped log manager simulating the
+  // leadup to quitting, then persist as would be done on quit.
+  {
+    MetricsLogStore log_store(&pref_service_, 0);
+    log_store.LoadPersistedUnsentLogs();
+    EXPECT_FALSE(log_store.has_unsent_logs());
+    log_store.StoreLog("a", MetricsLog::ONGOING_LOG);
+    log_store.PersistUnsentLogs();
+    EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+    EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG));
+  }
+
+  // Relaunch load and store more logs.
+  {
+    MetricsLogStore log_store(&pref_service_, 0);
+    log_store.LoadPersistedUnsentLogs();
+    EXPECT_TRUE(log_store.has_unsent_logs());
+    EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+    EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG));
+    log_store.StoreLog("x", MetricsLog::INITIAL_STABILITY_LOG);
+    log_store.StageNextLog();
+    log_store.StoreLog("b", MetricsLog::ONGOING_LOG);
+
+    EXPECT_TRUE(log_store.has_unsent_logs());
+    EXPECT_TRUE(log_store.has_staged_log());
+    EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+    EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG));
+
+    log_store.PersistUnsentLogs();
+    EXPECT_EQ(1U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+    EXPECT_EQ(2U, TypeCount(MetricsLog::ONGOING_LOG));
+  }
+
+  // Relaunch and verify that once logs are handled they are not re-persisted.
+  {
+    MetricsLogStore log_store(&pref_service_, 0);
+    log_store.LoadPersistedUnsentLogs();
+    EXPECT_TRUE(log_store.has_unsent_logs());
+
+    log_store.StageNextLog();
+    log_store.DiscardStagedLog();
+    // The initial log should be sent first; update the persisted storage to
+    // verify.
+    log_store.PersistUnsentLogs();
+    EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+    EXPECT_EQ(2U, TypeCount(MetricsLog::ONGOING_LOG));
+
+    // Handle the first ongoing log.
+    log_store.StageNextLog();
+    log_store.DiscardStagedLog();
+    EXPECT_TRUE(log_store.has_unsent_logs());
+
+    // Handle the last log.
+    log_store.StageNextLog();
+    log_store.DiscardStagedLog();
+    EXPECT_FALSE(log_store.has_unsent_logs());
+
+    // Nothing should have changed "on disk" since PersistUnsentLogs hasn't been
+    // called again.
+    EXPECT_EQ(2U, TypeCount(MetricsLog::ONGOING_LOG));
+    // Persist, and make sure nothing is left.
+    log_store.PersistUnsentLogs();
+    EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+    EXPECT_EQ(0U, TypeCount(MetricsLog::ONGOING_LOG));
+  }
+}
+
+TEST_F(MetricsLogStoreTest, StoreStagedOngoingLog) {
+  // Ensure that types are preserved when storing staged logs.
+  MetricsLogStore log_store(&pref_service_, 0);
+  log_store.LoadPersistedUnsentLogs();
+  log_store.StoreLog("a", MetricsLog::ONGOING_LOG);
+  log_store.StageNextLog();
+  log_store.PersistUnsentLogs();
+
+  EXPECT_EQ(0U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+  EXPECT_EQ(1U, TypeCount(MetricsLog::ONGOING_LOG));
+}
+
+TEST_F(MetricsLogStoreTest, StoreStagedInitialLog) {
+  // Ensure that types are preserved when storing staged logs.
+  MetricsLogStore log_store(&pref_service_, 0);
+  log_store.LoadPersistedUnsentLogs();
+  log_store.StoreLog("b", MetricsLog::INITIAL_STABILITY_LOG);
+  log_store.StageNextLog();
+  log_store.PersistUnsentLogs();
+
+  EXPECT_EQ(1U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+  EXPECT_EQ(0U, TypeCount(MetricsLog::ONGOING_LOG));
+}
+
+TEST_F(MetricsLogStoreTest, LargeLogDiscarding) {
+  // Set the size threshold very low, to verify that it's honored.
+  MetricsLogStore log_store(&pref_service_, 1);
+  log_store.LoadPersistedUnsentLogs();
+
+  log_store.StoreLog("persisted", MetricsLog::INITIAL_STABILITY_LOG);
+  log_store.StoreLog("not_persisted", MetricsLog::ONGOING_LOG);
+
+  // Only the stability log should be written out, due to the threshold.
+  log_store.PersistUnsentLogs();
+  EXPECT_EQ(1U, TypeCount(MetricsLog::INITIAL_STABILITY_LOG));
+  EXPECT_EQ(0U, TypeCount(MetricsLog::ONGOING_LOG));
+}
+
+TEST_F(MetricsLogStoreTest, DiscardOrder) {
+  // Ensure that the correct log is discarded if new logs are pushed while
+  // a log is staged.
+  MetricsLogStore log_store(&pref_service_, 0);
+  log_store.LoadPersistedUnsentLogs();
+
+  log_store.StoreLog("a", MetricsLog::ONGOING_LOG);
+  log_store.StoreLog("b", MetricsLog::ONGOING_LOG);
+  log_store.StageNextLog();
+  log_store.StoreLog("c", MetricsLog::INITIAL_STABILITY_LOG);
+  EXPECT_EQ(2U, log_store.ongoing_log_count());
+  EXPECT_EQ(1U, log_store.initial_log_count());
+  // Should discard the ongoing log staged earlier.
+  log_store.DiscardStagedLog();
+  EXPECT_EQ(1U, log_store.ongoing_log_count());
+  EXPECT_EQ(1U, log_store.initial_log_count());
+  // Initial log should be staged next.
+  log_store.StageNextLog();
+  log_store.DiscardStagedLog();
+  EXPECT_EQ(1U, log_store.ongoing_log_count());
+  EXPECT_EQ(0U, log_store.initial_log_count());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_log_unittest.cc b/components/metrics/metrics_log_unittest.cc
new file mode 100644
index 0000000..8af49a8
--- /dev/null
+++ b/components/metrics/metrics_log_unittest.cc
@@ -0,0 +1,360 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_log.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <string>
+
+#include "base/base64.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/bucket_ranges.h"
+#include "base/metrics/sample_vector.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "base/time/time.h"
+#include "components/metrics/delegating_provider.h"
+#include "components/metrics/environment_recorder.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/metrics/test_metrics_provider.h"
+#include "components/metrics/test_metrics_service_client.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/variations/active_field_trials.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/build_info.h"
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/current_module.h"
+#endif
+
+namespace metrics {
+
+namespace {
+
+const char kClientId[] = "bogus client ID";
+const int kSessionId = 127;
+
+class TestMetricsLog : public MetricsLog {
+ public:
+  TestMetricsLog(const std::string& client_id,
+                 int session_id,
+                 LogType log_type,
+                 MetricsServiceClient* client)
+      : MetricsLog(client_id, session_id, log_type, client) {}
+
+  ~TestMetricsLog() override {}
+
+  const ChromeUserMetricsExtension& uma_proto() const {
+    return *MetricsLog::uma_proto();
+  }
+
+  ChromeUserMetricsExtension* mutable_uma_proto() {
+    return MetricsLog::uma_proto();
+  }
+
+  const SystemProfileProto& system_profile() const {
+    return uma_proto().system_profile();
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestMetricsLog);
+};
+
+}  // namespace
+
+class MetricsLogTest : public testing::Test {
+ public:
+  MetricsLogTest() {}
+  ~MetricsLogTest() override {}
+
+ protected:
+  // Check that the values in |system_values| correspond to the test data
+  // defined at the top of this file.
+  void CheckSystemProfile(const SystemProfileProto& system_profile) {
+    EXPECT_EQ(TestMetricsServiceClient::kBrandForTesting,
+              system_profile.brand_code());
+
+    const SystemProfileProto::Hardware& hardware =
+        system_profile.hardware();
+
+    EXPECT_TRUE(hardware.has_cpu());
+    EXPECT_TRUE(hardware.cpu().has_vendor_name());
+    EXPECT_TRUE(hardware.cpu().has_signature());
+    EXPECT_TRUE(hardware.cpu().has_num_cores());
+
+    // TODO(isherman): Verify other data written into the protobuf as a result
+    // of this call.
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MetricsLogTest);
+};
+
+TEST_F(MetricsLogTest, LogType) {
+  TestMetricsServiceClient client;
+  TestingPrefServiceSimple prefs;
+
+  MetricsLog log1("id", 0, MetricsLog::ONGOING_LOG, &client);
+  EXPECT_EQ(MetricsLog::ONGOING_LOG, log1.log_type());
+
+  MetricsLog log2("id", 0, MetricsLog::INITIAL_STABILITY_LOG, &client);
+  EXPECT_EQ(MetricsLog::INITIAL_STABILITY_LOG, log2.log_type());
+}
+
+TEST_F(MetricsLogTest, BasicRecord) {
+  TestMetricsServiceClient client;
+  client.set_version_string("bogus version");
+  TestingPrefServiceSimple prefs;
+  MetricsLog log("totally bogus client ID", 137, MetricsLog::ONGOING_LOG,
+                 &client);
+  log.CloseLog();
+
+  std::string encoded;
+  log.GetEncodedLog(&encoded);
+
+  // A couple of fields are hard to mock, so these will be copied over directly
+  // for the expected output.
+  ChromeUserMetricsExtension parsed;
+  ASSERT_TRUE(parsed.ParseFromString(encoded));
+
+  ChromeUserMetricsExtension expected;
+  expected.set_client_id(5217101509553811875);  // Hashed bogus client ID
+  expected.set_session_id(137);
+
+  SystemProfileProto* system_profile = expected.mutable_system_profile();
+  system_profile->set_app_version("bogus version");
+  system_profile->set_channel(client.GetChannel());
+  system_profile->set_application_locale(client.GetApplicationLocale());
+
+#if defined(ADDRESS_SANITIZER)
+  system_profile->set_is_asan_build(true);
+#endif
+  metrics::SystemProfileProto::Hardware* hardware =
+      system_profile->mutable_hardware();
+#if !defined(OS_IOS)
+  hardware->set_cpu_architecture(base::SysInfo::OperatingSystemArchitecture());
+#endif
+  hardware->set_system_ram_mb(base::SysInfo::AmountOfPhysicalMemoryMB());
+  hardware->set_hardware_class(base::SysInfo::HardwareModelName());
+#if defined(OS_WIN)
+  hardware->set_dll_base(reinterpret_cast<uint64_t>(CURRENT_MODULE()));
+#endif
+
+  system_profile->mutable_os()->set_name(base::SysInfo::OperatingSystemName());
+  system_profile->mutable_os()->set_version(
+      base::SysInfo::OperatingSystemVersion());
+#if defined(OS_CHROMEOS)
+  system_profile->mutable_os()->set_kernel_version(
+      base::SysInfo::KernelVersion());
+#elif defined(OS_ANDROID)
+  system_profile->mutable_os()->set_build_fingerprint(
+      base::android::BuildInfo::GetInstance()->android_build_fp());
+  system_profile->set_app_package_name("test app");
+#endif
+
+  // Hard to mock.
+  system_profile->set_build_timestamp(
+      parsed.system_profile().build_timestamp());
+
+  EXPECT_EQ(expected.SerializeAsString(), encoded);
+}
+
+TEST_F(MetricsLogTest, HistogramBucketFields) {
+  // Create buckets: 1-5, 5-7, 7-8, 8-9, 9-10, 10-11, 11-12.
+  base::BucketRanges ranges(8);
+  ranges.set_range(0, 1);
+  ranges.set_range(1, 5);
+  ranges.set_range(2, 7);
+  ranges.set_range(3, 8);
+  ranges.set_range(4, 9);
+  ranges.set_range(5, 10);
+  ranges.set_range(6, 11);
+  ranges.set_range(7, 12);
+
+  base::SampleVector samples(1, &ranges);
+  samples.Accumulate(3, 1);   // Bucket 1-5.
+  samples.Accumulate(6, 1);   // Bucket 5-7.
+  samples.Accumulate(8, 1);   // Bucket 8-9. (7-8 skipped)
+  samples.Accumulate(10, 1);  // Bucket 10-11. (9-10 skipped)
+  samples.Accumulate(11, 1);  // Bucket 11-12.
+
+  TestMetricsServiceClient client;
+  TestingPrefServiceSimple prefs;
+  TestMetricsLog log(kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client);
+  log.RecordHistogramDelta("Test", samples);
+
+  const ChromeUserMetricsExtension& uma_proto = log.uma_proto();
+  const HistogramEventProto& histogram_proto =
+      uma_proto.histogram_event(uma_proto.histogram_event_size() - 1);
+
+  // Buckets with samples: 1-5, 5-7, 8-9, 10-11, 11-12.
+  // Should become: 1-/, 5-7, /-9, 10-/, /-12.
+  ASSERT_EQ(5, histogram_proto.bucket_size());
+
+  // 1-5 becomes 1-/ (max is same as next min).
+  EXPECT_TRUE(histogram_proto.bucket(0).has_min());
+  EXPECT_FALSE(histogram_proto.bucket(0).has_max());
+  EXPECT_EQ(1, histogram_proto.bucket(0).min());
+
+  // 5-7 stays 5-7 (no optimization possible).
+  EXPECT_TRUE(histogram_proto.bucket(1).has_min());
+  EXPECT_TRUE(histogram_proto.bucket(1).has_max());
+  EXPECT_EQ(5, histogram_proto.bucket(1).min());
+  EXPECT_EQ(7, histogram_proto.bucket(1).max());
+
+  // 8-9 becomes /-9 (min is same as max - 1).
+  EXPECT_FALSE(histogram_proto.bucket(2).has_min());
+  EXPECT_TRUE(histogram_proto.bucket(2).has_max());
+  EXPECT_EQ(9, histogram_proto.bucket(2).max());
+
+  // 10-11 becomes 10-/ (both optimizations apply, omit max is prioritized).
+  EXPECT_TRUE(histogram_proto.bucket(3).has_min());
+  EXPECT_FALSE(histogram_proto.bucket(3).has_max());
+  EXPECT_EQ(10, histogram_proto.bucket(3).min());
+
+  // 11-12 becomes /-12 (last record must keep max, min is same as max - 1).
+  EXPECT_FALSE(histogram_proto.bucket(4).has_min());
+  EXPECT_TRUE(histogram_proto.bucket(4).has_max());
+  EXPECT_EQ(12, histogram_proto.bucket(4).max());
+}
+
+TEST_F(MetricsLogTest, RecordEnvironment) {
+  TestMetricsServiceClient client;
+  TestMetricsLog log(kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client);
+
+  DelegatingProvider delegating_provider;
+  log.RecordEnvironment(&delegating_provider);
+  // Check that the system profile on the log has the correct values set.
+  CheckSystemProfile(log.system_profile());
+}
+
+TEST_F(MetricsLogTest, RecordEnvironmentEnableDefault) {
+  TestMetricsServiceClient client;
+  TestMetricsLog log_unknown(kClientId, kSessionId, MetricsLog::ONGOING_LOG,
+                             &client);
+
+  DelegatingProvider delegating_provider;
+  log_unknown.RecordEnvironment(&delegating_provider);
+  EXPECT_FALSE(log_unknown.system_profile().has_uma_default_state());
+
+  client.set_enable_default(EnableMetricsDefault::OPT_IN);
+  TestMetricsLog log_opt_in(kClientId, kSessionId, MetricsLog::ONGOING_LOG,
+                            &client);
+  log_opt_in.RecordEnvironment(&delegating_provider);
+  EXPECT_TRUE(log_opt_in.system_profile().has_uma_default_state());
+  EXPECT_EQ(SystemProfileProto_UmaDefaultState_OPT_IN,
+            log_opt_in.system_profile().uma_default_state());
+
+  client.set_enable_default(EnableMetricsDefault::OPT_OUT);
+  TestMetricsLog log_opt_out(kClientId, kSessionId, MetricsLog::ONGOING_LOG,
+                             &client);
+  log_opt_out.RecordEnvironment(&delegating_provider);
+  EXPECT_TRUE(log_opt_out.system_profile().has_uma_default_state());
+  EXPECT_EQ(SystemProfileProto_UmaDefaultState_OPT_OUT,
+            log_opt_out.system_profile().uma_default_state());
+
+  client.set_reporting_is_managed(true);
+  TestMetricsLog log_managed(kClientId, kSessionId, MetricsLog::ONGOING_LOG,
+                             &client);
+  log_managed.RecordEnvironment(&delegating_provider);
+  EXPECT_TRUE(log_managed.system_profile().has_uma_default_state());
+  EXPECT_EQ(SystemProfileProto_UmaDefaultState_POLICY_FORCED_ENABLED,
+            log_managed.system_profile().uma_default_state());
+}
+
+TEST_F(MetricsLogTest, InitialLogStabilityMetrics) {
+  TestMetricsServiceClient client;
+  TestMetricsLog log(kClientId, kSessionId, MetricsLog::INITIAL_STABILITY_LOG,
+                     &client);
+  TestMetricsProvider* test_provider = new TestMetricsProvider();
+  DelegatingProvider delegating_provider;
+  delegating_provider.RegisterMetricsProvider(
+      base::WrapUnique<MetricsProvider>(test_provider));
+  log.RecordEnvironment(&delegating_provider);
+  log.RecordPreviousSessionData(&delegating_provider);
+
+  // The test provider should have been called upon to provide initial
+  // stability and regular stability metrics.
+  EXPECT_TRUE(test_provider->provide_initial_stability_metrics_called());
+  EXPECT_TRUE(test_provider->provide_stability_metrics_called());
+}
+
+TEST_F(MetricsLogTest, OngoingLogStabilityMetrics) {
+  TestMetricsServiceClient client;
+  TestMetricsLog log(kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client);
+  TestMetricsProvider* test_provider = new TestMetricsProvider();
+  DelegatingProvider delegating_provider;
+  delegating_provider.RegisterMetricsProvider(
+      base::WrapUnique<MetricsProvider>(test_provider));
+  log.RecordEnvironment(&delegating_provider);
+  log.RecordCurrentSessionData(&delegating_provider, base::TimeDelta(),
+                               base::TimeDelta());
+
+  // The test provider should have been called upon to provide regular but not
+  // initial stability metrics.
+  EXPECT_FALSE(test_provider->provide_initial_stability_metrics_called());
+  EXPECT_TRUE(test_provider->provide_stability_metrics_called());
+}
+
+TEST_F(MetricsLogTest, ChromeChannelWrittenToProtobuf) {
+  TestMetricsServiceClient client;
+  TestMetricsLog log(kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client);
+  EXPECT_TRUE(log.uma_proto().system_profile().has_channel());
+}
+
+TEST_F(MetricsLogTest, ProductNotSetIfDefault) {
+  TestMetricsServiceClient client;
+  EXPECT_EQ(ChromeUserMetricsExtension::CHROME, client.GetProduct());
+  TestMetricsLog log(kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client);
+  // Check that the product isn't set, since it's default and also verify the
+  // default value is indeed equal to Chrome.
+  EXPECT_FALSE(log.uma_proto().has_product());
+  EXPECT_EQ(ChromeUserMetricsExtension::CHROME, log.uma_proto().product());
+}
+
+TEST_F(MetricsLogTest, ProductSetIfNotDefault) {
+  const int32_t kTestProduct = 100;
+  EXPECT_NE(ChromeUserMetricsExtension::CHROME, kTestProduct);
+
+  TestMetricsServiceClient client;
+  client.set_product(kTestProduct);
+  TestMetricsLog log(kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client);
+  // Check that the product is set to |kTestProduct|.
+  EXPECT_TRUE(log.uma_proto().has_product());
+  EXPECT_EQ(kTestProduct, log.uma_proto().product());
+}
+
+TEST_F(MetricsLogTest, TruncateEvents) {
+  TestMetricsServiceClient client;
+  TestMetricsLog log(kClientId, kSessionId, MetricsLog::ONGOING_LOG, &client);
+
+  for (int i = 0; i < internal::kUserActionEventLimit * 2; ++i) {
+    log.RecordUserAction("BasicAction");
+    EXPECT_EQ(i + 1, log.uma_proto().user_action_event_size());
+  }
+  for (int i = 0; i < internal::kOmniboxEventLimit * 2; ++i) {
+    // Add an empty omnibox event. Not fully realistic since these are normally
+    // supplied by a metrics provider.
+    log.mutable_uma_proto()->add_omnibox_event();
+    EXPECT_EQ(i + 1, log.uma_proto().omnibox_event_size());
+  }
+
+  // Truncate, and check that the current size is the limit.
+  log.TruncateEvents();
+  EXPECT_EQ(internal::kUserActionEventLimit,
+            log.uma_proto().user_action_event_size());
+  EXPECT_EQ(internal::kOmniboxEventLimit, log.uma_proto().omnibox_event_size());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_log_uploader.h b/components/metrics/metrics_log_uploader.h
new file mode 100644
index 0000000..882bab0
--- /dev/null
+++ b/components/metrics/metrics_log_uploader.h
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_
+#define COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+
+namespace metrics {
+
+class ReportingInfo;
+
+// MetricsLogUploader is an abstract base class for uploading UMA logs on behalf
+// of MetricsService.
+class MetricsLogUploader {
+ public:
+  // Type for OnUploadComplete callbacks.  These callbacks will receive three
+  // parameters: A response code, a net error code, and a boolean specifying
+  // if the connection was secure (over HTTPS).
+  typedef base::Callback<void(int, int, bool)> UploadCallback;
+
+  // Possible service types. This should correspond to a type from
+  // DataUseUserData.
+  enum MetricServiceType {
+    UMA,
+    UKM,
+  };
+
+  virtual ~MetricsLogUploader() {}
+
+  // Uploads a log with the specified |compressed_log_data| and |log_hash|.
+  // |log_hash| is expected to be the hex-encoded SHA1 hash of the log data
+  // before compression.
+  virtual void UploadLog(const std::string& compressed_log_data,
+                         const std::string& log_hash,
+                         const ReportingInfo& reporting_info) = 0;
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_LOG_UPLOADER_H_
diff --git a/components/metrics/metrics_pref_names.cc b/components/metrics/metrics_pref_names.cc
new file mode 100644
index 0000000..5dcb253
--- /dev/null
+++ b/components/metrics/metrics_pref_names.cc
@@ -0,0 +1,228 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_pref_names.h"
+
+namespace metrics {
+namespace prefs {
+
+// Set once, to the current epoch time, on the first run of chrome on this
+// machine. Attached to metrics reports forever thereafter.
+const char kInstallDate[] = "uninstall_metrics.installation_date2";
+
+// The metrics client GUID.
+// Note: The name client_id2 is a result of creating
+// new prefs to do a one-time reset of the previous values.
+const char kMetricsClientID[] = "user_experience_metrics.client_id2";
+
+// An enum value indicating the default value of the enable metrics reporting
+// checkbox shown during first-run. If it's opt-in, then the checkbox defaulted
+// to unchecked, if it's opt-out, then it defaulted to checked. This value is
+// only recorded during first-run, so older clients will not set it. The enum
+// used for the value is metrics::MetricsServiceClient::EnableMetricsDefault.
+const char kMetricsDefaultOptIn[] = "user_experience_metrics.default_opt_in";
+
+// Array of dictionaries that are each UMA logs that were supposed to be sent in
+// the first minute of a browser session. These logs include things like crash
+// count info, etc.
+const char kMetricsInitialLogs[] = "user_experience_metrics.initial_logs2";
+
+// The metrics entropy source.
+// Note: The name low_entropy_source2 is a result of creating
+// new prefs to do a one-time reset of the previous values.
+const char kMetricsLowEntropySource[] =
+    "user_experience_metrics.low_entropy_source2";
+
+// A machine ID used to detect when underlying hardware changes. It is only
+// stored locally and never transmitted in metrics reports.
+const char kMetricsMachineId[] = "user_experience_metrics.machine_id";
+
+// Array of dictionaries that are each UMA logs that were not sent because the
+// browser terminated before these accumulated metrics could be sent. These
+// logs typically include histograms and memory reports, as well as ongoing
+// user activities.
+const char kMetricsOngoingLogs[] = "user_experience_metrics.ongoing_logs2";
+
+// Boolean that indicates a cloned install has been detected and the metrics
+// client id and low entropy source should be reset.
+const char kMetricsResetIds[] = "user_experience_metrics.reset_metrics_ids";
+
+// Boolean that specifies whether or not crash reporting and metrics reporting
+// are sent over the network for analysis.
+const char kMetricsReportingEnabled[] =
+    "user_experience_metrics.reporting_enabled";
+
+// Date/time when the user opted in to UMA and generated the client id most
+// recently (local machine time, stored as a 64-bit time_t value).
+const char kMetricsReportingEnabledTimestamp[] =
+    "user_experience_metrics.client_id_timestamp";
+
+// The metrics client session ID.
+const char kMetricsSessionID[] = "user_experience_metrics.session_id";
+
+// The prefix of the last-seen timestamp for persistent histogram files.
+// Values are named for the files themselves.
+const char kMetricsLastSeenPrefix[] =
+    "user_experience_metrics.last_seen.";
+
+// Number of times the browser has been able to register crash reporting.
+const char kStabilityBreakpadRegistrationSuccess[] =
+    "user_experience_metrics.stability.breakpad_registration_ok";
+
+// Number of times the browser has failed to register crash reporting.
+const char kStabilityBreakpadRegistrationFail[] =
+    "user_experience_metrics.stability.breakpad_registration_fail";
+
+// A time stamp at which time the browser was known to be alive. Used to
+// evaluate whether the browser crash was due to a whole system crash.
+// At minimum this is updated each time the "exited_cleanly" preference is
+// modified, but can also be optionally updated on a slow schedule.
+const char kStabilityBrowserLastLiveTimeStamp[] =
+    "user_experience_metrics.stability.browser_last_live_timestamp";
+
+// Total number of child process crashes (other than renderer / extension
+// renderer ones, and plugin children, which are counted separately) since the
+// last report.
+const char kStabilityChildProcessCrashCount[] =
+    "user_experience_metrics.stability.child_process_crash_count";
+
+// Number of times the application exited uncleanly since the last report.
+const char kStabilityCrashCount[] =
+    "user_experience_metrics.stability.crash_count";
+
+// Number of times the application exited uncleanly since the last report
+// without gms core update.
+const char kStabilityCrashCountWithoutGmsCoreUpdate[] =
+    "user_experience_metrics.stability.crash_count_without_gms_core_update";
+
+// Number of times the initial stability log upload was deferred to the next
+// startup.
+const char kStabilityDeferredCount[] =
+    "user_experience_metrics.stability.deferred_count";
+
+// Number of times stability data was discarded. This is accumulated since the
+// last report, even across versions.
+const char kStabilityDiscardCount[] =
+    "user_experience_metrics.stability.discard_count";
+
+// Number of times the browser has been run under a debugger.
+const char kStabilityDebuggerPresent[] =
+    "user_experience_metrics.stability.debugger_present";
+
+// Number of times the browser has not been run under a debugger.
+const char kStabilityDebuggerNotPresent[] =
+    "user_experience_metrics.stability.debugger_not_present";
+
+// An enum value to indicate the execution phase the browser was in.
+const char kStabilityExecutionPhase[] =
+    "user_experience_metrics.stability.execution_phase";
+
+// True if the previous run of the program exited cleanly.
+const char kStabilityExitedCleanly[] =
+    "user_experience_metrics.stability.exited_cleanly";
+
+// Number of times an extension renderer process crashed since the last report.
+const char kStabilityExtensionRendererCrashCount[] =
+    "user_experience_metrics.stability.extension_renderer_crash_count";
+
+// Number of times an extension renderer process failed to launch since the last
+// report.
+const char kStabilityExtensionRendererFailedLaunchCount[] =
+    "user_experience_metrics.stability.extension_renderer_failed_launch_count";
+
+// Number of times an extension renderer process successfully launched since the
+// last report.
+const char kStabilityExtensionRendererLaunchCount[] =
+    "user_experience_metrics.stability.extension_renderer_launch_count";
+
+// The GMS core version used in Chrome.
+const char kStabilityGmsCoreVersion[] =
+    "user_experience_metrics.stability.gms_core_version";
+
+// Number of times the session end did not complete.
+const char kStabilityIncompleteSessionEndCount[] =
+    "user_experience_metrics.stability.incomplete_session_end_count";
+
+// Number of times the application was launched since last report.
+const char kStabilityLaunchCount[] =
+    "user_experience_metrics.stability.launch_count";
+
+// Number of times a page load event occurred since the last report.
+const char kStabilityPageLoadCount[] =
+    "user_experience_metrics.stability.page_load_count";
+
+// Number of times a renderer process crashed since the last report.
+const char kStabilityRendererCrashCount[] =
+    "user_experience_metrics.stability.renderer_crash_count";
+
+// Number of times a renderer process failed to launch since the last report.
+const char kStabilityRendererFailedLaunchCount[] =
+    "user_experience_metrics.stability.renderer_failed_launch_count";
+
+// Number of times the renderer has become non-responsive since the last
+// report.
+const char kStabilityRendererHangCount[] =
+    "user_experience_metrics.stability.renderer_hang_count";
+
+// Number of times a renderer process successfully launched since the last
+// report.
+const char kStabilityRendererLaunchCount[] =
+    "user_experience_metrics.stability.renderer_launch_count";
+
+// Base64 encoded serialized UMA system profile proto from the previous session.
+const char kStabilitySavedSystemProfile[] =
+    "user_experience_metrics.stability.saved_system_profile";
+
+// SHA-1 hash of the serialized UMA system profile proto (hex encoded).
+const char kStabilitySavedSystemProfileHash[] =
+    "user_experience_metrics.stability.saved_system_profile_hash";
+
+// False if we received a session end and either we crashed during processing
+// the session end or ran out of time and windows terminated us.
+const char kStabilitySessionEndCompleted[] =
+    "user_experience_metrics.stability.session_end_completed";
+
+// Build time, in seconds since an epoch, which is used to assure that stability
+// metrics reported reflect stability of the same build.
+const char kStabilityStatsBuildTime[] =
+    "user_experience_metrics.stability.stats_buildtime";
+
+// Version string of previous run, which is used to assure that stability
+// metrics reported under current version reflect stability of the same version.
+const char kStabilityStatsVersion[] =
+    "user_experience_metrics.stability.stats_version";
+
+// Number of times the application exited uncleanly and the system session
+// embedding the browser session ended abnormally since the last report.
+// Windows only.
+const char kStabilitySystemCrashCount[] =
+    "user_experience_metrics.stability.system_crash_count";
+
+// Number of times the version number stored in prefs did not match the
+// serialized system profile version number.
+const char kStabilityVersionMismatchCount[] =
+    "user_experience_metrics.stability.version_mismatch_count";
+
+// The keys below are strictly increasing counters over the lifetime of
+// a chrome installation. They are (optionally) sent up to the uninstall
+// survey in the event of uninstallation.
+const char kUninstallLaunchCount[] = "uninstall_metrics.launch_count";
+const char kUninstallMetricsPageLoadCount[] =
+    "uninstall_metrics.page_load_count";
+const char kUninstallMetricsUptimeSec[] = "uninstall_metrics.uptime_sec";
+
+// Dictionary for measuring cellular data used by UKM service during last 7
+// days.
+const char kUkmCellDataUse[] = "user_experience_metrics.ukm_cell_datause";
+
+// Dictionary for measuring cellular data used by UMA service during last 7
+// days.
+const char kUmaCellDataUse[] = "user_experience_metrics.uma_cell_datause";
+
+// Dictionary for measuring cellular data used by user including chrome services
+// per day.
+const char kUserCellDataUse[] = "user_experience_metrics.user_call_datause";
+
+}  // namespace prefs
+}  // namespace metrics
diff --git a/components/metrics/metrics_pref_names.h b/components/metrics/metrics_pref_names.h
new file mode 100644
index 0000000..2024c49
--- /dev/null
+++ b/components/metrics/metrics_pref_names.h
@@ -0,0 +1,79 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_PREF_NAMES_H_
+#define COMPONENTS_METRICS_METRICS_PREF_NAMES_H_
+
+namespace metrics {
+namespace prefs {
+
+// Alphabetical list of preference names specific to the metrics
+// component. Document each in the .cc file.
+extern const char kDeprecatedMetricsInitialLogs[];
+extern const char kDeprecatedMetricsOngoingLogs[];
+extern const char kInstallDate[];
+extern const char kMetricsClientID[];
+extern const char kMetricsDefaultOptIn[];
+extern const char kMetricsInitialLogs[];
+extern const char kMetricsLowEntropySource[];
+extern const char kMetricsMachineId[];
+extern const char kMetricsOngoingLogs[];
+extern const char kMetricsResetIds[];
+
+// For finding out whether metrics and crash reporting is enabled use the
+// relevant embedder-specific subclass of MetricsServiceAccessor instead of
+// reading this pref directly; see the comments on metrics_service_accessor.h.
+// (NOTE: If within //chrome, use
+// ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled()).
+extern const char kMetricsReportingEnabled[];
+extern const char kMetricsReportingEnabledTimestamp[];
+extern const char kMetricsSessionID[];
+extern const char kMetricsLastSeenPrefix[];
+
+// Preferences for recording stability logs.
+extern const char kStabilityBreakpadRegistrationFail[];
+extern const char kStabilityBreakpadRegistrationSuccess[];
+extern const char kStabilityBrowserLastLiveTimeStamp[];
+extern const char kStabilityChildProcessCrashCount[];
+extern const char kStabilityCrashCount[];
+extern const char kStabilityCrashCountWithoutGmsCoreUpdate[];
+extern const char kStabilityDebuggerNotPresent[];
+extern const char kStabilityDebuggerPresent[];
+extern const char kStabilityDeferredCount[];
+extern const char kStabilityDiscardCount[];
+extern const char kStabilityExecutionPhase[];
+extern const char kStabilityExitedCleanly[];
+extern const char kStabilityExtensionRendererCrashCount[];
+extern const char kStabilityExtensionRendererFailedLaunchCount[];
+extern const char kStabilityExtensionRendererLaunchCount[];
+extern const char kStabilityGmsCoreVersion[];
+extern const char kStabilityIncompleteSessionEndCount[];
+extern const char kStabilityLaunchCount[];
+extern const char kStabilityPageLoadCount[];
+extern const char kStabilityRendererCrashCount[];
+extern const char kStabilityRendererFailedLaunchCount[];
+extern const char kStabilityRendererHangCount[];
+extern const char kStabilityRendererLaunchCount[];
+extern const char kStabilitySavedSystemProfile[];
+extern const char kStabilitySavedSystemProfileHash[];
+extern const char kStabilitySessionEndCompleted[];
+extern const char kStabilityStatsBuildTime[];
+extern const char kStabilityStatsVersion[];
+extern const char kStabilitySystemCrashCount[];
+extern const char kStabilityVersionMismatchCount[];
+
+// Preferences for generating metrics at uninstall time.
+extern const char kUninstallLaunchCount[];
+extern const char kUninstallMetricsPageLoadCount[];
+extern const char kUninstallMetricsUptimeSec[];
+
+// For measuring data use for throttling UMA log uploads on cellular.
+extern const char kUkmCellDataUse[];
+extern const char kUmaCellDataUse[];
+extern const char kUserCellDataUse[];
+
+}  // namespace prefs
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_PREF_NAMES_H_
diff --git a/components/metrics/metrics_provider.cc b/components/metrics/metrics_provider.cc
new file mode 100644
index 0000000..d5408bb
--- /dev/null
+++ b/components/metrics/metrics_provider.cc
@@ -0,0 +1,75 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_provider.h"
+
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
+
+namespace metrics {
+
+MetricsProvider::MetricsProvider() {
+}
+
+MetricsProvider::~MetricsProvider() {
+}
+
+void MetricsProvider::Init() {
+}
+
+void MetricsProvider::AsyncInit(const base::Closure& done_callback) {
+  done_callback.Run();
+}
+
+void MetricsProvider::OnDidCreateMetricsLog() {
+}
+
+void MetricsProvider::OnRecordingEnabled() {
+}
+
+void MetricsProvider::OnRecordingDisabled() {
+}
+
+void MetricsProvider::OnAppEnterBackground() {
+}
+
+bool MetricsProvider::ProvideIndependentMetrics(
+    SystemProfileProto* system_profile_proto,
+    base::HistogramSnapshotManager* snapshot_manager) {
+  return false;
+}
+
+void MetricsProvider::ProvideSystemProfileMetrics(
+    SystemProfileProto* system_profile_proto) {
+}
+
+bool MetricsProvider::HasPreviousSessionData() {
+  return false;
+}
+
+void MetricsProvider::ProvidePreviousSessionData(
+    ChromeUserMetricsExtension* uma_proto) {
+  ProvideStabilityMetrics(uma_proto->mutable_system_profile());
+}
+
+void MetricsProvider::ProvideCurrentSessionData(
+    ChromeUserMetricsExtension* uma_proto) {
+  ProvideStabilityMetrics(uma_proto->mutable_system_profile());
+}
+
+void MetricsProvider::ProvideStabilityMetrics(
+    SystemProfileProto* system_profile_proto) {
+}
+
+void MetricsProvider::ClearSavedStabilityMetrics() {
+}
+
+void MetricsProvider::RecordHistogramSnapshots(
+    base::HistogramSnapshotManager* snapshot_manager) {
+}
+
+void MetricsProvider::RecordInitialHistogramSnapshots(
+    base::HistogramSnapshotManager* snapshot_manager) {
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_provider.h b/components/metrics/metrics_provider.h
new file mode 100644
index 0000000..591c63f
--- /dev/null
+++ b/components/metrics/metrics_provider.h
@@ -0,0 +1,111 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_METRICS_PROVIDER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+
+namespace base {
+class HistogramSnapshotManager;
+}  // namespace base
+
+namespace metrics {
+
+class ChromeUserMetricsExtension;
+class SystemProfileProto;
+
+// MetricsProvider is an interface allowing different parts of the UMA protos to
+// be filled out by different classes.
+class MetricsProvider {
+ public:
+  MetricsProvider();
+  virtual ~MetricsProvider();
+
+  // Called after initialiazation of MetricsService and field trials.
+  virtual void Init();
+
+  // Called during service initialization to allow the provider to start any
+  // async initialization tasks.  The service will wait for the provider to
+  // call |done_callback| before generating logs for the current session.
+  virtual void AsyncInit(const base::Closure& done_callback);
+
+  // Called when a new MetricsLog is created.
+  virtual void OnDidCreateMetricsLog();
+
+  // Called when metrics recording has been enabled.
+  virtual void OnRecordingEnabled();
+
+  // Called when metrics recording has been disabled.
+  virtual void OnRecordingDisabled();
+
+  // Called when the application is going into background mode, on platforms
+  // where applications may be killed when going into the background (Android,
+  // iOS). Providers that buffer histogram data in memory should persist
+  // histograms in this callback, as the application may be killed without
+  // further notification after this callback.
+  virtual void OnAppEnterBackground();
+
+  // Provides a complete and independent system profile + metrics for uploading.
+  // Any histograms added to the |snapshot_manager| will also be included. A
+  // return of false indicates there are none. Will be called repeatedly until
+  // there is nothing else.
+  virtual bool ProvideIndependentMetrics(
+      SystemProfileProto* system_profile_proto,
+      base::HistogramSnapshotManager* snapshot_manager);
+
+  // Provides additional metrics into the system profile.
+  virtual void ProvideSystemProfileMetrics(
+      SystemProfileProto* system_profile_proto);
+
+  // Called once at startup to see whether this provider has critical data
+  // to provide about the previous session.
+  // Returning true will trigger ProvidePreviousSessionData on all other
+  // registered metrics providers.
+  // Default implementation always returns false.
+  virtual bool HasPreviousSessionData();
+
+  // Called when building a log about the previous session, so the provider
+  // can provide data about it.  Stability metrics can be provided
+  // directly into |stability_proto| fields or by logging stability histograms
+  // via the UMA_STABILITY_HISTOGRAM_ENUMERATION() macro.
+  virtual void ProvidePreviousSessionData(
+      ChromeUserMetricsExtension* uma_proto);
+
+  // Called when building a log about the current session, so the provider
+  // can provide data about it.
+  virtual void ProvideCurrentSessionData(ChromeUserMetricsExtension* uma_proto);
+
+  // Provides additional stability metrics. Stability metrics can be provided
+  // directly into |stability_proto| fields or by logging stability histograms
+  // via the UMA_STABILITY_HISTOGRAM_ENUMERATION() macro.
+  virtual void ProvideStabilityMetrics(
+      SystemProfileProto* system_profile_proto);
+
+  // Called to indicate that saved stability prefs should be cleared, e.g.
+  // because they are from an old version and should not be kept.
+  virtual void ClearSavedStabilityMetrics();
+
+  // Called during regular collection to explicitly load histogram snapshots
+  // using a snapshot manager. PrepareDeltas() will have already been called
+  // and FinishDeltas() will be called later; calls to only PrepareDelta(),
+  // not PrepareDeltas (plural), should be made.
+  virtual void RecordHistogramSnapshots(
+      base::HistogramSnapshotManager* snapshot_manager);
+
+  // Called during collection of initial metrics to explicitly load histogram
+  // snapshots using a snapshot manager. PrepareDeltas() will have already
+  // been called and FinishDeltas() will be called later; calls to only
+  // PrepareDelta(), not PrepareDeltas (plural), should be made.
+  virtual void RecordInitialHistogramSnapshots(
+      base::HistogramSnapshotManager* snapshot_manager);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MetricsProvider);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_PROVIDER_H_
diff --git a/components/metrics/metrics_reporting_default_state.cc b/components/metrics/metrics_reporting_default_state.cc
new file mode 100644
index 0000000..a166600
--- /dev/null
+++ b/components/metrics/metrics_reporting_default_state.cc
@@ -0,0 +1,36 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_reporting_default_state.h"
+
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace metrics {
+
+void RegisterMetricsReportingStatePrefs(PrefRegistrySimple* registry) {
+  registry->RegisterIntegerPref(prefs::kMetricsDefaultOptIn,
+                                EnableMetricsDefault::DEFAULT_UNKNOWN);
+}
+
+void RecordMetricsReportingDefaultState(PrefService* local_state,
+                                        EnableMetricsDefault default_state) {
+  DCHECK_EQ(GetMetricsReportingDefaultState(local_state),
+            EnableMetricsDefault::DEFAULT_UNKNOWN);
+  local_state->SetInteger(prefs::kMetricsDefaultOptIn, default_state);
+}
+
+void ForceRecordMetricsReportingDefaultState(
+    PrefService* local_state,
+    EnableMetricsDefault default_state) {
+  local_state->SetInteger(prefs::kMetricsDefaultOptIn, default_state);
+}
+
+EnableMetricsDefault GetMetricsReportingDefaultState(PrefService* local_state) {
+  return static_cast<EnableMetricsDefault>(
+      local_state->GetInteger(prefs::kMetricsDefaultOptIn));
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_reporting_default_state.h b/components/metrics/metrics_reporting_default_state.h
new file mode 100644
index 0000000..ad81e99
--- /dev/null
+++ b/components/metrics/metrics_reporting_default_state.h
@@ -0,0 +1,48 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_METRICS_REPORTING_DEFAULT_STATE_H_
+#define COMPONENTS_METRICS_METRICS_REPORTING_DEFAULT_STATE_H_
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace metrics {
+
+// Metrics reporting default state. This relates to the state of the enable
+// checkbox shown on first-run. This enum is used to store values in a pref, and
+// shouldn't be renumbered.
+enum EnableMetricsDefault {
+  // We only record the value during first-run. The default of existing
+  // installs is considered unknown.
+  DEFAULT_UNKNOWN,
+  // The first-run checkbox was unchecked by default.
+  OPT_IN,
+  // The first-run checkbox was checked by default.
+  OPT_OUT,
+};
+
+// Register prefs relating to metrics reporting state. Currently only registers
+// a pref for metrics reporting default opt-in state.
+void RegisterMetricsReportingStatePrefs(PrefRegistrySimple* registry);
+
+// Sets whether metrics reporting was opt-in or not. If it was opt-in, then the
+// enable checkbox on first-run was default unchecked. If it was opt-out, then
+// the checkbox was default checked. This should only be set once, and only
+// during first-run.
+void RecordMetricsReportingDefaultState(PrefService* local_state,
+                                        EnableMetricsDefault default_state);
+
+// Same as above, but does not verify the current state is UNKNOWN.
+void ForceRecordMetricsReportingDefaultState(
+    PrefService* local_state,
+    EnableMetricsDefault default_state);
+
+// Gets information about the default value for the enable metrics reporting
+// checkbox shown during first-run.
+EnableMetricsDefault GetMetricsReportingDefaultState(PrefService* local_state);
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_REPORTING_DEFAULT_STATE_H_
diff --git a/components/metrics/metrics_reporting_service.cc b/components/metrics/metrics_reporting_service.cc
new file mode 100644
index 0000000..4ab08d7
--- /dev/null
+++ b/components/metrics/metrics_reporting_service.cc
@@ -0,0 +1,97 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ReportingService specialized to report UMA metrics.
+
+#include "components/metrics/metrics_reporting_service.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/persisted_logs_metrics_impl.h"
+#include "components/metrics/url_constants.h"
+#include "components/prefs/pref_registry_simple.h"
+
+namespace metrics {
+
+namespace {
+
+// If an upload fails, and the transmission was over this byte count, then we
+// will discard the log, and not try to retransmit it.  We also don't persist
+// the log to the prefs for transmission during the next chrome session if this
+// limit is exceeded.
+const size_t kUploadLogAvoidRetransmitSize = 100 * 1024;
+
+}  // namespace
+
+// static
+void MetricsReportingService::RegisterPrefs(PrefRegistrySimple* registry) {
+  ReportingService::RegisterPrefs(registry);
+  MetricsLogStore::RegisterPrefs(registry);
+}
+
+MetricsReportingService::MetricsReportingService(MetricsServiceClient* client,
+                                                 PrefService* local_state)
+    : ReportingService(client, local_state, kUploadLogAvoidRetransmitSize),
+      metrics_log_store_(local_state, kUploadLogAvoidRetransmitSize) {}
+
+MetricsReportingService::~MetricsReportingService() {}
+
+LogStore* MetricsReportingService::log_store() {
+  return &metrics_log_store_;
+}
+
+std::string MetricsReportingService::GetUploadUrl() const {
+  return client()->GetMetricsServerUrl();
+}
+
+std::string MetricsReportingService::GetInsecureUploadUrl() const {
+  return client()->GetInsecureMetricsServerUrl();
+}
+
+base::StringPiece MetricsReportingService::upload_mime_type() const {
+  return kDefaultMetricsMimeType;
+}
+
+MetricsLogUploader::MetricServiceType MetricsReportingService::service_type()
+    const {
+  return MetricsLogUploader::UMA;
+}
+
+void MetricsReportingService::LogActualUploadInterval(
+    base::TimeDelta interval) {
+  UMA_HISTOGRAM_CUSTOM_COUNTS("UMA.ActualLogUploadInterval",
+                              interval.InMinutes(), 1,
+                              base::TimeDelta::FromHours(12).InMinutes(), 50);
+}
+
+void MetricsReportingService::LogCellularConstraint(bool upload_canceled) {
+  UMA_HISTOGRAM_BOOLEAN("UMA.LogUpload.Canceled.CellularConstraint",
+                        upload_canceled);
+}
+
+void MetricsReportingService::LogResponseOrErrorCode(int response_code,
+                                                     int error_code,
+                                                     bool was_https) {
+  if (was_https) {
+    base::UmaHistogramSparse("UMA.LogUpload.ResponseOrErrorCode",
+                             response_code >= 0 ? response_code : error_code);
+  } else {
+    base::UmaHistogramSparse("UMA.LogUpload.ResponseOrErrorCode.HTTP",
+                             response_code >= 0 ? response_code : error_code);
+  }
+}
+
+void MetricsReportingService::LogSuccess(size_t log_size) {
+  UMA_HISTOGRAM_COUNTS_10000("UMA.LogSize.OnSuccess", log_size / 1024);
+}
+
+void MetricsReportingService::LogLargeRejection(size_t log_size) {
+  UMA_HISTOGRAM_COUNTS_1M("UMA.Large Rejected Log was Discarded",
+                          static_cast<int>(log_size));
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_reporting_service.h b/components/metrics/metrics_reporting_service.h
new file mode 100644
index 0000000..38961b2
--- /dev/null
+++ b/components/metrics/metrics_reporting_service.h
@@ -0,0 +1,68 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines a service that sends metrics logs to a server.
+
+#ifndef COMPONENTS_METRICS_METRICS_REPORTING_SERVICE_H_
+#define COMPONENTS_METRICS_METRICS_REPORTING_SERVICE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/metrics/metrics_log_store.h"
+#include "components/metrics/reporting_service.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace metrics {
+
+class MetricsServiceClient;
+
+// MetricsReportingService is concrete implementation of ReportingService for
+// UMA logs. It uses a MetricsLogStore as its LogStore, reports to the UMA
+// endpoint, and logs some histograms with the UMA prefix.
+class MetricsReportingService : public ReportingService {
+ public:
+  // Creates a ReportingService with the given |client|, |local_state|.
+  // Does not take ownership of the parameters; instead it stores a weak
+  // pointer to each. Caller should ensure that the parameters are valid for
+  // the lifetime of this class.
+  MetricsReportingService(MetricsServiceClient* client,
+                          PrefService* local_state);
+  ~MetricsReportingService() override;
+
+  MetricsLogStore* metrics_log_store() { return &metrics_log_store_; }
+  const MetricsLogStore* metrics_log_store() const {
+    return &metrics_log_store_;
+  }
+
+  // Registers local state prefs used by this class.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+  // ReportingService:
+  LogStore* log_store() override;
+  std::string GetUploadUrl() const override;
+  std::string GetInsecureUploadUrl() const override;
+  base::StringPiece upload_mime_type() const override;
+  MetricsLogUploader::MetricServiceType service_type() const override;
+  void LogActualUploadInterval(base::TimeDelta interval) override;
+  void LogCellularConstraint(bool upload_canceled) override;
+  void LogResponseOrErrorCode(int response_code,
+                              int error_code,
+                              bool was_https) override;
+  void LogSuccess(size_t log_size) override;
+  void LogLargeRejection(size_t log_size) override;
+
+  MetricsLogStore metrics_log_store_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsReportingService);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_REPORTING_SERVICE_H_
diff --git a/components/metrics/metrics_rotation_scheduler.cc b/components/metrics/metrics_rotation_scheduler.cc
new file mode 100644
index 0000000..c760b4a
--- /dev/null
+++ b/components/metrics/metrics_rotation_scheduler.cc
@@ -0,0 +1,53 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_rotation_scheduler.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "build/build_config.h"
+
+namespace metrics {
+
+MetricsRotationScheduler::MetricsRotationScheduler(
+    const base::Closure& upload_callback,
+    const base::Callback<base::TimeDelta(void)>& upload_interval_callback)
+    : MetricsScheduler(upload_callback),
+      init_task_complete_(false),
+      waiting_for_init_task_complete_(false),
+      upload_interval_callback_(upload_interval_callback) {}
+
+MetricsRotationScheduler::~MetricsRotationScheduler() {}
+
+void MetricsRotationScheduler::InitTaskComplete() {
+  DCHECK(!init_task_complete_);
+  init_task_complete_ = true;
+  if (waiting_for_init_task_complete_) {
+    waiting_for_init_task_complete_ = false;
+    TriggerTask();
+  } else {
+    LogMetricsInitSequence(INIT_TASK_COMPLETED_FIRST);
+  }
+}
+
+void MetricsRotationScheduler::RotationFinished() {
+  TaskDone(upload_interval_callback_.Run());
+}
+
+void MetricsRotationScheduler::LogMetricsInitSequence(InitSequence sequence) {
+  UMA_HISTOGRAM_ENUMERATION("UMA.InitSequence", sequence,
+                            INIT_SEQUENCE_ENUM_SIZE);
+}
+
+void MetricsRotationScheduler::TriggerTask() {
+  // If the timer fired before the init task has completed, don't trigger the
+  // upload yet - wait for the init task to complete and do it then.
+  if (!init_task_complete_) {
+    LogMetricsInitSequence(TIMER_FIRED_FIRST);
+    waiting_for_init_task_complete_ = true;
+    return;
+  }
+  MetricsScheduler::TriggerTask();
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_rotation_scheduler.h b/components/metrics/metrics_rotation_scheduler.h
new file mode 100644
index 0000000..20a2a44
--- /dev/null
+++ b/components/metrics/metrics_rotation_scheduler.h
@@ -0,0 +1,62 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_ROTATION_SCHEDULER_H_
+#define COMPONENTS_METRICS_METRICS_ROTATION_SCHEDULER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_scheduler.h"
+
+namespace metrics {
+
+// Scheduler task to drive a MetricsService object's uploading.
+class MetricsRotationScheduler : public MetricsScheduler {
+ public:
+  // Creates MetricsRotationScheduler object with the given |rotation_callback|
+  // callback to call when log rotation should happen and |interval_callback|
+  // to determine the interval between rotations in steady state.
+  // |rotation_callback| must arrange to call RotationFinished on completion.
+  MetricsRotationScheduler(
+      const base::Closure& rotation_callback,
+      const base::Callback<base::TimeDelta(void)>& interval_callback);
+  ~MetricsRotationScheduler() override;
+
+  // Callback from MetricsService when the startup init task has completed.
+  void InitTaskComplete();
+
+  // Callback from MetricsService when a triggered rotation finishes.
+  void RotationFinished();
+
+ protected:
+  enum InitSequence {
+    TIMER_FIRED_FIRST,
+    INIT_TASK_COMPLETED_FIRST,
+    INIT_SEQUENCE_ENUM_SIZE,
+  };
+
+ private:
+  // Record the init sequence order histogram.
+  virtual void LogMetricsInitSequence(InitSequence sequence);
+
+  // MetricsScheduler:
+  void TriggerTask() override;
+
+  // Whether the InitTaskComplete() callback has been called.
+  bool init_task_complete_;
+
+  // Whether the initial scheduled upload timer has fired before the init task
+  // has been completed.
+  bool waiting_for_init_task_complete_;
+
+  // Callback function used to get the standard upload time.
+  base::Callback<base::TimeDelta(void)> upload_interval_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsRotationScheduler);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_ROTATION_SCHEDULER_H_
diff --git a/components/metrics/metrics_scheduler.cc b/components/metrics/metrics_scheduler.cc
new file mode 100644
index 0000000..86d1157
--- /dev/null
+++ b/components/metrics/metrics_scheduler.cc
@@ -0,0 +1,64 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_scheduler.h"
+
+#include "build/build_config.h"
+
+namespace metrics {
+
+namespace {
+
+// The delay, in seconds, after startup before sending the first log message.
+#if defined(OS_ANDROID) || defined(OS_IOS)
+// Sessions are more likely to be short on a mobile device, so handle the
+// initial log quickly.
+const int kInitialIntervalSeconds = 15;
+#else
+const int kInitialIntervalSeconds = 60;
+#endif
+
+}  // namespace
+
+MetricsScheduler::MetricsScheduler(const base::Closure& task_callback)
+    : task_callback_(task_callback),
+      interval_(base::TimeDelta::FromSeconds(kInitialIntervalSeconds)),
+      running_(false),
+      callback_pending_(false) {}
+
+MetricsScheduler::~MetricsScheduler() {}
+
+void MetricsScheduler::Start() {
+  running_ = true;
+  ScheduleNextTask();
+}
+
+void MetricsScheduler::Stop() {
+  running_ = false;
+  if (timer_.IsRunning())
+    timer_.Stop();
+}
+
+void MetricsScheduler::TaskDone(base::TimeDelta next_interval) {
+  DCHECK(callback_pending_);
+  callback_pending_ = false;
+  interval_ = next_interval;
+  if (running_)
+    ScheduleNextTask();
+}
+
+void MetricsScheduler::TriggerTask() {
+  callback_pending_ = true;
+  task_callback_.Run();
+}
+
+void MetricsScheduler::ScheduleNextTask() {
+  DCHECK(running_);
+  if (timer_.IsRunning() || callback_pending_)
+    return;
+
+  timer_.Start(FROM_HERE, interval_, this, &MetricsScheduler::TriggerTask);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_scheduler.h b/components/metrics/metrics_scheduler.h
new file mode 100644
index 0000000..fc031fb
--- /dev/null
+++ b/components/metrics/metrics_scheduler.h
@@ -0,0 +1,65 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_SCHEDULER_H_
+#define COMPONENTS_METRICS_METRICS_SCHEDULER_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+
+namespace metrics {
+
+// Scheduler task to drive a MetricsService object's uploading.
+class MetricsScheduler {
+ public:
+  // Creates MetricsScheduler object with the given |task_callback|
+  // callback to call when a task should happen.
+  explicit MetricsScheduler(const base::Closure& task_callback);
+  virtual ~MetricsScheduler();
+
+  // Starts scheduling uploads. This in a no-op if the scheduler is already
+  // running, so it is safe to call more than once.
+  void Start();
+
+  // Stops scheduling uploads.
+  void Stop();
+
+ protected:
+  // Subclasses should provide task_callback with a wrapper to call this with.
+  // This indicates the triggered task was completed/cancelled and the next
+  // call can be scheduled.
+  void TaskDone(base::TimeDelta next_interval);
+
+  // Called by the Timer when it's time to run the task.
+  virtual void TriggerTask();
+
+ private:
+  // Schedules a future call to TriggerTask if one isn't already pending.
+  void ScheduleNextTask();
+
+  // The method to call when task should happen.
+  const base::Closure task_callback_;
+
+  // Uses a one-shot timer rather than a repeating one because the task may be
+  // async, and the length of the interval may change.
+  base::OneShotTimer timer_;
+
+  // The interval between being told an task is done and starting the next task.
+  base::TimeDelta interval_;
+
+  // Indicates that the scheduler is running (i.e., that Start has been called
+  // more recently than Stop).
+  bool running_;
+
+  // Indicates that the last triggered task hasn't resolved yet.
+  bool callback_pending_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsScheduler);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_SCHEDULER_H_
diff --git a/components/metrics/metrics_service.cc b/components/metrics/metrics_service.cc
new file mode 100644
index 0000000..bfa17fe
--- /dev/null
+++ b/components/metrics/metrics_service.cc
@@ -0,0 +1,921 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//------------------------------------------------------------------------------
+// Description of the life cycle of a instance of MetricsService.
+//
+//  OVERVIEW
+//
+// A MetricsService instance is typically created at application startup.  It is
+// the central controller for the acquisition of log data, and the automatic
+// transmission of that log data to an external server.  Its major job is to
+// manage logs, grouping them for transmission, and transmitting them.  As part
+// of its grouping, MS finalizes logs by including some just-in-time gathered
+// memory statistics, snapshotting the current stats of numerous histograms,
+// closing the logs, translating to protocol buffer format, and compressing the
+// results for transmission.  Transmission includes submitting a compressed log
+// as data in a URL-post, and retransmitting (or retaining at process
+// termination) if the attempted transmission failed.  Retention across process
+// terminations is done using the the PrefServices facilities. The retained logs
+// (the ones that never got transmitted) are compressed and base64-encoded
+// before being persisted.
+//
+// Logs fall into one of two categories: "initial logs," and "ongoing logs."
+// There is at most one initial log sent for each complete run of Chrome (from
+// startup, to browser shutdown).  An initial log is generally transmitted some
+// short time (1 minute?) after startup, and includes stats such as recent crash
+// info, the number and types of plugins, etc.  The external server's response
+// to the initial log conceptually tells this MS if it should continue
+// transmitting logs (during this session). The server response can actually be
+// much more detailed, and always includes (at a minimum) how often additional
+// ongoing logs should be sent.
+//
+// After the above initial log, a series of ongoing logs will be transmitted.
+// The first ongoing log actually begins to accumulate information stating when
+// the MS was first constructed.  Note that even though the initial log is
+// commonly sent a full minute after startup, the initial log does not include
+// much in the way of user stats.   The most common interlog period (delay)
+// is 30 minutes. That time period starts when the first user action causes a
+// logging event.  This means that if there is no user action, there may be long
+// periods without any (ongoing) log transmissions.  Ongoing logs typically
+// contain very detailed records of user activities (ex: opened tab, closed
+// tab, fetched URL, maximized window, etc.)  In addition, just before an
+// ongoing log is closed out, a call is made to gather memory statistics.  Those
+// memory statistics are deposited into a histogram, and the log finalization
+// code is then called.  In the finalization, a call to a Histogram server
+// acquires a list of all local histograms that have been flagged for upload
+// to the UMA server.  The finalization also acquires the most recent number
+// of page loads, along with any counts of renderer or plugin crashes.
+//
+// When the browser shuts down, there will typically be a fragment of an ongoing
+// log that has not yet been transmitted.  At shutdown time, that fragment is
+// closed (including snapshotting histograms), and persisted, for potential
+// transmission during a future run of the product.
+//
+// There are two slightly abnormal shutdown conditions.  There is a
+// "disconnected scenario," and a "really fast startup and shutdown" scenario.
+// In the "never connected" situation, the user has (during the running of the
+// process) never established an internet connection.  As a result, attempts to
+// transmit the initial log have failed, and a lot(?) of data has accumulated in
+// the ongoing log (which didn't yet get closed, because there was never even a
+// contemplation of sending it).  There is also a kindred "lost connection"
+// situation, where a loss of connection prevented an ongoing log from being
+// transmitted, and a (still open) log was stuck accumulating a lot(?) of data,
+// while the earlier log retried its transmission.  In both of these
+// disconnected situations, two logs need to be, and are, persistently stored
+// for future transmission.
+//
+// The other unusual shutdown condition, termed "really fast startup and
+// shutdown," involves the deliberate user termination of the process before
+// the initial log is even formed or transmitted. In that situation, no logging
+// is done, but the historical crash statistics remain (unlogged) for inclusion
+// in a future run's initial log.  (i.e., we don't lose crash stats).
+//
+// With the above overview, we can now describe the state machine's various
+// states, based on the State enum specified in the state_ member.  Those states
+// are:
+//
+//  INITIALIZED,          // Constructor was called.
+//  INIT_TASK_SCHEDULED,  // Waiting for deferred init tasks to finish.
+//  INIT_TASK_DONE,       // Waiting for timer to send initial log.
+//  SENDING_LOGS,         // Sending logs and creating new ones when we run out.
+//
+// In more detail, we have:
+//
+//    INITIALIZED,            // Constructor was called.
+// The MS has been constructed, but has taken no actions to compose the
+// initial log.
+//
+//    INIT_TASK_SCHEDULED,    // Waiting for deferred init tasks to finish.
+// Typically about 30 seconds after startup, a task is sent to a second thread
+// (the file thread) to perform deferred (lower priority and slower)
+// initialization steps such as getting the list of plugins.  That task will
+// (when complete) make an async callback (via a Task) to indicate the
+// completion.
+//
+//    INIT_TASK_DONE,         // Waiting for timer to send initial log.
+// The callback has arrived, and it is now possible for an initial log to be
+// created.  This callback typically arrives back less than one second after
+// the deferred init task is dispatched.
+//
+//    SENDING_LOGS,  // Sending logs an creating new ones when we run out.
+// Logs from previous sessions have been loaded, and initial logs have been
+// created (an optional stability log and the first metrics log).  We will
+// send all of these logs, and when run out, we will start cutting new logs
+// to send.  We will also cut a new log if we expect a shutdown.
+//
+// The progression through the above states is simple, and sequential.
+// States proceed from INITIAL to SENDING_LOGS, and remain in the latter until
+// shutdown.
+//
+// Also note that whenever we successfully send a log, we mirror the list
+// of logs into the PrefService. This ensures that IF we crash, we won't start
+// up and retransmit our old logs again.
+//
+// Due to race conditions, it is always possible that a log file could be sent
+// twice.  For example, if a log file is sent, but not yet acknowledged by
+// the external server, and the user shuts down, then a copy of the log may be
+// saved for re-transmission.  These duplicates could be filtered out server
+// side, but are not expected to be a significant problem.
+//
+//
+//------------------------------------------------------------------------------
+
+#include "components/metrics/metrics_service.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_piece.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/metrics/environment_recorder.h"
+#include "components/metrics/field_trials_provider.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_log_manager.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_rotation_scheduler.h"
+#include "components/metrics/metrics_service_client.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/metrics/persistent_system_profile.h"
+#include "components/metrics/stability_metrics_provider.h"
+#include "components/metrics/url_constants.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/entropy_provider.h"
+
+namespace metrics {
+
+namespace {
+
+// The delay, in seconds, after starting recording before doing expensive
+// initialization work.
+#if defined(OS_ANDROID) || defined(OS_IOS)
+// On mobile devices, a significant portion of sessions last less than a minute.
+// Use a shorter timer on these platforms to avoid losing data.
+// TODO(dfalcantara): To avoid delaying startup, tighten up initialization so
+//                    that it occurs after the user gets their initial page.
+const int kInitializationDelaySeconds = 5;
+#else
+const int kInitializationDelaySeconds = 30;
+#endif
+
+// The browser last live timestamp is updated every 15 minutes.
+const int kUpdateAliveTimestampSeconds = 15 * 60;
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+void MarkAppCleanShutdownAndCommit(CleanExitBeacon* clean_exit_beacon,
+                                   PrefService* local_state) {
+  clean_exit_beacon->WriteBeaconValue(true);
+  ExecutionPhaseManager(local_state).OnAppEnterBackground();
+  // Start writing right away (write happens on a different thread).
+  local_state->CommitPendingWrite();
+}
+#endif  // defined(OS_ANDROID) || defined(OS_IOS)
+
+}  // namespace
+
+// static
+MetricsService::ShutdownCleanliness MetricsService::clean_shutdown_status_ =
+    MetricsService::CLEANLY_SHUTDOWN;
+
+// static
+void MetricsService::RegisterPrefs(PrefRegistrySimple* registry) {
+  CleanExitBeacon::RegisterPrefs(registry);
+  MetricsStateManager::RegisterPrefs(registry);
+  MetricsLog::RegisterPrefs(registry);
+  StabilityMetricsProvider::RegisterPrefs(registry);
+  ExecutionPhaseManager::RegisterPrefs(registry);
+  MetricsReportingService::RegisterPrefs(registry);
+
+  registry->RegisterIntegerPref(prefs::kMetricsSessionID, -1);
+
+  registry->RegisterInt64Pref(prefs::kUninstallLaunchCount, 0);
+  registry->RegisterInt64Pref(prefs::kUninstallMetricsUptimeSec, 0);
+}
+
+MetricsService::MetricsService(MetricsStateManager* state_manager,
+                               MetricsServiceClient* client,
+                               PrefService* local_state)
+    : reporting_service_(client, local_state),
+      histogram_snapshot_manager_(this),
+      state_manager_(state_manager),
+      client_(client),
+      local_state_(local_state),
+      recording_state_(UNSET),
+      test_mode_active_(false),
+      state_(INITIALIZED),
+      idle_since_last_transmission_(false),
+      session_id_(-1),
+      self_ptr_factory_(this) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(state_manager_);
+  DCHECK(client_);
+  DCHECK(local_state_);
+
+  RegisterMetricsProvider(
+      std::make_unique<StabilityMetricsProvider>(local_state_));
+
+  RegisterMetricsProvider(state_manager_->GetProvider());
+
+  RegisterMetricsProvider(std::make_unique<variations::FieldTrialsProvider>(
+      &synthetic_trial_registry_, base::StringPiece()));
+}
+
+MetricsService::~MetricsService() {
+  DisableRecording();
+}
+
+void MetricsService::InitializeMetricsRecordingState() {
+  reporting_service_.Initialize();
+  InitializeMetricsState();
+
+  base::Closure upload_callback =
+      base::Bind(&MetricsService::StartScheduledUpload,
+                 self_ptr_factory_.GetWeakPtr());
+
+  rotation_scheduler_.reset(new MetricsRotationScheduler(
+      upload_callback,
+      // MetricsServiceClient outlives MetricsService, and
+      // MetricsRotationScheduler is tied to the lifetime of |this|.
+      base::Bind(&MetricsServiceClient::GetStandardUploadInterval,
+                 base::Unretained(client_))));
+
+  // Init() has to be called after LogCrash() in order for LogCrash() to work.
+  delegating_provider_.Init();
+}
+
+void MetricsService::Start() {
+  HandleIdleSinceLastTransmission(false);
+  EnableRecording();
+  EnableReporting();
+}
+
+void MetricsService::StartRecordingForTests() {
+  test_mode_active_ = true;
+  EnableRecording();
+  DisableReporting();
+}
+
+void MetricsService::StartUpdatingLastLiveTimestamp() {
+  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&MetricsService::UpdateLastLiveTimestampTask,
+                     self_ptr_factory_.GetWeakPtr()),
+      base::TimeDelta::FromSeconds(kUpdateAliveTimestampSeconds));
+}
+
+void MetricsService::Stop() {
+  HandleIdleSinceLastTransmission(false);
+  DisableReporting();
+  DisableRecording();
+}
+
+void MetricsService::EnableReporting() {
+  if (reporting_service_.reporting_active())
+    return;
+  reporting_service_.EnableReporting();
+  StartSchedulerIfNecessary();
+}
+
+void MetricsService::DisableReporting() {
+  reporting_service_.DisableReporting();
+}
+
+std::string MetricsService::GetClientId() {
+  return state_manager_->client_id();
+}
+
+int64_t MetricsService::GetInstallDate() {
+  return state_manager_->GetInstallDate();
+}
+
+bool MetricsService::WasLastShutdownClean() const {
+  return state_manager_->clean_exit_beacon()->exited_cleanly();
+}
+
+void MetricsService::EnableRecording() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (recording_state_ == ACTIVE)
+    return;
+  recording_state_ = ACTIVE;
+
+  state_manager_->ForceClientIdCreation();
+  client_->SetMetricsClientId(state_manager_->client_id());
+
+  SystemProfileProto system_profile;
+  MetricsLog::RecordCoreSystemProfile(client_, &system_profile);
+  GlobalPersistentSystemProfile::GetInstance()->SetSystemProfile(
+      system_profile, /*complete=*/false);
+
+  if (!log_manager_.current_log())
+    OpenNewLog();
+
+  delegating_provider_.OnRecordingEnabled();
+
+  base::RemoveActionCallback(action_callback_);
+  action_callback_ = base::Bind(&MetricsService::OnUserAction,
+                                base::Unretained(this));
+  base::AddActionCallback(action_callback_);
+}
+
+void MetricsService::DisableRecording() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (recording_state_ == INACTIVE)
+    return;
+  recording_state_ = INACTIVE;
+
+  base::RemoveActionCallback(action_callback_);
+
+  delegating_provider_.OnRecordingDisabled();
+
+  PushPendingLogsToPersistentStorage();
+}
+
+bool MetricsService::recording_active() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return recording_state_ == ACTIVE;
+}
+
+bool MetricsService::reporting_active() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return reporting_service_.reporting_active();
+}
+
+bool MetricsService::has_unsent_logs() const {
+  return reporting_service_.metrics_log_store()->has_unsent_logs();
+}
+
+void MetricsService::RecordDelta(const base::HistogramBase& histogram,
+                                 const base::HistogramSamples& snapshot) {
+  log_manager_.current_log()->RecordHistogramDelta(histogram.histogram_name(),
+                                                   snapshot);
+}
+
+void MetricsService::HandleIdleSinceLastTransmission(bool in_idle) {
+  // If there wasn't a lot of action, maybe the computer was asleep, in which
+  // case, the log transmissions should have stopped.  Here we start them up
+  // again.
+  if (!in_idle && idle_since_last_transmission_)
+    StartSchedulerIfNecessary();
+  idle_since_last_transmission_ = in_idle;
+}
+
+void MetricsService::OnApplicationNotIdle() {
+  if (recording_state_ == ACTIVE)
+    HandleIdleSinceLastTransmission(false);
+}
+
+void MetricsService::RecordStartOfSessionEnd() {
+  LogCleanShutdown(false);
+}
+
+void MetricsService::RecordCompletedSessionEnd() {
+  LogCleanShutdown(true);
+}
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+void MetricsService::OnAppEnterBackground() {
+  rotation_scheduler_->Stop();
+  reporting_service_.Stop();
+
+  MarkAppCleanShutdownAndCommit(state_manager_->clean_exit_beacon(),
+                                local_state_);
+
+  // Give providers a chance to persist histograms as part of being
+  // backgrounded.
+  delegating_provider_.OnAppEnterBackground();
+
+  // At this point, there's no way of knowing when the process will be
+  // killed, so this has to be treated similar to a shutdown, closing and
+  // persisting all logs. Unlinke a shutdown, the state is primed to be ready
+  // to continue logging and uploading if the process does return.
+  if (recording_active() && state_ >= SENDING_LOGS) {
+    PushPendingLogsToPersistentStorage();
+    // Persisting logs closes the current log, so start recording a new log
+    // immediately to capture any background work that might be done before the
+    // process is killed.
+    OpenNewLog();
+  }
+}
+
+void MetricsService::OnAppEnterForeground() {
+  state_manager_->clean_exit_beacon()->WriteBeaconValue(false);
+  ExecutionPhaseManager(local_state_).OnAppEnterForeground();
+  StartSchedulerIfNecessary();
+}
+#else
+void MetricsService::LogNeedForCleanShutdown() {
+  state_manager_->clean_exit_beacon()->WriteBeaconValue(false);
+  // Redundant setting to be sure we call for a clean shutdown.
+  clean_shutdown_status_ = NEED_TO_SHUTDOWN;
+}
+#endif  // defined(OS_ANDROID) || defined(OS_IOS)
+
+// static
+void MetricsService::SetExecutionPhase(ExecutionPhase execution_phase,
+                                       PrefService* local_state) {
+  ExecutionPhaseManager(local_state).SetExecutionPhase(execution_phase);
+}
+
+void MetricsService::RecordBreakpadRegistration(bool success) {
+  StabilityMetricsProvider(local_state_).RecordBreakpadRegistration(success);
+}
+
+void MetricsService::RecordBreakpadHasDebugger(bool has_debugger) {
+  StabilityMetricsProvider(local_state_)
+      .RecordBreakpadHasDebugger(has_debugger);
+}
+
+void MetricsService::ClearSavedStabilityMetrics() {
+  delegating_provider_.ClearSavedStabilityMetrics();
+}
+
+void MetricsService::PushExternalLog(const std::string& log) {
+  log_store()->StoreLog(log, MetricsLog::ONGOING_LOG);
+}
+
+void MetricsService::UpdateMetricsUsagePrefs(const std::string& service_name,
+                                             int message_size,
+                                             bool is_cellular) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  reporting_service_.UpdateMetricsUsagePrefs(service_name, message_size,
+                                             is_cellular);
+}
+
+//------------------------------------------------------------------------------
+// private methods
+//------------------------------------------------------------------------------
+
+
+//------------------------------------------------------------------------------
+// Initialization methods
+
+void MetricsService::InitializeMetricsState() {
+  const int64_t buildtime = MetricsLog::GetBuildTime();
+  const std::string version = client_->GetVersionString();
+
+  bool version_changed = false;
+  EnvironmentRecorder recorder(local_state_);
+  int64_t previous_buildtime = recorder.GetLastBuildtime();
+  std::string previous_version = recorder.GetLastVersion();
+  if (previous_buildtime != buildtime || previous_version != version) {
+    recorder.SetBuildtimeAndVersion(buildtime, version);
+    version_changed = true;
+  }
+
+  session_id_ = local_state_->GetInteger(prefs::kMetricsSessionID);
+
+  StabilityMetricsProvider provider(local_state_);
+  if (!state_manager_->clean_exit_beacon()->exited_cleanly()) {
+    provider.LogCrash(
+        state_manager_->clean_exit_beacon()->browser_last_live_timestamp());
+    // Reset flag, and wait until we call LogNeedForCleanShutdown() before
+    // monitoring.
+    state_manager_->clean_exit_beacon()->WriteBeaconValue(true);
+    ExecutionPhaseManager manager(local_state_);
+    base::UmaHistogramSparse("Chrome.Browser.CrashedExecutionPhase",
+                             static_cast<int>(manager.GetExecutionPhase()));
+    manager.SetExecutionPhase(ExecutionPhase::UNINITIALIZED_PHASE);
+  }
+
+  // HasPreviousSessionData is called first to ensure it is never bypassed.
+  const bool is_initial_stability_log_required =
+      delegating_provider_.HasPreviousSessionData() ||
+      !state_manager_->clean_exit_beacon()->exited_cleanly();
+  bool has_initial_stability_log = false;
+  if (is_initial_stability_log_required) {
+    // If the previous session didn't exit cleanly, or if any provider
+    // explicitly requests it, prepare an initial stability log -
+    // provided UMA is enabled.
+    if (state_manager_->IsMetricsReportingEnabled()) {
+      has_initial_stability_log = PrepareInitialStabilityLog(previous_version);
+      if (!has_initial_stability_log)
+        provider.LogStabilityLogDeferred();
+    }
+  }
+
+  // If the version changed, but no initial stability log was generated, clear
+  // the stability stats from the previous version (so that they don't get
+  // attributed to the current version). This could otherwise happen due to a
+  // number of different edge cases, such as if the last version crashed before
+  // it could save off a system profile or if UMA reporting is disabled (which
+  // normally results in stats being accumulated).
+  if (version_changed && !has_initial_stability_log) {
+    ClearSavedStabilityMetrics();
+    provider.LogStabilityDataDiscarded();
+  }
+
+  // If the version changed, the system profile is obsolete and needs to be
+  // cleared. This is to avoid the stability data misattribution that could
+  // occur if the current version crashed before saving its own system profile.
+  // Note however this clearing occurs only after preparing the initial
+  // stability log, an operation that requires the previous version's system
+  // profile. At this point, stability metrics pertaining to the previous
+  // version have been cleared.
+  if (version_changed)
+    recorder.ClearEnvironmentFromPrefs();
+
+  // Update session ID.
+  ++session_id_;
+  local_state_->SetInteger(prefs::kMetricsSessionID, session_id_);
+
+  // Notify stability metrics providers about the launch.
+  provider.LogLaunch();
+  SetExecutionPhase(ExecutionPhase::START_METRICS_RECORDING, local_state_);
+  provider.CheckLastSessionEndCompleted();
+
+  // Call GetUptimes() for the first time, thus allowing all later calls
+  // to record incremental uptimes accurately.
+  base::TimeDelta ignored_uptime_parameter;
+  base::TimeDelta startup_uptime;
+  GetUptimes(local_state_, &startup_uptime, &ignored_uptime_parameter);
+  DCHECK_EQ(0, startup_uptime.InMicroseconds());
+
+  // Bookkeeping for the uninstall metrics.
+  IncrementLongPrefsValue(prefs::kUninstallLaunchCount);
+}
+
+void MetricsService::OnUserAction(const std::string& action) {
+  log_manager_.current_log()->RecordUserAction(action);
+  HandleIdleSinceLastTransmission(false);
+}
+
+void MetricsService::FinishedInitTask() {
+  DCHECK_EQ(INIT_TASK_SCHEDULED, state_);
+  state_ = INIT_TASK_DONE;
+
+  // Create the initial log.
+  if (!initial_metrics_log_) {
+    initial_metrics_log_ = CreateLog(MetricsLog::ONGOING_LOG);
+    delegating_provider_.OnDidCreateMetricsLog();
+  }
+
+  rotation_scheduler_->InitTaskComplete();
+}
+
+void MetricsService::GetUptimes(PrefService* pref,
+                                base::TimeDelta* incremental_uptime,
+                                base::TimeDelta* uptime) {
+  base::TimeTicks now = base::TimeTicks::Now();
+  // If this is the first call, init |first_updated_time_| and
+  // |last_updated_time_|.
+  if (last_updated_time_.is_null()) {
+    first_updated_time_ = now;
+    last_updated_time_ = now;
+  }
+  *incremental_uptime = now - last_updated_time_;
+  *uptime = now - first_updated_time_;
+  last_updated_time_ = now;
+
+  const int64_t incremental_time_secs = incremental_uptime->InSeconds();
+  if (incremental_time_secs > 0) {
+    int64_t metrics_uptime = pref->GetInt64(prefs::kUninstallMetricsUptimeSec);
+    metrics_uptime += incremental_time_secs;
+    pref->SetInt64(prefs::kUninstallMetricsUptimeSec, metrics_uptime);
+  }
+}
+
+//------------------------------------------------------------------------------
+// Recording control methods
+
+void MetricsService::OpenNewLog() {
+  DCHECK(!log_manager_.current_log());
+
+  log_manager_.BeginLoggingWithLog(CreateLog(MetricsLog::ONGOING_LOG));
+  delegating_provider_.OnDidCreateMetricsLog();
+  if (state_ == INITIALIZED) {
+    // We only need to schedule that run once.
+    state_ = INIT_TASK_SCHEDULED;
+
+    base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&MetricsService::StartInitTask,
+                       self_ptr_factory_.GetWeakPtr()),
+        base::TimeDelta::FromSeconds(kInitializationDelaySeconds));
+
+    base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&MetricsService::PrepareProviderMetricsTask,
+                       self_ptr_factory_.GetWeakPtr()),
+        base::TimeDelta::FromSeconds(2 * kInitializationDelaySeconds));
+  }
+}
+
+void MetricsService::StartInitTask() {
+  delegating_provider_.AsyncInit(base::Bind(&MetricsService::FinishedInitTask,
+                                            self_ptr_factory_.GetWeakPtr()));
+}
+
+void MetricsService::CloseCurrentLog() {
+  if (!log_manager_.current_log())
+    return;
+
+  // If a persistent allocator is in use, update its internal histograms (such
+  // as how much memory is being used) before reporting.
+  base::PersistentHistogramAllocator* allocator =
+      base::GlobalHistogramAllocator::Get();
+  if (allocator)
+    allocator->UpdateTrackingHistograms();
+
+  // Put incremental data (histogram deltas, and realtime stats deltas) at the
+  // end of all log transmissions (initial log handles this separately).
+  // RecordIncrementalStabilityElements only exists on the derived
+  // MetricsLog class.
+  MetricsLog* current_log = log_manager_.current_log();
+  DCHECK(current_log);
+  RecordCurrentEnvironment(current_log);
+  base::TimeDelta incremental_uptime;
+  base::TimeDelta uptime;
+  GetUptimes(local_state_, &incremental_uptime, &uptime);
+  current_log->RecordCurrentSessionData(&delegating_provider_,
+                                        incremental_uptime, uptime);
+  RecordCurrentHistograms();
+  current_log->TruncateEvents();
+  DVLOG(1) << "Generated an ongoing log.";
+  log_manager_.FinishCurrentLog(log_store());
+}
+
+void MetricsService::PushPendingLogsToPersistentStorage() {
+  if (state_ < SENDING_LOGS)
+    return;  // We didn't and still don't have time to get plugin list etc.
+
+  CloseCurrentLog();
+  log_store()->PersistUnsentLogs();
+}
+
+//------------------------------------------------------------------------------
+// Transmission of logs methods
+
+void MetricsService::StartSchedulerIfNecessary() {
+  // Never schedule cutting or uploading of logs in test mode.
+  if (test_mode_active_)
+    return;
+
+  // Even if reporting is disabled, the scheduler is needed to trigger the
+  // creation of the initial log, which must be done in order for any logs to be
+  // persisted on shutdown or backgrounding.
+  if (recording_active() &&
+      (reporting_active() || state_ < SENDING_LOGS)) {
+    rotation_scheduler_->Start();
+    reporting_service_.Start();
+  }
+}
+
+void MetricsService::StartScheduledUpload() {
+  DVLOG(1) << "StartScheduledUpload";
+  DCHECK(state_ >= INIT_TASK_DONE);
+
+  // If we're getting no notifications, then the log won't have much in it, and
+  // it's possible the computer is about to go to sleep, so don't upload and
+  // stop the scheduler.
+  // If recording has been turned off, the scheduler doesn't need to run.
+  // If reporting is off, proceed if the initial log hasn't been created, since
+  // that has to happen in order for logs to be cut and stored when persisting.
+  // TODO(stuartmorgan): Call Stop() on the scheduler when reporting and/or
+  // recording are turned off instead of letting it fire and then aborting.
+  if (idle_since_last_transmission_ ||
+      !recording_active() ||
+      (!reporting_active() && state_ >= SENDING_LOGS)) {
+    rotation_scheduler_->Stop();
+    rotation_scheduler_->RotationFinished();
+    return;
+  }
+
+  // If there are unsent logs, send the next one. If not, start the asynchronous
+  // process of finalizing the current log for upload.
+  if (state_ == SENDING_LOGS && has_unsent_logs()) {
+    reporting_service_.Start();
+    rotation_scheduler_->RotationFinished();
+  } else {
+    // There are no logs left to send, so start creating a new one.
+    client_->CollectFinalMetricsForLog(
+        base::Bind(&MetricsService::OnFinalLogInfoCollectionDone,
+                   self_ptr_factory_.GetWeakPtr()));
+  }
+}
+
+void MetricsService::OnFinalLogInfoCollectionDone() {
+  DVLOG(1) << "OnFinalLogInfoCollectionDone";
+
+  // Abort if metrics were turned off during the final info gathering.
+  if (!recording_active()) {
+    rotation_scheduler_->Stop();
+    rotation_scheduler_->RotationFinished();
+    return;
+  }
+
+  if (state_ == INIT_TASK_DONE) {
+    PrepareInitialMetricsLog();
+  } else {
+    DCHECK_EQ(SENDING_LOGS, state_);
+    CloseCurrentLog();
+    OpenNewLog();
+  }
+  reporting_service_.Start();
+  rotation_scheduler_->RotationFinished();
+  HandleIdleSinceLastTransmission(true);
+}
+
+bool MetricsService::PrepareInitialStabilityLog(
+    const std::string& prefs_previous_version) {
+  DCHECK_EQ(INITIALIZED, state_);
+
+  std::unique_ptr<MetricsLog> initial_stability_log(
+      CreateLog(MetricsLog::INITIAL_STABILITY_LOG));
+
+  // Do not call OnDidCreateMetricsLog here because the stability
+  // log describes stats from the _previous_ session.
+  std::string system_profile_app_version;
+  if (!initial_stability_log->LoadSavedEnvironmentFromPrefs(
+          local_state_, &system_profile_app_version)) {
+    return false;
+  }
+  if (system_profile_app_version != prefs_previous_version)
+    StabilityMetricsProvider(local_state_).LogStabilityVersionMismatch();
+
+  log_manager_.PauseCurrentLog();
+  log_manager_.BeginLoggingWithLog(std::move(initial_stability_log));
+
+  // Note: Some stability providers may record stability stats via histograms,
+  //       so this call has to be after BeginLoggingWithLog().
+  log_manager_.current_log()->RecordPreviousSessionData(&delegating_provider_);
+  RecordCurrentStabilityHistograms();
+
+  DVLOG(1) << "Generated an stability log.";
+  log_manager_.FinishCurrentLog(log_store());
+  log_manager_.ResumePausedLog();
+
+  // Store unsent logs, including the stability log that was just saved, so
+  // that they're not lost in case of a crash before upload time.
+  log_store()->PersistUnsentLogs();
+
+  return true;
+}
+
+void MetricsService::PrepareInitialMetricsLog() {
+  DCHECK_EQ(INIT_TASK_DONE, state_);
+
+  RecordCurrentEnvironment(initial_metrics_log_.get());
+  base::TimeDelta incremental_uptime;
+  base::TimeDelta uptime;
+  GetUptimes(local_state_, &incremental_uptime, &uptime);
+
+  // Histograms only get written to the current log, so make the new log current
+  // before writing them.
+  log_manager_.PauseCurrentLog();
+  log_manager_.BeginLoggingWithLog(std::move(initial_metrics_log_));
+
+  // Note: Some stability providers may record stability stats via histograms,
+  //       so this call has to be after BeginLoggingWithLog().
+  log_manager_.current_log()->RecordCurrentSessionData(
+      &delegating_provider_, base::TimeDelta(), base::TimeDelta());
+  RecordCurrentHistograms();
+
+  DVLOG(1) << "Generated an initial log.";
+  log_manager_.FinishCurrentLog(log_store());
+  log_manager_.ResumePausedLog();
+
+  // Store unsent logs, including the initial log that was just saved, so
+  // that they're not lost in case of a crash before upload time.
+  log_store()->PersistUnsentLogs();
+
+  state_ = SENDING_LOGS;
+}
+
+void MetricsService::IncrementLongPrefsValue(const char* path) {
+  int64_t value = local_state_->GetInt64(path);
+  local_state_->SetInt64(path, value + 1);
+}
+
+bool MetricsService::UmaMetricsProperlyShutdown() {
+  CHECK(clean_shutdown_status_ == CLEANLY_SHUTDOWN ||
+        clean_shutdown_status_ == NEED_TO_SHUTDOWN);
+  return clean_shutdown_status_ == CLEANLY_SHUTDOWN;
+}
+
+void MetricsService::RegisterMetricsProvider(
+    std::unique_ptr<MetricsProvider> provider) {
+  DCHECK_EQ(INITIALIZED, state_);
+  delegating_provider_.RegisterMetricsProvider(std::move(provider));
+}
+
+void MetricsService::CheckForClonedInstall() {
+  state_manager_->CheckForClonedInstall();
+}
+
+std::unique_ptr<MetricsLog> MetricsService::CreateLog(
+    MetricsLog::LogType log_type) {
+  return std::make_unique<MetricsLog>(state_manager_->client_id(), session_id_,
+                                      log_type, client_);
+}
+
+std::string MetricsService::RecordCurrentEnvironmentHelper(
+    MetricsLog* log,
+    PrefService* local_state,
+    DelegatingProvider* delegating_provider) {
+  const SystemProfileProto& system_profile =
+      log->RecordEnvironment(delegating_provider);
+  EnvironmentRecorder recorder(local_state);
+  return recorder.SerializeAndRecordEnvironmentToPrefs(system_profile);
+}
+
+void MetricsService::RecordCurrentEnvironment(MetricsLog* log) {
+  DCHECK(client_);
+  std::string serialized_proto =
+      RecordCurrentEnvironmentHelper(log, local_state_, &delegating_provider_);
+  GlobalPersistentSystemProfile::GetInstance()->SetSystemProfile(
+      serialized_proto, /*complete=*/true);
+  client_->OnEnvironmentUpdate(&serialized_proto);
+}
+
+void MetricsService::RecordCurrentHistograms() {
+  DCHECK(log_manager_.current_log());
+
+  // "true" indicates that StatisticsRecorder should include histograms held in
+  // persistent storage.
+  base::StatisticsRecorder::PrepareDeltas(
+      true, base::Histogram::kNoFlags,
+      base::Histogram::kUmaTargetedHistogramFlag, &histogram_snapshot_manager_);
+  delegating_provider_.RecordHistogramSnapshots(&histogram_snapshot_manager_);
+}
+
+void MetricsService::RecordCurrentStabilityHistograms() {
+  DCHECK(log_manager_.current_log());
+  // "true" indicates that StatisticsRecorder should include histograms held in
+  // persistent storage.
+  base::StatisticsRecorder::PrepareDeltas(
+      true, base::Histogram::kNoFlags,
+      base::Histogram::kUmaStabilityHistogramFlag,
+      &histogram_snapshot_manager_);
+  delegating_provider_.RecordInitialHistogramSnapshots(
+      &histogram_snapshot_manager_);
+}
+
+bool MetricsService::PrepareProviderMetricsLog() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Create a new log. This will have some defaut values injected in it but
+  // those will be overwritten when an embedded profile is extracted.
+  std::unique_ptr<MetricsLog> log = CreateLog(MetricsLog::INDEPENDENT_LOG);
+
+  for (auto& provider : delegating_provider_.GetProviders()) {
+    if (log->LoadIndependentMetrics(provider.get())) {
+      log_manager_.PauseCurrentLog();
+      log_manager_.BeginLoggingWithLog(std::move(log));
+      log_manager_.FinishCurrentLog(log_store());
+      log_manager_.ResumePausedLog();
+      return true;
+    }
+  }
+  return false;
+}
+
+void MetricsService::PrepareProviderMetricsTask() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  bool found = PrepareProviderMetricsLog();
+  base::TimeDelta next_check = found ? base::TimeDelta::FromSeconds(5)
+                                     : base::TimeDelta::FromMinutes(15);
+  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&MetricsService::PrepareProviderMetricsTask,
+                     self_ptr_factory_.GetWeakPtr()),
+      next_check);
+}
+
+void MetricsService::LogCleanShutdown(bool end_completed) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Redundant setting to assure that we always reset this value at shutdown
+  // (and that we don't use some alternate path, and not call LogCleanShutdown).
+  clean_shutdown_status_ = CLEANLY_SHUTDOWN;
+  client_->OnLogCleanShutdown();
+  state_manager_->clean_exit_beacon()->WriteBeaconValue(true);
+  SetExecutionPhase(ExecutionPhase::SHUTDOWN_COMPLETE, local_state_);
+  StabilityMetricsProvider(local_state_).MarkSessionEndCompleted(end_completed);
+}
+
+void MetricsService::UpdateLastLiveTimestampTask() {
+  state_manager_->clean_exit_beacon()->UpdateLastLiveTimestamp();
+
+  // Schecule the next update.
+  StartUpdatingLastLiveTimestamp();
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_service.h b/components/metrics/metrics_service.h
new file mode 100644
index 0000000..6471314
--- /dev/null
+++ b/components/metrics/metrics_service.h
@@ -0,0 +1,402 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines a service that collects information about the user
+// experience in order to help improve future versions of the app.
+
+#ifndef COMPONENTS_METRICS_METRICS_SERVICE_H_
+#define COMPONENTS_METRICS_METRICS_SERVICE_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram_flattener.h"
+#include "base/metrics/histogram_snapshot_manager.h"
+#include "base/metrics/user_metrics.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/metrics/clean_exit_beacon.h"
+#include "components/metrics/delegating_provider.h"
+#include "components/metrics/execution_phase.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_log_manager.h"
+#include "components/metrics/metrics_log_store.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/metrics_reporting_service.h"
+#include "components/metrics/net/network_metrics_provider.h"
+#include "components/variations/synthetic_trial_registry.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace base {
+class HistogramSamples;
+class PrefService;
+}
+
+namespace metrics {
+
+class MetricsRotationScheduler;
+class MetricsServiceClient;
+class MetricsStateManager;
+
+// See metrics_service.cc for a detailed description.
+class MetricsService : public base::HistogramFlattener {
+ public:
+  // Creates the MetricsService with the given |state_manager|, |client|, and
+  // |local_state|.  Does not take ownership of the paramaters; instead stores
+  // a weak pointer to each. Caller should ensure that the parameters are valid
+  // for the lifetime of this class.
+  MetricsService(MetricsStateManager* state_manager,
+                 MetricsServiceClient* client,
+                 PrefService* local_state);
+  ~MetricsService() override;
+
+  // Initializes metrics recording state. Updates various bookkeeping values in
+  // prefs and sets up the scheduler. This is a separate function rather than
+  // being done by the constructor so that field trials could be created before
+  // this is run.
+  void InitializeMetricsRecordingState();
+
+  // Starts the metrics system, turning on recording and uploading of metrics.
+  // Should be called when starting up with metrics enabled, or when metrics
+  // are turned on.
+  void Start();
+
+  // Starts the metrics system in a special test-only mode. Metrics won't ever
+  // be uploaded or persisted in this mode, but metrics will be recorded in
+  // memory.
+  void StartRecordingForTests();
+
+  // Starts updating the "last live" browser timestamp.
+  void StartUpdatingLastLiveTimestamp();
+
+  // Shuts down the metrics system. Should be called at shutdown, or if metrics
+  // are turned off.
+  void Stop();
+
+  // Enable/disable transmission of accumulated logs and crash reports (dumps).
+  // Calling Start() automatically enables reporting, but sending is
+  // asyncronous so this can be called immediately after Start() to prevent
+  // any uploading.
+  void EnableReporting();
+  void DisableReporting();
+
+  // Returns the client ID for this client, or the empty string if metrics
+  // recording is not currently running.
+  std::string GetClientId();
+
+  // Returns the install date of the application, in seconds since the epoch.
+  int64_t GetInstallDate();
+
+  // Returns the date at which the current metrics client ID was created as
+  // an int64_t containing seconds since the epoch.
+  int64_t GetMetricsReportingEnabledDate();
+
+  // Returns true if the last session exited cleanly.
+  bool WasLastShutdownClean() const;
+
+  // Registers local state prefs used by this class.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+  // HistogramFlattener:
+  void RecordDelta(const base::HistogramBase& histogram,
+                   const base::HistogramSamples& snapshot) override;
+
+  // This should be called when the application is not idle, i.e. the user seems
+  // to be interacting with the application.
+  void OnApplicationNotIdle();
+
+  // Invoked when we get a WM_SESSIONEND. This places a value in prefs that is
+  // reset when RecordCompletedSessionEnd is invoked.
+  void RecordStartOfSessionEnd();
+
+  // This should be called when the application is shutting down. It records
+  // that session end was successful.
+  void RecordCompletedSessionEnd();
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+  // Called when the application is going into background mode.
+  void OnAppEnterBackground();
+
+  // Called when the application is coming out of background mode.
+  void OnAppEnterForeground();
+#else
+  // Set the dirty flag, which will require a later call to LogCleanShutdown().
+  void LogNeedForCleanShutdown();
+#endif  // defined(OS_ANDROID) || defined(OS_IOS)
+
+  static void SetExecutionPhase(ExecutionPhase execution_phase,
+                                PrefService* local_state);
+
+  // Saves in the preferences if the crash report registration was successful.
+  // This count is eventually send via UMA logs.
+  void RecordBreakpadRegistration(bool success);
+
+  // Saves in the preferences if the browser is running under a debugger.
+  // This count is eventually send via UMA logs.
+  void RecordBreakpadHasDebugger(bool has_debugger);
+
+  bool recording_active() const;
+  bool reporting_active() const;
+  bool has_unsent_logs() const;
+
+  // Redundant test to ensure that we are notified of a clean exit.
+  // This value should be true when process has completed shutdown.
+  static bool UmaMetricsProperlyShutdown();
+
+  // Register the specified |provider| to provide additional metrics into the
+  // UMA log. Should be called during MetricsService initialization only.
+  void RegisterMetricsProvider(std::unique_ptr<MetricsProvider> provider);
+
+  // Check if this install was cloned or imaged from another machine. If a
+  // clone is detected, reset the client id and low entropy source. This
+  // should not be called more than once.
+  void CheckForClonedInstall();
+
+  // Clears the stability metrics that are saved in local state.
+  void ClearSavedStabilityMetrics();
+
+  // Pushes a log that has been generated by an external component.
+  void PushExternalLog(const std::string& log);
+
+  // Updates data usage tracking prefs with the specified values.
+  void UpdateMetricsUsagePrefs(const std::string& service_name,
+                               int message_size,
+                               bool is_cellular);
+
+  variations::SyntheticTrialRegistry* synthetic_trial_registry() {
+    return &synthetic_trial_registry_;
+  }
+
+ protected:
+  // Exposed for testing.
+  MetricsLogManager* log_manager() { return &log_manager_; }
+  MetricsLogStore* log_store() {
+    return reporting_service_.metrics_log_store();
+  }
+
+  // Records the current environment (system profile) in |log|, and persists
+  // the results in prefs.
+  // Exposed for testing.
+  static std::string RecordCurrentEnvironmentHelper(
+      MetricsLog* log,
+      PrefService* local_state,
+      DelegatingProvider* delegating_provider);
+
+ private:
+  // The MetricsService has a lifecycle that is stored as a state.
+  // See metrics_service.cc for description of this lifecycle.
+  enum State {
+    INITIALIZED,          // Constructor was called.
+    INIT_TASK_SCHEDULED,  // Waiting for deferred init tasks to finish.
+    INIT_TASK_DONE,       // Waiting for timer to send initial log.
+    SENDING_LOGS,         // Sending logs an creating new ones when we run out.
+  };
+
+  enum ShutdownCleanliness {
+    CLEANLY_SHUTDOWN = 0xdeadbeef,
+    NEED_TO_SHUTDOWN = ~CLEANLY_SHUTDOWN
+  };
+
+  // The current state of recording for the MetricsService. The state is UNSET
+  // until set to something else, at which point it remains INACTIVE or ACTIVE
+  // for the lifetime of the object.
+  enum RecordingState {
+    INACTIVE,
+    ACTIVE,
+    UNSET
+  };
+
+  // Calls into the client to initialize some system profile metrics.
+  void StartInitTask();
+
+  // Callback that moves the state to INIT_TASK_DONE. When this is called, the
+  // state should be INIT_TASK_SCHEDULED.
+  void FinishedInitTask();
+
+  void OnUserAction(const std::string& action);
+
+  // Get the amount of uptime since this process started and since the last
+  // call to this function.  Also updates the cumulative uptime metric (stored
+  // as a pref) for uninstall.  Uptimes are measured using TimeTicks, which
+  // guarantees that it is monotonic and does not jump if the user changes
+  // their clock.  The TimeTicks implementation also makes the clock not
+  // count time the computer is suspended.
+  void GetUptimes(PrefService* pref,
+                  base::TimeDelta* incremental_uptime,
+                  base::TimeDelta* uptime);
+
+  // Turns recording on or off.
+  // DisableRecording() also forces a persistent save of logging state (if
+  // anything has been recorded, or transmitted).
+  void EnableRecording();
+  void DisableRecording();
+
+  // If in_idle is true, sets idle_since_last_transmission to true.
+  // If in_idle is false and idle_since_last_transmission_ is true, sets
+  // idle_since_last_transmission to false and starts the timer (provided
+  // starting the timer is permitted).
+  void HandleIdleSinceLastTransmission(bool in_idle);
+
+  // Set up client ID, session ID, etc.
+  void InitializeMetricsState();
+
+  // Opens a new log for recording user experience metrics.
+  void OpenNewLog();
+
+  // Closes out the current log after adding any last information.
+  void CloseCurrentLog();
+
+  // Pushes the text of the current and staged logs into persistent storage.
+  // Called when Chrome shuts down.
+  void PushPendingLogsToPersistentStorage();
+
+  // Ensures that scheduler is running, assuming the current settings are such
+  // that metrics should be reported. If not, this is a no-op.
+  void StartSchedulerIfNecessary();
+
+  // Starts the process of uploading metrics data.
+  void StartScheduledUpload();
+
+  // Called by the client via a callback when final log info collection is
+  // complete.
+  void OnFinalLogInfoCollectionDone();
+
+  // Prepares the initial stability log, which is only logged when the previous
+  // run of Chrome crashed.  This log contains any stability metrics left over
+  // from that previous run, and only these stability metrics.  It uses the
+  // system profile from the previous session.  |prefs_previous_version| is used
+  // to validate the version number recovered from the system profile.  Returns
+  // true if a log was created.
+  bool PrepareInitialStabilityLog(const std::string& prefs_previous_version);
+
+  // Prepares the initial metrics log, which includes startup histograms and
+  // profiler data, as well as incremental stability-related metrics.
+  void PrepareInitialMetricsLog();
+
+  // Reads, increments and then sets the specified long preference that is
+  // stored as a string.
+  void IncrementLongPrefsValue(const char* path);
+
+  // Records that the browser was shut down cleanly.
+  void LogCleanShutdown(bool end_completed);
+
+  // Creates a new MetricsLog instance with the given |log_type|.
+  std::unique_ptr<MetricsLog> CreateLog(MetricsLog::LogType log_type);
+
+  // Records the current environment (system profile) in |log|, and persists
+  // the results in prefs and GlobalPersistentSystemProfile.
+  // Exposed for testing.
+  void RecordCurrentEnvironment(MetricsLog* log);
+
+  // Record complete list of histograms into the current log.
+  // Called when we close a log.
+  void RecordCurrentHistograms();
+
+  // Record complete list of stability histograms into the current log,
+  // i.e., histograms with the |kUmaStabilityHistogramFlag| flag set.
+  void RecordCurrentStabilityHistograms();
+
+  // Record a single independent profile and associated histogram from
+  // metrics providers. If this returns true, one was found and there may
+  // be more.
+  bool PrepareProviderMetricsLog();
+
+  // Records one independent histogram log and then reschedules itself to
+  // check for others. The interval is so as to not adversely impact the UI.
+  void PrepareProviderMetricsTask();
+
+  // Updates the "last live" browser timestamp and schedules the next update.
+  void UpdateLastLiveTimestampTask();
+
+  // Sub-service for uploading logs.
+  MetricsReportingService reporting_service_;
+
+  // Manager for the various in-flight logs.
+  MetricsLogManager log_manager_;
+
+  // |histogram_snapshot_manager_| prepares histogram deltas for transmission.
+  base::HistogramSnapshotManager histogram_snapshot_manager_;
+
+  // Used to manage various metrics reporting state prefs, such as client id,
+  // low entropy source and whether metrics reporting is enabled. Weak pointer.
+  MetricsStateManager* const state_manager_;
+
+  // Used to interact with the embedder. Weak pointer; must outlive |this|
+  // instance.
+  MetricsServiceClient* const client_;
+
+  // Registered metrics providers.
+  DelegatingProvider delegating_provider_;
+
+  PrefService* local_state_;
+
+  base::ActionCallback action_callback_;
+
+  // Indicate whether recording and reporting are currently happening.
+  // These should not be set directly, but by calling SetRecording and
+  // SetReporting.
+  RecordingState recording_state_;
+
+  // Indicate whether test mode is enabled, where the initial log should never
+  // be cut, and logs are neither persisted nor uploaded.
+  bool test_mode_active_;
+
+  // The progression of states made by the browser are recorded in the following
+  // state.
+  State state_;
+
+  // The initial metrics log, used to record startup metrics (histograms and
+  // profiler data). Note that if a crash occurred in the previous session, an
+  // initial stability log may be sent before this.
+  std::unique_ptr<MetricsLog> initial_metrics_log_;
+
+  // Whether the MetricsService object has received any notifications since
+  // the last time a transmission was sent.
+  bool idle_since_last_transmission_;
+
+  // A number that identifies the how many times the app has been launched.
+  int session_id_;
+
+  // The scheduler for determining when log rotations should happen.
+  std::unique_ptr<MetricsRotationScheduler> rotation_scheduler_;
+
+  // Stores the time of the first call to |GetUptimes()|.
+  base::TimeTicks first_updated_time_;
+
+  // Stores the time of the last call to |GetUptimes()|.
+  base::TimeTicks last_updated_time_;
+
+  variations::SyntheticTrialRegistry synthetic_trial_registry_;
+
+  // Redundant marker to check that we completed our shutdown, and set the
+  // exited-cleanly bit in the prefs.
+  static ShutdownCleanliness clean_shutdown_status_;
+
+  FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest, IsPluginProcess);
+  FRIEND_TEST_ALL_PREFIXES(MetricsServiceTest,
+                           PermutedEntropyCacheClearedWhenLowEntropyReset);
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // Weak pointers factory used to post task on different threads. All weak
+  // pointers managed by this factory have the same lifetime as MetricsService.
+  base::WeakPtrFactory<MetricsService> self_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsService);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_SERVICE_H_
diff --git a/components/metrics/metrics_service_accessor.cc b/components/metrics/metrics_service_accessor.cc
new file mode 100644
index 0000000..f00d2e8
--- /dev/null
+++ b/components/metrics/metrics_service_accessor.cc
@@ -0,0 +1,96 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_service_accessor.h"
+
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_service.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/hashing.h"
+
+namespace metrics {
+namespace {
+
+bool g_force_official_enabled_test = false;
+
+bool IsMetricsReportingEnabledForOfficialBuild(PrefService* pref_service) {
+  // In official builds, disable metrics when reporting field trials are
+  // forced; otherwise, use the value of the user's preference to determine
+  // whether to enable metrics reporting.
+  return !base::CommandLine::ForCurrentProcess()->HasSwitch(
+             switches::kForceFieldTrials) &&
+         pref_service->GetBoolean(prefs::kMetricsReportingEnabled);
+}
+
+}  // namespace
+
+// static
+bool MetricsServiceAccessor::IsMetricsReportingEnabled(
+    PrefService* pref_service) {
+#if defined(GOOGLE_CHROME_BUILD)
+  return IsMetricsReportingEnabledForOfficialBuild(pref_service);
+#else
+  // In non-official builds, disable metrics reporting completely.
+  return g_force_official_enabled_test
+             ? IsMetricsReportingEnabledForOfficialBuild(pref_service)
+             : false;
+#endif  // defined(GOOGLE_CHROME_BUILD)
+}
+
+// static
+bool MetricsServiceAccessor::RegisterSyntheticFieldTrial(
+    MetricsService* metrics_service,
+    base::StringPiece trial_name,
+    base::StringPiece group_name) {
+  return RegisterSyntheticFieldTrialWithNameAndGroupHash(
+      metrics_service, variations::HashName(trial_name),
+      variations::HashName(group_name));
+}
+
+// static
+bool MetricsServiceAccessor::RegisterSyntheticMultiGroupFieldTrial(
+    MetricsService* metrics_service,
+    base::StringPiece trial_name,
+    const std::vector<uint32_t>& group_name_hashes) {
+  if (!metrics_service)
+    return false;
+
+  metrics_service->synthetic_trial_registry()
+      ->RegisterSyntheticMultiGroupFieldTrial(variations::HashName(trial_name),
+                                              group_name_hashes);
+  return true;
+}
+
+// static
+bool MetricsServiceAccessor::RegisterSyntheticFieldTrialWithNameHash(
+    MetricsService* metrics_service,
+    uint32_t trial_name_hash,
+    base::StringPiece group_name) {
+  return RegisterSyntheticFieldTrialWithNameAndGroupHash(
+      metrics_service, trial_name_hash, variations::HashName(group_name));
+}
+
+// static
+bool MetricsServiceAccessor::RegisterSyntheticFieldTrialWithNameAndGroupHash(
+    MetricsService* metrics_service,
+    uint32_t trial_name_hash,
+    uint32_t group_name_hash) {
+  if (!metrics_service)
+    return false;
+
+  variations::SyntheticTrialGroup trial_group(trial_name_hash, group_name_hash);
+  metrics_service->synthetic_trial_registry()->RegisterSyntheticFieldTrial(
+      trial_group);
+  return true;
+}
+
+// static
+void MetricsServiceAccessor::SetForceIsMetricsReportingEnabledPrefLookup(
+    bool value) {
+  g_force_official_enabled_test = value;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_service_accessor.h b/components/metrics/metrics_service_accessor.h
new file mode 100644
index 0000000..3a36278
--- /dev/null
+++ b/components/metrics/metrics_service_accessor.h
@@ -0,0 +1,81 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_SERVICE_ACCESSOR_H_
+#define COMPONENTS_METRICS_METRICS_SERVICE_ACCESSOR_H_
+
+#include <stdint.h>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+
+class PrefService;
+
+namespace metrics {
+
+class MetricsService;
+
+// This class limits and documents access to metrics service helper methods.
+// These methods are protected so each user has to inherit own program-specific
+// specialization and enable access there by declaring friends.
+class MetricsServiceAccessor {
+ protected:
+  // Constructor declared as protected to enable inheritance. Descendants should
+  // disallow instantiation.
+  MetricsServiceAccessor() {}
+
+  // Returns whether metrics reporting is enabled, using the value of the
+  // kMetricsReportingEnabled pref in |pref_service| to determine whether user
+  // has enabled reporting.
+  static bool IsMetricsReportingEnabled(PrefService* pref_service);
+
+
+  // Registers a field trial name and group with |metrics_service| (if not
+  // null), to be used to annotate a UMA report with a particular configuration
+  // state. Returns true on success.
+  // See the comment on MetricsService::RegisterSyntheticFieldTrial() for
+  // details.
+  static bool RegisterSyntheticFieldTrial(MetricsService* metrics_service,
+                                          base::StringPiece trial_name,
+                                          base::StringPiece group_name);
+
+  // Registers a field trial name and set of groups with |metrics_service| (if
+  // not null), to be used to annotate a UMA report with a particular
+  // configuration state. Returns true on success.
+  // See the comment on MetricsService::RegisterSyntheticMultiGroupFieldTrial()
+  // for details.
+  static bool RegisterSyntheticMultiGroupFieldTrial(
+      MetricsService* metrics_service,
+      base::StringPiece trial_name,
+      const std::vector<uint32_t>& group_name_hashes);
+
+  // Same as RegisterSyntheticFieldTrial above, but takes in the trial name as a
+  // hash rather than computing the hash from the string.
+  static bool RegisterSyntheticFieldTrialWithNameHash(
+      MetricsService* metrics_service,
+      uint32_t trial_name_hash,
+      base::StringPiece group_name);
+
+  // Same as RegisterSyntheticFieldTrial above, but takes in the trial and group
+  // names as hashes rather than computing those hashes from the strings.
+  static bool RegisterSyntheticFieldTrialWithNameAndGroupHash(
+      MetricsService* metrics_service,
+      uint32_t trial_name_hash,
+      uint32_t group_name_hash);
+
+  // IsMetricsReportingEnabled() in non-official builds unconditionally returns
+  // false. This results in different behavior for tests running in official vs
+  // non-official builds. To get consistent behavior call this with true, which
+  // forces non-official builds to look at the prefs value official builds look
+  // at.
+  static void SetForceIsMetricsReportingEnabledPrefLookup(bool value);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MetricsServiceAccessor);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_SERVICE_ACCESSOR_H_
diff --git a/components/metrics/metrics_service_client.cc b/components/metrics/metrics_service_client.cc
new file mode 100644
index 0000000..a80dc8a
--- /dev/null
+++ b/components/metrics/metrics_service_client.cc
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_service_client.h"
+
+#include "components/metrics/url_constants.h"
+
+namespace metrics {
+
+MetricsServiceClient::MetricsServiceClient() {}
+
+MetricsServiceClient::~MetricsServiceClient() {}
+
+ukm::UkmService* MetricsServiceClient::GetUkmService() {
+  return nullptr;
+}
+
+bool MetricsServiceClient::IsReportingPolicyManaged() {
+  return false;
+}
+
+EnableMetricsDefault MetricsServiceClient::GetMetricsReportingDefaultState() {
+  return EnableMetricsDefault::DEFAULT_UNKNOWN;
+}
+
+bool MetricsServiceClient::IsUMACellularUploadLogicEnabled() {
+  return false;
+}
+
+std::string MetricsServiceClient::GetMetricsServerUrl() {
+  return kNewMetricsServerUrl;
+}
+
+std::string MetricsServiceClient::GetInsecureMetricsServerUrl() {
+  return kNewMetricsServerUrlInsecure;
+}
+
+bool MetricsServiceClient::SyncStateAllowsUkm() {
+  return false;
+}
+
+bool MetricsServiceClient::SyncStateAllowsExtensionUkm() {
+  return false;
+}
+
+bool MetricsServiceClient::AreNotificationListenersEnabledOnAllProfiles() {
+  return false;
+}
+
+void MetricsServiceClient::SetUpdateRunningServicesCallback(
+    const base::Closure& callback) {
+  update_running_services_ = callback;
+}
+
+void MetricsServiceClient::UpdateRunningServices() {
+  if (update_running_services_)
+    update_running_services_.Run();
+}
+
+std::string MetricsServiceClient::GetAppPackageName() {
+  return std::string();
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_service_client.h b/components/metrics/metrics_service_client.h
new file mode 100644
index 0000000..c1bc828
--- /dev/null
+++ b/components/metrics/metrics_service_client.h
@@ -0,0 +1,151 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_SERVICE_CLIENT_H_
+#define COMPONENTS_METRICS_METRICS_SERVICE_CLIENT_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "components/metrics/metrics_reporting_default_state.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace ukm {
+class UkmService;
+}
+
+namespace metrics {
+
+class MetricsLogUploader;
+class MetricsService;
+
+// An abstraction of operations that depend on the embedder's (e.g. Chrome)
+// environment.
+class MetricsServiceClient {
+ public:
+  MetricsServiceClient();
+  virtual ~MetricsServiceClient();
+
+  // Returns the MetricsService instance that this client is associated with.
+  // With the exception of testing contexts, the returned instance must be valid
+  // for the lifetime of this object (typically, the embedder's client
+  // implementation will own the MetricsService instance being returned).
+  virtual MetricsService* GetMetricsService() = 0;
+
+  // Returns the UkmService instance that this client is associated with.
+  virtual ukm::UkmService* GetUkmService();
+
+  // Registers the client id with other services (e.g. crash reporting), called
+  // when metrics recording gets enabled.
+  virtual void SetMetricsClientId(const std::string& client_id) = 0;
+
+  // Returns the product value to use in uploaded reports, which will be used to
+  // set the ChromeUserMetricsExtension.product field. See comments on that
+  // field on why it's an int32_t rather than an enum.
+  virtual int32_t GetProduct() = 0;
+
+  // Returns the current application locale (e.g. "en-US").
+  virtual std::string GetApplicationLocale() = 0;
+
+  // Retrieves the brand code string associated with the install, returning
+  // false if no brand code is available.
+  virtual bool GetBrand(std::string* brand_code) = 0;
+
+  // Returns the release channel (e.g. stable, beta, etc) of the application.
+  virtual SystemProfileProto::Channel GetChannel() = 0;
+
+  // Returns the version of the application as a string.
+  virtual std::string GetVersionString() = 0;
+
+  // Called by the metrics service when a new environment has been recorded.
+  // Takes the serialized environment as a parameter. The contents of
+  // |serialized_environment| are consumed by the call, but the caller maintains
+  // ownership.
+  virtual void OnEnvironmentUpdate(std::string* serialized_environment) {}
+
+  // Called by the metrics service to record a clean shutdown.
+  virtual void OnLogCleanShutdown() {}
+
+  // Called prior to a metrics log being closed, allowing the client to collect
+  // extra histograms that will go in that log. Asynchronous API - the client
+  // implementation should call |done_callback| when complete.
+  virtual void CollectFinalMetricsForLog(
+      const base::Closure& done_callback) = 0;
+
+  // Get the URL of the metrics server.
+  virtual std::string GetMetricsServerUrl();
+
+  // Get the fallback HTTP URL of the metrics server.
+  virtual std::string GetInsecureMetricsServerUrl();
+
+  // Creates a MetricsLogUploader with the specified parameters (see comments on
+  // MetricsLogUploader for details).
+  virtual std::unique_ptr<MetricsLogUploader> CreateUploader(
+      base::StringPiece server_url,
+      base::StringPiece insecure_server_url,
+      base::StringPiece mime_type,
+      metrics::MetricsLogUploader::MetricServiceType service_type,
+      const MetricsLogUploader::UploadCallback& on_upload_complete) = 0;
+
+  // Returns the standard interval between upload attempts.
+  virtual base::TimeDelta GetStandardUploadInterval() = 0;
+
+  // Called on plugin loading errors.
+  virtual void OnPluginLoadingError(const base::FilePath& plugin_path) {}
+
+  // Called on renderer crashes in some embedders (e.g., those that do not use
+  // //content and thus do not have //content's notification system available
+  // as a mechanism for observing renderer crashes).
+  virtual void OnRendererProcessCrash() {}
+
+  // Returns whether metrics reporting is managed by policy.
+  virtual bool IsReportingPolicyManaged();
+
+  // Gets information about the default value for the metrics reporting checkbox
+  // shown during first-run.
+  virtual EnableMetricsDefault GetMetricsReportingDefaultState();
+
+  // Returns whether cellular logic is enabled for metrics reporting.
+  virtual bool IsUMACellularUploadLogicEnabled();
+
+  // Returns true iff sync is in a state that allows UKM to be enabled.
+  // See //components/ukm/observers/sync_disable_observer.h for details.
+  virtual bool SyncStateAllowsUkm();
+
+  // Returns true iff sync is in a state that allows UKM to capture extensions.
+  // See //components/ukm/observers/sync_disable_observer.h for details.
+  virtual bool SyncStateAllowsExtensionUkm();
+
+  // Returns whether UKM notification listeners were attached to all profiles.
+  virtual bool AreNotificationListenersEnabledOnAllProfiles();
+
+  // Gets the Chrome package name for Android. Returns empty string for other
+  // platforms.
+  virtual std::string GetAppPackageName();
+
+  // Sets the callback to run MetricsServiceManager::UpdateRunningServices.
+  void SetUpdateRunningServicesCallback(const base::Closure& callback);
+
+  // Notify MetricsServiceManager to UpdateRunningServices using callback.
+  void UpdateRunningServices();
+
+ private:
+  base::Closure update_running_services_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsServiceClient);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_SERVICE_CLIENT_H_
diff --git a/components/metrics/metrics_service_unittest.cc b/components/metrics/metrics_service_unittest.cc
new file mode 100644
index 0000000..4350238
--- /dev/null
+++ b/components/metrics/metrics_service_unittest.cc
@@ -0,0 +1,443 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_service.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/metrics/user_metrics.h"
+#include "base/stl_util.h"
+#include "base/strings/string16.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/metrics/client_info.h"
+#include "components/metrics/environment_recorder.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/metrics/metrics_upload_scheduler.h"
+#include "components/metrics/test_enabled_state_provider.h"
+#include "components/metrics/test_metrics_provider.h"
+#include "components/metrics/test_metrics_service_client.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace metrics {
+
+namespace {
+
+void YieldUntil(base::Time when) {
+  while (base::Time::Now() <= when)
+    base::PlatformThread::YieldCurrentThread();
+}
+
+void StoreNoClientInfoBackup(const ClientInfo& /* client_info */) {
+}
+
+std::unique_ptr<ClientInfo> ReturnNoBackup() {
+  return std::unique_ptr<ClientInfo>();
+}
+
+class TestMetricsService : public MetricsService {
+ public:
+  TestMetricsService(MetricsStateManager* state_manager,
+                     MetricsServiceClient* client,
+                     PrefService* local_state)
+      : MetricsService(state_manager, client, local_state) {}
+  ~TestMetricsService() override {}
+
+  using MetricsService::log_manager;
+  using MetricsService::log_store;
+  using MetricsService::RecordCurrentEnvironmentHelper;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestMetricsService);
+};
+
+class TestMetricsLog : public MetricsLog {
+ public:
+  TestMetricsLog(const std::string& client_id,
+                 int session_id,
+                 MetricsServiceClient* client)
+      : MetricsLog(client_id, session_id, MetricsLog::ONGOING_LOG, client) {}
+
+  ~TestMetricsLog() override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestMetricsLog);
+};
+
+class MetricsServiceTest : public testing::Test {
+ public:
+  MetricsServiceTest()
+      : task_runner_(new base::TestSimpleTaskRunner),
+        task_runner_handle_(task_runner_),
+        enabled_state_provider_(new TestEnabledStateProvider(false, false)) {
+    base::SetRecordActionTaskRunner(task_runner_);
+    MetricsService::RegisterPrefs(testing_local_state_.registry());
+  }
+
+  ~MetricsServiceTest() override {
+    MetricsService::SetExecutionPhase(ExecutionPhase::UNINITIALIZED_PHASE,
+                                      GetLocalState());
+  }
+
+  MetricsStateManager* GetMetricsStateManager() {
+    // Lazy-initialize the metrics_state_manager so that it correctly reads the
+    // stability state from prefs after tests have a chance to initialize it.
+    if (!metrics_state_manager_) {
+      metrics_state_manager_ = MetricsStateManager::Create(
+          GetLocalState(), enabled_state_provider_.get(), base::string16(),
+          base::Bind(&StoreNoClientInfoBackup), base::Bind(&ReturnNoBackup));
+    }
+    return metrics_state_manager_.get();
+  }
+
+  PrefService* GetLocalState() { return &testing_local_state_; }
+
+  // Sets metrics reporting as enabled for testing.
+  void EnableMetricsReporting() {
+    enabled_state_provider_->set_consent(true);
+    enabled_state_provider_->set_enabled(true);
+  }
+
+  // Finds a histogram with the specified |name_hash| in |histograms|.
+  const base::HistogramBase* FindHistogram(
+      const base::StatisticsRecorder::Histograms& histograms,
+      uint64_t name_hash) {
+    for (const base::HistogramBase* histogram : histograms) {
+      if (name_hash == base::HashMetricName(histogram->histogram_name()))
+        return histogram;
+    }
+    return nullptr;
+  }
+
+  // Checks whether |uma_log| contains any histograms that are not flagged
+  // with kUmaStabilityHistogramFlag. Stability logs should only contain such
+  // histograms.
+  void CheckForNonStabilityHistograms(
+      const ChromeUserMetricsExtension& uma_log) {
+    const int kStabilityFlags = base::HistogramBase::kUmaStabilityHistogramFlag;
+    const base::StatisticsRecorder::Histograms histograms =
+        base::StatisticsRecorder::GetHistograms();
+    for (int i = 0; i < uma_log.histogram_event_size(); ++i) {
+      const uint64_t hash = uma_log.histogram_event(i).name_hash();
+
+      const base::HistogramBase* histogram = FindHistogram(histograms, hash);
+      EXPECT_TRUE(histogram) << hash;
+
+      EXPECT_EQ(kStabilityFlags, histogram->flags() & kStabilityFlags) << hash;
+    }
+  }
+
+ protected:
+  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+  base::ThreadTaskRunnerHandle task_runner_handle_;
+  base::test::ScopedFeatureList feature_list_;
+
+ private:
+  std::unique_ptr<TestEnabledStateProvider> enabled_state_provider_;
+  TestingPrefServiceSimple testing_local_state_;
+  std::unique_ptr<MetricsStateManager> metrics_state_manager_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsServiceTest);
+};
+
+}  // namespace
+
+TEST_F(MetricsServiceTest, InitialStabilityLogAfterCleanShutDown) {
+  EnableMetricsReporting();
+  GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, true);
+
+  TestMetricsServiceClient client;
+  TestMetricsService service(
+      GetMetricsStateManager(), &client, GetLocalState());
+
+  TestMetricsProvider* test_provider = new TestMetricsProvider();
+  service.RegisterMetricsProvider(
+      std::unique_ptr<MetricsProvider>(test_provider));
+
+  service.InitializeMetricsRecordingState();
+
+  // No initial stability log should be generated.
+  EXPECT_FALSE(service.has_unsent_logs());
+
+  // Ensure that HasPreviousSessionData() is always called on providers,
+  // for consistency, even if other conditions already indicate their presence.
+  EXPECT_TRUE(test_provider->has_initial_stability_metrics_called());
+
+  // The test provider should not have been called upon to provide initial
+  // stability nor regular stability metrics.
+  EXPECT_FALSE(test_provider->provide_initial_stability_metrics_called());
+  EXPECT_FALSE(test_provider->provide_stability_metrics_called());
+}
+
+TEST_F(MetricsServiceTest, InitialStabilityLogAtProviderRequest) {
+  EnableMetricsReporting();
+
+  // Save an existing system profile to prefs, to correspond to what would be
+  // saved from a previous session.
+  TestMetricsServiceClient client;
+  TestMetricsLog log("client", 1, &client);
+  DelegatingProvider delegating_provider;
+  TestMetricsService::RecordCurrentEnvironmentHelper(&log, GetLocalState(),
+                                                     &delegating_provider);
+
+  // Record stability build time and version from previous session, so that
+  // stability metrics (including exited cleanly flag) won't be cleared.
+  EnvironmentRecorder(GetLocalState())
+      .SetBuildtimeAndVersion(MetricsLog::GetBuildTime(),
+                              client.GetVersionString());
+
+  // Set the clean exit flag, as that will otherwise cause a stabilty
+  // log to be produced, irrespective provider requests.
+  GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, true);
+
+  TestMetricsService service(
+      GetMetricsStateManager(), &client, GetLocalState());
+  // Add a metrics provider that requests a stability log.
+  TestMetricsProvider* test_provider = new TestMetricsProvider();
+  test_provider->set_has_initial_stability_metrics(true);
+  service.RegisterMetricsProvider(
+      std::unique_ptr<MetricsProvider>(test_provider));
+
+  service.InitializeMetricsRecordingState();
+
+  // The initial stability log should be generated and persisted in unsent logs.
+  MetricsLogStore* log_store = service.log_store();
+  EXPECT_TRUE(log_store->has_unsent_logs());
+  EXPECT_FALSE(log_store->has_staged_log());
+
+  // Ensure that HasPreviousSessionData() is always called on providers,
+  // for consistency, even if other conditions already indicate their presence.
+  EXPECT_TRUE(test_provider->has_initial_stability_metrics_called());
+
+  // The test provider should have been called upon to provide initial
+  // stability and regular stability metrics.
+  EXPECT_TRUE(test_provider->provide_initial_stability_metrics_called());
+  EXPECT_TRUE(test_provider->provide_stability_metrics_called());
+
+  // Stage the log and retrieve it.
+  log_store->StageNextLog();
+  EXPECT_TRUE(log_store->has_staged_log());
+
+  std::string uncompressed_log;
+  EXPECT_TRUE(
+      compression::GzipUncompress(log_store->staged_log(), &uncompressed_log));
+
+  ChromeUserMetricsExtension uma_log;
+  EXPECT_TRUE(uma_log.ParseFromString(uncompressed_log));
+
+  EXPECT_TRUE(uma_log.has_client_id());
+  EXPECT_TRUE(uma_log.has_session_id());
+  EXPECT_TRUE(uma_log.has_system_profile());
+  EXPECT_EQ(0, uma_log.user_action_event_size());
+  EXPECT_EQ(0, uma_log.omnibox_event_size());
+  EXPECT_EQ(0, uma_log.perf_data_size());
+  CheckForNonStabilityHistograms(uma_log);
+
+  // As there wasn't an unclean shutdown, this log has zero crash count.
+  EXPECT_EQ(0, uma_log.system_profile().stability().crash_count());
+}
+
+TEST_F(MetricsServiceTest, InitialStabilityLogAfterCrash) {
+  EnableMetricsReporting();
+  GetLocalState()->ClearPref(prefs::kStabilityExitedCleanly);
+
+  // Set up prefs to simulate restarting after a crash.
+
+  // Save an existing system profile to prefs, to correspond to what would be
+  // saved from a previous session.
+  TestMetricsServiceClient client;
+  TestMetricsLog log("client", 1, &client);
+  DelegatingProvider delegating_provider;
+  TestMetricsService::RecordCurrentEnvironmentHelper(&log, GetLocalState(),
+                                                     &delegating_provider);
+
+  // Record stability build time and version from previous session, so that
+  // stability metrics (including exited cleanly flag) won't be cleared.
+  EnvironmentRecorder(GetLocalState())
+      .SetBuildtimeAndVersion(MetricsLog::GetBuildTime(),
+                              client.GetVersionString());
+
+  GetLocalState()->SetBoolean(prefs::kStabilityExitedCleanly, false);
+
+  TestMetricsService service(
+      GetMetricsStateManager(), &client, GetLocalState());
+  // Add a provider.
+  TestMetricsProvider* test_provider = new TestMetricsProvider();
+  service.RegisterMetricsProvider(
+      std::unique_ptr<MetricsProvider>(test_provider));
+  service.InitializeMetricsRecordingState();
+
+  // The initial stability log should be generated and persisted in unsent logs.
+  MetricsLogStore* log_store = service.log_store();
+  EXPECT_TRUE(log_store->has_unsent_logs());
+  EXPECT_FALSE(log_store->has_staged_log());
+
+  // Ensure that HasPreviousSessionData() is always called on providers,
+  // for consistency, even if other conditions already indicate their presence.
+  EXPECT_TRUE(test_provider->has_initial_stability_metrics_called());
+
+  // The test provider should have been called upon to provide initial
+  // stability and regular stability metrics.
+  EXPECT_TRUE(test_provider->provide_initial_stability_metrics_called());
+  EXPECT_TRUE(test_provider->provide_stability_metrics_called());
+
+  // Stage the log and retrieve it.
+  log_store->StageNextLog();
+  EXPECT_TRUE(log_store->has_staged_log());
+
+  std::string uncompressed_log;
+  EXPECT_TRUE(
+      compression::GzipUncompress(log_store->staged_log(), &uncompressed_log));
+
+  ChromeUserMetricsExtension uma_log;
+  EXPECT_TRUE(uma_log.ParseFromString(uncompressed_log));
+
+  EXPECT_TRUE(uma_log.has_client_id());
+  EXPECT_TRUE(uma_log.has_session_id());
+  EXPECT_TRUE(uma_log.has_system_profile());
+  EXPECT_EQ(0, uma_log.user_action_event_size());
+  EXPECT_EQ(0, uma_log.omnibox_event_size());
+  EXPECT_EQ(0, uma_log.perf_data_size());
+  CheckForNonStabilityHistograms(uma_log);
+
+  EXPECT_EQ(1, uma_log.system_profile().stability().crash_count());
+}
+
+TEST_F(MetricsServiceTest,
+       MetricsProviderOnRecordingDisabledCalledOnInitialStop) {
+  TestMetricsServiceClient client;
+  TestMetricsService service(
+      GetMetricsStateManager(), &client, GetLocalState());
+
+  TestMetricsProvider* test_provider = new TestMetricsProvider();
+  service.RegisterMetricsProvider(
+      std::unique_ptr<MetricsProvider>(test_provider));
+
+  service.InitializeMetricsRecordingState();
+  service.Stop();
+
+  EXPECT_TRUE(test_provider->on_recording_disabled_called());
+}
+
+TEST_F(MetricsServiceTest, MetricsProvidersInitialized) {
+  TestMetricsServiceClient client;
+  TestMetricsService service(
+      GetMetricsStateManager(), &client, GetLocalState());
+
+  TestMetricsProvider* test_provider = new TestMetricsProvider();
+  service.RegisterMetricsProvider(
+      std::unique_ptr<MetricsProvider>(test_provider));
+
+  service.InitializeMetricsRecordingState();
+
+  EXPECT_TRUE(test_provider->init_called());
+}
+
+TEST_F(MetricsServiceTest, SplitRotation) {
+  TestMetricsServiceClient client;
+  TestMetricsService service(GetMetricsStateManager(), &client,
+                             GetLocalState());
+  service.InitializeMetricsRecordingState();
+  service.Start();
+  // Rotation loop should create a log and mark state as idle.
+  // Upload loop should start upload or be restarted.
+  // The independent-metrics upload job will be started and always be a task.
+  task_runner_->RunPendingTasks();
+  // Rotation loop should terminated due to being idle.
+  // Upload loop should start uploading if it isn't already.
+  task_runner_->RunPendingTasks();
+  EXPECT_TRUE(client.uploader()->is_uploading());
+  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
+  service.OnApplicationNotIdle();
+  EXPECT_TRUE(client.uploader()->is_uploading());
+  EXPECT_EQ(2U, task_runner_->NumPendingTasks());
+  // Log generation should be suppressed due to unsent log.
+  // Idle state should not be reset.
+  task_runner_->RunPendingTasks();
+  EXPECT_TRUE(client.uploader()->is_uploading());
+  EXPECT_EQ(2U, task_runner_->NumPendingTasks());
+  // Make sure idle state was not reset.
+  task_runner_->RunPendingTasks();
+  EXPECT_TRUE(client.uploader()->is_uploading());
+  EXPECT_EQ(2U, task_runner_->NumPendingTasks());
+  // Upload should not be rescheduled, since there are no other logs.
+  client.uploader()->CompleteUpload(200);
+  EXPECT_FALSE(client.uploader()->is_uploading());
+  EXPECT_EQ(2U, task_runner_->NumPendingTasks());
+  // Running should generate a log, restart upload loop, and mark idle.
+  task_runner_->RunPendingTasks();
+  EXPECT_FALSE(client.uploader()->is_uploading());
+  EXPECT_EQ(3U, task_runner_->NumPendingTasks());
+  // Upload should start, and rotation loop should idle out.
+  task_runner_->RunPendingTasks();
+  EXPECT_TRUE(client.uploader()->is_uploading());
+  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
+  // Uploader should reschedule when there is another log available.
+  service.PushExternalLog("Blah");
+  client.uploader()->CompleteUpload(200);
+  EXPECT_FALSE(client.uploader()->is_uploading());
+  EXPECT_EQ(2U, task_runner_->NumPendingTasks());
+  // Upload should start.
+  task_runner_->RunPendingTasks();
+  EXPECT_TRUE(client.uploader()->is_uploading());
+  EXPECT_EQ(1U, task_runner_->NumPendingTasks());
+}
+
+TEST_F(MetricsServiceTest, LastLiveTimestamp) {
+  TestMetricsServiceClient client;
+  TestMetricsService service(GetMetricsStateManager(), &client,
+                             GetLocalState());
+
+  base::Time initial_last_live_time =
+      GetLocalState()->GetTime(prefs::kStabilityBrowserLastLiveTimeStamp);
+
+  service.InitializeMetricsRecordingState();
+  service.Start();
+
+  task_runner_->RunPendingTasks();
+  size_t num_pending_tasks = task_runner_->NumPendingTasks();
+
+  service.StartUpdatingLastLiveTimestamp();
+
+  // Starting the update sequence should not write anything, but should
+  // set up for a later write.
+  EXPECT_EQ(
+      initial_last_live_time,
+      GetLocalState()->GetTime(prefs::kStabilityBrowserLastLiveTimeStamp));
+  EXPECT_EQ(num_pending_tasks + 1, task_runner_->NumPendingTasks());
+
+  // To avoid flakiness, yield until we're over a microsecond threshold.
+  YieldUntil(initial_last_live_time + base::TimeDelta::FromMicroseconds(2));
+
+  task_runner_->RunPendingTasks();
+
+  // Verify that the time has updated in local state.
+  base::Time updated_last_live_time =
+      GetLocalState()->GetTime(prefs::kStabilityBrowserLastLiveTimeStamp);
+  EXPECT_LT(initial_last_live_time, updated_last_live_time);
+
+  // Double check that an update schedules again...
+  YieldUntil(updated_last_live_time + base::TimeDelta::FromMicroseconds(2));
+
+  task_runner_->RunPendingTasks();
+  EXPECT_LT(
+      updated_last_live_time,
+      GetLocalState()->GetTime(prefs::kStabilityBrowserLastLiveTimeStamp));
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_state_manager.cc b/components/metrics/metrics_state_manager.cc
new file mode 100644
index 0000000..f4515c9
--- /dev/null
+++ b/components/metrics/metrics_state_manager.cc
@@ -0,0 +1,397 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_state_manager.h"
+
+#include <stddef.h>
+#include <utility>
+
+#include <memory>
+
+#include "base/command_line.h"
+#include "base/guid.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/metrics/cloned_install_detector.h"
+#include "components/metrics/enabled_state_provider.h"
+#include "components/metrics/machine_id_provider.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/metrics_switches.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/caching_permuted_entropy_provider.h"
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace metrics {
+
+namespace {
+
+// The argument used to generate a non-identifying entropy source. We want no
+// more than 13 bits of entropy, so use this max to return a number in the range
+// [0, 7999] as the entropy source (12.97 bits of entropy).
+const int kMaxLowEntropySize = 8000;
+
+// Default prefs value for prefs::kMetricsLowEntropySource to indicate that
+// the value has not yet been set.
+const int kLowEntropySourceNotSet = -1;
+
+// Generates a new non-identifying entropy source used to seed persistent
+// activities.
+int GenerateLowEntropySource() {
+  return base::RandInt(0, kMaxLowEntropySize - 1);
+}
+
+// Records the given |low_entorpy_source_value| in a histogram.
+void LogLowEntropyValue(int low_entropy_source_value) {
+  base::UmaHistogramSparse("UMA.LowEntropySourceValue",
+                           low_entropy_source_value);
+}
+
+int64_t ReadEnabledDate(PrefService* local_state) {
+  return local_state->GetInt64(prefs::kMetricsReportingEnabledTimestamp);
+}
+
+int64_t ReadInstallDate(PrefService* local_state) {
+  return local_state->GetInt64(prefs::kInstallDate);
+}
+
+// Round a timestamp measured in seconds since epoch to one with a granularity
+// of an hour. This can be used before uploaded potentially sensitive
+// timestamps.
+int64_t RoundSecondsToHour(int64_t time_in_seconds) {
+  return 3600 * (time_in_seconds / 3600);
+}
+
+// Records the cloned install histogram.
+void LogClonedInstall() {
+  // Equivalent to UMA_HISTOGRAM_BOOLEAN with the stability flag set.
+  UMA_STABILITY_HISTOGRAM_ENUMERATION("UMA.IsClonedInstall", 1, 2);
+}
+
+class MetricsStateMetricsProvider : public MetricsProvider {
+ public:
+  MetricsStateMetricsProvider(PrefService* local_state,
+                              bool metrics_ids_were_reset,
+                              std::string previous_client_id)
+      : local_state_(local_state),
+        metrics_ids_were_reset_(metrics_ids_were_reset),
+        previous_client_id_(std::move(previous_client_id)) {}
+
+  // MetricsProvider:
+  void ProvideSystemProfileMetrics(
+      SystemProfileProto* system_profile) override {
+    system_profile->set_uma_enabled_date(
+        RoundSecondsToHour(ReadEnabledDate(local_state_)));
+    system_profile->set_install_date(
+        RoundSecondsToHour(ReadInstallDate(local_state_)));
+  }
+
+  void ProvidePreviousSessionData(
+      ChromeUserMetricsExtension* uma_proto) override {
+    if (metrics_ids_were_reset_) {
+      LogClonedInstall();
+      if (!previous_client_id_.empty()) {
+        // If we know the previous client id, overwrite the client id for the
+        // previous session log so the log contains the client id at the time
+        // of the previous session. This allows better attribution of crashes
+        // to earlier behavior. If the previous client id is unknown, leave
+        // the current client id.
+        uma_proto->set_client_id(MetricsLog::Hash(previous_client_id_));
+      }
+    }
+  }
+
+  void ProvideCurrentSessionData(
+      ChromeUserMetricsExtension* uma_proto) override {
+    if (local_state_->GetBoolean(prefs::kMetricsResetIds))
+      LogClonedInstall();
+  }
+
+ private:
+  PrefService* const local_state_;
+  const bool metrics_ids_were_reset_;
+  // |previous_client_id_| is set only (if known) when |metrics_ids_were_reset_|
+  const std::string previous_client_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsStateMetricsProvider);
+};
+
+}  // namespace
+
+// static
+bool MetricsStateManager::instance_exists_ = false;
+
+MetricsStateManager::MetricsStateManager(
+    PrefService* local_state,
+    EnabledStateProvider* enabled_state_provider,
+    const base::string16& backup_registry_key,
+    const StoreClientInfoCallback& store_client_info,
+    const LoadClientInfoCallback& retrieve_client_info)
+    : local_state_(local_state),
+      enabled_state_provider_(enabled_state_provider),
+      store_client_info_(store_client_info),
+      load_client_info_(retrieve_client_info),
+      clean_exit_beacon_(backup_registry_key, local_state),
+      low_entropy_source_(kLowEntropySourceNotSet),
+      entropy_source_returned_(ENTROPY_SOURCE_NONE),
+      metrics_ids_were_reset_(false) {
+  ResetMetricsIDsIfNecessary();
+  if (enabled_state_provider_->IsConsentGiven())
+    ForceClientIdCreation();
+
+  // Set the install date if this is our first run.
+  int64_t install_date = local_state_->GetInt64(prefs::kInstallDate);
+  if (install_date == 0)
+    local_state_->SetInt64(prefs::kInstallDate, base::Time::Now().ToTimeT());
+
+  DCHECK(!instance_exists_);
+  instance_exists_ = true;
+}
+
+MetricsStateManager::~MetricsStateManager() {
+  DCHECK(instance_exists_);
+  instance_exists_ = false;
+}
+
+std::unique_ptr<MetricsProvider> MetricsStateManager::GetProvider() {
+  return std::make_unique<MetricsStateMetricsProvider>(
+      local_state_, metrics_ids_were_reset_, previous_client_id_);
+}
+
+bool MetricsStateManager::IsMetricsReportingEnabled() {
+  return enabled_state_provider_->IsReportingEnabled();
+}
+
+int64_t MetricsStateManager::GetInstallDate() const {
+  return ReadInstallDate(local_state_);
+}
+
+void MetricsStateManager::ForceClientIdCreation() {
+  {
+    std::string client_id_from_prefs =
+        local_state_->GetString(prefs::kMetricsClientID);
+    // If client id in prefs matches the cached copy, return early.
+    if (!client_id_from_prefs.empty() && client_id_from_prefs == client_id_)
+      return;
+    client_id_.swap(client_id_from_prefs);
+  }
+
+  if (!client_id_.empty())
+    return;
+
+  const std::unique_ptr<ClientInfo> client_info_backup = LoadClientInfo();
+  if (client_info_backup) {
+    client_id_ = client_info_backup->client_id;
+
+    const base::Time now = base::Time::Now();
+
+    // Save the recovered client id and also try to reinstantiate the backup
+    // values for the dates corresponding with that client id in order to avoid
+    // weird scenarios where we could report an old client id with a recent
+    // install date.
+    local_state_->SetString(prefs::kMetricsClientID, client_id_);
+    local_state_->SetInt64(prefs::kInstallDate,
+                           client_info_backup->installation_date != 0
+                               ? client_info_backup->installation_date
+                               : now.ToTimeT());
+    local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp,
+                           client_info_backup->reporting_enabled_date != 0
+                               ? client_info_backup->reporting_enabled_date
+                               : now.ToTimeT());
+
+    base::TimeDelta recovered_installation_age;
+    if (client_info_backup->installation_date != 0) {
+      recovered_installation_age =
+          now - base::Time::FromTimeT(client_info_backup->installation_date);
+    }
+    UMA_HISTOGRAM_COUNTS_10000("UMA.ClientIdBackupRecoveredWithAge",
+                               recovered_installation_age.InHours());
+
+    // Flush the backup back to persistent storage in case we re-generated
+    // missing data above.
+    BackUpCurrentClientInfo();
+    return;
+  }
+
+  // Failing attempts at getting an existing client ID, generate a new one.
+  client_id_ = base::GenerateGUID();
+  local_state_->SetString(prefs::kMetricsClientID, client_id_);
+
+  // Record the timestamp of when the user opted in to UMA.
+  local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp,
+                         base::Time::Now().ToTimeT());
+
+  BackUpCurrentClientInfo();
+}
+
+void MetricsStateManager::CheckForClonedInstall() {
+  DCHECK(!cloned_install_detector_);
+
+  if (!MachineIdProvider::HasId())
+    return;
+
+  cloned_install_detector_ = std::make_unique<ClonedInstallDetector>();
+  cloned_install_detector_->CheckForClonedInstall(local_state_);
+}
+
+std::unique_ptr<const base::FieldTrial::EntropyProvider>
+MetricsStateManager::CreateDefaultEntropyProvider() {
+  if (enabled_state_provider_->IsConsentGiven()) {
+    // For metrics reporting-enabled users, we combine the client ID and low
+    // entropy source to get the final entropy source. Otherwise, only use the
+    // low entropy source.
+    // This has two useful properties:
+    //  1) It makes the entropy source less identifiable for parties that do not
+    //     know the low entropy source.
+    //  2) It makes the final entropy source resettable.
+    const int low_entropy_source_value = GetLowEntropySource();
+
+    UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_HIGH);
+    const std::string high_entropy_source =
+        client_id_ + base::IntToString(low_entropy_source_value);
+    return std::unique_ptr<const base::FieldTrial::EntropyProvider>(
+        new variations::SHA1EntropyProvider(high_entropy_source));
+  }
+
+  UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_LOW);
+  return CreateLowEntropyProvider();
+}
+
+std::unique_ptr<const base::FieldTrial::EntropyProvider>
+MetricsStateManager::CreateLowEntropyProvider() {
+  const int low_entropy_source_value = GetLowEntropySource();
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+  return std::unique_ptr<const base::FieldTrial::EntropyProvider>(
+      new variations::CachingPermutedEntropyProvider(
+          local_state_, low_entropy_source_value, kMaxLowEntropySize));
+#else
+  return std::unique_ptr<const base::FieldTrial::EntropyProvider>(
+      new variations::PermutedEntropyProvider(low_entropy_source_value,
+                                              kMaxLowEntropySize));
+#endif
+}
+
+// static
+std::unique_ptr<MetricsStateManager> MetricsStateManager::Create(
+    PrefService* local_state,
+    EnabledStateProvider* enabled_state_provider,
+    const base::string16& backup_registry_key,
+    const StoreClientInfoCallback& store_client_info,
+    const LoadClientInfoCallback& retrieve_client_info) {
+  std::unique_ptr<MetricsStateManager> result;
+  // Note: |instance_exists_| is updated in the constructor and destructor.
+  if (!instance_exists_) {
+    result.reset(new MetricsStateManager(local_state, enabled_state_provider,
+                                         backup_registry_key, store_client_info,
+                                         retrieve_client_info));
+  }
+  return result;
+}
+
+// static
+void MetricsStateManager::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterBooleanPref(prefs::kMetricsResetIds, false);
+  registry->RegisterStringPref(prefs::kMetricsClientID, std::string());
+  registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0);
+  registry->RegisterIntegerPref(prefs::kMetricsLowEntropySource,
+                                kLowEntropySourceNotSet);
+  registry->RegisterInt64Pref(prefs::kInstallDate, 0);
+
+  ClonedInstallDetector::RegisterPrefs(registry);
+  variations::CachingPermutedEntropyProvider::RegisterPrefs(registry);
+}
+
+void MetricsStateManager::BackUpCurrentClientInfo() {
+  ClientInfo client_info;
+  client_info.client_id = client_id_;
+  client_info.installation_date = ReadInstallDate(local_state_);
+  client_info.reporting_enabled_date = ReadEnabledDate(local_state_);
+  store_client_info_.Run(client_info);
+}
+
+std::unique_ptr<ClientInfo> MetricsStateManager::LoadClientInfo() {
+  std::unique_ptr<ClientInfo> client_info = load_client_info_.Run();
+
+  // The GUID retrieved should be valid unless retrieval failed.
+  // If not, return nullptr. This will result in a new GUID being generated by
+  // the calling function ForceClientIdCreation().
+  if (client_info && !base::IsValidGUID(client_info->client_id))
+    return nullptr;
+
+  return client_info;
+}
+
+int MetricsStateManager::GetLowEntropySource() {
+  UpdateLowEntropySource();
+  return low_entropy_source_;
+}
+
+void MetricsStateManager::UpdateLowEntropySource() {
+  // Note that the default value for the low entropy source and the default pref
+  // value are both kLowEntropySourceNotSet, which is used to identify if the
+  // value has been set or not.
+  if (low_entropy_source_ != kLowEntropySourceNotSet)
+    return;
+
+  const base::CommandLine* command_line(base::CommandLine::ForCurrentProcess());
+  // Only try to load the value from prefs if the user did not request a
+  // reset.
+  // Otherwise, skip to generating a new value.
+  if (!command_line->HasSwitch(switches::kResetVariationState)) {
+    int value = local_state_->GetInteger(prefs::kMetricsLowEntropySource);
+    // If the value is outside the [0, kMaxLowEntropySize) range, re-generate
+    // it below.
+    if (value >= 0 && value < kMaxLowEntropySize) {
+      low_entropy_source_ = value;
+      LogLowEntropyValue(low_entropy_source_);
+      return;
+    }
+  }
+
+  low_entropy_source_ = GenerateLowEntropySource();
+  LogLowEntropyValue(low_entropy_source_);
+  local_state_->SetInteger(prefs::kMetricsLowEntropySource,
+                           low_entropy_source_);
+  variations::CachingPermutedEntropyProvider::ClearCache(local_state_);
+}
+
+void MetricsStateManager::UpdateEntropySourceReturnedValue(
+    EntropySourceType type) {
+  if (entropy_source_returned_ != ENTROPY_SOURCE_NONE)
+    return;
+
+  entropy_source_returned_ = type;
+  UMA_HISTOGRAM_ENUMERATION("UMA.EntropySourceType", type,
+                            ENTROPY_SOURCE_ENUM_SIZE);
+}
+
+void MetricsStateManager::ResetMetricsIDsIfNecessary() {
+  if (!local_state_->GetBoolean(prefs::kMetricsResetIds))
+    return;
+  metrics_ids_were_reset_ = true;
+  previous_client_id_ = local_state_->GetString(prefs::kMetricsClientID);
+
+  UMA_HISTOGRAM_BOOLEAN("UMA.MetricsIDsReset", true);
+
+  DCHECK(client_id_.empty());
+  DCHECK_EQ(kLowEntropySourceNotSet, low_entropy_source_);
+
+  local_state_->ClearPref(prefs::kMetricsClientID);
+  local_state_->ClearPref(prefs::kMetricsLowEntropySource);
+  local_state_->ClearPref(prefs::kMetricsResetIds);
+
+  // Also clear the backed up client info.
+  store_client_info_.Run(ClientInfo());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_state_manager.h b/components/metrics/metrics_state_manager.h
new file mode 100644
index 0000000..a1a15b3
--- /dev/null
+++ b/components/metrics/metrics_state_manager.h
@@ -0,0 +1,217 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_
+#define COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/metrics/field_trial.h"
+#include "base/strings/string16.h"
+#include "components/metrics/clean_exit_beacon.h"
+#include "components/metrics/client_info.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace metrics {
+
+class ClonedInstallDetector;
+class EnabledStateProvider;
+class MetricsProvider;
+
+// Responsible for managing MetricsService state prefs, specifically the UMA
+// client id and low entropy source. Code outside the metrics directory should
+// not be instantiating or using this class directly.
+class MetricsStateManager final {
+ public:
+  // A callback that can be invoked to store client info to persistent storage.
+  // Storing an empty client_id will resulted in the backup being voided.
+  typedef base::Callback<void(const ClientInfo& client_info)>
+      StoreClientInfoCallback;
+
+  // A callback that can be invoked to load client info stored through the
+  // StoreClientInfoCallback.
+  typedef base::Callback<std::unique_ptr<ClientInfo>(void)>
+      LoadClientInfoCallback;
+
+  ~MetricsStateManager();
+
+  std::unique_ptr<MetricsProvider> GetProvider();
+
+  // Returns true if the user has consented to sending metric reports, and there
+  // is no other reason to disable reporting. One such reason is client
+  // sampling, and this client isn't in the sample.
+  bool IsMetricsReportingEnabled();
+
+  // Returns the install date of the application, in seconds since the epoch.
+  int64_t GetInstallDate() const;
+
+  // Returns the client ID for this client, or the empty string if the user is
+  // not opted in to metrics reporting.
+  const std::string& client_id() const { return client_id_; }
+
+  // The CleanExitBeacon, used to determine whether the previous Chrome browser
+  // session terminated gracefully.
+  CleanExitBeacon* clean_exit_beacon() { return &clean_exit_beacon_; }
+  const CleanExitBeacon* clean_exit_beacon() const {
+    return &clean_exit_beacon_;
+  }
+
+  // Forces the client ID to be generated. This is useful in case it's needed
+  // before recording.
+  void ForceClientIdCreation();
+
+  // Checks if this install was cloned or imaged from another machine. If a
+  // clone is detected, resets the client id and low entropy source. This
+  // should not be called more than once.
+  void CheckForClonedInstall();
+
+  // Returns the preferred entropy provider used to seed persistent activities
+  // based on whether or not metrics reporting is permitted on this client.
+  //
+  // If there's consent to report metrics, this method returns an entropy
+  // provider that has a high source of entropy, partially based on the client
+  // ID. Otherwise, it returns an entropy provider that is based on a low
+  // entropy source.
+  std::unique_ptr<const base::FieldTrial::EntropyProvider>
+  CreateDefaultEntropyProvider();
+
+  // Returns an entropy provider that is based on a low entropy source. This
+  // provider is the same type of provider returned by
+  // CreateDefaultEntropyProvider when there's no consent to report metrics, but
+  // will be a new instance.
+  std::unique_ptr<const base::FieldTrial::EntropyProvider>
+  CreateLowEntropyProvider();
+
+  // Creates the MetricsStateManager, enforcing that only a single instance
+  // of the class exists at a time. Returns NULL if an instance exists already.
+  // On Windows, |backup_registry_key| is used to store a backup of the clean
+  // exit beacon. It is ignored on other platforms.
+  static std::unique_ptr<MetricsStateManager> Create(
+      PrefService* local_state,
+      EnabledStateProvider* enabled_state_provider,
+      const base::string16& backup_registry_key,
+      const StoreClientInfoCallback& store_client_info,
+      const LoadClientInfoCallback& load_client_info);
+
+  // Registers local state prefs used by this class.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, CheckProviderResetIds);
+  FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, EntropySourceUsed_Low);
+  FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, EntropySourceUsed_High);
+  FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, LowEntropySource0NotReset);
+  FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest,
+                           PermutedEntropyCacheClearedWhenLowEntropyReset);
+  FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, ResetBackup);
+  FRIEND_TEST_ALL_PREFIXES(MetricsStateManagerTest, ResetMetricsIDs);
+
+  // Designates which entropy source was returned from this class.
+  // This is used for testing to validate that we return the correct source
+  // depending on the state of the service.
+  enum EntropySourceType {
+    ENTROPY_SOURCE_NONE,
+    ENTROPY_SOURCE_LOW,
+    ENTROPY_SOURCE_HIGH,
+    ENTROPY_SOURCE_ENUM_SIZE,
+  };
+
+  // Creates the MetricsStateManager with the given |local_state|. Uses
+  // |enabled_state_provider| to query whether there is consent for metrics
+  // reporting, and if it is enabled. Clients should instead use Create(), which
+  // enforces that a single instance of this class be alive at any given time.
+  // |store_client_info| should back up client info to persistent storage such
+  // that it is later retrievable by |load_client_info|.
+  MetricsStateManager(PrefService* local_state,
+                      EnabledStateProvider* enabled_state_provider,
+                      const base::string16& backup_registry_key,
+                      const StoreClientInfoCallback& store_client_info,
+                      const LoadClientInfoCallback& load_client_info);
+
+  // Backs up the current client info via |store_client_info_|.
+  void BackUpCurrentClientInfo();
+
+  // Loads the client info via |load_client_info_|.
+  std::unique_ptr<ClientInfo> LoadClientInfo();
+
+  // Returns the low entropy source for this client. This is a random value
+  // that is non-identifying amongst browser clients. This method will
+  // generate the entropy source value if it has not been called before.
+  int GetLowEntropySource();
+
+  // Generates the low entropy source value for this client if it is not
+  // already set.
+  void UpdateLowEntropySource();
+
+  // Updates |entropy_source_returned_| with |type| iff the current value is
+  // ENTROPY_SOURCE_NONE and logs the new value in a histogram.
+  void UpdateEntropySourceReturnedValue(EntropySourceType type);
+
+  // Returns the first entropy source that was returned by this service since
+  // start up, or NONE if neither was returned yet. This is exposed for testing
+  // only.
+  EntropySourceType entropy_source_returned() const {
+    return entropy_source_returned_;
+  }
+
+  // Reset the client id and low entropy source if the kMetricsResetMetricIDs
+  // pref is true.
+  void ResetMetricsIDsIfNecessary();
+
+  // Whether an instance of this class exists. Used to enforce that there aren't
+  // multiple instances of this class at a given time.
+  static bool instance_exists_;
+
+  // Weak pointer to the local state prefs store.
+  PrefService* const local_state_;
+
+  // Weak pointer to an enabled state provider. Used to know whether the user
+  // has consented to reporting, and if reporting should be done.
+  EnabledStateProvider* enabled_state_provider_;
+
+  // A callback run during client id creation so this MetricsStateManager can
+  // store a backup of the newly generated ID.
+  const StoreClientInfoCallback store_client_info_;
+
+  // A callback run if this MetricsStateManager can't get the client id from
+  // its typical location and wants to attempt loading it from this backup.
+  const LoadClientInfoCallback load_client_info_;
+
+  // A beacon used to determine whether the previous Chrome browser session
+  // terminated gracefully.
+  CleanExitBeacon clean_exit_beacon_;
+
+  // The identifier that's sent to the server with the log reports.
+  std::string client_id_;
+
+  // The non-identifying low entropy source value.
+  int low_entropy_source_;
+
+  // The last entropy source returned by this service, used for testing.
+  EntropySourceType entropy_source_returned_;
+
+  // The value of prefs::kMetricsResetIds seen upon startup, i.e., the value
+  // that was appropriate in the previous session. Used when reporting previous
+  // session (stability) data.
+  bool metrics_ids_were_reset_;
+
+  // The value of the metrics id before reseting. Only possibly valid if the
+  // metrics id was reset. May be blank if the metrics id was reset but Chrome
+  // has no record of what the previous metrics id was.
+  std::string previous_client_id_;
+
+  std::unique_ptr<ClonedInstallDetector> cloned_install_detector_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsStateManager);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_STATE_MANAGER_H_
diff --git a/components/metrics/metrics_state_manager_unittest.cc b/components/metrics/metrics_state_manager_unittest.cc
new file mode 100644
index 0000000..0e1f148
--- /dev/null
+++ b/components/metrics/metrics_state_manager_unittest.cc
@@ -0,0 +1,465 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_state_manager.h"
+
+#include <ctype.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "components/metrics/client_info.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_service.h"
+#include "components/metrics/metrics_switches.h"
+#include "components/metrics/test_enabled_state_provider.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/variations/caching_permuted_entropy_provider.h"
+#include "components/variations/pref_names.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+class MetricsStateManagerTest : public testing::Test {
+ public:
+  MetricsStateManagerTest()
+      : test_begin_time_(base::Time::Now().ToTimeT()),
+        enabled_state_provider_(new TestEnabledStateProvider(false, false)) {
+    MetricsService::RegisterPrefs(prefs_.registry());
+  }
+
+  std::unique_ptr<MetricsStateManager> CreateStateManager() {
+    return MetricsStateManager::Create(
+        &prefs_, enabled_state_provider_.get(), base::string16(),
+        base::Bind(&MetricsStateManagerTest::MockStoreClientInfoBackup,
+                   base::Unretained(this)),
+        base::Bind(&MetricsStateManagerTest::LoadFakeClientInfoBackup,
+                   base::Unretained(this)));
+  }
+
+  // Sets metrics reporting as enabled for testing.
+  void EnableMetricsReporting() {
+    enabled_state_provider_->set_consent(true);
+    enabled_state_provider_->set_enabled(true);
+  }
+
+  void SetClientInfoPrefs(const ClientInfo& client_info) {
+    prefs_.SetString(prefs::kMetricsClientID, client_info.client_id);
+    prefs_.SetInt64(prefs::kInstallDate, client_info.installation_date);
+    prefs_.SetInt64(prefs::kMetricsReportingEnabledTimestamp,
+                    client_info.reporting_enabled_date);
+  }
+
+  void SetFakeClientInfoBackup(const ClientInfo& client_info) {
+    fake_client_info_backup_.reset(new ClientInfo);
+    fake_client_info_backup_->client_id = client_info.client_id;
+    fake_client_info_backup_->installation_date = client_info.installation_date;
+    fake_client_info_backup_->reporting_enabled_date =
+        client_info.reporting_enabled_date;
+  }
+
+ protected:
+  TestingPrefServiceSimple prefs_;
+
+  // Last ClientInfo stored by the MetricsStateManager via
+  // MockStoreClientInfoBackup.
+  std::unique_ptr<ClientInfo> stored_client_info_backup_;
+
+  // If set, will be returned via LoadFakeClientInfoBackup if requested by the
+  // MetricsStateManager.
+  std::unique_ptr<ClientInfo> fake_client_info_backup_;
+
+  const int64_t test_begin_time_;
+
+ private:
+  // Stores the |client_info| in |stored_client_info_backup_| for verification
+  // by the tests later.
+  void MockStoreClientInfoBackup(const ClientInfo& client_info) {
+    stored_client_info_backup_.reset(new ClientInfo);
+    stored_client_info_backup_->client_id = client_info.client_id;
+    stored_client_info_backup_->installation_date =
+        client_info.installation_date;
+    stored_client_info_backup_->reporting_enabled_date =
+        client_info.reporting_enabled_date;
+
+    // Respect the contract that storing an empty client_id voids the existing
+    // backup (required for the last section of the ForceClientIdCreation test
+    // below).
+    if (client_info.client_id.empty())
+      fake_client_info_backup_.reset();
+  }
+
+  // Hands out a copy of |fake_client_info_backup_| if it is set.
+  std::unique_ptr<ClientInfo> LoadFakeClientInfoBackup() {
+    if (!fake_client_info_backup_)
+      return std::unique_ptr<ClientInfo>();
+
+    std::unique_ptr<ClientInfo> backup_copy(new ClientInfo);
+    backup_copy->client_id = fake_client_info_backup_->client_id;
+    backup_copy->installation_date =
+        fake_client_info_backup_->installation_date;
+    backup_copy->reporting_enabled_date =
+        fake_client_info_backup_->reporting_enabled_date;
+    return backup_copy;
+  }
+
+  std::unique_ptr<TestEnabledStateProvider> enabled_state_provider_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsStateManagerTest);
+};
+
+// Ensure the ClientId is formatted as expected.
+TEST_F(MetricsStateManagerTest, ClientIdCorrectlyFormatted) {
+  std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+  state_manager->ForceClientIdCreation();
+
+  const std::string client_id = state_manager->client_id();
+  EXPECT_EQ(36U, client_id.length());
+
+  for (size_t i = 0; i < client_id.length(); ++i) {
+    char current = client_id[i];
+    if (i == 8 || i == 13 || i == 18 || i == 23)
+      EXPECT_EQ('-', current);
+    else
+      EXPECT_TRUE(isxdigit(current));
+  }
+}
+
+TEST_F(MetricsStateManagerTest, EntropySourceUsed_Low) {
+  std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+  state_manager->CreateDefaultEntropyProvider();
+  EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_LOW,
+            state_manager->entropy_source_returned());
+}
+
+TEST_F(MetricsStateManagerTest, EntropySourceUsed_High) {
+  EnableMetricsReporting();
+  std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+  state_manager->CreateDefaultEntropyProvider();
+  EXPECT_EQ(MetricsStateManager::ENTROPY_SOURCE_HIGH,
+            state_manager->entropy_source_returned());
+}
+
+TEST_F(MetricsStateManagerTest, LowEntropySource0NotReset) {
+  std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+
+  // Get the low entropy source once, to initialize it.
+  state_manager->GetLowEntropySource();
+
+  // Now, set it to 0 and ensure it doesn't get reset.
+  state_manager->low_entropy_source_ = 0;
+  EXPECT_EQ(0, state_manager->GetLowEntropySource());
+  // Call it another time, just to make sure.
+  EXPECT_EQ(0, state_manager->GetLowEntropySource());
+}
+
+TEST_F(MetricsStateManagerTest,
+       PermutedEntropyCacheClearedWhenLowEntropyReset) {
+  const PrefService::Preference* low_entropy_pref =
+      prefs_.FindPreference(prefs::kMetricsLowEntropySource);
+  const char* kCachePrefName =
+      variations::prefs::kVariationsPermutedEntropyCache;
+  int low_entropy_value = -1;
+
+  // First, generate an initial low entropy source value.
+  {
+    EXPECT_TRUE(low_entropy_pref->IsDefaultValue());
+
+    std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+    state_manager->GetLowEntropySource();
+
+    EXPECT_FALSE(low_entropy_pref->IsDefaultValue());
+    EXPECT_TRUE(low_entropy_pref->GetValue()->GetAsInteger(&low_entropy_value));
+  }
+
+  // Now, set a dummy value in the permuted entropy cache pref and verify that
+  // another call to GetLowEntropySource() doesn't clobber it when
+  // --reset-variation-state wasn't specified.
+  {
+    prefs_.SetString(kCachePrefName, "test");
+
+    std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+    state_manager->GetLowEntropySource();
+
+    EXPECT_EQ("test", prefs_.GetString(kCachePrefName));
+    EXPECT_EQ(low_entropy_value,
+              prefs_.GetInteger(prefs::kMetricsLowEntropySource));
+  }
+
+  // Verify that the cache does get reset if --reset-variations-state is passed.
+  {
+    base::CommandLine::ForCurrentProcess()->AppendSwitch(
+        switches::kResetVariationState);
+
+    std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+    state_manager->GetLowEntropySource();
+
+    EXPECT_TRUE(prefs_.GetString(kCachePrefName).empty());
+  }
+}
+
+// Check that setting the kMetricsResetIds pref to true causes the client id to
+// be reset. We do not check that the low entropy source is reset because we
+// cannot ensure that metrics state manager won't generate the same id again.
+TEST_F(MetricsStateManagerTest, ResetMetricsIDs) {
+  // Set an initial client id in prefs. It should not be possible for the
+  // metrics state manager to generate this id randomly.
+  const std::string kInitialClientId = "initial client id";
+  prefs_.SetString(prefs::kMetricsClientID, kInitialClientId);
+
+  // Make sure the initial client id isn't reset by the metrics state manager.
+  {
+    std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+    state_manager->ForceClientIdCreation();
+    EXPECT_EQ(kInitialClientId, state_manager->client_id());
+    EXPECT_FALSE(state_manager->metrics_ids_were_reset_);
+  }
+
+  // Set the reset pref to cause the IDs to be reset.
+  prefs_.SetBoolean(prefs::kMetricsResetIds, true);
+
+  // Cause the actual reset to happen.
+  {
+    std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+    state_manager->ForceClientIdCreation();
+    EXPECT_NE(kInitialClientId, state_manager->client_id());
+    EXPECT_TRUE(state_manager->metrics_ids_were_reset_);
+    EXPECT_EQ(kInitialClientId, state_manager->previous_client_id_);
+
+    state_manager->GetLowEntropySource();
+
+    EXPECT_FALSE(prefs_.GetBoolean(prefs::kMetricsResetIds));
+  }
+
+  EXPECT_NE(kInitialClientId, prefs_.GetString(prefs::kMetricsClientID));
+}
+
+TEST_F(MetricsStateManagerTest, ForceClientIdCreation) {
+  const int64_t kFakeInstallationDate = 12345;
+  prefs_.SetInt64(prefs::kInstallDate, kFakeInstallationDate);
+
+  {
+    std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+
+    // client_id shouldn't be auto-generated if metrics reporting is not
+    // enabled.
+    EXPECT_EQ(std::string(), state_manager->client_id());
+    EXPECT_EQ(0, prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp));
+
+    // Confirm that the initial ForceClientIdCreation call creates the client id
+    // and backs it up via MockStoreClientInfoBackup.
+    EXPECT_FALSE(stored_client_info_backup_);
+    state_manager->ForceClientIdCreation();
+    EXPECT_NE(std::string(), state_manager->client_id());
+    EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
+              test_begin_time_);
+
+    ASSERT_TRUE(stored_client_info_backup_);
+    EXPECT_EQ(state_manager->client_id(),
+              stored_client_info_backup_->client_id);
+    EXPECT_EQ(kFakeInstallationDate,
+              stored_client_info_backup_->installation_date);
+    EXPECT_EQ(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
+              stored_client_info_backup_->reporting_enabled_date);
+  }
+}
+
+TEST_F(MetricsStateManagerTest, LoadPrefs) {
+  ClientInfo client_info;
+  client_info.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEF";
+  client_info.installation_date = 1112;
+  client_info.reporting_enabled_date = 2223;
+  SetClientInfoPrefs(client_info);
+
+  EnableMetricsReporting();
+  {
+    EXPECT_FALSE(fake_client_info_backup_);
+    EXPECT_FALSE(stored_client_info_backup_);
+
+    std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+
+    // client_id should be auto-obtained from the constructor when metrics
+    // reporting is enabled.
+    EXPECT_EQ(client_info.client_id, state_manager->client_id());
+
+    // The backup should not be modified.
+    ASSERT_FALSE(stored_client_info_backup_);
+
+    // Re-forcing client id creation shouldn't cause another backup and
+    // shouldn't affect the existing client id.
+    state_manager->ForceClientIdCreation();
+    EXPECT_FALSE(stored_client_info_backup_);
+    EXPECT_EQ(client_info.client_id, state_manager->client_id());
+  }
+}
+
+TEST_F(MetricsStateManagerTest, PreferPrefs) {
+  ClientInfo client_info;
+  client_info.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEF";
+  client_info.installation_date = 1112;
+  client_info.reporting_enabled_date = 2223;
+  SetClientInfoPrefs(client_info);
+
+  ClientInfo client_info2;
+  client_info2.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE";
+  client_info2.installation_date = 1111;
+  client_info2.reporting_enabled_date = 2222;
+  SetFakeClientInfoBackup(client_info2);
+
+  EnableMetricsReporting();
+  {
+    // The backup should be ignored if we already have a client id.
+
+    EXPECT_FALSE(stored_client_info_backup_);
+
+    std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+    EXPECT_EQ(client_info.client_id, state_manager->client_id());
+
+    // The backup should not be modified.
+    ASSERT_FALSE(stored_client_info_backup_);
+  }
+}
+
+TEST_F(MetricsStateManagerTest, RestoreBackup) {
+  ClientInfo client_info;
+  client_info.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEF";
+  client_info.installation_date = 1112;
+  client_info.reporting_enabled_date = 2223;
+  SetClientInfoPrefs(client_info);
+
+  ClientInfo client_info2;
+  client_info2.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE";
+  client_info2.installation_date = 1111;
+  client_info2.reporting_enabled_date = 2222;
+  SetFakeClientInfoBackup(client_info2);
+
+  prefs_.ClearPref(prefs::kMetricsClientID);
+  prefs_.ClearPref(prefs::kMetricsReportingEnabledTimestamp);
+
+  EnableMetricsReporting();
+  {
+    // The backup should kick in if the client id has gone missing. It should
+    // replace remaining and missing dates as well.
+
+    EXPECT_FALSE(stored_client_info_backup_);
+
+    std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+    EXPECT_EQ(client_info2.client_id, state_manager->client_id());
+    EXPECT_EQ(client_info2.installation_date,
+              prefs_.GetInt64(prefs::kInstallDate));
+    EXPECT_EQ(client_info2.reporting_enabled_date,
+              prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp));
+
+    EXPECT_TRUE(stored_client_info_backup_);
+  }
+}
+
+TEST_F(MetricsStateManagerTest, ResetBackup) {
+  ClientInfo client_info;
+  client_info.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE";
+  client_info.installation_date = 1111;
+  client_info.reporting_enabled_date = 2222;
+
+  SetFakeClientInfoBackup(client_info);
+  SetClientInfoPrefs(client_info);
+
+  prefs_.SetBoolean(prefs::kMetricsResetIds, true);
+
+  EnableMetricsReporting();
+  {
+    // Upon request to reset metrics ids, the existing backup should not be
+    // restored.
+
+    std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+
+    // A brand new client id should have been generated.
+    EXPECT_NE(std::string(), state_manager->client_id());
+    EXPECT_NE(client_info.client_id, state_manager->client_id());
+    EXPECT_TRUE(state_manager->metrics_ids_were_reset_);
+    EXPECT_EQ(client_info.client_id, state_manager->previous_client_id_);
+    EXPECT_TRUE(stored_client_info_backup_);
+
+    // The installation date should not have been affected.
+    EXPECT_EQ(client_info.installation_date,
+              prefs_.GetInt64(prefs::kInstallDate));
+
+    // The metrics-reporting-enabled date will be reset to Now().
+    EXPECT_GE(prefs_.GetInt64(prefs::kMetricsReportingEnabledTimestamp),
+              test_begin_time_);
+  }
+}
+
+TEST_F(MetricsStateManagerTest, CheckProvider) {
+  int64_t kInstallDate = 1373051956;
+  int64_t kInstallDateExpected = 1373050800;  // Computed from kInstallDate.
+  int64_t kEnabledDate = 1373001211;
+  int64_t kEnabledDateExpected = 1373000400;  // Computed from kEnabledDate.
+
+  ClientInfo client_info;
+  client_info.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE";
+  client_info.installation_date = kInstallDate;
+  client_info.reporting_enabled_date = kEnabledDate;
+
+  SetFakeClientInfoBackup(client_info);
+  SetClientInfoPrefs(client_info);
+
+  std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+  std::unique_ptr<MetricsProvider> provider = state_manager->GetProvider();
+  SystemProfileProto system_profile;
+  provider->ProvideSystemProfileMetrics(&system_profile);
+  EXPECT_EQ(system_profile.install_date(), kInstallDateExpected);
+  EXPECT_EQ(system_profile.uma_enabled_date(), kEnabledDateExpected);
+
+  base::HistogramTester histogram_tester;
+  ChromeUserMetricsExtension uma_proto;
+  provider->ProvidePreviousSessionData(&uma_proto);
+  // The client_id field in the proto should not be overwritten.
+  EXPECT_FALSE(uma_proto.has_client_id());
+  // Nothing should have been emitted to the cloned install histogram.
+  histogram_tester.ExpectTotalCount("UMA.IsClonedInstall", 0);
+}
+
+TEST_F(MetricsStateManagerTest, CheckProviderResetIds) {
+  int64_t kInstallDate = 1373051956;
+  int64_t kInstallDateExpected = 1373050800;  // Computed from kInstallDate.
+  int64_t kEnabledDate = 1373001211;
+  int64_t kEnabledDateExpected = 1373000400;  // Computed from kEnabledDate.
+
+  ClientInfo client_info;
+  client_info.client_id = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE";
+  client_info.installation_date = kInstallDate;
+  client_info.reporting_enabled_date = kEnabledDate;
+
+  SetFakeClientInfoBackup(client_info);
+  SetClientInfoPrefs(client_info);
+
+  // Set the reset pref to cause the IDs to be reset.
+  prefs_.SetBoolean(prefs::kMetricsResetIds, true);
+  std::unique_ptr<MetricsStateManager> state_manager(CreateStateManager());
+  EXPECT_NE(client_info.client_id, state_manager->client_id());
+  EXPECT_TRUE(state_manager->metrics_ids_were_reset_);
+  EXPECT_EQ(client_info.client_id, state_manager->previous_client_id_);
+
+  std::unique_ptr<MetricsProvider> provider = state_manager->GetProvider();
+  SystemProfileProto system_profile;
+  provider->ProvideSystemProfileMetrics(&system_profile);
+  EXPECT_EQ(system_profile.install_date(), kInstallDateExpected);
+  EXPECT_EQ(system_profile.uma_enabled_date(), kEnabledDateExpected);
+
+  base::HistogramTester histogram_tester;
+  ChromeUserMetricsExtension uma_proto;
+  provider->ProvidePreviousSessionData(&uma_proto);
+  EXPECT_EQ(MetricsLog::Hash(state_manager->previous_client_id_),
+            uma_proto.client_id());
+  histogram_tester.ExpectUniqueSample("UMA.IsClonedInstall", 1, 1);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_switches.cc b/components/metrics/metrics_switches.cc
new file mode 100644
index 0000000..38f65ae
--- /dev/null
+++ b/components/metrics/metrics_switches.cc
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_switches.h"
+
+namespace metrics {
+namespace switches {
+
+// Enables the recording of metrics reports but disables reporting. In contrast
+// to kDisableMetrics, this executes all the code that a normal client would
+// use for reporting, except the report is dropped rather than sent to the
+// server. This is useful for finding issues in the metrics code during UI and
+// performance tests.
+const char kMetricsRecordingOnly[] = "metrics-recording-only";
+
+// Forces a reset of the one-time-randomized FieldTrials on this client, also
+// known as the Chrome Variations state.
+const char kResetVariationState[] = "reset-variation-state";
+
+// Forces metrics reporting to be enabled.
+const char kForceEnableMetricsReporting[] = "force-enable-metrics-reporting";
+
+}  // namespace switches
+}  // namespace metrics
diff --git a/components/metrics/metrics_switches.h b/components/metrics/metrics_switches.h
new file mode 100644
index 0000000..10e41e9
--- /dev/null
+++ b/components/metrics/metrics_switches.h
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_SWITCHES_H_
+#define COMPONENTS_METRICS_METRICS_SWITCHES_H_
+
+namespace metrics {
+namespace switches {
+
+// Alphabetical list of switches specific to the metrics component. Document
+// each in the .cc file.
+
+extern const char kMetricsRecordingOnly[];
+extern const char kResetVariationState[];
+extern const char kForceEnableMetricsReporting[];
+
+}  // namespace switches
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_SWITCHES_H_
diff --git a/components/metrics/metrics_upload_scheduler.cc b/components/metrics/metrics_upload_scheduler.cc
new file mode 100644
index 0000000..b7b7364
--- /dev/null
+++ b/components/metrics/metrics_upload_scheduler.cc
@@ -0,0 +1,92 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/metrics_upload_scheduler.h"
+
+#include <stdint.h>
+
+#include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "build/build_config.h"
+#include "components/metrics/metrics_scheduler.h"
+
+namespace metrics {
+
+namespace {
+
+// When uploading metrics to the server fails, we progressively wait longer and
+// longer before sending the next log. This backoff process helps reduce load
+// on a server that is having issues.
+// The following is the multiplier we use to expand that inter-log duration.
+const double kBackoffMultiplier = 2;
+
+// The maximum backoff interval in hours.
+const int kMaxBackoffIntervalHours = 24;
+
+// Minutes to wait if we are unable to upload due to data usage cap.
+const int kOverDataUsageIntervalMinutes = 5;
+
+// Increases the upload interval each time it's called, to handle the case
+// where the server is having issues.
+base::TimeDelta BackOffUploadInterval(base::TimeDelta interval) {
+  DCHECK_GT(kBackoffMultiplier, 1.0);
+  interval = base::TimeDelta::FromMicroseconds(static_cast<int64_t>(
+      kBackoffMultiplier * interval.InMicroseconds()));
+
+  base::TimeDelta max_interval =
+      base::TimeDelta::FromHours(kMaxBackoffIntervalHours);
+  if (interval > max_interval || interval.InSeconds() < 0) {
+    interval = max_interval;
+  }
+  return interval;
+}
+
+// Time delay after a log is uploaded successfully before attempting another.
+// On mobile, keeping the radio on is very expensive, so prefer to keep this
+// short and send in bursts.
+base::TimeDelta GetUnsentLogsInterval() {
+  return base::TimeDelta::FromSeconds(3);
+}
+
+// Initial time delay after a log uploaded fails before retrying it.
+base::TimeDelta GetInitialBackoffInterval() {
+  return base::TimeDelta::FromMinutes(5);
+}
+
+}  // namespace
+
+MetricsUploadScheduler::MetricsUploadScheduler(
+    const base::Closure& upload_callback)
+    : MetricsScheduler(upload_callback),
+      unsent_logs_interval_(GetUnsentLogsInterval()),
+      initial_backoff_interval_(GetInitialBackoffInterval()),
+      backoff_interval_(initial_backoff_interval_) {}
+
+MetricsUploadScheduler::~MetricsUploadScheduler() {}
+
+void MetricsUploadScheduler::UploadFinished(bool server_is_healthy) {
+  // If the server is having issues, back off. Otherwise, reset to default
+  // (unless there are more logs to send, in which case the next upload should
+  // happen sooner).
+  if (!server_is_healthy) {
+    TaskDone(backoff_interval_);
+    backoff_interval_ = BackOffUploadInterval(backoff_interval_);
+  } else {
+    backoff_interval_ = initial_backoff_interval_;
+    TaskDone(unsent_logs_interval_);
+  }
+}
+
+void MetricsUploadScheduler::StopAndUploadCancelled() {
+  Stop();
+  TaskDone(unsent_logs_interval_);
+}
+
+void MetricsUploadScheduler::UploadOverDataUsageCap() {
+  TaskDone(base::TimeDelta::FromMinutes(kOverDataUsageIntervalMinutes));
+}
+
+}  // namespace metrics
diff --git a/components/metrics/metrics_upload_scheduler.h b/components/metrics/metrics_upload_scheduler.h
new file mode 100644
index 0000000..a97b7cb
--- /dev/null
+++ b/components/metrics/metrics_upload_scheduler.h
@@ -0,0 +1,51 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_METRICS_UPLOAD_SCHEDULER_H_
+#define COMPONENTS_METRICS_METRICS_UPLOAD_SCHEDULER_H_
+
+#include "base/callback.h"
+#include "base/feature_list.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_scheduler.h"
+
+namespace metrics {
+
+// Scheduler task to drive a ReportingService object's uploading.
+class MetricsUploadScheduler : public MetricsScheduler {
+ public:
+  // Creates MetricsUploadScheduler object with the given |upload_callback|
+  // callback to call when uploading should happen.  The callback must
+  // arrange to call either UploadFinished or UploadCancelled on completion.
+  explicit MetricsUploadScheduler(const base::Closure& upload_callback);
+  ~MetricsUploadScheduler() override;
+
+  // Callback from MetricsService when a triggered upload finishes.
+  void UploadFinished(bool server_is_healthy);
+
+  // Callback from MetricsService when an upload is cancelled.
+  // Also stops the scheduler.
+  void StopAndUploadCancelled();
+
+  // Callback from MetricsService when an upload is cancelled because it would
+  // be over the allowed data usage cap.
+  void UploadOverDataUsageCap();
+
+ private:
+  // Time to wait between uploads on success.
+  const base::TimeDelta unsent_logs_interval_;
+
+  // Initial time to wait between upload retry attempts.
+  const base::TimeDelta initial_backoff_interval_;
+
+  // Time to wait for the next upload attempt if the next one fails.
+  base::TimeDelta backoff_interval_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsUploadScheduler);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_METRICS_UPLOAD_SCHEDULER_H_
diff --git a/components/metrics/net/DEPS b/components/metrics/net/DEPS
new file mode 100644
index 0000000..633e4af
--- /dev/null
+++ b/components/metrics/net/DEPS
@@ -0,0 +1,11 @@
+include_rules = [
+  "+chromeos/dbus",
+  "+chromeos/network",
+  "+components/data_use_measurement/core",
+  "+components/encrypted_messages",
+  "+components/variations",
+  "+net",
+  "+services/network/public/cpp",
+  "+services/network/test",
+  "+third_party/cros_system_api",
+]
diff --git a/components/metrics/net/cellular_logic_helper.cc b/components/metrics/net/cellular_logic_helper.cc
new file mode 100644
index 0000000..33b351e
--- /dev/null
+++ b/components/metrics/net/cellular_logic_helper.cc
@@ -0,0 +1,47 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/cellular_logic_helper.h"
+
+#include "net/base/network_change_notifier.h"
+
+namespace metrics {
+
+namespace {
+
+// Standard interval between log uploads, in seconds.
+#if defined(OS_ANDROID) || defined(OS_IOS)
+const int kStandardUploadIntervalSeconds = 5 * 60;           // Five minutes.
+const int kStandardUploadIntervalCellularSeconds = 15 * 60;  // Fifteen minutes.
+#else
+const int kStandardUploadIntervalSeconds = 30 * 60;  // Thirty minutes.
+#endif
+
+#if defined(OS_ANDROID)
+const bool kDefaultCellularLogicEnabled = true;
+#else
+const bool kDefaultCellularLogicEnabled = false;
+#endif
+
+}  // namespace
+
+base::TimeDelta GetUploadInterval() {
+#if defined(OS_ANDROID) || defined(OS_IOS)
+  if (IsCellularLogicEnabled())
+    return base::TimeDelta::FromSeconds(kStandardUploadIntervalCellularSeconds);
+#endif
+  return base::TimeDelta::FromSeconds(kStandardUploadIntervalSeconds);
+}
+
+// Returns true if current connection type is cellular and cellular logic is
+// enabled.
+bool IsCellularLogicEnabled() {
+  if (!kDefaultCellularLogicEnabled)
+    return false;
+
+  return net::NetworkChangeNotifier::IsConnectionCellular(
+      net::NetworkChangeNotifier::GetConnectionType());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/net/cellular_logic_helper.h b/components/metrics/net/cellular_logic_helper.h
new file mode 100644
index 0000000..b5c7061
--- /dev/null
+++ b/components/metrics/net/cellular_logic_helper.h
@@ -0,0 +1,21 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_NET_CELLULAR_LOGIC_HELPER_H_
+#define COMPONENTS_METRICS_NET_CELLULAR_LOGIC_HELPER_H_
+
+#include "base/time/time.h"
+
+namespace metrics {
+
+// Returns UMA log upload interval based on OS and ongoing cellular experiment.
+base::TimeDelta GetUploadInterval();
+
+// Returns true if current connection type is cellular and user is assigned to
+// experimental group for enabled cellular uploads.
+bool IsCellularLogicEnabled();
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_NET_CELLULAR_LOGIC_HELPER_H_
diff --git a/components/metrics/net/net_metrics_log_uploader.cc b/components/metrics/net/net_metrics_log_uploader.cc
new file mode 100644
index 0000000..0d937ce
--- /dev/null
+++ b/components/metrics/net/net_metrics_log_uploader.cc
@@ -0,0 +1,295 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/net_metrics_log_uploader.h"
+
+#include "base/base64.h"
+#include "base/feature_list.h"
+#include "base/metrics/histogram_macros.h"
+#include "components/data_use_measurement/core/data_use_user_data.h"
+#include "components/encrypted_messages/encrypted_message.pb.h"
+#include "components/encrypted_messages/message_encrypter.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "net/base/load_flags.h"
+#include "net/base/url_util.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "net/url_request/url_fetcher.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "third_party/metrics_proto/reporting_info.pb.h"
+#include "url/gurl.h"
+
+namespace {
+
+const base::Feature kHttpRetryFeature{"UMAHttpRetry",
+                                      base::FEATURE_ENABLED_BY_DEFAULT};
+
+// Constants used for encrypting logs that are sent over HTTP. The
+// corresponding private key is used by the metrics server to decrypt logs.
+const char kEncryptedMessageLabel[] = "metrics log";
+
+const uint8_t kServerPublicKey[] = {
+    0x51, 0xcc, 0x52, 0x67, 0x42, 0x47, 0x3b, 0x10, 0xe8, 0x63, 0x18,
+    0x3c, 0x61, 0xa7, 0x96, 0x76, 0x86, 0x91, 0x40, 0x71, 0x39, 0x5f,
+    0x31, 0x1a, 0x39, 0x5b, 0x76, 0xb1, 0x6b, 0x3d, 0x6a, 0x2b};
+
+const uint32_t kServerPublicKeyVersion = 1;
+
+net::NetworkTrafficAnnotationTag GetNetworkTrafficAnnotation(
+    const metrics::MetricsLogUploader::MetricServiceType& service_type) {
+  // The code in this function should remain so that we won't need a default
+  // case that does not have meaningful annotation.
+  if (service_type == metrics::MetricsLogUploader::UMA) {
+    return net::DefineNetworkTrafficAnnotation("metrics_report_uma", R"(
+        semantics {
+          sender: "Metrics UMA Log Uploader"
+          description:
+            "Report of usage statistics and crash-related data about Chromium. "
+            "Usage statistics contain information such as preferences, button "
+            "clicks, and memory usage and do not include web page URLs or "
+            "personal information. See more at "
+            "https://www.google.com/chrome/browser/privacy/ under 'Usage "
+            "statistics and crash reports'. Usage statistics are tied to a "
+            "pseudonymous machine identifier and not to your email address."
+          trigger:
+            "Reports are automatically generated on startup and at intervals "
+            "while Chromium is running."
+          data:
+            "A protocol buffer with usage statistics and crash related data."
+          destination: GOOGLE_OWNED_SERVICE
+        }
+        policy {
+          cookies_allowed: NO
+          setting:
+            "Users can enable or disable this feature by disabling "
+            "'Automatically send usage statistics and crash reports to Google' "
+            "in Chromium's settings under Advanced Settings, Privacy. The "
+            "feature is enabled by default."
+          chrome_policy {
+            MetricsReportingEnabled {
+              policy_options {mode: MANDATORY}
+              MetricsReportingEnabled: false
+            }
+          }
+        })");
+  }
+  DCHECK_EQ(service_type, metrics::MetricsLogUploader::UKM);
+  return net::DefineNetworkTrafficAnnotation("metrics_report_ukm", R"(
+      semantics {
+        sender: "Metrics UKM Log Uploader"
+        description:
+          "Report of usage statistics that are keyed by URLs to Chromium, "
+          "sent only if the profile has History Sync. This includes "
+          "information about the web pages you visit and your usage of them, "
+          "such as page load speed. This will also include URLs and "
+          "statistics related to downloaded files. If Extension Sync is "
+          "enabled, these statistics will also include information about "
+          "the extensions that have been installed from Chrome Web Store. "
+          "Google only stores usage statistics associated with published "
+          "extensions, and URLs that are known by Google’s search index. "
+          "Usage statistics are tied to a pseudonymous machine identifier "
+          "and not to your email address."
+        trigger:
+          "Reports are automatically generated on startup and at intervals "
+          "while Chromium is running with Sync enabled."
+        data:
+          "A protocol buffer with usage statistics and associated URLs."
+        destination: GOOGLE_OWNED_SERVICE
+      }
+      policy {
+        cookies_allowed: NO
+        setting:
+          "Users can enable or disable this feature by disabling "
+          "'Automatically send usage statistics and crash reports to Google' "
+          "in Chromium's settings under Advanced Settings, Privacy. This is "
+          "only enabled if all active profiles have History/Extension Sync "
+          "enabled without a Sync passphrase."
+        chrome_policy {
+          MetricsReportingEnabled {
+            policy_options {mode: MANDATORY}
+            MetricsReportingEnabled: false
+          }
+        }
+      })");
+}
+
+std::string SerializeReportingInfo(
+    const metrics::ReportingInfo& reporting_info) {
+  std::string result;
+  std::string bytes;
+  bool success = reporting_info.SerializeToString(&bytes);
+  DCHECK(success);
+  base::Base64Encode(bytes, &result);
+  return result;
+}
+
+void RecordUploadSizeForServiceTypeHistograms(
+    int64_t content_length,
+    metrics::MetricsLogUploader::MetricServiceType service_type) {
+  switch (service_type) {
+    case metrics::MetricsLogUploader::UMA:
+      UMA_HISTOGRAM_COUNTS_1M("UMA.LogUploader.UploadSize", content_length);
+      break;
+    case metrics::MetricsLogUploader::UKM:
+      UMA_HISTOGRAM_COUNTS_1M("UKM.LogUploader.UploadSize", content_length);
+      break;
+  }
+}
+
+}  // namespace
+
+namespace metrics {
+
+NetMetricsLogUploader::NetMetricsLogUploader(
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+    base::StringPiece server_url,
+    base::StringPiece mime_type,
+    MetricsLogUploader::MetricServiceType service_type,
+    const MetricsLogUploader::UploadCallback& on_upload_complete)
+    : url_loader_factory_(std::move(url_loader_factory)),
+      server_url_(server_url),
+      mime_type_(mime_type.data(), mime_type.size()),
+      service_type_(service_type),
+      on_upload_complete_(on_upload_complete) {}
+
+NetMetricsLogUploader::NetMetricsLogUploader(
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+    base::StringPiece server_url,
+    base::StringPiece insecure_server_url,
+    base::StringPiece mime_type,
+    MetricsLogUploader::MetricServiceType service_type,
+    const MetricsLogUploader::UploadCallback& on_upload_complete)
+    : url_loader_factory_(std::move(url_loader_factory)),
+      server_url_(server_url),
+      insecure_server_url_(insecure_server_url),
+      mime_type_(mime_type.data(), mime_type.size()),
+      service_type_(service_type),
+      on_upload_complete_(on_upload_complete) {}
+
+NetMetricsLogUploader::~NetMetricsLogUploader() {
+}
+
+void NetMetricsLogUploader::UploadLog(const std::string& compressed_log_data,
+                                      const std::string& log_hash,
+                                      const ReportingInfo& reporting_info) {
+  // If this attempt is a retry, there was a network error, the last attempt was
+  // over https, and there is an insecure url set, attempt this upload over
+  // HTTP.
+  // Currently we only retry over HTTP if the retry-uma-over-http flag is set.
+  if (!insecure_server_url_.is_empty() && reporting_info.attempt_count() > 1 &&
+      reporting_info.last_error_code() != 0 &&
+      reporting_info.last_attempt_was_https() &&
+      base::FeatureList::IsEnabled(kHttpRetryFeature)) {
+    UploadLogToURL(compressed_log_data, log_hash, reporting_info,
+                   insecure_server_url_);
+    return;
+  }
+  UploadLogToURL(compressed_log_data, log_hash, reporting_info, server_url_);
+}
+
+void NetMetricsLogUploader::UploadLogToURL(
+    const std::string& compressed_log_data,
+    const std::string& log_hash,
+    const ReportingInfo& reporting_info,
+    const GURL& url) {
+  DCHECK(!log_hash.empty());
+
+  // TODO(crbug.com/808498): Restore the data use measurement when bug is fixed.
+
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->url = url;
+  // Drop cookies and auth data.
+  resource_request->allow_credentials = false;
+  resource_request->method = "POST";
+
+  std::string reporting_info_string = SerializeReportingInfo(reporting_info);
+  // If we are not using HTTPS for this upload, encrypt it. We do not encrypt
+  // requests to localhost to allow testing with a local collector that doesn't
+  // have decryption enabled.
+  bool should_encrypt =
+      !url.SchemeIs(url::kHttpsScheme) && !net::IsLocalhost(url);
+  if (should_encrypt) {
+    std::string encrypted_hash;
+    std::string base64_encoded_hash;
+    if (!EncryptString(log_hash, &encrypted_hash)) {
+      on_upload_complete_.Run(0, net::ERR_FAILED, false);
+      return;
+    }
+    base::Base64Encode(encrypted_hash, &base64_encoded_hash);
+    resource_request->headers.SetHeader("X-Chrome-UMA-Log-SHA1",
+                                        base64_encoded_hash);
+
+    std::string encrypted_reporting_info;
+    std::string base64_reporting_info;
+    if (!EncryptString(reporting_info_string, &encrypted_reporting_info)) {
+      on_upload_complete_.Run(0, net::ERR_FAILED, false);
+      return;
+    }
+    base::Base64Encode(encrypted_reporting_info, &base64_reporting_info);
+    resource_request->headers.SetHeader("X-Chrome-UMA-ReportingInfo",
+                                        base64_reporting_info);
+  } else {
+    resource_request->headers.SetHeader("X-Chrome-UMA-Log-SHA1", log_hash);
+    resource_request->headers.SetHeader("X-Chrome-UMA-ReportingInfo",
+                                        reporting_info_string);
+    // Tell the server that we're uploading gzipped protobufs only if we are not
+    // encrypting, since encrypted messages have to be decrypted server side
+    // after decryption, not before.
+    resource_request->headers.SetHeader("content-encoding", "gzip");
+  }
+
+  url_loader_ = network::SimpleURLLoader::Create(
+      std::move(resource_request), GetNetworkTrafficAnnotation(service_type_));
+
+  if (should_encrypt) {
+    std::string encrypted_message;
+    if (!EncryptString(compressed_log_data, &encrypted_message)) {
+      url_loader_.reset();
+      on_upload_complete_.Run(0, net::ERR_FAILED, false);
+      return;
+    }
+    url_loader_->AttachStringForUpload(encrypted_message, mime_type_);
+    RecordUploadSizeForServiceTypeHistograms(encrypted_message.size(),
+                                             service_type_);
+  } else {
+    url_loader_->AttachStringForUpload(compressed_log_data, mime_type_);
+    RecordUploadSizeForServiceTypeHistograms(compressed_log_data.size(),
+                                             service_type_);
+  }
+
+  // It's safe to use |base::Unretained(this)| here, because |this| owns
+  // the |url_loader_|, and the callback will be cancelled if the |url_loader_|
+  // is destroyed.
+  url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+      url_loader_factory_.get(),
+      base::BindOnce(&NetMetricsLogUploader::OnURLLoadComplete,
+                     base::Unretained(this)));
+}
+
+// The callback is only invoked if |url_loader_| it was bound against is alive.
+void NetMetricsLogUploader::OnURLLoadComplete(
+    std::unique_ptr<std::string> response_body) {
+  int response_code = -1;
+  if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers)
+    response_code = url_loader_->ResponseInfo()->headers->response_code();
+
+  int error_code = url_loader_->NetError();
+
+  bool was_https = url_loader_->GetFinalURL().SchemeIs(url::kHttpsScheme);
+  url_loader_.reset();
+  on_upload_complete_.Run(response_code, error_code, was_https);
+}
+
+bool NetMetricsLogUploader::EncryptString(const std::string& plaintext,
+                                          std::string* encrypted) {
+  encrypted_messages::EncryptedMessage encrypted_message;
+  if (!encrypted_messages::EncryptSerializedMessage(
+          kServerPublicKey, kServerPublicKeyVersion, kEncryptedMessageLabel,
+          plaintext, &encrypted_message) ||
+      !encrypted_message.SerializeToString(encrypted)) {
+    return false;
+  }
+  return true;
+}
+}  // namespace metrics
diff --git a/components/metrics/net/net_metrics_log_uploader.h b/components/metrics/net/net_metrics_log_uploader.h
new file mode 100644
index 0000000..99fce26
--- /dev/null
+++ b/components/metrics/net/net_metrics_log_uploader.h
@@ -0,0 +1,86 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_
+#define COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "third_party/metrics_proto/reporting_info.pb.h"
+#include "url/gurl.h"
+
+namespace network {
+class SharedURLLoaderFactory;
+class SimpleURLLoader;
+}  // namespace network
+
+namespace metrics {
+
+// Implementation of MetricsLogUploader using the Chrome network stack.
+class NetMetricsLogUploader : public MetricsLogUploader {
+ public:
+  // Constructs a NetMetricsLogUploader which uploads data to |server_url| with
+  // the specified |mime_type|. The |service_type| marks which service the
+  // data usage should be attributed to. The |on_upload_complete| callback will
+  // be called with the HTTP response code of the upload or with -1 on an error.
+  NetMetricsLogUploader(
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+      base::StringPiece server_url,
+      base::StringPiece mime_type,
+      MetricsLogUploader::MetricServiceType service_type,
+      const MetricsLogUploader::UploadCallback& on_upload_complete);
+
+  // This constructor allows a secondary non-HTTPS URL to be passed in as
+  // |insecure_server_url|. That URL is used as a fallback if a connection
+  // to |server_url| fails, requests are encrypted when sent to an HTTP URL.
+  NetMetricsLogUploader(
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+      base::StringPiece server_url,
+      base::StringPiece insecure_server_url,
+      base::StringPiece mime_type,
+      MetricsLogUploader::MetricServiceType service_type,
+      const MetricsLogUploader::UploadCallback& on_upload_complete);
+
+  ~NetMetricsLogUploader() override;
+
+  // MetricsLogUploader:
+  // Uploads a log to the server_url specified in the constructor.
+  void UploadLog(const std::string& compressed_log_data,
+                 const std::string& log_hash,
+                 const ReportingInfo& reporting_info) override;
+
+ private:
+  // Uploads a log to a URL passed as a parameter.
+  void UploadLogToURL(const std::string& compressed_log_data,
+                      const std::string& log_hash,
+                      const ReportingInfo& reporting_info,
+                      const GURL& url);
+
+  void OnURLLoadComplete(std::unique_ptr<std::string> response_body);
+
+  // Encrypts a |plaintext| string, using the encrypted_messages component,
+  // returns |encrypted| which is a serialized EncryptedMessage object.
+  bool EncryptString(const std::string& plaintext, std::string* encrypted);
+
+  // The URLLoader factory for loads done using the network stack.
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+
+  const GURL server_url_;
+  const GURL insecure_server_url_;
+  const std::string mime_type_;
+  const MetricsLogUploader ::MetricServiceType service_type_;
+  const MetricsLogUploader::UploadCallback on_upload_complete_;
+  // The outstanding transmission appears as a URL Fetch operation.
+  std::unique_ptr<network::SimpleURLLoader> url_loader_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetMetricsLogUploader);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_NET_NET_METRICS_LOG_UPLOADER_H_
diff --git a/components/metrics/net/net_metrics_log_uploader_unittest.cc b/components/metrics/net/net_metrics_log_uploader_unittest.cc
new file mode 100644
index 0000000..f6aecdc
--- /dev/null
+++ b/components/metrics/net/net_metrics_log_uploader_unittest.cc
@@ -0,0 +1,187 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/net_metrics_log_uploader.h"
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "components/encrypted_messages/encrypted_message.pb.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/reporting_info.pb.h"
+#include "third_party/zlib/google/compression_utils.h"
+#include "url/gurl.h"
+
+namespace metrics {
+
+class NetMetricsLogUploaderTest : public testing::Test {
+ public:
+  NetMetricsLogUploaderTest()
+      : on_upload_complete_count_(0),
+        test_shared_url_loader_factory_(
+            base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+                &test_url_loader_factory_)) {
+    test_url_loader_factory_.SetInterceptor(base::BindLambdaForTesting(
+        [&](const network::ResourceRequest& request) {
+          upload_data_ = network::GetUploadData(request);
+          headers_ = request.headers;
+          loop_.Quit();
+        }));
+  }
+
+  void CreateAndOnUploadCompleteReuseUploader() {
+    ReportingInfo reporting_info;
+    reporting_info.set_attempt_count(10);
+    uploader_.reset(new NetMetricsLogUploader(
+        test_shared_url_loader_factory_, "https://dummy_server", "dummy_mime",
+        MetricsLogUploader::UMA,
+        base::Bind(&NetMetricsLogUploaderTest::OnUploadCompleteReuseUploader,
+                   base::Unretained(this))));
+    uploader_->UploadLog("initial_dummy_data", "initial_dummy_hash",
+                         reporting_info);
+  }
+
+  void CreateUploaderAndUploadToSecureURL(const std::string& url) {
+    ReportingInfo dummy_reporting_info;
+    uploader_.reset(new NetMetricsLogUploader(
+        test_shared_url_loader_factory_, url, "dummy_mime",
+        MetricsLogUploader::UMA,
+        base::Bind(&NetMetricsLogUploaderTest::DummyOnUploadComplete,
+                   base::Unretained(this))));
+    uploader_->UploadLog("dummy_data", "dummy_hash", dummy_reporting_info);
+  }
+
+  void CreateUploaderAndUploadToInsecureURL() {
+    ReportingInfo dummy_reporting_info;
+    uploader_.reset(new NetMetricsLogUploader(
+        test_shared_url_loader_factory_, "http://dummy_insecure_server",
+        "dummy_mime", MetricsLogUploader::UMA,
+        base::Bind(&NetMetricsLogUploaderTest::DummyOnUploadComplete,
+                   base::Unretained(this))));
+    std::string compressed_message;
+    // Compress the data since the encryption code expects a compressed log,
+    // and tries to decompress it before encrypting it.
+    compression::GzipCompress("dummy_data", &compressed_message);
+    uploader_->UploadLog(compressed_message, "dummy_hash",
+                         dummy_reporting_info);
+  }
+
+  void DummyOnUploadComplete(int response_code,
+                             int error_code,
+                             bool was_https) {}
+
+  void OnUploadCompleteReuseUploader(int response_code,
+                                     int error_code,
+                                     bool was_https) {
+    ++on_upload_complete_count_;
+    if (on_upload_complete_count_ == 1) {
+      ReportingInfo reporting_info;
+      reporting_info.set_attempt_count(20);
+      uploader_->UploadLog("dummy_data", "dummy_hash", reporting_info);
+    }
+  }
+
+  network::TestURLLoaderFactory::PendingRequest* GetPendingRequest(
+      size_t index) {
+    if (index >= test_url_loader_factory_.pending_requests()->size())
+      return nullptr;
+    auto* request = &(*test_url_loader_factory_.pending_requests())[index];
+    DCHECK(request);
+    return request;
+  }
+
+  int on_upload_complete_count() const {
+    return on_upload_complete_count_;
+  }
+
+  network::TestURLLoaderFactory* test_url_loader_factory() {
+    return &test_url_loader_factory_;
+  }
+
+  const net::HttpRequestHeaders& last_request_headers() { return headers_; }
+
+  const std::string& last_upload_data() { return upload_data_; }
+
+  void WaitForRequest() { loop_.Run(); }
+
+ private:
+  std::unique_ptr<NetMetricsLogUploader> uploader_;
+  int on_upload_complete_count_;
+
+  network::TestURLLoaderFactory test_url_loader_factory_;
+  scoped_refptr<network::SharedURLLoaderFactory>
+      test_shared_url_loader_factory_;
+
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+  base::RunLoop loop_;
+  std::string upload_data_;
+  net::HttpRequestHeaders headers_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetMetricsLogUploaderTest);
+};
+
+void CheckReportingInfoHeader(net::HttpRequestHeaders headers,
+                              int expected_attempt_count) {
+  std::string reporting_info_base64;
+  EXPECT_TRUE(
+      headers.GetHeader("X-Chrome-UMA-ReportingInfo", &reporting_info_base64));
+  std::string reporting_info_string;
+  EXPECT_TRUE(
+      base::Base64Decode(reporting_info_base64, &reporting_info_string));
+  ReportingInfo reporting_info;
+  EXPECT_TRUE(reporting_info.ParseFromString(reporting_info_string));
+  EXPECT_EQ(reporting_info.attempt_count(), expected_attempt_count);
+}
+
+TEST_F(NetMetricsLogUploaderTest, OnUploadCompleteReuseUploader) {
+  CreateAndOnUploadCompleteReuseUploader();
+  WaitForRequest();
+
+  // Mimic the initial fetcher callback.
+  CheckReportingInfoHeader(last_request_headers(), 10);
+  test_url_loader_factory()->SimulateResponseWithoutRemovingFromPendingList(
+      GetPendingRequest(0), "");
+
+  // Mimic the second fetcher callback.
+  CheckReportingInfoHeader(last_request_headers(), 20);
+  test_url_loader_factory()->SimulateResponseWithoutRemovingFromPendingList(
+      GetPendingRequest(1), "");
+
+  EXPECT_EQ(on_upload_complete_count(), 2);
+}
+
+// Test that attempting to upload to an HTTP URL results in an encrypted
+// message.
+TEST_F(NetMetricsLogUploaderTest, MessageOverHTTPIsEncrypted) {
+  CreateUploaderAndUploadToInsecureURL();
+  WaitForRequest();
+  encrypted_messages::EncryptedMessage message;
+  EXPECT_TRUE(message.ParseFromString(last_upload_data()));
+}
+
+// Test that attempting to upload to an HTTPS URL results in an unencrypted
+// message.
+TEST_F(NetMetricsLogUploaderTest, MessageOverHTTPSIsNotEncrypted) {
+  CreateUploaderAndUploadToSecureURL("https://dummy_secure_server");
+  WaitForRequest();
+  EXPECT_EQ(last_upload_data(), "dummy_data");
+}
+
+// Test that attempting to upload to localhost over http results in an
+// unencrypted message.
+TEST_F(NetMetricsLogUploaderTest, MessageOverHTTPLocalhostIsNotEncrypted) {
+  CreateUploaderAndUploadToSecureURL("http://localhost");
+  WaitForRequest();
+  EXPECT_EQ(last_upload_data(), "dummy_data");
+}
+
+}  // namespace metrics
diff --git a/components/metrics/net/network_metrics_provider.cc b/components/metrics/net/network_metrics_provider.cc
new file mode 100644
index 0000000..d175648
--- /dev/null
+++ b/components/metrics/net/network_metrics_provider.cc
@@ -0,0 +1,483 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/network_metrics_provider.h"
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/bind_helpers.h"
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/sparse_histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "net/base/net_errors.h"
+#include "net/nqe/effective_connection_type_observer.h"
+#include "net/nqe/network_quality_estimator.h"
+
+#if defined(OS_CHROMEOS)
+#include "components/metrics/net/wifi_access_point_info_provider_chromeos.h"
+#endif  // OS_CHROMEOS
+
+namespace metrics {
+
+SystemProfileProto::Network::EffectiveConnectionType
+ConvertEffectiveConnectionType(
+    net::EffectiveConnectionType effective_connection_type) {
+  switch (effective_connection_type) {
+    case net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN:
+      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
+    case net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G:
+      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_SLOW_2G;
+    case net::EFFECTIVE_CONNECTION_TYPE_2G:
+      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G;
+    case net::EFFECTIVE_CONNECTION_TYPE_3G:
+      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_3G;
+    case net::EFFECTIVE_CONNECTION_TYPE_4G:
+      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_4G;
+    case net::EFFECTIVE_CONNECTION_TYPE_OFFLINE:
+      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_OFFLINE;
+    case net::EFFECTIVE_CONNECTION_TYPE_LAST:
+      NOTREACHED();
+      return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
+  }
+  NOTREACHED();
+  return SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
+}
+
+// Listens to the changes in the effective conection type.
+class NetworkMetricsProvider::EffectiveConnectionTypeObserver
+    : public net::EffectiveConnectionTypeObserver {
+ public:
+  // |network_quality_estimator| is used to provide the network quality
+  // estimates. Guaranteed to be non-null. |callback| is run on
+  // |callback_task_runner|, and provides notifications about the changes in the
+  // effective connection type.
+  EffectiveConnectionTypeObserver(
+      base::Callback<void(net::EffectiveConnectionType)> callback,
+      const scoped_refptr<base::SequencedTaskRunner>& callback_task_runner)
+      : network_quality_estimator_(nullptr),
+        callback_(callback),
+        callback_task_runner_(callback_task_runner) {
+    DCHECK(callback_);
+    DCHECK(callback_task_runner_);
+    // |this| is initialized and used on the IO thread using
+    // |network_quality_task_runner_|.
+    thread_checker_.DetachFromThread();
+  }
+
+  ~EffectiveConnectionTypeObserver() override {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    if (network_quality_estimator_)
+      network_quality_estimator_->RemoveEffectiveConnectionTypeObserver(this);
+  }
+
+  // Initializes |this| on IO thread using |network_quality_task_runner_|. This
+  // is the same thread on which |network_quality_estimator| lives.
+  void Init(net::NetworkQualityEstimator* network_quality_estimator) {
+    network_quality_estimator_ = network_quality_estimator;
+    if (network_quality_estimator_)
+      network_quality_estimator_->AddEffectiveConnectionTypeObserver(this);
+  }
+
+ private:
+  // net::EffectiveConnectionTypeObserver:
+  void OnEffectiveConnectionTypeChanged(
+      net::EffectiveConnectionType type) override {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    callback_task_runner_->PostTask(FROM_HERE, base::BindOnce(callback_, type));
+  }
+
+  // Notifies |this| when there is a change in the effective connection type.
+  net::NetworkQualityEstimator* network_quality_estimator_;
+
+  // Called when the effective connection type is changed.
+  base::Callback<void(net::EffectiveConnectionType)> callback_;
+
+  // Task runner on which |callback_| is run.
+  scoped_refptr<base::SequencedTaskRunner> callback_task_runner_;
+
+  base::ThreadChecker thread_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(EffectiveConnectionTypeObserver);
+};
+
+NetworkMetricsProvider::NetworkMetricsProvider(
+    std::unique_ptr<NetworkQualityEstimatorProvider>
+        network_quality_estimator_provider)
+    : connection_type_is_ambiguous_(false),
+      network_change_notifier_initialized_(false),
+      wifi_phy_layer_protocol_is_ambiguous_(false),
+      wifi_phy_layer_protocol_(net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN),
+      total_aborts_(0),
+      total_codes_(0),
+      network_quality_estimator_provider_(
+          std::move(network_quality_estimator_provider)),
+      effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
+      min_effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
+      max_effective_connection_type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
+      weak_ptr_factory_(this) {
+  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
+  connection_type_ = net::NetworkChangeNotifier::GetConnectionType();
+  if (connection_type_ != net::NetworkChangeNotifier::CONNECTION_UNKNOWN)
+    network_change_notifier_initialized_ = true;
+
+  ProbeWifiPHYLayerProtocol();
+
+  if (network_quality_estimator_provider_) {
+    effective_connection_type_observer_.reset(
+        new EffectiveConnectionTypeObserver(
+            base::Bind(
+                &NetworkMetricsProvider::OnEffectiveConnectionTypeChanged,
+                base::Unretained(this)),
+            base::ThreadTaskRunnerHandle::Get()));
+
+    // Get the network quality estimator and initialize
+    // |effective_connection_type_observer_| on the same task runner on  which
+    // the network quality estimator lives. It is safe to use base::Unretained
+    // here since both |network_quality_estimator_provider_| and
+    // |effective_connection_type_observer_| are owned by |this|, and
+    // |network_quality_estimator_provider_| is deleted before
+    // |effective_connection_type_observer_|.
+    network_quality_estimator_provider_->PostReplyNetworkQualityEstimator(
+        base::Bind(
+            &EffectiveConnectionTypeObserver::Init,
+            base::Unretained(effective_connection_type_observer_.get())));
+  }
+}
+
+NetworkMetricsProvider::~NetworkMetricsProvider() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
+
+  if (network_quality_estimator_provider_) {
+    scoped_refptr<base::SequencedTaskRunner> network_quality_task_runner =
+        network_quality_estimator_provider_->GetTaskRunner();
+
+    // |network_quality_estimator_provider_| must be deleted before
+    // |effective_connection_type_observer_| since
+    // |effective_connection_type_observer_| may callback into
+    // |effective_connection_type_observer_|.
+    network_quality_estimator_provider_.reset();
+
+    if (network_quality_task_runner &&
+        !network_quality_task_runner->DeleteSoon(
+            FROM_HERE, effective_connection_type_observer_.release())) {
+      NOTREACHED() << " ECT observer was not deleted successfully";
+    }
+  }
+}
+
+void NetworkMetricsProvider::ProvideCurrentSessionData(
+    ChromeUserMetricsExtension*) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // ProvideCurrentSessionData is called on the main thread, at the time a
+  // metrics record is being finalized.
+  net::NetworkChangeNotifier::FinalizingMetricsLogRecord();
+  LogAggregatedMetrics();
+}
+
+void NetworkMetricsProvider::ProvideSystemProfileMetrics(
+    SystemProfileProto* system_profile) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(!connection_type_is_ambiguous_ ||
+         network_change_notifier_initialized_);
+  SystemProfileProto::Network* network = system_profile->mutable_network();
+  network->set_connection_type_is_ambiguous(connection_type_is_ambiguous_);
+  network->set_connection_type(GetConnectionType());
+  network->set_wifi_phy_layer_protocol_is_ambiguous(
+      wifi_phy_layer_protocol_is_ambiguous_);
+  network->set_wifi_phy_layer_protocol(GetWifiPHYLayerProtocol());
+
+  network->set_min_effective_connection_type(
+      ConvertEffectiveConnectionType(min_effective_connection_type_));
+  network->set_max_effective_connection_type(
+      ConvertEffectiveConnectionType(max_effective_connection_type_));
+
+  // Update the connection type. Note that this is necessary to set the network
+  // type to "none" if there is no network connection for an entire UMA logging
+  // window, since OnConnectionTypeChanged() ignores transitions to the "none"
+  // state.
+  connection_type_ = net::NetworkChangeNotifier::GetConnectionType();
+  if (connection_type_ != net::NetworkChangeNotifier::CONNECTION_UNKNOWN)
+    network_change_notifier_initialized_ = true;
+  // Reset the "ambiguous" flags, since a new metrics log session has started.
+  connection_type_is_ambiguous_ = false;
+  wifi_phy_layer_protocol_is_ambiguous_ = false;
+  min_effective_connection_type_ = effective_connection_type_;
+  max_effective_connection_type_ = effective_connection_type_;
+
+  if (!wifi_access_point_info_provider_) {
+#if defined(OS_CHROMEOS)
+    wifi_access_point_info_provider_.reset(
+        new WifiAccessPointInfoProviderChromeos());
+#else
+    wifi_access_point_info_provider_.reset(
+        new WifiAccessPointInfoProvider());
+#endif  // OS_CHROMEOS
+  }
+
+  // Connected wifi access point information.
+  WifiAccessPointInfoProvider::WifiAccessPointInfo info;
+  if (wifi_access_point_info_provider_->GetInfo(&info))
+    WriteWifiAccessPointProto(info, network);
+}
+
+void NetworkMetricsProvider::OnNetworkChanged(
+    net::NetworkChangeNotifier::ConnectionType type) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // To avoid reporting an ambiguous connection type for users on flaky
+  // connections, ignore transitions to the "none" state. Note that the
+  // connection type is refreshed in ProvideSystemProfileMetrics() each time a
+  // new UMA logging window begins, so users who genuinely transition to offline
+  // mode for an extended duration will still be at least partially represented
+  // in the metrics logs.
+  if (type == net::NetworkChangeNotifier::CONNECTION_NONE) {
+    network_change_notifier_initialized_ = true;
+    return;
+  }
+
+  DCHECK(network_change_notifier_initialized_ ||
+         connection_type_ == net::NetworkChangeNotifier::CONNECTION_UNKNOWN);
+
+  if (type != connection_type_ &&
+      connection_type_ != net::NetworkChangeNotifier::CONNECTION_NONE &&
+      network_change_notifier_initialized_) {
+    // If |network_change_notifier_initialized_| is false, it implies that this
+    // is the first connection change callback received from network change
+    // notifier, and the previous connection type was CONNECTION_UNKNOWN. In
+    // that case, connection type should not be marked as ambiguous since there
+    // was no actual change in the connection type.
+    connection_type_is_ambiguous_ = true;
+  }
+
+  network_change_notifier_initialized_ = true;
+  connection_type_ = type;
+
+  ProbeWifiPHYLayerProtocol();
+}
+
+SystemProfileProto::Network::ConnectionType
+NetworkMetricsProvider::GetConnectionType() const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  switch (connection_type_) {
+    case net::NetworkChangeNotifier::CONNECTION_NONE:
+      return SystemProfileProto::Network::CONNECTION_NONE;
+    case net::NetworkChangeNotifier::CONNECTION_UNKNOWN:
+      return SystemProfileProto::Network::CONNECTION_UNKNOWN;
+    case net::NetworkChangeNotifier::CONNECTION_ETHERNET:
+      return SystemProfileProto::Network::CONNECTION_ETHERNET;
+    case net::NetworkChangeNotifier::CONNECTION_WIFI:
+      return SystemProfileProto::Network::CONNECTION_WIFI;
+    case net::NetworkChangeNotifier::CONNECTION_2G:
+      return SystemProfileProto::Network::CONNECTION_2G;
+    case net::NetworkChangeNotifier::CONNECTION_3G:
+      return SystemProfileProto::Network::CONNECTION_3G;
+    case net::NetworkChangeNotifier::CONNECTION_4G:
+      return SystemProfileProto::Network::CONNECTION_4G;
+    case net::NetworkChangeNotifier::CONNECTION_BLUETOOTH:
+      return SystemProfileProto::Network::CONNECTION_BLUETOOTH;
+  }
+  NOTREACHED();
+  return SystemProfileProto::Network::CONNECTION_UNKNOWN;
+}
+
+SystemProfileProto::Network::WifiPHYLayerProtocol
+NetworkMetricsProvider::GetWifiPHYLayerProtocol() const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  switch (wifi_phy_layer_protocol_) {
+    case net::WIFI_PHY_LAYER_PROTOCOL_NONE:
+      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_NONE;
+    case net::WIFI_PHY_LAYER_PROTOCOL_ANCIENT:
+      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_ANCIENT;
+    case net::WIFI_PHY_LAYER_PROTOCOL_A:
+      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_A;
+    case net::WIFI_PHY_LAYER_PROTOCOL_B:
+      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_B;
+    case net::WIFI_PHY_LAYER_PROTOCOL_G:
+      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_G;
+    case net::WIFI_PHY_LAYER_PROTOCOL_N:
+      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_N;
+    case net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN:
+      return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN;
+  }
+  NOTREACHED();
+  return SystemProfileProto::Network::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN;
+}
+
+void NetworkMetricsProvider::ProbeWifiPHYLayerProtocol() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  base::PostTaskWithTraitsAndReplyWithResult(
+      FROM_HERE,
+      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+       base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+      base::BindOnce(&net::GetWifiPHYLayerProtocol),
+      base::BindOnce(&NetworkMetricsProvider::OnWifiPHYLayerProtocolResult,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void NetworkMetricsProvider::OnWifiPHYLayerProtocolResult(
+    net::WifiPHYLayerProtocol mode) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (wifi_phy_layer_protocol_ != net::WIFI_PHY_LAYER_PROTOCOL_UNKNOWN &&
+      mode != wifi_phy_layer_protocol_) {
+    wifi_phy_layer_protocol_is_ambiguous_ = true;
+  }
+  wifi_phy_layer_protocol_ = mode;
+}
+
+void NetworkMetricsProvider::WriteWifiAccessPointProto(
+    const WifiAccessPointInfoProvider::WifiAccessPointInfo& info,
+    SystemProfileProto::Network* network_proto) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  SystemProfileProto::Network::WifiAccessPoint* access_point_info =
+      network_proto->mutable_access_point_info();
+  SystemProfileProto::Network::WifiAccessPoint::SecurityMode security =
+      SystemProfileProto::Network::WifiAccessPoint::SECURITY_UNKNOWN;
+  switch (info.security) {
+    case WifiAccessPointInfoProvider::WIFI_SECURITY_NONE:
+      security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_NONE;
+      break;
+    case WifiAccessPointInfoProvider::WIFI_SECURITY_WPA:
+      security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_WPA;
+      break;
+    case WifiAccessPointInfoProvider::WIFI_SECURITY_WEP:
+      security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_WEP;
+      break;
+    case WifiAccessPointInfoProvider::WIFI_SECURITY_RSN:
+      security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_RSN;
+      break;
+    case WifiAccessPointInfoProvider::WIFI_SECURITY_802_1X:
+      security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_802_1X;
+      break;
+    case WifiAccessPointInfoProvider::WIFI_SECURITY_PSK:
+      security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_PSK;
+      break;
+    case WifiAccessPointInfoProvider::WIFI_SECURITY_UNKNOWN:
+      security = SystemProfileProto::Network::WifiAccessPoint::SECURITY_UNKNOWN;
+      break;
+  }
+  access_point_info->set_security_mode(security);
+
+  // |bssid| is xx:xx:xx:xx:xx:xx, extract the first three components and
+  // pack into a uint32_t.
+  std::string bssid = info.bssid;
+  if (bssid.size() == 17 && bssid[2] == ':' && bssid[5] == ':' &&
+      bssid[8] == ':' && bssid[11] == ':' && bssid[14] == ':') {
+    std::string vendor_prefix_str;
+    uint32_t vendor_prefix;
+
+    base::RemoveChars(bssid.substr(0, 9), ":", &vendor_prefix_str);
+    DCHECK_EQ(6U, vendor_prefix_str.size());
+    if (base::HexStringToUInt(vendor_prefix_str, &vendor_prefix))
+      access_point_info->set_vendor_prefix(vendor_prefix);
+    else
+      NOTREACHED();
+  }
+
+  // Return if vendor information is not provided.
+  if (info.model_number.empty() && info.model_name.empty() &&
+      info.device_name.empty() && info.oui_list.empty())
+    return;
+
+  SystemProfileProto::Network::WifiAccessPoint::VendorInformation* vendor =
+      access_point_info->mutable_vendor_info();
+  if (!info.model_number.empty())
+    vendor->set_model_number(info.model_number);
+  if (!info.model_name.empty())
+    vendor->set_model_name(info.model_name);
+  if (!info.device_name.empty())
+    vendor->set_device_name(info.device_name);
+
+  // Return if OUI list is not provided.
+  if (info.oui_list.empty())
+    return;
+
+  // Parse OUI list.
+  for (const base::StringPiece& oui_str : base::SplitStringPiece(
+           info.oui_list, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
+    uint32_t oui;
+    if (base::HexStringToUInt(oui_str, &oui)) {
+      vendor->add_element_identifier(oui);
+    } else {
+      DLOG(WARNING) << "Error when parsing OUI list of the WiFi access point";
+    }
+  }
+}
+
+void NetworkMetricsProvider::LogAggregatedMetrics() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  base::HistogramBase* error_codes = base::SparseHistogram::FactoryGet(
+      "Net.ErrorCodesForMainFrame4",
+      base::HistogramBase::kUmaTargetedHistogramFlag);
+  std::unique_ptr<base::HistogramSamples> samples =
+      error_codes->SnapshotSamples();
+  base::HistogramBase::Count new_aborts =
+      samples->GetCount(-net::ERR_ABORTED) - total_aborts_;
+  base::HistogramBase::Count new_codes = samples->TotalCount() - total_codes_;
+  if (new_codes > 0) {
+    UMA_HISTOGRAM_CUSTOM_COUNTS("Net.ErrAborted.CountPerUpload2", new_aborts, 1,
+                                100000000, 50);
+    UMA_HISTOGRAM_PERCENTAGE("Net.ErrAborted.ProportionPerUpload",
+                             (100 * new_aborts) / new_codes);
+    total_codes_ += new_codes;
+    total_aborts_ += new_aborts;
+  }
+}
+
+void NetworkMetricsProvider::OnEffectiveConnectionTypeChanged(
+    net::EffectiveConnectionType type) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  effective_connection_type_ = type;
+
+  if (effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN ||
+      effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_OFFLINE) {
+    // The effective connection type may be reported as Unknown if there is a
+    // change in the connection type. Disregard it since network requests can't
+    // be send during the changes in connection type. Similarly, disregard
+    // offline as the type since it may be reported as the effective connection
+    // type for a short period when there is a change in the connection type.
+    return;
+  }
+
+  if (min_effective_connection_type_ ==
+          net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN &&
+      max_effective_connection_type_ ==
+          net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) {
+    min_effective_connection_type_ = type;
+    max_effective_connection_type_ = type;
+    return;
+  }
+
+  if (min_effective_connection_type_ ==
+          net::EFFECTIVE_CONNECTION_TYPE_OFFLINE &&
+      max_effective_connection_type_ ==
+          net::EFFECTIVE_CONNECTION_TYPE_OFFLINE) {
+    min_effective_connection_type_ = type;
+    max_effective_connection_type_ = type;
+    return;
+  }
+
+  min_effective_connection_type_ =
+      std::min(min_effective_connection_type_, effective_connection_type_);
+  max_effective_connection_type_ =
+      std::max(max_effective_connection_type_, effective_connection_type_);
+
+  DCHECK_EQ(
+      min_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+      max_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN);
+  DCHECK_EQ(
+      min_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_OFFLINE,
+      max_effective_connection_type_ == net::EFFECTIVE_CONNECTION_TYPE_OFFLINE);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/net/network_metrics_provider.h b/components/metrics/net/network_metrics_provider.h
new file mode 100644
index 0000000..3b9932f
--- /dev/null
+++ b/components/metrics/net/network_metrics_provider.h
@@ -0,0 +1,159 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_NETWORK_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_NET_NETWORK_METRICS_PROVIDER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/metrics/histogram_base.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/thread_checker.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/net/wifi_access_point_info_provider.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/network_interfaces.h"
+#include "net/nqe/effective_connection_type.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace net {
+class NetworkQualityEstimator;
+}
+
+namespace metrics {
+
+SystemProfileProto::Network::EffectiveConnectionType
+ConvertEffectiveConnectionType(
+    net::EffectiveConnectionType effective_connection_type);
+
+// Registers as observer with net::NetworkChangeNotifier and keeps track of
+// the network environment.
+class NetworkMetricsProvider
+    : public MetricsProvider,
+      public net::NetworkChangeNotifier::NetworkChangeObserver {
+ public:
+  // Class that provides |this| with the network quality estimator.
+  class NetworkQualityEstimatorProvider {
+   public:
+    virtual ~NetworkQualityEstimatorProvider() {}
+
+    // Returns the network quality estimator by calling |io_callback|. The
+    // returned network quality estimator may be nullptr. |io_callback| must be
+    // called on the IO thread. |io_callback| can be destroyed on IO thread only
+    // after |this| is destroyed.
+    virtual void PostReplyNetworkQualityEstimator(
+        base::Callback<void(net::NetworkQualityEstimator*)> io_callback) = 0;
+
+    // Returns the task runner on which |this| should be used and destroyed.
+    virtual scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() = 0;
+
+   protected:
+    NetworkQualityEstimatorProvider() {}
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(NetworkQualityEstimatorProvider);
+  };
+
+  // Creates a NetworkMetricsProvider, where
+  // |network_quality_estimator_provider| should be set if it is useful to
+  // attach the quality of the network to the metrics report.
+  explicit NetworkMetricsProvider(
+      std::unique_ptr<NetworkQualityEstimatorProvider>
+          network_quality_estimator_provider = nullptr);
+  ~NetworkMetricsProvider() override;
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(NetworkMetricsProviderTest, EffectiveConnectionType);
+  FRIEND_TEST_ALL_PREFIXES(NetworkMetricsProviderTest,
+                           ECTAmbiguousOnConnectionTypeChange);
+  FRIEND_TEST_ALL_PREFIXES(NetworkMetricsProviderTest,
+                           ECTNotAmbiguousOnOffline);
+  FRIEND_TEST_ALL_PREFIXES(NetworkMetricsProviderTest,
+                           ConnectionTypeIsAmbiguous);
+
+  // Listens to the changes in the effective conection type.
+  class EffectiveConnectionTypeObserver;
+
+  // MetricsProvider:
+  void ProvideCurrentSessionData(
+      ChromeUserMetricsExtension* uma_proto) override;
+  void ProvideSystemProfileMetrics(SystemProfileProto* system_profile) override;
+
+  // NetworkChangeObserver:
+  void OnNetworkChanged(
+      net::NetworkChangeNotifier::ConnectionType type) override;
+
+  SystemProfileProto::Network::ConnectionType GetConnectionType() const;
+  SystemProfileProto::Network::WifiPHYLayerProtocol GetWifiPHYLayerProtocol()
+      const;
+
+  // Posts a call to net::GetWifiPHYLayerProtocol on the blocking pool.
+  void ProbeWifiPHYLayerProtocol();
+  // Callback from the blocking pool with the result of
+  // net::GetWifiPHYLayerProtocol.
+  void OnWifiPHYLayerProtocolResult(net::WifiPHYLayerProtocol mode);
+
+  // Writes info about the wireless access points that this system is
+  // connected to.
+  void WriteWifiAccessPointProto(
+      const WifiAccessPointInfoProvider::WifiAccessPointInfo& info,
+      SystemProfileProto::Network* network_proto);
+
+  // Logs metrics that are functions of other metrics being uploaded.
+  void LogAggregatedMetrics();
+
+  // Notifies |this| that the effective connection type of the current network
+  // has changed to |type|.
+  void OnEffectiveConnectionTypeChanged(net::EffectiveConnectionType type);
+
+  // True if |connection_type_| changed during the lifetime of the log.
+  bool connection_type_is_ambiguous_;
+  // The connection type according to net::NetworkChangeNotifier.
+  net::NetworkChangeNotifier::ConnectionType connection_type_;
+  // True if the network change notifier has been initialized.
+  bool network_change_notifier_initialized_;
+
+  // True if |wifi_phy_layer_protocol_| changed during the lifetime of the log.
+  bool wifi_phy_layer_protocol_is_ambiguous_;
+  // The PHY mode of the currently associated access point obtained via
+  // net::GetWifiPHYLayerProtocol.
+  net::WifiPHYLayerProtocol wifi_phy_layer_protocol_;
+
+  // Helper object for retrieving connected wifi access point information.
+  std::unique_ptr<WifiAccessPointInfoProvider> wifi_access_point_info_provider_;
+
+  // These metrics track histogram totals for the Net.ErrorCodesForMainFrame4
+  // histogram. They are used to compute deltas at upload time.
+  base::HistogramBase::Count total_aborts_;
+  base::HistogramBase::Count total_codes_;
+
+  // Provides the network quality estimator. May be null.
+  std::unique_ptr<NetworkQualityEstimatorProvider>
+      network_quality_estimator_provider_;
+
+  // Listens to the changes in the effective connection type. Initialized and
+  // destroyed on the IO thread. May be null.
+  std::unique_ptr<EffectiveConnectionTypeObserver>
+      effective_connection_type_observer_;
+
+  // Last known effective connection type.
+  net::EffectiveConnectionType effective_connection_type_;
+
+  // Minimum and maximum effective connection type since the metrics were last
+  // provided.
+  net::EffectiveConnectionType min_effective_connection_type_;
+  net::EffectiveConnectionType max_effective_connection_type_;
+
+  base::ThreadChecker thread_checker_;
+
+  base::WeakPtrFactory<NetworkMetricsProvider> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkMetricsProvider);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_NET_NETWORK_METRICS_PROVIDER_H_
diff --git a/components/metrics/net/network_metrics_provider_unittest.cc b/components/metrics/net/network_metrics_provider_unittest.cc
new file mode 100644
index 0000000..b5e6507
--- /dev/null
+++ b/components/metrics/net/network_metrics_provider_unittest.cc
@@ -0,0 +1,290 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/network_metrics_provider.h"
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "net/base/network_change_notifier.h"
+#include "net/nqe/network_quality_estimator_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+#if defined(OS_CHROMEOS)
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/network/network_handler.h"
+#endif  // OS_CHROMEOS
+
+namespace metrics {
+
+namespace {
+
+class TestNetworkQualityEstimatorProvider
+    : public NetworkMetricsProvider::NetworkQualityEstimatorProvider {
+ public:
+  explicit TestNetworkQualityEstimatorProvider(
+      net::TestNetworkQualityEstimator* estimator)
+      : estimator_(estimator) {}
+  ~TestNetworkQualityEstimatorProvider() override {}
+
+ private:
+  // NetworkMetricsProvider::NetworkQualityEstimatorProvider:
+  scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() override {
+    return base::ThreadTaskRunnerHandle::Get();
+  }
+
+  void PostReplyNetworkQualityEstimator(
+      base::Callback<void(net::NetworkQualityEstimator*)> callback) override {
+    callback.Run(estimator_);
+  }
+
+  net::TestNetworkQualityEstimator* estimator_;
+  DISALLOW_COPY_AND_ASSIGN(TestNetworkQualityEstimatorProvider);
+};
+
+}  // namespace
+
+class NetworkMetricsProviderTest : public testing::Test {
+ protected:
+  NetworkMetricsProviderTest()
+      : scoped_task_environment_(
+            base::test::ScopedTaskEnvironment::MainThreadType::IO) {
+#if defined(OS_CHROMEOS)
+    chromeos::DBusThreadManager::Initialize();
+    chromeos::NetworkHandler::Initialize();
+#endif  // OS_CHROMEOS
+  }
+
+ private:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+};
+
+// Verifies that the effective connection type is correctly set.
+TEST_F(NetworkMetricsProviderTest, EffectiveConnectionType) {
+  net::TestNetworkQualityEstimator estimator;
+  std::unique_ptr<NetworkMetricsProvider::NetworkQualityEstimatorProvider>
+      estimator_provider(base::WrapUnique(
+          new TestNetworkQualityEstimatorProvider(&estimator)));
+  SystemProfileProto system_profile;
+  NetworkMetricsProvider network_metrics_provider(
+      std::move(estimator_provider));
+
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+            network_metrics_provider.effective_connection_type_);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+            network_metrics_provider.min_effective_connection_type_);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+            network_metrics_provider.max_effective_connection_type_);
+  network_metrics_provider.ProvideSystemProfileMetrics(&system_profile);
+  EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+            system_profile.network().min_effective_connection_type());
+  EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+            system_profile.network().max_effective_connection_type());
+
+  // Set RTT so that the effective connection type is computed as 2G.
+  estimator.set_recent_http_rtt(base::TimeDelta::FromMilliseconds(1500));
+  estimator.SetStartTimeNullHttpRtt(base::TimeDelta::FromMilliseconds(1500));
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+            network_metrics_provider.effective_connection_type_);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+            network_metrics_provider.min_effective_connection_type_);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+            network_metrics_provider.max_effective_connection_type_);
+  // Running a request would cause the effective connection type to be computed
+  // as 2G, and observers to be notified.
+  estimator.RunOneRequest();
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G,
+            network_metrics_provider.effective_connection_type_);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G,
+            network_metrics_provider.min_effective_connection_type_);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G,
+            network_metrics_provider.max_effective_connection_type_);
+  network_metrics_provider.ProvideSystemProfileMetrics(&system_profile);
+  EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G,
+            system_profile.network().min_effective_connection_type());
+  EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G,
+            system_profile.network().max_effective_connection_type());
+
+  // Set RTT so that the effective connection type is computed as SLOW_2G.
+  estimator.set_recent_http_rtt(base::TimeDelta::FromMilliseconds(3000));
+  estimator.SetStartTimeNullHttpRtt(base::TimeDelta::FromMilliseconds(3000));
+  // Running a request would cause the effective connection type to be computed
+  // as SLOW_2G, and observers to be notified.
+  estimator.RunOneRequest();
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G,
+            network_metrics_provider.effective_connection_type_);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G,
+            network_metrics_provider.min_effective_connection_type_);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G,
+            network_metrics_provider.max_effective_connection_type_);
+  network_metrics_provider.ProvideSystemProfileMetrics(&system_profile);
+  // Effective connection type changed from 2G to SLOW_2G during the lifetime of
+  // the log. Minimum value of ECT must be different from the maximum value.
+  EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_SLOW_2G,
+            system_profile.network().min_effective_connection_type());
+  EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G,
+            system_profile.network().max_effective_connection_type());
+
+  // Getting the system profile again should return the current effective
+  // connection type.
+  network_metrics_provider.ProvideSystemProfileMetrics(&system_profile);
+  EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_SLOW_2G,
+            system_profile.network().min_effective_connection_type());
+  EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_SLOW_2G,
+            system_profile.network().max_effective_connection_type());
+}
+
+// Verifies that the effective connection type is not set to UNKNOWN when there
+// is a change in the connection type.
+TEST_F(NetworkMetricsProviderTest, ECTAmbiguousOnConnectionTypeChange) {
+  net::TestNetworkQualityEstimator estimator;
+  std::unique_ptr<NetworkMetricsProvider::NetworkQualityEstimatorProvider>
+      estimator_provider(base::WrapUnique(
+          new TestNetworkQualityEstimatorProvider(&estimator)));
+  SystemProfileProto system_profile;
+  NetworkMetricsProvider network_metrics_provider(
+      std::move(estimator_provider));
+
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+            network_metrics_provider.effective_connection_type_);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+            network_metrics_provider.min_effective_connection_type_);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+            network_metrics_provider.max_effective_connection_type_);
+
+  // Set RTT so that the effective connection type is computed as 2G.
+  estimator.set_recent_http_rtt(base::TimeDelta::FromMilliseconds(1500));
+  estimator.SetStartTimeNullHttpRtt(base::TimeDelta::FromMilliseconds(1500));
+  // Running a request would cause the effective connection type to be computed
+  // as 2G, and observers to be notified.
+  estimator.RunOneRequest();
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G,
+            network_metrics_provider.effective_connection_type_);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G,
+            network_metrics_provider.min_effective_connection_type_);
+  EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G,
+            network_metrics_provider.max_effective_connection_type_);
+
+  // There is no change in the connection type. Effective connection types
+  // should be reported as 2G.
+  network_metrics_provider.ProvideSystemProfileMetrics(&system_profile);
+  EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G,
+            system_profile.network().min_effective_connection_type());
+  EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G,
+            system_profile.network().max_effective_connection_type());
+
+  // Even with change in the connection type, effective connection types
+  // should be reported as 2G.
+  network_metrics_provider.OnNetworkChanged(
+      net::NetworkChangeNotifier::CONNECTION_2G);
+  network_metrics_provider.ProvideSystemProfileMetrics(&system_profile);
+  EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G,
+            system_profile.network().min_effective_connection_type());
+  EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G,
+            system_profile.network().max_effective_connection_type());
+}
+
+// Verifies that the effective connection type is not set to UNKNOWN when the
+// connection type is OFFLINE.
+TEST_F(NetworkMetricsProviderTest, ECTNotAmbiguousOnOffline) {
+  for (net::EffectiveConnectionType force_ect :
+       {net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN,
+        net::EFFECTIVE_CONNECTION_TYPE_OFFLINE}) {
+    std::unique_ptr<net::NetworkQualityEstimatorParams> params =
+        std::make_unique<net::NetworkQualityEstimatorParams>(
+            std::map<std::string, std::string>());
+    net::NetworkQualityEstimatorParams* params_ptr = params.get();
+    net::TestNetworkQualityEstimator estimator(std::move(params));
+
+    std::unique_ptr<NetworkMetricsProvider::NetworkQualityEstimatorProvider>
+        estimator_provider(base::WrapUnique(
+            new TestNetworkQualityEstimatorProvider(&estimator)));
+    SystemProfileProto system_profile;
+    NetworkMetricsProvider network_metrics_provider(
+        std::move(estimator_provider));
+
+    params_ptr->SetForcedEffectiveConnectionType(
+        net::EFFECTIVE_CONNECTION_TYPE_2G);
+    estimator.RunOneRequest();
+
+    params_ptr->SetForcedEffectiveConnectionType(force_ect);
+    estimator.RunOneRequest();
+    network_metrics_provider.ProvideSystemProfileMetrics(&system_profile);
+    EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G,
+              system_profile.network().min_effective_connection_type());
+    EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G,
+              system_profile.network().max_effective_connection_type());
+
+    params_ptr->SetForcedEffectiveConnectionType(
+        net::EFFECTIVE_CONNECTION_TYPE_4G);
+    estimator.RunOneRequest();
+
+    network_metrics_provider.ProvideSystemProfileMetrics(&system_profile);
+    EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_4G,
+              system_profile.network().min_effective_connection_type());
+    EXPECT_EQ(SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_4G,
+              system_profile.network().max_effective_connection_type());
+  }
+}
+
+// Verifies that the connection type is ambiguous boolean is correctly set.
+TEST_F(NetworkMetricsProviderTest, ConnectionTypeIsAmbiguous) {
+  net::TestNetworkQualityEstimator estimator;
+  std::unique_ptr<NetworkMetricsProvider::NetworkQualityEstimatorProvider>
+      estimator_provider(base::WrapUnique(
+          new TestNetworkQualityEstimatorProvider(&estimator)));
+  SystemProfileProto system_profile;
+  NetworkMetricsProvider network_metrics_provider(
+      std::move(estimator_provider));
+  estimator.RunOneRequest();
+
+  EXPECT_EQ(net::NetworkChangeNotifier::CONNECTION_UNKNOWN,
+            network_metrics_provider.connection_type_);
+  EXPECT_FALSE(network_metrics_provider.connection_type_is_ambiguous_);
+  EXPECT_FALSE(network_metrics_provider.network_change_notifier_initialized_);
+
+  // When a connection type change callback is received, network change notifier
+  // should be marked as initialized.
+  network_metrics_provider.OnNetworkChanged(
+      net::NetworkChangeNotifier::CONNECTION_2G);
+  EXPECT_EQ(net::NetworkChangeNotifier::CONNECTION_2G,
+            network_metrics_provider.connection_type_);
+  // Connection type should not be marked as ambiguous when a delayed connection
+  // type change callback is received due to delayed initialization of the
+  // network change notifier.
+  EXPECT_FALSE(network_metrics_provider.connection_type_is_ambiguous_);
+  EXPECT_TRUE(network_metrics_provider.network_change_notifier_initialized_);
+
+  // On collection of the system profile, |connection_type_is_ambiguous_| should
+  // stay false, and |network_change_notifier_initialized_| should remain true.
+  network_metrics_provider.ProvideSystemProfileMetrics(&system_profile);
+  EXPECT_FALSE(network_metrics_provider.connection_type_is_ambiguous_);
+  EXPECT_TRUE(network_metrics_provider.network_change_notifier_initialized_);
+  EXPECT_FALSE(system_profile.network().connection_type_is_ambiguous());
+  EXPECT_EQ(SystemProfileProto::Network::CONNECTION_2G,
+            system_profile.network().connection_type());
+
+  network_metrics_provider.OnNetworkChanged(
+      net::NetworkChangeNotifier::CONNECTION_3G);
+  EXPECT_TRUE(network_metrics_provider.connection_type_is_ambiguous_);
+  EXPECT_TRUE(network_metrics_provider.network_change_notifier_initialized_);
+
+  // On collection of the system profile, |connection_type_is_ambiguous_| should
+  // be reset to false, and |network_change_notifier_initialized_| should remain
+  // true.
+  network_metrics_provider.ProvideSystemProfileMetrics(&system_profile);
+  EXPECT_FALSE(network_metrics_provider.connection_type_is_ambiguous_);
+  EXPECT_TRUE(network_metrics_provider.network_change_notifier_initialized_);
+  EXPECT_TRUE(system_profile.network().connection_type_is_ambiguous());
+  EXPECT_EQ(SystemProfileProto::Network::CONNECTION_3G,
+            system_profile.network().connection_type());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/net/wifi_access_point_info_provider.cc b/components/metrics/net/wifi_access_point_info_provider.cc
new file mode 100644
index 0000000..21e04b1
--- /dev/null
+++ b/components/metrics/net/wifi_access_point_info_provider.cc
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/wifi_access_point_info_provider.h"
+
+namespace metrics {
+
+WifiAccessPointInfoProvider::WifiAccessPointInfo::WifiAccessPointInfo() {
+}
+
+WifiAccessPointInfoProvider::WifiAccessPointInfo::~WifiAccessPointInfo() {
+}
+
+WifiAccessPointInfoProvider::WifiAccessPointInfoProvider() {
+}
+
+WifiAccessPointInfoProvider::~WifiAccessPointInfoProvider() {
+}
+
+bool WifiAccessPointInfoProvider::GetInfo(WifiAccessPointInfo *info) {
+  return false;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/net/wifi_access_point_info_provider.h b/components/metrics/net/wifi_access_point_info_provider.h
new file mode 100644
index 0000000..3a761da
--- /dev/null
+++ b/components/metrics/net/wifi_access_point_info_provider.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_H_
+#define COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_H_
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace metrics {
+
+// Interface for accessing connected wireless access point information.
+class WifiAccessPointInfoProvider {
+ public:
+  // Wifi access point security mode definitions.
+  enum WifiSecurityMode {
+    WIFI_SECURITY_UNKNOWN = 0,
+    WIFI_SECURITY_WPA = 1,
+    WIFI_SECURITY_WEP = 2,
+    WIFI_SECURITY_RSN = 3,
+    WIFI_SECURITY_802_1X = 4,
+    WIFI_SECURITY_PSK = 5,
+    WIFI_SECURITY_NONE
+  };
+
+  // Information of the currently connected wifi access point.
+  struct WifiAccessPointInfo {
+    WifiAccessPointInfo();
+    ~WifiAccessPointInfo();
+    WifiSecurityMode security;
+    std::string bssid;
+    std::string model_number;
+    std::string model_name;
+    std::string device_name;
+    std::string oui_list;
+  };
+
+  WifiAccessPointInfoProvider();
+  virtual ~WifiAccessPointInfoProvider();
+
+  // Fill in the wifi access point info if device is currently connected to a
+  // wifi access point. Return true if device is connected to a wifi access
+  // point, false otherwise.
+  virtual bool GetInfo(WifiAccessPointInfo *info);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(WifiAccessPointInfoProvider);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_H_
diff --git a/components/metrics/net/wifi_access_point_info_provider_chromeos.cc b/components/metrics/net/wifi_access_point_info_provider_chromeos.cc
new file mode 100644
index 0000000..3654e93
--- /dev/null
+++ b/components/metrics/net/wifi_access_point_info_provider_chromeos.cc
@@ -0,0 +1,123 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/net/wifi_access_point_info_provider_chromeos.h"
+
+#include <stdint.h>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/strings/string_number_conversions.h"
+#include "chromeos/network/network_configuration_handler.h"
+#include "chromeos/network/network_handler.h"
+#include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
+#include "chromeos/network/shill_property_util.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+using chromeos::NetworkHandler;
+
+namespace metrics {
+
+WifiAccessPointInfoProviderChromeos::WifiAccessPointInfoProviderChromeos() {
+  NetworkHandler::Get()->network_state_handler()->AddObserver(this, FROM_HERE);
+
+  // Update initial connection state.
+  DefaultNetworkChanged(
+      NetworkHandler::Get()->network_state_handler()->DefaultNetwork());
+}
+
+WifiAccessPointInfoProviderChromeos::~WifiAccessPointInfoProviderChromeos() {
+  NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
+                                                                 FROM_HERE);
+}
+
+bool WifiAccessPointInfoProviderChromeos::GetInfo(WifiAccessPointInfo* info) {
+  // Wifi access point information is not provided if the BSSID is empty.
+  // This assumes the BSSID is never empty when access point information exists.
+  if (wifi_access_point_info_.bssid.empty())
+    return false;
+
+  *info = wifi_access_point_info_;
+  return true;
+}
+
+void WifiAccessPointInfoProviderChromeos::DefaultNetworkChanged(
+    const chromeos::NetworkState* default_network) {
+  // Reset access point info to prevent reporting of out-dated data.
+  wifi_access_point_info_ = WifiAccessPointInfo();
+
+  // Skip non-wifi connections
+  if (!default_network || default_network->type() != shill::kTypeWifi)
+    return;
+
+  // Retrieve access point info for wifi connection.
+  NetworkHandler::Get()->network_configuration_handler()->GetShillProperties(
+      default_network->path(),
+      base::Bind(&WifiAccessPointInfoProviderChromeos::ParseInfo, AsWeakPtr()),
+      chromeos::network_handler::ErrorCallback());
+}
+
+void WifiAccessPointInfoProviderChromeos::ParseInfo(
+    const std::string &service_path,
+    const base::DictionaryValue& properties) {
+  // Skip services that contain "_nomap" in the SSID.
+  std::string ssid = chromeos::shill_property_util::GetSSIDFromProperties(
+      properties, false /* verbose_logging */, nullptr);
+  if (ssid.find("_nomap", 0) != std::string::npos)
+    return;
+
+  std::string bssid;
+  if (!properties.GetStringWithoutPathExpansion(shill::kWifiBSsid, &bssid) ||
+      bssid.empty())
+    return;
+
+  // Filter out BSSID with local bit set in the first byte.
+  uint32_t first_octet;
+  if (!base::HexStringToUInt(bssid.substr(0, 2), &first_octet))
+    NOTREACHED();
+  if (first_octet & 0x2)
+    return;
+  wifi_access_point_info_.bssid = bssid;
+
+  // Parse security info.
+  std::string security;
+  properties.GetStringWithoutPathExpansion(
+      shill::kSecurityProperty, &security);
+  wifi_access_point_info_.security = WIFI_SECURITY_UNKNOWN;
+  if (security == shill::kSecurityWpa)
+    wifi_access_point_info_.security = WIFI_SECURITY_WPA;
+  else if (security == shill::kSecurityWep)
+    wifi_access_point_info_.security = WIFI_SECURITY_WEP;
+  else if (security == shill::kSecurityRsn)
+    wifi_access_point_info_.security = WIFI_SECURITY_RSN;
+  else if (security == shill::kSecurity8021x)
+    wifi_access_point_info_.security = WIFI_SECURITY_802_1X;
+  else if (security == shill::kSecurityPsk)
+    wifi_access_point_info_.security = WIFI_SECURITY_PSK;
+  else if (security == shill::kSecurityNone)
+    wifi_access_point_info_.security = WIFI_SECURITY_NONE;
+
+  properties.GetStringWithoutPathExpansion(
+      shill::kWifiBSsid, &wifi_access_point_info_.bssid);
+  const base::DictionaryValue* vendor_dict = NULL;
+  if (!properties.GetDictionaryWithoutPathExpansion(
+          shill::kWifiVendorInformationProperty,
+          &vendor_dict))
+    return;
+
+  vendor_dict->GetStringWithoutPathExpansion(
+      shill::kVendorWPSModelNumberProperty,
+      &wifi_access_point_info_.model_number);
+  vendor_dict->GetStringWithoutPathExpansion(
+      shill::kVendorWPSModelNameProperty,
+      &wifi_access_point_info_.model_name);
+  vendor_dict->GetStringWithoutPathExpansion(
+      shill::kVendorWPSDeviceNameProperty,
+      &wifi_access_point_info_.device_name);
+  vendor_dict->GetStringWithoutPathExpansion(shill::kVendorOUIListProperty,
+                                             &wifi_access_point_info_.oui_list);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/net/wifi_access_point_info_provider_chromeos.h b/components/metrics/net/wifi_access_point_info_provider_chromeos.h
new file mode 100644
index 0000000..d310239
--- /dev/null
+++ b/components/metrics/net/wifi_access_point_info_provider_chromeos.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_CHROMEOS_H_
+#define COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_CHROMEOS_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "chromeos/network/network_state_handler_observer.h"
+#include "components/metrics/net/wifi_access_point_info_provider.h"
+
+namespace metrics {
+
+// WifiAccessPointInfoProviderChromeos provides the connected wifi
+// acccess point information for chromeos.
+class WifiAccessPointInfoProviderChromeos
+    : public WifiAccessPointInfoProvider,
+      public chromeos::NetworkStateHandlerObserver,
+      public base::SupportsWeakPtr<WifiAccessPointInfoProviderChromeos> {
+ public:
+  WifiAccessPointInfoProviderChromeos();
+  ~WifiAccessPointInfoProviderChromeos() override;
+
+  // WifiAccessPointInfoProvider:
+  bool GetInfo(WifiAccessPointInfo* info) override;
+
+  // NetworkStateHandlerObserver:
+  void DefaultNetworkChanged(
+      const chromeos::NetworkState* default_network) override;
+
+ private:
+  // Callback from Shill.Service.GetProperties. Parses |properties| to obtain
+  // the wifi access point information.
+  void ParseInfo(const std::string& service_path,
+                 const base::DictionaryValue& properties);
+
+  WifiAccessPointInfo wifi_access_point_info_;
+
+  DISALLOW_COPY_AND_ASSIGN(WifiAccessPointInfoProviderChromeos);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_NET_WIFI_ACCESS_POINT_INFO_PROVIDER_CHROMEOS_H_
diff --git a/components/metrics/persisted_logs.cc b/components/metrics/persisted_logs.cc
new file mode 100644
index 0000000..74d1ca7
--- /dev/null
+++ b/components/metrics/persisted_logs.cc
@@ -0,0 +1,227 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/persisted_logs.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/base64.h"
+#include "base/md5.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/timer/elapsed_timer.h"
+#include "components/metrics/persisted_logs_metrics.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace metrics {
+
+namespace {
+
+const char kLogHashKey[] = "hash";
+const char kLogTimestampKey[] = "timestamp";
+const char kLogDataKey[] = "data";
+
+std::string EncodeToBase64(const std::string& to_convert) {
+  // CHECK to diagnose crbug.com/695433
+  CHECK(to_convert.data());
+  std::string base64_result;
+  base::Base64Encode(to_convert, &base64_result);
+  return base64_result;
+}
+
+std::string DecodeFromBase64(const std::string& to_convert) {
+  std::string result;
+  base::Base64Decode(to_convert, &result);
+  return result;
+}
+
+}  // namespace
+
+void PersistedLogs::LogInfo::Init(PersistedLogsMetrics* metrics,
+                                  const std::string& log_data,
+                                  const std::string& log_timestamp) {
+  DCHECK(!log_data.empty());
+
+  if (!compression::GzipCompress(log_data, &compressed_log_data)) {
+    NOTREACHED();
+    return;
+  }
+
+  metrics->RecordCompressionRatio(compressed_log_data.size(), log_data.size());
+
+  hash = base::SHA1HashString(log_data);
+  timestamp = log_timestamp;
+}
+
+PersistedLogs::PersistedLogs(std::unique_ptr<PersistedLogsMetrics> metrics,
+                             PrefService* local_state,
+                             const char* pref_name,
+                             size_t min_log_count,
+                             size_t min_log_bytes,
+                             size_t max_log_size)
+    : metrics_(std::move(metrics)),
+      local_state_(local_state),
+      pref_name_(pref_name),
+      min_log_count_(min_log_count),
+      min_log_bytes_(min_log_bytes),
+      max_log_size_(max_log_size != 0 ? max_log_size : static_cast<size_t>(-1)),
+      staged_log_index_(-1) {
+  DCHECK(local_state_);
+  // One of the limit arguments must be non-zero.
+  DCHECK(min_log_count_ > 0 || min_log_bytes_ > 0);
+}
+
+PersistedLogs::~PersistedLogs() {}
+
+bool PersistedLogs::has_unsent_logs() const {
+  return !!size();
+}
+
+// True if a log has been staged.
+bool PersistedLogs::has_staged_log() const {
+  return staged_log_index_ != -1;
+}
+
+// Returns the element in the front of the list.
+const std::string& PersistedLogs::staged_log() const {
+  DCHECK(has_staged_log());
+  return list_[staged_log_index_].compressed_log_data;
+}
+
+// Returns the element in the front of the list.
+const std::string& PersistedLogs::staged_log_hash() const {
+  DCHECK(has_staged_log());
+  return list_[staged_log_index_].hash;
+}
+
+// Returns the timestamp of the element in the front of the list.
+const std::string& PersistedLogs::staged_log_timestamp() const {
+  DCHECK(has_staged_log());
+  return list_[staged_log_index_].timestamp;
+}
+
+void PersistedLogs::StageNextLog() {
+  // CHECK, rather than DCHECK, because swap()ing with an empty list causes
+  // hard-to-identify crashes much later.
+  CHECK(!list_.empty());
+  DCHECK(!has_staged_log());
+  staged_log_index_ = list_.size() - 1;
+  DCHECK(has_staged_log());
+}
+
+void PersistedLogs::DiscardStagedLog() {
+  // CHECK, rather than DCHECK, to diagnose cause of crashes from the field,
+  // for crbug.com/695433.
+  CHECK(has_staged_log());
+  DCHECK_LT(static_cast<size_t>(staged_log_index_), list_.size());
+  list_.erase(list_.begin() + staged_log_index_);
+  staged_log_index_ = -1;
+}
+
+void PersistedLogs::PersistUnsentLogs() const {
+  ListPrefUpdate update(local_state_, pref_name_);
+  WriteLogsToPrefList(update.Get());
+}
+
+void PersistedLogs::LoadPersistedUnsentLogs() {
+  ReadLogsFromPrefList(*local_state_->GetList(pref_name_));
+}
+
+void PersistedLogs::StoreLog(const std::string& log_data) {
+  list_.push_back(LogInfo());
+  list_.back().Init(metrics_.get(), log_data,
+                    base::Int64ToString(base::Time::Now().ToTimeT()));
+}
+
+void PersistedLogs::Purge() {
+  if (has_staged_log()) {
+    DiscardStagedLog();
+  }
+  list_.clear();
+  local_state_->ClearPref(pref_name_);
+}
+
+void PersistedLogs::ReadLogsFromPrefList(const base::ListValue& list_value) {
+  if (list_value.empty()) {
+    metrics_->RecordLogReadStatus(PersistedLogsMetrics::LIST_EMPTY);
+    return;
+  }
+
+  const size_t log_count = list_value.GetSize();
+
+  DCHECK(list_.empty());
+  list_.resize(log_count);
+
+  for (size_t i = 0; i < log_count; ++i) {
+    const base::DictionaryValue* dict;
+    if (!list_value.GetDictionary(i, &dict) ||
+        !dict->GetString(kLogDataKey, &list_[i].compressed_log_data) ||
+        !dict->GetString(kLogHashKey, &list_[i].hash)) {
+      list_.clear();
+      metrics_->RecordLogReadStatus(
+          PersistedLogsMetrics::LOG_STRING_CORRUPTION);
+      return;
+    }
+
+    list_[i].compressed_log_data =
+        DecodeFromBase64(list_[i].compressed_log_data);
+    list_[i].hash = DecodeFromBase64(list_[i].hash);
+    // Ignoring the success of this step as timestamp might not be there for
+    // older logs.
+    // NOTE: Should be added to the check with other fields once migration is
+    // over.
+    dict->GetString(kLogTimestampKey, &list_[i].timestamp);
+  }
+
+  metrics_->RecordLogReadStatus(PersistedLogsMetrics::RECALL_SUCCESS);
+}
+
+void PersistedLogs::WriteLogsToPrefList(base::ListValue* list_value) const {
+  list_value->Clear();
+
+  // Keep the most recent logs which are smaller than |max_log_size_|.
+  // We keep at least |min_log_bytes_| and |min_log_count_| of logs before
+  // discarding older logs.
+  size_t start = list_.size();
+  size_t saved_log_count = 0;
+  size_t bytes_used = 0;
+  for (; start > 0; --start) {
+    size_t log_size = list_[start - 1].compressed_log_data.length();
+    if (bytes_used >= min_log_bytes_ &&
+        saved_log_count >= min_log_count_) {
+      break;
+    }
+    // Oversized logs won't be persisted, so don't count them.
+    if (log_size > max_log_size_)
+      continue;
+    bytes_used += log_size;
+    ++saved_log_count;
+  }
+  int dropped_logs_num = start - 1;
+
+  for (size_t i = start; i < list_.size(); ++i) {
+    size_t log_size = list_[i].compressed_log_data.length();
+    if (log_size > max_log_size_) {
+      metrics_->RecordDroppedLogSize(log_size);
+      dropped_logs_num++;
+      continue;
+    }
+    std::unique_ptr<base::DictionaryValue> dict_value(
+        new base::DictionaryValue);
+    dict_value->SetString(kLogHashKey, EncodeToBase64(list_[i].hash));
+    dict_value->SetString(kLogDataKey,
+                          EncodeToBase64(list_[i].compressed_log_data));
+    dict_value->SetString(kLogTimestampKey, list_[i].timestamp);
+    list_value->Append(std::move(dict_value));
+  }
+  if (dropped_logs_num > 0)
+    metrics_->RecordDroppedLogsNum(dropped_logs_num);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/persisted_logs.h b/components/metrics/persisted_logs.h
new file mode 100644
index 0000000..f95b820
--- /dev/null
+++ b/components/metrics/persisted_logs.h
@@ -0,0 +1,125 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_PERSISTED_LOGS_H_
+#define COMPONENTS_METRICS_PERSISTED_LOGS_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/values.h"
+#include "components/metrics/log_store.h"
+
+class PrefService;
+
+namespace metrics {
+
+class PersistedLogsMetrics;
+
+// Maintains a list of unsent logs that are written and restored from disk.
+class PersistedLogs : public LogStore {
+ public:
+  // Constructs a PersistedLogs that stores data in |local_state| under the
+  // preference |pref_name|.
+  // Calling code is responsible for ensuring that the lifetime of |local_state|
+  // is longer than the lifetime of PersistedLogs.
+  //
+  // When saving logs to disk, stores either the first |min_log_count| logs, or
+  // at least |min_log_bytes| bytes of logs, whichever is greater.
+  //
+  // If the optional |max_log_size| parameter is non-zero, all logs larger than
+  // that limit will be skipped when writing to disk.
+  PersistedLogs(std::unique_ptr<PersistedLogsMetrics> metrics,
+                PrefService* local_state,
+                const char* pref_name,
+                size_t min_log_count,
+                size_t min_log_bytes,
+                size_t max_log_size);
+  ~PersistedLogs();
+
+  // LogStore:
+  bool has_unsent_logs() const override;
+  bool has_staged_log() const override;
+  const std::string& staged_log() const override;
+  const std::string& staged_log_hash() const override;
+  void StageNextLog() override;
+  void DiscardStagedLog() override;
+  void PersistUnsentLogs() const override;
+  void LoadPersistedUnsentLogs() override;
+
+  // Adds a log to the list.
+  void StoreLog(const std::string& log_data);
+
+  // Delete all logs, in memory and on disk.
+  void Purge();
+
+  // Returns the timestamp of the element in the front of the list.
+  const std::string& staged_log_timestamp() const;
+
+  // The number of elements currently stored.
+  size_t size() const { return list_.size(); }
+
+ private:
+  // Writes the list to the ListValue.
+  void WriteLogsToPrefList(base::ListValue* list) const;
+
+  // Reads the list from the ListValue.
+  void ReadLogsFromPrefList(const base::ListValue& list);
+
+  // An object for recording UMA metrics.
+  std::unique_ptr<PersistedLogsMetrics> metrics_;
+
+  // A weak pointer to the PrefService object to read and write the preference
+  // from.  Calling code should ensure this object continues to exist for the
+  // lifetime of the PersistedLogs object.
+  PrefService* local_state_;
+
+  // The name of the preference to serialize logs to/from.
+  const char* pref_name_;
+
+  // We will keep at least this |min_log_count_| logs or |min_log_bytes_| bytes
+  // of logs, whichever is greater, when writing to disk.  These apply after
+  // skipping logs greater than |max_log_size_|.
+  const size_t min_log_count_;
+  const size_t min_log_bytes_;
+
+  // Logs greater than this size will not be written to disk.
+  const size_t max_log_size_;
+
+  struct LogInfo {
+    // Initializes the members based on uncompressed |log_data| and
+    // |log_timestamp|.
+    // |metrics| is the parent's metrics_ object, and should not be held.
+    void Init(PersistedLogsMetrics* metrics,
+              const std::string& log_data,
+              const std::string& log_timestamp);
+
+    // Compressed log data - a serialized protobuf that's been gzipped.
+    std::string compressed_log_data;
+
+    // The SHA1 hash of log, stored to catch errors from memory corruption.
+    std::string hash;
+
+    // The timestamp of when the log was created as a time_t value.
+    std::string timestamp;
+  };
+  // A list of all of the stored logs, stored with SHA1 hashes to check for
+  // corruption while they are stored in memory.
+  std::vector<LogInfo> list_;
+
+  // The index and type of the log staged for upload. If nothing has been
+  // staged, the index will be -1.
+  int staged_log_index_;
+
+  DISALLOW_COPY_AND_ASSIGN(PersistedLogs);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_PERSISTED_LOGS_H_
diff --git a/components/metrics/persisted_logs_metrics.h b/components/metrics/persisted_logs_metrics.h
new file mode 100644
index 0000000..e3c58dd
--- /dev/null
+++ b/components/metrics/persisted_logs_metrics.h
@@ -0,0 +1,52 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_PERSISTED_LOGS_METRICS_H_
+#define COMPONENTS_METRICS_PERSISTED_LOGS_METRICS_H_
+
+#include "base/macros.h"
+#include "components/metrics/persisted_logs.h"
+
+namespace metrics {
+
+// Interface for recording metrics from PersistedLogs.
+class PersistedLogsMetrics {
+ public:
+  // Used to produce a histogram that keeps track of the status of recalling
+  // persisted per logs.
+  enum LogReadStatus {
+    RECALL_SUCCESS,         // We were able to correctly recall a persisted log.
+    LIST_EMPTY,             // Attempting to recall from an empty list.
+    LIST_SIZE_MISSING,      // Failed to recover list size using GetAsInteger().
+    LIST_SIZE_TOO_SMALL,    // Too few elements in the list (less than 3).
+    LIST_SIZE_CORRUPTION,   // List size is not as expected.
+    LOG_STRING_CORRUPTION,  // Failed to recover log string using GetAsString().
+    CHECKSUM_CORRUPTION,    // Failed to verify checksum.
+    CHECKSUM_STRING_CORRUPTION,     // Failed to recover checksum string using
+                                    // GetAsString().
+    DECODE_FAIL,                    // Failed to decode log.
+    DEPRECATED_XML_PROTO_MISMATCH,  // The XML and protobuf logs have
+                                    // inconsistent data.
+    END_RECALL_STATUS  // Number of bins to use to create the histogram.
+  };
+
+  PersistedLogsMetrics() {}
+  virtual ~PersistedLogsMetrics() {}
+
+  virtual void RecordLogReadStatus(LogReadStatus status){};
+
+  virtual void RecordCompressionRatio(
+    size_t compressed_size, size_t original_size) {}
+
+  virtual void RecordDroppedLogSize(size_t size) {}
+
+  virtual void RecordDroppedLogsNum(int dropped_logs_num) {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PersistedLogsMetrics);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_PERSISTED_LOGS_METRICS_H_
diff --git a/components/metrics/persisted_logs_metrics_impl.cc b/components/metrics/persisted_logs_metrics_impl.cc
new file mode 100644
index 0000000..7142ba5
--- /dev/null
+++ b/components/metrics/persisted_logs_metrics_impl.cc
@@ -0,0 +1,33 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/persisted_logs_metrics_impl.h"
+
+#include "base/metrics/histogram_macros.h"
+
+namespace metrics {
+
+void PersistedLogsMetricsImpl::RecordLogReadStatus(
+    PersistedLogsMetrics::LogReadStatus status) {
+  UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs", status,
+                            PersistedLogsMetrics::END_RECALL_STATUS);
+}
+
+void PersistedLogsMetricsImpl::RecordCompressionRatio(
+    size_t compressed_size, size_t original_size) {
+  UMA_HISTOGRAM_PERCENTAGE(
+      "UMA.ProtoCompressionRatio",
+      static_cast<int>(100 * compressed_size / original_size));
+}
+
+void PersistedLogsMetricsImpl::RecordDroppedLogSize(size_t size) {
+  UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted",
+                       static_cast<int>(size));
+}
+
+void PersistedLogsMetricsImpl::RecordDroppedLogsNum(int dropped_logs_num) {
+  UMA_HISTOGRAM_COUNTS("UMA.UnsentLogs.Dropped", dropped_logs_num);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/persisted_logs_metrics_impl.h b/components/metrics/persisted_logs_metrics_impl.h
new file mode 100644
index 0000000..544acf9
--- /dev/null
+++ b/components/metrics/persisted_logs_metrics_impl.h
@@ -0,0 +1,32 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_PERSISTED_LOGS_METRICS_IMPL_H_
+#define COMPONENTS_METRICS_PERSISTED_LOGS_METRICS_IMPL_H_
+
+#include "base/macros.h"
+#include "components/metrics/persisted_logs_metrics.h"
+
+namespace metrics {
+
+// Implementation for recording metrics from PersistedLogs.
+class PersistedLogsMetricsImpl : public PersistedLogsMetrics {
+ public:
+  PersistedLogsMetricsImpl() {}
+  ~PersistedLogsMetricsImpl() override {}
+
+  // PersistedLogsMetrics:
+  void RecordLogReadStatus(PersistedLogsMetrics::LogReadStatus status) override;
+  void RecordCompressionRatio(
+    size_t compressed_size, size_t original_size) override;
+  void RecordDroppedLogSize(size_t size) override;
+  void RecordDroppedLogsNum(int dropped_logs_num) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PersistedLogsMetricsImpl);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_PERSISTED_LOGS_METRICS_IMPL_H_
diff --git a/components/metrics/persisted_logs_unittest.cc b/components/metrics/persisted_logs_unittest.cc
new file mode 100644
index 0000000..1a6c931
--- /dev/null
+++ b/components/metrics/persisted_logs_unittest.cc
@@ -0,0 +1,290 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/persisted_logs.h"
+
+#include <stddef.h>
+
+#include "base/base64.h"
+#include "base/macros.h"
+#include "base/rand_util.h"
+#include "base/sha1.h"
+#include "base/values.h"
+#include "components/metrics/persisted_logs_metrics_impl.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace metrics {
+
+namespace {
+
+const char kTestPrefName[] = "TestPref";
+const size_t kLogCountLimit = 3;
+const size_t kLogByteLimit = 1000;
+
+// Compresses |log_data| and returns the result.
+std::string Compress(const std::string& log_data) {
+  std::string compressed_log_data;
+  EXPECT_TRUE(compression::GzipCompress(log_data, &compressed_log_data));
+  return compressed_log_data;
+}
+
+// Generates and returns log data such that its size after compression is at
+// least |min_compressed_size|.
+std::string GenerateLogWithMinCompressedSize(size_t min_compressed_size) {
+  // Since the size check is done against a compressed log, generate enough
+  // data that compresses to larger than |log_size|.
+  std::string rand_bytes = base::RandBytesAsString(min_compressed_size);
+  while (Compress(rand_bytes).size() < min_compressed_size)
+    rand_bytes.append(base::RandBytesAsString(min_compressed_size));
+  std::string base64_data_for_logging;
+  base::Base64Encode(rand_bytes, &base64_data_for_logging);
+  SCOPED_TRACE(testing::Message() << "Using random data "
+                                  << base64_data_for_logging);
+  return rand_bytes;
+}
+
+class PersistedLogsTest : public testing::Test {
+ public:
+  PersistedLogsTest() {
+    prefs_.registry()->RegisterListPref(kTestPrefName);
+  }
+
+ protected:
+  TestingPrefServiceSimple prefs_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PersistedLogsTest);
+};
+
+class TestPersistedLogs : public PersistedLogs {
+ public:
+  TestPersistedLogs(PrefService* service, size_t min_log_bytes)
+      : PersistedLogs(std::unique_ptr<PersistedLogsMetricsImpl>(
+                             new PersistedLogsMetricsImpl()),
+                      service,
+                      kTestPrefName,
+                      kLogCountLimit,
+                      min_log_bytes,
+                      0) {}
+
+  // Stages and removes the next log, while testing it's value.
+  void ExpectNextLog(const std::string& expected_log) {
+    StageNextLog();
+    EXPECT_EQ(staged_log(), Compress(expected_log));
+    DiscardStagedLog();
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestPersistedLogs);
+};
+
+}  // namespace
+
+// Store and retrieve empty list_value.
+TEST_F(PersistedLogsTest, EmptyLogList) {
+  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+  persisted_logs.PersistUnsentLogs();
+  const base::ListValue* list_value = prefs_.GetList(kTestPrefName);
+  EXPECT_EQ(0U, list_value->GetSize());
+
+  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+  result_persisted_logs.LoadPersistedUnsentLogs();
+  EXPECT_EQ(0U, result_persisted_logs.size());
+}
+
+// Store and retrieve a single log value.
+TEST_F(PersistedLogsTest, SingleElementLogList) {
+  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+  persisted_logs.StoreLog("Hello world!");
+  persisted_logs.PersistUnsentLogs();
+
+  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+  result_persisted_logs.LoadPersistedUnsentLogs();
+  EXPECT_EQ(1U, result_persisted_logs.size());
+
+  // Verify that the result log matches the initial log.
+  persisted_logs.StageNextLog();
+  result_persisted_logs.StageNextLog();
+  EXPECT_EQ(persisted_logs.staged_log(), result_persisted_logs.staged_log());
+  EXPECT_EQ(persisted_logs.staged_log_hash(),
+            result_persisted_logs.staged_log_hash());
+  EXPECT_EQ(persisted_logs.staged_log_timestamp(),
+            result_persisted_logs.staged_log_timestamp());
+}
+
+// Store a set of logs over the length limit, but smaller than the min number of
+// bytes.
+TEST_F(PersistedLogsTest, LongButTinyLogList) {
+  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+  size_t log_count = kLogCountLimit * 5;
+  for (size_t i = 0; i < log_count; ++i)
+    persisted_logs.StoreLog("x");
+
+  persisted_logs.PersistUnsentLogs();
+
+  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+  result_persisted_logs.LoadPersistedUnsentLogs();
+  EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size());
+
+  result_persisted_logs.ExpectNextLog("x");
+}
+
+// Store a set of logs over the length limit, but that doesn't reach the minimum
+// number of bytes until after passing the length limit.
+TEST_F(PersistedLogsTest, LongButSmallLogList) {
+  size_t log_count = kLogCountLimit * 5;
+  size_t log_size = 50;
+
+  std::string first_kept = "First to keep";
+  first_kept.resize(log_size, ' ');
+
+  std::string blank_log = std::string(log_size, ' ');
+
+  std::string last_kept = "Last to keep";
+  last_kept.resize(log_size, ' ');
+
+  // Set the byte limit enough to keep everything but the first two logs.
+  const size_t min_log_bytes =
+      Compress(first_kept).length() + Compress(last_kept).length() +
+      (log_count - 4) * Compress(blank_log).length();
+  TestPersistedLogs persisted_logs(&prefs_, min_log_bytes);
+
+  persisted_logs.StoreLog("one");
+  persisted_logs.StoreLog("two");
+  persisted_logs.StoreLog(first_kept);
+  for (size_t i = persisted_logs.size(); i < log_count - 1; ++i) {
+    persisted_logs.StoreLog(blank_log);
+  }
+  persisted_logs.StoreLog(last_kept);
+  persisted_logs.PersistUnsentLogs();
+
+  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+  result_persisted_logs.LoadPersistedUnsentLogs();
+  EXPECT_EQ(persisted_logs.size() - 2, result_persisted_logs.size());
+
+  result_persisted_logs.ExpectNextLog(last_kept);
+  while (result_persisted_logs.size() > 1) {
+    result_persisted_logs.ExpectNextLog(blank_log);
+  }
+  result_persisted_logs.ExpectNextLog(first_kept);
+}
+
+// Store a set of logs within the length limit, but well over the minimum
+// number of bytes.
+TEST_F(PersistedLogsTest, ShortButLargeLogList) {
+  // Make the total byte count about twice the minimum.
+  size_t log_count = kLogCountLimit;
+  size_t log_size = (kLogByteLimit / log_count) * 2;
+  std::string log_data = GenerateLogWithMinCompressedSize(log_size);
+
+  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+  for (size_t i = 0; i < log_count; ++i) {
+    persisted_logs.StoreLog(log_data);
+  }
+  persisted_logs.PersistUnsentLogs();
+
+  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+  result_persisted_logs.LoadPersistedUnsentLogs();
+  EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size());
+}
+
+// Store a set of logs over the length limit, and over the minimum number of
+// bytes.
+TEST_F(PersistedLogsTest, LongAndLargeLogList) {
+  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+  // Include twice the max number of logs.
+  size_t log_count = kLogCountLimit * 2;
+  // Make the total byte count about four times the minimum.
+  size_t log_size = (kLogByteLimit / log_count) * 4;
+
+  std::string target_log = "First to keep";
+  target_log += GenerateLogWithMinCompressedSize(log_size);
+
+  std::string log_data = GenerateLogWithMinCompressedSize(log_size);
+  for (size_t i = 0; i < log_count; ++i) {
+    if (i == log_count - kLogCountLimit)
+      persisted_logs.StoreLog(target_log);
+    else
+      persisted_logs.StoreLog(log_data);
+  }
+
+  persisted_logs.PersistUnsentLogs();
+
+  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+  result_persisted_logs.LoadPersistedUnsentLogs();
+  EXPECT_EQ(kLogCountLimit, result_persisted_logs.size());
+
+  while (result_persisted_logs.size() > 1) {
+    result_persisted_logs.ExpectNextLog(log_data);
+  }
+  result_persisted_logs.ExpectNextLog(target_log);
+}
+
+// Check that the store/stage/discard functions work as expected.
+TEST_F(PersistedLogsTest, Staging) {
+  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+  std::string tmp;
+
+  EXPECT_FALSE(persisted_logs.has_staged_log());
+  persisted_logs.StoreLog("one");
+  EXPECT_FALSE(persisted_logs.has_staged_log());
+  persisted_logs.StoreLog("two");
+  persisted_logs.StageNextLog();
+  EXPECT_TRUE(persisted_logs.has_staged_log());
+  EXPECT_EQ(persisted_logs.staged_log(), Compress("two"));
+  persisted_logs.StoreLog("three");
+  EXPECT_EQ(persisted_logs.staged_log(), Compress("two"));
+  EXPECT_EQ(persisted_logs.size(), 3U);
+  persisted_logs.DiscardStagedLog();
+  EXPECT_FALSE(persisted_logs.has_staged_log());
+  EXPECT_EQ(persisted_logs.size(), 2U);
+  persisted_logs.StageNextLog();
+  EXPECT_EQ(persisted_logs.staged_log(), Compress("three"));
+  persisted_logs.DiscardStagedLog();
+  persisted_logs.StageNextLog();
+  EXPECT_EQ(persisted_logs.staged_log(), Compress("one"));
+  persisted_logs.DiscardStagedLog();
+  EXPECT_FALSE(persisted_logs.has_staged_log());
+  EXPECT_EQ(persisted_logs.size(), 0U);
+}
+
+TEST_F(PersistedLogsTest, DiscardOrder) {
+  // Ensure that the correct log is discarded if new logs are pushed while
+  // a log is staged.
+  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+
+  persisted_logs.StoreLog("one");
+  persisted_logs.StageNextLog();
+  persisted_logs.StoreLog("two");
+  persisted_logs.DiscardStagedLog();
+  persisted_logs.PersistUnsentLogs();
+
+  TestPersistedLogs result_persisted_logs(&prefs_, kLogByteLimit);
+  result_persisted_logs.LoadPersistedUnsentLogs();
+  EXPECT_EQ(1U, result_persisted_logs.size());
+  result_persisted_logs.ExpectNextLog("two");
+}
+
+
+TEST_F(PersistedLogsTest, Hashes) {
+  const char kFooText[] = "foo";
+  const std::string foo_hash = base::SHA1HashString(kFooText);
+
+  TestPersistedLogs persisted_logs(&prefs_, kLogByteLimit);
+  persisted_logs.StoreLog(kFooText);
+  persisted_logs.StageNextLog();
+
+  EXPECT_EQ(Compress(kFooText), persisted_logs.staged_log());
+  EXPECT_EQ(foo_hash, persisted_logs.staged_log_hash());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/persistent_system_profile.cc b/components/metrics/persistent_system_profile.cc
new file mode 100644
index 0000000..fa35a44
--- /dev/null
+++ b/components/metrics/persistent_system_profile.cc
@@ -0,0 +1,440 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/persistent_system_profile.h"
+
+#include <set>
+
+#include "base/atomicops.h"
+#include "base/bits.h"
+#include "base/memory/singleton.h"
+#include "base/metrics/persistent_memory_allocator.h"
+#include "base/pickle.h"
+#include "base/stl_util.h"
+#include "components/variations/active_field_trials.h"
+
+namespace metrics {
+
+namespace {
+
+// To provide atomic addition of records so that there is no confusion between
+// writers and readers, all of the metadata about a record is contained in a
+// structure that can be stored as a single atomic 32-bit word.
+union RecordHeader {
+  struct {
+    unsigned continued : 1;  // Flag indicating if there is more after this.
+    unsigned type : 7;       // The type of this record.
+    unsigned amount : 24;    // The amount of data to follow.
+  } as_parts;
+  base::subtle::Atomic32 as_atomic;
+};
+
+constexpr uint32_t kTypeIdSystemProfile = 0x330A7150;  // SHA1(SystemProfile)
+constexpr size_t kSystemProfileAllocSize = 4 << 10;    // 4 KiB
+constexpr size_t kMaxRecordSize = (1 << 24) - sizeof(RecordHeader);
+
+static_assert(sizeof(RecordHeader) == sizeof(base::subtle::Atomic32),
+              "bad RecordHeader size");
+
+// Calculate the size of a record based on the amount of data. This adds room
+// for the record header and rounds up to the next multiple of the record-header
+// size.
+size_t CalculateRecordSize(size_t data_amount) {
+  return base::bits::Align(data_amount + sizeof(RecordHeader),
+                           sizeof(RecordHeader));
+}
+
+}  // namespace
+
+PersistentSystemProfile::RecordAllocator::RecordAllocator(
+    base::PersistentMemoryAllocator* memory_allocator,
+    size_t min_size)
+    : allocator_(memory_allocator),
+      has_complete_profile_(false),
+      alloc_reference_(0),
+      alloc_size_(0),
+      end_offset_(0) {
+  AddSegment(min_size);
+}
+
+PersistentSystemProfile::RecordAllocator::RecordAllocator(
+    const base::PersistentMemoryAllocator* memory_allocator)
+    : allocator_(
+          const_cast<base::PersistentMemoryAllocator*>(memory_allocator)),
+      alloc_reference_(0),
+      alloc_size_(0),
+      end_offset_(0) {}
+
+void PersistentSystemProfile::RecordAllocator::Reset() {
+  // Clear the first word of all blocks so they're known to be "empty".
+  alloc_reference_ = 0;
+  while (NextSegment()) {
+    // Get the block as a char* and cast it. It can't be fetched directly as
+    // an array of RecordHeader because that's not a fundamental type and only
+    // arrays of fundamental types are allowed.
+    RecordHeader* header =
+        reinterpret_cast<RecordHeader*>(allocator_->GetAsArray<char>(
+            alloc_reference_, kTypeIdSystemProfile, sizeof(RecordHeader)));
+    DCHECK(header);
+    base::subtle::NoBarrier_Store(&header->as_atomic, 0);
+  }
+
+  // Reset member variables.
+  has_complete_profile_ = false;
+  alloc_reference_ = 0;
+  alloc_size_ = 0;
+  end_offset_ = 0;
+}
+
+bool PersistentSystemProfile::RecordAllocator::Write(RecordType type,
+                                                     base::StringPiece record) {
+  const char* data = record.data();
+  size_t remaining_size = record.size();
+
+  // Allocate space and write records until everything has been stored.
+  do {
+    if (end_offset_ == alloc_size_) {
+      if (!AddSegment(remaining_size))
+        return false;
+    }
+    // Write out as much of the data as possible. |data| and |remaining_size|
+    // are updated in place.
+    if (!WriteData(type, &data, &remaining_size))
+      return false;
+  } while (remaining_size > 0);
+
+  return true;
+}
+
+bool PersistentSystemProfile::RecordAllocator::HasMoreData() const {
+  if (alloc_reference_ == 0 && !NextSegment())
+    return false;
+
+  char* block =
+      allocator_->GetAsArray<char>(alloc_reference_, kTypeIdSystemProfile,
+                                   base::PersistentMemoryAllocator::kSizeAny);
+  if (!block)
+    return false;
+
+  RecordHeader header;
+  header.as_atomic = base::subtle::Acquire_Load(
+      reinterpret_cast<base::subtle::Atomic32*>(block + end_offset_));
+  return header.as_parts.type != kUnusedSpace;
+}
+
+bool PersistentSystemProfile::RecordAllocator::Read(RecordType* type,
+                                                    std::string* record) const {
+  *type = kUnusedSpace;
+  record->clear();
+
+  // Access data and read records until everything has been loaded.
+  while (true) {
+    if (end_offset_ == alloc_size_) {
+      if (!NextSegment())
+        return false;
+    }
+    if (ReadData(type, record))
+      return *type != kUnusedSpace;
+  }
+}
+
+bool PersistentSystemProfile::RecordAllocator::NextSegment() const {
+  base::PersistentMemoryAllocator::Iterator iter(allocator_, alloc_reference_);
+  alloc_reference_ = iter.GetNextOfType(kTypeIdSystemProfile);
+  alloc_size_ = allocator_->GetAllocSize(alloc_reference_);
+  end_offset_ = 0;
+  return alloc_reference_ != 0;
+}
+
+bool PersistentSystemProfile::RecordAllocator::AddSegment(size_t min_size) {
+  if (NextSegment()) {
+    // The first record-header should have been zeroed as part of the allocation
+    // or by the "reset" procedure.
+    DCHECK_EQ(0, base::subtle::NoBarrier_Load(
+                     allocator_->GetAsArray<base::subtle::Atomic32>(
+                         alloc_reference_, kTypeIdSystemProfile, 1)));
+    return true;
+  }
+
+  DCHECK_EQ(0U, alloc_reference_);
+  DCHECK_EQ(0U, end_offset_);
+
+  size_t size =
+      std::max(CalculateRecordSize(min_size), kSystemProfileAllocSize);
+
+  uint32_t ref = allocator_->Allocate(size, kTypeIdSystemProfile);
+  if (!ref)
+    return false;  // Allocator must be full.
+  allocator_->MakeIterable(ref);
+
+  alloc_reference_ = ref;
+  alloc_size_ = allocator_->GetAllocSize(ref);
+  return true;
+}
+
+bool PersistentSystemProfile::RecordAllocator::WriteData(RecordType type,
+                                                         const char** data,
+                                                         size_t* data_size) {
+  char* block =
+      allocator_->GetAsArray<char>(alloc_reference_, kTypeIdSystemProfile,
+                                   base::PersistentMemoryAllocator::kSizeAny);
+  if (!block)
+    return false;  // It's bad if there is no accessible block.
+
+  const size_t max_write_size = std::min(
+      kMaxRecordSize, alloc_size_ - end_offset_ - sizeof(RecordHeader));
+  const size_t write_size = std::min(*data_size, max_write_size);
+  const size_t record_size = CalculateRecordSize(write_size);
+  DCHECK_LT(write_size, record_size);
+
+  // Write the data and the record header.
+  RecordHeader header;
+  header.as_atomic = 0;
+  header.as_parts.type = type;
+  header.as_parts.amount = write_size;
+  header.as_parts.continued = (write_size < *data_size);
+  size_t offset = end_offset_;
+  end_offset_ += record_size;
+  DCHECK_GE(alloc_size_, end_offset_);
+  if (end_offset_ < alloc_size_) {
+    // An empty record header has to be next before this one gets written.
+    base::subtle::NoBarrier_Store(
+        reinterpret_cast<base::subtle::Atomic32*>(block + end_offset_), 0);
+  }
+  memcpy(block + offset + sizeof(header), *data, write_size);
+  base::subtle::Release_Store(
+      reinterpret_cast<base::subtle::Atomic32*>(block + offset),
+      header.as_atomic);
+
+  // Account for what was stored and prepare for follow-on records with any
+  // remaining data.
+  *data += write_size;
+  *data_size -= write_size;
+
+  return true;
+}
+
+bool PersistentSystemProfile::RecordAllocator::ReadData(
+    RecordType* type,
+    std::string* record) const {
+  DCHECK_GT(alloc_size_, end_offset_);
+
+  char* block =
+      allocator_->GetAsArray<char>(alloc_reference_, kTypeIdSystemProfile,
+                                   base::PersistentMemoryAllocator::kSizeAny);
+  if (!block) {
+    *type = kUnusedSpace;
+    return true;  // No more data.
+  }
+
+  // Get and validate the record header.
+  RecordHeader header;
+  header.as_atomic = base::subtle::Acquire_Load(
+      reinterpret_cast<base::subtle::Atomic32*>(block + end_offset_));
+  bool continued = !!header.as_parts.continued;
+  if (header.as_parts.type == kUnusedSpace) {
+    *type = kUnusedSpace;
+    return true;  // End of all records.
+  } else if (*type == kUnusedSpace) {
+    *type = static_cast<RecordType>(header.as_parts.type);
+  } else if (*type != header.as_parts.type) {
+    NOTREACHED();  // Continuation didn't match start of record.
+    *type = kUnusedSpace;
+    record->clear();
+    return false;
+  }
+  size_t read_size = header.as_parts.amount;
+  if (end_offset_ + sizeof(header) + read_size > alloc_size_) {
+    NOTREACHED();  // Invalid header amount.
+    *type = kUnusedSpace;
+    return true;  // Don't try again.
+  }
+
+  // Append the record data to the output string.
+  record->append(block + end_offset_ + sizeof(header), read_size);
+  end_offset_ += CalculateRecordSize(read_size);
+  DCHECK_GE(alloc_size_, end_offset_);
+
+  return !continued;
+}
+
+PersistentSystemProfile::PersistentSystemProfile() {}
+
+PersistentSystemProfile::~PersistentSystemProfile() {}
+
+void PersistentSystemProfile::RegisterPersistentAllocator(
+    base::PersistentMemoryAllocator* memory_allocator) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  // Create and store the allocator. A |min_size| of "1" ensures that a memory
+  // block is reserved now.
+  RecordAllocator allocator(memory_allocator, 1);
+  allocators_.push_back(std::move(allocator));
+  all_have_complete_profile_ = false;
+}
+
+void PersistentSystemProfile::DeregisterPersistentAllocator(
+    base::PersistentMemoryAllocator* memory_allocator) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  // This would be more efficient with a std::map but it's not expected that
+  // allocators will get deregistered with any frequency, if at all.
+  base::EraseIf(allocators_, [=](RecordAllocator& records) {
+    return records.allocator() == memory_allocator;
+  });
+}
+
+void PersistentSystemProfile::SetSystemProfile(
+    const std::string& serialized_profile,
+    bool complete) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  if (allocators_.empty() || serialized_profile.empty())
+    return;
+
+  for (auto& allocator : allocators_) {
+    // Don't overwrite a complete profile with an incomplete one.
+    if (!complete && allocator.has_complete_profile())
+      continue;
+    // A full system profile always starts fresh. Incomplete keeps existing
+    // records for merging.
+    if (complete)
+      allocator.Reset();
+    // Write out the serialized profile.
+    allocator.Write(kSystemProfileProto, serialized_profile);
+    // Indicate if this is a complete profile.
+    if (complete)
+      allocator.set_complete_profile();
+  }
+
+  if (complete)
+    all_have_complete_profile_ = true;
+}
+
+void PersistentSystemProfile::SetSystemProfile(
+    const SystemProfileProto& profile,
+    bool complete) {
+  // Avoid serialization if passed profile is not complete and all allocators
+  // already have complete ones.
+  if (!complete && all_have_complete_profile_)
+    return;
+
+  std::string serialized_profile;
+  if (!profile.SerializeToString(&serialized_profile))
+    return;
+  SetSystemProfile(serialized_profile, complete);
+}
+
+void PersistentSystemProfile::AddFieldTrial(base::StringPiece trial,
+                                            base::StringPiece group) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK(!trial.empty());
+  DCHECK(!group.empty());
+
+  base::Pickle pickler;
+  pickler.WriteString(trial);
+  pickler.WriteString(group);
+
+  WriteToAll(kFieldTrialInfo,
+             base::StringPiece(static_cast<const char*>(pickler.data()),
+                               pickler.size()));
+}
+
+// static
+bool PersistentSystemProfile::HasSystemProfile(
+    const base::PersistentMemoryAllocator& memory_allocator) {
+  const RecordAllocator records(&memory_allocator);
+  return records.HasMoreData();
+}
+
+// static
+bool PersistentSystemProfile::GetSystemProfile(
+    const base::PersistentMemoryAllocator& memory_allocator,
+    SystemProfileProto* system_profile) {
+  const RecordAllocator records(&memory_allocator);
+
+  RecordType type;
+  std::string record;
+  do {
+    if (!records.Read(&type, &record))
+      return false;
+  } while (type != kSystemProfileProto);
+
+  if (!system_profile)
+    return true;
+
+  if (!system_profile->ParseFromString(record))
+    return false;
+
+  MergeUpdateRecords(memory_allocator, system_profile);
+  return true;
+}
+
+// static
+void PersistentSystemProfile::MergeUpdateRecords(
+    const base::PersistentMemoryAllocator& memory_allocator,
+    SystemProfileProto* system_profile) {
+  const RecordAllocator records(&memory_allocator);
+
+  RecordType type;
+  std::string record;
+  std::set<uint32_t> known_field_trial_ids;
+
+  // This is done separate from the code that gets the profile because it
+  // compartmentalizes the code and makes it possible to reuse this section
+  // should it be needed to merge "update" records into a new "complete"
+  // system profile that somehow didn't get all the updates.
+  while (records.Read(&type, &record)) {
+    switch (type) {
+      case kUnusedSpace:
+        // These should never be returned.
+        NOTREACHED();
+        break;
+
+      case kSystemProfileProto:
+        // Profile was passed in; ignore this one.
+        break;
+
+      case kFieldTrialInfo: {
+        // Get the set of known trial IDs so duplicates don't get added.
+        if (known_field_trial_ids.empty()) {
+          for (int i = 0; i < system_profile->field_trial_size(); ++i) {
+            known_field_trial_ids.insert(
+                system_profile->field_trial(i).name_id());
+          }
+        }
+
+        base::Pickle pickler(record.data(), record.size());
+        base::PickleIterator iter(pickler);
+        base::StringPiece trial;
+        base::StringPiece group;
+        if (iter.ReadStringPiece(&trial) && iter.ReadStringPiece(&group)) {
+          variations::ActiveGroupId field_ids =
+              variations::MakeActiveGroupId(trial, group);
+          if (!base::ContainsKey(known_field_trial_ids, field_ids.name)) {
+            SystemProfileProto::FieldTrial* field_trial =
+                system_profile->add_field_trial();
+            field_trial->set_name_id(field_ids.name);
+            field_trial->set_group_id(field_ids.group);
+            known_field_trial_ids.insert(field_ids.name);
+          }
+        }
+      } break;
+    }
+  }
+}
+
+void PersistentSystemProfile::WriteToAll(RecordType type,
+                                         base::StringPiece record) {
+  for (auto& allocator : allocators_)
+    allocator.Write(type, record);
+}
+
+GlobalPersistentSystemProfile* GlobalPersistentSystemProfile::GetInstance() {
+  return base::Singleton<
+      GlobalPersistentSystemProfile,
+      base::LeakySingletonTraits<GlobalPersistentSystemProfile>>::get();
+}
+
+}  // namespace metrics
diff --git a/components/metrics/persistent_system_profile.h b/components/metrics/persistent_system_profile.h
new file mode 100644
index 0000000..6c854a9
--- /dev/null
+++ b/components/metrics/persistent_system_profile.h
@@ -0,0 +1,160 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_METRICS_PERSISTENT_SYSTEM_PROFILE_H_
+#define BASE_METRICS_PERSISTENT_SYSTEM_PROFILE_H_
+
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "base/threading/thread_checker.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+class PersistentMemoryAllocator;
+}  // namespace base
+
+namespace metrics {
+
+// Manages a copy of the system profile inside persistent memory segments.
+class PersistentSystemProfile {
+ public:
+  PersistentSystemProfile();
+  ~PersistentSystemProfile();
+
+  // This object can store records in multiple memory allocators.
+  void RegisterPersistentAllocator(
+      base::PersistentMemoryAllocator* memory_allocator);
+  void DeregisterPersistentAllocator(
+      base::PersistentMemoryAllocator* memory_allocator);
+
+  // Stores a complete system profile. Use the version taking the serialized
+  // version if available to avoid multiple serialization actions. The
+  // |complete| flag indicates that this profile contains all known information
+  // and can replace whatever exists. If the flag is false, the profile will be
+  // stored only if there is nothing else already present.
+  void SetSystemProfile(const std::string& serialized_profile, bool complete);
+  void SetSystemProfile(const SystemProfileProto& profile, bool complete);
+
+  // Records the existence of a field trial.
+  void AddFieldTrial(base::StringPiece trial, base::StringPiece group);
+
+  // Tests if a persistent memory allocator contains an system profile.
+  static bool HasSystemProfile(
+      const base::PersistentMemoryAllocator& memory_allocator);
+
+  // Retrieves the system profile from a persistent memory allocator. Returns
+  // true if a profile was successfully retrieved. If null is passed for the
+  // |system_profile|, only a basic check for the existence of one will be
+  // done.
+  static bool GetSystemProfile(
+      const base::PersistentMemoryAllocator& memory_allocator,
+      SystemProfileProto* system_profile);
+
+ private:
+  friend class PersistentSystemProfileTest;
+
+  // Defines record types that can be stored inside our local Allocators.
+  enum RecordType : uint8_t {
+    kUnusedSpace = 0,  // The default value for empty memory.
+    kSystemProfileProto,
+    kFieldTrialInfo,
+  };
+
+  // A class for managing record allocations inside a persistent memory segment.
+  class RecordAllocator {
+   public:
+    // Construct an allocator for writing.
+    RecordAllocator(base::PersistentMemoryAllocator* memory_allocator,
+                    size_t min_size);
+
+    // Construct an allocator for reading.
+    RecordAllocator(const base::PersistentMemoryAllocator* memory_allocator);
+
+    // These methods manage writing records to the allocator. Do not mix these
+    // with "read" calls; it's one or the other.
+    void Reset();
+    bool Write(RecordType type, base::StringPiece record);
+
+    // Read a record from the allocator. Do not mix this with "write" calls;
+    // it's one or the other.
+    bool HasMoreData() const;
+    bool Read(RecordType* type, std::string* record) const;
+
+    base::PersistentMemoryAllocator* allocator() { return allocator_; }
+
+    bool has_complete_profile() { return has_complete_profile_; }
+    void set_complete_profile() { has_complete_profile_ = true; }
+
+   private:
+    // Advance to the next record segment in the memory allocator.
+    bool NextSegment() const;
+
+    // Advance to the next record segment, creating a new one if necessary with
+    // sufficent |min_size| space.
+    bool AddSegment(size_t min_size);
+
+    // Writes data to the current position, updating the passed values past
+    // the amount written. Returns false in case of an error.
+    bool WriteData(RecordType type, const char** data, size_t* data_size);
+
+    // Reads data from the current position, updating the passed string
+    // in-place. |type| must be initialized to kUnusedSpace and |record| must
+    // be an empty string before the first call but unchanged thereafter.
+    // Returns true when record is complete.
+    bool ReadData(RecordType* type, std::string* record) const;
+
+    // This never changes but can't be "const" because vector calls operator=().
+    base::PersistentMemoryAllocator* allocator_;  // Storage location.
+
+    // Indicates if a complete profile has been stored.
+    bool has_complete_profile_;
+
+    // These change even though the underlying data may be "const".
+    mutable uint32_t alloc_reference_;  // Last storage block.
+    mutable size_t alloc_size_;         // Size of the block.
+    mutable size_t end_offset_;         // End of data in block.
+
+    // Copy and assign are allowed for easy use with STL containers.
+  };
+
+  // Write a record to all registered allocators.
+  void WriteToAll(RecordType type, base::StringPiece record);
+
+  // Merges all "update" records into a system profile.
+  static void MergeUpdateRecords(
+      const base::PersistentMemoryAllocator& memory_allocator,
+      SystemProfileProto* system_profile);
+
+  // The list of registered persistent allocators, described by RecordAllocator
+  // instances.
+  std::vector<RecordAllocator> allocators_;
+
+  // Indicates if a complete profile has been stored to all allocators.
+  bool all_have_complete_profile_ = false;
+
+  THREAD_CHECKER(thread_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(PersistentSystemProfile);
+};
+
+// A singleton instance of the above.
+class GlobalPersistentSystemProfile : public PersistentSystemProfile {
+ public:
+  static GlobalPersistentSystemProfile* GetInstance();
+
+ private:
+  friend struct base::DefaultSingletonTraits<GlobalPersistentSystemProfile>;
+
+  GlobalPersistentSystemProfile() {}
+  ~GlobalPersistentSystemProfile() {}
+
+  DISALLOW_COPY_AND_ASSIGN(GlobalPersistentSystemProfile);
+};
+
+}  // namespace metrics
+
+#endif  // BASE_METRICS_PERSISTENT_SYSTEM_PROFILE_H_
diff --git a/components/metrics/persistent_system_profile_unittest.cc b/components/metrics/persistent_system_profile_unittest.cc
new file mode 100644
index 0000000..b3a7ec6
--- /dev/null
+++ b/components/metrics/persistent_system_profile_unittest.cc
@@ -0,0 +1,172 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/persistent_system_profile.h"
+
+#include <memory>
+
+#include "base/logging.h"
+#include "base/metrics/persistent_memory_allocator.h"
+#include "base/rand_util.h"
+#include "components/variations/hashing.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+class PersistentSystemProfileTest : public testing::Test {
+ public:
+  const int32_t kAllocatorMemorySize = 1 << 20;  // 1 MiB
+
+  PersistentSystemProfileTest() {}
+  ~PersistentSystemProfileTest() override {}
+
+  void SetUp() override {
+    memory_allocator_ = std::make_unique<base::LocalPersistentMemoryAllocator>(
+        kAllocatorMemorySize, 0, "");
+    records_ = std::make_unique<PersistentSystemProfile::RecordAllocator>(
+        memory_allocator_.get());
+    persistent_profile_.RegisterPersistentAllocator(memory_allocator_.get());
+  }
+
+  void TearDown() override {
+    persistent_profile_.DeregisterPersistentAllocator(memory_allocator_.get());
+    records_.reset();
+    memory_allocator_.reset();
+  }
+
+  void WriteRecord(uint8_t type, const std::string& record) {
+    persistent_profile_.allocators_[0].Write(
+        static_cast<PersistentSystemProfile::RecordType>(type), record);
+  }
+
+  bool ReadRecord(uint8_t* type, std::string* record) {
+    PersistentSystemProfile::RecordType rec_type;
+
+    bool success = records_->Read(&rec_type, record);
+    *type = rec_type;  // Convert to uint8_t for testing.
+    return success;
+  }
+
+  base::PersistentMemoryAllocator* memory_allocator() {
+    return memory_allocator_.get();
+  }
+
+  PersistentSystemProfile* persistent_profile() { return &persistent_profile_; }
+
+ private:
+  PersistentSystemProfile persistent_profile_;
+  std::unique_ptr<base::PersistentMemoryAllocator> memory_allocator_;
+  std::unique_ptr<PersistentSystemProfile::RecordAllocator> records_;
+
+  DISALLOW_COPY_AND_ASSIGN(PersistentSystemProfileTest);
+};
+
+TEST_F(PersistentSystemProfileTest, Create) {
+  uint32_t type;
+  base::PersistentMemoryAllocator::Iterator iter(memory_allocator());
+  base::PersistentMemoryAllocator::Reference ref = iter.GetNext(&type);
+  DCHECK(ref);
+  DCHECK_NE(0U, type);
+}
+
+TEST_F(PersistentSystemProfileTest, RecordSplitting) {
+  const size_t kRecordSize = 100 << 10;  // 100 KiB
+  std::vector<char> buffer;
+  buffer.resize(kRecordSize);
+  base::RandBytes(&buffer[0], kRecordSize);
+
+  WriteRecord(42, std::string(&buffer[0], kRecordSize));
+
+  uint8_t type;
+  std::string record;
+  ASSERT_TRUE(ReadRecord(&type, &record));
+  EXPECT_EQ(42U, type);
+  ASSERT_EQ(kRecordSize, record.size());
+  for (size_t i = 0; i < kRecordSize; ++i)
+    EXPECT_EQ(buffer[i], record[i]);
+}
+
+TEST_F(PersistentSystemProfileTest, ProfileStorage) {
+  SystemProfileProto proto1;
+  SystemProfileProto::FieldTrial* trial = proto1.add_field_trial();
+  trial->set_name_id(123);
+  trial->set_group_id(456);
+
+  persistent_profile()->SetSystemProfile(proto1, false);
+
+  SystemProfileProto proto2;
+  ASSERT_TRUE(PersistentSystemProfile::HasSystemProfile(*memory_allocator()));
+  ASSERT_TRUE(
+      PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &proto2));
+  ASSERT_EQ(1, proto2.field_trial_size());
+  EXPECT_EQ(123U, proto2.field_trial(0).name_id());
+  EXPECT_EQ(456U, proto2.field_trial(0).group_id());
+
+  // Check that the profile can be overwritten.
+
+  trial = proto1.add_field_trial();
+  trial->set_name_id(78);
+  trial->set_group_id(90);
+
+  persistent_profile()->SetSystemProfile(proto1, true);
+
+  ASSERT_TRUE(
+      PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &proto2));
+  ASSERT_EQ(2, proto2.field_trial_size());
+  EXPECT_EQ(123U, proto2.field_trial(0).name_id());
+  EXPECT_EQ(456U, proto2.field_trial(0).group_id());
+  EXPECT_EQ(78U, proto2.field_trial(1).name_id());
+  EXPECT_EQ(90U, proto2.field_trial(1).group_id());
+
+  // Check that the profile won't be overwritten by a new non-complete profile.
+
+  trial = proto1.add_field_trial();
+  trial->set_name_id(0xC0DE);
+  trial->set_group_id(0xFEED);
+
+  persistent_profile()->SetSystemProfile(proto1, false);
+
+  ASSERT_TRUE(
+      PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &proto2));
+  ASSERT_EQ(2, proto2.field_trial_size());
+  EXPECT_EQ(123U, proto2.field_trial(0).name_id());
+  EXPECT_EQ(456U, proto2.field_trial(0).group_id());
+  EXPECT_EQ(78U, proto2.field_trial(1).name_id());
+  EXPECT_EQ(90U, proto2.field_trial(1).group_id());
+}
+
+TEST_F(PersistentSystemProfileTest, ProfileExtensions) {
+  persistent_profile()->AddFieldTrial("sna", "foo");
+
+  SystemProfileProto fetched;
+  ASSERT_FALSE(
+      PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &fetched));
+
+  SystemProfileProto proto;
+  SystemProfileProto::FieldTrial* trial = proto.add_field_trial();
+  trial->set_name_id(123);
+  trial->set_group_id(456);
+
+  persistent_profile()->SetSystemProfile(proto, false);
+  ASSERT_TRUE(
+      PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &fetched));
+  ASSERT_EQ(2, fetched.field_trial_size());
+  EXPECT_EQ(123U, fetched.field_trial(0).name_id());
+  EXPECT_EQ(456U, fetched.field_trial(0).group_id());
+  EXPECT_EQ(variations::HashName("sna"), fetched.field_trial(1).name_id());
+  EXPECT_EQ(variations::HashName("foo"), fetched.field_trial(1).group_id());
+
+  persistent_profile()->AddFieldTrial("foo", "bar");
+  ASSERT_TRUE(
+      PersistentSystemProfile::GetSystemProfile(*memory_allocator(), &fetched));
+  ASSERT_EQ(3, fetched.field_trial_size());
+  EXPECT_EQ(123U, fetched.field_trial(0).name_id());
+  EXPECT_EQ(456U, fetched.field_trial(0).group_id());
+  EXPECT_EQ(variations::HashName("sna"), fetched.field_trial(1).name_id());
+  EXPECT_EQ(variations::HashName("foo"), fetched.field_trial(1).group_id());
+  EXPECT_EQ(variations::HashName("foo"), fetched.field_trial(2).name_id());
+  EXPECT_EQ(variations::HashName("bar"), fetched.field_trial(2).group_id());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/public/cpp/BUILD.gn b/components/metrics/public/cpp/BUILD.gn
new file mode 100644
index 0000000..b9c2af5
--- /dev/null
+++ b/components/metrics/public/cpp/BUILD.gn
@@ -0,0 +1,20 @@
+# 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("//mojo/public/tools/bindings/mojom.gni")
+
+source_set("call_stack_unit_tests") {
+  testonly = true
+  sources = [
+    "call_stack_profile_struct_traits_unittest.cc",
+  ]
+
+  deps = [
+    "//base",
+    "//components/metrics/public/interfaces:call_stack_mojo_test_bindings",
+    "//mojo/public/cpp/bindings",
+    "//testing/gtest",
+    "//third_party/metrics_proto",
+  ]
+}
diff --git a/components/metrics/public/cpp/OWNERS b/components/metrics/public/cpp/OWNERS
new file mode 100644
index 0000000..2c44a46
--- /dev/null
+++ b/components/metrics/public/cpp/OWNERS
@@ -0,0 +1,6 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+per-file *_struct_traits*.*=set noparent
+per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS
+per-file *.typemap=set noparent
+per-file *.typemap=file://ipc/SECURITY_OWNERS
diff --git a/components/metrics/public/cpp/call_stack_profile.typemap b/components/metrics/public/cpp/call_stack_profile.typemap
new file mode 100644
index 0000000..e7fa49f
--- /dev/null
+++ b/components/metrics/public/cpp/call_stack_profile.typemap
@@ -0,0 +1,14 @@
+# 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.
+
+mojom =
+    "//components/metrics/public/interfaces/call_stack_profile_collector.mojom"
+public_headers = [ "//third_party/metrics_proto/sampled_profile.pb.h" ]
+traits_headers =
+    [ "//components/metrics/public/cpp/call_stack_profile_struct_traits.h" ]
+deps = [
+  "//third_party/metrics_proto",
+]
+type_mappings =
+    [ "metrics.mojom.SampledProfile=metrics::SampledProfile[move_only]" ]
diff --git a/components/metrics/public/cpp/call_stack_profile_struct_traits.h b/components/metrics/public/cpp/call_stack_profile_struct_traits.h
new file mode 100644
index 0000000..bdcc617
--- /dev/null
+++ b/components/metrics/public/cpp/call_stack_profile_struct_traits.h
@@ -0,0 +1,48 @@
+// 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.
+
+// Defines StructTraits specializations for translating between mojo types and
+// metrics:: types, with data validity checks.
+
+#ifndef COMPONENTS_METRICS_PUBLIC_CPP_CALL_STACK_PROFILE_STRUCT_TRAITS_H_
+#define COMPONENTS_METRICS_PUBLIC_CPP_CALL_STACK_PROFILE_STRUCT_TRAITS_H_
+
+#include <string>
+
+#include "base/strings/string_piece.h"
+#include "components/metrics/public/interfaces/call_stack_profile_collector.mojom.h"
+#include "third_party/metrics_proto/sampled_profile.pb.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<metrics::mojom::SampledProfileDataView,
+                    metrics::SampledProfile> {
+  static std::string contents(const metrics::SampledProfile& profile) {
+    std::string output;
+    profile.SerializeToString(&output);
+    return output;
+  }
+
+  static bool Read(metrics::mojom::SampledProfileDataView data,
+                   metrics::SampledProfile* out) {
+    base::StringPiece contents;
+    if (!data.ReadContents(&contents))
+      return false;
+
+    if (!out->ParseFromArray(contents.data(), contents.size()))
+      return false;
+
+    // This is purely a sanity check to minimize bad data uploaded, and not
+    // required for security reasons.
+    if (!out->unknown_fields().empty())
+      return false;
+
+    return true;
+  }
+};
+
+}  // namespace mojo
+
+#endif  // COMPONENTS_METRICS_PUBLIC_CPP_CALL_STACK_PROFILE_STRUCT_TRAITS_H_
diff --git a/components/metrics/public/cpp/call_stack_profile_struct_traits_unittest.cc b/components/metrics/public/cpp/call_stack_profile_struct_traits_unittest.cc
new file mode 100644
index 0000000..236d619
--- /dev/null
+++ b/components/metrics/public/cpp/call_stack_profile_struct_traits_unittest.cc
@@ -0,0 +1,101 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <utility>
+
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "components/metrics/public/interfaces/call_stack_profile_collector_test.mojom.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/sampled_profile.pb.h"
+
+namespace metrics {
+
+class CallStackProfileCollectorTestImpl
+    : public mojom::CallStackProfileCollectorTest {
+ public:
+  explicit CallStackProfileCollectorTestImpl(
+      mojo::InterfaceRequest<mojom::CallStackProfileCollectorTest> request)
+      : binding_(this, std::move(request)) {
+  }
+
+  // CallStackProfileCollectorTest:
+  void BounceSampledProfile(SampledProfile in,
+                            BounceSampledProfileCallback callback) override {
+    std::move(callback).Run(in);
+  }
+
+ private:
+  mojo::Binding<CallStackProfileCollectorTest> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(CallStackProfileCollectorTestImpl);
+};
+
+class CallStackProfileStructTraitsTest : public testing::Test {
+ public:
+  CallStackProfileStructTraitsTest() : impl_(MakeRequest(&proxy_)) {}
+
+ protected:
+  base::MessageLoop message_loop_;
+  mojom::CallStackProfileCollectorTestPtr proxy_;
+  CallStackProfileCollectorTestImpl impl_;
+
+  DISALLOW_COPY_AND_ASSIGN(CallStackProfileStructTraitsTest);
+};
+
+// Checks serialization/deserialization of SampledProfile.
+TEST_F(CallStackProfileStructTraitsTest, SampledProfile) {
+  // Construct a SampledProfile protocol buffer message.
+  SampledProfile input_proto;
+
+  CallStackProfile* proto_profile = input_proto.mutable_call_stack_profile();
+
+  CallStackProfile::Sample* proto_sample =
+      proto_profile->add_deprecated_sample();
+  proto_sample->set_count(1);
+  CallStackProfile::Location* location = proto_sample->add_frame();
+  location->set_address(0x10ULL);
+  location->set_module_id_index(0);
+
+  CallStackProfile::ModuleIdentifier* module_id =
+      proto_profile->add_module_id();
+  module_id->set_build_id("a");
+  module_id->set_name_md5_prefix(111U);
+
+  proto_profile->set_profile_duration_ms(1000);
+  proto_profile->set_sampling_period_ms(2000);
+
+  // Send the message round trip, and verify those values.
+  SampledProfile output_proto;
+  EXPECT_TRUE(
+      proxy_->BounceSampledProfile(std::move(input_proto), &output_proto));
+
+  const CallStackProfile& out_profile = output_proto.call_stack_profile();
+
+  ASSERT_EQ(1, out_profile.deprecated_sample_size());
+  ASSERT_EQ(1, out_profile.deprecated_sample(0).frame_size());
+
+  ASSERT_TRUE(out_profile.deprecated_sample(0).frame(0).has_address());
+  EXPECT_EQ(0x10ULL, out_profile.deprecated_sample(0).frame(0).address());
+
+  ASSERT_TRUE(out_profile.deprecated_sample(0).frame(0).has_module_id_index());
+  EXPECT_EQ(0, out_profile.deprecated_sample(0).frame(0).module_id_index());
+
+  ASSERT_EQ(1, out_profile.module_id().size());
+
+  ASSERT_TRUE(out_profile.module_id(0).has_build_id());
+  ASSERT_EQ("a", out_profile.module_id(0).build_id());
+
+  ASSERT_TRUE(out_profile.module_id(0).has_name_md5_prefix());
+  ASSERT_EQ(111U, out_profile.module_id(0).name_md5_prefix());
+
+  ASSERT_TRUE(out_profile.has_profile_duration_ms());
+  EXPECT_EQ(1000, out_profile.profile_duration_ms());
+
+  ASSERT_TRUE(out_profile.has_sampling_period_ms());
+  EXPECT_EQ(2000, out_profile.sampling_period_ms());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/public/cpp/call_stack_profile_unittest.typemap b/components/metrics/public/cpp/call_stack_profile_unittest.typemap
new file mode 100644
index 0000000..5a8cd99
--- /dev/null
+++ b/components/metrics/public/cpp/call_stack_profile_unittest.typemap
@@ -0,0 +1,18 @@
+# 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 file is necessary because without it compiling
+# call_stack_profile_struct_traits_unittest produces error below:
+# "gen\third_party/metrics_proto/sampled_profile.pb.h(9,10):  fatal error:
+# 'google/protobuf/stubs/common.h' file not found".
+
+mojom = "//components/metrics/public/interfaces/call_stack_profile_collector_test.mojom"
+public_headers = [ "//third_party/metrics_proto/sampled_profile.pb.h" ]
+traits_headers =
+    [ "//components/metrics/public/cpp/call_stack_profile_struct_traits.h" ]
+deps = [
+  "//third_party/metrics_proto",
+]
+type_mappings =
+    [ "metrics.mojom.SampledProfile=metrics::SampledProfile[move_only]" ]
diff --git a/components/metrics/public/cpp/typemaps.gni b/components/metrics/public/cpp/typemaps.gni
new file mode 100644
index 0000000..9e4e1db
--- /dev/null
+++ b/components/metrics/public/cpp/typemaps.gni
@@ -0,0 +1,8 @@
+# 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.
+
+typemaps = [
+  "//components/metrics/public/cpp/call_stack_profile.typemap",
+  "//components/metrics/public/cpp/call_stack_profile_unittest.typemap",
+]
diff --git a/components/metrics/public/interfaces/BUILD.gn b/components/metrics/public/interfaces/BUILD.gn
new file mode 100644
index 0000000..2a84979
--- /dev/null
+++ b/components/metrics/public/interfaces/BUILD.gn
@@ -0,0 +1,31 @@
+# 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("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("call_stack_mojo_bindings") {
+  sources = [
+    "call_stack_profile_collector.mojom",
+  ]
+
+  deps = [
+    "//mojo/public/mojom/base",
+  ]
+}
+
+mojom("call_stack_mojo_test_bindings") {
+  sources = [
+    "call_stack_profile_collector_test.mojom",
+  ]
+
+  deps = [
+    ":call_stack_mojo_bindings",
+  ]
+}
+
+mojom("single_sample_metrics_mojo_bindings") {
+  sources = [
+    "single_sample_metrics.mojom",
+  ]
+}
diff --git a/components/metrics/public/interfaces/OWNERS b/components/metrics/public/interfaces/OWNERS
new file mode 100644
index 0000000..1544352
--- /dev/null
+++ b/components/metrics/public/interfaces/OWNERS
@@ -0,0 +1,4 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+per-file *_struct_traits*.*=set noparent
+per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/components/metrics/public/interfaces/call_stack_profile_collector.mojom b/components/metrics/public/interfaces/call_stack_profile_collector.mojom
new file mode 100644
index 0000000..51fcf81
--- /dev/null
+++ b/components/metrics/public/interfaces/call_stack_profile_collector.mojom
@@ -0,0 +1,21 @@
+// 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.
+
+module metrics.mojom;
+
+import "mojo/public/mojom/base/time.mojom";
+
+// |contents| is a serialized protobuf from
+// src/third_party/metrics_proto/sampled_profile.proto.
+//
+// We pass this state via serialized protobuf because that is the ultimate
+// metrics upload format.
+struct SampledProfile {
+  string contents;
+};
+
+interface CallStackProfileCollector {
+  Collect(mojo_base.mojom.TimeTicks start_timestamp,
+          SampledProfile profile);
+};
diff --git a/components/metrics/public/interfaces/call_stack_profile_collector_test.mojom b/components/metrics/public/interfaces/call_stack_profile_collector_test.mojom
new file mode 100644
index 0000000..c7725c6
--- /dev/null
+++ b/components/metrics/public/interfaces/call_stack_profile_collector_test.mojom
@@ -0,0 +1,12 @@
+// 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.
+
+module metrics.mojom;
+
+import "components/metrics/public/interfaces/call_stack_profile_collector.mojom";
+
+interface CallStackProfileCollectorTest {
+  [Sync]
+  BounceSampledProfile(SampledProfile in) => (SampledProfile out);
+};
diff --git a/components/metrics/public/interfaces/single_sample_metrics.mojom b/components/metrics/public/interfaces/single_sample_metrics.mojom
new file mode 100644
index 0000000..788150c
--- /dev/null
+++ b/components/metrics/public/interfaces/single_sample_metrics.mojom
@@ -0,0 +1,24 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module metrics.mojom;
+
+// See components/metrics/single_sample_metrics_factory_impl.h for details.
+interface SingleSampleMetricsProvider {
+  // Returns a SingleSampleMetric.
+  //
+  // A single sample metric only reports its sample once at destruction time.
+  // The sample may be changed prior to destruction using the SetSample() method
+  // as many times as desired.
+  //
+  // See base/metrics/histograms.h for parameter definitions. |request| is the
+  // returned histogram.
+  AcquireSingleSampleMetric(string histogram_name, int32 min, int32 max,
+                            uint32 bucket_count, int32 flags,
+                            SingleSampleMetric& request);
+};
+
+interface SingleSampleMetric {
+  SetSample(int32 sample);
+};
diff --git a/components/metrics/reporting_service.cc b/components/metrics/reporting_service.cc
new file mode 100644
index 0000000..6382804
--- /dev/null
+++ b/components/metrics/reporting_service.cc
@@ -0,0 +1,214 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ReportingService handles uploading serialized logs to a server.
+
+#include "components/metrics/reporting_service.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/metrics/data_use_tracker.h"
+#include "components/metrics/log_store.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "components/metrics/metrics_service_client.h"
+#include "components/metrics/metrics_upload_scheduler.h"
+
+namespace metrics {
+
+// static
+void ReportingService::RegisterPrefs(PrefRegistrySimple* registry) {
+  DataUseTracker::RegisterPrefs(registry);
+}
+
+ReportingService::ReportingService(MetricsServiceClient* client,
+                                   PrefService* local_state,
+                                   size_t max_retransmit_size)
+    : client_(client),
+      max_retransmit_size_(max_retransmit_size),
+      reporting_active_(false),
+      log_upload_in_progress_(false),
+      data_use_tracker_(DataUseTracker::Create(local_state)),
+      self_ptr_factory_(this) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(client_);
+  DCHECK(local_state);
+}
+
+ReportingService::~ReportingService() {
+  DisableReporting();
+}
+
+void ReportingService::Initialize() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!upload_scheduler_);
+  log_store()->LoadPersistedUnsentLogs();
+  base::Closure send_next_log_callback = base::Bind(
+      &ReportingService::SendNextLog, self_ptr_factory_.GetWeakPtr());
+  upload_scheduler_.reset(new MetricsUploadScheduler(send_next_log_callback));
+}
+
+void ReportingService::Start() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (reporting_active_)
+    upload_scheduler_->Start();
+}
+
+void ReportingService::Stop() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (upload_scheduler_)
+    upload_scheduler_->Stop();
+}
+
+void ReportingService::EnableReporting() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (reporting_active_)
+    return;
+  reporting_active_ = true;
+  Start();
+}
+
+void ReportingService::DisableReporting() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  reporting_active_ = false;
+  Stop();
+}
+
+bool ReportingService::reporting_active() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return reporting_active_;
+}
+
+void ReportingService::UpdateMetricsUsagePrefs(const std::string& service_name,
+                                               int message_size,
+                                               bool is_cellular) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (data_use_tracker_) {
+    data_use_tracker_->UpdateMetricsUsagePrefs(service_name, message_size,
+                                               is_cellular);
+  }
+}
+
+//------------------------------------------------------------------------------
+// private methods
+//------------------------------------------------------------------------------
+
+void ReportingService::SendNextLog() {
+  DVLOG(1) << "SendNextLog";
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (!last_upload_finish_time_.is_null()) {
+    LogActualUploadInterval(base::TimeTicks::Now() - last_upload_finish_time_);
+    last_upload_finish_time_ = base::TimeTicks();
+  }
+  if (!reporting_active()) {
+    upload_scheduler_->StopAndUploadCancelled();
+    return;
+  }
+  if (!log_store()->has_unsent_logs()) {
+    // Should only get here if serializing the log failed somehow.
+    upload_scheduler_->Stop();
+    // Reset backoff interval
+    upload_scheduler_->UploadFinished(true);
+    return;
+  }
+  if (!log_store()->has_staged_log()) {
+    reporting_info_.set_attempt_count(0);
+    log_store()->StageNextLog();
+  }
+
+  // Proceed to stage the log for upload if log size satisfies cellular log
+  // upload constrains.
+  bool upload_canceled = false;
+  bool is_cellular_logic = client_->IsUMACellularUploadLogicEnabled();
+  if (is_cellular_logic && data_use_tracker_ &&
+      !data_use_tracker_->ShouldUploadLogOnCellular(
+          log_store()->staged_log_hash().size())) {
+    upload_scheduler_->UploadOverDataUsageCap();
+    upload_canceled = true;
+  } else {
+    SendStagedLog();
+  }
+  if (is_cellular_logic) {
+    LogCellularConstraint(upload_canceled);
+  }
+}
+
+void ReportingService::SendStagedLog() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(log_store()->has_staged_log());
+  if (!log_store()->has_staged_log())
+    return;
+
+  DCHECK(!log_upload_in_progress_);
+  log_upload_in_progress_ = true;
+
+  if (!log_uploader_) {
+    log_uploader_ = client_->CreateUploader(
+        GetUploadUrl(), GetInsecureUploadUrl(), upload_mime_type(),
+        service_type(),
+        base::Bind(&ReportingService::OnLogUploadComplete,
+                   self_ptr_factory_.GetWeakPtr()));
+  }
+
+  reporting_info_.set_attempt_count(reporting_info_.attempt_count() + 1);
+
+  const std::string hash =
+      base::HexEncode(log_store()->staged_log_hash().data(),
+                      log_store()->staged_log_hash().size());
+  log_uploader_->UploadLog(log_store()->staged_log(), hash, reporting_info_);
+}
+
+void ReportingService::OnLogUploadComplete(int response_code,
+                                           int error_code,
+                                           bool was_https) {
+  DVLOG(1) << "OnLogUploadComplete:" << response_code;
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(log_upload_in_progress_);
+  log_upload_in_progress_ = false;
+
+  reporting_info_.set_last_response_code(response_code);
+  reporting_info_.set_last_error_code(error_code);
+  reporting_info_.set_last_attempt_was_https(was_https);
+
+  // Log a histogram to track response success vs. failure rates.
+  LogResponseOrErrorCode(response_code, error_code, was_https);
+
+  bool upload_succeeded = response_code == 200;
+
+  // Staged log could have been removed already (such as by Purge() in some
+  // implementations), otherwise we may remove it here.
+  if (log_store()->has_staged_log()) {
+    // Provide boolean for error recovery (allow us to ignore response_code).
+    bool discard_log = false;
+    const size_t log_size = log_store()->staged_log().length();
+    if (upload_succeeded) {
+      LogSuccess(log_size);
+    } else if (log_size > max_retransmit_size_) {
+      LogLargeRejection(log_size);
+      discard_log = true;
+    } else if (response_code == 400) {
+      // Bad syntax.  Retransmission won't work.
+      discard_log = true;
+    }
+
+    if (upload_succeeded || discard_log) {
+      log_store()->DiscardStagedLog();
+      // Store the updated list to disk now that the removed log is uploaded.
+      log_store()->PersistUnsentLogs();
+    }
+  }
+
+  // Error 400 indicates a problem with the log, not with the server, so
+  // don't consider that a sign that the server is in trouble.
+  bool server_is_healthy = upload_succeeded || response_code == 400;
+
+  if (!log_store()->has_unsent_logs()) {
+    DVLOG(1) << "Stopping upload_scheduler_.";
+    upload_scheduler_->Stop();
+  }
+  upload_scheduler_->UploadFinished(server_is_healthy);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/reporting_service.h b/components/metrics/reporting_service.h
new file mode 100644
index 0000000..d724430
--- /dev/null
+++ b/components/metrics/reporting_service.h
@@ -0,0 +1,153 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines a service that sends metrics logs to a server.
+
+#ifndef COMPONENTS_METRICS_REPORTING_SERVICE_H_
+#define COMPONENTS_METRICS_REPORTING_SERVICE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/metrics/data_use_tracker.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "third_party/metrics_proto/reporting_info.pb.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace metrics {
+
+class LogStore;
+class MetricsUploadScheduler;
+class MetricsServiceClient;
+
+// ReportingService is an abstract class which uploads serialized logs from a
+// LogStore to a remote server. A concrete implementation of this class must
+// provide the specific LogStore and parameters for the MetricsLogUploader, and
+// can also implement hooks to record histograms based on certain events that
+// occur while attempting to upload logs.
+class ReportingService {
+ public:
+  // Creates a ReportingService with the given |client|, |local_state|, and
+  // |max_retransmit_size|. Does not take ownership of the parameters; instead
+  // it stores a weak pointer to each. Caller should ensure that the parameters
+  // are valid for the lifetime of this class.
+  ReportingService(MetricsServiceClient* client,
+                   PrefService* local_state,
+                   size_t max_retransmit_size);
+  virtual ~ReportingService();
+
+  // Completes setup tasks that can't be done at construction time.
+  // Loads persisted logs and creates the MetricsUploadScheduler.
+  void Initialize();
+
+  // Starts the metrics reporting system.
+  // Should be called when metrics enabled or new logs are created.
+  // When the service is already running, this is a safe no-op.
+  void Start();
+
+  // Shuts down the metrics system. Should be called at shutdown, or if metrics
+  // are turned off.
+  void Stop();
+
+  // Enable/disable transmission of accumulated logs and crash reports (dumps).
+  // Calling Start() automatically enables reporting, but sending is
+  // asyncronous so this can be called immediately after Start() to prevent
+  // any uploading.
+  void EnableReporting();
+  void DisableReporting();
+
+  // True iff reporting is currently enabled.
+  bool reporting_active() const;
+
+  // Updates data usage tracking prefs with the specified values.
+  void UpdateMetricsUsagePrefs(const std::string& service_name,
+                               int message_size,
+                               bool is_cellular);
+
+  // Registers local state prefs used by this class. This should only be called
+  // once.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ protected:
+  MetricsServiceClient* client() const { return client_; };
+
+ private:
+  // Retrieves the log store backing this service.
+  virtual LogStore* log_store() = 0;
+
+  // Getters for MetricsLogUploader parameters.
+  virtual std::string GetUploadUrl() const = 0;
+  virtual std::string GetInsecureUploadUrl() const = 0;
+  virtual base::StringPiece upload_mime_type() const = 0;
+  virtual MetricsLogUploader::MetricServiceType service_type() const = 0;
+
+  // Methods for recording data to histograms.
+  virtual void LogActualUploadInterval(base::TimeDelta interval) {}
+  virtual void LogCellularConstraint(bool upload_canceled) {}
+  virtual void LogResponseOrErrorCode(int response_code,
+                                      int error_code,
+                                      bool was_https) {}
+  virtual void LogSuccess(size_t log_size) {}
+  virtual void LogLargeRejection(size_t log_size) {}
+
+  // If recording is enabled, begins uploading the next completed log from
+  // the log manager, staging it if necessary.
+  void SendNextLog();
+
+  // Uploads the currently staged log (which must be non-null).
+  void SendStagedLog();
+
+  // Called after transmission completes (either successfully or with failure).
+  void OnLogUploadComplete(int response_code, int error_code, bool was_https);
+
+  // Used to interact with the embedder. Weak pointer; must outlive |this|
+  // instance.
+  MetricsServiceClient* const client_;
+
+  // Largest log size to attempt to retransmit.
+  size_t max_retransmit_size_;
+
+  // Indicate whether recording and reporting are currently happening.
+  // These should not be set directly, but by calling SetRecording and
+  // SetReporting.
+  bool reporting_active_;
+
+  // Instance of the helper class for uploading logs.
+  std::unique_ptr<MetricsLogUploader> log_uploader_;
+
+  // Whether there is a current log upload in progress.
+  bool log_upload_in_progress_;
+
+  // The scheduler for determining when uploads should happen.
+  std::unique_ptr<MetricsUploadScheduler> upload_scheduler_;
+
+  // Pointer used for obtaining data use pref updater callback on above layers.
+  std::unique_ptr<DataUseTracker> data_use_tracker_;
+
+  // The tick count of the last time log upload has been finished and null if no
+  // upload has been done yet.
+  base::TimeTicks last_upload_finish_time_;
+
+  // Info on current reporting state to send along with reports.
+  ReportingInfo reporting_info_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // Weak pointers factory used to post task on different threads. All weak
+  // pointers managed by this factory have the same lifetime as
+  // ReportingService.
+  base::WeakPtrFactory<ReportingService> self_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReportingService);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_REPORTING_SERVICE_H_
diff --git a/components/metrics/reporting_service_unittest.cc b/components/metrics/reporting_service_unittest.cc
new file mode 100644
index 0000000..fb838cb
--- /dev/null
+++ b/components/metrics/reporting_service_unittest.cc
@@ -0,0 +1,142 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/reporting_service.h"
+
+#include <stdint.h>
+
+#include <deque>
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/sha1.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/metrics/log_store.h"
+#include "components/metrics/test_metrics_service_client.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace metrics {
+
+namespace {
+
+const char kTestUploadUrl[] = "test_url";
+const char kTestMimeType[] = "test_mime_type";
+
+class TestLogStore : public LogStore {
+ public:
+  TestLogStore() {}
+  ~TestLogStore() {}
+
+  void AddLog(const std::string& log) { logs_.push_back(log); }
+
+  // LogStore:
+  bool has_unsent_logs() const override { return !logs_.empty(); }
+  bool has_staged_log() const override { return !staged_log_hash_.empty(); }
+  const std::string& staged_log() const override { return logs_.front(); }
+  const std::string& staged_log_hash() const override {
+    return staged_log_hash_;
+  }
+  void StageNextLog() override {
+    if (has_unsent_logs())
+      staged_log_hash_ = base::SHA1HashString(logs_.front());
+  }
+  void DiscardStagedLog() override {
+    if (!has_staged_log())
+      return;
+    logs_.pop_front();
+    staged_log_hash_.clear();
+  }
+  void PersistUnsentLogs() const override {}
+  void LoadPersistedUnsentLogs() override {}
+
+ private:
+  std::string staged_log_hash_;
+  std::deque<std::string> logs_;
+};
+
+class TestReportingService : public ReportingService {
+ public:
+  TestReportingService(MetricsServiceClient* client, PrefService* local_state)
+      : ReportingService(client, local_state, 100) {
+    Initialize();
+  }
+  ~TestReportingService() override {}
+
+  void AddLog(const std::string& log) { log_store_.AddLog(log); }
+
+ private:
+  // ReportingService:
+  LogStore* log_store() override { return &log_store_; }
+  std::string GetUploadUrl() const override { return kTestUploadUrl; }
+  std::string GetInsecureUploadUrl() const override { return kTestUploadUrl; }
+  base::StringPiece upload_mime_type() const override { return kTestMimeType; }
+  MetricsLogUploader::MetricServiceType service_type() const override {
+    return MetricsLogUploader::MetricServiceType::UMA;
+  }
+
+  TestLogStore log_store_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestReportingService);
+};
+
+class ReportingServiceTest : public testing::Test {
+ public:
+  ReportingServiceTest()
+      : task_runner_(new base::TestSimpleTaskRunner),
+        task_runner_handle_(task_runner_) {
+    ReportingService::RegisterPrefs(testing_local_state_.registry());
+  }
+
+  ~ReportingServiceTest() override {}
+
+  PrefService* GetLocalState() { return &testing_local_state_; }
+
+ protected:
+  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+  base::ThreadTaskRunnerHandle task_runner_handle_;
+  TestMetricsServiceClient client_;
+
+ private:
+  TestingPrefServiceSimple testing_local_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReportingServiceTest);
+};
+
+}  // namespace
+
+TEST_F(ReportingServiceTest, BasicTest) {
+  TestReportingService service(&client_, GetLocalState());
+  service.AddLog("log1");
+  service.AddLog("log2");
+
+  service.EnableReporting();
+  task_runner_->RunPendingTasks();
+  client_.uploader()->is_uploading();
+  EXPECT_TRUE(client_.uploader()->is_uploading());
+  EXPECT_EQ(1, client_.uploader()->reporting_info().attempt_count());
+  EXPECT_FALSE(client_.uploader()->reporting_info().has_last_response_code());
+
+  client_.uploader()->CompleteUpload(404);
+  task_runner_->RunPendingTasks();
+  EXPECT_TRUE(client_.uploader()->is_uploading());
+  EXPECT_EQ(2, client_.uploader()->reporting_info().attempt_count());
+  EXPECT_EQ(404, client_.uploader()->reporting_info().last_response_code());
+
+  client_.uploader()->CompleteUpload(200);
+  task_runner_->RunPendingTasks();
+  EXPECT_TRUE(client_.uploader()->is_uploading());
+  EXPECT_EQ(1, client_.uploader()->reporting_info().attempt_count());
+  EXPECT_EQ(200, client_.uploader()->reporting_info().last_response_code());
+
+  client_.uploader()->CompleteUpload(200);
+  EXPECT_EQ(0U, task_runner_->NumPendingTasks());
+  EXPECT_FALSE(client_.uploader()->is_uploading());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/serialization/metric_sample.cc b/components/metrics/serialization/metric_sample.cc
new file mode 100644
index 0000000..24f3757
--- /dev/null
+++ b/components/metrics/serialization/metric_sample.cc
@@ -0,0 +1,196 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/serialization/metric_sample.h"
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+
+namespace metrics {
+
+MetricSample::MetricSample(MetricSample::SampleType sample_type,
+                           const std::string& metric_name,
+                           int sample,
+                           int min,
+                           int max,
+                           int bucket_count)
+    : type_(sample_type),
+      name_(metric_name),
+      sample_(sample),
+      min_(min),
+      max_(max),
+      bucket_count_(bucket_count) {
+}
+
+MetricSample::~MetricSample() {
+}
+
+bool MetricSample::IsValid() const {
+  return name().find(' ') == std::string::npos &&
+         name().find('\0') == std::string::npos && !name().empty();
+}
+
+std::string MetricSample::ToString() const {
+  if (type_ == CRASH) {
+    return base::StringPrintf("crash%c%s%c",
+                              '\0',
+                              name().c_str(),
+                              '\0');
+  }
+  if (type_ == SPARSE_HISTOGRAM) {
+    return base::StringPrintf("sparsehistogram%c%s %d%c",
+                              '\0',
+                              name().c_str(),
+                              sample_,
+                              '\0');
+  }
+  if (type_ == LINEAR_HISTOGRAM) {
+    return base::StringPrintf("linearhistogram%c%s %d %d%c",
+                              '\0',
+                              name().c_str(),
+                              sample_,
+                              max_,
+                              '\0');
+  }
+  if (type_ == HISTOGRAM) {
+    return base::StringPrintf("histogram%c%s %d %d %d %d%c",
+                              '\0',
+                              name().c_str(),
+                              sample_,
+                              min_,
+                              max_,
+                              bucket_count_,
+                              '\0');
+  }
+  // The type can only be USER_ACTION.
+  CHECK_EQ(type_, USER_ACTION);
+  return base::StringPrintf("useraction%c%s%c", '\0', name().c_str(), '\0');
+}
+
+int MetricSample::sample() const {
+  CHECK_NE(type_, USER_ACTION);
+  CHECK_NE(type_, CRASH);
+  return sample_;
+}
+
+int MetricSample::min() const {
+  CHECK_EQ(type_, HISTOGRAM);
+  return min_;
+}
+
+int MetricSample::max() const {
+  CHECK_NE(type_, CRASH);
+  CHECK_NE(type_, USER_ACTION);
+  CHECK_NE(type_, SPARSE_HISTOGRAM);
+  return max_;
+}
+
+int MetricSample::bucket_count() const {
+  CHECK_EQ(type_, HISTOGRAM);
+  return bucket_count_;
+}
+
+// static
+std::unique_ptr<MetricSample> MetricSample::CrashSample(
+    const std::string& crash_name) {
+  return std::unique_ptr<MetricSample>(
+      new MetricSample(CRASH, crash_name, 0, 0, 0, 0));
+}
+
+// static
+std::unique_ptr<MetricSample> MetricSample::HistogramSample(
+    const std::string& histogram_name,
+    int sample,
+    int min,
+    int max,
+    int bucket_count) {
+  return std::unique_ptr<MetricSample>(new MetricSample(
+      HISTOGRAM, histogram_name, sample, min, max, bucket_count));
+}
+
+// static
+std::unique_ptr<MetricSample> MetricSample::ParseHistogram(
+    const std::string& serialized_histogram) {
+  std::vector<base::StringPiece> parts = base::SplitStringPiece(
+      serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+
+  if (parts.size() != 5)
+    return std::unique_ptr<MetricSample>();
+  int sample, min, max, bucket_count;
+  if (parts[0].empty() || !base::StringToInt(parts[1], &sample) ||
+      !base::StringToInt(parts[2], &min) ||
+      !base::StringToInt(parts[3], &max) ||
+      !base::StringToInt(parts[4], &bucket_count)) {
+    return std::unique_ptr<MetricSample>();
+  }
+
+  return HistogramSample(parts[0].as_string(), sample, min, max, bucket_count);
+}
+
+// static
+std::unique_ptr<MetricSample> MetricSample::SparseHistogramSample(
+    const std::string& histogram_name,
+    int sample) {
+  return std::unique_ptr<MetricSample>(
+      new MetricSample(SPARSE_HISTOGRAM, histogram_name, sample, 0, 0, 0));
+}
+
+// static
+std::unique_ptr<MetricSample> MetricSample::ParseSparseHistogram(
+    const std::string& serialized_histogram) {
+  std::vector<base::StringPiece> parts = base::SplitStringPiece(
+      serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  if (parts.size() != 2)
+    return std::unique_ptr<MetricSample>();
+  int sample;
+  if (parts[0].empty() || !base::StringToInt(parts[1], &sample))
+    return std::unique_ptr<MetricSample>();
+
+  return SparseHistogramSample(parts[0].as_string(), sample);
+}
+
+// static
+std::unique_ptr<MetricSample> MetricSample::LinearHistogramSample(
+    const std::string& histogram_name,
+    int sample,
+    int max) {
+  return std::unique_ptr<MetricSample>(
+      new MetricSample(LINEAR_HISTOGRAM, histogram_name, sample, 0, max, 0));
+}
+
+// static
+std::unique_ptr<MetricSample> MetricSample::ParseLinearHistogram(
+    const std::string& serialized_histogram) {
+  std::vector<base::StringPiece> parts = base::SplitStringPiece(
+      serialized_histogram, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  int sample, max;
+  if (parts.size() != 3)
+    return std::unique_ptr<MetricSample>();
+  if (parts[0].empty() || !base::StringToInt(parts[1], &sample) ||
+      !base::StringToInt(parts[2], &max)) {
+    return std::unique_ptr<MetricSample>();
+  }
+
+  return LinearHistogramSample(parts[0].as_string(), sample, max);
+}
+
+// static
+std::unique_ptr<MetricSample> MetricSample::UserActionSample(
+    const std::string& action_name) {
+  return std::unique_ptr<MetricSample>(
+      new MetricSample(USER_ACTION, action_name, 0, 0, 0, 0));
+}
+
+bool MetricSample::IsEqual(const MetricSample& metric) {
+  return type_ == metric.type_ && name_ == metric.name_ &&
+         sample_ == metric.sample_ && min_ == metric.min_ &&
+         max_ == metric.max_ && bucket_count_ == metric.bucket_count_;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/serialization/metric_sample.h b/components/metrics/serialization/metric_sample.h
new file mode 100644
index 0000000..5a64c41
--- /dev/null
+++ b/components/metrics/serialization/metric_sample.h
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_SERIALIZATION_METRIC_SAMPLE_H_
+#define COMPONENTS_METRICS_SERIALIZATION_METRIC_SAMPLE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+
+namespace metrics {
+
+// This class is used by libmetrics (ChromeOS) to serialize
+// and deserialize measurements to send them to a metrics sending service.
+// It is meant to be a simple container with serialization functions.
+class MetricSample {
+ public:
+  // Types of metric sample used.
+  enum SampleType {
+    CRASH,
+    HISTOGRAM,
+    LINEAR_HISTOGRAM,
+    SPARSE_HISTOGRAM,
+    USER_ACTION
+  };
+
+  ~MetricSample();
+
+  // Returns true if the sample is valid (can be serialized without ambiguity).
+  //
+  // This function should be used to filter bad samples before serializing them.
+  bool IsValid() const;
+
+  // Getters for type and name. All types of metrics have these so we do not
+  // need to check the type.
+  SampleType type() const { return type_; }
+  const std::string& name() const { return name_; }
+
+  // Getters for sample, min, max, bucket_count.
+  // Check the metric type to make sure the request make sense. (ex: a crash
+  // sample does not have a bucket_count so we crash if we call bucket_count()
+  // on it.)
+  int sample() const;
+  int min() const;
+  int max() const;
+  int bucket_count() const;
+
+  // Returns a serialized version of the sample.
+  //
+  // The serialized message for each type is:
+  // crash: crash\0|name_|\0
+  // user action: useraction\0|name_|\0
+  // histogram: histogram\0|name_| |sample_| |min_| |max_| |bucket_count_|\0
+  // sparsehistogram: sparsehistogram\0|name_| |sample_|\0
+  // linearhistogram: linearhistogram\0|name_| |sample_| |max_|\0
+  std::string ToString() const;
+
+  // Builds a crash sample.
+  static std::unique_ptr<MetricSample> CrashSample(
+      const std::string& crash_name);
+
+  // Builds a histogram sample.
+  static std::unique_ptr<MetricSample> HistogramSample(
+      const std::string& histogram_name,
+      int sample,
+      int min,
+      int max,
+      int bucket_count);
+  // Deserializes a histogram sample.
+  static std::unique_ptr<MetricSample> ParseHistogram(
+      const std::string& serialized);
+
+  // Builds a sparse histogram sample.
+  static std::unique_ptr<MetricSample> SparseHistogramSample(
+      const std::string& histogram_name,
+      int sample);
+  // Deserializes a sparse histogram sample.
+  static std::unique_ptr<MetricSample> ParseSparseHistogram(
+      const std::string& serialized);
+
+  // Builds a linear histogram sample.
+  static std::unique_ptr<MetricSample>
+  LinearHistogramSample(const std::string& histogram_name, int sample, int max);
+  // Deserializes a linear histogram sample.
+  static std::unique_ptr<MetricSample> ParseLinearHistogram(
+      const std::string& serialized);
+
+  // Builds a user action sample.
+  static std::unique_ptr<MetricSample> UserActionSample(
+      const std::string& action_name);
+
+  // Returns true if sample and this object represent the same sample (type,
+  // name, sample, min, max, bucket_count match).
+  bool IsEqual(const MetricSample& sample);
+
+ private:
+  MetricSample(SampleType sample_type,
+               const std::string& metric_name,
+               const int sample,
+               const int min,
+               const int max,
+               const int bucket_count);
+
+  const SampleType type_;
+  const std::string name_;
+  const int sample_;
+  const int min_;
+  const int max_;
+  const int bucket_count_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricSample);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_SERIALIZATION_METRIC_SAMPLE_H_
diff --git a/components/metrics/serialization/serialization_utils.cc b/components/metrics/serialization/serialization_utils.cc
new file mode 100644
index 0000000..7214b5d
--- /dev/null
+++ b/components/metrics/serialization/serialization_utils.cc
@@ -0,0 +1,219 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/serialization/serialization_utils.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <sys/file.h>
+
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "components/metrics/serialization/metric_sample.h"
+
+#define READ_WRITE_ALL_FILE_FLAGS \
+  (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
+
+namespace metrics {
+namespace {
+
+// Reads the next message from |file_descriptor| into |message|.
+//
+// |message| will be set to the empty string if no message could be read (EOF)
+// or the message was badly constructed.
+//
+// Returns false if no message can be read from this file anymore (EOF or
+// unrecoverable error).
+bool ReadMessage(int fd, std::string* message) {
+  CHECK(message);
+
+  int result;
+  int32_t message_size;
+  const int32_t message_header_size = sizeof(message_size);
+  // The file containing the metrics does not leave the device so the writer and
+  // the reader will always have the same endianness.
+  result = HANDLE_EINTR(read(fd, &message_size, message_header_size));
+  if (result < 0) {
+    DPLOG(ERROR) << "reading metrics message header";
+    return false;
+  }
+  if (result == 0) {
+    // This indicates a normal EOF.
+    return false;
+  }
+  if (result < message_header_size) {
+    DLOG(ERROR) << "bad read size " << result << ", expecting "
+                << sizeof(message_size);
+    return false;
+  }
+
+  // kMessageMaxLength applies to the entire message: the 4-byte
+  // length field and the content.
+  if (message_size > SerializationUtils::kMessageMaxLength) {
+    DLOG(ERROR) << "message too long : " << message_size;
+    if (HANDLE_EINTR(lseek(fd, message_size - 4, SEEK_CUR)) == -1) {
+      DLOG(ERROR) << "error while skipping message. abort";
+      return false;
+    }
+    // Badly formatted message was skipped. Treat the badly formatted sample as
+    // an empty sample.
+    message->clear();
+    return true;
+  }
+
+  if (message_size < message_header_size) {
+    DLOG(ERROR) << "message too short : " << message_size;
+    return false;
+  }
+
+  message_size -= message_header_size;  // The message size includes itself.
+  char buffer[SerializationUtils::kMessageMaxLength];
+  if (!base::ReadFromFD(fd, buffer, message_size)) {
+    DPLOG(ERROR) << "reading metrics message body";
+    return false;
+  }
+  *message = std::string(buffer, message_size);
+  return true;
+}
+
+}  // namespace
+
+std::unique_ptr<MetricSample> SerializationUtils::ParseSample(
+    const std::string& sample) {
+  if (sample.empty())
+    return std::unique_ptr<MetricSample>();
+
+  std::vector<std::string> parts = base::SplitString(
+      sample, std::string(1, '\0'),
+      base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  // We should have two null terminated strings so split should produce
+  // three chunks.
+  if (parts.size() != 3) {
+    DLOG(ERROR) << "splitting message on \\0 produced " << parts.size()
+                << " parts (expected 3)";
+    return std::unique_ptr<MetricSample>();
+  }
+  const std::string& name = parts[0];
+  const std::string& value = parts[1];
+
+  if (base::LowerCaseEqualsASCII(name, "crash"))
+    return MetricSample::CrashSample(value);
+  if (base::LowerCaseEqualsASCII(name, "histogram"))
+    return MetricSample::ParseHistogram(value);
+  if (base::LowerCaseEqualsASCII(name, "linearhistogram"))
+    return MetricSample::ParseLinearHistogram(value);
+  if (base::LowerCaseEqualsASCII(name, "sparsehistogram"))
+    return MetricSample::ParseSparseHistogram(value);
+  if (base::LowerCaseEqualsASCII(name, "useraction"))
+    return MetricSample::UserActionSample(value);
+  DLOG(ERROR) << "invalid event type: " << name << ", value: " << value;
+  return std::unique_ptr<MetricSample>();
+}
+
+void SerializationUtils::ReadAndTruncateMetricsFromFile(
+    const std::string& filename,
+    std::vector<std::unique_ptr<MetricSample>>* metrics) {
+  struct stat stat_buf;
+  int result;
+
+  result = stat(filename.c_str(), &stat_buf);
+  if (result < 0) {
+    if (errno != ENOENT)
+      DPLOG(ERROR) << "bad metrics file stat: " << filename;
+
+    // Nothing to collect---try later.
+    return;
+  }
+  if (stat_buf.st_size == 0) {
+    // Also nothing to collect.
+    return;
+  }
+  base::ScopedFD fd(open(filename.c_str(), O_RDWR));
+  if (fd.get() < 0) {
+    DPLOG(ERROR) << "cannot open: " << filename;
+    return;
+  }
+  result = flock(fd.get(), LOCK_EX);
+  if (result < 0) {
+    DPLOG(ERROR) << "cannot lock: " << filename;
+    return;
+  }
+
+  // This processes all messages in the log. When all messages are
+  // read and processed, or an error occurs, truncate the file to zero size.
+  for (;;) {
+    std::string message;
+
+    if (!ReadMessage(fd.get(), &message))
+      break;
+
+    std::unique_ptr<MetricSample> sample = ParseSample(message);
+    if (sample)
+      metrics->push_back(std::move(sample));
+  }
+
+  result = ftruncate(fd.get(), 0);
+  if (result < 0)
+    DPLOG(ERROR) << "truncate metrics log: " << filename;
+
+  result = flock(fd.get(), LOCK_UN);
+  if (result < 0)
+    DPLOG(ERROR) << "unlock metrics log: " << filename;
+}
+
+bool SerializationUtils::WriteMetricToFile(const MetricSample& sample,
+                                           const std::string& filename) {
+  if (!sample.IsValid())
+    return false;
+
+  base::ScopedFD file_descriptor(open(filename.c_str(),
+                                      O_WRONLY | O_APPEND | O_CREAT,
+                                      READ_WRITE_ALL_FILE_FLAGS));
+
+  if (file_descriptor.get() < 0) {
+    DPLOG(ERROR) << "error opening the file: " << filename;
+    return false;
+  }
+
+  fchmod(file_descriptor.get(), READ_WRITE_ALL_FILE_FLAGS);
+  // Grab a lock to avoid chrome truncating the file
+  // underneath us. Keep the file locked as briefly as possible.
+  // Freeing file_descriptor will close the file and and remove the lock.
+  if (HANDLE_EINTR(flock(file_descriptor.get(), LOCK_EX)) < 0) {
+    DPLOG(ERROR) << "error locking: " << filename;
+    return false;
+  }
+
+  std::string msg = sample.ToString();
+  int32_t size = msg.length() + sizeof(int32_t);
+  if (size > kMessageMaxLength) {
+    DPLOG(ERROR) << "cannot write message: too long: " << filename;
+    return false;
+  }
+
+  // The file containing the metrics samples will only be read by programs on
+  // the same device so we do not check endianness.
+  if (!base::WriteFileDescriptor(file_descriptor.get(),
+                                 reinterpret_cast<char*>(&size),
+                                 sizeof(size))) {
+    DPLOG(ERROR) << "error writing message length: " << filename;
+    return false;
+  }
+
+  if (!base::WriteFileDescriptor(
+          file_descriptor.get(), msg.c_str(), msg.size())) {
+    DPLOG(ERROR) << "error writing message: " << filename;
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/serialization/serialization_utils.h b/components/metrics/serialization/serialization_utils.h
new file mode 100644
index 0000000..c741cb2
--- /dev/null
+++ b/components/metrics/serialization/serialization_utils.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
+#define COMPONENTS_METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace metrics {
+
+class MetricSample;
+
+// Metrics helpers to serialize and deserialize metrics collected by
+// ChromeOS.
+namespace SerializationUtils {
+
+// Deserializes a sample passed as a string and return a sample.
+// The return value will either be a scoped_ptr to a Metric sample (if the
+// deserialization was successful) or a NULL scoped_ptr.
+std::unique_ptr<MetricSample> ParseSample(const std::string& sample);
+
+// Reads all samples from a file and truncate the file when done.
+void ReadAndTruncateMetricsFromFile(
+    const std::string& filename,
+    std::vector<std::unique_ptr<MetricSample>>* metrics);
+
+// Serializes a sample and write it to filename.
+// The format for the message is:
+//  message_size, serialized_message
+// where
+//  * message_size is the total length of the message (message_size +
+//    serialized_message) on 4 bytes
+//  * serialized_message is the serialized version of sample (using ToString)
+//
+//  NB: the file will never leave the device so message_size will be written
+//  with the architecture's endianness.
+bool WriteMetricToFile(const MetricSample& sample, const std::string& filename);
+
+// Maximum length of a serialized message
+static const int kMessageMaxLength = 1024;
+
+}  // namespace SerializationUtils
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_SERIALIZATION_SERIALIZATION_UTILS_H_
diff --git a/components/metrics/serialization/serialization_utils_unittest.cc b/components/metrics/serialization/serialization_utils_unittest.cc
new file mode 100644
index 0000000..5685a1f
--- /dev/null
+++ b/components/metrics/serialization/serialization_utils_unittest.cc
@@ -0,0 +1,172 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/serialization/serialization_utils.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "components/metrics/serialization/metric_sample.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace {
+
+class SerializationUtilsTest : public testing::Test {
+ protected:
+  SerializationUtilsTest() {
+    bool success = temporary_dir.CreateUniqueTempDir();
+    if (success) {
+      base::FilePath dir_path = temporary_dir.GetPath();
+      filename = dir_path.value() + "chromeossampletest";
+      filepath = base::FilePath(filename);
+    }
+  }
+
+  void SetUp() override { base::DeleteFile(filepath, false); }
+
+  void TestSerialization(MetricSample* sample) {
+    std::string serialized(sample->ToString());
+    ASSERT_EQ('\0', serialized.back());
+    std::unique_ptr<MetricSample> deserialized =
+        SerializationUtils::ParseSample(serialized);
+    ASSERT_TRUE(deserialized);
+    EXPECT_TRUE(sample->IsEqual(*deserialized.get()));
+  }
+
+  std::string filename;
+  base::ScopedTempDir temporary_dir;
+  base::FilePath filepath;
+};
+
+TEST_F(SerializationUtilsTest, CrashSerializeTest) {
+  TestSerialization(MetricSample::CrashSample("test").get());
+}
+
+TEST_F(SerializationUtilsTest, HistogramSerializeTest) {
+  TestSerialization(
+      MetricSample::HistogramSample("myhist", 13, 1, 100, 10).get());
+}
+
+TEST_F(SerializationUtilsTest, LinearSerializeTest) {
+  TestSerialization(
+      MetricSample::LinearHistogramSample("linearhist", 12, 30).get());
+}
+
+TEST_F(SerializationUtilsTest, SparseSerializeTest) {
+  TestSerialization(MetricSample::SparseHistogramSample("mysparse", 30).get());
+}
+
+TEST_F(SerializationUtilsTest, UserActionSerializeTest) {
+  TestSerialization(MetricSample::UserActionSample("myaction").get());
+}
+
+TEST_F(SerializationUtilsTest, IllegalNameAreFilteredTest) {
+  std::unique_ptr<MetricSample> sample1 =
+      MetricSample::SparseHistogramSample("no space", 10);
+  std::unique_ptr<MetricSample> sample2 = MetricSample::LinearHistogramSample(
+      base::StringPrintf("here%cbhe", '\0'), 1, 3);
+
+  EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample1.get(), filename));
+  EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample2.get(), filename));
+  int64_t size = 0;
+
+  ASSERT_TRUE(!PathExists(filepath) || base::GetFileSize(filepath, &size));
+
+  EXPECT_EQ(0, size);
+}
+
+TEST_F(SerializationUtilsTest, BadInputIsCaughtTest) {
+  std::string input(
+      base::StringPrintf("sparsehistogram%cname foo%c", '\0', '\0'));
+  EXPECT_EQ(nullptr, MetricSample::ParseSparseHistogram(input).get());
+}
+
+TEST_F(SerializationUtilsTest, MessageSeparatedByZero) {
+  std::unique_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash");
+
+  SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+  int64_t size = 0;
+  ASSERT_TRUE(base::GetFileSize(filepath, &size));
+  // 4 bytes for the size
+  // 5 bytes for crash
+  // 7 bytes for mycrash
+  // 2 bytes for the \0
+  // -> total of 18
+  EXPECT_EQ(size, 18);
+}
+
+TEST_F(SerializationUtilsTest, MessagesTooLongAreDiscardedTest) {
+  // Creates a message that is bigger than the maximum allowed size.
+  // As we are adding extra character (crash, \0s, etc), if the name is
+  // kMessageMaxLength long, it will be too long.
+  std::string name(SerializationUtils::kMessageMaxLength, 'c');
+
+  std::unique_ptr<MetricSample> crash = MetricSample::CrashSample(name);
+  EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*crash.get(), filename));
+  int64_t size = 0;
+  ASSERT_TRUE(base::GetFileSize(filepath, &size));
+  EXPECT_EQ(0, size);
+}
+
+TEST_F(SerializationUtilsTest, ReadLongMessageTest) {
+  base::File test_file(filepath,
+                       base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND);
+  std::string message(SerializationUtils::kMessageMaxLength + 1, 'c');
+
+  int32_t message_size = message.length() + sizeof(int32_t);
+  test_file.WriteAtCurrentPos(reinterpret_cast<const char*>(&message_size),
+                              sizeof(message_size));
+  test_file.WriteAtCurrentPos(message.c_str(), message.length());
+  test_file.Close();
+
+  std::unique_ptr<MetricSample> crash = MetricSample::CrashSample("test");
+  SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+
+  std::vector<std::unique_ptr<MetricSample>> samples;
+  SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &samples);
+  ASSERT_EQ(size_t(1), samples.size());
+  ASSERT_TRUE(samples[0].get() != nullptr);
+  EXPECT_TRUE(crash->IsEqual(*samples[0]));
+}
+
+TEST_F(SerializationUtilsTest, WriteReadTest) {
+  std::unique_ptr<MetricSample> hist =
+      MetricSample::HistogramSample("myhist", 1, 2, 3, 4);
+  std::unique_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash");
+  std::unique_ptr<MetricSample> lhist =
+      MetricSample::LinearHistogramSample("linear", 1, 10);
+  std::unique_ptr<MetricSample> shist =
+      MetricSample::SparseHistogramSample("mysparse", 30);
+  std::unique_ptr<MetricSample> action =
+      MetricSample::UserActionSample("myaction");
+
+  SerializationUtils::WriteMetricToFile(*hist.get(), filename);
+  SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+  SerializationUtils::WriteMetricToFile(*lhist.get(), filename);
+  SerializationUtils::WriteMetricToFile(*shist.get(), filename);
+  SerializationUtils::WriteMetricToFile(*action.get(), filename);
+  std::vector<std::unique_ptr<MetricSample>> vect;
+  SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &vect);
+  ASSERT_EQ(vect.size(), size_t(5));
+  for (auto& sample : vect) {
+    ASSERT_NE(nullptr, sample.get());
+  }
+  EXPECT_TRUE(hist->IsEqual(*vect[0]));
+  EXPECT_TRUE(crash->IsEqual(*vect[1]));
+  EXPECT_TRUE(lhist->IsEqual(*vect[2]));
+  EXPECT_TRUE(shist->IsEqual(*vect[3]));
+  EXPECT_TRUE(action->IsEqual(*vect[4]));
+
+  int64_t size = 0;
+  ASSERT_TRUE(base::GetFileSize(filepath, &size));
+  ASSERT_EQ(0, size);
+}
+
+}  // namespace
+}  // namespace metrics
diff --git a/components/metrics/single_sample_metrics.cc b/components/metrics/single_sample_metrics.cc
new file mode 100644
index 0000000..201ef73
--- /dev/null
+++ b/components/metrics/single_sample_metrics.cc
@@ -0,0 +1,85 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/single_sample_metrics.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/metrics/single_sample_metrics.h"
+#include "base/threading/thread_checker.h"
+#include "components/metrics/single_sample_metrics_factory_impl.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+
+namespace metrics {
+
+namespace {
+
+class MojoSingleSampleMetric : public mojom::SingleSampleMetric {
+ public:
+  MojoSingleSampleMetric(const std::string& histogram_name,
+                         base::HistogramBase::Sample min,
+                         base::HistogramBase::Sample max,
+                         uint32_t bucket_count,
+                         int32_t flags)
+      : metric_(histogram_name, min, max, bucket_count, flags) {}
+  ~MojoSingleSampleMetric() override {}
+
+ private:
+  // mojom::SingleSampleMetric:
+  void SetSample(base::HistogramBase::Sample sample) override {
+    metric_.SetSample(sample);
+  }
+
+  base::DefaultSingleSampleMetric metric_;
+
+  DISALLOW_COPY_AND_ASSIGN(MojoSingleSampleMetric);
+};
+
+class MojoSingleSampleMetricsProvider
+    : public mojom::SingleSampleMetricsProvider {
+ public:
+  MojoSingleSampleMetricsProvider() {}
+  ~MojoSingleSampleMetricsProvider() override {
+    DCHECK(thread_checker_.CalledOnValidThread());
+  }
+
+ private:
+  // mojom::SingleSampleMetricsProvider:
+  void AcquireSingleSampleMetric(
+      const std::string& histogram_name,
+      base::HistogramBase::Sample min,
+      base::HistogramBase::Sample max,
+      uint32_t bucket_count,
+      int32_t flags,
+      mojom::SingleSampleMetricRequest request) override {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    mojo::MakeStrongBinding(std::make_unique<MojoSingleSampleMetric>(
+                                histogram_name, min, max, bucket_count, flags),
+                            std::move(request));
+  }
+
+  // Providers must be created, used on, and destroyed on the same thread.
+  base::ThreadChecker thread_checker_;
+
+  DISALLOW_COPY_AND_ASSIGN(MojoSingleSampleMetricsProvider);
+};
+
+}  // namespace
+
+// static
+void InitializeSingleSampleMetricsFactory(CreateProviderCB create_provider_cb) {
+  base::SingleSampleMetricsFactory::SetFactory(
+      std::make_unique<SingleSampleMetricsFactoryImpl>(
+          std::move(create_provider_cb)));
+}
+
+// static
+void CreateSingleSampleMetricsProvider(
+    mojom::SingleSampleMetricsProviderRequest request) {
+  mojo::MakeStrongBinding(std::make_unique<MojoSingleSampleMetricsProvider>(),
+                          std::move(request));
+}
+
+}  // namespace metrics
diff --git a/components/metrics/single_sample_metrics.h b/components/metrics/single_sample_metrics.h
new file mode 100644
index 0000000..989f6a2
--- /dev/null
+++ b/components/metrics/single_sample_metrics.h
@@ -0,0 +1,41 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_SINGLE_SAMPLE_METRICS_H_
+#define COMPONENTS_METRICS_SINGLE_SAMPLE_METRICS_H_
+
+#include "base/callback.h"
+#include "components/metrics/public/interfaces/single_sample_metrics.mojom.h"
+
+namespace metrics {
+
+using CreateProviderCB =
+    base::RepeatingCallback<void(mojom::SingleSampleMetricsProviderRequest)>;
+
+// Initializes and sets the base::SingleSampleMetricsFactory for the current
+// process. |create_provider_cb| is used to create provider instances per each
+// thread that the factory is used on; this is necessary since the underlying
+// providers must only be used on the same thread as construction.
+//
+// We use a callback here to avoid taking additional DEPS on content and a
+// service_manager::Connector() for simplicity and to avoid the need for
+// using the service test harness in metrics unittests.
+//
+// Typically this is called in the process where termination may occur without
+// warning; e.g. perhaps a renderer process.
+extern void InitializeSingleSampleMetricsFactory(
+    CreateProviderCB create_provider_cb);
+
+// Creates a mojom::SingleSampleMetricsProvider capable of vending single sample
+// metrics attached to a mojo pipe.
+//
+// Typically this is given to a service_manager::BinderRegistry in the process
+// that has a deterministic shutdown path and which serves as a stable endpoint
+// for the factory created by the above initialize method in another process.
+extern void CreateSingleSampleMetricsProvider(
+    mojom::SingleSampleMetricsProviderRequest request);
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_SINGLE_SAMPLE_METRICS_H_
diff --git a/components/metrics/single_sample_metrics_factory_impl.cc b/components/metrics/single_sample_metrics_factory_impl.cc
new file mode 100644
index 0000000..4ab48f8
--- /dev/null
+++ b/components/metrics/single_sample_metrics_factory_impl.cc
@@ -0,0 +1,92 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/single_sample_metrics_factory_impl.h"
+
+#include <memory>
+
+#include "base/threading/thread_checker.h"
+
+namespace metrics {
+
+namespace {
+
+class SingleSampleMetricImpl : public base::SingleSampleMetric {
+ public:
+  SingleSampleMetricImpl(mojom::SingleSampleMetricPtr metric)
+      : metric_(std::move(metric)) {}
+
+  ~SingleSampleMetricImpl() override {
+    DCHECK(thread_checker_.CalledOnValidThread());
+  }
+
+  void SetSample(base::HistogramBase::Sample sample) override {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    metric_->SetSample(sample);
+  }
+
+ private:
+  base::ThreadChecker thread_checker_;
+  mojom::SingleSampleMetricPtr metric_;
+
+  DISALLOW_COPY_AND_ASSIGN(SingleSampleMetricImpl);
+};
+
+}  // namespace
+
+SingleSampleMetricsFactoryImpl::SingleSampleMetricsFactoryImpl(
+    CreateProviderCB create_provider_cb)
+    : create_provider_cb_(std::move(create_provider_cb)) {}
+
+SingleSampleMetricsFactoryImpl::~SingleSampleMetricsFactoryImpl() {}
+
+std::unique_ptr<base::SingleSampleMetric>
+SingleSampleMetricsFactoryImpl::CreateCustomCountsMetric(
+    const std::string& histogram_name,
+    base::HistogramBase::Sample min,
+    base::HistogramBase::Sample max,
+    uint32_t bucket_count) {
+  return CreateMetric(histogram_name, min, max, bucket_count,
+                      base::HistogramBase::kUmaTargetedHistogramFlag);
+}
+
+void SingleSampleMetricsFactoryImpl::DestroyProviderForTesting() {
+  if (auto* provider = provider_tls_.Get())
+    delete provider;
+  provider_tls_.Set(nullptr);
+}
+
+std::unique_ptr<base::SingleSampleMetric>
+SingleSampleMetricsFactoryImpl::CreateMetric(const std::string& histogram_name,
+                                             base::HistogramBase::Sample min,
+                                             base::HistogramBase::Sample max,
+                                             uint32_t bucket_count,
+                                             int32_t flags) {
+  mojom::SingleSampleMetricPtr metric;
+  GetProvider()->AcquireSingleSampleMetric(histogram_name, min, max,
+                                           bucket_count, flags,
+                                           mojo::MakeRequest(&metric));
+  return std::make_unique<SingleSampleMetricImpl>(std::move(metric));
+}
+
+mojom::SingleSampleMetricsProvider*
+SingleSampleMetricsFactoryImpl::GetProvider() {
+  // Check the current TLS slot to see if we have created a provider already for
+  // this thread.
+  if (auto* provider = provider_tls_.Get())
+    return provider->get();
+
+  // If not, create a new one which will persist until process shutdown and put
+  // it in the TLS slot for the current thread.
+  mojom::SingleSampleMetricsProviderPtr* provider =
+      new mojom::SingleSampleMetricsProviderPtr();
+  provider_tls_.Set(provider);
+
+  // Start the provider connection and return it; it won't be fully connected
+  // until later, but mojo will buffer all calls prior to completion.
+  create_provider_cb_.Run(mojo::MakeRequest(provider));
+  return provider->get();
+}
+
+}  // namespace metrics
diff --git a/components/metrics/single_sample_metrics_factory_impl.h b/components/metrics/single_sample_metrics_factory_impl.h
new file mode 100644
index 0000000..297324f
--- /dev/null
+++ b/components/metrics/single_sample_metrics_factory_impl.h
@@ -0,0 +1,71 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_SINGLE_VALUE_HISTOGRAM_FACTORY_IMPL_H_
+#define COMPONENTS_METRICS_SINGLE_VALUE_HISTOGRAM_FACTORY_IMPL_H_
+
+#include <string>
+
+#include "base/metrics/single_sample_metrics.h"
+#include "base/threading/thread_local.h"
+#include "components/metrics/public/interfaces/single_sample_metrics.mojom.h"
+#include "components/metrics/single_sample_metrics.h"
+
+namespace metrics {
+
+// SingleSampleMetricsFactory implementation for creating SingleSampleMetric
+// instances that communicate over mojo to instances in another process.
+//
+// Persistance outside of the current process allows these metrics to record a
+// sample even in the event of sudden process termination. As an example, this
+// is useful for garbage collected objects which may never get a chance to run
+// their destructors in the event of a fast shutdown event (process kill).
+class SingleSampleMetricsFactoryImpl : public base::SingleSampleMetricsFactory {
+ public:
+  // Constructs a factory capable of vending single sample metrics from any
+  // thread. |create_provider_cb| will be called from arbitrary threads to
+  // create providers as necessary; the callback must handle thread safety.
+  //
+  // We use a callback here to avoid taking additional DEPS on content and a
+  // service_manager::Connector() for simplicitly and to avoid the need for
+  // using the service test harness just for instantiating this class.
+  explicit SingleSampleMetricsFactoryImpl(CreateProviderCB create_provider_cb);
+  ~SingleSampleMetricsFactoryImpl() override;
+
+  // base::SingleSampleMetricsFactory:
+  std::unique_ptr<base::SingleSampleMetric> CreateCustomCountsMetric(
+      const std::string& histogram_name,
+      base::HistogramBase::Sample min,
+      base::HistogramBase::Sample max,
+      uint32_t bucket_count) override;
+
+  // Providers live forever in production, but tests should be kind and clean up
+  // after themselves to avoid tests trampling on one another. Destroys the
+  // provider in the TLS slot for the calling thread.
+  void DestroyProviderForTesting();
+
+ private:
+  // Creates a single sample metric.
+  std::unique_ptr<base::SingleSampleMetric> CreateMetric(
+      const std::string& histogram_name,
+      base::HistogramBase::Sample min,
+      base::HistogramBase::Sample max,
+      uint32_t bucket_count,
+      int32_t flags);
+
+  // Gets the SingleSampleMetricsProvider for the current thread. If none
+  // exists, then a new instance is created and set in the TLS slot.
+  mojom::SingleSampleMetricsProvider* GetProvider();
+
+  CreateProviderCB create_provider_cb_;
+
+  // Per thread storage slot for the mojo provider.
+  base::ThreadLocalPointer<mojom::SingleSampleMetricsProviderPtr> provider_tls_;
+
+  DISALLOW_COPY_AND_ASSIGN(SingleSampleMetricsFactoryImpl);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_SINGLE_VALUE_HISTOGRAM_FACTORY_IMPL_H_
diff --git a/components/metrics/single_sample_metrics_factory_impl_unittest.cc b/components/metrics/single_sample_metrics_factory_impl_unittest.cc
new file mode 100644
index 0000000..7362085
--- /dev/null
+++ b/components/metrics/single_sample_metrics_factory_impl_unittest.cc
@@ -0,0 +1,184 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/single_sample_metrics_factory_impl.h"
+
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/dummy_histogram.h"
+#include "base/run_loop.h"
+#include "base/test/gtest_util.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/threading/thread.h"
+#include "components/metrics/single_sample_metrics.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+const base::HistogramBase::Sample kMin = 1;
+const base::HistogramBase::Sample kMax = 10;
+const uint32_t kBucketCount = 10;
+const char kMetricName[] = "Single.Sample.Metric";
+
+class SingleSampleMetricsFactoryImplTest : public testing::Test {
+ public:
+  SingleSampleMetricsFactoryImplTest() : thread_("TestThread") {
+    InitializeSingleSampleMetricsFactory(
+        base::BindRepeating(&SingleSampleMetricsFactoryImplTest::CreateProvider,
+                            base::Unretained(this)));
+    factory_ = static_cast<SingleSampleMetricsFactoryImpl*>(
+        base::SingleSampleMetricsFactory::Get());
+  }
+
+  ~SingleSampleMetricsFactoryImplTest() override {
+    factory_->DestroyProviderForTesting();
+    if (thread_.IsRunning())
+      ShutdownThread();
+    base::SingleSampleMetricsFactory::DeleteFactoryForTesting();
+  }
+
+ protected:
+  void StartThread() { ASSERT_TRUE(thread_.Start()); }
+
+  void ShutdownThread() {
+    thread_.task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &SingleSampleMetricsFactoryImpl::DestroyProviderForTesting,
+            base::Unretained(factory_)));
+    thread_.Stop();
+  }
+
+  void CreateProvider(mojom::SingleSampleMetricsProviderRequest request) {
+    CreateSingleSampleMetricsProvider(std::move(request));
+    provider_count_++;
+  }
+
+  std::unique_ptr<base::SingleSampleMetric> CreateMetricOnThread() {
+    std::unique_ptr<base::SingleSampleMetric> metric;
+    base::RunLoop run_loop;
+    thread_.task_runner()->PostTaskAndReply(
+        FROM_HERE,
+        base::BindOnce(
+            &SingleSampleMetricsFactoryImplTest::CreateAndStoreMetric,
+            base::Unretained(this), &metric),
+        run_loop.QuitClosure());
+    run_loop.Run();
+    return metric;
+  }
+
+  void CreateAndStoreMetric(std::unique_ptr<base::SingleSampleMetric>* metric) {
+    *metric = factory_->CreateCustomCountsMetric(kMetricName, kMin, kMax,
+                                                 kBucketCount);
+  }
+
+  base::MessageLoop message_looqp_;
+  SingleSampleMetricsFactoryImpl* factory_;
+  base::Thread thread_;
+  size_t provider_count_ = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SingleSampleMetricsFactoryImplTest);
+};
+
+}  // namespace
+
+TEST_F(SingleSampleMetricsFactoryImplTest, SingleProvider) {
+  std::unique_ptr<base::SingleSampleMetric> metric1 =
+      factory_->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount);
+
+  std::unique_ptr<base::SingleSampleMetric> metric2 =
+      factory_->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount);
+
+  // Verify that only a single provider is created for multiple metrics.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1u, provider_count_);
+}
+
+TEST_F(SingleSampleMetricsFactoryImplTest, DoesNothing) {
+  base::HistogramTester tester;
+
+  std::unique_ptr<base::SingleSampleMetric> metric =
+      factory_->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount);
+  metric.reset();
+
+  // Verify that no sample is recorded if SetSample() is never called.
+  base::RunLoop().RunUntilIdle();
+  tester.ExpectTotalCount(kMetricName, 0);
+}
+
+TEST_F(SingleSampleMetricsFactoryImplTest, DefaultSingleSampleMetricWithValue) {
+  base::HistogramTester tester;
+  std::unique_ptr<base::SingleSampleMetric> metric =
+      factory_->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount);
+
+  const base::HistogramBase::Sample kLastSample = 9;
+  metric->SetSample(1);
+  metric->SetSample(3);
+  metric->SetSample(5);
+  metric->SetSample(kLastSample);
+  metric.reset();
+
+  // Verify only the last sample sent to SetSample() is recorded.
+  base::RunLoop().RunUntilIdle();
+  tester.ExpectUniqueSample(kMetricName, kLastSample, 1);
+
+  // Verify construction implicitly by requesting a histogram with the same
+  // parameters; this test relies on the fact that histogram objects are unique
+  // per name. Different parameters will result in a Dummy histogram returned.
+  EXPECT_EQ(base::DummyHistogram::GetInstance(),
+            base::Histogram::FactoryGet(kMetricName, 1, 3, 3,
+                                        base::HistogramBase::kNoFlags));
+  EXPECT_NE(base::DummyHistogram::GetInstance(),
+            base::Histogram::FactoryGet(
+                kMetricName, kMin, kMax, kBucketCount,
+                base::HistogramBase::kUmaTargetedHistogramFlag));
+}
+
+TEST_F(SingleSampleMetricsFactoryImplTest, MultithreadedMetrics) {
+  base::HistogramTester tester;
+  std::unique_ptr<base::SingleSampleMetric> metric =
+      factory_->CreateCustomCountsMetric(kMetricName, kMin, kMax, kBucketCount);
+  EXPECT_EQ(1u, provider_count_);
+
+  StartThread();
+
+  std::unique_ptr<base::SingleSampleMetric> threaded_metric =
+      CreateMetricOnThread();
+  ASSERT_TRUE(threaded_metric);
+
+  // A second provider should be created to handle requests on our new thread.
+  EXPECT_EQ(2u, provider_count_);
+
+  // Calls from the wrong thread should DCHECK.
+  EXPECT_DCHECK_DEATH(threaded_metric->SetSample(5));
+  EXPECT_DCHECK_DEATH(threaded_metric.reset());
+
+  // Test that samples are set on each thread correctly.
+  const base::HistogramBase::Sample kSample = 7;
+
+  {
+    metric->SetSample(kSample);
+
+    base::RunLoop run_loop;
+    thread_.task_runner()->PostTaskAndReply(
+        FROM_HERE,
+        base::BindOnce(&base::SingleSampleMetric::SetSample,
+                       base::Unretained(threaded_metric.get()), kSample),
+        run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
+  // Release metrics and shutdown thread to ensure destruction completes.
+  thread_.task_runner()->DeleteSoon(FROM_HERE, threaded_metric.release());
+  ShutdownThread();
+
+  metric.reset();
+  base::RunLoop().RunUntilIdle();
+
+  tester.ExpectUniqueSample(kMetricName, kSample, 2);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/stability_metrics_helper.cc b/components/metrics/stability_metrics_helper.cc
new file mode 100644
index 0000000..517fc71
--- /dev/null
+++ b/components/metrics/stability_metrics_helper.cc
@@ -0,0 +1,322 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/stability_metrics_helper.h"
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/user_metrics.h"
+#include "build/build_config.h"
+#include "build/buildflag.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/variations/hashing.h"
+#include "extensions/buildflags/buildflags.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+#if defined(OS_WIN)
+#include <windows.h>  // Needed for STATUS_* codes
+#endif
+
+#if defined(OS_CHROMEOS)
+#include "components/metrics/system_memory_stats_recorder.h"
+#endif
+
+namespace metrics {
+
+namespace {
+
+enum RendererType {
+  RENDERER_TYPE_RENDERER = 1,
+  RENDERER_TYPE_EXTENSION,
+  // NOTE: Add new action types only immediately above this line. Also,
+  // make sure the enum list in tools/metrics/histograms/histograms.xml is
+  // updated with any change in here.
+  RENDERER_TYPE_COUNT
+};
+
+// Converts an exit code into something that can be inserted into our
+// histograms (which expect non-negative numbers less than MAX_INT).
+int MapCrashExitCodeForHistogram(int exit_code) {
+#if defined(OS_WIN)
+  // Since |abs(STATUS_GUARD_PAGE_VIOLATION) == MAX_INT| it causes problems in
+  // histograms.cc. Solve this by remapping it to a smaller value, which
+  // hopefully doesn't conflict with other codes.
+  if (static_cast<DWORD>(exit_code) == STATUS_GUARD_PAGE_VIOLATION)
+    return 0x1FCF7EC3;  // Randomly picked number.
+#endif
+
+  return std::abs(exit_code);
+}
+
+void RecordChildKills(RendererType histogram_type) {
+  UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildKills",
+                            histogram_type, RENDERER_TYPE_COUNT);
+}
+
+// Macro for logging the age of a crashed process.
+//
+// Notes:
+// - IMPORTANT: When changing the constants below, please change the names of
+//   the histograms logged via UMA_HISTOGRAM_CRASHED_PROCESS_AGE.
+// - 99th percentile of Memory.Experimental.Renderer.Uptime hovers around 17h.
+// - |kCrashedProcessAgeMin| is as low as possible, so that we may with
+//   high-confidence categorize crashes that occur during early startup (e.g.
+//   crashes that end up with STATUS_DLL_INIT_FAILED or STATUS_DLL_NOT_FOUND).
+// - Note that even with just 50 buckets, we still get narrow and accurate
+//   buckets at the lower end: 0ms, 1ms, 2ms, 3ms, 4-5ms, 6-8ms, 9-12ms, ...
+constexpr auto kCrashedProcessAgeMin = base::TimeDelta::FromMilliseconds(1);
+constexpr auto kCrashedProcessAgeMax = base::TimeDelta::FromHours(48);
+constexpr uint32_t kCrashedProcessAgeCount = 50;
+#define UMA_HISTOGRAM_CRASHED_PROCESS_AGE(histogram_name, uptime)           \
+  UMA_HISTOGRAM_CUSTOM_TIMES(histogram_name, uptime, kCrashedProcessAgeMin, \
+                             kCrashedProcessAgeMax, kCrashedProcessAgeCount)
+
+}  // namespace
+
+StabilityMetricsHelper::StabilityMetricsHelper(PrefService* local_state)
+    : local_state_(local_state) {
+  DCHECK(local_state_);
+}
+
+StabilityMetricsHelper::~StabilityMetricsHelper() {}
+
+void StabilityMetricsHelper::ProvideStabilityMetrics(
+    SystemProfileProto* system_profile_proto) {
+  SystemProfileProto_Stability* stability_proto =
+      system_profile_proto->mutable_stability();
+
+  int count = local_state_->GetInteger(prefs::kStabilityPageLoadCount);
+  if (count) {
+    stability_proto->set_page_load_count(count);
+    local_state_->SetInteger(prefs::kStabilityPageLoadCount, 0);
+  }
+
+  count = local_state_->GetInteger(prefs::kStabilityChildProcessCrashCount);
+  if (count) {
+    stability_proto->set_child_process_crash_count(count);
+    local_state_->SetInteger(prefs::kStabilityChildProcessCrashCount, 0);
+  }
+
+  count = local_state_->GetInteger(prefs::kStabilityRendererCrashCount);
+  if (count) {
+    stability_proto->set_renderer_crash_count(count);
+    local_state_->SetInteger(prefs::kStabilityRendererCrashCount, 0);
+  }
+
+  count = local_state_->GetInteger(prefs::kStabilityRendererFailedLaunchCount);
+  if (count) {
+    stability_proto->set_renderer_failed_launch_count(count);
+    local_state_->SetInteger(prefs::kStabilityRendererFailedLaunchCount, 0);
+  }
+
+  count = local_state_->GetInteger(prefs::kStabilityRendererLaunchCount);
+  if (count) {
+    stability_proto->set_renderer_launch_count(count);
+    local_state_->SetInteger(prefs::kStabilityRendererLaunchCount, 0);
+  }
+
+  count =
+      local_state_->GetInteger(prefs::kStabilityExtensionRendererCrashCount);
+  if (count) {
+    stability_proto->set_extension_renderer_crash_count(count);
+    local_state_->SetInteger(prefs::kStabilityExtensionRendererCrashCount, 0);
+  }
+
+  count = local_state_->GetInteger(
+      prefs::kStabilityExtensionRendererFailedLaunchCount);
+  if (count) {
+    stability_proto->set_extension_renderer_failed_launch_count(count);
+    local_state_->SetInteger(
+        prefs::kStabilityExtensionRendererFailedLaunchCount, 0);
+  }
+
+  count = local_state_->GetInteger(prefs::kStabilityRendererHangCount);
+  if (count) {
+    stability_proto->set_renderer_hang_count(count);
+    local_state_->SetInteger(prefs::kStabilityRendererHangCount, 0);
+  }
+
+  count =
+      local_state_->GetInteger(prefs::kStabilityExtensionRendererLaunchCount);
+  if (count) {
+    stability_proto->set_extension_renderer_launch_count(count);
+    local_state_->SetInteger(prefs::kStabilityExtensionRendererLaunchCount, 0);
+  }
+}
+
+void StabilityMetricsHelper::ClearSavedStabilityMetrics() {
+  // Clear all the prefs used in this class in UMA reports (which doesn't
+  // include |kUninstallMetricsPageLoadCount| as it's not sent up by UMA).
+  local_state_->SetInteger(prefs::kStabilityChildProcessCrashCount, 0);
+  local_state_->SetInteger(prefs::kStabilityExtensionRendererCrashCount, 0);
+  local_state_->SetInteger(prefs::kStabilityExtensionRendererFailedLaunchCount,
+                           0);
+  local_state_->SetInteger(prefs::kStabilityExtensionRendererLaunchCount, 0);
+  local_state_->SetInteger(prefs::kStabilityPageLoadCount, 0);
+  local_state_->SetInteger(prefs::kStabilityRendererCrashCount, 0);
+  local_state_->SetInteger(prefs::kStabilityRendererFailedLaunchCount, 0);
+  local_state_->SetInteger(prefs::kStabilityRendererHangCount, 0);
+  local_state_->SetInteger(prefs::kStabilityRendererLaunchCount, 0);
+}
+
+// static
+void StabilityMetricsHelper::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterIntegerPref(prefs::kStabilityChildProcessCrashCount, 0);
+  registry->RegisterIntegerPref(prefs::kStabilityExtensionRendererCrashCount,
+                                0);
+  registry->RegisterIntegerPref(
+      prefs::kStabilityExtensionRendererFailedLaunchCount, 0);
+  registry->RegisterIntegerPref(prefs::kStabilityExtensionRendererLaunchCount,
+                                0);
+  registry->RegisterIntegerPref(prefs::kStabilityPageLoadCount, 0);
+  registry->RegisterIntegerPref(prefs::kStabilityRendererCrashCount, 0);
+  registry->RegisterIntegerPref(prefs::kStabilityRendererFailedLaunchCount, 0);
+  registry->RegisterIntegerPref(prefs::kStabilityRendererHangCount, 0);
+  registry->RegisterIntegerPref(prefs::kStabilityRendererLaunchCount, 0);
+
+  registry->RegisterInt64Pref(prefs::kUninstallMetricsPageLoadCount, 0);
+}
+
+void StabilityMetricsHelper::IncreaseRendererCrashCount() {
+  IncrementPrefValue(prefs::kStabilityRendererCrashCount);
+}
+
+void StabilityMetricsHelper::BrowserUtilityProcessLaunched(
+    const std::string& metrics_name) {
+  uint32_t hash = variations::HashName(metrics_name);
+  base::UmaHistogramSparse("ChildProcess.Launched.UtilityProcessHash", hash);
+}
+
+void StabilityMetricsHelper::BrowserUtilityProcessCrashed(
+    const std::string& metrics_name,
+    int exit_code) {
+  // TODO(wfh): there doesn't appear to be a good way to log these exit_codes
+  // without adding something into the stability proto, so for now only log the
+  // crash and if the numbers are high enough, logging exit codes can be added
+  // later.
+  uint32_t hash = variations::HashName(metrics_name);
+  base::UmaHistogramSparse("ChildProcess.Crashed.UtilityProcessHash", hash);
+}
+
+void StabilityMetricsHelper::BrowserChildProcessCrashed() {
+  IncrementPrefValue(prefs::kStabilityChildProcessCrashCount);
+}
+
+void StabilityMetricsHelper::LogLoadStarted(bool is_incognito) {
+  base::RecordAction(base::UserMetricsAction("PageLoad"));
+  if (is_incognito)
+    base::RecordAction(base::UserMetricsAction("PageLoadInIncognito"));
+  IncrementPrefValue(prefs::kStabilityPageLoadCount);
+  IncrementLongPrefsValue(prefs::kUninstallMetricsPageLoadCount);
+  // We need to save the prefs, as page load count is a critical stat, and it
+  // might be lost due to a crash :-(.
+}
+
+void StabilityMetricsHelper::LogRendererCrash(
+    bool was_extension_process,
+    base::TerminationStatus status,
+    int exit_code,
+    base::Optional<base::TimeDelta> uptime) {
+  RendererType histogram_type =
+      was_extension_process ? RENDERER_TYPE_EXTENSION : RENDERER_TYPE_RENDERER;
+
+  switch (status) {
+    case base::TERMINATION_STATUS_NORMAL_TERMINATION:
+      break;
+    case base::TERMINATION_STATUS_PROCESS_CRASHED:
+    case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
+    case base::TERMINATION_STATUS_OOM:
+      if (was_extension_process) {
+#if !BUILDFLAG(ENABLE_EXTENSIONS)
+        NOTREACHED();
+#endif
+        IncrementPrefValue(prefs::kStabilityExtensionRendererCrashCount);
+
+        base::UmaHistogramSparse("CrashExitCodes.Extension",
+                                 MapCrashExitCodeForHistogram(exit_code));
+        if (uptime.has_value()) {
+          UMA_HISTOGRAM_CRASHED_PROCESS_AGE(
+              "Stability.CrashedProcessAge.Extension", uptime.value());
+        }
+      } else {
+        IncrementPrefValue(prefs::kStabilityRendererCrashCount);
+
+        base::UmaHistogramSparse("CrashExitCodes.Renderer",
+                                 MapCrashExitCodeForHistogram(exit_code));
+        if (uptime.has_value()) {
+          UMA_HISTOGRAM_CRASHED_PROCESS_AGE(
+              "Stability.CrashedProcessAge.Renderer", uptime.value());
+        }
+      }
+
+      UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildCrashes",
+                                histogram_type, RENDERER_TYPE_COUNT);
+      break;
+    case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
+      RecordChildKills(histogram_type);
+      break;
+#if defined(OS_ANDROID)
+    case base::TERMINATION_STATUS_OOM_PROTECTED:
+      // TODO(wfh): Check if this should be a Kill or a Crash on Android.
+      break;
+#endif
+#if defined(OS_CHROMEOS)
+    case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
+      RecordChildKills(histogram_type);
+      UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildKills.OOM",
+                                was_extension_process ? 2 : 1, 3);
+      RecordMemoryStats(was_extension_process
+                            ? RECORD_MEMORY_STATS_EXTENSIONS_OOM_KILLED
+                            : RECORD_MEMORY_STATS_CONTENTS_OOM_KILLED);
+      break;
+#endif
+    case base::TERMINATION_STATUS_STILL_RUNNING:
+      UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.DisconnectedAlive",
+                                histogram_type, RENDERER_TYPE_COUNT);
+      break;
+    case base::TERMINATION_STATUS_LAUNCH_FAILED:
+      UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildLaunchFailures",
+                                histogram_type, RENDERER_TYPE_COUNT);
+      base::UmaHistogramSparse(
+          "BrowserRenderProcessHost.ChildLaunchFailureCodes", exit_code);
+      if (was_extension_process)
+        IncrementPrefValue(prefs::kStabilityExtensionRendererFailedLaunchCount);
+      else
+        IncrementPrefValue(prefs::kStabilityRendererFailedLaunchCount);
+      break;
+    case base::TERMINATION_STATUS_MAX_ENUM:
+      NOTREACHED();
+      break;
+  }
+}
+
+void StabilityMetricsHelper::LogRendererLaunched(bool was_extension_process) {
+  if (was_extension_process)
+    IncrementPrefValue(prefs::kStabilityExtensionRendererLaunchCount);
+  else
+    IncrementPrefValue(prefs::kStabilityRendererLaunchCount);
+}
+
+void StabilityMetricsHelper::IncrementPrefValue(const char* path) {
+  int value = local_state_->GetInteger(path);
+  local_state_->SetInteger(path, value + 1);
+}
+
+void StabilityMetricsHelper::IncrementLongPrefsValue(const char* path) {
+  int64_t value = local_state_->GetInt64(path);
+  local_state_->SetInt64(path, value + 1);
+}
+
+void StabilityMetricsHelper::LogRendererHang() {
+  IncrementPrefValue(prefs::kStabilityRendererHangCount);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/stability_metrics_helper.h b/components/metrics/stability_metrics_helper.h
new file mode 100644
index 0000000..5a22882
--- /dev/null
+++ b/components/metrics/stability_metrics_helper.h
@@ -0,0 +1,78 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_STABILITY_METRICS_HELPER_H_
+#define COMPONENTS_METRICS_STABILITY_METRICS_HELPER_H_
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/process/kill.h"
+#include "base/time/time.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace metrics {
+
+class SystemProfileProto;
+
+// StabilityMetricsHelper is a class that providers functionality common to
+// different embedders' stability metrics providers.
+class StabilityMetricsHelper {
+ public:
+  explicit StabilityMetricsHelper(PrefService* local_state);
+  ~StabilityMetricsHelper();
+
+  // Provides stability metrics.
+  void ProvideStabilityMetrics(SystemProfileProto* system_profile_proto);
+
+  // Clears the gathered stability metrics.
+  void ClearSavedStabilityMetrics();
+
+  // Records a utility process launch with name |metrics_name|.
+  void BrowserUtilityProcessLaunched(const std::string& metrics_name);
+
+  // Records a utility process crash with name |metrics_name|.
+  void BrowserUtilityProcessCrashed(const std::string& metrics_name,
+                                    int exit_code);
+
+  // Records a browser child process crash.
+  void BrowserChildProcessCrashed();
+
+  // Logs the initiation of a page load.
+  void LogLoadStarted(bool is_incognito);
+
+  // Records a renderer process crash.
+  void LogRendererCrash(bool was_extension_process,
+                        base::TerminationStatus status,
+                        int exit_code,
+                        base::Optional<base::TimeDelta> uptime);
+
+  // Records that a new renderer process was successfully launched.
+  void LogRendererLaunched(bool was_extension_process);
+
+  // Records a renderer process hang.
+  void LogRendererHang();
+
+  // Registers local state prefs used by this class.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+  // Increments the RendererCrash pref.
+  void IncreaseRendererCrashCount();
+
+ private:
+  // Increments an Integer pref value specified by |path|.
+  void IncrementPrefValue(const char* path);
+
+  // Increments a 64-bit Integer pref value specified by |path|.
+  void IncrementLongPrefsValue(const char* path);
+
+  PrefService* local_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(StabilityMetricsHelper);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_STABILITY_METRICS_HELPER_H_
diff --git a/components/metrics/stability_metrics_helper_unittest.cc b/components/metrics/stability_metrics_helper_unittest.cc
new file mode 100644
index 0000000..66fc041
--- /dev/null
+++ b/components/metrics/stability_metrics_helper_unittest.cc
@@ -0,0 +1,159 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/stability_metrics_helper.h"
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "build/build_config.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/prefs/testing_pref_service.h"
+#include "extensions/buildflags/buildflags.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace metrics {
+
+namespace {
+
+enum RendererType {
+  RENDERER_TYPE_RENDERER = 1,
+  RENDERER_TYPE_EXTENSION,
+  // NOTE: Add new action types only immediately above this line. Also,
+  // make sure the enum list in tools/metrics/histograms/histograms.xml is
+  // updated with any change in here.
+  RENDERER_TYPE_COUNT
+};
+
+class StabilityMetricsHelperTest : public testing::Test {
+ protected:
+  StabilityMetricsHelperTest() : prefs_(new TestingPrefServiceSimple) {
+    StabilityMetricsHelper::RegisterPrefs(prefs()->registry());
+  }
+
+  TestingPrefServiceSimple* prefs() { return prefs_.get(); }
+
+ private:
+  std::unique_ptr<TestingPrefServiceSimple> prefs_;
+
+  DISALLOW_COPY_AND_ASSIGN(StabilityMetricsHelperTest);
+};
+
+}  // namespace
+
+TEST_F(StabilityMetricsHelperTest, BrowserChildProcessCrashed) {
+  StabilityMetricsHelper helper(prefs());
+
+  helper.BrowserChildProcessCrashed();
+  helper.BrowserChildProcessCrashed();
+
+  // Call ProvideStabilityMetrics to check that it will force pending tasks to
+  // be executed immediately.
+  metrics::SystemProfileProto system_profile;
+
+  helper.ProvideStabilityMetrics(&system_profile);
+
+  // Check current number of instances created.
+  const metrics::SystemProfileProto_Stability& stability =
+      system_profile.stability();
+
+  EXPECT_EQ(2, stability.child_process_crash_count());
+}
+
+TEST_F(StabilityMetricsHelperTest, LogRendererCrash) {
+  StabilityMetricsHelper helper(prefs());
+  base::HistogramTester histogram_tester;
+  const base::TimeDelta kUptime = base::TimeDelta::FromSeconds(123);
+
+  // Crash and abnormal termination should increment renderer crash count.
+  helper.LogRendererCrash(false, base::TERMINATION_STATUS_PROCESS_CRASHED, 1,
+                          kUptime);
+
+  helper.LogRendererCrash(false, base::TERMINATION_STATUS_ABNORMAL_TERMINATION,
+                          1, kUptime);
+
+  // OOM should increment renderer crash count.
+  helper.LogRendererCrash(false, base::TERMINATION_STATUS_OOM, 1, kUptime);
+
+  // Kill does not increment renderer crash count.
+  helper.LogRendererCrash(false, base::TERMINATION_STATUS_PROCESS_WAS_KILLED, 1,
+                          kUptime);
+
+  // Failed launch increments failed launch count.
+  helper.LogRendererCrash(false, base::TERMINATION_STATUS_LAUNCH_FAILED, 1,
+                          kUptime);
+
+  metrics::SystemProfileProto system_profile;
+
+  // Call ProvideStabilityMetrics to check that it will force pending tasks to
+  // be executed immediately.
+  helper.ProvideStabilityMetrics(&system_profile);
+
+  EXPECT_EQ(3, system_profile.stability().renderer_crash_count());
+  EXPECT_EQ(1, system_profile.stability().renderer_failed_launch_count());
+  EXPECT_EQ(0, system_profile.stability().extension_renderer_crash_count());
+
+  histogram_tester.ExpectUniqueSample("CrashExitCodes.Renderer", 1, 3);
+  histogram_tester.ExpectBucketCount("BrowserRenderProcessHost.ChildCrashes",
+                                     RENDERER_TYPE_RENDERER, 3);
+
+  // One launch failure each.
+  histogram_tester.ExpectBucketCount(
+      "BrowserRenderProcessHost.ChildLaunchFailures", RENDERER_TYPE_RENDERER,
+      1);
+
+  // TERMINATION_STATUS_PROCESS_WAS_KILLED for a renderer.
+  histogram_tester.ExpectBucketCount("BrowserRenderProcessHost.ChildKills",
+                                     RENDERER_TYPE_RENDERER, 1);
+  histogram_tester.ExpectBucketCount("BrowserRenderProcessHost.ChildKills",
+                                     RENDERER_TYPE_EXTENSION, 0);
+  histogram_tester.ExpectBucketCount(
+      "BrowserRenderProcessHost.ChildLaunchFailureCodes", 1, 1);
+  histogram_tester.ExpectUniqueSample("Stability.CrashedProcessAge.Renderer",
+                                      kUptime.InMilliseconds(), 3);
+}
+
+// Note: ENABLE_EXTENSIONS is set to false in Android
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+TEST_F(StabilityMetricsHelperTest, LogRendererCrashEnableExtensions) {
+  StabilityMetricsHelper helper(prefs());
+  base::HistogramTester histogram_tester;
+  const base::TimeDelta kUptime = base::TimeDelta::FromSeconds(123);
+
+  // Crash and abnormal termination should increment extension crash count.
+  helper.LogRendererCrash(true, base::TERMINATION_STATUS_PROCESS_CRASHED, 1,
+                          kUptime);
+
+  // OOM should increment extension renderer crash count.
+  helper.LogRendererCrash(true, base::TERMINATION_STATUS_OOM, 1, kUptime);
+
+  // Failed launch increments extension failed launch count.
+  helper.LogRendererCrash(true, base::TERMINATION_STATUS_LAUNCH_FAILED, 1,
+                          kUptime);
+
+  metrics::SystemProfileProto system_profile;
+  helper.ProvideStabilityMetrics(&system_profile);
+
+  EXPECT_EQ(0, system_profile.stability().renderer_crash_count());
+  EXPECT_EQ(2, system_profile.stability().extension_renderer_crash_count());
+  EXPECT_EQ(
+      1, system_profile.stability().extension_renderer_failed_launch_count());
+
+  histogram_tester.ExpectBucketCount(
+      "BrowserRenderProcessHost.ChildLaunchFailureCodes", 1, 1);
+  histogram_tester.ExpectUniqueSample("CrashExitCodes.Extension", 1, 2);
+  histogram_tester.ExpectBucketCount("BrowserRenderProcessHost.ChildCrashes",
+                                     RENDERER_TYPE_EXTENSION, 2);
+  histogram_tester.ExpectBucketCount(
+      "BrowserRenderProcessHost.ChildLaunchFailures", RENDERER_TYPE_EXTENSION,
+      1);
+  histogram_tester.ExpectUniqueSample("Stability.CrashedProcessAge.Extension",
+                                      kUptime.InMilliseconds(), 2);
+}
+#endif
+
+}  // namespace metrics
diff --git a/components/metrics/stability_metrics_provider.cc b/components/metrics/stability_metrics_provider.cc
new file mode 100644
index 0000000..7d411d3
--- /dev/null
+++ b/components/metrics/stability_metrics_provider.cc
@@ -0,0 +1,262 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/stability_metrics_provider.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "build/build_config.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+#if defined(OS_ANDROID)
+#include "base/android/build_info.h"
+#endif
+#if defined(OS_WIN)
+#include "components/metrics/system_session_analyzer_win.h"
+#endif
+
+namespace metrics {
+
+namespace {
+
+#if defined(OS_ANDROID)
+bool HasGmsCoreVersionChanged(PrefService* local_state) {
+  std::string previous_version =
+      local_state->GetString(prefs::kStabilityGmsCoreVersion);
+  std::string current_version =
+      base::android::BuildInfo::GetInstance()->gms_version_code();
+
+  // If the last version is empty, treat it as consistent.
+  if (previous_version.empty())
+    return false;
+
+  return previous_version != current_version;
+}
+
+void UpdateGmsCoreVersionPref(PrefService* local_state) {
+  std::string current_version =
+      base::android::BuildInfo::GetInstance()->gms_version_code();
+  local_state->SetString(prefs::kStabilityGmsCoreVersion, current_version);
+}
+#endif
+
+}  // namespace
+
+StabilityMetricsProvider::StabilityMetricsProvider(PrefService* local_state)
+    : local_state_(local_state) {}
+
+StabilityMetricsProvider::~StabilityMetricsProvider() = default;
+
+// static
+void StabilityMetricsProvider::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterIntegerPref(prefs::kStabilityCrashCount, 0);
+  registry->RegisterIntegerPref(prefs::kStabilityIncompleteSessionEndCount, 0);
+  registry->RegisterBooleanPref(prefs::kStabilitySessionEndCompleted, true);
+  registry->RegisterIntegerPref(prefs::kStabilityLaunchCount, 0);
+  registry->RegisterIntegerPref(prefs::kStabilityBreakpadRegistrationFail, 0);
+  registry->RegisterIntegerPref(prefs::kStabilityBreakpadRegistrationSuccess,
+                                0);
+  registry->RegisterIntegerPref(prefs::kStabilityDebuggerPresent, 0);
+  registry->RegisterIntegerPref(prefs::kStabilityDebuggerNotPresent, 0);
+  registry->RegisterIntegerPref(prefs::kStabilityDeferredCount, 0);
+  registry->RegisterIntegerPref(prefs::kStabilityDiscardCount, 0);
+  registry->RegisterIntegerPref(prefs::kStabilityVersionMismatchCount, 0);
+#if defined(OS_ANDROID)
+  registry->RegisterStringPref(prefs::kStabilityGmsCoreVersion, "");
+  registry->RegisterIntegerPref(prefs::kStabilityCrashCountWithoutGmsCoreUpdate,
+                                0);
+#endif
+#if defined(OS_WIN)
+  registry->RegisterIntegerPref(prefs::kStabilitySystemCrashCount, 0);
+#endif
+}
+
+void StabilityMetricsProvider::Init() {
+#if defined(OS_ANDROID)
+  // This method has to be called after HasGmsCoreVersionChanged() to avoid
+  // overwriting thie result.
+  UpdateGmsCoreVersionPref(local_state_);
+#endif
+}
+
+void StabilityMetricsProvider::ClearSavedStabilityMetrics() {
+  local_state_->SetInteger(prefs::kStabilityCrashCount, 0);
+  local_state_->SetInteger(prefs::kStabilityIncompleteSessionEndCount, 0);
+  local_state_->SetInteger(prefs::kStabilityBreakpadRegistrationSuccess, 0);
+  local_state_->SetInteger(prefs::kStabilityBreakpadRegistrationFail, 0);
+  local_state_->SetInteger(prefs::kStabilityDebuggerPresent, 0);
+  local_state_->SetInteger(prefs::kStabilityDebuggerNotPresent, 0);
+  local_state_->SetInteger(prefs::kStabilityLaunchCount, 0);
+  local_state_->SetBoolean(prefs::kStabilitySessionEndCompleted, true);
+  local_state_->SetInteger(prefs::kStabilityDeferredCount, 0);
+  // Note: kStabilityDiscardCount is not cleared as its intent is to measure
+  // the number of times data is discarded, even across versions.
+  local_state_->SetInteger(prefs::kStabilityVersionMismatchCount, 0);
+#if defined(OS_WIN)
+  local_state_->SetInteger(prefs::kStabilitySystemCrashCount, 0);
+#endif
+}
+
+void StabilityMetricsProvider::ProvideStabilityMetrics(
+    SystemProfileProto* system_profile) {
+  SystemProfileProto::Stability* stability =
+      system_profile->mutable_stability();
+
+  int pref_value = 0;
+
+  if (GetPrefValue(prefs::kStabilityLaunchCount, &pref_value))
+    stability->set_launch_count(pref_value);
+
+  if (GetPrefValue(prefs::kStabilityCrashCount, &pref_value))
+    stability->set_crash_count(pref_value);
+
+#if defined(OS_ANDROID)
+  if (GetPrefValue(prefs::kStabilityCrashCountWithoutGmsCoreUpdate,
+                   &pref_value)) {
+    stability->set_crash_count_without_gms_core_update(pref_value);
+  }
+#endif
+
+  if (GetPrefValue(prefs::kStabilityIncompleteSessionEndCount, &pref_value))
+    stability->set_incomplete_shutdown_count(pref_value);
+
+  if (GetPrefValue(prefs::kStabilityBreakpadRegistrationSuccess, &pref_value))
+    stability->set_breakpad_registration_success_count(pref_value);
+
+  if (GetPrefValue(prefs::kStabilityBreakpadRegistrationFail, &pref_value))
+    stability->set_breakpad_registration_failure_count(pref_value);
+
+  if (GetPrefValue(prefs::kStabilityDebuggerPresent, &pref_value))
+    stability->set_debugger_present_count(pref_value);
+
+  if (GetPrefValue(prefs::kStabilityDebuggerNotPresent, &pref_value))
+    stability->set_debugger_not_present_count(pref_value);
+
+  // Note: only logging the following histograms for non-zero values.
+  if (GetPrefValue(prefs::kStabilityDeferredCount, &pref_value)) {
+    UMA_STABILITY_HISTOGRAM_COUNTS_100(
+        "Stability.Internals.InitialStabilityLogDeferredCount", pref_value);
+  }
+
+  // Note: only logging the following histograms for non-zero values.
+  if (GetPrefValue(prefs::kStabilityDiscardCount, &pref_value)) {
+    UMA_STABILITY_HISTOGRAM_COUNTS_100("Stability.Internals.DataDiscardCount",
+                                       pref_value);
+  }
+
+  // Note: only logging the following histograms for non-zero values.
+  if (GetPrefValue(prefs::kStabilityVersionMismatchCount, &pref_value)) {
+    UMA_STABILITY_HISTOGRAM_COUNTS_100(
+        "Stability.Internals.VersionMismatchCount", pref_value);
+  }
+
+#if defined(OS_WIN)
+  if (GetPrefValue(prefs::kStabilitySystemCrashCount, &pref_value)) {
+    UMA_STABILITY_HISTOGRAM_COUNTS_100("Stability.Internals.SystemCrashCount",
+                                       pref_value);
+  }
+#endif
+}
+
+void StabilityMetricsProvider::RecordBreakpadRegistration(bool success) {
+  if (!success)
+    IncrementPrefValue(prefs::kStabilityBreakpadRegistrationFail);
+  else
+    IncrementPrefValue(prefs::kStabilityBreakpadRegistrationSuccess);
+}
+
+void StabilityMetricsProvider::RecordBreakpadHasDebugger(bool has_debugger) {
+  if (!has_debugger)
+    IncrementPrefValue(prefs::kStabilityDebuggerNotPresent);
+  else
+    IncrementPrefValue(prefs::kStabilityDebuggerPresent);
+}
+
+void StabilityMetricsProvider::CheckLastSessionEndCompleted() {
+  if (!local_state_->GetBoolean(prefs::kStabilitySessionEndCompleted)) {
+    IncrementPrefValue(prefs::kStabilityIncompleteSessionEndCount);
+    // This is marked false when we get a WM_ENDSESSION.
+    MarkSessionEndCompleted(true);
+  }
+}
+
+void StabilityMetricsProvider::MarkSessionEndCompleted(bool end_completed) {
+  local_state_->SetBoolean(prefs::kStabilitySessionEndCompleted, end_completed);
+}
+
+void StabilityMetricsProvider::LogCrash(base::Time last_live_timestamp) {
+  IncrementPrefValue(prefs::kStabilityCrashCount);
+
+#if defined(OS_ANDROID)
+  // On Android, if there is an update for GMS core when Chrome is running,
+  // Chrome will be killed and restart. This is expected and we should only
+  // report crash if the GMS core version has not been changed.
+  if (!HasGmsCoreVersionChanged(local_state_))
+    IncrementPrefValue(prefs::kStabilityCrashCountWithoutGmsCoreUpdate);
+#endif
+
+#if defined(OS_WIN)
+  MaybeLogSystemCrash(last_live_timestamp);
+#endif
+}
+
+void StabilityMetricsProvider::LogStabilityLogDeferred() {
+  IncrementPrefValue(prefs::kStabilityDeferredCount);
+}
+
+void StabilityMetricsProvider::LogStabilityDataDiscarded() {
+  IncrementPrefValue(prefs::kStabilityDiscardCount);
+}
+
+void StabilityMetricsProvider::LogLaunch() {
+  IncrementPrefValue(prefs::kStabilityLaunchCount);
+}
+
+void StabilityMetricsProvider::LogStabilityVersionMismatch() {
+  IncrementPrefValue(prefs::kStabilityVersionMismatchCount);
+}
+
+#if defined(OS_WIN)
+bool StabilityMetricsProvider::IsUncleanSystemSession(
+    base::Time last_live_timestamp) {
+  DCHECK_NE(base::Time(), last_live_timestamp);
+  // There's a non-null last live timestamp, see if this occurred in
+  // a Windows system session that ended uncleanly. The expectation is that
+  // |last_live_timestamp| will have occurred in the immediately previous system
+  // session, but if the system has been restarted many times since Chrome last
+  // ran, that's not necessarily true. Log traversal can be expensive, so we
+  // limit the analyzer to reaching back three previous system sessions to bound
+  // the cost of the traversal.
+  SystemSessionAnalyzer analyzer(3);
+
+  SystemSessionAnalyzer::Status status =
+      analyzer.IsSessionUnclean(last_live_timestamp);
+
+  return status == SystemSessionAnalyzer::UNCLEAN;
+}
+
+void StabilityMetricsProvider::MaybeLogSystemCrash(
+    base::Time last_live_timestamp) {
+  if (last_live_timestamp != base::Time() &&
+      IsUncleanSystemSession(last_live_timestamp)) {
+    IncrementPrefValue(prefs::kStabilitySystemCrashCount);
+  }
+}
+#endif
+
+void StabilityMetricsProvider::IncrementPrefValue(const char* path) {
+  int value = local_state_->GetInteger(path);
+  local_state_->SetInteger(path, value + 1);
+}
+
+int StabilityMetricsProvider::GetPrefValue(const char* path, int* value) {
+  *value = local_state_->GetInteger(path);
+  if (*value != 0)
+    local_state_->SetInteger(path, 0);
+  return *value;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/stability_metrics_provider.h b/components/metrics/stability_metrics_provider.h
new file mode 100644
index 0000000..3aeff63
--- /dev/null
+++ b/components/metrics/stability_metrics_provider.h
@@ -0,0 +1,67 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_STABILITY_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_STABILITY_METRICS_PROVIDER_H_
+
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "components/metrics/metrics_provider.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace metrics {
+
+class SystemProfileProto;
+
+// Stores and loads system information to prefs for stability logs.
+class StabilityMetricsProvider : public MetricsProvider {
+ public:
+  StabilityMetricsProvider(PrefService* local_state);
+  ~StabilityMetricsProvider() override;
+
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+  void RecordBreakpadRegistration(bool success);
+  void RecordBreakpadHasDebugger(bool has_debugger);
+
+  void CheckLastSessionEndCompleted();
+  void MarkSessionEndCompleted(bool end_completed);
+
+  void LogCrash(base::Time last_live_timestamp);
+  void LogStabilityLogDeferred();
+  void LogStabilityDataDiscarded();
+  void LogLaunch();
+  void LogStabilityVersionMismatch();
+
+ private:
+#if defined(OS_WIN)
+  // This function is virtual for testing. The |last_live_timestamp| is a
+  // time point where the previous browser was known to be alive, and is used
+  // to determine whether the system session embedding that timestamp terminated
+  // uncleanly.
+  virtual bool IsUncleanSystemSession(base::Time last_live_timestamp);
+  void MaybeLogSystemCrash(base::Time last_live_timestamp);
+#endif
+  // Increments an Integer pref value specified by |path|.
+  void IncrementPrefValue(const char* path);
+
+  // Gets pref value specified by |path| and resets it to 0 after retrieving.
+  int GetPrefValue(const char* path, int* value);
+
+  // MetricsProvider:
+  void Init() override;
+  void ClearSavedStabilityMetrics() override;
+  void ProvideStabilityMetrics(
+      SystemProfileProto* system_profile_proto) override;
+
+  PrefService* local_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(StabilityMetricsProvider);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_STABILITY_METRICS_PROVIDER_H_
diff --git a/components/metrics/stability_metrics_provider_unittest.cc b/components/metrics/stability_metrics_provider_unittest.cc
new file mode 100644
index 0000000..e0ba0ce
--- /dev/null
+++ b/components/metrics/stability_metrics_provider_unittest.cc
@@ -0,0 +1,132 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/stability_metrics_provider.h"
+
+#include "base/test/metrics/histogram_tester.h"
+#include "build/build_config.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace metrics {
+
+class StabilityMetricsProviderTest : public testing::Test {
+ public:
+  StabilityMetricsProviderTest() {
+    StabilityMetricsProvider::RegisterPrefs(prefs_.registry());
+  }
+
+  ~StabilityMetricsProviderTest() override {}
+
+ protected:
+  TestingPrefServiceSimple prefs_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(StabilityMetricsProviderTest);
+};
+
+TEST_F(StabilityMetricsProviderTest, ProvideStabilityMetrics) {
+  StabilityMetricsProvider stability_provider(&prefs_);
+  MetricsProvider* provider = &stability_provider;
+  SystemProfileProto system_profile;
+  provider->ProvideStabilityMetrics(&system_profile);
+
+  const SystemProfileProto_Stability& stability = system_profile.stability();
+  // Initial log metrics: only expected if non-zero.
+  EXPECT_FALSE(stability.has_launch_count());
+  EXPECT_FALSE(stability.has_crash_count());
+  EXPECT_FALSE(stability.has_incomplete_shutdown_count());
+  EXPECT_FALSE(stability.has_breakpad_registration_success_count());
+  EXPECT_FALSE(stability.has_breakpad_registration_failure_count());
+  EXPECT_FALSE(stability.has_debugger_present_count());
+  EXPECT_FALSE(stability.has_debugger_not_present_count());
+}
+
+TEST_F(StabilityMetricsProviderTest, RecordStabilityMetrics) {
+  {
+    StabilityMetricsProvider recorder(&prefs_);
+    recorder.LogLaunch();
+    recorder.LogCrash(base::Time());
+    recorder.MarkSessionEndCompleted(false);
+    recorder.CheckLastSessionEndCompleted();
+    recorder.RecordBreakpadRegistration(true);
+    recorder.RecordBreakpadRegistration(false);
+    recorder.RecordBreakpadHasDebugger(true);
+    recorder.RecordBreakpadHasDebugger(false);
+  }
+
+  {
+    StabilityMetricsProvider stability_provider(&prefs_);
+    MetricsProvider* provider = &stability_provider;
+    SystemProfileProto system_profile;
+    provider->ProvideStabilityMetrics(&system_profile);
+
+    const SystemProfileProto_Stability& stability = system_profile.stability();
+    // Initial log metrics: only expected if non-zero.
+    EXPECT_EQ(1, stability.launch_count());
+    EXPECT_EQ(1, stability.crash_count());
+    EXPECT_EQ(1, stability.incomplete_shutdown_count());
+    EXPECT_EQ(1, stability.breakpad_registration_success_count());
+    EXPECT_EQ(1, stability.breakpad_registration_failure_count());
+    EXPECT_EQ(1, stability.debugger_present_count());
+    EXPECT_EQ(1, stability.debugger_not_present_count());
+  }
+}
+
+#if defined(OS_WIN)
+namespace {
+
+class TestingStabilityMetricsProvider : public StabilityMetricsProvider {
+ public:
+  TestingStabilityMetricsProvider(PrefService* local_state,
+                                  base::Time unclean_session_time)
+      : StabilityMetricsProvider(local_state),
+        unclean_session_time_(unclean_session_time) {}
+
+  bool IsUncleanSystemSession(base::Time last_live_timestamp) override {
+    return last_live_timestamp == unclean_session_time_;
+  }
+
+ private:
+  const base::Time unclean_session_time_;
+};
+
+}  // namespace
+
+TEST_F(StabilityMetricsProviderTest, RecordSystemCrashMetrics) {
+  {
+    base::Time unclean_time = base::Time::Now();
+    TestingStabilityMetricsProvider recorder(&prefs_, unclean_time);
+
+    // Any crash with a last_live_timestamp equal to unclean_time will
+    // be logged as a system crash as per the implementation of
+    // TestingStabilityMetricsProvider, so this will log a system crash.
+    recorder.LogCrash(unclean_time);
+
+    // Record a crash with no system crash.
+    recorder.LogCrash(unclean_time - base::TimeDelta::FromMinutes(1));
+  }
+
+  {
+    StabilityMetricsProvider stability_provider(&prefs_);
+    MetricsProvider* provider = &stability_provider;
+    SystemProfileProto system_profile;
+
+    base::HistogramTester histogram_tester;
+
+    provider->ProvideStabilityMetrics(&system_profile);
+
+    const SystemProfileProto_Stability& stability = system_profile.stability();
+    // Two crashes, one system crash.
+    EXPECT_EQ(2, stability.crash_count());
+
+    histogram_tester.ExpectTotalCount("Stability.Internals.SystemCrashCount",
+                                      1);
+  }
+}
+
+#endif
+
+}  // namespace metrics
diff --git a/components/metrics/system_memory_stats_recorder.h b/components/metrics/system_memory_stats_recorder.h
new file mode 100644
index 0000000..bdd30f0
--- /dev/null
+++ b/components/metrics/system_memory_stats_recorder.h
@@ -0,0 +1,30 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_SYSTEM_MEMORY_STATS_RECORDER_H_
+#define COMPONENTS_METRICS_SYSTEM_MEMORY_STATS_RECORDER_H_
+
+namespace metrics {
+
+// Record a memory size in megabytes, over a potential interval up to 32 GB.
+#define UMA_HISTOGRAM_LARGE_MEMORY_MB(name, sample) \
+  UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, 1, 32768, 50)
+
+// The type of memory UMA stats to be recorded in RecordMemoryStats.
+enum RecordMemoryStatsType {
+  // When a tab was discarded.
+  RECORD_MEMORY_STATS_TAB_DISCARDED,
+
+  // Right after the renderer for contents was killed.
+  RECORD_MEMORY_STATS_CONTENTS_OOM_KILLED,
+
+  // Right after the renderer for extensions was killed.
+  RECORD_MEMORY_STATS_EXTENSIONS_OOM_KILLED,
+};
+
+void RecordMemoryStats(RecordMemoryStatsType type);
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_SYSTEM_MEMORY_STATS_RECORDER_H_
diff --git a/components/metrics/system_memory_stats_recorder_linux.cc b/components/metrics/system_memory_stats_recorder_linux.cc
new file mode 100644
index 0000000..c69dbaa
--- /dev/null
+++ b/components/metrics/system_memory_stats_recorder_linux.cc
@@ -0,0 +1,98 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/system_memory_stats_recorder.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "base/process/process_metrics.h"
+#include "build/build_config.h"
+
+namespace metrics {
+
+// Record a size in megabytes, a potential interval from 250MB up to 32 GB.
+#define UMA_HISTOGRAM_ALLOCATED_MEGABYTES(name, sample) \
+  UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample, 250, 32768, 50)
+
+// Records a statistics |sample| for UMA histogram |name|
+// using a linear distribution of buckets.
+#define UMA_HISTOGRAM_LINEAR(name, sample, max, buckets)                       \
+  STATIC_HISTOGRAM_POINTER_BLOCK(                                              \
+      name, Add(sample),                                                       \
+      base::LinearHistogram::FactoryGet(                                       \
+          name,                                                                \
+          1, /* Minimum. The 0 bin for underflow is automatically added. */    \
+          max + 1,     /* Ensure bucket size of |maximum| / |bucket_count|. */ \
+          buckets + 2, /*  Account for the underflow and overflow bins. */     \
+          base::Histogram::kUmaTargetedHistogramFlag))
+
+#define UMA_HISTOGRAM_MEGABYTES_LINEAR(name, sample) \
+  UMA_HISTOGRAM_LINEAR(name, sample, 2500, 50)
+
+void RecordMemoryStats(RecordMemoryStatsType type) {
+  base::SystemMemoryInfoKB memory;
+  if (!base::GetSystemMemoryInfo(&memory))
+    return;
+#if defined(OS_CHROMEOS)
+  // Record graphics GEM object size in a histogram with 50 MB buckets.
+  int mem_graphics_gem_mb = 0;
+  if (memory.gem_size != -1)
+    mem_graphics_gem_mb = memory.gem_size / 1024 / 1024;
+
+  // Record shared memory (used by renderer/GPU buffers).
+  int mem_shmem_mb = memory.shmem / 1024;
+#endif
+
+  // On Intel, graphics objects are in anonymous pages, but on ARM they are
+  // not. For a total "allocated count" add in graphics pages on ARM.
+  int mem_allocated_mb = (memory.active_anon + memory.inactive_anon) / 1024;
+#if defined(OS_CHROMEOS) && defined(ARCH_CPU_ARM_FAMILY)
+  mem_allocated_mb += mem_graphics_gem_mb;
+#endif
+
+  int mem_available_mb =
+      (memory.active_file + memory.inactive_file + memory.free) / 1024;
+
+  switch (type) {
+    case RECORD_MEMORY_STATS_TAB_DISCARDED: {
+#if defined(OS_CHROMEOS)
+      UMA_HISTOGRAM_MEGABYTES_LINEAR("Tabs.Discard.MemGraphicsMB",
+                                     mem_graphics_gem_mb);
+      UMA_HISTOGRAM_MEGABYTES_LINEAR("Tabs.Discard.MemShmemMB", mem_shmem_mb);
+#endif
+      UMA_HISTOGRAM_ALLOCATED_MEGABYTES("Tabs.Discard.MemAllocatedMB",
+                                        mem_allocated_mb);
+      UMA_HISTOGRAM_LARGE_MEMORY_MB("Tabs.Discard.MemAvailableMB",
+                                    mem_available_mb);
+      break;
+    }
+    case RECORD_MEMORY_STATS_CONTENTS_OOM_KILLED: {
+#if defined(OS_CHROMEOS)
+      UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Contents.MemGraphicsMB",
+                                     mem_graphics_gem_mb);
+      UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Contents.MemShmemMB",
+                                     mem_shmem_mb);
+#endif
+      UMA_HISTOGRAM_ALLOCATED_MEGABYTES(
+          "Memory.OOMKill.Contents.MemAllocatedMB", mem_allocated_mb);
+      UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.OOMKill.Contents.MemAvailableMB",
+                                    mem_available_mb);
+      break;
+    }
+    case RECORD_MEMORY_STATS_EXTENSIONS_OOM_KILLED: {
+#if defined(OS_CHROMEOS)
+      UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Extensions.MemGraphicsMB",
+                                     mem_graphics_gem_mb);
+      UMA_HISTOGRAM_MEGABYTES_LINEAR("Memory.OOMKill.Extensions.MemShmemMB",
+                                     mem_shmem_mb);
+#endif
+      UMA_HISTOGRAM_ALLOCATED_MEGABYTES(
+          "Memory.OOMKill.Extensions.MemAllocatedMB", mem_allocated_mb);
+      UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.OOMKill.Extensions.MemAvailableMB",
+                                    mem_available_mb);
+      break;
+    }
+  }
+}
+
+}  // namespace metrics
diff --git a/components/metrics/system_memory_stats_recorder_win.cc b/components/metrics/system_memory_stats_recorder_win.cc
new file mode 100644
index 0000000..7d01182
--- /dev/null
+++ b/components/metrics/system_memory_stats_recorder_win.cc
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/system_memory_stats_recorder.h"
+
+#include <windows.h>
+
+#include "base/metrics/histogram_macros.h"
+#include "base/process/process_metrics.h"
+
+namespace metrics {
+namespace {
+enum { kMBytes = 1024 * 1024 };
+}
+
+void RecordMemoryStats(RecordMemoryStatsType type) {
+  MEMORYSTATUSEX mem_status;
+  mem_status.dwLength = sizeof(mem_status);
+  if (!::GlobalMemoryStatusEx(&mem_status))
+    return;
+
+  switch (type) {
+    case RECORD_MEMORY_STATS_TAB_DISCARDED: {
+      UMA_HISTOGRAM_CUSTOM_COUNTS("Memory.Stats.Win.MemoryLoad",
+                                  mem_status.dwMemoryLoad, 1, 100, 101);
+      UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.TotalPhys2",
+                                    mem_status.ullTotalPhys / kMBytes);
+      UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.AvailPhys2",
+                                    mem_status.ullAvailPhys / kMBytes);
+      UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.TotalPageFile2",
+                                    mem_status.ullTotalPageFile / kMBytes);
+      UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.AvailPageFile2",
+                                    mem_status.ullAvailPageFile / kMBytes);
+      UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.TotalVirtual2",
+                                    mem_status.ullTotalVirtual / kMBytes);
+      UMA_HISTOGRAM_LARGE_MEMORY_MB("Memory.Stats.Win.AvailVirtual2",
+                                    mem_status.ullAvailVirtual / kMBytes);
+      break;
+    }
+    default:
+      NOTREACHED() << "Received unexpected notification";
+      break;
+  }
+}
+
+}  // namespace metrics
diff --git a/components/metrics/system_session_analyzer_win.cc b/components/metrics/system_session_analyzer_win.cc
new file mode 100644
index 0000000..e64501f
--- /dev/null
+++ b/components/metrics/system_session_analyzer_win.cc
@@ -0,0 +1,263 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/system_session_analyzer_win.h"
+
+#include "base/macros.h"
+#include "base/time/time.h"
+
+namespace metrics {
+
+namespace {
+
+// The name of the log channel to query.
+const wchar_t kChannelName[] = L"System";
+
+// Event ids of system startup / shutdown events. These were obtained from
+// inspection of the System log in Event Viewer on Windows 10:
+//   - id 6005: "The Event log service was started."
+//   - id 6006: "The Event log service was stopped."
+//   - id 6008: "The previous system shutdown at <time> on <date> was
+//               unexpected."
+const uint16_t kIdSessionStart = 6005U;
+const uint16_t kIdSessionEnd = 6006U;
+const uint16_t kIdSessionEndUnclean = 6008U;
+
+// An XPATH expression to query for system startup / shutdown events. The query
+// is expected to retrieve exactly one event for each startup (kIdSessionStart)
+// and one event for each shutdown (either kIdSessionEnd or
+// kIdSessionEndUnclean).
+const wchar_t kSessionEventsQuery[] =
+    L"*[System[Provider[@Name='eventlog']"
+    L" and (EventID=6005 or EventID=6006 or EventID=6008)]]";
+
+// XPath expressions to attributes of interest.
+const wchar_t kEventIdPath[] = L"Event/System/EventID";
+const wchar_t kEventTimePath[] = L"Event/System/TimeCreated/@SystemTime";
+
+// The timeout to use for calls to ::EvtNext.
+const uint32_t kTimeoutMs = 5000;
+
+base::Time ULLFileTimeToTime(ULONGLONG time_ulonglong) {
+  // Copy low / high parts as FILETIME is not always 64bit aligned.
+  ULARGE_INTEGER time;
+  time.QuadPart = time_ulonglong;
+  FILETIME ft;
+  ft.dwLowDateTime = time.LowPart;
+  ft.dwHighDateTime = time.HighPart;
+
+  return base::Time::FromFileTime(ft);
+}
+
+bool GetEventInfo(EVT_HANDLE context,
+                  EVT_HANDLE event,
+                  SystemSessionAnalyzer::EventInfo* info) {
+  DCHECK(context);
+  DCHECK(event);
+  DCHECK(info);
+
+  // Retrieve attributes of interest from the event. We expect the context to
+  // specify the retrieval of two attributes (event id and event time), each
+  // with a specific type.
+  const DWORD kAttributeCnt = 2U;
+  std::vector<EVT_VARIANT> buffer(kAttributeCnt);
+  DWORD buffer_size = kAttributeCnt * sizeof(EVT_VARIANT);
+  DWORD buffer_used = 0U;
+  DWORD retrieved_attribute_cnt = 0U;
+  if (!::EvtRender(context, event, EvtRenderEventValues, buffer_size,
+                   buffer.data(), &buffer_used, &retrieved_attribute_cnt)) {
+    DLOG(ERROR) << "Failed to render the event.";
+    return false;
+  }
+
+  // Validate the count and types of the retrieved attributes.
+  if ((retrieved_attribute_cnt != kAttributeCnt) ||
+      (buffer[0].Type != EvtVarTypeUInt16) ||
+      (buffer[1].Type != EvtVarTypeFileTime)) {
+    return false;
+  }
+
+  info->event_id = buffer[0].UInt16Val;
+  info->event_time = ULLFileTimeToTime(buffer[1].FileTimeVal);
+
+  return true;
+}
+
+}  // namespace
+
+SystemSessionAnalyzer::SystemSessionAnalyzer(uint32_t max_session_cnt)
+    : max_session_cnt_(max_session_cnt), sessions_queried_(0) {}
+
+SystemSessionAnalyzer::~SystemSessionAnalyzer() {}
+
+SystemSessionAnalyzer::Status SystemSessionAnalyzer::IsSessionUnclean(
+    base::Time timestamp) {
+  if (!EnsureInitialized())
+    return FAILED;
+
+  while (timestamp < coverage_start_ && sessions_queried_ < max_session_cnt_) {
+    // Fetch the next session start and end events.
+    std::vector<EventInfo> events;
+    if (!FetchEvents(2U, &events) || events.size() != 2)
+      return FAILED;
+
+    if (!ProcessSession(events[0], events[1]))
+      return FAILED;
+
+    ++sessions_queried_;
+  }
+
+  if (timestamp < coverage_start_)
+    return OUTSIDE_RANGE;
+
+  // Get the first session starting after the timestamp.
+  std::map<base::Time, base::TimeDelta>::const_iterator it =
+      unclean_sessions_.upper_bound(timestamp);
+  if (it == unclean_sessions_.begin())
+    return CLEAN;  // No prior unclean session.
+
+  // Get the previous session and see if it encompasses the timestamp.
+  --it;
+  bool is_spanned = (timestamp - it->first) <= it->second;
+  return is_spanned ? UNCLEAN : CLEAN;
+}
+
+bool SystemSessionAnalyzer::FetchEvents(size_t requested_events,
+                                        std::vector<EventInfo>* event_infos) {
+  DCHECK(event_infos);
+
+  if (!EnsureHandlesOpened())
+    return false;
+
+  DCHECK(query_handle_.get());
+
+  // Retrieve events: 2 events per session, plus the current session's start.
+  DWORD desired_event_cnt = requested_events;
+  std::vector<EVT_HANDLE> events_raw(desired_event_cnt, NULL);
+  DWORD event_cnt = 0U;
+  BOOL success = ::EvtNext(query_handle_.get(), desired_event_cnt,
+                           events_raw.data(), kTimeoutMs, 0, &event_cnt);
+
+  // Ensure handles get closed. The MSDN sample seems to imply handles may need
+  // to be closed event in if EvtNext failed.
+  std::vector<EvtHandle> events(desired_event_cnt);
+  for (size_t i = 0; i < event_cnt; ++i)
+    events[i].reset(events_raw[i]);
+
+  if (!success) {
+    DLOG(ERROR) << "Failed to retrieve events.";
+    return false;
+  }
+
+  std::vector<EventInfo> event_infos_tmp;
+  event_infos_tmp.reserve(event_cnt);
+
+  EventInfo info = {};
+  for (size_t i = 0; i < event_cnt; ++i) {
+    if (!GetEventInfo(render_context_.get(), events[i].get(), &info))
+      return false;
+    event_infos_tmp.push_back(info);
+  }
+
+  event_infos->swap(event_infos_tmp);
+  return true;
+}
+
+bool SystemSessionAnalyzer::EnsureInitialized() {
+  if (!initialized_) {
+    DCHECK(!init_success_);
+    init_success_ = Initialize();
+    initialized_ = true;
+  }
+
+  return init_success_;
+}
+
+bool SystemSessionAnalyzer::EnsureHandlesOpened() {
+  // Create the event query.
+  // Note: requesting events from newest to oldest.
+  if (!query_handle_.get()) {
+    query_handle_.reset(
+        ::EvtQuery(nullptr, kChannelName, kSessionEventsQuery,
+                   EvtQueryChannelPath | EvtQueryReverseDirection));
+    if (!query_handle_.get()) {
+      DLOG(ERROR) << "Event query failed.";
+      return false;
+    }
+  }
+
+  if (!render_context_.get()) {
+    // Create the render context for extracting information from the events.
+    render_context_ = CreateRenderContext();
+    if (!render_context_.get())
+      return false;
+  }
+
+  return true;
+}
+
+bool SystemSessionAnalyzer::Initialize() {
+  DCHECK(!initialized_);
+
+  // Fetch the first (current) session start event and the first session,
+  // comprising an end and a start event for a total of 3 events.
+  std::vector<EventInfo> events;
+  if (!FetchEvents(3U, &events))
+    return false;
+
+  // Validate that the initial event is what we expect.
+  if (events.size() != 3 || events[0].event_id != kIdSessionStart)
+    return false;
+
+  // Initialize the coverage start to allow detecting event time inversion.
+  coverage_start_ = events[0].event_time;
+
+  if (!ProcessSession(events[1], events[2]))
+    return false;
+
+  sessions_queried_ = 1;
+
+  return true;
+}
+
+bool SystemSessionAnalyzer::ProcessSession(const EventInfo& end,
+                                           const EventInfo& start) {
+  // Validate the ordering of events (newest to oldest). The  expectation is a
+  // (start / [unclean]shutdown) pair of events for each session.
+  if (coverage_start_ < end.event_time)
+    return false;
+  if (end.event_time < start.event_time)
+    return false;
+
+  // Process a (start / shutdown) event pair, validating the types of events
+  // and recording unclean sessions.
+  if (start.event_id != kIdSessionStart)
+    return false;  // Unexpected event type.
+  if (end.event_id != kIdSessionEnd && end.event_id != kIdSessionEndUnclean)
+    return false;  // Unexpected event type.
+
+  if (end.event_id == kIdSessionEndUnclean) {
+    unclean_sessions_.insert(
+        std::make_pair(start.event_time, end.event_time - start.event_time));
+  }
+
+  coverage_start_ = start.event_time;
+
+  return true;
+}
+
+SystemSessionAnalyzer::EvtHandle SystemSessionAnalyzer::CreateRenderContext() {
+  LPCWSTR value_paths[] = {kEventIdPath, kEventTimePath};
+  const DWORD kValueCnt = arraysize(value_paths);
+
+  EVT_HANDLE context = nullptr;
+  context =
+      ::EvtCreateRenderContext(kValueCnt, value_paths, EvtRenderContextValues);
+  if (!context)
+    DLOG(ERROR) << "Failed to create render context.";
+
+  return EvtHandle(context);
+}
+
+}  // namespace metrics
diff --git a/components/metrics/system_session_analyzer_win.h b/components/metrics/system_session_analyzer_win.h
new file mode 100644
index 0000000..2bd83e1
--- /dev/null
+++ b/components/metrics/system_session_analyzer_win.h
@@ -0,0 +1,102 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_BROWSER_WATCHER_SYSTEM_SESSION_ANALYZER_WIN_H_
+#define COMPONENTS_BROWSER_WATCHER_SYSTEM_SESSION_ANALYZER_WIN_H_
+
+#include <windows.h>
+#include <winevt.h>
+
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/time/time.h"
+
+namespace metrics {
+
+// Analyzes system session events for unclean sessions. Initialization is
+// expensive and therefore done lazily, as the analyzer is instantiated before
+// knowing whether it will be used.
+class SystemSessionAnalyzer {
+ public:
+  enum Status {
+    FAILED = 0,
+    CLEAN = 1,
+    UNCLEAN = 2,
+    OUTSIDE_RANGE = 3,
+  };
+
+  // Minimal information about a log event.
+  struct EventInfo {
+    uint16_t event_id;
+    base::Time event_time;
+  };
+
+  // Creates a SystemSessionAnalyzer that will analyze system sessions based on
+  // events pertaining to as many as |max_session_cnt| of the most recent system
+  // sessions.
+  explicit SystemSessionAnalyzer(uint32_t max_session_cnt);
+  virtual ~SystemSessionAnalyzer();
+
+  // Returns an analysis status for the system session that contains
+  // |timestamp|.
+  virtual Status IsSessionUnclean(base::Time timestamp);
+
+ protected:
+  // Queries for the next |requested_events|. On success, returns true and
+  // |event_infos| contains up to |requested_events| events ordered from newest
+  // to oldest.
+  // Returns false otherwise. Virtual for unit testing.
+  virtual bool FetchEvents(size_t requested_events,
+                           std::vector<EventInfo>* event_infos);
+
+ private:
+  struct EvtHandleCloser {
+    using pointer = EVT_HANDLE;
+    void operator()(EVT_HANDLE handle) const {
+      if (handle)
+        ::EvtClose(handle);
+    }
+  };
+  using EvtHandle = std::unique_ptr<EVT_HANDLE, EvtHandleCloser>;
+
+  FRIEND_TEST_ALL_PREFIXES(SystemSessionAnalyzerTest, FetchEvents);
+
+  bool EnsureInitialized();
+  bool EnsureHandlesOpened();
+  bool Initialize();
+  // Validates that |end| and |start| have sane event IDs and event times.
+  // Updates |coverage_start_| and adds the session to unclean_sessions_
+  // as appropriate.
+  bool ProcessSession(const EventInfo& end, const EventInfo& start);
+
+  EvtHandle CreateRenderContext();
+
+  // The maximal number of sessions to query events for.
+  uint32_t max_session_cnt_;
+  uint32_t sessions_queried_;
+
+  bool initialized_ = false;
+  bool init_success_ = false;
+
+  // A handle to the query, valid after a successful initialize.
+  EvtHandle query_handle_;
+  // A handle to the event render context, valid after a successful initialize.
+  EvtHandle render_context_;
+
+  // Information about unclean sessions: start time to session duration.
+  std::map<base::Time, base::TimeDelta> unclean_sessions_;
+
+  // Timestamp of the oldest event.
+  base::Time coverage_start_;
+
+  DISALLOW_COPY_AND_ASSIGN(SystemSessionAnalyzer);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_BROWSER_WATCHER_SYSTEM_SESSION_ANALYZER_WIN_H_
diff --git a/components/metrics/system_session_analyzer_win_unittest.cc b/components/metrics/system_session_analyzer_win_unittest.cc
new file mode 100644
index 0000000..bd7b1f8
--- /dev/null
+++ b/components/metrics/system_session_analyzer_win_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/system_session_analyzer_win.h"
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "base/time/time.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+
+namespace {
+
+const uint16_t kIdSessionStart = 6005U;
+const uint16_t kIdSessionEnd = 6006U;
+const uint16_t kIdSessionEndUnclean = 6008U;
+
+}  // namespace
+
+// Ensure the fetcher retrieves events.
+TEST(SystemSessionAnalyzerTest, FetchEvents) {
+  SystemSessionAnalyzer analyzer(0);
+  std::vector<SystemSessionAnalyzer::EventInfo> events;
+  ASSERT_TRUE(analyzer.FetchEvents(1U, &events));
+  EXPECT_EQ(1U, events.size());
+}
+
+// Ensure the fetcher's retrieved events conform to our expectations.
+// Note: this test fails if the host system doesn't have at least 1 prior
+// session.
+TEST(SystemSessionAnalyzerTest, ValidateEvents) {
+  SystemSessionAnalyzer analyzer(1U);
+  EXPECT_EQ(SystemSessionAnalyzer::CLEAN,
+            analyzer.IsSessionUnclean(base::Time::Now()));
+}
+
+// Stubs FetchEvents.
+class StubSystemSessionAnalyzer : public SystemSessionAnalyzer {
+ public:
+  StubSystemSessionAnalyzer(uint32_t max_session_cnt)
+      : SystemSessionAnalyzer(max_session_cnt) {}
+
+  bool FetchEvents(size_t requested_events,
+                   std::vector<EventInfo>* event_infos) override {
+    DCHECK(event_infos);
+    size_t num_to_copy = std::min(requested_events, events_.size());
+    if (num_to_copy) {
+      event_infos->clear();
+      event_infos->insert(event_infos->begin(), events_.begin(),
+                          events_.begin() + num_to_copy);
+      events_.erase(events_.begin(), events_.begin() + num_to_copy);
+    }
+
+    return true;
+  }
+
+  void AddEvent(const EventInfo& info) { events_.push_back(info); }
+
+ private:
+  std::vector<EventInfo> events_;
+};
+
+TEST(SystemSessionAnalyzerTest, StandardCase) {
+  StubSystemSessionAnalyzer analyzer(2U);
+
+  base::Time time = base::Time::Now();
+  analyzer.AddEvent({kIdSessionStart, time});
+  analyzer.AddEvent(
+      {kIdSessionEndUnclean, time - base::TimeDelta::FromSeconds(10)});
+  analyzer.AddEvent({kIdSessionStart, time - base::TimeDelta::FromSeconds(20)});
+  analyzer.AddEvent({kIdSessionEnd, time - base::TimeDelta::FromSeconds(22)});
+  analyzer.AddEvent({kIdSessionStart, time - base::TimeDelta::FromSeconds(28)});
+
+  EXPECT_EQ(SystemSessionAnalyzer::OUTSIDE_RANGE,
+            analyzer.IsSessionUnclean(time - base::TimeDelta::FromSeconds(30)));
+  EXPECT_EQ(SystemSessionAnalyzer::CLEAN,
+            analyzer.IsSessionUnclean(time - base::TimeDelta::FromSeconds(25)));
+  EXPECT_EQ(SystemSessionAnalyzer::UNCLEAN,
+            analyzer.IsSessionUnclean(time - base::TimeDelta::FromSeconds(20)));
+  EXPECT_EQ(SystemSessionAnalyzer::UNCLEAN,
+            analyzer.IsSessionUnclean(time - base::TimeDelta::FromSeconds(15)));
+  EXPECT_EQ(SystemSessionAnalyzer::UNCLEAN,
+            analyzer.IsSessionUnclean(time - base::TimeDelta::FromSeconds(10)));
+  EXPECT_EQ(SystemSessionAnalyzer::CLEAN,
+            analyzer.IsSessionUnclean(time - base::TimeDelta::FromSeconds(5)));
+  EXPECT_EQ(SystemSessionAnalyzer::CLEAN,
+            analyzer.IsSessionUnclean(time + base::TimeDelta::FromSeconds(5)));
+}
+
+TEST(SystemSessionAnalyzerTest, NoEvent) {
+  StubSystemSessionAnalyzer analyzer(0U);
+  EXPECT_EQ(SystemSessionAnalyzer::FAILED,
+            analyzer.IsSessionUnclean(base::Time::Now()));
+}
+
+TEST(SystemSessionAnalyzerTest, TimeInversion) {
+  StubSystemSessionAnalyzer analyzer(1U);
+
+  base::Time time = base::Time::Now();
+  analyzer.AddEvent({kIdSessionStart, time});
+  analyzer.AddEvent({kIdSessionEnd, time + base::TimeDelta::FromSeconds(1)});
+  analyzer.AddEvent({kIdSessionStart, time - base::TimeDelta::FromSeconds(1)});
+
+  EXPECT_EQ(SystemSessionAnalyzer::FAILED,
+            analyzer.IsSessionUnclean(base::Time::Now()));
+}
+
+TEST(SystemSessionAnalyzerTest, IdInversion) {
+  StubSystemSessionAnalyzer analyzer(1U);
+
+  base::Time time = base::Time::Now();
+  analyzer.AddEvent({kIdSessionStart, time});
+  analyzer.AddEvent({kIdSessionStart, time - base::TimeDelta::FromSeconds(1)});
+  analyzer.AddEvent({kIdSessionEnd, time - base::TimeDelta::FromSeconds(2)});
+
+  EXPECT_EQ(SystemSessionAnalyzer::FAILED,
+            analyzer.IsSessionUnclean(base::Time::Now()));
+}
+
+}  // namespace metrics
diff --git a/components/metrics/test_enabled_state_provider.cc b/components/metrics/test_enabled_state_provider.cc
new file mode 100644
index 0000000..d8c59e4
--- /dev/null
+++ b/components/metrics/test_enabled_state_provider.cc
@@ -0,0 +1,17 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/test_enabled_state_provider.h"
+
+namespace metrics {
+
+bool TestEnabledStateProvider::IsConsentGiven() const {
+  return consent_;
+}
+
+bool TestEnabledStateProvider::IsReportingEnabled() const {
+  return enabled_;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/test_enabled_state_provider.h b/components/metrics/test_enabled_state_provider.h
new file mode 100644
index 0000000..29fd7f1
--- /dev/null
+++ b/components/metrics/test_enabled_state_provider.h
@@ -0,0 +1,37 @@
+// 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.
+
+#ifndef COMPONENTS_METRICS_TEST_ENABLED_STATE_PROVIDER_H_
+#define COMPONENTS_METRICS_TEST_ENABLED_STATE_PROVIDER_H_
+
+#include "base/macros.h"
+#include "components/metrics/enabled_state_provider.h"
+
+namespace metrics {
+
+// A simple concrete implementation of the EnabledStateProvider interface, for
+// use in tests.
+class TestEnabledStateProvider : public EnabledStateProvider {
+ public:
+  TestEnabledStateProvider(bool consent, bool enabled)
+      : consent_(consent), enabled_(enabled) {}
+  ~TestEnabledStateProvider() override {}
+
+  // EnabledStateProvider
+  bool IsConsentGiven() const override;
+  bool IsReportingEnabled() const override;
+
+  void set_consent(bool consent) { consent_ = consent; }
+  void set_enabled(bool enabled) { enabled_ = enabled; }
+
+ private:
+  bool consent_;
+  bool enabled_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestEnabledStateProvider);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_TEST_ENABLED_STATE_PROVIDER_H_
diff --git a/components/metrics/test_metrics_log_uploader.cc b/components/metrics/test_metrics_log_uploader.cc
new file mode 100644
index 0000000..acb49d3
--- /dev/null
+++ b/components/metrics/test_metrics_log_uploader.cc
@@ -0,0 +1,31 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/test_metrics_log_uploader.h"
+#include "components/metrics/metrics_log_uploader.h"
+
+namespace metrics {
+
+TestMetricsLogUploader::TestMetricsLogUploader(
+    const MetricsLogUploader::UploadCallback& on_upload_complete)
+    : on_upload_complete_(on_upload_complete), is_uploading_(false) {}
+
+TestMetricsLogUploader::~TestMetricsLogUploader() = default;
+
+void TestMetricsLogUploader::CompleteUpload(int response_code) {
+  DCHECK(is_uploading_);
+  is_uploading_ = false;
+  last_reporting_info_.Clear();
+  on_upload_complete_.Run(response_code, 0, false);
+}
+
+void TestMetricsLogUploader::UploadLog(const std::string& compressed_log_data,
+                                       const std::string& log_hash,
+                                       const ReportingInfo& reporting_info) {
+  DCHECK(!is_uploading_);
+  is_uploading_ = true;
+  last_reporting_info_ = reporting_info;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/test_metrics_log_uploader.h b/components/metrics/test_metrics_log_uploader.h
new file mode 100644
index 0000000..c8d724a
--- /dev/null
+++ b/components/metrics/test_metrics_log_uploader.h
@@ -0,0 +1,42 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_TEST_METRICS_LOG_UPLOADER_H_
+#define COMPONENTS_METRICS_TEST_METRICS_LOG_UPLOADER_H_
+
+#include "components/metrics/metrics_log_uploader.h"
+#include "third_party/metrics_proto/reporting_info.pb.h"
+
+namespace metrics {
+
+class TestMetricsLogUploader : public MetricsLogUploader {
+ public:
+  explicit TestMetricsLogUploader(
+      const MetricsLogUploader::UploadCallback& on_upload_complete);
+  ~TestMetricsLogUploader() override;
+
+  // Mark the current upload complete with the given response code.
+  void CompleteUpload(int response_code);
+
+  // Check if UploadLog has been called.
+  bool is_uploading() const { return is_uploading_; }
+
+  const ReportingInfo& reporting_info() const { return last_reporting_info_; }
+
+ private:
+  // MetricsLogUploader:
+  void UploadLog(const std::string& compressed_log_data,
+                 const std::string& log_hash,
+                 const ReportingInfo& reporting_info) override;
+
+  const MetricsLogUploader::UploadCallback on_upload_complete_;
+  ReportingInfo last_reporting_info_;
+  bool is_uploading_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestMetricsLogUploader);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_TEST_METRICS_LOG_UPLOADER_H_
diff --git a/components/metrics/test_metrics_provider.cc b/components/metrics/test_metrics_provider.cc
new file mode 100644
index 0000000..1c7bb5e
--- /dev/null
+++ b/components/metrics/test_metrics_provider.cc
@@ -0,0 +1,42 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/test_metrics_provider.h"
+
+#include "base/metrics/histogram_macros.h"
+
+namespace metrics {
+
+void TestMetricsProvider::Init() {
+  init_called_ = true;
+}
+
+void TestMetricsProvider::OnRecordingDisabled() {
+  on_recording_disabled_called_ = true;
+}
+
+bool TestMetricsProvider::HasPreviousSessionData() {
+  has_initial_stability_metrics_called_ = true;
+  return has_initial_stability_metrics_;
+}
+
+void TestMetricsProvider::ProvidePreviousSessionData(
+    ChromeUserMetricsExtension* uma_proto) {
+  UMA_STABILITY_HISTOGRAM_ENUMERATION("TestMetricsProvider.Initial", 1, 2);
+  provide_initial_stability_metrics_called_ = true;
+  ProvideCurrentSessionData(nullptr);
+}
+
+void TestMetricsProvider::ProvideCurrentSessionData(
+    ChromeUserMetricsExtension* uma_proto) {
+  UMA_STABILITY_HISTOGRAM_ENUMERATION("TestMetricsProvider.Regular", 1, 2);
+  provide_stability_metrics_called_ = true;
+}
+
+void TestMetricsProvider::ProvideSystemProfileMetrics(
+    SystemProfileProto* system_profile_proto) {
+  provide_system_profile_metrics_called_ = true;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/test_metrics_provider.h b/components/metrics/test_metrics_provider.h
new file mode 100644
index 0000000..2578954
--- /dev/null
+++ b/components/metrics/test_metrics_provider.h
@@ -0,0 +1,69 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_TEST_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_TEST_METRICS_PROVIDER_H_
+
+#include "base/macros.h"
+#include "components/metrics/metrics_provider.h"
+
+namespace metrics {
+
+// A simple implementation of MetricsProvider that checks that its providing
+// functions are called, for use in tests.
+class TestMetricsProvider : public MetricsProvider {
+ public:
+  TestMetricsProvider()
+      : init_called_(false),
+        on_recording_disabled_called_(false),
+        has_initial_stability_metrics_(false),
+        has_initial_stability_metrics_called_(false),
+        provide_initial_stability_metrics_called_(false),
+        provide_stability_metrics_called_(false),
+        provide_system_profile_metrics_called_(false) {}
+
+  // MetricsProvider:
+  void Init() override;
+  void OnRecordingDisabled() override;
+  bool HasPreviousSessionData() override;
+  void ProvidePreviousSessionData(
+      ChromeUserMetricsExtension* uma_proto) override;
+  void ProvideCurrentSessionData(
+      ChromeUserMetricsExtension* uma_proto) override;
+  void ProvideSystemProfileMetrics(
+      SystemProfileProto* system_profile_proto) override;
+
+  bool init_called() { return init_called_; }
+  bool on_recording_disabled_called() { return on_recording_disabled_called_; }
+  bool has_initial_stability_metrics_called() {
+    return has_initial_stability_metrics_called_;
+  }
+  void set_has_initial_stability_metrics(bool has_initial_stability_metrics) {
+    has_initial_stability_metrics_ = has_initial_stability_metrics;
+  }
+  bool provide_initial_stability_metrics_called() const {
+    return provide_initial_stability_metrics_called_;
+  }
+  bool provide_stability_metrics_called() const {
+    return provide_stability_metrics_called_;
+  }
+  bool provide_system_profile_metrics_called() const {
+    return provide_system_profile_metrics_called_;
+  }
+
+ private:
+  bool init_called_;
+  bool on_recording_disabled_called_;
+  bool has_initial_stability_metrics_;
+  bool has_initial_stability_metrics_called_;
+  bool provide_initial_stability_metrics_called_;
+  bool provide_stability_metrics_called_;
+  bool provide_system_profile_metrics_called_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestMetricsProvider);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_TEST_METRICS_PROVIDER_H_
diff --git a/components/metrics/test_metrics_service_client.cc b/components/metrics/test_metrics_service_client.cc
new file mode 100644
index 0000000..4859b4e
--- /dev/null
+++ b/components/metrics/test_metrics_service_client.cc
@@ -0,0 +1,89 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/test_metrics_service_client.h"
+
+#include <memory>
+
+#include "base/callback.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
+
+namespace metrics {
+
+// static
+const char TestMetricsServiceClient::kBrandForTesting[] = "brand_for_testing";
+
+TestMetricsServiceClient::TestMetricsServiceClient()
+    : version_string_("5.0.322.0-64-devel"),
+      product_(ChromeUserMetricsExtension::CHROME),
+      reporting_is_managed_(false),
+      enable_default_(EnableMetricsDefault::DEFAULT_UNKNOWN) {}
+
+TestMetricsServiceClient::~TestMetricsServiceClient() {
+}
+
+metrics::MetricsService* TestMetricsServiceClient::GetMetricsService() {
+  return nullptr;
+}
+
+void TestMetricsServiceClient::SetMetricsClientId(
+    const std::string& client_id) {
+  client_id_ = client_id;
+}
+
+int32_t TestMetricsServiceClient::GetProduct() {
+  return product_;
+}
+
+std::string TestMetricsServiceClient::GetApplicationLocale() {
+  return "en-US";
+}
+
+bool TestMetricsServiceClient::GetBrand(std::string* brand_code) {
+  *brand_code = kBrandForTesting;
+  return true;
+}
+
+SystemProfileProto::Channel TestMetricsServiceClient::GetChannel() {
+  return SystemProfileProto::CHANNEL_BETA;
+}
+
+std::string TestMetricsServiceClient::GetVersionString() {
+  return version_string_;
+}
+
+void TestMetricsServiceClient::CollectFinalMetricsForLog(
+    const base::Closure& done_callback) {
+  done_callback.Run();
+}
+
+std::unique_ptr<MetricsLogUploader> TestMetricsServiceClient::CreateUploader(
+    base::StringPiece server_url,
+    base::StringPiece insecure_server_url,
+    base::StringPiece mime_type,
+    MetricsLogUploader::MetricServiceType service_type,
+    const MetricsLogUploader::UploadCallback& on_upload_complete) {
+  uploader_ = new TestMetricsLogUploader(on_upload_complete);
+  return std::unique_ptr<MetricsLogUploader>(uploader_);
+}
+
+base::TimeDelta TestMetricsServiceClient::GetStandardUploadInterval() {
+  return base::TimeDelta::FromMinutes(5);
+}
+
+bool TestMetricsServiceClient::IsReportingPolicyManaged() {
+  return reporting_is_managed_;
+}
+
+EnableMetricsDefault
+TestMetricsServiceClient::GetMetricsReportingDefaultState() {
+  return enable_default_;
+}
+
+std::string TestMetricsServiceClient::GetAppPackageName() {
+  return "test app";
+}
+
+}  // namespace metrics
diff --git a/components/metrics/test_metrics_service_client.h b/components/metrics/test_metrics_service_client.h
new file mode 100644
index 0000000..c495536
--- /dev/null
+++ b/components/metrics/test_metrics_service_client.h
@@ -0,0 +1,75 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_TEST_METRICS_SERVICE_CLIENT_H_
+#define COMPONENTS_METRICS_TEST_METRICS_SERVICE_CLIENT_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/metrics/metrics_log_uploader.h"
+#include "components/metrics/metrics_service_client.h"
+#include "components/metrics/test_metrics_log_uploader.h"
+
+namespace metrics {
+
+// A simple concrete implementation of the MetricsServiceClient interface, for
+// use in tests.
+class TestMetricsServiceClient : public MetricsServiceClient {
+ public:
+  static const char kBrandForTesting[];
+
+  TestMetricsServiceClient();
+  ~TestMetricsServiceClient() override;
+
+  // MetricsServiceClient:
+  metrics::MetricsService* GetMetricsService() override;
+  void SetMetricsClientId(const std::string& client_id) override;
+  int32_t GetProduct() override;
+  std::string GetApplicationLocale() override;
+  bool GetBrand(std::string* brand_code) override;
+  SystemProfileProto::Channel GetChannel() override;
+  std::string GetVersionString() override;
+  void CollectFinalMetricsForLog(const base::Closure& done_callback) override;
+  std::unique_ptr<MetricsLogUploader> CreateUploader(
+      base::StringPiece server_url,
+      base::StringPiece insecure_server_url,
+      base::StringPiece mime_type,
+      MetricsLogUploader::MetricServiceType service_type,
+      const MetricsLogUploader::UploadCallback& on_upload_complete) override;
+  base::TimeDelta GetStandardUploadInterval() override;
+  bool IsReportingPolicyManaged() override;
+  EnableMetricsDefault GetMetricsReportingDefaultState() override;
+  std::string GetAppPackageName() override;
+
+  const std::string& get_client_id() const { return client_id_; }
+  // Returns a weak ref to the last created uploader.
+  TestMetricsLogUploader* uploader() { return uploader_; }
+  void set_version_string(const std::string& str) { version_string_ = str; }
+  void set_product(int32_t product) { product_ = product; }
+  void set_reporting_is_managed(bool managed) {
+    reporting_is_managed_ = managed;
+  }
+  void set_enable_default(EnableMetricsDefault enable_default) {
+    enable_default_ = enable_default;
+  }
+
+ private:
+  std::string client_id_;
+  std::string version_string_;
+  int32_t product_;
+  bool reporting_is_managed_;
+  EnableMetricsDefault enable_default_;
+
+  // A weak ref to the last created TestMetricsLogUploader.
+  TestMetricsLogUploader* uploader_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestMetricsServiceClient);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_TEST_METRICS_SERVICE_CLIENT_H_
diff --git a/components/metrics/ui/DEPS b/components/metrics/ui/DEPS
new file mode 100644
index 0000000..e141313
--- /dev/null
+++ b/components/metrics/ui/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+ui/display",
+  "+ui/gfx",
+]
diff --git a/components/metrics/ui/screen_info_metrics_provider.cc b/components/metrics/ui/screen_info_metrics_provider.cc
new file mode 100644
index 0000000..f35639a
--- /dev/null
+++ b/components/metrics/ui/screen_info_metrics_provider.cc
@@ -0,0 +1,97 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/ui/screen_info_metrics_provider.h"
+
+#include "build/build_config.h"
+#include "third_party/metrics_proto/system_profile.pb.h"
+#include "ui/display/display.h"
+#include "ui/display/screen.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace metrics {
+
+#if defined(OS_WIN)
+
+namespace {
+
+struct ScreenDPIInformation {
+  double max_dpi_x;
+  double max_dpi_y;
+};
+
+// Called once for each connected monitor.
+BOOL CALLBACK GetMonitorDPICallback(HMONITOR, HDC hdc, LPRECT, LPARAM dwData) {
+  const double kMillimetersPerInch = 25.4;
+  ScreenDPIInformation* screen_info =
+      reinterpret_cast<ScreenDPIInformation*>(dwData);
+  // Size of screen, in mm.
+  DWORD size_x = GetDeviceCaps(hdc, HORZSIZE);
+  DWORD size_y = GetDeviceCaps(hdc, VERTSIZE);
+  double dpi_x = (size_x > 0) ?
+      GetDeviceCaps(hdc, HORZRES) / (size_x / kMillimetersPerInch) : 0;
+  double dpi_y = (size_y > 0) ?
+      GetDeviceCaps(hdc, VERTRES) / (size_y / kMillimetersPerInch) : 0;
+  screen_info->max_dpi_x = std::max(dpi_x, screen_info->max_dpi_x);
+  screen_info->max_dpi_y = std::max(dpi_y, screen_info->max_dpi_y);
+  return TRUE;
+}
+
+void WriteScreenDPIInformationProto(SystemProfileProto::Hardware* hardware) {
+  HDC desktop_dc = GetDC(nullptr);
+  if (desktop_dc) {
+    ScreenDPIInformation si = {0, 0};
+    if (EnumDisplayMonitors(desktop_dc, nullptr, GetMonitorDPICallback,
+                            reinterpret_cast<LPARAM>(&si))) {
+      hardware->set_max_dpi_x(si.max_dpi_x);
+      hardware->set_max_dpi_y(si.max_dpi_y);
+    }
+    ReleaseDC(GetDesktopWindow(), desktop_dc);
+  }
+}
+
+}  // namespace
+
+#endif  // defined(OS_WIN)
+
+ScreenInfoMetricsProvider::ScreenInfoMetricsProvider() {
+}
+
+ScreenInfoMetricsProvider::~ScreenInfoMetricsProvider() {
+}
+
+void ScreenInfoMetricsProvider::ProvideSystemProfileMetrics(
+    SystemProfileProto* system_profile_proto) {
+  SystemProfileProto::Hardware* hardware =
+      system_profile_proto->mutable_hardware();
+
+  const gfx::Size display_size = GetScreenSize();
+  hardware->set_primary_screen_width(display_size.width());
+  hardware->set_primary_screen_height(display_size.height());
+  hardware->set_primary_screen_scale_factor(GetScreenDeviceScaleFactor());
+  hardware->set_screen_count(GetScreenCount());
+
+#if defined(OS_WIN)
+  WriteScreenDPIInformationProto(hardware);
+#endif
+}
+
+gfx::Size ScreenInfoMetricsProvider::GetScreenSize() const {
+  return display::Screen::GetScreen()->GetPrimaryDisplay().GetSizeInPixel();
+}
+
+float ScreenInfoMetricsProvider::GetScreenDeviceScaleFactor() const {
+  return display::Screen::GetScreen()
+      ->GetPrimaryDisplay()
+      .device_scale_factor();
+}
+
+int ScreenInfoMetricsProvider::GetScreenCount() const {
+  return display::Screen::GetScreen()->GetNumDisplays();
+}
+
+}  // namespace metrics
diff --git a/components/metrics/ui/screen_info_metrics_provider.h b/components/metrics/ui/screen_info_metrics_provider.h
new file mode 100644
index 0000000..51ef2b7
--- /dev/null
+++ b/components/metrics/ui/screen_info_metrics_provider.h
@@ -0,0 +1,42 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_UI_SCREEN_INFO_METRICS_PROVIDER_H_
+#define COMPONENTS_METRICS_UI_SCREEN_INFO_METRICS_PROVIDER_H_
+
+#include "base/macros.h"
+#include "components/metrics/metrics_provider.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace metrics {
+
+// ScreenInfoMetricsProvider provides metrics related to screen info.
+class ScreenInfoMetricsProvider : public MetricsProvider {
+ public:
+  ScreenInfoMetricsProvider();
+  ~ScreenInfoMetricsProvider() override;
+
+  // MetricsProvider:
+  void ProvideSystemProfileMetrics(
+      SystemProfileProto* system_profile_proto) override;
+
+ protected:
+  // Exposed for the sake of mocking in test code.
+
+  // Returns the screen size for the primary monitor.
+  virtual gfx::Size GetScreenSize() const;
+
+  // Returns the device scale factor for the primary monitor.
+  virtual float GetScreenDeviceScaleFactor() const;
+
+  // Returns the number of monitors the user is using.
+  virtual int GetScreenCount() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScreenInfoMetricsProvider);
+};
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_UI_SCREEN_INFO_METRICS_PROVIDER_H_
diff --git a/components/metrics/ui/screen_info_metrics_provider_unittest.cc b/components/metrics/ui/screen_info_metrics_provider_unittest.cc
new file mode 100644
index 0000000..836e977
--- /dev/null
+++ b/components/metrics/ui/screen_info_metrics_provider_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/ui/screen_info_metrics_provider.h"
+
+#include "base/macros.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace metrics {
+
+namespace {
+
+const int kScreenWidth = 1024;
+const int kScreenHeight = 768;
+const int kScreenCount = 3;
+const float kScreenScaleFactor = 2;
+
+class TestScreenInfoMetricsProvider : public ScreenInfoMetricsProvider {
+ public:
+  TestScreenInfoMetricsProvider() {}
+  ~TestScreenInfoMetricsProvider() override {}
+
+ private:
+  gfx::Size GetScreenSize() const override {
+    return gfx::Size(kScreenWidth, kScreenHeight);
+  }
+
+  float GetScreenDeviceScaleFactor() const override {
+    return kScreenScaleFactor;
+  }
+
+  int GetScreenCount() const override { return kScreenCount; }
+
+  DISALLOW_COPY_AND_ASSIGN(TestScreenInfoMetricsProvider);
+};
+
+}  // namespace
+
+class ScreenInfoMetricsProviderTest : public testing::Test {
+ public:
+  ScreenInfoMetricsProviderTest() {}
+  ~ScreenInfoMetricsProviderTest() override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScreenInfoMetricsProviderTest);
+};
+
+TEST_F(ScreenInfoMetricsProviderTest, ProvideSystemProfileMetrics) {
+  TestScreenInfoMetricsProvider provider;
+  ChromeUserMetricsExtension uma_proto;
+
+  provider.ProvideSystemProfileMetrics(uma_proto.mutable_system_profile());
+
+  // Check that the system profile has the correct values set.
+  const SystemProfileProto::Hardware& hardware =
+      uma_proto.system_profile().hardware();
+  EXPECT_EQ(kScreenWidth, hardware.primary_screen_width());
+  EXPECT_EQ(kScreenHeight, hardware.primary_screen_height());
+  EXPECT_EQ(kScreenScaleFactor, hardware.primary_screen_scale_factor());
+  EXPECT_EQ(kScreenCount, hardware.screen_count());
+}
+
+}  // namespace metrics
diff --git a/components/metrics/url_constants.cc b/components/metrics/url_constants.cc
new file mode 100644
index 0000000..f60d33b
--- /dev/null
+++ b/components/metrics/url_constants.cc
@@ -0,0 +1,19 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/url_constants.h"
+
+namespace metrics {
+
+const char kNewMetricsServerUrl[] =
+    "https://clientservices.googleapis.com/uma/v2";
+
+const char kNewMetricsServerUrlInsecure[] =
+    "http://clientservices.googleapis.com/uma/v2";
+
+const char kOldMetricsServerUrl[] = "https://clients4.google.com/uma/v2";
+
+const char kDefaultMetricsMimeType[] = "application/vnd.chrome.uma";
+
+} // namespace metrics
diff --git a/components/metrics/url_constants.h b/components/metrics/url_constants.h
new file mode 100644
index 0000000..0f4c58c
--- /dev/null
+++ b/components/metrics/url_constants.h
@@ -0,0 +1,24 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_URL_CONSTANTS_H_
+#define COMPONENTS_METRICS_URL_CONSTANTS_H_
+
+namespace metrics {
+
+// The new metrics server's URL.
+extern const char kNewMetricsServerUrl[];
+
+// The HTTP fallback metrics server's URL.
+extern const char kNewMetricsServerUrlInsecure[];
+
+// The old metrics server's URL.
+extern const char kOldMetricsServerUrl[];
+
+// The default MIME type for the uploaded metrics data.
+extern const char kDefaultMetricsMimeType[];
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_URL_CONSTANTS_H_
diff --git a/components/metrics/version_utils.cc b/components/metrics/version_utils.cc
new file mode 100644
index 0000000..d24932b
--- /dev/null
+++ b/components/metrics/version_utils.cc
@@ -0,0 +1,40 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/version_utils.h"
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "components/version_info/version_info.h"
+
+namespace metrics {
+
+std::string GetVersionString() {
+  std::string version = version_info::GetVersionNumber();
+#if defined(ARCH_CPU_64_BITS)
+  version += "-64";
+#endif  // defined(ARCH_CPU_64_BITS)
+  if (!version_info::IsOfficialBuild())
+    version.append("-devel");
+  return version;
+}
+
+SystemProfileProto::Channel AsProtobufChannel(version_info::Channel channel) {
+  switch (channel) {
+    case version_info::Channel::UNKNOWN:
+      return SystemProfileProto::CHANNEL_UNKNOWN;
+    case version_info::Channel::CANARY:
+      return SystemProfileProto::CHANNEL_CANARY;
+    case version_info::Channel::DEV:
+      return SystemProfileProto::CHANNEL_DEV;
+    case version_info::Channel::BETA:
+      return SystemProfileProto::CHANNEL_BETA;
+    case version_info::Channel::STABLE:
+      return SystemProfileProto::CHANNEL_STABLE;
+  }
+  NOTREACHED();
+  return SystemProfileProto::CHANNEL_UNKNOWN;
+}
+
+}  // namespace metrics
diff --git a/components/metrics/version_utils.h b/components/metrics/version_utils.h
new file mode 100644
index 0000000..387d664
--- /dev/null
+++ b/components/metrics/version_utils.h
@@ -0,0 +1,28 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_VERSION_UTILS_H_
+#define COMPONENTS_METRICS_VERSION_UTILS_H_
+
+#include <string>
+
+#include "third_party/metrics_proto/system_profile.pb.h"
+
+namespace version_info {
+enum class Channel;
+}
+
+namespace metrics {
+
+// Build a string including the Chrome app version, suffixed by "-64" on 64-bit
+// platforms, and "-devel" on developer builds.
+std::string GetVersionString();
+
+// Translates version_info::Channel to the equivalent
+// SystemProfileProto::Channel.
+SystemProfileProto::Channel AsProtobufChannel(version_info::Channel channel);
+
+}  // namespace metrics
+
+#endif  // COMPONENTS_METRICS_VERSION_UTILS_H_
diff --git a/components/metrics_services_manager/BUILD.gn b/components/metrics_services_manager/BUILD.gn
new file mode 100644
index 0000000..7be3654
--- /dev/null
+++ b/components/metrics_services_manager/BUILD.gn
@@ -0,0 +1,22 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("metrics_services_manager") {
+  sources = [
+    "metrics_services_manager.cc",
+    "metrics_services_manager.h",
+    "metrics_services_manager_client.cc",
+    "metrics_services_manager_client.h",
+  ]
+
+  deps = [
+    "//base",
+    "//components/metrics",
+    "//components/rappor",
+    "//components/ukm",
+    "//components/variations",
+    "//components/variations/service",
+    "//services/network/public/cpp:cpp",
+  ]
+}
diff --git a/components/metrics_services_manager/DEPS b/components/metrics_services_manager/DEPS
new file mode 100644
index 0000000..d90fc81
--- /dev/null
+++ b/components/metrics_services_manager/DEPS
@@ -0,0 +1,9 @@
+include_rules = [
+  "-components",
+  "+components/metrics",
+  "+components/metrics_services_manager",
+  "+components/rappor",
+  "+components/ukm",
+  "+components/variations",
+  "+services/network",
+]
diff --git a/components/metrics_services_manager/METADATA b/components/metrics_services_manager/METADATA
new file mode 100644
index 0000000..f64a0e6
--- /dev/null
+++ b/components/metrics_services_manager/METADATA
@@ -0,0 +1,21 @@
+name: "metrics_services_manager"
+description:
+  "Filtered subtree at components/metrics_services_manager."
+
+third_party {
+  url {
+    type: LOCAL_SOURCE
+    value: "https://cobalt.googlesource.com/components/metrics_services_manager_filtered_mirror"
+  }
+  url {
+    type: GIT
+    value: "https://github.com/chromium/chromium"
+  }
+  # Closest commit hash to m70.
+  version: "c413b7aea586859accffb96cd77b2b5078ff6607"
+  last_upgrade_date {
+    year: 2018
+    month: 05
+    day: 22
+  }
+}
diff --git a/components/metrics_services_manager/OWNERS b/components/metrics_services_manager/OWNERS
new file mode 100644
index 0000000..c929027
--- /dev/null
+++ b/components/metrics_services_manager/OWNERS
@@ -0,0 +1,3 @@
+file://base/metrics/OWNERS
+
+# COMPONENT: Internals>Metrics
diff --git a/components/metrics_services_manager/metrics_services_manager.cc b/components/metrics_services_manager/metrics_services_manager.cc
new file mode 100644
index 0000000..4952631
--- /dev/null
+++ b/components/metrics_services_manager/metrics_services_manager.cc
@@ -0,0 +1,177 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics_services_manager/metrics_services_manager.h"
+
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "components/metrics/metrics_service.h"
+#include "components/metrics/metrics_service_client.h"
+#include "components/metrics/metrics_state_manager.h"
+#include "components/metrics/metrics_switches.h"
+#include "components/metrics_services_manager/metrics_services_manager_client.h"
+#include "components/rappor/rappor_service_impl.h"
+#include "components/ukm/ukm_service.h"
+#include "components/variations/service/variations_service.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+namespace metrics_services_manager {
+
+MetricsServicesManager::MetricsServicesManager(
+    std::unique_ptr<MetricsServicesManagerClient> client)
+    : client_(std::move(client)),
+      may_upload_(false),
+      may_record_(false),
+      consent_given_(false) {
+  DCHECK(client_);
+}
+
+MetricsServicesManager::~MetricsServicesManager() {}
+
+std::unique_ptr<const base::FieldTrial::EntropyProvider>
+MetricsServicesManager::CreateEntropyProvider() {
+  return client_->CreateEntropyProvider();
+}
+
+metrics::MetricsService* MetricsServicesManager::GetMetricsService() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  return GetMetricsServiceClient()->GetMetricsService();
+}
+
+rappor::RapporServiceImpl* MetricsServicesManager::GetRapporServiceImpl() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!rappor_service_) {
+    rappor_service_ = client_->CreateRapporServiceImpl();
+    rappor_service_->Initialize(client_->GetURLLoaderFactory());
+  }
+  return rappor_service_.get();
+}
+
+ukm::UkmService* MetricsServicesManager::GetUkmService() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  return GetMetricsServiceClient()->GetUkmService();
+}
+
+variations::VariationsService* MetricsServicesManager::GetVariationsService() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!variations_service_)
+    variations_service_ = client_->CreateVariationsService();
+  return variations_service_.get();
+}
+
+void MetricsServicesManager::OnPluginLoadingError(
+    const base::FilePath& plugin_path) {
+  GetMetricsServiceClient()->OnPluginLoadingError(plugin_path);
+}
+
+void MetricsServicesManager::OnRendererProcessCrash() {
+  GetMetricsServiceClient()->OnRendererProcessCrash();
+}
+
+metrics::MetricsServiceClient*
+MetricsServicesManager::GetMetricsServiceClient() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  if (!metrics_service_client_) {
+    metrics_service_client_ = client_->CreateMetricsServiceClient();
+    // base::Unretained is safe since |this| owns the metrics_service_client_.
+    metrics_service_client_->SetUpdateRunningServicesCallback(
+        base::Bind(&MetricsServicesManager::UpdateRunningServices,
+                   base::Unretained(this)));
+  }
+  return metrics_service_client_.get();
+}
+
+void MetricsServicesManager::UpdatePermissions(bool current_may_record,
+                                               bool current_consent_given,
+                                               bool current_may_upload) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // If the user has opted out of metrics, delete local UKM state. We Only check
+  // consent for UKM.
+  if (consent_given_ && !current_consent_given) {
+    ukm::UkmService* ukm = GetUkmService();
+    if (ukm) {
+      ukm->Purge();
+      ukm->ResetClientId();
+    }
+  }
+
+  // Stash the current permissions so that we can update the RapporServiceImpl
+  // correctly when the Rappor preference changes.
+  may_record_ = current_may_record;
+  consent_given_ = current_consent_given;
+  may_upload_ = current_may_upload;
+  UpdateRunningServices();
+}
+
+void MetricsServicesManager::UpdateRunningServices() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  metrics::MetricsService* metrics = GetMetricsService();
+
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+  if (cmdline->HasSwitch(metrics::switches::kMetricsRecordingOnly)) {
+    metrics->StartRecordingForTests();
+    GetRapporServiceImpl()->Update(true, false);
+    return;
+  }
+
+  client_->UpdateRunningServices(may_record_, may_upload_);
+
+  if (may_record_) {
+    if (!metrics->recording_active())
+      metrics->Start();
+    if (may_upload_)
+      metrics->EnableReporting();
+    else
+      metrics->DisableReporting();
+  } else {
+    metrics->Stop();
+  }
+
+  UpdateUkmService();
+
+  GetRapporServiceImpl()->Update(may_record_, may_upload_);
+}
+
+void MetricsServicesManager::UpdateUkmService() {
+  ukm::UkmService* ukm = GetUkmService();
+  if (!ukm)
+    return;
+
+  bool listeners_active =
+      GetMetricsServiceClient()->AreNotificationListenersEnabledOnAllProfiles();
+  bool sync_enabled = client_->IsMetricsReportingForceEnabled() ||
+                      metrics_service_client_->SyncStateAllowsUkm();
+  bool is_incognito = client_->IsIncognitoSessionActive();
+
+  if (consent_given_ && listeners_active && sync_enabled && !is_incognito) {
+    // TODO(skare): revise this - merged in a big change
+    ukm->EnableRecording(
+        metrics_service_client_->SyncStateAllowsExtensionUkm());
+    if (may_upload_)
+      ukm->EnableReporting();
+    else
+      ukm->DisableReporting();
+  } else {
+    ukm->DisableRecording();
+    ukm->DisableReporting();
+  }
+}
+
+void MetricsServicesManager::UpdateUploadPermissions(bool may_upload) {
+  if (client_->IsMetricsReportingForceEnabled()) {
+    UpdatePermissions(true, true, true);
+    return;
+  }
+
+  UpdatePermissions(client_->IsMetricsReportingEnabled(),
+                    client_->IsMetricsConsentGiven(), may_upload);
+}
+
+bool MetricsServicesManager::IsMetricsReportingEnabled() const {
+  return client_->IsMetricsReportingEnabled();
+}
+
+}  // namespace metrics_services_manager
diff --git a/components/metrics_services_manager/metrics_services_manager.h b/components/metrics_services_manager/metrics_services_manager.h
new file mode 100644
index 0000000..912bf9a
--- /dev/null
+++ b/components/metrics_services_manager/metrics_services_manager.h
@@ -0,0 +1,138 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_SERVICES_MANAGER_METRICS_SERVICES_MANAGER_H_
+#define COMPONENTS_METRICS_SERVICES_MANAGER_METRICS_SERVICES_MANAGER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/metrics/field_trial.h"
+#include "base/threading/thread_checker.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace metrics {
+class MetricsService;
+class MetricsServiceClient;
+class MetricsStateManager;
+}
+
+namespace rappor {
+class RapporServiceImpl;
+}
+
+namespace ukm {
+class UkmService;
+}
+
+namespace variations {
+class VariationsService;
+}
+
+namespace metrics_services_manager {
+
+class MetricsServicesManagerClient;
+
+// MetricsServicesManager is a helper class for embedders that use the various
+// metrics-related services in a Chrome-like fashion: MetricsService (via its
+// client), RapporServiceImpl and VariationsService.
+class MetricsServicesManager {
+ public:
+  // Creates the MetricsServicesManager with the given client.
+  explicit MetricsServicesManager(
+      std::unique_ptr<MetricsServicesManagerClient> client);
+  virtual ~MetricsServicesManager();
+
+  // Returns the preferred entropy provider used to seed persistent activities
+  // based on whether or not metrics reporting is permitted on this client.
+  //
+  // If there's consent to report metrics, this method returns an entropy
+  // provider that has a high source of entropy, partially based on the client
+  // ID. Otherwise, it returns an entropy provider that is based on a low
+  // entropy source.
+  std::unique_ptr<const base::FieldTrial::EntropyProvider>
+  CreateEntropyProvider();
+
+  // Returns the MetricsService, creating it if it hasn't been created yet (and
+  // additionally creating the MetricsServiceClient in that case).
+  metrics::MetricsService* GetMetricsService();
+
+  // Returns the RapporServiceImpl, creating it if it hasn't been created yet.
+  rappor::RapporServiceImpl* GetRapporServiceImpl();
+
+  // Returns the UkmService, creating it if it hasn't been created yet.
+  ukm::UkmService* GetUkmService();
+
+  // Returns the VariationsService, creating it if it hasn't been created yet.
+  variations::VariationsService* GetVariationsService();
+
+  // Should be called when a plugin loading error occurs.
+  void OnPluginLoadingError(const base::FilePath& plugin_path);
+
+  // Some embedders use this method to notify the metrics system when a
+  // renderer process exits unexpectedly.
+  void OnRendererProcessCrash();
+
+  // Update the managed services when permissions for uploading metrics change.
+  void UpdateUploadPermissions(bool may_upload);
+
+  // Gets the current state of metric reporting.
+  bool IsMetricsReportingEnabled() const;
+
+ private:
+  // Update the managed services when permissions for recording/uploading
+  // metrics change.
+  void UpdateRapporServiceImpl();
+
+  // Returns the MetricsServiceClient, creating it if it hasn't been
+  // created yet (and additionally creating the MetricsService in that case).
+  metrics::MetricsServiceClient* GetMetricsServiceClient();
+
+  metrics::MetricsStateManager* GetMetricsStateManager();
+
+  // Update which services are running to match current permissions.
+  void UpdateRunningServices();
+
+  // Update the state of UkmService to match current permissions.
+  void UpdateUkmService();
+
+  // Update the managed services when permissions for recording/uploading
+  // metrics change.
+  void UpdatePermissions(bool current_may_record,
+                         bool current_consent_given,
+                         bool current_may_upload);
+
+  // The client passed in from the embedder.
+  const std::unique_ptr<MetricsServicesManagerClient> client_;
+
+  // Ensures that all functions are called from the same thread.
+  base::ThreadChecker thread_checker_;
+
+  // The current metrics reporting setting.
+  bool may_upload_;
+
+  // The current metrics recording setting.
+  bool may_record_;
+
+  // The current metrics setting reflecting if consent was given.
+  bool consent_given_;
+
+  // The MetricsServiceClient. Owns the MetricsService.
+  std::unique_ptr<metrics::MetricsServiceClient> metrics_service_client_;
+
+  // The RapporServiceImpl, for RAPPOR metric uploads.
+  std::unique_ptr<rappor::RapporServiceImpl> rappor_service_;
+
+  // The VariationsService, for server-side experiments infrastructure.
+  std::unique_ptr<variations::VariationsService> variations_service_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsServicesManager);
+};
+
+}  // namespace metrics_services_manager
+
+#endif  // COMPONENTS_METRICS_SERVICES_MANAGER_METRICS_SERVICES_MANAGER_H_
diff --git a/components/metrics_services_manager/metrics_services_manager_client.cc b/components/metrics_services_manager/metrics_services_manager_client.cc
new file mode 100644
index 0000000..05c6869
--- /dev/null
+++ b/components/metrics_services_manager/metrics_services_manager_client.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics_services_manager/metrics_services_manager_client.h"
+
+namespace metrics_services_manager {
+
+bool MetricsServicesManagerClient::IsMetricsReportingForceEnabled() {
+  return false;
+}
+
+}  // namespace metrics_services_manager
diff --git a/components/metrics_services_manager/metrics_services_manager_client.h b/components/metrics_services_manager/metrics_services_manager_client.h
new file mode 100644
index 0000000..138f1e0
--- /dev/null
+++ b/components/metrics_services_manager/metrics_services_manager_client.h
@@ -0,0 +1,70 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_SERVICES_MANAGER_METRICS_SERVICES_MANAGER_CLIENT_H_
+#define COMPONENTS_METRICS_SERVICES_MANAGER_METRICS_SERVICES_MANAGER_CLIENT_H_
+
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/metrics/field_trial.h"
+
+namespace metrics {
+class MetricsServiceClient;
+}
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace rappor {
+class RapporServiceImpl;
+}
+
+namespace variations {
+class VariationsService;
+}
+
+namespace metrics_services_manager {
+
+// MetricsServicesManagerClient is an interface that allows
+// MetricsServicesManager to interact with its embedder.
+class MetricsServicesManagerClient {
+ public:
+  virtual ~MetricsServicesManagerClient() {}
+
+  // Methods that create the various services in the context of the embedder.
+  virtual std::unique_ptr<rappor::RapporServiceImpl>
+  CreateRapporServiceImpl() = 0;
+  virtual std::unique_ptr<variations::VariationsService>
+  CreateVariationsService() = 0;
+  virtual std::unique_ptr<metrics::MetricsServiceClient>
+  CreateMetricsServiceClient() = 0;
+  virtual std::unique_ptr<const base::FieldTrial::EntropyProvider>
+  CreateEntropyProvider() = 0;
+
+  // Returns the URL loader factory which the metrics services should use.
+  virtual scoped_refptr<network::SharedURLLoaderFactory>
+  GetURLLoaderFactory() = 0;
+
+  // Returns whether metrics reporting is enabled.
+  virtual bool IsMetricsReportingEnabled() = 0;
+
+  // Returns whether metrics consent is given.
+  virtual bool IsMetricsConsentGiven() = 0;
+
+  // Returns whether there are any Incognito browsers/tabs open.
+  virtual bool IsIncognitoSessionActive() = 0;
+
+  // Update the running state of metrics services managed by the embedder, for
+  // example, crash reporting.
+  virtual void UpdateRunningServices(bool may_record, bool may_upload) {}
+
+  // If the user has forced metrics collection on via the override flag.
+  virtual bool IsMetricsReportingForceEnabled();
+};
+
+}  // namespace metrics_services_manager
+
+#endif  // COMPONENTS_METRICS_SERVICES_MANAGER_METRICS_SERVICES_MANAGER_CLIENT_H_
diff --git a/components/prefs/json_pref_store.cc b/components/prefs/json_pref_store.cc
index 7d8d189..d0d7481 100644
--- a/components/prefs/json_pref_store.cc
+++ b/components/prefs/json_pref_store.cc
@@ -206,9 +206,13 @@
   base::Value* old_value = nullptr;
   prefs_->Get(key, &old_value);
   if (!old_value || !value->Equals(old_value)) {
+#if defined(STARBOARD)
     // Value::DictionaryValue::Set creates a nested dictionary treating a URL
     // key as a path, SetKey avoids this.
-    prefs_->SetKey(key, std::move(*value.get()));
+    prefs_->SetKey(key, base::Value::FromUniquePtrValue(std::move(value)));
+#else
+    prefs_->Set(key, std::move(value));
+#endif
     ReportValueChanged(key, flags);
   }
 }
@@ -222,7 +226,11 @@
   base::Value* old_value = nullptr;
   prefs_->Get(key, &old_value);
   if (!old_value || !value->Equals(old_value)) {
-    prefs_->SetPath({key}, base::Value::FromUniquePtrValue(std::move(value)));
+#if defined(STARBOARD)
+    prefs_->SetKey(key, base::Value::FromUniquePtrValue(std::move(value)));
+#else
+    prefs_->Set(key, std::move(value));
+#endif
     ScheduleWrite(flags);
   }
 }
diff --git a/components/ukm/BUILD.gn b/components/ukm/BUILD.gn
new file mode 100644
index 0000000..6bb8424
--- /dev/null
+++ b/components/ukm/BUILD.gn
@@ -0,0 +1,118 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//testing/test.gni")
+
+# The Url-Keyed Metrics (UKM) service is responsible for gathering and
+# uploading reports that contain fine grained performance metrics including
+# URLs for top-level navigations.
+static_library("ukm") {
+  sources = [
+    "persisted_logs_metrics_impl.cc",
+    "persisted_logs_metrics_impl.h",
+    "ukm_pref_names.cc",
+    "ukm_pref_names.h",
+    "ukm_recorder_impl.cc",
+    "ukm_recorder_impl.h",
+    "ukm_reporting_service.cc",
+    "ukm_reporting_service.h",
+    "ukm_rotation_scheduler.cc",
+    "ukm_rotation_scheduler.h",
+    "ukm_service.cc",
+    "ukm_service.h",
+  ]
+
+  public_deps = [
+    "//services/metrics/public/cpp:metrics_cpp",
+    "//services/metrics/public/cpp:ukm_builders",
+    "//services/metrics/public/mojom",
+    "//third_party/metrics_proto",
+  ]
+
+  deps = [
+    "//base",
+    "//components/data_use_measurement/core",
+    "//components/metrics",
+    "//components/prefs",
+    "//components/variations",
+    "//url",
+  ]
+}
+
+# Helper library for observing signals that we need to clear any local data.
+static_library("observers") {
+  sources = [
+    "observers/history_delete_observer.cc",
+    "observers/history_delete_observer.h",
+    "observers/sync_disable_observer.cc",
+    "observers/sync_disable_observer.h",
+  ]
+
+  deps = [
+    "//base",
+    "//components/history/core/browser",
+    "//components/sync",
+  ]
+
+  public_deps = [
+    "//components/unified_consent",
+  ]
+}
+
+static_library("test_support") {
+  testonly = true
+  sources = [
+    "test_ukm_recorder.cc",
+    "test_ukm_recorder.h",
+  ]
+
+  public_deps = [
+    ":ukm",
+    "//third_party/metrics_proto",
+  ]
+  deps = [
+    "//base",
+    "//components/metrics:test_support",
+    "//components/prefs:test_support",
+    "//testing/gtest:gtest",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "observers/sync_disable_observer_unittest.cc",
+    "ukm_service_unittest.cc",
+  ]
+
+  deps = [
+    ":observers",
+    ":test_support",
+    ":ukm",
+    "//base",
+    "//base/test:test_support",
+    "//components/metrics",
+    "//components/metrics:test_support",
+    "//components/prefs:test_support",
+    "//components/sync",
+    "//components/sync:test_support_driver",
+    "//components/sync_preferences:test_support",
+    "//components/variations",
+    "//net:test_support",
+    "//services/metrics/public/cpp:ukm_builders",
+    "//testing/gtest",
+    "//third_party/zlib/google:compression_utils",
+    "//url",
+  ]
+}
+
+# Convenience testing target
+test("ukm_unittests") {
+  deps = [
+    ":unit_tests",
+    "//base",
+    "//base/test:test_support",
+    "//components/test:run_all_unittests",
+  ]
+}
diff --git a/components/ukm/DEPS b/components/ukm/DEPS
new file mode 100644
index 0000000..053cb84
--- /dev/null
+++ b/components/ukm/DEPS
@@ -0,0 +1,16 @@
+include_rules = [
+  "+components/metrics",
+  "+components/prefs",
+  "+components/unified_consent",
+  "+components/variations",
+  "+mojo/public",
+  "+services/metrics/public",
+  "+third_party/metrics_proto",
+  "+third_party/zlib/google",
+]
+
+specific_include_rules = {
+  ".*unittest\.cc": [
+    "+components/sync_preferences/testing_pref_service_syncable.h",
+  ]
+}
\ No newline at end of file
diff --git a/components/ukm/METADATA b/components/ukm/METADATA
new file mode 100644
index 0000000..7947c4b
--- /dev/null
+++ b/components/ukm/METADATA
@@ -0,0 +1,21 @@
+name: "ukm"
+description:
+  "Filtered subtree at components/ukm."
+
+third_party {
+  url {
+    type: LOCAL_SOURCE
+    value: "https://cobalt.googlesource.com/components_ukm_filtered_mirror"
+  }
+  url {
+    type: GIT
+    value: "https://github.com/chromium/chromium"
+  }
+  # Closest commit hash to m70.
+  version: "73162fc55e079d35b6f8c02a6ceee6298f0b3b7e"
+  last_upgrade_date {
+    year: 2018
+    month: 08
+    day: 29
+  }
+}
diff --git a/components/ukm/OWNERS b/components/ukm/OWNERS
new file mode 100644
index 0000000..684cb16
--- /dev/null
+++ b/components/ukm/OWNERS
@@ -0,0 +1,3 @@
+file://base/metrics/OWNERS
+
+# COMPONENT: Internals>Metrics>UKM
diff --git a/components/ukm/content/BUILD.gn b/components/ukm/content/BUILD.gn
new file mode 100644
index 0000000..42ee889
--- /dev/null
+++ b/components/ukm/content/BUILD.gn
@@ -0,0 +1,42 @@
+# Copyright 2017 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("content") {
+  sources = [
+    "app_source_url_recorder.cc",
+    "app_source_url_recorder.h",
+    "source_url_recorder.cc",
+    "source_url_recorder.h",
+  ]
+  deps = [
+    "//base",
+    "//components/crx_file",
+    "//content/public/browser",
+    "//services/metrics/public/cpp:metrics_cpp",
+    "//services/metrics/public/cpp:ukm_builders",
+    "//services/metrics/public/mojom",
+    "//url",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "app_source_url_recorder_test.cc",
+    "source_url_recorder_test.cc",
+  ]
+  deps = [
+    ":content",
+    "//base",
+    "//base/test:test_support",
+    "//components/metrics",
+    "//components/metrics:test_support",
+    "//components/ukm",
+    "//components/ukm:test_support",
+    "//content/public/browser",
+    "//content/test:test_support",
+    "//testing/gtest",
+    "//url",
+  ]
+}
diff --git a/components/ukm/content/DEPS b/components/ukm/content/DEPS
new file mode 100644
index 0000000..8ec5f07
--- /dev/null
+++ b/components/ukm/content/DEPS
@@ -0,0 +1,8 @@
+include_rules = [
+  "+components/crx_file/id_util.h",
+  "+content/public/browser",
+  "+content/public/common",
+  "+content/public/test",
+  "+content/shell",
+  "+third_party/blink/public/platform/ukm.mojom.h",
+]
diff --git a/components/ukm/content/app_source_url_recorder.cc b/components/ukm/content/app_source_url_recorder.cc
new file mode 100644
index 0000000..93f16c5
--- /dev/null
+++ b/components/ukm/content/app_source_url_recorder.cc
@@ -0,0 +1,51 @@
+// 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.
+
+#include "components/ukm/content/app_source_url_recorder.h"
+
+#include "base/atomic_sequence_num.h"
+#include "components/crx_file/id_util.h"
+#include "services/metrics/public/cpp/delegating_ukm_recorder.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
+#include "url/gurl.h"
+
+namespace ukm {
+
+SourceId AssignNewAppId() {
+  static base::AtomicSequenceNumber seq;
+  return ConvertToSourceId(seq.GetNext() + 1, SourceIdType::APP_ID);
+}
+
+SourceId AppSourceUrlRecorder::GetSourceIdForChromeApp(const std::string& id) {
+  GURL url("chrome-extension://" + id);
+  return GetSourceIdForUrl(url);
+}
+
+SourceId AppSourceUrlRecorder::GetSourceIdForArc(
+    const std::string& package_name) {
+  const std::string package_name_hash =
+      crx_file::id_util::GenerateId(package_name);
+  GURL url("app://play/" + package_name_hash);
+  return GetSourceIdForUrl(url);
+}
+
+SourceId AppSourceUrlRecorder::GetSourceIdForPWA(const GURL& url) {
+  return GetSourceIdForUrl(url);
+}
+
+SourceId AppSourceUrlRecorder::GetSourceIdForUrl(const GURL& url) {
+  ukm::DelegatingUkmRecorder* const recorder =
+      ukm::DelegatingUkmRecorder::Get();
+  if (!recorder)
+    return kInvalidSourceId;
+
+  const SourceId source_id = AssignNewAppId();
+  if (base::FeatureList::IsEnabled(kUkmAppLogging)) {
+    recorder->UpdateAppURL(source_id, url);
+  }
+  return source_id;
+}
+
+}  // namespace ukm
diff --git a/components/ukm/content/app_source_url_recorder.h b/components/ukm/content/app_source_url_recorder.h
new file mode 100644
index 0000000..4b98434
--- /dev/null
+++ b/components/ukm/content/app_source_url_recorder.h
@@ -0,0 +1,40 @@
+// 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.
+
+#ifndef COMPONENTS_UKM_CONTENT_APP_SOURCE_URL_RECORDER_H_
+#define COMPONENTS_UKM_CONTENT_APP_SOURCE_URL_RECORDER_H_
+
+#include "services/metrics/public/cpp/ukm_source_id.h"
+
+#include "base/feature_list.h"
+
+#include <string>
+
+class GURL;
+
+namespace ukm {
+
+const base::Feature kUkmAppLogging{"UkmAppLogging",
+                                   base::FEATURE_DISABLED_BY_DEFAULT};
+
+class AppSourceUrlRecorder {
+ private:
+  friend class AppSourceUrlRecorderTest;
+
+  // Get a UKM SourceId for a Chrome app.
+  static SourceId GetSourceIdForChromeApp(const std::string& id);
+
+  // Get a UKM SourceId for an Arc app.
+  static SourceId GetSourceIdForArc(const std::string& package_name);
+
+  // Get a UKM SourceId for a PWA.
+  static SourceId GetSourceIdForPWA(const GURL& url);
+
+  // For internal use only.
+  static SourceId GetSourceIdForUrl(const GURL& url);
+};
+
+}  // namespace ukm
+
+#endif  // COMPONENTS_UKM_CONTENT_APP_SOURCE_URL_RECORDER_H_
diff --git a/components/ukm/content/app_source_url_recorder_test.cc b/components/ukm/content/app_source_url_recorder_test.cc
new file mode 100644
index 0000000..ae4807b
--- /dev/null
+++ b/components/ukm/content/app_source_url_recorder_test.cc
@@ -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.
+
+#include "components/ukm/content/app_source_url_recorder.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "components/ukm/test_ukm_recorder.h"
+#include "content/public/test/test_renderer_host.h"
+#include "services/metrics/public/cpp/ukm_source.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace ukm {
+
+class AppSourceUrlRecorderTest : public content::RenderViewHostTestHarness {
+ public:
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(kUkmAppLogging);
+    content::RenderViewHostTestHarness::SetUp();
+  }
+
+ protected:
+  SourceId GetSourceIdForArc(const std::string& package_name) {
+    return AppSourceUrlRecorder::GetSourceIdForArc(package_name);
+  }
+
+  SourceId GetSourceIdForPWA(const GURL& url) {
+    return AppSourceUrlRecorder::GetSourceIdForPWA(url);
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+  TestAutoSetUkmRecorder test_ukm_recorder_;
+};
+
+TEST_F(AppSourceUrlRecorderTest, CheckArc) {
+  SourceId id = GetSourceIdForArc("com.google.play");
+
+  std::string com_google_play_hash("pjhgmeephkiehhlkfcoginnkbphkdang");
+  GURL expected_url("app://play/" + com_google_play_hash);
+
+  const auto& sources = test_ukm_recorder_.GetSources();
+  ASSERT_EQ(1ul, sources.size());
+
+  ASSERT_NE(kInvalidSourceId, id);
+  auto it = sources.find(id);
+  ASSERT_NE(sources.end(), it);
+  EXPECT_EQ(expected_url, it->second->url());
+  EXPECT_EQ(1u, it->second->urls().size());
+}
+
+TEST_F(AppSourceUrlRecorderTest, CheckPWA) {
+  GURL url("https://pwa_example_url.com");
+  SourceId id = GetSourceIdForPWA(url);
+
+  const auto& sources = test_ukm_recorder_.GetSources();
+  ASSERT_EQ(1ul, sources.size());
+
+  ASSERT_NE(kInvalidSourceId, id);
+  auto it = sources.find(id);
+  ASSERT_NE(sources.end(), it);
+  EXPECT_EQ(url, it->second->url());
+  EXPECT_EQ(1u, it->second->urls().size());
+}
+
+}  // namespace ukm
diff --git a/components/ukm/content/source_url_recorder.cc b/components/ukm/content/source_url_recorder.cc
new file mode 100644
index 0000000..b2741f7
--- /dev/null
+++ b/components/ukm/content/source_url_recorder.cc
@@ -0,0 +1,293 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ukm/content/source_url_recorder.h"
+
+#include <utility>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_binding_set.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+#include "services/metrics/public/cpp/delegating_ukm_recorder.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
+#include "third_party/blink/public/platform/ukm.mojom.h"
+#include "url/gurl.h"
+
+namespace ukm {
+
+namespace internal {
+
+int64_t CreateUniqueTabId() {
+  static int64_t unique_id_counter = 0;
+  return ++unique_id_counter;
+}
+
+// SourceUrlRecorderWebContentsObserver is responsible for recording UKM source
+// URLs, for all (any only) main frame navigations in a given WebContents.
+// SourceUrlRecorderWebContentsObserver records both the final URL for a
+// navigation, and, if the navigation was redirected, the initial URL as well.
+class SourceUrlRecorderWebContentsObserver
+    : public blink::mojom::UkmSourceIdFrameHost,
+      public content::WebContentsObserver,
+      public content::WebContentsUserData<
+          SourceUrlRecorderWebContentsObserver> {
+ public:
+  // Creates a SourceUrlRecorderWebContentsObserver for the given
+  // WebContents. If a SourceUrlRecorderWebContentsObserver is already
+  // associated with the WebContents, this method is a no-op.
+  static void CreateForWebContents(content::WebContents* web_contents);
+
+  // content::WebContentsObserver:
+  void DidStartNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void DidFinishNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void DidOpenRequestedURL(content::WebContents* new_contents,
+                           content::RenderFrameHost* source_render_frame_host,
+                           const GURL& url,
+                           const content::Referrer& referrer,
+                           WindowOpenDisposition disposition,
+                           ui::PageTransition transition,
+                           bool started_from_context_menu,
+                           bool renderer_initiated) override;
+
+  ukm::SourceId GetLastCommittedSourceId() const;
+
+  // blink::mojom::UkmSourceIdFrameHost
+  void SetDocumentSourceId(int64_t source_id) override;
+
+ private:
+  explicit SourceUrlRecorderWebContentsObserver(
+      content::WebContents* web_contents);
+  friend class content::WebContentsUserData<
+      SourceUrlRecorderWebContentsObserver>;
+
+  // Record any pending DocumentCreated events to UKM.
+  void MaybeFlushPendingEvents();
+
+  void MaybeRecordUrl(content::NavigationHandle* navigation_handle,
+                      const GURL& initial_url);
+
+  // Recieves document source IDs from the renderer.
+  content::WebContentsFrameBindingSet<blink::mojom::UkmSourceIdFrameHost>
+      bindings_;
+
+  // Map from navigation ID to the initial URL for that navigation.
+  base::flat_map<int64_t, GURL> pending_navigations_;
+
+  // Holds pending DocumentCreated events.
+  struct PendingEvent {
+    PendingEvent() = delete;
+    PendingEvent(int64_t source_id,
+                 bool is_main_frame,
+                 bool is_cross_origin_frame)
+        : source_id(source_id),
+          is_main_frame(is_main_frame),
+          is_cross_origin_frame(is_cross_origin_frame) {}
+
+    int64_t source_id;
+    bool is_main_frame;
+    bool is_cross_origin_frame;
+  };
+  std::vector<PendingEvent> pending_document_created_events_;
+
+  SourceId last_committed_source_id_;
+
+  // The source id before |last_committed_source_id_|.
+  SourceId previous_committed_source_id_;
+
+  // The source id of the last committed source in the tab that opened this tab.
+  // Will be set to kInvalidSourceId after the first navigation in this tab is
+  // finished.
+  SourceId opener_source_id_;
+
+  const int64_t tab_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(SourceUrlRecorderWebContentsObserver);
+};
+
+SourceUrlRecorderWebContentsObserver::SourceUrlRecorderWebContentsObserver(
+    content::WebContents* web_contents)
+    : content::WebContentsObserver(web_contents),
+      bindings_(web_contents, this),
+      last_committed_source_id_(ukm::kInvalidSourceId),
+      previous_committed_source_id_(ukm::kInvalidSourceId),
+      opener_source_id_(ukm::kInvalidSourceId),
+      tab_id_(CreateUniqueTabId()) {}
+
+void SourceUrlRecorderWebContentsObserver::DidStartNavigation(
+    content::NavigationHandle* navigation_handle) {
+  // UKM only records URLs for main frame (web page) navigations, so ignore
+  // non-main frame navs. Additionally, at least for the time being, we don't
+  // track metrics for same-document navigations (e.g. changes in URL fragment,
+  // or URL changes due to history.pushState) in UKM.
+  if (!navigation_handle->IsInMainFrame() ||
+      navigation_handle->IsSameDocument()) {
+    return;
+  }
+
+  // UKM doesn't want to record URLs for downloads. However, at the point a
+  // navigation is started, we don't yet know if the navigation will result in a
+  // download. Thus, we record the URL at the time a navigation was initiated,
+  // and only record it later, once we verify that the navigation didn't result
+  // in a download.
+  pending_navigations_.insert(std::make_pair(
+      navigation_handle->GetNavigationId(), navigation_handle->GetURL()));
+
+  // Clear any unassociated pending events.
+  pending_document_created_events_.clear();
+}
+
+void SourceUrlRecorderWebContentsObserver::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  auto it = pending_navigations_.find(navigation_handle->GetNavigationId());
+  if (it == pending_navigations_.end())
+    return;
+
+  DCHECK(navigation_handle->IsInMainFrame());
+  DCHECK(!navigation_handle->IsSameDocument());
+
+  if (navigation_handle->HasCommitted()) {
+    previous_committed_source_id_ = last_committed_source_id_;
+    last_committed_source_id_ = ukm::ConvertToSourceId(
+        navigation_handle->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID);
+  }
+
+  GURL initial_url = std::move(it->second);
+  pending_navigations_.erase(it);
+
+  // UKM doesn't want to record URLs for navigations that result in downloads.
+  if (navigation_handle->IsDownload())
+    return;
+
+  MaybeRecordUrl(navigation_handle, initial_url);
+
+  MaybeFlushPendingEvents();
+
+  // Reset the opener source id. Only the first source in a tab should have an
+  // opener.
+  opener_source_id_ = kInvalidSourceId;
+}
+
+void SourceUrlRecorderWebContentsObserver::DidOpenRequestedURL(
+    content::WebContents* new_contents,
+    content::RenderFrameHost* source_render_frame_host,
+    const GURL& url,
+    const content::Referrer& referrer,
+    WindowOpenDisposition disposition,
+    ui::PageTransition transition,
+    bool started_from_context_menu,
+    bool renderer_initiated) {
+  auto* new_recorder =
+      SourceUrlRecorderWebContentsObserver::FromWebContents(new_contents);
+  if (!new_recorder)
+    return;
+  new_recorder->opener_source_id_ = GetLastCommittedSourceId();
+}
+
+ukm::SourceId SourceUrlRecorderWebContentsObserver::GetLastCommittedSourceId()
+    const {
+  return last_committed_source_id_;
+}
+
+void SourceUrlRecorderWebContentsObserver::SetDocumentSourceId(
+    int64_t source_id) {
+  content::RenderFrameHost* main_frame = web_contents()->GetMainFrame();
+  content::RenderFrameHost* current_frame = bindings_.GetCurrentTargetFrame();
+  bool is_main_frame = main_frame == current_frame;
+  bool is_cross_origin_frame =
+      is_main_frame ? false
+                    : !main_frame->GetLastCommittedOrigin().IsSameOriginWith(
+                          current_frame->GetLastCommittedOrigin());
+
+  pending_document_created_events_.emplace_back(
+      source_id, !bindings_.GetCurrentTargetFrame()->GetParent(),
+      is_cross_origin_frame);
+  MaybeFlushPendingEvents();
+}
+
+void SourceUrlRecorderWebContentsObserver::MaybeFlushPendingEvents() {
+  if (!last_committed_source_id_)
+    return;
+
+  ukm::DelegatingUkmRecorder* ukm_recorder = ukm::DelegatingUkmRecorder::Get();
+  if (!ukm_recorder)
+    return;
+
+  while (!pending_document_created_events_.empty()) {
+    auto record = pending_document_created_events_.back();
+
+    ukm::builders::DocumentCreated(record.source_id)
+        .SetNavigationSourceId(last_committed_source_id_)
+        .SetIsMainFrame(record.is_main_frame)
+        .SetIsCrossOriginFrame(record.is_cross_origin_frame)
+        .Record(ukm_recorder);
+
+    pending_document_created_events_.pop_back();
+  }
+}
+
+void SourceUrlRecorderWebContentsObserver::MaybeRecordUrl(
+    content::NavigationHandle* navigation_handle,
+    const GURL& initial_url) {
+  DCHECK(navigation_handle->IsInMainFrame());
+  DCHECK(!navigation_handle->IsSameDocument());
+
+  ukm::DelegatingUkmRecorder* ukm_recorder = ukm::DelegatingUkmRecorder::Get();
+  if (!ukm_recorder)
+    return;
+
+  UkmSource::NavigationData navigation_data;
+  const GURL& final_url = navigation_handle->GetURL();
+  // TODO(crbug.com/869123): This check isn't quite correct, as self redirecting
+  // is possible. This may also be changed to include the entire redirect chain.
+  if (final_url != initial_url)
+    navigation_data.urls = {initial_url};
+  navigation_data.urls.push_back(final_url);
+
+  // Careful note: the current navigation may have failed.
+  navigation_data.previous_source_id = navigation_handle->HasCommitted()
+                                           ? previous_committed_source_id_
+                                           : last_committed_source_id_;
+  navigation_data.opener_source_id = opener_source_id_;
+  navigation_data.tab_id = tab_id_;
+
+  const ukm::SourceId source_id = ukm::ConvertToSourceId(
+      navigation_handle->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID);
+  ukm_recorder->RecordNavigation(source_id, navigation_data);
+}
+
+// static
+void SourceUrlRecorderWebContentsObserver::CreateForWebContents(
+    content::WebContents* web_contents) {
+  if (!SourceUrlRecorderWebContentsObserver::FromWebContents(web_contents)) {
+    web_contents->SetUserData(
+        SourceUrlRecorderWebContentsObserver::UserDataKey(),
+        base::WrapUnique(
+            new SourceUrlRecorderWebContentsObserver(web_contents)));
+  }
+}
+
+}  // namespace internal
+
+void InitializeSourceUrlRecorderForWebContents(
+    content::WebContents* web_contents) {
+  internal::SourceUrlRecorderWebContentsObserver::CreateForWebContents(
+      web_contents);
+}
+
+SourceId GetSourceIdForWebContentsDocument(
+    const content::WebContents* web_contents) {
+  const internal::SourceUrlRecorderWebContentsObserver* obs =
+      internal::SourceUrlRecorderWebContentsObserver::FromWebContents(
+          web_contents);
+  return obs ? obs->GetLastCommittedSourceId() : kInvalidSourceId;
+}
+
+}  // namespace ukm
diff --git a/components/ukm/content/source_url_recorder.h b/components/ukm/content/source_url_recorder.h
new file mode 100644
index 0000000..64f704d
--- /dev/null
+++ b/components/ukm/content/source_url_recorder.h
@@ -0,0 +1,27 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UKM_CONTENT_SOURCE_URL_RECORDER_H_
+#define COMPONENTS_UKM_CONTENT_SOURCE_URL_RECORDER_H_
+
+#include "services/metrics/public/cpp/ukm_source_id.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+namespace ukm {
+
+// Initializes recording of UKM source URLs for the given WebContents.
+void InitializeSourceUrlRecorderForWebContents(
+    content::WebContents* web_contents);
+
+// Get a UKM SourceId for the currently committed document of web contents.
+// Returns kInvalidSourceId if no commit has been observed.
+SourceId GetSourceIdForWebContentsDocument(
+    const content::WebContents* web_contents);
+
+}  // namespace ukm
+
+#endif  // COMPONENTS_UKM_CONTENT_SOURCE_URL_RECORDER_H_
diff --git a/components/ukm/content/source_url_recorder_browsertest.cc b/components/ukm/content/source_url_recorder_browsertest.cc
new file mode 100644
index 0000000..bb52494
--- /dev/null
+++ b/components/ukm/content/source_url_recorder_browsertest.cc
@@ -0,0 +1,161 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/test/scoped_feature_list.h"
+#include "components/ukm/content/source_url_recorder.h"
+#include "components/ukm/test_ukm_recorder.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/navigation_handle_observer.h"
+#include "content/shell/browser/shell.h"
+#include "content/shell/browser/shell_browser_context.h"
+#include "content/shell/browser/shell_download_manager_delegate.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_source.h"
+
+class SourceUrlRecorderWebContentsObserverBrowserTest
+    : public content::ContentBrowserTest {
+ protected:
+  SourceUrlRecorderWebContentsObserverBrowserTest() {
+    scoped_feature_list_.InitAndEnableFeature(ukm::kUkmFeature);
+  }
+
+  ~SourceUrlRecorderWebContentsObserverBrowserTest() override {}
+
+  void SetUpOnMainThread() override {
+    content::ContentBrowserTest::SetUpOnMainThread();
+
+    ASSERT_TRUE(embedded_test_server()->Start());
+
+    test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
+    ukm::InitializeSourceUrlRecorderForWebContents(shell()->web_contents());
+  }
+
+  const ukm::UkmSource* GetSourceForNavigationId(int64_t navigation_id) {
+    CHECK_GT(navigation_id, 0);
+    const ukm::SourceId source_id =
+        ukm::ConvertToSourceId(navigation_id, ukm::SourceIdType::NAVIGATION_ID);
+    return test_ukm_recorder_->GetSourceForSourceId(source_id);
+  }
+
+  GURL GetAssociatedURLForWebContentsDocument() {
+    const ukm::UkmSource* src = test_ukm_recorder_->GetSourceForSourceId(
+        ukm::GetSourceIdForWebContentsDocument(shell()->web_contents()));
+    return src ? src->url() : GURL();
+  }
+
+  const ukm::TestAutoSetUkmRecorder& test_ukm_recorder() const {
+    return *test_ukm_recorder_;
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
+
+  DISALLOW_COPY_AND_ASSIGN(SourceUrlRecorderWebContentsObserverBrowserTest);
+};
+
+class SourceUrlRecorderWebContentsObserverDownloadBrowserTest
+    : public SourceUrlRecorderWebContentsObserverBrowserTest {
+ protected:
+  void SetUpOnMainThread() override {
+    SourceUrlRecorderWebContentsObserverBrowserTest::SetUpOnMainThread();
+
+    // Set up a test download directory, in order to prevent prompting for
+    // handling downloads.
+    ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir());
+    content::ShellDownloadManagerDelegate* delegate =
+        static_cast<content::ShellDownloadManagerDelegate*>(
+            shell()
+                ->web_contents()
+                ->GetBrowserContext()
+                ->GetDownloadManagerDelegate());
+    delegate->SetDownloadBehaviorForTesting(downloads_directory_.GetPath());
+  }
+
+ private:
+  base::ScopedTempDir downloads_directory_;
+};
+
+IN_PROC_BROWSER_TEST_F(SourceUrlRecorderWebContentsObserverBrowserTest, Basic) {
+  using Entry = ukm::builders::DocumentCreated;
+
+  GURL url = embedded_test_server()->GetURL("/title1.html");
+  content::NavigationHandleObserver observer(shell()->web_contents(), url);
+  content::NavigateToURL(shell(), url);
+  EXPECT_TRUE(observer.has_committed());
+  const ukm::UkmSource* source =
+      GetSourceForNavigationId(observer.navigation_id());
+  EXPECT_NE(nullptr, source);
+  EXPECT_EQ(url, source->url());
+  EXPECT_EQ(1u, source->urls().size());
+
+  EXPECT_EQ(url, GetAssociatedURLForWebContentsDocument());
+
+  // Check we have created a DocumentCreated event.
+  auto ukm_entries = test_ukm_recorder().GetEntriesByName(Entry::kEntryName);
+  EXPECT_EQ(1u, ukm_entries.size());
+  EXPECT_EQ(source->id(), *test_ukm_recorder().GetEntryMetric(
+                              ukm_entries[0], Entry::kNavigationSourceIdName));
+  EXPECT_EQ(1, *test_ukm_recorder().GetEntryMetric(ukm_entries[0],
+                                                   Entry::kIsMainFrameName));
+  EXPECT_NE(source->id(), ukm_entries[0]->source_id);
+}
+
+IN_PROC_BROWSER_TEST_F(SourceUrlRecorderWebContentsObserverBrowserTest,
+                       IgnoreUrlInSubframe) {
+  using Entry = ukm::builders::DocumentCreated;
+
+  GURL main_url = embedded_test_server()->GetURL("/page_with_iframe.html");
+  GURL subframe_url = embedded_test_server()->GetURL("/title1.html");
+
+  content::NavigationHandleObserver main_observer(shell()->web_contents(),
+                                                  main_url);
+  content::NavigationHandleObserver subframe_observer(shell()->web_contents(),
+                                                      subframe_url);
+  content::NavigateToURL(shell(), main_url);
+  EXPECT_TRUE(main_observer.has_committed());
+  EXPECT_TRUE(main_observer.is_main_frame());
+  EXPECT_TRUE(subframe_observer.has_committed());
+  EXPECT_FALSE(subframe_observer.is_main_frame());
+
+  const ukm::UkmSource* source =
+      GetSourceForNavigationId(main_observer.navigation_id());
+  EXPECT_NE(nullptr, source);
+  EXPECT_EQ(main_url, source->url());
+  EXPECT_EQ(nullptr,
+            GetSourceForNavigationId(subframe_observer.navigation_id()));
+
+  EXPECT_EQ(main_url, GetAssociatedURLForWebContentsDocument());
+
+  // Check we have created a DocumentCreated event for both frames.
+  auto ukm_entries = test_ukm_recorder().GetEntriesByName(Entry::kEntryName);
+  EXPECT_EQ(2u, ukm_entries.size());
+  EXPECT_EQ(source->id(), *test_ukm_recorder().GetEntryMetric(
+                              ukm_entries[0], Entry::kNavigationSourceIdName));
+  EXPECT_EQ(0, *test_ukm_recorder().GetEntryMetric(ukm_entries[0],
+                                                   Entry::kIsMainFrameName));
+  EXPECT_EQ(source->id(), *test_ukm_recorder().GetEntryMetric(
+                              ukm_entries[1], Entry::kNavigationSourceIdName));
+  EXPECT_EQ(1, *test_ukm_recorder().GetEntryMetric(ukm_entries[1],
+                                                   Entry::kIsMainFrameName));
+  EXPECT_NE(ukm_entries[0]->source_id, ukm_entries[1]->source_id);
+  EXPECT_NE(source->id(), ukm_entries[0]->source_id);
+  EXPECT_NE(source->id(), ukm_entries[1]->source_id);
+}
+
+IN_PROC_BROWSER_TEST_F(SourceUrlRecorderWebContentsObserverDownloadBrowserTest,
+                       IgnoreDownload) {
+  GURL url(embedded_test_server()->GetURL("/download-test1.lib"));
+  content::NavigationHandleObserver observer(shell()->web_contents(), url);
+  content::NavigateToURL(shell(), url);
+  EXPECT_FALSE(observer.has_committed());
+  EXPECT_TRUE(observer.is_download());
+  EXPECT_EQ(nullptr, GetSourceForNavigationId(observer.navigation_id()));
+  EXPECT_EQ(GURL(), GetAssociatedURLForWebContentsDocument());
+}
diff --git a/components/ukm/content/source_url_recorder_test.cc b/components/ukm/content/source_url_recorder_test.cc
new file mode 100644
index 0000000..aaed558
--- /dev/null
+++ b/components/ukm/content/source_url_recorder_test.cc
@@ -0,0 +1,101 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ukm/content/source_url_recorder.h"
+#include "components/ukm/test_ukm_recorder.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/navigation_simulator.h"
+#include "content/public/test/test_renderer_host.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
+#include "services/metrics/public/cpp/ukm_source.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using content::NavigationSimulator;
+
+class SourceUrlRecorderWebContentsObserverTest
+    : public content::RenderViewHostTestHarness {
+ public:
+  void SetUp() override {
+    content::RenderViewHostTestHarness::SetUp();
+    ukm::InitializeSourceUrlRecorderForWebContents(web_contents());
+  }
+
+  GURL GetAssociatedURLForWebContentsDocument() {
+    const ukm::UkmSource* src = test_ukm_recorder_.GetSourceForSourceId(
+        ukm::GetSourceIdForWebContentsDocument(web_contents()));
+    return src ? src->url() : GURL();
+  }
+
+ protected:
+  ukm::TestAutoSetUkmRecorder test_ukm_recorder_;
+};
+
+TEST_F(SourceUrlRecorderWebContentsObserverTest, Basic) {
+  GURL url("https://www.example.com/");
+  NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), url);
+
+  const auto& sources = test_ukm_recorder_.GetSources();
+  EXPECT_EQ(1ul, sources.size());
+  for (const auto& kv : sources) {
+    EXPECT_EQ(url, kv.second->url());
+    EXPECT_EQ(1u, kv.second->urls().size());
+  }
+}
+
+TEST_F(SourceUrlRecorderWebContentsObserverTest, InitialUrl) {
+  GURL initial_url("https://www.a.com/");
+  GURL final_url("https://www.b.com/");
+  auto simulator =
+      NavigationSimulator::CreateRendererInitiated(initial_url, main_rfh());
+  simulator->Start();
+  simulator->Redirect(final_url);
+  simulator->Commit();
+  const auto& sources = test_ukm_recorder_.GetSources();
+  EXPECT_EQ(1ul, sources.size());
+  for (const auto& kv : sources) {
+    EXPECT_EQ(final_url, kv.second->url());
+    EXPECT_EQ(initial_url, kv.second->urls().front());
+  }
+
+  EXPECT_EQ(final_url, GetAssociatedURLForWebContentsDocument());
+}
+
+TEST_F(SourceUrlRecorderWebContentsObserverTest, IgnoreUrlInSubframe) {
+  GURL main_frame_url("https://www.example.com/");
+  GURL sub_frame_url("https://www.example.com/iframe.html");
+  NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(),
+                                                    main_frame_url);
+  NavigationSimulator::NavigateAndCommitFromDocument(
+      sub_frame_url,
+      content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe"));
+
+  const auto& sources = test_ukm_recorder_.GetSources();
+  EXPECT_EQ(1ul, sources.size());
+  for (const auto& kv : sources) {
+    EXPECT_EQ(main_frame_url, kv.second->url());
+    EXPECT_EQ(1u, kv.second->urls().size());
+  }
+
+  EXPECT_EQ(main_frame_url, GetAssociatedURLForWebContentsDocument());
+}
+
+TEST_F(SourceUrlRecorderWebContentsObserverTest, IgnoreSameDocumentNavigation) {
+  GURL url("https://www.example.com/");
+  GURL same_document_url("https://www.example.com/#samedocument");
+  NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(), url);
+  NavigationSimulator::CreateRendererInitiated(same_document_url, main_rfh())
+      ->CommitSameDocument();
+
+  EXPECT_EQ(same_document_url, web_contents()->GetLastCommittedURL());
+
+  const auto& sources = test_ukm_recorder_.GetSources();
+  EXPECT_EQ(1ul, sources.size());
+  for (const auto& kv : sources) {
+    EXPECT_EQ(url, kv.second->url());
+    EXPECT_EQ(1u, kv.second->urls().size());
+  }
+
+  EXPECT_EQ(url, GetAssociatedURLForWebContentsDocument());
+}
diff --git a/components/ukm/debug/BUILD.gn b/components/ukm/debug/BUILD.gn
new file mode 100644
index 0000000..e4f6781
--- /dev/null
+++ b/components/ukm/debug/BUILD.gn
@@ -0,0 +1,30 @@
+# 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("//third_party/closure_compiler/compile_js.gni")
+
+source_set("util") {
+  sources = [
+    "ukm_debug_data_extractor.cc",
+    "ukm_debug_data_extractor.h",
+  ]
+  deps = [
+    "//base",
+    "//components/ukm",
+    "//services/metrics/public/cpp:ukm_builders",
+  ]
+}
+
+js_type_check("closure_compile") {
+  deps = [
+    ":ukm_internals",
+  ]
+}
+
+js_library("ukm_internals") {
+  deps = [
+    "//ui/webui/resources/js:cr",
+    "//ui/webui/resources/js:util",
+  ]
+}
diff --git a/components/ukm/debug/ukm_debug_data_extractor.cc b/components/ukm/debug/ukm_debug_data_extractor.cc
new file mode 100644
index 0000000..4877914
--- /dev/null
+++ b/components/ukm/debug/ukm_debug_data_extractor.cc
@@ -0,0 +1,118 @@
+// 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.
+
+#include "components/ukm/debug/ukm_debug_data_extractor.h"
+
+#include <inttypes.h>
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include "base/strings/stringprintf.h"
+#include "services/metrics/public/cpp/ukm_decode.h"
+#include "services/metrics/public/cpp/ukm_source.h"
+#include "url/gurl.h"
+
+namespace ukm {
+namespace debug {
+
+namespace {
+
+struct SourceData {
+  UkmSource* source;
+  std::vector<mojom::UkmEntry*> entries;
+};
+
+std::string GetName(const ukm::builders::EntryDecoder& decoder, uint64_t hash) {
+  const auto it = decoder.metric_map.find(hash);
+  if (it == decoder.metric_map.end())
+    return base::StringPrintf("Unknown %" PRIu64, hash);
+  return it->second;
+}
+
+base::Value ConvertEntryToValue(const ukm::builders::DecodeMap& decode_map,
+                                const mojom::UkmEntry& entry) {
+  base::DictionaryValue entry_value;
+
+  const auto it = decode_map.find(entry.event_hash);
+  if (it == decode_map.end()) {
+    entry_value.SetKey("name",
+                       base::Value(static_cast<double>(entry.event_hash)));
+  } else {
+    entry_value.SetKey("name", base::Value(it->second.name));
+
+    base::ListValue metrics_list;
+    auto* metrics_list_storage = &metrics_list.GetList();
+    for (const auto& metric : entry.metrics) {
+      base::DictionaryValue metric_value;
+      metric_value.SetKey("name",
+                          base::Value(GetName(it->second, metric.first)));
+      metric_value.SetKey("value",
+                          base::Value(static_cast<double>(metric.second)));
+      metrics_list_storage->push_back(std::move(metric_value));
+    }
+    entry_value.SetKey("metrics", std::move(metrics_list));
+  }
+  return std::move(entry_value);
+}
+
+}  // namespace
+
+UkmDebugDataExtractor::UkmDebugDataExtractor() = default;
+
+UkmDebugDataExtractor::~UkmDebugDataExtractor() = default;
+
+// static
+base::Value UkmDebugDataExtractor::GetStructuredData(
+    const UkmService* ukm_service) {
+  if (!ukm_service)
+    return {};
+
+  base::DictionaryValue ukm_data;
+  ukm_data.SetKey("state", base::Value(ukm_service->recording_enabled_));
+  ukm_data.SetKey("client_id",
+                  base::Value(static_cast<double>(ukm_service->client_id_)));
+  ukm_data.SetKey("session_id",
+                  base::Value(static_cast<double>(ukm_service->session_id_)));
+
+  std::map<SourceId, SourceData> source_data;
+  for (const auto& kv : ukm_service->recordings_.sources) {
+    source_data[kv.first].source = kv.second.get();
+  }
+
+  for (const auto& v : ukm_service->recordings_.entries) {
+    source_data[v->source_id].entries.push_back(v.get());
+  }
+
+  base::ListValue sources_list;
+  auto* source_list_storage = &sources_list.GetList();
+  for (const auto& kv : source_data) {
+    const auto* src = kv.second.source;
+
+    base::DictionaryValue source_value;
+    if (src) {
+      source_value.SetKey("id", base::Value(static_cast<double>(src->id())));
+      source_value.SetKey("url", base::Value(src->url().spec()));
+    } else {
+      source_value.SetKey("id", base::Value(static_cast<double>(kv.first)));
+    }
+
+    base::ListValue entries_list;
+    auto* entries_list_storage = &entries_list.GetList();
+    for (auto* entry : kv.second.entries) {
+      entries_list_storage->push_back(
+          ConvertEntryToValue(ukm_service->decode_map_, *entry));
+    }
+
+    source_value.SetKey("entries", std::move(entries_list));
+
+    source_list_storage->push_back(std::move(source_value));
+  }
+  ukm_data.SetKey("sources", std::move(sources_list));
+  return std::move(ukm_data);
+}
+
+}  // namespace debug
+}  // namespace ukm
diff --git a/components/ukm/debug/ukm_debug_data_extractor.h b/components/ukm/debug/ukm_debug_data_extractor.h
new file mode 100644
index 0000000..dc2810d
--- /dev/null
+++ b/components/ukm/debug/ukm_debug_data_extractor.h
@@ -0,0 +1,36 @@
+// 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.
+
+#ifndef COMPONENTS_UKM_DEBUG_UKM_DEBUG_DATA_EXTRACTOR_H_
+#define COMPONENTS_UKM_DEBUG_UKM_DEBUG_DATA_EXTRACTOR_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/values.h"
+#include "components/ukm/ukm_service.h"
+
+namespace ukm {
+
+class UkmService;
+
+namespace debug {
+
+// Extracts UKM data as an HTML page for debugging purposes.
+class UkmDebugDataExtractor {
+ public:
+  UkmDebugDataExtractor();
+  ~UkmDebugDataExtractor();
+
+  // Returns UKM data structured in a DictionaryValue.
+  static base::Value GetStructuredData(const UkmService* ukm_service);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UkmDebugDataExtractor);
+};
+
+}  // namespace debug
+}  // namespace ukm
+
+#endif  // COMPONENTS_UKM_DEBUG_UKM_DEBUG_DATA_EXTRACTOR_H_
diff --git a/components/ukm/debug/ukm_internals.html b/components/ukm/debug/ukm_internals.html
new file mode 100644
index 0000000..34c5e43
--- /dev/null
+++ b/components/ukm/debug/ukm_internals.html
@@ -0,0 +1,20 @@
+<!-- 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.-->
+<!doctype html>
+<html lang="en">
+<meta charset="utf-8">
+<script src="chrome://resources/js/cr.js"></script>
+<script src="chrome://resources/js/promise_resolver.js"></script>
+<script src="chrome://resources/js/util.js"></script>
+<title>UKM Debug page</title>
+<h1>UKM Debug page</h1>
+<div>
+  <p>Is Enabled:<span id="state"></span></p>
+  <p>Client Id:<span id="clientid"></span></p>
+  <p>Session Id:<span id="sessionid"></span></p>
+  <h2>Sources</h2>
+  <div id="sources"></div>
+</div>
+<script src="ukm_internals.js"></script>
+</html>
diff --git a/components/ukm/debug/ukm_internals.js b/components/ukm/debug/ukm_internals.js
new file mode 100644
index 0000000..85c1c7a
--- /dev/null
+++ b/components/ukm/debug/ukm_internals.js
@@ -0,0 +1,78 @@
+// 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.
+
+/**
+ * @typedef {{
+ *   name: string,
+ *   value: string
+ * }}
+ */
+var Metric;
+
+/**
+ * @typedef {{
+ *   name: string,
+ *   metrics: !Array<!Metric>
+ * }}
+ */
+var UkmEntry;
+
+/**
+ * @typedef {{
+ *   url: string,
+ *   id: string,
+ *   entries: !Array<UkmEntry>,
+ * }}
+ */
+var UkmDataSource;
+
+/**
+ * The Ukm data sent from the browser.
+ * @typedef {{
+ *   state: boolean,
+ *   client_id: string,
+ *   session_id: string,
+ *   sources: !Array<!UkmDataSource>,
+ * }}
+ */
+var UkmData;
+
+/**
+ * Fetches data from the Ukm service and updates the DOM to display it as a
+ * list.
+ */
+function updateUkmData() {
+  cr.sendWithPromise('requestUkmData').then((/** @type {UkmData} */ data) => {
+    $('state').innerText = data.state ? 'True' : 'False';
+    $('clientid').innerText = data.client_id;
+    $('sessionid').innerText = data.session_id;
+
+    let sourceDiv = $('sources');
+    for (const source of data.sources) {
+      const sourceElement = document.createElement('h3');
+      if (source.url !== undefined)
+        sourceElement.innerText = `Id: ${source.id} Url: ${source.url}`;
+      else
+        sourceElement.innerText = `Id: ${source.id}`;
+      sourceDiv.appendChild(sourceElement);
+
+      for (const entry of source.entries) {
+        const entryElement = document.createElement('h4');
+        entryElement.innerText = `Entry: ${entry.name}`;
+        sourceDiv.appendChild(entryElement);
+
+        if (entry.metrics === undefined)
+          continue;
+        for (const metric of entry.metrics) {
+          const metricElement = document.createElement('h5');
+          metricElement.innerText =
+              `Metric: ${metric.name} Value: ${metric.value}`;
+          sourceDiv.appendChild(metricElement);
+        }
+      }
+    }
+  });
+}
+
+updateUkmData();
diff --git a/components/ukm/observers/DEPS b/components/ukm/observers/DEPS
new file mode 100644
index 0000000..a8ed862
--- /dev/null
+++ b/components/ukm/observers/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+components/history/core/browser",
+  "+components/sync",
+]
diff --git a/components/ukm/observers/history_delete_observer.cc b/components/ukm/observers/history_delete_observer.cc
new file mode 100644
index 0000000..da42600
--- /dev/null
+++ b/components/ukm/observers/history_delete_observer.cc
@@ -0,0 +1,34 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ukm/observers/history_delete_observer.h"
+
+#include "components/history/core/browser/history_service.h"
+
+namespace ukm {
+
+HistoryDeleteObserver::HistoryDeleteObserver() : history_observer_(this) {}
+
+HistoryDeleteObserver::~HistoryDeleteObserver() {}
+
+void HistoryDeleteObserver::ObserveServiceForDeletions(
+    history::HistoryService* history_service) {
+  if (history_service) {
+    history_observer_.Add(history_service);
+  }
+}
+
+void HistoryDeleteObserver::OnURLsDeleted(
+    history::HistoryService* history_service,
+    const history::DeletionInfo& deletion_info) {
+  if (!deletion_info.is_from_expiration())
+    OnHistoryDeleted();
+}
+
+void HistoryDeleteObserver::HistoryServiceBeingDeleted(
+    history::HistoryService* history_service) {
+  history_observer_.Remove(history_service);
+}
+
+}  // namespace ukm
diff --git a/components/ukm/observers/history_delete_observer.h b/components/ukm/observers/history_delete_observer.h
new file mode 100644
index 0000000..ea48bfc
--- /dev/null
+++ b/components/ukm/observers/history_delete_observer.h
@@ -0,0 +1,44 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UKM_OBSERVERS_HISTORY_DELETE_OBSERVER_H_
+#define COMPONENTS_UKM_OBSERVERS_HISTORY_DELETE_OBSERVER_H_
+
+#include <set>
+
+#include "base/scoped_observer.h"
+#include "components/history/core/browser/history_service_observer.h"
+
+namespace ukm {
+
+// Observes multiple HistoryService objects for any events that delete history.
+// Handles cleanup and removing observers as objects are destroyed.
+class HistoryDeleteObserver : public history::HistoryServiceObserver {
+ public:
+  HistoryDeleteObserver();
+  ~HistoryDeleteObserver() override;
+
+  // Starts observing a service for history deletions.
+  void ObserveServiceForDeletions(history::HistoryService* history_service);
+
+  // history::HistoryServiceObserver
+  void OnURLsDeleted(history::HistoryService* history_service,
+                     const history::DeletionInfo& deletion_info) override;
+  void HistoryServiceBeingDeleted(
+      history::HistoryService* history_service) override;
+
+ protected:
+  virtual void OnHistoryDeleted() = 0;
+
+ private:
+  // Tracks observed history services, for cleanup.
+  ScopedObserver<history::HistoryService, history::HistoryServiceObserver>
+      history_observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(HistoryDeleteObserver);
+};
+
+}  // namespace ukm
+
+#endif  // COMPONENTS_UKM_OBSERVERS_HISTORY_DELETE_OBSERVER_H_
diff --git a/components/ukm/observers/sync_disable_observer.cc b/components/ukm/observers/sync_disable_observer.cc
new file mode 100644
index 0000000..820dbb3
--- /dev/null
+++ b/components/ukm/observers/sync_disable_observer.cc
@@ -0,0 +1,227 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ukm/observers/sync_disable_observer.h"
+
+#include "base/feature_list.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/stl_util.h"
+#include "components/sync/driver/sync_token_status.h"
+#include "components/sync/engine/connection_status.h"
+#include "components/unified_consent/url_keyed_data_collection_consent_helper.h"
+
+using unified_consent::UrlKeyedDataCollectionConsentHelper;
+
+namespace ukm {
+
+const base::Feature kUkmCheckAuthErrorFeature{"UkmCheckAuthError",
+                                              base::FEATURE_ENABLED_BY_DEFAULT};
+
+namespace {
+
+enum DisableInfo {
+  DISABLED_BY_NONE,
+  DISABLED_BY_HISTORY,
+  DISABLED_BY_INITIALIZED,
+  DISABLED_BY_HISTORY_INITIALIZED,
+  DISABLED_BY_CONNECTED,
+  DISABLED_BY_HISTORY_CONNECTED,
+  DISABLED_BY_INITIALIZED_CONNECTED,
+  DISABLED_BY_HISTORY_INITIALIZED_CONNECTED,
+  DISABLED_BY_PASSPHRASE,
+  DISABLED_BY_HISTORY_PASSPHRASE,
+  DISABLED_BY_INITIALIZED_PASSPHRASE,
+  DISABLED_BY_HISTORY_INITIALIZED_PASSPHRASE,
+  DISABLED_BY_CONNECTED_PASSPHRASE,
+  DISABLED_BY_HISTORY_CONNECTED_PASSPHRASE,
+  DISABLED_BY_INITIALIZED_CONNECTED_PASSPHRASE,
+  DISABLED_BY_HISTORY_INITIALIZED_CONNECTED_PASSPHRASE,
+  DISABLED_BY_ANONYMIZED_DATA_COLLECTION,
+  MAX_DISABLE_INFO
+};
+
+void RecordDisableInfo(DisableInfo info) {
+  UMA_HISTOGRAM_ENUMERATION("UKM.SyncDisable.Info", info, MAX_DISABLE_INFO);
+}
+
+}  // namespace
+
+SyncDisableObserver::SyncDisableObserver() : sync_observer_(this) {}
+
+SyncDisableObserver::~SyncDisableObserver() {
+  for (const auto& entry : consent_helpers_) {
+    entry.second->RemoveObserver(this);
+  }
+}
+
+bool SyncDisableObserver::SyncState::AllowsUkm() const {
+  if (anonymized_data_collection_state == DataCollectionState::kIgnored)
+    return history_enabled && initialized && connected && !passphrase_protected;
+  else
+    return anonymized_data_collection_state == DataCollectionState::kEnabled;
+}
+
+bool SyncDisableObserver::SyncState::AllowsUkmWithExtension() const {
+  return AllowsUkm() && extensions_enabled && initialized && connected &&
+         !passphrase_protected;
+}
+
+// static
+SyncDisableObserver::SyncState SyncDisableObserver::GetSyncState(
+    syncer::SyncService* sync_service,
+    UrlKeyedDataCollectionConsentHelper* consent_helper) {
+  syncer::SyncTokenStatus status = sync_service->GetSyncTokenStatus();
+  SyncState state;
+  state.history_enabled = sync_service->GetPreferredDataTypes().Has(
+      syncer::HISTORY_DELETE_DIRECTIVES);
+  state.extensions_enabled =
+      sync_service->GetPreferredDataTypes().Has(syncer::EXTENSIONS);
+  state.initialized = sync_service->IsEngineInitialized();
+  state.connected = !base::FeatureList::IsEnabled(kUkmCheckAuthErrorFeature) ||
+                    status.connection_status == syncer::CONNECTION_OK;
+  state.passphrase_protected =
+      state.initialized && sync_service->IsUsingSecondaryPassphrase();
+  if (consent_helper) {
+    state.anonymized_data_collection_state =
+        consent_helper->IsEnabled() ? DataCollectionState::kEnabled
+                                    : DataCollectionState::kDisabled;
+  }
+  return state;
+}
+
+void SyncDisableObserver::ObserveServiceForSyncDisables(
+    syncer::SyncService* sync_service,
+    PrefService* prefs,
+    bool is_unified_consent_enabled) {
+  std::unique_ptr<UrlKeyedDataCollectionConsentHelper> consent_helper;
+  if (is_unified_consent_enabled) {
+    consent_helper = UrlKeyedDataCollectionConsentHelper::
+        NewAnonymizedDataCollectionConsentHelper(true, prefs, sync_service);
+  }
+
+  SyncState state = GetSyncState(sync_service, consent_helper.get());
+  previous_states_[sync_service] = state;
+
+  if (consent_helper) {
+    consent_helper->AddObserver(this);
+    consent_helpers_[sync_service] = std::move(consent_helper);
+  }
+  sync_observer_.Add(sync_service);
+  UpdateAllProfileEnabled(false);
+}
+
+void SyncDisableObserver::UpdateAllProfileEnabled(bool must_purge) {
+  bool all_sync_states_allow_ukm = CheckSyncStateOnAllProfiles();
+  bool all_sync_states_allow_extension_ukm =
+      all_sync_states_allow_ukm && CheckSyncStateForExtensionsOnAllProfiles();
+  // Any change in sync settings needs to call OnSyncPrefsChanged so that the
+  // new settings take effect.
+  if (must_purge || (all_sync_states_allow_ukm != all_sync_states_allow_ukm_) ||
+      (all_sync_states_allow_extension_ukm !=
+       all_sync_states_allow_extension_ukm_)) {
+    all_sync_states_allow_ukm_ = all_sync_states_allow_ukm;
+    all_sync_states_allow_extension_ukm_ = all_sync_states_allow_extension_ukm;
+    OnSyncPrefsChanged(must_purge);
+  }
+}
+
+bool SyncDisableObserver::CheckSyncStateOnAllProfiles() {
+  if (previous_states_.empty())
+    return false;
+  for (const auto& kv : previous_states_) {
+    const SyncDisableObserver::SyncState& state = kv.second;
+    if (!state.AllowsUkm()) {
+      int disabled_by = 0;
+      if (state.anonymized_data_collection_state ==
+          DataCollectionState::kIgnored) {
+        if (!state.history_enabled)
+          disabled_by |= 1 << 0;
+        if (!state.initialized)
+          disabled_by |= 1 << 1;
+        if (!state.connected)
+          disabled_by |= 1 << 2;
+        if (state.passphrase_protected)
+          disabled_by |= 1 << 3;
+      } else {
+        DCHECK_EQ(DataCollectionState::kDisabled,
+                  state.anonymized_data_collection_state);
+        disabled_by |= 1 << 4;
+      }
+      RecordDisableInfo(DisableInfo(disabled_by));
+      return false;
+    }
+  }
+  RecordDisableInfo(DISABLED_BY_NONE);
+  return true;
+}
+
+bool SyncDisableObserver::CheckSyncStateForExtensionsOnAllProfiles() {
+  if (previous_states_.empty())
+    return false;
+  for (const auto& kv : previous_states_) {
+    const SyncDisableObserver::SyncState& state = kv.second;
+    if (!state.extensions_enabled)
+      return false;
+  }
+  return true;
+}
+
+void SyncDisableObserver::OnStateChanged(syncer::SyncService* sync) {
+  UrlKeyedDataCollectionConsentHelper* consent_helper = nullptr;
+  auto found = consent_helpers_.find(sync);
+  if (found != consent_helpers_.end())
+    consent_helper = found->second.get();
+  UpdateSyncState(sync, consent_helper);
+}
+
+void SyncDisableObserver::OnUrlKeyedDataCollectionConsentStateChanged(
+    unified_consent::UrlKeyedDataCollectionConsentHelper* consent_helper) {
+  DCHECK(consent_helper);
+  syncer::SyncService* sync_service = nullptr;
+  for (const auto& entry : consent_helpers_) {
+    if (consent_helper == entry.second.get()) {
+      sync_service = entry.first;
+      break;
+    }
+  }
+  DCHECK(sync_service);
+  UpdateSyncState(sync_service, consent_helper);
+}
+
+void SyncDisableObserver::UpdateSyncState(
+    syncer::SyncService* sync,
+    UrlKeyedDataCollectionConsentHelper* consent_helper) {
+  DCHECK(base::ContainsKey(previous_states_, sync));
+  const SyncDisableObserver::SyncState& previous_state = previous_states_[sync];
+  DCHECK(previous_state.anonymized_data_collection_state ==
+             DataCollectionState::kIgnored ||
+         consent_helper);
+  SyncDisableObserver::SyncState state = GetSyncState(sync, consent_helper);
+  // Trigger a purge if sync state no longer allows UKM.
+  bool must_purge = previous_state.AllowsUkm() && !state.AllowsUkm();
+  previous_states_[sync] = state;
+  UpdateAllProfileEnabled(must_purge);
+}
+
+void SyncDisableObserver::OnSyncShutdown(syncer::SyncService* sync) {
+  DCHECK(base::ContainsKey(previous_states_, sync));
+  auto found = consent_helpers_.find(sync);
+  if (found != consent_helpers_.end()) {
+    found->second->RemoveObserver(this);
+    consent_helpers_.erase(found);
+  }
+  sync_observer_.Remove(sync);
+  previous_states_.erase(sync);
+  UpdateAllProfileEnabled(false);
+}
+
+bool SyncDisableObserver::SyncStateAllowsUkm() {
+  return all_sync_states_allow_ukm_;
+}
+
+bool SyncDisableObserver::SyncStateAllowsExtensionUkm() {
+  return all_sync_states_allow_extension_ukm_;
+}
+
+}  // namespace ukm
diff --git a/components/ukm/observers/sync_disable_observer.h b/components/ukm/observers/sync_disable_observer.h
new file mode 100644
index 0000000..e051443
--- /dev/null
+++ b/components/ukm/observers/sync_disable_observer.h
@@ -0,0 +1,161 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UKM_OBSERVERS_SYNC_DISABLE_OBSERVER_H_
+#define COMPONENTS_UKM_OBSERVERS_SYNC_DISABLE_OBSERVER_H_
+
+#include <map>
+
+#include "base/scoped_observer.h"
+#include "components/sync/driver/sync_service.h"
+#include "components/sync/driver/sync_service_observer.h"
+#include "components/unified_consent/url_keyed_data_collection_consent_helper.h"
+
+class PrefService;
+
+namespace ukm {
+
+// Observer that monitors whether UKM is allowed for all profiles.
+//
+// For one profile, UKM is allowed under the following conditions:
+// * If unified consent is disabled, then UKM is allowed for the profile iff
+//   sync history is active;
+// * If unified consent is enabled, then UKM is allowed for the profile iff
+//   URL-keyed anonymized data collectiion is enabled.
+class SyncDisableObserver
+    : public syncer::SyncServiceObserver,
+      public unified_consent::UrlKeyedDataCollectionConsentHelper::Observer {
+ public:
+  SyncDisableObserver();
+  ~SyncDisableObserver() override;
+
+  // Starts observing a service for sync disables.
+  void ObserveServiceForSyncDisables(syncer::SyncService* sync_service,
+                                     PrefService* pref_service,
+                                     bool is_unified_consent_enabled);
+
+  // Returns true iff all sync states alllow UKM to be enabled. This means that
+  // for all profiles:
+  // * If unified consent is disabled, then sync is initialized, connected, has
+  //   the HISTORY_DELETE_DIRECTIVES data type enabled, and does not have a
+  //   secondary passphrase enabled.
+  // * If unified consent is enabled, then URL-keyed anonymized data collection
+  //   is enabled for that profile.
+  virtual bool SyncStateAllowsUkm();
+
+  // Returns true iff sync is in a state that allows UKM to capture extensions.
+  // This means that all profiles have EXTENSIONS data type enabled for syncing.
+  virtual bool SyncStateAllowsExtensionUkm();
+
+ protected:
+  // Called after state changes and some profile has sync disabled.
+  // If |must_purge| is true, sync was disabled for some profile, and
+  // local data should be purged.
+  virtual void OnSyncPrefsChanged(bool must_purge) = 0;
+
+ private:
+  // syncer::SyncServiceObserver:
+  void OnStateChanged(syncer::SyncService* sync) override;
+  void OnSyncShutdown(syncer::SyncService* sync) override;
+
+  // unified_consent::UrlKeyedDataCollectionConsentHelper::Observer:
+  void OnUrlKeyedDataCollectionConsentStateChanged(
+      unified_consent::UrlKeyedDataCollectionConsentHelper* consent_helper)
+      override;
+
+  // Recomputes all_profiles_enabled_ state from previous_states_;
+  void UpdateAllProfileEnabled(bool must_purge);
+
+  // Returns true iff all sync states in previous_states_ allow UKM.
+  // If there are no profiles being observed, this returns false.
+  bool CheckSyncStateOnAllProfiles();
+
+  // Returns true iff all sync states in previous_states_ allow extension UKM.
+  // If there are no profiles being observed, this returns false.
+  bool CheckSyncStateForExtensionsOnAllProfiles();
+
+  // Tracks observed history services, for cleanup.
+  ScopedObserver<syncer::SyncService, syncer::SyncServiceObserver>
+      sync_observer_;
+
+  enum class DataCollectionState {
+    // Matches the case when unified consent feature is disabled
+    kIgnored,
+    // Unified consent feature is enabled and the user has disabled URL-keyed
+    // anonymized data collection.
+    kDisabled,
+    // Unified consent feature is enabled and the user has enabled URL-keyed
+    // anonymized data collection.
+    kEnabled
+  };
+
+  // State data about sync services that we need to remember.
+  struct SyncState {
+    // Returns true if this sync state allows UKM:
+    // * If unified consent is disabled, then sync is initialized, connected,
+    //   has history data type enabled, and does not have a secondary passphrase
+    //   enabled.
+    // * If unified consent is enabled, then URL-keyed anonymized data
+    //   collection is enabled.
+    bool AllowsUkm() const;
+    // Returns true if |AllowUkm| and if sync extensions are enabled.
+    bool AllowsUkmWithExtension() const;
+
+    // If the user has history sync enabled.
+    bool history_enabled = false;
+    // If the user has extension sync enabled.
+    bool extensions_enabled = false;
+    // Whether the sync service has been initialized.
+    bool initialized = false;
+    // Whether the sync service is active and operational.
+    bool connected = false;
+    // Whether user data is hidden by a secondary passphrase.
+    // This is not valid if the state is not initialized.
+    bool passphrase_protected = false;
+
+    // Whether anonymized data collection is enabled.
+    // Note: This is not managed by sync service. It was added in this enum
+    // for convenience.
+    DataCollectionState anonymized_data_collection_state =
+        DataCollectionState::kIgnored;
+  };
+
+  // Updates the sync state for |sync| service. Updates all profiles if needed.
+  void UpdateSyncState(
+      syncer::SyncService* sync,
+      unified_consent::UrlKeyedDataCollectionConsentHelper* consent_helper);
+
+  // Gets the current state of a SyncService.
+  // A non-null |consent_helper| implies that Unified Consent is enabled.
+  static SyncState GetSyncState(
+      syncer::SyncService* sync,
+      unified_consent::UrlKeyedDataCollectionConsentHelper* consent_helper);
+
+  // The state of the sync services being observed.
+  std::map<syncer::SyncService*, SyncState> previous_states_;
+
+  // The list of URL-keyed anonymized data collection consent helpers.
+  //
+  // Note: UrlKeyedDataCollectionConsentHelper do not rely on sync when
+  // unified consent feature is enabled but there must be exactly one per
+  // Chromium profile. As there is a single sync service per profile, it is safe
+  // to key them by sync service instead of introducing an additional map.
+  std::map<
+      syncer::SyncService*,
+      std::unique_ptr<unified_consent::UrlKeyedDataCollectionConsentHelper>>
+      consent_helpers_;
+
+  // Tracks if UKM is allowed on all profiles after the last state change.
+  bool all_sync_states_allow_ukm_ = false;
+
+  // Tracks if extension sync was enabled on all profiles after the last state
+  // change.
+  bool all_sync_states_allow_extension_ukm_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(SyncDisableObserver);
+};
+
+}  // namespace ukm
+
+#endif  // COMPONENTS_UKM_OBSERVERS_SYNC_DISABLE_OBSERVER_H_
diff --git a/components/ukm/observers/sync_disable_observer_unittest.cc b/components/ukm/observers/sync_disable_observer_unittest.cc
new file mode 100644
index 0000000..5208ebb
--- /dev/null
+++ b/components/ukm/observers/sync_disable_observer_unittest.cc
@@ -0,0 +1,339 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ukm/observers/sync_disable_observer.h"
+
+#include "base/observer_list.h"
+#include "components/sync/driver/fake_sync_service.h"
+#include "components/sync/driver/sync_token_status.h"
+#include "components/sync/engine/connection_status.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "components/unified_consent/pref_names.h"
+#include "components/unified_consent/unified_consent_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ukm {
+
+namespace {
+
+class MockSyncService : public syncer::FakeSyncService {
+ public:
+  MockSyncService() {}
+  ~MockSyncService() override { Shutdown(); }
+
+  void SetStatus(bool has_passphrase, bool history_enabled) {
+    initialized_ = true;
+    has_passphrase_ = has_passphrase;
+    preferred_data_types_ =
+        history_enabled
+            ? syncer::ModelTypeSet(syncer::HISTORY_DELETE_DIRECTIVES)
+            : syncer::ModelTypeSet();
+    NotifyObserversOfStateChanged();
+  }
+
+  void SetConnectionStatus(syncer::ConnectionStatus status) {
+    connection_status_ = status;
+    NotifyObserversOfStateChanged();
+  }
+
+  void Shutdown() override {
+    for (auto& observer : observers_) {
+      observer.OnSyncShutdown(this);
+    }
+  }
+
+  void NotifyObserversOfStateChanged() {
+    for (auto& observer : observers_) {
+      observer.OnStateChanged(this);
+    }
+  }
+
+ private:
+  // syncer::FakeSyncService:
+  void AddObserver(syncer::SyncServiceObserver* observer) override {
+    observers_.AddObserver(observer);
+  }
+  void RemoveObserver(syncer::SyncServiceObserver* observer) override {
+    observers_.RemoveObserver(observer);
+  }
+  TransportState GetTransportState() const override {
+    return initialized_ ? TransportState::ACTIVE : TransportState::INITIALIZING;
+  }
+  bool IsUsingSecondaryPassphrase() const override { return has_passphrase_; }
+  syncer::ModelTypeSet GetPreferredDataTypes() const override {
+    return preferred_data_types_;
+  }
+  syncer::SyncTokenStatus GetSyncTokenStatus() const override {
+    syncer::SyncTokenStatus status;
+    status.connection_status = connection_status_;
+    return status;
+  }
+
+  bool initialized_ = false;
+  bool has_passphrase_ = false;
+  syncer::ConnectionStatus connection_status_ = syncer::CONNECTION_OK;
+  syncer::ModelTypeSet preferred_data_types_;
+
+  // The list of observers of the SyncService state.
+  base::ObserverList<syncer::SyncServiceObserver>::Unchecked observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockSyncService);
+};
+
+class TestSyncDisableObserver : public SyncDisableObserver {
+ public:
+  TestSyncDisableObserver() : purged_(false), notified_(false) {}
+  ~TestSyncDisableObserver() override {}
+
+  bool ResetPurged() {
+    bool was_purged = purged_;
+    purged_ = false;
+    return was_purged;
+  }
+
+  bool ResetNotified() {
+    bool notified = notified_;
+    notified_ = false;
+    return notified;
+  }
+
+ private:
+  // SyncDisableObserver:
+  void OnSyncPrefsChanged(bool must_purge) override {
+    notified_ = true;
+    purged_ = purged_ || must_purge;
+  }
+  bool purged_;
+  bool notified_;
+  DISALLOW_COPY_AND_ASSIGN(TestSyncDisableObserver);
+};
+
+class SyncDisableObserverTest : public testing::Test {
+ public:
+  SyncDisableObserverTest() {}
+  void RegisterUrlKeyedAnonymizedDataCollectionPref(
+      sync_preferences::TestingPrefServiceSyncable& prefs) {
+    unified_consent::UnifiedConsentService::RegisterPrefs(prefs.registry());
+  }
+
+  void SetUrlKeyedAnonymizedDataCollectionEnabled(PrefService* prefs,
+                                                  bool enabled) {
+    prefs->SetBoolean(
+        unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
+        enabled);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SyncDisableObserverTest);
+};
+
+}  // namespace
+
+TEST_F(SyncDisableObserverTest, NoProfiles) {
+  TestSyncDisableObserver observer;
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_FALSE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+TEST_F(SyncDisableObserverTest, OneEnabled_UnifiedConsentDisabled) {
+  TestSyncDisableObserver observer;
+  MockSyncService sync;
+  sync.SetStatus(false, true);
+  observer.ObserveServiceForSyncDisables(&sync, nullptr, false);
+  EXPECT_TRUE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+TEST_F(SyncDisableObserverTest, OneEnabled_UnifiedConsentEnabled) {
+  sync_preferences::TestingPrefServiceSyncable prefs;
+  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, true);
+  MockSyncService sync;
+  for (bool has_passphrase : {true, false}) {
+    for (bool history_enabled : {true, false}) {
+      TestSyncDisableObserver observer;
+      sync.SetStatus(has_passphrase, history_enabled);
+      observer.ObserveServiceForSyncDisables(&sync, &prefs, true);
+      EXPECT_TRUE(observer.SyncStateAllowsUkm());
+      EXPECT_TRUE(observer.ResetNotified());
+      EXPECT_FALSE(observer.ResetPurged());
+    }
+  }
+}
+
+TEST_F(SyncDisableObserverTest, Passphrased_UnifiedConsentDisabled) {
+  MockSyncService sync;
+  sync.SetStatus(true, true);
+  TestSyncDisableObserver observer;
+  observer.ObserveServiceForSyncDisables(&sync, nullptr, false);
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_FALSE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+TEST_F(SyncDisableObserverTest, HistoryDisabled_UnifiedConsentDisabled) {
+  TestSyncDisableObserver observer;
+  MockSyncService sync;
+  sync.SetStatus(false, false);
+  observer.ObserveServiceForSyncDisables(&sync, nullptr, false);
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_FALSE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+TEST_F(SyncDisableObserverTest, AuthError_UnifiedConsentDisabled) {
+  TestSyncDisableObserver observer;
+  MockSyncService sync;
+  sync.SetStatus(false, true);
+  observer.ObserveServiceForSyncDisables(&sync, nullptr, false);
+  EXPECT_TRUE(observer.SyncStateAllowsUkm());
+  sync.SetConnectionStatus(syncer::CONNECTION_AUTH_ERROR);
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  sync.SetConnectionStatus(syncer::CONNECTION_OK);
+  EXPECT_TRUE(observer.SyncStateAllowsUkm());
+}
+
+TEST_F(SyncDisableObserverTest, MixedProfiles1_UnifiedConsentDisabled) {
+  TestSyncDisableObserver observer;
+  MockSyncService sync1;
+  sync1.SetStatus(false, false);
+  observer.ObserveServiceForSyncDisables(&sync1, nullptr, false);
+  MockSyncService sync2;
+  sync2.SetStatus(false, true);
+  observer.ObserveServiceForSyncDisables(&sync2, nullptr, false);
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_FALSE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+TEST_F(SyncDisableObserverTest, MixedProfiles2_UnifiedConsentDisabled) {
+  TestSyncDisableObserver observer;
+  MockSyncService sync1;
+  sync1.SetStatus(false, true);
+  observer.ObserveServiceForSyncDisables(&sync1, nullptr, false);
+  EXPECT_TRUE(observer.ResetNotified());
+
+  MockSyncService sync2;
+  sync2.SetStatus(false, false);
+  observer.ObserveServiceForSyncDisables(&sync2, nullptr, false);
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+  sync2.Shutdown();
+  EXPECT_TRUE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+TEST_F(SyncDisableObserverTest, TwoEnabled_UnifiedConsentDisabled) {
+  TestSyncDisableObserver observer;
+  MockSyncService sync1;
+  sync1.SetStatus(false, true);
+  observer.ObserveServiceForSyncDisables(&sync1, nullptr, false);
+  EXPECT_TRUE(observer.ResetNotified());
+  MockSyncService sync2;
+  sync2.SetStatus(false, true);
+  observer.ObserveServiceForSyncDisables(&sync2, nullptr, false);
+  EXPECT_TRUE(observer.SyncStateAllowsUkm());
+  EXPECT_FALSE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+TEST_F(SyncDisableObserverTest, TwoEnabled_UnifiedConsentEnabled) {
+  sync_preferences::TestingPrefServiceSyncable prefs2;
+  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs2);
+  TestSyncDisableObserver observer;
+
+  // First profile has sync enabled.
+  MockSyncService sync1;
+  sync1.SetStatus(false, true);
+  observer.ObserveServiceForSyncDisables(&sync1, nullptr, false);
+  EXPECT_TRUE(observer.ResetNotified());
+
+  // Second profile has URL-keyed anonymized data collection enabled.
+  MockSyncService sync2;
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs2, true);
+  observer.ObserveServiceForSyncDisables(&sync2, &prefs2, true);
+  EXPECT_TRUE(observer.SyncStateAllowsUkm());
+  EXPECT_FALSE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+TEST_F(SyncDisableObserverTest, OneAddRemove_UnifiedConsentDisabled) {
+  TestSyncDisableObserver observer;
+  MockSyncService sync;
+  observer.ObserveServiceForSyncDisables(&sync, nullptr, false);
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_FALSE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+  sync.SetStatus(false, true);
+  EXPECT_TRUE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+  sync.Shutdown();
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+TEST_F(SyncDisableObserverTest, OneAddRemove_UnifiedConsentEnabled) {
+  sync_preferences::TestingPrefServiceSyncable prefs;
+  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
+  TestSyncDisableObserver observer;
+  MockSyncService sync;
+  observer.ObserveServiceForSyncDisables(&sync, &prefs, true);
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_FALSE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, true);
+  EXPECT_TRUE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+  sync.Shutdown();
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+TEST_F(SyncDisableObserverTest, PurgeOnDisable_UnifiedConsentDisabled) {
+  TestSyncDisableObserver observer;
+  MockSyncService sync;
+  sync.SetStatus(false, true);
+  observer.ObserveServiceForSyncDisables(&sync, nullptr, false);
+  EXPECT_TRUE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+  sync.SetStatus(false, false);
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_TRUE(observer.ResetPurged());
+  sync.Shutdown();
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_FALSE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+TEST_F(SyncDisableObserverTest, PurgeOnDisable_UnifiedConsentEnabled) {
+  sync_preferences::TestingPrefServiceSyncable prefs;
+  RegisterUrlKeyedAnonymizedDataCollectionPref(prefs);
+  TestSyncDisableObserver observer;
+  MockSyncService sync;
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, true);
+  observer.ObserveServiceForSyncDisables(&sync, &prefs, true);
+  EXPECT_TRUE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+  SetUrlKeyedAnonymizedDataCollectionEnabled(&prefs, false);
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_TRUE(observer.ResetNotified());
+  EXPECT_TRUE(observer.ResetPurged());
+  sync.Shutdown();
+  EXPECT_FALSE(observer.SyncStateAllowsUkm());
+  EXPECT_FALSE(observer.ResetNotified());
+  EXPECT_FALSE(observer.ResetPurged());
+}
+
+}  // namespace ukm
diff --git a/components/ukm/persisted_logs_metrics_impl.cc b/components/ukm/persisted_logs_metrics_impl.cc
new file mode 100644
index 0000000..fa5fa97
--- /dev/null
+++ b/components/ukm/persisted_logs_metrics_impl.cc
@@ -0,0 +1,36 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ukm/persisted_logs_metrics_impl.h"
+
+#include "base/metrics/histogram_macros.h"
+
+namespace ukm {
+
+PersistedLogsMetricsImpl::PersistedLogsMetricsImpl() = default;
+
+PersistedLogsMetricsImpl::~PersistedLogsMetricsImpl() = default;
+
+void PersistedLogsMetricsImpl::RecordLogReadStatus(
+    metrics::PersistedLogsMetrics::LogReadStatus status) {
+  UMA_HISTOGRAM_ENUMERATION("UKM.PersistentLogRecall.Status", status,
+                            metrics::PersistedLogsMetrics::END_RECALL_STATUS);
+}
+
+void PersistedLogsMetricsImpl::RecordCompressionRatio(size_t compressed_size,
+                                                      size_t original_size) {
+  UMA_HISTOGRAM_PERCENTAGE(
+      "UKM.ProtoCompressionRatio",
+      static_cast<int>(100 * compressed_size / original_size));
+}
+
+void PersistedLogsMetricsImpl::RecordDroppedLogSize(size_t size) {
+  UMA_HISTOGRAM_COUNTS_1M("UKM.UnsentLogs.DroppedSize", static_cast<int>(size));
+}
+
+void PersistedLogsMetricsImpl::RecordDroppedLogsNum(int dropped_logs_num) {
+  UMA_HISTOGRAM_COUNTS_10000("UKM.UnsentLogs.NumDropped", dropped_logs_num);
+}
+
+}  // namespace ukm
diff --git a/components/ukm/persisted_logs_metrics_impl.h b/components/ukm/persisted_logs_metrics_impl.h
new file mode 100644
index 0000000..a1aefe6
--- /dev/null
+++ b/components/ukm/persisted_logs_metrics_impl.h
@@ -0,0 +1,33 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UKM_PERSISTED_LOGS_METRICS_IMPL_H_
+#define COMPONENTS_UKM_PERSISTED_LOGS_METRICS_IMPL_H_
+
+#include "base/macros.h"
+#include "components/metrics/persisted_logs_metrics.h"
+
+namespace ukm {
+
+// Implementation for recording metrics from PersistedLogs.
+class PersistedLogsMetricsImpl : public metrics::PersistedLogsMetrics {
+ public:
+  PersistedLogsMetricsImpl();
+  ~PersistedLogsMetricsImpl() override;
+
+  // metrics::PersistedLogsMetrics:
+  void RecordLogReadStatus(
+      metrics::PersistedLogsMetrics::LogReadStatus status) override;
+  void RecordCompressionRatio(size_t compressed_size,
+                              size_t original_size) override;
+  void RecordDroppedLogSize(size_t size) override;
+  void RecordDroppedLogsNum(int dropped_logs_num) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PersistedLogsMetricsImpl);
+};
+
+}  // namespace ukm
+
+#endif  // COMPONENTS_UKM_PERSISTED_LOGS_METRICS_IMPL_H_
diff --git a/components/ukm/test_ukm_recorder.cc b/components/ukm/test_ukm_recorder.cc
new file mode 100644
index 0000000..445348f
--- /dev/null
+++ b/components/ukm/test_ukm_recorder.cc
@@ -0,0 +1,143 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ukm/test_ukm_recorder.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include "base/logging.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "services/metrics/public/cpp/delegating_ukm_recorder.h"
+#include "services/metrics/public/cpp/ukm_source.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ukm {
+
+namespace {
+
+// Merge the data from |in| to |out|.
+void MergeEntry(const mojom::UkmEntry* in, mojom::UkmEntry* out) {
+  if (out->event_hash) {
+    EXPECT_EQ(out->source_id, in->source_id);
+    EXPECT_EQ(out->event_hash, in->event_hash);
+  } else {
+    out->event_hash = in->event_hash;
+    out->source_id = in->source_id;
+  }
+  for (const auto& metric : in->metrics) {
+    out->metrics.emplace(metric);
+  }
+}
+
+}  // namespace
+
+TestUkmRecorder::TestUkmRecorder() {
+  EnableRecording(/*extensions=*/true);
+  StoreWhitelistedEntries();
+  DisableSamplingForTesting();
+}
+
+TestUkmRecorder::~TestUkmRecorder() {
+};
+
+bool TestUkmRecorder::ShouldRestrictToWhitelistedSourceIds() const {
+  // In tests, we want to record all source ids (not just those that are
+  // whitelisted).
+  return false;
+}
+
+bool TestUkmRecorder::ShouldRestrictToWhitelistedEntries() const {
+  // In tests, we want to record all entries (not just those that are
+  // whitelisted).
+  return false;
+}
+
+const UkmSource* TestUkmRecorder::GetSourceForSourceId(
+    SourceId source_id) const {
+  const UkmSource* source = nullptr;
+  for (const auto& kv : sources()) {
+    if (kv.second->id() == source_id) {
+      DCHECK_EQ(nullptr, source);
+      source = kv.second.get();
+    }
+  }
+  return source;
+}
+
+std::vector<const mojom::UkmEntry*> TestUkmRecorder::GetEntriesByName(
+    base::StringPiece entry_name) const {
+  uint64_t hash = base::HashMetricName(entry_name);
+  std::vector<const mojom::UkmEntry*> result;
+  for (const auto& it : entries()) {
+    if (it->event_hash == hash)
+      result.push_back(it.get());
+  }
+  return result;
+}
+
+std::map<ukm::SourceId, mojom::UkmEntryPtr>
+TestUkmRecorder::GetMergedEntriesByName(base::StringPiece entry_name) const {
+  uint64_t hash = base::HashMetricName(entry_name);
+  std::map<ukm::SourceId, mojom::UkmEntryPtr> result;
+  for (const auto& it : entries()) {
+    if (it->event_hash != hash)
+      continue;
+    mojom::UkmEntryPtr& entry_ptr = result[it->source_id];
+    if (!entry_ptr)
+      entry_ptr = mojom::UkmEntry::New();
+    MergeEntry(it.get(), entry_ptr.get());
+  }
+  return result;
+}
+
+void TestUkmRecorder::ExpectEntrySourceHasUrl(const mojom::UkmEntry* entry,
+                                              const GURL& url) const {
+  const UkmSource* src = GetSourceForSourceId(entry->source_id);
+  if (src == nullptr) {
+    FAIL() << "Entry source id has no associated Source.";
+    return;
+  }
+  EXPECT_EQ(src->url(), url);
+}
+
+// static
+bool TestUkmRecorder::EntryHasMetric(const mojom::UkmEntry* entry,
+                                     base::StringPiece metric_name) {
+  return GetEntryMetric(entry, metric_name) != nullptr;
+}
+
+// static
+const int64_t* TestUkmRecorder::GetEntryMetric(const mojom::UkmEntry* entry,
+                                               base::StringPiece metric_name) {
+  uint64_t hash = base::HashMetricName(metric_name);
+  const auto it = entry->metrics.find(hash);
+  if (it != entry->metrics.end())
+    return &it->second;
+  return nullptr;
+}
+
+// static
+void TestUkmRecorder::ExpectEntryMetric(const mojom::UkmEntry* entry,
+                                        base::StringPiece metric_name,
+                                        int64_t expected_value) {
+  const int64_t* metric = GetEntryMetric(entry, metric_name);
+  if (metric == nullptr) {
+    FAIL() << "Failed to find metric for event: " << metric_name;
+    return;
+  }
+  EXPECT_EQ(expected_value, *metric) << " for metric:" << metric_name;
+}
+
+TestAutoSetUkmRecorder::TestAutoSetUkmRecorder() : self_ptr_factory_(this) {
+  DelegatingUkmRecorder::Get()->AddDelegate(self_ptr_factory_.GetWeakPtr());
+}
+
+TestAutoSetUkmRecorder::~TestAutoSetUkmRecorder() {
+  DelegatingUkmRecorder::Get()->RemoveDelegate(this);
+};
+
+}  // namespace ukm
diff --git a/components/ukm/test_ukm_recorder.h b/components/ukm/test_ukm_recorder.h
new file mode 100644
index 0000000..6950c3c
--- /dev/null
+++ b/components/ukm/test_ukm_recorder.h
@@ -0,0 +1,94 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UKM_TEST_UKM_RECORDER_H_
+#define COMPONENTS_UKM_TEST_UKM_RECORDER_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "components/ukm/ukm_recorder_impl.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
+#include "services/metrics/public/mojom/ukm_interface.mojom.h"
+#include "url/gurl.h"
+
+namespace ukm {
+
+// Wraps an UkmRecorder with additional accessors used for testing.
+class TestUkmRecorder : public UkmRecorderImpl {
+ public:
+  TestUkmRecorder();
+  ~TestUkmRecorder() override;
+
+  bool ShouldRestrictToWhitelistedSourceIds() const override;
+  bool ShouldRestrictToWhitelistedEntries() const override;
+
+  size_t sources_count() const { return sources().size(); }
+
+  size_t entries_count() const { return entries().size(); }
+
+  using UkmRecorderImpl::UpdateSourceURL;
+  using UkmRecorderImpl::RecordOtherURL;
+
+  // Gets all recorded UkmSource data.
+  const std::map<ukm::SourceId, std::unique_ptr<UkmSource>>& GetSources()
+      const {
+    return sources();
+  }
+
+  // Gets UkmSource data for a single SourceId.
+  const UkmSource* GetSourceForSourceId(ukm::SourceId source_id) const;
+
+  // Gets all of the entries recorded for entry name.
+  std::vector<const mojom::UkmEntry*> GetEntriesByName(
+      base::StringPiece entry_name) const;
+
+  // Gets the data for all entries with given entry name, merged to one entry
+  // for each source id. Intended for singular="true" metrics.
+  std::map<ukm::SourceId, mojom::UkmEntryPtr> GetMergedEntriesByName(
+      base::StringPiece entry_name) const;
+
+  // Checks if an entry is associated with a url.
+  void ExpectEntrySourceHasUrl(const mojom::UkmEntry* entry,
+                               const GURL& url) const;
+
+  // Expects the value of a metric from an entry.
+  static void ExpectEntryMetric(const mojom::UkmEntry* entry,
+                                base::StringPiece metric_name,
+                                int64_t expected_value);
+
+  // Checks if an entry contains a specific metric.
+  static bool EntryHasMetric(const mojom::UkmEntry* entry,
+                             base::StringPiece metric_name);
+
+  // Gets the value of a metric from an entry. Returns nullptr if the metric is
+  // not found.
+  static const int64_t* GetEntryMetric(const mojom::UkmEntry* entry,
+                                       base::StringPiece metric_name);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestUkmRecorder);
+};
+
+// Similar to a TestUkmRecorder, but also sets itself as the global UkmRecorder
+// on construction, and unsets itself on destruction.
+class TestAutoSetUkmRecorder : public TestUkmRecorder {
+ public:
+  TestAutoSetUkmRecorder();
+  ~TestAutoSetUkmRecorder() override;
+
+ private:
+  base::WeakPtrFactory<TestAutoSetUkmRecorder> self_ptr_factory_;
+};
+
+}  // namespace ukm
+
+#endif  // COMPONENTS_UKM_TEST_UKM_RECORDER_H_
diff --git a/components/ukm/ukm_pref_names.cc b/components/ukm/ukm_pref_names.cc
new file mode 100644
index 0000000..23ad637
--- /dev/null
+++ b/components/ukm/ukm_pref_names.cc
@@ -0,0 +1,20 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ukm/ukm_pref_names.h"
+
+namespace ukm {
+namespace prefs {
+
+// A random uint64 value unique for each chrome install.
+const char kUkmClientId[] = "ukm.client_id";
+
+// Preference which stores serialized UKM logs to be uploaded.
+const char kUkmPersistedLogs[] = "ukm.persisted_logs";
+
+// Preference which stores the UKM session id.
+const char kUkmSessionId[] = "ukm.session_id";
+
+}  // namespace prefs
+}  // namespace ukm
diff --git a/components/ukm/ukm_pref_names.h b/components/ukm/ukm_pref_names.h
new file mode 100644
index 0000000..b0e4208
--- /dev/null
+++ b/components/ukm/ukm_pref_names.h
@@ -0,0 +1,20 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UKM_UKM_PREF_NAMES_H_
+#define COMPONENTS_UKM_UKM_PREF_NAMES_H_
+
+namespace ukm {
+namespace prefs {
+
+// Alphabetical list of preference names specific to the UKM
+// component. Keep alphabetized, and document each in the .cc file.
+extern const char kUkmClientId[];
+extern const char kUkmPersistedLogs[];
+extern const char kUkmSessionId[];
+
+}  // namespace prefs
+}  // namespace ukm
+
+#endif  // COMPONENTS_UKM_UKM_PREF_NAMES_H_
diff --git a/components/ukm/ukm_recorder_impl.cc b/components/ukm/ukm_recorder_impl.cc
new file mode 100644
index 0000000..2f0dc35
--- /dev/null
+++ b/components/ukm/ukm_recorder_impl.cc
@@ -0,0 +1,606 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ukm/ukm_recorder_impl.h"
+
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/feature_list.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/rand_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "components/variations/variations_associated_data.h"
+#include "services/metrics/public/cpp/ukm_decode.h"
+#include "services/metrics/public/cpp/ukm_source.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
+#include "third_party/metrics_proto/ukm/entry.pb.h"
+#include "third_party/metrics_proto/ukm/report.pb.h"
+#include "third_party/metrics_proto/ukm/source.pb.h"
+#include "url/gurl.h"
+
+namespace ukm {
+
+namespace {
+
+// Note: kChromeUIScheme is defined in content, which this code can't
+// depend on - since it's used by iOS too. kExtensionScheme is defined
+// in extensions which also isn't always available here. kAppScheme
+// will be defined in code that isn't available here.
+const char kChromeUIScheme[] = "chrome";
+const char kExtensionScheme[] = "chrome-extension";
+const char kAppScheme[] = "app";
+
+const base::Feature kUkmSamplingRateFeature{"UkmSamplingRate",
+                                            base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Gets the list of whitelisted Entries as string. Format is a comma separated
+// list of Entry names (as strings).
+std::string GetWhitelistEntries() {
+  return base::GetFieldTrialParamValueByFeature(kUkmFeature,
+                                                "WhitelistEntries");
+}
+
+bool IsWhitelistedSourceId(SourceId source_id) {
+  return GetSourceIdType(source_id) == SourceIdType::NAVIGATION_ID ||
+         GetSourceIdType(source_id) == SourceIdType::APP_ID;
+}
+
+// Gets the maximum number of Sources we'll keep in memory before discarding any
+// new ones being added.
+size_t GetMaxSources() {
+  constexpr size_t kDefaultMaxSources = 500;
+  return static_cast<size_t>(base::GetFieldTrialParamByFeatureAsInt(
+      kUkmFeature, "MaxSources", kDefaultMaxSources));
+}
+
+// Gets the maximum number of unreferenced Sources kept after purging sources
+// that were added to the log.
+size_t GetMaxKeptSources() {
+  constexpr size_t kDefaultMaxKeptSources = 100;
+  return static_cast<size_t>(base::GetFieldTrialParamByFeatureAsInt(
+      kUkmFeature, "MaxKeptSources", kDefaultMaxKeptSources));
+}
+
+// Gets the maximum number of Entries we'll keep in memory before discarding any
+// new ones being added.
+size_t GetMaxEntries() {
+  constexpr size_t kDefaultMaxEntries = 5000;
+  return static_cast<size_t>(base::GetFieldTrialParamByFeatureAsInt(
+      kUkmFeature, "MaxEntries", kDefaultMaxEntries));
+}
+
+// Returns whether |url| has one of the schemes supported for logging to UKM.
+// URLs with other schemes will not be logged.
+bool HasSupportedScheme(const GURL& url) {
+  return url.SchemeIsHTTPOrHTTPS() || url.SchemeIs(url::kFtpScheme) ||
+         url.SchemeIs(url::kAboutScheme) || url.SchemeIs(kChromeUIScheme) ||
+         url.SchemeIs(kExtensionScheme) || url.SchemeIs(kAppScheme);
+}
+
+// True if we should record the initial_url field of the UKM Source proto.
+bool ShouldRecordInitialUrl() {
+  return base::GetFieldTrialParamByFeatureAsBool(kUkmFeature,
+                                                 "RecordInitialUrl", false);
+}
+
+enum class DroppedDataReason {
+  NOT_DROPPED = 0,
+  RECORDING_DISABLED = 1,
+  MAX_HIT = 2,
+  NOT_WHITELISTED = 3,
+  UNSUPPORTED_URL_SCHEME = 4,
+  SAMPLED_OUT = 5,
+  EXTENSION_URLS_DISABLED = 6,
+  EXTENSION_NOT_SYNCED = 7,
+  NOT_MATCHED = 8,
+  EMPTY_URL = 9,
+  NUM_DROPPED_DATA_REASONS
+};
+
+void RecordDroppedSource(DroppedDataReason reason) {
+  UMA_HISTOGRAM_ENUMERATION(
+      "UKM.Sources.Dropped", static_cast<int>(reason),
+      static_cast<int>(DroppedDataReason::NUM_DROPPED_DATA_REASONS));
+}
+
+void RecordDroppedEntry(DroppedDataReason reason) {
+  UMA_HISTOGRAM_ENUMERATION(
+      "UKM.Entries.Dropped", static_cast<int>(reason),
+      static_cast<int>(DroppedDataReason::NUM_DROPPED_DATA_REASONS));
+}
+
+void StoreEntryProto(const mojom::UkmEntry& in, Entry* out) {
+  DCHECK(!out->has_source_id());
+  DCHECK(!out->has_event_hash());
+
+  out->set_source_id(in.source_id);
+  out->set_event_hash(in.event_hash);
+  for (const auto& metric : in.metrics) {
+    Entry::Metric* proto_metric = out->add_metrics();
+    proto_metric->set_metric_hash(metric.first);
+    proto_metric->set_value(metric.second);
+  }
+}
+
+GURL SanitizeURL(const GURL& url) {
+  GURL::Replacements remove_params;
+  remove_params.ClearUsername();
+  remove_params.ClearPassword();
+  // chrome:// and about: URLs params are never used for navigation, only to
+  // prepopulate data on the page, so don't include their params.
+  if (url.SchemeIs(url::kAboutScheme) || url.SchemeIs("chrome")) {
+    remove_params.ClearQuery();
+  }
+  if (url.SchemeIs(kExtensionScheme)) {
+    remove_params.ClearPath();
+    remove_params.ClearQuery();
+    remove_params.ClearRef();
+  }
+  return url.ReplaceComponents(remove_params);
+}
+
+void AppendWhitelistedUrls(
+    const std::map<SourceId, std::unique_ptr<UkmSource>>& sources,
+    std::unordered_set<std::string>* urls) {
+  for (const auto& kv : sources) {
+    if (IsWhitelistedSourceId(kv.first)) {
+      urls->insert(kv.second->url().spec());
+      // Some non-navigation sources only record origin as a URL.
+      // Add the origin from the navigation source to match those too.
+      urls->insert(kv.second->url().GetOrigin().spec());
+    }
+  }
+}
+
+bool HasUnknownMetrics(const ukm::builders::DecodeMap& decode_map,
+                       const mojom::UkmEntry& entry) {
+  const auto it = decode_map.find(entry.event_hash);
+  if (it == decode_map.end())
+    return true;
+  const auto& metric_map = it->second.metric_map;
+  for (const auto& metric : entry.metrics) {
+    if (metric_map.count(metric.first) == 0)
+      return true;
+  }
+  return false;
+}
+
+}  // namespace
+
+UkmRecorderImpl::UkmRecorderImpl() : recording_enabled_(false) {}
+UkmRecorderImpl::~UkmRecorderImpl() = default;
+
+// static
+void UkmRecorderImpl::CreateFallbackSamplingTrial(
+    bool is_stable_channel,
+    base::FeatureList* feature_list) {
+  static const char kSampledGroup_Stable[] = "Sampled_NoSeed_Stable";
+  static const char kSampledGroup_Other[] = "Sampled_NoSeed_Other";
+  const char* sampled_group = kSampledGroup_Other;
+  int default_sampling = 1;  // Sampling is 1-in-N; this is N.
+
+  // Nothing is sampled out except for "stable" which omits almost everything
+  // in this configuration. This is done so that clients that fail to receive
+  // a configuration from the server do not bias aggregated results because
+  // of a relatively large number of records from them.
+  if (is_stable_channel) {
+    sampled_group = kSampledGroup_Stable;
+    default_sampling = 1000000;
+  }
+
+  scoped_refptr<base::FieldTrial> trial(
+      base::FieldTrialList::FactoryGetFieldTrial(
+          kUkmSamplingRateFeature.name, 100, sampled_group,
+          base::FieldTrialList::kNoExpirationYear, 1, 1,
+          base::FieldTrial::ONE_TIME_RANDOMIZED, nullptr));
+
+  // Everybody (100%) should have a sampling configuration.
+  std::map<std::string, std::string> params = {
+      {"_default_sampling", base::IntToString(default_sampling)}};
+  variations::AssociateVariationParams(trial->trial_name(), sampled_group,
+                                       params);
+  trial->AppendGroup(sampled_group, 100);
+
+  // Setup the feature.
+  feature_list->RegisterFieldTrialOverride(
+      kUkmSamplingRateFeature.name, base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+      trial.get());
+}
+
+UkmRecorderImpl::EventAggregate::EventAggregate() = default;
+UkmRecorderImpl::EventAggregate::~EventAggregate() = default;
+
+UkmRecorderImpl::Recordings::Recordings() = default;
+UkmRecorderImpl::Recordings& UkmRecorderImpl::Recordings::operator=(
+    Recordings&&) = default;
+UkmRecorderImpl::Recordings::~Recordings() = default;
+
+void UkmRecorderImpl::Recordings::Reset() {
+  *this = Recordings();
+}
+
+void UkmRecorderImpl::Recordings::SourceCounts::Reset() {
+  *this = SourceCounts();
+}
+
+void UkmRecorderImpl::EnableRecording(bool extensions) {
+  DVLOG(1) << "UkmRecorderImpl::EnableRecording, extensions=" << extensions;
+  recording_enabled_ = true;
+  extensions_enabled_ = extensions;
+}
+
+void UkmRecorderImpl::DisableRecording() {
+  DVLOG(1) << "UkmRecorderImpl::DisableRecording";
+  recording_enabled_ = false;
+  extensions_enabled_ = false;
+}
+
+void UkmRecorderImpl::DisableSamplingForTesting() {
+  sampling_enabled_ = false;
+}
+
+void UkmRecorderImpl::Purge() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  recordings_.Reset();
+}
+
+void UkmRecorderImpl::SetIsWebstoreExtensionCallback(
+    const IsWebstoreExtensionCallback& callback) {
+  is_webstore_extension_callback_ = callback;
+}
+
+void UkmRecorderImpl::StoreRecordingsInReport(Report* report) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  std::set<SourceId> ids_seen;
+  for (const auto& entry : recordings_.entries) {
+    Entry* proto_entry = report->add_entries();
+    StoreEntryProto(*entry, proto_entry);
+    ids_seen.insert(entry->source_id);
+  }
+
+  std::unordered_set<std::string> url_whitelist;
+  recordings_.carryover_urls_whitelist.swap(url_whitelist);
+  AppendWhitelistedUrls(recordings_.sources, &url_whitelist);
+
+  std::vector<std::unique_ptr<UkmSource>> unsent_sources;
+  int unmatched_sources = 0;
+  for (auto& kv : recordings_.sources) {
+    // If the source id is not whitelisted, don't send it unless it has
+    // associated entries and the URL matches a URL of a whitelisted source.
+    // Note: If ShouldRestrictToWhitelistedSourceIds() is true, this logic will
+    // not be hit as the source would have already been filtered in
+    // UpdateSourceURL().
+    if (!IsWhitelistedSourceId(kv.first)) {
+      // UkmSource should not keep initial_url for non-navigation source IDs.
+      DCHECK_EQ(1u, kv.second->urls().size());
+      if (!url_whitelist.count(kv.second->url().spec())) {
+        RecordDroppedSource(DroppedDataReason::NOT_MATCHED);
+        unmatched_sources++;
+        continue;
+      }
+      if (!base::ContainsKey(ids_seen, kv.first)) {
+        unsent_sources.push_back(std::move(kv.second));
+        continue;
+      }
+    }
+    Source* proto_source = report->add_sources();
+    kv.second->PopulateProto(proto_source);
+    if (!ShouldRecordInitialUrl())
+      proto_source->clear_initial_url();
+  }
+  for (const auto& event_and_aggregate : recordings_.event_aggregations) {
+    if (event_and_aggregate.second.metrics.empty())
+      continue;
+    const EventAggregate& event_aggregate = event_and_aggregate.second;
+    Aggregate* proto_aggregate = report->add_aggregates();
+    proto_aggregate->set_source_id(0);  // Across all sources.
+    proto_aggregate->set_event_hash(event_and_aggregate.first);
+    proto_aggregate->set_total_count(event_aggregate.total_count);
+    proto_aggregate->set_dropped_due_to_limits(
+        event_aggregate.dropped_due_to_limits);
+    proto_aggregate->set_dropped_due_to_sampling(
+        event_aggregate.dropped_due_to_sampling);
+    proto_aggregate->set_dropped_due_to_whitelist(
+        event_aggregate.dropped_due_to_whitelist);
+    for (const auto& metric_and_aggregate : event_aggregate.metrics) {
+      const MetricAggregate& aggregate = metric_and_aggregate.second;
+      Aggregate::Metric* proto_metric = proto_aggregate->add_metrics();
+      proto_metric->set_metric_hash(metric_and_aggregate.first);
+      proto_metric->set_value_sum(aggregate.value_sum);
+      proto_metric->set_value_square_sum(aggregate.value_square_sum);
+      if (aggregate.total_count != event_aggregate.total_count) {
+        proto_metric->set_total_count(aggregate.total_count);
+      }
+      if (aggregate.dropped_due_to_limits !=
+          event_aggregate.dropped_due_to_limits) {
+        proto_metric->set_dropped_due_to_limits(
+            aggregate.dropped_due_to_limits);
+      }
+      if (aggregate.dropped_due_to_sampling !=
+          event_aggregate.dropped_due_to_sampling) {
+        proto_metric->set_dropped_due_to_sampling(
+            aggregate.dropped_due_to_sampling);
+      }
+      if (aggregate.dropped_due_to_whitelist !=
+          event_aggregate.dropped_due_to_whitelist) {
+        proto_metric->set_dropped_due_to_whitelist(
+            aggregate.dropped_due_to_whitelist);
+      }
+    }
+  }
+
+  UMA_HISTOGRAM_COUNTS_1000("UKM.Sources.SerializedCount",
+                            recordings_.sources.size() - unsent_sources.size());
+  UMA_HISTOGRAM_COUNTS_100000("UKM.Entries.SerializedCount2",
+                              recordings_.entries.size());
+  UMA_HISTOGRAM_COUNTS_1000("UKM.Sources.UnsentSourcesCount",
+                            unsent_sources.size());
+
+  Report::SourceCounts* source_counts_proto = report->mutable_source_counts();
+  source_counts_proto->set_observed(recordings_.source_counts.observed);
+  source_counts_proto->set_navigation_sources(
+      recordings_.source_counts.navigation_sources);
+  source_counts_proto->set_unmatched_sources(unmatched_sources);
+  source_counts_proto->set_deferred_sources(unsent_sources.size());
+  source_counts_proto->set_carryover_sources(
+      recordings_.source_counts.carryover_sources);
+
+  recordings_.sources.clear();
+  recordings_.source_counts.Reset();
+  recordings_.entries.clear();
+  recordings_.event_aggregations.clear();
+
+  // Keep at most |max_kept_sources|, prioritizing most-recent entries (by
+  // creation time).
+  const size_t max_kept_sources = GetMaxKeptSources();
+  if (unsent_sources.size() > max_kept_sources) {
+    std::nth_element(unsent_sources.begin(),
+                     unsent_sources.begin() + max_kept_sources,
+                     unsent_sources.end(),
+                     [](const std::unique_ptr<ukm::UkmSource>& lhs,
+                        const std::unique_ptr<ukm::UkmSource>& rhs) {
+                       return lhs->creation_time() > rhs->creation_time();
+                     });
+    unsent_sources.resize(max_kept_sources);
+  }
+
+  for (auto& source : unsent_sources) {
+    // We already matched these sources against the URL whitelist.
+    // Re-whitelist them for the next report.
+    recordings_.carryover_urls_whitelist.insert(source->url().spec());
+    recordings_.sources.emplace(source->id(), std::move(source));
+  }
+  UMA_HISTOGRAM_COUNTS_1000("UKM.Sources.KeptSourcesCount",
+                            recordings_.sources.size());
+  recordings_.source_counts.carryover_sources = recordings_.sources.size();
+}
+
+bool UkmRecorderImpl::ShouldRestrictToWhitelistedSourceIds() const {
+  return base::GetFieldTrialParamByFeatureAsBool(
+      kUkmFeature, "RestrictToWhitelistedSourceIds", false);
+}
+
+bool UkmRecorderImpl::ShouldRestrictToWhitelistedEntries() const {
+  return true;
+}
+
+void UkmRecorderImpl::UpdateSourceURL(SourceId source_id,
+                                      const GURL& unsanitized_url) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const GURL sanitized_url = SanitizeURL(unsanitized_url);
+  if (!ShouldRecordUrl(source_id, sanitized_url))
+    return;
+
+  // TODO(csharrison): These checks can probably move to ShouldRecordUrl.
+
+  if (base::ContainsKey(recordings_.sources, source_id))
+    return;
+
+  if (recordings_.sources.size() >= GetMaxSources()) {
+    RecordDroppedSource(DroppedDataReason::MAX_HIT);
+    return;
+  }
+  RecordSource(std::make_unique<UkmSource>(source_id, sanitized_url));
+}
+
+void UkmRecorderImpl::UpdateAppURL(SourceId source_id, const GURL& url) {
+  if (!extensions_enabled_) {
+    RecordDroppedSource(DroppedDataReason::EXTENSION_URLS_DISABLED);
+    return;
+  }
+  UpdateSourceURL(source_id, url);
+}
+
+void UkmRecorderImpl::RecordNavigation(
+    SourceId source_id,
+    const UkmSource::NavigationData& unsanitized_navigation_data) {
+  DCHECK(GetSourceIdType(source_id) == SourceIdType::NAVIGATION_ID);
+  // TODO(csharrison): Consider changing this behavior so the Source isn't event
+  // recorded at all if the final URL in |unsanitized_navigation_data| should
+  // not be recorded.
+  std::vector<GURL> urls;
+  for (const GURL& url : unsanitized_navigation_data.urls) {
+    const GURL sanitized_url = SanitizeURL(url);
+    if (ShouldRecordUrl(source_id, sanitized_url))
+      urls.push_back(std::move(sanitized_url));
+  }
+
+  // None of the URLs passed the ShouldRecordUrl check, so do not create a new
+  // Source for them.
+  if (urls.empty())
+    return;
+
+  UkmSource::NavigationData sanitized_navigation_data =
+      unsanitized_navigation_data.CopyWithSanitizedUrls(urls);
+  // TODO(csharrison): This check can probably move to ShouldRecordUrl.
+  DCHECK(!base::ContainsKey(recordings_.sources, source_id));
+  if (recordings_.sources.size() >= GetMaxSources()) {
+    RecordDroppedSource(DroppedDataReason::MAX_HIT);
+    return;
+  }
+  RecordSource(
+      std::make_unique<UkmSource>(source_id, sanitized_navigation_data));
+}
+
+bool UkmRecorderImpl::ShouldRecordUrl(SourceId source_id,
+                                      const GURL& sanitized_url) const {
+  if (!recording_enabled_) {
+    RecordDroppedSource(DroppedDataReason::RECORDING_DISABLED);
+    return false;
+  }
+
+  if (ShouldRestrictToWhitelistedSourceIds() &&
+      !IsWhitelistedSourceId(source_id)) {
+    RecordDroppedSource(DroppedDataReason::NOT_WHITELISTED);
+    return false;
+  }
+
+  if (sanitized_url.is_empty()) {
+    RecordDroppedSource(DroppedDataReason::EMPTY_URL);
+    return false;
+  }
+
+  if (!HasSupportedScheme(sanitized_url)) {
+    RecordDroppedSource(DroppedDataReason::UNSUPPORTED_URL_SCHEME);
+    DVLOG(2) << "Dropped Unsupported UKM URL:" << source_id << ":"
+             << sanitized_url.spec();
+    return false;
+  }
+
+  // Extension URLs need to be specifically enabled and the extension synced.
+  if (sanitized_url.SchemeIs(kExtensionScheme)) {
+    DCHECK_EQ(sanitized_url.GetWithEmptyPath(), sanitized_url);
+    if (!extensions_enabled_) {
+      RecordDroppedSource(DroppedDataReason::EXTENSION_URLS_DISABLED);
+      return false;
+    }
+    if (!is_webstore_extension_callback_ ||
+        !is_webstore_extension_callback_.Run(sanitized_url.host_piece())) {
+      RecordDroppedSource(DroppedDataReason::EXTENSION_NOT_SYNCED);
+      return false;
+    }
+  }
+  return true;
+}
+
+void UkmRecorderImpl::RecordSource(std::unique_ptr<UkmSource> source) {
+  SourceId source_id = source->id();
+  if (GetSourceIdType(source_id) == SourceIdType::NAVIGATION_ID)
+    recordings_.source_counts.navigation_sources++;
+  recordings_.source_counts.observed++;
+  recordings_.sources.emplace(source_id, std::move(source));
+}
+
+void UkmRecorderImpl::AddEntry(mojom::UkmEntryPtr entry) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  DCHECK(!HasUnknownMetrics(decode_map_, *entry));
+
+  if (!recording_enabled_) {
+    RecordDroppedEntry(DroppedDataReason::RECORDING_DISABLED);
+    return;
+  }
+
+  EventAggregate& event_aggregate =
+      recordings_.event_aggregations[entry->event_hash];
+  event_aggregate.total_count++;
+  for (const auto& metric : entry->metrics) {
+    MetricAggregate& aggregate = event_aggregate.metrics[metric.first];
+    double value = metric.second;
+    aggregate.total_count++;
+    aggregate.value_sum += value;
+    aggregate.value_square_sum += value * value;
+  }
+
+  if (ShouldRestrictToWhitelistedEntries() &&
+      !base::ContainsKey(whitelisted_entry_hashes_, entry->event_hash)) {
+    RecordDroppedEntry(DroppedDataReason::NOT_WHITELISTED);
+    event_aggregate.dropped_due_to_whitelist++;
+    for (auto& metric : entry->metrics)
+      event_aggregate.metrics[metric.first].dropped_due_to_whitelist++;
+    return;
+  }
+
+  if (default_sampling_rate_ == 0)
+    LoadExperimentSamplingInfo();
+
+  auto found = event_sampling_rates_.find(entry->event_hash);
+  int sampling_rate = (found != event_sampling_rates_.end())
+                          ? found->second
+                          : default_sampling_rate_;
+  if (sampling_enabled_ &&
+      (sampling_rate == 0 ||
+       (sampling_rate > 1 && base::RandInt(1, sampling_rate) != 1))) {
+    RecordDroppedEntry(DroppedDataReason::SAMPLED_OUT);
+    event_aggregate.dropped_due_to_sampling++;
+    for (auto& metric : entry->metrics)
+      event_aggregate.metrics[metric.first].dropped_due_to_sampling++;
+    return;
+  }
+
+  if (recordings_.entries.size() >= GetMaxEntries()) {
+    RecordDroppedEntry(DroppedDataReason::MAX_HIT);
+    event_aggregate.dropped_due_to_limits++;
+    for (auto& metric : entry->metrics)
+      event_aggregate.metrics[metric.first].dropped_due_to_limits++;
+    return;
+  }
+
+  recordings_.entries.push_back(std::move(entry));
+}
+
+void UkmRecorderImpl::LoadExperimentSamplingInfo() {
+  DCHECK_EQ(0, default_sampling_rate_);
+  std::map<std::string, std::string> params;
+
+  if (base::FeatureList::IsEnabled(kUkmSamplingRateFeature)) {
+    // Enabled may have various parameters to control sampling.
+    if (base::GetFieldTrialParamsByFeature(kUkmSamplingRateFeature, &params)) {
+      for (const auto& kv : params) {
+        const std::string& key = kv.first;
+        if (key.length() == 0)
+          continue;
+
+        // Keys starting with an underscore are global configuration.
+        if (key.at(0) == '_') {
+          if (key == "_default_sampling") {
+            int sampling;
+            if (base::StringToInt(kv.second, &sampling) && sampling >= 0)
+              default_sampling_rate_ = sampling;
+          }
+          continue;
+        }
+
+        // Anything else is an event name.
+        int sampling;
+        if (base::StringToInt(kv.second, &sampling) && sampling >= 0)
+          event_sampling_rates_[base::HashMetricName(key)] = sampling;
+      }
+    }
+  }
+
+  // Default rate must be >0 to indicate that load is complete.
+  if (default_sampling_rate_ == 0)
+    default_sampling_rate_ = 1;
+}
+
+void UkmRecorderImpl::StoreWhitelistedEntries() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  const auto entries =
+      base::SplitString(GetWhitelistEntries(), ",", base::TRIM_WHITESPACE,
+                        base::SPLIT_WANT_NONEMPTY);
+  for (const auto& entry_string : entries)
+    whitelisted_entry_hashes_.insert(base::HashMetricName(entry_string));
+  decode_map_ = ::ukm::builders::CreateDecodeMap();
+}
+
+}  // namespace ukm
diff --git a/components/ukm/ukm_recorder_impl.h b/components/ukm/ukm_recorder_impl.h
new file mode 100644
index 0000000..7f3084f
--- /dev/null
+++ b/components/ukm/ukm_recorder_impl.h
@@ -0,0 +1,201 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UKM_UKM_RECORDER_IMPL_H_
+#define COMPONENTS_UKM_UKM_RECORDER_IMPL_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/containers/flat_map.h"
+#include "base/sequence_checker.h"
+#include "base/strings/string_piece.h"
+#include "services/metrics/public/cpp/ukm_decode.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
+#include "services/metrics/public/mojom/ukm_interface.mojom.h"
+
+namespace metrics {
+class UkmBrowserTestBase;
+class UkmEGTestHelper;
+}
+
+namespace ukm {
+class Report;
+class UkmSource;
+class UkmUtilsForTest;
+
+namespace debug {
+class UkmDebugDataExtractor;
+}
+
+class UkmRecorderImpl : public UkmRecorder {
+  using IsWebstoreExtensionCallback =
+      base::RepeatingCallback<bool(base::StringPiece id)>;
+
+ public:
+  UkmRecorderImpl();
+  ~UkmRecorderImpl() override;
+
+  // Unconditionally attempts to create a field trial to control client side
+  // metrics/crash sampling to use as a fallback when one hasn't been
+  // provided. This is expected to occur on first-run on platforms that don't
+  // have first-run variations support. This should only be called when there is
+  // no existing field trial controlling the sampling feature.
+  static void CreateFallbackSamplingTrial(bool is_stable_channel,
+                                          base::FeatureList* feature_list);
+
+  // Enables/disables recording control if data is allowed to be collected. The
+  // |extensions| flag separately controls recording of chrome-extension://
+  // URLs; this flag should reflect the "sync extensions" user setting.
+  void EnableRecording(bool extensions);
+  void DisableRecording();
+
+  // Disables sampling for testing purposes.
+  void DisableSamplingForTesting() override;
+
+  // Deletes stored recordings.
+  void Purge();
+
+  // Sets a callback for determining if an extension URL can be recorded.
+  void SetIsWebstoreExtensionCallback(
+      const IsWebstoreExtensionCallback& callback);
+
+ protected:
+  // Cache the list of whitelisted entries from the field trial parameter.
+  void StoreWhitelistedEntries();
+
+  // Writes recordings into a report proto, and clears recordings.
+  void StoreRecordingsInReport(Report* report);
+
+  const std::map<SourceId, std::unique_ptr<UkmSource>>& sources() const {
+    return recordings_.sources;
+  }
+
+  const std::vector<mojom::UkmEntryPtr>& entries() const {
+    return recordings_.entries;
+  }
+
+  // UkmRecorder:
+  void UpdateSourceURL(SourceId source_id, const GURL& url) override;
+  void UpdateAppURL(SourceId source_id, const GURL& url) override;
+  void RecordNavigation(
+      SourceId source_id,
+      const UkmSource::NavigationData& navigation_data) override;
+  using UkmRecorder::RecordOtherURL;
+
+  virtual bool ShouldRestrictToWhitelistedSourceIds() const;
+
+  virtual bool ShouldRestrictToWhitelistedEntries() const;
+
+ private:
+  friend ::metrics::UkmBrowserTestBase;
+  friend ::metrics::UkmEGTestHelper;
+  friend ::ukm::debug::UkmDebugDataExtractor;
+  friend ::ukm::UkmUtilsForTest;
+
+  struct MetricAggregate {
+    uint64_t total_count = 0;
+    double value_sum = 0;
+    double value_square_sum = 0.0;
+    uint64_t dropped_due_to_limits = 0;
+    uint64_t dropped_due_to_sampling = 0;
+    uint64_t dropped_due_to_whitelist = 0;
+  };
+
+  struct EventAggregate {
+    EventAggregate();
+    ~EventAggregate();
+
+    base::flat_map<uint64_t, MetricAggregate> metrics;
+    uint64_t total_count = 0;
+    uint64_t dropped_due_to_limits = 0;
+    uint64_t dropped_due_to_sampling = 0;
+    uint64_t dropped_due_to_whitelist = 0;
+  };
+
+  using MetricAggregateMap = std::map<uint64_t, MetricAggregate>;
+
+  // Returns true if |sanitized_url| should be recorded.
+  bool ShouldRecordUrl(SourceId source_id, const GURL& sanitized_url) const;
+
+  void RecordSource(std::unique_ptr<UkmSource> source);
+
+  void AddEntry(mojom::UkmEntryPtr entry) override;
+
+  // Load sampling configurations from field-trial information.
+  void LoadExperimentSamplingInfo();
+
+  // Whether recording new data is currently allowed.
+  bool recording_enabled_ = false;
+
+  // Indicates whether recording is enabled for extensions.
+  bool extensions_enabled_ = false;
+
+  // Indicates if sampling has been enabled.
+  bool sampling_enabled_ = true;
+
+  // Callback for checking extension IDs.
+  IsWebstoreExtensionCallback is_webstore_extension_callback_;
+
+  // Map from hashes to entry and metric names.
+  ukm::builders::DecodeMap decode_map_;
+
+  // Whitelisted Entry hashes, only the ones in this set will be recorded.
+  std::set<uint64_t> whitelisted_entry_hashes_;
+
+  // Sampling configurations, loaded from a field-trial.
+  int default_sampling_rate_ = 0;
+  base::flat_map<uint64_t, int> event_sampling_rates_;
+
+  // Contains data from various recordings which periodically get serialized
+  // and cleared by StoreRecordingsInReport() and may be Purged().
+  struct Recordings {
+    Recordings();
+    Recordings& operator=(Recordings&&);
+    ~Recordings();
+
+    // Data captured by UpdateSourceUrl().
+    std::map<SourceId, std::unique_ptr<UkmSource>> sources;
+
+    // Data captured by AddEntry().
+    std::vector<mojom::UkmEntryPtr> entries;
+
+    // URLs of sources that matched a whitelist url, but were not included in
+    // the report generated by the last log rotation because we haven't seen any
+    // events for that source yet.
+    std::unordered_set<std::string> carryover_urls_whitelist;
+
+    // Aggregate information for collected event metrics.
+    std::map<uint64_t, EventAggregate> event_aggregations;
+
+    // Aggregated counters about Sources recorded in the current log.
+    struct SourceCounts {
+      // Count of URLs recorded for all sources.
+      size_t observed = 0;
+      // Count of URLs recorded for all SourceIdType::NAVIGATION_ID Sources.
+      size_t navigation_sources = 0;
+      // Sources carried over (not recorded) from a previous logging rotation.
+      size_t carryover_sources = 0;
+
+      // Resets all of the data.
+      void Reset();
+    };
+    SourceCounts source_counts;
+
+    // Resets all of the data.
+    void Reset();
+  };
+  Recordings recordings_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+}  // namespace ukm
+
+#endif  // COMPONENTS_UKM_UKM_RECORDER_IMPL_H_
diff --git a/components/ukm/ukm_reporting_service.cc b/components/ukm/ukm_reporting_service.cc
new file mode 100644
index 0000000..cf2b1c8
--- /dev/null
+++ b/components/ukm/ukm_reporting_service.cc
@@ -0,0 +1,112 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ReportingService specialized to report UKM metrics.
+
+#include "components/ukm/ukm_reporting_service.h"
+
+#include <memory>
+
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/ukm/persisted_logs_metrics_impl.h"
+#include "components/ukm/ukm_pref_names.h"
+#include "components/ukm/ukm_service.h"
+
+namespace ukm {
+
+namespace {
+
+// The UKM server's URL.
+constexpr char kMimeType[] = "application/vnd.chrome.ukm";
+
+// The number of UKM logs that will be stored in PersistedLogs before logs
+// start being dropped.
+constexpr int kMinPersistedLogs = 8;
+
+// The number of bytes UKM logs that will be stored in PersistedLogs before
+// logs start being dropped.
+// This ensures that a reasonable amount of history will be stored even if there
+// is a long series of very small logs.
+constexpr int kMinPersistedBytes = 300000;
+
+// If an upload fails, and the transmission was over this byte count, then we
+// will discard the log, and not try to retransmit it.  We also don't persist
+// the log to the prefs for transmission during the next chrome session if this
+// limit is exceeded.
+constexpr size_t kMaxLogRetransmitSize = 100 * 1024;
+
+std::string GetServerUrl() {
+  constexpr char kDefaultServerUrl[] = "https://clients4.google.com/ukm";
+  std::string server_url =
+      base::GetFieldTrialParamValueByFeature(kUkmFeature, "ServerUrl");
+  if (!server_url.empty())
+    return server_url;
+  return kDefaultServerUrl;
+}
+
+}  // namespace
+
+// static
+void UkmReportingService::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterListPref(prefs::kUkmPersistedLogs);
+  // Base class already registered by MetricsReportingService::RegisterPrefs
+  // ReportingService::RegisterPrefs(registry);
+}
+
+UkmReportingService::UkmReportingService(metrics::MetricsServiceClient* client,
+                                         PrefService* local_state)
+    : ReportingService(client, local_state, kMaxLogRetransmitSize),
+      persisted_logs_(std::make_unique<ukm::PersistedLogsMetricsImpl>(),
+                      local_state,
+                      prefs::kUkmPersistedLogs,
+                      kMinPersistedLogs,
+                      kMinPersistedBytes,
+                      kMaxLogRetransmitSize) {}
+
+UkmReportingService::~UkmReportingService() {}
+
+metrics::LogStore* UkmReportingService::log_store() {
+  return &persisted_logs_;
+}
+
+std::string UkmReportingService::GetUploadUrl() const {
+  return GetServerUrl();
+}
+
+std::string UkmReportingService::GetInsecureUploadUrl() const {
+  return "";
+}
+
+base::StringPiece UkmReportingService::upload_mime_type() const {
+  return kMimeType;
+}
+
+metrics::MetricsLogUploader::MetricServiceType
+UkmReportingService::service_type() const {
+  return metrics::MetricsLogUploader::UKM;
+}
+
+void UkmReportingService::LogCellularConstraint(bool upload_canceled) {
+  UMA_HISTOGRAM_BOOLEAN("UKM.LogUpload.Canceled.CellularConstraint",
+                        upload_canceled);
+}
+
+void UkmReportingService::LogResponseOrErrorCode(int response_code,
+                                                 int error_code,
+                                                 bool was_https) {
+  // |was_https| is ignored since all UKM logs are received over HTTPS.
+  base::UmaHistogramSparse("UKM.LogUpload.ResponseOrErrorCode",
+                           response_code >= 0 ? response_code : error_code);
+}
+
+void UkmReportingService::LogSuccess(size_t log_size) {
+  UMA_HISTOGRAM_COUNTS_10000("UKM.LogSize.OnSuccess", log_size / 1024);
+}
+
+void UkmReportingService::LogLargeRejection(size_t log_size) {}
+
+}  // namespace metrics
diff --git a/components/ukm/ukm_reporting_service.h b/components/ukm/ukm_reporting_service.h
new file mode 100644
index 0000000..a805395
--- /dev/null
+++ b/components/ukm/ukm_reporting_service.h
@@ -0,0 +1,69 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file defines a service that sends ukm logs to a server.
+
+#ifndef COMPONENTS_UKM_UKM_REPORTING_SERVICE_H_
+#define COMPONENTS_UKM_UKM_REPORTING_SERVICE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/metrics/persisted_logs.h"
+#include "components/metrics/reporting_service.h"
+
+class PrefService;
+class PrefRegistrySimple;
+
+namespace metrics {
+class MetricsServiceClient;
+}
+
+namespace ukm {
+
+// A service that uploads logs to the UKM server.
+class UkmReportingService : public metrics::ReportingService {
+ public:
+  // Creates the UkmReportingService with the given |client|, and
+  // |local_state|.  Does not take ownership of the paramaters; instead stores
+  // a weak pointer to each. Caller should ensure that the parameters are valid
+  // for the lifetime of this class.
+  UkmReportingService(metrics::MetricsServiceClient* client,
+                      PrefService* local_state);
+  ~UkmReportingService() override;
+
+  // At startup, prefs needs to be called with a list of all the pref names and
+  // types we'll be using.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+  metrics::PersistedLogs* ukm_log_store() { return &persisted_logs_; }
+  const metrics::PersistedLogs* ukm_log_store() const {
+    return &persisted_logs_;
+  }
+
+ private:
+  // metrics:ReportingService:
+  metrics::LogStore* log_store() override;
+  std::string GetUploadUrl() const override;
+  // Returns an empty string since retrying over HTTP is not enabled for UKM
+  std::string GetInsecureUploadUrl() const override;
+  base::StringPiece upload_mime_type() const override;
+  metrics::MetricsLogUploader::MetricServiceType service_type() const override;
+  void LogCellularConstraint(bool upload_canceled) override;
+  void LogResponseOrErrorCode(int response_code,
+                              int error_code,
+                              bool was_https) override;
+  void LogSuccess(size_t log_size) override;
+  void LogLargeRejection(size_t log_size) override;
+
+  metrics::PersistedLogs persisted_logs_;
+
+  DISALLOW_COPY_AND_ASSIGN(UkmReportingService);
+};
+
+}  // namespace ukm
+
+#endif  // COMPONENTS_UKM_UKM_REPORTING_SERVICE_H_
diff --git a/components/ukm/ukm_rotation_scheduler.cc b/components/ukm/ukm_rotation_scheduler.cc
new file mode 100644
index 0000000..787ed1a
--- /dev/null
+++ b/components/ukm/ukm_rotation_scheduler.cc
@@ -0,0 +1,24 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ukm/ukm_rotation_scheduler.h"
+
+#include "base/metrics/histogram_macros.h"
+
+namespace ukm {
+
+UkmRotationScheduler::UkmRotationScheduler(
+    const base::Closure& upload_callback,
+    const base::Callback<base::TimeDelta(void)>& upload_interval_callback)
+    : metrics::MetricsRotationScheduler(upload_callback,
+                                        upload_interval_callback) {}
+
+UkmRotationScheduler::~UkmRotationScheduler() = default;
+
+void UkmRotationScheduler::LogMetricsInitSequence(InitSequence sequence) {
+  UMA_HISTOGRAM_ENUMERATION("UKM.InitSequence", sequence,
+                            INIT_SEQUENCE_ENUM_SIZE);
+}
+
+}  // namespace ukm
diff --git a/components/ukm/ukm_rotation_scheduler.h b/components/ukm/ukm_rotation_scheduler.h
new file mode 100644
index 0000000..5c83527
--- /dev/null
+++ b/components/ukm/ukm_rotation_scheduler.h
@@ -0,0 +1,33 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UKM_METRICS_REPORTING_SCHEDULER_H_
+#define COMPONENTS_UKM_METRICS_REPORTING_SCHEDULER_H_
+
+#include "base/time/time.h"
+#include "components/metrics/metrics_rotation_scheduler.h"
+
+namespace ukm {
+
+// Scheduler to drive a UkmService object's log rotations.
+class UkmRotationScheduler : public metrics::MetricsRotationScheduler {
+ public:
+  // Creates UkmRotationScheduler object with the given |rotation_callback|
+  // callback to call when log rotation should happen and |interval_callback|
+  // to determine the interval between rotations in steady state.
+  UkmRotationScheduler(
+      const base::Closure& rotation_callback,
+      const base::Callback<base::TimeDelta(void)>& interval_callback);
+  ~UkmRotationScheduler() override;
+
+ private:
+  // Record the init sequence order histogram.
+  void LogMetricsInitSequence(InitSequence sequence) override;
+
+  DISALLOW_COPY_AND_ASSIGN(UkmRotationScheduler);
+};
+
+}  // namespace ukm
+
+#endif  // COMPONENTS_UKM_METRICS_REPORTING_SCHEDULER_H_
diff --git a/components/ukm/ukm_service.cc b/components/ukm/ukm_service.cc
new file mode 100644
index 0000000..2f6b875
--- /dev/null
+++ b/components/ukm/ukm_service.cc
@@ -0,0 +1,258 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ukm/ukm_service.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/rand_util.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "components/metrics/metrics_log.h"
+#include "components/metrics/metrics_service_client.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/ukm/persisted_logs_metrics_impl.h"
+#include "components/ukm/ukm_pref_names.h"
+#include "components/ukm/ukm_rotation_scheduler.h"
+#include "services/metrics/public/cpp/delegating_ukm_recorder.h"
+#include "third_party/metrics_proto/ukm/report.pb.h"
+
+namespace ukm {
+
+namespace {
+
+// Generates a new client id and stores it in prefs.
+uint64_t GenerateClientId(PrefService* pref_service) {
+  uint64_t client_id = 0;
+  while (!client_id)
+    client_id = base::RandUint64();
+  pref_service->SetInt64(prefs::kUkmClientId, client_id);
+
+  // Also reset the session id counter.
+  pref_service->SetInteger(prefs::kUkmSessionId, 0);
+  return client_id;
+}
+
+uint64_t LoadOrGenerateClientId(PrefService* pref_service) {
+  uint64_t client_id = pref_service->GetInt64(prefs::kUkmClientId);
+  if (!client_id)
+    client_id = GenerateClientId(pref_service);
+  return client_id;
+}
+
+int32_t LoadSessionId(PrefService* pref_service) {
+  int32_t session_id = pref_service->GetInteger(prefs::kUkmSessionId);
+  ++session_id;  // increment session id, once per session
+  pref_service->SetInteger(prefs::kUkmSessionId, session_id);
+  return session_id;
+}
+
+}  // namespace
+
+UkmService::UkmService(PrefService* pref_service,
+                       metrics::MetricsServiceClient* client,
+                       bool restrict_to_whitelist_entries)
+    : pref_service_(pref_service),
+      restrict_to_whitelist_entries_(restrict_to_whitelist_entries),
+      client_id_(0),
+      session_id_(0),
+      report_count_(0),
+      client_(client),
+      reporting_service_(client, pref_service),
+      initialize_started_(false),
+      initialize_complete_(false),
+      self_ptr_factory_(this) {
+  DCHECK(pref_service_);
+  DCHECK(client_);
+  DVLOG(1) << "UkmService::Constructor";
+
+  reporting_service_.Initialize();
+
+  base::Closure rotate_callback =
+      base::Bind(&UkmService::RotateLog, self_ptr_factory_.GetWeakPtr());
+  // MetricsServiceClient outlives UkmService, and
+  // MetricsReportingScheduler is tied to the lifetime of |this|.
+  const base::Callback<base::TimeDelta(void)>& get_upload_interval_callback =
+      base::Bind(&metrics::MetricsServiceClient::GetStandardUploadInterval,
+                 base::Unretained(client_));
+  scheduler_.reset(new ukm::UkmRotationScheduler(rotate_callback,
+                                                 get_upload_interval_callback));
+
+  StoreWhitelistedEntries();
+
+  DelegatingUkmRecorder::Get()->AddDelegate(self_ptr_factory_.GetWeakPtr());
+}
+
+UkmService::~UkmService() {
+  DisableReporting();
+  DelegatingUkmRecorder::Get()->RemoveDelegate(this);
+}
+
+void UkmService::Initialize() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!initialize_started_);
+  DVLOG(1) << "UkmService::Initialize";
+  initialize_started_ = true;
+
+  DCHECK_EQ(0, report_count_);
+  client_id_ = LoadOrGenerateClientId(pref_service_);
+  session_id_ = LoadSessionId(pref_service_);
+  metrics_providers_.Init();
+
+  StartInitTask();
+}
+
+void UkmService::EnableReporting() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DVLOG(1) << "UkmService::EnableReporting";
+  if (reporting_service_.reporting_active())
+    return;
+
+  metrics_providers_.OnRecordingEnabled();
+
+  if (!initialize_started_)
+    Initialize();
+  scheduler_->Start();
+  reporting_service_.EnableReporting();
+}
+
+void UkmService::DisableReporting() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DVLOG(1) << "UkmService::DisableReporting";
+
+  reporting_service_.DisableReporting();
+
+  metrics_providers_.OnRecordingDisabled();
+
+  scheduler_->Stop();
+  Flush();
+}
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+void UkmService::OnAppEnterForeground() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DVLOG(1) << "UkmService::OnAppEnterForeground";
+
+  // If initialize_started_ is false, UKM has not yet been started, so bail. The
+  // scheduler will instead be started via EnableReporting().
+  if (!initialize_started_)
+    return;
+
+  scheduler_->Start();
+}
+
+void UkmService::OnAppEnterBackground() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DVLOG(1) << "UkmService::OnAppEnterBackground";
+
+  if (!initialize_started_)
+    return;
+
+  scheduler_->Stop();
+
+  // Give providers a chance to persist ukm data as part of being backgrounded.
+  metrics_providers_.OnAppEnterBackground();
+
+  Flush();
+}
+#endif
+
+void UkmService::Flush() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (initialize_complete_)
+    BuildAndStoreLog();
+  reporting_service_.ukm_log_store()->PersistUnsentLogs();
+}
+
+void UkmService::Purge() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DVLOG(1) << "UkmService::Purge";
+  reporting_service_.ukm_log_store()->Purge();
+  UkmRecorderImpl::Purge();
+}
+
+// TODO(bmcquade): rename this to something more generic, like
+// ResetClientState. Consider resetting all prefs here.
+void UkmService::ResetClientId() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  client_id_ = GenerateClientId(pref_service_);
+  session_id_ = LoadSessionId(pref_service_);
+  report_count_ = 0;
+}
+
+void UkmService::RegisterMetricsProvider(
+    std::unique_ptr<metrics::MetricsProvider> provider) {
+  metrics_providers_.RegisterMetricsProvider(std::move(provider));
+}
+
+// static
+void UkmService::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterInt64Pref(prefs::kUkmClientId, 0);
+  registry->RegisterIntegerPref(prefs::kUkmSessionId, 0);
+  UkmReportingService::RegisterPrefs(registry);
+}
+
+void UkmService::StartInitTask() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DVLOG(1) << "UkmService::StartInitTask";
+  metrics_providers_.AsyncInit(base::Bind(&UkmService::FinishedInitTask,
+                                          self_ptr_factory_.GetWeakPtr()));
+}
+
+void UkmService::FinishedInitTask() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DVLOG(1) << "UkmService::FinishedInitTask";
+  initialize_complete_ = true;
+  scheduler_->InitTaskComplete();
+}
+
+void UkmService::RotateLog() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DVLOG(1) << "UkmService::RotateLog";
+  if (!reporting_service_.ukm_log_store()->has_unsent_logs())
+    BuildAndStoreLog();
+  reporting_service_.Start();
+  scheduler_->RotationFinished();
+}
+
+void UkmService::BuildAndStoreLog() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DVLOG(1) << "UkmService::BuildAndStoreLog";
+
+  // Suppress generating a log if we have no new data to include.
+  // TODO(zhenw): add a histogram here to debug if this case is hitting a lot.
+  if (sources().empty() && entries().empty())
+    return;
+
+  Report report;
+  report.set_client_id(client_id_);
+  report.set_session_id(session_id_);
+  report.set_report_id(++report_count_);
+
+  StoreRecordingsInReport(&report);
+
+  metrics::MetricsLog::RecordCoreSystemProfile(client_,
+                                               report.mutable_system_profile());
+
+  metrics_providers_.ProvideSystemProfileMetrics(
+      report.mutable_system_profile());
+
+  std::string serialized_log;
+  report.SerializeToString(&serialized_log);
+  reporting_service_.ukm_log_store()->StoreLog(serialized_log);
+}
+
+bool UkmService::ShouldRestrictToWhitelistedEntries() const {
+  return restrict_to_whitelist_entries_;
+}
+
+}  // namespace ukm
diff --git a/components/ukm/ukm_service.h b/components/ukm/ukm_service.h
new file mode 100644
index 0000000..e5c6136
--- /dev/null
+++ b/components/ukm/ukm_service.h
@@ -0,0 +1,154 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_UKM_UKM_SERVICE_H_
+#define COMPONENTS_UKM_UKM_SERVICE_H_
+
+#include <stddef.h>
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "build/build_config.h"
+#include "components/metrics/delegating_provider.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/metrics/metrics_rotation_scheduler.h"
+#include "components/ukm/ukm_recorder_impl.h"
+#include "components/ukm/ukm_reporting_service.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace metrics {
+class MetricsServiceClient;
+class UkmBrowserTestBase;
+class UkmEGTestHelper;
+}
+
+namespace ukm {
+
+namespace debug {
+class UkmDebugDataExtractor;
+}
+
+// The URL-Keyed Metrics (UKM) service is responsible for gathering and
+// uploading reports that contain fine grained performance metrics including
+// URLs for top-level navigations.
+class UkmService : public UkmRecorderImpl {
+ public:
+  // Constructs a UkmService.
+  // Calling code is responsible for ensuring that the lifetime of
+  // |pref_service| is longer than the lifetime of UkmService.
+  UkmService(PrefService* pref_service,
+             metrics::MetricsServiceClient* client,
+             bool restrict_to_whitelist_entries);
+  ~UkmService() override;
+
+  // Initializes the UKM service.
+  void Initialize();
+
+  // Enables/disables transmission of accumulated logs. Logs that have already
+  // been created will remain persisted to disk.
+  void EnableReporting();
+  void DisableReporting();
+
+#if defined(OS_ANDROID) || defined(OS_IOS)
+  void OnAppEnterBackground();
+  void OnAppEnterForeground();
+#endif
+
+  // Records any collected data into logs, and writes to disk.
+  void Flush();
+
+  // Deletes any unsent local data.
+  void Purge();
+
+  // Resets the client id stored in prefs.
+  void ResetClientId();
+
+  // Registers the specified |provider| to provide additional metrics into the
+  // UKM log. Should be called during MetricsService initialization only.
+  void RegisterMetricsProvider(
+      std::unique_ptr<metrics::MetricsProvider> provider);
+
+  // Registers the names of all of the preferences used by UkmService in
+  // the provided PrefRegistry.
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+  int32_t report_count() const { return report_count_; }
+
+ private:
+  friend ::metrics::UkmBrowserTestBase;
+  friend ::metrics::UkmEGTestHelper;
+  friend ::ukm::debug::UkmDebugDataExtractor;
+  friend ::ukm::UkmUtilsForTest;
+
+  // Starts metrics client initialization.
+  void StartInitTask();
+
+  // Called when initialization tasks are complete, to notify the scheduler
+  // that it can begin calling RotateLog.
+  void FinishedInitTask();
+
+  // Periodically called by scheduler_ to advance processing of logs.
+  void RotateLog();
+
+  // Constructs a new Report from available data and stores it in
+  // persisted_logs_.
+  void BuildAndStoreLog();
+
+  // Starts an upload of the next log from persisted_logs_.
+  void StartScheduledUpload();
+
+  // Called by log_uploader_ when the an upload is completed.
+  void OnLogUploadComplete(int response_code);
+
+  // ukm::UkmRecorderImpl:
+  bool ShouldRestrictToWhitelistedEntries() const override;
+
+  // A weak pointer to the PrefService used to read and write preferences.
+  PrefService* pref_service_;
+
+  // If true, only whitelisted Entries should be recorded.
+  bool restrict_to_whitelist_entries_;
+
+  // The UKM client id stored in prefs.
+  uint64_t client_id_;
+
+  // The UKM session id stored in prefs.
+  int32_t session_id_;
+
+  // The number of reports generated this session.
+  int32_t report_count_;
+
+  // Used to interact with the embedder. Weak pointer; must outlive |this|
+  // instance.
+  metrics::MetricsServiceClient* const client_;
+
+  // Registered metrics providers.
+  metrics::DelegatingProvider metrics_providers_;
+
+  // Log reporting service.
+  ukm::UkmReportingService reporting_service_;
+
+  // The scheduler for determining when uploads should happen.
+  std::unique_ptr<metrics::MetricsRotationScheduler> scheduler_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  bool initialize_started_;
+  bool initialize_complete_;
+
+  // Weak pointers factory used to post task on different threads. All weak
+  // pointers managed by this factory have the same lifetime as UkmService.
+  base::WeakPtrFactory<UkmService> self_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(UkmService);
+};
+
+}  // namespace ukm
+
+#endif  // COMPONENTS_UKM_UKM_SERVICE_H_
diff --git a/components/ukm/ukm_service_unittest.cc b/components/ukm/ukm_service_unittest.cc
new file mode 100644
index 0000000..e73c0c2
--- /dev/null
+++ b/components/ukm/ukm_service_unittest.cc
@@ -0,0 +1,1144 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/ukm/ukm_service.h"
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/hash.h"
+#include "base/metrics/metrics_hashes.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "components/metrics/test_metrics_provider.h"
+#include "components/metrics/test_metrics_service_client.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/ukm/persisted_logs_metrics_impl.h"
+#include "components/ukm/ukm_pref_names.h"
+#include "components/variations/variations_associated_data.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_entry_builder.h"
+#include "services/metrics/public/cpp/ukm_source.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/metrics_proto/ukm/report.pb.h"
+#include "third_party/metrics_proto/ukm/source.pb.h"
+#include "third_party/zlib/google/compression_utils.h"
+
+namespace ukm {
+
+// Some arbitrary events used in tests.
+using TestEvent1 = ukm::builders::PageLoad;
+const char* kTestEvent1Metric1 =
+    TestEvent1::kPaintTiming_NavigationToFirstContentfulPaintName;
+const char* kTestEvent1Metric2 = TestEvent1::kNet_CacheBytesName;
+using TestEvent2 = ukm::builders::Memory_Experimental;
+const char* kTestEvent2Metric1 = TestEvent2::kArrayBufferName;
+const char* kTestEvent2Metric2 = TestEvent2::kBlinkGCName;
+using TestEvent3 = ukm::builders::Previews;
+
+std::string Entry1And2Whitelist() {
+  return std::string(TestEvent1::kEntryName) + ',' + TestEvent2::kEntryName;
+}
+
+// A small shim exposing UkmRecorder methods to tests.
+class TestRecordingHelper {
+ public:
+  explicit TestRecordingHelper(UkmRecorder* recorder) : recorder_(recorder) {
+    recorder_->DisableSamplingForTesting();
+  }
+
+  void UpdateSourceURL(SourceId source_id, const GURL& url) {
+    recorder_->UpdateSourceURL(source_id, url);
+  }
+
+  void RecordNavigation(SourceId source_id,
+                        const UkmSource::NavigationData& navigation_data) {
+    recorder_->RecordNavigation(source_id, navigation_data);
+  }
+
+ private:
+  UkmRecorder* recorder_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestRecordingHelper);
+};
+
+namespace {
+
+bool TestIsWebstoreExtension(base::StringPiece id) {
+  return (id == "bhcnanendmgjjeghamaccjnochlnhcgj");
+}
+
+// TODO(rkaplow): consider making this a generic testing class in
+// components/variations.
+class ScopedUkmFeatureParams {
+ public:
+  ScopedUkmFeatureParams(
+      base::FeatureList::OverrideState feature_state,
+      const std::map<std::string, std::string>& variation_params) {
+    static const char kTestFieldTrialName[] = "TestTrial";
+    static const char kTestExperimentGroupName[] = "TestGroup";
+
+    variations::testing::ClearAllVariationParams();
+
+    EXPECT_TRUE(variations::AssociateVariationParams(
+        kTestFieldTrialName, kTestExperimentGroupName, variation_params));
+
+    base::FieldTrial* field_trial = base::FieldTrialList::CreateFieldTrial(
+        kTestFieldTrialName, kTestExperimentGroupName);
+
+    std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
+    feature_list->RegisterFieldTrialOverride(kUkmFeature.name, feature_state,
+                                             field_trial);
+
+    // Since we are adding a scoped feature list after browser start, copy over
+    // the existing feature list to prevent inconsistency.
+    base::FeatureList* existing_feature_list = base::FeatureList::GetInstance();
+    if (existing_feature_list) {
+      std::string enabled_features;
+      std::string disabled_features;
+      base::FeatureList::GetInstance()->GetFeatureOverrides(&enabled_features,
+                                                            &disabled_features);
+      feature_list->InitializeFromCommandLine(enabled_features,
+                                              disabled_features);
+    }
+
+    scoped_feature_list_.InitWithFeatureList(std::move(feature_list));
+  }
+
+  ~ScopedUkmFeatureParams() { variations::testing::ClearAllVariationParams(); }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedUkmFeatureParams);
+};
+
+class UkmServiceTest : public testing::Test {
+ public:
+  UkmServiceTest()
+      : task_runner_(new base::TestSimpleTaskRunner),
+        task_runner_handle_(task_runner_) {
+    UkmService::RegisterPrefs(prefs_.registry());
+    ClearPrefs();
+  }
+
+  void ClearPrefs() {
+    prefs_.ClearPref(prefs::kUkmClientId);
+    prefs_.ClearPref(prefs::kUkmSessionId);
+    prefs_.ClearPref(prefs::kUkmPersistedLogs);
+  }
+
+  int GetPersistedLogCount() {
+    const base::ListValue* list_value =
+        prefs_.GetList(prefs::kUkmPersistedLogs);
+    return list_value->GetSize();
+  }
+
+  Report GetPersistedReport() {
+    EXPECT_GE(GetPersistedLogCount(), 1);
+    metrics::PersistedLogs result_persisted_logs(
+        std::make_unique<ukm::PersistedLogsMetricsImpl>(), &prefs_,
+        prefs::kUkmPersistedLogs,
+        3,     // log count limit
+        1000,  // byte limit
+        0);
+
+    result_persisted_logs.LoadPersistedUnsentLogs();
+    result_persisted_logs.StageNextLog();
+
+    std::string uncompressed_log_data;
+    EXPECT_TRUE(compression::GzipUncompress(result_persisted_logs.staged_log(),
+                                            &uncompressed_log_data));
+
+    Report report;
+    EXPECT_TRUE(report.ParseFromString(uncompressed_log_data));
+    return report;
+  }
+
+  static SourceId GetWhitelistedSourceId(int64_t id) {
+    return ConvertToSourceId(id, SourceIdType::NAVIGATION_ID);
+  }
+
+  static SourceId GetNonWhitelistedSourceId(int64_t id) {
+    return ConvertToSourceId(id, SourceIdType::UKM);
+  }
+
+ protected:
+  TestingPrefServiceSimple prefs_;
+  metrics::TestMetricsServiceClient client_;
+
+  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
+  base::ThreadTaskRunnerHandle task_runner_handle_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UkmServiceTest);
+};
+
+}  // namespace
+
+TEST_F(UkmServiceTest, EnableDisableSchedule) {
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  EXPECT_FALSE(task_runner_->HasPendingTask());
+  service.Initialize();
+  EXPECT_FALSE(task_runner_->HasPendingTask());
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+  EXPECT_TRUE(task_runner_->HasPendingTask());
+  service.DisableReporting();
+  task_runner_->RunPendingTasks();
+  EXPECT_FALSE(task_runner_->HasPendingTask());
+}
+
+TEST_F(UkmServiceTest, PersistAndPurge) {
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", Entry1And2Whitelist()}});
+
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+  EXPECT_EQ(GetPersistedLogCount(), 0);
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  SourceId id = GetWhitelistedSourceId(0);
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar"));
+  // Should init, generate a log, and start an upload for source.
+  task_runner_->RunPendingTasks();
+  EXPECT_TRUE(client_.uploader()->is_uploading());
+  // Flushes the generated log to disk and generates a new entry.
+  TestEvent1(id).Record(&service);
+  service.Flush();
+  EXPECT_EQ(GetPersistedLogCount(), 2);
+  service.Purge();
+  EXPECT_EQ(GetPersistedLogCount(), 0);
+}
+
+TEST_F(UkmServiceTest, Purge) {
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+  EXPECT_EQ(GetPersistedLogCount(), 0);
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  // Record some data
+  auto id = GetWhitelistedSourceId(0);
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar1"));
+  TestEvent1(id).Record(&service);
+
+  // Purge should delete data, so there shouldn't be anything left to upload.
+  service.Purge();
+  service.Flush();
+  EXPECT_EQ(0, GetPersistedLogCount());
+}
+
+TEST_F(UkmServiceTest, SourceSerialization) {
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+  EXPECT_EQ(GetPersistedLogCount(), 0);
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  UkmSource::NavigationData navigation_data;
+  navigation_data.urls = {GURL("https://google.com/initial"),
+                          GURL("https://google.com/final")};
+
+  ukm::SourceId id = GetWhitelistedSourceId(0);
+  recorder.RecordNavigation(id, navigation_data);
+
+  service.Flush();
+  EXPECT_EQ(GetPersistedLogCount(), 1);
+
+  Report proto_report = GetPersistedReport();
+  EXPECT_EQ(1, proto_report.sources_size());
+  EXPECT_TRUE(proto_report.has_session_id());
+  const Source& proto_source = proto_report.sources(0);
+
+  EXPECT_EQ(id, proto_source.id());
+  EXPECT_EQ(GURL("https://google.com/final").spec(), proto_source.url());
+  EXPECT_FALSE(proto_source.has_initial_url());
+}
+
+TEST_F(UkmServiceTest, AddEntryWithEmptyMetrics) {
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", Entry1And2Whitelist()}});
+
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+  ASSERT_EQ(0, GetPersistedLogCount());
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  ukm::SourceId id = GetWhitelistedSourceId(0);
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar"));
+
+  TestEvent1(id).Record(&service);
+  service.Flush();
+  ASSERT_EQ(1, GetPersistedLogCount());
+  Report proto_report = GetPersistedReport();
+  EXPECT_EQ(1, proto_report.entries_size());
+}
+
+TEST_F(UkmServiceTest, MetricsProviderTest) {
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", Entry1And2Whitelist()}});
+
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+
+  metrics::TestMetricsProvider* provider = new metrics::TestMetricsProvider();
+  service.RegisterMetricsProvider(
+      std::unique_ptr<metrics::MetricsProvider>(provider));
+
+  service.Initialize();
+
+  // Providers have not supplied system profile information yet.
+  EXPECT_FALSE(provider->provide_system_profile_metrics_called());
+
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  ukm::SourceId id = GetWhitelistedSourceId(0);
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar"));
+  TestEvent1(id).Record(&service);
+  service.Flush();
+  EXPECT_EQ(GetPersistedLogCount(), 1);
+
+  Report proto_report = GetPersistedReport();
+  EXPECT_EQ(1, proto_report.sources_size());
+  EXPECT_EQ(1, proto_report.entries_size());
+
+  // Providers have now supplied system profile information.
+  EXPECT_TRUE(provider->provide_system_profile_metrics_called());
+}
+
+TEST_F(UkmServiceTest, LogsRotation) {
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+  EXPECT_EQ(GetPersistedLogCount(), 0);
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  EXPECT_EQ(0, service.report_count());
+
+  // Log rotation should generate a log.
+  const ukm::SourceId id = GetWhitelistedSourceId(0);
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar"));
+  task_runner_->RunPendingTasks();
+  EXPECT_EQ(1, service.report_count());
+  EXPECT_TRUE(client_.uploader()->is_uploading());
+
+  // Rotation shouldn't generate a log due to one being pending.
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar"));
+  task_runner_->RunPendingTasks();
+  EXPECT_EQ(1, service.report_count());
+  EXPECT_TRUE(client_.uploader()->is_uploading());
+
+  // Completing the upload should clear pending log, then log rotation should
+  // generate another log.
+  client_.uploader()->CompleteUpload(200);
+  task_runner_->RunPendingTasks();
+  EXPECT_EQ(2, service.report_count());
+
+  // Check that rotations keep working.
+  for (int i = 3; i < 6; i++) {
+    task_runner_->RunPendingTasks();
+    client_.uploader()->CompleteUpload(200);
+    recorder.UpdateSourceURL(id, GURL("https://google.com/foobar"));
+    task_runner_->RunPendingTasks();
+    EXPECT_EQ(i, service.report_count());
+  }
+}
+
+TEST_F(UkmServiceTest, LogsUploadedOnlyWhenHavingSourcesOrEntries) {
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  // Testing two whitelisted Entries.
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", Entry1And2Whitelist()}});
+
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+  EXPECT_EQ(GetPersistedLogCount(), 0);
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  EXPECT_TRUE(task_runner_->HasPendingTask());
+  // Neither rotation or Flush should generate logs
+  task_runner_->RunPendingTasks();
+  service.Flush();
+  EXPECT_EQ(GetPersistedLogCount(), 0);
+
+  ukm::SourceId id = GetWhitelistedSourceId(0);
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar"));
+  // Includes a Source, so will persist.
+  service.Flush();
+  EXPECT_EQ(GetPersistedLogCount(), 1);
+
+  TestEvent1(id).Record(&service);
+  // Includes an Entry, so will persist.
+  service.Flush();
+  EXPECT_EQ(GetPersistedLogCount(), 2);
+
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar"));
+  TestEvent1(id).Record(&service);
+  // Includes a Source and an Entry, so will persist.
+  service.Flush();
+  EXPECT_EQ(GetPersistedLogCount(), 3);
+
+  // Current log has no Sources.
+  service.Flush();
+  EXPECT_EQ(GetPersistedLogCount(), 3);
+}
+
+TEST_F(UkmServiceTest, GetNewSourceID) {
+  ukm::SourceId id1 = UkmRecorder::GetNewSourceID();
+  ukm::SourceId id2 = UkmRecorder::GetNewSourceID();
+  ukm::SourceId id3 = UkmRecorder::GetNewSourceID();
+  EXPECT_NE(id1, id2);
+  EXPECT_NE(id1, id3);
+  EXPECT_NE(id2, id3);
+}
+
+TEST_F(UkmServiceTest, RecordInitialUrl) {
+  for (bool should_record_initial_url : {true, false}) {
+    base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+    ScopedUkmFeatureParams params(
+        base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+        {{"RecordInitialUrl", should_record_initial_url ? "true" : "false"}});
+
+    ClearPrefs();
+    UkmService service(&prefs_, &client_,
+                       true /* restrict_to_whitelisted_entries */);
+    TestRecordingHelper recorder(&service);
+    EXPECT_EQ(GetPersistedLogCount(), 0);
+    service.Initialize();
+    task_runner_->RunUntilIdle();
+    service.EnableRecording(/*extensions=*/false);
+    service.EnableReporting();
+
+    ukm::SourceId id = GetWhitelistedSourceId(0);
+    UkmSource::NavigationData navigation_data;
+    navigation_data.urls = {GURL("https://google.com/initial"),
+                            GURL("https://google.com/final")};
+    recorder.RecordNavigation(id, navigation_data);
+
+    service.Flush();
+    EXPECT_EQ(GetPersistedLogCount(), 1);
+
+    Report proto_report = GetPersistedReport();
+    EXPECT_EQ(1, proto_report.sources_size());
+    const Source& proto_source = proto_report.sources(0);
+
+    EXPECT_EQ(id, proto_source.id());
+    EXPECT_EQ(GURL("https://google.com/final").spec(), proto_source.url());
+    EXPECT_EQ(should_record_initial_url, proto_source.has_initial_url());
+    if (should_record_initial_url) {
+      EXPECT_EQ(GURL("https://google.com/initial").spec(),
+                proto_source.initial_url());
+    }
+  }
+}
+
+TEST_F(UkmServiceTest, RestrictToWhitelistedSourceIds) {
+  const GURL kURL = GURL("https://example.com/");
+  for (bool restrict_to_whitelisted_source_ids : {true, false}) {
+    base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+    ScopedUkmFeatureParams params(
+        base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+        {{"RestrictToWhitelistedSourceIds",
+          restrict_to_whitelisted_source_ids ? "true" : "false"},
+         {"WhitelistEntries", Entry1And2Whitelist()}});
+
+    ClearPrefs();
+    UkmService service(&prefs_, &client_,
+                       true /* restrict_to_whitelisted_entries */);
+    TestRecordingHelper recorder(&service);
+    EXPECT_EQ(GetPersistedLogCount(), 0);
+    service.Initialize();
+    task_runner_->RunUntilIdle();
+    service.EnableRecording(/*extensions=*/false);
+    service.EnableReporting();
+
+    ukm::SourceId id1 = GetWhitelistedSourceId(0);
+    recorder.UpdateSourceURL(id1, kURL);
+    TestEvent1(id1).Record(&service);
+
+    // Create a non-navigation-based sourceid, which should not be whitelisted.
+    ukm::SourceId id2 = GetNonWhitelistedSourceId(1);
+    recorder.UpdateSourceURL(id2, kURL);
+    TestEvent1(id2).Record(&service);
+
+    service.Flush();
+    ASSERT_EQ(GetPersistedLogCount(), 1);
+    Report proto_report = GetPersistedReport();
+    ASSERT_GE(proto_report.sources_size(), 1);
+
+    // The whitelisted source should always be recorded.
+    const Source& proto_source1 = proto_report.sources(0);
+    EXPECT_EQ(id1, proto_source1.id());
+    EXPECT_EQ(kURL.spec(), proto_source1.url());
+
+    // The non-whitelisted source should only be recorded if we aren't
+    // restricted to whitelisted source ids.
+    if (restrict_to_whitelisted_source_ids) {
+      ASSERT_EQ(1, proto_report.sources_size());
+    } else {
+      ASSERT_EQ(2, proto_report.sources_size());
+      const Source& proto_source2 = proto_report.sources(1);
+      EXPECT_EQ(id2, proto_source2.id());
+      EXPECT_EQ(kURL.spec(), proto_source2.url());
+    }
+  }
+}
+
+TEST_F(UkmServiceTest, RecordSessionId) {
+  ClearPrefs();
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+  EXPECT_EQ(0, GetPersistedLogCount());
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  auto id = GetWhitelistedSourceId(0);
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar"));
+
+  service.Flush();
+  EXPECT_EQ(1, GetPersistedLogCount());
+
+  auto proto_report = GetPersistedReport();
+  EXPECT_TRUE(proto_report.has_session_id());
+  EXPECT_EQ(1, proto_report.report_id());
+}
+
+TEST_F(UkmServiceTest, SourceSize) {
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  // Set a threshold of number of Sources via Feature Params.
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"MaxSources", "2"}});
+
+  ClearPrefs();
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+  EXPECT_EQ(0, GetPersistedLogCount());
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  auto id = GetWhitelistedSourceId(0);
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar1"));
+  id = GetWhitelistedSourceId(1);
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar2"));
+  id = GetWhitelistedSourceId(2);
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar3"));
+
+  service.Flush();
+  EXPECT_EQ(1, GetPersistedLogCount());
+
+  auto proto_report = GetPersistedReport();
+  // Note, 2 instead of 3 sources, since we overrode the max number of sources
+  // via Feature params.
+  EXPECT_EQ(2, proto_report.sources_size());
+}
+
+TEST_F(UkmServiceTest, PurgeMidUpload) {
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+  EXPECT_EQ(GetPersistedLogCount(), 0);
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  auto id = GetWhitelistedSourceId(0);
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar1"));
+  // Should init, generate a log, and start an upload.
+  task_runner_->RunPendingTasks();
+  EXPECT_TRUE(client_.uploader()->is_uploading());
+  // Purge should delete all logs, including the one being sent.
+  service.Purge();
+  // Upload succeeds after logs was deleted.
+  client_.uploader()->CompleteUpload(200);
+  EXPECT_EQ(GetPersistedLogCount(), 0);
+  EXPECT_FALSE(client_.uploader()->is_uploading());
+}
+
+TEST_F(UkmServiceTest, WhitelistEntryTest) {
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  // Testing two whitelisted Entries.
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", Entry1And2Whitelist()}});
+
+  ClearPrefs();
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+  EXPECT_EQ(0, GetPersistedLogCount());
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  auto id = GetWhitelistedSourceId(0);
+  recorder.UpdateSourceURL(id, GURL("https://google.com/foobar1"));
+
+  TestEvent1(id).Record(&service);
+  TestEvent2(id).Record(&service);
+  // Note that this third entry is not in the whitelist.
+  TestEvent3(id).Record(&service);
+
+  service.Flush();
+  EXPECT_EQ(1, GetPersistedLogCount());
+  Report proto_report = GetPersistedReport();
+
+  // Verify we've added one source and 2 entries.
+  EXPECT_EQ(1, proto_report.sources_size());
+  ASSERT_EQ(2, proto_report.entries_size());
+
+  const Entry& proto_entry_a = proto_report.entries(0);
+  EXPECT_EQ(id, proto_entry_a.source_id());
+  EXPECT_EQ(base::HashMetricName(TestEvent1::kEntryName),
+            proto_entry_a.event_hash());
+
+  const Entry& proto_entry_b = proto_report.entries(1);
+  EXPECT_EQ(id, proto_entry_b.source_id());
+  EXPECT_EQ(base::HashMetricName(TestEvent2::kEntryName),
+            proto_entry_b.event_hash());
+}
+
+TEST_F(UkmServiceTest, SourceURLLength) {
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+  EXPECT_EQ(0, GetPersistedLogCount());
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  auto id = GetWhitelistedSourceId(0);
+
+  // This URL is too long to be recorded fully.
+  const std::string long_string =
+      "https://example.com/" + std::string(10000, 'a');
+  recorder.UpdateSourceURL(id, GURL(long_string));
+
+  service.Flush();
+  EXPECT_EQ(1, GetPersistedLogCount());
+
+  auto proto_report = GetPersistedReport();
+  ASSERT_EQ(1, proto_report.sources_size());
+  const Source& proto_source = proto_report.sources(0);
+  EXPECT_EQ("URLTooLong", proto_source.url());
+}
+
+TEST_F(UkmServiceTest, UnreferencedNonWhitelistedSources) {
+  const GURL kURL("https://google.com/foobar");
+  for (bool restrict_to_whitelisted_source_ids : {true, false}) {
+    base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+    // Set a threshold of number of Sources via Feature Params.
+    ScopedUkmFeatureParams params(
+        base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+        {{"MaxKeptSources", "3"},
+         {"WhitelistEntries", Entry1And2Whitelist()},
+         {"RestrictToWhitelistedSourceIds",
+          restrict_to_whitelisted_source_ids ? "true" : "false"}});
+
+    ClearPrefs();
+    UkmService service(&prefs_, &client_,
+                       true /* restrict_to_whitelisted_entries */);
+    TestRecordingHelper recorder(&service);
+    EXPECT_EQ(0, GetPersistedLogCount());
+    service.Initialize();
+    task_runner_->RunUntilIdle();
+    service.EnableRecording(/*extensions=*/false);
+    service.EnableReporting();
+
+    // Record with whitelisted ID to whitelist the URL.
+    // Use a larger ID to make it last in the proto.
+    ukm::SourceId whitelisted_id = GetWhitelistedSourceId(100);
+    recorder.UpdateSourceURL(whitelisted_id, kURL);
+
+    std::vector<SourceId> ids;
+    base::TimeTicks last_time = base::TimeTicks::Now();
+    for (int i = 0; i < 6; ++i) {
+      // Wait until base::TimeTicks::Now() no longer equals |last_time|. This
+      // ensures each source has a unique timestamp to avoid flakes. Should take
+      // between 1-15ms per documented resolution of base::TimeTicks.
+      while (base::TimeTicks::Now() == last_time) {
+        base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1));
+      }
+
+      ids.push_back(GetNonWhitelistedSourceId(i));
+      recorder.UpdateSourceURL(ids.back(), kURL);
+      last_time = base::TimeTicks::Now();
+    }
+
+    // Add whitelisted entries for 0, 2 and non-whitelisted entries for 2, 3.
+    TestEvent1(ids[0]).Record(&service);
+    TestEvent2(ids[2]).Record(&service);
+    TestEvent3(ids[2]).Record(&service);
+    TestEvent3(ids[3]).Record(&service);
+
+    service.Flush();
+    EXPECT_EQ(1, GetPersistedLogCount());
+    auto proto_report = GetPersistedReport();
+
+    if (restrict_to_whitelisted_source_ids) {
+      EXPECT_EQ(1, proto_report.source_counts().observed());
+      EXPECT_EQ(1, proto_report.source_counts().navigation_sources());
+      EXPECT_EQ(0, proto_report.source_counts().unmatched_sources());
+      EXPECT_EQ(0, proto_report.source_counts().deferred_sources());
+      EXPECT_EQ(0, proto_report.source_counts().carryover_sources());
+
+      ASSERT_EQ(1, proto_report.sources_size());
+    } else {
+      EXPECT_EQ(7, proto_report.source_counts().observed());
+      EXPECT_EQ(1, proto_report.source_counts().navigation_sources());
+      EXPECT_EQ(0, proto_report.source_counts().unmatched_sources());
+      EXPECT_EQ(4, proto_report.source_counts().deferred_sources());
+      EXPECT_EQ(0, proto_report.source_counts().carryover_sources());
+
+      ASSERT_EQ(3, proto_report.sources_size());
+      EXPECT_EQ(ids[0], proto_report.sources(0).id());
+      EXPECT_EQ(kURL.spec(), proto_report.sources(0).url());
+      EXPECT_EQ(ids[2], proto_report.sources(1).id());
+      EXPECT_EQ(kURL.spec(), proto_report.sources(1).url());
+    }
+
+    // Since MaxKeptSources is 3, only Sources 5, 4, 3 should be retained.
+    // Log entries under 0, 1, 3 and 4. Log them in reverse order - which
+    // shouldn't affect source ordering in the output.
+    //  - Source 0 should not be re-transmitted since it was sent before.
+    //  - Source 1 should not be transmitted due to MaxKeptSources param.
+    //  - Sources 3 and 4 should be transmitted since they were not sent before.
+    TestEvent1(ids[4]).Record(&service);
+    TestEvent1(ids[3]).Record(&service);
+    TestEvent1(ids[1]).Record(&service);
+    TestEvent1(ids[0]).Record(&service);
+
+    service.Flush();
+    EXPECT_EQ(2, GetPersistedLogCount());
+    proto_report = GetPersistedReport();
+
+    if (restrict_to_whitelisted_source_ids) {
+      EXPECT_EQ(0, proto_report.source_counts().observed());
+      EXPECT_EQ(0, proto_report.source_counts().navigation_sources());
+      EXPECT_EQ(0, proto_report.source_counts().unmatched_sources());
+      EXPECT_EQ(0, proto_report.source_counts().deferred_sources());
+      EXPECT_EQ(0, proto_report.source_counts().carryover_sources());
+
+      ASSERT_EQ(0, proto_report.sources_size());
+    } else {
+      EXPECT_EQ(0, proto_report.source_counts().observed());
+      EXPECT_EQ(0, proto_report.source_counts().navigation_sources());
+      EXPECT_EQ(0, proto_report.source_counts().unmatched_sources());
+      EXPECT_EQ(1, proto_report.source_counts().deferred_sources());
+      EXPECT_EQ(3, proto_report.source_counts().carryover_sources());
+
+      ASSERT_EQ(2, proto_report.sources_size());
+      EXPECT_EQ(ids[3], proto_report.sources(0).id());
+      EXPECT_EQ(kURL.spec(), proto_report.sources(0).url());
+      EXPECT_EQ(ids[4], proto_report.sources(1).id());
+      EXPECT_EQ(kURL.spec(), proto_report.sources(1).url());
+    }
+  }
+}
+
+TEST_F(UkmServiceTest, NonWhitelistedUrls) {
+  const GURL kURL("https://google.com/foobar");
+  struct {
+    GURL url;
+    bool expected_kept;
+  } test_cases[] = {
+      {GURL("https://google.com/foobar"), true},
+      // For origin-only URLs, only the origin needs to be matched.
+      {GURL("https://google.com"), true},
+      {GURL("https://google.com/foobar2"), false},
+      {GURL("https://other.com"), false},
+  };
+
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", Entry1And2Whitelist()}});
+
+  for (const auto& test : test_cases) {
+    ClearPrefs();
+    UkmService service(&prefs_, &client_,
+                       true /* restrict_to_whitelisted_entries */);
+    TestRecordingHelper recorder(&service);
+
+    ASSERT_EQ(GetPersistedLogCount(), 0);
+    service.Initialize();
+    task_runner_->RunUntilIdle();
+    service.EnableRecording(/*extensions=*/false);
+    service.EnableReporting();
+
+    // Record with whitelisted ID to whitelist the URL.
+    ukm::SourceId whitelist_id = GetWhitelistedSourceId(1);
+    recorder.UpdateSourceURL(whitelist_id, kURL);
+
+    // Record non whitelisted ID with a entry.
+    ukm::SourceId nonwhitelist_id = GetNonWhitelistedSourceId(100);
+    recorder.UpdateSourceURL(nonwhitelist_id, test.url);
+    TestEvent1(nonwhitelist_id).Record(&service);
+
+    service.Flush();
+    ASSERT_EQ(1, GetPersistedLogCount());
+    auto proto_report = GetPersistedReport();
+
+    EXPECT_EQ(2, proto_report.source_counts().observed());
+    EXPECT_EQ(1, proto_report.source_counts().navigation_sources());
+    if (test.expected_kept) {
+      EXPECT_EQ(0, proto_report.source_counts().unmatched_sources());
+      ASSERT_EQ(2, proto_report.sources_size());
+      EXPECT_EQ(whitelist_id, proto_report.sources(0).id());
+      EXPECT_EQ(kURL, proto_report.sources(0).url());
+      EXPECT_EQ(nonwhitelist_id, proto_report.sources(1).id());
+      EXPECT_EQ(test.url, proto_report.sources(1).url());
+    } else {
+      EXPECT_EQ(1, proto_report.source_counts().unmatched_sources());
+      ASSERT_EQ(1, proto_report.sources_size());
+      EXPECT_EQ(whitelist_id, proto_report.sources(0).id());
+      EXPECT_EQ(kURL, proto_report.sources(0).url());
+    }
+  }
+}
+
+TEST_F(UkmServiceTest, NonWhitelistedCarryoverUrls) {
+  const GURL kURL("https://google.com/foobar");
+
+  struct {
+    // Source1 is recorded during the first rotation with no entry.
+    // An entry for it is recorded in the second rotation.
+    GURL source1_url;
+    // Should Source1 be seen in second rotation's log.
+    bool expect_source1;
+    // Source2 is recorded during the second rotation with an entry.
+    GURL source2_url;
+    // Should Source2 be seen in second rotation's log.
+    bool expect_source2;
+  } test_cases[] = {
+      // Recording the URL captures in the whitelist, which will also allow
+      // exact matches of the same URL.
+      {GURL("https://google.com/foobar"), true,
+       GURL("https://google.com/foobar"), true},
+      // Capturing a full URL shouldn't allow origin matches.
+      {GURL("https://google.com/foobar"), true, GURL("https://google.com"),
+       false},
+      // Uncaptured URLs won't get matched.
+      {GURL("https://google.com/foobar"), true, GURL("https://other.com"),
+       false},
+      // Origin should be capturable, and will remember the same origin.
+      {GURL("https://google.com"), true, GURL("https://google.com"), true},
+      // If the origin is captured, only the origin is remembered.
+      {GURL("https://google.com"), true, GURL("https://google.com/foobar"),
+       false},
+      // Uncaptured URLs won't get matched.
+      {GURL("https://google.com"), true, GURL("https://other.com"), false},
+      // If the URL isn't captured in the first round, it won't capture later.
+      {GURL("https://other.com"), false, GURL("https://google.com/foobar"),
+       false},
+      {GURL("https://other.com"), false, GURL("https://google.com"), false},
+      // Entries shouldn't whitelist themselves.
+      {GURL("https://other.com"), false, GURL("https://other.com"), false},
+  };
+
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE,
+                                {{"WhitelistEntries", Entry1And2Whitelist()}});
+
+  for (const auto& test : test_cases) {
+    ClearPrefs();
+    UkmService service(&prefs_, &client_,
+                       true /* restrict_to_whitelisted_entries */);
+    TestRecordingHelper recorder(&service);
+
+    EXPECT_EQ(GetPersistedLogCount(), 0);
+    service.Initialize();
+    task_runner_->RunUntilIdle();
+    service.EnableRecording(/*extensions=*/false);
+    service.EnableReporting();
+
+    // Record with whitelisted ID to whitelist the URL.
+    ukm::SourceId whitelist_id = GetWhitelistedSourceId(1);
+    recorder.UpdateSourceURL(whitelist_id, kURL);
+
+    // Record test Source1 without an event.
+    ukm::SourceId nonwhitelist_id1 = GetNonWhitelistedSourceId(100);
+    recorder.UpdateSourceURL(nonwhitelist_id1, test.source1_url);
+
+    service.Flush();
+    ASSERT_EQ(1, GetPersistedLogCount());
+    auto proto_report = GetPersistedReport();
+
+    EXPECT_EQ(2, proto_report.source_counts().observed());
+    EXPECT_EQ(1, proto_report.source_counts().navigation_sources());
+    EXPECT_EQ(0, proto_report.source_counts().carryover_sources());
+    if (test.expect_source1) {
+      EXPECT_EQ(0, proto_report.source_counts().unmatched_sources());
+      EXPECT_EQ(1, proto_report.source_counts().deferred_sources());
+    } else {
+      EXPECT_EQ(1, proto_report.source_counts().unmatched_sources());
+      EXPECT_EQ(0, proto_report.source_counts().deferred_sources());
+    }
+    ASSERT_EQ(1, proto_report.sources_size());
+    EXPECT_EQ(whitelist_id, proto_report.sources(0).id());
+    EXPECT_EQ(kURL, proto_report.sources(0).url());
+
+    // Record the Source2 and events for Source1 and Source2.
+    ukm::SourceId nonwhitelist_id2 = GetNonWhitelistedSourceId(101);
+    recorder.UpdateSourceURL(nonwhitelist_id2, test.source2_url);
+    TestEvent1(nonwhitelist_id1).Record(&service);
+    TestEvent1(nonwhitelist_id2).Record(&service);
+
+    service.Flush();
+    ASSERT_EQ(2, GetPersistedLogCount());
+    proto_report = GetPersistedReport();
+
+    EXPECT_EQ(1, proto_report.source_counts().observed());
+    EXPECT_EQ(0, proto_report.source_counts().navigation_sources());
+    EXPECT_EQ(0, proto_report.source_counts().deferred_sources());
+    if (!test.expect_source1) {
+      EXPECT_FALSE(test.expect_source2);
+      EXPECT_EQ(1, proto_report.source_counts().unmatched_sources());
+      EXPECT_EQ(0, proto_report.source_counts().carryover_sources());
+      ASSERT_EQ(0, proto_report.sources_size());
+    } else if (!test.expect_source2) {
+      EXPECT_EQ(1, proto_report.source_counts().unmatched_sources());
+      EXPECT_EQ(1, proto_report.source_counts().carryover_sources());
+      ASSERT_EQ(1, proto_report.sources_size());
+      EXPECT_EQ(nonwhitelist_id1, proto_report.sources(0).id());
+      EXPECT_EQ(test.source1_url, proto_report.sources(0).url());
+    } else {
+      EXPECT_EQ(0, proto_report.source_counts().unmatched_sources());
+      EXPECT_EQ(1, proto_report.source_counts().carryover_sources());
+      ASSERT_EQ(2, proto_report.sources_size());
+      EXPECT_EQ(nonwhitelist_id1, proto_report.sources(0).id());
+      EXPECT_EQ(test.source1_url, proto_report.sources(0).url());
+      EXPECT_EQ(nonwhitelist_id2, proto_report.sources(1).id());
+      EXPECT_EQ(test.source2_url, proto_report.sources(1).url());
+    }
+  }
+}
+
+TEST_F(UkmServiceTest, SupportedSchemes) {
+  struct {
+    const char* url;
+    bool expected_kept;
+  } test_cases[] = {
+      {"http://google.ca/", true},
+      {"https://google.ca/", true},
+      {"ftp://google.ca/", true},
+      {"about:blank", true},
+      {"chrome://version/", true},
+      {"app://play/abcdefghijklmnopqrstuvwxyzabcdef/", true},
+      // chrome-extension are controlled by TestIsWebstoreExtension, above.
+      {"chrome-extension://bhcnanendmgjjeghamaccjnochlnhcgj/", true},
+      {"chrome-extension://abcdefghijklmnopqrstuvwxyzabcdef/", false},
+      {"file:///tmp/", false},
+      {"abc://google.ca/", false},
+      {"www.google.ca/", false},
+  };
+
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE, {});
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+  service.SetIsWebstoreExtensionCallback(
+      base::BindRepeating(&TestIsWebstoreExtension));
+
+  EXPECT_EQ(GetPersistedLogCount(), 0);
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/true);
+  service.EnableReporting();
+
+  int64_t id_counter = 1;
+  int expected_kept_count = 0;
+  for (const auto& test : test_cases) {
+    auto source_id = GetWhitelistedSourceId(id_counter++);
+    recorder.UpdateSourceURL(source_id, GURL(test.url));
+    TestEvent1(source_id).Record(&service);
+    if (test.expected_kept)
+      ++expected_kept_count;
+  }
+
+  service.Flush();
+  EXPECT_EQ(GetPersistedLogCount(), 1);
+  Report proto_report = GetPersistedReport();
+
+  EXPECT_EQ(expected_kept_count, proto_report.sources_size());
+  for (const auto& test : test_cases) {
+    bool found = false;
+    for (int i = 0; i < proto_report.sources_size(); ++i) {
+      if (proto_report.sources(i).url() == test.url) {
+        found = true;
+        break;
+      }
+    }
+    EXPECT_EQ(test.expected_kept, found) << test.url;
+  }
+}
+
+TEST_F(UkmServiceTest, SupportedSchemesNoExtensions) {
+  struct {
+    const char* url;
+    bool expected_kept;
+  } test_cases[] = {
+      {"http://google.ca/", true},
+      {"https://google.ca/", true},
+      {"ftp://google.ca/", true},
+      {"about:blank", true},
+      {"chrome://version/", true},
+      {"app://play/abcdefghijklmnopqrstuvwxyzabcdef/", true},
+      {"chrome-extension://bhcnanendmgjjeghamaccjnochlnhcgj/", false},
+      {"chrome-extension://abcdefghijklmnopqrstuvwxyzabcdef/", false},
+      {"file:///tmp/", false},
+      {"abc://google.ca/", false},
+      {"www.google.ca/", false},
+  };
+
+  base::FieldTrialList field_trial_list(nullptr /* entropy_provider */);
+  ScopedUkmFeatureParams params(base::FeatureList::OVERRIDE_ENABLE_FEATURE, {});
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+
+  EXPECT_EQ(GetPersistedLogCount(), 0);
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  int64_t id_counter = 1;
+  int expected_kept_count = 0;
+  for (const auto& test : test_cases) {
+    auto source_id = GetWhitelistedSourceId(id_counter++);
+    recorder.UpdateSourceURL(source_id, GURL(test.url));
+    TestEvent1(source_id).Record(&service);
+    if (test.expected_kept)
+      ++expected_kept_count;
+  }
+
+  service.Flush();
+  EXPECT_EQ(GetPersistedLogCount(), 1);
+  Report proto_report = GetPersistedReport();
+
+  EXPECT_EQ(expected_kept_count, proto_report.sources_size());
+  for (const auto& test : test_cases) {
+    bool found = false;
+    for (int i = 0; i < proto_report.sources_size(); ++i) {
+      if (proto_report.sources(i).url() == test.url) {
+        found = true;
+        break;
+      }
+    }
+    EXPECT_EQ(test.expected_kept, found) << test.url;
+  }
+}
+
+TEST_F(UkmServiceTest, SanitizeUrlAuthParams) {
+  UkmService service(&prefs_, &client_,
+                     true /* restrict_to_whitelisted_entries */);
+  TestRecordingHelper recorder(&service);
+  EXPECT_EQ(0, GetPersistedLogCount());
+  service.Initialize();
+  task_runner_->RunUntilIdle();
+  service.EnableRecording(/*extensions=*/false);
+  service.EnableReporting();
+
+  auto id = GetWhitelistedSourceId(0);
+  recorder.UpdateSourceURL(id, GURL("https://username:password@example.com/"));
+
+  service.Flush();
+  EXPECT_EQ(1, GetPersistedLogCount());
+
+  auto proto_report = GetPersistedReport();
+  ASSERT_EQ(1, proto_report.sources_size());
+  const Source& proto_source = proto_report.sources(0);
+  EXPECT_EQ("https://example.com/", proto_source.url());
+}
+
+TEST_F(UkmServiceTest, SanitizeChromeUrlParams) {
+  struct {
+    const char* url;
+    const char* expected_url;
+  } test_cases[] = {
+      {"chrome://version/?foo=bar", "chrome://version/"},
+      {"about:blank?foo=bar", "about:blank"},
+      {"chrome://histograms/Variations", "chrome://histograms/Variations"},
+      {"http://google.ca/?foo=bar", "http://google.ca/?foo=bar"},
+      {"https://google.ca/?foo=bar", "https://google.ca/?foo=bar"},
+      {"ftp://google.ca/?foo=bar", "ftp://google.ca/?foo=bar"},
+      {"chrome-extension://bhcnanendmgjjeghamaccjnochlnhcgj/foo.html?a=b",
+       "chrome-extension://bhcnanendmgjjeghamaccjnochlnhcgj/"},
+  };
+
+  for (const auto& test : test_cases) {
+    ClearPrefs();
+
+    UkmService service(&prefs_, &client_,
+                       true /* restrict_to_whitelisted_entries */);
+    TestRecordingHelper recorder(&service);
+    service.SetIsWebstoreExtensionCallback(
+        base::BindRepeating(&TestIsWebstoreExtension));
+
+    EXPECT_EQ(0, GetPersistedLogCount());
+    service.Initialize();
+    task_runner_->RunUntilIdle();
+    service.EnableRecording(/*extensions=*/true);
+    service.EnableReporting();
+
+    auto id = GetWhitelistedSourceId(0);
+    recorder.UpdateSourceURL(id, GURL(test.url));
+
+    service.Flush();
+    EXPECT_EQ(1, GetPersistedLogCount());
+
+    auto proto_report = GetPersistedReport();
+    ASSERT_EQ(1, proto_report.sources_size());
+    const Source& proto_source = proto_report.sources(0);
+    EXPECT_EQ(test.expected_url, proto_source.url());
+  }
+}
+
+}  // namespace ukm
diff --git a/components/variations/BUILD.gn b/components/variations/BUILD.gn
new file mode 100644
index 0000000..1a46296
--- /dev/null
+++ b/components/variations/BUILD.gn
@@ -0,0 +1,203 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if (!use_cobalt_customizations) {
+  import("//testing/test.gni")
+}
+
+if (is_android) {
+  import("//build/config/android/rules.gni")
+}
+
+static_library("variations") {
+  sources = [
+    "active_field_trials.cc",
+    "active_field_trials.h",
+    "android/variations_associated_data_android.cc",
+    "android/variations_seed_bridge.cc",
+    "android/variations_seed_bridge.h",
+    "caching_permuted_entropy_provider.cc",
+    "caching_permuted_entropy_provider.h",
+    "child_process_field_trial_syncer.cc",
+    "child_process_field_trial_syncer.h",
+    "client_filterable_state.cc",
+    "client_filterable_state.h",
+    "entropy_provider.cc",
+    "entropy_provider.h",
+    "experiment_labels.cc",
+    "experiment_labels.h",
+    "hashing.cc",
+    "hashing.h",
+    "metrics.cc",
+    "metrics.h",
+    "platform_field_trials.h",
+    "pref_names.cc",
+    "pref_names.h",
+    "processed_study.cc",
+    "processed_study.h",
+    "proto/client_variations.proto",
+    "proto/permuted_entropy_cache.proto",
+    "proto/study.proto",
+    "proto/variations_seed.proto",
+    "seed_response.cc",
+    "seed_response.h",
+    "study_filtering.cc",
+    "study_filtering.h",
+    "synthetic_trial_registry.cc",
+    "synthetic_trial_registry.h",
+    "synthetic_trials.cc",
+    "synthetic_trials.h",
+    "synthetic_trials_active_group_id_provider.cc",
+    "synthetic_trials_active_group_id_provider.h",
+    "variations_associated_data.cc",
+    "variations_associated_data.h",
+    "variations_crash_keys.cc",
+    "variations_crash_keys.h",
+    "variations_experiment_util.cc",
+    "variations_experiment_util.h",
+    "variations_http_header_provider.cc",
+    "variations_http_header_provider.h",
+    "variations_id_collection.cc",
+    "variations_id_collection.h",
+    "variations_request_scheduler.cc",
+    "variations_request_scheduler.h",
+    "variations_seed_processor.cc",
+    "variations_seed_processor.h",
+    "variations_seed_simulator.cc",
+    "variations_seed_simulator.h",
+    "variations_seed_store.cc",
+    "variations_seed_store.h",
+    "variations_switches.cc",
+    "variations_switches.h",
+    "variations_url_constants.cc",
+    "variations_url_constants.h",
+  ]
+
+  if (is_starboard) {
+    sources -= [
+      # These bring in JNI, which we do not want to bring into Cobalt.
+      "android/variations_associated_data_android.cc",
+      "android/variations_seed_bridge.cc",
+      "android/variations_seed_bridge.h",
+      # The ".proto" files were referenced here directly, but caused the build
+      # to fail.
+      "proto/client_variations.proto",
+      "proto/permuted_entropy_cache.proto",
+      "proto/study.proto",
+      "proto/variations_seed.proto",
+    ]
+  }
+
+  if (is_android || is_ios) {
+    sources += [
+      "variations_request_scheduler_mobile.cc",
+      "variations_request_scheduler_mobile.h",
+    ]
+  }
+
+  deps = [
+    "proto",
+    "//base",
+    "//components/crash/core/common:crash_key",
+    "//components/prefs",
+    "//crypto",
+    "//third_party/protobuf:protobuf_lite",
+    "//third_party/zlib/google:compression_utils",
+  ]
+
+  if (is_android) {
+    deps += [ ":jni" ]
+  }
+}
+
+if (is_android) {
+  generate_jni("jni") {
+    sources = [
+      "android/java/src/org/chromium/components/variations/VariationsAssociatedData.java",
+      "android/java/src/org/chromium/components/variations/firstrun/VariationsSeedBridge.java",
+    ]
+    jni_package = "variations"
+  }
+
+  android_library("load_seed_result_enum_java") {
+    deps = [ "//base:base_java" ]
+    srcjar_deps = [ ":load_seed_result_enum_srcjar" ]
+  }
+
+  java_cpp_enum("load_seed_result_enum_srcjar") {
+    sources = [ "metrics.h" ]
+  }
+}
+
+static_library("test_support") {
+  testonly = true
+  sources = [
+    "variations_params_manager.cc",
+    "variations_params_manager.h",
+  ]
+
+  public_deps = [
+    ":variations",
+  ]
+
+  deps = [
+    "field_trial_config:field_trial_config",
+    "//base/test:test_support",
+  ]
+}
+
+# TODO(b/283258321): Re-enable as many tests as posible.
+if (!use_cobalt_customizations) {
+  source_set("unit_tests") {
+    testonly = true
+    sources = [
+      "active_field_trials_unittest.cc",
+      "caching_permuted_entropy_provider_unittest.cc",
+      "child_process_field_trial_syncer_unittest.cc",
+      "entropy_provider_unittest.cc",
+      "experiment_labels_unittest.cc",
+      "hashing_unittest.cc",
+      "net/variations_command_line_unittest.cc",
+      "net/variations_http_headers_unittest.cc",
+      "study_filtering_unittest.cc",
+      "synthetic_trial_registry_unittest.cc",
+      "variations_associated_data_unittest.cc",
+      "variations_crash_keys_unittest.cc",
+      "variations_http_header_provider_unittest.cc",
+      "variations_id_collection_unittest.cc",
+      "variations_request_scheduler_unittest.cc",
+      "variations_seed_processor_unittest.cc",
+      "variations_seed_simulator_unittest.cc",
+      "variations_seed_store_unittest.cc",
+    ]
+
+    if (is_android || is_ios) {
+      sources += [ "variations_request_scheduler_mobile_unittest.cc" ]
+    }
+
+    deps = [
+      ":variations",
+      "net",
+      "proto",
+      "//base/test:test_support",
+      "//components/crash/core/common:crash_key",
+      "//components/prefs:test_support",
+      "//components/variations/field_trial_config:field_trial_config",
+      "//testing/gmock",
+      "//testing/gtest",
+      "//third_party/zlib/google:compression_utils",
+    ]
+  }
+
+  # Convenience testing target
+  test("variations_unittests") {
+    sources = [ "//components/test/run_all_unittests.cc" ]
+    deps = [
+      ":unit_tests",
+      "//components/test:test_support",
+      "//components/variations/field_trial_config:unit_tests",
+      "//components/variations/service:unit_tests",
+    ]
+  }
+}
diff --git a/components/variations/DEPS b/components/variations/DEPS
new file mode 100644
index 0000000..9f3a043
--- /dev/null
+++ b/components/variations/DEPS
@@ -0,0 +1,13 @@
+# This component is shared with the Chrome OS build, so it's important to limit
+# dependencies to a minimal set.
+include_rules = [
+  "-components",
+  "+components/compression",
+  "+components/crash/core/common",
+  "+components/prefs",
+  "+components/variations",
+  "+crypto",
+  "-net",
+  "+third_party/protobuf",
+  "+third_party/zlib/google",
+]
diff --git a/components/variations/METADATA b/components/variations/METADATA
new file mode 100644
index 0000000..50dbb3a
--- /dev/null
+++ b/components/variations/METADATA
@@ -0,0 +1,21 @@
+name: "variations"
+description:
+  "Filtered subtree at components/variations."
+
+third_party {
+  url {
+    type: LOCAL_SOURCE
+    value: "https://cobalt.googlesource.com/components/variations_filtered_mirror"
+  }
+  url {
+    type: GIT
+    value: "https://github.com/chromium/chromium"
+  }
+  # Closest commit hash to m70.
+  version: "00dc8f7e427f0f9db220aebffe77b4181cb68cf3"
+  last_upgrade_date {
+    year: 2018
+    month: 08
+    day: 30
+  }
+}
diff --git a/components/variations/OWNERS b/components/variations/OWNERS
new file mode 100644
index 0000000..03a77ec
--- /dev/null
+++ b/components/variations/OWNERS
@@ -0,0 +1,3 @@
+file://base/metrics/OWNERS
+
+# COMPONENT: Internals>Metrics>Variations
diff --git a/components/variations/active_field_trials.cc b/components/variations/active_field_trials.cc
new file mode 100644
index 0000000..f18dfe1
--- /dev/null
+++ b/components/variations/active_field_trials.cc
@@ -0,0 +1,92 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/active_field_trials.h"
+
+#include <stddef.h>
+
+#include <vector>
+
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/variations/hashing.h"
+#include "components/variations/synthetic_trials_active_group_id_provider.h"
+
+namespace variations {
+
+namespace {
+
+// Populates |name_group_ids| based on |active_groups|. Field trial names are
+// suffixed with |suffix| before hashing is executed.
+void GetFieldTrialActiveGroupIdsForActiveGroups(
+    base::StringPiece suffix,
+    const base::FieldTrial::ActiveGroups& active_groups,
+    std::vector<ActiveGroupId>* name_group_ids) {
+  DCHECK(name_group_ids->empty());
+  for (auto it = active_groups.begin(); it != active_groups.end(); ++it) {
+    name_group_ids->push_back(
+        MakeActiveGroupId(it->trial_name + suffix.as_string(),
+                          it->group_name + suffix.as_string()));
+  }
+}
+
+void AppendActiveGroupIdsAsStrings(
+    const std::vector<ActiveGroupId> name_group_ids,
+    std::vector<std::string>* output) {
+  for (const auto& active_group_id : name_group_ids) {
+    output->push_back(base::StringPrintf("%x-%x", active_group_id.name,
+                                         active_group_id.group));
+  }
+}
+
+}  // namespace
+
+ActiveGroupId MakeActiveGroupId(base::StringPiece trial_name,
+                                base::StringPiece group_name) {
+  ActiveGroupId id;
+  id.name = HashName(trial_name);
+  id.group = HashName(group_name);
+  return id;
+}
+
+void GetFieldTrialActiveGroupIds(base::StringPiece suffix,
+                                 std::vector<ActiveGroupId>* name_group_ids) {
+  DCHECK(name_group_ids->empty());
+  // A note on thread safety: Since GetActiveFieldTrialGroups() is thread
+  // safe, and we operate on a separate list of that data, this function is
+  // technically thread safe as well, with respect to the FieldTrialList data.
+  base::FieldTrial::ActiveGroups active_groups;
+  base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
+  GetFieldTrialActiveGroupIdsForActiveGroups(suffix, active_groups,
+                                             name_group_ids);
+}
+
+void GetFieldTrialActiveGroupIdsAsStrings(base::StringPiece suffix,
+                                          std::vector<std::string>* output) {
+  DCHECK(output->empty());
+  std::vector<ActiveGroupId> name_group_ids;
+  GetFieldTrialActiveGroupIds(suffix, &name_group_ids);
+  AppendActiveGroupIdsAsStrings(name_group_ids, output);
+}
+
+void GetSyntheticTrialGroupIdsAsString(std::vector<std::string>* output) {
+  std::vector<ActiveGroupId> name_group_ids;
+  SyntheticTrialsActiveGroupIdProvider::GetInstance()->GetActiveGroupIds(
+      &name_group_ids);
+  AppendActiveGroupIdsAsStrings(name_group_ids, output);
+}
+
+namespace testing {
+
+void TestGetFieldTrialActiveGroupIds(
+    base::StringPiece suffix,
+    const base::FieldTrial::ActiveGroups& active_groups,
+    std::vector<ActiveGroupId>* name_group_ids) {
+  GetFieldTrialActiveGroupIdsForActiveGroups(suffix, active_groups,
+                                             name_group_ids);
+}
+
+}  // namespace testing
+
+}  // namespace variations
diff --git a/components/variations/active_field_trials.h b/components/variations/active_field_trials.h
new file mode 100644
index 0000000..ecadf08
--- /dev/null
+++ b/components/variations/active_field_trials.h
@@ -0,0 +1,79 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_ACTIVE_FIELD_TRIALS_H_
+#define COMPONENTS_VARIATIONS_ACTIVE_FIELD_TRIALS_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "base/metrics/field_trial.h"
+#include "base/strings/string_piece.h"
+
+namespace variations {
+
+// The Unique ID of a trial and its active group, where the name and group
+// identifiers are hashes of the trial and group name strings.
+struct ActiveGroupId {
+  uint32_t name;
+  uint32_t group;
+};
+
+// Returns an ActiveGroupId struct for the given trial and group names.
+ActiveGroupId MakeActiveGroupId(base::StringPiece trial_name,
+                                base::StringPiece group_name);
+
+// We need to supply a Compare class for templates since ActiveGroupId is a
+// user-defined type.
+struct ActiveGroupIdCompare {
+  bool operator() (const ActiveGroupId& lhs, const ActiveGroupId& rhs) const {
+    // The group and name fields are just SHA-1 Hashes, so we just need to treat
+    // them as IDs and do a less-than comparison. We test group first, since
+    // name is more likely to collide.
+    if (lhs.group != rhs.group)
+      return lhs.group < rhs.group;
+    return lhs.name < rhs.name;
+  }
+};
+
+// Fills the supplied vector |name_group_ids| (which must be empty when called)
+// with unique ActiveGroupIds for each Field Trial that has a chosen group.
+// Field Trials for which a group has not been chosen yet are NOT returned in
+// this list. Field trial names are suffixed with |suffix| before hashing is
+// executed.
+void GetFieldTrialActiveGroupIds(base::StringPiece suffix,
+                                 std::vector<ActiveGroupId>* name_group_ids);
+
+// Fills the supplied vector |output| (which must be empty when called) with
+// unique string representations of ActiveGroupIds for each Field Trial that
+// has a chosen group. The strings are formatted as "<TrialName>-<GroupName>",
+// with the names as hex strings. Field Trials for which a group has not been
+// chosen yet are NOT returned in this list. Field trial names are suffixed with
+// |suffix| before hashing is executed.
+void GetFieldTrialActiveGroupIdsAsStrings(base::StringPiece suffix,
+                                          std::vector<std::string>* output);
+
+// TODO(rkaplow): Support suffixing for synthetic trials.
+// Fills the supplied vector |output| (which must be empty when called) with
+// unique string representations of ActiveGroupIds for each Syntehtic Trial
+// group. The strings are formatted as "<TrialName>-<GroupName>",
+// with the names as hex strings. Synthetic Field Trials for which a group
+// which hasn't been chosen yet are NOT returned in this list.
+void GetSyntheticTrialGroupIdsAsString(std::vector<std::string>* output);
+
+// Expose some functions for testing. These functions just wrap functionality
+// that is implemented above.
+namespace testing {
+
+void TestGetFieldTrialActiveGroupIds(
+    base::StringPiece suffix,
+    const base::FieldTrial::ActiveGroups& active_groups,
+    std::vector<ActiveGroupId>* name_group_ids);
+
+}  // namespace testing
+
+}  // namespace variations
+
+#endif  // COMPONENTS_VARIATIONS_ACTIVE_FIELD_TRIALS_H_
diff --git a/components/variations/active_field_trials_unittest.cc b/components/variations/active_field_trials_unittest.cc
new file mode 100644
index 0000000..b934517
--- /dev/null
+++ b/components/variations/active_field_trials_unittest.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/active_field_trials.h"
+
+#include <stddef.h>
+
+#include "base/strings/string_piece.h"
+#include "components/variations/hashing.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace variations {
+
+TEST(ActiveFieldTrialsTest, GetFieldTrialActiveGroups) {
+  typedef std::set<ActiveGroupId, ActiveGroupIdCompare> ActiveGroupIdSet;
+  std::string trial_one("trial one");
+  std::string group_one("group one");
+  std::string trial_two("trial two");
+  std::string group_two("group two");
+
+  base::FieldTrial::ActiveGroups active_groups;
+  base::FieldTrial::ActiveGroup active_group;
+  active_group.trial_name = trial_one;
+  active_group.group_name = group_one;
+  active_groups.push_back(active_group);
+
+  active_group.trial_name = trial_two;
+  active_group.group_name = group_two;
+  active_groups.push_back(active_group);
+
+  // Create our expected groups of IDs.
+  ActiveGroupIdSet expected_groups;
+  ActiveGroupId name_group_id;
+  name_group_id.name = HashName(trial_one);
+  name_group_id.group = HashName(group_one);
+  expected_groups.insert(name_group_id);
+  name_group_id.name = HashName(trial_two);
+  name_group_id.group = HashName(group_two);
+  expected_groups.insert(name_group_id);
+
+  std::vector<ActiveGroupId> active_group_ids;
+  testing::TestGetFieldTrialActiveGroupIds(base::StringPiece(), active_groups,
+                                           &active_group_ids);
+  EXPECT_EQ(2U, active_group_ids.size());
+  for (size_t i = 0; i < active_group_ids.size(); ++i) {
+    ActiveGroupIdSet::iterator expected_group =
+        expected_groups.find(active_group_ids[i]);
+    EXPECT_FALSE(expected_group == expected_groups.end());
+    expected_groups.erase(expected_group);
+  }
+  EXPECT_EQ(0U, expected_groups.size());
+}
+
+TEST(ActiveFieldTrialsTest, GetFieldTrialActiveGroupsWithSuffix) {
+  std::string trial_one("trial one");
+  std::string group_one("group one");
+  std::string suffix("some_suffix");
+
+  base::FieldTrial::ActiveGroups active_groups;
+  base::FieldTrial::ActiveGroup active_group;
+  active_group.trial_name = trial_one;
+  active_group.group_name = group_one;
+  active_groups.push_back(active_group);
+
+  std::vector<ActiveGroupId> active_group_ids;
+  testing::TestGetFieldTrialActiveGroupIds(suffix, active_groups,
+                                           &active_group_ids);
+  EXPECT_EQ(1U, active_group_ids.size());
+
+  uint32_t expected_name = HashName("trial onesome_suffix");
+  uint32_t expected_group = HashName("group onesome_suffix");
+  EXPECT_EQ(expected_name, active_group_ids[0].name);
+  EXPECT_EQ(expected_group, active_group_ids[0].group);
+}
+
+}  // namespace variations
diff --git a/components/variations/android/BUILD.gn b/components/variations/android/BUILD.gn
new file mode 100644
index 0000000..ec12159
--- /dev/null
+++ b/components/variations/android/BUILD.gn
@@ -0,0 +1,31 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/rules.gni")
+
+if (!use_cobalt_customizations) {
+  android_library("variations_java") {
+    deps = [
+      "//base:base_java",
+      "//third_party/android_tools:android_support_core_utils_java",
+    ]
+    java_files = [
+      "java/src/org/chromium/components/variations/VariationsAssociatedData.java",
+      "java/src/org/chromium/components/variations/firstrun/VariationsSeedBridge.java",
+      "java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java",
+    ]
+  }
+
+  junit_binary("components_variations_junit_tests") {
+    java_files = [ "junit/src/org/chromium/components/variations/firstrun/VariationsSeedFetcherTest.java" ]
+    deps = [
+      ":variations_java",
+      "//base:base_java",
+      "//base:base_java_test_support",
+      "//base:base_junit_test_support",
+      "//third_party/hamcrest:hamcrest_java",
+    ]
+  }
+}
+
diff --git a/components/variations/android/DEPS b/components/variations/android/DEPS
new file mode 100644
index 0000000..c80012b
--- /dev/null
+++ b/components/variations/android/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+jni",
+]
diff --git a/components/variations/android/java/src/org/chromium/components/variations/VariationsAssociatedData.java b/components/variations/android/java/src/org/chromium/components/variations/VariationsAssociatedData.java
new file mode 100644
index 0000000..8d7264d
--- /dev/null
+++ b/components/variations/android/java/src/org/chromium/components/variations/VariationsAssociatedData.java
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.variations;
+
+import org.chromium.base.annotations.JNINamespace;
+
+import java.util.HashMap;
+
+/**
+ * Wrapper for variations.
+ */
+@JNINamespace("variations::android")
+public final class VariationsAssociatedData {
+
+    private VariationsAssociatedData() {
+    }
+
+    /**
+     * @param trialName The name of the trial to get the param value for.
+     * @param paramName The name of the param for which to get the value.
+     * @return The parameter value. Empty string if the field trial does not exist or the specified
+     *     parameter does not exist.
+     */
+    public static String getVariationParamValue(String trialName, String paramName) {
+        return nativeGetVariationParamValue(trialName, paramName);
+    }
+
+    public static HashMap<String, String> getFeedbackMap() {
+        HashMap<String, String> map = new HashMap<String, String>();
+        map.put("Chrome Variations", nativeGetFeedbackVariations());
+        return map;
+    }
+
+    private static native String nativeGetVariationParamValue(String trialName, String paramName);
+    private static native String nativeGetFeedbackVariations();
+}
diff --git a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedBridge.java b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedBridge.java
new file mode 100644
index 0000000..93500c6
--- /dev/null
+++ b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedBridge.java
@@ -0,0 +1,116 @@
+// 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.
+
+package org.chromium.components.variations.firstrun;
+
+import android.util.Base64;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.annotations.CalledByNative;
+
+/**
+ * VariationsSeedBridge is a class which is used to pass variations first run seed that was fetched
+ * before the actual Chrome first run to Chromium core. Class provides methods to store the seed
+ * in SharedPreferences and to get the seed from there. To store raw seed data class serializes
+ * byte[] to Base64 encoded string and decodes this string before passing to C++ side.
+ */
+public class VariationsSeedBridge {
+    protected static final String VARIATIONS_FIRST_RUN_SEED_BASE64 = "variations_seed_base64";
+    protected static final String VARIATIONS_FIRST_RUN_SEED_SIGNATURE = "variations_seed_signature";
+    protected static final String VARIATIONS_FIRST_RUN_SEED_COUNTRY = "variations_seed_country";
+    protected static final String VARIATIONS_FIRST_RUN_SEED_DATE = "variations_seed_date";
+    protected static final String VARIATIONS_FIRST_RUN_SEED_IS_GZIP_COMPRESSED =
+            "variations_seed_is_gzip_compressed";
+
+    // This pref is used to store information about successful seed storing on the C++ side, in
+    // order to not fetch the seed again.
+    protected static final String VARIATIONS_FIRST_RUN_SEED_NATIVE_STORED =
+            "variations_seed_native_stored";
+
+    protected static String getVariationsFirstRunSeedPref(String prefName) {
+        return ContextUtils.getAppSharedPreferences().getString(prefName, "");
+    }
+
+    /**
+     * Stores variations seed data (raw data, seed signature and country code) in SharedPreferences.
+     * CalledByNative attribute is used by unit tests code to set test data.
+     */
+    @CalledByNative
+    public static void setVariationsFirstRunSeed(byte[] rawSeed, String signature, String country,
+            String date, boolean isGzipCompressed) {
+        ContextUtils.getAppSharedPreferences()
+                .edit()
+                .putString(VARIATIONS_FIRST_RUN_SEED_BASE64,
+                        Base64.encodeToString(rawSeed, Base64.NO_WRAP))
+                .putString(VARIATIONS_FIRST_RUN_SEED_SIGNATURE, signature)
+                .putString(VARIATIONS_FIRST_RUN_SEED_COUNTRY, country)
+                .putString(VARIATIONS_FIRST_RUN_SEED_DATE, date)
+                .putBoolean(VARIATIONS_FIRST_RUN_SEED_IS_GZIP_COMPRESSED, isGzipCompressed)
+                .apply();
+    }
+
+    @CalledByNative
+    private static void clearFirstRunPrefs() {
+        ContextUtils.getAppSharedPreferences()
+                .edit()
+                .remove(VARIATIONS_FIRST_RUN_SEED_BASE64)
+                .remove(VARIATIONS_FIRST_RUN_SEED_SIGNATURE)
+                .remove(VARIATIONS_FIRST_RUN_SEED_COUNTRY)
+                .remove(VARIATIONS_FIRST_RUN_SEED_DATE)
+                .remove(VARIATIONS_FIRST_RUN_SEED_IS_GZIP_COMPRESSED)
+                .apply();
+    }
+
+    /**
+     * Returns the status of the variations first run fetch: was it successful or not.
+     */
+    public static boolean hasJavaPref() {
+        return !ContextUtils.getAppSharedPreferences()
+                        .getString(VARIATIONS_FIRST_RUN_SEED_BASE64, "")
+                        .isEmpty();
+    }
+
+    /**
+     * Returns the status of the variations seed storing on the C++ side: was it successful or not.
+     */
+    public static boolean hasNativePref() {
+        return ContextUtils.getAppSharedPreferences().getBoolean(
+                VARIATIONS_FIRST_RUN_SEED_NATIVE_STORED, false);
+    }
+
+    @CalledByNative
+    private static void markVariationsSeedAsStored() {
+        ContextUtils.getAppSharedPreferences()
+                .edit()
+                .putBoolean(VARIATIONS_FIRST_RUN_SEED_NATIVE_STORED, true)
+                .apply();
+    }
+
+    @CalledByNative
+    private static byte[] getVariationsFirstRunSeedData() {
+        return Base64.decode(
+                getVariationsFirstRunSeedPref(VARIATIONS_FIRST_RUN_SEED_BASE64), Base64.NO_WRAP);
+    }
+
+    @CalledByNative
+    private static String getVariationsFirstRunSeedSignature() {
+        return getVariationsFirstRunSeedPref(VARIATIONS_FIRST_RUN_SEED_SIGNATURE);
+    }
+
+    @CalledByNative
+    private static String getVariationsFirstRunSeedCountry() {
+        return getVariationsFirstRunSeedPref(VARIATIONS_FIRST_RUN_SEED_COUNTRY);
+    }
+
+    @CalledByNative
+    private static String getVariationsFirstRunSeedDate() {
+        return getVariationsFirstRunSeedPref(VARIATIONS_FIRST_RUN_SEED_DATE);
+    }
+
+    @CalledByNative
+    private static boolean getVariationsFirstRunSeedIsGzipCompressed() {
+        return ContextUtils.getAppSharedPreferences().getBoolean(
+                VARIATIONS_FIRST_RUN_SEED_IS_GZIP_COMPRESSED, false);
+    }
+}
diff --git a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
new file mode 100644
index 0000000..0808161
--- /dev/null
+++ b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
@@ -0,0 +1,303 @@
+// 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.
+
+package org.chromium.components.variations.firstrun;
+
+import android.content.SharedPreferences;
+import android.os.SystemClock;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.metrics.CachedMetrics.SparseHistogramSample;
+import org.chromium.base.metrics.CachedMetrics.TimesHistogramSample;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Fetches the variations seed before the actual first run of Chrome.
+ */
+public class VariationsSeedFetcher {
+    private static final String TAG = "VariationsSeedFetch";
+
+    public enum VariationsPlatform { ANDROID, ANDROID_WEBVIEW }
+
+    private static final String VARIATIONS_SERVER_URL =
+            "https://clientservices.googleapis.com/chrome-variations/seed?osname=";
+
+    private static final int BUFFER_SIZE = 4096;
+    private static final int READ_TIMEOUT = 3000; // time in ms
+    private static final int REQUEST_TIMEOUT = 1000; // time in ms
+
+    // Values for the "Variations.FirstRun.SeedFetchResult" sparse histogram, which also logs
+    // HTTP result codes. These are negative so that they don't conflict with the HTTP codes.
+    // These values should not be renumbered or re-used since they are logged to UMA.
+    private static final int SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION = -3;
+    private static final int SEED_FETCH_RESULT_TIMEOUT = -2;
+    private static final int SEED_FETCH_RESULT_IOEXCEPTION = -1;
+
+    @VisibleForTesting
+    static final String VARIATIONS_INITIALIZED_PREF = "variations_initialized";
+
+    // Synchronization lock to make singleton thread-safe.
+    private static final Object sLock = new Object();
+
+    private static VariationsSeedFetcher sInstance;
+
+    @VisibleForTesting
+    public VariationsSeedFetcher() {}
+
+    public static VariationsSeedFetcher get() {
+        // TODO(aberent) Check not running on UI thread. Doing so however makes Robolectric testing
+        // of dependent classes difficult.
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new VariationsSeedFetcher();
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Override the VariationsSeedFetcher, typically with a mock, for testing classes that depend on
+     * this one.
+     * @param fetcher the mock.
+     */
+    @VisibleForTesting
+    public static void setVariationsSeedFetcherForTesting(VariationsSeedFetcher fetcher) {
+        sInstance = fetcher;
+    }
+
+    @VisibleForTesting
+    protected HttpURLConnection getServerConnection(
+            VariationsPlatform platform, String restrictMode, String milestone, String channel)
+            throws MalformedURLException, IOException {
+        String urlString = getConnectionString(platform, restrictMode, milestone, channel);
+        URL url = new URL(urlString);
+        return (HttpURLConnection) url.openConnection();
+    }
+
+    @VisibleForTesting
+    protected String getConnectionString(
+            VariationsPlatform platform, String restrictMode, String milestone, String channel) {
+        String urlString = VARIATIONS_SERVER_URL;
+        switch (platform) {
+            case ANDROID:
+                urlString += "android";
+                break;
+            case ANDROID_WEBVIEW:
+                urlString += "android_webview";
+                break;
+            default:
+                assert false;
+        }
+        if (restrictMode != null && !restrictMode.isEmpty()) {
+            urlString += "&restrict=" + restrictMode;
+        }
+        if (milestone != null && !milestone.isEmpty()) {
+            urlString += "&milestone=" + milestone;
+        }
+        if (channel != null && !channel.isEmpty()) {
+            urlString += "&channel=" + channel;
+        }
+
+        return urlString;
+    }
+
+    /**
+     * Object holding the seed data and related fields retrieved from HTTP headers.
+     */
+    public static class SeedInfo {
+        // If you add fields, see VariationsTestUtils.
+        public String signature;
+        public String country;
+        public String date;
+        public boolean isGzipCompressed;
+        public byte[] seedData;
+
+        public Date parseDate() throws ParseException {
+            // The date field comes from the HTTP "Date" header, which has this format. (See RFC
+            // 2616, sections 3.3.1 and 14.18.) SimpleDateFormat is weirdly not thread-safe, so
+            // instantiate a new one for each call.
+            return new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US).parse(date);
+        }
+
+        @Override
+        public String toString() {
+            return "SeedInfo{signature=\"" + signature + "\" country=\"" + country
+                    + "\" date=\"" + date + " isGzipCompressed=" + isGzipCompressed
+                    + " seedData=" + Arrays.toString(seedData);
+        }
+    }
+
+
+    /**
+     * Fetch the first run variations seed.
+     * @param restrictMode The restrict mode parameter to pass to the server via a URL param.
+     * @param milestone The milestone parameter to pass to the server via a URL param.
+     * @param channel The channel parameter to pass to the server via a URL param.
+     */
+    public void fetchSeed(String restrictMode, String milestone, String channel) {
+        assert !ThreadUtils.runningOnUiThread();
+        // Prevent multiple simultaneous fetches
+        synchronized (sLock) {
+            SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
+            // Early return if an attempt has already been made to fetch the seed, even if it
+            // failed. Only attempt to get the initial Java seed once, since a failure probably
+            // indicates a network problem that is unlikely to be resolved by a second attempt.
+            // Note that VariationsSeedBridge.hasNativePref() is a pure Java function, reading an
+            // Android preference that is set when the seed is fetched by the native code.
+            if (prefs.getBoolean(VARIATIONS_INITIALIZED_PREF, false)
+                    || VariationsSeedBridge.hasNativePref()) {
+                return;
+            }
+
+            try {
+                SeedInfo info = downloadContent(
+                        VariationsPlatform.ANDROID, restrictMode, milestone, channel);
+                VariationsSeedBridge.setVariationsFirstRunSeed(info.seedData, info.signature,
+                        info.country, info.date, info.isGzipCompressed);
+            } catch (IOException e) {
+                Log.e(TAG, "IOException when fetching variations seed.", e);
+                // Exceptions are handled and logged in the downloadContent method, so we don't
+                // need any exception handling here. The only reason we need a catch-statement here
+                // is because those exceptions are re-thrown from downloadContent to skip the
+                // normal logic flow within that method.
+            }
+            // VARIATIONS_INITIALIZED_PREF should still be set to true when exceptions occur
+            prefs.edit().putBoolean(VARIATIONS_INITIALIZED_PREF, true).apply();
+        }
+    }
+
+    private void recordFetchResultOrCode(int resultOrCode) {
+        SparseHistogramSample histogram =
+                new SparseHistogramSample("Variations.FirstRun.SeedFetchResult");
+        histogram.record(resultOrCode);
+    }
+
+    private void recordSeedFetchTime(long timeDeltaMillis) {
+        Log.i(TAG, "Fetched first run seed in " + timeDeltaMillis + " ms");
+        TimesHistogramSample histogram = new TimesHistogramSample(
+                "Variations.FirstRun.SeedFetchTime", TimeUnit.MILLISECONDS);
+        histogram.record(timeDeltaMillis);
+    }
+
+    private void recordSeedConnectTime(long timeDeltaMillis) {
+        TimesHistogramSample histogram = new TimesHistogramSample(
+                "Variations.FirstRun.SeedConnectTime", TimeUnit.MILLISECONDS);
+        histogram.record(timeDeltaMillis);
+    }
+
+    /**
+     * Download the variations seed data with platform and retrictMode.
+     * @param platform the platform parameter to let server only return experiments which can be
+     * run on that platform.
+     * @param restrictMode the restrict mode parameter to pass to the server via a URL param.
+     * @param milestone the milestone parameter to pass to the server via a URL param.
+     * @param channel the channel parameter to pass to the server via a URL param.
+     * @return the object holds the seed data and its related header fields.
+     * @throws SocketTimeoutException when fetching seed connection times out.
+     * @throws UnknownHostException when fetching seed connection has an unknown host.
+     * @throws IOException when response code is not HTTP_OK or transmission fails on the open
+     * connection.
+     */
+    public SeedInfo downloadContent(
+            VariationsPlatform platform, String restrictMode, String milestone, String channel)
+            throws SocketTimeoutException, UnknownHostException, IOException {
+        HttpURLConnection connection = null;
+        try {
+            long startTimeMillis = SystemClock.elapsedRealtime();
+            connection = getServerConnection(platform, restrictMode, milestone, channel);
+            connection.setReadTimeout(READ_TIMEOUT);
+            connection.setConnectTimeout(REQUEST_TIMEOUT);
+            connection.setDoInput(true);
+            connection.setRequestProperty("A-IM", "gzip");
+            connection.connect();
+            int responseCode = connection.getResponseCode();
+            recordFetchResultOrCode(responseCode);
+            if (responseCode != HttpURLConnection.HTTP_OK) {
+                String errorMsg = "Non-OK response code = " + responseCode;
+                Log.w(TAG, errorMsg);
+                throw new IOException(errorMsg);
+            }
+
+            recordSeedConnectTime(SystemClock.elapsedRealtime() - startTimeMillis);
+
+            SeedInfo info = new SeedInfo();
+            info.seedData = getRawSeed(connection);
+            info.signature = getHeaderFieldOrEmpty(connection, "X-Seed-Signature");
+            info.country = getHeaderFieldOrEmpty(connection, "X-Country");
+            info.date = getHeaderFieldOrEmpty(connection, "Date");
+            info.isGzipCompressed = getHeaderFieldOrEmpty(connection, "IM").equals("gzip");
+            recordSeedFetchTime(SystemClock.elapsedRealtime() - startTimeMillis);
+            return info;
+        } catch (SocketTimeoutException e) {
+            recordFetchResultOrCode(SEED_FETCH_RESULT_TIMEOUT);
+            Log.w(TAG, "SocketTimeoutException timeout when fetching variations seed.", e);
+            throw e;
+        } catch (UnknownHostException e) {
+            recordFetchResultOrCode(SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION);
+            Log.w(TAG, "UnknownHostException unknown host when fetching variations seed.", e);
+            throw e;
+        } catch (IOException e) {
+            recordFetchResultOrCode(SEED_FETCH_RESULT_IOEXCEPTION);
+            Log.w(TAG, "IOException when fetching variations seed.", e);
+            throw e;
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    /**
+     * Convert a input stream into a byte array.
+     * @param inputStream the input stream
+     * @return the byte array which holds the data from the input stream
+     * @throws IOException if I/O error occurs when reading data from the input stream
+     */
+    public static byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException {
+        ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
+        byte[] buffer = new byte[BUFFER_SIZE];
+        int charactersReadCount = 0;
+        while ((charactersReadCount = inputStream.read(buffer)) != -1) {
+            byteBuffer.write(buffer, 0, charactersReadCount);
+        }
+        return byteBuffer.toByteArray();
+    }
+
+    private String getHeaderFieldOrEmpty(HttpURLConnection connection, String name) {
+        String headerField = connection.getHeaderField(name);
+        if (headerField == null) {
+            return "";
+        }
+        return headerField.trim();
+    }
+
+    private byte[] getRawSeed(HttpURLConnection connection) throws IOException {
+        InputStream inputStream = null;
+        try {
+            inputStream = connection.getInputStream();
+            return convertInputStreamToByteArray(inputStream);
+        } finally {
+            if (inputStream != null) {
+                inputStream.close();
+            }
+        }
+    }
+}
diff --git a/components/variations/android/junit/src/org/chromium/components/variations/firstrun/VariationsSeedFetcherTest.java b/components/variations/android/junit/src/org/chromium/components/variations/firstrun/VariationsSeedFetcherTest.java
new file mode 100644
index 0000000..4f9597b
--- /dev/null
+++ b/components/variations/android/junit/src/org/chromium/components/variations/firstrun/VariationsSeedFetcherTest.java
@@ -0,0 +1,183 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.variations.firstrun;