Import Cobalt 25.master.0.1033342
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..4817f40
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,20 @@
+# Code owners are automatically requested for review when someone opens a pull
+# request that modifies code that they own. Code owners are not automatically
+# requested to review draft pull requests.
+#
+# For the full docs on CODEOWNERS, see:
+# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
+
+# Metrics owners
+/cobalt/browser/metrics/ @joeltine
+/components/metrics/ @joeltine
+/tools/metrics/ @joeltine
+
+# Temporary monitoring for merges to Chromium upstreams
+/base/ @andrewsavage1
+/build/ @andrewsavage1
+/crypto/ @andrewsavage1
+/net/ @andrewsavage1
+/third_party/abseil-cpp/ @andrewsavage1
+/third_party/modp_b64/ @andrewsavage1
+/url/ @andrewsavage1
diff --git a/.github/actions/api_leak_detector/action.yaml b/.github/actions/api_leak_detector/action.yaml
index bc2d532..b40b062 100644
--- a/.github/actions/api_leak_detector/action.yaml
+++ b/.github/actions/api_leak_detector/action.yaml
@@ -18,4 +18,9 @@
         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
+        if [ -z "${{matrix.sb_api_version}}"]; then
+          SB_API_VERSION_FLAG=""
+        else
+          SB_API_VERSION_FLAG="--sb_api_version=${{matrix.sb_api_version}}"
+        fi
+        python3 starboard/tools/api_leak_detector/api_leak_detector.py -p ${{ matrix.target_platform }} -c ${{matrix.config}} --submit-check $MANIFEST_FLAG ${SB_API_VERSION_FLAG}
diff --git a/.github/actions/docker/action.yaml b/.github/actions/docker/action.yaml
index 0c27851..3b53e8c 100644
--- a/.github/actions/docker/action.yaml
+++ b/.github/actions/docker/action.yaml
@@ -79,7 +79,7 @@
           echo "need_to_build=false" >> $GITHUB_ENV
         fi
       shell: bash
-    - name: Build containers with docker-compose
+    - name: Build containers with Docker Compose
       id: build-image
       if: env.need_to_build == 'true'
       env:
diff --git a/.github/actions/docker_win/action.yaml b/.github/actions/docker_win/action.yaml
index a082e1e..6d16aee 100644
--- a/.github/actions/docker_win/action.yaml
+++ b/.github/actions/docker_win/action.yaml
@@ -56,7 +56,7 @@
           echo "need_to_build=false" >> $GITHUB_ENV
         fi
       shell: bash
-    - name: Build containers with docker-compose
+    - name: Build containers with Docker Compose
       if: env.need_to_build == 'true'
       env:
         DOCKER_CPUS: 2
diff --git a/.github/actions/gn/action.yaml b/.github/actions/gn/action.yaml
index 7afac03..d5b5b58 100644
--- a/.github/actions/gn/action.yaml
+++ b/.github/actions/gn/action.yaml
@@ -31,6 +31,11 @@
             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}}\""
+        if [ -z "${{matrix.sb_api_version}}"]; then
+          SB_API_VERSION_FLAG=""
+        else
+          SB_API_VERSION_FLAG="sb_api_version=${{matrix.sb_api_version}}"
+        fi
+        gn gen $GITHUB_WORKSPACE/out/${BUILD_PLATFORM}_${{matrix.config}} --args="target_platform=\"${BUILD_PLATFORM}\" ${SB_API_VERSION_FLAG} ${{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_host_test/action.yaml b/.github/actions/on_host_test/action.yaml
index 7d9efa0..20469f7 100644
--- a/.github/actions/on_host_test/action.yaml
+++ b/.github/actions/on_host_test/action.yaml
@@ -84,8 +84,6 @@
           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
-        elif [[ "${{matrix.shard}}" == 'evergreen-as-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 evergreen
         elif [[ "${{matrix.shard}}" == 'coverage' ]]; then
           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}} -r ${loader_args} --xml_output_dir=${XML_FILES_DIR} --coverage_dir=${COVERAGE_DIR} --coverage_report
         else
diff --git a/.github/config/android-arm.json b/.github/config/android-arm.json
index 50e30ce..9ca34bb 100644
--- a/.github/config/android-arm.json
+++ b/.github/config/android-arm.json
@@ -8,8 +8,7 @@
       "2",
       "3",
       "black_box_test"
-    ],
-    "test_attempts": 2
+    ]
   },
   "platforms": [
     "android-arm"
diff --git a/.github/config/android-arm64.json b/.github/config/android-arm64.json
index fe96371..92f2e4a 100644
--- a/.github/config/android-arm64.json
+++ b/.github/config/android-arm64.json
@@ -8,8 +8,7 @@
       "2",
       "3",
       "black_box_test"
-    ],
-    "test_attempts": 2
+    ]
   },
   "platforms": [
     "android-arm64"
diff --git a/.github/config/android-x86.json b/.github/config/android-x86.json
index 7e371e9..8a7b08b 100644
--- a/.github/config/android-x86.json
+++ b/.github/config/android-x86.json
@@ -8,8 +8,7 @@
       "2",
       "3",
       "black_box_test"
-    ],
-    "test_attempts": 2
+    ]
   },
   "platforms": [
     "android-x86"
diff --git a/.github/config/evergreen-arm-hardfp.json b/.github/config/evergreen-arm-hardfp.json
index 8067c72..1744d28 100644
--- a/.github/config/evergreen-arm-hardfp.json
+++ b/.github/config/evergreen-arm-hardfp.json
@@ -9,8 +9,7 @@
       "1",
       "2",
       "3"
-    ],
-    "test_attempts": 2
+    ]
   },
   "platforms": [
     "evergreen-arm-hardfp",
@@ -35,7 +34,7 @@
       "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",
+      "sb_api_version": "15",
       "dimension": "release_version=regex:10.*"
     },
     {
@@ -45,7 +44,7 @@
       "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",
+      "sb_api_version": "14",
       "dimension": "release_version=regex:10.*"
     },
     {
@@ -55,7 +54,7 @@
       "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",
+      "sb_api_version": "13",
       "dimension": "release_version=regex:10.*"
     }
   ]
diff --git a/.github/config/evergreen-arm-softfp-no-loader.json b/.github/config/evergreen-arm-softfp-no-loader.json
new file mode 100644
index 0000000..a3ecb52
--- /dev/null
+++ b/.github/config/evergreen-arm-softfp-no-loader.json
@@ -0,0 +1,16 @@
+{
+  "docker_service": "build-evergreen",
+  "platforms": [
+    "evergreen-arm-softfp-sbversion-13"
+  ],
+  "includes": [
+    {
+      "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":"13"
+    }
+  ]
+}
diff --git a/.github/config/evergreen-arm-softfp.json b/.github/config/evergreen-arm-softfp.json
index a90304c..6f72d53 100644
--- a/.github/config/evergreen-arm-softfp.json
+++ b/.github/config/evergreen-arm-softfp.json
@@ -1,10 +1,20 @@
 {
-  "docker_service": "build-evergreen",
+  "docker_service": "build-android-evergreen",
+  "bootloader": "android-arm",
+  "on_device_test": {
+    "enabled": false,
+    "tests": [
+      "evergreen_test",
+      "0",
+      "1",
+      "2",
+      "3"
+    ]
+  },
   "platforms": [
     "evergreen-arm-softfp",
     "evergreen-arm-softfp-sbversion-15",
-		"evergreen-arm-softfp-sbversion-14",
-    "evergreen-arm-softfp-sbversion-13"
+		"evergreen-arm-softfp-sbversion-14"
   ],
   "includes": [
     {
@@ -12,7 +22,8 @@
       "platform":"evergreen-arm-softfp",
       "target_platform":"evergreen-arm-softfp",
       "target_cpu":"target_cpu=\\\"arm\\\"",
-      "extra_gn_arguments":"use_asan=false"
+      "extra_gn_arguments":"use_asan=false",
+      "bootloader_extra_gn_arguments": "target_os=\\\"android\\\" sb_is_evergreen_compatible=true"
     },
     {
       "name":"sbversion-15",
@@ -20,7 +31,8 @@
       "target_platform":"evergreen-arm-softfp",
       "target_cpu":"target_cpu=\\\"arm\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "sb_api_version":"sb_api_version=15"
+      "sb_api_version":"15",
+      "bootloader_extra_gn_arguments": "target_os=\\\"android\\\" sb_is_evergreen_compatible=true"
     },
     {
       "name":"sbversion-14",
@@ -28,15 +40,8 @@
       "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"
+      "sb_api_version":"14",
+      "bootloader_extra_gn_arguments": "target_os=\\\"android\\\" sb_is_evergreen_compatible=true"
     }
   ]
 }
diff --git a/.github/config/evergreen-arm64.json b/.github/config/evergreen-arm64.json
index 607c332..416f70e 100644
--- a/.github/config/evergreen-arm64.json
+++ b/.github/config/evergreen-arm64.json
@@ -20,7 +20,7 @@
       "target_platform":"evergreen-arm64",
       "target_cpu":"target_cpu=\\\"arm64\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "sb_api_version":"sb_api_version=15"
+      "sb_api_version":"15"
     },
     {
       "name":"sbversion-14",
@@ -28,7 +28,7 @@
       "target_platform":"evergreen-arm64",
       "target_cpu":"target_cpu=\\\"arm64\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "sb_api_version":"sb_api_version=14"
+      "sb_api_version":"14"
     },
     {
       "name":"sbversion-13",
@@ -36,7 +36,7 @@
       "target_platform":"evergreen-arm64",
       "target_cpu":"target_cpu=\\\"arm64\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "sb_api_version":"sb_api_version=13"
+      "sb_api_version":"13"
     }
   ]
 }
diff --git a/.github/config/evergreen-x64.json b/.github/config/evergreen-x64.json
index cd33d24..25ae453 100644
--- a/.github/config/evergreen-x64.json
+++ b/.github/config/evergreen-x64.json
@@ -2,16 +2,7 @@
   "docker_service": "build-linux-evergreen",
   "on_host_test": true,
   "bootloader": "linux-x64x11",
-  "on_host_test_shards": [
-    "0",
-    "1",
-    "2",
-    "3",
-    "blackbox",
-    "wpt",
-    "evergreen",
-    "evergreen-as-blackbox"
-  ],
+  "on_host_test_shards": ["0", "1", "2", "3", "blackbox", "wpt", "evergreen"],
   "platforms": [
     "evergreen-x64",
     "evergreen-x64-sbversion-15",
@@ -32,7 +23,7 @@
       "target_platform":"evergreen-x64",
       "target_cpu":"target_cpu=\\\"x64\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "sb_api_version":"sb_api_version=15"
+      "sb_api_version":"15"
     },
     {
       "name":"sbversion-14",
@@ -40,7 +31,7 @@
       "target_platform":"evergreen-x64",
       "target_cpu":"target_cpu=\\\"x64\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "sb_api_version":"sb_api_version=14"
+      "sb_api_version":"14"
     },
     {
       "name":"sbversion-13",
@@ -48,7 +39,7 @@
       "target_platform":"evergreen-x64",
       "target_cpu":"target_cpu=\\\"x64\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "sb_api_version":"sb_api_version=13"
+      "sb_api_version":"13"
     }
   ]
 }
diff --git a/.github/config/evergreen-x86.json b/.github/config/evergreen-x86.json
index be80dde..0a97cfc 100644
--- a/.github/config/evergreen-x86.json
+++ b/.github/config/evergreen-x86.json
@@ -20,7 +20,7 @@
       "target_platform":"evergreen-x86",
       "target_cpu":"target_cpu=\\\"x86\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "sb_api_version":"sb_api_version=15"
+      "sb_api_version":"15"
     },
     {
       "name":"sbversion-14",
@@ -28,7 +28,7 @@
       "target_platform":"evergreen-x86",
       "target_cpu":"target_cpu=\\\"x86\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "sb_api_version":"sb_api_version=14"
+      "sb_api_version":"14"
     },
     {
       "name":"sbversion-13",
@@ -36,7 +36,7 @@
       "target_platform":"evergreen-x86",
       "target_cpu":"target_cpu=\\\"x86\\\"",
       "extra_gn_arguments":"use_asan=false",
-      "sb_api_version":"sb_api_version=13"
+      "sb_api_version":"13"
     }
   ]
 }
diff --git a/.github/config/linux-clang-3-9.json b/.github/config/linux-clang-3-9.json
index 59bcc19..f994506 100644
--- a/.github/config/linux-clang-3-9.json
+++ b/.github/config/linux-clang-3-9.json
@@ -10,7 +10,7 @@
       "name":"clang-3-9",
       "platform":"linux-x64x11-clang-3-9",
       "target_platform":"linux-x64x11-clang-3-9",
-      "extra_gn_arguments":"using_old_compiler=true"
+      "extra_gn_arguments":"using_old_compiler=true build_with_separate_cobalt_toolchain=true"
     }
   ]
 }
diff --git a/.github/config/linux-gcc-6-3.json b/.github/config/linux-gcc-6-3.json
index 9b65750..4223ca8 100644
--- a/.github/config/linux-gcc-6-3.json
+++ b/.github/config/linux-gcc-6-3.json
@@ -1,5 +1,7 @@
 {
   "docker_service": "build-linux-gcc",
+  "on_host_test": true,
+  "on_host_test_shards": ["0", "1", "2", "3", "blackbox", "wpt"],
   "platforms": [
     "linux-x64x11-gcc-6-3"
   ],
@@ -8,7 +10,7 @@
       "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"
+      "extra_gn_arguments":"using_old_compiler=true build_with_separate_cobalt_toolchain=true"
     }
   ]
 }
diff --git a/.github/config/linux-modular.json b/.github/config/linux-modular.json
index ee3f9b1..6773b48 100644
--- a/.github/config/linux-modular.json
+++ b/.github/config/linux-modular.json
@@ -1,7 +1,7 @@
 {
   "docker_service": "build-linux",
   "on_host_test": true,
-  "on_host_test_shards": ["0", "1", "2", "3", "wpt"],
+  "on_host_test_shards": ["0", "1", "2", "3", "blackbox", "wpt"],
   "platforms": [
     "linux-x64x11-modular"
   ],
@@ -10,7 +10,7 @@
       "name":"modular",
       "platform":"linux-x64x11-modular",
       "target_platform":"linux-x64x11",
-      "extra_gn_arguments":"build_with_separate_cobalt_toolchain=true"
+      "extra_gn_arguments":"build_with_separate_cobalt_toolchain=true use_contrib_cast=true"
     }
   ]
 }
diff --git a/.github/config/linux.json b/.github/config/linux.json
index 79ceb0b..6db4064 100644
--- a/.github/config/linux.json
+++ b/.github/config/linux.json
@@ -30,19 +30,19 @@
       "name":"sbversion-13",
       "platform":"linux-x64x11-sbversion-13",
       "target_platform":"linux-x64x11",
-      "sb_api_version":"sb_api_version=13"
+      "sb_api_version":"13"
     },
     {
       "name":"sbversion-14",
       "platform":"linux-x64x11-sbversion-14",
       "target_platform":"linux-x64x11",
-      "sb_api_version":"sb_api_version=14"
+      "sb_api_version":"14"
     },
     {
       "name":"sbversion-15",
       "platform":"linux-x64x11-sbversion-15",
       "target_platform":"linux-x64x11",
-      "sb_api_version":"sb_api_version=15"
+      "sb_api_version":"15"
     }
   ]
 }
diff --git a/.github/config/raspi-2-skia.json b/.github/config/raspi-2-skia.json
index f99fb73..e83ea1f 100644
--- a/.github/config/raspi-2-skia.json
+++ b/.github/config/raspi-2-skia.json
@@ -9,7 +9,7 @@
       "platform":"raspi-2-skia",
       "target_platform":"raspi-2-skia",
       "target_cpu":"target_cpu=\\\"arm\\\"",
-      "extra_gn_arguments": "is_clang=false"
+      "extra_gn_arguments": "build_with_separate_cobalt_toolchain=true use_asan=false"
     }
   ]
 }
diff --git a/.github/config/raspi-2.json b/.github/config/raspi-2.json
index ab2a1e6..6690bf2 100644
--- a/.github/config/raspi-2.json
+++ b/.github/config/raspi-2.json
@@ -9,13 +9,10 @@
       "3",
       "4",
       "5"
-    ],
-    "test_attempts": 2
+    ]
   },
   "platforms": [
     "raspi-2",
-    "raspi-2-sbversion-13",
-    "raspi-2-sbversion-14",
     "raspi-2-sbversion-15"
   ],
   "includes": [
@@ -24,25 +21,7 @@
       "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",
+      "extra_gn_arguments": "build_with_separate_cobalt_toolchain=true use_asan=false",
       "dimension": "release_version=regex:10.*"
     },
     {
@@ -50,8 +29,8 @@
       "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",
+      "extra_gn_arguments": "build_with_separate_cobalt_toolchain=true use_asan=false",
+      "sb_api_version": "15",
       "dimension": "release_version=regex:10.*"
     }
   ]
diff --git a/.github/workflows/auto_assign.yaml b/.github/workflows/auto_assign.yaml
deleted file mode 100644
index 3a47169..0000000
--- a/.github/workflows/auto_assign.yaml
+++ /dev/null
@@ -1,35 +0,0 @@
-name: PR Reviewer Auto Assignment
-
-on:
-  pull_request_target:
-    types:
-      - opened
-      - reopened
-
-concurrency:
-  group: '${{ github.workflow }}-${{ github.event_name }} @ ${{ github.event.pull_request.number || github.sha }}'
-  cancel-in-progress: true
-
-jobs:
-  assign-reviewer:
-    runs-on: ubuntu-latest
-    permissions:
-      pull-requests: write
-    steps:
-    - name: Check if PR author is outside collaborator and assign reviewer
-      env:
-        PR_AUTHOR_LOGIN: ${{ github.event.pull_request.user.login }}
-        REPO_NAME: ${{ github.event.repository.full_name }}
-        PR_NUMBER: ${{ github.event.number }}
-      run: |
-        PERMISSION_LEVEL=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-          "https://api.github.com/repos/$REPO_NAME/collaborators/$PR_AUTHOR_LOGIN/permission" | jq -r .role_name)
-
-        if [ "$PERMISSION_LEVEL" == "none" ] || [ "$PERMISSION_LEVEL" == "read" ]; then
-          echo "PR author is an outside collaborator. Adding label..."
-
-          curl -s -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-            -H "Accept: application/vnd.github.v3+json" \
-            -d '["outside collaborator"]' \
-            "https://api.github.com/repos/$REPO_NAME/issues/$PR_NUMBER/labels"
-        fi
diff --git a/.github/workflows/evergreen.yaml b/.github/workflows/evergreen.yaml
index 22e56e4..bc9a056 100644
--- a/.github/workflows/evergreen.yaml
+++ b/.github/workflows/evergreen.yaml
@@ -49,6 +49,15 @@
       platform: evergreen-arm-softfp
       nightly: ${{ github.event.inputs.nightly }}
       run_api_leak_detector: true
+  evergreen-arm-softfp-no-loader:
+    uses: ./.github/workflows/main.yaml
+    permissions:
+      packages: write
+      pull-requests: write
+    with:
+      platform: evergreen-arm-softfp-no-loader
+      nightly: ${{ github.event.inputs.nightly }}
+      run_api_leak_detector: true
   evergreen-arm64:
     uses: ./.github/workflows/main.yaml
     permissions:
diff --git a/.github/workflows/gradle.yaml b/.github/workflows/gradle.yaml
index 88ce9fd..7dec25a 100644
--- a/.github/workflows/gradle.yaml
+++ b/.github/workflows/gradle.yaml
@@ -19,6 +19,7 @@
 
     steps:
       - uses: kaidokert/checkout@v3.5.999
+        timeout-minutes: 30
       - name: Set up JDK 11
         uses: actions/setup-java@v3
         with:
diff --git a/.github/workflows/label-cherry-pick.yaml b/.github/workflows/label-cherry-pick.yaml
index 1a80289..f58d0a4 100644
--- a/.github/workflows/label-cherry-pick.yaml
+++ b/.github/workflows/label-cherry-pick.yaml
@@ -9,6 +9,9 @@
 jobs:
   prepare_branch_list:
     runs-on: ubuntu-latest
+    if: |
+      github.event.pull_request.merged == true &&
+      github.event.pull_request.merge_commit_sha != null
     outputs:
       target_branch: ${{ steps.set-branches.outputs.target_branch }}
     steps:
@@ -21,8 +24,10 @@
         BASE_REF: ${{ github.base_ref }}
       run: |
         if [[ $EVENT_ACTION == 'closed' ]]; then
+          # Get a list of the labels from the PR.
           labels=$(echo "$PR_LABELS" | jq -r '.[].name')
         else
+          # Or get the label that was added on the merged PR.
           labels=$LABEL_NAME
         fi
 
@@ -32,23 +37,19 @@
           if [[ $branch == $BASE_REF ]]; then
             continue
           fi
-
-          for label in $labels; do
-            if [[ $label == "cp-$branch" ]]; then
-              filtered_branches+=("$branch")
-            fi
-          done
+          if [[ ${labels[@]} =~ "cp-$branch" ]]; then
+            filtered_branches+=("$branch")
+          fi
         done
 
-        echo "target_branch=$(echo -n "$filtered_branches" | jq -cRs 'split("\n")')" >> $GITHUB_OUTPUT
+        echo "target_branch=$(echo -n "${filtered_branches[@]}" | jq -cRs 'split(" ")')" >> $GITHUB_OUTPUT
 
   cherry_pick:
     runs-on: ubuntu-latest
+    permissions:
+      issues: write
     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
+    if: needs.prepare_branch_list.outputs.target_branch != '[]'
     strategy:
       matrix:
         target_branch: ${{ fromJson(needs.prepare_branch_list.outputs.target_branch) }}
@@ -57,9 +58,11 @@
       REPOSITORY: ${{ github.repository }}
       GITHUB_REF: ${{ github.ref }}
       MERGE_COMMIT_SHA: ${{ github.event.pull_request.merge_commit_sha }}
+      CHERRY_PICK_BRANCH: cherry-pick-${{ matrix.target_branch }}-${{ github.event.pull_request.number }}
     steps:
       - name: Checkout repository
         uses: kaidokert/checkout@v3.5.999
+        timeout-minutes: 30
         with:
           ref: ${{ matrix.target_branch }}
           fetch-depth: 0
@@ -71,27 +74,35 @@
           git config --global user.email "github@google.com"
 
       - name: Cherry pick merge commit
+        id: cherry-pick
+        continue-on-error: true
         run: |
+          set -x
           git fetch origin ${{ matrix.target_branch }}
+
           set +e
-          git cherry-pick -x $MERGE_COMMIT_SHA
+          # Select the first parent as the mainline tree. This is necessary if
+          # the commit has multiple parents as the cherry-pick will fail otherwise.
+          git cherry-pick -x --mainline=1 ${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
+          if [ ${RES} -ne 0 ]; then
+            # If the cherry pick failed due to a merge conflict we can
+            # add the conflicting file and create the commit anyway.
             git add .
             git cherry-pick --continue
           fi
+          exit ${RES}
 
       - name: Create Pull Request
+        id: create-pr
+        continue-on-error: true
         uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04 # v4.2.3
         with:
           token: ${{ secrets.CHERRY_PICK_TOKEN }}
-          draft: ${{ env.CREATE_PR_AS_DRAFT }}
+          draft: ${{ steps.cherry-pick.outcome == 'failure' }}
           base: ${{ matrix.target_branch }}
-          branch: "${{ matrix.target_branch }}-${{ github.event.pull_request.number }}"
+          branch: ${{ env.CHERRY_PICK_BRANCH }}
           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 }}"
@@ -99,3 +110,26 @@
             Refer to the original PR: https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}
 
             ${{ github.event.pull_request.body }}
+
+      - name: Comment on failure
+        uses: actions/github-script@v6
+        with:
+          github-token: ${{ secrets.CHERRY_PICK_TOKEN }}
+          script: |
+            if ('${{ steps.create-pr.outputs.pull-request-number }}' == '') {
+              // Comment on the originating PR if creating a cherry pick PR failed.
+              github.rest.issues.createComment({
+                issue_number: context.payload.number,
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                body: '> [!IMPORTANT]\n> Creating the cherry pick PR failed! Check the log at ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} for details.'
+              });
+            } else if ('${{ steps.cherry-pick.outcome }}' == 'failure') {
+              // Comment on the new PR if the cherry pick failed.
+              github.rest.issues.createComment({
+                issue_number: '${{ steps.create-pr.outputs.pull-request-number }}',
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                body: '> [!IMPORTANT]\n> There were merge conflicts while cherry picking! Check out [${{ env.CHERRY_PICK_BRANCH }}](${{ github.repository }}/tree/${{ env.CHERRY_PICK_BRANCH }}) and fix the conflicts before proceeding. Check the log at ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} for details.'
+              });
+            }
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
index 5baaaba..76f456b 100644
--- a/.github/workflows/lint.yaml
+++ b/.github/workflows/lint.yaml
@@ -38,13 +38,14 @@
           rm /tmp/gn.zip
       - name: Checkout
         uses: kaidokert/checkout@v3.5.999
+        timeout-minutes: 30
         with:
           fetch-depth: 0
           persist-credentials: false
       - name: Setup Python
         uses: actions/setup-python@v4
         with:
-          python-version: '^3.7.x'
+          python-version: '3.7'
       - name: Install Pip Packages
         run: pip install -r ${GITHUB_WORKSPACE}/requirements.txt
       - name: Download Resources
diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml
index bff0dff..43db38a 100644
--- a/.github/workflows/linux.yaml
+++ b/.github/workflows/linux.yaml
@@ -38,6 +38,7 @@
     with:
       platform: linux-clang-3-9
       nightly: ${{ github.event.inputs.nightly }}
+      modular: true
   linux-gcc-6-3:
     uses: ./.github/workflows/main.yaml
     permissions:
@@ -46,6 +47,7 @@
     with:
       platform: linux-gcc-6-3
       nightly: ${{ github.event.inputs.nightly }}
+      modular: true
   # TODO(b/285632780): Enable blackbox tests for modular linux workflows.
   linux-modular:
     uses: ./.github/workflows/main.yaml
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index e0cd9b5..6f9e38a 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -47,7 +47,7 @@
   STARBOARD_TOOLCHAINS_DIR: /root/starboard-toolchains
 
 concurrency:
-  group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.pull_request.number || github.sha }}'
+  group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.label.name || 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
@@ -71,6 +71,7 @@
     steps:
       - id: checkout
         uses: kaidokert/checkout@v3.5.999
+        timeout-minutes: 30
         with:
           fetch-depth: 1
           persist-credentials: false
@@ -86,23 +87,45 @@
             ${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
+        shell: bash
         run: |
-          echo "docker_service=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -r '.docker_service')" >> $GITHUB_ENV
-          echo $platforms
+          platforms=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.platforms')
+          echo "platforms=${platforms}" >> $GITHUB_ENV
+      - id: set-includes
+        shell: bash
+        run: |
+          includes=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.includes')
+          echo "includes=${includes}" >> $GITHUB_ENV
+      - id: set-on-device-test
+        shell: bash
+        run: |
+          on_device_test=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.on_device_test')
+          echo "on_device_test=${on_device_test}" >> $GITHUB_ENV
+      - id: set-on-device-test-attempts
+        shell: bash
+        run: |
+          on_device_test_attempts=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.on_device_test.test_attempts // empty')
+          echo "on_device_test_attempts=${on_device_test_attempts}" >> $GITHUB_ENV
+      - id: set-on-host-test
+        shell: bash
+        run: |
+          on_host_test=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.on_host_test')
+          echo "on_host_test=${on_host_test}" >> $GITHUB_ENV
+      - id: set-on-host-test-shards
+        shell: bash
+        run: |
+          on_host_test_shards=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.on_host_test_shards')
+          echo "on_host_test_shards=${on_host_test_shards}" >> $GITHUB_ENV
+      - id: set-on-host-test-bootloader
+        shell: bash
+        run: |
+          bootloader=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.bootloader')
+          echo "bootloader=${bootloader}" >> $GITHUB_ENV
+      - id: set-docker-service
+        shell: bash
+        run: |
+          docker_service=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -r '.docker_service')
+          echo "docker_service=${docker_service}" >> $GITHUB_ENV
     outputs:
       platforms: ${{ env.platforms }}
       includes: ${{ env.includes }}
@@ -122,8 +145,9 @@
     steps:
       - name: Checkout files
         uses: kaidokert/checkout@v3.5.999
+        timeout-minutes: 30
         with:
-          fetch-depth: 2
+          fetch-depth: 0
           persist-credentials: false
       - name: Login to Docker Registry ${{env.REGISTRY}}
         uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
@@ -139,7 +163,9 @@
           docker_image: cobalt-${{ needs.initialize.outputs.docker_service }}
       - name: Set Docker Tag Output
         id: set-docker-tag-output
+        shell: bash
         run: |
+          set -u
           echo $DOCKER_TAG
           echo "docker_tag=$DOCKER_TAG"  >> $GITHUB_ENV
     outputs:
@@ -155,6 +181,7 @@
     steps:
       - name: Checkout files
         uses: kaidokert/checkout@v3.5.999
+        timeout-minutes: 30
         with:
           fetch-depth: 2
           persist-credentials: false
@@ -172,7 +199,9 @@
           docker_image: cobalt-linux-x64x11-unittest
       - name: Set Docker Tag Output
         id: set-docker-unittest-tag-output
+        shell: bash
         run: |
+          set -u
           echo $DOCKER_TAG
           echo "docker_unittest_tag=$DOCKER_TAG" >> $GITHUB_ENV
     outputs:
@@ -199,6 +228,7 @@
     steps:
       - name: Checkout
         uses: kaidokert/checkout@v3.5.999
+        timeout-minutes: 30
         with:
           # Use fetch depth of 0 to get full history for a valid build id.
           fetch-depth: 0
@@ -225,7 +255,11 @@
       # 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
+        shell: bash
+        run: |
+           set -u
+           COBALT_BOOTLOADER="${{needs.initialize.outputs.bootloader}}"
+           echo "COBALT_BOOTLOADER=${COBALT_BOOTLOADER}" >> $GITHUB_ENV
       # Build bootloader for on-host tests if necessary.
       - name: Bootloader GN
         if: ${{ needs.initialize.outputs.bootloader != 'null' && matrix.config == 'devel' }}
@@ -282,6 +316,7 @@
     steps:
       - name: Checkout
         uses: kaidokert/checkout@v3.5.999
+        timeout-minutes: 30
         with:
           fetch-depth: 1
           persist-credentials: false
@@ -313,6 +348,7 @@
     steps:
       - name: Checkout
         uses: kaidokert/checkout@v3.5.999
+        timeout-minutes: 30
         with:
           fetch-depth: 1
           persist-credentials: false
diff --git a/.github/workflows/main_win.yaml b/.github/workflows/main_win.yaml
index ea1a301..0b9f14b 100644
--- a/.github/workflows/main_win.yaml
+++ b/.github/workflows/main_win.yaml
@@ -38,7 +38,7 @@
   STARBOARD_TOOLCHAINS_DIR: /root/starboard-toolchains
 
 concurrency:
-  group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.pull_request.number || github.sha }}'
+  group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.label.name || 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
@@ -57,7 +57,8 @@
       github.event.action != 'labeled' ||
       (
         github.event.action == 'labeled' &&
-        github.event.label.name == 'runtest'
+        github.event.label.name == 'runtest' ||
+        github.event.label.name == 'on_device'
       )
     steps:
       - id: Checkout
@@ -77,19 +78,40 @@
             ${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
+        shell: bash
+        run: |
+          platforms=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.platforms')
+          echo "platforms=${platforms}" >> $GITHUB_ENV
       - id: set-includes
-        run: echo "includes=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.includes')" >> $GITHUB_ENV
+        shell: bash
+        run: |
+          includes=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.includes')
+          echo "includes=${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
+        shell: bash
+        run: |
+          on_device_test=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.on_device_test.enabled')
+          echo "on_device_test=${on_device_test}" >> $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
+        shell: bash
+        run: |
+          on_host_test=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.on_host_test')
+          echo "on_host_test=${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
+        shell: bash
+        run: |
+          on_host_test_shards=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -c '.on_host_test_shards')
+          echo "on_host_test_shards=${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
+        shell: bash
+        run: |
+          docker_service=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.docker_service')
+          echo "docker_service=${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
+        shell: bash
+        run: |
+          docker_runner_service=$(cat ${GITHUB_WORKSPACE}/.github/config/${{ inputs.platform }}.json | jq -rc '.docker_runner_service')
+          echo "docker_runner_service=${docker_runner_service}" >> $GITHUB_ENV
     outputs:
       platforms: ${{ env.platforms }}
       includes: ${{ env.includes }}
@@ -108,7 +130,7 @@
       - name: Checkout files
         uses: kaidokert/checkout@v3.5.999
         with:
-          fetch-depth: 2
+          fetch-depth: 0
           persist-credentials: false
       - name: Login to Docker Registry ${{env.REGISTRY}}
         uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # v2.1.0
diff --git a/.github/workflows/manual-cherry-pick.yaml b/.github/workflows/manual-cherry-pick.yaml
index d4bca26..f48cb11 100644
--- a/.github/workflows/manual-cherry-pick.yaml
+++ b/.github/workflows/manual-cherry-pick.yaml
@@ -47,6 +47,7 @@
     steps:
     - name: Checkout code
       uses: kaidokert/checkout@v3.5.999
+      timeout-minutes: 30
       with:
         ref: ${{ env.RELEASE_BRANCH }}
         persist-credentials: false
diff --git a/.github/workflows/outside_collaborator.yaml b/.github/workflows/outside_collaborator.yaml
new file mode 100644
index 0000000..a8913e3
--- /dev/null
+++ b/.github/workflows/outside_collaborator.yaml
@@ -0,0 +1,35 @@
+name: Outside Collaborator
+
+on:
+  pull_request_target:
+    types:
+      - opened
+      - reopened
+
+concurrency:
+  group: '${{ github.workflow }}-${{ github.event_name }} @ ${{ github.event.pull_request.number || github.sha }}'
+  cancel-in-progress: true
+
+jobs:
+  assign-reviewer:
+    runs-on: ubuntu-latest
+    permissions:
+      pull-requests: write
+    steps:
+    - name: Check if PR author is outside collaborator
+      env:
+        PR_AUTHOR_LOGIN: ${{ github.event.pull_request.user.login }}
+        REPO_NAME: ${{ github.event.repository.full_name }}
+        PR_NUMBER: ${{ github.event.number }}
+      run: |
+        PERMISSION_LEVEL=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
+          "https://api.github.com/repos/$REPO_NAME/collaborators/$PR_AUTHOR_LOGIN/permission" | jq -r .role_name)
+
+        if [ "$PERMISSION_LEVEL" == "none" ] || [ "$PERMISSION_LEVEL" == "read" ]; then
+          echo "PR author is an outside collaborator. Adding label..."
+
+          curl -s -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
+            -H "Accept: application/vnd.github.v3+json" \
+            -d '["outside collaborator"]' \
+            "https://api.github.com/repos/$REPO_NAME/issues/$PR_NUMBER/labels"
+        fi
diff --git a/.github/workflows/stub.yaml b/.github/workflows/stub.yaml
index 8c40a4e..b5953b9 100644
--- a/.github/workflows/stub.yaml
+++ b/.github/workflows/stub.yaml
@@ -26,6 +26,6 @@
       pull-requests: write
     with:
       platform: stub
-      nightly: 'false'
+      nightly: ${{ github.event.inputs.nightly }}
       run_api_leak_detector: true
       leak_manifest_filename: "gn_built_docker_debian10_manifest"
diff --git a/.github/workflows/unit_test_report.yaml b/.github/workflows/unit_test_report.yaml
index 8f43f24..5d9cf16 100644
--- a/.github/workflows/unit_test_report.yaml
+++ b/.github/workflows/unit_test_report.yaml
@@ -9,11 +9,12 @@
     types:
       - completed
 
+# TODO(b/293508740): Report failed workflow runs back to triggering PR.
+
 jobs:
   unit-test-report:
-    permissions: {}
-      # TODO(b/293508740): Report failed workflow runs back to triggering PR.
-      # pull-requests: write
+    permissions:
+      pull-requests: write
     if: ${{ github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure' }}
     runs-on: ubuntu-latest
     name: Upload Unit Test Reports
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ea415f3..2173391 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -11,7 +11,7 @@
             base|
             build|
             buildtools|
-            cobalt/updater/win|
+            cobalt/updater|
             components/crash/core/common|
             components/metrics_services_manager|
             components/metrics|
@@ -23,15 +23,20 @@
             extensions/buildflags|
             glimp/include|
             net|
+            internal/kokoro/third-party|
             internal/starboard/shared/playstation/glimp/shaders|
             testing|
             third_party|
             tools/gyp|
+            tools/python|
             url
         )/
         |
         components/update_client/((?!cobalt).)*$
         |
+        # Ignore everything under tools/metrics _except_ Cobalt files. We
+        # need those validated to keep the telemetry/metrics pipeline working.
+        tools/metrics/((?!cobalt\/).)*$|
         .*\.pb\.cc$ |
         .*\.pb\.h$ |
         .*\.patch$ |
@@ -47,6 +52,7 @@
     -   id: end-of-file-fixer
     -   id: trailing-whitespace
     -   id: mixed-line-ending
+    -   id: check-xml
 
 -   repo: https://cobalt.googlesource.com/codespell
     rev: 67c489d36dd4c52cbb9e4755d90c35c6231842ef  # v2.0.0
@@ -87,6 +93,7 @@
         entry: clang-format
         language: python
         types: [c++]
+        exclude_types: [objective-c++]
         args: [-i, -style=file]
         additional_dependencies: ['clang-format']
     -   id: cpplint
diff --git a/AUTHORS b/AUTHORS
index 99cc139..364b1bb 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -17,3 +17,4 @@
 MediaTek <*@mediatek.com>
 MIPS <*@mips.com>
 Vewd <*@vewd.com>
+Broadcom <*@broadcom.com>
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3853124..0604bc7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -44,7 +44,7 @@
 ### Contribution guidelines and standards
 
 Before sending your pull request for
-[review](https://github.com/tensorflow/tensorflow/pulls),
+[review](https://github.com/youtube/cobalt/pulls),
 make sure your changes are consistent with the guidelines and follow the
 Cobalt coding style.
 
@@ -89,11 +89,11 @@
 
 #### Running unit tests
 
-First, ensure Docker and docker-compose are installed on your system. Then,
+First, ensure Docker and Docker Compose are installed on your system. Then,
 you can run unit tests for our linux reference implementation using:
 
 ```bash
-$ docker-compose up --build --no-start linux-x64x11-unittest
-$ PLATFORM=linux-x64x11 CONFIG=devel TARGET=all docker-compose run linux-x64x11
-$ PLATFORM=linux-x64x11 CONFIG=devel docker-compose run linux-x64x11-unittest
+$ docker compose up --build --no-start linux-x64x11-unittest
+$ PLATFORM=linux-x64x11 CONFIG=devel TARGET=all docker compose run linux-x64x11
+$ PLATFORM=linux-x64x11 CONFIG=devel docker compose run linux-x64x11-unittest
 ```
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 509aa37..6835715 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -1561,7 +1561,7 @@
     public_configs = [ ":base_public_defines" ]
     deps += [
       "//starboard/common",
-      "//starboard",
+      "//starboard:starboard_group",
       "//starboard/client_porting/eztime",
     ]
     deps -= [
diff --git a/base/METADATA b/base/METADATA
index bc1bf40..6a30c57 100644
--- a/base/METADATA
+++ b/base/METADATA
@@ -9,12 +9,12 @@
   }
   url {
     type: GIT
-    value: "https://chromium.googlesource.com/chromium/src/base"
+    value: "https://chromium.googlesource.com/chromium/src"
   }
-  version: "2fb7110f70453ccb8951428611330d95c496f3cd"
+  version: "71.0.3578.141"
   last_upgrade_date {
-    year: 2018
-    month: 10
-    day: 8
+    year: 2023
+    month: 8
+    day: 23
   }
 }
diff --git a/base/allocator/allocator_shim_override_libc_symbols.h b/base/allocator/allocator_shim_override_libc_symbols.h
index 956a0e5..ce688d9 100644
--- a/base/allocator/allocator_shim_override_libc_symbols.h
+++ b/base/allocator/allocator_shim_override_libc_symbols.h
@@ -18,15 +18,15 @@
 
 extern "C" {
 
-SHIM_ALWAYS_EXPORT void* SbMemoryAllocate(size_t size) __THROW {
+SHIM_ALWAYS_EXPORT void* malloc(size_t size) __THROW {
   return ShimMalloc(size, nullptr);
 }
 
-SHIM_ALWAYS_EXPORT void SbMemoryDeallocate(void* ptr) __THROW {
+SHIM_ALWAYS_EXPORT void free(void* ptr) __THROW {
   ShimFree(ptr, nullptr);
 }
 
-SHIM_ALWAYS_EXPORT void* SbMemoryReallocate(void* ptr, size_t size) __THROW {
+SHIM_ALWAYS_EXPORT void* realloc(void* ptr, size_t size) __THROW {
   return ShimRealloc(ptr, size, nullptr);
 }
 
diff --git a/base/allocator/allocator_shim_override_ucrt_symbols_win.h b/base/allocator/allocator_shim_override_ucrt_symbols_win.h
index a29b9ee..ecd659c 100644
--- a/base/allocator/allocator_shim_override_ucrt_symbols_win.h
+++ b/base/allocator/allocator_shim_override_ucrt_symbols_win.h
@@ -52,15 +52,15 @@
 }
 
 // These symbols override the CRT's implementation of the same functions.
-__declspec(restrict) void* SbMemoryAllocate(size_t size) {
+__declspec(restrict) void* malloc(size_t size) {
   return ShimMalloc(size, nullptr);
 }
 
-void SbMemoryDeallocate(void* ptr) {
+void free(void* ptr) {
   ShimFree(ptr, nullptr);
 }
 
-__declspec(restrict) void* SbMemoryReallocate(void* ptr, size_t size) {
+__declspec(restrict) void* realloc(void* ptr, size_t size) {
   return ShimRealloc(ptr, size, nullptr);
 }
 
diff --git a/base/allocator/allocator_shim_unittest.cc b/base/allocator/allocator_shim_unittest.cc
index 8b82a0f..0ba3b0a 100644
--- a/base/allocator/allocator_shim_unittest.cc
+++ b/base/allocator/allocator_shim_unittest.cc
@@ -247,8 +247,8 @@
 
   void ThreadMain() override {
     event_->Wait();
-    void* temp = SbMemoryAllocate(1);
-    void* res = SbMemoryReallocate(temp, 0xFEED);
+    void* temp = malloc(1);
+    void* res = realloc(temp, 0xFEED);
     EXPECT_EQ(temp, res);
   }
 
@@ -274,7 +274,7 @@
 TEST_F(AllocatorShimTest, InterceptLibcSymbols) {
   InsertAllocatorDispatch(&g_mock_dispatch);
 
-  void* alloc_ptr = SbMemoryAllocate(19);
+  void* alloc_ptr = malloc(19);
   ASSERT_NE(nullptr, alloc_ptr);
   ASSERT_GE(allocs_intercepted_by_size[19], 1u);
 
@@ -314,46 +314,46 @@
   ASSERT_GE(aligned_allocs_intercepted_by_size[kPageSize], 1u);
 #endif  // !OS_WIN && !OS_MACOSX
 
-  char* realloc_ptr = static_cast<char*>(SbMemoryAllocate(10));
+  char* realloc_ptr = static_cast<char*>(malloc(10));
   strcpy(realloc_ptr, "foobar");
   void* old_realloc_ptr = realloc_ptr;
-  SbMemoryReallocate_ptr =
-      static_cast<char*>(SbMemoryReallocate(SbMemoryReallocate_ptr, 73));
+  realloc_ptr =
+      static_cast<char*>(realloc(realloc_ptr, 73));
   ASSERT_GE(reallocs_intercepted_by_size[73], 1u);
   ASSERT_GE(reallocs_intercepted_by_addr[Hash(old_realloc_ptr)], 1u);
   ASSERT_EQ(0, strcmp(realloc_ptr, "foobar"));
 
-  SbMemoryDeallocate(alloc_ptr);
+  free(alloc_ptr);
   ASSERT_GE(frees_intercepted_by_addr[Hash(alloc_ptr)], 1u);
 
-  SbMemoryDeallocate(zero_alloc_ptr);
+  free(zero_alloc_ptr);
   ASSERT_GE(frees_intercepted_by_addr[Hash(zero_alloc_ptr)], 1u);
 
 #if !defined(OS_WIN) && !defined(OS_MACOSX)
-  SbMemoryDeallocate(memalign_ptr);
+  free(memalign_ptr);
   ASSERT_GE(frees_intercepted_by_addr[Hash(memalign_ptr)], 1u);
 
-  SbMemoryDeallocate(pvalloc_ptr);
+  free(pvalloc_ptr);
   ASSERT_GE(frees_intercepted_by_addr[Hash(pvalloc_ptr)], 1u);
 #endif  // !OS_WIN && !OS_MACOSX
 
 #if !defined(OS_WIN)
-  SbMemoryDeallocate(posix_memalign_ptr);
+  free(posix_memalign_ptr);
   ASSERT_GE(frees_intercepted_by_addr[Hash(posix_memalign_ptr)], 1u);
 
-  SbMemoryDeallocate(valloc_ptr);
+  free(valloc_ptr);
   ASSERT_GE(frees_intercepted_by_addr[Hash(valloc_ptr)], 1u);
 #endif  // !OS_WIN
 
-  SbMemoryDeallocate(realloc_ptr);
+  free(realloc_ptr);
   ASSERT_GE(frees_intercepted_by_addr[Hash(realloc_ptr)], 1u);
 
   RemoveAllocatorDispatchForTesting(&g_mock_dispatch);
 
-  void* non_hooked_ptr = SbMemoryAllocate(4095);
+  void* non_hooked_ptr = malloc(4095);
   ASSERT_NE(nullptr, non_hooked_ptr);
   ASSERT_EQ(0u, allocs_intercepted_by_size[4095]);
-  SbMemoryDeallocate(non_hooked_ptr);
+  free(non_hooked_ptr);
 }
 
 #if defined(OS_MACOSX)
@@ -385,7 +385,7 @@
 TEST_F(AllocatorShimTest, InterceptLibcSymbolsFreeDefiniteSize) {
   InsertAllocatorDispatch(&g_mock_dispatch);
 
-  void* alloc_ptr = SbMemoryAllocate(19);
+  void* alloc_ptr = malloc(19);
   ASSERT_NE(nullptr, alloc_ptr);
   ASSERT_GE(allocs_intercepted_by_size[19], 1u);
 
diff --git a/base/allocator/partition_allocator/partition_alloc.cc b/base/allocator/partition_allocator/partition_alloc.cc
index c7343fc..5703931 100644
--- a/base/allocator/partition_allocator/partition_alloc.cc
+++ b/base/allocator/partition_allocator/partition_alloc.cc
@@ -262,7 +262,7 @@
                                    size_t new_size,
                                    const char* type_name) {
 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
-  void* result = SbMemoryReallocate(ptr, new_size);
+  void* result = realloc(ptr, new_size);
   CHECK(result || flags & PartitionAllocReturnNull);
   return result;
 #else
diff --git a/base/allocator/partition_allocator/partition_alloc.h b/base/allocator/partition_allocator/partition_alloc.h
index af49684..de6bbe3 100644
--- a/base/allocator/partition_allocator/partition_alloc.h
+++ b/base/allocator/partition_allocator/partition_alloc.h
@@ -274,7 +274,7 @@
                                               size_t size,
                                               const char* type_name) {
 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
-  void* result = SbMemoryAllocate(size);
+  void* result = malloc(size);
   CHECK(result);
   return result;
 #else
@@ -314,7 +314,7 @@
 
 ALWAYS_INLINE void PartitionFree(void* ptr) {
 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
-  SbMemoryDeallocate(ptr);
+  free(ptr);
 #else
   void* original_ptr = ptr;
   // TODO(palmer): Check ptr alignment before continuing. Shall we do the check
@@ -354,7 +354,7 @@
 
 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
   const bool zero_fill = flags & PartitionAllocZeroFill;
-  void* result = zero_fill ? calloc(1, size) : SbMemoryAllocate(size);
+  void* result = zero_fill ? calloc(1, size) : malloc(size);
   CHECK(result || flags & PartitionAllocReturnNull);
   return result;
 #else
@@ -386,7 +386,7 @@
 
 ALWAYS_INLINE void PartitionRootGeneric::Free(void* ptr) {
 #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
-  SbMemoryDeallocate(ptr);
+  free(ptr);
 #else
   DCHECK(this->initialized);
 
diff --git a/base/allocator/tcmalloc_unittest.cc b/base/allocator/tcmalloc_unittest.cc
index 66f256f..b9bbbf8 100644
--- a/base/allocator/tcmalloc_unittest.cc
+++ b/base/allocator/tcmalloc_unittest.cc
@@ -23,11 +23,11 @@
 // implementation of the allocator. Otherwise, the compiler may specifically
 // recognize the calls to malloc and free in our tests and optimize them away.
 NOINLINE void* TCMallocDoMallocForTest(size_t size) {
-  return SbMemoryAllocate(size);
+  return malloc(size);
 }
 
 NOINLINE void TCMallocDoFreeForTest(void* ptr) {
-  SbMemoryDeallocate(ptr);
+  free(ptr);
 }
 #endif
 
@@ -83,7 +83,7 @@
     for (size_t i = 0; i < n * s; i++) {
       EXPECT_EQ('\0', p[i]);
     }
-    SbMemoryDeallocate(p);
+    free(p);
   }
 }
 
@@ -97,12 +97,12 @@
   // Try allocating data with a bunch of alignments and sizes
   for (int size = 1; size < 1048576; size *= 2) {
     unsigned char* ptr =
-        reinterpret_cast<unsigned char*>(SbMemoryAllocate(size));
+        reinterpret_cast<unsigned char*>(malloc(size));
     // Should be 2 byte aligned
     EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(ptr) & 1);
     Fill(ptr, size);
     EXPECT_TRUE(Valid(ptr, size));
-    SbMemoryDeallocate(ptr);
+    free(ptr);
   }
 }
 
@@ -128,19 +128,19 @@
   int deltas[] = {1, -2, 4, -8, 16, -32, 64, -128};
 
   for (unsigned s = 0; s < sizeof(start_sizes) / sizeof(*start_sizes); ++s) {
-    void* p = SbMemoryAllocate(start_sizes[s]);
+    void* p = malloc(start_sizes[s]);
     ASSERT_TRUE(p);
     // The larger the start-size, the larger the non-reallocing delta.
     for (unsigned d = 0; d < s * 2; ++d) {
-      void* new_p = SbMemoryReallocate(p, start_sizes[s] + deltas[d]);
+      void* new_p = realloc(p, start_sizes[s] + deltas[d]);
       ASSERT_EQ(p, new_p);  // realloc should not allocate new memory
     }
     // Test again, but this time reallocing smaller first.
     for (unsigned d = 0; d < s * 2; ++d) {
-      void* new_p = SbMemoryReallocate(p, start_sizes[s] - deltas[d]);
+      void* new_p = realloc(p, start_sizes[s] - deltas[d]);
       ASSERT_EQ(p, new_p);  // realloc should not allocate new memory
     }
-    SbMemoryDeallocate(p);
+    free(p);
   }
 }
 #endif
@@ -149,15 +149,15 @@
   for (int src_size = 0; src_size >= 0; src_size = NextSize(src_size)) {
     for (int dst_size = 0; dst_size >= 0; dst_size = NextSize(dst_size)) {
       unsigned char* src =
-          reinterpret_cast<unsigned char*>(SbMemoryAllocate(src_size));
+          reinterpret_cast<unsigned char*>(malloc(src_size));
       Fill(src, src_size);
       unsigned char* dst =
-          reinterpret_cast<unsigned char*>(SbMemoryReallocate(src, dst_size));
+          reinterpret_cast<unsigned char*>(realloc(src, dst_size));
       EXPECT_TRUE(Valid(dst, min(src_size, dst_size)));
       Fill(dst, dst_size);
       EXPECT_TRUE(Valid(dst, dst_size));
       if (dst != nullptr)
-        SbMemoryDeallocate(dst);
+        free(dst);
     }
   }
 
@@ -171,22 +171,22 @@
   // packed cache, so some entries are evicted from the cache.
   // The cache has 2^12 entries, keyed by page number.
   const int kNumEntries = 1 << 14;
-  int** p = reinterpret_cast<int**>(SbMemoryAllocate(sizeof(*p) * kNumEntries));
+  int** p = reinterpret_cast<int**>(malloc(sizeof(*p) * kNumEntries));
   int sum = 0;
   for (int i = 0; i < kNumEntries; i++) {
     // no page size is likely to be bigger than 8192?
-    p[i] = reinterpret_cast<int*>(SbMemoryAllocate(8192));
+    p[i] = reinterpret_cast<int*>(malloc(8192));
     p[i][1000] = i;  // use memory deep in the heart of p
   }
   for (int i = 0; i < kNumEntries; i++) {
-    p[i] = reinterpret_cast<int*>(SbMemoryReallocate(p[i], 9000));
+    p[i] = reinterpret_cast<int*>(realloc(p[i], 9000));
   }
   for (int i = 0; i < kNumEntries; i++) {
     sum += p[i][1000];
-    SbMemoryDeallocate(p[i]);
+    free(p[i]);
   }
   EXPECT_EQ(kNumEntries / 2 * (kNumEntries - 1), sum);  // assume kNE is even
-  SbMemoryDeallocate(p);
+  free(p);
 }
 
 #ifdef NDEBUG
diff --git a/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
index e9dd36d..197bdeb 100644
--- a/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
+++ b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
@@ -24,6 +24,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.TransitionDrawable;
 import android.graphics.drawable.VectorDrawable;
 import android.net.Uri;
 import android.os.Build;
@@ -45,6 +46,7 @@
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.textclassifier.TextClassifier;
 import android.widget.ImageView;
+import android.widget.PopupWindow;
 import android.widget.TextView;
 
 import java.io.File;
@@ -325,6 +327,17 @@
     }
 
     /**
+     * Set elevation if supported.
+     */
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public static boolean setElevation(PopupWindow window, float elevationValue) {
+        if (!isElevationSupported()) return false;
+
+        window.setElevation(elevationValue);
+        return true;
+    }
+
+    /**
      *  Gets an intent to start the Android system notification settings activity for an app.
      *
      *  @param context Context of the app whose settings intent should be returned.
@@ -732,6 +745,7 @@
     /**
      * Creates regular LayerDrawable on Android L+. On older versions creates a helper class that
      * fixes issues around {@link LayerDrawable#mutate()}. See https://crbug.com/890317 for details.
+     * See also {@link #createTransitionDrawable}.
      * @param layers A list of drawables to use as layers in this new drawable.
      */
     public static LayerDrawable createLayerDrawable(@NonNull Drawable[] layers) {
@@ -741,6 +755,19 @@
         return new LayerDrawable(layers);
     }
 
+    /**
+     * Creates regular TransitionDrawable on Android L+. On older versions creates a helper class
+     * that fixes issues around {@link TransitionDrawable#mutate()}. See https://crbug.com/892061
+     * for details. See also {@link #createLayerDrawable}.
+     * @param layers A list of drawables to use as layers in this new drawable.
+     */
+    public static TransitionDrawable createTransitionDrawable(@NonNull Drawable[] layers) {
+        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
+            return new TransitionDrawableCompat(layers);
+        }
+        return new TransitionDrawable(layers);
+    }
+
     private static class LayerDrawableCompat extends LayerDrawable {
         private boolean mMutated;
 
@@ -748,6 +775,7 @@
             super(layers);
         }
 
+        @NonNull
         @Override
         public Drawable mutate() {
             // LayerDrawable in Android K loses bounds of layers, so this method works around that.
@@ -756,24 +784,61 @@
                 return this;
             }
 
-            // Save bounds before mutation.
-            Rect[] oldBounds = new Rect[getNumberOfLayers()];
-            for (int i = 0; i < getNumberOfLayers(); i++) {
-                oldBounds[i] = getDrawable(i).getBounds();
-            }
-
+            Rect[] oldBounds = getLayersBounds(this);
             Drawable superResult = super.mutate();
-            if (superResult != this) {
-                // Unexpected, LayerDrawable.mutate() always returns this.
-                return superResult;
-            }
-
-            // Restore the saved bounds.
-            for (int i = 0; i < getNumberOfLayers(); i++) {
-                getDrawable(i).setBounds(oldBounds[i]);
-            }
+            // LayerDrawable.mutate() always returns this, bail out if this isn't the case.
+            if (superResult != this) return superResult;
+            restoreLayersBounds(this, oldBounds);
             mMutated = true;
             return this;
         }
     }
+
+    private static class TransitionDrawableCompat extends TransitionDrawable {
+        private boolean mMutated;
+
+        TransitionDrawableCompat(@NonNull Drawable[] layers) {
+            super(layers);
+        }
+
+        @NonNull
+        @Override
+        public Drawable mutate() {
+            // LayerDrawable in Android K loses bounds of layers, so this method works around that.
+            if (mMutated) {
+                // This object has already been mutated and shouldn't have any shared state.
+                return this;
+            }
+            Rect[] oldBounds = getLayersBounds(this);
+            Drawable superResult = super.mutate();
+            // TransitionDrawable.mutate() always returns this, bail out if this isn't the case.
+            if (superResult != this) return superResult;
+            restoreLayersBounds(this, oldBounds);
+            mMutated = true;
+            return this;
+        }
+    }
+
+    /**
+     * Helper for {@link LayerDrawableCompat#mutate} and {@link TransitionDrawableCompat#mutate}.
+     * Obtains the bounds of layers so they can be restored after a mutation.
+     */
+    private static Rect[] getLayersBounds(LayerDrawable layerDrawable) {
+        Rect[] result = new Rect[layerDrawable.getNumberOfLayers()];
+        for (int i = 0; i < layerDrawable.getNumberOfLayers(); i++) {
+            result[i] = layerDrawable.getDrawable(i).getBounds();
+        }
+        return result;
+    }
+
+    /**
+     * Helper for {@link LayerDrawableCompat#mutate} and {@link TransitionDrawableCompat#mutate}.
+     * Restores the bounds of layers after a mutation.
+     */
+    private static void restoreLayersBounds(LayerDrawable layerDrawable, Rect[] oldBounds) {
+        assert layerDrawable.getNumberOfLayers() == oldBounds.length;
+        for (int i = 0; i < layerDrawable.getNumberOfLayers(); i++) {
+            layerDrawable.getDrawable(i).setBounds(oldBounds[i]);
+        }
+    }
 }
diff --git a/base/android/jni_generator/AndroidManifest.xml b/base/android/jni_generator/AndroidManifest.xml
index 1555a81..aa0b7c5 100644
--- a/base/android/jni_generator/AndroidManifest.xml
+++ b/base/android/jni_generator/AndroidManifest.xml
@@ -7,7 +7,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jni.generator">
 
-    <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="33" />
+    <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="34" />
     <application></application>
 
 </manifest>
diff --git a/base/android/scoped_java_ref.h b/base/android/scoped_java_ref.h
index 9e596a8..4bf5a61 100644
--- a/base/android/scoped_java_ref.h
+++ b/base/android/scoped_java_ref.h
@@ -36,42 +36,51 @@
 };
 
 // Forward declare the generic java reference template class.
-template<typename T> class JavaRef;
+template <typename T>
+class JavaRef;
 
 // Template specialization of JavaRef, which acts as the base class for all
 // other JavaRef<> template types. This allows you to e.g. pass
 // ScopedJavaLocalRef<jstring> into a function taking const JavaRef<jobject>&
-template<>
+template <>
 class BASE_EXPORT JavaRef<jobject> {
  public:
-  // Initializes a null reference. Don't add anything else here; it's inlined.
-  constexpr JavaRef() : obj_(nullptr) {}
+  // Initializes a null reference.
+  constexpr JavaRef() {}
 
   // Allow nullptr to be converted to JavaRef. This avoids having to declare an
   // empty JavaRef just to pass null to a function, and makes C++ "nullptr" and
   // Java "null" equivalent.
-  constexpr JavaRef(std::nullptr_t) : JavaRef() {}
+  constexpr JavaRef(std::nullptr_t) {}
 
   // Public to allow destruction of null JavaRef objects.
-  // Don't add anything else here; it's inlined.
   ~JavaRef() {}
 
+  // TODO(torne): maybe rename this to get() for consistency with unique_ptr
+  // once there's fewer unnecessary uses of it in the codebase.
   jobject obj() const { return obj_; }
 
+  explicit operator bool() const { return obj_ != nullptr; }
+
+  // Deprecated. Just use bool conversion.
+  // TODO(torne): replace usage and remove this.
   bool is_null() const { return obj_ == nullptr; }
 
  protected:
-  // Takes ownership of the |obj| reference passed; requires it to be a local
-  // reference type.
+// Takes ownership of the |obj| reference passed; requires it to be a local
+// reference type.
 #if DCHECK_IS_ON()
   // Implementation contains a DCHECK; implement out-of-line when DCHECK_IS_ON.
   JavaRef(JNIEnv* env, jobject obj);
 #else
-  // Don't add anything else here; it's inlined.
   JavaRef(JNIEnv* env, jobject obj) : obj_(obj) {}
 #endif
 
-  void swap(JavaRef& other) { std::swap(obj_, other.obj_); }
+  // Used for move semantics. obj_ must have been released first if non-null.
+  void steal(JavaRef&& other) {
+    obj_ = other.obj_;
+    other.obj_ = nullptr;
+  }
 
   // The following are implementation detail convenience methods, for
   // use by the sub-classes.
@@ -82,7 +91,7 @@
   jobject ReleaseInternal();
 
  private:
-  jobject obj_;
+  jobject obj_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(JavaRef);
 };
@@ -90,11 +99,11 @@
 // Generic base class for ScopedJavaLocalRef and ScopedJavaGlobalRef. Useful
 // for allowing functions to accept a reference without having to mandate
 // whether it is a local or global type.
-template<typename T>
+template <typename T>
 class JavaRef : public JavaRef<jobject> {
  public:
-  JavaRef() {}
-  JavaRef(std::nullptr_t) : JavaRef<jobject>(nullptr) {}
+  constexpr JavaRef() {}
+  constexpr JavaRef(std::nullptr_t) {}
   ~JavaRef() {}
 
   T obj() const { return static_cast<T>(JavaRef<jobject>::obj()); }
@@ -110,7 +119,7 @@
 // Method parameters should not be deleted, and so this class exists purely to
 // wrap them as a JavaRef<T> in the JNI binding generator. Do not create
 // instances manually.
-template<typename T>
+template <typename T>
 class JavaParamRef : public JavaRef<T> {
  public:
   // Assumes that |obj| is a parameter passed to a JNI method from Java.
@@ -121,7 +130,7 @@
   // methods directly from C++ and pass null for objects which are not actually
   // used by the implementation (e.g. the caller object); allow this to keep
   // working.
-  JavaParamRef(std::nullptr_t) : JavaRef<T>(nullptr) {}
+  JavaParamRef(std::nullptr_t) {}
 
   ~JavaParamRef() {}
 
@@ -143,81 +152,126 @@
 // single thread. If you wish to have the reference outlive the current
 // callstack (e.g. as a class member) or you wish to pass it across threads,
 // use a ScopedJavaGlobalRef instead.
-template<typename T>
+template <typename T>
 class ScopedJavaLocalRef : public JavaRef<T> {
  public:
-  constexpr ScopedJavaLocalRef() : env_(nullptr) {}
-  constexpr ScopedJavaLocalRef(std::nullptr_t) : env_(nullptr) {}
-
-  // Non-explicit copy constructor, to allow ScopedJavaLocalRef to be returned
-  // by value as this is the normal usage pattern.
-  ScopedJavaLocalRef(const ScopedJavaLocalRef<T>& other)
-      : env_(other.env_) {
-    this->SetNewLocalRef(env_, other.obj());
+  // Take ownership of a bare jobject. This does not create a new reference.
+  // This should only be used by JNI helper functions, or in cases where code
+  // must call JNIEnv methods directly.
+  static ScopedJavaLocalRef Adopt(JNIEnv* env, T obj) {
+    return ScopedJavaLocalRef(env, obj);
   }
 
-  ScopedJavaLocalRef(ScopedJavaLocalRef<T>&& other) : env_(other.env_) {
-    this->swap(other);
+  constexpr ScopedJavaLocalRef() {}
+  constexpr ScopedJavaLocalRef(std::nullptr_t) {}
+
+  // Copy constructor. This is required in addition to the copy conversion
+  // constructor below.
+  ScopedJavaLocalRef(const ScopedJavaLocalRef& other) : env_(other.env_) {
+    JavaRef<T>::SetNewLocalRef(env_, other.obj());
   }
 
-  explicit ScopedJavaLocalRef(const JavaRef<T>& other) : env_(nullptr) {
-    this->Reset(other);
+  // Copy conversion constructor.
+  template <typename U,
+            typename = std::enable_if_t<std::is_convertible<U, T>::value>>
+  ScopedJavaLocalRef(const ScopedJavaLocalRef<U>& other) : env_(other.env_) {
+    JavaRef<T>::SetNewLocalRef(env_, other.obj());
   }
 
+  // Move constructor. This is required in addition to the move conversion
+  // constructor below.
+  ScopedJavaLocalRef(ScopedJavaLocalRef&& other) : env_(other.env_) {
+    JavaRef<T>::steal(std::move(other));
+  }
+
+  // Move conversion constructor.
+  template <typename U,
+            typename = std::enable_if_t<std::is_convertible<U, T>::value>>
+  ScopedJavaLocalRef(ScopedJavaLocalRef<U>&& other) : env_(other.env_) {
+    JavaRef<T>::steal(std::move(other));
+  }
+
+  // Constructor for other JavaRef types.
+  explicit ScopedJavaLocalRef(const JavaRef<T>& other) { Reset(other); }
+
   // Assumes that |obj| is a local reference to a Java object and takes
-  // ownership  of this local reference.
-  // TODO(torne): this shouldn't be used outside of JNI helper functions but
-  // there are currently some cases where there aren't helpers for things.
+  // ownership of this local reference.
+  // TODO(torne): make legitimate uses call Adopt() instead, and make this
+  // private.
   ScopedJavaLocalRef(JNIEnv* env, T obj) : JavaRef<T>(env, obj), env_(env) {}
 
-  ~ScopedJavaLocalRef() {
-    this->Reset();
+  ~ScopedJavaLocalRef() { Reset(); }
+
+  // Null assignment, for disambiguation.
+  ScopedJavaLocalRef& operator=(std::nullptr_t) {
+    Reset();
+    return *this;
   }
 
-  // Overloaded assignment operator defined for consistency with the implicit
-  // copy constructor.
-  void operator=(const ScopedJavaLocalRef<T>& other) {
-    this->Reset(other);
+  // Copy assignment.
+  ScopedJavaLocalRef& operator=(const ScopedJavaLocalRef& other) {
+    Reset(other);
+    return *this;
   }
 
-  void operator=(ScopedJavaLocalRef<T>&& other) {
+  // Copy conversion assignment.
+  template <typename U,
+            typename = std::enable_if_t<std::is_convertible<U, T>::value>>
+  ScopedJavaLocalRef& operator=(const ScopedJavaLocalRef<U>& other) {
+    Reset(other);
+    return *this;
+  }
+
+  // Move assignment.
+  template <typename U,
+            typename = std::enable_if_t<std::is_convertible<U, T>::value>>
+  ScopedJavaLocalRef& operator=(ScopedJavaLocalRef<U>&& other) {
     env_ = other.env_;
-    this->swap(other);
+    Reset();
+    JavaRef<T>::steal(std::move(other));
+    return *this;
   }
 
-  void Reset() {
-    this->ResetLocalRef(env_);
+  // Assignment for other JavaRef types.
+  ScopedJavaLocalRef& operator=(const JavaRef<T>& other) {
+    Reset(other);
+    return *this;
   }
 
-  void Reset(const ScopedJavaLocalRef<T>& other) {
+  void Reset() { JavaRef<T>::ResetLocalRef(env_); }
+
+  template <typename U,
+            typename = std::enable_if_t<std::is_convertible<U, T>::value>>
+  void Reset(const ScopedJavaLocalRef<U>& other) {
     // We can copy over env_ here as |other| instance must be from the same
     // thread as |this| local ref. (See class comment for multi-threading
     // limitations, and alternatives).
-    this->Reset(other.env_, other.obj());
+    Reset(other.env_, other.obj());
   }
 
   void Reset(const JavaRef<T>& other) {
     // If |env_| was not yet set (is still null) it will be attached to the
     // current thread in SetNewLocalRef().
-    this->Reset(env_, other.obj());
+    Reset(env_, other.obj());
   }
 
   // Creates a new local reference to the Java object, unlike the constructor
   // with the same parameters that takes ownership of the existing reference.
-  // TODO(torne): these should match as this is confusing.
-  void Reset(JNIEnv* env, T obj) { env_ = this->SetNewLocalRef(env, obj); }
+  // Deprecated. Don't use bare jobjects; use a JavaRef as the input.
+  // TODO(torne): fix existing usage and remove this.
+  void Reset(JNIEnv* env, T obj) {
+    env_ = JavaRef<T>::SetNewLocalRef(env, obj);
+  }
 
   // Releases the local reference to the caller. The caller *must* delete the
   // local reference when it is done with it. Note that calling a Java method
   // is *not* a transfer of ownership and Release() should not be used.
-  T Release() {
-    return static_cast<T>(this->ReleaseInternal());
-  }
+  T Release() { return static_cast<T>(JavaRef<T>::ReleaseInternal()); }
 
  private:
   // This class is only good for use on the thread it was created on so
   // it's safe to cache the non-threadsafe JNIEnv* inside this object.
-  JNIEnv* env_;
+  JNIEnv* env_ = nullptr;
 
   // Prevent ScopedJavaLocalRef(JNIEnv*, T obj) from being used to take
   // ownership of a JavaParamRef's underlying object - parameters are not
@@ -225,58 +279,112 @@
   // TODO(torne): this can be removed once JavaParamRef no longer has an
   // implicit conversion back to T.
   ScopedJavaLocalRef(JNIEnv* env, const JavaParamRef<T>& other);
+
+  // Friend required to get env_ from conversions.
+  template <typename U>
+  friend class ScopedJavaLocalRef;
 };
 
 // Holds a global reference to a Java object. The global reference is scoped
 // to the lifetime of this object. This class does not hold onto any JNIEnv*
 // passed to it, hence it is safe to use across threads (within the constraints
 // imposed by the underlying Java object that it references).
-template<typename T>
+template <typename T>
 class ScopedJavaGlobalRef : public JavaRef<T> {
  public:
   constexpr ScopedJavaGlobalRef() {}
   constexpr ScopedJavaGlobalRef(std::nullptr_t) {}
 
-  ScopedJavaGlobalRef(const ScopedJavaGlobalRef<T>& other) {
-    this->Reset(other);
+  // Copy constructor. This is required in addition to the copy conversion
+  // constructor below.
+  ScopedJavaGlobalRef(const ScopedJavaGlobalRef& other) { Reset(other); }
+
+  // Copy conversion constructor.
+  template <typename U,
+            typename = std::enable_if_t<std::is_convertible<U, T>::value>>
+  ScopedJavaGlobalRef(const ScopedJavaGlobalRef<U>& other) {
+    Reset(other);
   }
 
-  ScopedJavaGlobalRef(ScopedJavaGlobalRef<T>&& other) { this->swap(other); }
-
-  ScopedJavaGlobalRef(JNIEnv* env, T obj) { this->Reset(env, obj); }
-
-  explicit ScopedJavaGlobalRef(const JavaRef<T>& other) { this->Reset(other); }
-
-  ~ScopedJavaGlobalRef() {
-    this->Reset();
+  // Move constructor. This is required in addition to the move conversion
+  // constructor below.
+  ScopedJavaGlobalRef(ScopedJavaGlobalRef&& other) {
+    JavaRef<T>::steal(std::move(other));
   }
 
-  // Overloaded assignment operator defined for consistency with the implicit
-  // copy constructor.
-  void operator=(const ScopedJavaGlobalRef<T>& other) {
-    this->Reset(other);
+  // Move conversion constructor.
+  template <typename U,
+            typename = std::enable_if_t<std::is_convertible<U, T>::value>>
+  ScopedJavaGlobalRef(ScopedJavaGlobalRef<U>&& other) {
+    JavaRef<T>::steal(std::move(other));
   }
 
-  void operator=(ScopedJavaGlobalRef<T>&& other) { this->swap(other); }
+  // Conversion constructor for other JavaRef types.
+  explicit ScopedJavaGlobalRef(const JavaRef<T>& other) { Reset(other); }
 
-  void Reset() {
-    this->ResetGlobalRef();
+  // Create a new global reference to the object.
+  // Deprecated. Don't use bare jobjects; use a JavaRef as the input.
+  ScopedJavaGlobalRef(JNIEnv* env, T obj) { Reset(env, obj); }
+
+  ~ScopedJavaGlobalRef() { Reset(); }
+
+  // Null assignment, for disambiguation.
+  ScopedJavaGlobalRef& operator=(std::nullptr_t) {
+    Reset();
+    return *this;
   }
 
-  void Reset(const JavaRef<T>& other) { this->Reset(nullptr, other.obj()); }
+  // Copy assignment.
+  ScopedJavaGlobalRef& operator=(const ScopedJavaGlobalRef& other) {
+    Reset(other);
+    return *this;
+  }
 
+  // Copy conversion assignment.
+  template <typename U,
+            typename = std::enable_if_t<std::is_convertible<U, T>::value>>
+  ScopedJavaGlobalRef& operator=(const ScopedJavaGlobalRef<U>& other) {
+    Reset(other);
+    return *this;
+  }
+
+  // Move assignment.
+  template <typename U,
+            typename = std::enable_if_t<std::is_convertible<U, T>::value>>
+  ScopedJavaGlobalRef& operator=(ScopedJavaGlobalRef<U>&& other) {
+    Reset();
+    JavaRef<T>::steal(std::move(other));
+    return *this;
+  }
+
+  // Assignment for other JavaRef types.
+  ScopedJavaGlobalRef& operator=(const JavaRef<T>& other) {
+    Reset(other);
+    return *this;
+  }
+
+  void Reset() { JavaRef<T>::ResetGlobalRef(); }
+
+  template <typename U,
+            typename = std::enable_if_t<std::is_convertible<U, T>::value>>
+  void Reset(const ScopedJavaGlobalRef<U>& other) {
+    Reset(nullptr, other.obj());
+  }
+
+  void Reset(const JavaRef<T>& other) { Reset(nullptr, other.obj()); }
+
+  // Deprecated. You can just use Reset(const JavaRef&).
   void Reset(JNIEnv* env, const JavaParamRef<T>& other) {
-    this->Reset(env, other.obj());
+    Reset(env, other.obj());
   }
 
-  void Reset(JNIEnv* env, T obj) { this->SetNewGlobalRef(env, obj); }
+  // Deprecated. Don't use bare jobjects; use a JavaRef as the input.
+  void Reset(JNIEnv* env, T obj) { JavaRef<T>::SetNewGlobalRef(env, obj); }
 
   // Releases the global reference to the caller. The caller *must* delete the
   // global reference when it is done with it. Note that calling a Java method
   // is *not* a transfer of ownership and Release() should not be used.
-  T Release() {
-    return static_cast<T>(this->ReleaseInternal());
-  }
+  T Release() { return static_cast<T>(JavaRef<T>::ReleaseInternal()); }
 };
 
 }  // namespace android
diff --git a/base/android/scoped_java_ref_unittest.cc b/base/android/scoped_java_ref_unittest.cc
index 99d035b..d46c5ac 100644
--- a/base/android/scoped_java_ref_unittest.cc
+++ b/base/android/scoped_java_ref_unittest.cc
@@ -8,6 +8,9 @@
 #include "base/android/jni_string.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#define EXPECT_SAME_OBJECT(a, b) \
+  EXPECT_TRUE(env->IsSameObject(a.obj(), b.obj()))
+
 namespace base {
 namespace android {
 
@@ -69,13 +72,110 @@
   JNIEnv* env = AttachCurrentThread();
   ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(env, "string");
   ScopedJavaGlobalRef<jstring> global(str);
+
+  // Contextual conversions to bool should be allowed.
+  EXPECT_TRUE(str);
+  EXPECT_FALSE(JavaRef<jobject>());
+
+  // All the types should convert from nullptr, even JavaRef.
+  {
+    JavaRef<jstring> null_ref(nullptr);
+    EXPECT_FALSE(null_ref);
+    ScopedJavaLocalRef<jobject> null_local(nullptr);
+    EXPECT_FALSE(null_local);
+    ScopedJavaGlobalRef<jarray> null_global(nullptr);
+    EXPECT_FALSE(null_global);
+  }
+
+  // Local and global refs should {copy,move}-{construct,assign}.
+  // Moves should leave the source as null.
+  {
+    ScopedJavaLocalRef<jstring> str2(str);
+    EXPECT_SAME_OBJECT(str2, str);
+    ScopedJavaLocalRef<jstring> str3(std::move(str2));
+    EXPECT_SAME_OBJECT(str3, str);
+    EXPECT_FALSE(str2);
+    ScopedJavaLocalRef<jstring> str4;
+    str4 = str;
+    EXPECT_SAME_OBJECT(str4, str);
+    ScopedJavaLocalRef<jstring> str5;
+    str5 = std::move(str4);
+    EXPECT_SAME_OBJECT(str5, str);
+    EXPECT_FALSE(str4);
+  }
+  {
+    ScopedJavaGlobalRef<jstring> str2(global);
+    EXPECT_SAME_OBJECT(str2, str);
+    ScopedJavaGlobalRef<jstring> str3(std::move(str2));
+    EXPECT_SAME_OBJECT(str3, str);
+    EXPECT_FALSE(str2);
+    ScopedJavaGlobalRef<jstring> str4;
+    str4 = global;
+    EXPECT_SAME_OBJECT(str4, str);
+    ScopedJavaGlobalRef<jstring> str5;
+    str5 = std::move(str4);
+    EXPECT_SAME_OBJECT(str5, str);
+    EXPECT_FALSE(str4);
+  }
+
+  // As above but going from jstring to jobject.
+  {
+    ScopedJavaLocalRef<jobject> obj2(str);
+    EXPECT_SAME_OBJECT(obj2, str);
+    ScopedJavaLocalRef<jobject> obj3(std::move(obj2));
+    EXPECT_SAME_OBJECT(obj3, str);
+    EXPECT_FALSE(obj2);
+    ScopedJavaLocalRef<jobject> obj4;
+    obj4 = str;
+    EXPECT_SAME_OBJECT(obj4, str);
+    ScopedJavaLocalRef<jobject> obj5;
+    obj5 = std::move(obj4);
+    EXPECT_SAME_OBJECT(obj5, str);
+    EXPECT_FALSE(obj4);
+  }
+  {
+    ScopedJavaGlobalRef<jobject> obj2(global);
+    EXPECT_SAME_OBJECT(obj2, str);
+    ScopedJavaGlobalRef<jobject> obj3(std::move(obj2));
+    EXPECT_SAME_OBJECT(obj3, str);
+    EXPECT_FALSE(obj2);
+    ScopedJavaGlobalRef<jobject> obj4;
+    obj4 = global;
+    EXPECT_SAME_OBJECT(obj4, str);
+    ScopedJavaGlobalRef<jobject> obj5;
+    obj5 = std::move(obj4);
+    EXPECT_SAME_OBJECT(obj5, str);
+    EXPECT_FALSE(obj4);
+  }
+
+  // Explicit copy construction or assignment between global<->local is allowed,
+  // but not implicit conversions.
+  {
+    ScopedJavaLocalRef<jstring> new_local(global);
+    EXPECT_SAME_OBJECT(new_local, str);
+    new_local = global;
+    EXPECT_SAME_OBJECT(new_local, str);
+    ScopedJavaGlobalRef<jstring> new_global(str);
+    EXPECT_SAME_OBJECT(new_global, str);
+    new_global = str;
+    EXPECT_SAME_OBJECT(new_local, str);
+    static_assert(!std::is_convertible<ScopedJavaLocalRef<jobject>,
+                                       ScopedJavaGlobalRef<jobject>>::value,
+                  "");
+    static_assert(!std::is_convertible<ScopedJavaGlobalRef<jobject>,
+                                       ScopedJavaLocalRef<jobject>>::value,
+                  "");
+  }
+
+  // Converting between local/global while also converting to jobject also works
+  // because JavaRef<jobject> is the base class.
   {
     ScopedJavaGlobalRef<jobject> global_obj(str);
     ScopedJavaLocalRef<jobject> local_obj(global);
     const JavaRef<jobject>& obj_ref1(str);
     const JavaRef<jobject>& obj_ref2(global);
-    EXPECT_TRUE(env->IsSameObject(obj_ref1.obj(), obj_ref2.obj()));
-    EXPECT_TRUE(env->IsSameObject(global_obj.obj(), obj_ref2.obj()));
+    EXPECT_SAME_OBJECT(obj_ref1, obj_ref2);
+    EXPECT_SAME_OBJECT(global_obj, obj_ref2);
   }
   global.Reset(str);
   const JavaRef<jstring>& str_ref = str;
@@ -99,7 +199,7 @@
     EXPECT_EQ(1, g_local_refs);
     EXPECT_EQ(2, g_global_refs);
 
-    ScopedJavaLocalRef<jstring> str2(env, str.Release());
+    auto str2 = ScopedJavaLocalRef<jstring>::Adopt(env, str.Release());
     EXPECT_EQ(1, g_local_refs);
     {
       ScopedJavaLocalRef<jstring> str3(str2);
diff --git a/base/bind_unittest.cc b/base/bind_unittest.cc
index 36b45f7..3aec7aa 100644
--- a/base/bind_unittest.cc
+++ b/base/bind_unittest.cc
@@ -1091,7 +1091,7 @@
   using MoveOnlyVector = std::vector<std::unique_ptr<int>>;
 
   MoveOnlyVector v;
-  v.push_back(WrapUnique(new int(12345)));
+  v.push_back(std::make_unique<int>(12345));
 
   // Early binding should work:
   base::Callback<MoveOnlyVector()> bound_cb =
diff --git a/base/cancelable_callback_unittest.cc b/base/cancelable_callback_unittest.cc
index 373498c..42e753f 100644
--- a/base/cancelable_callback_unittest.cc
+++ b/base/cancelable_callback_unittest.cc
@@ -9,7 +9,6 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/location.h"
-#include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
@@ -198,7 +197,7 @@
   int result = 0;
   CancelableCallback<void(std::unique_ptr<int>)> cb(
       base::Bind(&OnMoveOnlyReceived, base::Unretained(&result)));
-  cb.callback().Run(base::WrapUnique(new int(kExpectedResult)));
+  cb.callback().Run(std::make_unique<int>(kExpectedResult));
 
   EXPECT_EQ(kExpectedResult, result);
 }
diff --git a/base/containers/README.md b/base/containers/README.md
index e521881..e788262 100644
--- a/base/containers/README.md
+++ b/base/containers/README.md
@@ -65,7 +65,7 @@
 | `base::flat_map`, `base::flat_set`         | 24 bytes              | 0 (see notes)     | No                |
 | `base::small_map`                          | 24 bytes (see notes)  | 32 bytes          | No                |
 
-**Takeaways:** `std::unordered_map` and `std::unordered_map` have high
+**Takeaways:** `std::unordered_map` and `std::unordered_set` have high
 overhead for small container sizes, so prefer these only for larger workloads.
 
 Code size comparisons for a block of code (see appendix) on Windows using
diff --git a/base/containers/mru_cache_unittest.cc b/base/containers/mru_cache_unittest.cc
index 8de235a..30ebc2c 100644
--- a/base/containers/mru_cache_unittest.cc
+++ b/base/containers/mru_cache_unittest.cc
@@ -7,7 +7,6 @@
 #include <cstddef>
 #include <memory>
 
-#include "base/memory/ptr_util.h"
 #include "base/trace_event/memory_usage_estimator.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -200,8 +199,8 @@
 
   // First insert and item and then overwrite it.
   static const int kItem1Key = 1;
-  cache.Put(kItem1Key, WrapUnique(new CachedItem(20)));
-  cache.Put(kItem1Key, WrapUnique(new CachedItem(22)));
+  cache.Put(kItem1Key, std::make_unique<CachedItem>(20));
+  cache.Put(kItem1Key, std::make_unique<CachedItem>(22));
 
   // There should still be one item, and one extra live item.
   auto iter = cache.Get(kItem1Key);
@@ -217,8 +216,8 @@
   // go away.
   {
     Cache cache2(Cache::NO_AUTO_EVICT);
-    cache2.Put(1, WrapUnique(new CachedItem(20)));
-    cache2.Put(2, WrapUnique(new CachedItem(20)));
+    cache2.Put(1, std::make_unique<CachedItem>(20));
+    cache2.Put(2, std::make_unique<CachedItem>(20));
   }
 
   // There should be no objects leaked.
@@ -227,8 +226,8 @@
   // Check that Clear() also frees things correctly.
   {
     Cache cache2(Cache::NO_AUTO_EVICT);
-    cache2.Put(1, WrapUnique(new CachedItem(20)));
-    cache2.Put(2, WrapUnique(new CachedItem(20)));
+    cache2.Put(1, std::make_unique<CachedItem>(20));
+    cache2.Put(2, std::make_unique<CachedItem>(20));
     EXPECT_EQ(initial_count + 2, cached_item_live_count);
     cache2.Clear();
     EXPECT_EQ(initial_count, cached_item_live_count);
diff --git a/base/containers/vector_buffer.h b/base/containers/vector_buffer.h
index ea1caa3..7710ecb 100644
--- a/base/containers/vector_buffer.h
+++ b/base/containers/vector_buffer.h
@@ -50,7 +50,7 @@
 #endif
   VectorBuffer(size_t count)
       : buffer_(reinterpret_cast<T*>(
-            SbMemoryAllocate(CheckMul(sizeof(T), count).ValueOrDie()))),
+            malloc(CheckMul(sizeof(T), count).ValueOrDie()))),
         capacity_(count) {
   }
   VectorBuffer(VectorBuffer&& other) noexcept
@@ -59,10 +59,10 @@
     other.capacity_ = 0;
   }
 
-  ~VectorBuffer() { SbMemoryDeallocate(buffer_); }
+  ~VectorBuffer() { free(buffer_); }
 
   VectorBuffer& operator=(VectorBuffer&& other) {
-    SbMemoryDeallocate(buffer_);
+    free(buffer_);
     buffer_ = other.buffer_;
     capacity_ = other.capacity_;
 
diff --git a/base/debug/activity_analyzer.cc b/base/debug/activity_analyzer.cc
index b3bb730..b383790 100644
--- a/base/debug/activity_analyzer.cc
+++ b/base/debug/activity_analyzer.cc
@@ -12,7 +12,6 @@
 #include "base/files/memory_mapped_file.h"
 #include "base/lazy_instance.h"
 #include "base/logging.h"
-#include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/stl_util.h"
 #include "base/strings/string_util.h"
@@ -108,7 +107,7 @@
     return nullptr;
   }
 
-  return WrapUnique(new GlobalActivityAnalyzer(std::move(allocator)));
+  return std::make_unique<GlobalActivityAnalyzer>(std::move(allocator));
 }
 
 #if !defined(OS_NACL)
diff --git a/base/debug/thread_heap_usage_tracker_unittest.cc b/base/debug/thread_heap_usage_tracker_unittest.cc
index baed91e..5fa9c3d 100644
--- a/base/debug/thread_heap_usage_tracker_unittest.cc
+++ b/base/debug/thread_heap_usage_tracker_unittest.cc
@@ -137,7 +137,7 @@
                          void* context) {
     EXPECT_EQ(&g_mock_dispatch, self);
 
-    void* ret = SbMemoryAllocate(size);
+    void* ret = malloc(size);
     g_self->RecordAlloc(ret, size);
     return ret;
   }
@@ -161,7 +161,7 @@
 
     // This is a cheat as it doesn't return aligned allocations. This has the
     // advantage of working for all platforms for this test.
-    void* ret = SbMemoryAllocate(size);
+    void* ret = malloc(size);
     g_self->RecordAlloc(ret, size);
     return ret;
   }
@@ -173,7 +173,7 @@
     EXPECT_EQ(&g_mock_dispatch, self);
 
     g_self->DeleteAlloc(address);
-    void* ret = SbMemoryReallocate(address, size);
+    void* ret = realloc(address, size);
     g_self->RecordAlloc(ret, size);
     return ret;
   }
@@ -184,7 +184,7 @@
     EXPECT_EQ(&g_mock_dispatch, self);
 
     g_self->DeleteAlloc(address);
-    SbMemoryDeallocate(address);
+    free(address);
   }
 
   static size_t OnGetSizeEstimateFn(const AllocatorDispatch* self,
@@ -576,12 +576,12 @@
   usage_tracker.Start();
 
   ThreadHeapUsage u1 = ThreadHeapUsageTracker::GetUsageSnapshot();
-  void* ptr = SbMemoryAllocate(kAllocSize);
+  void* ptr = malloc(kAllocSize);
   // Prevent the compiler from optimizing out the malloc/free pair.
   ASSERT_NE(nullptr, ptr);
 
   ThreadHeapUsage u2 = ThreadHeapUsageTracker::GetUsageSnapshot();
-  SbMemoryDeallocate(ptr);
+  free(ptr);
 
   usage_tracker.Stop(false);
   ThreadHeapUsage u3 = usage_tracker.usage();
diff --git a/base/feature_list_unittest.cc b/base/feature_list_unittest.cc
index aae1bc6..79f52ca 100644
--- a/base/feature_list_unittest.cc
+++ b/base/feature_list_unittest.cc
@@ -9,7 +9,6 @@
 
 #include "base/format_macros.h"
 #include "base/macros.h"
-#include "base/memory/ptr_util.h"
 #include "base/metrics/field_trial.h"
 #include "base/metrics/persistent_memory_allocator.h"
 #include "base/strings/string_piece.h"
@@ -44,7 +43,7 @@
 class FeatureListTest : public testing::Test {
  public:
   FeatureListTest() : feature_list_(nullptr) {
-    RegisterFeatureListInstance(WrapUnique(new FeatureList));
+    RegisterFeatureListInstance(std::make_unique<FeatureList>());
   }
   ~FeatureListTest() override { ClearFeatureListInstance(); }
 
diff --git a/base/files/file_util.cc b/base/files/file_util.cc
index befe725..7ba55fe 100644
--- a/base/files/file_util.cc
+++ b/base/files/file_util.cc
@@ -94,7 +94,7 @@
     file2.read(buffer2, BUFFER_SIZE);
 
     if ((file1.eof() != file2.eof()) || (file1.gcount() != file2.gcount()) ||
-        (SbMemoryCompare(buffer1, buffer2,
+        (memcmp(buffer1, buffer2,
                          static_cast<size_t>(file1.gcount())))) {
       file1.close();
       file2.close();
diff --git a/base/files/file_util_unittest.cc b/base/files/file_util_unittest.cc
index 44570e2..293d4ee 100644
--- a/base/files/file_util_unittest.cc
+++ b/base/files/file_util_unittest.cc
@@ -2396,7 +2396,7 @@
   // Restore the original $TMP.
   if (original_tmp) {
     ::_tputenv_s(kTmpKey, original_tmp);
-    SbMemoryDeallocate(original_tmp);
+    free(original_tmp);
   } else {
     ::_tputenv_s(kTmpKey, _T(""));
   }
diff --git a/base/i18n/base_i18n_export.h b/base/i18n/base_i18n_export.h
index e8a2add..ef498e9 100644
--- a/base/i18n/base_i18n_export.h
+++ b/base/i18n/base_i18n_export.h
@@ -5,7 +5,11 @@
 #ifndef BASE_I18N_BASE_I18N_EXPORT_H_
 #define BASE_I18N_BASE_I18N_EXPORT_H_
 
-#if defined(COMPONENT_BUILD)
+#ifdef USE_COBALT_CUSTOMIZATIONS
+#include "starboard/configuration.h"
+#endif // USE_COBALT_CUSTOMIZATIONS
+
+#if defined(COMPONENT_BUILD) || SB_IS(MODULAR) && !SB_IS(EVERGREEN)
 #if defined(WIN32)
 
 #if defined(BASE_I18N_IMPLEMENTATION)
@@ -22,7 +26,7 @@
 #endif
 #endif
 
-#else  // defined(COMPONENT_BUILD)
+#else  // defined(COMPONENT_BUILD) || SB_IS(MODULAR) && !SB_IS(EVERGREEN)
 #define BASE_I18N_EXPORT
 #endif
 
diff --git a/base/logging.cc b/base/logging.cc
index 809e141..5a159ff 100644
--- a/base/logging.cc
+++ b/base/logging.cc
@@ -478,9 +478,15 @@
     case LOG_ERROR:
       return kSbLogPriorityError;
     case LOG_FATAL:
-    case LOG_VERBOSE:
       return kSbLogPriorityFatal;
+    case LOG_VERBOSE:
     default:
+      if (level <= LOG_VERBOSE) {
+        // Verbose level can be any negative integer, sanity check its range to
+        // filter out potential errors.
+        DCHECK_GE(level, -256);
+        return kSbLogPriorityInfo;
+      }
       NOTREACHED() << "Unrecognized log level.";
       return kSbLogPriorityInfo;
   }
diff --git a/base/mac/sdk_forward_declarations.h b/base/mac/sdk_forward_declarations.h
index eebf55c..c5b52c5 100644
--- a/base/mac/sdk_forward_declarations.h
+++ b/base/mac/sdk_forward_declarations.h
@@ -360,10 +360,4 @@
 // ----------------------------------------------------------------------------
 BASE_EXPORT extern "C" NSString* const kCWSSIDDidChangeNotification;
 
-// Once Chrome is built with at least the macOS 10.13 SDK, everything within
-// this preprocessor block can be removed.
-#if !defined(MAC_OS_X_VERSION_10_13)
-typedef NSString* NSTextCheckingOptionKey;
-#endif
-
 #endif  // BASE_MAC_SDK_FORWARD_DECLARATIONS_H_
diff --git a/base/macros.h b/base/macros.h
index 93d3fc3..a3edaa6 100644
--- a/base/macros.h
+++ b/base/macros.h
@@ -81,6 +81,9 @@
 //   static base::NoDestructor<Factory> instance;
 //   return *instance;
 // }
+//
+// Removal of this macro is tracked in https://crbug.com/893317.
+//
 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 #define CR_DEFINE_STATIC_LOCAL(type, name, arguments) \
   static type& name = *new type arguments
diff --git a/base/memory/aligned_memory.cc b/base/memory/aligned_memory.cc
index 9a6d88f..6cf239f 100644
--- a/base/memory/aligned_memory.cc
+++ b/base/memory/aligned_memory.cc
@@ -20,9 +20,7 @@
   DCHECK_EQ(alignment & (alignment - 1), 0U);
   DCHECK_EQ(alignment % sizeof(void*), 0U);
   void* ptr = nullptr;
-#if defined(STARBOARD)
-  ptr = SbMemoryAllocateAligned(alignment, size);
-#elif defined(COMPILER_MSVC)
+#if defined(COMPILER_MSVC)
   ptr = _aligned_malloc(size, alignment);
 // Android technically supports posix_memalign(), but does not expose it in
 // the current version of the library headers used by Chrome.  Luckily,
diff --git a/base/memory/aligned_memory.h b/base/memory/aligned_memory.h
index 943692c..224f1fb 100644
--- a/base/memory/aligned_memory.h
+++ b/base/memory/aligned_memory.h
@@ -119,15 +119,11 @@
 BASE_EXPORT void* AlignedAlloc(size_t size, size_t alignment);
 
 inline void AlignedFree(void* ptr) {
-#if defined(STARBOARD)
-  SbMemoryDeallocateAligned(ptr);
-#else
 #if defined(COMPILER_MSVC)
   _aligned_free(ptr);
 #else
   free(ptr);
 #endif
-#endif  // defined(STARBOARD)
 }
 
 // Deleter for use with unique_ptr. E.g., use as
diff --git a/base/memory/free_deleter.h b/base/memory/free_deleter.h
index 0378c0c..91649a4 100644
--- a/base/memory/free_deleter.h
+++ b/base/memory/free_deleter.h
@@ -18,7 +18,7 @@
 // std::unique_ptr<int, base::FreeDeleter> foo_ptr(
 //     static_cast<int*>(malloc(sizeof(int))));
 struct FreeDeleter {
-  inline void operator()(void* ptr) const { SbMemoryDeallocate(ptr); }
+  inline void operator()(void* ptr) const { free(ptr); }
 };
 
 }  // namespace base
diff --git a/base/memory/platform_shared_memory_region_fuchsia.cc b/base/memory/platform_shared_memory_region_fuchsia.cc
index 6e72d5e..378877f 100644
--- a/base/memory/platform_shared_memory_region_fuchsia.cc
+++ b/base/memory/platform_shared_memory_region_fuchsia.cc
@@ -142,7 +142,8 @@
                                      "lead to this region being non-modifiable";
 
   zx::vmo vmo;
-  zx_status_t status = zx::vmo::create(rounded_size, 0, &vmo);
+  zx_status_t status =
+      zx::vmo::create(rounded_size, ZX_VMO_NON_RESIZABLE, &vmo);
   if (status != ZX_OK) {
     ZX_DLOG(ERROR, status) << "zx_vmo_create";
     return {};
diff --git a/base/memory/shared_memory_fuchsia.cc b/base/memory/shared_memory_fuchsia.cc
index a79d937..ffad5b2 100644
--- a/base/memory/shared_memory_fuchsia.cc
+++ b/base/memory/shared_memory_fuchsia.cc
@@ -54,7 +54,8 @@
   requested_size_ = options.size;
   mapped_size_ = bits::Align(requested_size_, GetPageSize());
   zx::vmo vmo;
-  zx_status_t status = zx::vmo::create(mapped_size_, 0, &vmo);
+  zx_status_t status =
+      zx::vmo::create(mapped_size_, ZX_VMO_NON_RESIZABLE, &vmo);
   if (status != ZX_OK) {
     ZX_DLOG(ERROR, status) << "zx_vmo_create";
     return false;
diff --git a/base/memory/shared_memory_region_unittest.cc b/base/memory/shared_memory_region_unittest.cc
index 1098360..d68177e 100644
--- a/base/memory/shared_memory_region_unittest.cc
+++ b/base/memory/shared_memory_region_unittest.cc
@@ -133,7 +133,7 @@
 
   // Verify that the second mapping reflects changes in the first.
   memset(this->rw_mapping_.memory(), '#', kRegionSize);
-  EXPECT_EQ(SbMemoryCompare(this->rw_mapping_.memory(), mapping.memory(),
+  EXPECT_EQ(memcmp(this->rw_mapping_.memory(), mapping.memory(),
                             kRegionSize),
             0);
 }
diff --git a/base/memory/shared_memory_win_unittest.cc b/base/memory/shared_memory_win_unittest.cc
index 6c869ac..0857284 100644
--- a/base/memory/shared_memory_win_unittest.cc
+++ b/base/memory/shared_memory_win_unittest.cc
@@ -92,7 +92,7 @@
   uint32_t handle_as_int = base::win::HandleToUint32(handle);
 
   std::unique_ptr<char, base::FreeDeleter> buffer(
-      static_cast<char*>(SbMemoryAllocate(1000)));
+      static_cast<char*>(malloc(1000)));
   size_t index = 0;
   while (handle_as_int > 0) {
     buffer.get()[index] = handle_as_int % 10;
diff --git a/base/metrics/bucket_ranges.cc b/base/metrics/bucket_ranges.cc
index 2723c3e..39b3793 100644
--- a/base/metrics/bucket_ranges.cc
+++ b/base/metrics/bucket_ranges.cc
@@ -74,8 +74,7 @@
 // the CRC correct for big-endian vs little-ending calculations.  All we need is
 // a nice hash, that tends to depend on all the bits of the sample, with very
 // little chance of changes in one place impacting changes in another place.
-// Temporary non-static for https://crbug.com/836238
-/*static*/ uint32_t Crc32(uint32_t sum, HistogramBase::Sample value) {
+static uint32_t Crc32(uint32_t sum, HistogramBase::Sample value) {
   union {
     HistogramBase::Sample range;
     unsigned char bytes[sizeof(HistogramBase::Sample)];
diff --git a/base/metrics/bucket_ranges.h b/base/metrics/bucket_ranges.h
index 837cc93..e1dc09d 100644
--- a/base/metrics/bucket_ranges.h
+++ b/base/metrics/bucket_ranges.h
@@ -97,7 +97,6 @@
 //////////////////////////////////////////////////////////////////////////////
 // Expose only for test.
 BASE_EXPORT extern const uint32_t kCrcTable[256];
-uint32_t Crc32(uint32_t sum, HistogramBase::Sample value);
 
 }  // namespace base
 
diff --git a/base/metrics/histogram.cc b/base/metrics/histogram.cc
index 82a3c86..ef56619 100644
--- a/base/metrics/histogram.cc
+++ b/base/metrics/histogram.cc
@@ -158,8 +158,6 @@
 
 HistogramBase* Histogram::Factory::Build() {
   HistogramBase* histogram = StatisticsRecorder::FindHistogram(name_);
-  const bool found = (histogram != nullptr);
-  debug::Alias(&found);
   if (!histogram) {
     // TODO(gayane): |HashMetricName()| is called again in Histogram
     // constructor. Refactor code to avoid the additional call.
@@ -169,99 +167,10 @@
       return DummyHistogram::GetInstance();
     // To avoid racy destruction at shutdown, the following will be leaked.
     const BucketRanges* created_ranges = CreateRanges();
-    CHECK(created_ranges->HasValidChecksum()) << name_;
-
-// Temporary check for https://crbug.com/836238
-#if defined(OS_WIN)  // Only Windows has a debugger that makes this useful.
-    if (bucket_count_ > 0 &&
-        maximum_ != created_ranges->range(bucket_count_ - 1)) {
-      // Create local copies of the parameters to be sure they'll be available
-      // in the crash dump for the debugger to see.
-      DEBUG_ALIAS_FOR_CSTR(h_name, name_.c_str(), 100);
-      HistogramType h_type = histogram_type_;
-      Sample h_min = minimum_;
-      Sample h_max = maximum_;
-      uint32_t h_count = bucket_count_;
-      debug::Alias(&h_type);
-      debug::Alias(&h_min);
-      debug::Alias(&h_max);
-      debug::Alias(&h_count);
-      uint32_t ranges_min = created_ranges->range(1);
-      uint32_t ranges_max = created_ranges->range(bucket_count_ - 1);
-      debug::Alias(&ranges_min);
-      debug::Alias(&ranges_max);
-      CHECK(false) << name_;
-    }
-#endif
-
-// Temporary check for https://crbug.com/836238
-#if defined(OS_WIN)  // Only Windows has a debugger that makes this useful.
-    std::unique_ptr<const BucketRanges> recreated_ranges(CreateRanges());
-    for (uint32_t i = 0; i < bucket_count_; ++i) {
-      uint32_t created_range = created_ranges->range(i);
-      uint32_t recreated_range = recreated_ranges->range(i);
-      debug::Alias(&created_range);
-      debug::Alias(&recreated_range);
-      if (created_range != recreated_range) {
-        // Create local copies of the parameters to be sure they'll be available
-        // in the crash dump for the debugger to see.
-        DEBUG_ALIAS_FOR_CSTR(h_name, name_.c_str(), 100);
-        HistogramType h_type = histogram_type_;
-        uint32_t b_count = bucket_count_;
-        size_t c_count = created_ranges->size() - 1;
-        size_t r_count = recreated_ranges->size() - 1;
-        bool c_valid = created_ranges->HasValidChecksum();
-        bool r_valid = recreated_ranges->HasValidChecksum();
-        CHECK(recreated_ranges->Equals(created_ranges)) << name_;
-        debug::Alias(&h_type);
-        debug::Alias(&b_count);
-        debug::Alias(&c_count);
-        debug::Alias(&r_count);
-        debug::Alias(&c_valid);
-        debug::Alias(&r_valid);
-        CHECK(false) << name_;
-      }
-    }
-    CHECK(recreated_ranges->Equals(created_ranges));
-#endif
 
     const BucketRanges* registered_ranges =
         StatisticsRecorder::RegisterOrDeleteDuplicateRanges(created_ranges);
 
-// Temporary check for https://crbug.com/836238
-#if defined(OS_WIN)  // Only Windows has a debugger that makes this useful.
-    bool using_created_ranges = (registered_ranges == created_ranges);
-    bool equal_ranges = registered_ranges->Equals(recreated_ranges.get());
-    debug::Alias(&using_created_ranges);
-    debug::Alias(&equal_ranges);
-    for (uint32_t i = 0; i < bucket_count_; ++i) {
-      uint32_t created_range = recreated_ranges->range(i);
-      uint32_t registered_range = registered_ranges->range(i);
-      debug::Alias(&created_range);
-      debug::Alias(&registered_range);
-      if (created_range != registered_range) {
-        // Create local copies of the parameters to be sure they'll be available
-        // in the crash dump for the debugger to see.
-        DEBUG_ALIAS_FOR_CSTR(h_name, name_.c_str(), 100);
-        HistogramType h_type = histogram_type_;
-        uint32_t b_count = bucket_count_;
-        size_t c_count = recreated_ranges->size() - 1;
-        size_t r_count = registered_ranges->size() - 1;
-        bool c_valid = recreated_ranges->HasValidChecksum();
-        bool r_valid = registered_ranges->HasValidChecksum();
-        CHECK(recreated_ranges->Equals(registered_ranges)) << name_;
-        debug::Alias(&h_type);
-        debug::Alias(&b_count);
-        debug::Alias(&c_count);
-        debug::Alias(&r_count);
-        debug::Alias(&c_valid);
-        debug::Alias(&r_valid);
-        CHECK(false) << name_;
-      }
-    }
-    CHECK(recreated_ranges->Equals(registered_ranges));
-#endif
-
     // In most cases, the bucket-count, minimum, and maximum values are known
     // when the code is written and so are passed in explicitly. In other
     // cases (such as with a CustomHistogram), they are calculated dynamically
@@ -333,43 +242,6 @@
                        static_cast<Sample>(HashMetricName(name_)));
     DLOG(ERROR) << "Histogram " << name_
                 << " has mismatched construction arguments";
-
-// crbug.com/836238: Temporarily create crashes for this condition in order
-// to find out why it is happening for many metrics that are hard-coded and
-// thus should never have a mismatch.
-// TODO(bcwhite): Revert this once some crashes have been collected.
-#if defined(OS_WIN)  // Only Windows has a debugger that makes this useful.
-    // Don't crash for linear histograms as these have never shown an error
-    // but mismitches still occur because of extensions that have different
-    // enumeration lists than what is inside Chrome. Continue to return the
-    // "dummy" histogram (below) instead.
-    if (histogram_type_ != LINEAR_HISTOGRAM) {
-      // Create local copies of the parameters to be sure they'll be available
-      // in the crash dump for the debugger to see.
-      const Histogram* h = static_cast<Histogram*>(histogram);
-      Sample hash_32 = static_cast<Sample>(HashMetricName(name_));
-      debug::Alias(&hash_32);
-      DEBUG_ALIAS_FOR_CSTR(new_name, name_.c_str(), 100);
-      HistogramType new_type = histogram_type_;
-      Sample new_min = minimum_;
-      Sample new_max = maximum_;
-      uint32_t new_count = bucket_count_;
-      debug::Alias(&new_type);
-      debug::Alias(&new_min);
-      debug::Alias(&new_max);
-      debug::Alias(&new_count);
-      DEBUG_ALIAS_FOR_CSTR(old_name, h->histogram_name(), 100);
-      HistogramType old_type = h->GetHistogramType();
-      Sample old_min = h->declared_min();
-      Sample old_max = h->declared_max();
-      uint32_t old_count = h->bucket_count();
-      debug::Alias(&old_type);
-      debug::Alias(&old_min);
-      debug::Alias(&old_max);
-      debug::Alias(&old_count);
-      CHECK(false) << name_;
-    }
-#endif
     return DummyHistogram::GetInstance();
   }
   return histogram;
@@ -467,15 +339,6 @@
   ranges->set_range(bucket_index, current);
   size_t bucket_count = ranges->bucket_count();
 
-  // Temporary for https://crbug.com/836238
-  uint32_t checksum = static_cast<uint32_t>(bucket_count + 1);
-  checksum = Crc32(checksum, 0);
-  checksum = Crc32(checksum, current);
-  debug::Alias(&minimum);
-  debug::Alias(&maximum);
-  debug::Alias(&bucket_count);
-  debug::Alias(&checksum);
-
   while (bucket_count > ++bucket_index) {
     double log_current;
     log_current = log(static_cast<double>(current));
@@ -490,12 +353,9 @@
     else
       ++current;  // Just do a narrow bucket, and keep trying.
     ranges->set_range(bucket_index, current);
-    checksum = Crc32(checksum, current);
   }
   ranges->set_range(ranges->bucket_count(), HistogramBase::kSampleType_MAX);
   ranges->ResetChecksum();
-  checksum = Crc32(checksum, HistogramBase::kSampleType_MAX);
-  CHECK_EQ(checksum, ranges->checksum());
 }
 
 // static
@@ -1126,32 +986,14 @@
   double max = maximum;
   size_t bucket_count = ranges->bucket_count();
 
-  // Temporary for https://crbug.com/836238
-  bool is_enum = (minimum == 1 &&
-                  static_cast<Sample>(bucket_count) == maximum - minimum + 2);
-  uint32_t checksum = static_cast<uint32_t>(bucket_count + 1);
-  checksum = Crc32(checksum, 0);
-  debug::Alias(&minimum);
-  debug::Alias(&maximum);
-  debug::Alias(&min);
-  debug::Alias(&max);
-  debug::Alias(&bucket_count);
-  debug::Alias(&checksum);
-  debug::Alias(&is_enum);
-
   for (size_t i = 1; i < bucket_count; ++i) {
     double linear_range =
         (min * (bucket_count - 1 - i) + max * (i - 1)) / (bucket_count - 2);
     uint32_t range = static_cast<Sample>(linear_range + 0.5);
-    if (is_enum)
-      CHECK_EQ(static_cast<uint32_t>(i), range);
     ranges->set_range(i, range);
-    checksum = Crc32(checksum, range);
   }
   ranges->set_range(ranges->bucket_count(), HistogramBase::kSampleType_MAX);
   ranges->ResetChecksum();
-  checksum = Crc32(checksum, HistogramBase::kSampleType_MAX);
-  CHECK_EQ(checksum, ranges->checksum());
 }
 
 // static
diff --git a/base/metrics/histogram_unittest.cc b/base/metrics/histogram_unittest.cc
index 7c95fac..a976d0a 100644
--- a/base/metrics/histogram_unittest.cc
+++ b/base/metrics/histogram_unittest.cc
@@ -640,7 +640,6 @@
   EXPECT_FALSE(iter.SkipBytes(1));
 }
 
-#if 0  // TODO(crbug.com/836238): Temporarily disabled for field crash test.
 TEST_P(HistogramTest, BadConstruction) {
   HistogramBase* histogram = Histogram::FactoryGet(
       "BadConstruction", 0, 100, 8, HistogramBase::kNoFlags);
@@ -666,7 +665,6 @@
       "BadConstructionLinear", 10, 100, 8, HistogramBase::kNoFlags);
   EXPECT_EQ(DummyHistogram::GetInstance(), bad_histogram);
 }
-#endif
 
 TEST_P(HistogramTest, FactoryTime) {
   const int kTestCreateCount = 1 << 14;  // Must be power-of-2.
diff --git a/base/metrics/persistent_memory_allocator.cc b/base/metrics/persistent_memory_allocator.cc
index 24ac0c0..831a5e7 100644
--- a/base/metrics/persistent_memory_allocator.cc
+++ b/base/metrics/persistent_memory_allocator.cc
@@ -995,7 +995,7 @@
   // achieve the same basic result but the acquired memory has to be
   // explicitly zeroed and thus realized immediately (i.e. all pages are
   // added to the process now istead of only when first accessed).
-  address = SbMemoryAllocate(size);
+  address = malloc(size);
   DPCHECK(address);
   memset(address, 0, size);
   return Memory(address, MEM_MALLOC);
@@ -1006,7 +1006,7 @@
                                                            size_t size,
                                                            MemoryType type) {
   if (type == MEM_MALLOC) {
-    SbMemoryDeallocate(memory);
+    free(memory);
     return;
   }
 
diff --git a/base/metrics/persistent_sample_map.cc b/base/metrics/persistent_sample_map.cc
index f38b9d1..e07b716 100644
--- a/base/metrics/persistent_sample_map.cc
+++ b/base/metrics/persistent_sample_map.cc
@@ -5,7 +5,6 @@
 #include "base/metrics/persistent_sample_map.h"
 
 #include "base/logging.h"
-#include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/persistent_histogram_allocator.h"
 #include "base/numerics/safe_conversions.h"
@@ -154,7 +153,7 @@
   // Have to override "const" in order to make sure all samples have been
   // loaded before trying to iterate over the map.
   const_cast<PersistentSampleMap*>(this)->ImportSamples(-1, true);
-  return WrapUnique(new PersistentSampleMapIterator(sample_counts_));
+  return std::make_unique<PersistentSampleMapIterator>(sample_counts_);
 }
 
 // static
diff --git a/base/metrics/sample_map.cc b/base/metrics/sample_map.cc
index b441afa..f925238 100644
--- a/base/metrics/sample_map.cc
+++ b/base/metrics/sample_map.cc
@@ -5,7 +5,6 @@
 #include "base/metrics/sample_map.h"
 
 #include "base/logging.h"
-#include "base/memory/ptr_util.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/stl_util.h"
 
@@ -106,7 +105,7 @@
 }
 
 std::unique_ptr<SampleCountIterator> SampleMap::Iterator() const {
-  return WrapUnique(new SampleMapIterator(sample_counts_));
+  return std::make_unique<SampleMapIterator>(sample_counts_);
 }
 
 bool SampleMap::AddSubtractImpl(SampleCountIterator* iter, Operator op) {
diff --git a/base/metrics/single_sample_metrics_unittest.cc b/base/metrics/single_sample_metrics_unittest.cc
index cca7514..974ba7e 100644
--- a/base/metrics/single_sample_metrics_unittest.cc
+++ b/base/metrics/single_sample_metrics_unittest.cc
@@ -4,7 +4,6 @@
 
 #include "base/metrics/single_sample_metrics.h"
 
-#include "base/memory/ptr_util.h"
 #include "base/metrics/dummy_histogram.h"
 #include "base/test/gtest_util.h"
 #include "base/test/metrics/histogram_tester.h"
@@ -42,14 +41,14 @@
   EXPECT_EQ(factory, SingleSampleMetricsFactory::Get());
 
   // Setting a factory after the default has been instantiated should fail.
-  EXPECT_DCHECK_DEATH(SingleSampleMetricsFactory::SetFactory(
-      WrapUnique<SingleSampleMetricsFactory>(nullptr)));
+  EXPECT_DCHECK_DEATH(SingleSampleMetricsFactory::SetFactory(nullptr));
 }
 
 TEST_F(SingleSampleMetricsTest, CustomFactoryGetSet) {
-  SingleSampleMetricsFactory* factory = new DefaultSingleSampleMetricsFactory();
-  SingleSampleMetricsFactory::SetFactory(WrapUnique(factory));
-  EXPECT_EQ(factory, SingleSampleMetricsFactory::Get());
+  auto factory = std::make_unique<DefaultSingleSampleMetricsFactory>();
+  SingleSampleMetricsFactory* factory_raw = factory.get();
+  SingleSampleMetricsFactory::SetFactory(std::move(factory));
+  EXPECT_EQ(factory_raw, SingleSampleMetricsFactory::Get());
 }
 
 TEST_F(SingleSampleMetricsTest, DefaultSingleSampleMetricNoValue) {
@@ -81,7 +80,6 @@
   // Verify only the last sample sent to SetSample() is recorded.
   tester.ExpectUniqueSample(kMetricName, kLastSample, 1);
 
-#if 0  // TODO(crbug.com/836238): Temporarily disabled for field crash test.
   // 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.
@@ -91,7 +89,6 @@
   EXPECT_NE(DummyHistogram::GetInstance(),
             Histogram::FactoryGet(kMetricName, kMin, kMax, kBucketCount,
                                   HistogramBase::kUmaTargetedHistogramFlag));
-#endif
 }
 
 TEST_F(SingleSampleMetricsTest, MultipleMetricsAreDistinct) {
diff --git a/base/nix/xdg_util.cc b/base/nix/xdg_util.cc
index 890a7c8..591a4c6 100644
--- a/base/nix/xdg_util.cc
+++ b/base/nix/xdg_util.cc
@@ -46,7 +46,7 @@
   char* xdg_dir = xdg_user_dir_lookup(dir_name);
   if (xdg_dir) {
     path = FilePath(xdg_dir);
-    SbMemoryDeallocate(xdg_dir);
+    free(xdg_dir);
   } else {
     PathService::Get(DIR_HOME, &path);
     path = path.Append(fallback_dir);
diff --git a/base/pickle.cc b/base/pickle.cc
index 5f01ea8..6dc126f 100644
--- a/base/pickle.cc
+++ b/base/pickle.cc
@@ -268,7 +268,7 @@
 
 Pickle::~Pickle() {
   if (capacity_after_header_ != kCapacityReadOnly)
-    SbMemoryDeallocate(header_);
+    free(header_);
 }
 
 Pickle& Pickle::operator=(const Pickle& other) {
@@ -280,7 +280,7 @@
     capacity_after_header_ = 0;
   }
   if (header_size_ != other.header_size_) {
-    SbMemoryDeallocate(header_);
+    free(header_);
     header_ = nullptr;
     header_size_ = other.header_size_;
   }
@@ -339,7 +339,7 @@
 void Pickle::Resize(size_t new_capacity) {
   CHECK_NE(capacity_after_header_, kCapacityReadOnly);
   capacity_after_header_ = bits::Align(new_capacity, kPayloadUnit);
-  void* p = SbMemoryReallocate(header_, GetTotalAllocatedSize());
+  void* p = realloc(header_, GetTotalAllocatedSize());
   CHECK(p);
   header_ = reinterpret_cast<Header*>(p);
 }
diff --git a/base/process/memory_fuchsia.cc b/base/process/memory_fuchsia.cc
index 0a13e55..ac570d1 100644
--- a/base/process/memory_fuchsia.cc
+++ b/base/process/memory_fuchsia.cc
@@ -20,7 +20,7 @@
 }
 
 bool UncheckedMalloc(size_t size, void** result) {
-  *result = SbMemoryAllocate(size);
+  *result = malloc(size);
   return *result != nullptr;
 }
 
diff --git a/base/process/memory_linux.cc b/base/process/memory_linux.cc
index 6e68602..4f58f41 100644
--- a/base/process/memory_linux.cc
+++ b/base/process/memory_linux.cc
@@ -130,7 +130,7 @@
   *result = allocator::UncheckedAlloc(size);
 #elif defined(MEMORY_TOOL_REPLACES_ALLOCATOR) || \
     (!defined(LIBC_GLIBC) && !defined(USE_TCMALLOC))
-  *result = SbMemoryAllocate(size);
+  *result = malloc(size);
 #elif defined(LIBC_GLIBC) && !defined(USE_TCMALLOC)
   *result = __libc_malloc(size);
 #elif defined(USE_TCMALLOC)
diff --git a/base/process/memory_starboard.cc b/base/process/memory_starboard.cc
index 809267c..0167758 100644
--- a/base/process/memory_starboard.cc
+++ b/base/process/memory_starboard.cc
@@ -27,7 +27,7 @@
 }
 
 bool UncheckedMalloc(size_t size, void** result) {
-  *result = SbMemoryAllocate(size);
+  *result = malloc(size);
   return *result != nullptr;
 }
 
diff --git a/base/process/memory_stubs.cc b/base/process/memory_stubs.cc
index 56291ee..e56f72c 100644
--- a/base/process/memory_stubs.cc
+++ b/base/process/memory_stubs.cc
@@ -34,7 +34,7 @@
 // failure to allocate.
 
 bool UncheckedMalloc(size_t size, void** result) {
-  *result = SbMemoryAllocate(size);
+  *result = malloc(size);
   return *result != nullptr;
 }
 
diff --git a/base/process/memory_unittest.cc b/base/process/memory_unittest.cc
index 7c34d73..4aef5b3 100644
--- a/base/process/memory_unittest.cc
+++ b/base/process/memory_unittest.cc
@@ -70,12 +70,12 @@
 #if ARCH_CPU_64_BITS
   // On 64 bit Macs, the malloc system automatically abort()s on heap corruption
   // but does not output anything.
-  ASSERT_DEATH(SbMemoryDeallocate(buf), "");
+  ASSERT_DEATH(free(buf), "");
 #elif defined(ADDRESS_SANITIZER)
   // AddressSanitizer replaces malloc() and prints a different error message on
   // heap corruption.
-  ASSERT_DEATH(SbMemoryDeallocate(buf),
-               "attempting SbMemoryDeallocate on address which "
+  ASSERT_DEATH(free(buf),
+               "attempting free on address which "
                "was not malloc\\(\\)-ed");
 #else
   ADD_FAILURE() << "This test is not supported in this build configuration.";
@@ -179,14 +179,14 @@
 TEST_F(OutOfMemoryDeathTest, Malloc) {
   ASSERT_EXIT({
       SetUpInDeathAssert();
-      value_ = SbMemoryAllocate(test_size_);
+      value_ = malloc(test_size_);
     }, testing::ExitedWithCode(kExitCode), kOomRegex);
 }
 
 TEST_F(OutOfMemoryDeathTest, Realloc) {
   ASSERT_EXIT({
       SetUpInDeathAssert();
-      value_ = SbMemoryReallocate(nullptr, test_size_);
+      value_ = realloc(nullptr, test_size_);
     }, testing::ExitedWithCode(kExitCode), kOomRegex);
 }
 
@@ -258,14 +258,14 @@
 TEST_F(OutOfMemoryDeathTest, SecurityMalloc) {
   ASSERT_EXIT({
       SetUpInDeathAssert();
-      value_ = SbMemoryAllocate(insecure_test_size_);
+      value_ = malloc(insecure_test_size_);
     }, testing::ExitedWithCode(kExitCode), kOomRegex);
 }
 
 TEST_F(OutOfMemoryDeathTest, SecurityRealloc) {
   ASSERT_EXIT({
       SetUpInDeathAssert();
-      value_ = SbMemoryReallocate(nullptr, insecure_test_size_);
+      value_ = realloc(nullptr, insecure_test_size_);
     }, testing::ExitedWithCode(kExitCode), kOomRegex);
 }
 
@@ -507,7 +507,7 @@
 TEST_F(OutOfMemoryHandledTest, UncheckedMalloc) {
   EXPECT_TRUE(base::UncheckedMalloc(kSafeMallocSize, &value_));
   EXPECT_TRUE(value_ != nullptr);
-  SbMemoryDeallocate(value_);
+  free(value_);
 
   EXPECT_FALSE(base::UncheckedMalloc(test_size_, &value_));
   EXPECT_TRUE(value_ == nullptr);
@@ -519,7 +519,7 @@
   const char* bytes = static_cast<const char*>(value_);
   for (size_t i = 0; i < kSafeMallocSize; ++i)
     EXPECT_EQ(0, bytes[i]);
-  SbMemoryDeallocate(value_);
+  free(value_);
 
   EXPECT_TRUE(
       base::UncheckedCalloc(kSafeCallocItems, kSafeCallocSize, &value_));
@@ -527,7 +527,7 @@
   bytes = static_cast<const char*>(value_);
   for (size_t i = 0; i < (kSafeCallocItems * kSafeCallocSize); ++i)
     EXPECT_EQ(0, bytes[i]);
-  SbMemoryDeallocate(value_);
+  free(value_);
 
   EXPECT_FALSE(base::UncheckedCalloc(1, test_size_, &value_));
   EXPECT_TRUE(value_ == nullptr);
diff --git a/base/process/process_info_mac.cc b/base/process/process_info_mac.cc
index a1cfa00..17c936c 100644
--- a/base/process/process_info_mac.cc
+++ b/base/process/process_info_mac.cc
@@ -26,7 +26,7 @@
     return Time();
 
   std::unique_ptr<struct kinfo_proc, base::FreeDeleter> proc(
-      static_cast<struct kinfo_proc*>(SbMemoryAllocate(len)));
+      static_cast<struct kinfo_proc*>(malloc(len)));
   if (sysctl(mib, arraysize(mib), proc.get(), &len, NULL, 0) < 0)
     return Time();
   return Time::FromTimeVal(proc->kp_proc.p_un.__p_starttime);
diff --git a/base/process/process_metrics_win.cc b/base/process/process_metrics_win.cc
index acb8135..4cd53bc 100644
--- a/base/process/process_metrics_win.cc
+++ b/base/process/process_metrics_win.cc
@@ -203,7 +203,7 @@
 
  private:
   void Clear() {
-    SbMemoryDeallocate(buffer_);
+    free(buffer_);
     buffer_ = nullptr;
   }
 
diff --git a/base/profiler/win32_stack_frame_unwinder.cc b/base/profiler/win32_stack_frame_unwinder.cc
index 9d610e1..51b9ffe 100644
--- a/base/profiler/win32_stack_frame_unwinder.cc
+++ b/base/profiler/win32_stack_frame_unwinder.cc
@@ -9,7 +9,6 @@
 #include <utility>
 
 #include "base/macros.h"
-#include "base/memory/ptr_util.h"
 #include "starboard/types.h"
 
 namespace base {
@@ -113,7 +112,7 @@
 Win32StackFrameUnwinder::UnwindFunctions::UnwindFunctions() {}
 
 Win32StackFrameUnwinder::Win32StackFrameUnwinder()
-    : Win32StackFrameUnwinder(WrapUnique(new Win32UnwindFunctions)) {}
+    : Win32StackFrameUnwinder(std::make_unique<Win32UnwindFunctions>()) {}
 
 Win32StackFrameUnwinder::~Win32StackFrameUnwinder() {}
 
diff --git a/base/sampling_heap_profiler/sampling_heap_profiler_unittest.cc b/base/sampling_heap_profiler/sampling_heap_profiler_unittest.cc
index 70e481e..630a3cc 100644
--- a/base/sampling_heap_profiler/sampling_heap_profiler_unittest.cc
+++ b/base/sampling_heap_profiler/sampling_heap_profiler_unittest.cc
@@ -66,8 +66,8 @@
   sampler->SetSamplingInterval(1024);
   sampler->Start();
   sampler->AddSamplesObserver(&collector);
-  void* volatile p = SbMemoryAllocate(10000);
-  SbMemoryDeallocate(p);
+  void* volatile p = malloc(10000);
+  free(p);
   sampler->Stop();
   sampler->RemoveSamplesObserver(&collector);
   EXPECT_TRUE(collector.sample_added);
@@ -83,8 +83,8 @@
   sampler->AddSamplesObserver(&collector);
   {
     PoissonAllocationSampler::ScopedMuteThreadSamples muted_scope;
-    void* volatile p = SbMemoryAllocate(10000);
-    SbMemoryDeallocate(p);
+    void* volatile p = malloc(10000);
+    free(p);
   }
   sampler->Stop();
   sampler->RemoveSamplesObserver(&collector);
@@ -111,17 +111,17 @@
 const int kNumberOfAllocations = 10000;
 
 NOINLINE void Allocate1() {
-  void* p = SbMemoryAllocate(400);
+  void* p = malloc(400);
   base::debug::Alias(&p);
 }
 
 NOINLINE void Allocate2() {
-  void* p = SbMemoryAllocate(700);
+  void* p = malloc(700);
   base::debug::Alias(&p);
 }
 
 NOINLINE void Allocate3() {
-  void* p = SbMemoryAllocate(20480);
+  void* p = malloc(20480);
   base::debug::Alias(&p);
 }
 
diff --git a/base/security_unittest.cc b/base/security_unittest.cc
index f1ad48b..efb19fd 100644
--- a/base/security_unittest.cc
+++ b/base/security_unittest.cc
@@ -150,7 +150,7 @@
   // the sophisticated allocators.
   size_t kAllocSize = 1<<20;
   std::unique_ptr<char, base::FreeDeleter> ptr(
-      static_cast<char*>(SbMemoryAllocate(kAllocSize)));
+      static_cast<char*>(malloc(kAllocSize)));
   ASSERT_TRUE(ptr != nullptr);
   // If two pointers are separated by less than 512MB, they are considered
   // to be in the same area.
diff --git a/base/strings/string_util_starboard.h b/base/strings/string_util_starboard.h
index 9de5572..c162395 100644
--- a/base/strings/string_util_starboard.h
+++ b/base/strings/string_util_starboard.h
@@ -25,18 +25,6 @@
 
 namespace base {
 
-inline char* strdup(const char* str) {
-  return SbStringDuplicate(str);
-}
-
-inline int strcasecmp(const char* string1, const char* string2) {
-  return SbStringCompareNoCase(string1, string2);
-}
-
-inline int strncasecmp(const char* string1, const char* string2, size_t count) {
-  return SbStringCompareNoCaseN(string1, string2, count);
-}
-
 #if defined(vsnprintf)
 #undef vsnprintf
 #endif
diff --git a/base/sys_info_posix.cc b/base/sys_info_posix.cc
index 276ba1e..ffdf84d 100644
--- a/base/sys_info_posix.cc
+++ b/base/sys_info_posix.cc
@@ -178,7 +178,7 @@
 }
 #endif
 
-#if !defined(OS_MACOSX) && !defined(OS_ANDROID) && !(OS_CHROMEOS)
+#if !defined(OS_MACOSX) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
 // static
 std::string SysInfo::OperatingSystemVersion() {
   struct utsname info;
diff --git a/base/syslog_logging.cc b/base/syslog_logging.cc
index 6fce9ca..568bf6a 100644
--- a/base/syslog_logging.cc
+++ b/base/syslog_logging.cc
@@ -43,6 +43,11 @@
   g_event_id = event_id;
 }
 
+void ResetEventSourceForTesting() {
+  delete g_event_source_name;
+  g_event_source_name = nullptr;
+}
+
 #endif  // defined(OS_WIN)
 
 EventLogMessage::EventLogMessage(const char* file,
diff --git a/base/syslog_logging.h b/base/syslog_logging.h
index 736a5b2..b67aeff 100644
--- a/base/syslog_logging.h
+++ b/base/syslog_logging.h
@@ -27,6 +27,10 @@
 void BASE_EXPORT SetEventSource(const std::string& name,
                                 uint16_t category,
                                 uint32_t event_id);
+
+// The event source may get set more than once in tests.  This function allows
+// a test to reset the source when needed.
+void BASE_EXPORT ResetEventSourceForTesting();
 #endif  // defined(OS_WIN)
 
 // Creates a formatted message on the system event log. That would be the
diff --git a/base/task/task_scheduler/scheduler_worker_stack_unittest.cc b/base/task/task_scheduler/scheduler_worker_stack_unittest.cc
index ab18123..1b4276c 100644
--- a/base/task/task_scheduler/scheduler_worker_stack_unittest.cc
+++ b/base/task/task_scheduler/scheduler_worker_stack_unittest.cc
@@ -5,7 +5,6 @@
 #include "base/task/task_scheduler/scheduler_worker_stack.h"
 
 #include "base/logging.h"
-#include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/metrics/statistics_recorder.h"
 #include "base/task/task_scheduler/scheduler_worker.h"
@@ -49,15 +48,15 @@
   }
   void SetUp() override {
     worker_a_ = MakeRefCounted<SchedulerWorker>(
-        ThreadPriority::NORMAL, WrapUnique(new MockSchedulerWorkerDelegate),
+        ThreadPriority::NORMAL, std::make_unique<MockSchedulerWorkerDelegate>(),
         task_tracker_.GetTrackedRef());
     ASSERT_TRUE(worker_a_);
     worker_b_ = MakeRefCounted<SchedulerWorker>(
-        ThreadPriority::NORMAL, WrapUnique(new MockSchedulerWorkerDelegate),
+        ThreadPriority::NORMAL, std::make_unique<MockSchedulerWorkerDelegate>(),
         task_tracker_.GetTrackedRef());
     ASSERT_TRUE(worker_b_);
     worker_c_ = MakeRefCounted<SchedulerWorker>(
-        ThreadPriority::NORMAL, WrapUnique(new MockSchedulerWorkerDelegate),
+        ThreadPriority::NORMAL, std::make_unique<MockSchedulerWorkerDelegate>(),
         task_tracker_.GetTrackedRef());
     ASSERT_TRUE(worker_c_);
   }
diff --git a/base/task/task_scheduler/task_scheduler_impl_unittest.cc b/base/task/task_scheduler/task_scheduler_impl_unittest.cc
index a516894..74434ae 100644
--- a/base/task/task_scheduler/task_scheduler_impl_unittest.cc
+++ b/base/task/task_scheduler/task_scheduler_impl_unittest.cc
@@ -14,7 +14,6 @@
 #include "base/cfi_buildflags.h"
 #include "base/debug/stack_trace.h"
 #include "base/macros.h"
-#include "base/memory/ptr_util.h"
 #include "base/metrics/field_trial.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/statistics_recorder.h"
@@ -465,9 +464,9 @@
   StartTaskScheduler();
   std::vector<std::unique_ptr<ThreadPostingTasks>> threads_posting_tasks;
   for (const auto& traits_execution_mode_pair : GetTraitsExecutionModePairs()) {
-    threads_posting_tasks.push_back(WrapUnique(
-        new ThreadPostingTasks(&scheduler_, traits_execution_mode_pair.traits,
-                               traits_execution_mode_pair.execution_mode)));
+    threads_posting_tasks.push_back(std::make_unique<ThreadPostingTasks>(
+        &scheduler_, traits_execution_mode_pair.traits,
+        traits_execution_mode_pair.execution_mode));
     threads_posting_tasks.back()->Start();
   }
 
diff --git a/base/test/malloc_wrapper.cc b/base/test/malloc_wrapper.cc
index 0880266..09ac637 100644
--- a/base/test/malloc_wrapper.cc
+++ b/base/test/malloc_wrapper.cc
@@ -10,5 +10,5 @@
 #include "starboard/types.h"
 
 void* MallocWrapper(size_t size) {
-  return SbMemoryAllocate(size);
+  return malloc(size);
 }
diff --git a/base/test/trace_event_analyzer_unittest.cc b/base/test/trace_event_analyzer_unittest.cc
index 43e758d..ac37e5e 100644
--- a/base/test/trace_event_analyzer_unittest.cc
+++ b/base/test/trace_event_analyzer_unittest.cc
@@ -5,7 +5,6 @@
 #include "base/test/trace_event_analyzer.h"
 
 #include "base/bind.h"
-#include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/platform_thread.h"
@@ -101,7 +100,7 @@
   event.arg_numbers["int"] = static_cast<double>(int_num);
   event.arg_numbers["double"] = double_num;
   event.arg_strings["string"] = str;
-  event.arg_values["dict"] = WrapUnique(new base::DictionaryValue());
+  event.arg_values["dict"] = std::make_unique<base::DictionaryValue>();
 
   ASSERT_TRUE(event.HasNumberArg("false"));
   ASSERT_TRUE(event.HasNumberArg("true"));
diff --git a/base/third_party/dmg_fp/dtoa.cc b/base/third_party/dmg_fp/dtoa.cc
index a32694d..c5aedfd 100644
--- a/base/third_party/dmg_fp/dtoa.cc
+++ b/base/third_party/dmg_fp/dtoa.cc
@@ -111,7 +111,7 @@
  *	if memory is available and otherwise does something you deem
  *	appropriate.  If MALLOC is undefined, malloc will be invoked
  *	directly -- and assumed always to succeed.  Similarly, if you
- *	want something other than the system's SbMemoryDeallocate() to be called to
+ *	want something other than the system's free() to be called to
  *	recycle memory acquired from MALLOC, #define FREE to be the
  *	name of the alternate routine.  (FREE or free is only called in
  *	pathological cases, e.g., in a dtoa call after a dtoa return in
@@ -230,7 +230,7 @@
 #endif
 #else
 #ifdef STARBOARD
-#define MALLOC SbMemoryAllocate
+#define MALLOC malloc
 #else
 #define MALLOC malloc
 #endif  // STARBOARD
@@ -610,7 +610,7 @@
 #ifdef FREE
 			FREE((void*)v);
 #else
-                  SbMemoryDeallocate((void*)v);
+                  free((void*)v);
 #endif
 		else {
 			ACQUIRE_DTOA_LOCK(0);
diff --git a/base/third_party/libevent/evrpc.h b/base/third_party/libevent/evrpc.h
index 64e6d35..583d616 100644
--- a/base/third_party/libevent/evrpc.h
+++ b/base/third_party/libevent/evrpc.h
@@ -186,7 +186,7 @@
       void* cbarg) {                                                          \
     struct evrpc_status status;                                               \
     struct evrpc_request_wrapper* ctx;                                        \
-    ctx = (struct evrpc_request_wrapper*)SbMemoryAllocate(                    \
+    ctx = (struct evrpc_request_wrapper*)malloc(                              \
         sizeof(struct evrpc_request_wrapper));                                \
     if (ctx == NULL)                                                          \
       goto error;                                                             \
@@ -194,7 +194,7 @@
     ctx->evcon = NULL;                                                        \
     ctx->name = strdup(#rpcname);                                             \
     if (ctx->name == NULL) {                                                  \
-      SbMemoryDeallocate(ctx);                                                      \
+      free(ctx);                                                              \
       goto error;                                                             \
     }                                                                         \
     ctx->cb = (void (*)(struct evrpc_status*, void*, void*, void*))cb;        \
diff --git a/base/third_party/libevent/min_heap.h b/base/third_party/libevent/min_heap.h
index 79fbc70..61e265c 100644
--- a/base/third_party/libevent/min_heap.h
+++ b/base/third_party/libevent/min_heap.h
@@ -59,7 +59,7 @@
 void min_heap_ctor(min_heap_t* s) { s->p = 0; s->n = 0; s->a = 0; }
 void min_heap_dtor(min_heap_t* s) {
   if (s->p)
-    SbMemoryDeallocate(s->p);
+    free(s->p);
 }
 void min_heap_elem_init(struct event* e) { e->min_heap_idx = -1; }
 int min_heap_empty(min_heap_t* s) { return 0u == s->n; }
@@ -115,7 +115,7 @@
         unsigned a = s->a ? s->a * 2 : 8;
         if(a < n)
             a = n;
-        if (!(p = (struct event**)SbMemoryReallocate(s->p, a * sizeof *p)))
+        if (!(p = (struct event**)realloc(s->p, a * sizeof *p)))
           return -1;
         s->p = p;
         s->a = a;
diff --git a/base/third_party/valgrind/valgrind.h b/base/third_party/valgrind/valgrind.h
index 3f2c7fa..1b24528 100644
--- a/base/third_party/valgrind/valgrind.h
+++ b/base/third_party/valgrind/valgrind.h
@@ -4296,7 +4296,7 @@
   VG_USERREQ__COUNT_ERRORS = 0x1201,
 
   /* These are useful and can be interpreted by any tool that
-     tracks SbMemoryAllocate() et al, by using vg_replace_SbMemoryAllocate.c. */
+     tracks malloc() et al, by using vg_replace_malloc.c. */
   VG_USERREQ__MALLOCLIKE_BLOCK = 0x1301,
   VG_USERREQ__FREELIKE_BLOCK = 0x1302,
   /* Memory pool support. */
@@ -4570,8 +4570,8 @@
 /* Several Valgrind tools (Memcheck, Massif, Helgrind, DRD) rely on knowing
    when heap blocks are allocated in order to give accurate results.  This
    happens automatically for the standard allocator functions such as
-   SbMemoryAllocate(), calloc(), SbMemoryReallocate(), memalign(), new, new[],
-   SbMemoryDeallocate(), delete, delete[], etc.
+   malloc(), calloc(), realloc(), memalign(), new, new[],
+   free(), delete, delete[], etc.
 
    But if your program uses a custom allocator, this doesn't automatically
    happen, and Valgrind will not do as well.  For example, if you allocate
@@ -4586,7 +4586,7 @@
    that it can be handled accurately by Valgrind.
 
    VALGRIND_MALLOCLIKE_BLOCK marks a region of memory as having been allocated
-   by a SbMemoryAllocate()-like function.  For Memcheck (an illustrative case),
+   by a malloc()-like function.  For Memcheck (an illustrative case),
    this does two things:
 
    - It records that the block has been allocated.  This means any addresses
diff --git a/base/third_party/xdg_user_dirs/xdg_user_dir_lookup.cc b/base/third_party/xdg_user_dirs/xdg_user_dir_lookup.cc
index 598659c..9eb8a66 100644
--- a/base/third_party/xdg_user_dirs/xdg_user_dir_lookup.cc
+++ b/base/third_party/xdg_user_dirs/xdg_user_dir_lookup.cc
@@ -47,7 +47,7 @@
  * type the value returned is @fallback.
  *
  * The return value is newly allocated and must be freed with
- * SbMemoryDeallocate(). The return value is never NULL if @fallback != NULL, unless
+ * free(). The return value is never NULL if @fallback != NULL, unless
  * out of memory.
  **/
 static char *
@@ -190,7 +190,7 @@
  * to ~/Desktop.
  *
  * The return value is newly allocated and must be freed with
- * SbMemoryDeallocate().
+ * free().
  **/
 char *
 xdg_user_dir_lookup (const char *type)
diff --git a/base/time/time_exploded_posix.cc b/base/time/time_exploded_posix.cc
index eecdf8d..ad7ab41 100644
--- a/base/time/time_exploded_posix.cc
+++ b/base/time/time_exploded_posix.cc
@@ -76,7 +76,7 @@
   ret = mktime(tm);
   if (tz) {
     setenv("TZ", tz, 1);
-    SbMemoryDeallocate(tz);
+    free(tz);
   } else {
     unsetenv("TZ");
   }
diff --git a/base/tools_sanity_unittest.cc b/base/tools_sanity_unittest.cc
index 467e443..9834d0d 100644
--- a/base/tools_sanity_unittest.cc
+++ b/base/tools_sanity_unittest.cc
@@ -140,9 +140,9 @@
 }
 
 TEST(ToolsSanityTest, MAYBE_AccessesToMallocMemory) {
-  char* foo = reinterpret_cast<char*>(SbMemoryAllocate(10));
+  char* foo = reinterpret_cast<char*>(malloc(10));
   MakeSomeErrors(foo, 10);
-  SbMemoryDeallocate(foo);
+  free(foo);
   // Use after free.
   HARMFUL_ACCESS(foo[5] = 0, "heap-use-after-free");
 }
diff --git a/base/trace_event/category_registry.cc b/base/trace_event/category_registry.cc
index 6b5abd9..230aa75 100644
--- a/base/trace_event/category_registry.cc
+++ b/base/trace_event/category_registry.cc
@@ -116,7 +116,7 @@
   // TODO(primiano): this strdup should be removed. The only documented reason
   // for it was TraceWatchEvent, which is gone. However, something might have
   // ended up relying on this. Needs some auditing before removal.
-  const char* category_name_copy = SbStringDuplicate(category_name);
+  const char* category_name_copy = strdup(category_name);
   ANNOTATE_LEAKING_OBJECT_PTR(category_name_copy);
 
   *category = &g_categories[category_index];
diff --git a/base/trace_event/heap_profiler_allocation_context_tracker.cc b/base/trace_event/heap_profiler_allocation_context_tracker.cc
index 45ee149..a2725dc 100644
--- a/base/trace_event/heap_profiler_allocation_context_tracker.cc
+++ b/base/trace_event/heap_profiler_allocation_context_tracker.cc
@@ -73,7 +73,7 @@
   // Use tid if we don't have a thread name.
   SbStringFormatF(name, sizeof(name), "%lu",
                   static_cast<unsigned long>(PlatformThread::CurrentId()));
-  return SbStringDuplicate(name);
+  return strdup(name);
 }
 
 }  // namespace
diff --git a/base/trace_event/memory_dump_manager_unittest.cc b/base/trace_event/memory_dump_manager_unittest.cc
index be9d3fe..63b658b 100644
--- a/base/trace_event/memory_dump_manager_unittest.cc
+++ b/base/trace_event/memory_dump_manager_unittest.cc
@@ -14,7 +14,6 @@
 #include "base/command_line.h"
 #include "base/debug/thread_heap_usage_tracker.h"
 #include "base/macros.h"
-#include "base/memory/ptr_util.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/synchronization/waitable_event.h"
@@ -424,11 +423,11 @@
   // we will pop out one thread/MemoryDumpProvider, each MDP is supposed to be
   // invoked a number of times equal to its index.
   for (uint32_t i = kNumInitialThreads; i > 0; --i) {
-    threads.push_back(WrapUnique(new Thread("test thread")));
+    threads.push_back(std::make_unique<Thread>("test thread"));
     auto* thread = threads.back().get();
     thread->Start();
     scoped_refptr<SingleThreadTaskRunner> task_runner = thread->task_runner();
-    mdps.push_back(WrapUnique(new MockMemoryDumpProvider()));
+    mdps.push_back(std::make_unique<MockMemoryDumpProvider>());
     auto* mdp = mdps.back().get();
     RegisterDumpProvider(mdp, task_runner, kDefaultOptions);
     EXPECT_CALL(*mdp, OnMemoryDump(_, _))
@@ -601,9 +600,8 @@
   std::vector<std::unique_ptr<MockMemoryDumpProvider>> mdps;
 
   for (int i = 0; i < 2; i++) {
-    threads.push_back(
-        WrapUnique(new TestIOThread(TestIOThread::kAutoStart)));
-    mdps.push_back(WrapUnique(new MockMemoryDumpProvider()));
+    threads.push_back(std::make_unique<TestIOThread>(TestIOThread::kAutoStart));
+    mdps.push_back(std::make_unique<MockMemoryDumpProvider>());
     RegisterDumpProvider(mdps.back().get(), threads.back()->task_runner(),
                          kDefaultOptions);
   }
@@ -650,9 +648,8 @@
   std::vector<std::unique_ptr<MockMemoryDumpProvider>> mdps;
 
   for (int i = 0; i < 2; i++) {
-    threads.push_back(
-        WrapUnique(new TestIOThread(TestIOThread::kAutoStart)));
-    mdps.push_back(WrapUnique(new MockMemoryDumpProvider()));
+    threads.push_back(std::make_unique<TestIOThread>(TestIOThread::kAutoStart));
+    mdps.push_back(std::make_unique<MockMemoryDumpProvider>());
     RegisterDumpProvider(mdps.back().get(), threads.back()->task_runner(),
                          kDefaultOptions);
   }
diff --git a/base/trace_event/trace_event_argument_unittest.cc b/base/trace_event/trace_event_argument_unittest.cc
index e08f782..a658503 100644
--- a/base/trace_event/trace_event_argument_unittest.cc
+++ b/base/trace_event/trace_event_argument_unittest.cc
@@ -6,7 +6,6 @@
 
 #include <utility>
 
-#include "base/memory/ptr_util.h"
 #include "base/values.h"
 #include "starboard/types.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -100,14 +99,14 @@
   Value bool_value(true);
   Value double_value(42.0f);
 
-  auto dict_value = WrapUnique(new DictionaryValue);
+  auto dict_value = std::make_unique<DictionaryValue>();
   dict_value->SetBoolean("bool", true);
   dict_value->SetInteger("int", 42);
   dict_value->SetDouble("double", 42.0f);
   dict_value->SetString("string", std::string("a") + "b");
   dict_value->SetString("string", std::string("a") + "b");
 
-  auto list_value = WrapUnique(new ListValue);
+  auto list_value = std::make_unique<ListValue>();
   list_value->AppendBoolean(false);
   list_value->AppendInteger(1);
   list_value->AppendString("in_list");
diff --git a/base/trace_event/trace_event_unittest.cc b/base/trace_event/trace_event_unittest.cc
index 67e8fb3..9c2d476 100644
--- a/base/trace_event/trace_event_unittest.cc
+++ b/base/trace_event/trace_event_unittest.cc
@@ -160,7 +160,7 @@
     if (TraceLog::GetInstance())
       EXPECT_FALSE(TraceLog::GetInstance()->IsEnabled());
     PlatformThread::SetName(old_thread_name_ ? old_thread_name_ : "");
-    SbMemoryDeallocate(old_thread_name_);
+    free(old_thread_name_);
     old_thread_name_ = nullptr;
     // We want our singleton torn down after each test.
     TraceLog::ResetForTesting();
diff --git a/base/values_unittest.cc b/base/values_unittest.cc
index 784dd02..420dd37 100644
--- a/base/values_unittest.cc
+++ b/base/values_unittest.cc
@@ -13,7 +13,6 @@
 #include <vector>
 
 #include "base/containers/adapters.h"
-#include "base/memory/ptr_util.h"
 #include "base/strings/string16.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
@@ -1235,7 +1234,7 @@
 
   std::unique_ptr<ListValue> list(new ListValue);
   list->Append(std::make_unique<Value>());
-  list->Append(WrapUnique(new DictionaryValue));
+  list->Append(std::make_unique<DictionaryValue>());
   auto list_copy = std::make_unique<Value>(list->Clone());
 
   ListValue* list_weak = dv.SetList("f", std::move(list));
diff --git a/base/win/windows_types.h b/base/win/windows_types.h
index 9fb5e30..13067d5 100644
--- a/base/win/windows_types.h
+++ b/base/win/windows_types.h
@@ -74,16 +74,20 @@
 #define CHROME_DECLARE_HANDLE(name) \
   struct name##__;                  \
   typedef struct name##__* name
+CHROME_DECLARE_HANDLE(HDESK);
 CHROME_DECLARE_HANDLE(HGLRC);
 CHROME_DECLARE_HANDLE(HICON);
 CHROME_DECLARE_HANDLE(HINSTANCE);
 CHROME_DECLARE_HANDLE(HKEY);
 CHROME_DECLARE_HANDLE(HKL);
 CHROME_DECLARE_HANDLE(HMENU);
+CHROME_DECLARE_HANDLE(HWINSTA);
 CHROME_DECLARE_HANDLE(HWND);
-typedef HINSTANCE HMODULE;
 #undef CHROME_DECLARE_HANDLE
 
+typedef LPVOID HINTERNET;
+typedef HINSTANCE HMODULE;
+typedef PVOID LSA_HANDLE;
 
 // Forward declare some Windows struct/typedef sets.
 
@@ -105,6 +109,8 @@
 
 typedef struct tagNMHDR NMHDR;
 
+typedef PVOID PSID;
+
 // Declare Chrome versions of some Windows structures. These are needed for
 // when we need a concrete type but don't want to pull in Windows.h. We can't
 // declare the Windows types so we declare our types and cast to the Windows
diff --git a/base/win/windows_version.cc b/base/win/windows_version.cc
index 6fc3185..bb41a07 100644
--- a/base/win/windows_version.cc
+++ b/base/win/windows_version.cc
@@ -11,6 +11,7 @@
 #include "base/file_version_info_win.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
+#include "base/no_destructor.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/win/registry.h"
 
@@ -74,28 +75,6 @@
   return VERSION_PRE_XP;
 }
 
-// Retrieve a version from kernel32. This is useful because when running in
-// compatibility mode for a down-level version of the OS, the file version of
-// kernel32 will still be the "real" version.
-Version GetVersionFromKernel32() {
-  std::unique_ptr<FileVersionInfoWin> file_version_info(
-      static_cast<FileVersionInfoWin*>(
-          FileVersionInfoWin::CreateFileVersionInfo(
-              base::FilePath(FILE_PATH_LITERAL("kernel32.dll")))));
-  if (file_version_info) {
-    const int major =
-        HIWORD(file_version_info->fixed_file_info()->dwFileVersionMS);
-    const int minor =
-        LOWORD(file_version_info->fixed_file_info()->dwFileVersionMS);
-    const int build =
-        HIWORD(file_version_info->fixed_file_info()->dwFileVersionLS);
-    return MajorMinorBuildToVersion(major, minor, build);
-  }
-
-  NOTREACHED();
-  return VERSION_WIN_LAST;
-}
-
 // Returns the the "UBR" value from the registry. Introduced in Windows 10,
 // this undocumented value appears to be similar to a patch number.
 // Returns 0 if the value does not exist or it could not be read.
@@ -155,8 +134,6 @@
                const _SYSTEM_INFO& system_info,
                int os_type)
     : version_(VERSION_PRE_XP),
-      kernel32_version_(VERSION_PRE_XP),
-      got_kernel32_version_(false),
       architecture_(OTHER_ARCHITECTURE),
       wow64_status_(GetWOW64StatusForProcess(GetCurrentProcess())) {
   version_number_.major = version_info.dwMajorVersion;
@@ -247,11 +224,34 @@
 }
 
 Version OSInfo::Kernel32Version() const {
-  if (!got_kernel32_version_) {
-    kernel32_version_ = GetVersionFromKernel32();
-    got_kernel32_version_ = true;
-  }
-  return kernel32_version_;
+  static const Version kernel32_version =
+      MajorMinorBuildToVersion(Kernel32BaseVersion().components()[0],
+                               Kernel32BaseVersion().components()[1],
+                               Kernel32BaseVersion().components()[2]);
+  return kernel32_version;
+}
+
+// Retrieve a version from kernel32. This is useful because when running in
+// compatibility mode for a down-level version of the OS, the file version of
+// kernel32 will still be the "real" version.
+base::Version OSInfo::Kernel32BaseVersion() const {
+  static const base::NoDestructor<base::Version> version([] {
+    std::unique_ptr<FileVersionInfoWin> file_version_info(
+        static_cast<FileVersionInfoWin*>(
+            FileVersionInfoWin::CreateFileVersionInfo(
+                base::FilePath(FILE_PATH_LITERAL("kernel32.dll")))));
+    DCHECK(file_version_info);
+    const int major =
+        HIWORD(file_version_info->fixed_file_info()->dwFileVersionMS);
+    const int minor =
+        LOWORD(file_version_info->fixed_file_info()->dwFileVersionMS);
+    const int build =
+        HIWORD(file_version_info->fixed_file_info()->dwFileVersionLS);
+    const int patch =
+        LOWORD(file_version_info->fixed_file_info()->dwFileVersionLS);
+    return base::Version(std::vector<uint32_t>{major, minor, build, patch});
+  }());
+  return *version;
 }
 
 std::string OSInfo::processor_model_name() {
diff --git a/base/win/windows_version.h b/base/win/windows_version.h
index 22b9d00..64f8041 100644
--- a/base/win/windows_version.h
+++ b/base/win/windows_version.h
@@ -11,6 +11,7 @@
 
 #include "base/base_export.h"
 #include "base/macros.h"
+#include "base/version.h"
 
 typedef void* HANDLE;
 struct _OSVERSIONINFOEXW;
@@ -108,6 +109,7 @@
 
   Version version() const { return version_; }
   Version Kernel32Version() const;
+  base::Version Kernel32BaseVersion() const;
   // The next two functions return arrays of values, [major, minor(, build)].
   VersionNumber version_number() const { return version_number_; }
   VersionType version_type() const { return version_type_; }
@@ -133,8 +135,6 @@
   ~OSInfo();
 
   Version version_;
-  mutable Version kernel32_version_;
-  mutable bool got_kernel32_version_;
   VersionNumber version_number_;
   VersionType version_type_;
   ServicePack service_pack_;
diff --git a/build/METADATA b/build/METADATA
index 91f4e6b..282cadc 100644
--- a/build/METADATA
+++ b/build/METADATA
@@ -11,7 +11,7 @@
     type: GIT
     value: "https://chromium.googlesource.com/chromium/src/build"
   }
-  version: "4cb2bd7db6575df5a62f65ea60fb7ca2f2ff9f05"
+  version: "cfaca2819f0d31f6bb086250f240ae9fc4a07074"
   last_upgrade_date {
     year: 2021
     month: 5
diff --git a/build/config/arm.gni b/build/config/arm.gni
index dd1b6c0..60ef642 100644
--- a/build/config/arm.gni
+++ b/build/config/arm.gni
@@ -49,12 +49,12 @@
     if (current_os == "android" || target_os == "android") {
       arm_float_abi = "softfp"
     } else {
-      declare_args() {
-        # The ARM floating point mode. This is either the string "hard", "soft",
-        # or "softfp". An empty string means to use the default one for the
-        # arm_version.
-        arm_float_abi = ""
-      }
+    declare_args() {
+      # The ARM floating point mode. This is either the string "hard", "soft",
+      # or "softfp". An empty string means to use the default one for the
+      # arm_version.
+      arm_float_abi = ""
+    }
     }
   }
   assert(arm_float_abi == "" || arm_float_abi == "hard" ||
diff --git a/build/config/clang/clang.gni b/build/config/clang/clang.gni
index 9cfbf6b..9775296 100644
--- a/build/config/clang/clang.gni
+++ b/build/config/clang/clang.gni
@@ -5,7 +5,7 @@
 import("//build/toolchain/toolchain.gni")
 
 if (!use_cobalt_customizations) {
-  default_clang_base_path = "//third_party/llvm-build/Release+Asserts"
+default_clang_base_path = "//third_party/llvm-build/Release+Asserts"
 }
 
 declare_args() {
@@ -16,7 +16,7 @@
       is_clang && !is_nacl && !use_xcode_clang &&
       default_toolchain != "//build/toolchain/cros:target"
 
-  if (!use_cobalt_customizations) {
-    clang_base_path = default_clang_base_path
-  }
+if (!use_cobalt_customizations) {
+  clang_base_path = default_clang_base_path
+}
 }
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 838ebcd..36702d9 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -14,7 +14,7 @@
 import("//build/config/coverage/coverage.gni")
 import("//build/config/dcheck_always_on.gni")
 if (!use_cobalt_customizations) {
-  import("//build/config/gclient_args.gni")
+import("//build/config/gclient_args.gni")
 }
 import("//build/config/host_byteorder.gni")
 import("//build/config/sanitizers/sanitizers.gni")
@@ -1597,26 +1597,26 @@
         ]
         if (!use_cobalt_customizations) {
           cflags += [
-            # An ABI compat warning we don't care about, https://crbug.com/1102157
-            # TODO(thakis): Push this to the (few) targets that need it,
-            # instead of having a global flag.
-            "-Wno-psabi",
+          # An ABI compat warning we don't care about, https://crbug.com/1102157
+          # TODO(thakis): Push this to the (few) targets that need it,
+          # instead of having a global flag.
+          "-Wno-psabi",
 
-            # TODO(https://crbug.com/989932): Evaluate and possibly enable.
-            "-Wno-implicit-int-float-conversion",
+          # TODO(https://crbug.com/989932): Evaluate and possibly enable.
+          "-Wno-implicit-int-float-conversion",
 
-            # TODO(https://crbug.com/999886): Clean up, enable.
-            "-Wno-final-dtor-non-final-class",
+          # TODO(https://crbug.com/999886): Clean up, enable.
+          "-Wno-final-dtor-non-final-class",
 
-            # TODO(https://crbug.com/1016945) Clean up, enable.
-            "-Wno-builtin-assume-aligned-alignment",
+          # TODO(https://crbug.com/1016945) Clean up, enable.
+          "-Wno-builtin-assume-aligned-alignment",
 
-            # TODO(https://crbug.com/1028110): Evaluate and possible enable.
-            "-Wno-deprecated-copy",
+          # TODO(https://crbug.com/1028110): Evaluate and possible enable.
+          "-Wno-deprecated-copy",
 
-            # TODO(https://crbug.com/1050281): Clean up, enable.
-            "-Wno-non-c-typedef-for-linkage",
-          ]
+          # TODO(https://crbug.com/1050281): Clean up, enable.
+          "-Wno-non-c-typedef-for-linkage",
+        ]
         }
 
         cflags_c += [
@@ -1625,12 +1625,12 @@
         ]
 
         if (!use_cobalt_customizations) {
-          if (enable_wmax_tokens) {
-            cflags += [ "-Wmax-tokens" ]
-          } else {
-            # TODO(https://crbug.com/1049569): Remove after Clang 87b235db.
-            cflags += [ "-Wno-max-tokens" ]
-          }
+        if (enable_wmax_tokens) {
+          cflags += [ "-Wmax-tokens" ]
+        } else {
+          # TODO(https://crbug.com/1049569): Remove after Clang 87b235db.
+          cflags += [ "-Wno-max-tokens" ]
+        }
         }
       }
     }
@@ -1648,7 +1648,7 @@
       # The platform should set warning flags.
       cflags = []
     } else {
-      cflags = [ "/W4" ]  # Warning level 4.
+    cflags = [ "/W4" ]  # Warning level 4.
     }
 
     if (is_clang) {
@@ -1899,11 +1899,8 @@
       defines = [ "_HAS_EXCEPTIONS=0" ]
     }
   } else {
-    # This hack ensures raspi does not compile anything with -fno-exceptions.
-    if (!defined(enable_exceptions_override) || !enable_exceptions_override) {
-      cflags_cc = [ "-fno-exceptions" ]
-      cflags_objcc = cflags_cc
-    }
+    cflags_cc = [ "-fno-exceptions" ]
+    cflags_objcc = cflags_cc
   }
 }
 
@@ -2005,11 +2002,11 @@
   if (is_starboard) {
     common_optimize_on_cflags = []
   } else {
-    common_optimize_on_cflags = [
-      "/Ob2",  # Both explicit and auto inlining.
-      "/Oy-",  # Disable omitting frame pointers, must be after /O2.
-      "/Zc:inline",  # Remove unreferenced COMDAT (faster links).
-    ]
+  common_optimize_on_cflags = [
+    "/Ob2",  # Both explicit and auto inlining.
+    "/Oy-",  # Disable omitting frame pointers, must be after /O2.
+    "/Zc:inline",  # Remove unreferenced COMDAT (faster links).
+  ]
   }
   if (!is_asan) {
     common_optimize_on_cflags += [
diff --git a/build/config/mac/mac_sdk.gni b/build/config/mac/mac_sdk.gni
index 9c456b3..56d1bc3 100644
--- a/build/config/mac/mac_sdk.gni
+++ b/build/config/mac/mac_sdk.gni
@@ -4,7 +4,7 @@
 
 import("//build/config/chrome_build.gni")
 if (!is_starboard) {
-  import("//build/config/gclient_args.gni")
+import("//build/config/gclient_args.gni")
 }
 import("//build/config/mac/mac_sdk_overrides.gni")
 import("//build/toolchain/goma.gni")
diff --git a/build/config/sanitizers/BUILD.gn b/build/config/sanitizers/BUILD.gn
index 50aa4c9..95c0a5f 100644
--- a/build/config/sanitizers/BUILD.gn
+++ b/build/config/sanitizers/BUILD.gn
@@ -116,10 +116,10 @@
   sources = [ "//build/sanitizers/sanitizer_options.cc" ]
 
   if (!use_cobalt_customizations) {
-    # Don't compile this target with any sanitizer code. It can be called from
-    # the sanitizer runtimes, so instrumenting these functions could cause
-    # recursive calls into the runtime if there is an error.
-    configs -= [ "//build/config/sanitizers:default_sanitizer_flags" ]
+  # Don't compile this target with any sanitizer code. It can be called from
+  # the sanitizer runtimes, so instrumenting these functions could cause
+  # recursive calls into the runtime if there is an error.
+  configs -= [ "//build/config/sanitizers:default_sanitizer_flags" ]
   }
 
   if (is_asan) {
diff --git a/build/config/win/BUILD.gn b/build/config/win/BUILD.gn
index 3996b5a..3b89f43 100644
--- a/build/config/win/BUILD.gn
+++ b/build/config/win/BUILD.gn
@@ -8,13 +8,12 @@
 import("//build/config/sanitizers/sanitizers.gni")
 import("//build/config/win/control_flow_guard.gni")
 import("//build/config/win/visual_studio_version.gni")
+if (!is_starboard) {
+import("//build/timestamp.gni")
+}
 import("//build/toolchain/goma.gni")
 import("//build/toolchain/toolchain.gni")
 
-if (!is_starboard) {
-  import("//build/timestamp.gni")
-}
-
 assert(is_win)
 
 declare_args() {
@@ -298,30 +297,30 @@
   }
 
   if (!is_starboard) {
-    vcvars_toolchain_data = exec_script("../../toolchain/win/setup_toolchain.py",
-                                        [
-                                          visual_studio_path,
-                                          windows_sdk_path,
-                                          visual_studio_runtime_dirs,
-                                          current_os,
-                                          current_cpu,
-                                          "none",
-                                        ],
-                                        "scope")
+  vcvars_toolchain_data = exec_script("../../toolchain/win/setup_toolchain.py",
+                                      [
+                                        visual_studio_path,
+                                        windows_sdk_path,
+                                        visual_studio_runtime_dirs,
+                                        current_os,
+                                        current_cpu,
+                                        "none",
+                                      ],
+                                      "scope")
 
-    vc_lib_path = vcvars_toolchain_data.vc_lib_path
-    if (defined(vcvars_toolchain_data.vc_lib_atlmfc_path)) {
-      vc_lib_atlmfc_path = vcvars_toolchain_data.vc_lib_atlmfc_path
-    }
-    vc_lib_um_path = vcvars_toolchain_data.vc_lib_um_path
+  vc_lib_path = vcvars_toolchain_data.vc_lib_path
+  if (defined(vcvars_toolchain_data.vc_lib_atlmfc_path)) {
+    vc_lib_atlmfc_path = vcvars_toolchain_data.vc_lib_atlmfc_path
+  }
+  vc_lib_um_path = vcvars_toolchain_data.vc_lib_um_path
 
-    lib_dirs = [
-      "$vc_lib_um_path",
-      "$vc_lib_path",
-    ]
-    if (defined(vc_lib_atlmfc_path)) {
-      lib_dirs += [ "$vc_lib_atlmfc_path" ]
-    }
+  lib_dirs = [
+    "$vc_lib_um_path",
+    "$vc_lib_path",
+  ]
+  if (defined(vc_lib_atlmfc_path)) {
+    lib_dirs += [ "$vc_lib_atlmfc_path" ]
+  }
   }
 }
 
diff --git a/build/config/win/visual_studio_version.gni b/build/config/win/visual_studio_version.gni
index 63b9a97..81e4216 100644
--- a/build/config/win/visual_studio_version.gni
+++ b/build/config/win/visual_studio_version.gni
@@ -39,27 +39,29 @@
 
   declare_args() {
     msvc_path = "$visual_studio_path/VC/Tools/MSVC/$visual_studio_version"
+
+    llvm_clang_path = "$visual_studio_path/VC/Tools/Llvm/x64/bin"
   }
 } else {
-  declare_args() {
-    # Path to Visual Studio. If empty, the default is used which is to use the
-    # automatic toolchain in depot_tools. If set, you must also set the
-    # visual_studio_version and wdk_path.
-    visual_studio_path = ""
+declare_args() {
+  # Path to Visual Studio. If empty, the default is used which is to use the
+  # automatic toolchain in depot_tools. If set, you must also set the
+  # visual_studio_version and wdk_path.
+  visual_studio_path = ""
 
-    # Version of Visual Studio pointed to by the visual_studio_path.
-    # Currently always "2015".
-    visual_studio_version = ""
+  # Version of Visual Studio pointed to by the visual_studio_path.
+  # Currently always "2015".
+  visual_studio_version = ""
 
-    # Directory of the Windows driver kit. If visual_studio_path is empty, this
-    # will be auto-filled.
-    wdk_path = ""
+  # Directory of the Windows driver kit. If visual_studio_path is empty, this
+  # will be auto-filled.
+  wdk_path = ""
 
-    # Full path to the Windows SDK, not including a backslash at the end.
-    # This value is the default location, override if you have a different
-    # installation location.
-    windows_sdk_path = "C:\Program Files (x86)\Windows Kits\10"
-  }
+  # Full path to the Windows SDK, not including a backslash at the end.
+  # This value is the default location, override if you have a different
+  # installation location.
+  windows_sdk_path = "C:\Program Files (x86)\Windows Kits\10"
+}
 }
 
 if (visual_studio_path == "") {
diff --git a/build/toolchain/gcc_toolchain.gni b/build/toolchain/gcc_toolchain.gni
index 2a2cc3c..ceb1ff9 100644
--- a/build/toolchain/gcc_toolchain.gni
+++ b/build/toolchain/gcc_toolchain.gni
@@ -156,11 +156,11 @@
       # ensure that it's always the same, regardless of the values that may be
       # set on those toolchains.
       if (!use_cobalt_customizations) {
-        host_toolchain = host_toolchain
+      host_toolchain = host_toolchain
 
-        if (!defined(invoker_toolchain_args.v8_current_cpu)) {
-          v8_current_cpu = invoker_toolchain_args.current_cpu
-        }
+      if (!defined(invoker_toolchain_args.v8_current_cpu)) {
+        v8_current_cpu = invoker_toolchain_args.current_cpu
+      }
       }
     }
 
@@ -394,7 +394,7 @@
       if (!is_starboard_toolchain && is_starboard && sb_is_modular) {
         command = "$asm -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{cflags}} {{asmflags}}${extra_asmflags} -c {{source}} -o {{output}}"
       } else {
-        command = "$asm -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{asmflags}}${extra_asmflags} -c {{source}} -o {{output}}"
+      command = "$asm -MMD -MF $depfile ${rebuild_string}{{defines}} {{include_dirs}} {{asmflags}}${extra_asmflags} -c {{source}} -o {{output}}"
       }
 
       depsformat = "gcc"
@@ -407,7 +407,8 @@
         rspfile = "{{output}}.rsp"
         rspfile_content = "{{inputs_newline}}"
         command = "\"$ar\" {{arflags}} rcsD {{output}} @\"$rspfile\""
-      } else if (current_os == "aix") {
+      } else
+      if (current_os == "aix") {
         # AIX does not support either -D (deterministic output) or response
         # files.
         command = "$ar -X64 {{arflags}} -r -c -s {{output}} {{inputs}}"
diff --git a/build/toolchain/win/msvc_toolchain.gni b/build/toolchain/win/msvc_toolchain.gni
index 92c98d4..01c3ebb 100644
--- a/build/toolchain/win/msvc_toolchain.gni
+++ b/build/toolchain/win/msvc_toolchain.gni
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+assert(use_cobalt_customizations)
+
 import("//build/toolchain/cc_wrapper.gni")
 import("//build/toolchain/toolchain.gni")
 
@@ -19,15 +21,19 @@
 
 template("msvc_toolchain") {
   # Write the environment variables file in the out directory.
-  required_environment_variables = ["SYSTEMROOT", "TEMP", "TMP"]
+  required_environment_variables = [
+    "SYSTEMROOT",
+    "TEMP",
+    "TMP",
+  ]
   optional_environment_variables = [
     "INCLUDE",
     "LIB",
     "PATH",
     "PATHEXT",
-    "XEDK", # TODO: What does this do?
-    "IS_DOCKER", # needed for ninja to invoke docker-specific logic
-    "IS_CI",     # needed for ninja to exclude some logic on GKE
+    "XEDK",  # TODO: What does this do?
+    "IS_DOCKER",  # needed for ninja to invoke docker-specific logic
+    "IS_CI",  # needed for ninja to exclude some logic on GKE
 
     # The remaining variables should be explicitly enumerated.
     # "cell_.*",
@@ -51,7 +57,11 @@
 
   nul = "$0x00"
   env_block = string_join(nul, environment_key_value_pairs) + nul
-  write_file("$root_build_dir/environment.$target_cpu", env_block)
+
+  # If the default toolchain shares its name with another toolchain,
+  # there could be a deadlock where both toolchains try to write to the same environment file.
+  # See b/297227714 for more context.
+  write_file("$root_build_dir/$target_name/environment.$target_cpu", env_block)
 
   toolchain(target_name) {
     # When invoking this toolchain not as the default one, these args will be
@@ -70,8 +80,7 @@
     # Object files go in this directory.
     object_subdir = "{{target_out_dir}}/{{label_name}}"
 
-    env = "environment.$target_cpu"
-
+    env = "$target_name/environment.$target_cpu"
     cl = invoker.cl
     lib = invoker.lib
     link = invoker.link
@@ -119,7 +128,7 @@
     tool("asm") {
       description = "ASM {{output}}"
       outputs = [ "$object_subdir/{{source_name_part}}.obj" ]
-      command ="$env_wrapper$asm /nologo /Fo{{output}} /c {{defines}} {{include_dirs}} {{asmflags}} {{source}}"
+      command = "$env_wrapper$asm /nologo /Fo{{output}} /c {{defines}} {{include_dirs}} {{asmflags}} {{source}}"
     }
 
     sys_lib_flags = "${invoker.sys_lib_flags} "  # Note trailing space.
@@ -163,9 +172,7 @@
       ]
       link_output = libname
       depend_output = libname
-      runtime_outputs = [
-        dllname,
-      ]
+      runtime_outputs = [ dllname ]
 
       # Since the above commands only updates the .lib file when it changes, ask
       # Ninja to check if the timestamp actually changed to know if downstream
@@ -189,9 +196,7 @@
       default_output_extension = ".dll"
       default_output_dir = "{{root_out_dir}}"
       description = "LINK_MODULE(DLL) {{output}}"
-      outputs = [
-        dllname,
-      ]
+      outputs = [ dllname ]
       runtime_outputs = outputs
 
       # The use of inputs_newline is to work around a fixed per-line buffer
@@ -210,9 +215,7 @@
       default_output_extension = ".exe"
       default_output_dir = "{{root_out_dir}}"
       description = "LINK {{output}}"
-      outputs = [
-        exename,
-      ]
+      outputs = [ exename ]
       runtime_outputs = outputs
 
       # The use of inputs_newline is to work around a fixed per-line buffer
diff --git a/build/win/BUILD.gn b/build/win/BUILD.gn
index 25e5963..6f3dd53 100644
--- a/build/win/BUILD.gn
+++ b/build/win/BUILD.gn
@@ -10,15 +10,15 @@
   # Never use a manifest in Starboard.
   group("default_exe_manifest") {}
 } else {
-  # Depending on this target will cause the manifests for Chrome's default
-  # Windows and common control compatibility and elevation for executables.
-  windows_manifest("default_exe_manifest") {
-    sources = [
-      as_invoker_manifest,
-      common_controls_manifest,
-      default_compatibility_manifest,
-    ]
-  }
+# Depending on this target will cause the manifests for Chrome's default
+# Windows and common control compatibility and elevation for executables.
+windows_manifest("default_exe_manifest") {
+  sources = [
+    as_invoker_manifest,
+    common_controls_manifest,
+    default_compatibility_manifest,
+  ]
+}
 }
 
 if (is_win) {
@@ -26,75 +26,75 @@
          "Windows cross-builds from Mac must be 64-bit.")
 
   if (!is_starboard) {
-    action("copy_cdb_to_output") {
-      script = "//build/win/copy_cdb_to_output.py"
-      inputs = [
-        script,
-        "//build/vs_toolchain.py",
-      ]
-      outputs = [
-        "$root_out_dir/cdb/cdb.exe",
-        "$root_out_dir/cdb/dbgeng.dll",
-        "$root_out_dir/cdb/dbghelp.dll",
-        "$root_out_dir/cdb/dbgmodel.dll",
-        "$root_out_dir/cdb/winext/ext.dll",
-        "$root_out_dir/cdb/winext/uext.dll",
-        "$root_out_dir/cdb/winxp/exts.dll",
-        "$root_out_dir/cdb/winxp/ntsdexts.dll",
-      ]
-      if (current_cpu != "arm64") {
-        # The UCRT files are not redistributable for ARM64 Win32.
-        outputs += [
-          "$root_out_dir/cdb/api-ms-win-core-console-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-datetime-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-debug-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-errorhandling-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-file-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-file-l1-2-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-file-l2-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-handle-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-heap-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-interlocked-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-libraryloader-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-localization-l1-2-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-memory-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-namedpipe-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-processenvironment-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-processthreads-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-processthreads-l1-1-1.dll",
-          "$root_out_dir/cdb/api-ms-win-core-profile-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-rtlsupport-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-string-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-synch-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-synch-l1-2-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-sysinfo-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-timezone-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-core-util-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-conio-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-convert-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-environment-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-filesystem-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-heap-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-locale-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-math-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-multibyte-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-private-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-process-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-runtime-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-stdio-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-string-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-time-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-crt-utility-l1-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-downlevel-kernel32-l2-1-0.dll",
-          "$root_out_dir/cdb/api-ms-win-eventing-provider-l1-1-0.dll",
-          "$root_out_dir/cdb/ucrtbase.dll",
-        ]
-      }
-      args = [
-        rebase_path("$root_out_dir/cdb", root_out_dir),
-        current_cpu,
+  action("copy_cdb_to_output") {
+    script = "//build/win/copy_cdb_to_output.py"
+    inputs = [
+      script,
+      "//build/vs_toolchain.py",
+    ]
+    outputs = [
+      "$root_out_dir/cdb/cdb.exe",
+      "$root_out_dir/cdb/dbgeng.dll",
+      "$root_out_dir/cdb/dbghelp.dll",
+      "$root_out_dir/cdb/dbgmodel.dll",
+      "$root_out_dir/cdb/winext/ext.dll",
+      "$root_out_dir/cdb/winext/uext.dll",
+      "$root_out_dir/cdb/winxp/exts.dll",
+      "$root_out_dir/cdb/winxp/ntsdexts.dll",
+    ]
+    if (current_cpu != "arm64") {
+      # The UCRT files are not redistributable for ARM64 Win32.
+      outputs += [
+        "$root_out_dir/cdb/api-ms-win-core-console-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-datetime-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-debug-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-errorhandling-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-file-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-file-l1-2-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-file-l2-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-handle-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-heap-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-interlocked-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-libraryloader-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-localization-l1-2-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-memory-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-namedpipe-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-processenvironment-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-processthreads-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-processthreads-l1-1-1.dll",
+        "$root_out_dir/cdb/api-ms-win-core-profile-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-rtlsupport-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-string-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-synch-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-synch-l1-2-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-sysinfo-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-timezone-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-core-util-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-conio-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-convert-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-environment-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-filesystem-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-heap-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-locale-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-math-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-multibyte-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-private-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-process-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-runtime-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-stdio-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-string-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-time-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-crt-utility-l1-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-downlevel-kernel32-l2-1-0.dll",
+        "$root_out_dir/cdb/api-ms-win-eventing-provider-l1-1-0.dll",
+        "$root_out_dir/cdb/ucrtbase.dll",
       ]
     }
+    args = [
+      rebase_path("$root_out_dir/cdb", root_out_dir),
+      current_cpu,
+    ]
+  }
   }
 
   group("runtime_libs") {
diff --git a/cobalt/audio/audio_device.cc b/cobalt/audio/audio_device.cc
index c2806c0..00e8233 100644
--- a/cobalt/audio/audio_device.cc
+++ b/cobalt/audio/audio_device.cc
@@ -30,6 +30,11 @@
 namespace {
 const int kRenderBufferSizeFrames = 1024;
 const int kDefaultFramesPerChannel = 8 * kRenderBufferSizeFrames;
+
+int AlignUp(int value, int alignment) {
+  int decremented_value = value - 1;
+  return decremented_value + alignment - (decremented_value % alignment);
+}
 }  // namespace
 
 class AudioDevice::Impl {
@@ -83,11 +88,13 @@
     : number_of_channels_(number_of_channels),
       output_sample_type_(GetPreferredOutputStarboardSampleType()),
       render_callback_(callback),
-      frames_per_channel_(std::max(SbAudioSinkGetMinBufferSizeInFrames(
-                                       number_of_channels, output_sample_type_,
-                                       kStandardOutputSampleRate) +
-                                       kRenderBufferSizeFrames * 2,
-                                   kDefaultFramesPerChannel)),
+      frames_per_channel_(
+          std::max(AlignUp(SbAudioSinkGetMinBufferSizeInFrames(
+                               number_of_channels, output_sample_type_,
+                               kStandardOutputSampleRate) +
+                               kRenderBufferSizeFrames * 2,
+                           kRenderBufferSizeFrames),
+                   kDefaultFramesPerChannel)),
       input_audio_bus_(static_cast<size_t>(number_of_channels),
                        static_cast<size_t>(kRenderBufferSizeFrames),
                        GetPreferredOutputSampleType(), AudioBus::kPlanar),
@@ -97,6 +104,7 @@
   DCHECK(number_of_channels_ == 1 || number_of_channels_ == 2)
       << "Invalid number of channels: " << number_of_channels_;
   DCHECK(render_callback_);
+  DCHECK(frames_per_channel_ % kRenderBufferSizeFrames == 0);
   DCHECK(SbAudioSinkIsAudioFrameStorageTypeSupported(
       kSbMediaAudioFrameStorageTypeInterleaved))
       << "Only interleaved frame storage is supported.";
diff --git a/cobalt/base/BUILD.gn b/cobalt/base/BUILD.gn
index 5552a2e..940d1bb 100644
--- a/cobalt/base/BUILD.gn
+++ b/cobalt/base/BUILD.gn
@@ -60,6 +60,7 @@
     "log_message_handler.cc",
     "log_message_handler.h",
     "message_queue.h",
+    "on_metric_upload_event.h",
     "on_screen_keyboard_hidden_event.h",
     "on_screen_keyboard_shown_event.h",
     "path_provider.cc",
@@ -121,7 +122,7 @@
   deps = [
     ":base",
     "//cobalt/test:run_all_unittests",
-    "//starboard",
+    "//starboard:starboard_group",
     "//testing/gmock",
     "//testing/gtest",
   ]
diff --git a/cobalt/base/circular_buffer_shell.cc b/cobalt/base/circular_buffer_shell.cc
index 84988bb..2ccbe9a 100644
--- a/cobalt/base/circular_buffer_shell.cc
+++ b/cobalt/base/circular_buffer_shell.cc
@@ -13,9 +13,6 @@
 
 #if defined(STARBOARD)
 #include "starboard/memory.h"
-#define malloc SbMemoryAllocate
-#define realloc SbMemoryReallocate
-#define free SbMemoryDeallocate
 #endif
 
 static inline void* add_to_pointer(void* pointer, size_t amount) {
diff --git a/cobalt/base/on_metric_upload_event.h b/cobalt/base/on_metric_upload_event.h
new file mode 100644
index 0000000..d011f0a
--- /dev/null
+++ b/cobalt/base/on_metric_upload_event.h
@@ -0,0 +1,61 @@
+// 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_BASE_ON_METRIC_UPLOAD_EVENT_H_
+#define COBALT_BASE_ON_METRIC_UPLOAD_EVENT_H_
+
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string_util.h"
+#include "cobalt/base/event.h"
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/h5vcc/h5vcc_metric_type.h"
+
+namespace base {
+
+// Event sent when a metric payload is ready for upload.
+class OnMetricUploadEvent : public Event {
+ public:
+  OnMetricUploadEvent(const cobalt::h5vcc::H5vccMetricType& metric_type,
+                      const std::string& serialized_proto)
+      : metric_type_(metric_type), serialized_proto_(serialized_proto) {}
+
+  explicit OnMetricUploadEvent(const Event* event) {
+    CHECK(event != nullptr);
+    const base::OnMetricUploadEvent* on_metric_upload_event =
+        base::polymorphic_downcast<const base::OnMetricUploadEvent*>(event);
+    metric_type_ = on_metric_upload_event->metric_type();
+    serialized_proto_ = on_metric_upload_event->serialized_proto();
+  }
+
+  const cobalt::h5vcc::H5vccMetricType& metric_type() const {
+    return metric_type_;
+  }
+
+  const std::string& serialized_proto() const { return serialized_proto_; }
+
+
+  BASE_EVENT_SUBCLASS(OnMetricUploadEvent);
+
+ private:
+  cobalt::h5vcc::H5vccMetricType metric_type_;
+  std::string serialized_proto_;
+};
+
+}  // namespace base
+
+#endif  // COBALT_BASE_ON_METRIC_UPLOAD_EVENT_H_
diff --git a/cobalt/bindings/_env.py b/cobalt/bindings/_env.py
deleted file mode 100644
index 4b705c2..0000000
--- a/cobalt/bindings/_env.py
+++ /dev/null
@@ -1,34 +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.
-#
-"""Sets PYTHONPATH for third_party/blink modules to work correctly."""
-
-from os import path
-import sys
-
-_REPO_ROOT = path.abspath(
-    path.join(path.dirname(__file__), path.pardir, path.pardir))
-_BLINK_PATHS = [
-    path.join(_REPO_ROOT, 'third_party', 'blink', 'Tools', 'Scripts'),
-    path.join(_REPO_ROOT, 'third_party', 'blink', 'Source', 'bindings',
-              'scripts'),
-]
-
-if _REPO_ROOT not in sys.path:
-  sys.path.insert(0, _REPO_ROOT)
-
-for path in _BLINK_PATHS:
-  if path not in sys.path:
-    sys.path.append(path)
diff --git a/cobalt/bindings/code_generator_cobalt.py b/cobalt/bindings/code_generator_cobalt.py
index 6756410..18f0a15 100644
--- a/cobalt/bindings/code_generator_cobalt.py
+++ b/cobalt/bindings/code_generator_cobalt.py
@@ -22,7 +22,12 @@
 import os
 import sys
 
-import _env  # pylint: disable=unused-import
+bindings_dir = os.path.join(
+    os.path.dirname(__file__),
+    '../../third_party/blink/Source/bindings/scripts')
+sys.path.insert(0, bindings_dir)
+
+# pylint:disable=wrong-import-position
 from cobalt.bindings import path_generator
 from cobalt.bindings.contexts import ContextBuilder
 from cobalt.bindings.name_conversion import get_interface_name
diff --git a/cobalt/bindings/contexts.py b/cobalt/bindings/contexts.py
index 7cdbea0..4f4543a 100644
--- a/cobalt/bindings/contexts.py
+++ b/cobalt/bindings/contexts.py
@@ -18,7 +18,6 @@
 dicts that will be used by Jinja in JS bindings generation.
 """
 
-import _env  # pylint: disable=unused-import
 from cobalt.bindings.name_conversion import capitalize_function_name
 from cobalt.bindings.name_conversion import convert_to_cobalt_constant_name
 from cobalt.bindings.name_conversion import convert_to_cobalt_enumeration_value
diff --git a/cobalt/bindings/flatten_idls_test.py b/cobalt/bindings/flatten_idls_test.py
index 0401c72..eea743e 100644
--- a/cobalt/bindings/flatten_idls_test.py
+++ b/cobalt/bindings/flatten_idls_test.py
@@ -20,7 +20,6 @@
 import platform
 import unittest
 
-from . import _env  # pylint: disable=unused-import
 from cobalt.bindings import flatten_idls
 
 
diff --git a/cobalt/bindings/generate_conversion_header.py b/cobalt/bindings/generate_conversion_header.py
index 69d0c1e..e7822f0 100644
--- a/cobalt/bindings/generate_conversion_header.py
+++ b/cobalt/bindings/generate_conversion_header.py
@@ -20,7 +20,14 @@
 from optparse import OptionParser  # pylint: disable=deprecated-module
 import os
 import pickle
+import sys
 
+bindings_dir = os.path.join(
+    os.path.dirname(__file__),
+    '../../third_party/blink/Source/bindings/scripts')
+sys.path.insert(0, bindings_dir)
+
+# pylint:disable=wrong-import-position
 from utilities import ComponentInfoProviderCobalt
 from utilities import write_file
 
diff --git a/cobalt/bindings/idl_compiler_cobalt.py b/cobalt/bindings/idl_compiler_cobalt.py
index 6b8c09d..fa2622f 100644
--- a/cobalt/bindings/idl_compiler_cobalt.py
+++ b/cobalt/bindings/idl_compiler_cobalt.py
@@ -21,7 +21,6 @@
 import os
 import pickle
 
-import _env  # pylint: disable=unused-import
 from idl_compiler import IdlCompiler
 from utilities import ComponentInfoProviderCobalt
 from utilities import idl_filename_to_interface_name
diff --git a/cobalt/bindings/path_generator_test.py b/cobalt/bindings/path_generator_test.py
index a6f2ce0..65cacaf 100644
--- a/cobalt/bindings/path_generator_test.py
+++ b/cobalt/bindings/path_generator_test.py
@@ -16,7 +16,6 @@
 
 import unittest
 
-from . import _env  # pylint: disable=unused-import
 from cobalt.bindings.path_generator import PathBuilder
 
 
diff --git a/cobalt/bindings/run_cobalt_bindings_tests.py b/cobalt/bindings/run_cobalt_bindings_tests.py
index b843085..2bcc26b 100755
--- a/cobalt/bindings/run_cobalt_bindings_tests.py
+++ b/cobalt/bindings/run_cobalt_bindings_tests.py
@@ -25,7 +25,6 @@
 import argparse
 import os
 import sys
-import _env  # pylint: disable=unused-import
 
 from cobalt.bindings.idl_compiler_cobalt import IdlCompilerCobalt
 from cobalt.bindings.v8c.code_generator_v8c import CodeGeneratorV8c
diff --git a/cobalt/bindings/v8c/_env.py b/cobalt/bindings/v8c/_env.py
deleted file mode 100644
index ea1f9b1..0000000
--- a/cobalt/bindings/v8c/_env.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.
-#
-"""Ask the parent directory to load the project environment."""
-
-from imp import load_source  # pylint: disable=deprecated-module
-from os import path
-import sys
-
-_ENV = path.abspath(path.join(path.dirname(__file__), path.pardir, '_env.py'))
-if not path.exists(_ENV):
-  print(f'{__file__}: Can\'t find repo root.\nMissing parent: {_ENV}')
-  sys.exit(1)
-load_source('', _ENV)
diff --git a/cobalt/bindings/v8c/generate_conversion_header_v8c.py b/cobalt/bindings/v8c/generate_conversion_header_v8c.py
index c14dea7..4d48b39 100644
--- a/cobalt/bindings/v8c/generate_conversion_header_v8c.py
+++ b/cobalt/bindings/v8c/generate_conversion_header_v8c.py
@@ -15,7 +15,6 @@
 
 import sys
 
-import _env  # pylint: disable=unused-import
 from cobalt.bindings.generate_conversion_header import generate_header
 from cobalt.bindings.v8c.code_generator_v8c import CodeGeneratorV8c
 
diff --git a/cobalt/bindings/v8c/idl_compiler_v8c.py b/cobalt/bindings/v8c/idl_compiler_v8c.py
index 09ca3f4..da13a0e 100644
--- a/cobalt/bindings/v8c/idl_compiler_v8c.py
+++ b/cobalt/bindings/v8c/idl_compiler_v8c.py
@@ -19,8 +19,14 @@
 
 import logging
 import sys
+import os
 
-import _env  #pylint: disable=import-error,unused-import
+bindings_dir = os.path.join(
+    os.path.dirname(__file__),
+    '../../../third_party/blink/Source/bindings/scripts')
+sys.path.insert(0, bindings_dir)
+
+# pylint:disable=wrong-import-position
 from cobalt.bindings.idl_compiler_cobalt import generate_bindings
 from cobalt.bindings.v8c.code_generator_v8c import CodeGeneratorV8c
 
diff --git a/cobalt/black_box_tests/black_box_cobalt_runner.py b/cobalt/black_box_tests/black_box_cobalt_runner.py
index c3829c6..8bf20e1 100644
--- a/cobalt/black_box_tests/black_box_cobalt_runner.py
+++ b/cobalt/black_box_tests/black_box_cobalt_runner.py
@@ -50,8 +50,7 @@
                target_params=None,
                success_message=None,
                poll_until_wait_seconds=POLL_UNTIL_WAIT_SECONDS,
-               web_server_port=None,
-               **kwargs):
+               web_server_port=None):
     # 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".
@@ -66,8 +65,7 @@
         target_params,
         success_message,
         poll_until_wait_seconds=poll_until_wait_seconds,
-        web_server_port=web_server_port,
-        **kwargs)
+        web_server_port=web_server_port)
 
     self.poll_until_wait_seconds = poll_until_wait_seconds
 
diff --git a/cobalt/black_box_tests/black_box_tests.py b/cobalt/black_box_tests/black_box_tests.py
index 3c6c44e..2c07220 100755
--- a/cobalt/black_box_tests/black_box_tests.py
+++ b/cobalt/black_box_tests/black_box_tests.py
@@ -35,7 +35,6 @@
 from starboard.tools import log_level
 
 _DISABLED_BLACKBOXTEST_CONFIGS = [
-    'android-arm/devel',
     'android-arm64/devel',
     'android-x86/devel',
     'evergreen-arm/devel',
@@ -43,13 +42,6 @@
     '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',
-    'evergreen-x64/devel',
-]
-
 _PORT_SELECTION_RETRY_LIMIT = 10
 _PORT_SELECTION_RANGE = [5000, 7000]
 # List of blocked ports.
@@ -79,7 +71,10 @@
     'h5vcc_storage_write_verify_test',
     'http_cache',
     'persistent_cookie',
+    'pointer_event_on_fixed_element_test',
+    'pointer_event_on_cropped_element_test',
     'scroll',
+    'scroll_with_none_pointer_events',
     'service_worker_add_to_cache_test',
     'service_worker_cache_keys_test',
     'service_worker_controller_activation_test',
@@ -109,11 +104,6 @@
 _TESTS_NEEDING_DEEP_LINK = [
     'deep_links',
 ]
-# These tests can only run on Evergreen-compatible platforms.
-_TESTS_EVERGREEN_END_TO_END = [
-    # Temporary disable test to publish new SB 16 changes b/149243810
-    #'evergreen_verify_qa_channel_update_test',
-]
 # Location of test files.
 _TEST_DIR_PATH = 'cobalt.black_box_tests.tests.'
 
@@ -181,6 +171,9 @@
       loader_out_directory=launcher_params.loader_out_directory)
 
   test_targets = []
+  test_filters = build.GetPlatformConfig(
+      _launcher_params.platform).GetApplicationConfiguration(
+          'cobalt').GetTestFilters()
 
   if test_set in ['all', 'blackbox']:
     test_targets = _TESTS_NO_SIGNAL
@@ -196,33 +189,14 @@
 
   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:
-    test_suite.addTest(unittest.TestLoader().loadTestsFromModule(
-        importlib.import_module(_TEST_DIR_PATH + test)))
+    filter_hit = 0
+    for filtered_test in test_filters:
+      if test == filtered_test.test_name:
+        filter_hit = 1
+        continue
+    if filter_hit == 0:
+      test_suite.addTest(unittest.TestLoader().loadTestsFromModule(
+          importlib.import_module(_TEST_DIR_PATH + test)))
   return test_suite
 
 
@@ -258,12 +232,13 @@
 
     # TODO: Remove generation of --dev_servers_listen_ip once executable will
     # be able to bind correctly with incomplete support of IPv6
-    if args.device_id and IsValidIpAddress(args.device_id):
-      _launcher_params.target_params.append(
-          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}')
+    if _launcher_params.platform not in ['android-arm', 'android-arm64']:
+      if args.device_id and IsValidIpAddress(args.device_id):
+        _launcher_params.target_params.append(
+            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}')
     _launcher_params.target_params.append(
         f'--web-platform-test-server=http://web-platform.test:{_wpt_http_port}')
 
@@ -291,45 +266,24 @@
     if self.use_proxy and self.proxy_port == '-1':
       return 1
 
-    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):
+      logging.warning('Blackbox tests disabled for platform:%s config:%s',
+                      _launcher_params.platform, _launcher_params.config)
       return 0
 
     def LoadAndRunTests():
       if self.args.test_name:
         suite = unittest.TestLoader().loadTestsFromName(_TEST_DIR_PATH +
                                                         self.args.test_name)
-        return_code = not unittest.TextTestRunner(
-            verbosity=2, stream=sys.stdout).run(suite).wasSuccessful()
-        return return_code
       else:
-        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
+        suite = LoadTests(_launcher_params, self.args.test_set)
+      # 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
 
     if self.use_proxy:
       logging.info('Using proxy port: %s', self.proxy_port)
@@ -438,9 +392,7 @@
       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')
+      '--test_set', choices=['all', 'wpt', 'blackbox'], default='all')
   args, _ = parser.parse_known_args()
 
   log_level.InitializeLogging(args)
diff --git a/cobalt/black_box_tests/testdata/evergreen_test.html b/cobalt/black_box_tests/testdata/evergreen_test.html
deleted file mode 100644
index b989250..0000000
--- a/cobalt/black_box_tests/testdata/evergreen_test.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!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
deleted file mode 100644
index cb03636..0000000
--- a/cobalt/black_box_tests/testdata/evergreen_test_script.js
+++ /dev/null
@@ -1,101 +0,0 @@
-// 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/pointer_event_on_cropped_element_test.html b/cobalt/black_box_tests/testdata/pointer_event_on_cropped_element_test.html
new file mode 100644
index 0000000..ccdcec3
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/pointer_event_on_cropped_element_test.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+
+<html>
+
+<head>
+    <title>Cobalt pointer event test on cropped element</title>
+    <script src='black_box_js_test_utils.js'></script>
+    <script src='pointer_event_test_utils.js'></script>
+    <style>
+        #upper_element {
+            position: absolute;
+            background-color: blue;
+            height: 10rem;
+            width: 200px;
+            top: 250px;
+        }
+
+        #lower_element {
+            position: absolute;
+            background-color: red;
+            height: 10rem;
+            width: 200px;
+            top: 280px;
+        }
+    </style>
+</head>
+
+<body>
+    <div class="list">
+        <div id="upper_element" class="upper_element"></div>
+        <div id="lower_element" class="lower_element"></div>
+    </div>
+    <div id="end" class="end">End</div>
+    <script>
+        const expected_events = [
+            // actions.move_to_element(lower_element).pause(_SLEEP_AFTER_MOVE_TIME)
+            { name: 'pointermove', id: 'lower_element', phase: Event.AT_TARGET },
+            // actions.click()
+            { name: 'pointerdown', id: 'lower_element', phase: Event.AT_TARGET },
+            { name: 'pointerup', id: 'lower_element', phase: Event.AT_TARGET },
+            // actions.move_to_element_with_offset(upper_element, 5, 5).pause(_SLEEP_AFTER_MOVE_TIME)
+            { name: 'pointermove', id: 'upper_element', phase: Event.AT_TARGET },
+            // actions.click()
+            { name: 'pointerdown', id: 'upper_element', phase: Event.AT_TARGET },
+            { name: 'pointerup', id: 'upper_element', phase: Event.AT_TARGET },
+            // actions.move_to_element(upper_element).pause(_SLEEP_AFTER_MOVE_TIME)
+            // No pointer event on cropper element.
+            { name: 'pointermove', id: 'lower_element', phase: Event.AT_TARGET },
+            // actions.click()
+            { name: 'pointerdown', id: 'lower_element', phase: Event.AT_TARGET },
+            { name: 'pointerup', id: 'lower_element', phase: Event.AT_TARGET },
+        ];
+
+        function endTest(e) {
+            assertTrue(failure_count === 0);
+            onEndTest();
+        }
+
+        function setAllHandlers(prefix, selector, callback) {
+            setHandlers(prefix + 'down', selector, callback);
+            setHandlers(prefix + 'up', selector, callback);
+            setHandlers(prefix + 'move', selector, callback);
+            setHandlers(prefix + 'cancel', selector, callback);
+        }
+
+        window.onload = () => {
+            setAllHandlers('pointer', '#upper_element', logEvent);
+            setAllHandlers('pointer', '#lower_element', logEvent);
+            setHandlers('click', '#end', endTest);
+            setupFinished();
+        }
+    </script>
+</body>
+
+
+</html>
diff --git a/cobalt/black_box_tests/testdata/pointer_event_on_fixed_element_test.html b/cobalt/black_box_tests/testdata/pointer_event_on_fixed_element_test.html
new file mode 100644
index 0000000..2eb474f
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/pointer_event_on_fixed_element_test.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+
+<head>
+    <meta charset="utf-8">
+    <script src='black_box_js_test_utils.js'></script>
+    <script src='pointer_event_test_utils.js'></script>
+    <style>
+        #container {
+            overflow: hidden;
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            margin: auto;
+            width: 100%;
+            height: 50%;
+            background: rgba(255, 0, 0, 1);
+        }
+
+        #surface {
+            background: rgba(0, 0, 255, .5);
+            height: 100%;
+            width: 100%;
+            position: fixed;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+        }
+    </style>
+</head>
+
+<body>
+    <script>
+        const expected_events = [
+            // actions.move_to_element(container).pause(_SLEEP_AFTER_MOVE_TIME)
+            { name: 'pointermove', id: 'surface', phase: Event.AT_TARGET },
+            { name: 'pointermove', id: 'container', phase: Event.BUBBLING_PHASE },
+            // actions.click(container)
+            { name: 'pointermove', id: 'surface', phase: Event.AT_TARGET },
+            { name: 'pointermove', id: 'container', phase: Event.BUBBLING_PHASE },
+            { name: 'pointerdown', id: 'surface', phase: Event.AT_TARGET },
+            { name: 'pointerdown', id: 'container', phase: Event.BUBBLING_PHASE },
+            { name: 'pointerup', id: 'surface', phase: Event.AT_TARGET },
+            { name: 'pointerup', id: 'container', phase: Event.BUBBLING_PHASE },
+            // actions.move_to_element(surface).pause(_SLEEP_AFTER_MOVE_TIME)
+            { name: 'pointermove', id: 'surface', phase: Event.AT_TARGET },
+            { name: 'pointermove', id: 'container', phase: Event.BUBBLING_PHASE },
+            // actions.click(surface)
+            { name: 'pointermove', id: 'surface', phase: Event.AT_TARGET },
+            { name: 'pointermove', id: 'container', phase: Event.BUBBLING_PHASE },
+            { name: 'pointerdown', id: 'surface', phase: Event.AT_TARGET },
+            { name: 'pointerdown', id: 'container', phase: Event.BUBBLING_PHASE },
+            { name: 'pointerup', id: 'surface', phase: Event.AT_TARGET },
+            { name: 'pointerup', id: 'container', phase: Event.BUBBLING_PHASE },
+        ];
+
+        function endTest(e) {
+            console.log('Ending test.')
+            assertTrue(failure_count == 0);
+            onEndTest();
+        }
+
+        function setAllHandlers(prefix, selector, callback) {
+            setHandlers(prefix + 'down', selector, callback);
+            setHandlers(prefix + 'up', selector, callback);
+            setHandlers(prefix + 'move', selector, callback);
+        }
+
+        window.onload = function () {
+            setAllHandlers('pointer', '.track', logEvent);
+            setHandlers('click', '#surface', endTest);
+            console.log("Setup finished");
+            setupFinished();
+        }
+    </script>
+    <div id="container" class="track">
+        <div id="surface" class="track"></div>
+    </div>
+
+</body>
diff --git a/cobalt/black_box_tests/testdata/pointer_event_test_utils.js b/cobalt/black_box_tests/testdata/pointer_event_test_utils.js
new file mode 100644
index 0000000..35970a8
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/pointer_event_test_utils.js
@@ -0,0 +1,71 @@
+// 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.
+
+// This file provides test utility functions for pointer event related
+// tests.
+
+// Fail if the test is not finished within 30 seconds.
+const kTimeout = 30 * 1000;
+setTimeout(fail, kTimeout);
+
+function fail() {
+    console.log("Failing due to timeout!");
+    assertTrue(false);
+}
+
+function phaseName(phase) {
+    switch (phase) {
+        case Event.NONE: return 'none';
+        case Event.CAPTURING_PHASE: return 'capturing';
+        case Event.AT_TARGET: return 'at target';
+        case Event.BUBBLING_PHASE: return 'bubbling';
+    }
+    return `[unknown: '${phase}']`;
+}
+
+let current_event_number = 0
+let failure_count = 0;
+
+function checkEvent(e) {
+    let matched = false;
+    while (current_event_number < expected_events.length) {
+      const {name, id, phase} = expected_events[current_event_number]
+      current_event_number++;
+      if (name === e.name && id === e.id && phase === e.phase) {
+        matched = true;
+        break;
+      }
+      console.error(`Missing Event ['${name}', '${id}', '${phaseName(phase)}']`);
+      failure_count++;
+    }
+    if (!matched) {
+      console.error(`Unexpected Event ['${e.name}', '${e.id}', '${phaseName(e.phase)}']`);
+      failure_count++;
+    }
+}
+
+function logEvent(e) {
+    const pointertype = e.pointerType ? e.pointerType + ' ' : '';
+    const id = this.getAttribute('id');
+    checkEvent({ name: e.type, id: id, phase: e.eventPhase });
+    console.log(`${e.type} ${pointertype}${id} (${this.getAttribute('class')}) \
+        [${phaseName(e.eventPhase)}] (${e.screenX} ${e.screenY})`);
+}
+
+function setHandlers(event, selector, callback) {
+    var elements = document.querySelectorAll(selector);
+    for (var i = 0; i < elements.length; ++i) {
+        elements[i].addEventListener(event, callback);
+    }
+}
diff --git a/cobalt/black_box_tests/testdata/pointer_test.html b/cobalt/black_box_tests/testdata/pointer_test.html
index 340e5da..6b4c476 100644
--- a/cobalt/black_box_tests/testdata/pointer_test.html
+++ b/cobalt/black_box_tests/testdata/pointer_test.html
@@ -1,547 +1,500 @@
 <!DOCTYPE html>
 
 <html>
-  <head>
-    <title>Cobalt pointer test</title>
-    <script src='black_box_js_test_utils.js'></script>
-    <style>
-      .size10 {
-        padding: 10px;
-      }
-      .size20 {
-        padding: 20px;
-      }
-      .size40 {
-        padding: 40px;
-      }
-      .black {
-        background-color: #FFF;
-      }
-      .grey {
-        background-color: #888;
-      }
-      .green {
-        background-color: #0F0;
-      }
-      .blue {
-        background-color: #00F;
-      }
-      .cyan {
-        background-color: #0FF;
-      }
-      .purple {
-        background-color: #F0F;
-      }
-      .yellow {
-        background-color: #FF0;
-      }
-    </style>
-  </head>
-  <body class="black">
-    <script>
-      // Fail if the test is not finished within 15 seconds.
-      var kTimeout = 30 * 1000;
-      var failTimer = setTimeout(fail, kTimeout);
 
-      function fail() {
-          console.log("Failing due to timeout!");
-          assertTrue(false);
-      }
+<head>
+  <title>Cobalt pointer test</title>
+  <script src='black_box_js_test_utils.js'></script>
+  <script src='pointer_event_test_utils.js'></script>
+  <style>
+    .size10 {
+      padding: 10px;
+    }
 
-      function phasename(phase) {
-        switch(phase) {
-          case 0: return 'none';
-          case 1: return 'capturing';
-          case 2: return 'at target';
-          case 3: return 'bubbling';
+    .size20 {
+      padding: 20px;
+    }
+
+    .size40 {
+      padding: 40px;
+    }
+
+    .black {
+      background-color: #FFF;
+    }
+
+    .grey {
+      background-color: #888;
+    }
+
+    .green {
+      background-color: #0F0;
+    }
+
+    .blue {
+      background-color: #00F;
+    }
+
+    .cyan {
+      background-color: #0FF;
+    }
+
+    .purple {
+      background-color: #F0F;
+    }
+
+    .yellow {
+      background-color: #FF0;
+    }
+  </style>
+</head>
+
+<body class="black">
+  <script>
+    const expected_events = [
+      // name, id, phase
+      // actions.move_to_element(top_one).pause(_SLEEP_AFTER_MOVE_TIME)
+      { name: 'pointermove', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'pointerover', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerenter', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'pointerenter', id: 'top', phase: Event.AT_TARGET },
+      { name: 'pointerenter', id: 'outer', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseenter', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'mouseenter', id: 'top', phase: Event.AT_TARGET },
+      { name: 'mouseenter', id: 'outer', phase: Event.AT_TARGET },
+      // actions.move_to_element(top_two).pause(_SLEEP_AFTER_MOVE_TIME)
+      { name: 'pointerout', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'pointerout', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerleave', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseleave', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top_two', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE }, ,
+      { name: 'mousemove', id: 'top_two', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'top_two', phase: Event.AT_TARGET },
+      { name: 'pointerover', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerenter', id: 'top_two', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'top_two', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseenter', id: 'top_two', phase: Event.AT_TARGET },
+      // actions.move_to_element_with_offset(top_two, 10, 10).pause(_SLEEP_AFTER_MOVE_TIME)
+      { name: 'pointermove', id: 'top_two', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'top_two', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      // actions.move_to_element_with_offset(top_two, 0, 0).pause(_SLEEP_AFTER_MOVE_TIME)
+      { name: 'pointermove', id: 'top_two', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE }, ,
+      { name: 'mousemove', id: 'top_two', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      // actions.move_to_element_with_offset(top_two, -10, 0).pause(_SLEEP_AFTER_MOVE_TIME)
+      { name: 'pointerout', id: 'top_two', phase: Event.AT_TARGET },
+      { name: 'pointerout', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerleave', id: 'top_two', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'top_two', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseleave', id: 'top_two', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'pointerover', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerenter', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseenter', id: 'top_one', phase: Event.AT_TARGET },
+      // actions.click(top_three)
+      { name: 'pointerout', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'pointerout', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerleave', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseleave', id: 'top_one', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'pointerover', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerenter', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseenter', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'pointerdown', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'pointerdown', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerdown', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mousedown', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'mousedown', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mousedown', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerup', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'pointerup', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerup', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseup', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'mouseup', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseup', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'click', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'click', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'click', id: 'outer', phase: Event.BUBBLING_PHASE },
+      // actions.click_and_hold(top_four)
+      { name: 'pointerout', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'pointerout', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerleave', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseleave', id: 'top_three', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE }, ,
+      { name: 'mousemove', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'pointerover', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerenter', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseenter', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'pointerdown', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'pointerdown', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerdown', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mousedown', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'mousedown', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mousedown', id: 'outer', phase: Event.BUBBLING_PHASE },
+      // actions.release(top_five)
+      { name: 'pointermove', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerup', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'pointerup', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerup', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseup', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'mouseup', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseup', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'click', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'click', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'click', id: 'outer', phase: Event.BUBBLING_PHASE },
+      // actions.move_to_element(top_six).pause(_SLEEP_AFTER_MOVE_TIME)
+      { name: 'pointerout', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'pointerout', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerleave', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseleave', id: 'top_four', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top_six', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'top_six', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'top_six', phase: Event.AT_TARGET },
+      { name: 'pointerover', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerenter', id: 'top_six', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'top_six', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseenter', id: 'top_six', phase: Event.AT_TARGET },
+      // actions.move_to_element(bottom_six).pause(_SLEEP_AFTER_MOVE_TIME)
+      { name: 'pointerout', id: 'top_six', phase: Event.AT_TARGET },
+      { name: 'pointerout', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerleave', id: 'top_six', phase: Event.AT_TARGET },
+      { name: 'pointerleave', id: 'top', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'top_six', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'top', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseleave', id: 'top_six', phase: Event.AT_TARGET },
+      { name: 'mouseleave', id: 'top', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'bottom_six', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'bottom_six', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'bottom_six', phase: Event.AT_TARGET },
+      { name: 'pointerover', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerenter', id: 'bottom_six', phase: Event.AT_TARGET },
+      { name: 'pointerenter', id: 'bottom', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'bottom_six', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseenter', id: 'bottom_six', phase: Event.AT_TARGET },
+      { name: 'mouseenter', id: 'bottom', phase: Event.AT_TARGET },
+      // actions.click(bottom_five)
+      { name: 'pointerout', id: 'bottom_six', phase: Event.AT_TARGET },
+      { name: 'pointerout', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerleave', id: 'bottom_six', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'bottom_six', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseleave', id: 'bottom_six', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'bottom_five', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'bottom_five', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'bottom_five', phase: Event.AT_TARGET },
+      { name: 'pointerover', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerenter', id: 'bottom_five', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'bottom_five', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseenter', id: 'bottom_five', phase: Event.AT_TARGET },
+      { name: 'pointerdown', id: 'bottom_five', phase: Event.AT_TARGET },
+      { name: 'pointerdown', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerdown', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerup', id: 'bottom_five', phase: Event.AT_TARGET },
+      { name: 'pointerup', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerup', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'click', id: 'bottom_five', phase: Event.AT_TARGET },
+      { name: 'click', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'click', id: 'outer', phase: Event.BUBBLING_PHASE },
+      // actions.click_and_hold(bottom_four)
+      { name: 'pointerout', id: 'bottom_five', phase: Event.AT_TARGET },
+      { name: 'pointerout', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerleave', id: 'bottom_five', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'bottom_five', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseleave', id: 'bottom_five', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'pointerover', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerenter', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseenter', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'pointerdown', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'pointerdown', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerdown', id: 'outer', phase: Event.BUBBLING_PHASE },
+      // actions.release(bottom_three)
+      { name: 'pointermove', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerup', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'pointerup', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerup', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'click', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'click', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'click', id: 'outer', phase: Event.BUBBLING_PHASE },
+      // actions.move_to_element(bottom_two).pause(_SLEEP_AFTER_MOVE_TIME)
+      { name: 'pointerout', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'pointerout', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerleave', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'bottom', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseout', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseleave', id: 'bottom_four', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'bottom_two', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'bottom_two', phase: Event.AT_TARGET },
+      { name: 'pointerover', id: 'bottom_two', phase: Event.AT_TARGET },
+      { name: 'pointerenter', id: 'bottom_two', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'bottom_two', phase: Event.AT_TARGET },
+      { name: 'mouseenter', id: 'bottom_two', phase: Event.AT_TARGET },
+      // actions.move_to_element(bottom_one).pause(_SLEEP_AFTER_MOVE_TIME)
+      { name: 'pointerout', id: 'bottom_two', phase: Event.AT_TARGET },
+      { name: 'pointerleave', id: 'bottom_two', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'bottom_two', phase: Event.AT_TARGET },
+      { name: 'mouseleave', id: 'bottom_two', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'bottom_one', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'bottom_one', phase: Event.AT_TARGET },
+      { name: 'pointerover', id: 'bottom_one', phase: Event.AT_TARGET },
+      { name: 'pointerenter', id: 'bottom_one', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'bottom_one', phase: Event.AT_TARGET },
+      { name: 'mouseenter', id: 'bottom_one', phase: Event.AT_TARGET },
+      // find_element_by_id(runner, 'end').click()
+      { name: 'pointerout', id: 'bottom_one', phase: Event.AT_TARGET },
+      { name: 'pointerleave', id: 'bottom_one', phase: Event.AT_TARGET },
+      { name: 'pointerleave', id: 'bottom', phase: Event.AT_TARGET },
+      { name: 'mouseout', id: 'bottom_one', phase: Event.AT_TARGET },
+      { name: 'mouseleave', id: 'bottom_one', phase: Event.AT_TARGET },
+      { name: 'mouseleave', id: 'bottom', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'end', phase: Event.AT_TARGET },
+      { name: 'pointermove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mousemove', id: 'end', phase: Event.AT_TARGET },
+      { name: 'mousemove', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerover', id: 'end', phase: Event.AT_TARGET },
+      { name: 'pointerover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerenter', id: 'end', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'end', phase: Event.AT_TARGET },
+      { name: 'mouseover', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseenter', id: 'end', phase: Event.AT_TARGET },
+      { name: 'pointerdown', id: 'end', phase: Event.AT_TARGET },
+      { name: 'pointerdown', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mousedown', id: 'end', phase: Event.AT_TARGET },
+      { name: 'mousedown', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'pointerup', id: 'end', phase: Event.AT_TARGET },
+      { name: 'pointerup', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'mouseup', id: 'end', phase: Event.AT_TARGET },
+      { name: 'mouseup', id: 'outer', phase: Event.BUBBLING_PHASE },
+      { name: 'click', id: 'end', phase: Event.AT_TARGET },
+      { name: 'click', id: 'outer', phase: Event.BUBBLING_PHASE }]
+
+    function endTest(e) {
+      console.log('Ending test.')
+      assertTrue(failure_count == 0);
+      onEndTest();
+    }
+
+    function cancel(e) {
+      console.log('cancel');
+      e.preventDefault();
+    }
+
+    function stop(e) {
+      console.log('stop');
+      e.stopPropagation();
+    }
+
+    function capture(e) {
+      console.log('capture');
+      e.target.setPointerCapture(e.pointerId);
+    }
+
+    // If the event type has value 'type', then report an error if the
+    // 'name' property on the event target already has 'value'. Otherwise,
+    // set it to the 'value'. This is used to detect erroneous boundary
+    // events (enter/leave, over/out), and up/down event sequences on an
+    // element.
+    function trackAndVerifyTargetState(event, type, name, value) {
+      if (event.type == type) {
+        if (event.target[name] == value) {
+          console.log('ERROR: ' + type + 'event received while ' +
+            name + ' == ' + event.target[name]);
+          assertTrue(event.target[name] != value);
         }
-        return ' [unknown: ' + phase + ']';
+        event.target[name] = value;
       }
+    }
 
-      var expected_events = [
-        // name, id, phase
-        // actions.move_to_element(top_one).pause(_SLEEP_AFTER_MOVE_TIME)
-        ['pointermove', 'top_one', 'at target'],
-        ['pointermove', 'top', 'bubbling'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['mousemove', 'top_one', 'at target'],
-        ['mousemove', 'top', 'bubbling'],
-        ['mousemove', 'outer', 'bubbling'],
-        ['pointerover', 'top_one', 'at target'],
-        ['pointerover', 'top', 'bubbling'],
-        ['pointerover', 'outer', 'bubbling'],
-        ['pointerenter', 'top_one', 'at target'],
-        ['pointerenter', 'top', 'at target'],
-        ['pointerenter', 'outer', 'at target'],
-        ['mouseover', 'top_one', 'at target'],
-        ['mouseover', 'top', 'bubbling'],
-        ['mouseover', 'outer', 'bubbling'],
-        ['mouseenter', 'top_one', 'at target'],
-        ['mouseenter', 'top', 'at target'],
-        ['mouseenter', 'outer', 'at target'],
-        // actions.move_to_element(top_two).pause(_SLEEP_AFTER_MOVE_TIME)
-        ['pointerout', 'top_one', 'at target'],
-        ['pointerout', 'top', 'bubbling'],
-        ['pointerout', 'outer', 'bubbling'],
-        ['pointerleave', 'top_one', 'at target'],
-        ['mouseout', 'top_one', 'at target'],
-        ['mouseout', 'top', 'bubbling'],
-        ['mouseout', 'outer', 'bubbling'],
-        ['mouseleave', 'top_one', 'at target'],
-        ['pointermove', 'top_two', 'at target'],
-        ['pointermove', 'top', 'bubbling'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['mousemove', 'top_two', 'at target'],
-        ['mousemove', 'top', 'bubbling'],
-        ['mousemove', 'outer', 'bubbling'],
-        ['pointerover', 'top_two', 'at target'],
-        ['pointerover', 'top', 'bubbling'],
-        ['pointerover', 'outer', 'bubbling'],
-        ['pointerenter', 'top_two', 'at target'],
-        ['mouseover', 'top_two', 'at target'],
-        ['mouseover', 'top', 'bubbling'],
-        ['mouseover', 'outer', 'bubbling'],
-        ['mouseenter', 'top_two', 'at target'],
-        // actions.move_to_element_with_offset(top_two, 10, 10).pause(_SLEEP_AFTER_MOVE_TIME)
-        ['pointermove', 'top_two', 'at target'],
-        ['pointermove', 'top', 'bubbling'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['mousemove', 'top_two', 'at target'],
-        ['mousemove', 'top', 'bubbling'],
-        ['mousemove', 'outer', 'bubbling'],
-        // actions.move_to_element_with_offset(top_two, 0, 0).pause(_SLEEP_AFTER_MOVE_TIME)
-        ['pointermove', 'top_two', 'at target'],
-        ['pointermove', 'top', 'bubbling'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['mousemove', 'top_two', 'at target'],
-        ['mousemove', 'top', 'bubbling'],
-        ['mousemove', 'outer', 'bubbling'],
-        // actions.move_to_element_with_offset(top_two, -10, 0).pause(_SLEEP_AFTER_MOVE_TIME)
-        ['pointerout', 'top_two', 'at target'],
-        ['pointerout', 'top', 'bubbling'],
-        ['pointerout', 'outer', 'bubbling'],
-        ['pointerleave', 'top_two', 'at target'],
-        ['mouseout', 'top_two', 'at target'],
-        ['mouseout', 'top', 'bubbling'],
-        ['mouseout', 'outer', 'bubbling'],
-        ['mouseleave', 'top_two', 'at target'],
-        ['pointermove', 'top_one', 'at target'],
-        ['pointermove', 'top', 'bubbling'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['mousemove', 'top_one', 'at target'],
-        ['mousemove', 'top', 'bubbling'],
-        ['mousemove', 'outer', 'bubbling'],
-        ['pointerover', 'top_one', 'at target'],
-        ['pointerover', 'top', 'bubbling'],
-        ['pointerover', 'outer', 'bubbling'],
-        ['pointerenter', 'top_one', 'at target'],
-        ['mouseover', 'top_one', 'at target'],
-        ['mouseover', 'top', 'bubbling'],
-        ['mouseover', 'outer', 'bubbling'],
-        ['mouseenter', 'top_one', 'at target'],
-        // actions.click(top_three)
-        ['pointerout', 'top_one', 'at target'],
-        ['pointerout', 'top', 'bubbling'],
-        ['pointerout', 'outer', 'bubbling'],
-        ['pointerleave', 'top_one', 'at target'],
-        ['mouseout', 'top_one', 'at target'],
-        ['mouseout', 'top', 'bubbling'],
-        ['mouseout', 'outer', 'bubbling'],
-        ['mouseleave', 'top_one', 'at target'],
-        ['pointermove', 'top_three', 'at target'],
-        ['pointermove', 'top', 'bubbling'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['mousemove', 'top_three', 'at target'],
-        ['mousemove', 'top', 'bubbling'],
-        ['mousemove', 'outer', 'bubbling'],
-        ['pointerover', 'top_three', 'at target'],
-        ['pointerover', 'top', 'bubbling'],
-        ['pointerover', 'outer', 'bubbling'],
-        ['pointerenter', 'top_three', 'at target'],
-        ['mouseover', 'top_three', 'at target'],
-        ['mouseover', 'top', 'bubbling'],
-        ['mouseover', 'outer', 'bubbling'],
-        ['mouseenter', 'top_three', 'at target'],
-        ['pointerdown', 'top_three', 'at target'],
-        ['pointerdown', 'top', 'bubbling'],
-        ['pointerdown', 'outer', 'bubbling'],
-        ['mousedown', 'top_three', 'at target'],
-        ['mousedown', 'top', 'bubbling'],
-        ['mousedown', 'outer', 'bubbling'],
-        ['pointerup', 'top_three', 'at target'],
-        ['pointerup', 'top', 'bubbling'],
-        ['pointerup', 'outer', 'bubbling'],
-        ['mouseup', 'top_three', 'at target'],
-        ['mouseup', 'top', 'bubbling'],
-        ['mouseup', 'outer', 'bubbling'],
-        ['click', 'top_three', 'at target'],
-        ['click', 'top', 'bubbling'],
-        ['click', 'outer', 'bubbling'],
-        // actions.click_and_hold(top_four)
-        ['pointerout', 'top_three', 'at target'],
-        ['pointerout', 'top', 'bubbling'],
-        ['pointerout', 'outer', 'bubbling'],
-        ['pointerleave', 'top_three', 'at target'],
-        ['mouseout', 'top_three', 'at target'],
-        ['mouseout', 'top', 'bubbling'],
-        ['mouseout', 'outer', 'bubbling'],
-        ['mouseleave', 'top_three', 'at target'],
-        ['pointermove', 'top_four', 'at target'],
-        ['pointermove', 'top', 'bubbling'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['mousemove', 'top_four', 'at target'],
-        ['mousemove', 'top', 'bubbling'],
-        ['mousemove', 'outer', 'bubbling'],
-        ['pointerover', 'top_four', 'at target'],
-        ['pointerover', 'top', 'bubbling'],
-        ['pointerover', 'outer', 'bubbling'],
-        ['pointerenter', 'top_four', 'at target'],
-        ['mouseover', 'top_four', 'at target'],
-        ['mouseover', 'top', 'bubbling'],
-        ['mouseover', 'outer', 'bubbling'],
-        ['mouseenter', 'top_four', 'at target'],
-        ['pointerdown', 'top_four', 'at target'],
-        ['pointerdown', 'top', 'bubbling'],
-        ['pointerdown', 'outer', 'bubbling'],
-        ['mousedown', 'top_four', 'at target'],
-        ['mousedown', 'top', 'bubbling'],
-        ['mousedown', 'outer', 'bubbling'],
-        // actions.release(top_five)
-        ['pointermove', 'top_four', 'at target'],
-        ['pointermove', 'top', 'bubbling'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['mousemove', 'top_four', 'at target'],
-        ['mousemove', 'top', 'bubbling'],
-        ['mousemove', 'outer', 'bubbling'],
-        ['pointerup', 'top_four', 'at target'],
-        ['pointerup', 'top', 'bubbling'],
-        ['pointerup', 'outer', 'bubbling'],
-        ['mouseup', 'top_four', 'at target'],
-        ['mouseup', 'top', 'bubbling'],
-        ['mouseup', 'outer', 'bubbling'],
-        ['click', 'top_four', 'at target'],
-        ['click', 'top', 'bubbling'],
-        ['click', 'outer', 'bubbling'],
-        // actions.move_to_element(top_six).pause(_SLEEP_AFTER_MOVE_TIME)
-        ['pointerout', 'top_four', 'at target'],
-        ['pointerout', 'top', 'bubbling'],
-        ['pointerout', 'outer', 'bubbling'],
-        ['pointerleave', 'top_four', 'at target'],
-        ['mouseout', 'top_four', 'at target'],
-        ['mouseout', 'top', 'bubbling'],
-        ['mouseout', 'outer', 'bubbling'],
-        ['mouseleave', 'top_four', 'at target'],
-        ['pointermove', 'top_six', 'at target'],
-        ['pointermove', 'top', 'bubbling'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['mousemove', 'top_six', 'at target'],
-        ['mousemove', 'top', 'bubbling'],
-        ['mousemove', 'outer', 'bubbling'],
-        ['pointerover', 'top_six', 'at target'],
-        ['pointerover', 'top', 'bubbling'],
-        ['pointerover', 'outer', 'bubbling'],
-        ['pointerenter', 'top_six', 'at target'],
-        ['mouseover', 'top_six', 'at target'],
-        ['mouseover', 'top', 'bubbling'],
-        ['mouseover', 'outer', 'bubbling'],
-        ['mouseenter', 'top_six', 'at target'],
-        // actions.move_to_element(bottom_six).pause(_SLEEP_AFTER_MOVE_TIME)
-        ['pointerout', 'top_six', 'at target'],
-        ['pointerout', 'top', 'bubbling'],
-        ['pointerout', 'outer', 'bubbling'],
-        ['pointerleave', 'top_six', 'at target'],
-        ['pointerleave', 'top', 'at target'],
-        ['mouseout', 'top_six', 'at target'],
-        ['mouseout', 'top', 'bubbling'],
-        ['mouseout', 'outer', 'bubbling'],
-        ['mouseleave', 'top_six', 'at target'],
-        ['mouseleave', 'top', 'at target'],
-        ['pointermove', 'bottom_six', 'at target'],
-        ['pointermove', 'bottom', 'bubbling'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['mousemove', 'bottom_six', 'at target'],
-        ['mousemove', 'bottom', 'bubbling'],
-        ['mousemove', 'outer', 'bubbling'],
-        ['pointerover', 'bottom_six', 'at target'],
-        ['pointerover', 'bottom', 'bubbling'],
-        ['pointerover', 'outer', 'bubbling'],
-        ['pointerenter', 'bottom_six', 'at target'],
-        ['pointerenter', 'bottom', 'at target'],
-        ['mouseover', 'bottom_six', 'at target'],
-        ['mouseover', 'bottom', 'bubbling'],
-        ['mouseover', 'outer', 'bubbling'],
-        ['mouseenter', 'bottom_six', 'at target'],
-        ['mouseenter', 'bottom', 'at target'],
-        // actions.click(bottom_five)
-        ['pointerout', 'bottom_six', 'at target'],
-        ['pointerout', 'bottom', 'bubbling'],
-        ['pointerout', 'outer', 'bubbling'],
-        ['pointerleave', 'bottom_six', 'at target'],
-        ['mouseout', 'bottom_six', 'at target'],
-        ['mouseout', 'bottom', 'bubbling'],
-        ['mouseout', 'outer', 'bubbling'],
-        ['mouseleave', 'bottom_six', 'at target'],
-        ['pointermove', 'bottom_five', 'at target'],
-        ['pointermove', 'bottom', 'bubbling'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['mousemove', 'bottom_five', 'at target'],
-        ['mousemove', 'bottom', 'bubbling'],
-        ['mousemove', 'outer', 'bubbling'],
-        ['pointerover', 'bottom_five', 'at target'],
-        ['pointerover', 'bottom', 'bubbling'],
-        ['pointerover', 'outer', 'bubbling'],
-        ['pointerenter', 'bottom_five', 'at target'],
-        ['mouseover', 'bottom_five', 'at target'],
-        ['mouseover', 'bottom', 'bubbling'],
-        ['mouseover', 'outer', 'bubbling'],
-        ['mouseenter', 'bottom_five', 'at target'],
-        ['pointerdown', 'bottom_five', 'at target'],
-        ['pointerdown', 'bottom', 'bubbling'],
-        ['pointerdown', 'outer', 'bubbling'],
-        ['pointerup', 'bottom_five', 'at target'],
-        ['pointerup', 'bottom', 'bubbling'],
-        ['pointerup', 'outer', 'bubbling'],
-        ['click', 'bottom_five', 'at target'],
-        ['click', 'bottom', 'bubbling'],
-        ['click', 'outer', 'bubbling'],
-        // actions.click_and_hold(bottom_four)
-        ['pointerout', 'bottom_five', 'at target'],
-        ['pointerout', 'bottom', 'bubbling'],
-        ['pointerout', 'outer', 'bubbling'],
-        ['pointerleave', 'bottom_five', 'at target'],
-        ['mouseout', 'bottom_five', 'at target'],
-        ['mouseout', 'bottom', 'bubbling'],
-        ['mouseout', 'outer', 'bubbling'],
-        ['mouseleave', 'bottom_five', 'at target'],
-        ['pointermove', 'bottom_four', 'at target'],
-        ['pointermove', 'bottom', 'bubbling'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['mousemove', 'bottom_four', 'at target'],
-        ['mousemove', 'bottom', 'bubbling'],
-        ['mousemove', 'outer', 'bubbling'],
-        ['pointerover', 'bottom_four', 'at target'],
-        ['pointerover', 'bottom', 'bubbling'],
-        ['pointerover', 'outer', 'bubbling'],
-        ['pointerenter', 'bottom_four', 'at target'],
-        ['mouseover', 'bottom_four', 'at target'],
-        ['mouseover', 'bottom', 'bubbling'],
-        ['mouseover', 'outer', 'bubbling'],
-        ['mouseenter', 'bottom_four', 'at target'],
-        ['pointerdown', 'bottom_four', 'at target'],
-        ['pointerdown', 'bottom', 'bubbling'],
-        ['pointerdown', 'outer', 'bubbling'],
-        // actions.release(bottom_three)
-        ['pointermove', 'bottom_four', 'at target'],
-        ['pointermove', 'bottom', 'bubbling'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['pointerup', 'bottom_four', 'at target'],
-        ['pointerup', 'bottom', 'bubbling'],
-        ['pointerup', 'outer', 'bubbling'],
-        ['click', 'bottom_four', 'at target'],
-        ['click', 'bottom', 'bubbling'],
-        ['click', 'outer', 'bubbling'],
-        // actions.move_to_element(bottom_two).pause(_SLEEP_AFTER_MOVE_TIME)
-        ['pointerout', 'bottom_four', 'at target'],
-        ['pointerout', 'bottom', 'bubbling'],
-        ['pointerout', 'outer', 'bubbling'],
-        ['pointerleave', 'bottom_four', 'at target'],
-        ['mouseout', 'bottom_four', 'at target'],
-        ['mouseout', 'bottom', 'bubbling'],
-        ['mouseout', 'outer', 'bubbling'],
-        ['mouseleave', 'bottom_four', 'at target'],
-        ['pointermove', 'bottom_two', 'at target'],
-        ['mousemove', 'bottom_two', 'at target'],
-        ['pointerover', 'bottom_two', 'at target'],
-        ['pointerenter', 'bottom_two', 'at target'],
-        ['mouseover', 'bottom_two', 'at target'],
-        ['mouseenter', 'bottom_two', 'at target'],
-        // actions.move_to_element(bottom_one).pause(_SLEEP_AFTER_MOVE_TIME)
-        ['pointerout', 'bottom_two', 'at target'],
-        ['pointerleave', 'bottom_two', 'at target'],
-        ['mouseout', 'bottom_two', 'at target'],
-        ['mouseleave', 'bottom_two', 'at target'],
-        ['pointermove', 'bottom_one', 'at target'],
-        ['mousemove', 'bottom_one', 'at target'],
-        ['pointerover', 'bottom_one', 'at target'],
-        ['pointerenter', 'bottom_one', 'at target'],
-        ['mouseover', 'bottom_one', 'at target'],
-        ['mouseenter', 'bottom_one', 'at target'],
-        // find_element_by_id(runner, 'end').click()
-        ['pointerout', 'bottom_one', 'at target'],
-        ['pointerleave', 'bottom_one', 'at target'],
-        ['pointerleave', 'bottom', 'at target'],
-        ['mouseout', 'bottom_one', 'at target'],
-        ['mouseleave', 'bottom_one', 'at target'],
-        ['mouseleave', 'bottom', 'at target'],
-        ['pointermove', 'end', 'at target'],
-        ['pointermove', 'outer', 'bubbling'],
-        ['mousemove', 'end', 'at target'],
-        ['mousemove', 'outer', 'bubbling'],
-        ['pointerover', 'end', 'at target'],
-        ['pointerover', 'outer', 'bubbling'],
-        ['pointerenter', 'end', 'at target'],
-        ['mouseover', 'end', 'at target'],
-        ['mouseover', 'outer', 'bubbling'],
-        ['mouseenter', 'end', 'at target'],
-        ['pointerdown', 'end', 'at target'],
-        ['pointerdown', 'outer', 'bubbling'],
-        ['mousedown', 'end', 'at target'],
-        ['mousedown', 'outer', 'bubbling'],
-        ['pointerup', 'end', 'at target'],
-        ['pointerup', 'outer', 'bubbling'],
-        ['mouseup', 'end', 'at target'],
-        ['mouseup', 'outer', 'bubbling'],
-        ['click', 'end', 'at target'],
-        ['click', 'outer', 'bubbling']];
+    function checkState(e) {
+      // Check the target element state when the event is 'at target'.
+      if (e.eventPhase == 2) {
+        // Verify that there is not a duplicated or missing event for enter,
+        // leave, over, out, up, or down.
+        trackAndVerifyTargetState(e, 'mouseenter', 'mouseenter', true);
+        trackAndVerifyTargetState(e, 'mouseleave', 'mouseenter', false);
+        trackAndVerifyTargetState(e, 'mouseover', 'mouseover', true);
+        trackAndVerifyTargetState(e, 'mouseout', 'mouseover', false);
+        trackAndVerifyTargetState(e, 'mousedown', 'mousedown', true);
+        trackAndVerifyTargetState(e, 'mouseup', 'mousedown', false);
 
-      var current_event_number = 0
-      var failure_count = 0;
-
-      function CheckEvent(name, id, phase) {
-        while ((current_event_number < expected_events.length) &&
-               ((expected_events[current_event_number][0] != name) ||
-               (expected_events[current_event_number][1] != id) ||
-               (expected_events[current_event_number][2] != phase))) {
-          console.log('ERROR: Missing Event [\'' +
-                      expected_events[current_event_number][0] + '\', \'' +
-                      expected_events[current_event_number][1] + '\', \'' +
-                      expected_events[current_event_number][2] + '\'],');
-          failure_count += 1;
-          current_event_number += 1;
-        }
-        if (current_event_number < expected_events.length) {
-          current_event_number += 1;
-        } else {
-          failure_count += 1;
-          console.log('ERROR: Unexpected Event [\'' + name + '\', \'' +
-                      id + '\', \'' + phase + '\'],');
-        }
+        trackAndVerifyTargetState(e, 'pointerenter', 'pointerenter', true);
+        trackAndVerifyTargetState(e, 'pointerleave', 'pointerenter', false);
+        trackAndVerifyTargetState(e, 'pointerover', 'pointerover', true);
+        trackAndVerifyTargetState(e, 'pointerout', 'pointerover', false);
+        trackAndVerifyTargetState(e, 'pointerdown', 'pointerdown', true);
+        trackAndVerifyTargetState(e, 'pointerup', 'pointerdown', false);
       }
+    }
 
-      function LogEvent(e) {
-        var pointertype = e.pointerType ? e.pointerType + ' ' : '';
-        var id = this.getAttribute('id')
-        CheckEvent(e.type, id, phasename(e.eventPhase))
-        console.log(e.type + ' ' + pointertype + id +
-                    ' (' + this.getAttribute('class') + ')' +
-                    ' [' + phasename(e.eventPhase) + ']' +
-                    ' (' + e.screenX + ',' + e.screenY + ')');
-      }
+    function setAllHandlers(prefix, selector, callback) {
+      setHandlers(prefix + 'enter', selector, callback);
+      setHandlers(prefix + 'leave', selector, callback);
+      setHandlers(prefix + 'over', selector, callback);
+      setHandlers(prefix + 'out', selector, callback);
+      setHandlers(prefix + 'down', selector, callback);
+      setHandlers(prefix + 'up', selector, callback);
+      setHandlers(prefix + 'move', selector, callback);
+    }
 
-      function EndTest(e) {
-        console.log('Ending test.')
-        assertTrue(failure_count == 0);
-        onEndTest();
-      }
+    window.onload = function () {
+      setAllHandlers('mouse', '.track', logEvent);
+      setAllHandlers('pointer', '.track', logEvent);
+      setHandlers('click', '.track', logEvent);
+      setAllHandlers('mouse', '.cancel', cancel);
+      setAllHandlers('pointer', '.cancel', cancel);
+      setAllHandlers('mouse', '.stop', stop);
+      setAllHandlers('pointer', '.stop', stop);
+      setHandlers('pointerdown', '.capture', capture);
+      setHandlers('click', '.end', endTest);
+      setAllHandlers('mouse', '*', checkState);
+      setAllHandlers('pointer', '*', checkState);
+      console.log("Setup finished");
+      setupFinished();
+    }
 
-      function Cancel(e) {
-        console.log('cancel');
-        e.preventDefault();
-      }
+  </script>
+  <div id="outer" class="track size40 grey">
+    <div id="top" class="track size20 blue">
+      <span id="top_one" class="track size10 cyan"></span>
+      <span id="top_two" class="track size10 purple"></span>
+      *A*
+      <span id="top_three" class="track size10 yellow"></span>
+      *B*
+      <span id="top_four" class="track capture size10 grey"></span>
+      <span id="top_five" class="track size10 green"></span>
+      <span id="top_six" class="track size10 cyan"></span>
+    </div>
+    <div id="bottom" class="track size20 green">
+      <span id="bottom_one" class="track stop size10 cyan"></span>
+      <span id="bottom_two" class="track stop size10 purple"></span>
+      *A*
+      <span id="bottom_three" class="track cancel size10 yellow"></span>
+      *B*
+      <span id="bottom_four" class="track cancel capture size10 grey"></span>
+      <span id="bottom_five" class="track cancel size10 blue"></span>
+      <span id="bottom_six" class="track size10 cyan"></span>
+    </div>
+    <div id="end" class="end track size10 blue">
+    </div>
+</body>
 
-      function Stop(e) {
-        console.log('stop');
-        e.stopPropagation();
-      }
-
-      function Capture(e) {
-        console.log('capture');
-        e.target.setPointerCapture(e.pointerId);
-      }
-
-      // If the event type has value 'type', then report an error if the
-      // 'name' property on the event target already has 'value'. Otherwise,
-      // set it to the 'value'. This is used to detect erroneous boundary
-      // events (enter/leave, over/out), and up/down event sequences on an
-      // element.
-      function TrackAndVerifyTargetState(event, type, name, value) {
-        if (event.type == type) {
-          if (event.target[name] == value) {
-            console.log('ERROR: ' + type + 'event received while ' +
-                        name + ' == ' + event.target[name]);
-            assertTrue(event.target[name] != value);
-          }
-          event.target[name] = value;
-        }
-      }
-
-      function CheckState(e) {
-        // Check the target element state when the event is 'at target'.
-        if (e.eventPhase == 2)  {
-          // Verify that there is not a duplicated or missing event for enter,
-          // leave, over, out, up, or down.
-          TrackAndVerifyTargetState(e, 'mouseenter', 'mouseenter', true);
-          TrackAndVerifyTargetState(e, 'mouseleave', 'mouseenter', false);
-          TrackAndVerifyTargetState(e, 'mouseover', 'mouseover', true);
-          TrackAndVerifyTargetState(e, 'mouseout', 'mouseover', false);
-          TrackAndVerifyTargetState(e, 'mousedown', 'mousedown', true);
-          TrackAndVerifyTargetState(e, 'mouseup', 'mousedown', false);
-
-          TrackAndVerifyTargetState(e, 'pointerenter', 'pointerenter', true);
-          TrackAndVerifyTargetState(e, 'pointerleave', 'pointerenter', false);
-          TrackAndVerifyTargetState(e, 'pointerover', 'pointerover', true);
-          TrackAndVerifyTargetState(e, 'pointerout', 'pointerover', false);
-          TrackAndVerifyTargetState(e, 'pointerdown', 'pointerdown', true);
-          TrackAndVerifyTargetState(e, 'pointerup', 'pointerdown', false);
-        }
-      }
-
-      function SetHandlers(event, selector, callback) {
-        var elements = document.querySelectorAll(selector);
-        for (var i = 0; i < elements.length; ++i) {
-          elements[i].addEventListener(event, callback);
-        }
-      }
-
-      function SetAllHandlers(prefix, selector, callback) {
-        SetHandlers(prefix + 'enter', selector, callback);
-        SetHandlers(prefix + 'leave', selector, callback);
-        SetHandlers(prefix + 'over', selector, callback);
-        SetHandlers(prefix + 'out', selector, callback);
-        SetHandlers(prefix + 'down', selector, callback);
-        SetHandlers(prefix + 'up', selector, callback);
-        SetHandlers(prefix + 'move', selector, callback);
-      }
-
-      window.onload = function() {
-        SetAllHandlers('mouse', '.track', LogEvent);
-        SetAllHandlers('pointer', '.track', LogEvent);
-        SetHandlers('click', '.track', LogEvent);
-        SetAllHandlers('mouse', '.cancel', Cancel);
-        SetAllHandlers('pointer', '.cancel', Cancel);
-        SetAllHandlers('mouse', '.stop', Stop);
-        SetAllHandlers('pointer', '.stop', Stop);
-        SetHandlers('pointerdown', '.capture', Capture);
-        SetHandlers('click', '.end', EndTest);
-        SetAllHandlers('mouse', '*', CheckState);
-        SetAllHandlers('pointer', '*', CheckState);
-        console.log("Setup finished");
-        setupFinished();
-      }
-
-    </script>
-    <div id="outer" class="track size40 grey">
-      <div id="top" class="track size20 blue">
-        <span id="top_one" class="track size10 cyan"></span>
-        <span id="top_two" class="track size10 purple"></span>
-        *A*
-        <span id="top_three" class="track size10 yellow"></span>
-        *B*
-        <span id="top_four" class="track capture size10 grey"></span>
-        <span id="top_five" class="track size10 green"></span>
-        <span id="top_six" class="track size10 cyan"></span>
-      </div>
-      <div id="bottom" class="track size20 green">
-        <span id="bottom_one" class="track stop size10 cyan"></span>
-        <span id="bottom_two" class="track stop size10 purple"></span>
-        *A*
-        <span id="bottom_three" class="track cancel size10 yellow"></span>
-        *B*
-        <span id="bottom_four" class="track cancel capture size10 grey"></span>
-        <span id="bottom_five" class="track cancel size10 blue"></span>
-        <span id="bottom_six" class="track size10 cyan"></span>
-      </div>
-      <div id="end" class="end track size10 blue">
-      </div>
-  </body>
 </html>
diff --git a/cobalt/black_box_tests/testdata/scroll.html b/cobalt/black_box_tests/testdata/scroll.html
index d8adade..5f45e9b 100644
--- a/cobalt/black_box_tests/testdata/scroll.html
+++ b/cobalt/black_box_tests/testdata/scroll.html
@@ -5,6 +5,7 @@
 <head>
   <title>Cobalt scroll test</title>
   <script src='black_box_js_test_utils.js'></script>
+  <script src='pointer_event_test_utils.js'></script>
   <style>
     .app {
       position: absolute;
@@ -16,7 +17,6 @@
     }
 
     .row {
-      /* pointer-events: auto; */
       position: relative;
       overflow: auto;
       height: 20rem;
@@ -35,18 +35,48 @@
 <body>
   <div class="app" style="overflow:auto;">
     <div id="row" class="row" style="height: 20rem;">
-      <div id="top_one" class="tile">One</div>
+      <div id="tile" class="tile">One</div>
     </div>
   </div>
 </body>
 <script>
+  const expected_events = [
+    // actions.move_to_element(row).pause(_SLEEP_AFTER_MOVE_TIME)
+    { name: 'pointermove', id: 'tile', phase: Event.AT_TARGET },
+    { name: 'pointermove', id: 'row', phase: Event.BUBBLING_PHASE },
+
+    // actions.click_and_hold(row).pause(_SLEEP_AFTER_MOVE_TIME)
+    { name: 'pointermove', id: 'tile', phase: Event.AT_TARGET },
+    { name: 'pointermove', id: 'row', phase: Event.BUBBLING_PHASE },
+
+    // actions.move_by_offset(-500, 0).pause(_SLEEP_AFTER_MOVE_TIME)
+    { name: 'pointerdown', id: 'tile', phase: Event.AT_TARGET },
+    { name: 'pointerdown', id: 'row', phase: Event.BUBBLING_PHASE },
+    { name: 'pointercancel', id: 'tile', phase: Event.AT_TARGET },
+    { name: 'pointercancel', id: 'row', phase: Event.BUBBLING_PHASE },
+    { name: 'scroll', id: 'row', phase: Event.AT_TARGET },
+  ]
+
   function checkEndState() {
     const scroll_container = document.getElementById("row");
     assertTrue(scroll_container.scrollLeft > 0);
+    assertEqual(failure_count, 0);
     onEndTest();
   }
 
+  function setAllHandlers(prefix, selector, callback) {
+    setHandlers(prefix + 'down', selector, callback);
+    setHandlers(prefix + 'up', selector, callback);
+    setHandlers(prefix + 'move', selector, callback);
+    setHandlers(prefix + 'cancel', selector, callback);
+}
+
   window.onload = () => {
+    setAllHandlers('pointer', '#row', logEvent);
+    setAllHandlers('pointer', '#tile', logEvent);
+    setHandlers('scroll', '#row', logEvent);
+    setHandlers('scroll', '#tile', logEvent);
+    setHandlers('scroll', '.app', logEvent);
     setupFinished();
   }
 
diff --git a/cobalt/black_box_tests/testdata/scroll_with_none_pointer_events.html b/cobalt/black_box_tests/testdata/scroll_with_none_pointer_events.html
new file mode 100644
index 0000000..c23a9ed
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/scroll_with_none_pointer_events.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+
+<html>
+
+<head>
+  <title>Cobalt scroll test</title>
+  <script src='black_box_js_test_utils.js'></script>
+  <style>
+    .app {
+      position: absolute;
+      width: 80rem;
+      height: 45rem;
+      overflow: hidden;
+      padding: 20px;
+      z-index: 1;
+    }
+
+    .row {
+      position: relative;
+      overflow: auto;
+      height: 20rem;
+      width: 80rem;
+    }
+
+    .tile {
+      position: absolute;
+      height: 10rem;
+      width: 200rem;
+      background: linear-gradient(0.25turn, #f4aca9, #ebf8e1, #bce6f3);
+    }
+  </style>
+</head>
+
+<body>
+  <div class="app" style="overflow:auto;">
+    <div id="row" class="row" style="height: 20rem; pointer-events: none;">
+      <div id="top_one" class="tile">One</div>
+    </div>
+  </div>
+</body>
+<script>
+  const scroll_container = document.getElementById("row");
+
+  function checkState(key) {
+    assertTrue(scroll_container.scrollLeft == 0);
+    if (key == 2) {
+      onEndTest();
+    }
+  }
+
+  window.onload = () => {
+    setupFinished();
+  }
+
+  window.onkeydown = (event) => {
+    if (event.key == 0 || event.key == 2) {
+      checkState(event.key);
+    } else if (event.key == 1) {
+      scroll_container.style.pointerEvents = 'auto';
+    }
+  }
+</script>
+
+</html>
diff --git a/cobalt/black_box_tests/testdata/service_worker_add_to_cache_test.html b/cobalt/black_box_tests/testdata/service_worker_add_to_cache_test.html
index c8a0a85..44e74cb 100644
--- a/cobalt/black_box_tests/testdata/service_worker_add_to_cache_test.html
+++ b/cobalt/black_box_tests/testdata/service_worker_add_to_cache_test.html
@@ -27,7 +27,11 @@
     const unregisterAll = () => navigator.serviceWorker.getRegistrations().then(registrations =>
         Promise.all(registrations.map(r => r.unregister())));
     const fail = msg => {
-      if (msg) {
+      if (msg instanceof DOMException) {
+        console.error(`[DOMException (code=${msg.code})] ${msg.name}: ${msg.message}`);
+      } else if (msg instanceof Error) {
+        console.error(`[Error] ${msg.name}: ${msg.message}`);
+      } else if (msg) {
         console.error(msg);
       }
       unregisterAll().then(notReached);
@@ -49,9 +53,9 @@
       registration.active.postMessage('start-test');
     });
 
-    unregisterAll().then(() => {
-      navigator.serviceWorker.register('service_worker_add_to_cache_test.js').catch(fail);
-    });
+    unregisterAll()
+      .then(() => navigator.serviceWorker.register('service_worker_add_to_cache_test.js'))
+      .catch(fail);
 
     setupFinished();
   </script>
diff --git a/cobalt/black_box_tests/testdata/service_worker_cache_keys_test.html b/cobalt/black_box_tests/testdata/service_worker_cache_keys_test.html
index 0bedc3e..e884bcd 100644
--- a/cobalt/black_box_tests/testdata/service_worker_cache_keys_test.html
+++ b/cobalt/black_box_tests/testdata/service_worker_cache_keys_test.html
@@ -29,7 +29,11 @@
     const unregisterAll = () => navigator.serviceWorker.getRegistrations().then(registrations =>
         Promise.all(registrations.map(r => r.unregister())));
     const fail = msg => {
-      if (msg) {
+      if (msg instanceof DOMException) {
+        console.error(`[DOMException (code=${msg.code})] ${msg.name}: ${msg.message}`);
+      } else if (msg instanceof Error) {
+        console.error(`[Error] ${msg.name}: ${msg.message}`);
+      } else if (msg) {
         console.error(msg);
       }
       unregisterAll().then(notReached);
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
deleted file mode 100644
index 12e422b..0000000
--- a/cobalt/black_box_tests/tests/evergreen_verify_qa_channel_update_test.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# 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/pointer_event_on_cropped_element_test.py b/cobalt/black_box_tests/tests/pointer_event_on_cropped_element_test.py
new file mode 100644
index 0000000..5facb85
--- /dev/null
+++ b/cobalt/black_box_tests/tests/pointer_event_on_cropped_element_test.py
@@ -0,0 +1,70 @@
+# 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 events for an extended sequence of pointer moves and clicks."""
+
+# This test generates an extended sequence of pointer move and click events,
+# and verifies the corresponding sequence of pointer, mouse, and click events
+# dispatched to the HTML elements. The test includes use of pointer capture and
+# use of preventDefault() and stopPropagation() on the events, as well as event
+# bubbling.
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import logging
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+from selenium.webdriver.common.action_chains import ActionChains
+
+# Time to sleep after a mouse move to give the device time to process it and
+# to avoid collapsing with subsequent move events.
+_SLEEP_AFTER_MOVE_TIME = 0.5
+
+
+def find_element_by_id(runner, id_selector):
+  return runner.webdriver.find_elements_by_css_selector('#' + id_selector)[0]
+
+
+class PointerTest(black_box_tests.BlackBoxTestCase):
+  """Tests pointer and mouse event."""
+
+  def test_pointer_events(self):
+    with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+      url = server.GetURL(
+          file_name='testdata/pointer_event_on_cropped_element_test.html')
+
+      with self.CreateCobaltRunner(url=url) as runner:
+        logging.info('JS Test Setup WaitForJSTestsSetup')
+        runner.WaitForJSTestsSetup()
+        logging.info('JS Test Setup')
+        self.assertTrue(runner.webdriver)
+
+        upper_element = find_element_by_id(runner, 'upper_element')
+        lower_element = find_element_by_id(runner, 'lower_element')
+        end = find_element_by_id(runner, 'end')
+
+        actions = ActionChains(runner.webdriver)
+        actions.move_to_element(lower_element).pause(_SLEEP_AFTER_MOVE_TIME)
+        actions.click()
+        actions.move_to_element_with_offset(upper_element, 5,
+                                            5).pause(_SLEEP_AFTER_MOVE_TIME)
+        actions.click()
+        actions.move_to_element(upper_element).pause(_SLEEP_AFTER_MOVE_TIME)
+        actions.click()
+        actions.click(end)
+        actions.perform()
+
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/black_box_tests/tests/pointer_event_on_fixed_element_test.py b/cobalt/black_box_tests/tests/pointer_event_on_fixed_element_test.py
new file mode 100644
index 0000000..893c9c8
--- /dev/null
+++ b/cobalt/black_box_tests/tests/pointer_event_on_fixed_element_test.py
@@ -0,0 +1,64 @@
+# 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 events for an extended sequence of pointer moves and clicks."""
+
+# This test generates an extended sequence of pointer move and click events,
+# and verifies the corresponding sequence of pointer, mouse, and click events
+# dispatched to the HTML elements. The test includes use of pointer capture and
+# use of preventDefault() and stopPropagation() on the events, as well as event
+# bubbling.
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import logging
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+from selenium.webdriver.common.action_chains import ActionChains
+
+# Time to sleep after a mouse move to give the device time to process it and
+# to avoid collapsing with subsequent move events.
+_SLEEP_AFTER_MOVE_TIME = 0.5
+
+
+def find_element_by_id(runner, id_selector):
+  return runner.webdriver.find_elements_by_css_selector('#' + id_selector)[0]
+
+
+class PointerTest(black_box_tests.BlackBoxTestCase):
+  """Tests pointer and mouse event."""
+
+  def test_pointer_events(self):
+    with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+      url = server.GetURL(
+          file_name='testdata/pointer_event_on_fixed_element_test.html')
+
+      with self.CreateCobaltRunner(url=url) as runner:
+        logging.info('JS Test Setup WaitForJSTestsSetup')
+        runner.WaitForJSTestsSetup()
+        logging.info('JS Test Setup')
+        self.assertTrue(runner.webdriver)
+
+        container = find_element_by_id(runner, 'container')
+        surface = find_element_by_id(runner, 'surface')
+
+        actions = ActionChains(runner.webdriver)
+        actions.move_to_element(container).pause(_SLEEP_AFTER_MOVE_TIME)
+        actions.click(container).pause(_SLEEP_AFTER_MOVE_TIME)
+        actions.move_to_element(surface).pause(_SLEEP_AFTER_MOVE_TIME)
+        actions.click(surface).pause(_SLEEP_AFTER_MOVE_TIME)
+        actions.perform()
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/black_box_tests/tests/scroll.py b/cobalt/black_box_tests/tests/scroll.py
index e0db0fd..58b104d 100644
--- a/cobalt/black_box_tests/tests/scroll.py
+++ b/cobalt/black_box_tests/tests/scroll.py
@@ -14,7 +14,6 @@
 """Tests for scroll containers."""
 
 import logging
-import traceback
 
 from cobalt.black_box_tests import black_box_tests
 from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
@@ -34,31 +33,22 @@
   """Tests pointer and mouse event."""
 
   def test_pointer_events(self):
-    try:
-      with ThreadedWebServer(
-          binding_address=self.GetBindingAddress()) as server:
-        url = server.GetURL(file_name='testdata/scroll.html')
+    with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+      url = server.GetURL(file_name='testdata/scroll.html')
 
-        with self.CreateCobaltRunner(url=url) as runner:
-          logging.info('JS Test Setup WaitForJSTestsSetup')
-          runner.WaitForJSTestsSetup()
-          logging.info('JS Test Setup')
-          self.assertTrue(runner.webdriver)
+      with self.CreateCobaltRunner(url=url) as runner:
+        logging.info('JS Test Setup WaitForJSTestsSetup')
+        runner.WaitForJSTestsSetup()
+        logging.info('JS Test Setup')
+        self.assertTrue(runner.webdriver)
 
-          row = find_element_by_id(runner, 'row')
+        row = find_element_by_id(runner, 'row')
 
-          # Perform mouse actions with ActionChains.
-          #   https://www.selenium.dev/selenium/docs/api/py/webdriver/selenium.webdriver.common.action_chains.html#module-selenium.webdriver.common.action_chains  # pylint: disable=line-too-long
-          actions = ActionChains(runner.webdriver)
-          actions.click_and_hold(row).pause(_SLEEP_AFTER_MOVE_TIME)
-          actions.move_by_offset(-100, 0)
-          actions.release()
-          actions.perform()
-          runner.SendKeys(keys.Keys.NUMPAD0)
-          self.assertTrue(runner.JSTestsSucceeded())
-    except:  # pylint: disable=bare-except
-      traceback.print_exc()
-      # Consider an exception being thrown as a test failure.
-      self.fail('Test failure')
-    finally:
-      logging.info('Cleaning up.')
+        actions = ActionChains(runner.webdriver)
+        actions.move_to_element(row).pause(_SLEEP_AFTER_MOVE_TIME)
+        actions.click_and_hold(row).pause(_SLEEP_AFTER_MOVE_TIME)
+        actions.move_by_offset(-500, 0).pause(_SLEEP_AFTER_MOVE_TIME)
+        actions.release()
+        actions.perform()
+        runner.SendKeys(keys.Keys.NUMPAD0)
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/black_box_tests/tests/scroll_with_none_pointer_events.py b/cobalt/black_box_tests/tests/scroll_with_none_pointer_events.py
new file mode 100644
index 0000000..f67fab6
--- /dev/null
+++ b/cobalt/black_box_tests/tests/scroll_with_none_pointer_events.py
@@ -0,0 +1,65 @@
+# 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 for scroll containers."""
+
+import logging
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+from selenium.webdriver.common.action_chains import ActionChains
+from selenium.webdriver.common import keys
+
+# Time to sleep after a mouse move to give the device time to process it and
+# to avoid collapsing with subsequent move events.
+_SLEEP_AFTER_MOVE_TIME = 0.5
+
+
+def find_element_by_id(runner, id_selector):
+  return runner.webdriver.find_elements_by_css_selector('#' + id_selector)[0]
+
+
+class PointerTest(black_box_tests.BlackBoxTestCase):
+  """Tests pointer and mouse event."""
+
+  def test_pointer_events(self):
+    with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+      url = server.GetURL(
+          file_name='testdata/scroll_with_none_pointer_events.html')
+
+      with self.CreateCobaltRunner(url=url) as runner:
+        logging.info('JS Test Setup WaitForJSTestsSetup')
+        runner.WaitForJSTestsSetup()
+        logging.info('JS Test Setup')
+        self.assertTrue(runner.webdriver)
+
+        row = find_element_by_id(runner, 'row')
+        actions = ActionChains(runner.webdriver)
+
+        # No scrolling when pointer-events:none
+        actions.click_and_hold(row).pause(_SLEEP_AFTER_MOVE_TIME)
+        actions.move_by_offset(-100, 0)
+        actions.release()
+        actions.perform()
+        runner.SendKeys(keys.Keys.NUMPAD0)
+
+        # No scrolling if swipe starts with pointer-events:none,
+        # even if pointer-events is enabled during the swipe
+        actions2 = ActionChains(runner.webdriver)
+        actions2.click_and_hold(row).pause(_SLEEP_AFTER_MOVE_TIME)
+        actions2.send_keys(keys.Keys.NUMPAD1).pause(_SLEEP_AFTER_MOVE_TIME)
+        actions2.move_by_offset(-100, 0)
+        actions2.release()
+        actions2.perform()
+        runner.SendKeys(keys.Keys.NUMPAD2)
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn
index 97ac9fa..7e742bc 100644
--- a/cobalt/browser/BUILD.gn
+++ b/cobalt/browser/BUILD.gn
@@ -55,6 +55,7 @@
     ":browser",
     ":browser_switches",
     "//cobalt/base",
+    "//cobalt/css_parser",
     "//net",
   ]
   data_deps = [
@@ -117,7 +118,6 @@
     "client_hint_headers.h",
     "device_authentication.cc",
     "device_authentication.h",
-    "lifecycle_observer.h",
     "on_screen_keyboard_starboard_bridge.cc",
     "on_screen_keyboard_starboard_bridge.h",
     "render_tree_combiner.cc",
@@ -199,7 +199,7 @@
     "//components/metrics_services_manager",
     "//crypto",
     "//net",
-    "//starboard",
+    "//starboard:starboard_group",
     "//starboard/common",
     "//third_party/icu:icui18n",
     "//third_party/protobuf:protobuf_lite",
@@ -412,3 +412,10 @@
 cache_templates("cached_jinja_templates") {
   output_dir = _bindings_scripts_output_dir
 }
+
+group("test_dependencies_on_browser") {
+  testonly = true
+
+  # TODO: 297087147 - Depend on smaller targets than browser.
+  deps = [ "//cobalt/browser" ]
+}
diff --git a/cobalt/browser/application.cc b/cobalt/browser/application.cc
index e05b7d0..1f73882 100644
--- a/cobalt/browser/application.cc
+++ b/cobalt/browser/application.cc
@@ -116,7 +116,7 @@
 const int64_t kWatchdogTimeWait = 2000000;
 
 bool IsStringNone(const std::string& str) {
-  return !base::strcasecmp(str.c_str(), "none");
+  return !strcasecmp(str.c_str(), "none");
 }
 
 #if defined(ENABLE_WEBDRIVER) || defined(ENABLE_DEBUGGER)
@@ -458,7 +458,12 @@
 }
 
 int StringToLogLevel(const std::string& log_level) {
-  if (log_level == "info") {
+  if (log_level == "verbose") {
+    // The lower the verbose level is, the more messages are logged.  Set it to
+    // a lower enough value to ensure that all known verbose messages are
+    // logged.
+    return logging::LOG_VERBOSE - 15;
+  } else if (log_level == "info") {
     return logging::LOG_INFO;
   } else if (log_level == "warning") {
     return logging::LOG_WARNING;
@@ -696,9 +701,6 @@
                        base::kApplicationStateStarted, kWatchdogTimeInterval,
                        kWatchdogTimeWait, watchdog::NONE);
 
-  cobalt::cache::Cache::GetInstance()->set_persistent_settings(
-      persistent_settings_.get());
-
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   base::Optional<cssom::ViewportSize> requested_viewport_size =
       GetRequestedViewportSize(command_line);
@@ -1021,6 +1023,8 @@
         base::TimeDelta::FromSeconds(duration_in_seconds));
   }
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
+
+  AddCrashLogApplicationState(base::kApplicationStateStarted);
 }
 
 Application::~Application() {
@@ -1520,6 +1524,7 @@
       metrics::kMetricEnabledSettingName, false);
   auto metric_event_interval = persistent_settings_->GetPersistentSettingAsInt(
       metrics::kMetricEventIntervalSettingName, 300);
+  metrics_services_manager_->SetEventDispatcher(&event_dispatcher_);
   metrics_services_manager_->SetUploadInterval(metric_event_interval);
   metrics_services_manager_->ToggleMetricsEnabled(is_metrics_enabled);
   // Metric recording state initialization _must_ happen before we bootstrap
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc
index d83200f..f44b2b2 100644
--- a/cobalt/browser/browser_module.cc
+++ b/cobalt/browser/browser_module.cc
@@ -52,6 +52,7 @@
 #include "cobalt/math/matrix3_f.h"
 #include "cobalt/overlay_info/overlay_info_registry.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
+#include "cobalt/trace_event/scoped_trace_to_file.h"
 #include "cobalt/ui_navigation/scroll_engine/scroll_engine.h"
 #include "cobalt/web/csp_delegate_factory.h"
 #include "cobalt/web/navigator_ua_data.h"
@@ -161,6 +162,13 @@
     "is useful when trying to target testing to certain codecs, since other "
     "codecs will get picked as a fallback as a result.";
 
+const char kNavigateTimedTrace[] = "navigate_timed_trace";
+const char kNavigateTimedTraceShortHelp[] =
+    "Request a timed trace from the next navigation.";
+const char kNavigateTimedTraceLongHelp[] =
+    "When this is called, a timed trace will start at the next navigation "
+    "and run for the given number of seconds.";
+
 void ScreenshotCompleteCallback(const base::FilePath& output_path) {
   DLOG(INFO) << "Screenshot written to " << output_path.value();
 }
@@ -268,6 +276,11 @@
                      base::Unretained(this)),
           kDisableMediaCodecsCommandShortHelp,
           kDisableMediaCodecsCommandLongHelp)),
+      ALLOW_THIS_IN_INITIALIZER_LIST(navigate_timed_trace_command_handler_(
+          kNavigateTimedTrace,
+          base::Bind(&BrowserModule::OnNavigateTimedTrace,
+                     base::Unretained(this)),
+          kNavigateTimedTraceShortHelp, kNavigateTimedTraceLongHelp)),
 #endif  // defined(ENABLE_DEBUGGER)
       has_resumed_(base::WaitableEvent::ResetPolicy::MANUAL,
                    base::WaitableEvent::InitialState::NOT_SIGNALED),
@@ -289,7 +302,7 @@
 
   platform_info_.reset(new browser::UserAgentPlatformInfo());
   service_worker_registry_.reset(new ServiceWorkerRegistry(
-      &web_settings_, network_module, platform_info_.get(), url));
+      &web_settings_, network_module, platform_info_.get()));
 
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
   SbCoreDumpRegisterHandler(BrowserModule::CoreDumpHandler, this);
@@ -462,7 +475,9 @@
   }
   debug_console_.reset();
 #endif
+
   DestroySplashScreen();
+  DestroyScrollEngine();
   // Make sure the WebModule is destroyed before the ServiceWorkerRegistry
   if (web_module_) {
     lifecycle_observers_.RemoveObserver(web_module_.get());
@@ -478,6 +493,7 @@
   DLOG(INFO) << "In BrowserModule::Navigate " << url;
   TRACE_EVENT1("cobalt::browser", "BrowserModule::Navigate()", "url",
                url.spec());
+
   // Reset the waitable event regardless of the thread. This ensures that the
   // webdriver won't incorrectly believe that the webmodule has finished loading
   // when it calls Navigate() and waits for the |web_module_loaded_| signal.
@@ -492,33 +508,51 @@
 
   // First try any registered handlers. If one of these handles the URL, we
   // don't use the web module.
-  if (TryURLHandlers(url)) {
+  if (NavigateTryURLHandlers(url)) {
     return;
   }
 
   // Clear error handling once we're told to navigate, either because it's the
   // retry from the error or something decided we should navigate elsewhere.
-  on_error_retry_timer_.Stop();
-  waiting_for_error_retry_ = false;
+  NavigateResetErrorHandling();
 
   // Navigations aren't allowed if the app is frozen. If this is the case,
   // simply set the pending navigate url, which will cause the navigation to
   // occur when Cobalt resumes, and return.
-  if (application_state_ == base::kApplicationStateFrozen) {
-    pending_navigate_url_ = url;
+  if (NavigateHandleStateFrozen(url)) {
     return;
   }
 
-  // Now that we know the navigation is occurring, clear out
-  // |pending_navigate_url_|.
-  pending_navigate_url_ = GURL::EmptyGURL();
+  // Destroys the old WebModule, increments the navigation generation
+  // number, and resets the main WebModule layer
+  NavigateResetWebModule();
 
+  // checks whether a service worker should be started for the given URL
+  auto service_worker_started_event = std::make_unique<base::WaitableEvent>();
+  bool can_start_service_worker =
+      NavigateServiceWorkerSetups(url, service_worker_started_event.get());
+
+  // Wait until after the old WebModule is destroyed before setting the navigate
+  // time so that it won't be included in the time taken to load the URL.
+  navigate_time_ = base::TimeTicks::Now().ToInternalValue();
+
+  const ViewportSize viewport_size = GetViewportSize();
+
+  // Show a splash screen while we're waiting for the web page to load.
+  NavigateSetupSplashScreen(url, viewport_size);
+
+  NavigateSetupScrollEngine();
+
+  NavigateCreateWebModule(url, can_start_service_worker,
+                          service_worker_started_event.get(), viewport_size);
+}
+
+void BrowserModule::NavigateResetWebModule() {
 #if defined(ENABLE_DEBUGGER)
   if (web_module_) {
     web_module_->FreezeDebugger(&debugger_state_);
   }
 #endif  // defined(ENABLE_DEBUGGER)
-
   // Destroy old WebModule first, so we don't get a memory high-watermark after
   // the second WebModule's constructor runs, but before
   // std::unique_ptr::reset() is run.
@@ -535,22 +569,66 @@
 
   main_web_module_layer_->Reset();
 
+#if defined(ENABLE_DEBUGGER)
+  // Check to see if a timed_trace has been set, indicating that we should
+  // begin a timed trace upon startup.
+  if (navigate_timed_trace_duration_ != base::TimeDelta()) {
+    trace_event::TraceToFileForDuration(
+        base::FilePath(FILE_PATH_LITERAL("timed_trace.json")),
+        navigate_timed_trace_duration_);
+    navigate_timed_trace_duration_ = base::TimeDelta();
+  }
+#endif  // defined(ENABLE_DEBUGGER)
+}
+
+void BrowserModule::NavigateResetErrorHandling() {
+  on_error_retry_timer_.Stop();
+  waiting_for_error_retry_ = false;
+}
+
+bool BrowserModule::NavigateHandleStateFrozen(const GURL& url) {
+  if (application_state_ == base::kApplicationStateFrozen) {
+    pending_navigate_url_ = url;
+    return true;
+  }
+
+  // Now that we know the navigation is occurring, clear out
+  // |pending_navigate_url_|.
+  pending_navigate_url_ = GURL::EmptyGURL();
+  return false;
+}
+
+bool BrowserModule::NavigateServiceWorkerSetups(
+    const GURL& url, base::WaitableEvent* service_worker_started_event) {
   // Service worker should only start for HTTP or HTTPS fetches.
   // https://fetch.spec.whatwg.org/commit-snapshots/8f8ab504da6ca9681db5c7f8aa3d1f4b6bf8840c/#http-fetch
   bool can_start_service_worker = url.SchemeIsHTTPOrHTTPS();
-  auto service_worker_started_event = std::make_unique<base::WaitableEvent>();
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+  if (watchdog) {
+    std::vector<std::string> service_worker_clients = {
+        worker::WorkerConsts::kServiceWorkerRegistryName,
+        worker::WorkerConsts::kServiceWorkerName};
+    std::string violation_json =
+        watchdog->GetWatchdogViolations(service_worker_clients);
+    {
+      if (violation_json != "") {
+        LOG(WARNING) << "Service Worker watchdog violation detected: "
+                     << violation_json;
+        LOG(WARNING) << "Erase Service Worker registration map.";
+        can_start_service_worker = false;
+        service_worker_registry_->EraseRegistrationMap();
+      }
+    }
+  }
   if (can_start_service_worker) {
     service_worker_registry_->EnsureServiceWorkerStarted(
-        url::Origin::Create(url), url, service_worker_started_event.get());
+        url::Origin::Create(url), url, service_worker_started_event);
   }
+  return can_start_service_worker;
+}
 
-  // Wait until after the old WebModule is destroyed before setting the navigate
-  // time so that it won't be included in the time taken to load the URL.
-  navigate_time_ = base::TimeTicks::Now().ToInternalValue();
-
-  // Show a splash screen while we're waiting for the web page to load.
-  const ViewportSize viewport_size = GetViewportSize();
-
+void BrowserModule::NavigateSetupSplashScreen(
+    const GURL& url, const ViewportSize viewport_size) {
   DestroySplashScreen();
   if (options_.enable_splash_screen_on_reloads ||
       main_web_module_generation_ == 1) {
@@ -571,10 +649,19 @@
       lifecycle_observers_.AddObserver(splash_screen_.get());
     }
   }
+}
 
+void BrowserModule::NavigateSetupScrollEngine() {
+  DestroyScrollEngine();
   scroll_engine_.reset(new ui_navigation::scroll_engine::ScrollEngine());
+  lifecycle_observers_.AddObserver(scroll_engine_.get());
   scroll_engine_->thread()->Start();
+}
 
+void BrowserModule::NavigateCreateWebModule(
+    const GURL& url, bool can_start_service_worker,
+    base::WaitableEvent* service_worker_started_event,
+    const ViewportSize viewport_size) {
 // Create new WebModule.
 #if !defined(COBALT_FORCE_CSP)
   options_.web_module_options.csp_insecure_allowed_token =
@@ -604,8 +691,8 @@
   }
 
   options.provide_screenshot_function =
-      base::Bind(&ScreenShotWriter::RequestScreenshotToMemoryUnencoded,
-                 base::Unretained(screen_shot_writer_.get()));
+      base::Bind(&BrowserModule::RequestScreenshotToMemoryUnencoded,
+                 base::Unretained(this));
 
 #if defined(ENABLE_DEBUGGER)
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
@@ -719,6 +806,14 @@
   return web_module_loaded_.TimedWait(timeout);
 }
 
+void BrowserModule::EnsureScreenShotWriter() {
+  if (!screen_shot_writer_ && renderer_module_) {
+    screen_shot_writer_.reset(
+        new ScreenShotWriter(renderer_module_->pipeline()));
+  }
+}
+
+#if defined(ENABLE_WEBDRIVER) || defined(ENABLE_DEBUGGER)
 void BrowserModule::RequestScreenshotToFile(
     const base::FilePath& path,
     loader::image::EncodedStaticImage::ImageFormat image_format,
@@ -726,7 +821,9 @@
     const base::Closure& done_callback) {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::RequestScreenshotToFile()");
   DCHECK_EQ(base::MessageLoop::current(), self_message_loop_);
+  EnsureScreenShotWriter();
   DCHECK(screen_shot_writer_);
+  if (!screen_shot_writer_) return;
 
   scoped_refptr<render_tree::Node> render_tree;
   web_module_->DoSynchronousLayoutAndGetRenderTree(&render_tree);
@@ -744,7 +841,9 @@
     const base::Optional<math::Rect>& clip_rect,
     const ScreenShotWriter::ImageEncodeCompleteCallback& screenshot_ready) {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::RequestScreenshotToMemory()");
+  EnsureScreenShotWriter();
   DCHECK(screen_shot_writer_);
+  if (!screen_shot_writer_) return;
   // Note: This does not have to be called from self_message_loop_.
 
   scoped_refptr<render_tree::Node> render_tree;
@@ -757,6 +856,19 @@
   screen_shot_writer_->RequestScreenshotToMemory(image_format, render_tree,
                                                  clip_rect, screenshot_ready);
 }
+#endif  // defined(ENABLE_WEBDRIVER) || defined(ENABLE_DEBUGGER)
+
+// Request a screenshot to memory without compressing the image.
+void BrowserModule::RequestScreenshotToMemoryUnencoded(
+    const scoped_refptr<render_tree::Node>& render_tree_root,
+    const base::Optional<math::Rect>& clip_rect,
+    const renderer::Pipeline::RasterizationCompleteCallback& callback) {
+  EnsureScreenShotWriter();
+  DCHECK(screen_shot_writer_);
+  if (!screen_shot_writer_) return;
+  screen_shot_writer_->RequestScreenshotToMemoryUnencoded(render_tree_root,
+                                                          clip_rect, callback);
+}
 
 void BrowserModule::ProcessRenderTreeSubmissionQueue() {
   TRACE_EVENT0("cobalt::browser",
@@ -1079,6 +1191,13 @@
   SubmitCurrentRenderTreeToRenderer();
 }
 
+void BrowserModule::OnNavigateTimedTrace(const std::string& time) {
+  double duration_in_seconds = 0;
+  base::StringToDouble(time, &duration_in_seconds);
+  navigate_timed_trace_duration_ =
+      base::TimeDelta::FromMilliseconds(static_cast<int64_t>(
+          duration_in_seconds * base::Time::kMillisecondsPerSecond));
+}
 #endif  // defined(ENABLE_DEBUGGER)
 
 void BrowserModule::OnOnScreenKeyboardInputEventProduced(
@@ -1345,7 +1464,7 @@
   }
 }
 
-bool BrowserModule::TryURLHandlers(const GURL& url) {
+bool BrowserModule::NavigateTryURLHandlers(const GURL& url) {
   for (URLHandlerCollection::const_iterator iter = url_handlers_.begin();
        iter != url_handlers_.end(); ++iter) {
     if (iter->Run(url)) {
@@ -1379,6 +1498,20 @@
   }
 }
 
+void BrowserModule::DestroyScrollEngine() {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::DestroyScrollEngine()");
+  if (base::MessageLoop::current() != self_message_loop_) {
+    self_message_loop_->task_runner()->PostTask(
+        FROM_HERE, base::Bind(&BrowserModule::DestroyScrollEngine, weak_this_));
+    return;
+  }
+  if (scroll_engine_ &&
+      lifecycle_observers_.HasObserver(scroll_engine_.get())) {
+    lifecycle_observers_.RemoveObserver(scroll_engine_.get());
+  }
+  scroll_engine_.reset();
+}
+
 #if defined(ENABLE_WEBDRIVER)
 std::unique_ptr<webdriver::SessionDriver> BrowserModule::CreateSessionDriver(
     const webdriver::protocol::SessionId& session_id) {
@@ -1686,7 +1819,7 @@
       system_window_.get(),
       RendererModuleWithCameraOptions(options_.renderer_module_options,
                                       input_device_manager_->camera_3d())));
-  screen_shot_writer_.reset(new ScreenShotWriter(renderer_module_->pipeline()));
+  screen_shot_writer_.reset();
 }
 
 void BrowserModule::DestroyRendererModule() {
@@ -2126,11 +2259,7 @@
   auto url_request_context = network_module_->url_request_context();
   auto http_cache = url_request_context->http_transaction_factory()->GetCache();
   if (!http_cache) return;
-  auto cache_backend = static_cast<disk_cache::CobaltBackendImpl*>(
-      http_cache->GetCurrentBackend());
-  if (cache_backend) {
-    cache_backend->ValidatePersistentSettings();
-  }
+  network_module_->url_request_context()->ValidateCachePersistentSettings();
 }
 
 }  // namespace browser
diff --git a/cobalt/browser/browser_module.h b/cobalt/browser/browser_module.h
index 00fd5ce..abf05fd 100644
--- a/cobalt/browser/browser_module.h
+++ b/cobalt/browser/browser_module.h
@@ -25,6 +25,7 @@
 #include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
+#include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "cobalt/base/accessibility_caption_settings_changed_event.h"
 #include "cobalt/base/application_state.h"
@@ -139,6 +140,7 @@
   // |pending_navigate_url_| to the specified url, which will trigger a
   // navigation when Cobalt resumes.
   void Navigate(const GURL& url_reference);
+
   // Reloads web module.
   void Reload();
 
@@ -150,6 +152,10 @@
   void AddURLHandler(const URLHandler::URLHandlerCallback& callback);
   void RemoveURLHandler(const URLHandler::URLHandlerCallback& callback);
 
+  // Start the ScreenShotWriter if it's not already running.
+  void EnsureScreenShotWriter();
+
+#if defined(ENABLE_WEBDRIVER) || defined(ENABLE_DEBUGGER)
   // Request a screenshot to be written to the specified path. Callback will
   // be fired after the screenshot has been written to disk.
   void RequestScreenshotToFile(
@@ -163,6 +169,13 @@
       loader::image::EncodedStaticImage::ImageFormat image_format,
       const base::Optional<math::Rect>& clip_rect,
       const ScreenShotWriter::ImageEncodeCompleteCallback& screenshot_ready);
+#endif  // defined(ENABLE_WEBDRIVER) || defined(ENABLE_DEBUGGER)
+
+  // Request a screenshot to memory without compressing the image.
+  void RequestScreenshotToMemoryUnencoded(
+      const scoped_refptr<render_tree::Node>& render_tree_root,
+      const base::Optional<math::Rect>& clip_rect,
+      const renderer::Pipeline::RasterizationCompleteCallback& callback);
 
 #if defined(ENABLE_WEBDRIVER)
   std::unique_ptr<webdriver::SessionDriver> CreateSessionDriver(
@@ -334,13 +347,34 @@
   bool FilterKeyEventForHotkeys(base::Token type,
                                 const dom::KeyboardEventInit& event);
 
+  void NavigateResetErrorHandling();
+
+  bool NavigateHandleStateFrozen(const GURL& url);
+
+  void NavigateResetWebModule();
+
+  bool NavigateServiceWorkerSetups(
+      const GURL& url, base::WaitableEvent* service_worker_started_event);
+
+  void NavigateSetupSplashScreen(const GURL& url,
+                                 const cssom::ViewportSize viewport_size);
+
+  void NavigateSetupScrollEngine();
+
+  void NavigateCreateWebModule(
+      const GURL& url, bool can_start_service_worker,
+      base::WaitableEvent* service_worker_started_event,
+      const cssom::ViewportSize viewport_size);
+
   // Tries all registered URL handlers for a URL. Returns true if one of the
   // handlers handled the URL, false if otherwise.
-  bool TryURLHandlers(const GURL& url);
+  bool NavigateTryURLHandlers(const GURL& url);
 
   // Destroys the splash screen, if currently displayed.
   void DestroySplashScreen(base::TimeDelta close_time = base::TimeDelta());
 
+  void DestroyScrollEngine();
+
   // Called when web module has received window.close().
   void OnWindowClose(base::TimeDelta close_time);
 
@@ -363,6 +397,8 @@
       const browser::WebModule::LayoutResults& layout_results);
   void OnDebugConsoleRenderTreeProduced(
       const browser::WebModule::LayoutResults& layout_results);
+
+  void OnNavigateTimedTrace(const std::string& time);
 #endif  // defined(ENABLE_DEBUGGER)
 
 #if defined(ENABLE_WEBDRIVER)
@@ -638,6 +674,12 @@
 
   // Saves the previous debugger state to be restored in the new WebModule.
   std::unique_ptr<debug::backend::DebuggerState> debugger_state_;
+
+  // Amount of time to run a Timed Trace after Navigate
+  base::TimeDelta navigate_timed_trace_duration_;
+
+  debug::console::ConsoleCommandManager::CommandHandler
+      navigate_timed_trace_command_handler_;
 #endif  // defined(ENABLE_DEBUGGER)
 
   // The splash screen. The pointer wrapped here should be non-NULL iff
diff --git a/cobalt/browser/idl_files.gni b/cobalt/browser/idl_files.gni
index 12adde0..ca2b572 100644
--- a/cobalt/browser/idl_files.gni
+++ b/cobalt/browser/idl_files.gni
@@ -170,6 +170,7 @@
   "//cobalt/h5vcc/h5vcc_screen.idl",
   "//cobalt/h5vcc/h5vcc_system.idl",
   "//cobalt/h5vcc/h5vcc_trace_event.idl",
+  "//cobalt/h5vcc/h5vcc_net_log.idl",
   "//cobalt/h5vcc/h5vcc_updater.idl",
 
   "//cobalt/media_capture/blob_event.idl",
diff --git a/cobalt/browser/main.cc b/cobalt/browser/main.cc
index 7d0d0a0..ff13b37 100644
--- a/cobalt/browser/main.cc
+++ b/cobalt/browser/main.cc
@@ -18,6 +18,7 @@
 #include "cobalt/base/wrap_main.h"
 #include "cobalt/browser/application.h"
 #include "cobalt/browser/switches.h"
+#include "cobalt/css_parser/switches.h"
 #include "cobalt/version.h"
 
 namespace {
@@ -41,6 +42,7 @@
           cobalt::browser::switches::kHelp)) {
     SbLogRaw("Options: \n");
     SbLogRaw(cobalt::browser::switches::HelpMessage().c_str());
+    SbLogRaw(cobalt::css_parser::switches::HelpMessage().c_str());
     return true;
   }
   return false;
diff --git a/cobalt/browser/metrics/BUILD.gn b/cobalt/browser/metrics/BUILD.gn
index 3f25f1a..414d1b5 100644
--- a/cobalt/browser/metrics/BUILD.gn
+++ b/cobalt/browser/metrics/BUILD.gn
@@ -24,11 +24,11 @@
     "cobalt_metrics_services_manager.h",
     "cobalt_metrics_services_manager_client.cc",
     "cobalt_metrics_services_manager_client.h",
-    "cobalt_metrics_uploader_callback.h",
   ]
 
   deps = [
     "//base",
+    "//cobalt/base",
     "//cobalt/browser:generated_types",
     "//cobalt/h5vcc:metric_event_handler_wrapper",
     "//components/metrics",
@@ -51,7 +51,9 @@
   deps = [
     ":metrics",
     "//base",
+    "//cobalt/base",
     "//cobalt/browser:generated_types",
+    "//cobalt/browser:test_dependencies_on_browser",
     "//cobalt/h5vcc",
     "//cobalt/h5vcc:metric_event_handler_wrapper",
     "//cobalt/test:run_all_unittests",
diff --git a/cobalt/browser/metrics/cobalt_metrics_log_uploader.cc b/cobalt/browser/metrics/cobalt_metrics_log_uploader.cc
index 49f1c81..955ef04 100644
--- a/cobalt/browser/metrics/cobalt_metrics_log_uploader.cc
+++ b/cobalt/browser/metrics/cobalt_metrics_log_uploader.cc
@@ -14,9 +14,12 @@
 
 #include "cobalt/browser/metrics/cobalt_metrics_log_uploader.h"
 
+#include <memory>
+
 #include "base/base64url.h"
 #include "base/logging.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
+#include "cobalt/base/event_dispatcher.h"
+#include "cobalt/base/on_metric_upload_event.h"
 #include "cobalt/h5vcc/h5vcc_metric_type.h"
 #include "components/metrics/log_decoder.h"
 #include "components/metrics/metrics_log_uploader.h"
@@ -49,7 +52,7 @@
     const std::string& compressed_log_data, const std::string& log_hash,
     const ::metrics::ReportingInfo& reporting_info) {
   if (service_type_ == ::metrics::MetricsLogUploader::UMA) {
-    if (upload_handler_ != nullptr) {
+    if (event_dispatcher_ != nullptr) {
       std::string uncompressed_serialized_proto;
       ::metrics::DecodeLogData(compressed_log_data,
                                &uncompressed_serialized_proto);
@@ -67,8 +70,11 @@
       base::Base64UrlEncode(cobalt_uma_event.SerializeAsString(),
                             base::Base64UrlEncodePolicy::INCLUDE_PADDING,
                             &base64_encoded_proto);
-      upload_handler_->Run(h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma,
-                           base64_encoded_proto);
+
+      event_dispatcher_->DispatchEvent(
+          std::unique_ptr<base::Event>(new base::OnMetricUploadEvent(
+              h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma,
+              base64_encoded_proto)));
     }
   }
 
@@ -79,9 +85,9 @@
                           /*was_https*/ true);
 }
 
-void CobaltMetricsLogUploader::SetOnUploadHandler(
-    const CobaltMetricsUploaderCallback* upload_handler) {
-  upload_handler_ = upload_handler;
+void CobaltMetricsLogUploader::SetEventDispatcher(
+    const base::EventDispatcher* event_dispatcher) {
+  event_dispatcher_ = event_dispatcher;
 }
 
 }  // namespace metrics
diff --git a/cobalt/browser/metrics/cobalt_metrics_log_uploader.h b/cobalt/browser/metrics/cobalt_metrics_log_uploader.h
index fa71662..8b5b73d 100644
--- a/cobalt/browser/metrics/cobalt_metrics_log_uploader.h
+++ b/cobalt/browser/metrics/cobalt_metrics_log_uploader.h
@@ -20,7 +20,7 @@
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/strings/string_piece.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
+#include "cobalt/base/event_dispatcher.h"
 #include "cobalt/h5vcc/metric_event_handler_wrapper.h"
 #include "components/metrics/metrics_log_uploader.h"
 #include "third_party/metrics_proto/reporting_info.pb.h"
@@ -43,6 +43,9 @@
 
   virtual ~CobaltMetricsLogUploader() {}
 
+  // Set event dispatcher to be used to publish any metrics events (eg upload).
+  void SetEventDispatcher(const base::EventDispatcher* event_dispatcher);
+
   // 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.
@@ -50,15 +53,10 @@
                  const std::string& log_hash,
                  const ::metrics::ReportingInfo& reporting_info);
 
-  // Sets the event handler wrapper to be called when metrics are ready for
-  // upload. This should be the JavaScript H5vcc callback implementation.
-  void SetOnUploadHandler(
-      const CobaltMetricsUploaderCallback* metric_event_handler);
-
  private:
   const ::metrics::MetricsLogUploader::MetricServiceType service_type_;
   const ::metrics::MetricsLogUploader::UploadCallback on_upload_complete_;
-  const CobaltMetricsUploaderCallback* upload_handler_ = nullptr;
+  const base::EventDispatcher* event_dispatcher_ = nullptr;
 };
 
 }  // namespace metrics
diff --git a/cobalt/browser/metrics/cobalt_metrics_log_uploader_test.cc b/cobalt/browser/metrics/cobalt_metrics_log_uploader_test.cc
index 78e7d18..f6927a8 100644
--- a/cobalt/browser/metrics/cobalt_metrics_log_uploader_test.cc
+++ b/cobalt/browser/metrics/cobalt_metrics_log_uploader_test.cc
@@ -18,7 +18,10 @@
 
 #include "base/base64url.h"
 #include "base/test/mock_callback.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
+#include "cobalt/base/event.h"
+#include "cobalt/base/event_dispatcher.h"
+#include "cobalt/base/on_metric_upload_event.h"
+#include "cobalt/h5vcc/h5vcc_metric_type.h"
 #include "cobalt/h5vcc/h5vcc_metrics.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -26,7 +29,6 @@
 #include "third_party/metrics_proto/cobalt_uma_event.pb.h"
 #include "third_party/metrics_proto/reporting_info.pb.h"
 #include "third_party/zlib/google/compression_utils.h"
-
 namespace cobalt {
 namespace browser {
 namespace metrics {
@@ -43,6 +45,12 @@
 class CobaltMetricsLogUploaderTest : public ::testing::Test {
  public:
   void SetUp() override {
+    dispatcher_ = std::make_unique<base::EventDispatcher>();
+    dispatcher_->AddEventCallback(
+        base::OnMetricUploadEvent::TypeId(),
+        base::Bind(&CobaltMetricsLogUploaderTest::OnMetricUploadEventHandler,
+                   base::Unretained(this)));
+
     uploader_ = std::make_unique<CobaltMetricsLogUploader>(
         ::metrics::MetricsLogUploader::MetricServiceType::UMA,
         base::Bind(&CobaltMetricsLogUploaderTest::UploadCompleteCallback,
@@ -51,6 +59,13 @@
 
   void TearDown() override {}
 
+  void OnMetricUploadEventHandler(const base::Event* event) {
+    std::unique_ptr<base::OnMetricUploadEvent> on_metric_upload_event(
+        new base::OnMetricUploadEvent(event));
+    last_metric_type_ = on_metric_upload_event->metric_type();
+    last_serialized_proto_ = on_metric_upload_event->serialized_proto();
+  }
+
   void UploadCompleteCallback(int response_code, int error_code,
                               bool was_https) {
     callback_count_++;
@@ -58,13 +73,14 @@
 
  protected:
   std::unique_ptr<CobaltMetricsLogUploader> uploader_;
+  std::unique_ptr<base::EventDispatcher> dispatcher_;
   int callback_count_ = 0;
+  cobalt::h5vcc::H5vccMetricType last_metric_type_;
+  std::string last_serialized_proto_ = "";
 };
 
 TEST_F(CobaltMetricsLogUploaderTest, TriggersUploadHandler) {
-  base::MockCallback<CobaltMetricsUploaderCallback> mock_upload_handler;
-  const auto cb = mock_upload_handler.Get();
-  uploader_->SetOnUploadHandler(&cb);
+  uploader_->SetEventDispatcher(dispatcher_.get());
   ::metrics::ReportingInfo dummy_reporting_info;
   dummy_reporting_info.set_attempt_count(33);
   ::metrics::ChromeUserMetricsExtension uma_log;
@@ -87,12 +103,11 @@
   base::Base64UrlEncode(cobalt_event.SerializeAsString(),
                         base::Base64UrlEncodePolicy::INCLUDE_PADDING,
                         &base64_encoded_proto);
-  EXPECT_CALL(mock_upload_handler,
-              Run(Eq(h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma),
-                  StrEq(base64_encoded_proto)))
-      .Times(1);
   uploader_->UploadLog(compressed_message, "fake_hash", dummy_reporting_info);
   ASSERT_EQ(callback_count_, 1);
+  ASSERT_EQ(last_metric_type_,
+            cobalt::h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma);
+  ASSERT_EQ(last_serialized_proto_, base64_encoded_proto);
 
   ::metrics::ChromeUserMetricsExtension uma_log2;
   uma_log2.set_session_id(456);
@@ -108,11 +123,10 @@
   base::Base64UrlEncode(cobalt_event2.SerializeAsString(),
                         base::Base64UrlEncodePolicy::INCLUDE_PADDING,
                         &base64_encoded_proto2);
-  EXPECT_CALL(mock_upload_handler,
-              Run(Eq(h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma),
-                  StrEq(base64_encoded_proto2)))
-      .Times(1);
   uploader_->UploadLog(compressed_message2, "fake_hash", dummy_reporting_info);
+  ASSERT_EQ(last_metric_type_,
+            cobalt::h5vcc::H5vccMetricType::kH5vccMetricTypeCobaltUma);
+  ASSERT_EQ(last_serialized_proto_, base64_encoded_proto2);
   ASSERT_EQ(callback_count_, 2);
 }
 
@@ -121,17 +135,15 @@
       ::metrics::MetricsLogUploader::MetricServiceType::UKM,
       base::Bind(&CobaltMetricsLogUploaderTest::UploadCompleteCallback,
                  base::Unretained(this))));
-  base::MockCallback<CobaltMetricsUploaderCallback> mock_upload_handler;
-  const auto cb = mock_upload_handler.Get();
-  uploader_->SetOnUploadHandler(&cb);
+  uploader_->SetEventDispatcher(dispatcher_.get());
   ::metrics::ReportingInfo dummy_reporting_info;
   ::metrics::ChromeUserMetricsExtension uma_log;
   uma_log.set_session_id(1234);
   uma_log.set_client_id(1234);
   std::string compressed_message;
   compression::GzipCompress(uma_log.SerializeAsString(), &compressed_message);
-  EXPECT_CALL(mock_upload_handler, Run(_, _)).Times(0);
   uploader_->UploadLog(compressed_message, "fake_hash", dummy_reporting_info);
+  ASSERT_EQ(last_serialized_proto_, "");
   // Even though we don't upload this log, we still need to trigger the complete
   // callback so the metric code can keep running.
   ASSERT_EQ(callback_count_, 1);
diff --git a/cobalt/browser/metrics/cobalt_metrics_service_client.cc b/cobalt/browser/metrics/cobalt_metrics_service_client.cc
index 2b4d21a..c3408a9 100644
--- a/cobalt/browser/metrics/cobalt_metrics_service_client.cc
+++ b/cobalt/browser/metrics/cobalt_metrics_service_client.cc
@@ -24,9 +24,9 @@
 #include "base/memory/singleton.h"
 #include "base/strings/string16.h"
 #include "base/time/time.h"
+#include "cobalt/base/event_dispatcher.h"
 #include "cobalt/browser/metrics/cobalt_enabled_state_provider.h"
 #include "cobalt/browser/metrics/cobalt_metrics_log_uploader.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
 #include "components/metrics/enabled_state_provider.h"
 #include "components/metrics/metrics_log_uploader.h"
 #include "components/metrics/metrics_pref_names.h"
@@ -50,11 +50,11 @@
 // Upload Handler.
 const int kStandardUploadIntervalSeconds = 5 * 60;  // 5 minutes.
 
-void CobaltMetricsServiceClient::SetOnUploadHandler(
-    const CobaltMetricsUploaderCallback* uploader_callback) {
-  upload_handler_ = uploader_callback;
+void CobaltMetricsServiceClient::SetEventDispatcher(
+    const base::EventDispatcher* event_dispatcher) {
+  event_dispatcher_ = event_dispatcher;
   if (log_uploader_) {
-    log_uploader_->SetOnUploadHandler(upload_handler_);
+    log_uploader_->SetEventDispatcher(event_dispatcher);
   }
 }
 
@@ -77,7 +77,8 @@
 
 void CobaltMetricsServiceClient::SetMetricsClientId(
     const std::string& client_id) {
-  // TODO(b/286066035): What to do with client id here?
+  // ClientId is unnecessary within Cobalt. We expect the web client responsible
+  // for uploading these to have its own concept of device/client identifiers.
 }
 
 // TODO(b/286884542): Audit all stub implementations in this class and reaffirm
@@ -153,8 +154,8 @@
   auto uploader = std::make_unique<CobaltMetricsLogUploader>(
       service_type, on_upload_complete);
   log_uploader_ = uploader.get();
-  if (upload_handler_ != nullptr) {
-    log_uploader_->SetOnUploadHandler(upload_handler_);
+  if (event_dispatcher_ != nullptr) {
+    log_uploader_->SetEventDispatcher(event_dispatcher_);
   }
   return uploader;
 }
diff --git a/cobalt/browser/metrics/cobalt_metrics_service_client.h b/cobalt/browser/metrics/cobalt_metrics_service_client.h
index c7860f0..c8efa8f 100644
--- a/cobalt/browser/metrics/cobalt_metrics_service_client.h
+++ b/cobalt/browser/metrics/cobalt_metrics_service_client.h
@@ -23,9 +23,9 @@
 #include "base/callback.h"
 #include "base/strings/string16.h"
 #include "base/time/time.h"
+#include "cobalt/base/event_dispatcher.h"
 #include "cobalt/browser/metrics/cobalt_enabled_state_provider.h"
 #include "cobalt/browser/metrics/cobalt_metrics_log_uploader.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
 #include "components/metrics/metrics_log_uploader.h"
 #include "components/metrics/metrics_reporting_default_state.h"
 #include "components/metrics/metrics_service.h"
@@ -48,10 +48,8 @@
  public:
   ~CobaltMetricsServiceClient() override{};
 
-  // Sets the uploader handler to be called when metrics are ready for
-  // upload.
-  void SetOnUploadHandler(
-      const CobaltMetricsUploaderCallback* uploader_callback);
+  // Set event dispatcher to be used to publish any metrics events (eg upload).
+  void SetEventDispatcher(const base::EventDispatcher* event_dispatcher);
 
   // Returns the MetricsService instance that this client is associated with.
   // With the exception of testing contexts, the returned instance must be valid
@@ -165,10 +163,10 @@
 
   CobaltMetricsLogUploader* log_uploader_ = nullptr;
 
-  const CobaltMetricsUploaderCallback* upload_handler_ = nullptr;
-
   uint32_t custom_upload_interval_ = UINT32_MAX;
 
+  const base::EventDispatcher* event_dispatcher_ = nullptr;
+
   DISALLOW_COPY_AND_ASSIGN(CobaltMetricsServiceClient);
 };
 
diff --git a/cobalt/browser/metrics/cobalt_metrics_services_manager.cc b/cobalt/browser/metrics/cobalt_metrics_services_manager.cc
index acb5d8b..f816858 100644
--- a/cobalt/browser/metrics/cobalt_metrics_services_manager.cc
+++ b/cobalt/browser/metrics/cobalt_metrics_services_manager.cc
@@ -17,6 +17,7 @@
 #include <memory>
 
 #include "base/logging.h"
+#include "cobalt/base/event_dispatcher.h"
 #include "cobalt/browser/metrics/cobalt_metrics_service_client.h"
 #include "cobalt/browser/metrics/cobalt_metrics_services_manager_client.h"
 #include "components/metrics_services_manager/metrics_services_manager.h"
@@ -41,23 +42,27 @@
   return instance_;
 }
 
-void CobaltMetricsServicesManager::DeleteInstance() { delete instance_; }
-
-void CobaltMetricsServicesManager::SetOnUploadHandler(
-    const CobaltMetricsUploaderCallback* uploader_callback) {
-  instance_->task_runner_->PostTask(
-      FROM_HERE,
-      base::Bind(&CobaltMetricsServicesManager::SetOnUploadHandlerInternal,
-                 base::Unretained(instance_), uploader_callback));
+void CobaltMetricsServicesManager::DeleteInstance() {
+  delete instance_;
+  instance_ = nullptr;
 }
 
-void CobaltMetricsServicesManager::SetOnUploadHandlerInternal(
-    const CobaltMetricsUploaderCallback* uploader_callback) {
+void CobaltMetricsServicesManager::SetEventDispatcher(
+    base::EventDispatcher* event_dispatcher) {
+  if (instance_ != nullptr) {
+    instance_->task_runner_->PostTask(
+        FROM_HERE,
+        base::Bind(&CobaltMetricsServicesManager::SetEventDispatcherInternal,
+                   base::Unretained(instance_), event_dispatcher));
+  }
+}
+
+void CobaltMetricsServicesManager::SetEventDispatcherInternal(
+    base::EventDispatcher* event_dispatcher) {
   CobaltMetricsServiceClient* client =
       static_cast<CobaltMetricsServiceClient*>(GetMetricsServiceClient());
   DCHECK(client);
-  client->SetOnUploadHandler(uploader_callback);
-  LOG(INFO) << "New Cobalt Telemetry metric upload handler bound.";
+  client->SetEventDispatcher(event_dispatcher);
 }
 
 void CobaltMetricsServicesManager::ToggleMetricsEnabled(bool is_enabled) {
diff --git a/cobalt/browser/metrics/cobalt_metrics_services_manager.h b/cobalt/browser/metrics/cobalt_metrics_services_manager.h
index da31294..20a4345 100644
--- a/cobalt/browser/metrics/cobalt_metrics_services_manager.h
+++ b/cobalt/browser/metrics/cobalt_metrics_services_manager.h
@@ -20,8 +20,8 @@
 
 #include "base//memory/scoped_refptr.h"
 #include "base/single_thread_task_runner.h"
+#include "cobalt/base/event_dispatcher.h"
 #include "cobalt/browser/metrics/cobalt_metrics_services_manager_client.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
 #include "components/metrics_services_manager/metrics_services_manager.h"
 #include "components/metrics_services_manager/metrics_services_manager_client.h"
 
@@ -54,10 +54,9 @@
   // Destructs the static instance of CobaltMetricsServicesManager.
   static void DeleteInstance();
 
-  // Sets the upload handler onto the current static instance of
-  // CobaltMetricsServicesManager.
-  static void SetOnUploadHandler(
-      const CobaltMetricsUploaderCallback* uploader_callback);
+  // Sets an event dispatcher to call when metric events happen (e.g., on
+  // upload).
+  static void SetEventDispatcher(base::EventDispatcher* event_dispatcher);
 
   // Toggles whether metric reporting is enabled via
   // CobaltMetricsServicesManager.
@@ -68,8 +67,7 @@
   static void SetUploadInterval(uint32_t interval_seconds);
 
  private:
-  void SetOnUploadHandlerInternal(
-      const CobaltMetricsUploaderCallback* uploader_callback);
+  void SetEventDispatcherInternal(base::EventDispatcher* event_dispatcher);
 
   void ToggleMetricsEnabledInternal(bool is_enabled);
 
diff --git a/cobalt/browser/metrics/cobalt_metrics_uploader_callback.h b/cobalt/browser/metrics/cobalt_metrics_uploader_callback.h
deleted file mode 100644
index db980c5..0000000
--- a/cobalt/browser/metrics/cobalt_metrics_uploader_callback.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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_METRICS_COBALT_METRICS_UPLOADER_CALLBACK_H_
-#define COBALT_BROWSER_METRICS_COBALT_METRICS_UPLOADER_CALLBACK_H_
-
-#include <string>
-
-#include "base/callback.h"
-#include "cobalt/h5vcc/h5vcc_metric_type.h"
-
-namespace cobalt {
-namespace browser {
-namespace metrics {
-
-typedef base::RepeatingCallback<void(
-    const cobalt::h5vcc::H5vccMetricType& metric_type,
-    const std::string& serialized_proto)>
-    CobaltMetricsUploaderCallback;
-
-}  // namespace metrics
-}  // namespace browser
-}  // namespace cobalt
-
-#endif  // COBALT_BROWSER_METRICS_COBALT_METRICS_UPLOADER_CALLBACK_H_
diff --git a/cobalt/browser/service_worker_registry.cc b/cobalt/browser/service_worker_registry.cc
index bbfdf0f..2a62dac 100644
--- a/cobalt/browser/service_worker_registry.cc
+++ b/cobalt/browser/service_worker_registry.cc
@@ -31,8 +31,6 @@
 // Signals the given WaitableEvent.
 void SignalWaitableEvent(base::WaitableEvent* event) { event->Signal(); }
 
-// The watchdog client name used to represent service worker registry thread.
-const char kWatchdogName[] = "service worker registry";
 // The watchdog time interval in microseconds allowed between pings before
 // triggering violations.
 const int64_t kWatchdogTimeInterval = 15000000;
@@ -51,7 +49,7 @@
 
 ServiceWorkerRegistry::ServiceWorkerRegistry(
     web::WebSettings* web_settings, network::NetworkModule* network_module,
-    web::UserAgentPlatformInfo* platform_info, const GURL& url)
+    web::UserAgentPlatformInfo* platform_info)
     : thread_("ServiceWorkerRegistry") {
   if (!thread_.Start()) return;
   DCHECK(message_loop());
@@ -61,7 +59,8 @@
   // Registers service worker thread as a watchdog client.
   if (watchdog) {
     watchdog_registered_ = true;
-    watchdog->Register(kWatchdogName, kWatchdogName,
+    watchdog->Register(worker::WorkerConsts::kServiceWorkerRegistryName,
+                       worker::WorkerConsts::kServiceWorkerRegistryName,
                        base::kApplicationStateStarted, kWatchdogTimeInterval,
                        kWatchdogTimeWait, watchdog::PING);
     message_loop()->task_runner()->PostDelayedTask(
@@ -74,7 +73,7 @@
   message_loop()->task_runner()->PostTask(
       FROM_HERE,
       base::Bind(&ServiceWorkerRegistry::Initialize, base::Unretained(this),
-                 web_settings, network_module, platform_info, url));
+                 web_settings, network_module, platform_info));
 
   // Register as a destruction observer to shut down the Web Agent once all
   // pending tasks have been executed and the message loop is about to be
@@ -101,7 +100,7 @@
   watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
   if (watchdog) {
     watchdog_registered_ = false;
-    watchdog->Unregister(kWatchdogName);
+    watchdog->Unregister(worker::WorkerConsts::kServiceWorkerRegistryName);
   }
 
   // Ensure that the destruction observer got added before stopping the thread.
@@ -120,7 +119,7 @@
   // If watchdog is already unregistered or shut down, stop ping watchdog.
   if (!watchdog_registered_ || !watchdog) return;
 
-  watchdog->Ping(kWatchdogName);
+  watchdog->Ping(worker::WorkerConsts::kServiceWorkerRegistryName);
   message_loop()->task_runner()->PostDelayedTask(
       FROM_HERE,
       base::Bind(&ServiceWorkerRegistry::PingWatchdog, base::Unretained(this)),
@@ -134,6 +133,10 @@
                                                        done_event);
 }
 
+void ServiceWorkerRegistry::EraseRegistrationMap() {
+  service_worker_context()->EraseRegistrationMap();
+}
+
 worker::ServiceWorkerContext* ServiceWorkerRegistry::service_worker_context() {
   // Ensure that the thread had a chance to allocate the object.
   destruction_observer_added_.Wait();
@@ -142,11 +145,11 @@
 
 void ServiceWorkerRegistry::Initialize(
     web::WebSettings* web_settings, network::NetworkModule* network_module,
-    web::UserAgentPlatformInfo* platform_info, const GURL& url) {
+    web::UserAgentPlatformInfo* platform_info) {
   TRACE_EVENT0("cobalt::browser", "ServiceWorkerRegistry::Initialize()");
   DCHECK_EQ(base::MessageLoop::current(), message_loop());
   service_worker_context_.reset(new worker::ServiceWorkerContext(
-      web_settings, network_module, platform_info, message_loop(), url));
+      web_settings, network_module, platform_info, message_loop()));
 }
 
 }  // namespace browser
diff --git a/cobalt/browser/service_worker_registry.h b/cobalt/browser/service_worker_registry.h
index 4880d6a..de31979 100644
--- a/cobalt/browser/service_worker_registry.h
+++ b/cobalt/browser/service_worker_registry.h
@@ -35,8 +35,7 @@
  public:
   ServiceWorkerRegistry(web::WebSettings* web_settings,
                         network::NetworkModule* network_module,
-                        web::UserAgentPlatformInfo* platform_info,
-                        const GURL& url);
+                        web::UserAgentPlatformInfo* platform_info);
   ~ServiceWorkerRegistry();
 
   // The message loop this object is running on.
@@ -49,6 +48,8 @@
                                   const GURL& client_url,
                                   base::WaitableEvent* done_event);
 
+  void EraseRegistrationMap();
+
   worker::ServiceWorkerContext* service_worker_context();
 
  private:
@@ -56,7 +57,7 @@
   // the dedicated thread.
   void Initialize(web::WebSettings* web_settings,
                   network::NetworkModule* network_module,
-                  web::UserAgentPlatformInfo* platform_info, const GURL& url);
+                  web::UserAgentPlatformInfo* platform_info);
 
   void PingWatchdog();
 
diff --git a/cobalt/browser/splash_screen.h b/cobalt/browser/splash_screen.h
index f50c2e4..3fc1202 100644
--- a/cobalt/browser/splash_screen.h
+++ b/cobalt/browser/splash_screen.h
@@ -57,7 +57,6 @@
   }
 
   // LifecycleObserver implementation.
-  // LifecycleObserver implementation.
   void Blur(SbTimeMonotonic timestamp) override { web_module_->Blur(0); }
   void Conceal(render_tree::ResourceProvider* resource_provider,
                SbTimeMonotonic timestamp) override {
diff --git a/cobalt/browser/switches.cc b/cobalt/browser/switches.cc
index 70bb756..11c7db6 100644
--- a/cobalt/browser/switches.cc
+++ b/cobalt/browser/switches.cc
@@ -229,7 +229,7 @@
 
 const char kMinLogLevel[] = "min_log_level";
 const char kMinLogLevelHelp[] =
-    "Set the minimum logging level: info|warning|error|fatal.";
+    "Set the minimum logging level: verbose|info|warning|error|fatal.";
 const char kDisableJavaScriptJit[] = "disable_javascript_jit";
 const char kDisableJavaScriptJitHelp[] =
     "Specifies that javascript jit should be disabled.";
diff --git a/cobalt/build/build_info.py b/cobalt/build/build_info.py
index aed2646..51172fa 100755
--- a/cobalt/build/build_info.py
+++ b/cobalt/build/build_info.py
@@ -24,7 +24,7 @@
 FILE_DIR = os.path.dirname(__file__)
 COMMIT_COUNT_BUILD_ID_OFFSET = 1000000
 
-_BUILD_ID_PATTERN = '^BUILD_NUMBER=([1-9][0-9]{6,})$'
+_BUILD_ID_PATTERN = '^(Build-Id: |BUILD_NUMBER=)([1-9][0-9]{6,})$'
 _GIT_REV_PATTERN = '^GitOrigin-RevId: ([0-9a-f]{40})$'
 _COBALT_VERSION_PATTERN = '^#define COBALT_VERSION "(.*)"$'
 
@@ -40,7 +40,7 @@
   match_build_id = compiled_build_id_pattern.search(output)
   if not match_build_id:
     return None, None
-  build_id = match_build_id.group(1)
+  build_id = match_build_id.group(2)
 
   # Gets git rev.
   compiled_git_rev_pattern = re.compile(_GIT_REV_PATTERN, flags=re.MULTILINE)
@@ -57,7 +57,7 @@
   output = subprocess.check_output(['git', 'rev-list', '--count', 'HEAD'],
                                    cwd=cwd)
   build_id = int(output.strip().decode()) + COMMIT_COUNT_BUILD_ID_OFFSET
-  return build_id
+  return str(build_id)
 
 
 def _get_last_commit_with_format(placeholder, cwd):
diff --git a/cobalt/build/cobalt_configuration.py b/cobalt/build/cobalt_configuration.py
index e1320ab..e5d04f6 100644
--- a/cobalt/build/cobalt_configuration.py
+++ b/cobalt/build/cobalt_configuration.py
@@ -133,11 +133,9 @@
         'media_capture_test',
         'media_session_test',
         'media_stream_test',
-        # TODO(b/292030213): Crashes on evergreen
-        # 'media_test',
+        'media_test',
         'memory_store_test',
         'metrics_test',
-        'nb_test',
         'net_unittests',
         'network_test',
         'overlay_info_test',
@@ -159,3 +157,8 @@
         'xhr_test',
         'zip_unittests',
     ]
+
+  def GetTestBlackBoxTargets(self):
+    return [
+        'blackbox',
+    ]
diff --git a/cobalt/build/get_build_id_test.py b/cobalt/build/get_build_id_test.py
index 562179f..26e7b2b 100644
--- a/cobalt/build/get_build_id_test.py
+++ b/cobalt/build/get_build_id_test.py
@@ -87,8 +87,9 @@
       self.make_commit()
     build_number = build_info.get_build_id_from_commit_count(
         cwd=self.test_dir.name)
-    self.assertEqual(build_number,
-                     num_commits + build_info.COMMIT_COUNT_BUILD_ID_OFFSET)
+    self.assertEqual(
+        int(build_number),
+        num_commits + build_info.COMMIT_COUNT_BUILD_ID_OFFSET)
 
   def testCommitsOutrankCommitCount(self):
     self.make_commit()
@@ -102,8 +103,9 @@
     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 + build_info.COMMIT_COUNT_BUILD_ID_OFFSET)
+    self.assertEqual(
+        int(build_number),
+        num_commits + build_info.COMMIT_COUNT_BUILD_ID_OFFSET)
 
 
 if __name__ == '__main__':
diff --git a/cobalt/build/gn.py b/cobalt/build/gn.py
index e8c4472..57d38ec 100755
--- a/cobalt/build/gn.py
+++ b/cobalt/build/gn.py
@@ -85,15 +85,23 @@
       help='Whether or not to overwrite an existing args.gn file if one exists '
       'in the out directory. In general, if the file exists, you should run '
       '`gn args <out_directory>` to edit it instead.')
-  known_args, unknown_args = parser.parse_known_args()
+  parser.add_argument(
+      '--no-check',
+      default=False,
+      action='store_true',
+      help='Pass this flag to disable the header dependency gn check.')
+  script_args, gen_args = parser.parse_known_args()
 
-  if known_args.out_directory:
-    builds_out_directory = known_args.out_directory
+  if not script_args.no_check:
+    gen_args.append('--check')
+
+  if script_args.out_directory:
+    builds_out_directory = script_args.out_directory
   else:
     builds_directory = os.getenv('COBALT_BUILDS_DIRECTORY',
-                                 known_args.builds_directory or 'out')
+                                 script_args.builds_directory or 'out')
     builds_out_directory = os.path.join(
-        builds_directory, f'{known_args.platform}_{known_args.build_type}')
+        builds_directory, f'{script_args.platform}_{script_args.build_type}')
 
-  main(builds_out_directory, known_args.platform, known_args.build_type,
-       known_args.overwrite_args, unknown_args)
+  main(builds_out_directory, script_args.platform, script_args.build_type,
+       script_args.overwrite_args, gen_args)
diff --git a/cobalt/cache/cache.cc b/cobalt/cache/cache.cc
index d88a6ec..42eee8e 100644
--- a/cobalt/cache/cache.cc
+++ b/cobalt/cache/cache.cc
@@ -25,7 +25,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/values.h"
 #include "cobalt/configuration/configuration.h"
-#include "cobalt/persistent_storage/persistent_settings.h"
 #include "net/disk_cache/cobalt/cobalt_backend_impl.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/extension/javascript_cache.h"
@@ -201,19 +200,6 @@
   return nullptr;
 }
 
-void Cache::set_enabled(bool enabled) { enabled_ = enabled; }
-
-void Cache::set_persistent_settings(
-    persistent_storage::PersistentSettings* persistent_settings) {
-  persistent_settings_ = persistent_settings;
-
-  // Guaranteed to be called before any calls to Retrieve()
-  // since set_persistent_settings() is called from the Application()
-  // constructor before the NetworkModule is initialized.
-  set_enabled(persistent_settings_->GetPersistentSettingAsBool(
-      disk_cache::kCacheEnabledPersistentSettingsKey, true));
-}
-
 MemoryCappedDirectory* Cache::GetMemoryCappedDirectory(
     disk_cache::ResourceType resource_type) {
   base::AutoLock auto_lock(lock_);
@@ -222,16 +208,6 @@
     return it->second.get();
   }
 
-  // Read in size from persistent storage.
-  auto metadata = disk_cache::kTypeMetadata[resource_type];
-  if (persistent_settings_) {
-    uint32_t bucket_size = static_cast<uint32_t>(
-        persistent_settings_->GetPersistentSettingAsDouble(
-            metadata.directory, metadata.max_size_bytes));
-    disk_cache::kTypeMetadata[resource_type] = {metadata.directory,
-                                                bucket_size};
-  }
-
   auto cache_directory = GetCacheDirectory(resource_type);
   auto max_size = GetMaxCacheStorageInBytes(resource_type);
   if (!cache_directory || !max_size) {
@@ -248,16 +224,9 @@
 void Cache::Resize(disk_cache::ResourceType resource_type, uint32_t bytes) {
   if (resource_type != disk_cache::ResourceType::kCacheApi &&
       resource_type != disk_cache::ResourceType::kCompiledScript &&
-      resource_type != disk_cache::ResourceType::kServiceWorkerScript)
+      resource_type != disk_cache::ResourceType::kServiceWorkerScript) {
     return;
-  if (bytes == disk_cache::kTypeMetadata[resource_type].max_size_bytes) return;
-
-  if (persistent_settings_) {
-    persistent_settings_->SetPersistentSetting(
-        disk_cache::kTypeMetadata[resource_type].directory,
-        std::make_unique<base::Value>(static_cast<double>(bytes)));
   }
-  disk_cache::kTypeMetadata[resource_type].max_size_bytes = bytes;
   auto* memory_capped_directory = GetMemoryCappedDirectory(resource_type);
   if (memory_capped_directory) {
     memory_capped_directory->Resize(bytes);
@@ -273,7 +242,7 @@
     case disk_cache::ResourceType::kCacheApi:
     case disk_cache::ResourceType::kCompiledScript:
     case disk_cache::ResourceType::kServiceWorkerScript:
-      return disk_cache::kTypeMetadata[resource_type].max_size_bytes;
+      return disk_cache::settings::GetQuota(resource_type);
     default:
       return base::nullopt;
   }
@@ -334,7 +303,7 @@
       resource_type == disk_cache::ResourceType::kCacheApi) {
     return true;
   }
-  if (!enabled_) {
+  if (!disk_cache::settings::GetCacheEnabled()) {
     return false;
   }
   if (resource_type == disk_cache::ResourceType::kCompiledScript) {
diff --git a/cobalt/cache/cache.h b/cobalt/cache/cache.h
index 40bd225..e98f335 100644
--- a/cobalt/cache/cache.h
+++ b/cobalt/cache/cache.h
@@ -26,12 +26,12 @@
 #include "base/files/file_enumerator.h"
 #include "base/files/file_path.h"
 #include "base/macros.h"
+#include "base/message_loop/message_loop.h"
 #include "base/optional.h"
 #include "base/synchronization/lock.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/values.h"
 #include "cobalt/cache/memory_capped_directory.h"
-#include "cobalt/persistent_storage/persistent_settings.h"
 #include "net/disk_cache/cobalt/resource_type.h"
 
 namespace base {
@@ -67,14 +67,9 @@
   base::Optional<uint32_t> GetMaxCacheStorageInBytes(
       disk_cache::ResourceType resource_type);
 
-  void set_enabled(bool enabled);
-
-  void set_persistent_settings(
-      persistent_storage::PersistentSettings* persistent_settings);
-
  private:
   friend struct base::DefaultSingletonTraits<Cache>;
-  Cache() {}
+  Cache() = default;
 
   MemoryCappedDirectory* GetMemoryCappedDirectory(
       disk_cache::ResourceType resource_type);
@@ -91,9 +86,6 @@
   std::map<disk_cache::ResourceType,
            std::map<uint32_t, std::vector<base::WaitableEvent*>>>
       pending_;
-  bool enabled_ = true;
-
-  persistent_storage::PersistentSettings* persistent_settings_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(Cache);
 };  // class Cache
diff --git a/cobalt/csp/source_list.cc b/cobalt/csp/source_list.cc
index 902ba39..a4268fb 100644
--- a/cobalt/csp/source_list.cc
+++ b/cobalt/csp/source_list.cc
@@ -35,7 +35,7 @@
   const char* position = begin;
   SkipWhile<IsSourceCharacter>(&position, end);
   size_t len = static_cast<size_t>(position - begin);
-  if (base::strncasecmp("'none'", begin, len) != 0) {
+  if (strncasecmp("'none'", begin, len) != 0) {
     return false;
   }
 
@@ -400,7 +400,7 @@
   const char* prefix = "'nonce-";
 
   if (nonce_length <= strlen(prefix) ||
-      base::strncasecmp(prefix, begin, strlen(prefix)) != 0) {
+      strncasecmp(prefix, begin, strlen(prefix)) != 0) {
     return true;
   }
 
@@ -433,8 +433,7 @@
   for (size_t i = 0; i < arraysize(kSupportedPrefixes); ++i) {
     const HashPrefix& algorithm = kSupportedPrefixes[i];
     if (hash_length > strlen(algorithm.prefix) &&
-        base::strncasecmp(algorithm.prefix, begin, strlen(algorithm.prefix)) ==
-            0) {
+        strncasecmp(algorithm.prefix, begin, strlen(algorithm.prefix)) == 0) {
       prefix = algorithm.prefix;
       *hash_algorithm = algorithm.type;
       break;
diff --git a/cobalt/css_parser/BUILD.gn b/cobalt/css_parser/BUILD.gn
index bff1cfa..d05d218 100644
--- a/cobalt/css_parser/BUILD.gn
+++ b/cobalt/css_parser/BUILD.gn
@@ -76,6 +76,8 @@
     "shadow_property_parse_structures.cc",
     "shadow_property_parse_structures.h",
     "string_pool.h",
+    "switches.cc",
+    "switches.h",
     "text_decoration_shorthand_property_parse_structures.cc",
     "text_decoration_shorthand_property_parse_structures.h",
     "transition_shorthand_property_parse_structures.cc",
@@ -92,6 +94,7 @@
 
   deps = [
     ":css_grammar",
+    "//base",
     "//cobalt/base",
     "//cobalt/cssom",
     "//starboard/common",
@@ -118,7 +121,9 @@
     ":css_grammar",
     ":css_parser",
     "//base",
+    "//cobalt/browser:test_dependencies_on_browser",
     "//cobalt/cssom",
+    "//cobalt/dom",
     "//cobalt/test:run_all_unittests",
     "//testing/gmock",
     "//testing/gtest",
diff --git a/cobalt/css_parser/grammar.h b/cobalt/css_parser/grammar.h
index faf3adf..a0a5347 100644
--- a/cobalt/css_parser/grammar.h
+++ b/cobalt/css_parser/grammar.h
@@ -107,8 +107,8 @@
 
 #if defined(STARBOARD)
 #include "starboard/memory.h"
-#define YYFREE SbMemoryDeallocate
-#define YYMALLOC SbMemoryAllocate
+#define YYFREE free
+#define YYMALLOC malloc
 #endif
 
 // A header generated by Bison must be included inside our namespace
diff --git a/cobalt/css_parser/parser.cc b/cobalt/css_parser/parser.cc
index ccc9b42..9b60505 100644
--- a/cobalt/css_parser/parser.cc
+++ b/cobalt/css_parser/parser.cc
@@ -88,6 +88,10 @@
 #include "cobalt/cssom/unicode_range_value.h"
 #include "cobalt/cssom/universal_selector.h"
 #include "cobalt/cssom/url_value.h"
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+#include "base/command_line.h"
+#include "cobalt/css_parser/switches.h"
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 
 namespace cobalt {
 namespace css_parser {
@@ -539,11 +543,23 @@
 void LogWarningCallback(const ::base::DebuggerHooks* debugger_hooks,
                         const std::string& message) {
   CLOG(WARNING, *debugger_hooks) << message;
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+  ::base::CommandLine* command_line = ::base::CommandLine::ForCurrentProcess();
+  if (command_line->GetSwitchValueASCII(switches::kOnCssWarning) == "crash") {
+    IMMEDIATE_CRASH() << message;
+  }
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 }
 
 void LogErrorCallback(const ::base::DebuggerHooks* debugger_hooks,
                       const std::string& message) {
   CLOG(ERROR, *debugger_hooks) << message;
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+  ::base::CommandLine* command_line = ::base::CommandLine::ForCurrentProcess();
+  if (command_line->GetSwitchValueASCII(switches::kOnCssError) == "crash") {
+    IMMEDIATE_CRASH() << message;
+  }
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
 }
 
 }  // namespace
diff --git a/cobalt/css_parser/switches.cc b/cobalt/css_parser/switches.cc
new file mode 100644
index 0000000..3818593
--- /dev/null
+++ b/cobalt/css_parser/switches.cc
@@ -0,0 +1,53 @@
+// 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/css_parser/switches.h"
+
+#include <map>
+
+namespace cobalt {
+namespace css_parser {
+namespace switches {
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+const char kOnCssError[] = "on_css_error";
+const char kOnCssErrorHelp[] =
+    "If set to \"crash\", crashes on CSS error even when recoverable.";
+
+const char kOnCssWarning[] = "on_css_warning";
+const char kOnCssWarningHelp[] = "If set to \"crash\", crashes on CSS warning.";
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+
+std::string HelpMessage() {
+  std::string help_message;
+  std::map<std::string, const char*> help_map {
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+    {kOnCssError, kOnCssErrorHelp}, {kOnCssWarning, kOnCssWarningHelp},
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+  };
+
+  for (const auto& switch_message : help_map) {
+    help_message.append("  --")
+        .append(switch_message.first)
+        .append("\n")
+        .append("  ")
+        .append(switch_message.second)
+        .append("\n\n");
+  }
+  return help_message;
+}
+
+}  // namespace switches
+}  // namespace css_parser
+}  // namespace cobalt
diff --git a/cobalt/css_parser/switches.h b/cobalt/css_parser/switches.h
new file mode 100644
index 0000000..a59ea7a
--- /dev/null
+++ b/cobalt/css_parser/switches.h
@@ -0,0 +1,37 @@
+// 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_CSS_PARSER_SWITCHES_H_
+#define COBALT_CSS_PARSER_SWITCHES_H_
+
+#include <string>
+
+namespace cobalt {
+namespace css_parser {
+namespace switches {
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+extern const char kOnCssError[];
+extern const char kOnCssErrorHelp[];
+extern const char kOnCssWarning[];
+extern const char kOnCssWarningHelp[];
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+
+std::string HelpMessage();
+
+}  // namespace switches
+}  // namespace css_parser
+}  // namespace cobalt
+
+#endif  // COBALT_CSS_PARSER_SWITCHES_H_
diff --git a/cobalt/cssom/BUILD.gn b/cobalt/cssom/BUILD.gn
index b313c59..04dae86 100644
--- a/cobalt/cssom/BUILD.gn
+++ b/cobalt/cssom/BUILD.gn
@@ -319,8 +319,10 @@
 
   deps = [
     "//cobalt/base",
+    "//cobalt/browser:test_dependencies_on_browser",
     "//cobalt/css_parser",
     "//cobalt/cssom",
+    "//cobalt/dom",
     "//cobalt/math",
     "//cobalt/test:run_all_unittests",
     "//testing/gmock",
diff --git a/cobalt/cssom/computed_style.cc b/cobalt/cssom/computed_style.cc
index 9f9212d..8c43d38 100644
--- a/cobalt/cssom/computed_style.cc
+++ b/cobalt/cssom/computed_style.cc
@@ -332,75 +332,7 @@
       computed_line_height_ = keyword;
       break;
 
-    case KeywordValue::kAbsolute:
-    case KeywordValue::kAlternate:
-    case KeywordValue::kAlternateReverse:
-    case KeywordValue::kAuto:
-    case KeywordValue::kBackwards:
-    case KeywordValue::kBaseline:
-    case KeywordValue::kBlock:
-    case KeywordValue::kBoth:
-    case KeywordValue::kBottom:
-    case KeywordValue::kBreakWord:
-    case KeywordValue::kCenter:
-    case KeywordValue::kClip:
-    case KeywordValue::kCollapse:
-    case KeywordValue::kColumn:
-    case KeywordValue::kColumnReverse:
-    case KeywordValue::kContain:
-    case KeywordValue::kContent:
-    case KeywordValue::kCover:
-    case KeywordValue::kCurrentColor:
-    case KeywordValue::kCursive:
-    case KeywordValue::kEllipsis:
-    case KeywordValue::kEnd:
-    case KeywordValue::kEquirectangular:
-    case KeywordValue::kFantasy:
-    case KeywordValue::kFixed:
-    case KeywordValue::kFlex:
-    case KeywordValue::kFlexEnd:
-    case KeywordValue::kFlexStart:
-    case KeywordValue::kForwards:
-    case KeywordValue::kHidden:
-    case KeywordValue::kInfinite:
-    case KeywordValue::kInherit:
-    case KeywordValue::kInitial:
-    case KeywordValue::kInline:
-    case KeywordValue::kInlineBlock:
-    case KeywordValue::kInlineFlex:
-    case KeywordValue::kLeft:
-    case KeywordValue::kLineThrough:
-    case KeywordValue::kMiddle:
-    case KeywordValue::kMonoscopic:
-    case KeywordValue::kMonospace:
-    case KeywordValue::kNone:
-    case KeywordValue::kNoRepeat:
-    case KeywordValue::kNowrap:
-    case KeywordValue::kPre:
-    case KeywordValue::kPreLine:
-    case KeywordValue::kPreWrap:
-    case KeywordValue::kRelative:
-    case KeywordValue::kRepeat:
-    case KeywordValue::kReverse:
-    case KeywordValue::kRight:
-    case KeywordValue::kRow:
-    case KeywordValue::kRowReverse:
-    case KeywordValue::kSansSerif:
-    case KeywordValue::kScroll:
-    case KeywordValue::kSerif:
-    case KeywordValue::kSolid:
-    case KeywordValue::kSpaceAround:
-    case KeywordValue::kSpaceBetween:
-    case KeywordValue::kStart:
-    case KeywordValue::kStatic:
-    case KeywordValue::kStereoscopicLeftRight:
-    case KeywordValue::kStereoscopicTopBottom:
-    case KeywordValue::kStretch:
-    case KeywordValue::kTop:
-    case KeywordValue::kUppercase:
-    case KeywordValue::kVisible:
-    case KeywordValue::kWrap:
-    case KeywordValue::kWrapReverse:
+    default:
       NOTREACHED();
   }
 }
@@ -447,75 +379,7 @@
       computed_margin_or_padding_edge_ = keyword;
       break;
 
-    case KeywordValue::kAbsolute:
-    case KeywordValue::kAlternate:
-    case KeywordValue::kAlternateReverse:
-    case KeywordValue::kBackwards:
-    case KeywordValue::kBaseline:
-    case KeywordValue::kBlock:
-    case KeywordValue::kBoth:
-    case KeywordValue::kBottom:
-    case KeywordValue::kBreakWord:
-    case KeywordValue::kCenter:
-    case KeywordValue::kClip:
-    case KeywordValue::kCollapse:
-    case KeywordValue::kColumn:
-    case KeywordValue::kColumnReverse:
-    case KeywordValue::kContain:
-    case KeywordValue::kContent:
-    case KeywordValue::kCover:
-    case KeywordValue::kCurrentColor:
-    case KeywordValue::kCursive:
-    case KeywordValue::kEllipsis:
-    case KeywordValue::kEnd:
-    case KeywordValue::kEquirectangular:
-    case KeywordValue::kFantasy:
-    case KeywordValue::kFixed:
-    case KeywordValue::kFlex:
-    case KeywordValue::kFlexEnd:
-    case KeywordValue::kFlexStart:
-    case KeywordValue::kForwards:
-    case KeywordValue::kHidden:
-    case KeywordValue::kInfinite:
-    case KeywordValue::kInherit:
-    case KeywordValue::kInitial:
-    case KeywordValue::kInline:
-    case KeywordValue::kInlineBlock:
-    case KeywordValue::kInlineFlex:
-    case KeywordValue::kLeft:
-    case KeywordValue::kLineThrough:
-    case KeywordValue::kMiddle:
-    case KeywordValue::kMonoscopic:
-    case KeywordValue::kMonospace:
-    case KeywordValue::kNone:
-    case KeywordValue::kNoRepeat:
-    case KeywordValue::kNormal:
-    case KeywordValue::kNowrap:
-    case KeywordValue::kPre:
-    case KeywordValue::kPreLine:
-    case KeywordValue::kPreWrap:
-    case KeywordValue::kRelative:
-    case KeywordValue::kRepeat:
-    case KeywordValue::kReverse:
-    case KeywordValue::kRight:
-    case KeywordValue::kRow:
-    case KeywordValue::kRowReverse:
-    case KeywordValue::kSansSerif:
-    case KeywordValue::kScroll:
-    case KeywordValue::kSerif:
-    case KeywordValue::kSolid:
-    case KeywordValue::kSpaceAround:
-    case KeywordValue::kSpaceBetween:
-    case KeywordValue::kStart:
-    case KeywordValue::kStatic:
-    case KeywordValue::kStereoscopicLeftRight:
-    case KeywordValue::kStereoscopicTopBottom:
-    case KeywordValue::kStretch:
-    case KeywordValue::kTop:
-    case KeywordValue::kUppercase:
-    case KeywordValue::kVisible:
-    case KeywordValue::kWrap:
-    case KeywordValue::kWrapReverse:
+    default:
       NOTREACHED();
   }
 }
@@ -569,75 +433,7 @@
       computed_position_offset_ = keyword;
       break;
 
-    case KeywordValue::kAbsolute:
-    case KeywordValue::kAlternate:
-    case KeywordValue::kAlternateReverse:
-    case KeywordValue::kBackwards:
-    case KeywordValue::kBaseline:
-    case KeywordValue::kBlock:
-    case KeywordValue::kBoth:
-    case KeywordValue::kBottom:
-    case KeywordValue::kBreakWord:
-    case KeywordValue::kCenter:
-    case KeywordValue::kClip:
-    case KeywordValue::kCollapse:
-    case KeywordValue::kColumn:
-    case KeywordValue::kColumnReverse:
-    case KeywordValue::kContain:
-    case KeywordValue::kContent:
-    case KeywordValue::kCover:
-    case KeywordValue::kCurrentColor:
-    case KeywordValue::kCursive:
-    case KeywordValue::kEllipsis:
-    case KeywordValue::kEnd:
-    case KeywordValue::kEquirectangular:
-    case KeywordValue::kFantasy:
-    case KeywordValue::kFixed:
-    case KeywordValue::kFlex:
-    case KeywordValue::kFlexEnd:
-    case KeywordValue::kFlexStart:
-    case KeywordValue::kForwards:
-    case KeywordValue::kHidden:
-    case KeywordValue::kInfinite:
-    case KeywordValue::kInherit:
-    case KeywordValue::kInitial:
-    case KeywordValue::kInline:
-    case KeywordValue::kInlineBlock:
-    case KeywordValue::kInlineFlex:
-    case KeywordValue::kLeft:
-    case KeywordValue::kLineThrough:
-    case KeywordValue::kMiddle:
-    case KeywordValue::kMonoscopic:
-    case KeywordValue::kMonospace:
-    case KeywordValue::kNone:
-    case KeywordValue::kNoRepeat:
-    case KeywordValue::kNormal:
-    case KeywordValue::kNowrap:
-    case KeywordValue::kPre:
-    case KeywordValue::kPreLine:
-    case KeywordValue::kPreWrap:
-    case KeywordValue::kRelative:
-    case KeywordValue::kRepeat:
-    case KeywordValue::kReverse:
-    case KeywordValue::kRight:
-    case KeywordValue::kRow:
-    case KeywordValue::kRowReverse:
-    case KeywordValue::kSansSerif:
-    case KeywordValue::kScroll:
-    case KeywordValue::kSerif:
-    case KeywordValue::kSolid:
-    case KeywordValue::kSpaceAround:
-    case KeywordValue::kSpaceBetween:
-    case KeywordValue::kStart:
-    case KeywordValue::kStatic:
-    case KeywordValue::kStereoscopicLeftRight:
-    case KeywordValue::kStereoscopicTopBottom:
-    case KeywordValue::kStretch:
-    case KeywordValue::kTop:
-    case KeywordValue::kUppercase:
-    case KeywordValue::kVisible:
-    case KeywordValue::kWrap:
-    case KeywordValue::kWrapReverse:
+    default:
       NOTREACHED();
   }
 }
@@ -714,75 +510,7 @@
       computed_height_ = keyword;
       break;
 
-    case KeywordValue::kAbsolute:
-    case KeywordValue::kAlternate:
-    case KeywordValue::kAlternateReverse:
-    case KeywordValue::kBackwards:
-    case KeywordValue::kBaseline:
-    case KeywordValue::kBlock:
-    case KeywordValue::kBoth:
-    case KeywordValue::kBottom:
-    case KeywordValue::kBreakWord:
-    case KeywordValue::kCenter:
-    case KeywordValue::kClip:
-    case KeywordValue::kCollapse:
-    case KeywordValue::kColumn:
-    case KeywordValue::kColumnReverse:
-    case KeywordValue::kContain:
-    case KeywordValue::kContent:
-    case KeywordValue::kCover:
-    case KeywordValue::kCurrentColor:
-    case KeywordValue::kCursive:
-    case KeywordValue::kEllipsis:
-    case KeywordValue::kEnd:
-    case KeywordValue::kEquirectangular:
-    case KeywordValue::kFantasy:
-    case KeywordValue::kFixed:
-    case KeywordValue::kFlex:
-    case KeywordValue::kFlexEnd:
-    case KeywordValue::kFlexStart:
-    case KeywordValue::kForwards:
-    case KeywordValue::kHidden:
-    case KeywordValue::kInfinite:
-    case KeywordValue::kInherit:
-    case KeywordValue::kInitial:
-    case KeywordValue::kInline:
-    case KeywordValue::kInlineBlock:
-    case KeywordValue::kInlineFlex:
-    case KeywordValue::kLeft:
-    case KeywordValue::kLineThrough:
-    case KeywordValue::kMiddle:
-    case KeywordValue::kMonoscopic:
-    case KeywordValue::kMonospace:
-    case KeywordValue::kNone:
-    case KeywordValue::kNoRepeat:
-    case KeywordValue::kNormal:
-    case KeywordValue::kNowrap:
-    case KeywordValue::kPre:
-    case KeywordValue::kPreLine:
-    case KeywordValue::kPreWrap:
-    case KeywordValue::kRelative:
-    case KeywordValue::kRepeat:
-    case KeywordValue::kReverse:
-    case KeywordValue::kRight:
-    case KeywordValue::kRow:
-    case KeywordValue::kRowReverse:
-    case KeywordValue::kSansSerif:
-    case KeywordValue::kScroll:
-    case KeywordValue::kSerif:
-    case KeywordValue::kSolid:
-    case KeywordValue::kSpaceAround:
-    case KeywordValue::kSpaceBetween:
-    case KeywordValue::kStart:
-    case KeywordValue::kStatic:
-    case KeywordValue::kStereoscopicLeftRight:
-    case KeywordValue::kStereoscopicTopBottom:
-    case KeywordValue::kStretch:
-    case KeywordValue::kTop:
-    case KeywordValue::kUppercase:
-    case KeywordValue::kVisible:
-    case KeywordValue::kWrap:
-    case KeywordValue::kWrapReverse:
+    default:
       NOTREACHED();
   }
 }
@@ -855,74 +583,7 @@
       computed_max_height_ = keyword;
       break;
 
-    case KeywordValue::kAbsolute:
-    case KeywordValue::kAlternate:
-    case KeywordValue::kAlternateReverse:
-    case KeywordValue::kBackwards:
-    case KeywordValue::kBaseline:
-    case KeywordValue::kBlock:
-    case KeywordValue::kBoth:
-    case KeywordValue::kBottom:
-    case KeywordValue::kBreakWord:
-    case KeywordValue::kCenter:
-    case KeywordValue::kClip:
-    case KeywordValue::kCollapse:
-    case KeywordValue::kColumn:
-    case KeywordValue::kColumnReverse:
-    case KeywordValue::kContain:
-    case KeywordValue::kContent:
-    case KeywordValue::kCover:
-    case KeywordValue::kCurrentColor:
-    case KeywordValue::kCursive:
-    case KeywordValue::kEllipsis:
-    case KeywordValue::kEnd:
-    case KeywordValue::kEquirectangular:
-    case KeywordValue::kFantasy:
-    case KeywordValue::kFixed:
-    case KeywordValue::kFlex:
-    case KeywordValue::kFlexEnd:
-    case KeywordValue::kFlexStart:
-    case KeywordValue::kForwards:
-    case KeywordValue::kHidden:
-    case KeywordValue::kInfinite:
-    case KeywordValue::kInherit:
-    case KeywordValue::kInitial:
-    case KeywordValue::kInline:
-    case KeywordValue::kInlineBlock:
-    case KeywordValue::kInlineFlex:
-    case KeywordValue::kLeft:
-    case KeywordValue::kLineThrough:
-    case KeywordValue::kMiddle:
-    case KeywordValue::kMonoscopic:
-    case KeywordValue::kMonospace:
-    case KeywordValue::kNoRepeat:
-    case KeywordValue::kNormal:
-    case KeywordValue::kNowrap:
-    case KeywordValue::kPre:
-    case KeywordValue::kPreLine:
-    case KeywordValue::kPreWrap:
-    case KeywordValue::kRelative:
-    case KeywordValue::kRepeat:
-    case KeywordValue::kReverse:
-    case KeywordValue::kRight:
-    case KeywordValue::kRow:
-    case KeywordValue::kRowReverse:
-    case KeywordValue::kSansSerif:
-    case KeywordValue::kScroll:
-    case KeywordValue::kSerif:
-    case KeywordValue::kSolid:
-    case KeywordValue::kSpaceAround:
-    case KeywordValue::kSpaceBetween:
-    case KeywordValue::kStart:
-    case KeywordValue::kStatic:
-    case KeywordValue::kStereoscopicLeftRight:
-    case KeywordValue::kStereoscopicTopBottom:
-    case KeywordValue::kStretch:
-    case KeywordValue::kTop:
-    case KeywordValue::kUppercase:
-    case KeywordValue::kVisible:
-    case KeywordValue::kWrap:
-    case KeywordValue::kWrapReverse:
+    default:
       NOTREACHED();
   }
 }
@@ -992,75 +653,7 @@
       computed_min_height_ = keyword;
       break;
 
-    case KeywordValue::kAbsolute:
-    case KeywordValue::kAlternate:
-    case KeywordValue::kAlternateReverse:
-    case KeywordValue::kBackwards:
-    case KeywordValue::kBaseline:
-    case KeywordValue::kBlock:
-    case KeywordValue::kBoth:
-    case KeywordValue::kBottom:
-    case KeywordValue::kBreakWord:
-    case KeywordValue::kCenter:
-    case KeywordValue::kClip:
-    case KeywordValue::kCollapse:
-    case KeywordValue::kColumn:
-    case KeywordValue::kColumnReverse:
-    case KeywordValue::kContain:
-    case KeywordValue::kContent:
-    case KeywordValue::kCover:
-    case KeywordValue::kCurrentColor:
-    case KeywordValue::kCursive:
-    case KeywordValue::kEllipsis:
-    case KeywordValue::kEnd:
-    case KeywordValue::kEquirectangular:
-    case KeywordValue::kFantasy:
-    case KeywordValue::kFixed:
-    case KeywordValue::kFlex:
-    case KeywordValue::kFlexEnd:
-    case KeywordValue::kFlexStart:
-    case KeywordValue::kForwards:
-    case KeywordValue::kHidden:
-    case KeywordValue::kInfinite:
-    case KeywordValue::kInherit:
-    case KeywordValue::kInitial:
-    case KeywordValue::kInline:
-    case KeywordValue::kInlineBlock:
-    case KeywordValue::kInlineFlex:
-    case KeywordValue::kLeft:
-    case KeywordValue::kLineThrough:
-    case KeywordValue::kMiddle:
-    case KeywordValue::kMonoscopic:
-    case KeywordValue::kMonospace:
-    case KeywordValue::kNone:
-    case KeywordValue::kNoRepeat:
-    case KeywordValue::kNormal:
-    case KeywordValue::kNowrap:
-    case KeywordValue::kPre:
-    case KeywordValue::kPreLine:
-    case KeywordValue::kPreWrap:
-    case KeywordValue::kRelative:
-    case KeywordValue::kRepeat:
-    case KeywordValue::kReverse:
-    case KeywordValue::kRight:
-    case KeywordValue::kRow:
-    case KeywordValue::kRowReverse:
-    case KeywordValue::kSansSerif:
-    case KeywordValue::kScroll:
-    case KeywordValue::kSerif:
-    case KeywordValue::kSolid:
-    case KeywordValue::kSpaceAround:
-    case KeywordValue::kSpaceBetween:
-    case KeywordValue::kStart:
-    case KeywordValue::kStatic:
-    case KeywordValue::kStereoscopicLeftRight:
-    case KeywordValue::kStereoscopicTopBottom:
-    case KeywordValue::kStretch:
-    case KeywordValue::kTop:
-    case KeywordValue::kUppercase:
-    case KeywordValue::kVisible:
-    case KeywordValue::kWrap:
-    case KeywordValue::kWrapReverse:
+    default:
       NOTREACHED();
   }
 }
@@ -1138,75 +731,7 @@
         computed_value_ = keyword;
         break;
 
-      case KeywordValue::kAbsolute:
-      case KeywordValue::kAlternate:
-      case KeywordValue::kAlternateReverse:
-      case KeywordValue::kBackwards:
-      case KeywordValue::kBaseline:
-      case KeywordValue::kBlock:
-      case KeywordValue::kBoth:
-      case KeywordValue::kBottom:
-      case KeywordValue::kBreakWord:
-      case KeywordValue::kCenter:
-      case KeywordValue::kClip:
-      case KeywordValue::kCollapse:
-      case KeywordValue::kColumn:
-      case KeywordValue::kColumnReverse:
-      case KeywordValue::kContain:
-      case KeywordValue::kContent:
-      case KeywordValue::kCover:
-      case KeywordValue::kCurrentColor:
-      case KeywordValue::kCursive:
-      case KeywordValue::kEllipsis:
-      case KeywordValue::kEnd:
-      case KeywordValue::kEquirectangular:
-      case KeywordValue::kFantasy:
-      case KeywordValue::kFixed:
-      case KeywordValue::kFlex:
-      case KeywordValue::kFlexEnd:
-      case KeywordValue::kFlexStart:
-      case KeywordValue::kForwards:
-      case KeywordValue::kHidden:
-      case KeywordValue::kInfinite:
-      case KeywordValue::kInherit:
-      case KeywordValue::kInitial:
-      case KeywordValue::kInline:
-      case KeywordValue::kInlineBlock:
-      case KeywordValue::kInlineFlex:
-      case KeywordValue::kLeft:
-      case KeywordValue::kLineThrough:
-      case KeywordValue::kMiddle:
-      case KeywordValue::kMonoscopic:
-      case KeywordValue::kMonospace:
-      case KeywordValue::kNone:
-      case KeywordValue::kNoRepeat:
-      case KeywordValue::kNormal:
-      case KeywordValue::kNowrap:
-      case KeywordValue::kPre:
-      case KeywordValue::kPreLine:
-      case KeywordValue::kPreWrap:
-      case KeywordValue::kRelative:
-      case KeywordValue::kRepeat:
-      case KeywordValue::kReverse:
-      case KeywordValue::kRight:
-      case KeywordValue::kRow:
-      case KeywordValue::kRowReverse:
-      case KeywordValue::kSansSerif:
-      case KeywordValue::kScroll:
-      case KeywordValue::kSerif:
-      case KeywordValue::kSolid:
-      case KeywordValue::kSpaceAround:
-      case KeywordValue::kSpaceBetween:
-      case KeywordValue::kStart:
-      case KeywordValue::kStatic:
-      case KeywordValue::kStereoscopicLeftRight:
-      case KeywordValue::kStereoscopicTopBottom:
-      case KeywordValue::kStretch:
-      case KeywordValue::kTop:
-      case KeywordValue::kUppercase:
-      case KeywordValue::kVisible:
-      case KeywordValue::kWrap:
-      case KeywordValue::kWrapReverse:
+      default:
         NOTREACHED();
     }
   }
@@ -1233,74 +758,7 @@
         computed_value_ = keyword;
         break;
 
-      case KeywordValue::kAbsolute:
-      case KeywordValue::kAlternate:
-      case KeywordValue::kAlternateReverse:
-      case KeywordValue::kBackwards:
-      case KeywordValue::kBaseline:
-      case KeywordValue::kBlock:
-      case KeywordValue::kBoth:
-      case KeywordValue::kBottom:
-      case KeywordValue::kBreakWord:
-      case KeywordValue::kCenter:
-      case KeywordValue::kClip:
-      case KeywordValue::kCollapse:
-      case KeywordValue::kColumn:
-      case KeywordValue::kColumnReverse:
-      case KeywordValue::kContain:
-      case KeywordValue::kCover:
-      case KeywordValue::kCurrentColor:
-      case KeywordValue::kCursive:
-      case KeywordValue::kEllipsis:
-      case KeywordValue::kEnd:
-      case KeywordValue::kEquirectangular:
-      case KeywordValue::kFantasy:
-      case KeywordValue::kFixed:
-      case KeywordValue::kFlex:
-      case KeywordValue::kFlexEnd:
-      case KeywordValue::kFlexStart:
-      case KeywordValue::kForwards:
-      case KeywordValue::kHidden:
-      case KeywordValue::kInfinite:
-      case KeywordValue::kInherit:
-      case KeywordValue::kInitial:
-      case KeywordValue::kInline:
-      case KeywordValue::kInlineBlock:
-      case KeywordValue::kInlineFlex:
-      case KeywordValue::kLeft:
-      case KeywordValue::kLineThrough:
-      case KeywordValue::kMiddle:
-      case KeywordValue::kMonoscopic:
-      case KeywordValue::kMonospace:
-      case KeywordValue::kNone:
-      case KeywordValue::kNoRepeat:
-      case KeywordValue::kNormal:
-      case KeywordValue::kNowrap:
-      case KeywordValue::kPre:
-      case KeywordValue::kPreLine:
-      case KeywordValue::kPreWrap:
-      case KeywordValue::kRelative:
-      case KeywordValue::kRepeat:
-      case KeywordValue::kReverse:
-      case KeywordValue::kRight:
-      case KeywordValue::kRow:
-      case KeywordValue::kRowReverse:
-      case KeywordValue::kSansSerif:
-      case KeywordValue::kScroll:
-      case KeywordValue::kSerif:
-      case KeywordValue::kSolid:
-      case KeywordValue::kSpaceAround:
-      case KeywordValue::kSpaceBetween:
-      case KeywordValue::kStart:
-      case KeywordValue::kStatic:
-      case KeywordValue::kStereoscopicLeftRight:
-      case KeywordValue::kStereoscopicTopBottom:
-      case KeywordValue::kStretch:
-      case KeywordValue::kTop:
-      case KeywordValue::kUppercase:
-      case KeywordValue::kVisible:
-      case KeywordValue::kWrap:
-      case KeywordValue::kWrapReverse:
+      default:
         NOTREACHED();
     }
   }
@@ -1358,74 +816,7 @@
       computed_min_max_width_ = keyword;
       break;
 
-    case KeywordValue::kAbsolute:
-    case KeywordValue::kAlternate:
-    case KeywordValue::kAlternateReverse:
-    case KeywordValue::kBackwards:
-    case KeywordValue::kBaseline:
-    case KeywordValue::kBlock:
-    case KeywordValue::kBoth:
-    case KeywordValue::kBottom:
-    case KeywordValue::kBreakWord:
-    case KeywordValue::kCenter:
-    case KeywordValue::kClip:
-    case KeywordValue::kCollapse:
-    case KeywordValue::kColumn:
-    case KeywordValue::kColumnReverse:
-    case KeywordValue::kContain:
-    case KeywordValue::kContent:
-    case KeywordValue::kCover:
-    case KeywordValue::kCurrentColor:
-    case KeywordValue::kCursive:
-    case KeywordValue::kEllipsis:
-    case KeywordValue::kEnd:
-    case KeywordValue::kEquirectangular:
-    case KeywordValue::kFantasy:
-    case KeywordValue::kFixed:
-    case KeywordValue::kFlex:
-    case KeywordValue::kFlexEnd:
-    case KeywordValue::kFlexStart:
-    case KeywordValue::kForwards:
-    case KeywordValue::kHidden:
-    case KeywordValue::kInfinite:
-    case KeywordValue::kInherit:
-    case KeywordValue::kInitial:
-    case KeywordValue::kInline:
-    case KeywordValue::kInlineBlock:
-    case KeywordValue::kInlineFlex:
-    case KeywordValue::kLeft:
-    case KeywordValue::kLineThrough:
-    case KeywordValue::kMiddle:
-    case KeywordValue::kMonoscopic:
-    case KeywordValue::kMonospace:
-    case KeywordValue::kNoRepeat:
-    case KeywordValue::kNormal:
-    case KeywordValue::kNowrap:
-    case KeywordValue::kPre:
-    case KeywordValue::kPreLine:
-    case KeywordValue::kPreWrap:
-    case KeywordValue::kRelative:
-    case KeywordValue::kRepeat:
-    case KeywordValue::kReverse:
-    case KeywordValue::kRight:
-    case KeywordValue::kRow:
-    case KeywordValue::kRowReverse:
-    case KeywordValue::kSansSerif:
-    case KeywordValue::kScroll:
-    case KeywordValue::kSerif:
-    case KeywordValue::kSolid:
-    case KeywordValue::kSpaceAround:
-    case KeywordValue::kSpaceBetween:
-    case KeywordValue::kStart:
-    case KeywordValue::kStatic:
-    case KeywordValue::kStereoscopicLeftRight:
-    case KeywordValue::kStereoscopicTopBottom:
-    case KeywordValue::kStretch:
-    case KeywordValue::kTop:
-    case KeywordValue::kUppercase:
-    case KeywordValue::kVisible:
-    case KeywordValue::kWrap:
-    case KeywordValue::kWrapReverse:
+    default:
       NOTREACHED();
   }
 }
@@ -1783,75 +1174,7 @@
       computed_background_image_ = keyword;
       break;
 
-    case KeywordValue::kAbsolute:
-    case KeywordValue::kAlternate:
-    case KeywordValue::kAlternateReverse:
-    case KeywordValue::kAuto:
-    case KeywordValue::kBackwards:
-    case KeywordValue::kBaseline:
-    case KeywordValue::kBlock:
-    case KeywordValue::kBoth:
-    case KeywordValue::kBottom:
-    case KeywordValue::kBreakWord:
-    case KeywordValue::kCenter:
-    case KeywordValue::kClip:
-    case KeywordValue::kCollapse:
-    case KeywordValue::kColumn:
-    case KeywordValue::kColumnReverse:
-    case KeywordValue::kContain:
-    case KeywordValue::kContent:
-    case KeywordValue::kCover:
-    case KeywordValue::kCurrentColor:
-    case KeywordValue::kCursive:
-    case KeywordValue::kEllipsis:
-    case KeywordValue::kEnd:
-    case KeywordValue::kEquirectangular:
-    case KeywordValue::kFantasy:
-    case KeywordValue::kFixed:
-    case KeywordValue::kFlex:
-    case KeywordValue::kFlexEnd:
-    case KeywordValue::kFlexStart:
-    case KeywordValue::kForwards:
-    case KeywordValue::kHidden:
-    case KeywordValue::kInfinite:
-    case KeywordValue::kInherit:
-    case KeywordValue::kInitial:
-    case KeywordValue::kInline:
-    case KeywordValue::kInlineBlock:
-    case KeywordValue::kInlineFlex:
-    case KeywordValue::kLeft:
-    case KeywordValue::kLineThrough:
-    case KeywordValue::kMiddle:
-    case KeywordValue::kMonoscopic:
-    case KeywordValue::kMonospace:
-    case KeywordValue::kNoRepeat:
-    case KeywordValue::kNormal:
-    case KeywordValue::kNowrap:
-    case KeywordValue::kPre:
-    case KeywordValue::kPreLine:
-    case KeywordValue::kPreWrap:
-    case KeywordValue::kRelative:
-    case KeywordValue::kRepeat:
-    case KeywordValue::kReverse:
-    case KeywordValue::kRight:
-    case KeywordValue::kRow:
-    case KeywordValue::kRowReverse:
-    case KeywordValue::kSansSerif:
-    case KeywordValue::kScroll:
-    case KeywordValue::kSerif:
-    case KeywordValue::kSolid:
-    case KeywordValue::kSpaceAround:
-    case KeywordValue::kSpaceBetween:
-    case KeywordValue::kStart:
-    case KeywordValue::kStatic:
-    case KeywordValue::kStereoscopicLeftRight:
-    case KeywordValue::kStereoscopicTopBottom:
-    case KeywordValue::kStretch:
-    case KeywordValue::kTop:
-    case KeywordValue::kUppercase:
-    case KeywordValue::kVisible:
-    case KeywordValue::kWrap:
-    case KeywordValue::kWrapReverse:
+    default:
       NOTREACHED();
       break;
   }
@@ -2079,73 +1402,7 @@
       computed_background_size_ = keyword;
       break;
 
-    case KeywordValue::kAbsolute:
-    case KeywordValue::kAlternate:
-    case KeywordValue::kAlternateReverse:
-    case KeywordValue::kBackwards:
-    case KeywordValue::kBaseline:
-    case KeywordValue::kBlock:
-    case KeywordValue::kBoth:
-    case KeywordValue::kBottom:
-    case KeywordValue::kBreakWord:
-    case KeywordValue::kCenter:
-    case KeywordValue::kClip:
-    case KeywordValue::kCollapse:
-    case KeywordValue::kColumn:
-    case KeywordValue::kColumnReverse:
-    case KeywordValue::kContent:
-    case KeywordValue::kCurrentColor:
-    case KeywordValue::kCursive:
-    case KeywordValue::kEllipsis:
-    case KeywordValue::kEnd:
-    case KeywordValue::kEquirectangular:
-    case KeywordValue::kFantasy:
-    case KeywordValue::kFixed:
-    case KeywordValue::kFlex:
-    case KeywordValue::kFlexEnd:
-    case KeywordValue::kFlexStart:
-    case KeywordValue::kForwards:
-    case KeywordValue::kHidden:
-    case KeywordValue::kInfinite:
-    case KeywordValue::kInherit:
-    case KeywordValue::kInitial:
-    case KeywordValue::kInline:
-    case KeywordValue::kInlineBlock:
-    case KeywordValue::kInlineFlex:
-    case KeywordValue::kLeft:
-    case KeywordValue::kLineThrough:
-    case KeywordValue::kMiddle:
-    case KeywordValue::kMonoscopic:
-    case KeywordValue::kMonospace:
-    case KeywordValue::kNone:
-    case KeywordValue::kNoRepeat:
-    case KeywordValue::kNormal:
-    case KeywordValue::kNowrap:
-    case KeywordValue::kPre:
-    case KeywordValue::kPreLine:
-    case KeywordValue::kPreWrap:
-    case KeywordValue::kRelative:
-    case KeywordValue::kRepeat:
-    case KeywordValue::kReverse:
-    case KeywordValue::kRight:
-    case KeywordValue::kRow:
-    case KeywordValue::kRowReverse:
-    case KeywordValue::kSansSerif:
-    case KeywordValue::kScroll:
-    case KeywordValue::kSerif:
-    case KeywordValue::kSolid:
-    case KeywordValue::kSpaceAround:
-    case KeywordValue::kSpaceBetween:
-    case KeywordValue::kStart:
-    case KeywordValue::kStatic:
-    case KeywordValue::kStereoscopicLeftRight:
-    case KeywordValue::kStereoscopicTopBottom:
-    case KeywordValue::kStretch:
-    case KeywordValue::kTop:
-    case KeywordValue::kUppercase:
-    case KeywordValue::kVisible:
-    case KeywordValue::kWrap:
-    case KeywordValue::kWrapReverse:
+    default:
       NOTREACHED();
   }
 }
@@ -2342,75 +1599,7 @@
       computed_shadow_ = keyword;
       break;
 
-    case KeywordValue::kAbsolute:
-    case KeywordValue::kAlternate:
-    case KeywordValue::kAlternateReverse:
-    case KeywordValue::kAuto:
-    case KeywordValue::kBackwards:
-    case KeywordValue::kBaseline:
-    case KeywordValue::kBlock:
-    case KeywordValue::kBoth:
-    case KeywordValue::kBottom:
-    case KeywordValue::kBreakWord:
-    case KeywordValue::kCenter:
-    case KeywordValue::kClip:
-    case KeywordValue::kCollapse:
-    case KeywordValue::kColumn:
-    case KeywordValue::kColumnReverse:
-    case KeywordValue::kContain:
-    case KeywordValue::kContent:
-    case KeywordValue::kCover:
-    case KeywordValue::kCurrentColor:
-    case KeywordValue::kCursive:
-    case KeywordValue::kEllipsis:
-    case KeywordValue::kEnd:
-    case KeywordValue::kEquirectangular:
-    case KeywordValue::kFantasy:
-    case KeywordValue::kFixed:
-    case KeywordValue::kFlex:
-    case KeywordValue::kFlexEnd:
-    case KeywordValue::kFlexStart:
-    case KeywordValue::kForwards:
-    case KeywordValue::kHidden:
-    case KeywordValue::kInfinite:
-    case KeywordValue::kInherit:
-    case KeywordValue::kInitial:
-    case KeywordValue::kInline:
-    case KeywordValue::kInlineBlock:
-    case KeywordValue::kInlineFlex:
-    case KeywordValue::kLeft:
-    case KeywordValue::kLineThrough:
-    case KeywordValue::kMiddle:
-    case KeywordValue::kMonoscopic:
-    case KeywordValue::kMonospace:
-    case KeywordValue::kNoRepeat:
-    case KeywordValue::kNormal:
-    case KeywordValue::kNowrap:
-    case KeywordValue::kPre:
-    case KeywordValue::kPreLine:
-    case KeywordValue::kPreWrap:
-    case KeywordValue::kRelative:
-    case KeywordValue::kRepeat:
-    case KeywordValue::kReverse:
-    case KeywordValue::kRight:
-    case KeywordValue::kRow:
-    case KeywordValue::kRowReverse:
-    case KeywordValue::kSansSerif:
-    case KeywordValue::kScroll:
-    case KeywordValue::kSerif:
-    case KeywordValue::kSolid:
-    case KeywordValue::kSpaceAround:
-    case KeywordValue::kSpaceBetween:
-    case KeywordValue::kStart:
-    case KeywordValue::kStatic:
-    case KeywordValue::kStereoscopicLeftRight:
-    case KeywordValue::kStereoscopicTopBottom:
-    case KeywordValue::kStretch:
-    case KeywordValue::kTop:
-    case KeywordValue::kUppercase:
-    case KeywordValue::kVisible:
-    case KeywordValue::kWrap:
-    case KeywordValue::kWrapReverse:
+    default:
       NOTREACHED();
   }
 }
@@ -2713,75 +1902,7 @@
       computed_transform_list_ = keyword;
       break;
 
-    case KeywordValue::kAbsolute:
-    case KeywordValue::kAlternate:
-    case KeywordValue::kAlternateReverse:
-    case KeywordValue::kAuto:
-    case KeywordValue::kBackwards:
-    case KeywordValue::kBaseline:
-    case KeywordValue::kBlock:
-    case KeywordValue::kBoth:
-    case KeywordValue::kBottom:
-    case KeywordValue::kBreakWord:
-    case KeywordValue::kCenter:
-    case KeywordValue::kClip:
-    case KeywordValue::kCollapse:
-    case KeywordValue::kColumn:
-    case KeywordValue::kColumnReverse:
-    case KeywordValue::kContain:
-    case KeywordValue::kContent:
-    case KeywordValue::kCover:
-    case KeywordValue::kCurrentColor:
-    case KeywordValue::kCursive:
-    case KeywordValue::kEllipsis:
-    case KeywordValue::kEnd:
-    case KeywordValue::kEquirectangular:
-    case KeywordValue::kFantasy:
-    case KeywordValue::kFixed:
-    case KeywordValue::kFlex:
-    case KeywordValue::kFlexEnd:
-    case KeywordValue::kFlexStart:
-    case KeywordValue::kForwards:
-    case KeywordValue::kHidden:
-    case KeywordValue::kInfinite:
-    case KeywordValue::kInherit:
-    case KeywordValue::kInitial:
-    case KeywordValue::kInline:
-    case KeywordValue::kInlineBlock:
-    case KeywordValue::kInlineFlex:
-    case KeywordValue::kLeft:
-    case KeywordValue::kLineThrough:
-    case KeywordValue::kMiddle:
-    case KeywordValue::kMonoscopic:
-    case KeywordValue::kMonospace:
-    case KeywordValue::kNoRepeat:
-    case KeywordValue::kNormal:
-    case KeywordValue::kNowrap:
-    case KeywordValue::kPre:
-    case KeywordValue::kPreLine:
-    case KeywordValue::kPreWrap:
-    case KeywordValue::kRelative:
-    case KeywordValue::kRepeat:
-    case KeywordValue::kReverse:
-    case KeywordValue::kRight:
-    case KeywordValue::kRow:
-    case KeywordValue::kRowReverse:
-    case KeywordValue::kSansSerif:
-    case KeywordValue::kScroll:
-    case KeywordValue::kSerif:
-    case KeywordValue::kSolid:
-    case KeywordValue::kSpaceAround:
-    case KeywordValue::kSpaceBetween:
-    case KeywordValue::kStart:
-    case KeywordValue::kStatic:
-    case KeywordValue::kStereoscopicLeftRight:
-    case KeywordValue::kStereoscopicTopBottom:
-    case KeywordValue::kStretch:
-    case KeywordValue::kTop:
-    case KeywordValue::kUppercase:
-    case KeywordValue::kVisible:
-    case KeywordValue::kWrap:
-    case KeywordValue::kWrapReverse:
+    default:
       NOTREACHED();
   }
 }
@@ -3261,86 +2382,13 @@
       *value = position_offset_provider.computed_position_offset();
     } break;
 
-    // The specified value resolves directly to the computed value for these
-    // properties.
-    case kAlignContentProperty:
-    case kAlignItemsProperty:
-    case kAlignSelfProperty:
-    case kAnimationDelayProperty:
-    case kAnimationDirectionProperty:
-    case kAnimationDurationProperty:
-    case kAnimationFillModeProperty:
-    case kAnimationIterationCountProperty:
-    case kAnimationNameProperty:
-    case kAnimationTimingFunctionProperty:
-    case kBackgroundColorProperty:
-    case kBackgroundRepeatProperty:
-    case kBorderBottomStyleProperty:
-    case kBorderLeftStyleProperty:
-    case kBorderRightStyleProperty:
-    case kBorderTopStyleProperty:
-    case kColorProperty:
-    case kContentProperty:
-    case kDisplayProperty:
-    case kFilterProperty:
-    case kFlexDirectionProperty:
-    case kFlexGrowProperty:
-    case kFlexShrinkProperty:
-    case kFlexWrapProperty:
-    case kFontFamilyProperty:
-    case kFontStyleProperty:
-    case kIntersectionObserverRootMarginProperty:
-    case kJustifyContentProperty:
-    case kOpacityProperty:
-    case kOrderProperty:
-    case kOutlineStyleProperty:
-    case kOverflowProperty:
-    case kOverflowWrapProperty:
-    case kPointerEventsProperty:
-    case kPositionProperty:
-    case kTextAlignProperty:
-    case kTextDecorationLineProperty:
-    case kTextOverflowProperty:
-    case kTextTransformProperty:
-    case kTransitionDelayProperty:
-    case kTransitionDurationProperty:
-    case kTransitionPropertyProperty:
-    case kTransitionTimingFunctionProperty:
-    case kVerticalAlignProperty:
-    case kVisibilityProperty:
-    case kWhiteSpaceProperty:
-    case kWordWrapProperty:
-    case kZIndexProperty:
+    // The specified value resolves directly to the computed value for the
+    // remaining longhand properties. Shorthand properties and at-rule
+    // properties should not occur here because they do not have computed values
+    // themselves.
+    default:
       // Nothing.
       break;
-
-    // Shorthand properties and at-rule properties should not occur here because
-    // they do not have computed values themselves.
-    case kAllProperty:
-    case kAnimationProperty:
-    case kBackgroundProperty:
-    case kBorderBottomProperty:
-    case kBorderColorProperty:
-    case kBorderLeftProperty:
-    case kBorderProperty:
-    case kBorderRadiusProperty:
-    case kBorderRightProperty:
-    case kBorderStyleProperty:
-    case kBorderTopProperty:
-    case kBorderWidthProperty:
-    case kFlexProperty:
-    case kFlexFlowProperty:
-    case kFontProperty:
-    case kMarginProperty:
-    case kNoneProperty:
-    case kOutlineProperty:
-    case kPaddingProperty:
-    case kSrcProperty:
-    case kTextDecorationProperty:
-    case kTransitionProperty:
-    case kUnicodeRangeProperty:
-      NOTREACHED();
-      break;
   }
 }  // NOLINT(readability/fn_size)
 
@@ -3372,114 +2420,7 @@
       computed_outline_style_ = value;
       break;
 
-    case kAllProperty:
-    case kAlignContentProperty:
-    case kAlignItemsProperty:
-    case kAlignSelfProperty:
-    case kAnimationDelayProperty:
-    case kAnimationDirectionProperty:
-    case kAnimationDurationProperty:
-    case kAnimationFillModeProperty:
-    case kAnimationIterationCountProperty:
-    case kAnimationNameProperty:
-    case kAnimationProperty:
-    case kAnimationTimingFunctionProperty:
-    case kBackgroundColorProperty:
-    case kBackgroundImageProperty:
-    case kBackgroundPositionProperty:
-    case kBackgroundProperty:
-    case kBackgroundRepeatProperty:
-    case kBackgroundSizeProperty:
-    case kBorderBottomProperty:
-    case kBorderBottomColorProperty:
-    case kBorderBottomLeftRadiusProperty:
-    case kBorderBottomRightRadiusProperty:
-    case kBorderBottomWidthProperty:
-    case kBorderColorProperty:
-    case kBorderLeftProperty:
-    case kBorderLeftColorProperty:
-    case kBorderLeftWidthProperty:
-    case kBorderRadiusProperty:
-    case kBorderRightProperty:
-    case kBorderRightColorProperty:
-    case kBorderRightWidthProperty:
-    case kBorderStyleProperty:
-    case kBorderTopProperty:
-    case kBorderTopColorProperty:
-    case kBorderTopLeftRadiusProperty:
-    case kBorderTopRightRadiusProperty:
-    case kBorderTopWidthProperty:
-    case kBorderProperty:
-    case kBorderWidthProperty:
-    case kBottomProperty:
-    case kBoxShadowProperty:
-    case kContentProperty:
-    case kDisplayProperty:
-    case kFilterProperty:
-    case kFlexProperty:
-    case kFlexBasisProperty:
-    case kFlexDirectionProperty:
-    case kFlexFlowProperty:
-    case kFlexGrowProperty:
-    case kFlexShrinkProperty:
-    case kFlexWrapProperty:
-    case kFontFamilyProperty:
-    case kFontProperty:
-    case kFontStyleProperty:
-    case kFontWeightProperty:
-    case kHeightProperty:
-    case kIntersectionObserverRootMarginProperty:
-    case kJustifyContentProperty:
-    case kLeftProperty:
-    case kLineHeightProperty:
-    case kMarginBottomProperty:
-    case kMarginLeftProperty:
-    case kMarginProperty:
-    case kMarginRightProperty:
-    case kMarginTopProperty:
-    case kMaxHeightProperty:
-    case kMaxWidthProperty:
-    case kMinHeightProperty:
-    case kMinWidthProperty:
-    case kNoneProperty:
-    case kOpacityProperty:
-    case kOrderProperty:
-    case kOutlineProperty:
-    case kOutlineColorProperty:
-    case kOutlineWidthProperty:
-    case kOverflowProperty:
-    case kOverflowWrapProperty:
-    case kPaddingBottomProperty:
-    case kPaddingLeftProperty:
-    case kPaddingProperty:
-    case kPaddingRightProperty:
-    case kPaddingTopProperty:
-    case kPointerEventsProperty:
-    case kRightProperty:
-    case kSrcProperty:
-    case kTextAlignProperty:
-    case kTextDecorationColorProperty:
-    case kTextDecorationLineProperty:
-    case kTextDecorationProperty:
-    case kTextIndentProperty:
-    case kTextOverflowProperty:
-    case kTextShadowProperty:
-    case kTextTransformProperty:
-    case kTopProperty:
-    case kTransformOriginProperty:
-    case kTransformProperty:
-    case kTransitionDelayProperty:
-    case kTransitionDurationProperty:
-    case kTransitionProperty:
-    case kTransitionPropertyProperty:
-    case kTransitionTimingFunctionProperty:
-    case kUnicodeRangeProperty:
-    case kVerticalAlignProperty:
-    case kVisibilityProperty:
-    case kWhiteSpaceProperty:
-    case kWidthProperty:
-    case kWordWrapProperty:
-    case kZIndexProperty:
+    default:
       break;
   }
 }
diff --git a/cobalt/debug/BUILD.gn b/cobalt/debug/BUILD.gn
index 0bdf9b5..eb383aa 100644
--- a/cobalt/debug/BUILD.gn
+++ b/cobalt/debug/BUILD.gn
@@ -46,8 +46,8 @@
     "backend/render_overlay.h",
     "backend/script_debugger_agent.cc",
     "backend/script_debugger_agent.h",
-    "backend/tracing_agent.cc",
-    "backend/tracing_agent.h",
+    "backend/tracing_controller.cc",
+    "backend/tracing_controller.h",
     "command.h",
     "console/debug_hub.cc",
     "console/debug_hub.h",
@@ -77,7 +77,7 @@
     "//cobalt/web",
     "//net",
     "//net:http_server",
-    "//starboard",
+    "//starboard:starboard_group",
     "//third_party/devtools:devtools_frontend_resources",
   ]
 }
diff --git a/cobalt/debug/backend/debug_module.cc b/cobalt/debug/backend/debug_module.cc
index ea767af..b1b09c2 100644
--- a/cobalt/debug/backend/debug_module.cc
+++ b/cobalt/debug/backend/debug_module.cc
@@ -30,7 +30,7 @@
 constexpr char kCssAgent[] = "CssAgent";
 constexpr char kOverlayAgent[] = "OverlayAgent";
 constexpr char kPageAgent[] = "PageAgent";
-constexpr char kTracingAgent[] = "TracingAgent";
+constexpr char kTracingController[] = "TracingController";
 
 // Move the state for a particular agent out of the dictionary holding the
 // state for all agents. Returns a NULL JSONObject if either |agents_state| is
@@ -161,8 +161,8 @@
                                     std::move(page_render_layer),
                                     data.resource_provider));
   }
-  tracing_agent_.reset(
-      new TracingAgent(debug_dispatcher_.get(), script_debugger_.get()));
+  tracing_controller_.reset(
+      new TracingController(debug_dispatcher_.get(), script_debugger_.get()));
 
   // Hook up hybrid agent JavaScript to the DebugBackend.
   debug_backend_->BindAgents(css_agent_);
@@ -190,7 +190,7 @@
     overlay_agent_->Thaw(RemoveAgentState(kOverlayAgent, agents_state));
   if (page_agent_)
     page_agent_->Thaw(RemoveAgentState(kPageAgent, agents_state));
-  tracing_agent_->Thaw(RemoveAgentState(kTracingAgent, agents_state));
+  tracing_controller_->Thaw(RemoveAgentState(kTracingController, agents_state));
 
   is_frozen_ = false;
 }
@@ -213,7 +213,8 @@
     StoreAgentState(agents_state, kOverlayAgent, overlay_agent_->Freeze());
   if (page_agent_)
     StoreAgentState(agents_state, kPageAgent, page_agent_->Freeze());
-  StoreAgentState(agents_state, kTracingAgent, tracing_agent_->Freeze());
+  StoreAgentState(agents_state, kTracingController,
+                  tracing_controller_->Freeze());
 
   // Take the clients from the dispatcher last so they still get events that the
   // agents might send as part of being frozen.
diff --git a/cobalt/debug/backend/debug_module.h b/cobalt/debug/backend/debug_module.h
index 4736968..9185a3c 100644
--- a/cobalt/debug/backend/debug_module.h
+++ b/cobalt/debug/backend/debug_module.h
@@ -33,7 +33,7 @@
 #include "cobalt/debug/backend/page_agent.h"
 #include "cobalt/debug/backend/render_overlay.h"
 #include "cobalt/debug/backend/script_debugger_agent.h"
-#include "cobalt/debug/backend/tracing_agent.h"
+#include "cobalt/debug/backend/tracing_controller.h"
 #include "cobalt/debug/json_object.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/render_tree/resource_provider.h"
@@ -142,7 +142,7 @@
   std::unique_ptr<OverlayAgent> overlay_agent_;
   std::unique_ptr<PageAgent> page_agent_;
   std::unique_ptr<ScriptDebuggerAgent> script_debugger_agent_;
-  std::unique_ptr<TracingAgent> tracing_agent_;
+  std::unique_ptr<TracingController> tracing_controller_;
 };
 
 }  // namespace backend
diff --git a/cobalt/debug/backend/tracing_agent.cc b/cobalt/debug/backend/tracing_agent.cc
deleted file mode 100644
index 3c26d29..0000000
--- a/cobalt/debug/backend/tracing_agent.cc
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2019 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/debug/backend/tracing_agent.h"
-
-#include <utility>
-
-#include "base/bind.h"
-#include "base/values.h"
-#include "cobalt/script/script_debugger.h"
-
-namespace cobalt {
-namespace debug {
-namespace backend {
-
-namespace {
-// Definitions from the set specified here:
-// https://chromedevtools.github.io/devtools-protocol/tot/Tracing
-constexpr char kInspectorDomain[] = "Tracing";
-
-// State parameters
-constexpr char kStarted[] = "started";
-constexpr char kCategories[] = "categories";
-
-// Size in characters of JSON to batch dataCollected events.
-constexpr size_t kDataCollectedSize = 24 * 1024;
-}  // namespace
-
-TracingAgent::TracingAgent(DebugDispatcher* dispatcher,
-                           script::ScriptDebugger* script_debugger)
-    : dispatcher_(dispatcher),
-      script_debugger_(script_debugger),
-      tracing_started_(false),
-      collected_size_(0),
-      commands_(kInspectorDomain) {
-  DCHECK(dispatcher_);
-
-  commands_["end"] = base::Bind(&TracingAgent::End, base::Unretained(this));
-  commands_["start"] = base::Bind(&TracingAgent::Start, base::Unretained(this));
-}
-
-void TracingAgent::Thaw(JSONObject agent_state) {
-  dispatcher_->AddDomain(kInspectorDomain, commands_.Bind());
-  if (!agent_state) return;
-
-  // Restore state
-  categories_.clear();
-  for (const auto& category : agent_state->FindKey(kCategories)->GetList()) {
-    categories_.emplace_back(category.GetString());
-  }
-  tracing_started_ = agent_state->FindKey(kStarted)->GetBool();
-  if (tracing_started_) {
-    script_debugger_->StartTracing(categories_, this);
-  }
-}
-
-JSONObject TracingAgent::Freeze() {
-  if (tracing_started_) {
-    script_debugger_->StopTracing();
-  }
-
-  dispatcher_->RemoveDomain(kInspectorDomain);
-
-  // Save state
-  JSONObject agent_state(new base::DictionaryValue());
-  agent_state->SetKey(kStarted, base::Value(tracing_started_));
-  base::Value::ListStorage categories_list;
-  for (const auto& category : categories_) {
-    categories_list.emplace_back(category);
-  }
-  agent_state->SetKey(kCategories, base::Value(std::move(categories_list)));
-
-  return agent_state;
-}
-
-void TracingAgent::End(Command command) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  if (!tracing_started_) {
-    command.SendErrorResponse(Command::kInvalidRequest, "Tracing not started");
-    return;
-  }
-  tracing_started_ = false;
-  categories_.clear();
-  command.SendResponse();
-
-  script_debugger_->StopTracing();
-}
-
-void TracingAgent::Start(Command command) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  if (tracing_started_) {
-    command.SendErrorResponse(Command::kInvalidRequest,
-                              "Tracing already started");
-    return;
-  }
-
-  JSONObject params = JSONParse(command.GetParams());
-
-  // Parse comma-separated tracing categories parameter.
-  categories_.clear();
-  std::string category_param;
-  if (params->GetString("categories", &category_param)) {
-    for (size_t pos = 0, comma; pos < category_param.size(); pos = comma + 1) {
-      comma = category_param.find(',', pos);
-      if (comma == std::string::npos) comma = category_param.size();
-      std::string category = category_param.substr(pos, comma - pos);
-      categories_.push_back(category);
-    }
-  }
-
-  tracing_started_ = true;
-  script_debugger_->StartTracing(categories_, this);
-
-  command.SendResponse();
-}
-
-void TracingAgent::AppendTraceEvent(const std::string& trace_event_json) {
-  // We initialize a new list into which we collect events both when we start,
-  // and after each time it's released in |SendDataCollectedEvent|.
-  if (!collected_events_) {
-    collected_events_.reset(new base::ListValue());
-  }
-
-  JSONObject event = JSONParse(trace_event_json);
-  if (event) {
-    collected_events_->Append(std::move(event));
-    collected_size_ += trace_event_json.size();
-  }
-
-  if (collected_size_ >= kDataCollectedSize) {
-    SendDataCollectedEvent();
-  }
-}
-
-void TracingAgent::FlushTraceEvents() {
-  SendDataCollectedEvent();
-  dispatcher_->SendEvent(std::string(kInspectorDomain) + ".tracingComplete");
-}
-
-void TracingAgent::SendDataCollectedEvent() {
-  if (collected_events_) {
-    collected_size_ = 0;
-    JSONObject params(new base::DictionaryValue());
-    // Releasing the list into the value param avoids copying it.
-    params->Set("value", std::move(collected_events_));
-    dispatcher_->SendEvent(std::string(kInspectorDomain) + ".dataCollected",
-                           params);
-  }
-}
-
-}  // namespace backend
-}  // namespace debug
-}  // namespace cobalt
diff --git a/cobalt/debug/backend/tracing_agent.h b/cobalt/debug/backend/tracing_agent.h
deleted file mode 100644
index f2101fb..0000000
--- a/cobalt/debug/backend/tracing_agent.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2019 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_DEBUG_BACKEND_TRACING_AGENT_H_
-#define COBALT_DEBUG_BACKEND_TRACING_AGENT_H_
-
-#include <string>
-#include <vector>
-
-#include "base/threading/thread_checker.h"
-#include "base/time/time.h"
-#include "cobalt/debug/backend/command_map.h"
-#include "cobalt/debug/backend/debug_dispatcher.h"
-#include "cobalt/debug/command.h"
-#include "cobalt/script/script_debugger.h"
-
-namespace cobalt {
-namespace debug {
-namespace backend {
-
-// There aren't enable/disable commands in the Tracing domain, so the
-// TracingAgent doesn't use AgentBase.
-//
-// https://chromedevtools.github.io/devtools-protocol/tot/Tracing
-class TracingAgent : public script::ScriptDebugger::TraceDelegate {
- public:
-  explicit TracingAgent(DebugDispatcher* dispatcher,
-                        script::ScriptDebugger* script_debugger);
-
-  void Thaw(JSONObject agent_state);
-  JSONObject Freeze();
-
-  // TraceDelegate
-  void AppendTraceEvent(const std::string& trace_event_json) override;
-  void FlushTraceEvents() override;
-
- private:
-  void End(Command command);
-  void Start(Command command);
-
-  void SendDataCollectedEvent();
-
-  DebugDispatcher* dispatcher_;
-  script::ScriptDebugger* script_debugger_;
-
-  THREAD_CHECKER(thread_checker_);
-
-  bool tracing_started_;
-  std::vector<std::string> categories_;
-  size_t collected_size_;
-  JSONList collected_events_;
-
-  // Map of member functions implementing commands.
-  CommandMap commands_;
-};
-
-}  // namespace backend
-}  // namespace debug
-}  // namespace cobalt
-
-#endif  // COBALT_DEBUG_BACKEND_TRACING_AGENT_H_
diff --git a/cobalt/debug/backend/tracing_controller.cc b/cobalt/debug/backend/tracing_controller.cc
new file mode 100644
index 0000000..0a469dc
--- /dev/null
+++ b/cobalt/debug/backend/tracing_controller.cc
@@ -0,0 +1,310 @@
+// 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/debug/backend/tracing_controller.h"
+
+#include <iostream>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/threading/thread.h"
+#include "base/trace_event/trace_event.h"
+#include "base/values.h"
+#include "cobalt/script/script_debugger.h"
+#include "starboard/common/string.h"
+
+namespace cobalt {
+namespace debug {
+namespace backend {
+
+namespace {
+// Definitions from the set specified here:
+// https://chromedevtools.github.io/devtools-protocol/tot/Tracing
+constexpr char kInspectorDomain[] = "Tracing";
+
+// State parameters
+constexpr char kStarted[] = "started";
+constexpr char kCategories[] = "categories";
+
+}  // namespace
+
+
+///////////////// TRACE EVENT AGENT ///////////////////////////////////
+
+void TraceEventAgent::StartAgentTracing(
+    const TraceConfig& trace_config,
+    StartAgentTracingCallback on_start_callback) {
+  json_output_.json_output.clear();
+  trace_buffer_.SetOutputCallback(json_output_.GetCallback());
+
+  base::trace_event::TraceLog* tracelog =
+      base::trace_event::TraceLog::GetInstance();
+  DCHECK(!tracelog->IsEnabled());
+
+  tracelog->SetEnabled(trace_config,
+                       base::trace_event::TraceLog::RECORDING_MODE);
+
+  std::move(on_start_callback).Run(agent_name_, true);
+}
+
+void TraceEventAgent::StopAgentTracing(
+    StopAgentTracingCallback on_stop_callback) {
+  base::trace_event::TraceLog* trace_log =
+      base::trace_event::TraceLog::GetInstance();
+
+  DCHECK(trace_log->IsEnabled());
+  trace_log->SetDisabled(base::trace_event::TraceLog::RECORDING_MODE);
+
+  base::Thread thread("json_outputter");
+  thread.Start();
+
+  trace_buffer_.Start();
+  base::WaitableEvent waitable_event;
+  auto collect_data_callback =
+      base::Bind(&TraceEventAgent::CollectTraceData, base::Unretained(this),
+                 base::BindRepeating(&base::WaitableEvent::Signal,
+                                     base::Unretained(&waitable_event)));
+  //  Write out the actual data by calling Flush().  Within Flush(), this
+  //  will call OutputTraceData(), possibly multiple times.  We have to do this
+  //  on a thread as there will be tasks posted to the current thread for data
+  //  writing.
+  thread.message_loop()->task_runner()->PostTask(
+      FROM_HERE, base::BindRepeating(&base::trace_event::TraceLog::Flush,
+                                     base::Unretained(trace_log),
+                                     collect_data_callback, false));
+  waitable_event.Wait();
+  trace_buffer_.Finish();
+
+  std::move(on_stop_callback)
+      .Run(agent_name_, agent_event_label_,
+           base::RefCountedString::TakeString(&json_output_.json_output));
+}
+
+
+void TraceEventAgent::CollectTraceData(
+    base::OnceClosure finished_cb,
+    const scoped_refptr<base::RefCountedString>& event_string,
+    bool has_more_events) {
+  trace_buffer_.AddFragment(event_string->data());
+  if (!has_more_events) {
+    std::move(finished_cb).Run();
+  }
+}
+
+///////////////// TRACE V8 AGENT ///////////////////////////////////
+TraceV8Agent::TraceV8Agent(script::ScriptDebugger* script_debugger)
+    : script_debugger_(script_debugger) {}
+
+void TraceV8Agent::AppendTraceEvent(const std::string& trace_event_json) {
+  trace_buffer_.AddFragment(trace_event_json);
+}
+
+void TraceV8Agent::FlushTraceEvents() {
+  trace_buffer_.Finish();
+  std::move(on_stop_callback_)
+      .Run(agent_name_, agent_event_label_,
+           base::RefCountedString::TakeString(&json_output_.json_output));
+}
+
+void TraceV8Agent::StartAgentTracing(const TraceConfig& trace_config,
+                                     StartAgentTracingCallback callback) {
+  json_output_.json_output.clear();
+  trace_buffer_.SetOutputCallback(json_output_.GetCallback());
+  trace_buffer_.Start();
+
+  script_debugger_->StartTracing(std::vector<std::string>(), this);
+  std::move(callback).Run(agent_name_, true);
+}
+
+void TraceV8Agent::StopAgentTracing(StopAgentTracingCallback callback) {
+  on_stop_callback_ = std::move(callback);
+  script_debugger_->StopTracing();
+}
+
+///////////////// TRACING CONTROLLER //////////////////////////////////
+TracingController::TracingController(DebugDispatcher* dispatcher,
+                                     script::ScriptDebugger* script_debugger)
+    : dispatcher_(dispatcher),
+      tracing_started_(false),
+      collected_size_(0),
+      commands_(kInspectorDomain) {
+  DCHECK(dispatcher_);
+
+  commands_["end"] =
+      base::Bind(&TracingController::End, base::Unretained(this));
+  commands_["start"] =
+      base::Bind(&TracingController::Start, base::Unretained(this));
+
+  agents_.push_back(std::make_unique<TraceEventAgent>());
+  // agents_.push_back(std::make_unique<TraceV8Agent>(script_debugger));
+}
+
+void TracingController::Thaw(JSONObject agent_state) {
+  dispatcher_->AddDomain(kInspectorDomain, commands_.Bind());
+  if (!agent_state) return;
+
+  // Restore state
+  categories_.clear();
+  for (const auto& category : agent_state->FindKey(kCategories)->GetList()) {
+    categories_.emplace_back(category.GetString());
+  }
+  tracing_started_ = agent_state->FindKey(kStarted)->GetBool();
+  if (tracing_started_) {
+    agents_responded_ = 0;
+    auto config = base::trace_event::TraceConfig();
+    for (const auto& a : agents_) {
+      a->StartAgentTracing(config,
+                           base::BindOnce(&TracingController::OnStartTracing,
+                                          base::Unretained(this)));
+    }
+  }
+}
+
+JSONObject TracingController::Freeze() {
+  if (tracing_started_) {
+    for (const auto& a : agents_) {
+      a->StopAgentTracing(base::BindOnce(&TracingController::OnCancelTracing,
+                                         base::Unretained(this)));
+    }
+  }
+
+  dispatcher_->RemoveDomain(kInspectorDomain);
+
+  // Save state
+  JSONObject agent_state(new base::DictionaryValue());
+  agent_state->SetKey(kStarted, base::Value(tracing_started_));
+  base::Value::ListStorage categories_list;
+  for (const auto& category : categories_) {
+    categories_list.emplace_back(category);
+  }
+  agent_state->SetKey(kCategories, base::Value(std::move(categories_list)));
+
+  return agent_state;
+}
+
+void TracingController::End(Command command) {
+  if (!tracing_started_) {
+    command.SendErrorResponse(Command::kInvalidRequest, "Tracing not started");
+    return;
+  }
+  tracing_started_ = false;
+  categories_.clear();
+  command.SendResponse();
+
+  for (const auto& a : agents_) {
+    a->StopAgentTracing(base::BindOnce(&TracingController::OnStopTracing,
+                                       base::Unretained(this)));
+  }
+}
+
+void TracingController::Start(Command command) {
+  if (tracing_started_) {
+    command.SendErrorResponse(Command::kInvalidRequest,
+                              "Tracing already started");
+    return;
+  }
+  JSONObject params = JSONParse(command.GetParams());
+  // Parse comma-separated tracing categories parameter.
+  categories_.clear();
+  std::string category_param;
+  if (params->GetString("categories", &category_param)) {
+    for (size_t pos = 0, comma; pos < category_param.size(); pos = comma + 1) {
+      comma = category_param.find(',', pos);
+      if (comma == std::string::npos) comma = category_param.size();
+      std::string category = category_param.substr(pos, comma - pos);
+      categories_.push_back(category);
+    }
+  }
+
+  collected_events_.reset(new base::ListValue());
+
+  agents_responded_ = 0;
+  auto config = base::trace_event::TraceConfig();
+  for (const auto& a : agents_) {
+    a->StartAgentTracing(config,
+                         base::BindOnce(&TracingController::OnStartTracing,
+                                        base::Unretained(this)));
+  }
+  tracing_started_ = true;
+  command.SendResponse();
+}
+
+void TracingController::OnStartTracing(const std::string& agent_name,
+                                       bool success) {
+  LOG(INFO) << "Tracing Agent:" << agent_name << " Start tracing successful? "
+            << (success ? "true" : "false");
+}
+
+void TracingController::OnStopTracing(
+    const std::string& agent_name, const std::string& events_label,
+    const scoped_refptr<base::RefCountedString>& events_str_ptr) {
+  LOG(INFO) << "Tracing Agent:" << agent_name << " Stop tracing.";
+
+  std::unique_ptr<base::Value> root =
+      base::JSONReader::Read(events_str_ptr->data(), base::JSON_PARSE_RFC);
+
+  if (!root.get()) {
+    LOG(ERROR) << "Couldn't parse the events string.";
+  }
+
+  base::ListValue* root_list = nullptr;
+  root->GetAsList(&root_list);
+
+  // Move items into our aggregate collection
+  while (root_list->GetSize()) {
+    if (!collected_events_) {
+      collected_events_.reset(new base::ListValue());
+    }
+    std::unique_ptr<base::Value> item;
+    root_list->Remove(0, &item);
+    collected_events_->Append(std::move(item));
+
+    if (collected_events_->GetSize() >= 100) {
+      SendDataCollectedEvent();
+    }
+  }
+  FlushTraceEvents();
+}
+
+void TracingController::OnCancelTracing(
+    const std::string& agent_name, const std::string& events_label,
+    const scoped_refptr<base::RefCountedString>& events_str_ptr) {
+  LOG(INFO) << "Tracing Agent:" << agent_name << " Cancel tracing.";
+}
+
+void TracingController::SendDataCollectedEvent() {
+  if (collected_events_) {
+    std::string events;
+    collected_events_->GetString(0, &events);
+    JSONObject params(new base::DictionaryValue());
+    // Releasing the list into the value param avoids copying it.
+    params->Set("value", std::move(collected_events_));
+    dispatcher_->SendEvent(std::string(kInspectorDomain) + ".dataCollected",
+                           params);
+    collected_size_ = 0;
+  }
+}
+
+void TracingController::FlushTraceEvents() {
+  SendDataCollectedEvent();
+  agents_responded_++;
+  if (agents_responded_ == static_cast<int>(agents_.size())) {
+    dispatcher_->SendEvent(std::string(kInspectorDomain) + ".tracingComplete");
+  }
+}
+
+}  // namespace backend
+}  // namespace debug
+}  // namespace cobalt
diff --git a/cobalt/debug/backend/tracing_controller.h b/cobalt/debug/backend/tracing_controller.h
new file mode 100644
index 0000000..63b6735
--- /dev/null
+++ b/cobalt/debug/backend/tracing_controller.h
@@ -0,0 +1,153 @@
+// 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_DEBUG_BACKEND_TRACING_CONTROLLER_H_
+#define COBALT_DEBUG_BACKEND_TRACING_CONTROLLER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "base/trace_event/trace_buffer.h"
+#include "base/trace_event/tracing_agent.h"
+#include "cobalt/debug/backend/command_map.h"
+#include "cobalt/debug/backend/debug_dispatcher.h"
+#include "cobalt/debug/command.h"
+#include "cobalt/script/script_debugger.h"
+
+namespace cobalt {
+namespace debug {
+namespace backend {
+
+
+using base::trace_event::TraceConfig;
+using base::trace_event::TracingAgent;
+
+//////////////////////////////////////////////////////////////////////////////
+
+class TraceEventAgent : public TracingAgent {
+ public:
+  TraceEventAgent() = default;
+  ~TraceEventAgent() = default;
+
+  // TracingAgent Interface
+  std::string GetTracingAgentName() override { return agent_name_; }
+  std::string GetTraceEventLabel() override { return agent_event_label_; }
+  void StartAgentTracing(const TraceConfig& trace_config,
+                         StartAgentTracingCallback callback) override;
+  void StopAgentTracing(StopAgentTracingCallback callback) override;
+
+  void CancelTracing();
+
+ private:
+  void CollectTraceData(
+      base::OnceClosure finished_cb,
+      const scoped_refptr<base::RefCountedString>& event_string,
+      bool has_more_events);
+
+ private:
+  std::string agent_name_{"TraceEventAgent"};
+  std::string agent_event_label_{"Performance Tracing"};
+
+  base::trace_event::TraceResultBuffer trace_buffer_;
+  base::trace_event::TraceResultBuffer::SimpleOutput json_output_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TraceV8Agent : public TracingAgent,
+                     public script::ScriptDebugger::TraceDelegate {
+ public:
+  explicit TraceV8Agent(script::ScriptDebugger* script_debugger);
+  ~TraceV8Agent() = default;
+
+  // TraceDelegate Interface
+  void AppendTraceEvent(const std::string& trace_event_json) override;
+  void FlushTraceEvents() override;
+
+  // TracingAgent Interface
+  std::string GetTracingAgentName() override { return agent_name_; }
+  std::string GetTraceEventLabel() override { return agent_event_label_; }
+  void StartAgentTracing(const TraceConfig& trace_config,
+                         StartAgentTracingCallback callback) override;
+  void StopAgentTracing(StopAgentTracingCallback callback) override;
+
+ private:
+  std::string agent_name_{"TraceV8Agent"};
+  std::string agent_event_label_{"Performance Tracing"};
+
+  StopAgentTracingCallback on_stop_callback_;
+
+  THREAD_CHECKER(thread_checker_);
+  script::ScriptDebugger* script_debugger_;
+
+  // size_t collected_size_;
+  // JSONList collected_events_;
+  base::trace_event::TraceResultBuffer trace_buffer_;
+  base::trace_event::TraceResultBuffer::SimpleOutput json_output_;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// https://chromedevtools.github.io/devtools-protocol/tot/Tracing
+class TracingController {
+ public:
+  explicit TracingController(DebugDispatcher* dispatcher,
+                             script::ScriptDebugger* script_debugger);
+  ~TracingController() = default;
+
+  void Thaw(JSONObject agent_state);
+  JSONObject Freeze();
+
+ private:
+  void End(Command command);
+  void Start(Command command);
+
+  void OnStartTracing(const std::string& agent_name, bool success);
+  void OnStopTracing(
+      const std::string& agent_name, const std::string& events_label,
+      const scoped_refptr<base::RefCountedString>& events_str_ptr);
+  void OnCancelTracing(
+      const std::string& agent_name, const std::string& events_label,
+      const scoped_refptr<base::RefCountedString>& events_str_ptr);
+
+
+ private:
+  void SendDataCollectedEvent();
+  void FlushTraceEvents();
+
+ private:
+  DebugDispatcher* dispatcher_;
+  std::vector<std::unique_ptr<TracingAgent>> agents_;
+  std::atomic_int agents_responded_{0};
+
+  bool tracing_started_;
+  std::vector<std::string> categories_;
+
+  size_t collected_size_;
+  JSONList collected_events_;
+
+  // Map of member functions implementing commands.
+  CommandMap commands_;
+};
+
+}  // namespace backend
+}  // namespace debug
+}  // namespace cobalt
+
+#endif  // COBALT_DEBUG_BACKEND_TRACING_CONTROLLER_H_
diff --git a/cobalt/debug/console/command_manager.cc b/cobalt/debug/console/command_manager.cc
index 3c30ae3..99a9420 100644
--- a/cobalt/debug/console/command_manager.cc
+++ b/cobalt/debug/console/command_manager.cc
@@ -50,9 +50,9 @@
 // static
 bool ConsoleCommandManager::CommandHandler::IsOnEnableOrTrue(
     const std::string& message) {
-  return (SbStringCompareNoCase("on", message.c_str()) == 0) ||
-         (SbStringCompareNoCase("enable", message.c_str()) == 0) ||
-         (SbStringCompareNoCase("true", message.c_str()) == 0);
+  return (strcasecmp("on", message.c_str()) == 0) ||
+         (strcasecmp("enable", message.c_str()) == 0) ||
+         (strcasecmp("true", message.c_str()) == 0);
 }
 
 
diff --git a/cobalt/demos/content/BUILD.gn b/cobalt/demos/content/BUILD.gn
index 1d6ec9a..9d60175 100644
--- a/cobalt/demos/content/BUILD.gn
+++ b/cobalt/demos/content/BUILD.gn
@@ -32,6 +32,7 @@
     "deviceorientation-demo/deviceorientation-demo.html",
     "disable-jit/index.html",
     "dom-gc-demo/dom-gc-demo.html",
+    "draw/index.html",
     "dual-playback-demo/bear.mp4",
     "dual-playback-demo/dual-playback-demo.html",
     "eme-demo/eme-demo.html",
diff --git a/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.html b/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.html
new file mode 100644
index 0000000..3c40603
--- /dev/null
+++ b/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Configure Source Buffer Memory</title>
+  <style>
+    body {
+      background-color: #0f0;
+    }
+    video {
+      height: 240px;
+      width: 426px;
+    }
+  </style>
+</head>
+<body>
+  <script type="text/javascript" src="configure-source-buffer-memory.js"></script>
+  <video id="video"></video><br>
+  <div id="status"></div>
+</body>
+</html>
diff --git a/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.js b/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.js
new file mode 100644
index 0000000..3f2c675
--- /dev/null
+++ b/cobalt/demos/content/configure-source-buffer-memory/configure-source-buffer-memory.js
@@ -0,0 +1,142 @@
+// 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.
+
+"use strict";
+
+const QUOTA_EXCEEDED_ERROR_CODE = 22;
+const mimeCodec = 'video/webm; codecs="vp9"';
+const assetURL = 'vp9-720p.webm';
+
+const assetDuration = 15;
+const assetSize = 344064;
+
+let status_div;
+let video;
+
+
+function fetchAB(url, cb) {
+  console.log("Fetching.. ", url);
+  const xhr = new XMLHttpRequest();
+  xhr.open("get", url);
+  xhr.responseType = "arraybuffer";
+  xhr.onload = () => {
+    console.log("onLoad - calling Callback");
+    cb(xhr.response);
+  };
+  console.log('Sending request for media segment ...');
+  xhr.send();
+}
+
+function testAppendToBuffer(media_source, mem_limit) {
+  const mediaSource = media_source;
+  const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
+  if (mem_limit > 0) {
+    status_div.innerHTML += "Test SourceBuffer, setting memoryLimit to " + mem_limit + "<br>";
+    sourceBuffer.memoryLimit = mem_limit;
+  } else {
+    status_div.innerHTML += "Test SourceBuffer, leaving memoryLimit at default<br>";
+  }
+
+  let MIN_SIZE = 12 * 1024 * 1024;
+  let ESTIMATED_MIN_TIME = 12;
+
+  fetchAB(assetURL, (buf) => {
+    let expectedTime = 0;
+    let expectedSize = 0;
+    let appendCount = 0;
+
+
+    let onBufferFull = function(buffer_was_full) {
+      console.log("OnBufferFull! Quota exceeded? " + buffer_was_full + " appendCount:" + appendCount + " expectedTime:" + expectedTime);
+      status_div.innerHTML += "Finished! Quota exceeded? " + buffer_was_full + " appendCount:" + appendCount + " appended " + appendCount * assetSize + "<br>";
+    }
+
+    sourceBuffer.addEventListener("updateend", function onupdateend() {
+      console.log("Update end. State is " + sourceBuffer.updating);
+      appendCount++;
+      console.log("Append Count" + appendCount);
+      if (sourceBuffer.buffered.start(0) > 0 || expectedTime > sourceBuffer.buffered.end(0)) {
+         sourceBuffer.removeEventListener('updatedend', onupdateend);
+         onBufferFull(false);
+      } else {
+        expectedTime +=  assetDuration;
+        expectedSize += assetSize;
+        if (expectedSize > (10 * MIN_SIZE)) {
+          sourceBuffer.removeEventListener('updateend', onupdateend);
+          onBufferFull(false);
+          return;
+        }
+
+        try {
+          sourceBuffer.timestampOffset = expectedTime;
+        } catch(e) {
+          console.log("Unexpected error: " + e);
+        }
+
+        try {
+          sourceBuffer.appendBuffer(buf);
+        } catch(e) {
+          console.log("Wuff! QUOTA_EXCEEDED_ERROR!");
+          status_div.innerHTML += "Wuff! QUOTA_EXCEEDED<br>";
+          if (e.code == QUOTA_EXCEEDED_ERROR_CODE) {
+            sourceBuffer.removeEventListener('updateend', onupdateend);
+            onBufferFull(true);
+          } else {
+            console.log("Unexpected error: " + e);
+          }
+        }
+      }
+    });
+
+    console.log("First Append!");
+    sourceBuffer.appendBuffer(buf);
+    status_div.innerHTML += "First append. MemoryLimit is:" + sourceBuffer.memoryLimit + ".<br>";
+  });
+}
+
+function onSourceOpen() {
+  console.log("Source Open. This state:", this.readyState); // open
+  status_div.innerHTML += "Source Open. This state:" + this.readyState + "<br>";
+  status_div.innerHTML += "Lets test first source_buffer, defaults..<br>";
+  testAppendToBuffer(this, 0);
+
+  let new_mem_limit = 400 * 1024 * 1024;
+  status_div.innerHTML += "<br><br>Lets test second source_buffer, setting memory to:" + new_mem_limit + "<br>";
+  testAppendToBuffer(this, new_mem_limit);
+  video.play();
+}
+
+
+function createMediaSource() {
+
+  console.log('Video Get Element By Id...');
+  video = document.getElementById('video');
+  status_div = document.getElementById('status');
+  status_div.innerHTML += 'Video Get Element By Id...<br>';
+
+  console.log('Create Media Source...');
+  status_div.innerHTML += 'Create Media Source...<br>';
+  var mediaSource = new MediaSource;
+
+  console.log('Attaching MediaSource to video element ...');
+  status_div.innerHTML += 'Attaching MediaSource to video element ...<br>';
+  video.src = window.URL.createObjectURL(mediaSource);
+
+  console.log('Add event listener..');
+  status_div.innerHTML += 'Add event listener..<br>';
+  mediaSource.addEventListener('sourceopen', onSourceOpen);
+
+}
+
+addEventListener('load', createMediaSource);
diff --git a/cobalt/demos/content/configure-source-buffer-memory/vp9-720p.webm b/cobalt/demos/content/configure-source-buffer-memory/vp9-720p.webm
new file mode 100644
index 0000000..08a670e
--- /dev/null
+++ b/cobalt/demos/content/configure-source-buffer-memory/vp9-720p.webm
Binary files differ
diff --git a/cobalt/demos/content/draw/index.html b/cobalt/demos/content/draw/index.html
new file mode 100644
index 0000000..6e596c6
--- /dev/null
+++ b/cobalt/demos/content/draw/index.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <style>
+        .container {
+            width: 500px;
+            height: 500px;
+            position: relative;
+            background-color: white;
+        }
+
+        .pixel {
+            width: 5px;
+            height: 5px;
+            position: absolute;
+            background-color: red;
+        }
+    </style>
+</head>
+
+<body>
+    <div class="container" id="container"></div>
+</body>
+
+<script>
+    window.addEventListener('load', () => registerHandlers());
+
+    function registerHandlers() {
+        const container = document.getElementById('container');
+        let isDrawing = false;
+
+        container.addEventListener('pointerdown', (e) => {
+            isDrawing = true;
+            colorPixel(e);
+        });
+        document.addEventListener('pointerup', () => { isDrawing = false; });
+        container.addEventListener('pointermove', colorPixel);
+
+        function colorPixel(e) {
+            if (!isDrawing) return;
+
+            const x = Math.floor((e.clientX - container.offsetLeft) / 5) * 5;
+            const y = Math.floor((e.clientY - container.offsetTop) / 5) * 5;
+
+            const pixel = document.createElement('div');
+            pixel.classList.add('pixel');
+            pixel.style.left = `${x}px`;
+            pixel.style.top = `${y}px`;
+
+            container.appendChild(pixel);
+        }
+    }
+</script>
+
+</html>
diff --git a/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.html b/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.html
index 36e504f..8e3297a 100644
--- a/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.html
+++ b/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.html
@@ -23,7 +23,7 @@
           margin: 0;
         }
 
-        #player-layer {
+        #primary-player-layer {
           width: 100%;
           height: 100%;
         }
@@ -33,13 +33,14 @@
           height: 100%;
         }
 
-        #ui-layer {
+        #secondary-player-layer {
           position: absolute;
-          top: 15%;
-          height: 85%;
+          top: 60%;
+          height: 40%;
           width: 100%;
-          background-color: rgba(33, 33, 33, .75);
           padding: 24px;
+          display: flex;
+          justify-content: center;
         }
 
         .item {
@@ -48,22 +49,23 @@
           display: inline-block;
           margin: 24px;
           vertical-align: middle;
+          background-color: rgba(33, 33, 33, .75);
         }
     </style>
   </head>
   <body>
-    <div id="player-layer">
-      <video class="primary" id="primary-video" muted="1" autoplay="1"></video>
+    <div id="primary-player-layer">
+      <video id="primary-video" muted="1" autoplay="1"></video>
     </div>
-    <div id="ui-layer">
-      <div class="item" style="background-color: #D44">
-        <video class="secondary" id="secondary-video-1" muted="1" autoplay="1"></video>
+    <div id="secondary-player-layer">
+      <div class="item">
+        <video id="secondary-video-1" muted="1" autoplay="1"></video>
       </div>
-      <div class="item" style="background-color: #4D4">
-        <video class="secondary" id="secondary-video-2" muted="1" autoplay="1"></video>
+      <div class="item">
+        <video id="secondary-video-2" muted="1" autoplay="1"></video>
       </div>
-      <div class="item" style="background-color: #44D">
-        <video class="secondary" id="secondary-video-3" muted="1" autoplay="1"></video>
+      <div class="item">
+        <video id="secondary-video-3" muted="1" autoplay="1"></video>
       </div>
     </div>
     <script src="multi-encrypted-video.js"></script>
diff --git a/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.js b/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.js
index 028d11c..27dcddf 100644
--- a/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.js
+++ b/cobalt/demos/content/multi-encrypted-video/multi-encrypted-video.js
@@ -12,6 +12,58 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+
+// Dictionary mapping string descriptions to media file descriptions in the
+// form of [contentType, url, maxVideoCapabilities (for videos only), licenseUrl]
+const MEDIA_FILES = {
+  'av1_720p_60fps_drm': {
+    contentType: 'video/mp4; codecs="av01.0.05M.08"',
+    url: 'https://storage.googleapis.com/ytlr-cert.appspot.com/test-materials/media/av1-senc/sdr_720p60.mp4',
+    maxVideoCapabilities: 'width=1280; height=720',
+    licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=ik0&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=6508f99557a8385f&signature=5153900DAC410803EC269D252DAAA82BA6D8B825.495E631E406584A8EFCB4E9C9F3D45F6488B94E4',
+  },
+  // 40 MB
+  'h264_720p_24fps_drm': {
+    contentType: 'video/mp4; codecs="avc1.640028"',
+    url: 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/oops_cenc-20121114-145-no-clear-start.mp4',
+    maxVideoCapabilities: 'width=1280; height=720',
+    licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=03681262dc412c06&signature=9C4BE99E6F517B51FED1F0B3B31966D3C5DAB9D6.6A1F30BB35F3A39A4CA814B731450D4CBD198FFD',
+  },
+  // 38 MB
+  'h264_720p_60fps_drm': {
+    contentType: 'video/mp4; codecs="avc1.640028"',
+    url: 'https://storage.googleapis.com/ytlr-cert.appspot.com/test-materials/media/drml3NoHdcp_h264_720p_60fps_cenc.mp4',
+    maxVideoCapabilities: 'width=1280; height=720',
+    licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=03681262dc412c06&signature=9C4BE99E6F517B51FED1F0B3B31966D3C5DAB9D6.6A1F30BB35F3A39A4CA814B731450D4CBD198FFD',
+  },
+  // 32 MB
+  'vp9_720p_60fps_drm': {
+    contentType: 'video/webm; codecs="vp9"',
+    url: 'https://storage.googleapis.com/ytlr-cert.appspot.com/test-materials/media/drml3NoHdcp_vp9_720p_60fps_enc.webm',
+    maxVideoCapabilities: 'width=1280; height=720',
+    licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=f320151fa3f061b2&signature=81E7B33929F9F35922F7D2E96A5E7AC36F3218B2.673F553EE51A48438AE5E707AEC87A071B4FEF65'
+  },
+  // 1 MB
+  // Mono won't work with tunnel mode on many devices.
+  'aac_mono_drm': {
+    contentType: 'audio/mp4; codecs="mp4a.40.2"',
+    url: 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/oops_cenc-20121114-148.mp4',
+    licenseUrl: 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=03681262dc412c06&signature=9C4BE99E6F517B51FED1F0B3B31966D3C5DAB9D6.6A1F30BB35F3A39A4CA814B731450D4CBD198FFD',
+  },
+  // 2.8 MB
+  'aac_clear': {
+    contentType: 'audio/mp4; codecs="mp4a.40.2"',
+    url: 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/car-20120827-8c.mp4',
+  },
+  // 1.7 MB
+  'opus_clear': {
+    contentType: 'audio/webm; codecs="opus"',
+    url: 'https://storage.googleapis.com/ytlr-cert.appspot.com/test-materials/media/car_opus_med.webm',
+  },
+};
+
+mediaCache = {}
+
 function fetchArrayBuffer(method, url, body, callback) {
   var xhr = new XMLHttpRequest();
   xhr.responseType = 'arraybuffer';
@@ -22,6 +74,16 @@
   xhr.send(body);
 }
 
+async function fetchMediaData(mediaFileId) {
+  if (mediaFileId in mediaCache) {
+    return mediaCache[mediaFileId];
+  }
+
+  const response = await fetch(MEDIA_FILES[mediaFileId].url);
+  mediaCache[mediaFileId] = await response.arrayBuffer();
+  return mediaCache[mediaFileId];
+}
+
 function extractLicense(licenseArrayBuffer) {
   var licenseArray = new Uint8Array(licenseArrayBuffer);
   var licenseStartIndex = licenseArray.length - 2;
@@ -37,80 +99,156 @@
   return licenseArray.subarray(licenseStartIndex);
 }
 
-var videoContentType = 'video/mp4; codecs="avc1.640028"';
-var audioContentType = 'audio/mp4; codecs="mp4a.40.2"';
-
-function play(videoElementId, keySystem) {
-  navigator.requestMediaKeySystemAccess(keySystem, [{
-    'initDataTypes': ['cenc'],
-    'videoCapabilities': [{'contentType': videoContentType}],
-    'audioCapabilities': [{'contentType': audioContentType}]
-  }]).then(function(mediaKeySystemAccess) {
-    return mediaKeySystemAccess.createMediaKeys();
-  }).then(function(mediaKeys) {
-    var videoElement = document.getElementById(videoElementId);
-
-    if (videoElementId != 'primary-video') {
-      videoElement.setMaxVideoCapabilities('width=1280; height=720');
+async function createMediaKeySystem(isPrimaryVideo, audioContentType, videoContentType) {
+  const keySystems = isPrimaryVideo ? ['com.widevine.alpha'] : ['com.youtube.widevine.l3', 'com.widevine.alpha'];
+  for (keySystem of keySystems) {
+    try {
+      mediaKeySystemAccess = await navigator.requestMediaKeySystemAccess(keySystem, [{
+        'initDataTypes': ['cenc', 'webm'],
+        'audioCapabilities': [{'contentType': audioContentType}],
+        'videoCapabilities': [{'contentType': videoContentType}]}]);
+      return mediaKeySystemAccess.createMediaKeys();
+    } catch {
+      console.log('create keySystem ' + keySystem + ' failed.')
+      continue;
     }
-
-    if (mediaKeys.getMetrics) {
-      console.log('Found getMetrics(), calling it ...');
-      try {
-        mediaKeys.getMetrics();
-        console.log('Calling getMetrics() succeeded.');
-      } catch(e) {
-        console.log('Calling getMetrics() failed.');
-      }
-    }
-
-    videoElement.setMediaKeys(mediaKeys);
-
-    mediaKeySession = mediaKeys.createSession();
-    mediaKeySession.addEventListener('message', function(messageEvent) {
-      var licenseServerUrl = 'https://dash-mse-test.appspot.com/api/drm/widevine?drm_system=widevine&source=YOUTUBE&ip=0.0.0.0&ipbits=0&expire=19000000000&key=test_key1&sparams=ip,ipbits,expire,drm_system,source,video_id&video_id=03681262dc412c06&signature=9C4BE99E6F517B51FED1F0B3B31966D3C5DAB9D6.6A1F30BB35F3A39A4CA814B731450D4CBD198FFD';
-      fetchArrayBuffer('POST', licenseServerUrl, messageEvent.message,
-          function(licenseArrayBuffer) {
-            mediaKeySession.update(extractLicense(licenseArrayBuffer));
-          });
-    });
-
-    videoElement.addEventListener('encrypted', function(encryptedEvent) {
-      mediaKeySession.generateRequest(
-          encryptedEvent.initDataType, encryptedEvent.initData);
-    });
-
-    var mediaSource = new MediaSource();
-    mediaSource.addEventListener('sourceopen', function() {
-      var videoSourceBuffer = mediaSource.addSourceBuffer(videoContentType);
-      fetchArrayBuffer('GET',
-                      'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/oops_cenc-20121114-145-no-clear-start.mp4',
-                      null,
-                      function(videoArrayBuffer) {
-        videoSourceBuffer.appendBuffer(videoArrayBuffer);
-      });
-
-      var audioSourceBuffer = mediaSource.addSourceBuffer(audioContentType);
-      fetchArrayBuffer('GET',
-                      'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/oops_cenc-20121114-148.mp4',
-                      null,
-                      function(audioArrayBuffer) {
-        audioSourceBuffer.appendBuffer(audioArrayBuffer);
-      });
-    });
-
-    videoElement.src = URL.createObjectURL(mediaSource);
-    videoElement.play();
-  });
+  }
 }
 
-play('primary-video', 'com.widevine.alpha');
-window.setTimeout(function() {
-  play('secondary-video-1', 'com.youtube.widevine.l3');
-}, 10000);
-window.setTimeout(function() {
-  play('secondary-video-2', 'com.youtube.widevine.l3');
-}, 20000);
-window.setTimeout(function() {
-  play('secondary-video-3', 'com.youtube.widevine.l3');
-}, 30000);
+function createTunnelModeContentType(videoContentType, tunnelModeAttributeValue) {
+  return videoContentType + '; tunnelmode=' + tunnelModeAttributeValue;
+}
+
+function isTunnelModeSupported(videoContentType) {
+  if (!MediaSource.isTypeSupported(videoContentType)) {
+    // If the content type isn't supported at all, it won't be supported in
+    // tunnel mode.
+    return false;
+  }
+  if (MediaSource.isTypeSupported(createTunnelModeContentType(videoContentType, 'invalid'))) {
+    // The implementation doesn't understand the `tunnelmode` attribute.
+    return false;
+  }
+  return MediaSource.isTypeSupported(createTunnelModeContentType(videoContentType, 'true'));
+}
+
+async function play(videoElementId, videoFileId, optionalAudioFileId) {
+  const isPrimaryVideo = videoElementId == 'primary-video';
+
+  videoContentType = MEDIA_FILES[videoFileId].contentType;
+  if (isTunnelModeSupported(videoContentType)) {
+    videoContentType = createTunnelModeContentType(videoContentType, 'true');
+  }
+
+  var mediaKeys = await createMediaKeySystem(isPrimaryVideo, optionalAudioFileId ? MEDIA_FILES[optionalAudioFileId].contentType : MEDIA_FILES['opus_clear'].contentType, videoContentType);
+  var videoElement = document.getElementById(videoElementId);
+
+  if (!isPrimaryVideo && videoElement.setMaxVideoCapabilities) {
+    videoElement.setMaxVideoCapabilities(MEDIA_FILES[videoFileId].maxVideoCapabilities);
+  }
+
+  videoElement.setMediaKeys(mediaKeys);
+
+  mediaKeySession = mediaKeys.createSession();
+  var licenseServerUrl = MEDIA_FILES[videoFileId].licenseUrl;
+  mediaKeySession.addEventListener('message', function(messageEvent) {
+    fetchArrayBuffer('POST', licenseServerUrl, messageEvent.message,
+      function(licenseArrayBuffer) {
+        mediaKeySession.update(extractLicense(licenseArrayBuffer));
+      });
+  });
+
+  videoElement.addEventListener('encrypted', function(encryptedEvent) {
+    mediaKeySession.generateRequest(
+        encryptedEvent.initDataType, encryptedEvent.initData);
+  });
+
+  var mediaSource = new MediaSource();
+  mediaSource.addEventListener('sourceopen', async function() {
+    var videoSourceBuffer = mediaSource.addSourceBuffer(videoContentType);
+    var audioSourceBuffer;
+
+    if (optionalAudioFileId) {
+      audioSourceBuffer = mediaSource.addSourceBuffer(MEDIA_FILES[optionalAudioFileId].contentType);
+    }
+
+    var videoArrayBuffer = await fetchMediaData(videoFileId);
+    videoSourceBuffer.appendBuffer(videoArrayBuffer);
+
+    if (audioSourceBuffer) {
+      var audioArrayBuffer = await fetchMediaData(optionalAudioFileId);
+      audioSourceBuffer.appendBuffer(audioArrayBuffer);
+    }
+  });
+
+  videoElement.src = URL.createObjectURL(mediaSource);
+  videoElement.play();
+}
+
+function getGetParameters() {
+  var parsedParameters = {};
+
+  const urlComponents = window.location.href.split('?');
+  if (urlComponents.length < 2) {
+    return parsedParameters;
+  }
+
+  const query = urlComponents[1];
+  const parameters = query.split('&');
+
+  for (parameter of parameters) {
+    const split = parameter.split('=');
+    if (split.length == 0) {
+      continue;
+    }
+    if (split.length == 1) {
+      parsedParameters[split[0]] = '';
+    } else {
+      parsedParameters[split[0]] = split[1];
+    }
+  }
+
+  return parsedParameters;
+}
+
+function populateMediaFileIds() {
+  var mediaFileIds = [];
+  const getParameters = getGetParameters();
+
+  mediaFileIds['video0'] = getParameters['video0'] ?? 'vp9_720p_60fps_drm';
+  mediaFileIds['video1'] = getParameters['video1'] ?? 'h264_720p_24fps_drm';
+  mediaFileIds['video2'] = getParameters['video2'] ?? 'vp9_720p_60fps_drm';
+  mediaFileIds['video3'] = getParameters['video3'] ?? 'h264_720p_24fps_drm';
+  mediaFileIds['audio'] = getParameters['audio'] ?? 'opus_clear';
+
+  return mediaFileIds;
+}
+
+async function prefetchMediaData(mediaFileIds) {
+  for (mediaFileId of Object.keys(mediaFileIds)) {
+    await fetchMediaData(mediaFileIds[mediaFileId]);
+  }
+}
+
+async function main() {
+  if (window.h5vcc && window.h5vcc.settings) {
+    h5vcc.settings.set('MediaSource.EnableAvoidCopyingArrayBuffer', 1);
+  }
+
+  const mediaFileIds = populateMediaFileIds();
+  await prefetchMediaData(mediaFileIds);
+
+  play('primary-video', mediaFileIds['video0'], mediaFileIds['audio']);
+  window.setTimeout(function() {
+    play('secondary-video-1', mediaFileIds['video1']);
+  }, 10000);
+  window.setTimeout(function() {
+    play('secondary-video-2', mediaFileIds['video2']);
+  }, 20000);
+  window.setTimeout(function() {
+    play('secondary-video-3', mediaFileIds['video3']);
+  }, 30000);
+}
+
+
+main();
diff --git a/cobalt/demos/readme.md b/cobalt/demos/readme.md
index 26e67bb..6f5e12e 100644
--- a/cobalt/demos/readme.md
+++ b/cobalt/demos/readme.md
@@ -6,4 +6,4 @@
 `python3 -m http.server` in the `cobalt_src` directory and then running
 Cobalt and pointing the `--url` parameter to the file in question. For example:
 
-`out/.../cobalt --url=http://0.0.0.0:8000/cobalt/demos/content/watchdog-demo/index.html`
+`out/.../cobalt --url=http://127.0.0.1:8000/cobalt/demos/content/watchdog-demo/index.html`
diff --git a/cobalt/doc/cvals.md b/cobalt/doc/cvals.md
index 68317e4..99f2324 100644
--- a/cobalt/doc/cvals.md
+++ b/cobalt/doc/cvals.md
@@ -443,3 +443,17 @@
     stopped playing animations.
 *   **Time.Renderer.Rasterize.NewRenderTree** - Time when the most recent render
     tree was first rasterized.
+
+### Starboard
+
+#### PublicCVals
+
+*   **Starboard.FileWrite.Success** - Count of successful file writes.
+*   **Starboard.FileWrite.Fail** - Count of failed file writes.
+*   **Starboard.FileWrite.BytesWritten** - Bytes written to file.
+*   **Starboard.StorageWriteRecord.Success** - Count of successful storage
+    record writes.
+*   **Starboard.StorageWriteRecord.Fail** - Count of failed storage record
+    writes.
+*   **Starboard.StorageWriteRecord.BytesWritten** - Bytes written to storage
+    record.
diff --git a/cobalt/doc/docker_build.md b/cobalt/doc/docker_build.md
index a65e17d..b3c79e7 100644
--- a/cobalt/doc/docker_build.md
+++ b/cobalt/doc/docker_build.md
@@ -5,18 +5,18 @@
 
 The instructions below assume Docker is installed and is able to run basic
 [`hello-world` verification](https://docs.docker.com/get-started/#test-docker-installation).
-`docker-compose` command is expected to be available as well.
+`docker compose` command is expected to be available as well.
 
 ## Usage
 
 The simplest usage is:
 
-  `docker-compose run <platform>`
+  `docker compose run <platform>`
 
 By default, a debug build will be built, with `cobalt` as a target.
 You can override this with an environment variable, e.g.
 
-  `docker-compose run -e CONFIG=devel -e TARGET=nplb <platform>`
+  `docker compose run -e CONFIG=devel -e TARGET=nplb <platform>`
 
 where config is one of the four optimization levels, debug, devel, qa and gold,
 and target is the build target passed to `ninja`
@@ -32,9 +32,9 @@
 ## Customization
 
 To parametrize base operating system images used for the build, pass BASE_OS
-as an argument to docker-compose as follows:
+as an argument to docker compose as follows:
 
-  `docker-compose build --build-arg BASE_OS="ubuntu:bionic" base`
+  `docker compose build --build-arg BASE_OS="ubuntu:bionic" base`
 
 This parameter is defined in `docker/linux/base/Dockerfile` and is passed to
 Docker `FROM ...` statement.
@@ -73,6 +73,6 @@
 To debug build issues, enter the shell of the corresponding build container by
 launching the bash shell, i.e.
 
-  `docker-compose run linux-x64x11 /bin/bash`
+  `docker compose run linux-x64x11 /bin/bash`
 
 and try to build cobalt [with the usual `gn / ninja` flow](../../README.md#building-and-running-the-code).
diff --git a/cobalt/doc/memory_tuning.md b/cobalt/doc/memory_tuning.md
index 662fb59..297a7e4 100644
--- a/cobalt/doc/memory_tuning.md
+++ b/cobalt/doc/memory_tuning.md
@@ -1,4 +1,4 @@
-# Memory Tuning #
+# Memory Tuning
 
 Cobalt is designed to choose sensible parameters for memory-related options and
 parameters through a system called "AutoMem".
@@ -16,7 +16,7 @@
 *Setting `--max_cobalt_cpu_usage` and `--max_cobalt_gpu_usage` on the
 command line is a beta feature.*
 
-### Memory Settings Table ###
+### Memory Settings Table
 
 A table similar to the one below, will be printed on startup.
 
@@ -79,7 +79,7 @@
       * This value was AutoSet to a default value, but then was reduced in
       response to `max_cobalt_cpu_usage` or `max_cobalt_gpu_usage being` set too low.
 
-### Maximum Memory Table ###
+### Maximum Memory Table
 
 This second table is also printed at startup and details the sum of memory and
 maximum memory limits as reported by cobalt.
@@ -128,13 +128,13 @@
 will reduce their memory consumption. When this happens, look for the string
 *`AutoSet (Constrained)`* in the first table.
 
-## Setting Maximum Memory Values ##
+## Setting Maximum Memory Values
 
 The max cpu and gpu memory of the system can be set by command line:
   * `--max_cobalt_cpu_usage=160MB`
   * `--max_cobalt_gpu_usage=160MB`
 
-### Memory Scaling ###
+### Memory Scaling
 
 There are two primary ways in which the memory consumption settings will scale down.
 One is by specifying `--max_cobalt_cpu_usage` (or `max_cobalt_gpu_usage`) to a
@@ -152,7 +152,7 @@
  `--image_cache_size_in_bytes=auto` will allow `image_cache_size_in_bytes` to be
 flexible by disabling the value being set by a build setting.
 
-### Memory Warnings ###
+### Memory Warnings
 
 Cobalt will periodically check to see if the memory consumed by the application
 is less than the `--max_cobalt_cpu_usage` and `--max_cobalt_gpu_usage` amount.
@@ -160,7 +160,7 @@
 once to stdout for cpu and/or gpu memory systems.
 
 
-### Example 1 - Configuring for a memory restricted platform ###
+### Example 1 - Configuring for a memory restricted platform
 
 Let's say that we are configuring platform called "XXX":
 
@@ -186,7 +186,7 @@
 
   * `cobalt --max_cobalt_cpu_usage=160MB`
 
-### Example 2 - Configuring for a memory-plentiful platform ###
+### Example 2 - Configuring for a memory-plentiful platform
 
 The following command line will give a lot of memory to image cache and give
 500MB to `max_cobalt_cpu_usage` and `max_cobalt_gpu_usage`.
@@ -196,9 +196,9 @@
 --image_cache_size_in_bytes=80MB
 ~~~
 
-## API Reference ##
+## API Reference
 
-#### Memory System API ####
+#### Memory System API
 
   * `max_cobalt_cpu_usage`
     * This setting will set the maximum cpu memory that the app will consume.
@@ -218,7 +218,7 @@
       for `max_cobalt_gpu_usage` in commandline/starboard settings then no
       GPU memory checking is performed.
 
-#### Memory Setting API ####
+#### Memory Setting API
 
   * `image_cache_size_in_bytes`
     * See documentation *Image cache capacity* in `performance_tuning.md` for what
@@ -252,7 +252,7 @@
       for what this setting does.
     * Set via command line, or else Cobalt extension, or else automatically by Cobalt.
 
-#### Units for Command Line Settings ####
+#### Units for Command Line Settings
 
 Memory values passed into Cobalt via command line arguments support units such
 kb, mb, and gb for kilo-byte, megabyte, gigabytes. These units are case insensitive.
diff --git a/cobalt/doc/performance_tuning.md b/cobalt/doc/performance_tuning.md
index b130bcf..0fa0357 100644
--- a/cobalt/doc/performance_tuning.md
+++ b/cobalt/doc/performance_tuning.md
@@ -320,22 +320,6 @@
 **Tags:** *framerate, startup, browse-to-watch, input latency,*
 
 
-### Implement hardware image decoding
-
-The Starboard header file [`starboard/image.h`](../../starboard/image.h) defines
-functions that allow platforms to implement hardware-accelerated image
-decoding, if available.  In particular, if `SbImageIsDecodeSupported()` returns
-true for the specified mime type and output format, then instead of using the
-software-based libpng or libjpeg libraries, Cobalt will instead call
-`SbImageDecode()`.  `SbImageDecode()` is expected to return a decoded image as
-a `SbDecodeTarget` option, from which Cobalt will extract a GL texture or
-Blitter API surface object when rendering.  If non-CPU hardware is used to
-decode images, it would alleviate the load on the CPU, and possibly also
-increase the speed at which images can be decoded.
-
-**Tags:** *startup, browse-to-watch, input latency.*
-
-
 ### Use Chromium's about:tracing tool to debug Cobalt performance
 
 Cobalt has support for generating profiling data that is viewable through
diff --git a/cobalt/dom/BUILD.gn b/cobalt/dom/BUILD.gn
index 9ce1859..7a0ce1b 100644
--- a/cobalt/dom/BUILD.gn
+++ b/cobalt/dom/BUILD.gn
@@ -385,6 +385,7 @@
     "event_queue_test.cc",
     "font_cache_test.cc",
     "html_element_factory_test.cc",
+    "html_element_style_test.cc",
     "html_element_test.cc",
     "html_link_element_test.cc",
     "intersection_observer_test.cc",
diff --git a/cobalt/dom/document.cc b/cobalt/dom/document.cc
index e405cd9..6fb63cd 100644
--- a/cobalt/dom/document.cc
+++ b/cobalt/dom/document.cc
@@ -235,26 +235,26 @@
     script::ExceptionState* exception_state) {
   // https://www.w3.org/TR/dom/#dom-document-createevent
   // The match of interface name is case-insensitive.
-  if (base::strcasecmp(interface_name.c_str(), "event") == 0 ||
-      base::strcasecmp(interface_name.c_str(), "events") == 0 ||
-      base::strcasecmp(interface_name.c_str(), "htmlevents") == 0) {
+  if (strcasecmp(interface_name.c_str(), "event") == 0 ||
+      strcasecmp(interface_name.c_str(), "events") == 0 ||
+      strcasecmp(interface_name.c_str(), "htmlevents") == 0) {
     return new web::Event(web::Event::Uninitialized);
-  } else if (base::strcasecmp(interface_name.c_str(), "keyboardevent") == 0 ||
-             base::strcasecmp(interface_name.c_str(), "keyevents") == 0) {
+  } else if (strcasecmp(interface_name.c_str(), "keyboardevent") == 0 ||
+             strcasecmp(interface_name.c_str(), "keyevents") == 0) {
     return new KeyboardEvent(web::Event::Uninitialized);
-  } else if (base::strcasecmp(interface_name.c_str(), "messageevent") == 0) {
+  } else if (strcasecmp(interface_name.c_str(), "messageevent") == 0) {
     return new web::MessageEvent(web::Event::Uninitialized);
-  } else if (base::strcasecmp(interface_name.c_str(), "mouseevent") == 0 ||
-             base::strcasecmp(interface_name.c_str(), "mouseevents") == 0) {
+  } else if (strcasecmp(interface_name.c_str(), "mouseevent") == 0 ||
+             strcasecmp(interface_name.c_str(), "mouseevents") == 0) {
     return new MouseEvent(web::Event::Uninitialized);
-  } else if (base::strcasecmp(interface_name.c_str(), "uievent") == 0 ||
-             base::strcasecmp(interface_name.c_str(), "uievents") == 0) {
+  } else if (strcasecmp(interface_name.c_str(), "uievent") == 0 ||
+             strcasecmp(interface_name.c_str(), "uievents") == 0) {
     return new UIEvent(web::Event::Uninitialized);
-  } else if (base::strcasecmp(interface_name.c_str(), "wheelevent") == 0) {
+  } else if (strcasecmp(interface_name.c_str(), "wheelevent") == 0) {
     // This not in the spec, but commonly implemented to create a WheelEvent.
     //   https://www.w3.org/TR/2016/WD-uievents-20160804/#interface-wheelevent
     return new WheelEvent(web::Event::Uninitialized);
-  } else if (base::strcasecmp(interface_name.c_str(), "customevent") == 0) {
+  } else if (strcasecmp(interface_name.c_str(), "customevent") == 0) {
     return new web::CustomEvent(web::Event::Uninitialized);
   }
 
diff --git a/cobalt/dom/font_face_updater.cc b/cobalt/dom/font_face_updater.cc
index 9111114..a040069 100644
--- a/cobalt/dom/font_face_updater.cc
+++ b/cobalt/dom/font_face_updater.cc
@@ -135,69 +135,7 @@
     case cssom::KeywordValue::kInitial:
       font_family_.clear();
       break;
-    case cssom::KeywordValue::kAbsolute:
-    case cssom::KeywordValue::kAlternate:
-    case cssom::KeywordValue::kAlternateReverse:
-    case cssom::KeywordValue::kAuto:
-    case cssom::KeywordValue::kBackwards:
-    case cssom::KeywordValue::kBaseline:
-    case cssom::KeywordValue::kBlock:
-    case cssom::KeywordValue::kBoth:
-    case cssom::KeywordValue::kBottom:
-    case cssom::KeywordValue::kBreakWord:
-    case cssom::KeywordValue::kCenter:
-    case cssom::KeywordValue::kClip:
-    case cssom::KeywordValue::kCollapse:
-    case cssom::KeywordValue::kColumn:
-    case cssom::KeywordValue::kColumnReverse:
-    case cssom::KeywordValue::kContain:
-    case cssom::KeywordValue::kContent:
-    case cssom::KeywordValue::kCover:
-    case cssom::KeywordValue::kCurrentColor:
-    case cssom::KeywordValue::kEllipsis:
-    case cssom::KeywordValue::kEnd:
-    case cssom::KeywordValue::kEquirectangular:
-    case cssom::KeywordValue::kFixed:
-    case cssom::KeywordValue::kFlex:
-    case cssom::KeywordValue::kFlexEnd:
-    case cssom::KeywordValue::kFlexStart:
-    case cssom::KeywordValue::kForwards:
-    case cssom::KeywordValue::kHidden:
-    case cssom::KeywordValue::kInfinite:
-    case cssom::KeywordValue::kInline:
-    case cssom::KeywordValue::kInlineBlock:
-    case cssom::KeywordValue::kInlineFlex:
-    case cssom::KeywordValue::kLeft:
-    case cssom::KeywordValue::kLineThrough:
-    case cssom::KeywordValue::kMiddle:
-    case cssom::KeywordValue::kMonoscopic:
-    case cssom::KeywordValue::kNone:
-    case cssom::KeywordValue::kNoRepeat:
-    case cssom::KeywordValue::kNormal:
-    case cssom::KeywordValue::kNowrap:
-    case cssom::KeywordValue::kPre:
-    case cssom::KeywordValue::kPreLine:
-    case cssom::KeywordValue::kPreWrap:
-    case cssom::KeywordValue::kRelative:
-    case cssom::KeywordValue::kRepeat:
-    case cssom::KeywordValue::kReverse:
-    case cssom::KeywordValue::kRight:
-    case cssom::KeywordValue::kRow:
-    case cssom::KeywordValue::kRowReverse:
-    case cssom::KeywordValue::kScroll:
-    case cssom::KeywordValue::kSolid:
-    case cssom::KeywordValue::kSpaceAround:
-    case cssom::KeywordValue::kSpaceBetween:
-    case cssom::KeywordValue::kStart:
-    case cssom::KeywordValue::kStatic:
-    case cssom::KeywordValue::kStereoscopicLeftRight:
-    case cssom::KeywordValue::kStereoscopicTopBottom:
-    case cssom::KeywordValue::kStretch:
-    case cssom::KeywordValue::kTop:
-    case cssom::KeywordValue::kUppercase:
-    case cssom::KeywordValue::kVisible:
-    case cssom::KeywordValue::kWrap:
-    case cssom::KeywordValue::kWrapReverse:
+    default:
       NOTREACHED();
   }
 }
diff --git a/cobalt/dom/html_element.cc b/cobalt/dom/html_element.cc
index ef21c62..9ddbdcb 100644
--- a/cobalt/dom/html_element.cc
+++ b/cobalt/dom/html_element.cc
@@ -161,6 +161,22 @@
 base::LazyInstance<NonTrivialStaticFields>::DestructorAtExit
     non_trivial_static_fields = LAZY_INSTANCE_INITIALIZER;
 
+void InvalidateScrollAreaCacheOfAncestors(Node* node) {
+  for (Node* ancestor_node = node; ancestor_node;
+       ancestor_node = ancestor_node->parent_node()) {
+    Element* ancestor_element = ancestor_node->AsElement();
+    if (!ancestor_element) {
+      continue;
+    }
+    HTMLElement* ancestor_html_element = ancestor_element->AsHTMLElement();
+    if (!ancestor_html_element) {
+      continue;
+    }
+    if (ancestor_html_element->layout_boxes())
+      ancestor_html_element->layout_boxes()->scroll_area_cache().reset();
+  }
+}
+
 }  // namespace
 
 void HTMLElement::RuleMatchingState::Clear() {
@@ -1167,6 +1183,7 @@
 }
 
 void HTMLElement::InvalidateLayoutBoxSizes() {
+  InvalidateScrollAreaCacheOfAncestors(parent_node());
   if (layout_boxes_) {
     layout_boxes_->InvalidateSizes();
 
@@ -1232,6 +1249,9 @@
 
 void HTMLElement::OnUiNavScroll(SbTimeMonotonic /* time */) {
   Document* document = node_document();
+  if (document->hidden()) {
+    return;
+  }
   scoped_refptr<Window> window(document ? document->window() : nullptr);
   DispatchEvent(new UIEvent(base::Tokens::scroll(), web::Event::kNotBubbles,
                             web::Event::kNotCancelable, window));
diff --git a/cobalt/dom/html_element_style_test.cc b/cobalt/dom/html_element_style_test.cc
new file mode 100644
index 0000000..b93efcf
--- /dev/null
+++ b/cobalt/dom/html_element_style_test.cc
@@ -0,0 +1,260 @@
+// 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 <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/strings/stringprintf.h"
+#include "cobalt/bindings/testing/utils.h"
+#include "cobalt/dom/html_element.h"
+#include "cobalt/dom/screen.h"
+#include "cobalt/dom/testing/test_with_javascript.h"
+#include "cobalt/network_bridge/net_poster.h"
+#include "cobalt/script/testing/fake_script_value.h"
+#include "cobalt/web/error_event.h"
+#include "cobalt/web/message_event.h"
+#include "cobalt/web/testing/mock_event_listener.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace dom {
+
+using script::testing::FakeScriptValue;
+
+namespace {
+std::string GetStyleName(::testing::TestParamInfo<const char*> info) {
+  return std::string(info.param);
+}
+
+std::string CamelCaseToPropertyName(const char* camel_case) {
+  std::string snake;
+  for (const char* input = camel_case; *input; ++input) {
+    if (*input >= 'A' && *input <= 'Z') {
+      snake.push_back('-');
+      snake.push_back(std::tolower(*input));
+    } else {
+      snake.push_back(*input);
+    }
+  }
+  return snake;
+}
+
+const char* longhand_styles[] = {"alignContent",
+                                 "alignItems",
+                                 "alignSelf",
+                                 "animationDelay",
+                                 "animationDirection",
+                                 "animationDuration",
+                                 "animationFillMode",
+                                 "animationIterationCount",
+                                 "animationName",
+                                 "animationTimingFunction",
+                                 "backgroundColor",
+                                 "backgroundImage",
+                                 "backgroundPosition",
+                                 "backgroundRepeat",
+                                 "backgroundSize",
+                                 "borderBottomColor",
+                                 "borderBottomLeftRadius",
+                                 "borderBottomRightRadius",
+                                 "borderBottomStyle",
+                                 "borderBottomWidth",
+                                 "borderLeftColor",
+                                 "borderLeftStyle",
+                                 "borderLeftWidth",
+                                 "borderRightColor",
+                                 "borderRightStyle",
+                                 "borderRightWidth",
+                                 "borderTopColor",
+                                 "borderTopLeftRadius",
+                                 "borderTopRightRadius",
+                                 "borderTopStyle",
+                                 "borderTopWidth",
+                                 "bottom",
+                                 "boxShadow",
+                                 "color",
+                                 "content",
+                                 "display",
+                                 "filter",
+                                 "flexBasis",
+                                 "flexDirection",
+                                 "flexGrow",
+                                 "flexShrink",
+                                 "flexWrap",
+                                 "fontFamily",
+                                 "fontSize",
+                                 "fontStyle",
+                                 "fontWeight",
+                                 "height",
+                                 "justifyContent",
+                                 "left",
+                                 "lineHeight",
+                                 "marginBottom",
+                                 "marginLeft",
+                                 "marginRight",
+                                 "marginTop",
+                                 "maxHeight",
+                                 "maxWidth",
+                                 "minHeight",
+                                 "minWidth",
+                                 "opacity",
+                                 "order",
+                                 "outlineColor",
+                                 "outlineStyle",
+                                 "outlineWidth",
+                                 "overflow",
+                                 "overflowWrap",
+                                 "paddingBottom",
+                                 "paddingLeft",
+                                 "paddingRight",
+                                 "paddingTop",
+                                 "pointerEvents",
+                                 "position",
+                                 "right",
+                                 "textAlign",
+                                 "textDecorationColor",
+                                 "textDecorationLine",
+                                 "textIndent",
+                                 "textOverflow",
+                                 "textShadow",
+                                 "textTransform",
+                                 "top",
+                                 "transform",
+                                 "transformOrigin",
+                                 "transitionDelay",
+                                 "transitionDuration",
+                                 "transitionProperty",
+                                 "transitionTimingFunction",
+                                 "verticalAlign",
+                                 "visibility",
+                                 "whiteSpace",
+                                 "width",
+                                 "zIndex"};
+
+class HTMLElementStyleTestLongHandStyles
+    : public testing::TestWithJavaScriptBase,
+      public ::testing::TestWithParam<const char*> {};
+}  // namespace
+
+TEST_P(HTMLElementStyleTestLongHandStyles, SetStyle) {
+  const char* style_attribute_name = GetParam();
+  std::string property_name = CamelCaseToPropertyName(style_attribute_name);
+  std::string script = base::StringPrintf(
+      R"(
+    var style_name = '%s';
+    var result = style_name;
+    var div = document.createElement('div');
+    div.style.%s = 'initial';
+    if (div.style.%s != 'initial') { result = 'Could not be set to \'initial\': \'' + div.style.%s + '\''; }
+    if (div.style.cssText != '%s: initial;') { result = 'cssText is not set to \'initial\': cssText \'' + div.style.cssText + '\''; }
+    div.style.%s = 'inherit';
+    if (div.style.%s != 'inherit') { result = 'Could not be set to \'inherit\': \'' + div.style.%s + '\''; }
+    if (div.style.cssText != '%s: inherit;') { result = 'cssText is not set to \'inherit\': cssText \'' + div.style.cssText + '\''; }
+    result
+  )",
+      style_attribute_name, style_attribute_name, style_attribute_name,
+      style_attribute_name, property_name.c_str(), style_attribute_name,
+      style_attribute_name, style_attribute_name, property_name.c_str());
+  std::string result;
+  EXPECT_TRUE(EvaluateScript(script, &result));
+  EXPECT_EQ(style_attribute_name, result) << script;
+}
+
+INSTANTIATE_TEST_CASE_P(HTMLElementStyleTest,
+                        HTMLElementStyleTestLongHandStyles,
+                        ::testing::ValuesIn(longhand_styles), GetStyleName);
+
+namespace {
+const char* shorthand_simple_styles[] = {
+    "animation",   "background", "border",         "borderBottom", "borderLeft",
+    "borderRight", "borderTop",  "flex",           "font",         "margin",
+    "outline",     "padding",    "textDecoration", "transition"};
+
+class HTMLElementStyleTestShortHandStyles
+    : public testing::TestWithJavaScriptBase,
+      public ::testing::TestWithParam<const char*> {};
+}  // namespace
+
+TEST_P(HTMLElementStyleTestShortHandStyles, SetStyle) {
+  const char* style_attribute_name = GetParam();
+  std::string property_name = CamelCaseToPropertyName(style_attribute_name);
+  std::string script = base::StringPrintf(
+      R"(
+    var style_name = '%s';
+    var result = style_name;
+    var div = document.createElement('div');
+    div.style.%s = 'initial';
+    if (!div.style.cssText.includes('initial')) { result = 'cssText does not contain \'initial\': cssText \'' + div.style.cssText + '\''; }
+    if (!div.style.cssText.includes('%s')) { result = 'cssText does not contain \'%s\': cssText \'' + div.style.cssText + '\''; }
+    div.style.%s = 'inherit';
+    if (!div.style.cssText.includes('inherit')) { result = 'cssText does not contain \'inherit\': cssText \'' + div.style.cssText + '\''; }
+    if (!div.style.cssText.includes('%s')) { result = 'cssText does not contain \'%s\': cssText \'' + div.style.cssText + '\''; }
+    result
+  )",
+      style_attribute_name, style_attribute_name, property_name.c_str(),
+      property_name.c_str(), style_attribute_name, property_name.c_str(),
+      property_name.c_str());
+  std::string result;
+  EXPECT_TRUE(EvaluateScript(script, &result));
+  EXPECT_EQ(style_attribute_name, result) << script;
+}
+
+INSTANTIATE_TEST_CASE_P(HTMLElementStyleTest,
+                        HTMLElementStyleTestShortHandStyles,
+                        ::testing::ValuesIn(shorthand_simple_styles),
+                        GetStyleName);
+
+namespace {
+const char* renamed_styles[] = {
+    "borderColor",   // Shorthand for border[Top|Right|Bottom|Left]Color.
+    "borderRadius",  // Shorthand for
+                     // border[TopLeft|TopRight|BottomRight|BottomLeft]Radius.
+    "borderStyle",   // Shorthand for border[Top|Right|Bottom|Left]Style.
+    "borderWidth",   // Shorthand for border[Top|Right|Bottom|Left]Width.
+    "flexFlow",      // Shorthand for flexDirection and flexWrap.
+    "wordWrap",      // Alias for overflowWrap.
+};
+
+class HTMLElementStyleTestRenamedStyles
+    : public testing::TestWithJavaScriptBase,
+      public ::testing::TestWithParam<const char*> {};
+}  // namespace
+
+TEST_P(HTMLElementStyleTestRenamedStyles, SetStyle) {
+  const char* style_attribute_name = GetParam();
+  std::string script = base::StringPrintf(
+      R"(
+    var style_name = '%s';
+    var result = style_name;
+    var div = document.createElement('div');
+    div.style.%s = 'initial';
+    if (!div.style.cssText.includes('initial')) { result = 'cssText does not contain \'initial\': cssText \'' + div.style.cssText + '\''; }
+    div.style.%s = 'inherit';
+    if (!div.style.cssText.includes('inherit')) { result = 'cssText does not contain \'inherit\': cssText \'' + div.style.cssText + '\''; }
+    result
+  )",
+      style_attribute_name, style_attribute_name, style_attribute_name);
+  std::string result;
+  EXPECT_TRUE(EvaluateScript(script, &result));
+  EXPECT_EQ(style_attribute_name, result) << script;
+}
+
+INSTANTIATE_TEST_CASE_P(HTMLElementStyleTest, HTMLElementStyleTestRenamedStyles,
+                        ::testing::ValuesIn(renamed_styles), GetStyleName);
+
+
+}  // namespace dom
+}  // namespace cobalt
diff --git a/cobalt/dom/html_media_element.cc b/cobalt/dom/html_media_element.cc
index c3376ea..dfbfa2a 100644
--- a/cobalt/dom/html_media_element.cc
+++ b/cobalt/dom/html_media_element.cc
@@ -138,7 +138,7 @@
       ready_state_(WebMediaPlayer::kReadyStateHaveNothing),
       ready_state_maximum_(WebMediaPlayer::kReadyStateHaveNothing),
       volume_(1.0f),
-      last_seek_time_(0),
+      last_seek_time_(0.0),
       previous_progress_time_(std::numeric_limits<double>::max()),
       duration_(std::numeric_limits<double>::quiet_NaN()),
       playing_(false),
@@ -149,7 +149,7 @@
       resume_frozen_flag_(false),
       seeking_(false),
       controls_(false),
-      last_time_update_event_movie_time_(std::numeric_limits<float>::max()),
+      last_time_update_event_movie_time_(std::numeric_limits<double>::max()),
       processing_media_player_callback_(0),
       media_source_url_(std::string(kMediaSourceUrlProtocol) + ':' +
                         base::GenerateGUID()),
@@ -222,7 +222,7 @@
   }
 
   player_->UpdateBufferedTimeRanges(
-      [&](float start, float end) { buffered->Add(start, end); });
+      [&](double start, double end) { buffered->Add(start, end); });
   return buffered;
 }
 
@@ -344,11 +344,11 @@
   return seeking_;
 }
 
-float HTMLMediaElement::current_time(
+double HTMLMediaElement::current_time(
     script::ExceptionState* exception_state) const {
   if (!player_) {
     LOG(INFO) << 0 << " (because player is NULL)";
-    return 0;
+    return 0.0;
   }
 
   if (seeking_) {
@@ -356,15 +356,14 @@
     return last_seek_time_;
   }
 
-  float time = player_->GetCurrentTime();
+  const double time = player_->GetCurrentTime();
   MLOG() << time << " (from player)";
   return time;
 }
 
 void HTMLMediaElement::set_current_time(
-    float time, script::ExceptionState* exception_state) {
+    double time, script::ExceptionState* exception_state) {
   // 4.8.9.9 Seeking
-
   // 1 - If the media element's readyState is
   // WebMediaPlayer::kReadyStateHaveNothing, then raise an INVALID_STATE_ERR
   // exception.
@@ -377,10 +376,9 @@
   Seek(time);
 }
 
-float HTMLMediaElement::duration() const {
+double HTMLMediaElement::duration() const {
   MLOG() << duration_;
-  // TODO: Turn duration into double.
-  return static_cast<float>(duration_);
+  return duration_;
 }
 
 base::Time HTMLMediaElement::GetStartDate() const {
@@ -433,7 +431,7 @@
 const scoped_refptr<TimeRanges>& HTMLMediaElement::played() {
   MLOG();
   if (playing_) {
-    float time = current_time(NULL);
+    const double time = current_time(NULL);
     if (time > last_seek_time_) {
       AddPlayedRange(last_seek_time_, time);
     }
@@ -447,8 +445,8 @@
 }
 
 scoped_refptr<TimeRanges> HTMLMediaElement::seekable() const {
-  if (player_ && player_->GetMaxTimeSeekable() != 0) {
-    double max_time_seekable = player_->GetMaxTimeSeekable();
+  if (player_ && player_->GetMaxTimeSeekable() != 0.0) {
+    const double max_time_seekable = player_->GetMaxTimeSeekable();
     MLOG() << "(0, " << max_time_seekable << ")";
     return new TimeRanges(0, max_time_seekable);
   }
@@ -632,7 +630,7 @@
   ScheduleOwnEvent(base::Tokens::durationchange());
 
   if (request_seek) {
-    Seek(static_cast<float>(duration));
+    Seek(duration);
   }
 }
 
@@ -775,7 +773,7 @@
 
   // 2 - Asynchronously await a stable state.
   played_time_ranges_ = new TimeRanges;
-  last_seek_time_ = 0;
+  last_seek_time_ = 0.0;
 
   ConfigureMediaControls();
 }
@@ -1019,8 +1017,8 @@
     return;
   }
 
-  double time = base::Time::Now().ToDoubleT();
-  double time_delta = time - previous_progress_time_;
+  const double time = base::Time::Now().ToDoubleT();
+  const double time_delta = time - previous_progress_time_;
 
   if (player_->DidLoadingProgress()) {
     ScheduleOwnEvent(base::Tokens::progress());
@@ -1082,7 +1080,7 @@
 void HTMLMediaElement::ScheduleTimeupdateEvent(bool periodic_event) {
   // Some media engines make multiple "time changed" callbacks at the same time,
   // but we only want one event at a given time so filter here
-  float movie_time = current_time(NULL);
+  const double movie_time = current_time(NULL);
   if (movie_time != last_time_update_event_movie_time_) {
     if (!periodic_event && playback_progress_timer_.IsRunning()) {
       playback_progress_timer_.Reset();
@@ -1268,7 +1266,7 @@
   network_state_ = kNetworkIdle;
 }
 
-void HTMLMediaElement::Seek(float time) {
+void HTMLMediaElement::Seek(double time) {
   LOG(INFO) << "Seek to " << time << ".";
   // 4.8.9.9 Seeking - continued from set_current_time().
 
@@ -1279,7 +1277,7 @@
 
   // Get the current time before setting seeking_, last_seek_time_ is returned
   // once it is set.
-  float now = current_time(NULL);
+  const double now = current_time(NULL);
 
   // 2 - If the element's seeking IDL attribute is true, then another instance
   // of this algorithm is already running. Abort that other instance of the
@@ -1297,7 +1295,7 @@
 
   // 6 - If the new playback position is less than the earliest possible
   // position, let it be that position instead.
-  time = std::max(time, 0.f);
+  time = std::max(time, 0.0);
 
   // 7 - If the (possibly now changed) new playback position is not in one of
   // the ranges given in the seekable attribute, then let it be the position in
@@ -1329,7 +1327,7 @@
     seeking_ = false;
     return;
   }
-  time = static_cast<float>(seekable_ranges->Nearest(time));
+  time = seekable_ranges->Nearest(time);
 
   if (playing_) {
     if (last_seek_time_ < now) {
@@ -1361,7 +1359,7 @@
   ScheduleOwnEvent(base::Tokens::seeked());
 }
 
-void HTMLMediaElement::AddPlayedRange(float start, float end) {
+void HTMLMediaElement::AddPlayedRange(double start, double end) {
   if (!played_time_ranges_) {
     played_time_ranges_ = new TimeRanges;
   }
@@ -1411,7 +1409,7 @@
 
     playback_progress_timer_.Stop();
     playing_ = false;
-    float time = current_time(NULL);
+    const double time = current_time(NULL);
     if (time > last_seek_time_) {
       AddPlayedRange(last_seek_time_, time);
     }
@@ -1431,7 +1429,7 @@
 }
 
 bool HTMLMediaElement::EndedPlayback() const {
-  float dur = duration();
+  const double dur = duration();
   if (!player_ || std::isnan(dur)) {
     return false;
   }
@@ -1448,15 +1446,15 @@
   // direction of playback is forwards, Either the media element does not have a
   // loop attribute specified, or the media element has a current media
   // controller.
-  float now = current_time(NULL);
-  if (playback_rate_ > 0) {
-    return dur > 0 && now >= dur && !loop();
+  const double now = current_time(NULL);
+  if (playback_rate_ > 0.f) {
+    return dur > 0.0 && now >= dur && !loop();
   }
 
   // or the current playback position is the earliest possible position and the
   // direction of playback is backwards
-  if (playback_rate_ < 0) {
-    return now <= 0;
+  if (playback_rate_ < 0.f) {
+    return now <= 0.0;
   }
 
   return false;
@@ -1557,14 +1555,14 @@
   // already posted one at the current movie time.
   ScheduleTimeupdateEvent(false);
 
-  float now = current_time(NULL);
-  float dur = duration();
+  const double now = current_time(NULL);
+  const double dur = duration();
 
   // When the current playback position reaches the end of the media resource
   // when the direction of playback is forwards, then the user agent must follow
   // these steps:
   eos_played |=
-      !std::isnan(dur) && (0.0f != dur) && now >= dur && playback_rate_ > 0;
+      !std::isnan(dur) && (0.0 != dur) && now >= dur && playback_rate_ > 0.f;
   if (eos_played) {
     LOG(INFO) << "End of stream is played.";
     // If the media element has a loop attribute specified and does not have a
@@ -1573,7 +1571,7 @@
       sent_end_event_ = false;
       // then seek to the earliest possible position of the media resource and
       // abort these steps.
-      Seek(0);
+      Seek(0.0);
     } else {
       // If the media element does not have a current media controller, and the
       // media element has still ended playback, and the direction of playback
@@ -1603,7 +1601,7 @@
 
   ScheduleOwnEvent(base::Tokens::durationchange());
 
-  double now = current_time(NULL);
+  const double now = current_time(NULL);
   // Reset and update |duration_|.
   duration_ = std::numeric_limits<double>::quiet_NaN();
   if (player_ && ready_state_ >= WebMediaPlayer::kReadyStateHaveMetadata) {
@@ -1611,7 +1609,7 @@
   }
 
   if (now > duration_) {
-    Seek(static_cast<float>(duration_));
+    Seek(duration_);
   }
 
   EndProcessingMediaPlayerCallback();
@@ -1755,6 +1753,10 @@
                              exception_state);
     return;
   }
+
+  LOG(INFO) << "max video capabilities is changed from \""
+            << max_video_capabilities_ << "\" to \"" << max_video_capabilities
+            << "\"";
   max_video_capabilities_ = max_video_capabilities;
 }
 
diff --git a/cobalt/dom/html_media_element.h b/cobalt/dom/html_media_element.h
index 612e21c..db05a0c 100644
--- a/cobalt/dom/html_media_element.h
+++ b/cobalt/dom/html_media_element.h
@@ -105,9 +105,9 @@
   bool seeking() const;
 
   // Playback state
-  float current_time(script::ExceptionState* exception_state) const;
-  void set_current_time(float time, script::ExceptionState* exception_state);
-  float duration() const;
+  double current_time(script::ExceptionState* exception_state) const;
+  void set_current_time(double time, script::ExceptionState* exception_state);
+  double duration() const;
   base::Time GetStartDate() const;
   bool paused() const;
   bool resume_frozen_flag() const;
@@ -210,10 +210,10 @@
   void ChangeNetworkStateFromLoadingToIdle();
 
   // Playback
-  void Seek(float time);
+  void Seek(double time);
   void FinishSeek();
 
-  void AddPlayedRange(float start, float end);
+  void AddPlayedRange(double start, double end);
 
   void UpdateVolume();
   void UpdatePlayState();
@@ -269,7 +269,7 @@
   WebMediaPlayer::ReadyState ready_state_maximum_;
 
   float volume_;
-  float last_seek_time_;
+  double last_seek_time_;
   double previous_progress_time_;
 
   double duration_;
diff --git a/cobalt/dom/layout_boxes.h b/cobalt/dom/layout_boxes.h
index 440e982..b81a446 100644
--- a/cobalt/dom/layout_boxes.h
+++ b/cobalt/dom/layout_boxes.h
@@ -15,6 +15,8 @@
 #ifndef COBALT_DOM_LAYOUT_BOXES_H_
 #define COBALT_DOM_LAYOUT_BOXES_H_
 
+#include <utility>
+
 #include "base/memory/ref_counted.h"
 #include "cobalt/dom/directionality.h"
 #include "cobalt/dom/dom_rect_list.h"
@@ -89,6 +91,9 @@
   // Invalidate the layout box's render tree nodes.
   virtual void InvalidateRenderTreeNodes() = 0;
 
+  virtual base::Optional<std::pair<dom::Directionality, math::RectF>>&
+  scroll_area_cache() = 0;
+
   // Update the navigation item associated with the layout boxes.
   virtual void SetUiNavItem(
       const scoped_refptr<ui_navigation::NavItem>& item) = 0;
diff --git a/cobalt/dom/on_screen_keyboard.cc b/cobalt/dom/on_screen_keyboard.cc
index ec41ff4..74fbe83 100644
--- a/cobalt/dom/on_screen_keyboard.cc
+++ b/cobalt/dom/on_screen_keyboard.cc
@@ -51,8 +51,7 @@
   }
 
   // Handle rgb color notation rgb(R, G, B)
-  if (!is_hex && len >= 10 &&
-      SbStringCompareNoCaseN("rgb(", color_str, 4) == 0) {
+  if (!is_hex && len >= 10 && strncasecmp("rgb(", color_str, 4) == 0) {
     int rgb_tmp[3] = {-1, -1, -1};
     const char* ptr = color_str + 4;
     int i = 0;
diff --git a/cobalt/dom/source_buffer.cc b/cobalt/dom/source_buffer.cc
index 039303f..1ad5a59 100644
--- a/cobalt/dom/source_buffer.cc
+++ b/cobalt/dom/source_buffer.cc
@@ -521,6 +521,39 @@
   tracer->Trace(video_tracks_);
 }
 
+size_t SourceBuffer::memory_limit(
+    script::ExceptionState* exception_state) const {
+  if (!chunk_demuxer_) {
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
+    return 0;
+  }
+
+  size_t memory_limit = chunk_demuxer_->GetSourceBufferStreamMemoryLimit(id_);
+
+  if (memory_limit == 0) {
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
+  }
+  return memory_limit;
+}
+
+void SourceBuffer::set_memory_limit(size_t memory_limit,
+                                    script::ExceptionState* exception_state) {
+  if (!chunk_demuxer_) {
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
+    return;
+  }
+
+  if (memory_limit == 0) {
+    web::DOMException::Raise(web::DOMException::kNotSupportedErr,
+                             exception_state);
+    return;
+  }
+  chunk_demuxer_->SetSourceBufferStreamMemoryLimit(id_, memory_limit);
+}
+
 void SourceBuffer::OnInitSegmentReceived(std::unique_ptr<MediaTracks> tracks) {
   if (!first_initialization_segment_received_) {
     media_source_->SetSourceBufferActive(this, true);
diff --git a/cobalt/dom/source_buffer.h b/cobalt/dom/source_buffer.h
index 5869760..0da8e81 100644
--- a/cobalt/dom/source_buffer.h
+++ b/cobalt/dom/source_buffer.h
@@ -144,6 +144,9 @@
   DEFINE_WRAPPABLE_TYPE(SourceBuffer);
   void TraceMembers(script::Tracer* tracer) override;
 
+  size_t memory_limit(script::ExceptionState* exception_state) const;
+  void set_memory_limit(size_t limit, script::ExceptionState* exception_state);
+
  private:
   typedef ::media::MediaTracks MediaTracks;
   typedef script::ArrayBuffer ArrayBuffer;
diff --git a/cobalt/dom/source_buffer.idl b/cobalt/dom/source_buffer.idl
index 0d81b03..a963802 100644
--- a/cobalt/dom/source_buffer.idl
+++ b/cobalt/dom/source_buffer.idl
@@ -39,4 +39,10 @@
   // `InvalidStateError` if the SourceBuffer object has been removed from the
   // MediaSource object.
   [RaisesException] readonly attribute double writeHead;
+
+  // Non standard stream memory limit modifier. This will override the default
+  // stream memory limit which is tied to the resolution of the video.
+  // This will be passed down to the SourceBufferStream associated with this
+  // instance.
+  [RaisesException] attribute unsigned long long memoryLimit;
 };
diff --git a/cobalt/dom/testing/mock_layout_boxes.h b/cobalt/dom/testing/mock_layout_boxes.h
index 7065808..3d00913 100644
--- a/cobalt/dom/testing/mock_layout_boxes.h
+++ b/cobalt/dom/testing/mock_layout_boxes.h
@@ -15,6 +15,8 @@
 #ifndef COBALT_DOM_TESTING_MOCK_LAYOUT_BOXES_H_
 #define COBALT_DOM_TESTING_MOCK_LAYOUT_BOXES_H_
 
+#include <utility>
+
 #include "cobalt/dom/layout_boxes.h"
 
 namespace cobalt {
@@ -57,6 +59,8 @@
   MOCK_METHOD0(InvalidateSizes, void());
   MOCK_METHOD0(InvalidateCrossReferences, void());
   MOCK_METHOD0(InvalidateRenderTreeNodes, void());
+  MOCK_METHOD0(scroll_area_cache,
+               base::Optional<std::pair<dom::Directionality, math::RectF>>&());
   MOCK_METHOD1(SetUiNavItem,
                void(const scoped_refptr<ui_navigation::NavItem>& item));
 };
diff --git a/cobalt/dom/testing/test_with_javascript.h b/cobalt/dom/testing/test_with_javascript.h
index e51747f..58f43f3 100644
--- a/cobalt/dom/testing/test_with_javascript.h
+++ b/cobalt/dom/testing/test_with_javascript.h
@@ -34,10 +34,10 @@
 namespace testing {
 
 // Helper class for running tests in a Window JavaScript context.
-class TestWithJavaScript : public ::testing::Test {
+class TestWithJavaScriptBase {
  public:
-  TestWithJavaScript() { stub_window_.reset(new StubWindow()); }
-  ~TestWithJavaScript() {
+  TestWithJavaScriptBase() { stub_window_.reset(new StubWindow()); }
+  virtual ~TestWithJavaScriptBase() {
     stub_window_.reset();
     EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
   }
@@ -94,7 +94,7 @@
     global_environment()->EnableEval();
     global_environment()->SetReportEvalCallback(base::Closure());
     return script::SourceCode::CreateSourceCode(
-        js_code, base::SourceLocation(__FILE__, __LINE__, 1));
+        js_code, base::SourceLocation(js_code.c_str(), 1, 1));
   }
 
   std::unique_ptr<StubWindow> stub_window_;
@@ -102,6 +102,9 @@
   base::EventDispatcher event_dispatcher_;
 };
 
+class TestWithJavaScript : public TestWithJavaScriptBase,
+                           public ::testing::Test {};
+
 }  // namespace testing
 }  // namespace dom
 }  // namespace cobalt
diff --git a/cobalt/dom/window.h b/cobalt/dom/window.h
index 5bbf76b..db20c17 100644
--- a/cobalt/dom/window.h
+++ b/cobalt/dom/window.h
@@ -283,6 +283,21 @@
   scoped_refptr<Storage> local_storage() const;
   scoped_refptr<Storage> session_storage() const;
 
+  // Web API: WindowEventHandlers (implements)
+  const EventListenerScriptValue* onhashchange() {
+    return GetAttributeEventListener(base::Tokens::hashchange());
+  }
+  void set_onhashchange(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::hashchange(), event_listener);
+  }
+
+  const EventListenerScriptValue* onunload() {
+    return GetAttributeEventListener(base::Tokens::unload());
+  }
+  void set_onunload(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::unload(), event_listener);
+  }
+
   // Access to the Performance API (partial interface)
   //   https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#sec-window.performance-attribute
   const scoped_refptr<Performance>& performance() const;
diff --git a/cobalt/dom/window_event_handlers.idl b/cobalt/dom/window_event_handlers.idl
index 1249f02..09154ce 100644
--- a/cobalt/dom/window_event_handlers.idl
+++ b/cobalt/dom/window_event_handlers.idl
@@ -16,7 +16,10 @@
 
 [NoInterfaceObject] interface WindowEventHandlers {
   attribute EventHandler onbeforeunload;
+  attribute EventHandler onhashchange;
   attribute EventHandler onlanguagechange;
+  attribute EventHandler onmessage;
+  attribute EventHandler onmessageerror;
   attribute EventHandler onoffline;
   attribute EventHandler ononline;
   attribute EventHandler onrejectionhandled;
diff --git a/cobalt/dom/window_test.cc b/cobalt/dom/window_test.cc
index a984cb9..9f544e8 100644
--- a/cobalt/dom/window_test.cc
+++ b/cobalt/dom/window_test.cc
@@ -18,12 +18,14 @@
 #include <string>
 
 #include "base/bind.h"
+#include "base/strings/stringprintf.h"
 #include "cobalt/bindings/testing/utils.h"
 #include "cobalt/dom/screen.h"
 #include "cobalt/dom/testing/test_with_javascript.h"
 #include "cobalt/network_bridge/net_poster.h"
 #include "cobalt/script/testing/fake_script_value.h"
 #include "cobalt/web/error_event.h"
+#include "cobalt/web/message_event.h"
 #include "cobalt/web/testing/mock_event_listener.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -32,17 +34,20 @@
 namespace dom {
 
 using script::testing::FakeScriptValue;
-using web::testing::MockEventListener;
 
-class WindowTest : public testing::TestWithJavaScript {
+class WindowTestBase {
  protected:
-  WindowTest() { fake_event_listener_ = MockEventListener::Create(); }
+  WindowTestBase() {
+    fake_event_listener_ = web::testing::MockEventListener::Create();
+  }
 
-  ~WindowTest() override {}
+  virtual ~WindowTestBase() {}
 
-  std::unique_ptr<MockEventListener> fake_event_listener_;
+  std::unique_ptr<web::testing::MockEventListener> fake_event_listener_;
 };
 
+class WindowTest : public WindowTestBase, public testing::TestWithJavaScript {};
+
 TEST_F(WindowTest, WindowShouldNotHaveChildren) {
   EXPECT_EQ(window(), window()->window());
   EXPECT_EQ(window(), window()->self());
@@ -244,6 +249,56 @@
   // pass in the screenshot function callback.
 }
 
+TEST_F(WindowTest, MessageEvent) {
+  window()->AddEventListener(
+      "message",
+      FakeScriptValue<web::EventListener>(fake_event_listener_.get()), true);
+  fake_event_listener_->ExpectHandleEventCall("message", window());
+  window()->DispatchEvent(new web::MessageEvent("message"));
+}
+
+TEST_F(WindowTest, OnMessageEvent) {
+  std::string result;
+  EXPECT_TRUE(EvaluateScript("typeof window.onmessage", &result));
+  EXPECT_EQ("object", result);
+
+  EXPECT_TRUE(EvaluateScript(R"(
+    logString = '(empty)';
+    window.onmessage= function() {
+      logString = 'handled';
+    };
+    window.dispatchEvent(new MessageEvent('message'));
+    logString;
+  )",
+                             &result));
+  EXPECT_EQ("handled", result);
+}
+
+TEST_F(WindowTest, MessageErrorEvent) {
+  window()->AddEventListener(
+      "messageerror",
+      FakeScriptValue<web::EventListener>(fake_event_listener_.get()), true);
+  fake_event_listener_->ExpectHandleEventCall("messageerror", window());
+  window()->DispatchEvent(new web::MessageEvent("messageerror"));
+}
+
+TEST_F(WindowTest, OnMessageErrorEvent) {
+  std::string result;
+  EXPECT_TRUE(EvaluateScript("typeof window.onmessageerror", &result));
+  EXPECT_EQ("object", result);
+
+  EXPECT_TRUE(EvaluateScript(R"(
+    logString = '(empty)';
+    window.onmessageerror = function() {
+      logString = 'handled';
+    };
+    window.dispatchEvent(new Event('messageerror'));
+    logString;
+  )",
+                             &result));
+  EXPECT_EQ("handled", result);
+}
+
 TEST_F(WindowTest, ErrorEvent) {
   window()->AddEventListener(
       "error", FakeScriptValue<web::EventListener>(fake_event_listener_.get()),
@@ -260,7 +315,7 @@
 
   EXPECT_TRUE(EvaluateScript(R"(
     logString = '(empty)';
-    self.onerror = function() {
+    window.onerror = function() {
       logString = 'handled';
     };
     window.dispatchEvent(new ErrorEvent('error'));
@@ -270,55 +325,97 @@
   EXPECT_EQ("handled", result);
 }
 
-TEST_F(WindowTest, BeforeunloadEvent) {
-  window()->AddEventListener(
-      "beforeunload",
-      FakeScriptValue<web::EventListener>(fake_event_listener_.get()), true);
-  fake_event_listener_->ExpectHandleEventCall("beforeunload", window());
-  window()->DispatchEvent(new web::Event("beforeunload"));
+namespace {
+class WindowTestEventTest : public WindowTestBase,
+                            public testing::TestWithJavaScriptBase,
+                            public ::testing::TestWithParam<const char*> {};
+
+std::string GetEventName(::testing::TestParamInfo<const char*> info) {
+  return std::string(info.param);
 }
 
-TEST_F(WindowTest, OnBeforeunloadEvent) {
+const char* events[] = {"beforeunload",
+                        "blur",
+                        "click",
+                        "focus",
+                        "hashchange",
+                        "keydown",
+                        "keypress",
+                        "keyup",
+                        "languagechange",
+                        "load",
+                        "loadeddata",
+                        "loadedmetadata",
+                        "loadstart",
+                        "mousedown",
+                        "mouseenter",
+                        "mouseleave",
+                        "mousemove",
+                        "mouseout",
+                        "mouseover",
+                        "mouseup",
+                        "offline",
+                        "online",
+                        "pause",
+                        "play",
+                        "playing",
+                        "resize",
+                        "scroll",
+                        "transitionend",
+                        "gotpointercapture",
+                        "lostpointercapture",
+                        "pointercancel",
+                        "pointerdown",
+                        "pointerenter",
+                        "pointerleave",
+                        "pointermove",
+                        "pointerout",
+                        "pointerover",
+                        "pointerup",
+                        "progress",
+                        "ratechange",
+                        "rejectionhandled",
+                        "seeked",
+                        "seeking",
+                        "timeupdate",
+                        "unhandledrejection",
+                        "volumechange",
+                        "waiting",
+                        "wheel"};
+}  // namespace
+
+TEST_P(WindowTestEventTest, AddEventListener) {
+  const char* event_name = GetParam();
+  window()->AddEventListener(
+      event_name,
+      FakeScriptValue<web::EventListener>(fake_event_listener_.get()), true);
+  fake_event_listener_->ExpectHandleEventCall(event_name, window());
+  window()->DispatchEvent(new web::Event(event_name));
+}
+
+TEST_P(WindowTestEventTest, OnEvent) {
+  const char* event_name = GetParam();
   std::string result;
-  EXPECT_TRUE(EvaluateScript("typeof self.onbeforeunload", &result));
+  EXPECT_TRUE(EvaluateScript(
+      base::StringPrintf("typeof window.on%s", event_name), &result));
   EXPECT_EQ("object", result);
 
-  EXPECT_TRUE(EvaluateScript(R"(
+  EXPECT_TRUE(
+      EvaluateScript(base::StringPrintf(R"(
     logString = '(empty)';
-    self.onbeforeunload = function() {
-      logString = 'handled';
+    self.on%s = function() {
+      logString = '%s';
     };
-    self.dispatchEvent(new Event('beforeunload'));
+    self.dispatchEvent(new Event('%s'));
     logString;
   )",
-                             &result));
-  EXPECT_EQ("handled", result);
+                                        event_name, event_name, event_name),
+                     &result));
+  EXPECT_EQ(event_name, result);
 }
 
-TEST_F(WindowTest, LanguagechangeEvent) {
-  window()->AddEventListener(
-      "languagechange",
-      FakeScriptValue<web::EventListener>(fake_event_listener_.get()), true);
-  fake_event_listener_->ExpectHandleEventCall("languagechange", window());
-  window()->DispatchEvent(new web::Event("languagechange"));
-}
-
-TEST_F(WindowTest, OnLanguagechangeEvent) {
-  std::string result;
-  EXPECT_TRUE(EvaluateScript("typeof self.onlanguagechange", &result));
-  EXPECT_EQ("object", result);
-
-  EXPECT_TRUE(EvaluateScript(R"(
-    logString = '(empty)';
-    self.onlanguagechange = function() {
-      logString = 'handled';
-    };
-    self.dispatchEvent(new Event('languagechange'));
-    logString;
-  )",
-                             &result));
-  EXPECT_EQ("handled", result);
-}
+INSTANTIATE_TEST_CASE_P(WindowTest, WindowTestEventTest,
+                        ::testing::ValuesIn(events), GetEventName);
 
 // Test that when Window's network status change callbacks are triggered,
 // corresponding online and offline events are fired to listeners.
@@ -330,31 +427,6 @@
   window()->OnWindowOnOfflineEvent();
 }
 
-TEST_F(WindowTest, OfflineEventDispatch) {
-  window()->AddEventListener(
-      "offline",
-      FakeScriptValue<web::EventListener>(fake_event_listener_.get()), true);
-  fake_event_listener_->ExpectHandleEventCall("offline", window());
-  window()->DispatchEvent(new web::Event("offline"));
-}
-
-TEST_F(WindowTest, OnOfflineEvent) {
-  std::string result;
-  EXPECT_TRUE(EvaluateScript("typeof self.onoffline", &result));
-  EXPECT_EQ("object", result);
-
-  EXPECT_TRUE(EvaluateScript(R"(
-    logString = '(empty)';
-    self.onoffline = function() {
-      logString = 'handled';
-    };
-    self.dispatchEvent(new Event('offline'));
-    logString;
-  )",
-                             &result));
-  EXPECT_EQ("handled", result);
-}
-
 TEST_F(WindowTest, OnlineEvent) {
   window()->AddEventListener(
       "online", FakeScriptValue<web::EventListener>(fake_event_listener_.get()),
@@ -363,81 +435,6 @@
   window()->OnWindowOnOnlineEvent();
 }
 
-TEST_F(WindowTest, OnlineEventDispatch) {
-  window()->AddEventListener(
-      "online", FakeScriptValue<web::EventListener>(fake_event_listener_.get()),
-      true);
-  fake_event_listener_->ExpectHandleEventCall("online", window());
-  window()->DispatchEvent(new web::Event("online"));
-}
-
-TEST_F(WindowTest, OnOnlineEvent) {
-  std::string result;
-  EXPECT_TRUE(EvaluateScript("typeof self.ononline", &result));
-  EXPECT_EQ("object", result);
-
-  EXPECT_TRUE(EvaluateScript(R"(
-    logString = '(empty)';
-    self.ononline = function() {
-      logString = 'handled';
-    };
-    self.dispatchEvent(new Event('online'));
-    logString;
-  )",
-                             &result));
-  EXPECT_EQ("handled", result);
-}
-
-TEST_F(WindowTest, RejectionhandledEvent) {
-  window()->AddEventListener(
-      "rejectionhandled",
-      FakeScriptValue<web::EventListener>(fake_event_listener_.get()), true);
-  fake_event_listener_->ExpectHandleEventCall("rejectionhandled", window());
-  window()->DispatchEvent(new web::Event("rejectionhandled"));
-}
-
-TEST_F(WindowTest, OnRejectionhandledEvent) {
-  std::string result;
-  EXPECT_TRUE(EvaluateScript("typeof self.onrejectionhandled", &result));
-  EXPECT_EQ("object", result);
-
-  EXPECT_TRUE(EvaluateScript(R"(
-    logString = '(empty)';
-    self.onrejectionhandled = function() {
-      logString = 'handled';
-    };
-    self.dispatchEvent(new Event('rejectionhandled'));
-    logString;
-  )",
-                             &result));
-  EXPECT_EQ("handled", result);
-}
-
-TEST_F(WindowTest, UnhandledrejectionEvent) {
-  window()->AddEventListener(
-      "unhandledrejection",
-      FakeScriptValue<web::EventListener>(fake_event_listener_.get()), true);
-  fake_event_listener_->ExpectHandleEventCall("unhandledrejection", window());
-  window()->DispatchEvent(new web::Event("unhandledrejection"));
-}
-
-TEST_F(WindowTest, OnUnhandledrejectionEvent) {
-  std::string result;
-  EXPECT_TRUE(EvaluateScript("typeof self.onunhandledrejection", &result));
-  EXPECT_EQ("object", result);
-
-  EXPECT_TRUE(EvaluateScript(R"(
-    logString = '(empty)';
-    self.onunhandledrejection = function() {
-      logString = 'handled';
-    };
-    self.dispatchEvent(new Event('unhandledrejection'));
-    logString;
-  )",
-                             &result));
-  EXPECT_EQ("handled", result);
-}
-
 TEST_F(WindowTest, UnloadEvent) {
   window()->AddEventListener(
       "unload", FakeScriptValue<web::EventListener>(fake_event_listener_.get()),
@@ -469,6 +466,5 @@
   EXPECT_EQ("handled", result);
 }
 
-
 }  // namespace dom
 }  // namespace cobalt
diff --git a/cobalt/h5vcc/BUILD.gn b/cobalt/h5vcc/BUILD.gn
index 1e42192..1f34607 100644
--- a/cobalt/h5vcc/BUILD.gn
+++ b/cobalt/h5vcc/BUILD.gn
@@ -28,12 +28,6 @@
   has_pedantic_warnings = true
 
   sources = [
-    "dial/dial_http_request.cc",
-    "dial/dial_http_request.h",
-    "dial/dial_http_response.cc",
-    "dial/dial_http_response.h",
-    "dial/dial_server.cc",
-    "dial/dial_server.h",
     "h5vcc.cc",
     "h5vcc.h",
     "h5vcc_accessibility.cc",
@@ -49,6 +43,8 @@
     "h5vcc_event_listener_container.h",
     "h5vcc_metrics.cc",
     "h5vcc_metrics.h",
+    "h5vcc_net_log.cc",
+    "h5vcc_net_log.h",
     "h5vcc_platform_service.cc",
     "h5vcc_platform_service.h",
     "h5vcc_runtime.cc",
@@ -89,10 +85,23 @@
     "//cobalt/web:dom_exception",
     "//cobalt/worker",
     "//net",
-    "//net:http_server",
-    "//starboard",
+    "//starboard:starboard_group",
     "//third_party/protobuf:protobuf_lite",
   ]
+  if (enable_in_app_dial) {
+    sources += [
+      "dial/dial_http_request.cc",
+      "dial/dial_http_request.h",
+      "dial/dial_http_response.cc",
+      "dial/dial_http_response.h",
+      "dial/dial_server.cc",
+      "dial/dial_server.h",
+    ]
+    deps += [
+      "//cobalt/network:cobalt_dial_server",
+      "//net:http_server",
+    ]
+  }
 
   if (enable_account_manager) {
     sources += [
diff --git a/cobalt/h5vcc/dial/dial_http_response.h b/cobalt/h5vcc/dial/dial_http_response.h
index 9adbc34..eb95999 100644
--- a/cobalt/h5vcc/dial/dial_http_response.h
+++ b/cobalt/h5vcc/dial/dial_http_response.h
@@ -19,8 +19,9 @@
 #include <string>
 #include <vector>
 
+#include "cobalt/network/dial/dial_service_handler.h"
 #include "cobalt/script/wrappable.h"
-#include "net/dial/dial_service_handler.h"
+#include "net/server/http_server_response_info.h"
 
 namespace cobalt {
 namespace h5vcc {
diff --git a/cobalt/h5vcc/dial/dial_server.cc b/cobalt/h5vcc/dial/dial_server.cc
index 9bb528d..52b60d1 100644
--- a/cobalt/h5vcc/dial/dial_server.cc
+++ b/cobalt/h5vcc/dial/dial_server.cc
@@ -23,10 +23,10 @@
 
 #include "base/strings/string_util.h"
 #include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/network/dial/dial_service_handler.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/environment_settings.h"
-#include "net/dial/dial_service_handler.h"
 #include "net/server/http_server_request_info.h"
 
 namespace cobalt {
@@ -48,7 +48,7 @@
 
 }  // namespace
 
-class DialServer::ServiceHandler : public net::DialServiceHandler {
+class DialServer::ServiceHandler : public cobalt::network::DialServiceHandler {
  public:
   ServiceHandler(const base::WeakPtr<DialServer>& dial_server,
                  const std::string& service_name);
diff --git a/cobalt/h5vcc/dial/dial_server.h b/cobalt/h5vcc/dial/dial_server.h
index 20e89ad..af86564 100644
--- a/cobalt/h5vcc/dial/dial_server.h
+++ b/cobalt/h5vcc/dial/dial_server.h
@@ -26,11 +26,11 @@
 #include "cobalt/h5vcc/dial/dial_http_request.h"
 #include "cobalt/h5vcc/dial/dial_http_response.h"
 #include "cobalt/h5vcc/script_callback_wrapper.h"
+#include "cobalt/network/dial/dial_service.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/wrappable.h"
-#include "net/dial/dial_service.h"
 
 namespace cobalt {
 namespace h5vcc {
@@ -81,7 +81,7 @@
   THREAD_CHECKER(thread_checker_);
   CallbackMap callback_map_[kMethodCount];
 
-  scoped_refptr<net::DialServiceProxy> dial_service_proxy_;
+  scoped_refptr<network::DialServiceProxy> dial_service_proxy_;
   scoped_refptr<ServiceHandler> service_handler_;
 
   DISALLOW_COPY_AND_ASSIGN(DialServer);
diff --git a/cobalt/h5vcc/h5vcc.cc b/cobalt/h5vcc/h5vcc.cc
index 885b912..af8da35 100644
--- a/cobalt/h5vcc/h5vcc.cc
+++ b/cobalt/h5vcc/h5vcc.cc
@@ -30,7 +30,8 @@
   audio_config_array_ = new H5vccAudioConfigArray();
   c_val_ = new dom::CValView();
   crash_log_ = new H5vccCrashLog();
-  metrics_ = new H5vccMetrics(settings.persistent_settings);
+  metrics_ =
+      new H5vccMetrics(settings.persistent_settings, settings.event_dispatcher);
   runtime_ = new H5vccRuntime(settings.event_dispatcher);
   settings_ =
       new H5vccSettings(settings.set_web_setting_func, settings.media_module,
@@ -43,6 +44,7 @@
   storage_ =
       new H5vccStorage(settings.network_module, settings.persistent_settings);
   trace_event_ = new H5vccTraceEvent();
+  net_log_ = new H5vccNetLog(settings.network_module);
 #if SB_IS(EVERGREEN)
   updater_ = new H5vccUpdater(settings.updater_module);
   system_ = new H5vccSystem(updater_);
@@ -79,6 +81,7 @@
   tracer->Trace(storage_);
   tracer->Trace(system_);
   tracer->Trace(trace_event_);
+  tracer->Trace(net_log_);
 #if SB_IS(EVERGREEN)
   tracer->Trace(updater_);
 #endif
diff --git a/cobalt/h5vcc/h5vcc.h b/cobalt/h5vcc/h5vcc.h
index c74551f..9ca5953 100644
--- a/cobalt/h5vcc/h5vcc.h
+++ b/cobalt/h5vcc/h5vcc.h
@@ -25,6 +25,7 @@
 #include "cobalt/h5vcc/h5vcc_audio_config_array.h"
 #include "cobalt/h5vcc/h5vcc_crash_log.h"
 #include "cobalt/h5vcc/h5vcc_metrics.h"
+#include "cobalt/h5vcc/h5vcc_net_log.h"
 #include "cobalt/h5vcc/h5vcc_runtime.h"
 #include "cobalt/h5vcc/h5vcc_settings.h"
 #include "cobalt/h5vcc/h5vcc_storage.h"
@@ -85,6 +86,7 @@
   const scoped_refptr<H5vccTraceEvent>& trace_event() const {
     return trace_event_;
   }
+  const scoped_refptr<H5vccNetLog>& net_log() const { return net_log_; }
 #if SB_IS(EVERGREEN)
   const scoped_refptr<H5vccUpdater>& updater() const { return updater_; }
 #endif
@@ -103,6 +105,7 @@
   scoped_refptr<H5vccStorage> storage_;
   scoped_refptr<H5vccSystem> system_;
   scoped_refptr<H5vccTraceEvent> trace_event_;
+  scoped_refptr<H5vccNetLog> net_log_;
 #if SB_IS(EVERGREEN)
   scoped_refptr<H5vccUpdater> updater_;
 #endif
diff --git a/cobalt/h5vcc/h5vcc.idl b/cobalt/h5vcc/h5vcc.idl
index e65bfa4..b70848a 100644
--- a/cobalt/h5vcc/h5vcc.idl
+++ b/cobalt/h5vcc/h5vcc.idl
@@ -41,6 +41,7 @@
   readonly attribute H5vccStorage storage;
   readonly attribute H5vccSystem system;
   readonly attribute H5vccTraceEvent traceEvent;
+  readonly attribute H5vccNetLog netLog;
   [Conditional=SB_IS_EVERGREEN]
       readonly attribute H5vccUpdater updater;
 };
diff --git a/cobalt/h5vcc/h5vcc_crash_log.cc b/cobalt/h5vcc/h5vcc_crash_log.cc
index b3e76e7..4f775fe 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.cc
+++ b/cobalt/h5vcc/h5vcc_crash_log.cc
@@ -110,7 +110,8 @@
     SbSystemBreakIntoDebugger();
   }
   if (intent == kH5vccCrashTypeOutOfMemory) {
-    SbMemoryAllocateAligned(128, SIZE_MAX);
+    void* p = nullptr;
+    posix_memalign(&p, 128, SIZE_MAX);
   }
 }
 
@@ -191,6 +192,19 @@
   return "";
 }
 
+script::Sequence<std::string> H5vccCrashLog::GetWatchdogViolationClients() {
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+  script::Sequence<std::string> client_names;
+  if (watchdog) {
+    std::vector<std::string> client_string_names =
+        watchdog->GetWatchdogViolationClientNames();
+    for (std::size_t i = 0; i < client_string_names.size(); ++i) {
+      client_names.push_back(client_string_names[i]);
+    }
+  }
+  return client_names;
+}
+
 bool H5vccCrashLog::GetPersistentSettingWatchdogEnable() {
   watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
   if (watchdog) return watchdog->GetPersistentSettingWatchdogEnable();
diff --git a/cobalt/h5vcc/h5vcc_crash_log.h b/cobalt/h5vcc/h5vcc_crash_log.h
index ea06c88..e01d23e 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.h
+++ b/cobalt/h5vcc/h5vcc_crash_log.h
@@ -81,6 +81,8 @@
   std::string GetWatchdogViolations(
       const script::Sequence<std::string>& clients = {});
 
+  script::Sequence<std::string> GetWatchdogViolationClients();
+
   bool GetPersistentSettingWatchdogEnable();
 
   void SetPersistentSettingWatchdogEnable(bool enable_watchdog);
diff --git a/cobalt/h5vcc/h5vcc_crash_log.idl b/cobalt/h5vcc/h5vcc_crash_log.idl
index 8d4a3ba..e60e0ab 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.idl
+++ b/cobalt/h5vcc/h5vcc_crash_log.idl
@@ -86,6 +86,9 @@
   // }
   DOMString getWatchdogViolations(optional sequence<DOMString> clients);
 
+  // Returns a sequence of the client names that have watchdog violations.
+  sequence<DOMString> getWatchdogViolationClients();
+
   // Gets a persistent Watchdog setting that determines whether or not Watchdog
   // is enabled. When disabled, Watchdog behaves like a stub except that
   // persistent settings can still be get/set. Requires a restart to take
diff --git a/cobalt/h5vcc/h5vcc_metrics.cc b/cobalt/h5vcc/h5vcc_metrics.cc
index 0c9062a..583eafd 100644
--- a/cobalt/h5vcc/h5vcc_metrics.cc
+++ b/cobalt/h5vcc/h5vcc_metrics.cc
@@ -17,6 +17,9 @@
 #include <string>
 
 #include "base/values.h"
+#include "cobalt/base/event.h"
+#include "cobalt/base/event_dispatcher.h"
+#include "cobalt/base/on_metric_upload_event.h"
 #include "cobalt/browser/metrics/cobalt_metrics_service_client.h"
 #include "cobalt/browser/metrics/cobalt_metrics_services_manager.h"
 #include "cobalt/h5vcc/h5vcc_metric_type.h"
@@ -25,17 +28,34 @@
 namespace cobalt {
 namespace h5vcc {
 
+
+H5vccMetrics::H5vccMetrics(
+    persistent_storage::PersistentSettings* persistent_settings,
+    base::EventDispatcher* event_dispatcher)
+    : task_runner_(base::ThreadTaskRunnerHandle::Get()),
+      persistent_settings_(persistent_settings),
+      event_dispatcher_(event_dispatcher) {
+  DCHECK(event_dispatcher_);
+  on_metric_upload_event_callback_ =
+      base::Bind(&H5vccMetrics::OnMetricUploadEvent, base::Unretained(this));
+  event_dispatcher_->AddEventCallback(base::OnMetricUploadEvent::TypeId(),
+                                      on_metric_upload_event_callback_);
+}
+
+H5vccMetrics::~H5vccMetrics() {
+  event_dispatcher_->RemoveEventCallback(base::OnMetricUploadEvent::TypeId(),
+                                         on_metric_upload_event_callback_);
+}
+
+void H5vccMetrics::OnMetricUploadEvent(const base::Event* event) {
+  std::unique_ptr<base::OnMetricUploadEvent> on_metric_upload_event(
+      new base::OnMetricUploadEvent(event));
+  RunEventHandler(on_metric_upload_event->metric_type(),
+                  on_metric_upload_event->serialized_proto());
+}
+
 void H5vccMetrics::OnMetricEvent(
     const h5vcc::MetricEventHandlerWrapper::ScriptValue& event_handler) {
-  if (!uploader_callback_) {
-    run_event_handler_callback_ = std::make_unique<
-        cobalt::browser::metrics::CobaltMetricsUploaderCallback>(
-        base::BindRepeating(&H5vccMetrics::RunEventHandler,
-                            base::Unretained(this)));
-    browser::metrics::CobaltMetricsServicesManager::GetInstance()
-        ->SetOnUploadHandler(run_event_handler_callback_.get());
-  }
-
   uploader_callback_ =
       new h5vcc::MetricEventHandlerWrapper(this, event_handler);
 }
@@ -43,16 +63,20 @@
 void H5vccMetrics::RunEventHandler(
     const cobalt::h5vcc::H5vccMetricType& metric_type,
     const std::string& serialized_proto) {
-  task_runner_->PostTask(
-      FROM_HERE,
-      base::Bind(&H5vccMetrics::RunEventHandlerInternal, base::Unretained(this),
-                 metric_type, serialized_proto));
+  if (task_runner_ && task_runner_->HasAtLeastOneRef()) {
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::Bind(&H5vccMetrics::RunEventHandlerInternal,
+                   base::Unretained(this), metric_type, serialized_proto));
+  }
 }
 
 void H5vccMetrics::RunEventHandlerInternal(
     const cobalt::h5vcc::H5vccMetricType& metric_type,
     const std::string& serialized_proto) {
-  uploader_callback_->callback.value().Run(metric_type, serialized_proto);
+  if (uploader_callback_ != nullptr && uploader_callback_->HasAtLeastOneRef()) {
+    uploader_callback_->callback.value().Run(metric_type, serialized_proto);
+  }
 }
 
 void H5vccMetrics::Enable() { ToggleMetricsEnabled(true); }
diff --git a/cobalt/h5vcc/h5vcc_metrics.h b/cobalt/h5vcc/h5vcc_metrics.h
index a8f69ea..93a444a 100644
--- a/cobalt/h5vcc/h5vcc_metrics.h
+++ b/cobalt/h5vcc/h5vcc_metrics.h
@@ -20,7 +20,8 @@
 
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "cobalt/browser/metrics/cobalt_metrics_uploader_callback.h"
+#include "cobalt/base/event.h"
+#include "cobalt/base/event_dispatcher.h"
 #include "cobalt/h5vcc/h5vcc_metric_type.h"
 #include "cobalt/h5vcc/metric_event_handler_wrapper.h"
 #include "cobalt/persistent_storage/persistent_settings.h"
@@ -43,14 +44,15 @@
   typedef MetricEventHandler H5vccMetricEventHandler;
 
   explicit H5vccMetrics(
-      persistent_storage::PersistentSettings* persistent_settings)
-      : task_runner_(base::ThreadTaskRunnerHandle::Get()),
-        persistent_settings_(persistent_settings) {}
+      persistent_storage::PersistentSettings* persistent_settings,
+      base::EventDispatcher* event_dispatcher);
+
+  ~H5vccMetrics();
 
   H5vccMetrics(const H5vccMetrics&) = delete;
   H5vccMetrics& operator=(const H5vccMetrics&) = delete;
 
-  // Binds an event handler that will be invoked every time Cobalt wants to
+  // Binds a JS event handler that will be invoked every time Cobalt wants to
   // upload a metrics payload.
   void OnMetricEvent(
       const MetricEventHandlerWrapper::ScriptValue& event_handler);
@@ -81,14 +83,20 @@
       const cobalt::h5vcc::H5vccMetricType& metric_type,
       const std::string& serialized_proto);
 
-  scoped_refptr<h5vcc::MetricEventHandlerWrapper> uploader_callback_;
+  // Handler method triggered when EventDispatcher sends OnMetricUploadEvents.
+  void OnMetricUploadEvent(const base::Event* event);
 
-  std::unique_ptr<cobalt::browser::metrics::CobaltMetricsUploaderCallback>
-      run_event_handler_callback_;
+  scoped_refptr<h5vcc::MetricEventHandlerWrapper> uploader_callback_;
 
   scoped_refptr<base::SingleThreadTaskRunner> const task_runner_;
 
   persistent_storage::PersistentSettings* persistent_settings_;
+
+  // Non-owned reference used to receive application event callbacks, namely
+  // metric log upload events.
+  base::EventDispatcher* event_dispatcher_;
+
+  base::EventCallback on_metric_upload_event_callback_;
 };
 
 }  // namespace h5vcc
diff --git a/cobalt/h5vcc/h5vcc_net_log.cc b/cobalt/h5vcc/h5vcc_net_log.cc
new file mode 100644
index 0000000..8a5b3a2
--- /dev/null
+++ b/cobalt/h5vcc/h5vcc_net_log.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/h5vcc/h5vcc_net_log.h"
+
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "cobalt/base/cobalt_paths.h"
+#include "cobalt/network/cobalt_net_log.h"
+
+namespace cobalt {
+namespace h5vcc {
+
+H5vccNetLog::H5vccNetLog(cobalt::network::NetworkModule* network_module)
+    : network_module_{network_module} {}
+
+void H5vccNetLog::Start() { network_module_->StartNetLog(); }
+
+void H5vccNetLog::Stop() { network_module_->StopNetLog(); }
+
+std::string H5vccNetLog::StopAndRead() {
+  base::FilePath netlog_path = network_module_->StopNetLog();
+  std::string netlog_output{};
+  if (!netlog_path.empty()) {
+    ReadFileToString(netlog_path, &netlog_output);
+  }
+  return netlog_output;
+}
+
+
+}  // namespace h5vcc
+}  // namespace cobalt
diff --git a/cobalt/h5vcc/h5vcc_net_log.h b/cobalt/h5vcc/h5vcc_net_log.h
new file mode 100644
index 0000000..801839b
--- /dev/null
+++ b/cobalt/h5vcc/h5vcc_net_log.h
@@ -0,0 +1,45 @@
+// 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_H5VCC_H5VCC_NET_LOG_H_
+#define COBALT_H5VCC_H5VCC_NET_LOG_H_
+
+#include <string>
+
+#include "cobalt/network/network_module.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace h5vcc {
+
+class H5vccNetLog : public script::Wrappable {
+ public:
+  explicit H5vccNetLog(cobalt::network::NetworkModule* network_module);
+
+  void Start();
+  void Stop();
+  std::string StopAndRead();
+
+  DEFINE_WRAPPABLE_TYPE(H5vccNetLog);
+
+ private:
+  cobalt::network::NetworkModule* network_module_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(H5vccNetLog);
+};
+
+}  // namespace h5vcc
+}  // namespace cobalt
+
+#endif  // COBALT_H5VCC_H5VCC_NET_LOG_H_
diff --git a/cobalt/h5vcc/h5vcc_net_log.idl b/cobalt/h5vcc/h5vcc_net_log.idl
new file mode 100644
index 0000000..2c60792
--- /dev/null
+++ b/cobalt/h5vcc/h5vcc_net_log.idl
@@ -0,0 +1,19 @@
+// 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.
+
+interface H5vccNetLog {
+  void start();
+  void stop();
+  DOMString stopAndRead();
+};
diff --git a/cobalt/h5vcc/h5vcc_platform_service.cc b/cobalt/h5vcc/h5vcc_platform_service.cc
index aaa07e6..dc3671f 100644
--- a/cobalt/h5vcc/h5vcc_platform_service.cc
+++ b/cobalt/h5vcc/h5vcc_platform_service.cc
@@ -124,13 +124,13 @@
     web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              "Service unable to accept data currently.",
                              exception_state);
-    SbMemoryDeallocate(output_data);
+    free(output_data);
     return script::ArrayBuffer::New(environment_, 0);
   }
   auto output_buffer =
       script::ArrayBuffer::New(environment_, output_data, output_length);
   // Deallocate |output_data| which has been copied into the ArrayBuffer.
-  SbMemoryDeallocate(output_data);
+  free(output_data);
   return output_buffer;
 }
 
diff --git a/cobalt/h5vcc/h5vcc_runtime.cc b/cobalt/h5vcc/h5vcc_runtime.cc
index 15f682b..1ec7fcc 100644
--- a/cobalt/h5vcc/h5vcc_runtime.cc
+++ b/cobalt/h5vcc/h5vcc_runtime.cc
@@ -24,7 +24,8 @@
 namespace h5vcc {
 H5vccRuntime::H5vccRuntime(base::EventDispatcher* event_dispatcher)
     : event_dispatcher_(event_dispatcher),
-      message_loop_(base::MessageLoop::current()) {
+      task_runner_(base::ThreadTaskRunnerHandle::Get()) {
+  DCHECK(task_runner_);
   on_deep_link_ = new H5vccDeepLinkEventTarget(
       base::Bind(&H5vccRuntime::GetUnconsumedDeepLink, base::Unretained(this)));
   on_pause_ = new H5vccRuntimeEventTarget;
@@ -87,8 +88,9 @@
 void H5vccRuntime::OnEventForDeepLink(const base::Event* event) {
   std::unique_ptr<base::DeepLinkEvent> deep_link_event(
       new base::DeepLinkEvent(event));
-  if (base::MessageLoop::current() != message_loop_) {
-    message_loop_->task_runner()->PostTask(
+  if (!task_runner_) return;
+  if (!task_runner_->RunsTasksInCurrentSequence()) {
+    task_runner_->PostTask(
         FROM_HERE,
         base::Bind(&H5vccRuntime::OnDeepLinkEvent, base::Unretained(this),
                    base::Passed(std::move(deep_link_event))));
diff --git a/cobalt/h5vcc/h5vcc_runtime.h b/cobalt/h5vcc/h5vcc_runtime.h
index 560543f..6c0ebc1 100644
--- a/cobalt/h5vcc/h5vcc_runtime.h
+++ b/cobalt/h5vcc/h5vcc_runtime.h
@@ -19,6 +19,7 @@
 #include <string>
 
 #include "base/callback.h"
+#include "base/single_thread_task_runner.h"
 #include "base/threading/thread_checker.h"
 #include "cobalt/base/deep_link_event.h"
 #include "cobalt/base/event_dispatcher.h"
@@ -63,9 +64,9 @@
   base::EventCallback deep_link_event_callback_;
   base::OnceClosure consumed_callback_;
 
-  // Track the message loop that created this object so deep link events are
-  // handled from the same thread.
-  base::MessageLoop* message_loop_;
+  // Track the task runner from where this object is created so deep link events
+  // are handled from the same task runner.
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
   // Thread checker ensures all calls to DOM element are made from the same
   // thread that it is created in.
diff --git a/cobalt/h5vcc/h5vcc_settings.cc b/cobalt/h5vcc/h5vcc_settings.cc
index e6c7b26..59d76c8 100644
--- a/cobalt/h5vcc/h5vcc_settings.cc
+++ b/cobalt/h5vcc/h5vcc_settings.cc
@@ -23,16 +23,6 @@
 namespace cobalt {
 namespace h5vcc {
 
-namespace {
-// Only including needed video combinations for the moment.
-// option 0 disables all video codecs except h264
-// 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"}};
-};  // namespace
-
 H5vccSettings::H5vccSettings(
     const SetSettingFunc& set_web_setting_func,
     cobalt::media::MediaModule* media_module,
@@ -56,62 +46,47 @@
       persistent_settings_(persistent_settings) {
 }
 
-bool H5vccSettings::Set(const std::string& name, int32 value) const {
+bool H5vccSettings::Set(const std::string& name, SetValueType value) const {
   const char kMediaPrefix[] = "Media.";
-  const char kDisableMediaCodec[] = "DisableMediaCodec";
+  const char kMediaCodecBlockList[] = "MediaCodecBlockList";
   const char kNavigatorUAData[] = "NavigatorUAData";
-  const char kClientHintHeaders[] = "ClientHintHeaders";
   const char kQUIC[] = "QUIC";
 
 #if SB_IS(EVERGREEN)
   const char kUpdaterMinFreeSpaceBytes[] = "Updater.MinFreeSpaceBytes";
 #endif
 
-  if (name == kDisableMediaCodec &&
-      value < static_cast<int32>(kDisableCodecCombinations.size())) {
-    can_play_type_handler_->SetDisabledMediaCodecs(
-        kDisableCodecCombinations[value]);
+  if (name == kMediaCodecBlockList && value.IsType<std::string>() &&
+      value.AsType<std::string>().size() < 256) {
+    can_play_type_handler_->SetDisabledMediaCodecs(value.AsType<std::string>());
     return true;
   }
 
-  if (set_web_setting_func_ && set_web_setting_func_.Run(name, value)) {
+  if (set_web_setting_func_ && value.IsType<int32>() &&
+      set_web_setting_func_.Run(name, value.AsType<int32>())) {
     return true;
   }
 
-  if (name.rfind(kMediaPrefix, 0) == 0) {
-    return media_module_ ? media_module_->SetConfiguration(
-                               name.substr(strlen(kMediaPrefix)), value)
-                         : false;
+  if (name.rfind(kMediaPrefix, 0) == 0 && value.IsType<int32>()) {
+    return media_module_
+               ? media_module_->SetConfiguration(
+                     name.substr(strlen(kMediaPrefix)), value.AsType<int32>())
+               : false;
   }
 
-  if (name.compare(kNavigatorUAData) == 0 && value == 1) {
+  if (name.compare(kNavigatorUAData) == 0 && value.IsType<int32>() &&
+      value.AsType<int32>() == 1) {
     global_environment_->BindTo("userAgentData", user_agent_data_, "navigator");
     return true;
   }
 
-  if (name.compare(kClientHintHeaders) == 0) {
-    if (!persistent_settings_) {
-      return false;
-    } else {
-      persistent_settings_->SetPersistentSetting(
-          network::kClientHintHeadersEnabledPersistentSettingsKey,
-          std::make_unique<base::Value>(value));
-      // Tell NetworkModule (if exists) to re-query persistent settings.
-      if (network_module_) {
-        network_module_
-            ->SetEnableClientHintHeadersFlagsFromPersistentSettings();
-      }
-      return true;
-    }
-  }
-
-  if (name.compare(kQUIC) == 0) {
+  if (name.compare(kQUIC) == 0 && value.IsType<int32>()) {
     if (!persistent_settings_) {
       return false;
     } else {
       persistent_settings_->SetPersistentSetting(
           network::kQuicEnabledPersistentSettingsKey,
-          std::make_unique<base::Value>(value != 0));
+          std::make_unique<base::Value>(value.AsType<int32>() != 0));
       // Tell NetworkModule (if exists) to re-query persistent settings.
       if (network_module_) {
         network_module_->SetEnableQuicFromPersistentSettings();
@@ -121,13 +96,30 @@
   }
 
 #if SB_IS(EVERGREEN)