Import Cobalt 24.lts.1.1032413
diff --git a/.github/actions/on_host_test/action.yaml b/.github/actions/on_host_test/action.yaml
index 7df5e2f..a10a8bf 100644
--- a/.github/actions/on_host_test/action.yaml
+++ b/.github/actions/on_host_test/action.yaml
@@ -60,16 +60,11 @@
       run: |
         echo "PYTHONPATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV
         echo "TEST_RESULTS_DIR=${GITHUB_WORKSPACE}/unit-test-results" >> $GITHUB_ENV
-        echo "COVERAGE_DIR=${GITHUB_WORKSPACE}/coverage" >> $GITHUB_ENV
         echo "TEST_REPORT_FILE=${GITHUB_WORKSPACE}/${{matrix.platform}}-${{matrix.shard}}" >> $GITHUB_ENV
     - name: Run Tests
       shell: bash
       run: |
         set -x
-        # Starboard toolchains are downloaded to a different dir on github. Create a symlink to reassure our tooling that everything is fine.
-        if [ -d /root/starboard-toolchains ]; then
-          ln -s /root/starboard-toolchains /github/home/starboard-toolchains
-        fi
         loader_args=''
         if [ "${COBALT_BOOTLOADER}" != "null" ]; then
           loader_args="--loader_platform ${COBALT_BOOTLOADER} --loader_config ${{matrix.config}}"
@@ -82,10 +77,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=${TEST_RESULTS_DIR} --coverage_dir=${COVERAGE_DIR} --coverage_report
         else
           if [[ "${{inputs.os}}" == 'windows' ]]; then
             python3 ${GITHUB_WORKSPACE}/starboard/tools/testing/test_runner.py --platform ${{matrix.target_platform}} --config ${{matrix.config}} -s ${{matrix.shard}} -r
@@ -106,14 +97,3 @@
       with:
         name: unit-test-reports
         path: ${{env.TEST_REPORT_FILE}}
-    - name: Upload coverage html report
-      if: success() && matrix.shard == 'coverage'
-      uses: actions/upload-artifact@v3
-      with:
-        name: coverage-report
-        path: ${{env.COVERAGE_DIR}}/html
-    - name: Upload to Codecov
-      if: success() && matrix.shard == 'coverage'
-      uses: codecov/codecov-action@v3
-      with:
-        files: ${{env.COVERAGE_DIR}}/report.txt
diff --git a/.github/config/evergreen-x64.json b/.github/config/evergreen-x64.json
index cd33d24..b95a318 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"],
   "platforms": [
     "evergreen-x64",
     "evergreen-x64-sbversion-15",
diff --git a/.github/config/linux-coverage.json b/.github/config/linux-coverage.json
deleted file mode 100644
index f4c9006..0000000
--- a/.github/config/linux-coverage.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "docker_service": "build-linux",
-  "on_host_test": true,
-  "on_host_test_shards": ["coverage"],
-  "platforms": [
-    "linux-coverage"
-  ],
-  "includes": [
-    {
-      "name":"linux",
-      "platform":"linux-coverage",
-      "target_platform":"linux-x64x11",
-      "extra_gn_arguments":"use_clang_coverage=true"
-    }
-  ]
-}
diff --git a/.github/workflows/android.yaml b/.github/workflows/android_24.lts.1+.yaml
similarity index 86%
rename from .github/workflows/android.yaml
rename to .github/workflows/android_24.lts.1+.yaml
index 4622201..cec9f87 100644
--- a/.github/workflows/android.yaml
+++ b/.github/workflows/android_24.lts.1+.yaml
@@ -1,15 +1,13 @@
-name: android
+name: android_24.lts.1+
 
 on:
   pull_request:
-    types: [opened, reopened, synchronize, labeled]
+    types: [ready_for_review, opened, reopened, synchronize, labeled]
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
   push:
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
   schedule:
     # GMT timezone.
     - cron: '0 4 * * *'
diff --git a/.github/workflows/evergreen.yaml b/.github/workflows/evergreen_24.lts.1+.yaml
similarity index 91%
rename from .github/workflows/evergreen.yaml
rename to .github/workflows/evergreen_24.lts.1+.yaml
index 22e56e4..8ee3cc3 100644
--- a/.github/workflows/evergreen.yaml
+++ b/.github/workflows/evergreen_24.lts.1+.yaml
@@ -1,15 +1,13 @@
-name: evergreen
+name: evergreen_24.lts.1+
 
 on:
   pull_request:
-    types: [opened, reopened, synchronize, labeled]
+    types: [ready_for_review, opened, reopened, synchronize, labeled]
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
   push:
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
   schedule:
     # GMT timezone.
     - cron: '0 5 * * *'
diff --git a/.github/workflows/gradle.yaml b/.github/workflows/gradle_24.lts.1+.yaml
similarity index 94%
rename from .github/workflows/gradle.yaml
rename to .github/workflows/gradle_24.lts.1+.yaml
index 88ce9fd..0f384eb 100644
--- a/.github/workflows/gradle.yaml
+++ b/.github/workflows/gradle_24.lts.1+.yaml
@@ -1,11 +1,10 @@
-name: Java Tests
+name: Java Tests 24.lts.1+
 
 on:
   pull_request:
   push:
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
 
 concurrency:
   group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.pull_request.number || github.sha }}'
diff --git a/.github/workflows/label-cherry-pick.yaml b/.github/workflows/label-cherry-pick.yaml
index 6ac390a..ec19642 100644
--- a/.github/workflows/label-cherry-pick.yaml
+++ b/.github/workflows/label-cherry-pick.yaml
@@ -26,7 +26,7 @@
           labels=$LABEL_NAME
         fi
 
-        branches=("24.lts.1+" "23.lts.1+" "22.lts.1+" "21.lts.1+" "20.lts.1+" "19.lts.1+" "rc_11" "COBALT_9")
+        branches=("23.lts.1+" "22.lts.1+" "21.lts.1+" "20.lts.1+" "19.lts.1+" "19.android.1+" "rc_11" "COBALT_9")
         filtered_branches=()
         for branch in "${branches[@]}"; do
           if [[ $branch == $BASE_REF ]]; then
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
deleted file mode 100644
index 6a138cb..0000000
--- a/.github/workflows/lint.yaml
+++ /dev/null
@@ -1,70 +0,0 @@
-name: lint
-
-on:
-  pull_request:
-    types:
-      - opened
-      - edited
-      - reopened
-      - synchronize
-  push:
-    branches:
-      - main
-      - feature/*
-
-concurrency:
-  group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.pull_request.number || github.sha }}'
-  cancel-in-progress: true
-
-permissions: {}
-
-jobs:
-  lint:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Install clang-format Dependencies
-        run: |
-          sudo apt-get update
-          sudo apt-get install libncurses5
-      - name: Download GN via CIPD
-        env:
-          GN_SHA256SUM: 'af7b2dcb3905bca56655e12131b365f1cba8e159db80d2022330c4f522fab2ef  /tmp/gn.zip'
-          GN_HASH: r3styzkFvKVmVeEhMbNl8cuo4VnbgNICIzDE9SL6su8C
-        run: |
-          set -e -x
-          curl --location --silent --output /tmp/gn.zip "https://chrome-infra-packages.appspot.com/dl/gn/gn/linux-amd64/+/${GN_HASH}"
-          echo ${GN_SHA256SUM} | sha256sum --check
-          unzip /tmp/gn.zip -d /usr/local/bin
-          rm /tmp/gn.zip
-      - name: Checkout
-        uses: kaidokert/checkout@v3.5.999
-        with:
-          fetch-depth: 0
-          persist-credentials: false
-      - name: Setup Python
-        uses: actions/setup-python@v4
-        with:
-          python-version: '^3.7.x'
-      - name: Install Pip Packages
-        run: pip install -r ${GITHUB_WORKSPACE}/requirements.txt
-      - name: Download Resources
-        run: python ${GITHUB_WORKSPACE}/download_resources.py
-      - name: pre-commit
-        uses: ./.github/actions/pre_commit
-        with:
-          base_ref: ${{ github.event.pull_request.base.sha && github.event.pull_request.base.sha || github.event.before }}
-  check-bug-id:
-    name: Check Bug ID
-    runs-on: ubuntu-latest
-    steps:
-      - name: Check Bug ID Present
-        # v2
-        uses: gsactions/commit-message-checker@16fa2d5de096ae0d35626443bcd24f1e756cafee
-        with:
-          excludeTitle: true
-          excludeDescription: true
-          checkAllCommitMessages: true
-          accessToken: ${{ secrets.GITHUB_TOKEN }}
-          pattern: '^b\/\d+$'
-          flags: 'gm'
-          error: 'Commit message should include at least one bug ID on a separate line (e.g. b/12345).'
diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux_24.lts.1+.yaml
similarity index 65%
rename from .github/workflows/linux.yaml
rename to .github/workflows/linux_24.lts.1+.yaml
index e15041d..c5a684a 100644
--- a/.github/workflows/linux.yaml
+++ b/.github/workflows/linux_24.lts.1+.yaml
@@ -1,15 +1,13 @@
-name: linux
+name: linux_24.lts.1+
 
 on:
   pull_request:
-    types: [opened, reopened, synchronize, labeled]
+    types: [ready_for_review, opened, reopened, synchronize, labeled]
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
   push:
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
   schedule:
     # GMT timezone.
     - cron: '0 4 * * *'
@@ -56,17 +54,3 @@
       platform: linux-modular
       nightly: ${{ github.event.inputs.nightly }}
       modular: true
-  linux-coverage:
-    # Run on main branch for pushes, PRs and manual invocations.
-    if: |
-     ${{ github.ref == 'refs/heads/main' &&
-         (github.event_name == 'push' ||
-         github.event_name == 'pull_request' ||
-         (github.event_name == 'workflow_dispatch' && inputs.nightly == 'false')) }}
-    uses: ./.github/workflows/main.yaml
-    permissions:
-      packages: write
-      pull-requests: write
-    with:
-      platform: linux-coverage
-      nightly: ${{ github.event.inputs.nightly }}
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index b501c1d..b34f41e 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -61,12 +61,15 @@
       GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
       GITHUB_PR_REPO_URL: ${{ github.event.pull_request.base.repo.url }}
       GITHUB_EVENT_NUMBER: ${{ github.event.number }}
+    # All triggers except draft PRs, unless PR is labeled with runtest
     if: |
-      github.event.action != 'labeled' ||
+      github.event_name != 'pull_request' ||
       (
-        github.event.action == 'labeled' &&
-        github.event.label.name == 'runtest' ||
-        github.event.label.name == 'on_device'
+        github.event.pull_request.draft == false ||
+        (
+          github.event.action == 'labeled' &&
+          github.event.label.name == 'runtest'
+        )
       )
     steps:
       - id: checkout
@@ -252,7 +255,7 @@
           type: ondevice
           os: linux
 
-  # Runs on-device integration and unit tests.
+  # Runs on-host integration and unit tests.
   on-device-test:
     needs: [initialize, build]
     # Run ODT when on_device label is applied on PR.
diff --git a/.github/workflows/main_win.yaml b/.github/workflows/main_win.yaml
index 3285f1a..3fed7b8 100644
--- a/.github/workflows/main_win.yaml
+++ b/.github/workflows/main_win.yaml
@@ -54,10 +54,13 @@
       GITHUB_EVENT_NUMBER: ${{ github.event.number }}
     # All triggers except draft PRs, unless PR is labeled with runtest
     if: |
-      github.event.action != 'labeled' ||
+      github.event_name != 'pull_request' ||
       (
-        github.event.action == 'labeled' &&
-        github.event.label.name == 'runtest'
+        github.event.pull_request.draft == false ||
+        (
+          github.event.action == 'labeled' &&
+          github.event.label.name == 'runtest'
+        )
       )
     steps:
       - id: Checkout
diff --git a/.github/workflows/manual-cherry-pick.yaml b/.github/workflows/manual-cherry-pick.yaml
deleted file mode 100644
index d4bca26..0000000
--- a/.github/workflows/manual-cherry-pick.yaml
+++ /dev/null
@@ -1,72 +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.
-
-# Usage: Go to
-# https://https://github.com/youtube/cobalt/actions/workflows/manual-cherry-pick.yaml
-# and click "Run Workflow." Leave "Use Workflow From" set to "main", then
-# input the branch name and paste the cherry-pick commit and click Run. A PR
-# will be created.
-
-name: Release Branch Cherrypick
-on:
-  workflow_dispatch:
-    inputs:
-      # We use this instead of the "run on branch" argument because GitHub looks
-      # on that branch for a workflow.yml file, and we'd have to cherry-pick
-      # this file into those branches.
-      release_branch:
-        description: 'Release branch name (e.g. 23.lts.1+)'
-        required: true
-        type: string
-      git_commit:
-        description: 'Git commit to cherry-pick'
-        required: true
-        type: string
-
-jobs:
-  cherrypick:
-    name: Cherrypick to ${{ github.event.inputs.release_branch}} - ${{ github.event.inputs.git_commit }}
-    runs-on: ubuntu-latest
-    env:
-      ACCESS_TOKEN: ${{ secrets.CHERRY_PICK_TOKEN }}
-      RELEASE_BRANCH: ${{ github.event.inputs.release_branch }}
-      COMMIT_HASH: ${{ github.event.inputs.git_commit }}
-      REPOSITORY: ${{ github.repository }}
-      GITHUB_REF: ${{ github.ref }}
-    steps:
-    - name: Checkout code
-      uses: kaidokert/checkout@v3.5.999
-      with:
-        ref: ${{ env.RELEASE_BRANCH }}
-        persist-credentials: false
-    - name: Get some helpful info for formatting
-      id: cherrypick
-      run: |
-          git config --global user.name "GitHub Release Automation"
-          git config --global user.email "github@google.com"
-          git fetch origin $GITHUB_REF
-          git cherry-pick -x $COMMIT_HASH
-          echo "SHORTSHA=$(git log -1 $COMMIT_HASH --format="%h")" >> "$GITHUB_OUTPUT"
-          echo "TITLE=$(git log -1 $COMMIT_HASH --format="%s")" >> "$GITHUB_OUTPUT"
-    - name: Create Pull Request with changes
-      uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04 # v4.2.3
-      with:
-        title: '${{ env.RELEASE_BRANCH }} cherry-pick: ${{ steps.cherrypick.outputs.SHORTSHA }} "${{ steps.cherrypick.outputs.TITLE }}"'
-        committer: GitHub Release Automation <github@google.com>
-        token: ${{ secrets.CHERRY_PICK_TOKEN }}
-        base: ${{ env.RELEASE_BRANCH }}
-        branch: ${{ env.RELEASE_BRANCH }}-${{ steps.cherrypick.outputs.SHORTSHA }}
-        reviewers: ${{ github.actor }}
-        body: |
-          Refer to the original commit: https://github.com/${{ github.repository }}/commit/${{ github.event.inputs.git_commit }}
diff --git a/.github/workflows/nightly_trigger.yaml b/.github/workflows/nightly_trigger.yaml
deleted file mode 100644
index 2cb01b6..0000000
--- a/.github/workflows/nightly_trigger.yaml
+++ /dev/null
@@ -1,141 +0,0 @@
-name: nightly_trigger
-
-on:
-  schedule:
-    # GMT timezone.
-    - cron: '30 4 * * *'
-  workflow_dispatch:
-
-jobs:
-  trigger_23:
-    permissions:
-      actions: write
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: kaidokert/checkout@v3.5.999
-        with:
-          fetch-depth: 1
-          ref: 23.lts.1+
-          persist-credentials: false
-      - name: Trigger Nightly
-        run: |
-          set -x
-          gh workflow run android_23.lts.1+ --ref 23.lts.1+ -f nightly=true
-          gh workflow run evergreen_23.lts.1+ --ref 23.lts.1+ -f nightly=true
-          gh workflow run linux_23.lts.1+ --ref 23.lts.1+ -f nightly=true
-          gh workflow run raspi-2_23.lts.1+ --ref 23.lts.1+ -f nightly=true
-          gh workflow run win32_23.lts.1+ --ref 23.lts.1+ -f nightly=true
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-  trigger_22:
-    permissions:
-      actions: write
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: kaidokert/checkout@v3.5.999
-        with:
-          fetch-depth: 1
-          ref: 22.lts.1+
-          persist-credentials: false
-      - name: Trigger Nightly
-        run: |
-          set -x
-          gh workflow run android_22.lts.1+ --ref 22.lts.1+ -f nightly=true
-          gh workflow run evergreen_22.lts.1+ --ref 22.lts.1+ -f nightly=true
-          gh workflow run linux_22.lts.1+ --ref 22.lts.1+ -f nightly=true
-          gh workflow run raspi-2_22.lts.1+ --ref 22.lts.1+ -f nightly=true
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-  trigger_21:
-    permissions:
-      actions: write
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: kaidokert/checkout@v3.5.999
-        with:
-          fetch-depth: 1
-          ref: 21.lts.1+
-          persist-credentials: false
-      - name: Trigger Nightly
-        run: |
-          set -x
-          gh workflow run evergreen_21.lts.1+ --ref 21.lts.1+ -f nightly=true
-          gh workflow run linux_21.lts.1+ --ref 21.lts.1+ -f nightly=true
-          gh workflow run raspi-2_21.lts.1+ --ref 21.lts.1+ -f nightly=true
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-  trigger_20:
-    permissions:
-      actions: write
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: kaidokert/checkout@v3.5.999
-        with:
-          fetch-depth: 1
-          ref: 20.lts.1+
-          persist-credentials: false
-      - name: Trigger Nightly
-        run: |
-          set -x
-          gh workflow run linux_20.lts.1+ --ref 20.lts.1+ -f nightly=true
-          gh workflow run raspi-2_20.lts.1+ --ref 20.lts.1+ -f nightly=true
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-  trigger_19:
-    permissions:
-      actions: write
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: kaidokert/checkout@v3.5.999
-        with:
-          fetch-depth: 1
-          ref: 19.lts.1+
-          persist-credentials: false
-      - name: Trigger Nightly
-        run: |
-          set -x
-          gh workflow run linux_19.lts.1+ --ref 19.lts.1+ -f nightly=true
-          gh workflow run raspi-2_19.lts.1+ --ref 19.lts.1+ -f nightly=true
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-  trigger_rc_11:
-    permissions:
-      actions: write
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: kaidokert/checkout@v3.5.999
-        with:
-          fetch-depth: 1
-          ref: rc_11
-          persist-credentials: false
-      - name: Trigger Nightly
-        run: |
-          set -x
-          gh workflow run linux_rc_11 --ref rc_11 -f nightly=true
-          gh workflow run raspi-2_rc_11 --ref rc_11 -f nightly=true
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-  trigger_cobalt_9:
-    permissions:
-      actions: write
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: kaidokert/checkout@v3.5.999
-        with:
-          fetch-depth: 1
-          ref: COBALT_9
-          persist-credentials: false
-      - name: Trigger Nightly
-        run: |
-          set -x
-          gh workflow run linux_COBALT_9 --ref COBALT_9 -f nightly=true
-          gh workflow run raspi-2_COBALT_9 --ref COBALT_9 -f nightly=true
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/nightly_trigger_24.lts.1+.yaml b/.github/workflows/nightly_trigger_24.lts.1+.yaml
deleted file mode 100644
index f72b625..0000000
--- a/.github/workflows/nightly_trigger_24.lts.1+.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
-name: nightly_trigger_24.lts.1+
-
-on:
-  schedule:
-    # GMT timezone.
-    - cron: '30 5 * * *'
-  workflow_dispatch:
-
-jobs:
-  trigger_24:
-    permissions:
-      actions: write
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: kaidokert/checkout@v3.5.999
-        with:
-          fetch-depth: 1
-          ref: 24.lts.1+
-          persist-credentials: false
-      - name: Trigger Nightly
-        run: |
-          set -x
-          gh workflow run android_24.lts.1+ --ref 24.lts.1+ -f nightly=true
-          gh workflow run evergreen_24.lts.1+ --ref 24.lts.1+ -f nightly=true
-          gh workflow run linux_24.lts.1+ --ref 24.lts.1+ -f nightly=true
-          gh workflow run raspi-2_24.lts.1+ --ref 24.lts.1+ -f nightly=true
-          gh workflow run win32_24.lts.1+ --ref 24.lts.1+ -f nightly=true
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/pr_badges.yaml b/.github/workflows/pr_badges.yaml
deleted file mode 100644
index 3fcd93e..0000000
--- a/.github/workflows/pr_badges.yaml
+++ /dev/null
@@ -1,69 +0,0 @@
-name: PR badges
-
-on:
-  pull_request_target:
-    branches:
-      - 'feature/*'
-      - 'main'
-      - '24.lts.1\+'
-      - '23.lts.1\+'
-      - '22.lts.1\+'
-      - '21.lts.1\+'
-      - '20.lts.1\+'
-      - '19.lts.1\+'
-      - 'rc_11'
-      - 'COBALT_9'
-
-concurrency:
-  group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.pull_request.number || github.sha }}'
-  cancel-in-progress: true
-
-permissions:
-  pull-requests: write
-
-jobs:
-  comment:
-    runs-on: ubuntu-latest
-    env:
-      GITHUB_SERVER_URL: ${{github.server_url}}
-      GITHUB_REPO: ${{github.repository}}
-      GITHUB_HEAD_REF: ${{ github.head_ref }}
-    steps:
-      - uses: actions/github-script@v6
-        with:
-          script: |
-            // Get env vars.
-            const { GITHUB_SERVER_URL, GITHUB_REPO, GITHUB_HEAD_REF } = process.env
-            // Get the existing comments.
-            const {data: comments} = await github.rest.issues.listComments({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              issue_number: context.payload.number,
-            })
-
-            // Find any comment already made by the bot.
-            const botComment = comments.find(comment => {
-              return comment.user.type === 'Bot' && comment.body.includes('Build Status')
-            })
-            const workflows = ["lint", "android", "evergreen", "linux", "raspi-2", "stub", "win32"]
-            var commentBody = `
-            ## Build Status
-            | Workflow  | Status                                                                                                                                                                                                                                               |
-            | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-            `
-            for (let i = 0; i < workflows.length; i++) {
-              commentBody += "| " + workflows[i] + "      | [![" + workflows[i] + "](" + `${GITHUB_SERVER_URL}` + "/" + `${GITHUB_REPO}` + "/actions/workflows/" + workflows[i] + ".yaml/badge.svg?branch=" + `${GITHUB_HEAD_REF}` + ")](" + `${GITHUB_SERVER_URL}` + "/" + `${GITHUB_REPO}` + "/actions/workflows/" + workflows[i] + ".yaml?query=branch%3A" + `${GITHUB_HEAD_REF}` + ")                |\n"
-            }
-            if (botComment) {
-              await github.rest.issues.deleteComment({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                comment_id: botComment.id,
-              })
-            }
-            await github.rest.issues.createComment({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              issue_number: context.payload.number,
-              body: commentBody
-            })
diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest_24.lts.1+.yaml
similarity index 94%
rename from .github/workflows/pytest.yaml
rename to .github/workflows/pytest_24.lts.1+.yaml
index 787f056..d95a66f 100644
--- a/.github/workflows/pytest.yaml
+++ b/.github/workflows/pytest_24.lts.1+.yaml
@@ -1,11 +1,10 @@
-name: python-tests
+name: python-tests_24.lts.1+
 
 on:
   pull_request:
   push:
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
 
 concurrency:
   group: '${{ github.workflow }}-${{ github.event_name }}-${{ inputs.platform }} @ ${{ github.event.pull_request.number || github.sha }}'
diff --git a/.github/workflows/raspi-2.yaml b/.github/workflows/raspi-2_24.lts.1+.yaml
similarity index 83%
rename from .github/workflows/raspi-2.yaml
rename to .github/workflows/raspi-2_24.lts.1+.yaml
index ac42ac8..9eb1ec0 100644
--- a/.github/workflows/raspi-2.yaml
+++ b/.github/workflows/raspi-2_24.lts.1+.yaml
@@ -1,15 +1,13 @@
-name: raspi-2
+name: raspi-2_24.lts.1+
 
 on:
   pull_request:
-    types: [opened, reopened, synchronize, labeled]
+    types: [ready_for_review, opened, reopened, synchronize, labeled]
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
   push:
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
   schedule:
     # GMT timezone.
     - cron: '0 4 * * *'
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
deleted file mode 100644
index 4514e18..0000000
--- a/.github/workflows/scorecards.yml
+++ /dev/null
@@ -1,58 +0,0 @@
-# This workflow uses actions that are not certified by GitHub. They are provided
-# by a third-party and are governed by separate terms of service, privacy
-# policy, and support documentation.
-
-name: Scorecards supply-chain security
-on:
-  # For Branch-Protection check. Only the default branch is supported. See
-  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
-  branch_protection_rule:
-  # To guarantee Maintained check is occasionally updated. See
-  # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
-  schedule:
-    - cron: '18 14 * * 1'
-  push:
-    branches: [ main ]
-  pull_request:
-    branches: [ main ]
-
-# Declare default permissions as read only.
-permissions: read-all
-
-jobs:
-  analysis:
-    name: Scorecards analysis
-    runs-on: ubuntu-latest
-    permissions:
-      # Needed to upload the results to code-scanning dashboard.
-      security-events: write
-      # Needed to publish results and get a badge (see publish_results below).
-      id-token: write
-
-    steps:
-      - name: "Checkout code"
-        uses: actions/checkout@v3
-        with:
-          persist-credentials: false
-
-      - name: "Run analysis"
-        uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
-        with:
-          results_file: results.sarif
-          results_format: sarif
-          publish_results: true
-
-      # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
-      # format to the repository Actions tab.
-      - name: "Upload artifact"
-        uses: actions/upload-artifact@v3.1.1
-        with:
-          name: SARIF file
-          path: results.sarif
-          retention-days: 5
-
-      # Upload the results to GitHub's code scanning dashboard.
-      - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@v2.1.37
-        with:
-          sarif_file: results.sarif
diff --git a/.github/workflows/stub.yaml b/.github/workflows/stub_24.lts.1+.yaml
similarity index 78%
rename from .github/workflows/stub.yaml
rename to .github/workflows/stub_24.lts.1+.yaml
index 8c40a4e..eaf553e 100644
--- a/.github/workflows/stub.yaml
+++ b/.github/workflows/stub_24.lts.1+.yaml
@@ -1,15 +1,13 @@
-name: stub
+name: stub_24.lts.1+
 
 on:
   pull_request:
-    types: [opened, reopened, synchronize, labeled]
+    types: [ready_for_review, opened, reopened, synchronize, labeled]
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
   push:
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
   workflow_dispatch:
     inputs:
       nightly:
diff --git a/.github/workflows/win32.yaml b/.github/workflows/win32_24.lts.1+.yaml
similarity index 77%
rename from .github/workflows/win32.yaml
rename to .github/workflows/win32_24.lts.1+.yaml
index b522992..bebad50 100644
--- a/.github/workflows/win32.yaml
+++ b/.github/workflows/win32_24.lts.1+.yaml
@@ -1,15 +1,13 @@
-name: win32
+name: win32_24.lts.1+
 
 on:
   pull_request:
-    types: [opened, reopened, synchronize, labeled]
+    types: [ready_for_review, opened, reopened, synchronize, labeled]
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
   push:
     branches:
-      - main
-      - feature/*
+      - '24.lts.1\+'
   schedule:
     # GTM timezone.
     - cron: '0 4 * * *'
diff --git a/.github/workflows/workflow_trigger.yaml b/.github/workflows/workflow_trigger.yaml
deleted file mode 100644
index c4122d8..0000000
--- a/.github/workflows/workflow_trigger.yaml
+++ /dev/null
@@ -1,49 +0,0 @@
-name: workflow_trigger
-
-on:
-  workflow_dispatch:
-    inputs:
-      branch:
-        type: choice
-        description: Branch
-        options:
-          - '24.lts.1+'
-          - '23.lts.1+'
-          - '22.lts.1+'
-          - '21.lts.1+'
-          - '20.lts.1+'
-          - '19.lts.1+'
-          - 'rc_11'
-          - 'COBALT_9'
-      workflow:
-        type: choice
-        description: Workflow name
-        options:
-          - 'android'
-          - 'evergreen'
-          - 'linux'
-          - 'raspi'
-          - 'win32'
-      nightly:
-        description: 'Nightly workflow.'
-        required: true
-        type: boolean
-        default: false
-
-jobs:
-  trigger:
-    permissions:
-      actions: write
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: kaidokert/checkout@v3.5.999
-        with:
-          fetch-depth: 1
-          ref: ${{ github.event.branch }}
-      - name: Trigger Workflow
-        run: |
-          set -x
-          gh workflow run ${{ github.event.inputs.workflow }}_${{ github.event.inputs.branch }} --ref ${{ github.event.inputs.branch }} -f nightly=${{ github.event.inputs.nightly }}
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/BUILD_STATUS.md b/BUILD_STATUS.md
index 28ea016..b570a71 100644
--- a/BUILD_STATUS.md
+++ b/BUILD_STATUS.md
@@ -1,22 +1,22 @@
 # Build Status
 
-| Workflow  | Main | 24.lts.1+ | 23.lts.1+ | 22.lts.1+ | 21.lts.1+ | 20.lts.1+ | 19.lts.1+ | RC11 | COBALT 9 |
-| --------- | ---- | --------- | --------- | --------- | --------- | --------- | --------- | ---- | ---------|
-| Lint      | [![lint](https://github.com/youtube/cobalt/actions/workflows/lint.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/lint.yaml?query=event%3Apush+branch%3Amain) | | | | | | | | |
-| Android   | [![android](https://github.com/youtube/cobalt/actions/workflows/android.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/android.yaml?query=event%3Apush+branch%3Amain) | [![android_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/android_24.lts.1+.yaml?query=event%3Apush+branch%3A24.lts.1%2B) | [![android_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![android_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | | | | | |
-| Evergreen | [![evergreen](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml?query=event%3Apush+branch%3Amain) | [![evergreen_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_24.lts.1+.yaml?query=event%3Apush+branch%3A24.lts.1%2B) | [![evergreen_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![evergreen_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | [![evergreen_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml?query=branch%3A21.lts.1%2B+event%3Apush) | | | | |
-| Linux     | [![linux](https://github.com/youtube/cobalt/actions/workflows/linux.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux.yaml?query=event%3Apush+branch%3Amain) | [![linux_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_24.lts.1+.yaml?query=event%3Apush+branch%3A24.lts.1%2B) | [![linux_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![linux_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | [![linux_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml?query=branch%3A21.lts.1%2B+event%3Apush) | [![linux_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml?query=branch%3A20.lts.1%2B+event%3Apush) | [![linux_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml?query=branch%3A19.lts.1%2B+event%3Apush) | [![linux_rc_11](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml/badge.svg?branch=rc_11&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml?query=event%3Apush+branch%3Arc_11) | [![linux_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml?query=event%3Apush+branch%3ACOBALT_9) |
-| Raspi-2   | [![raspi-2](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml?query=event%3Apush+branch%3Amain) | [![raspi-2_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_24.lts.1+.yaml?query=event%3Apush+branch%3A24.lts.1%2B) | [![raspi-2_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![raspi-2_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | [![raspi-2_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml?query=branch%3A21.lts.1%2B+event%3Apush) | [![raspi-2_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml?query=branch%3A20.lts.1%2B+event%3Apush) | [![raspi-2_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml?query=branch%3A19.lts.1%2B+event%3Apush) | [![raspi-2_rc_11](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml/badge.svg?branch=rc_11&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml?query=event%3Apush+branch%3Arc_11) | [![raspi-2_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml?query=event%3Apush+branch%3ACOBALT_9) |
-| Stub      | [![stub](https://github.com/youtube/cobalt/actions/workflows/stub.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/stub.yaml?query=event%3Apush+branch%3Amain) | [![stub_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/stub_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/stub_24.lts.1+.yaml?query=event%3Apush+branch%3A24.lts.1%2B) | [![stub_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/stub_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/stub_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | | | | | | |
-| Win32     | [![win32](https://github.com/youtube/cobalt/actions/workflows/win32.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/win32.yaml?query=event%3Apush+branch%3Amain) | [![win32_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/win32_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/win32_24.lts.1+.yaml?query=event%3Apush+branch%3A24.lts.1%2B) | [![win32_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | | | | | | |
-| Python    | [![python](https://github.com/youtube/cobalt/actions/workflows/pytest.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/pytest.yaml?query=event%3Apush+branch%3Amain) | | | | | | | |
-| Java      | [![java](https://github.com/youtube/cobalt/actions/workflows/gradle.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/gradle.yaml?query=event%3Apush+branch%3Amain) | | | | | | | |
+| Workflow  | Main | 23.lts.1+ | 22.lts.1+ | 21.lts.1+ | 20.lts.1+ | 19.lts.1+ | RC11 | COBALT 9 |
+| --------- | ---- | --------- | --------- | --------- | --------- | --------- | ---- | ---------|
+| Lint      | [![lint](https://github.com/youtube/cobalt/actions/workflows/lint.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/lint.yaml?query=event%3Apush+branch%3Amain) | | | | | | | |
+| Android   | [![android](https://github.com/youtube/cobalt/actions/workflows/android.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/android.yaml?query=event%3Apush+branch%3Amain) | [![android_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![android_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | | | | | |
+| Evergreen | [![evergreen](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml?query=event%3Apush+branch%3Amain) | [![evergreen_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![evergreen_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | [![evergreen_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml?query=branch%3A21.lts.1%2B+event%3Apush) | | | | |
+| Linux     | [![linux](https://github.com/youtube/cobalt/actions/workflows/linux.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux.yaml?query=event%3Apush+branch%3Amain) | [![linux_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![linux_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | [![linux_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml?query=branch%3A21.lts.1%2B+event%3Apush) | [![linux_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml?query=branch%3A20.lts.1%2B+event%3Apush) | [![linux_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml?query=branch%3A19.lts.1%2B+event%3Apush) | [![linux_rc_11](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml/badge.svg?branch=rc_11&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml?query=event%3Apush+branch%3Arc_11) | [![linux_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml?query=event%3Apush+branch%3ACOBALT_9) |
+| Raspi-2   | [![raspi-2](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml?query=event%3Apush+branch%3Amain) | [![raspi-2_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | [![raspi-2_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Apush) | [![raspi-2_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml?query=branch%3A21.lts.1%2B+event%3Apush) | [![raspi-2_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml?query=branch%3A20.lts.1%2B+event%3Apush) | [![raspi-2_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml?query=branch%3A19.lts.1%2B+event%3Apush) | [![raspi-2_rc_11](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml/badge.svg?branch=rc_11&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml?query=event%3Apush+branch%3Arc_11) | [![raspi-2_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml?query=event%3Apush+branch%3ACOBALT_9) |
+| Stub      | [![stub](https://github.com/youtube/cobalt/actions/workflows/stub.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/stub.yaml?query=event%3Apush+branch%3Amain) | [![stub_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/stub_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/stub_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | | | | | | |
+| Win32     | [![win32](https://github.com/youtube/cobalt/actions/workflows/win32.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/win32.yaml?query=event%3Apush+branch%3Amain) | [![win32_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=push)](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml?query=event%3Apush+branch%3A23.lts.1%2B) | | | | | | |
+| Python    | [![python](https://github.com/youtube/cobalt/actions/workflows/pytest.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/pytest.yaml?query=event%3Apush+branch%3Amain) | | | | | | |
+| Java      | [![java](https://github.com/youtube/cobalt/actions/workflows/gradle.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/gradle.yaml?query=event%3Apush+branch%3Amain) | | | | | | |
 
 # Nightly builds
-| Workflow  | main | 24.lts.1+ | 23.lts.1+ | 22.lts.1+ | 21.lts.1+ | 20.lts.1+ | 19.lts.1+ | RC11 | COBALT 9 |
-| --------- | ---- | --------- | --------- | --------- | --------- | --------- | --------- | ---- | ---------|
-| Android   | [![android](https://github.com/youtube/cobalt/actions/workflows/android.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/android.yaml?query=event%3Aschedule+branch%3Amain) | [![android_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/android_24.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A24.lts.1%2B) | [![android_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![android_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | | | | | |
-| Evergreen | [![evergreen](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml?query=event%3Aschedule+branch%3Amain) | [![evergreen_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_24.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A24.lts.1%2B) | [![evergreen_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![evergreen_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | [![evergreen_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A21.lts.1%2B) | | | | |
-| Linux | [![linux](https://github.com/youtube/cobalt/actions/workflows/linux.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/linux.yaml?query=event%3Aschedule+branch%3Amain) | [![linux_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_24.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A24.lts.1%2B) | [![linux_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![linux_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | [![linux_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A21.lts.1%2B) | [![linux_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A20.lts.1%2B) | [![linux_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A19.lts.1%2B) | [![linux_rc_11](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml/badge.svg?branch=rc_11&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml?query=event%3Aworkflow_dispatch+branch%3Arc_11) | [![linux_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml?query=event%3Aworkflow_dispatch+branch%3ACOBALT_9) |
-| Raspi-2 | [![raspi-2](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml?query=event%3Aschedule+branch%3Amain) | [![raspi-2_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_24.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A24.lts.1%2B) | [![raspi-2_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![raspi-2_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | [![raspi-2_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A21.lts.1%2B) | [![raspi-2_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A20.lts.1%2B) | [![raspi-2_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A19.lts.1%2B) | [![raspi-2_rc_11](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml/badge.svg?branch=rc_11&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml?query=event%3Aworkflow_dispatch+branch%3Arc_11) | [![raspi-2_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml?query=event%3Aworkflow_dispatch+branch%3ACOBALT_9) |
-| Win32 | [![win32](https://github.com/youtube/cobalt/actions/workflows/win32.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/win32.yaml?query=event%3Aschedule+branch%3Amain) | [![win32_24.lts.1+](https://github.com/youtube/cobalt/actions/workflows/win32_24.lts.1+.yaml/badge.svg?branch=24.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/win32_24.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A24.lts.1%2B) | [![win32_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | | | | | | |
+| Workflow  | main | 23.lts.1+ | 22.lts.1+ | 21.lts.1+ | 20.lts.1+ | 19.lts.1+ | RC11 | COBALT 9 |
+| --------- | ---- | --------- | --------- | --------- | --------- | --------- | ---- | ---------|
+| Android   | [![android](https://github.com/youtube/cobalt/actions/workflows/android.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/android.yaml?query=event%3Aschedule+branch%3Amain) | [![android_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/android_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![android_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/android_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | | | | | |
+| Evergreen | [![evergreen](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml?query=event%3Aschedule+branch%3Amain) | [![evergreen_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![evergreen_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | [![evergreen_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/evergreen_21.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A21.lts.1%2B) | | | | |
+| Linux | [![linux](https://github.com/youtube/cobalt/actions/workflows/linux.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/linux.yaml?query=event%3Aschedule+branch%3Amain) | [![linux_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![linux_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | [![linux_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_21.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A21.lts.1%2B) | [![linux_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_20.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A20.lts.1%2B) | [![linux_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_19.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A19.lts.1%2B) | [![linux_rc_11](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml/badge.svg?branch=rc_11&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_rc_11.yaml?query=event%3Aworkflow_dispatch+branch%3Arc_11) | [![linux_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/linux_COBALT_9.yaml?query=event%3Aworkflow_dispatch+branch%3ACOBALT_9) |
+| Raspi-2 | [![raspi-2](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml?query=event%3Aschedule+branch%3Amain) | [![raspi-2_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | [![raspi-2_22.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml/badge.svg?branch=22.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_22.lts.1+.yaml?query=branch%3A22.lts.1%2B+event%3Aworkflow_dispatch) | [![raspi-2_21.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml/badge.svg?branch=21.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_21.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A21.lts.1%2B) | [![raspi-2_20.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml/badge.svg?branch=20.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_20.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A20.lts.1%2B) | [![raspi-2_19.lts.1+](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml/badge.svg?branch=19.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_19.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A19.lts.1%2B) | [![raspi-2_rc_11](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml/badge.svg?branch=rc_11&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_rc_11.yaml?query=event%3Aworkflow_dispatch+branch%3Arc_11) | [![raspi-2_COBALT_9](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml/badge.svg?branch=COBALT_9&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/raspi-2_COBALT_9.yaml?query=event%3Aworkflow_dispatch+branch%3ACOBALT_9) |
+| Win32 | [![win32](https://github.com/youtube/cobalt/actions/workflows/win32.yaml/badge.svg?branch=main&event=schedule)](https://github.com/youtube/cobalt/actions/workflows/win32.yaml?query=event%3Aschedule+branch%3Amain) | [![win32_23.lts.1+](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml/badge.svg?branch=23.lts.1%2B&event=workflow_dispatch)](https://github.com/youtube/cobalt/actions/workflows/win32_23.lts.1+.yaml?query=event%3Aworkflow_dispatch+branch%3A23.lts.1%2B) | | | | | | |
diff --git a/README.md b/README.md
index 0a349a3..fcfe4a6 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,4 @@
-# Cobalt [![Build Matrix](https://img.shields.io/badge/-Build%20Matrix-blueviolet)](https://github.com/youtube/cobalt/blob/main/BUILD_STATUS.md)
-
-[![codecov](https://codecov.io/github/youtube/cobalt/branch/main/graph/badge.svg?token=RR6MKKNYNV)](https://codecov.io/github/youtube/cobalt)
-[![lint](https://github.com/youtube/cobalt/actions/workflows/lint.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/lint.yaml?query=event%3Apush+branch%3Amain)
-[![java](https://github.com/youtube/cobalt/actions/workflows/gradle.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/gradle.yaml?query=event%3Apush+branch%3Amain)
-[![python](https://github.com/youtube/cobalt/actions/workflows/pytest.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/pytest.yaml?query=event%3Apush+branch%3Amain) \
-[![android](https://github.com/youtube/cobalt/actions/workflows/android.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/android.yaml?query=event%3Apush+branch%3Amain)
-[![evergreen](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/evergreen.yaml?query=event%3Apush+branch%3Amain)
-[![linux](https://github.com/youtube/cobalt/actions/workflows/linux.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/linux.yaml?query=event%3Apush+branch%3Amain)
-[![raspi-2](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/raspi-2.yaml?query=event%3Apush+branch%3Amain)
-[![win32](https://github.com/youtube/cobalt/actions/workflows/win32.yaml/badge.svg?branch=main&event=push)](https://github.com/youtube/cobalt/actions/workflows/win32.yaml?query=event%3Apush+branch%3Amain)
+# Cobalt [![Build Status](https://img.shields.io/badge/-Build%20Status-blueviolet)](https://github.com/youtube/cobalt/blob/main/BUILD_STATUS.md)
 
 ## Overview
 
diff --git a/build/config/coverage/BUILD.gn b/build/config/coverage/BUILD.gn
index fa0833e..09c227d 100644
--- a/build/config/coverage/BUILD.gn
+++ b/build/config/coverage/BUILD.gn
@@ -30,13 +30,5 @@
       # TODO(crbug.com/1194301): Remove this flag.
       cflags += [ "-fno-use-cxa-atexit" ]
     }
-
-    if (using_old_compiler) {
-      # These compiler flags aren't supported by the older clang compiler.
-      cflags -= [
-        "-limited-coverage-experimental=true",
-        "-fno-use-cxa-atexit",
-      ]
-    }
   }
 }
diff --git a/cobalt/black_box_tests/black_box_tests.py b/cobalt/black_box_tests/black_box_tests.py
index 99b11c4..ca5129e 100755
--- a/cobalt/black_box_tests/black_box_tests.py
+++ b/cobalt/black_box_tests/black_box_tests.py
@@ -46,7 +46,9 @@
     # 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',
+    # TODO(b/283144901): enable when the Starboard 16 binaries are released for
+    # Evergreen.
+    #'evergreen-x64/devel',
 ]
 
 _PORT_SELECTION_RETRY_LIMIT = 10
diff --git a/cobalt/black_box_tests/testdata/service_worker_controller_activation_test_worker.js b/cobalt/black_box_tests/testdata/service_worker_controller_activation_test_worker.js
index a1fab38..4bda3e0 100644
--- a/cobalt/black_box_tests/testdata/service_worker_controller_activation_test_worker.js
+++ b/cobalt/black_box_tests/testdata/service_worker_controller_activation_test_worker.js
@@ -23,5 +23,4 @@
 
 self.onactivate = function (e) {
   console.log('onactivate event received', e);
-  self.clients.claim();
 }
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc
index e8f1c28..f2c7ffe 100644
--- a/cobalt/browser/browser_module.cc
+++ b/cobalt/browser/browser_module.cc
@@ -629,8 +629,8 @@
 
   options.web_options.web_settings = &web_settings_;
   options.web_options.network_module = network_module_;
-  options.web_options.service_worker_context =
-      service_worker_registry_->service_worker_context();
+  options.web_options.service_worker_jobs =
+      service_worker_registry_->service_worker_jobs();
   options.web_options.platform_info = platform_info_.get();
   web_module_.reset(new WebModule("MainWebModule"));
   // Wait for service worker to start if one exists.
diff --git a/cobalt/browser/service_worker_registry.cc b/cobalt/browser/service_worker_registry.cc
index f5dd468..23a1a1c 100644
--- a/cobalt/browser/service_worker_registry.cc
+++ b/cobalt/browser/service_worker_registry.cc
@@ -22,6 +22,7 @@
 #include "base/threading/thread.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/network/network_module.h"
+#include "cobalt/worker/service_worker_jobs.h"
 
 namespace cobalt {
 namespace browser {
@@ -33,7 +34,7 @@
 
 void ServiceWorkerRegistry::WillDestroyCurrentMessageLoop() {
   // Clear all member variables allocated from the thread.
-  service_worker_context_.reset();
+  service_worker_jobs_.reset();
 }
 
 ServiceWorkerRegistry::ServiceWorkerRegistry(
@@ -74,20 +75,20 @@
   destruction_observer_added_.Wait();
   DCHECK_NE(thread_.message_loop(), base::MessageLoop::current());
   thread_.Stop();
-  DCHECK(!service_worker_context_);
+  DCHECK(!service_worker_jobs_);
 }
 
 void ServiceWorkerRegistry::EnsureServiceWorkerStarted(
     const url::Origin& storage_key, const GURL& client_url,
     base::WaitableEvent* done_event) {
-  service_worker_context()->EnsureServiceWorkerStarted(storage_key, client_url,
-                                                       done_event);
+  service_worker_jobs()->EnsureServiceWorkerStarted(storage_key, client_url,
+                                                    done_event);
 }
 
-worker::ServiceWorkerContext* ServiceWorkerRegistry::service_worker_context() {
+worker::ServiceWorkerJobs* ServiceWorkerRegistry::service_worker_jobs() {
   // Ensure that the thread had a chance to allocate the object.
   destruction_observer_added_.Wait();
-  return service_worker_context_.get();
+  return service_worker_jobs_.get();
 }
 
 void ServiceWorkerRegistry::Initialize(
@@ -95,7 +96,7 @@
     web::UserAgentPlatformInfo* platform_info, const GURL& url) {
   TRACE_EVENT0("cobalt::browser", "ServiceWorkerRegistry::Initialize()");
   DCHECK_EQ(base::MessageLoop::current(), message_loop());
-  service_worker_context_.reset(new worker::ServiceWorkerContext(
+  service_worker_jobs_.reset(new worker::ServiceWorkerJobs(
       web_settings, network_module, platform_info, message_loop(), url));
 }
 
diff --git a/cobalt/browser/service_worker_registry.h b/cobalt/browser/service_worker_registry.h
index 5b57118..8c37083 100644
--- a/cobalt/browser/service_worker_registry.h
+++ b/cobalt/browser/service_worker_registry.h
@@ -22,7 +22,7 @@
 #include "base/threading/thread.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/web/web_settings.h"
-#include "cobalt/worker/service_worker_context.h"
+#include "cobalt/worker/service_worker_jobs.h"
 
 namespace cobalt {
 namespace browser {
@@ -48,7 +48,7 @@
                                   const GURL& client_url,
                                   base::WaitableEvent* done_event);
 
-  worker::ServiceWorkerContext* service_worker_context();
+  worker::ServiceWorkerJobs* service_worker_jobs();
 
  private:
   // Called by the constructor to perform any other initialization required on
@@ -69,7 +69,7 @@
       base::WaitableEvent::ResetPolicy::MANUAL,
       base::WaitableEvent::InitialState::NOT_SIGNALED};
 
-  std::unique_ptr<worker::ServiceWorkerContext> service_worker_context_;
+  std::unique_ptr<worker::ServiceWorkerJobs> service_worker_jobs_;
 };
 
 }  // namespace browser
diff --git a/cobalt/h5vcc/h5vcc_metrics.idl b/cobalt/h5vcc/h5vcc_metrics.idl
index 29aa11f..add5085 100644
--- a/cobalt/h5vcc/h5vcc_metrics.idl
+++ b/cobalt/h5vcc/h5vcc_metrics.idl
@@ -34,10 +34,8 @@
   // Enable Cobalt metrics logging. Metrics are disabled by default and should
   // be enabled as soon as possible. When enabled, at regular intervals, the
   // bound onMetricEvent callback will be called with telemetry payloads to
-  // be uploaded and logged on the server. Both the enable/disable settings
-  // are persistent or "sticky". That is, you only have to call them once
-  // and that setting will persist through multiple app lifecycles until the
-  // enable/disable APIs are explicitly called again.
+  // be uploaded and logged on the server.
+  // TODO(b/286898092): Update docs here when setting is persistent.
   void enable();
 
   // Disable Cobalt metrics logging. If disabled, the metric event handler
@@ -49,9 +47,7 @@
 
   // Sets the frequency in which Cobalt metrics will be snapshotted and sent to
   // the metric event handler. Defaults to 5 minutes if not set. Must be > 0,
-  // else will be ignored. This setting is persistent. That is, it's "sticky"
-  // and will be persisted through multiple app lifetimes unless it's called
-  // again with a different value.
+  // else will be ignored.
   void setMetricEventInterval(unsigned long intervalSeconds);
 };
 
diff --git a/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt b/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt
index f821df1..22865ba 100644
--- a/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt
+++ b/cobalt/layout_tests/testdata/web-platform-tests/service-workers/web_platform_tests.txt
@@ -9,7 +9,6 @@
 service-worker/import-scripts-updated-flag.https.html, PASS
 service-worker/multiple-update.https.html, PASS
 service-worker/register-default-scope.https.html, PASS
-service-worker/register-wait-forever-in-install-worker.https.html, PASS
 service-worker/registration-basic.https.html, PASS
 service-worker/registration-security-error.https.html, PASS
 service-worker/registration-service-worker-attributes.https.html, PASS
@@ -39,6 +38,7 @@
 
 # b/234788479 Implement waiting for update worker state tasks in Install algorithm.
 service-worker/import-scripts-redirect.https.html, DISABLE
+service-worker/register-wait-forever-in-install-worker.https.html, DISABLE
 
 # "Module" type of dedicated worker is supported in Cobalt
 service-worker/dedicated-worker-service-worker-interception.https.html, DISABLE
diff --git a/cobalt/layout_tests/web_platform_tests.cc b/cobalt/layout_tests/web_platform_tests.cc
index 455c37e..05c2376 100644
--- a/cobalt/layout_tests/web_platform_tests.cc
+++ b/cobalt/layout_tests/web_platform_tests.cc
@@ -232,8 +232,8 @@
 
   web_module_options.web_options.web_settings = &web_settings;
   web_module_options.web_options.network_module = &network_module;
-  web_module_options.web_options.service_worker_context =
-      service_worker_registry->service_worker_context();
+  web_module_options.web_options.service_worker_jobs =
+      service_worker_registry->service_worker_jobs();
   web_module_options.web_options.platform_info = platform_info.get();
 
   // Prepare a slot for our results to be placed when ready.
diff --git a/cobalt/updater/unzipper.cc b/cobalt/updater/unzipper.cc
index 22d6423..1855b3a 100644
--- a/cobalt/updater/unzipper.cc
+++ b/cobalt/updater/unzipper.cc
@@ -14,9 +14,7 @@
 
 #include "cobalt/updater/unzipper.h"
 
-#include <string>
 #include <utility>
-
 #include "base/callback.h"
 #include "base/files/file_path.h"
 #include "starboard/time.h"
@@ -42,20 +40,6 @@
     LOG(INFO) << "Unzip took " << time_unzip_took / kSbTimeMillisecond
               << " milliseconds.";
   }
-
-#if defined(IN_MEMORY_UPDATES)
-  void Unzip(const std::string& zip_str, const base::FilePath& output_path,
-             UnzipCompleteCallback callback) override {
-    SbTimeMonotonic time_before_unzip = SbTimeGetMonotonicNow();
-    std::move(callback).Run(zip::Unzip(zip_str, output_path));
-    SbTimeMonotonic time_unzip_took =
-        SbTimeGetMonotonicNow() - time_before_unzip;
-    LOG(INFO) << "Unzip from string";
-    LOG(INFO) << "output_path = " << output_path;
-    LOG(INFO) << "Unzip took " << time_unzip_took / kSbTimeMillisecond
-              << " milliseconds.";
-  }
-#endif
 };
 
 }  // namespace
diff --git a/cobalt/version.h b/cobalt/version.h
index 12dce9f..a7deda9 100644
--- a/cobalt/version.h
+++ b/cobalt/version.h
@@ -35,6 +35,6 @@
 //                  release is cut.
 //.
 
-#define COBALT_VERSION "24.master.0"
+#define COBALT_VERSION "24.lts.1"
 
 #endif  // COBALT_VERSION_H_
diff --git a/cobalt/web/agent.cc b/cobalt/web/agent.cc
index 4efb0a4..d81242b 100644
--- a/cobalt/web/agent.cc
+++ b/cobalt/web/agent.cc
@@ -38,7 +38,7 @@
 #include "cobalt/web/url.h"
 #include "cobalt/web/window_or_worker_global_scope.h"
 #include "cobalt/worker/service_worker.h"
-#include "cobalt/worker/service_worker_context.h"
+#include "cobalt/worker/service_worker_jobs.h"
 #include "cobalt/worker/service_worker_object.h"
 #include "cobalt/worker/service_worker_registration.h"
 #include "cobalt/worker/service_worker_registration_object.h"
@@ -91,8 +91,8 @@
     DCHECK(fetcher_factory_);
     return fetcher_factory_->network_module();
   }
-  worker::ServiceWorkerContext* service_worker_context() const final {
-    return service_worker_context_;
+  worker::ServiceWorkerJobs* service_worker_jobs() const final {
+    return service_worker_jobs_;
   }
 
   const std::string& name() const final { return name_; };
@@ -217,7 +217,7 @@
   std::map<worker::ServiceWorkerObject*, scoped_refptr<worker::ServiceWorker>>
       service_worker_object_map_;
 
-  worker::ServiceWorkerContext* service_worker_context_;
+  worker::ServiceWorkerJobs* service_worker_jobs_;
   const web::UserAgentPlatformInfo* platform_info_;
 
   // https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-active-service-worker
@@ -250,7 +250,7 @@
 Impl::Impl(const std::string& name, const Agent::Options& options)
     : name_(name), web_settings_(options.web_settings) {
   TRACE_EVENT0("cobalt::web", "Agent::Impl::Impl()");
-  service_worker_context_ = options.service_worker_context;
+  service_worker_jobs_ = options.service_worker_jobs;
   platform_info_ = options.platform_info;
   blob_registry_.reset(new Blob::Registry);
 
@@ -352,9 +352,11 @@
   }
 #endif
 
-  if (service_worker_context_) {
-    service_worker_context_->RegisterWebContext(this);
-    service_worker_context_->SetActiveWorker(environment_settings_.get());
+  if (service_worker_jobs_) {
+    service_worker_jobs_->RegisterWebContext(this);
+  }
+  if (service_worker_jobs_) {
+    service_worker_jobs_->SetActiveWorker(environment_settings_.get());
   }
 }
 
@@ -531,8 +533,8 @@
   DCHECK(message_loop());
   DCHECK(thread_.IsRunning());
 
-  if (context() && context()->service_worker_context()) {
-    context()->service_worker_context()->UnregisterWebContext(context());
+  if (context() && context()->service_worker_jobs()) {
+    context()->service_worker_jobs()->UnregisterWebContext(context());
   }
 
   // Ensure that the destruction observer got added before stopping the thread.
diff --git a/cobalt/web/agent.h b/cobalt/web/agent.h
index 091cee0..d57d526 100644
--- a/cobalt/web/agent.h
+++ b/cobalt/web/agent.h
@@ -34,7 +34,7 @@
 
 namespace cobalt {
 namespace worker {
-class ServiceWorkerContext;
+class ServiceWorkerJobs;
 }
 namespace web {
 
@@ -69,7 +69,7 @@
     base::Callback<int(const std::string&, std::unique_ptr<char[]>*)>
         read_cache_callback;
 
-    worker::ServiceWorkerContext* service_worker_context = nullptr;
+    worker::ServiceWorkerJobs* service_worker_jobs = nullptr;
 
     const UserAgentPlatformInfo* platform_info = nullptr;
   };
diff --git a/cobalt/web/context.h b/cobalt/web/context.h
index b8fc450..3fdf42c 100644
--- a/cobalt/web/context.h
+++ b/cobalt/web/context.h
@@ -36,7 +36,7 @@
 class ServiceWorkerRegistration;
 class ServiceWorkerRegistrationObject;
 class ServiceWorker;
-class ServiceWorkerContext;
+class ServiceWorkerJobs;
 class ServiceWorkerObject;
 }  // namespace worker
 namespace web {
@@ -65,7 +65,7 @@
   virtual Blob::Registry* blob_registry() const = 0;
   virtual web::WebSettings* web_settings() const = 0;
   virtual network::NetworkModule* network_module() const = 0;
-  virtual worker::ServiceWorkerContext* service_worker_context() const = 0;
+  virtual worker::ServiceWorkerJobs* service_worker_jobs() const = 0;
 
   virtual const std::string& name() const = 0;
   virtual void SetupEnvironmentSettings(EnvironmentSettings* settings) = 0;
diff --git a/cobalt/web/testing/stub_web_context.h b/cobalt/web/testing/stub_web_context.h
index 487101b..87268f0 100644
--- a/cobalt/web/testing/stub_web_context.h
+++ b/cobalt/web/testing/stub_web_context.h
@@ -110,7 +110,7 @@
     return network_module_.get();
   }
 
-  worker::ServiceWorkerContext* service_worker_context() const final {
+  worker::ServiceWorkerJobs* service_worker_jobs() const final {
     NOTREACHED();
     return nullptr;
   }
diff --git a/cobalt/worker/BUILD.gn b/cobalt/worker/BUILD.gn
index 23aeaa1..24d8147 100644
--- a/cobalt/worker/BUILD.gn
+++ b/cobalt/worker/BUILD.gn
@@ -40,8 +40,6 @@
     "service_worker_consts.h",
     "service_worker_container.cc",
     "service_worker_container.h",
-    "service_worker_context.cc",
-    "service_worker_context.h",
     "service_worker_global_scope.cc",
     "service_worker_global_scope.h",
     "service_worker_jobs.cc",
diff --git a/cobalt/worker/clients.cc b/cobalt/worker/clients.cc
index c32966d..c78e12a 100644
--- a/cobalt/worker/clients.cc
+++ b/cobalt/worker/clients.cc
@@ -33,8 +33,8 @@
 #include "cobalt/web/dom_exception.h"
 #include "cobalt/web/environment_settings.h"
 #include "cobalt/worker/client.h"
-#include "cobalt/worker/service_worker_context.h"
 #include "cobalt/worker/service_worker_global_scope.h"
+#include "cobalt/worker/service_worker_jobs.h"
 #include "cobalt/worker/service_worker_object.h"
 
 namespace cobalt {
@@ -72,13 +72,12 @@
           settings_->context()->GetWindowOrWorkerGlobalScope(), promise));
 
   // 2. Run these substeps in parallel:
-  ServiceWorkerContext* context =
-      settings_->context()->service_worker_context();
-  DCHECK(context);
-  context->message_loop()->task_runner()->PostTask(
+  ServiceWorkerJobs* jobs = settings_->context()->service_worker_jobs();
+  DCHECK(jobs);
+  jobs->message_loop()->task_runner()->PostTask(
       FROM_HERE,
-      base::BindOnce(&ServiceWorkerContext::ClientsGetSubSteps,
-                     base::Unretained(context),
+      base::BindOnce(&ServiceWorkerJobs::ClientsGetSubSteps,
+                     base::Unretained(jobs),
                      base::Unretained(settings_->context()),
                      base::Unretained(GetAssociatedServiceWorker(settings_)),
                      std::move(promise_reference), id));
@@ -102,13 +101,12 @@
       promise_reference(new script::ValuePromiseSequenceWrappable::Reference(
           settings_->context()->GetWindowOrWorkerGlobalScope(), promise));
   // 2. Run the following steps in parallel:
-  ServiceWorkerContext* context =
-      settings_->context()->service_worker_context();
-  DCHECK(context);
-  context->message_loop()->task_runner()->PostTask(
+  ServiceWorkerJobs* jobs = settings_->context()->service_worker_jobs();
+  DCHECK(jobs);
+  jobs->message_loop()->task_runner()->PostTask(
       FROM_HERE,
-      base::BindOnce(&ServiceWorkerContext::ClientsMatchAllSubSteps,
-                     base::Unretained(context),
+      base::BindOnce(&ServiceWorkerJobs::ClientsMatchAllSubSteps,
+                     base::Unretained(jobs),
                      base::Unretained(settings_->context()),
                      base::Unretained(GetAssociatedServiceWorker(settings_)),
                      std::move(promise_reference),
@@ -158,13 +156,11 @@
          service_worker->state() == kServiceWorkerStateActivating);
 
   // 3. Run the following substeps in parallel:
-  ServiceWorkerContext* context =
-      settings_->context()->service_worker_context();
-  DCHECK(context);
-  context->message_loop()->task_runner()->PostTask(
+  ServiceWorkerJobs* jobs = settings_->context()->service_worker_jobs();
+  DCHECK(jobs);
+  jobs->message_loop()->task_runner()->PostTask(
       FROM_HERE,
-      base::BindOnce(&ServiceWorkerContext::ClaimSubSteps,
-                     base::Unretained(context),
+      base::BindOnce(&ServiceWorkerJobs::ClaimSubSteps, base::Unretained(jobs),
                      base::Unretained(settings_->context()),
                      base::Unretained(GetAssociatedServiceWorker(settings_)),
                      std::move(promise_reference)));
diff --git a/cobalt/worker/dedicated_worker.cc b/cobalt/worker/dedicated_worker.cc
index 489a987..624dbec 100644
--- a/cobalt/worker/dedicated_worker.cc
+++ b/cobalt/worker/dedicated_worker.cc
@@ -97,8 +97,8 @@
   options.outside_event_target = this;
   options.outside_port = outside_port_.get();
   options.options = worker_options_;
-  options.web_options.service_worker_context =
-      options.outside_context->service_worker_context();
+  options.web_options.service_worker_jobs =
+      options.outside_context->service_worker_jobs();
   // Store the current source location as the construction location, to be used
   // in the error event if worker loading of initialization fails.
   auto stack_trace =
diff --git a/cobalt/worker/extendable_event.cc b/cobalt/worker/extendable_event.cc
index 5b285dc..87d48cb 100644
--- a/cobalt/worker/extendable_event.cc
+++ b/cobalt/worker/extendable_event.cc
@@ -72,15 +72,14 @@
     web::Context* context =
         base::polymorphic_downcast<web::EnvironmentSettings*>(settings)
             ->context();
-    ServiceWorkerContext* worker_context = context->service_worker_context();
-    DCHECK(worker_context);
+    ServiceWorkerJobs* jobs = context->service_worker_jobs();
+    DCHECK(jobs);
     // 5.2.1. Let registration be the current global object's associated
     //        service worker's containing service worker registration.
-    worker_context->message_loop()->task_runner()->PostTask(
+    jobs->message_loop()->task_runner()->PostTask(
         FROM_HERE,
         base::BindOnce(
-            &ServiceWorkerContext::WaitUntilSubSteps,
-            base::Unretained(worker_context),
+            &ServiceWorkerJobs::WaitUntilSubSteps, base::Unretained(jobs),
             base::Unretained(context->GetWindowOrWorkerGlobalScope()
                                  ->AsServiceWorker()
                                  ->service_worker_object()
diff --git a/cobalt/worker/extendable_event.h b/cobalt/worker/extendable_event.h
index 5864eb0..fbd8136 100644
--- a/cobalt/worker/extendable_event.h
+++ b/cobalt/worker/extendable_event.h
@@ -35,8 +35,8 @@
 #include "cobalt/web/event.h"
 #include "cobalt/web/window_or_worker_global_scope.h"
 #include "cobalt/worker/extendable_event_init.h"
-#include "cobalt/worker/service_worker_context.h"
 #include "cobalt/worker/service_worker_global_scope.h"
+#include "cobalt/worker/service_worker_jobs.h"
 #include "cobalt/worker/service_worker_object.h"
 #include "cobalt/worker/service_worker_registration_object.h"
 #include "v8/include/v8.h"
diff --git a/cobalt/worker/service_worker.cc b/cobalt/worker/service_worker.cc
index 49cd005..b8fad61 100644
--- a/cobalt/worker/service_worker.cc
+++ b/cobalt/worker/service_worker.cc
@@ -24,8 +24,8 @@
 #include "cobalt/web/environment_settings_helper.h"
 #include "cobalt/web/event_target.h"
 #include "cobalt/web/message_port.h"
-#include "cobalt/worker/service_worker_context.h"
 #include "cobalt/worker/service_worker_global_scope.h"
+#include "cobalt/worker/service_worker_jobs.h"
 #include "cobalt/worker/service_worker_object.h"
 #include "cobalt/worker/service_worker_state.h"
 
@@ -56,14 +56,13 @@
   // "message" and serviceWorker is true, then return.
   if (service_worker->ShouldSkipEvent(base::Tokens::message())) return;
   // 6. Run these substeps in parallel:
-  ServiceWorkerContext* worker_context =
-      incumbent_settings->context()->service_worker_context();
-  DCHECK(worker_context);
-  worker_context->message_loop()->task_runner()->PostTask(
+  ServiceWorkerJobs* jobs =
+      incumbent_settings->context()->service_worker_jobs();
+  DCHECK(jobs);
+  jobs->message_loop()->task_runner()->PostTask(
       FROM_HERE,
-      base::BindOnce(&ServiceWorkerContext::ServiceWorkerPostMessageSubSteps,
-                     base::Unretained(worker_context),
-                     base::Unretained(service_worker),
+      base::BindOnce(&ServiceWorkerJobs::ServiceWorkerPostMessageSubSteps,
+                     base::Unretained(jobs), base::Unretained(service_worker),
                      base::Unretained(incumbent_settings->context()),
                      std::move(structured_clone)));
 }
diff --git a/cobalt/worker/service_worker_container.cc b/cobalt/worker/service_worker_container.cc
index 268481d..b392f5c 100644
--- a/cobalt/worker/service_worker_container.cc
+++ b/cobalt/worker/service_worker_container.cc
@@ -80,11 +80,11 @@
   if (ready_promise->State() == script::PromiseState::kPending) {
     //    3.1. Let client by this's service worker client.
     web::Context* client = environment_settings()->context();
-    ServiceWorkerContext* worker_context = client->service_worker_context();
-    worker_context->message_loop()->task_runner()->PostTask(
+    worker::ServiceWorkerJobs* jobs = client->service_worker_jobs();
+    jobs->message_loop()->task_runner()->PostTask(
         FROM_HERE,
-        base::BindOnce(&ServiceWorkerContext::MaybeResolveReadyPromiseSubSteps,
-                       base::Unretained(worker_context), client));
+        base::BindOnce(&ServiceWorkerJobs::MaybeResolveReadyPromiseSubSteps,
+                       base::Unretained(jobs), client));
   }
   // 4. Return readyPromise.
   return ready_promise;
@@ -155,10 +155,10 @@
   //    creation URL, options["type"], and options["updateViaCache"].
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
-      base::BindOnce(&ServiceWorkerContext::StartRegister,
-                     base::Unretained(client->service_worker_context()),
-                     scope_url, script_url, std::move(promise_reference),
-                     client, options.type(), options.update_via_cache()));
+      base::BindOnce(&ServiceWorkerJobs::StartRegister,
+                     base::Unretained(client->service_worker_jobs()), scope_url,
+                     script_url, std::move(promise_reference), client,
+                     options.type(), options.update_via_cache()));
   // 7. Return p.
   return promise;
 }
@@ -237,13 +237,12 @@
 
   // 7. Let promise be a new promise.
   // 8. Run the following substeps in parallel:
-  ServiceWorkerContext* worker_context = client->service_worker_context();
-  DCHECK(worker_context);
-  worker_context->message_loop()->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&ServiceWorkerContext::GetRegistrationSubSteps,
-                     base::Unretained(worker_context), storage_key, client_url,
-                     client, std::move(promise_reference)));
+  worker::ServiceWorkerJobs* jobs = client->service_worker_jobs();
+  DCHECK(jobs);
+  jobs->message_loop()->task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&ServiceWorkerJobs::GetRegistrationSubSteps,
+                                base::Unretained(jobs), storage_key, client_url,
+                                client, std::move(promise_reference)));
 }
 
 script::HandlePromiseSequenceWrappable
@@ -269,13 +268,13 @@
         promise_reference) {
   auto* client = environment_settings()->context();
   // https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistrations
-  ServiceWorkerContext* worker_context =
-      environment_settings()->context()->service_worker_context();
+  worker::ServiceWorkerJobs* jobs =
+      environment_settings()->context()->service_worker_jobs();
   url::Origin storage_key = environment_settings()->ObtainStorageKey();
-  worker_context->message_loop()->task_runner()->PostTask(
-      FROM_HERE, base::BindOnce(&ServiceWorkerContext::GetRegistrationsSubSteps,
-                                base::Unretained(worker_context), storage_key,
-                                client, std::move(promise_reference)));
+  jobs->message_loop()->task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&ServiceWorkerJobs::GetRegistrationsSubSteps,
+                                base::Unretained(jobs), storage_key, client,
+                                std::move(promise_reference)));
 }
 
 void ServiceWorkerContainer::StartMessages() {}
diff --git a/cobalt/worker/service_worker_container.h b/cobalt/worker/service_worker_container.h
index 3fc52ab..94679d6 100644
--- a/cobalt/worker/service_worker_container.h
+++ b/cobalt/worker/service_worker_container.h
@@ -26,7 +26,7 @@
 #include "cobalt/web/event_target.h"
 #include "cobalt/web/event_target_listener_info.h"
 #include "cobalt/worker/registration_options.h"
-#include "cobalt/worker/service_worker_context.h"
+#include "cobalt/worker/service_worker_jobs.h"
 #include "cobalt/worker/service_worker_registration.h"
 
 namespace cobalt {
diff --git a/cobalt/worker/service_worker_context.cc b/cobalt/worker/service_worker_context.cc
deleted file mode 100644
index 3dc9a4e..0000000
--- a/cobalt/worker/service_worker_context.cc
+++ /dev/null
@@ -1,1716 +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.
-
-#include "cobalt/worker/service_worker_context.h"
-
-#include <list>
-#include <utility>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/message_loop/message_loop_current.h"
-#include "base/time/time.h"
-#include "base/trace_event/trace_event.h"
-#include "cobalt/web/dom_exception.h"
-#include "cobalt/web/environment_settings.h"
-#include "cobalt/worker/extendable_event.h"
-#include "cobalt/worker/extendable_message_event.h"
-#include "cobalt/worker/service_worker_container.h"
-#include "cobalt/worker/service_worker_jobs.h"
-#include "cobalt/worker/window_client.h"
-
-namespace cobalt {
-namespace worker {
-
-namespace {
-
-const base::TimeDelta kWaitForAsynchronousExtensionsTimeout =
-    base::TimeDelta::FromSeconds(3);
-
-const base::TimeDelta kShutdownWaitTimeoutSecs =
-    base::TimeDelta::FromSeconds(5);
-
-bool PathContainsEscapedSlash(const GURL& url) {
-  const std::string path = url.path();
-  return (path.find("%2f") != std::string::npos ||
-          path.find("%2F") != std::string::npos ||
-          path.find("%5c") != std::string::npos ||
-          path.find("%5C") != std::string::npos);
-}
-
-void ResolveGetClientPromise(
-    web::Context* client, web::Context* worker_context,
-    std::unique_ptr<script::ValuePromiseWrappable::Reference>
-        promise_reference) {
-  TRACE_EVENT0("cobalt::worker", "ResolveGetClientPromise()");
-  // Algorithm for Resolve Get Client Promise:
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#resolve-get-client-promise
-
-  // 1. If client is an environment settings object, then:
-  // 1.1. If client is not a secure context, queue a task to reject promise with
-  //      a "SecurityError" DOMException, on promise’s relevant settings
-  //      object's responsible event loop using the DOM manipulation task
-  //      source, and abort these steps.
-  // 2. Else:
-  // 2.1. If client’s creation URL is not a potentially trustworthy URL, queue
-  //      a task to reject promise with a "SecurityError" DOMException, on
-  //      promise’s relevant settings object's responsible event loop using the
-  //      DOM manipulation task source, and abort these steps.
-  // In production, Cobalt requires https, therefore all clients are secure
-  // contexts.
-
-  // 3. If client is an environment settings object and is not a window client,
-  //    then:
-  if (!client->GetWindowOrWorkerGlobalScope()->IsWindow()) {
-    // 3.1. Let clientObject be the result of running Create Client algorithm
-    //      with client as the argument.
-    scoped_refptr<Client> client_object =
-        Client::Create(client->environment_settings());
-
-    // 3.2. Queue a task to resolve promise with clientObject, on promise’s
-    //      relevant settings object's responsible event loop using the DOM
-    //      manipulation task source, and abort these steps.
-    worker_context->message_loop()->task_runner()->PostTask(
-        FROM_HERE,
-        base::BindOnce(
-            [](std::unique_ptr<script::ValuePromiseWrappable::Reference>
-                   promise_reference,
-               scoped_refptr<Client> client_object) {
-              TRACE_EVENT0("cobalt::worker",
-                           "ResolveGetClientPromise() Resolve");
-              promise_reference->value().Resolve(client_object);
-            },
-            std::move(promise_reference), client_object));
-    return;
-  }
-  // 4. Else:
-  // 4.1. Let browsingContext be null.
-  // 4.2. If client is an environment settings object, set browsingContext to
-  //      client’s global object's browsing context.
-  // 4.3. Else, set browsingContext to client’s target browsing context.
-  // Note: Cobalt does not implement a distinction between environments and
-  // environment settings objects.
-  // 4.4. Queue a task to run the following steps on browsingContext’s event
-  //      loop using the user interaction task source:
-  // Note: The task below does not currently perform any actual
-  // functionality in the client context. It is included however to help future
-  // implementation for fetching values for WindowClient properties, with
-  // similar logic existing in ClientsMatchAllSubSteps.
-  client->message_loop()->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](web::Context* client, web::Context* worker_context,
-             std::unique_ptr<script::ValuePromiseWrappable::Reference>
-                 promise_reference) {
-            // 4.4.1. Let frameType be the result of running Get Frame Type with
-            //        browsingContext.
-            // Cobalt does not support nested or auxiliary browsing contexts.
-            // 4.4.2. Let visibilityState be browsingContext’s active document's
-            //        visibilityState attribute value.
-            // 4.4.3. Let focusState be the result of running the has focus
-            //        steps with browsingContext’s active document as the
-            //        argument.
-            // Handled in the WindowData constructor.
-            std::unique_ptr<WindowData> window_data(
-                new WindowData(client->environment_settings()));
-
-            // 4.4.4. Let ancestorOriginsList be the empty list.
-            // 4.4.5. If client is a window client, set ancestorOriginsList to
-            //        browsingContext’s active document's relevant global
-            //        object's Location object’s ancestor origins list's
-            //        associated list.
-            // Cobalt does not implement Location.ancestorOrigins.
-
-            // 4.4.6. Queue a task to run the following steps on promise’s
-            //        relevant settings object's responsible event loop using
-            //        the DOM manipulation task source:
-            worker_context->message_loop()->task_runner()->PostTask(
-                FROM_HERE,
-                base::BindOnce(
-                    [](std::unique_ptr<script::ValuePromiseWrappable::Reference>
-                           promise_reference,
-                       std::unique_ptr<WindowData> window_data) {
-                      // 4.4.6.1. If client’s discarded flag is set, resolve
-                      //          promise with undefined and abort these
-                      //          steps.
-                      // 4.4.6.2. Let windowClient be the result of running
-                      //          Create Window Client with client,
-                      //          frameType, visibilityState, focusState,
-                      //          and ancestorOriginsList.
-                      scoped_refptr<Client> window_client =
-                          WindowClient::Create(*window_data);
-                      // 4.4.6.3. Resolve promise with windowClient.
-                      promise_reference->value().Resolve(window_client);
-                    },
-                    std::move(promise_reference), std::move(window_data)));
-          },
-          client, worker_context, std::move(promise_reference)));
-  DCHECK_EQ(nullptr, promise_reference.get());
-}
-
-}  // namespace
-
-ServiceWorkerContext::ServiceWorkerContext(
-    web::WebSettings* web_settings, network::NetworkModule* network_module,
-    web::UserAgentPlatformInfo* platform_info, base::MessageLoop* message_loop,
-    const GURL& url)
-    : message_loop_(message_loop) {
-  DCHECK_EQ(message_loop_, base::MessageLoop::current());
-  jobs_ =
-      std::make_unique<ServiceWorkerJobs>(this, network_module, message_loop);
-
-  ServiceWorkerPersistentSettings::Options options(web_settings, network_module,
-                                                   platform_info, this, url);
-  scope_to_registration_map_.reset(new ServiceWorkerRegistrationMap(options));
-  DCHECK(scope_to_registration_map_);
-}
-
-ServiceWorkerContext::~ServiceWorkerContext() {
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  scope_to_registration_map_->HandleUserAgentShutdown(this);
-  scope_to_registration_map_->AbortAllActive();
-  scope_to_registration_map_.reset();
-  if (!web_context_registrations_.empty()) {
-    // Abort any Service Workers that remain.
-    for (auto& context : web_context_registrations_) {
-      DCHECK(context);
-      if (context->GetWindowOrWorkerGlobalScope()->IsServiceWorker()) {
-        ServiceWorkerGlobalScope* service_worker =
-            context->GetWindowOrWorkerGlobalScope()->AsServiceWorker();
-        if (service_worker && service_worker->service_worker_object()) {
-          service_worker->service_worker_object()->Abort();
-        }
-      }
-    }
-
-    // Wait for web context registrations to be cleared.
-    web_context_registrations_cleared_.TimedWait(kShutdownWaitTimeoutSecs);
-  }
-}
-
-void ServiceWorkerContext::StartRegister(
-    const base::Optional<GURL>& maybe_scope_url,
-    const GURL& script_url_with_fragment,
-    std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference,
-    web::Context* client, const WorkerType& type,
-    const ServiceWorkerUpdateViaCache& update_via_cache) {
-  TRACE_EVENT2("cobalt::worker", "ServiceWorkerContext::StartRegister()",
-               "scope", maybe_scope_url.value_or(GURL()).spec(), "script",
-               script_url_with_fragment.spec());
-  DCHECK_NE(message_loop(), base::MessageLoop::current());
-  DCHECK_EQ(client->message_loop(), base::MessageLoop::current());
-  // Algorithm for Start Register:
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#start-register-algorithm
-  // 1. If scriptURL is failure, reject promise with a TypeError and abort these
-  //    steps.
-  if (script_url_with_fragment.is_empty()) {
-    promise_reference->value().Reject(script::kTypeError);
-    return;
-  }
-
-  // 2. Set scriptURL’s fragment to null.
-  url::Replacements<char> replacements;
-  replacements.ClearRef();
-  GURL script_url = script_url_with_fragment.ReplaceComponents(replacements);
-  DCHECK(!script_url.has_ref() || script_url.ref().empty());
-  DCHECK(!script_url.is_empty());
-
-  // 3. If scriptURL’s scheme is not one of "http" and "https", reject promise
-  //    with a TypeError and abort these steps.
-  if (!script_url.SchemeIsHTTPOrHTTPS()) {
-    promise_reference->value().Reject(script::kTypeError);
-    return;
-  }
-
-  // 4. If any of the strings in scriptURL’s path contains either ASCII
-  //    case-insensitive "%2f" or ASCII case-insensitive "%5c", reject promise
-  //    with a TypeError and abort these steps.
-  if (PathContainsEscapedSlash(script_url)) {
-    promise_reference->value().Reject(script::kTypeError);
-    return;
-  }
-
-  DCHECK(client);
-  web::WindowOrWorkerGlobalScope* window_or_worker_global_scope =
-      client->GetWindowOrWorkerGlobalScope();
-  DCHECK(window_or_worker_global_scope);
-  web::CspDelegate* csp_delegate =
-      window_or_worker_global_scope->csp_delegate();
-  DCHECK(csp_delegate);
-  if (!csp_delegate->CanLoad(web::CspDelegate::kWorker, script_url,
-                             /* did_redirect*/ false)) {
-    promise_reference->value().Reject(new web::DOMException(
-        web::DOMException::kSecurityErr,
-        "Failed to register a ServiceWorker: The provided scriptURL ('" +
-            script_url.spec() + "') violates the Content Security Policy."));
-    return;
-  }
-
-  // 5. If scopeURL is null, set scopeURL to the result of parsing the string
-  //    "./" with scriptURL.
-  GURL scope_url = maybe_scope_url.value_or(script_url.Resolve("./"));
-
-  // 6. If scopeURL is failure, reject promise with a TypeError and abort these
-  //    steps.
-  if (scope_url.is_empty()) {
-    promise_reference->value().Reject(script::kTypeError);
-    return;
-  }
-
-  // 7. Set scopeURL’s fragment to null.
-  scope_url = scope_url.ReplaceComponents(replacements);
-  DCHECK(!scope_url.has_ref() || scope_url.ref().empty());
-  DCHECK(!scope_url.is_empty());
-
-  // 8. If scopeURL’s scheme is not one of "http" and "https", reject promise
-  //    with a TypeError and abort these steps.
-  if (!scope_url.SchemeIsHTTPOrHTTPS()) {
-    promise_reference->value().Reject(script::kTypeError);
-    return;
-  }
-
-  // 9. If any of the strings in scopeURL’s path contains either ASCII
-  //    case-insensitive "%2f" or ASCII case-insensitive "%5c", reject promise
-  //    with a TypeError and abort these steps.
-  if (PathContainsEscapedSlash(scope_url)) {
-    promise_reference->value().Reject(script::kTypeError);
-    return;
-  }
-
-  // 10. Let storage key be the result of running obtain a storage key given
-  //     client.
-  url::Origin storage_key = client->environment_settings()->ObtainStorageKey();
-
-  // 11. Let job be the result of running Create Job with register, storage key,
-  //     scopeURL, scriptURL, promise, and client.
-  std::unique_ptr<ServiceWorkerJobs::Job> job = jobs_->CreateJob(
-      ServiceWorkerJobs::kRegister, storage_key, scope_url, script_url,
-      ServiceWorkerJobs::JobPromiseType::Create(std::move(promise_reference)),
-      client);
-
-  // 12. Set job’s worker type to workerType.
-  // Cobalt only supports 'classic' worker type.
-
-  // 13. Set job’s update via cache mode to updateViaCache.
-  job->update_via_cache = update_via_cache;
-
-  // 14. Set job’s referrer to referrer.
-  // This is the same value as set in CreateJob().
-
-  // 15. Invoke Schedule Job with job.
-  jobs_->ScheduleJob(std::move(job));
-}
-
-void ServiceWorkerContext::SoftUpdate(
-    ServiceWorkerRegistrationObject* registration, bool force_bypass_cache) {
-  TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::SoftUpdate()");
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  DCHECK(registration);
-  // Algorithm for SoftUpdate:
-  //    https://www.w3.org/TR/2022/CRD-service-workers-20220712/#soft-update
-  // 1. Let newestWorker be the result of running Get Newest Worker algorithm
-  // passing registration as its argument.
-  ServiceWorkerObject* newest_worker = registration->GetNewestWorker();
-
-  // 2. If newestWorker is null, abort these steps.
-  if (newest_worker == nullptr) {
-    return;
-  }
-
-  // 3. Let job be the result of running Create Job with update, registration’s
-  // storage key, registration’s scope url, newestWorker’s script url, null, and
-  // null.
-  std::unique_ptr<ServiceWorkerJobs::Job> job = jobs_->CreateJobWithoutPromise(
-      ServiceWorkerJobs::kUpdate, registration->storage_key(),
-      registration->scope_url(), newest_worker->script_url());
-
-  // 4. Set job’s worker type to newestWorker’s type.
-  // Cobalt only supports 'classic' worker type.
-
-  // 5. Set job’s force bypass cache flag if forceBypassCache is true.
-  job->force_bypass_cache_flag = force_bypass_cache;
-
-  // 6. Invoke Schedule Job with job.
-  message_loop()->task_runner()->PostTask(
-      FROM_HERE, base::BindOnce(&ServiceWorkerJobs::ScheduleJob,
-                                base::Unretained(jobs_.get()), std::move(job)));
-  DCHECK(!job.get());
-}
-
-void ServiceWorkerContext::EnsureServiceWorkerStarted(
-    const url::Origin& storage_key, const GURL& client_url,
-    base::WaitableEvent* done_event) {
-  if (message_loop() != base::MessageLoop::current()) {
-    message_loop()->task_runner()->PostTask(
-        FROM_HERE,
-        base::BindOnce(&ServiceWorkerContext::EnsureServiceWorkerStarted,
-                       base::Unretained(this), storage_key, client_url,
-                       done_event));
-    return;
-  }
-  base::ScopedClosureRunner signal_done(base::BindOnce(
-      [](base::WaitableEvent* done_event) { done_event->Signal(); },
-      done_event));
-  base::TimeTicks start = base::TimeTicks::Now();
-  auto registration =
-      scope_to_registration_map_->GetRegistration(storage_key, client_url);
-  if (!registration) {
-    return;
-  }
-  auto service_worker_object = registration->active_worker();
-  if (!service_worker_object || service_worker_object->is_running()) {
-    return;
-  }
-  service_worker_object->ObtainWebAgentAndWaitUntilDone();
-}
-
-std::string* ServiceWorkerContext::RunServiceWorker(ServiceWorkerObject* worker,
-                                                    bool force_bypass_cache) {
-  TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::RunServiceWorker()");
-
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  DCHECK(worker);
-  // Algorithm for "Run Service Worker"
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-service-worker-algorithm
-
-  // 1. Let unsafeCreationTime be the unsafe shared current time.
-  auto unsafe_creation_time = base::TimeTicks::Now();
-  // 2. If serviceWorker is running, then return serviceWorker’s start status.
-  if (worker->is_running()) {
-    return worker->start_status();
-  }
-  // 3. If serviceWorker’s state is "redundant", then return failure.
-  if (worker->state() == kServiceWorkerStateRedundant) {
-    return nullptr;
-  }
-  // 4. Assert: serviceWorker’s start status is null.
-  DCHECK(worker->start_status() == nullptr);
-  // 5. Let script be serviceWorker’s script resource.
-  // 6. Assert: script is not null.
-  DCHECK(worker->HasScriptResource());
-  // 7. Let startFailed be false.
-  worker->store_start_failed(false);
-  // 8. Let agent be the result of obtaining a service worker agent, and run the
-  //    following steps in that context:
-  // 9. Wait for serviceWorker to be running, or for startFailed to be true.
-  worker->ObtainWebAgentAndWaitUntilDone();
-  // 10. If startFailed is true, then return failure.
-  if (worker->load_start_failed()) {
-    return nullptr;
-  }
-  // 11. Return serviceWorker’s start status.
-  return worker->start_status();
-}
-
-bool ServiceWorkerContext::WaitForAsynchronousExtensions(
-    const scoped_refptr<ServiceWorkerRegistrationObject>& registration) {
-  // TODO(b/240164388): Investigate a better approach for combining waiting
-  // for the ExtendableEvent while also allowing use of algorithms that run
-  // on the same thread from the event handler.
-  base::TimeTicks wait_start_time = base::TimeTicks::Now();
-  do {
-    if (registration->done_event()->TimedWait(
-            base::TimeDelta::FromMilliseconds(100)))
-      break;
-    base::MessageLoopCurrent::ScopedNestableTaskAllower allow;
-    base::RunLoop().RunUntilIdle();
-  } while ((base::TimeTicks::Now() - wait_start_time) <
-           kWaitForAsynchronousExtensionsTimeout);
-  return registration->done_event()->IsSignaled();
-}
-
-bool ServiceWorkerContext::IsAnyClientUsingRegistration(
-    ServiceWorkerRegistrationObject* registration) {
-  bool any_client_is_using = false;
-  for (auto& context : web_context_registrations_) {
-    // When a service worker client is controlled by a service worker, it is
-    // said that the service worker client is using the service worker’s
-    // containing service worker registration.
-    //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-control
-    if (context->is_controlled_by(registration->active_worker())) {
-      any_client_is_using = true;
-      break;
-    }
-  }
-  return any_client_is_using;
-}
-
-void ServiceWorkerContext::TryActivate(
-    ServiceWorkerRegistrationObject* registration) {
-  TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::TryActivate()");
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  // Algorithm for Try Activate:
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-activate-algorithm
-
-  // 1. If registration’s waiting worker is null, return.
-  if (!registration) return;
-  if (!registration->waiting_worker()) return;
-
-  // 2. If registration’s active worker is not null and registration’s active
-  //    worker's state is "activating", return.
-  if (registration->active_worker() &&
-      (registration->active_worker()->state() == kServiceWorkerStateActivating))
-    return;
-
-  // 3. Invoke Activate with registration if either of the following is true:
-
-  //    - registration’s active worker is null.
-  bool invoke_activate = registration->active_worker() == nullptr;
-
-  if (!invoke_activate) {
-    //    - The result of running Service Worker Has No Pending Events with
-    //      registration’s active worker is true...
-    if (ServiceWorkerHasNoPendingEvents(registration->active_worker())) {
-      //      ... and no service worker client is using registration...
-      bool any_client_using = IsAnyClientUsingRegistration(registration);
-      invoke_activate = !any_client_using;
-      //      ... or registration’s waiting worker's skip waiting flag is
-      //      set.
-      if (!invoke_activate && registration->waiting_worker()->skip_waiting())
-        invoke_activate = true;
-    }
-  }
-
-  if (invoke_activate) Activate(registration);
-}
-
-void ServiceWorkerContext::Activate(
-    ServiceWorkerRegistrationObject* registration) {
-  TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::Activate()");
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  // Algorithm for Activate:
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#activation-algorithm
-
-  // 1. If registration’s waiting worker is null, abort these steps.
-  if (registration->waiting_worker() == nullptr) return;
-  // 2. If registration’s active worker is not null, then:
-  if (registration->active_worker()) {
-    // 2.1. Terminate registration’s active worker.
-    TerminateServiceWorker(registration->active_worker());
-    // 2.2. Run the Update Worker State algorithm passing registration’s active
-    //      worker and "redundant" as the arguments.
-    UpdateWorkerState(registration->active_worker(),
-                      kServiceWorkerStateRedundant);
-  }
-  // 3. Run the Update Registration State algorithm passing registration,
-  //    "active" and registration’s waiting worker as the arguments.
-  UpdateRegistrationState(registration, kActive,
-                          registration->waiting_worker());
-  // 4. Run the Update Registration State algorithm passing registration,
-  //    "waiting" and null as the arguments.
-  UpdateRegistrationState(registration, kWaiting, nullptr);
-  // 5. Run the Update Worker State algorithm passing registration’s active
-  //    worker and "activating" as the arguments.
-  UpdateWorkerState(registration->active_worker(),
-                    kServiceWorkerStateActivating);
-  // 6. Let matchedClients be a list of service worker clients whose creation
-  //    URL matches registration’s storage key and registration’s scope url.
-  std::list<web::Context*> matched_clients;
-  for (auto& context : web_context_registrations_) {
-    url::Origin context_storage_key =
-        url::Origin::Create(context->environment_settings()->creation_url());
-    scoped_refptr<ServiceWorkerRegistrationObject> matched_registration =
-        scope_to_registration_map_->MatchServiceWorkerRegistration(
-            context_storage_key, registration->scope_url());
-    if (matched_registration == registration) {
-      matched_clients.push_back(context);
-    }
-  }
-  // 7. For each client of matchedClients, queue a task on client’s  responsible
-  //    event loop, using the DOM manipulation task source, to run the following
-  //    substeps:
-  for (auto& client : matched_clients) {
-    // 7.1. Let readyPromise be client’s global object's
-    //      ServiceWorkerContainer object’s ready
-    //      promise.
-    // 7.2. If readyPromise is null, then continue.
-    // 7.3. If readyPromise is pending, resolve
-    //      readyPromise with the the result of getting
-    //      the service worker registration object that
-    //      represents registration in readyPromise’s
-    //      relevant settings object.
-    client->message_loop()->task_runner()->PostTask(
-        FROM_HERE,
-        base::BindOnce(&ServiceWorkerContainer::MaybeResolveReadyPromise,
-                       base::Unretained(client->GetWindowOrWorkerGlobalScope()
-                                            ->navigator_base()
-                                            ->service_worker()
-                                            .get()),
-                       base::Unretained(registration)));
-  }
-  // 8. For each client of matchedClients:
-  // 8.1. If client is a window client, unassociate client’s responsible
-  //      document from its application cache, if it has one.
-  // 8.2. Else if client is a shared worker client, unassociate client’s
-  //      global object from its application cache, if it has one.
-  // Cobalt doesn't implement 'application cache':
-  //   https://www.w3.org/TR/2011/WD-html5-20110525/offline.html#applicationcache
-  // 9. For each service worker client client who is using registration:
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-use
-  for (const auto& client : web_context_registrations_) {
-    // When a service worker client is controlled by a service worker, it is
-    // said that the service worker client is using the service worker’s
-    // containing service worker registration.
-    //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-control
-    if (client->active_service_worker() &&
-        client->active_service_worker()
-                ->containing_service_worker_registration() == registration) {
-      // 9.1. Set client’s active worker to registration’s active worker.
-      client->set_active_service_worker(registration->active_worker());
-      // 9.2. Invoke Notify Controller Change algorithm with client as the
-      //      argument.
-      NotifyControllerChange(client);
-    }
-  }
-  // 10. Let activeWorker be registration’s active worker.
-  ServiceWorkerObject* active_worker = registration->active_worker();
-  bool activated = true;
-  // 11. If the result of running the Should Skip Event algorithm with
-  //     activeWorker and "activate" is false, then:
-  DCHECK(active_worker);
-  if (!active_worker->ShouldSkipEvent(base::Tokens::activate())) {
-    // 11.1. If the result of running the Run Service Worker algorithm with
-    //       activeWorker is not failure, then:
-    auto* run_result = RunServiceWorker(active_worker);
-    if (run_result) {
-      // 11.1.1. Queue a task task on activeWorker’s event loop using the DOM
-      //         manipulation task source to run the following steps:
-      DCHECK_EQ(active_worker->web_agent()->context(),
-                active_worker->worker_global_scope()
-                    ->environment_settings()
-                    ->context());
-      DCHECK(registration->done_event()->IsSignaled());
-      registration->done_event()->Reset();
-      active_worker->web_agent()
-          ->context()
-          ->message_loop()
-          ->task_runner()
-          ->PostBlockingTask(
-              FROM_HERE,
-              base::Bind(
-                  [](ServiceWorkerObject* active_worker,
-                     base::WaitableEvent* done_event) {
-                    auto done_callback =
-                        base::BindOnce([](base::WaitableEvent* done_event,
-                                          bool) { done_event->Signal(); },
-                                       done_event);
-                    auto* settings = active_worker->web_agent()
-                                         ->context()
-                                         ->environment_settings();
-                    scoped_refptr<ExtendableEvent> event(
-                        new ExtendableEvent(settings, base::Tokens::activate(),
-                                            std::move(done_callback)));
-                    // 11.1.1.1. Let e be the result of creating an event with
-                    //           ExtendableEvent.
-                    // 11.1.1.2. Initialize e’s type attribute to activate.
-                    // 11.1.1.3. Dispatch e at activeWorker’s global object.
-                    active_worker->worker_global_scope()->DispatchEvent(event);
-                    // 11.1.1.4. WaitForAsynchronousExtensions: Wait, in
-                    //           parallel, until e is not active.
-                    if (!event->IsActive()) {
-                      // If the event handler doesn't use waitUntil(), it will
-                      // already no longer be active, and there will never be a
-                      // callback to signal the done event.
-                      done_event->Signal();
-                    }
-                  },
-                  base::Unretained(active_worker), registration->done_event()));
-      // 11.1.2. Wait for task to have executed or been discarded.
-      // This waiting is done inside PostBlockingTask above.
-      // 11.1.3. Wait for the step labeled WaitForAsynchronousExtensions to
-      //         complete.
-      // TODO(b/240164388): Investigate a better approach for combining waiting
-      // for the ExtendableEvent while also allowing use of algorithms that run
-      // on the same thread from the event handler.
-      if (!WaitForAsynchronousExtensions(registration)) {
-        // Timeout
-        activated = false;
-      }
-    } else {
-      activated = false;
-    }
-  }
-  // 12. Run the Update Worker State algorithm passing registration’s active
-  //     worker and "activated" as the arguments.
-  if (activated && registration->active_worker()) {
-    UpdateWorkerState(registration->active_worker(),
-                      kServiceWorkerStateActivated);
-
-    // Persist registration since the waiting_worker has been updated to nullptr
-    // and the active_worker has been updated to the previous waiting_worker.
-    scope_to_registration_map_->PersistRegistration(registration->storage_key(),
-                                                    registration->scope_url());
-  }
-}
-
-void ServiceWorkerContext::NotifyControllerChange(web::Context* client) {
-  // Algorithm for Notify Controller Change:
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#notify-controller-change-algorithm
-  // 1. Assert: client is not null.
-  DCHECK(client);
-
-  // 2. If client is an environment settings object, queue a task to fire an
-  //    event named controllerchange at the ServiceWorkerContainer object that
-  //    client is associated with.
-  client->message_loop()->task_runner()->PostTask(
-      FROM_HERE, base::Bind(
-                     [](web::Context* client) {
-                       client->GetWindowOrWorkerGlobalScope()
-                           ->navigator_base()
-                           ->service_worker()
-                           ->DispatchEvent(new web::Event(
-                               base::Tokens::controllerchange()));
-                     },
-                     client));
-}
-
-bool ServiceWorkerContext::ServiceWorkerHasNoPendingEvents(
-    ServiceWorkerObject* worker) {
-  // Algorithm for Service Worker Has No Pending Events
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-has-no-pending-events
-  // TODO(b/240174245): Implement this using the 'set of extended events'.
-  NOTIMPLEMENTED();
-
-  // 1. For each event of worker’s set of extended events:
-  // 1.1. If event is active, return false.
-  // 2. Return true.
-  return true;
-}
-
-void ServiceWorkerContext::ClearRegistration(
-    ServiceWorkerRegistrationObject* registration) {
-  TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::ClearRegistration()");
-  // Algorithm for Clear Registration:
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clear-registration-algorithm
-  // 1. Run the following steps atomically.
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-
-  // 2. If registration’s installing worker is not null, then:
-  ServiceWorkerObject* installing_worker = registration->installing_worker();
-  if (installing_worker) {
-    // 2.1. Terminate registration’s installing worker.
-    TerminateServiceWorker(installing_worker);
-    // 2.2. Run the Update Worker State algorithm passing registration’s
-    //      installing worker and "redundant" as the arguments.
-    UpdateWorkerState(installing_worker, kServiceWorkerStateRedundant);
-    // 2.3. Run the Update Registration State algorithm passing registration,
-    //      "installing" and null as the arguments.
-    UpdateRegistrationState(registration, kInstalling, nullptr);
-  }
-
-  // 3. If registration’s waiting worker is not null, then:
-  ServiceWorkerObject* waiting_worker = registration->waiting_worker();
-  if (waiting_worker) {
-    // 3.1. Terminate registration’s waiting worker.
-    TerminateServiceWorker(waiting_worker);
-    // 3.2. Run the Update Worker State algorithm passing registration’s
-    //      waiting worker and "redundant" as the arguments.
-    UpdateWorkerState(waiting_worker, kServiceWorkerStateRedundant);
-    // 3.3. Run the Update Registration State algorithm passing registration,
-    //      "waiting" and null as the arguments.
-    UpdateRegistrationState(registration, kWaiting, nullptr);
-  }
-
-  // 4. If registration’s active worker is not null, then:
-  ServiceWorkerObject* active_worker = registration->active_worker();
-  if (active_worker) {
-    // 4.1. Terminate registration’s active worker.
-    TerminateServiceWorker(active_worker);
-    // 4.2. Run the Update Worker State algorithm passing registration’s
-    //      active worker and "redundant" as the arguments.
-    UpdateWorkerState(active_worker, kServiceWorkerStateRedundant);
-    // 4.3. Run the Update Registration State algorithm passing registration,
-    //      "active" and null as the arguments.
-    UpdateRegistrationState(registration, kActive, nullptr);
-  }
-
-  // Persist registration since the waiting_worker and active_worker have
-  // been updated to nullptr. This will remove any persisted registration
-  // if one exists.
-  scope_to_registration_map_->PersistRegistration(registration->storage_key(),
-                                                  registration->scope_url());
-}
-
-void ServiceWorkerContext::TryClearRegistration(
-    ServiceWorkerRegistrationObject* registration) {
-  TRACE_EVENT0("cobalt::worker",
-               "ServiceWorkerContext::TryClearRegistration()");
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  // Algorithm for Try Clear Registration:
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-clear-registration-algorithm
-
-  // 1. Invoke Clear Registration with registration if no service worker client
-  // is using registration and all of the following conditions are true:
-  if (IsAnyClientUsingRegistration(registration)) return;
-
-  //    . registration’s installing worker is null or the result of running
-  //      Service Worker Has No Pending Events with registration’s installing
-  //      worker is true.
-  if (registration->installing_worker() &&
-      !ServiceWorkerHasNoPendingEvents(registration->installing_worker()))
-    return;
-
-  //    . registration’s waiting worker is null or the result of running
-  //      Service Worker Has No Pending Events with registration’s waiting
-  //      worker is true.
-  if (registration->waiting_worker() &&
-      !ServiceWorkerHasNoPendingEvents(registration->waiting_worker()))
-    return;
-
-  //    . registration’s active worker is null or the result of running
-  //      ServiceWorker Has No Pending Events with registration’s active worker
-  //      is true.
-  if (registration->active_worker() &&
-      !ServiceWorkerHasNoPendingEvents(registration->active_worker()))
-    return;
-
-  ClearRegistration(registration);
-}
-
-void ServiceWorkerContext::UpdateRegistrationState(
-    ServiceWorkerRegistrationObject* registration, RegistrationState target,
-    const scoped_refptr<ServiceWorkerObject>& source) {
-  TRACE_EVENT2("cobalt::worker",
-               "ServiceWorkerContext::UpdateRegistrationState()", "target",
-               target, "source", source);
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  DCHECK(registration);
-  // Algorithm for Update Registration State:
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-registration-state-algorithm
-
-  // 1. Let registrationObjects be an array containing all the
-  //    ServiceWorkerRegistration objects associated with registration.
-  // This is implemented with a call to LookupServiceWorkerRegistration for each
-  // registered web context.
-
-  switch (target) {
-    // 2. If target is "installing", then:
-    case kInstalling: {
-      // 2.1. Set registration’s installing worker to source.
-      registration->set_installing_worker(source);
-      // 2.2. For each registrationObject in registrationObjects:
-      for (auto& context : web_context_registrations_) {
-        // 2.2.1. Queue a task to...
-        context->message_loop()->task_runner()->PostBlockingTask(
-            FROM_HERE,
-            base::Bind(
-                [](web::Context* context,
-                   ServiceWorkerRegistrationObject* registration) {
-                  // 2.2.1. ... set the installing attribute of
-                  //        registrationObject to null if registration’s
-                  //        installing worker is null, or the result of getting
-                  //        the service worker object that represents
-                  //        registration’s installing worker in
-                  //        registrationObject’s relevant settings object.
-                  auto registration_object =
-                      context->LookupServiceWorkerRegistration(registration);
-                  if (registration_object) {
-                    registration_object->set_installing(
-                        context->GetServiceWorker(
-                            registration->installing_worker()));
-                  }
-                },
-                context, base::Unretained(registration)));
-      }
-      break;
-    }
-    // 3. Else if target is "waiting", then:
-    case kWaiting: {
-      // 3.1. Set registration’s waiting worker to source.
-      registration->set_waiting_worker(source);
-      // 3.2. For each registrationObject in registrationObjects:
-      for (auto& context : web_context_registrations_) {
-        // 3.2.1. Queue a task to...
-        context->message_loop()->task_runner()->PostBlockingTask(
-            FROM_HERE,
-            base::Bind(
-                [](web::Context* context,
-                   ServiceWorkerRegistrationObject* registration) {
-                  // 3.2.1. ... set the waiting attribute of registrationObject
-                  //        to null if registration’s waiting worker is null, or
-                  //        the result of getting the service worker object that
-                  //        represents registration’s waiting worker in
-                  //        registrationObject’s relevant settings object.
-                  auto registration_object =
-                      context->LookupServiceWorkerRegistration(registration);
-                  if (registration_object) {
-                    registration_object->set_waiting(context->GetServiceWorker(
-                        registration->waiting_worker()));
-                  }
-                },
-                context, base::Unretained(registration)));
-      }
-      break;
-    }
-    // 4. Else if target is "active", then:
-    case kActive: {
-      // 4.1. Set registration’s active worker to source.
-      registration->set_active_worker(source);
-      // 4.2. For each registrationObject in registrationObjects:
-      for (auto& context : web_context_registrations_) {
-        // 4.2.1. Queue a task to...
-        context->message_loop()->task_runner()->PostBlockingTask(
-            FROM_HERE,
-            base::Bind(
-                [](web::Context* context,
-                   ServiceWorkerRegistrationObject* registration) {
-                  // 4.2.1. ... set the active attribute of registrationObject
-                  //        to null if registration’s active worker is null, or
-                  //        the result of getting the service worker object that
-                  //        represents registration’s active worker in
-                  //        registrationObject’s relevant settings object.
-                  auto registration_object =
-                      context->LookupServiceWorkerRegistration(registration);
-                  if (registration_object) {
-                    registration_object->set_active(context->GetServiceWorker(
-                        registration->active_worker()));
-                  }
-                },
-                context, base::Unretained(registration)));
-      }
-      break;
-    }
-    default:
-      NOTREACHED();
-  }
-}
-
-void ServiceWorkerContext::UpdateWorkerState(ServiceWorkerObject* worker,
-                                             ServiceWorkerState state) {
-  TRACE_EVENT1("cobalt::worker", "ServiceWorkerContext::UpdateWorkerState()",
-               "state", state);
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  DCHECK(worker);
-  if (!worker) {
-    return;
-  }
-  // Algorithm for Update Worker State:
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-state-algorithm
-  // 1. Assert: state is not "parsed".
-  DCHECK_NE(kServiceWorkerStateParsed, state);
-  // 2. Set worker's state to state.
-  worker->set_state(state);
-  auto worker_origin = loader::Origin(worker->script_url());
-  // 3. Let settingsObjects be all environment settings objects whose origin is
-  //    worker's script url's origin.
-  // 4. For each settingsObject of settingsObjects...
-  for (auto& context : web_context_registrations_) {
-    if (context->environment_settings()->GetOrigin() == worker_origin) {
-      // 4. ... queue a task on
-      //    settingsObject's responsible event loop in the DOM manipulation task
-      //    source to run the following steps:
-      context->message_loop()->task_runner()->PostBlockingTask(
-          FROM_HERE, base::Bind(
-                         [](web::Context* context, ServiceWorkerObject* worker,
-                            ServiceWorkerState state) {
-                           DCHECK_EQ(context->message_loop(),
-                                     base::MessageLoop::current());
-                           // 4.1. Let objectMap be settingsObject's service
-                           // worker object
-                           //      map.
-                           // 4.2. If objectMap[worker] does not exist, then
-                           // abort these
-                           //      steps.
-                           // 4.3. Let  workerObj be objectMap[worker].
-                           auto worker_obj =
-                               context->LookupServiceWorker(worker);
-                           if (worker_obj) {
-                             // 4.4. Set workerObj's state to state.
-                             worker_obj->set_state(state);
-                             // 4.5. Fire an event named statechange at
-                             // workerObj.
-                             worker_obj->DispatchEvent(
-                                 new web::Event(base::Tokens::statechange()));
-                           }
-                         },
-                         context, base::Unretained(worker), state));
-    }
-  }
-}
-
-void ServiceWorkerContext::HandleServiceWorkerClientUnload(
-    web::Context* client) {
-  TRACE_EVENT0("cobalt::worker",
-               "ServiceWorkerContext::HandleServiceWorkerClientUnload()");
-  // Algorithm for Handle Servicer Worker Client Unload:
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-client-unload-algorithm
-  DCHECK(client);
-  // 1. Run the following steps atomically.
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-
-  // 2. Let registration be the service worker registration used by client.
-  // 3. If registration is null, abort these steps.
-  ServiceWorkerObject* active_service_worker = client->active_service_worker();
-  if (!active_service_worker) return;
-  ServiceWorkerRegistrationObject* registration =
-      active_service_worker->containing_service_worker_registration();
-  if (!registration) return;
-
-  // 4. If any other service worker client is using registration, abort these
-  //    steps.
-  // Ensure the client is already removed from the registrations when this runs.
-  DCHECK(web_context_registrations_.end() ==
-         web_context_registrations_.find(client));
-  if (IsAnyClientUsingRegistration(registration)) return;
-
-  // 5. If registration is unregistered, invoke Try Clear Registration with
-  //    registration.
-  if (scope_to_registration_map_ &&
-      scope_to_registration_map_->IsUnregistered(registration)) {
-    TryClearRegistration(registration);
-  }
-
-  // 6. Invoke Try Activate with registration.
-  TryActivate(registration);
-}
-
-void ServiceWorkerContext::TerminateServiceWorker(ServiceWorkerObject* worker) {
-  TRACE_EVENT0("cobalt::worker",
-               "ServiceWorkerContext::TerminateServiceWorker()");
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  // Algorithm for Terminate Service Worker:
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#terminate-service-worker
-  // 1. Run the following steps in parallel with serviceWorker’s main loop:
-  // This runs in the ServiceWorkerRegistry thread.
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-
-  // 1.1. Let serviceWorkerGlobalScope be serviceWorker’s global object.
-  WorkerGlobalScope* service_worker_global_scope =
-      worker->worker_global_scope();
-
-  // 1.2. Set serviceWorkerGlobalScope’s closing flag to true.
-  if (service_worker_global_scope != nullptr)
-    service_worker_global_scope->set_closing_flag(true);
-
-  // 1.3. Remove all the items from serviceWorker’s set of extended events.
-  // TODO(b/240174245): Implement 'set of extended events'.
-
-  // 1.4. If there are any tasks, whose task source is either the handle fetch
-  //      task source or the handle functional event task source, queued in
-  //      serviceWorkerGlobalScope’s event loop’s task queues, queue them to
-  //      serviceWorker’s containing service worker registration’s corresponding
-  //      task queues in the same order using their original task sources, and
-  //      discard all the tasks (including tasks whose task source is neither
-  //      the handle fetch task source nor the handle functional event task
-  //      source) from serviceWorkerGlobalScope’s event loop’s task queues
-  //      without processing them.
-  // TODO(b/234787641): Queue tasks to the registration.
-
-  // Note: This step is not in the spec, but without this step the service
-  // worker object map will always keep an entry with a service worker instance
-  // for the terminated service worker, which besides leaking memory can lead to
-  // unexpected behavior when new service worker objects are created with the
-  // same key for the service worker object map (which in Cobalt's case
-  // happens when a new service worker object is constructed at the same
-  // memory address).
-  for (auto& context : web_context_registrations_) {
-    context->message_loop()->task_runner()->PostBlockingTask(
-        FROM_HERE, base::Bind(
-                       [](web::Context* context, ServiceWorkerObject* worker) {
-                         auto worker_obj = context->LookupServiceWorker(worker);
-                         if (worker_obj) {
-                           worker_obj->set_state(kServiceWorkerStateRedundant);
-                           worker_obj->DispatchEvent(
-                               new web::Event(base::Tokens::statechange()));
-                         }
-                         context->RemoveServiceWorker(worker);
-                       },
-                       context, base::Unretained(worker)));
-  }
-
-  // 1.5. Abort the script currently running in serviceWorker.
-  if (worker->is_running()) {
-    worker->Abort();
-  }
-
-  // 1.6. Set serviceWorker’s start status to null.
-  worker->set_start_status(nullptr);
-}
-
-void ServiceWorkerContext::MaybeResolveReadyPromiseSubSteps(
-    web::Context* client) {
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  // Algorithm for Sub steps of ServiceWorkerContainer.ready():
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-ready
-
-  //    3.1. Let client by this's service worker client.
-  //    3.2. Let storage key be the result of running obtain a storage
-  //         key given client.
-  url::Origin storage_key = client->environment_settings()->ObtainStorageKey();
-  //    3.3. Let registration be the result of running Match Service
-  //         Worker Registration given storage key and client’s
-  //         creation URL.
-  // TODO(b/234659851): Investigate whether this should use the creation URL
-  // directly instead.
-  const GURL& base_url = client->environment_settings()->creation_url();
-  GURL client_url = base_url.Resolve("");
-  scoped_refptr<ServiceWorkerRegistrationObject> registration =
-      scope_to_registration_map_->MatchServiceWorkerRegistration(storage_key,
-                                                                 client_url);
-  //    3.3. If registration is not null, and registration’s active
-  //         worker is not null, queue a task on readyPromise’s
-  //         relevant settings object's responsible event loop, using
-  //         the DOM manipulation task source, to resolve readyPromise
-  //         with the result of getting the service worker
-  //         registration object that represents registration in
-  //         readyPromise’s relevant settings object.
-  if (registration && registration->active_worker()) {
-    client->message_loop()->task_runner()->PostTask(
-        FROM_HERE,
-        base::BindOnce(&ServiceWorkerContainer::MaybeResolveReadyPromise,
-                       base::Unretained(client->GetWindowOrWorkerGlobalScope()
-                                            ->navigator_base()
-                                            ->service_worker()
-                                            .get()),
-                       registration));
-  }
-}
-
-void ServiceWorkerContext::GetRegistrationSubSteps(
-    const url::Origin& storage_key, const GURL& client_url,
-    web::Context* client,
-    std::unique_ptr<script::ValuePromiseWrappable::Reference>
-        promise_reference) {
-  TRACE_EVENT0("cobalt::worker",
-               "ServiceWorkerContext::GetRegistrationSubSteps()");
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  // Algorithm for Sub steps of ServiceWorkerContainer.getRegistration():
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-getRegistration
-
-  // 8.1. Let registration be the result of running Match Service Worker
-  //      Registration algorithm with clientURL as its argument.
-  scoped_refptr<ServiceWorkerRegistrationObject> registration =
-      scope_to_registration_map_->MatchServiceWorkerRegistration(storage_key,
-                                                                 client_url);
-  // 8.2. If registration is null, resolve promise with undefined and abort
-  //      these steps.
-  // 8.3. Resolve promise with the result of getting the service worker
-  //      registration object that represents registration in promise’s
-  //      relevant settings object.
-  client->message_loop()->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](web::Context* client,
-             std::unique_ptr<script::ValuePromiseWrappable::Reference> promise,
-             scoped_refptr<ServiceWorkerRegistrationObject> registration) {
-            TRACE_EVENT0(
-                "cobalt::worker",
-                "ServiceWorkerContext::GetRegistrationSubSteps() Resolve");
-            promise->value().Resolve(
-                client->GetServiceWorkerRegistration(registration));
-          },
-          client, std::move(promise_reference), registration));
-}
-
-void ServiceWorkerContext::GetRegistrationsSubSteps(
-    const url::Origin& storage_key, web::Context* client,
-    std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
-        promise_reference) {
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  std::vector<scoped_refptr<ServiceWorkerRegistrationObject>>
-      registration_objects =
-          scope_to_registration_map_->GetRegistrations(storage_key);
-  client->message_loop()->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](web::Context* client,
-             std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
-                 promise,
-             std::vector<scoped_refptr<ServiceWorkerRegistrationObject>>
-                 registration_objects) {
-            TRACE_EVENT0(
-                "cobalt::worker",
-                "ServiceWorkerContext::GetRegistrationSubSteps() Resolve");
-            script::Sequence<scoped_refptr<script::Wrappable>> registrations;
-            for (auto registration_object : registration_objects) {
-              registrations.push_back(scoped_refptr<script::Wrappable>(
-                  client->GetServiceWorkerRegistration(registration_object)
-                      .get()));
-            }
-            promise->value().Resolve(std::move(registrations));
-          },
-          client, std::move(promise_reference),
-          std::move(registration_objects)));
-}
-
-void ServiceWorkerContext::SkipWaitingSubSteps(
-    web::Context* worker_context, ServiceWorkerObject* service_worker,
-    std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference) {
-  TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::SkipWaitingSubSteps()");
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  // Check if the client web context is still active. This may trigger if
-  // skipWaiting() was called and service worker installation fails.
-  if (!IsWebContextRegistered(worker_context)) {
-    promise_reference.release();
-    return;
-  }
-
-  // Algorithm for Sub steps of ServiceWorkerGlobalScope.skipWaiting():
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-serviceworkerglobalscope-skipwaiting
-
-  // 2.1. Set service worker's skip waiting flag.
-  service_worker->set_skip_waiting();
-
-  // 2.2. Invoke Try Activate with service worker's containing service worker
-  // registration.
-  TryActivate(service_worker->containing_service_worker_registration());
-
-  // 2.3. Resolve promise with undefined.
-  worker_context->message_loop()->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](std::unique_ptr<script::ValuePromiseVoid::Reference> promise) {
-            promise->value().Resolve();
-          },
-          std::move(promise_reference)));
-}
-
-void ServiceWorkerContext::WaitUntilSubSteps(
-    ServiceWorkerRegistrationObject* registration) {
-  TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::WaitUntilSubSteps()");
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  // Sub steps for WaitUntil.
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-extendableevent-waituntil
-  // 5.2.2. If registration is unregistered, invoke Try Clear Registration
-  //        with registration.
-  if (scope_to_registration_map_->IsUnregistered(registration)) {
-    TryClearRegistration(registration);
-  }
-  // 5.2.3. If registration is not null, invoke Try Activate with
-  //        registration.
-  if (registration) {
-    TryActivate(registration);
-  }
-}
-
-void ServiceWorkerContext::ClientsGetSubSteps(
-    web::Context* worker_context,
-    ServiceWorkerObject* associated_service_worker,
-    std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference,
-    const std::string& id) {
-  TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::ClientsGetSubSteps()");
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  // Check if the client web context is still active. This may trigger if
-  // Clients.get() was called and service worker installation fails.
-  if (!IsWebContextRegistered(worker_context)) {
-    promise_reference.release();
-    return;
-  }
-  // Parallel sub steps (2) for algorithm for Clients.get(id):
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-get
-  // 2.1. For each service worker client client where the result of running
-  //      obtain a storage key given client equals the associated service
-  //      worker's containing service worker registration's storage key:
-  const url::Origin& storage_key =
-      associated_service_worker->containing_service_worker_registration()
-          ->storage_key();
-  for (auto& client : web_context_registrations_) {
-    url::Origin client_storage_key =
-        client->environment_settings()->ObtainStorageKey();
-    if (client_storage_key.IsSameOriginWith(storage_key)) {
-      // 2.1.1. If client’s id is not id, continue.
-      if (client->environment_settings()->id() != id) continue;
-
-      // 2.1.2. Wait for either client’s execution ready flag to be set or for
-      //        client’s discarded flag to be set.
-      // Web Contexts exist only in the web_context_registrations_ set when they
-      // are both execution ready and not discarded.
-
-      // 2.1.3. If client’s execution ready flag is set, then invoke Resolve Get
-      //        Client Promise with client and promise, and abort these steps.
-      ResolveGetClientPromise(client, worker_context,
-                              std::move(promise_reference));
-      return;
-    }
-  }
-  // 2.2. Resolve promise with undefined.
-  worker_context->message_loop()->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](std::unique_ptr<script::ValuePromiseWrappable::Reference>
-                 promise_reference) {
-            TRACE_EVENT0("cobalt::worker",
-                         "ServiceWorkerContext::ClientsGetSubSteps() Resolve");
-            promise_reference->value().Resolve(scoped_refptr<Client>());
-          },
-          std::move(promise_reference)));
-}
-
-void ServiceWorkerContext::ClientsMatchAllSubSteps(
-    web::Context* worker_context,
-    ServiceWorkerObject* associated_service_worker,
-    std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
-        promise_reference,
-    bool include_uncontrolled, ClientType type) {
-  TRACE_EVENT0("cobalt::worker",
-               "ServiceWorkerContext::ClientsMatchAllSubSteps()");
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  // Check if the worker web context is still active. This may trigger if
-  // Clients.matchAll() was called and service worker installation fails.
-  if (!IsWebContextRegistered(worker_context)) {
-    promise_reference.release();
-    return;
-  }
-
-  // Parallel sub steps (2) for algorithm for Clients.matchAll():
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-matchall
-  // 2.1. Let targetClients be a new list.
-  std::list<web::Context*> target_clients;
-
-  // 2.2. For each service worker client client where the result of running
-  //      obtain a storage key given client equals the associated service
-  //      worker's containing service worker registration's storage key:
-  const url::Origin& storage_key =
-      associated_service_worker->containing_service_worker_registration()
-          ->storage_key();
-  for (auto& client : web_context_registrations_) {
-    url::Origin client_storage_key =
-        client->environment_settings()->ObtainStorageKey();
-    if (client_storage_key.IsSameOriginWith(storage_key)) {
-      // 2.2.1. If client’s execution ready flag is unset or client’s discarded
-      //        flag is set, continue.
-      // Web Contexts exist only in the web_context_registrations_ set when they
-      // are both execution ready and not discarded.
-
-      // 2.2.2. If client is not a secure context, continue.
-      // In production, Cobalt requires https, therefore all workers and their
-      // owners are secure contexts.
-
-      // 2.2.3. If options["includeUncontrolled"] is false, and if client’s
-      //        active service worker is not the associated service worker,
-      //        continue.
-      if (!include_uncontrolled &&
-          (client->active_service_worker() != associated_service_worker)) {
-        continue;
-      }
-
-      // 2.2.4. Add client to targetClients.
-      target_clients.push_back(client);
-    }
-  }
-
-  // 2.3. Let matchedWindowData be a new list.
-  std::unique_ptr<std::vector<WindowData>> matched_window_data(
-      new std::vector<WindowData>);
-
-  // 2.4. Let matchedClients be a new list.
-  std::unique_ptr<std::vector<web::Context*>> matched_clients(
-      new std::vector<web::Context*>);
-
-  // 2.5. For each service worker client client in targetClients:
-  for (auto* client : target_clients) {
-    auto* global_scope = client->GetWindowOrWorkerGlobalScope();
-
-    if ((type == kClientTypeWindow || type == kClientTypeAll) &&
-        (global_scope->IsWindow())) {
-      // 2.5.1. If options["type"] is "window" or "all", and client is not an
-      //        environment settings object or is a window client, then:
-
-      // 2.5.1.1. Let windowData be [ "client" -> client, "ancestorOriginsList"
-      //          -> a new list ].
-      WindowData window_data(client->environment_settings());
-
-      // 2.5.1.2. Let browsingContext be null.
-
-      // 2.5.1.3. Let isClientEnumerable be true.
-      // For Cobalt, isClientEnumerable is always true because the clauses that
-      // would set it to false in 2.5.1.6. do not apply to Cobalt.
-
-      // 2.5.1.4. If client is an environment settings object, set
-      //          browsingContext to client’s global object's browsing context.
-      // 2.5.1.5. Else, set browsingContext to client’s target browsing context.
-      web::Context* browsing_context = client;
-
-      // 2.5.1.6. Queue a task task to run the following substeps on
-      //          browsingContext’s event loop using the user interaction task
-      //          source:
-      // Note: The task below does not currently perform any actual
-      // functionality. It is included however to help future implementation for
-      // fetching values for WindowClient properties, with similar logic
-      // existing in ResolveGetClientPromise.
-      browsing_context->message_loop()->task_runner()->PostBlockingTask(
-          FROM_HERE, base::Bind(
-                         [](WindowData* window_data) {
-                           // 2.5.1.6.1. If browsingContext has been discarded,
-                           //            then set isClientEnumerable to false
-                           //            and abort these steps.
-                           // 2.5.1.6.2. If client is a window client and
-                           //            client’s responsible document is not
-                           //            browsingContext’s active document, then
-                           //            set isClientEnumerable to false and
-                           //            abort these steps.
-                           // In Cobalt, the document of a window browsing
-                           // context doesn't change: When a new document is
-                           // created, a new browsing context is created with
-                           // it.
-
-                           // 2.5.1.6.3. Set windowData["frameType"] to the
-                           //            result of running Get Frame Type with
-                           //            browsingContext.
-                           // Cobalt does not support nested or auxiliary
-                           // browsing contexts.
-                           // 2.5.1.6.4. Set windowData["visibilityState"] to
-                           //            browsingContext’s active document's
-                           //            visibilityState attribute value.
-                           // 2.5.1.6.5. Set windowData["focusState"] to the
-                           //            result of running the has focus steps
-                           //            with browsingContext’s active document
-                           //            as the argument.
-
-                           // 2.5.1.6.6. If client is a window client, then set
-                           //            windowData["ancestorOriginsList"] to
-                           //            browsingContext’s active document's
-                           //            relevant global object's Location
-                           //            object’s ancestor origins list's
-                           //            associated list.
-                           // Cobalt does not implement
-                           // Location.ancestorOrigins.
-                         },
-                         &window_data));
-
-      // 2.5.1.7. Wait for task to have executed.
-      // The task above is posted as a blocking task.
-
-      // 2.5.1.8. If isClientEnumerable is true, then:
-
-      // 2.5.1.8.1. Add windowData to matchedWindowData.
-      matched_window_data->emplace_back(window_data);
-
-      // 2.5.2. Else if options["type"] is "worker" or "all" and client is a
-      //        dedicated worker client, or options["type"] is "sharedworker" or
-      //        "all" and client is a shared worker client, then:
-    } else if (((type == kClientTypeWorker || type == kClientTypeAll) &&
-                global_scope->IsDedicatedWorker())) {
-      // Note: Cobalt does not support shared workers.
-      // 2.5.2.1. Add client to matchedClients.
-      matched_clients->emplace_back(client);
-    }
-  }
-
-  // 2.6. Queue a task to run the following steps on promise’s relevant
-  // settings object's responsible event loop using the DOM manipulation
-  // task source:
-  worker_context->message_loop()->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
-                 promise_reference,
-             std::unique_ptr<std::vector<WindowData>> matched_window_data,
-             std::unique_ptr<std::vector<web::Context*>> matched_clients) {
-            TRACE_EVENT0("cobalt::worker",
-                         "ServiceWorkerContext::ClientsMatchAllSubSteps() "
-                         "Resolve Promise");
-            // 2.6.1. Let clientObjects be a new list.
-            script::Sequence<scoped_refptr<script::Wrappable>> client_objects;
-
-            // 2.6.2. For each windowData in matchedWindowData:
-            for (auto& window_data : *matched_window_data) {
-              // 2.6.2.1. Let WindowClient be the result of running
-              //          Create Window Client algorithm with
-              //          windowData["client"],
-              //          windowData["frameType"],
-              //          windowData["visibilityState"],
-              //          windowData["focusState"], and
-              //          windowData["ancestorOriginsList"] as the
-              //          arguments.
-              // TODO(b/235838698): Implement WindowClient methods.
-              scoped_refptr<Client> window_client =
-                  WindowClient::Create(window_data);
-
-              // 2.6.2.2. Append WindowClient to clientObjects.
-              client_objects.push_back(window_client);
-            }
-
-            // 2.6.3. For each client in matchedClients:
-            for (auto& client : *matched_clients) {
-              // 2.6.3.1. Let clientObject be the result of running
-              //          Create Client algorithm with client as the
-              //          argument.
-              scoped_refptr<Client> client_object =
-                  Client::Create(client->environment_settings());
-
-              // 2.6.3.2. Append clientObject to clientObjects.
-              client_objects.push_back(client_object);
-            }
-            // 2.6.4. Sort clientObjects such that:
-            //        . WindowClient objects whose browsing context has been
-            //          focused are placed first, sorted in the most recently
-            //          focused order.
-            //        . WindowClient objects whose browsing context has never
-            //          been focused are placed next, sorted in their service
-            //          worker client's creation order.
-            //        . Client objects whose associated service worker client is
-            //          a worker client are placed next, sorted in their service
-            //          worker client's creation order.
-            // TODO(b/235876598): Implement sorting of clientObjects.
-
-            // 2.6.5. Resolve promise with a new frozen array of clientObjects
-            //        in promise’s relevant Realm.
-            promise_reference->value().Resolve(client_objects);
-          },
-          std::move(promise_reference), std::move(matched_window_data),
-          std::move(matched_clients)));
-}
-
-void ServiceWorkerContext::ClaimSubSteps(
-    web::Context* worker_context,
-    ServiceWorkerObject* associated_service_worker,
-    std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference) {
-  TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::ClaimSubSteps()");
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-
-  // Check if the client web context is still active. This may trigger if
-  // Clients.claim() was called and service worker installation fails.
-  if (!IsWebContextRegistered(worker_context)) {
-    promise_reference.release();
-    return;
-  }
-
-  // Parallel sub steps (3) for algorithm for Clients.claim():
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-clients-claim
-  std::list<web::Context*> target_clients;
-
-  // 3.1. For each service worker client client where the result of running
-  //      obtain a storage key given client equals the service worker's
-  //      containing service worker registration's storage key:
-  const url::Origin& storage_key =
-      associated_service_worker->containing_service_worker_registration()
-          ->storage_key();
-  for (auto& client : web_context_registrations_) {
-    // Don't claim to be our own service worker.
-    if (client == worker_context) continue;
-    url::Origin client_storage_key =
-        client->environment_settings()->ObtainStorageKey();
-    if (client_storage_key.IsSameOriginWith(storage_key)) {
-      // 3.1.1. If client’s execution ready flag is unset or client’s discarded
-      //        flag is set, continue.
-      // Web Contexts exist only in the web_context_registrations_ set when they
-      // are both execution ready and not discarded.
-
-      // 3.1.2. If client is not a secure context, continue.
-      // In production, Cobalt requires https, therefore all clients are secure
-      // contexts.
-
-      // 3.1.3. Let storage key be the result of running obtain a storage key
-      //        given client.
-      // 3.1.4. Let registration be the result of running Match Service Worker
-      //        Registration given storage key and client’s creation URL.
-      // TODO(b/234659851): Investigate whether this should use the creation
-      // URL directly instead.
-      const GURL& base_url = client->environment_settings()->creation_url();
-      GURL client_url = base_url.Resolve("");
-      scoped_refptr<ServiceWorkerRegistrationObject> registration =
-          scope_to_registration_map_->MatchServiceWorkerRegistration(
-              client_storage_key, client_url);
-
-      // 3.1.5. If registration is not the service worker's containing service
-      //        worker registration, continue.
-      if (registration !=
-          associated_service_worker->containing_service_worker_registration()) {
-        continue;
-      }
-
-      // 3.1.6. If client’s active service worker is not the service worker,
-      //        then:
-      if (client->active_service_worker() != associated_service_worker) {
-        // 3.1.6.1. Invoke Handle Service Worker Client Unload with client as
-        //          the argument.
-        HandleServiceWorkerClientUnload(client);
-
-        // 3.1.6.2. Set client’s active service worker to service worker.
-        client->set_active_service_worker(associated_service_worker);
-
-        // 3.1.6.3. Invoke Notify Controller Change algorithm with client as the
-        //          argument.
-        NotifyControllerChange(client);
-      }
-    }
-  }
-  // 3.2. Resolve promise with undefined.
-  worker_context->message_loop()->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](std::unique_ptr<script::ValuePromiseVoid::Reference> promise) {
-            promise->value().Resolve();
-          },
-          std::move(promise_reference)));
-}
-
-void ServiceWorkerContext::ServiceWorkerPostMessageSubSteps(
-    ServiceWorkerObject* service_worker, web::Context* incumbent_client,
-    std::unique_ptr<script::StructuredClone> structured_clone) {
-  // Parallel sub steps (6) for algorithm for ServiceWorker.postMessage():
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-postmessage-options
-  // 3. Let incumbentGlobal be incumbentSettings’s global object.
-  // Note: The 'incumbent' is the sender of the message.
-  // 6.1 If the result of running the Run Service Worker algorithm with
-  //     serviceWorker is failure, then return.
-  auto* run_result = RunServiceWorker(service_worker);
-  if (!run_result) return;
-  if (!structured_clone || structured_clone->failed()) return;
-
-  // 6.2 Queue a task on the DOM manipulation task source to run the following
-  //     steps:
-  incumbent_client->message_loop()->task_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](ServiceWorkerObject* service_worker,
-             web::Context* incumbent_client,
-             std::unique_ptr<script::StructuredClone> structured_clone) {
-            web::EventTarget* event_target =
-                service_worker->worker_global_scope();
-            if (!event_target) return;
-
-            web::WindowOrWorkerGlobalScope* incumbent_global =
-                incumbent_client->GetWindowOrWorkerGlobalScope();
-            DCHECK_EQ(incumbent_client->environment_settings(),
-                      incumbent_global->environment_settings());
-            base::TypeId incumbent_type = incumbent_global->GetWrappableType();
-            ServiceWorkerObject* incumbent_worker =
-                incumbent_global->IsServiceWorker()
-                    ? incumbent_global->AsServiceWorker()
-                          ->service_worker_object()
-                    : nullptr;
-            base::MessageLoop* message_loop =
-                event_target->environment_settings()->context()->message_loop();
-            if (!message_loop) {
-              return;
-            }
-            message_loop->task_runner()->PostTask(
-                FROM_HERE,
-                base::BindOnce(
-                    [](const base::TypeId& incumbent_type,
-                       ServiceWorkerObject* incumbent_worker,
-                       web::Context* incumbent_client,
-                       web::EventTarget* event_target,
-                       std::unique_ptr<script::StructuredClone>
-                           structured_clone) {
-                      ExtendableMessageEventInit init_dict;
-                      if (incumbent_type ==
-                          base::GetTypeId<ServiceWorkerGlobalScope>()) {
-                        // 6.2.1. Let source be determined by switching on the
-                        //        type of incumbentGlobal:
-                        //        . ServiceWorkerGlobalScope
-                        //          The result of getting the service worker
-                        //          object that represents incumbentGlobal’s
-                        //          service worker in the relevant settings
-                        //          object of serviceWorker’s global object.
-                        init_dict.set_source(ExtendableMessageEvent::SourceType(
-                            event_target->environment_settings()
-                                ->context()
-                                ->GetServiceWorker(incumbent_worker)));
-                      } else if (incumbent_type ==
-                                 base::GetTypeId<dom::Window>()) {
-                        //        . Window
-                        //          a new WindowClient object that represents
-                        //          incumbentGlobal’s relevant settings object.
-                        init_dict.set_source(ExtendableMessageEvent::SourceType(
-                            WindowClient::Create(WindowData(
-                                incumbent_client->environment_settings()))));
-                      } else {
-                        //        . Otherwise
-                        //          a new Client object that represents
-                        //          incumbentGlobal’s associated worker
-                        init_dict.set_source(
-                            ExtendableMessageEvent::SourceType(Client::Create(
-                                incumbent_client->environment_settings())));
-                      }
-
-                      event_target->DispatchEvent(
-                          new worker::ExtendableMessageEvent(
-                              event_target->environment_settings(),
-                              base::Tokens::message(), init_dict,
-                              std::move(structured_clone)));
-                    },
-                    incumbent_type, base::Unretained(incumbent_worker),
-                    // Note: These should probably be weak pointers for when
-                    // the message sender disappears before the recipient
-                    // processes the event, but since base::WeakPtr
-                    // dereferencing isn't thread-safe, that can't actually be
-                    // used here.
-                    base::Unretained(incumbent_client),
-                    base::Unretained(event_target),
-                    std::move(structured_clone)));
-          },
-          base::Unretained(service_worker), base::Unretained(incumbent_client),
-          std::move(structured_clone)));
-}
-
-void ServiceWorkerContext::RegisterWebContext(web::Context* context) {
-  DCHECK_NE(nullptr, context);
-  web_context_registrations_cleared_.Reset();
-  if (base::MessageLoop::current() != message_loop()) {
-    DCHECK(message_loop());
-    message_loop()->task_runner()->PostTask(
-        FROM_HERE, base::BindOnce(&ServiceWorkerContext::RegisterWebContext,
-                                  base::Unretained(this), context));
-    return;
-  }
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  DCHECK_EQ(0, web_context_registrations_.count(context));
-  web_context_registrations_.insert(context);
-}
-
-void ServiceWorkerContext::SetActiveWorker(web::EnvironmentSettings* client) {
-  if (!client) return;
-  if (base::MessageLoop::current() != message_loop()) {
-    DCHECK(message_loop());
-    message_loop()->task_runner()->PostTask(
-        FROM_HERE, base::Bind(&ServiceWorkerContext::SetActiveWorker,
-                              base::Unretained(this), client));
-    return;
-  }
-  DCHECK(scope_to_registration_map_);
-  scoped_refptr<ServiceWorkerRegistrationObject> client_registration =
-      scope_to_registration_map_->MatchServiceWorkerRegistration(
-          client->ObtainStorageKey(), client->creation_url());
-  if (client_registration.get() && client_registration->active_worker()) {
-    client->context()->set_active_service_worker(
-        client_registration->active_worker());
-  } else {
-    client->context()->set_active_service_worker(nullptr);
-  }
-}
-
-void ServiceWorkerContext::UnregisterWebContext(web::Context* context) {
-  DCHECK_NE(nullptr, context);
-  if (base::MessageLoop::current() != message_loop()) {
-    // Block to ensure that the context is unregistered before it is destroyed.
-    DCHECK(message_loop());
-    message_loop()->task_runner()->PostBlockingTask(
-        FROM_HERE, base::Bind(&ServiceWorkerContext::UnregisterWebContext,
-                              base::Unretained(this), context));
-    return;
-  }
-  DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  DCHECK_EQ(1, web_context_registrations_.count(context));
-  web_context_registrations_.erase(context);
-  HandleServiceWorkerClientUnload(context);
-  PrepareForClientShutdown(context);
-  if (web_context_registrations_.empty()) {
-    web_context_registrations_cleared_.Signal();
-  }
-}
-
-void ServiceWorkerContext::PrepareForClientShutdown(web::Context* client) {
-  DCHECK(client);
-  if (!client) return;
-  DCHECK(base::MessageLoop::current() == message_loop());
-  // Note: This could be rewritten to use the decomposition declaration
-  // 'const auto& [scope, queue]' after switching to C++17.
-  jobs_->PrepareForClientShutdown(client);
-}
-
-}  // namespace worker
-}  // namespace cobalt
diff --git a/cobalt/worker/service_worker_context.h b/cobalt/worker/service_worker_context.h
deleted file mode 100644
index 1446fe8..0000000
--- a/cobalt/worker/service_worker_context.h
+++ /dev/null
@@ -1,221 +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_WORKER_SERVICE_WORKER_CONTEXT_H_
-#define COBALT_WORKER_SERVICE_WORKER_CONTEXT_H_
-
-#include <memory>
-#include <set>
-#include <string>
-
-#include "base/memory/scoped_refptr.h"
-#include "base/message_loop/message_loop.h"
-#include "base/optional.h"
-#include "base/synchronization/waitable_event.h"
-#include "cobalt/network/network_module.h"
-#include "cobalt/script/promise.h"
-#include "cobalt/script/script_value.h"
-#include "cobalt/script/script_value_factory.h"
-#include "cobalt/web/context.h"
-#include "cobalt/web/web_settings.h"
-#include "cobalt/worker/client_query_options.h"
-#include "cobalt/worker/service_worker_object.h"
-#include "cobalt/worker/service_worker_registration_map.h"
-#include "cobalt/worker/service_worker_registration_object.h"
-#include "cobalt/worker/worker_type.h"
-#include "url/gurl.h"
-#include "url/origin.h"
-
-namespace cobalt {
-namespace worker {
-
-class ServiceWorkerJobs;
-
-// Algorithms for Service Workers.
-//   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#algorithms
-class ServiceWorkerContext {
- public:
-  enum RegistrationState { kInstalling, kWaiting, kActive };
-
-  ServiceWorkerContext(web::WebSettings* web_settings,
-                       network::NetworkModule* network_module,
-                       web::UserAgentPlatformInfo* platform_info,
-                       base::MessageLoop* message_loop, const GURL& url);
-  ~ServiceWorkerContext();
-
-  base::MessageLoop* message_loop() { return message_loop_; }
-
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#start-register-algorithm
-  void StartRegister(const base::Optional<GURL>& scope_url,
-                     const GURL& script_url,
-                     std::unique_ptr<script::ValuePromiseWrappable::Reference>
-                         promise_reference,
-                     web::Context* client, const WorkerType& type,
-                     const ServiceWorkerUpdateViaCache& update_via_cache);
-
-  void MaybeResolveReadyPromiseSubSteps(web::Context* client);
-
-  // Sub steps (8) of ServiceWorkerContainer.getRegistration().
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-getRegistration
-  void GetRegistrationSubSteps(
-      const url::Origin& storage_key, const GURL& client_url,
-      web::Context* client,
-      std::unique_ptr<script::ValuePromiseWrappable::Reference>
-          promise_reference);
-
-  void GetRegistrationsSubSteps(
-      const url::Origin& storage_key, web::Context* client,
-      std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
-          promise_reference);
-
-  // Sub steps (2) of ServiceWorkerGlobalScope.skipWaiting().
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-serviceworkerglobalscope-skipwaiting
-  void SkipWaitingSubSteps(
-      web::Context* worker_context, ServiceWorkerObject* service_worker,
-      std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference);
-
-  // Sub steps for ExtendableEvent.WaitUntil().
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-extendableevent-waituntil
-  void WaitUntilSubSteps(ServiceWorkerRegistrationObject* registration);
-
-  // Parallel sub steps (2) for algorithm for Clients.get(id):
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-get
-  void ClientsGetSubSteps(
-      web::Context* worker_context,
-      ServiceWorkerObject* associated_service_worker,
-      std::unique_ptr<script::ValuePromiseWrappable::Reference>
-          promise_reference,
-      const std::string& id);
-
-  // Parallel sub steps (2) for algorithm for Clients.matchAll():
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-matchall
-  void ClientsMatchAllSubSteps(
-      web::Context* worker_context,
-      ServiceWorkerObject* associated_service_worker,
-      std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
-          promise_reference,
-      bool include_uncontrolled, ClientType type);
-
-  // Parallel sub steps (3) for algorithm for Clients.claim():
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-clients-claim
-  void ClaimSubSteps(
-      web::Context* worker_context,
-      ServiceWorkerObject* associated_service_worker,
-      std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference);
-
-  // Parallel sub steps (6) for algorithm for ServiceWorker.postMessage():
-  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-postmessage-options
-  void ServiceWorkerPostMessageSubSteps(
-      ServiceWorkerObject* service_worker, web::Context* incumbent_client,
-      std::unique_ptr<script::StructuredClone> structured_clone);
-
-  // Registration of web contexts that may have service workers.
-  void RegisterWebContext(web::Context* context);
-  void UnregisterWebContext(web::Context* context);
-  bool IsWebContextRegistered(web::Context* context) {
-    DCHECK(base::MessageLoop::current() == message_loop());
-    return web_context_registrations_.end() !=
-           web_context_registrations_.find(context);
-  }
-
-  // Ensure no references are kept to JS objects for a client that is about to
-  // be shutdown.
-  void PrepareForClientShutdown(web::Context* client);
-
-  // Set the active worker for a client if there is a matching service worker.
-  void SetActiveWorker(web::EnvironmentSettings* client);
-
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#activation-algorithm
-  void Activate(ServiceWorkerRegistrationObject* registration);
-
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clear-registration-algorithm
-  void ClearRegistration(ServiceWorkerRegistrationObject* registration);
-
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#soft-update
-  void SoftUpdate(ServiceWorkerRegistrationObject* registration,
-                  bool force_bypass_cache);
-
-  void EnsureServiceWorkerStarted(const url::Origin& storage_key,
-                                  const GURL& client_url,
-                                  base::WaitableEvent* done_event);
-
-  ServiceWorkerJobs* jobs() { return jobs_.get(); }
-  ServiceWorkerRegistrationMap* registration_map() {
-    return scope_to_registration_map_.get();
-  }
-  const std::set<web::Context*>& web_context_registrations() const {
-    return web_context_registrations_;
-  }
-
- private:
-  friend class ServiceWorkerJobs;
-
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-service-worker-algorithm
-  // The return value is a 'Completion or failure'.
-  // A failure is signaled by returning nullptr. Otherwise, the returned string
-  // points to the value of the Completion returned by the script runner
-  // abstraction.
-  std::string* RunServiceWorker(ServiceWorkerObject* worker,
-                                bool force_bypass_cache = false);
-
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-activate-algorithm
-  void TryActivate(ServiceWorkerRegistrationObject* registration);
-
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-has-no-pending-events
-  bool ServiceWorkerHasNoPendingEvents(ServiceWorkerObject* worker);
-
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-registration-state-algorithm
-  void UpdateRegistrationState(
-      ServiceWorkerRegistrationObject* registration, RegistrationState target,
-      const scoped_refptr<ServiceWorkerObject>& source);
-
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-state-algorithm
-  void UpdateWorkerState(ServiceWorkerObject* worker, ServiceWorkerState state);
-
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-client-unload-algorithm
-  void HandleServiceWorkerClientUnload(web::Context* client);
-
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#terminate-service-worker
-  void TerminateServiceWorker(ServiceWorkerObject* worker);
-
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#notify-controller-change-algorithm
-  void NotifyControllerChange(web::Context* client);
-
-  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-clear-registration-algorithm
-  void TryClearRegistration(ServiceWorkerRegistrationObject* registration);
-
-  bool IsAnyClientUsingRegistration(
-      ServiceWorkerRegistrationObject* registration);
-
-  // Returns false when the timeout is reached.
-  bool WaitForAsynchronousExtensions(
-      const scoped_refptr<ServiceWorkerRegistrationObject>& registration);
-
-  base::MessageLoop* message_loop_;
-
-  std::unique_ptr<ServiceWorkerRegistrationMap> scope_to_registration_map_;
-
-  std::unique_ptr<ServiceWorkerJobs> jobs_;
-
-  std::set<web::Context*> web_context_registrations_;
-
-  base::WaitableEvent web_context_registrations_cleared_ = {
-      base::WaitableEvent::ResetPolicy::MANUAL,
-      base::WaitableEvent::InitialState::NOT_SIGNALED};
-};
-
-}  // namespace worker
-}  // namespace cobalt
-
-#endif  // COBALT_WORKER_SERVICE_WORKER_CONTEXT_H_
diff --git a/cobalt/worker/service_worker_global_scope.cc b/cobalt/worker/service_worker_global_scope.cc
index 495046f..06b7dae 100644
--- a/cobalt/worker/service_worker_global_scope.cc
+++ b/cobalt/worker/service_worker_global_scope.cc
@@ -29,7 +29,7 @@
 #include "cobalt/worker/clients.h"
 #include "cobalt/worker/fetch_event.h"
 #include "cobalt/worker/fetch_event_init.h"
-#include "cobalt/worker/service_worker_context.h"
+#include "cobalt/worker/service_worker_jobs.h"
 #include "cobalt/worker/worker_settings.h"
 
 namespace cobalt {
@@ -180,12 +180,12 @@
       new script::ValuePromiseVoid::Reference(this, promise));
 
   // 2. Run the following substeps in parallel:
-  ServiceWorkerContext* worker_context =
-      environment_settings()->context()->service_worker_context();
-  worker_context->message_loop()->task_runner()->PostTask(
+  worker::ServiceWorkerJobs* jobs =
+      environment_settings()->context()->service_worker_jobs();
+  jobs->message_loop()->task_runner()->PostTask(
       FROM_HERE,
-      base::BindOnce(&ServiceWorkerContext::SkipWaitingSubSteps,
-                     base::Unretained(worker_context),
+      base::BindOnce(&ServiceWorkerJobs::SkipWaitingSubSteps,
+                     base::Unretained(jobs),
                      base::Unretained(environment_settings()->context()),
                      base::Unretained(service_worker_object_.get()),
                      std::move(promise_reference)));
@@ -226,13 +226,13 @@
   auto* registration =
       service_worker_object_->containing_service_worker_registration();
   if (registration && (main_resource || registration->stale())) {
-    ServiceWorkerContext* worker_context =
-        environment_settings()->context()->service_worker_context();
-    worker_context->message_loop()->task_runner()->PostTask(
-        FROM_HERE, base::BindOnce(&ServiceWorkerContext::SoftUpdate,
-                                  base::Unretained(worker_context),
-                                  base::Unretained(registration),
-                                  /*force_bypass_cache=*/false));
+    worker::ServiceWorkerJobs* jobs =
+        environment_settings()->context()->service_worker_jobs();
+    jobs->message_loop()->task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&ServiceWorkerJobs::SoftUpdate, base::Unretained(jobs),
+                       base::Unretained(registration),
+                       /*force_bypass_cache=*/false));
   }
 
   // TODO: handle the following steps in
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc
index 5c93575..25b2a58 100644
--- a/cobalt/worker/service_worker_jobs.cc
+++ b/cobalt/worker/service_worker_jobs.cc
@@ -14,22 +14,71 @@
 
 #include "cobalt/worker/service_worker_jobs.h"
 
+#include <list>
+#include <map>
+#include <memory>
+#include <queue>
+#include <string>
+#include <utility>
+#include <vector>
+
 #include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
 #include "base/message_loop/message_loop_current.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/tokens.h"
+#include "cobalt/base/type_id.h"
+#include "cobalt/loader/script_loader_factory.h"
+#include "cobalt/network/network_module.h"
+#include "cobalt/script/promise.h"
+#include "cobalt/script/script_exception.h"
+#include "cobalt/script/script_value.h"
+#include "cobalt/web/context.h"
+#include "cobalt/web/dom_exception.h"
 #include "cobalt/web/environment_settings.h"
+#include "cobalt/web/event.h"
+#include "cobalt/web/window_or_worker_global_scope.h"
+#include "cobalt/worker/client.h"
+#include "cobalt/worker/client_query_options.h"
+#include "cobalt/worker/client_type.h"
 #include "cobalt/worker/extendable_event.h"
+#include "cobalt/worker/extendable_message_event.h"
+#include "cobalt/worker/frame_type.h"
+#include "cobalt/worker/service_worker.h"
+#include "cobalt/worker/service_worker_consts.h"
+#include "cobalt/worker/service_worker_container.h"
+#include "cobalt/worker/service_worker_global_scope.h"
+#include "cobalt/worker/service_worker_registration.h"
+#include "cobalt/worker/service_worker_registration_object.h"
+#include "cobalt/worker/service_worker_update_via_cache.h"
+#include "cobalt/worker/window_client.h"
+#include "cobalt/worker/worker_type.h"
 #include "net/base/mime_util.h"
 #include "net/base/url_util.h"
+#include "starboard/common/atomic.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
 
 namespace cobalt {
 namespace worker {
 
 namespace {
 
+const base::TimeDelta kWaitForAsynchronousExtensionsTimeout =
+    base::TimeDelta::FromSeconds(3);
+
+const base::TimeDelta kShutdownWaitTimeoutSecs =
+    base::TimeDelta::FromSeconds(5);
+
 bool PathContainsEscapedSlash(const GURL& url) {
   const std::string path = url.path();
   return (path.find("%2f") != std::string::npos ||
@@ -85,22 +134,164 @@
 bool PermitAnyNonRedirectedURL(const GURL&, bool did_redirect) {
   return !did_redirect;
 }
-
 }  // namespace
 
-ServiceWorkerJobs::ServiceWorkerJobs(
-    ServiceWorkerContext* service_worker_context,
-    network::NetworkModule* network_module, base::MessageLoop* message_loop)
-    : service_worker_context_(service_worker_context),
-      message_loop_(message_loop) {
+ServiceWorkerJobs::ServiceWorkerJobs(web::WebSettings* web_settings,
+                                     network::NetworkModule* network_module,
+                                     web::UserAgentPlatformInfo* platform_info,
+                                     base::MessageLoop* message_loop,
+                                     const GURL& url)
+    : message_loop_(message_loop) {
   DCHECK_EQ(message_loop_, base::MessageLoop::current());
   fetcher_factory_.reset(new loader::FetcherFactory(network_module));
+  DCHECK(fetcher_factory_);
 
   script_loader_factory_.reset(new loader::ScriptLoaderFactory(
       "ServiceWorkerJobs", fetcher_factory_.get()));
+  DCHECK(script_loader_factory_);
+
+  ServiceWorkerPersistentSettings::Options options(web_settings, network_module,
+                                                   platform_info, this, url);
+  scope_to_registration_map_.reset(new ServiceWorkerRegistrationMap(options));
+  DCHECK(scope_to_registration_map_);
 }
 
-ServiceWorkerJobs::~ServiceWorkerJobs() {}
+ServiceWorkerJobs::~ServiceWorkerJobs() {
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  scope_to_registration_map_->HandleUserAgentShutdown(this);
+  scope_to_registration_map_->AbortAllActive();
+  scope_to_registration_map_.reset();
+  if (!web_context_registrations_.empty()) {
+    // Abort any Service Workers that remain.
+    for (auto& context : web_context_registrations_) {
+      DCHECK(context);
+      if (context->GetWindowOrWorkerGlobalScope()->IsServiceWorker()) {
+        ServiceWorkerGlobalScope* service_worker =
+            context->GetWindowOrWorkerGlobalScope()->AsServiceWorker();
+        if (service_worker && service_worker->service_worker_object()) {
+          service_worker->service_worker_object()->Abort();
+        }
+      }
+    }
+
+    // Wait for web context registrations to be cleared.
+    web_context_registrations_cleared_.TimedWait(kShutdownWaitTimeoutSecs);
+  }
+}
+
+void ServiceWorkerJobs::StartRegister(
+    const base::Optional<GURL>& maybe_scope_url,
+    const GURL& script_url_with_fragment,
+    std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference,
+    web::Context* client, const WorkerType& type,
+    const ServiceWorkerUpdateViaCache& update_via_cache) {
+  TRACE_EVENT2("cobalt::worker", "ServiceWorkerJobs::StartRegister()", "scope",
+               maybe_scope_url.value_or(GURL()).spec(), "script",
+               script_url_with_fragment.spec());
+  DCHECK_NE(message_loop(), base::MessageLoop::current());
+  DCHECK_EQ(client->message_loop(), base::MessageLoop::current());
+  // Algorithm for Start Register:
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#start-register-algorithm
+  // 1. If scriptURL is failure, reject promise with a TypeError and abort these
+  //    steps.
+  if (script_url_with_fragment.is_empty()) {
+    promise_reference->value().Reject(script::kTypeError);
+    return;
+  }
+
+  // 2. Set scriptURL’s fragment to null.
+  url::Replacements<char> replacements;
+  replacements.ClearRef();
+  GURL script_url = script_url_with_fragment.ReplaceComponents(replacements);
+  DCHECK(!script_url.has_ref() || script_url.ref().empty());
+  DCHECK(!script_url.is_empty());
+
+  // 3. If scriptURL’s scheme is not one of "http" and "https", reject promise
+  //    with a TypeError and abort these steps.
+  if (!script_url.SchemeIsHTTPOrHTTPS()) {
+    promise_reference->value().Reject(script::kTypeError);
+    return;
+  }
+
+  // 4. If any of the strings in scriptURL’s path contains either ASCII
+  //    case-insensitive "%2f" or ASCII case-insensitive "%5c", reject promise
+  //    with a TypeError and abort these steps.
+  if (PathContainsEscapedSlash(script_url)) {
+    promise_reference->value().Reject(script::kTypeError);
+    return;
+  }
+
+  DCHECK(client);
+  web::WindowOrWorkerGlobalScope* window_or_worker_global_scope =
+      client->GetWindowOrWorkerGlobalScope();
+  DCHECK(window_or_worker_global_scope);
+  web::CspDelegate* csp_delegate =
+      window_or_worker_global_scope->csp_delegate();
+  DCHECK(csp_delegate);
+  if (!csp_delegate->CanLoad(web::CspDelegate::kWorker, script_url,
+                             /* did_redirect*/ false)) {
+    promise_reference->value().Reject(new web::DOMException(
+        web::DOMException::kSecurityErr,
+        "Failed to register a ServiceWorker: The provided scriptURL ('" +
+            script_url.spec() + "') violates the Content Security Policy."));
+    return;
+  }
+
+  // 5. If scopeURL is null, set scopeURL to the result of parsing the string
+  //    "./" with scriptURL.
+  GURL scope_url = maybe_scope_url.value_or(script_url.Resolve("./"));
+
+  // 6. If scopeURL is failure, reject promise with a TypeError and abort these
+  //    steps.
+  if (scope_url.is_empty()) {
+    promise_reference->value().Reject(script::kTypeError);
+    return;
+  }
+
+  // 7. Set scopeURL’s fragment to null.
+  scope_url = scope_url.ReplaceComponents(replacements);
+  DCHECK(!scope_url.has_ref() || scope_url.ref().empty());
+  DCHECK(!scope_url.is_empty());
+
+  // 8. If scopeURL’s scheme is not one of "http" and "https", reject promise
+  //    with a TypeError and abort these steps.
+  if (!scope_url.SchemeIsHTTPOrHTTPS()) {
+    promise_reference->value().Reject(script::kTypeError);
+    return;
+  }
+
+  // 9. If any of the strings in scopeURL’s path contains either ASCII
+  //    case-insensitive "%2f" or ASCII case-insensitive "%5c", reject promise
+  //    with a TypeError and abort these steps.
+  if (PathContainsEscapedSlash(scope_url)) {
+    promise_reference->value().Reject(script::kTypeError);
+    return;
+  }
+
+  // 10. Let storage key be the result of running obtain a storage key given
+  //     client.
+  url::Origin storage_key = client->environment_settings()->ObtainStorageKey();
+
+  // 11. Let job be the result of running Create Job with register, storage key,
+  //     scopeURL, scriptURL, promise, and client.
+  std::unique_ptr<Job> job =
+      CreateJob(kRegister, storage_key, scope_url, script_url,
+                JobPromiseType::Create(std::move(promise_reference)), client);
+  DCHECK(!promise_reference);
+
+  // 12. Set job’s worker type to workerType.
+  // Cobalt only supports 'classic' worker type.
+
+  // 13. Set job’s update via cache mode to updateViaCache.
+  job->update_via_cache = update_via_cache;
+
+  // 14. Set job’s referrer to referrer.
+  // This is the same value as set in CreateJob().
+
+  // 15. Invoke Schedule Job with job.
+  ScheduleJob(std::move(job));
+  DCHECK(!job.get());
+}
 
 void ServiceWorkerJobs::PromiseErrorData::Reject(
     std::unique_ptr<JobPromiseType> promise) const {
@@ -175,7 +366,7 @@
     // 5.1. Set job’s containing job queue to jobQueue, and enqueue job to
     // jobQueue.
     job->containing_job_queue = job_queue;
-    if (!service_worker_context_->IsWebContextRegistered(job->client)) {
+    if (!IsWebContextRegistered(job->client)) {
       // Note: The client that requested the job has already exited and isn't
       // able to handle the promise.
       job->containing_job_queue->PrepareJobForClientShutdown(job, job->client);
@@ -205,7 +396,7 @@
     // 6.3. Else, set job’s containing job queue to jobQueue, and enqueue job to
     // jobQueue.
     job->containing_job_queue = job_queue;
-    if (!service_worker_context_->IsWebContextRegistered(job->client)) {
+    if (!IsWebContextRegistered(job->client)) {
       // Note: The client that requested the job has already exited and isn't
       // able to handle the promise.
       job->containing_job_queue->PrepareJobForClientShutdown(job, job->client);
@@ -355,8 +546,8 @@
   // 4. Let registration be the result of running Get Registration given job’s
   // storage key and job’s scope url.
   scoped_refptr<ServiceWorkerRegistrationObject> registration =
-      service_worker_context_->registration_map()->GetRegistration(
-          job->storage_key, job->scope_url);
+      scope_to_registration_map_->GetRegistration(job->storage_key,
+                                                  job->scope_url);
 
   // 5. If registration is not null, then:
   if (registration) {
@@ -382,7 +573,7 @@
 
     // 6.1 Invoke Set Registration algorithm with job’s storage key, job’s scope
     // url, and job’s update via cache mode.
-    registration = service_worker_context_->registration_map()->SetRegistration(
+    registration = scope_to_registration_map_->SetRegistration(
         job->storage_key, job->scope_url, job->update_via_cache);
   }
 
@@ -390,6 +581,69 @@
   Update(job);
 }
 
+void ServiceWorkerJobs::SoftUpdate(
+    ServiceWorkerRegistrationObject* registration, bool force_bypass_cache) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::SoftUpdate()");
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  DCHECK(registration);
+  // Algorithm for SoftUpdate:
+  //    https://www.w3.org/TR/2022/CRD-service-workers-20220712/#soft-update
+  // 1. Let newestWorker be the result of running Get Newest Worker algorithm
+  // passing registration as its argument.
+  ServiceWorkerObject* newest_worker = registration->GetNewestWorker();
+
+  // 2. If newestWorker is null, abort these steps.
+  if (newest_worker == nullptr) {
+    return;
+  }
+
+  // 3. Let job be the result of running Create Job with update, registration’s
+  // storage key, registration’s scope url, newestWorker’s script url, null, and
+  // null.
+  std::unique_ptr<Job> job = CreateJobWithoutPromise(
+      kUpdate, registration->storage_key(), registration->scope_url(),
+      newest_worker->script_url());
+
+  // 4. Set job’s worker type to newestWorker’s type.
+  // Cobalt only supports 'classic' worker type.
+
+  // 5. Set job’s force bypass cache flag if forceBypassCache is true.
+  job->force_bypass_cache_flag = force_bypass_cache;
+
+  // 6. Invoke Schedule Job with job.
+  message_loop()->task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&ServiceWorkerJobs::ScheduleJob,
+                                base::Unretained(this), std::move(job)));
+  DCHECK(!job.get());
+}
+
+void ServiceWorkerJobs::EnsureServiceWorkerStarted(
+    const url::Origin& storage_key, const GURL& client_url,
+    base::WaitableEvent* done_event) {
+  if (message_loop() != base::MessageLoop::current()) {
+    message_loop()->task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&ServiceWorkerJobs::EnsureServiceWorkerStarted,
+                       base::Unretained(this), storage_key, client_url,
+                       done_event));
+    return;
+  }
+  base::ScopedClosureRunner signal_done(base::BindOnce(
+      [](base::WaitableEvent* done_event) { done_event->Signal(); },
+      done_event));
+  base::TimeTicks start = base::TimeTicks::Now();
+  auto registration =
+      scope_to_registration_map_->GetRegistration(storage_key, client_url);
+  if (!registration) {
+    return;
+  }
+  auto service_worker_object = registration->active_worker();
+  if (!service_worker_object || service_worker_object->is_running()) {
+    return;
+  }
+  service_worker_object->ObtainWebAgentAndWaitUntilDone();
+}
+
 void ServiceWorkerJobs::Update(Job* job) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Update()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
@@ -400,8 +654,8 @@
   // 1. Let registration be the result of running Get Registration given job’s
   //    storage key and job’s scope url.
   scoped_refptr<ServiceWorkerRegistrationObject> registration =
-      service_worker_context_->registration_map()->GetRegistration(
-          job->storage_key, job->scope_url);
+      scope_to_registration_map_->GetRegistration(job->storage_key,
+                                                  job->scope_url);
 
   // 2. If registration is null, then:
   if (!registration) {
@@ -595,12 +849,12 @@
   //   8.19. If response’s cache state is not "local", set registration’s last
   //         update check time to the current time.
   scoped_refptr<ServiceWorkerRegistrationObject> registration =
-      service_worker_context_->registration_map()->GetRegistration(
-          state->job->storage_key, state->job->scope_url);
+      scope_to_registration_map_->GetRegistration(state->job->storage_key,
+                                                  state->job->scope_url);
   if (registration) {
     registration->set_last_update_check_time(base::Time::Now());
-    service_worker_context_->registration_map()->PersistRegistration(
-        registration->storage_key(), registration->scope_url());
+    scope_to_registration_map_->PersistRegistration(registration->storage_key(),
+                                                    registration->scope_url());
   }
   // TODO(b/228904017):
   //   8.20. Set hasUpdatedResources to true if any of the following are true:
@@ -638,9 +892,8 @@
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   bool check_promise = !state->job->no_promise_okay;
   if (state->job->no_promise_okay && !state->job->client &&
-      service_worker_context_->web_context_registrations().size() > 0) {
-    state->job->client =
-        *(service_worker_context_->web_context_registrations().begin());
+      web_context_registrations_.size() > 0) {
+    state->job->client = *(web_context_registrations_.begin());
   }
   if ((check_promise && !state->job->promise.get()) || !state->job->client) {
     // The job is already rejected, which means there was an error, or the
@@ -655,8 +908,8 @@
         state->job,
         PromiseErrorData(web::DOMException::kSecurityErr, error.value()));
     if (state->newest_worker == nullptr) {
-      service_worker_context_->registration_map()->RemoveRegistration(
-          state->job->storage_key, state->job->scope_url);
+      scope_to_registration_map_->RemoveRegistration(state->job->storage_key,
+                                                     state->job->scope_url);
     }
     FinishJob(state->job);
     return;
@@ -697,8 +950,8 @@
     // 9.2. If newestWorker is null, then remove registration
     //      map[(registration’s storage key, serialized scopeURL)].
     if (state->newest_worker == nullptr) {
-      service_worker_context_->registration_map()->RemoveRegistration(
-          state->job->storage_key, state->job->scope_url);
+      scope_to_registration_map_->RemoveRegistration(state->job->storage_key,
+                                                     state->job->scope_url);
     }
     // 9.3. Invoke Finish Job with job and abort these steps.
     FinishJob(state->job);
@@ -725,8 +978,8 @@
       "ServiceWorker", state->job->client->web_settings(),
       state->job->client->network_module(), state->registration);
   options.web_options.platform_info = state->job->client->platform_info();
-  options.web_options.service_worker_context =
-      state->job->client->service_worker_context();
+  options.web_options.service_worker_jobs =
+      state->job->client->service_worker_jobs();
   scoped_refptr<ServiceWorkerObject> worker(new ServiceWorkerObject(options));
   // 12. Set worker’s script url to job’s script url, worker’s script
   //     resource to script, worker’s type to job’s worker type, and worker’s
@@ -743,8 +996,7 @@
   bool force_bypass_cache = state->job->force_bypass_cache_flag;
   // 16. Let runResult be the result of running the Run Service Worker
   //     algorithm with worker and forceBypassCache.
-  auto* run_result = service_worker_context_->RunServiceWorker(
-      worker.get(), force_bypass_cache);
+  auto* run_result = RunServiceWorker(worker.get(), force_bypass_cache);
   bool run_result_is_success = run_result;
 
   // Post a task for the remaining steps, to let tasks posted by
@@ -765,8 +1017,8 @@
     // 17.2. If newestWorker is null, then remove registration
     //       map[(registration’s storage key, serialized scopeURL)].
     if (state->newest_worker == nullptr) {
-      service_worker_context_->registration_map()->RemoveRegistration(
-          state->job->storage_key, state->job->scope_url);
+      scope_to_registration_map_->RemoveRegistration(state->job->storage_key,
+                                                     state->job->scope_url);
     }
     // 17.3. Invoke Finish Job with job.
     FinishJob(state->job);
@@ -777,6 +1029,61 @@
   }
 }
 
+std::string* ServiceWorkerJobs::RunServiceWorker(ServiceWorkerObject* worker,
+                                                 bool force_bypass_cache) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::RunServiceWorker()");
+
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  DCHECK(worker);
+  // Algorithm for "Run Service Worker"
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-service-worker-algorithm
+
+  // 1. Let unsafeCreationTime be the unsafe shared current time.
+  auto unsafe_creation_time = base::TimeTicks::Now();
+  // 2. If serviceWorker is running, then return serviceWorker’s start status.
+  if (worker->is_running()) {
+    return worker->start_status();
+  }
+  // 3. If serviceWorker’s state is "redundant", then return failure.
+  if (worker->state() == kServiceWorkerStateRedundant) {
+    return nullptr;
+  }
+  // 4. Assert: serviceWorker’s start status is null.
+  DCHECK(worker->start_status() == nullptr);
+  // 5. Let script be serviceWorker’s script resource.
+  // 6. Assert: script is not null.
+  DCHECK(worker->HasScriptResource());
+  // 7. Let startFailed be false.
+  worker->store_start_failed(false);
+  // 8. Let agent be the result of obtaining a service worker agent, and run the
+  //    following steps in that context:
+  // 9. Wait for serviceWorker to be running, or for startFailed to be true.
+  worker->ObtainWebAgentAndWaitUntilDone();
+  // 10. If startFailed is true, then return failure.
+  if (worker->load_start_failed()) {
+    return nullptr;
+  }
+  // 11. Return serviceWorker’s start status.
+  return worker->start_status();
+}
+
+bool ServiceWorkerJobs::WaitForAsynchronousExtensions(
+    const scoped_refptr<ServiceWorkerRegistrationObject>& registration) {
+  // TODO(b/240164388): Investigate a better approach for combining waiting
+  // for the ExtendableEvent while also allowing use of algorithms that run
+  // on the same thread from the event handler.
+  base::TimeTicks wait_start_time = base::TimeTicks::Now();
+  do {
+    if (registration->done_event()->TimedWait(
+            base::TimeDelta::FromMilliseconds(100)))
+      break;
+    base::MessageLoopCurrent::ScopedNestableTaskAllower allow;
+    base::RunLoop().RunUntilIdle();
+  } while ((base::TimeTicks::Now() - wait_start_time) <
+           kWaitForAsynchronousExtensionsTimeout);
+  return registration->done_event()->IsSignaled();
+}
+
 void ServiceWorkerJobs::Install(
     Job* job, const scoped_refptr<ServiceWorkerObject>& worker,
     const scoped_refptr<ServiceWorkerRegistrationObject>& registration) {
@@ -802,13 +1109,12 @@
 
   // 4. Run the Update Registration State algorithm passing registration,
   //    "installing" and worker as the arguments.
-  service_worker_context_->UpdateRegistrationState(
-      registration, ServiceWorkerContext::kInstalling, worker);
+  UpdateRegistrationState(registration, kInstalling, worker);
 
   // 5. Run the Update Worker State algorithm passing registration’s installing
   //    worker and "installing" as the arguments.
-  service_worker_context_->UpdateWorkerState(registration->installing_worker(),
-                                             kServiceWorkerStateInstalling);
+  UpdateWorkerState(registration->installing_worker(),
+                    kServiceWorkerStateInstalling);
   // 6. Assert: job’s job promise is not null.
   DCHECK(job->no_promise_okay || job->promise.get() != nullptr);
   // 7. Invoke Resolve Job Promise with job and registration.
@@ -817,7 +1123,7 @@
   //    registration’s scope url's origin.
   auto registration_origin = loader::Origin(registration->scope_url());
   // 9. For each settingsObject of settingsObjects...
-  for (auto& context : service_worker_context_->web_context_registrations()) {
+  for (auto& context : web_context_registrations_) {
     if (context->environment_settings()->GetOrigin() == registration_origin) {
       // 9. ... queue a task on settingsObject’s responsible event loop in the
       //    DOM manipulation task source to run the following steps:
@@ -863,8 +1169,7 @@
     bool force_bypass_cache = job->force_bypass_cache_flag;
     // 11.2. If the result of running the Run Service Worker algorithm with
     //       installingWorker and forceBypassCache is failure, then:
-    auto* run_result = service_worker_context_->RunServiceWorker(
-        installing_worker, force_bypass_cache);
+    auto* run_result = RunServiceWorker(installing_worker, force_bypass_cache);
     if (!run_result) {
       // 11.2.1. Set installFailed to true.
       install_failed->store(true);
@@ -928,8 +1233,7 @@
       // This waiting is done inside PostBlockingTask above.
       // 11.3.3. Wait for the step labeled WaitForAsynchronousExtensions to
       //         complete.
-      if (!service_worker_context_->WaitForAsynchronousExtensions(
-              registration)) {
+      if (!WaitForAsynchronousExtensions(registration)) {
         // Timeout
         install_failed->store(true);
       }
@@ -940,18 +1244,17 @@
     // 12.1. Run the Update Worker State algorithm passing registration’s
     //       installing worker and "redundant" as the arguments.
     if (registration->installing_worker()) {
-      service_worker_context_->UpdateWorkerState(
-          registration->installing_worker(), kServiceWorkerStateRedundant);
+      UpdateWorkerState(registration->installing_worker(),
+                        kServiceWorkerStateRedundant);
     }
     // 12.2. Run the Update Registration State algorithm passing registration,
     //       "installing" and null as the arguments.
-    service_worker_context_->UpdateRegistrationState(
-        registration, ServiceWorkerContext::kInstalling, nullptr);
+    UpdateRegistrationState(registration, kInstalling, nullptr);
     // 12.3. If newestWorker is null, then remove registration
     //       map[(registration’s storage key, serialized registration’s
     //       scope url)].
     if (newest_worker == nullptr) {
-      service_worker_context_->registration_map()->RemoveRegistration(
+      scope_to_registration_map_->RemoveRegistration(
           registration->storage_key(), registration->scope_url());
     }
     // 12.4. Invoke Finish Job with job and abort these steps.
@@ -967,37 +1270,646 @@
   // 16. If registration’s waiting worker is not null, then:
   if (registration->waiting_worker()) {
     // 16.1. Terminate registration’s waiting worker.
-    service_worker_context_->TerminateServiceWorker(
-        registration->waiting_worker());
+    TerminateServiceWorker(registration->waiting_worker());
     // 16.2. Run the Update Worker State algorithm passing registration’s
     //       waiting worker and "redundant" as the arguments.
-    service_worker_context_->UpdateWorkerState(registration->waiting_worker(),
-                                               kServiceWorkerStateRedundant);
+    UpdateWorkerState(registration->waiting_worker(),
+                      kServiceWorkerStateRedundant);
   }
   // 17. Run the Update Registration State algorithm passing registration,
   //     "waiting" and registration’s installing worker as the arguments.
-  service_worker_context_->UpdateRegistrationState(
-      registration, ServiceWorkerContext::kWaiting,
-      registration->installing_worker());
+  UpdateRegistrationState(registration, kWaiting,
+                          registration->installing_worker());
   // 18. Run the Update Registration State algorithm passing registration,
   //     "installing" and null as the arguments.
-  service_worker_context_->UpdateRegistrationState(
-      registration, ServiceWorkerContext::kInstalling, nullptr);
+  UpdateRegistrationState(registration, kInstalling, nullptr);
   // 19. Run the Update Worker State algorithm passing registration’s waiting
   //     worker and "installed" as the arguments.
-  service_worker_context_->UpdateWorkerState(registration->waiting_worker(),
-                                             kServiceWorkerStateInstalled);
+  UpdateWorkerState(registration->waiting_worker(),
+                    kServiceWorkerStateInstalled);
   // 20. Invoke Finish Job with job.
   FinishJob(job);
   // 21. Wait for all the tasks queued by Update Worker State invoked in this
   //     algorithm to have executed.
   // TODO(b/234788479): Wait for tasks.
   // 22. Invoke Try Activate with registration.
-  service_worker_context_->TryActivate(registration);
+  TryActivate(registration);
 
   // Persist registration since the waiting_worker has been updated.
-  service_worker_context_->registration_map()->PersistRegistration(
-      registration->storage_key(), registration->scope_url());
+  scope_to_registration_map_->PersistRegistration(registration->storage_key(),
+                                                  registration->scope_url());
+}
+
+bool ServiceWorkerJobs::IsAnyClientUsingRegistration(
+    ServiceWorkerRegistrationObject* registration) {
+  bool any_client_is_using = false;
+  for (auto& context : web_context_registrations_) {
+    // When a service worker client is controlled by a service worker, it is
+    // said that the service worker client is using the service worker’s
+    // containing service worker registration.
+    //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-control
+    if (context->is_controlled_by(registration->active_worker())) {
+      any_client_is_using = true;
+      break;
+    }
+  }
+  return any_client_is_using;
+}
+
+void ServiceWorkerJobs::TryActivate(
+    ServiceWorkerRegistrationObject* registration) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::TryActivate()");
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Algorithm for Try Activate:
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-activate-algorithm
+
+  // 1. If registration’s waiting worker is null, return.
+  if (!registration) return;
+  if (!registration->waiting_worker()) return;
+
+  // 2. If registration’s active worker is not null and registration’s active
+  //    worker's state is "activating", return.
+  if (registration->active_worker() &&
+      (registration->active_worker()->state() == kServiceWorkerStateActivating))
+    return;
+
+  // 3. Invoke Activate with registration if either of the following is true:
+
+  //    - registration’s active worker is null.
+  bool invoke_activate = registration->active_worker() == nullptr;
+
+  if (!invoke_activate) {
+    //    - The result of running Service Worker Has No Pending Events with
+    //      registration’s active worker is true...
+    if (ServiceWorkerHasNoPendingEvents(registration->active_worker())) {
+      //      ... and no service worker client is using registration...
+      bool any_client_using = IsAnyClientUsingRegistration(registration);
+      invoke_activate = !any_client_using;
+      //      ... or registration’s waiting worker's skip waiting flag is
+      //      set.
+      if (!invoke_activate && registration->waiting_worker()->skip_waiting())
+        invoke_activate = true;
+    }
+  }
+
+  if (invoke_activate) Activate(registration);
+}
+
+void ServiceWorkerJobs::Activate(
+    ServiceWorkerRegistrationObject* registration) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Activate()");
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Algorithm for Activate:
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#activation-algorithm
+
+  // 1. If registration’s waiting worker is null, abort these steps.
+  if (registration->waiting_worker() == nullptr) return;
+  // 2. If registration’s active worker is not null, then:
+  if (registration->active_worker()) {
+    // 2.1. Terminate registration’s active worker.
+    TerminateServiceWorker(registration->active_worker());
+    // 2.2. Run the Update Worker State algorithm passing registration’s active
+    //      worker and "redundant" as the arguments.
+    UpdateWorkerState(registration->active_worker(),
+                      kServiceWorkerStateRedundant);
+  }
+  // 3. Run the Update Registration State algorithm passing registration,
+  //    "active" and registration’s waiting worker as the arguments.
+  UpdateRegistrationState(registration, kActive,
+                          registration->waiting_worker());
+  // 4. Run the Update Registration State algorithm passing registration,
+  //    "waiting" and null as the arguments.
+  UpdateRegistrationState(registration, kWaiting, nullptr);
+  // 5. Run the Update Worker State algorithm passing registration’s active
+  //    worker and "activating" as the arguments.
+  UpdateWorkerState(registration->active_worker(),
+                    kServiceWorkerStateActivating);
+  // 6. Let matchedClients be a list of service worker clients whose creation
+  //    URL matches registration’s storage key and registration’s scope url.
+  std::list<web::Context*> matched_clients;
+  for (auto& context : web_context_registrations_) {
+    url::Origin context_storage_key =
+        url::Origin::Create(context->environment_settings()->creation_url());
+    scoped_refptr<ServiceWorkerRegistrationObject> matched_registration =
+        scope_to_registration_map_->MatchServiceWorkerRegistration(
+            context_storage_key, registration->scope_url());
+    if (matched_registration == registration) {
+      matched_clients.push_back(context);
+    }
+  }
+  // 7. For each client of matchedClients, queue a task on client’s  responsible
+  //    event loop, using the DOM manipulation task source, to run the following
+  //    substeps:
+  for (auto& client : matched_clients) {
+    // 7.1. Let readyPromise be client’s global object's
+    //      ServiceWorkerContainer object’s ready
+    //      promise.
+    // 7.2. If readyPromise is null, then continue.
+    // 7.3. If readyPromise is pending, resolve
+    //      readyPromise with the the result of getting
+    //      the service worker registration object that
+    //      represents registration in readyPromise’s
+    //      relevant settings object.
+    client->message_loop()->task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&ServiceWorkerContainer::MaybeResolveReadyPromise,
+                       base::Unretained(client->GetWindowOrWorkerGlobalScope()
+                                            ->navigator_base()
+                                            ->service_worker()
+                                            .get()),
+                       base::Unretained(registration)));
+  }
+  // 8. For each client of matchedClients:
+  // 8.1. If client is a window client, unassociate client’s responsible
+  //      document from its application cache, if it has one.
+  // 8.2. Else if client is a shared worker client, unassociate client’s
+  //      global object from its application cache, if it has one.
+  // Cobalt doesn't implement 'application cache':
+  //   https://www.w3.org/TR/2011/WD-html5-20110525/offline.html#applicationcache
+  // 9. For each service worker client client who is using registration:
+  // Note: The spec defines "control" and "use" of a service worker from the
+  // value of the active service worker property of the client environment, but
+  // that property is set here, so here we should not use that exact definition
+  // to determine if the client is using this registration. Instead, we use the
+  // Match Service Worker Registration algorithm to find the registration for a
+  // client and compare it with the registration being activated.
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-use
+  for (const auto& client : web_context_registrations_) {
+    scoped_refptr<ServiceWorkerRegistrationObject> client_registration =
+        scope_to_registration_map_->MatchServiceWorkerRegistration(
+            client->environment_settings()->ObtainStorageKey(),
+            client->environment_settings()->creation_url());
+    // When a service worker client is controlled by a service worker, it is
+    // said that the service worker client is using the service worker’s
+    // containing service worker registration.
+    //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-control
+    if (client_registration.get() == registration) {
+      // 9.1. Set client’s active worker to registration’s active worker.
+      client->set_active_service_worker(registration->active_worker());
+      // 9.2. Invoke Notify Controller Change algorithm with client as the
+      //      argument.
+      NotifyControllerChange(client);
+    }
+  }
+  // 10. Let activeWorker be registration’s active worker.
+  ServiceWorkerObject* active_worker = registration->active_worker();
+  bool activated = true;
+  // 11. If the result of running the Should Skip Event algorithm with
+  //     activeWorker and "activate" is false, then:
+  DCHECK(active_worker);
+  if (!active_worker->ShouldSkipEvent(base::Tokens::activate())) {
+    // 11.1. If the result of running the Run Service Worker algorithm with
+    //       activeWorker is not failure, then:
+    auto* run_result = RunServiceWorker(active_worker);
+    if (run_result) {
+      // 11.1.1. Queue a task task on activeWorker’s event loop using the DOM
+      //         manipulation task source to run the following steps:
+      DCHECK_EQ(active_worker->web_agent()->context(),
+                active_worker->worker_global_scope()
+                    ->environment_settings()
+                    ->context());
+      DCHECK(registration->done_event()->IsSignaled());
+      registration->done_event()->Reset();
+      active_worker->web_agent()
+          ->context()
+          ->message_loop()
+          ->task_runner()
+          ->PostBlockingTask(
+              FROM_HERE,
+              base::Bind(
+                  [](ServiceWorkerObject* active_worker,
+                     base::WaitableEvent* done_event) {
+                    auto done_callback =
+                        base::BindOnce([](base::WaitableEvent* done_event,
+                                          bool) { done_event->Signal(); },
+                                       done_event);
+                    auto* settings = active_worker->web_agent()
+                                         ->context()
+                                         ->environment_settings();
+                    scoped_refptr<ExtendableEvent> event(
+                        new ExtendableEvent(settings, base::Tokens::activate(),
+                                            std::move(done_callback)));
+                    // 11.1.1.1. Let e be the result of creating an event with
+                    //           ExtendableEvent.
+                    // 11.1.1.2. Initialize e’s type attribute to activate.
+                    // 11.1.1.3. Dispatch e at activeWorker’s global object.
+                    active_worker->worker_global_scope()->DispatchEvent(event);
+                    // 11.1.1.4. WaitForAsynchronousExtensions: Wait, in
+                    //           parallel, until e is not active.
+                    if (!event->IsActive()) {
+                      // If the event handler doesn't use waitUntil(), it will
+                      // already no longer be active, and there will never be a
+                      // callback to signal the done event.
+                      done_event->Signal();
+                    }
+                  },
+                  base::Unretained(active_worker), registration->done_event()));
+      // 11.1.2. Wait for task to have executed or been discarded.
+      // This waiting is done inside PostBlockingTask above.
+      // 11.1.3. Wait for the step labeled WaitForAsynchronousExtensions to
+      //         complete.
+      // TODO(b/240164388): Investigate a better approach for combining waiting
+      // for the ExtendableEvent while also allowing use of algorithms that run
+      // on the same thread from the event handler.
+      if (!WaitForAsynchronousExtensions(registration)) {
+        // Timeout
+        activated = false;
+      }
+    } else {
+      activated = false;
+    }
+  }
+  // 12. Run the Update Worker State algorithm passing registration’s active
+  //     worker and "activated" as the arguments.
+  if (activated && registration->active_worker()) {
+    UpdateWorkerState(registration->active_worker(),
+                      kServiceWorkerStateActivated);
+
+    // Persist registration since the waiting_worker has been updated to nullptr
+    // and the active_worker has been updated to the previous waiting_worker.
+    scope_to_registration_map_->PersistRegistration(registration->storage_key(),
+                                                    registration->scope_url());
+  }
+}
+
+void ServiceWorkerJobs::NotifyControllerChange(web::Context* client) {
+  // Algorithm for Notify Controller Change:
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#notify-controller-change-algorithm
+  // 1. Assert: client is not null.
+  DCHECK(client);
+
+  // 2. If client is an environment settings object, queue a task to fire an
+  //    event named controllerchange at the ServiceWorkerContainer object that
+  //    client is associated with.
+  client->message_loop()->task_runner()->PostTask(
+      FROM_HERE, base::Bind(
+                     [](web::Context* client) {
+                       client->GetWindowOrWorkerGlobalScope()
+                           ->navigator_base()
+                           ->service_worker()
+                           ->DispatchEvent(new web::Event(
+                               base::Tokens::controllerchange()));
+                     },
+                     client));
+}
+
+bool ServiceWorkerJobs::ServiceWorkerHasNoPendingEvents(
+    ServiceWorkerObject* worker) {
+  // Algorithm for Service Worker Has No Pending Events
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-has-no-pending-events
+  // TODO(b/240174245): Implement this using the 'set of extended events'.
+  NOTIMPLEMENTED();
+
+  // 1. For each event of worker’s set of extended events:
+  // 1.1. If event is active, return false.
+  // 2. Return true.
+  return true;
+}
+
+void ServiceWorkerJobs::ClearRegistration(
+    ServiceWorkerRegistrationObject* registration) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::ClearRegistration()");
+  // Algorithm for Clear Registration:
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clear-registration-algorithm
+  // 1. Run the following steps atomically.
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+
+  // 2. If registration’s installing worker is not null, then:
+  ServiceWorkerObject* installing_worker = registration->installing_worker();
+  if (installing_worker) {
+    // 2.1. Terminate registration’s installing worker.
+    TerminateServiceWorker(installing_worker);
+    // 2.2. Run the Update Worker State algorithm passing registration’s
+    //      installing worker and "redundant" as the arguments.
+    UpdateWorkerState(installing_worker, kServiceWorkerStateRedundant);
+    // 2.3. Run the Update Registration State algorithm passing registration,
+    //      "installing" and null as the arguments.
+    UpdateRegistrationState(registration, kInstalling, nullptr);
+  }
+
+  // 3. If registration’s waiting worker is not null, then:
+  ServiceWorkerObject* waiting_worker = registration->waiting_worker();
+  if (waiting_worker) {
+    // 3.1. Terminate registration’s waiting worker.
+    TerminateServiceWorker(waiting_worker);
+    // 3.2. Run the Update Worker State algorithm passing registration’s
+    //      waiting worker and "redundant" as the arguments.
+    UpdateWorkerState(waiting_worker, kServiceWorkerStateRedundant);
+    // 3.3. Run the Update Registration State algorithm passing registration,
+    //      "waiting" and null as the arguments.
+    UpdateRegistrationState(registration, kWaiting, nullptr);
+  }
+
+  // 4. If registration’s active worker is not null, then:
+  ServiceWorkerObject* active_worker = registration->active_worker();
+  if (active_worker) {
+    // 4.1. Terminate registration’s active worker.
+    TerminateServiceWorker(active_worker);
+    // 4.2. Run the Update Worker State algorithm passing registration’s
+    //      active worker and "redundant" as the arguments.
+    UpdateWorkerState(active_worker, kServiceWorkerStateRedundant);
+    // 4.3. Run the Update Registration State algorithm passing registration,
+    //      "active" and null as the arguments.
+    UpdateRegistrationState(registration, kActive, nullptr);
+  }
+
+  // Persist registration since the waiting_worker and active_worker have
+  // been updated to nullptr. This will remove any persisted registration
+  // if one exists.
+  scope_to_registration_map_->PersistRegistration(registration->storage_key(),
+                                                  registration->scope_url());
+}
+
+void ServiceWorkerJobs::TryClearRegistration(
+    ServiceWorkerRegistrationObject* registration) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::TryClearRegistration()");
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Algorithm for Try Clear Registration:
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-clear-registration-algorithm
+
+  // 1. Invoke Clear Registration with registration if no service worker client
+  // is using registration and all of the following conditions are true:
+  if (IsAnyClientUsingRegistration(registration)) return;
+
+  //    . registration’s installing worker is null or the result of running
+  //      Service Worker Has No Pending Events with registration’s installing
+  //      worker is true.
+  if (registration->installing_worker() &&
+      !ServiceWorkerHasNoPendingEvents(registration->installing_worker()))
+    return;
+
+  //    . registration’s waiting worker is null or the result of running
+  //      Service Worker Has No Pending Events with registration’s waiting
+  //      worker is true.
+  if (registration->waiting_worker() &&
+      !ServiceWorkerHasNoPendingEvents(registration->waiting_worker()))
+    return;
+
+  //    . registration’s active worker is null or the result of running
+  //      ServiceWorker Has No Pending Events with registration’s active worker
+  //      is true.
+  if (registration->active_worker() &&
+      !ServiceWorkerHasNoPendingEvents(registration->active_worker()))
+    return;
+
+  ClearRegistration(registration);
+}
+
+void ServiceWorkerJobs::UpdateRegistrationState(
+    ServiceWorkerRegistrationObject* registration, RegistrationState target,
+    const scoped_refptr<ServiceWorkerObject>& source) {
+  TRACE_EVENT2("cobalt::worker", "ServiceWorkerJobs::UpdateRegistrationState()",
+               "target", target, "source", source);
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  DCHECK(registration);
+  // Algorithm for Update Registration State:
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-registration-state-algorithm
+
+  // 1. Let registrationObjects be an array containing all the
+  //    ServiceWorkerRegistration objects associated with registration.
+  // This is implemented with a call to LookupServiceWorkerRegistration for each
+  // registered web context.
+
+  switch (target) {
+    // 2. If target is "installing", then:
+    case kInstalling: {
+      // 2.1. Set registration’s installing worker to source.
+      registration->set_installing_worker(source);
+      // 2.2. For each registrationObject in registrationObjects:
+      for (auto& context : web_context_registrations_) {
+        // 2.2.1. Queue a task to...
+        context->message_loop()->task_runner()->PostBlockingTask(
+            FROM_HERE,
+            base::Bind(
+                [](web::Context* context,
+                   ServiceWorkerRegistrationObject* registration) {
+                  // 2.2.1. ... set the installing attribute of
+                  //        registrationObject to null if registration’s
+                  //        installing worker is null, or the result of getting
+                  //        the service worker object that represents
+                  //        registration’s installing worker in
+                  //        registrationObject’s relevant settings object.
+                  auto registration_object =
+                      context->LookupServiceWorkerRegistration(registration);
+                  if (registration_object) {
+                    registration_object->set_installing(
+                        context->GetServiceWorker(
+                            registration->installing_worker()));
+                  }
+                },
+                context, base::Unretained(registration)));
+      }
+      break;
+    }
+    // 3. Else if target is "waiting", then:
+    case kWaiting: {
+      // 3.1. Set registration’s waiting worker to source.
+      registration->set_waiting_worker(source);
+      // 3.2. For each registrationObject in registrationObjects:
+      for (auto& context : web_context_registrations_) {
+        // 3.2.1. Queue a task to...
+        context->message_loop()->task_runner()->PostBlockingTask(
+            FROM_HERE,
+            base::Bind(
+                [](web::Context* context,
+                   ServiceWorkerRegistrationObject* registration) {
+                  // 3.2.1. ... set the waiting attribute of registrationObject
+                  //        to null if registration’s waiting worker is null, or
+                  //        the result of getting the service worker object that
+                  //        represents registration’s waiting worker in
+                  //        registrationObject’s relevant settings object.
+                  auto registration_object =
+                      context->LookupServiceWorkerRegistration(registration);
+                  if (registration_object) {
+                    registration_object->set_waiting(context->GetServiceWorker(
+                        registration->waiting_worker()));
+                  }
+                },
+                context, base::Unretained(registration)));
+      }
+      break;
+    }
+    // 4. Else if target is "active", then:
+    case kActive: {
+      // 4.1. Set registration’s active worker to source.
+      registration->set_active_worker(source);
+      // 4.2. For each registrationObject in registrationObjects:
+      for (auto& context : web_context_registrations_) {
+        // 4.2.1. Queue a task to...
+        context->message_loop()->task_runner()->PostBlockingTask(
+            FROM_HERE,
+            base::Bind(
+                [](web::Context* context,
+                   ServiceWorkerRegistrationObject* registration) {
+                  // 4.2.1. ... set the active attribute of registrationObject
+                  //        to null if registration’s active worker is null, or
+                  //        the result of getting the service worker object that
+                  //        represents registration’s active worker in
+                  //        registrationObject’s relevant settings object.
+                  auto registration_object =
+                      context->LookupServiceWorkerRegistration(registration);
+                  if (registration_object) {
+                    registration_object->set_active(context->GetServiceWorker(
+                        registration->active_worker()));
+                  }
+                },
+                context, base::Unretained(registration)));
+      }
+      break;
+    }
+    default:
+      NOTREACHED();
+  }
+}
+
+void ServiceWorkerJobs::UpdateWorkerState(ServiceWorkerObject* worker,
+                                          ServiceWorkerState state) {
+  TRACE_EVENT1("cobalt::worker", "ServiceWorkerJobs::UpdateWorkerState()",
+               "state", state);
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  DCHECK(worker);
+  if (!worker) {
+    return;
+  }
+  // Algorithm for Update Worker State:
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-state-algorithm
+  // 1. Assert: state is not "parsed".
+  DCHECK_NE(kServiceWorkerStateParsed, state);
+  // 2. Set worker's state to state.
+  worker->set_state(state);
+  auto worker_origin = loader::Origin(worker->script_url());
+  // 3. Let settingsObjects be all environment settings objects whose origin is
+  //    worker's script url's origin.
+  // 4. For each settingsObject of settingsObjects...
+  for (auto& context : web_context_registrations_) {
+    if (context->environment_settings()->GetOrigin() == worker_origin) {
+      // 4. ... queue a task on
+      //    settingsObject's responsible event loop in the DOM manipulation task
+      //    source to run the following steps:
+      context->message_loop()->task_runner()->PostBlockingTask(
+          FROM_HERE, base::Bind(
+                         [](web::Context* context, ServiceWorkerObject* worker,
+                            ServiceWorkerState state) {
+                           DCHECK_EQ(context->message_loop(),
+                                     base::MessageLoop::current());
+                           // 4.1. Let objectMap be settingsObject's service
+                           // worker object
+                           //      map.
+                           // 4.2. If objectMap[worker] does not exist, then
+                           // abort these
+                           //      steps.
+                           // 4.3. Let  workerObj be objectMap[worker].
+                           auto worker_obj =
+                               context->LookupServiceWorker(worker);
+                           if (worker_obj) {
+                             // 4.4. Set workerObj's state to state.
+                             worker_obj->set_state(state);
+                             // 4.5. Fire an event named statechange at
+                             // workerObj.
+                             worker_obj->DispatchEvent(
+                                 new web::Event(base::Tokens::statechange()));
+                           }
+                         },
+                         context, base::Unretained(worker), state));
+    }
+  }
+}
+
+void ServiceWorkerJobs::HandleServiceWorkerClientUnload(web::Context* client) {
+  TRACE_EVENT0("cobalt::worker",
+               "ServiceWorkerJobs::HandleServiceWorkerClientUnload()");
+  // Algorithm for Handle Servicer Worker Client Unload:
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-client-unload-algorithm
+  DCHECK(client);
+  // 1. Run the following steps atomically.
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+
+  // 2. Let registration be the service worker registration used by client.
+  // 3. If registration is null, abort these steps.
+  ServiceWorkerObject* active_service_worker = client->active_service_worker();
+  if (!active_service_worker) return;
+  ServiceWorkerRegistrationObject* registration =
+      active_service_worker->containing_service_worker_registration();
+  if (!registration) return;
+
+  // 4. If any other service worker client is using registration, abort these
+  //    steps.
+  // Ensure the client is already removed from the registrations when this runs.
+  DCHECK(web_context_registrations_.end() ==
+         web_context_registrations_.find(client));
+  if (IsAnyClientUsingRegistration(registration)) return;
+
+  // 5. If registration is unregistered, invoke Try Clear Registration with
+  //    registration.
+  if (scope_to_registration_map_ &&
+      scope_to_registration_map_->IsUnregistered(registration)) {
+    TryClearRegistration(registration);
+  }
+
+  // 6. Invoke Try Activate with registration.
+  TryActivate(registration);
+}
+
+void ServiceWorkerJobs::TerminateServiceWorker(ServiceWorkerObject* worker) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::TerminateServiceWorker()");
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Algorithm for Terminate Service Worker:
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#terminate-service-worker
+  // 1. Run the following steps in parallel with serviceWorker’s main loop:
+  // This runs in the ServiceWorkerRegistry thread.
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+
+  // 1.1. Let serviceWorkerGlobalScope be serviceWorker’s global object.
+  WorkerGlobalScope* service_worker_global_scope =
+      worker->worker_global_scope();
+
+  // 1.2. Set serviceWorkerGlobalScope’s closing flag to true.
+  if (service_worker_global_scope != nullptr)
+    service_worker_global_scope->set_closing_flag(true);
+
+  // 1.3. Remove all the items from serviceWorker’s set of extended events.
+  // TODO(b/240174245): Implement 'set of extended events'.
+
+  // 1.4. If there are any tasks, whose task source is either the handle fetch
+  //      task source or the handle functional event task source, queued in
+  //      serviceWorkerGlobalScope’s event loop’s task queues, queue them to
+  //      serviceWorker’s containing service worker registration’s corresponding
+  //      task queues in the same order using their original task sources, and
+  //      discard all the tasks (including tasks whose task source is neither
+  //      the handle fetch task source nor the handle functional event task
+  //      source) from serviceWorkerGlobalScope’s event loop’s task queues
+  //      without processing them.
+  // TODO(b/234787641): Queue tasks to the registration.
+
+  // Note: This step is not in the spec, but without this step the service
+  // worker object map will always keep an entry with a service worker instance
+  // for the terminated service worker, which besides leaking memory can lead to
+  // unexpected behavior when new service worker objects are created with the
+  // same key for the service worker object map (which in Cobalt's case
+  // happens when a new service worker object is constructed at the same
+  // memory address).
+  for (auto& context : web_context_registrations_) {
+    context->message_loop()->task_runner()->PostBlockingTask(
+        FROM_HERE, base::Bind(
+                       [](web::Context* context, ServiceWorkerObject* worker) {
+                         auto worker_obj = context->LookupServiceWorker(worker);
+                         if (worker_obj) {
+                           worker_obj->set_state(kServiceWorkerStateRedundant);
+                           worker_obj->DispatchEvent(
+                               new web::Event(base::Tokens::statechange()));
+                         }
+                         context->RemoveServiceWorker(worker);
+                       },
+                       context, base::Unretained(worker)));
+  }
+
+  // 1.5. Abort the script currently running in serviceWorker.
+  if (worker->is_running()) {
+    worker->Abort();
+  }
+
+  // 1.6. Set serviceWorker’s start status to null.
+  worker->set_start_status(nullptr);
 }
 
 void ServiceWorkerJobs::Unregister(Job* job) {
@@ -1026,8 +1938,8 @@
   // 2. Let registration be the result of running Get Registration given job’s
   //    storage key and job’s scope url.
   scoped_refptr<ServiceWorkerRegistrationObject> registration =
-      service_worker_context_->registration_map()->GetRegistration(
-          job->storage_key, job->scope_url);
+      scope_to_registration_map_->GetRegistration(job->storage_key,
+                                                  job->scope_url);
 
   // 3. If registration is null, then:
   if (!registration) {
@@ -1041,14 +1953,14 @@
 
   // 4. Remove registration map[(registration’s storage key, job’s scope url)].
   // Keep the registration until this algorithm finishes.
-  service_worker_context_->registration_map()->RemoveRegistration(
-      registration->storage_key(), job->scope_url);
+  scope_to_registration_map_->RemoveRegistration(registration->storage_key(),
+                                                 job->scope_url);
 
   // 5. Invoke Resolve Job Promise with job and true.
   ResolveJobPromise(job, true);
 
   // 6. Invoke Try Clear Registration with registration.
-  service_worker_context_->TryClearRegistration(registration);
+  TryClearRegistration(registration);
 
   // 7. Invoke Finish Job with job.
   FinishJob(job);
@@ -1071,7 +1983,7 @@
   //      job promise with a new exception with errorData and a user
   //      agent-defined message, in equivalentJob’s client's Realm.
   if (job->client && job->promise != nullptr) {
-    DCHECK(service_worker_context_->IsWebContextRegistered(job->client));
+    DCHECK(IsWebContextRegistered(job->client));
     job->client->message_loop()->task_runner()->PostTask(
         FROM_HERE, base::BindOnce(
                        [](std::unique_ptr<JobPromiseType> promise,
@@ -1106,7 +2018,7 @@
   // 2.1 If equivalentJob’s client is null, continue to the next iteration of
   // the loop.
   if (job->client && job->promise != nullptr) {
-    DCHECK(service_worker_context_->IsWebContextRegistered(job->client));
+    DCHECK(IsWebContextRegistered(job->client));
     job->client->message_loop()->task_runner()->PostTask(
         FROM_HERE,
         base::BindOnce(
@@ -1170,6 +2082,784 @@
   }
 }
 
+void ServiceWorkerJobs::MaybeResolveReadyPromiseSubSteps(web::Context* client) {
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Algorithm for Sub steps of ServiceWorkerContainer.ready():
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-ready
+
+  //    3.1. Let client by this's service worker client.
+  //    3.2. Let storage key be the result of running obtain a storage
+  //         key given client.
+  url::Origin storage_key = client->environment_settings()->ObtainStorageKey();
+  //    3.3. Let registration be the result of running Match Service
+  //         Worker Registration given storage key and client’s
+  //         creation URL.
+  // TODO(b/234659851): Investigate whether this should use the creation URL
+  // directly instead.
+  const GURL& base_url = client->environment_settings()->creation_url();
+  GURL client_url = base_url.Resolve("");
+  scoped_refptr<ServiceWorkerRegistrationObject> registration =
+      scope_to_registration_map_->MatchServiceWorkerRegistration(storage_key,
+                                                                 client_url);
+  //    3.3. If registration is not null, and registration’s active
+  //         worker is not null, queue a task on readyPromise’s
+  //         relevant settings object's responsible event loop, using
+  //         the DOM manipulation task source, to resolve readyPromise
+  //         with the result of getting the service worker
+  //         registration object that represents registration in
+  //         readyPromise’s relevant settings object.
+  if (registration && registration->active_worker()) {
+    client->message_loop()->task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&ServiceWorkerContainer::MaybeResolveReadyPromise,
+                       base::Unretained(client->GetWindowOrWorkerGlobalScope()
+                                            ->navigator_base()
+                                            ->service_worker()
+                                            .get()),
+                       registration));
+  }
+}
+
+void ServiceWorkerJobs::GetRegistrationSubSteps(
+    const url::Origin& storage_key, const GURL& client_url,
+    web::Context* client,
+    std::unique_ptr<script::ValuePromiseWrappable::Reference>
+        promise_reference) {
+  TRACE_EVENT0("cobalt::worker",
+               "ServiceWorkerJobs::GetRegistrationSubSteps()");
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Algorithm for Sub steps of ServiceWorkerContainer.getRegistration():
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-getRegistration
+
+  // 8.1. Let registration be the result of running Match Service Worker
+  //      Registration algorithm with clientURL as its argument.
+  scoped_refptr<ServiceWorkerRegistrationObject> registration =
+      scope_to_registration_map_->MatchServiceWorkerRegistration(storage_key,
+                                                                 client_url);
+  // 8.2. If registration is null, resolve promise with undefined and abort
+  //      these steps.
+  // 8.3. Resolve promise with the result of getting the service worker
+  //      registration object that represents registration in promise’s
+  //      relevant settings object.
+  client->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](web::Context* client,
+             std::unique_ptr<script::ValuePromiseWrappable::Reference> promise,
+             scoped_refptr<ServiceWorkerRegistrationObject> registration) {
+            TRACE_EVENT0(
+                "cobalt::worker",
+                "ServiceWorkerJobs::GetRegistrationSubSteps() Resolve");
+            promise->value().Resolve(
+                client->GetServiceWorkerRegistration(registration));
+          },
+          client, std::move(promise_reference), registration));
+}
+
+void ServiceWorkerJobs::GetRegistrationsSubSteps(
+    const url::Origin& storage_key, web::Context* client,
+    std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
+        promise_reference) {
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  std::vector<scoped_refptr<ServiceWorkerRegistrationObject>>
+      registration_objects =
+          scope_to_registration_map_->GetRegistrations(storage_key);
+  client->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](web::Context* client,
+             std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
+                 promise,
+             std::vector<scoped_refptr<ServiceWorkerRegistrationObject>>
+                 registration_objects) {
+            TRACE_EVENT0(
+                "cobalt::worker",
+                "ServiceWorkerJobs::GetRegistrationSubSteps() Resolve");
+            script::Sequence<scoped_refptr<script::Wrappable>> registrations;
+            for (auto registration_object : registration_objects) {
+              registrations.push_back(scoped_refptr<script::Wrappable>(
+                  client->GetServiceWorkerRegistration(registration_object)
+                      .get()));
+            }
+            promise->value().Resolve(std::move(registrations));
+          },
+          client, std::move(promise_reference),
+          std::move(registration_objects)));
+}
+
+void ServiceWorkerJobs::SkipWaitingSubSteps(
+    web::Context* worker_context, ServiceWorkerObject* service_worker,
+    std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::SkipWaitingSubSteps()");
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Check if the client web context is still active. This may trigger if
+  // skipWaiting() was called and service worker installation fails.
+  if (!IsWebContextRegistered(worker_context)) {
+    promise_reference.release();
+    return;
+  }
+
+  // Algorithm for Sub steps of ServiceWorkerGlobalScope.skipWaiting():
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-serviceworkerglobalscope-skipwaiting
+
+  // 2.1. Set service worker's skip waiting flag.
+  service_worker->set_skip_waiting();
+
+  // 2.2. Invoke Try Activate with service worker's containing service worker
+  // registration.
+  TryActivate(service_worker->containing_service_worker_registration());
+
+  // 2.3. Resolve promise with undefined.
+  worker_context->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](std::unique_ptr<script::ValuePromiseVoid::Reference> promise) {
+            promise->value().Resolve();
+          },
+          std::move(promise_reference)));
+}
+
+void ServiceWorkerJobs::WaitUntilSubSteps(
+    ServiceWorkerRegistrationObject* registration) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::WaitUntilSubSteps()");
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Sub steps for WaitUntil.
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-extendableevent-waituntil
+  // 5.2.2. If registration is unregistered, invoke Try Clear Registration
+  //        with registration.
+  if (scope_to_registration_map_->IsUnregistered(registration)) {
+    TryClearRegistration(registration);
+  }
+  // 5.2.3. If registration is not null, invoke Try Activate with
+  //        registration.
+  if (registration) {
+    TryActivate(registration);
+  }
+}
+
+void ServiceWorkerJobs::ClientsGetSubSteps(
+    web::Context* worker_context,
+    ServiceWorkerObject* associated_service_worker,
+    std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference,
+    const std::string& id) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::ClientsGetSubSteps()");
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Check if the client web context is still active. This may trigger if
+  // Clients.get() was called and service worker installation fails.
+  if (!IsWebContextRegistered(worker_context)) {
+    promise_reference.release();
+    return;
+  }
+  // Parallel sub steps (2) for algorithm for Clients.get(id):
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-get
+  // 2.1. For each service worker client client where the result of running
+  //      obtain a storage key given client equals the associated service
+  //      worker's containing service worker registration's storage key:
+  const url::Origin& storage_key =
+      associated_service_worker->containing_service_worker_registration()
+          ->storage_key();
+  for (auto& client : web_context_registrations_) {
+    url::Origin client_storage_key =
+        client->environment_settings()->ObtainStorageKey();
+    if (client_storage_key.IsSameOriginWith(storage_key)) {
+      // 2.1.1. If client’s id is not id, continue.
+      if (client->environment_settings()->id() != id) continue;
+
+      // 2.1.2. Wait for either client’s execution ready flag to be set or for
+      //        client’s discarded flag to be set.
+      // Web Contexts exist only in the web_context_registrations_ set when they
+      // are both execution ready and not discarded.
+
+      // 2.1.3. If client’s execution ready flag is set, then invoke Resolve Get
+      //        Client Promise with client and promise, and abort these steps.
+      ResolveGetClientPromise(client, worker_context,
+                              std::move(promise_reference));
+      return;
+    }
+  }
+  // 2.2. Resolve promise with undefined.
+  worker_context->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](std::unique_ptr<script::ValuePromiseWrappable::Reference>
+                 promise_reference) {
+            TRACE_EVENT0("cobalt::worker",
+                         "ServiceWorkerJobs::ClientsGetSubSteps() Resolve");
+            promise_reference->value().Resolve(scoped_refptr<Client>());
+          },
+          std::move(promise_reference)));
+}
+
+void ServiceWorkerJobs::ResolveGetClientPromise(
+    web::Context* client, web::Context* worker_context,
+    std::unique_ptr<script::ValuePromiseWrappable::Reference>
+        promise_reference) {
+  TRACE_EVENT0("cobalt::worker",
+               "ServiceWorkerJobs::ResolveGetClientPromise()");
+  // Algorithm for Resolve Get Client Promise:
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#resolve-get-client-promise
+
+  // 1. If client is an environment settings object, then:
+  // 1.1. If client is not a secure context, queue a task to reject promise with
+  //      a "SecurityError" DOMException, on promise’s relevant settings
+  //      object's responsible event loop using the DOM manipulation task
+  //      source, and abort these steps.
+  // 2. Else:
+  // 2.1. If client’s creation URL is not a potentially trustworthy URL, queue
+  //      a task to reject promise with a "SecurityError" DOMException, on
+  //      promise’s relevant settings object's responsible event loop using the
+  //      DOM manipulation task source, and abort these steps.
+  // In production, Cobalt requires https, therefore all clients are secure
+  // contexts.
+
+  // 3. If client is an environment settings object and is not a window client,
+  //    then:
+  if (!client->GetWindowOrWorkerGlobalScope()->IsWindow()) {
+    // 3.1. Let clientObject be the result of running Create Client algorithm
+    //      with client as the argument.
+    scoped_refptr<Client> client_object =
+        Client::Create(client->environment_settings());
+
+    // 3.2. Queue a task to resolve promise with clientObject, on promise’s
+    //      relevant settings object's responsible event loop using the DOM
+    //      manipulation task source, and abort these steps.
+    worker_context->message_loop()->task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            [](std::unique_ptr<script::ValuePromiseWrappable::Reference>
+                   promise_reference,
+               scoped_refptr<Client> client_object) {
+              TRACE_EVENT0(
+                  "cobalt::worker",
+                  "ServiceWorkerJobs::ResolveGetClientPromise() Resolve");
+              promise_reference->value().Resolve(client_object);
+            },
+            std::move(promise_reference), client_object));
+    return;
+  }
+  // 4. Else:
+  // 4.1. Let browsingContext be null.
+  // 4.2. If client is an environment settings object, set browsingContext to
+  //      client’s global object's browsing context.
+  // 4.3. Else, set browsingContext to client’s target browsing context.
+  // Note: Cobalt does not implement a distinction between environments and
+  // environment settings objects.
+  // 4.4. Queue a task to run the following steps on browsingContext’s event
+  //      loop using the user interaction task source:
+  // Note: The task below does not currently perform any actual
+  // functionality in the client context. It is included however to help future
+  // implementation for fetching values for WindowClient properties, with
+  // similar logic existing in ClientsMatchAllSubSteps.
+  client->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](web::Context* client, web::Context* worker_context,
+             std::unique_ptr<script::ValuePromiseWrappable::Reference>
+                 promise_reference) {
+            // 4.4.1. Let frameType be the result of running Get Frame Type with
+            //        browsingContext.
+            // Cobalt does not support nested or auxiliary browsing contexts.
+            // 4.4.2. Let visibilityState be browsingContext’s active document's
+            //        visibilityState attribute value.
+            // 4.4.3. Let focusState be the result of running the has focus
+            //        steps with browsingContext’s active document as the
+            //        argument.
+            // Handled in the WindowData constructor.
+            std::unique_ptr<WindowData> window_data(
+                new WindowData(client->environment_settings()));
+
+            // 4.4.4. Let ancestorOriginsList be the empty list.
+            // 4.4.5. If client is a window client, set ancestorOriginsList to
+            //        browsingContext’s active document's relevant global
+            //        object's Location object’s ancestor origins list's
+            //        associated list.
+            // Cobalt does not implement Location.ancestorOrigins.
+
+            // 4.4.6. Queue a task to run the following steps on promise’s
+            //        relevant settings object's responsible event loop using
+            //        the DOM manipulation task source:
+            worker_context->message_loop()->task_runner()->PostTask(
+                FROM_HERE,
+                base::BindOnce(
+                    [](std::unique_ptr<script::ValuePromiseWrappable::Reference>
+                           promise_reference,
+                       std::unique_ptr<WindowData> window_data) {
+                      // 4.4.6.1. If client’s discarded flag is set, resolve
+                      //          promise with undefined and abort these
+                      //          steps.
+                      // 4.4.6.2. Let windowClient be the result of running
+                      //          Create Window Client with client,
+                      //          frameType, visibilityState, focusState,
+                      //          and ancestorOriginsList.
+                      scoped_refptr<Client> window_client =
+                          WindowClient::Create(*window_data);
+                      // 4.4.6.3. Resolve promise with windowClient.
+                      promise_reference->value().Resolve(window_client);
+                    },
+                    std::move(promise_reference), std::move(window_data)));
+          },
+          client, worker_context, std::move(promise_reference)));
+  DCHECK_EQ(nullptr, promise_reference.get());
+}
+
+void ServiceWorkerJobs::ClientsMatchAllSubSteps(
+    web::Context* worker_context,
+    ServiceWorkerObject* associated_service_worker,
+    std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
+        promise_reference,
+    bool include_uncontrolled, ClientType type) {
+  TRACE_EVENT0("cobalt::worker",
+               "ServiceWorkerJobs::ClientsMatchAllSubSteps()");
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Check if the worker web context is still active. This may trigger if
+  // Clients.matchAll() was called and service worker installation fails.
+  if (!IsWebContextRegistered(worker_context)) {
+    promise_reference.release();
+    return;
+  }
+
+  // Parallel sub steps (2) for algorithm for Clients.matchAll():
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-matchall
+  // 2.1. Let targetClients be a new list.
+  std::list<web::Context*> target_clients;
+
+  // 2.2. For each service worker client client where the result of running
+  //      obtain a storage key given client equals the associated service
+  //      worker's containing service worker registration's storage key:
+  const url::Origin& storage_key =
+      associated_service_worker->containing_service_worker_registration()
+          ->storage_key();
+  for (auto& client : web_context_registrations_) {
+    url::Origin client_storage_key =
+        client->environment_settings()->ObtainStorageKey();
+    if (client_storage_key.IsSameOriginWith(storage_key)) {
+      // 2.2.1. If client’s execution ready flag is unset or client’s discarded
+      //        flag is set, continue.
+      // Web Contexts exist only in the web_context_registrations_ set when they
+      // are both execution ready and not discarded.
+
+      // 2.2.2. If client is not a secure context, continue.
+      // In production, Cobalt requires https, therefore all workers and their
+      // owners are secure contexts.
+
+      // 2.2.3. If options["includeUncontrolled"] is false, and if client’s
+      //        active service worker is not the associated service worker,
+      //        continue.
+      if (!include_uncontrolled &&
+          (client->active_service_worker() != associated_service_worker)) {
+        continue;
+      }
+
+      // 2.2.4. Add client to targetClients.
+      target_clients.push_back(client);
+    }
+  }
+
+  // 2.3. Let matchedWindowData be a new list.
+  std::unique_ptr<std::vector<WindowData>> matched_window_data(
+      new std::vector<WindowData>);
+
+  // 2.4. Let matchedClients be a new list.
+  std::unique_ptr<std::vector<web::Context*>> matched_clients(
+      new std::vector<web::Context*>);
+
+  // 2.5. For each service worker client client in targetClients:
+  for (auto* client : target_clients) {
+    auto* global_scope = client->GetWindowOrWorkerGlobalScope();
+
+    if ((type == kClientTypeWindow || type == kClientTypeAll) &&
+        (global_scope->IsWindow())) {
+      // 2.5.1. If options["type"] is "window" or "all", and client is not an
+      //        environment settings object or is a window client, then:
+
+      // 2.5.1.1. Let windowData be [ "client" -> client, "ancestorOriginsList"
+      //          -> a new list ].
+      WindowData window_data(client->environment_settings());
+
+      // 2.5.1.2. Let browsingContext be null.
+
+      // 2.5.1.3. Let isClientEnumerable be true.
+      // For Cobalt, isClientEnumerable is always true because the clauses that
+      // would set it to false in 2.5.1.6. do not apply to Cobalt.
+
+      // 2.5.1.4. If client is an environment settings object, set
+      //          browsingContext to client’s global object's browsing context.
+      // 2.5.1.5. Else, set browsingContext to client’s target browsing context.
+      web::Context* browsing_context = client;
+
+      // 2.5.1.6. Queue a task task to run the following substeps on
+      //          browsingContext’s event loop using the user interaction task
+      //          source:
+      // Note: The task below does not currently perform any actual
+      // functionality. It is included however to help future implementation for
+      // fetching values for WindowClient properties, with similar logic
+      // existing in ResolveGetClientPromise.
+      browsing_context->message_loop()->task_runner()->PostBlockingTask(
+          FROM_HERE, base::Bind(
+                         [](WindowData* window_data) {
+                           // 2.5.1.6.1. If browsingContext has been discarded,
+                           //            then set isClientEnumerable to false
+                           //            and abort these steps.
+                           // 2.5.1.6.2. If client is a window client and
+                           //            client’s responsible document is not
+                           //            browsingContext’s active document, then
+                           //            set isClientEnumerable to false and
+                           //            abort these steps.
+                           // In Cobalt, the document of a window browsing
+                           // context doesn't change: When a new document is
+                           // created, a new browsing context is created with
+                           // it.
+
+                           // 2.5.1.6.3. Set windowData["frameType"] to the
+                           //            result of running Get Frame Type with
+                           //            browsingContext.
+                           // Cobalt does not support nested or auxiliary
+                           // browsing contexts.
+                           // 2.5.1.6.4. Set windowData["visibilityState"] to
+                           //            browsingContext’s active document's
+                           //            visibilityState attribute value.
+                           // 2.5.1.6.5. Set windowData["focusState"] to the
+                           //            result of running the has focus steps
+                           //            with browsingContext’s active document
+                           //            as the argument.
+
+                           // 2.5.1.6.6. If client is a window client, then set
+                           //            windowData["ancestorOriginsList"] to
+                           //            browsingContext’s active document's
+                           //            relevant global object's Location
+                           //            object’s ancestor origins list's
+                           //            associated list.
+                           // Cobalt does not implement
+                           // Location.ancestorOrigins.
+                         },
+                         &window_data));
+
+      // 2.5.1.7. Wait for task to have executed.
+      // The task above is posted as a blocking task.
+
+      // 2.5.1.8. If isClientEnumerable is true, then:
+
+      // 2.5.1.8.1. Add windowData to matchedWindowData.
+      matched_window_data->emplace_back(window_data);
+
+      // 2.5.2. Else if options["type"] is "worker" or "all" and client is a
+      //        dedicated worker client, or options["type"] is "sharedworker" or
+      //        "all" and client is a shared worker client, then:
+    } else if (((type == kClientTypeWorker || type == kClientTypeAll) &&
+                global_scope->IsDedicatedWorker())) {
+      // Note: Cobalt does not support shared workers.
+      // 2.5.2.1. Add client to matchedClients.
+      matched_clients->emplace_back(client);
+    }
+  }
+
+  // 2.6. Queue a task to run the following steps on promise’s relevant
+  // settings object's responsible event loop using the DOM manipulation
+  // task source:
+  worker_context->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
+                 promise_reference,
+             std::unique_ptr<std::vector<WindowData>> matched_window_data,
+             std::unique_ptr<std::vector<web::Context*>> matched_clients) {
+            TRACE_EVENT0(
+                "cobalt::worker",
+                "ServiceWorkerJobs::ClientsMatchAllSubSteps() Resolve Promise");
+            // 2.6.1. Let clientObjects be a new list.
+            script::Sequence<scoped_refptr<script::Wrappable>> client_objects;
+
+            // 2.6.2. For each windowData in matchedWindowData:
+            for (auto& window_data : *matched_window_data) {
+              // 2.6.2.1. Let WindowClient be the result of running
+              //          Create Window Client algorithm with
+              //          windowData["client"],
+              //          windowData["frameType"],
+              //          windowData["visibilityState"],
+              //          windowData["focusState"], and
+              //          windowData["ancestorOriginsList"] as the
+              //          arguments.
+              // TODO(b/235838698): Implement WindowClient methods.
+              scoped_refptr<Client> window_client =
+                  WindowClient::Create(window_data);
+
+              // 2.6.2.2. Append WindowClient to clientObjects.
+              client_objects.push_back(window_client);
+            }
+
+            // 2.6.3. For each client in matchedClients:
+            for (auto& client : *matched_clients) {
+              // 2.6.3.1. Let clientObject be the result of running
+              //          Create Client algorithm with client as the
+              //          argument.
+              scoped_refptr<Client> client_object =
+                  Client::Create(client->environment_settings());
+
+              // 2.6.3.2. Append clientObject to clientObjects.
+              client_objects.push_back(client_object);
+            }
+            // 2.6.4. Sort clientObjects such that:
+            //        . WindowClient objects whose browsing context has been
+            //          focused are placed first, sorted in the most recently
+            //          focused order.
+            //        . WindowClient objects whose browsing context has never
+            //          been focused are placed next, sorted in their service
+            //          worker client's creation order.
+            //        . Client objects whose associated service worker client is
+            //          a worker client are placed next, sorted in their service
+            //          worker client's creation order.
+            // TODO(b/235876598): Implement sorting of clientObjects.
+
+            // 2.6.5. Resolve promise with a new frozen array of clientObjects
+            //        in promise’s relevant Realm.
+            promise_reference->value().Resolve(client_objects);
+          },
+          std::move(promise_reference), std::move(matched_window_data),
+          std::move(matched_clients)));
+}
+
+void ServiceWorkerJobs::ClaimSubSteps(
+    web::Context* worker_context,
+    ServiceWorkerObject* associated_service_worker,
+    std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::ClaimSubSteps()");
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+
+  // Check if the client web context is still active. This may trigger if
+  // Clients.claim() was called and service worker installation fails.
+  if (!IsWebContextRegistered(worker_context)) {
+    promise_reference.release();
+    return;
+  }
+
+  // Parallel sub steps (3) for algorithm for Clients.claim():
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-clients-claim
+  std::list<web::Context*> target_clients;
+
+  // 3.1. For each service worker client client where the result of running
+  //      obtain a storage key given client equals the service worker's
+  //      containing service worker registration's storage key:
+  const url::Origin& storage_key =
+      associated_service_worker->containing_service_worker_registration()
+          ->storage_key();
+  for (auto& client : web_context_registrations_) {
+    // Don't claim to be our own service worker.
+    if (client == worker_context) continue;
+    url::Origin client_storage_key =
+        client->environment_settings()->ObtainStorageKey();
+    if (client_storage_key.IsSameOriginWith(storage_key)) {
+      // 3.1.1. If client’s execution ready flag is unset or client’s discarded
+      //        flag is set, continue.
+      // Web Contexts exist only in the web_context_registrations_ set when they
+      // are both execution ready and not discarded.
+
+      // 3.1.2. If client is not a secure context, continue.
+      // In production, Cobalt requires https, therefore all clients are secure
+      // contexts.
+
+      // 3.1.3. Let storage key be the result of running obtain a storage key
+      //        given client.
+      // 3.1.4. Let registration be the result of running Match Service Worker
+      //        Registration given storage key and client’s creation URL.
+      // TODO(b/234659851): Investigate whether this should use the creation
+      // URL directly instead.
+      const GURL& base_url = client->environment_settings()->creation_url();
+      GURL client_url = base_url.Resolve("");
+      scoped_refptr<ServiceWorkerRegistrationObject> registration =
+          scope_to_registration_map_->MatchServiceWorkerRegistration(
+              client_storage_key, client_url);
+
+      // 3.1.5. If registration is not the service worker's containing service
+      //        worker registration, continue.
+      if (registration !=
+          associated_service_worker->containing_service_worker_registration()) {
+        continue;
+      }
+
+      // 3.1.6. If client’s active service worker is not the service worker,
+      //        then:
+      if (client->active_service_worker() != associated_service_worker) {
+        // 3.1.6.1. Invoke Handle Service Worker Client Unload with client as
+        //          the argument.
+        HandleServiceWorkerClientUnload(client);
+
+        // 3.1.6.2. Set client’s active service worker to service worker.
+        client->set_active_service_worker(associated_service_worker);
+
+        // 3.1.6.3. Invoke Notify Controller Change algorithm with client as the
+        //          argument.
+        NotifyControllerChange(client);
+      }
+    }
+  }
+  // 3.2. Resolve promise with undefined.
+  worker_context->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](std::unique_ptr<script::ValuePromiseVoid::Reference> promise) {
+            promise->value().Resolve();
+          },
+          std::move(promise_reference)));
+}
+
+void ServiceWorkerJobs::ServiceWorkerPostMessageSubSteps(
+    ServiceWorkerObject* service_worker, web::Context* incumbent_client,
+    std::unique_ptr<script::StructuredClone> structured_clone) {
+  // Parallel sub steps (6) for algorithm for ServiceWorker.postMessage():
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-postmessage-options
+  // 3. Let incumbentGlobal be incumbentSettings’s global object.
+  // Note: The 'incumbent' is the sender of the message.
+  // 6.1 If the result of running the Run Service Worker algorithm with
+  //     serviceWorker is failure, then return.
+  auto* run_result = RunServiceWorker(service_worker);
+  if (!run_result) return;
+  if (!structured_clone || structured_clone->failed()) return;
+
+  // 6.2 Queue a task on the DOM manipulation task source to run the following
+  //     steps:
+  incumbent_client->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](ServiceWorkerObject* service_worker,
+             web::Context* incumbent_client,
+             std::unique_ptr<script::StructuredClone> structured_clone) {
+            if (!structured_clone || structured_clone->failed()) return;
+
+            web::EventTarget* event_target =
+                service_worker->worker_global_scope();
+            if (!event_target) return;
+
+            web::WindowOrWorkerGlobalScope* incumbent_global =
+                incumbent_client->GetWindowOrWorkerGlobalScope();
+            DCHECK_EQ(incumbent_client->environment_settings(),
+                      incumbent_global->environment_settings());
+            base::TypeId incumbent_type = incumbent_global->GetWrappableType();
+            ServiceWorkerObject* incumbent_worker =
+                incumbent_global->IsServiceWorker()
+                    ? incumbent_global->AsServiceWorker()
+                          ->service_worker_object()
+                    : nullptr;
+            base::MessageLoop* message_loop =
+                event_target->environment_settings()->context()->message_loop();
+            if (!message_loop) {
+              return;
+            }
+            message_loop->task_runner()->PostTask(
+                FROM_HERE,
+                base::BindOnce(
+                    [](const base::TypeId& incumbent_type,
+                       ServiceWorkerObject* incumbent_worker,
+                       web::Context* incumbent_client,
+                       web::EventTarget* event_target,
+                       std::unique_ptr<script::StructuredClone>
+                           structured_clone) {
+                      ExtendableMessageEventInit init_dict;
+                      if (incumbent_type ==
+                          base::GetTypeId<ServiceWorkerGlobalScope>()) {
+                        // 6.2.1. Let source be determined by switching on the
+                        //        type of incumbentGlobal:
+                        //        . ServiceWorkerGlobalScope
+                        //          The result of getting the service worker
+                        //          object that represents incumbentGlobal’s
+                        //          service worker in the relevant settings
+                        //          object of serviceWorker’s global object.
+                        init_dict.set_source(ExtendableMessageEvent::SourceType(
+                            event_target->environment_settings()
+                                ->context()
+                                ->GetServiceWorker(incumbent_worker)));
+                      } else if (incumbent_type ==
+                                 base::GetTypeId<dom::Window>()) {
+                        //        . Window
+                        //          a new WindowClient object that represents
+                        //          incumbentGlobal’s relevant settings object.
+                        init_dict.set_source(ExtendableMessageEvent::SourceType(
+                            WindowClient::Create(WindowData(
+                                incumbent_client->environment_settings()))));
+                      } else {
+                        //        . Otherwise
+                        //          a new Client object that represents
+                        //          incumbentGlobal’s associated worker
+                        init_dict.set_source(
+                            ExtendableMessageEvent::SourceType(Client::Create(
+                                incumbent_client->environment_settings())));
+                      }
+
+                      event_target->DispatchEvent(
+                          new worker::ExtendableMessageEvent(
+                              event_target->environment_settings(),
+                              base::Tokens::message(), init_dict,
+                              std::move(structured_clone)));
+                    },
+                    incumbent_type, base::Unretained(incumbent_worker),
+                    // Note: These should probably be weak pointers for when
+                    // the message sender disappears before the recipient
+                    // processes the event, but since base::WeakPtr
+                    // dereferencing isn't thread-safe, that can't actually be
+                    // used here.
+                    base::Unretained(incumbent_client),
+                    base::Unretained(event_target),
+                    std::move(structured_clone)));
+          },
+          base::Unretained(service_worker), base::Unretained(incumbent_client),
+          std::move(structured_clone)));
+}
+
+void ServiceWorkerJobs::RegisterWebContext(web::Context* context) {
+  DCHECK_NE(nullptr, context);
+  web_context_registrations_cleared_.Reset();
+  if (base::MessageLoop::current() != message_loop()) {
+    DCHECK(message_loop());
+    message_loop()->task_runner()->PostTask(
+        FROM_HERE, base::BindOnce(&ServiceWorkerJobs::RegisterWebContext,
+                                  base::Unretained(this), context));
+    return;
+  }
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  DCHECK_EQ(0, web_context_registrations_.count(context));
+  web_context_registrations_.insert(context);
+}
+
+void ServiceWorkerJobs::SetActiveWorker(web::EnvironmentSettings* client) {
+  if (!client) return;
+  if (base::MessageLoop::current() != message_loop()) {
+    DCHECK(message_loop());
+    message_loop()->task_runner()->PostTask(
+        FROM_HERE, base::Bind(&ServiceWorkerJobs::SetActiveWorker,
+                              base::Unretained(this), client));
+    return;
+  }
+  DCHECK(scope_to_registration_map_);
+  scoped_refptr<ServiceWorkerRegistrationObject> client_registration =
+      scope_to_registration_map_->MatchServiceWorkerRegistration(
+          client->ObtainStorageKey(), client->creation_url());
+  if (client_registration.get() && client_registration->active_worker()) {
+    client->context()->set_active_service_worker(
+        client_registration->active_worker());
+  } else {
+    client->context()->set_active_service_worker(nullptr);
+  }
+}
+
+void ServiceWorkerJobs::UnregisterWebContext(web::Context* context) {
+  DCHECK_NE(nullptr, context);
+  if (base::MessageLoop::current() != message_loop()) {
+    // Block to ensure that the context is unregistered before it is destroyed.
+    DCHECK(message_loop());
+    message_loop()->task_runner()->PostBlockingTask(
+        FROM_HERE, base::Bind(&ServiceWorkerJobs::UnregisterWebContext,
+                              base::Unretained(this), context));
+    return;
+  }
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  DCHECK_EQ(1, web_context_registrations_.count(context));
+  web_context_registrations_.erase(context);
+  HandleServiceWorkerClientUnload(context);
+  PrepareForClientShutdown(context);
+  if (web_context_registrations_.empty()) {
+    web_context_registrations_cleared_.Signal();
+  }
+}
+
 void ServiceWorkerJobs::PrepareForClientShutdown(web::Context* client) {
   DCHECK(client);
   if (!client) return;
diff --git a/cobalt/worker/service_worker_jobs.h b/cobalt/worker/service_worker_jobs.h
index adabe15..ffc80d9 100644
--- a/cobalt/worker/service_worker_jobs.h
+++ b/cobalt/worker/service_worker_jobs.h
@@ -18,13 +18,16 @@
 #include <deque>
 #include <map>
 #include <memory>
+#include <set>
 #include <string>
 #include <utility>
 
+#include "base/memory/ref_counted.h"
 #include "base/memory/scoped_refptr.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/task/sequence_manager/moveable_auto_lock.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/script_loader_factory.h"
@@ -35,9 +38,17 @@
 #include "cobalt/script/script_value_factory.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/dom_exception.h"
+#include "cobalt/web/web_settings.h"
+#include "cobalt/worker/client_query_options.h"
+#include "cobalt/worker/frame_type.h"
+#include "cobalt/worker/service_worker.h"
+#include "cobalt/worker/service_worker_consts.h"
 #include "cobalt/worker/service_worker_object.h"
+#include "cobalt/worker/service_worker_registration.h"
+#include "cobalt/worker/service_worker_registration_map.h"
 #include "cobalt/worker/service_worker_registration_object.h"
 #include "cobalt/worker/service_worker_update_via_cache.h"
+#include "cobalt/worker/worker_type.h"
 #include "starboard/common/atomic.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -45,8 +56,6 @@
 namespace cobalt {
 namespace worker {
 
-class ServiceWorkerContext;
-
 // Algorithms for Service Worker Jobs.
 //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#algorithms
 class ServiceWorkerJobs {
@@ -180,17 +189,101 @@
     std::deque<std::unique_ptr<Job>> jobs_;
   };
 
-  ServiceWorkerJobs(ServiceWorkerContext* service_worker_context,
+  ServiceWorkerJobs(web::WebSettings* web_settings,
                     network::NetworkModule* network_module,
-                    base::MessageLoop* message_loop);
+                    web::UserAgentPlatformInfo* platform_info,
+                    base::MessageLoop* message_loop, const GURL& url);
   ~ServiceWorkerJobs();
 
   base::MessageLoop* message_loop() { return message_loop_; }
 
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#start-register-algorithm
+  void StartRegister(const base::Optional<GURL>& scope_url,
+                     const GURL& script_url,
+                     std::unique_ptr<script::ValuePromiseWrappable::Reference>
+                         promise_reference,
+                     web::Context* client, const WorkerType& type,
+                     const ServiceWorkerUpdateViaCache& update_via_cache);
+
+  void MaybeResolveReadyPromiseSubSteps(web::Context* client);
+
+  // Sub steps (8) of ServiceWorkerContainer.getRegistration().
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-getRegistration
+  void GetRegistrationSubSteps(
+      const url::Origin& storage_key, const GURL& client_url,
+      web::Context* client,
+      std::unique_ptr<script::ValuePromiseWrappable::Reference>
+          promise_reference);
+
+  void GetRegistrationsSubSteps(
+      const url::Origin& storage_key, web::Context* client,
+      std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
+          promise_reference);
+
+  // Sub steps (2) of ServiceWorkerGlobalScope.skipWaiting().
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-serviceworkerglobalscope-skipwaiting
+  void SkipWaitingSubSteps(
+      web::Context* worker_context, ServiceWorkerObject* service_worker,
+      std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference);
+
+  // Sub steps for ExtendableEvent.WaitUntil().
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-extendableevent-waituntil
+  void WaitUntilSubSteps(ServiceWorkerRegistrationObject* registration);
+
+  // Parallel sub steps (2) for algorithm for Clients.get(id):
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-get
+  void ClientsGetSubSteps(
+      web::Context* worker_context,
+      ServiceWorkerObject* associated_service_worker,
+      std::unique_ptr<script::ValuePromiseWrappable::Reference>
+          promise_reference,
+      const std::string& id);
+
+  // Algorithm for Resolve Get Client Promise:
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#resolve-get-client-promise
+  void ResolveGetClientPromise(
+      web::Context* client, web::Context* worker_context,
+      std::unique_ptr<script::ValuePromiseWrappable::Reference>
+          promise_reference);
+
+  // Parallel sub steps (2) for algorithm for Clients.matchAll():
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-matchall
+  void ClientsMatchAllSubSteps(
+      web::Context* worker_context,
+      ServiceWorkerObject* associated_service_worker,
+      std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
+          promise_reference,
+      bool include_uncontrolled, ClientType type);
+
+  // Parallel sub steps (3) for algorithm for Clients.claim():
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-clients-claim
+  void ClaimSubSteps(
+      web::Context* worker_context,
+      ServiceWorkerObject* associated_service_worker,
+      std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference);
+
+  // Parallel sub steps (6) for algorithm for ServiceWorker.postMessage():
+  //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-postmessage-options
+  void ServiceWorkerPostMessageSubSteps(
+      ServiceWorkerObject* service_worker, web::Context* incumbent_client,
+      std::unique_ptr<script::StructuredClone> structured_clone);
+
+  // Registration of web contexts that may have service workers.
+  void RegisterWebContext(web::Context* context);
+  void UnregisterWebContext(web::Context* context);
+  bool IsWebContextRegistered(web::Context* context) {
+    DCHECK(base::MessageLoop::current() == message_loop());
+    return web_context_registrations_.end() !=
+           web_context_registrations_.find(context);
+  }
+
   // Ensure no references are kept to JS objects for a client that is about to
   // be shutdown.
   void PrepareForClientShutdown(web::Context* client);
 
+  // Set the active worker for a client if there is a matching service worker.
+  void SetActiveWorker(web::EnvironmentSettings* client);
+
   // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#create-job
   std::unique_ptr<Job> CreateJob(
       JobType type, const url::Origin& storage_key, const GURL& scope_url,
@@ -225,9 +318,21 @@
   // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#schedule-job
   void ScheduleJob(std::unique_ptr<Job> job);
 
- private:
-  friend class ServiceWorkerContext;
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#activation-algorithm
+  void Activate(ServiceWorkerRegistrationObject* registration);
 
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clear-registration-algorithm
+  void ClearRegistration(ServiceWorkerRegistrationObject* registration);
+
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#soft-update
+  void SoftUpdate(ServiceWorkerRegistrationObject* registration,
+                  bool force_bypass_cache);
+
+  void EnsureServiceWorkerStarted(const url::Origin& storage_key,
+                                  const GURL& client_url,
+                                  base::WaitableEvent* done_event);
+
+ private:
   // State used for the 'Update' algorithm.
   struct UpdateJobState : public base::RefCounted<UpdateJobState> {
     UpdateJobState(
@@ -275,6 +380,8 @@
     const std::string message_;
   };
 
+  enum RegistrationState { kInstalling, kWaiting, kActive };
+
   // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-job-equivalent
   bool ReturnJobsAreEquivalent(Job* one, Job* two);
 
@@ -321,21 +428,66 @@
   // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#finish-job-algorithm
   void FinishJob(Job* job);
 
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-service-worker-algorithm
+  // The return value is a 'Completion or failure'.
+  // A failure is signaled by returning nullptr. Otherwise, the returned string
+  // points to the value of the Completion returned by the script runner
+  // abstraction.
+  std::string* RunServiceWorker(ServiceWorkerObject* worker,
+                                bool force_bypass_cache = false);
+
   // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#installation-algorithm
   void Install(
       Job* job, const scoped_refptr<ServiceWorkerObject>& worker,
       const scoped_refptr<ServiceWorkerRegistrationObject>& registration);
 
-  ServiceWorkerContext* service_worker_context_;
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-activate-algorithm
+  void TryActivate(ServiceWorkerRegistrationObject* registration);
+
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-has-no-pending-events
+  bool ServiceWorkerHasNoPendingEvents(ServiceWorkerObject* worker);
+
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-registration-state-algorithm
+  void UpdateRegistrationState(
+      ServiceWorkerRegistrationObject* registration, RegistrationState target,
+      const scoped_refptr<ServiceWorkerObject>& source);
+
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-state-algorithm
+  void UpdateWorkerState(ServiceWorkerObject* worker, ServiceWorkerState state);
+
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-client-unload-algorithm
+  void HandleServiceWorkerClientUnload(web::Context* client);
+
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#terminate-service-worker
+  void TerminateServiceWorker(ServiceWorkerObject* worker);
+
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#notify-controller-change-algorithm
+  void NotifyControllerChange(web::Context* client);
+
+  // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-clear-registration-algorithm
+  void TryClearRegistration(ServiceWorkerRegistrationObject* registration);
+
+  bool IsAnyClientUsingRegistration(
+      ServiceWorkerRegistrationObject* registration);
+
+  // Returns false when the timeout is reached.
+  bool WaitForAsynchronousExtensions(
+      const scoped_refptr<ServiceWorkerRegistrationObject>& registration);
 
   // FetcherFactory that is used to create a fetcher according to URL.
   std::unique_ptr<loader::FetcherFactory> fetcher_factory_;
   // LoaderFactory that is used to acquire references to resources from a URL.
   std::unique_ptr<loader::ScriptLoaderFactory> script_loader_factory_;
-
   base::MessageLoop* message_loop_;
 
   JobQueueMap job_queue_map_;
+  std::unique_ptr<ServiceWorkerRegistrationMap> scope_to_registration_map_;
+
+  std::set<web::Context*> web_context_registrations_;
+
+  base::WaitableEvent web_context_registrations_cleared_ = {
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED};
 };
 
 }  // namespace worker
diff --git a/cobalt/worker/service_worker_persistent_settings.cc b/cobalt/worker/service_worker_persistent_settings.cc
index f74d63f..41a689d 100644
--- a/cobalt/worker/service_worker_persistent_settings.cc
+++ b/cobalt/worker/service_worker_persistent_settings.cc
@@ -31,7 +31,6 @@
 #include "cobalt/script/script_value.h"
 #include "cobalt/web/cache_utils.h"
 #include "cobalt/worker/service_worker_consts.h"
-#include "cobalt/worker/service_worker_context.h"
 #include "cobalt/worker/service_worker_jobs.h"
 #include "cobalt/worker/service_worker_registration_object.h"
 #include "cobalt/worker/service_worker_update_via_cache.h"
@@ -172,20 +171,20 @@
     registration_map.insert(std::make_pair(key, registration));
     registration->set_is_persisted(true);
 
-    options_.service_worker_context->message_loop()->task_runner()->PostTask(
+    options_.service_worker_jobs->message_loop()->task_runner()->PostTask(
         FROM_HERE,
-        base::BindOnce(&ServiceWorkerContext::Activate,
-                       base::Unretained(options_.service_worker_context),
+        base::BindOnce(&ServiceWorkerJobs::Activate,
+                       base::Unretained(options_.service_worker_jobs),
                        registration));
 
-    auto job = options_.service_worker_context->jobs()->CreateJobWithoutPromise(
+    auto job = options_.service_worker_jobs->CreateJobWithoutPromise(
         ServiceWorkerJobs::JobType::kUpdate, storage_key, scope,
         registration->waiting_worker()->script_url());
-    options_.service_worker_context->message_loop()->task_runner()->PostTask(
-        FROM_HERE, base::BindOnce(&ServiceWorkerJobs::ScheduleJob,
-                                  base::Unretained(
-                                      options_.service_worker_context->jobs()),
-                                  std::move(job)));
+    options_.service_worker_jobs->message_loop()->task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&ServiceWorkerJobs::ScheduleJob,
+                       base::Unretained(options_.service_worker_jobs),
+                       std::move(job)));
   }
 }
 
@@ -200,7 +199,7 @@
                                        options_.web_settings,
                                        options_.network_module, registration);
   options.web_options.platform_info = options_.platform_info;
-  options.web_options.service_worker_context = options_.service_worker_context;
+  options.web_options.service_worker_jobs = options_.service_worker_jobs;
   scoped_refptr<ServiceWorkerObject> worker(new ServiceWorkerObject(options));
 
   base::Value* script_url_value = value_dict->FindKeyOfType(
diff --git a/cobalt/worker/service_worker_persistent_settings.h b/cobalt/worker/service_worker_persistent_settings.h
index e595acb..1d4fd2e 100644
--- a/cobalt/worker/service_worker_persistent_settings.h
+++ b/cobalt/worker/service_worker_persistent_settings.h
@@ -48,16 +48,16 @@
     Options(web::WebSettings* web_settings,
             network::NetworkModule* network_module,
             web::UserAgentPlatformInfo* platform_info,
-            ServiceWorkerContext* service_worker_context, const GURL& url)
+            ServiceWorkerJobs* service_worker_jobs, const GURL& url)
         : web_settings(web_settings),
           network_module(network_module),
           platform_info(platform_info),
-          service_worker_context(service_worker_context),
+          service_worker_jobs(service_worker_jobs),
           url(url) {}
     web::WebSettings* web_settings;
     network::NetworkModule* network_module;
     web::UserAgentPlatformInfo* platform_info;
-    ServiceWorkerContext* service_worker_context;
+    ServiceWorkerJobs* service_worker_jobs;
     const GURL& url;
   };
 
diff --git a/cobalt/worker/service_worker_registration.cc b/cobalt/worker/service_worker_registration.cc
index 514463a..adba128 100644
--- a/cobalt/worker/service_worker_registration.cc
+++ b/cobalt/worker/service_worker_registration.cc
@@ -27,7 +27,6 @@
 #include "cobalt/web/environment_settings.h"
 #include "cobalt/web/window_or_worker_global_scope.h"
 #include "cobalt/worker/service_worker.h"
-#include "cobalt/worker/service_worker_context.h"
 #include "cobalt/worker/service_worker_global_scope.h"
 #include "cobalt/worker/service_worker_jobs.h"
 #include "cobalt/worker/service_worker_registration_object.h"
@@ -111,18 +110,20 @@
   // 6. Let job be the result of running Create Job with update, registration’s
   //    storage key, registration’s scope url, newestWorker’s script url,
   //    promise, and this's relevant settings object.
-  ServiceWorkerJobs* jobs =
-      environment_settings()->context()->service_worker_context()->jobs();
+  worker::ServiceWorkerJobs* jobs =
+      environment_settings()->context()->service_worker_jobs();
   std::unique_ptr<ServiceWorkerJobs::Job> job = jobs->CreateJob(
       ServiceWorkerJobs::JobType::kUpdate, registration_->storage_key(),
       registration_->scope_url(), newest_worker->script_url(),
       std::move(promise_reference), environment_settings()->context());
+  DCHECK(!promise_reference);
 
   // 7. Set job’s worker type to newestWorker’s type.
   // Cobalt only supports 'classic' worker type.
 
   // 8. Invoke Schedule Job with job.
   jobs->ScheduleJob(std::move(job));
+  DCHECK(!job.get());
 }
 
 script::HandlePromiseBool ServiceWorkerRegistration::Unregister() {
@@ -145,7 +146,7 @@
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
       base::BindOnce(
-          [](ServiceWorkerJobs* jobs, const url::Origin& storage_key,
+          [](worker::ServiceWorkerJobs* jobs, const url::Origin& storage_key,
              const GURL& scope_url,
              std::unique_ptr<script::ValuePromiseBool::Reference>
                  promise_reference,
@@ -162,7 +163,7 @@
             jobs->ScheduleJob(std::move(job));
             DCHECK(!job.get());
           },
-          environment_settings()->context()->service_worker_context()->jobs(),
+          environment_settings()->context()->service_worker_jobs(),
           registration_->storage_key(), registration_->scope_url(),
           std::move(promise_reference), environment_settings()->context()));
   // 5. Return promise.
diff --git a/cobalt/worker/service_worker_registration_map.cc b/cobalt/worker/service_worker_registration_map.cc
index 3532094..760c457 100644
--- a/cobalt/worker/service_worker_registration_map.cc
+++ b/cobalt/worker/service_worker_registration_map.cc
@@ -29,7 +29,7 @@
 #include "cobalt/script/exception_message.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/script/script_value.h"
-#include "cobalt/worker/service_worker_context.h"
+#include "cobalt/worker/service_worker_jobs.h"
 #include "cobalt/worker/service_worker_registration_object.h"
 #include "cobalt/worker/service_worker_update_via_cache.h"
 #include "url/gurl.h"
@@ -252,7 +252,7 @@
 }
 
 void ServiceWorkerRegistrationMap::HandleUserAgentShutdown(
-    ServiceWorkerContext* context) {
+    ServiceWorkerJobs* jobs) {
   TRACE_EVENT0("cobalt::worker",
                "ServiceWorkerRegistrationMap::HandleUserAgentShutdown()");
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -270,7 +270,7 @@
       // active worker is null, invoke Clear Registration with registration and
       // continue to the next iteration of the loop.
       if (!registration->waiting_worker() && !registration->active_worker()) {
-        context->ClearRegistration(registration);
+        jobs->ClearRegistration(registration);
         continue;
       } else {
         // 1.1.2. Else, set installingWorker to null.
@@ -283,7 +283,7 @@
       // substep in parallel:
 
       // 1.2.1. Invoke Activate with registration.
-      context->Activate(registration);
+      jobs->Activate(registration);
     }
   }
 }
diff --git a/cobalt/worker/service_worker_registration_map.h b/cobalt/worker/service_worker_registration_map.h
index 591f58f..f998319 100644
--- a/cobalt/worker/service_worker_registration_map.h
+++ b/cobalt/worker/service_worker_registration_map.h
@@ -37,8 +37,7 @@
 
 namespace cobalt {
 namespace worker {
-
-class ServiceWorkerContext;
+class ServiceWorkerJobs;
 
 // Algorithms for the service worker scope to registration map.
 //   https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-scope-to-registration-map
@@ -70,11 +69,11 @@
   bool IsUnregistered(ServiceWorkerRegistrationObject* registration);
 
   // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-user-agent-shutdown-algorithm
-  void HandleUserAgentShutdown(ServiceWorkerContext* context);
+  void HandleUserAgentShutdown(ServiceWorkerJobs* jobs);
 
   void AbortAllActive();
 
-  // Called from the end of ServiceWorkerContext Install, Activate, and Clear
+  // Called from the end of ServiceWorkerJobs Install, Activate, and Clear
   // Registration since these are the cases in which a service worker
   // registration's active_worker or waiting_worker are updated.
   void PersistRegistration(const url::Origin& storage_key, const GURL& scope);
diff --git a/components/crx_file/BUILD.gn b/components/crx_file/BUILD.gn
index 3a05532..2b103d2 100644
--- a/components/crx_file/BUILD.gn
+++ b/components/crx_file/BUILD.gn
@@ -12,9 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-if (is_starboard) {
-  import("//components/crx_file/testdata/sha1_files.gni")
-}
 import("//third_party/protobuf/proto_library.gni")
 
 # The accompanying crx_creator target has been left behind during the migration
@@ -38,60 +35,3 @@
     "//third_party/protobuf:protobuf_lite",
   ]
 }
-
-if (is_starboard) {
-  action("crx_file_download_test_data") {
-    install_content = true
-
-    script = "//tools/download_from_gcs.py"
-
-    sha_sources = []
-    foreach(sha1_file, sha1_files) {
-      sha_sources += [ string_join("/",
-                                   [
-                                     "testdata",
-                                     sha1_file,
-                                   ]) ]
-    }
-
-    sha_outputs = []
-    subdir = "components/crx_file"
-    outdir = "$sb_static_contents_output_data_dir/test/$subdir"
-    foreach(sha_source, sha_sources) {
-      sha_outputs += [ string_join("/",
-                                   [
-                                     outdir,
-                                     string_replace(sha_source, ".sha1", ""),
-                                   ]) ]
-    }
-
-    sources = sha_sources
-    outputs = sha_outputs
-
-    sha1_dir = rebase_path("testdata", root_build_dir)
-
-    args = [
-      "--bucket",
-      "cobalt-static-storage",
-      "--sha1",
-      sha1_dir,
-      "--output",
-      rebase_path("$outdir/testdata", root_build_dir),
-    ]
-  }
-
-  target(gtest_target_type, "crx_file_test") {
-    testonly = true
-
-    sources = [ "crx_verifier_unittest.cc" ]
-
-    deps = [
-      ":crx_file",
-      "//cobalt/test:run_all_unittests",
-      "//starboard:starboard_headers_only",
-      "//testing/gtest",
-    ]
-
-    data_deps = [ ":crx_file_download_test_data" ]
-  }
-}
diff --git a/components/crx_file/crx_verifier_unittest.cc b/components/crx_file/crx_verifier_unittest.cc
index 4c41e4e..8028b7c 100644
--- a/components/crx_file/crx_verifier_unittest.cc
+++ b/components/crx_file/crx_verifier_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright 2017 The Cobalt Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -7,24 +7,11 @@
 #include "base/files/file_path.h"
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
-#if defined(STARBOARD)
-#include "starboard/system.h"
-#endif
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
 
 base::FilePath TestFile(const std::string& file) {
-#if defined(STARBOARD)
-  std::vector<char> buf(kSbFileMaxPath);
-  SbSystemGetPath(kSbSystemPathContentDirectory, buf.data(), kSbFileMaxPath);
-  return base::FilePath(buf.data())
-      .AppendASCII("test")
-      .AppendASCII("components")
-      .AppendASCII("crx_file")
-      .AppendASCII("testdata")
-      .AppendASCII(file);
-#else
   base::FilePath path;
   base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
   return path.AppendASCII("components")
@@ -32,7 +19,6 @@
       .AppendASCII("data")
       .AppendASCII("crx_file")
       .AppendASCII(file);
-#endif
 }
 
 constexpr char kOjjHash[] = "ojjgnpkioondelmggbekfhllhdaimnho";
diff --git a/components/crx_file/testdata/sha1_files.gni b/components/crx_file/testdata/sha1_files.gni
deleted file mode 100644
index f6f5917..0000000
--- a/components/crx_file/testdata/sha1_files.gni
+++ /dev/null
@@ -1,21 +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.
-
-sha1_files = [
-  "unsigned.crx3.sha1",
-  "valid.crx2.sha1",
-  "valid_no_publisher.crx3.sha1",
-  "valid_publisher.crx3.sha1",
-  "valid_test_publisher.crx3.sha1",
-]
diff --git a/components/crx_file/testdata/unsigned.crx3.sha1 b/components/crx_file/testdata/unsigned.crx3.sha1
deleted file mode 100644
index 3913e3d..0000000
--- a/components/crx_file/testdata/unsigned.crx3.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ad85238e7e3afe325e6be902aeb018e0d14fe3de
diff --git a/components/crx_file/testdata/valid.crx2.sha1 b/components/crx_file/testdata/valid.crx2.sha1
deleted file mode 100644
index 3bde513..0000000
--- a/components/crx_file/testdata/valid.crx2.sha1
+++ /dev/null
@@ -1 +0,0 @@
-6bd2e7950cdb59ad8d2fc53bdc43be1c207e8173
diff --git a/components/crx_file/testdata/valid_no_publisher.crx3.sha1 b/components/crx_file/testdata/valid_no_publisher.crx3.sha1
deleted file mode 100644
index 9735180..0000000
--- a/components/crx_file/testdata/valid_no_publisher.crx3.sha1
+++ /dev/null
@@ -1 +0,0 @@
-baf9dd313fa5c4228fe05cce3d292bd6d7006342
diff --git a/components/crx_file/testdata/valid_publisher.crx3.sha1 b/components/crx_file/testdata/valid_publisher.crx3.sha1
deleted file mode 100644
index d4bbfc1..0000000
--- a/components/crx_file/testdata/valid_publisher.crx3.sha1
+++ /dev/null
@@ -1 +0,0 @@
-6fbbf33b4ad56ff25608f975fc78840eb41be1ba
diff --git a/components/crx_file/testdata/valid_test_publisher.crx3.sha1 b/components/crx_file/testdata/valid_test_publisher.crx3.sha1
deleted file mode 100644
index 419bb87..0000000
--- a/components/crx_file/testdata/valid_test_publisher.crx3.sha1
+++ /dev/null
@@ -1 +0,0 @@
-9059d7419401f8764d6788006747f3407fdba6f0
diff --git a/components/update_client/unzip/unzip_impl.cc b/components/update_client/unzip/unzip_impl.cc
index 3dcfab8..1fa9058 100644
--- a/components/update_client/unzip/unzip_impl.cc
+++ b/components/update_client/unzip/unzip_impl.cc
@@ -20,13 +20,6 @@
              UnzipCompleteCallback callback) override {
     unzip::Unzip(callback_.Run(), zip_file, destination, std::move(callback));
   }
-#if defined(IN_MEMORY_UPDATES)
-  void Unzip(const std::string& zip_str,
-             const base::FilePath& destination,
-             UnzipCompleteCallback callback) override {
-    unzip::Unzip(callback_.Run(), zip_str, destination, std::move(callback));
-  }
-#endif
 
  private:
   const UnzipChromiumFactory::Callback callback_;
diff --git a/components/update_client/unzip/unzip_impl_cobalt.cc b/components/update_client/unzip/unzip_impl_cobalt.cc
index 9b7b82b..40882b5 100644
--- a/components/update_client/unzip/unzip_impl_cobalt.cc
+++ b/components/update_client/unzip/unzip_impl_cobalt.cc
@@ -1,12 +1,10 @@
-// Copyright 2019 The Cobalt Authors. All rights reserved.
+// Copyright 2019 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "components/update_client/unzip/unzip_impl_cobalt.h"
 
-#include <string>
 #include <utility>
-
 #include "base/callback.h"
 #include "base/files/file_path.h"
 #include "starboard/time.h"
@@ -25,13 +23,6 @@
              UnzipCompleteCallback callback) override {
     std::move(callback).Run(zip::Unzip(zip_path, output_path));
   }
-#if defined(IN_MEMORY_UPDATES)
-  void Unzip(const std::string& zip_str,
-             const base::FilePath& output_path,
-             UnzipCompleteCallback callback) override {
-    std::move(callback).Run(zip::Unzip(zip_str, output_path));
-  }
-#endif
 };
 
 }  // namespace
diff --git a/components/update_client/unzipper.h b/components/update_client/unzipper.h
index 5d1e602..6aed58c 100644
--- a/components/update_client/unzipper.h
+++ b/components/update_client/unzipper.h
@@ -25,12 +25,6 @@
                      const base::FilePath& destination,
                      UnzipCompleteCallback callback) = 0;
 
-#if defined(IN_MEMORY_UPDATES)
-  virtual void Unzip(const std::string& zip_str,
-                     const base::FilePath& destination,
-                     UnzipCompleteCallback callback) = 0;
-#endif
-
  protected:
   Unzipper() = default;
 
diff --git a/docker-compose.yml b/docker-compose.yml
index c519990..245eff7 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -58,8 +58,6 @@
   build:
     context: ./docker/linux
     dockerfile: unittest/Dockerfile
-    args:
-      - CLANG_VER=${CLANG_VER:-365097-f7e52fbd-8}
   image: cobalt-linux-x64x11-unittest
   environment:
     - PLATFORM=${PLATFORM:-linux-x64x11}
@@ -437,13 +435,8 @@
 
   linux-x64x11-sbversion12-evergreen:
     <<: *build-common-definitions
-    build:
-      context: ./docker/linux
-      dockerfile: linux-x64x11/Dockerfile
-      args:
-        - FROM_IMAGE=cobalt-build-evergreen
-    image: cobalt-build-linux-x64x11-evergreen
-    depends_on: [ build-evergreen ]
+    image: cobalt-build-linux-evergreen
+    depends_on: [ build-linux-evergreen ]
     environment:
       <<: *shared-build-env
       PLATFORM: linux-x64x11-sbversion-12
@@ -452,11 +445,11 @@
 
   # Example usage of unittest:
   # 1. Build the containers for which you want to unittest
-  # docker-compose up --build --no-start linux-x64x11-unittest
+  # docker-compose up --build --no-start linux-x64x11 unittest
   # 2. Build the 'all' target for the platform you want to test
   # PLATFORM=linux-x64x11 CONFIG=devel TARGET=all docker-compose run linux-x64x11
   # 3. Run the unittests for that target.
-  # PLATFORM=linux-x64x11 CONFIG=devel docker-compose run linux-x64x11-unittest
+  # PLATFORM=linux-x64x11 CONFIG=devel TARGET=all docker-compose run unittest
   linux-x64x11-unittest:
     <<: *shared-unittest-definitions
 
diff --git a/docker/docsite/Dockerfile b/docker/docsite/Dockerfile
index f4e1592..0b2424e 100644
--- a/docker/docsite/Dockerfile
+++ b/docker/docsite/Dockerfile
@@ -33,6 +33,12 @@
     && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
     && rm -rf /var/lib/{apt,dpkg,cache,log}
 
+COPY Gemfile /app/Gemfile
+# Note: This file was generated by running a working version of this Docker
+# container. Then it was committed back to the codebase to be copied over as
+# part of the build steps.
+COPY Gemfile.lock /app/Gemfile.lock
+RUN bundle install --gemfile=/app/Gemfile
 
 # We create and use a non-root user explicitly so that the generated and
 # modified files maintain the same permissions as the user that launched the
@@ -47,14 +53,6 @@
 RUN mkdir /project_out_dir \
     && chown ${USER:-defaultuser}:defaultgroup /project_out_dir
 
-
-COPY Gemfile /app/Gemfile
-# Note: This file was generated by running a working version of this Docker
-# container. Then it was committed back to the codebase to be copied over as
-# part of the build steps.
-COPY Gemfile.lock /app/Gemfile.lock
-RUN bundle install --gemfile=/app/Gemfile
-
 USER ${USER:-defaultuser}
 
 CMD /code/third_party/internal/repo-publishing-toolkit-local/preview-site.sh run
diff --git a/docker/docsite/Gemfile.lock b/docker/docsite/Gemfile.lock
index 96ec167..074ae8c 100644
--- a/docker/docsite/Gemfile.lock
+++ b/docker/docsite/Gemfile.lock
@@ -2,13 +2,13 @@
   remote: https://rubygems.org/
   specs:
     RedCloth (4.2.9)
-    activesupport (6.1.7.3)
+    activesupport (6.1.7.2)
       concurrent-ruby (~> 1.0, >= 1.0.2)
       i18n (>= 1.6, < 2)
       minitest (>= 5.1)
       tzinfo (~> 2.0)
       zeitwerk (~> 2.3)
-    addressable (2.8.4)
+    addressable (2.8.1)
       public_suffix (>= 2.0.2, < 6.0)
     blankslate (2.1.2.4)
     bourbon (7.3.0)
@@ -52,7 +52,7 @@
     html-pipeline (1.9.0)
       activesupport (>= 2)
       nokogiri (~> 1.4)
-    i18n (1.14.1)
+    i18n (1.12.0)
       concurrent-ruby (~> 1.0)
     jekyll (2.4.0)
       classifier-reborn (~> 2.0)
@@ -116,7 +116,7 @@
     pygments.rb (0.6.3)
       posix-spawn (~> 0.3.6)
       yajl-ruby (~> 1.2.0)
-    racc (1.7.0)
+    racc (1.6.2)
     rb-fsevent (0.11.2)
     rb-inotify (0.10.1)
       ffi (~> 1.0)
@@ -132,13 +132,13 @@
     sawyer (0.9.2)
       addressable (>= 2.3.5)
       faraday (>= 0.17.3, < 3)
-    thor (1.2.2)
+    thor (1.2.1)
     toml (0.1.2)
       parslet (~> 1.5.0)
     tzinfo (2.0.6)
       concurrent-ruby (~> 1.0)
     yajl-ruby (1.2.3)
-    zeitwerk (2.6.8)
+    zeitwerk (2.6.7)
 
 PLATFORMS
   ruby
diff --git a/docker/linux/base/build/Dockerfile b/docker/linux/base/build/Dockerfile
index 4d0306d..38fa8ce 100644
--- a/docker/linux/base/build/Dockerfile
+++ b/docker/linux/base/build/Dockerfile
@@ -110,7 +110,5 @@
     && echo ${CLANG_16_VER} >> ${CLANG_16_TC_HOME}/cr_build_revision \
     && rm clang-llvmorg-${CLANG_16_VER}.tgz
 
-RUN git config --global --add safe.directory /code
-
 WORKDIR /code
 CMD ["/usr/bin/python","--version"]
diff --git a/docker/linux/unittest/Dockerfile b/docker/linux/unittest/Dockerfile
index 3b8c901..c9f7915 100644
--- a/docker/linux/unittest/Dockerfile
+++ b/docker/linux/unittest/Dockerfile
@@ -14,8 +14,6 @@
 
 FROM cobalt-base
 
-ARG HOME=/root
-
 RUN apt update -qqy \
     && apt install -qqy --no-install-recommends \
         libasound2 \
@@ -38,22 +36,6 @@
 COPY ./unittest/requirements.txt /opt/requirements.txt
 RUN python3 -m pip install --require-hashes --no-deps -r /opt/requirements.txt
 
-# === Install Clang 16 toolchain coverage tools.
-ARG TC_ROOT=${HOME}/starboard-toolchains/
-ARG CLANG_16_VER=16-init-17653-g39da55e8-2
-ARG CLANG_16_TC_HOME=${TC_ROOT}/x86_64-linux-gnu-clang-chromium-${CLANG_16_VER}
-ARG CLANG_16_BASE_URL=https://commondatastorage.googleapis.com/chromium-browser-clang
-
-RUN echo ${CLANG_16_BASE_URL}/Linux_x64/llvm-code-coverage-llvmorg-${CLANG_VERSION}.tgz
-
-RUN cd /tmp \
-    && mkdir -p ${CLANG_16_TC_HOME} \
-    && curl --silent -O -J \
-        ${CLANG_16_BASE_URL}/Linux_x64/llvm-code-coverage-llvmorg-${CLANG_16_VER}.tgz \
-    && tar xf llvm-code-coverage-llvmorg-${CLANG_16_VER}.tgz -C ${CLANG_16_TC_HOME} \
-    && echo ${CLANG_16_VER} >> ${CLANG_16_TC_HOME}/cr_build_revision \
-    && rm llvm-code-coverage-llvmorg-${CLANG_16_VER}.tgz
-
 WORKDIR /out
 # Sets the locale in the environment. This is needed for NPLB unit tests.
 ENV LANG en_US.UTF-8
diff --git a/starboard/android/shared/test_filters.py b/starboard/android/shared/test_filters.py
index efade80..b2a269b 100644
--- a/starboard/android/shared/test_filters.py
+++ b/starboard/android/shared/test_filters.py
@@ -47,9 +47,9 @@
         'PlayerComponentsTests/PlayerComponentsTest.Pause/*ec3*',
     ],
     'nplb': [
+        'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.SecondaryPlayerTest/*',
         # Enable multiplayer tests once it's supported.
         'MultiplePlayerTests/*',
-        'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.SecondaryPlayerTest/*',
 
         # This test is failing because localhost is not defined for IPv6 in
         # /etc/hosts.
diff --git a/starboard/doc/evergreen/cobalt_evergreen_overview.md b/starboard/doc/evergreen/cobalt_evergreen_overview.md
index 8e2591c..e614373 100644
--- a/starboard/doc/evergreen/cobalt_evergreen_overview.md
+++ b/starboard/doc/evergreen/cobalt_evergreen_overview.md
@@ -131,7 +131,7 @@
 
 *   `kSbSystemPathStorageDirectory`
     *   Dedicated location for storing Cobalt Evergreen-related binaries
-    *   This path must be writable and have at least 64MB of reserved space for
+    *   This path must be writable and have at least 96MB of reserved space for
         Evergreen updates. Please see the “Platforms Requirements” section below
         for more details.
 *   `kSbMemoryMapProtectExec`
@@ -195,12 +195,12 @@
 
 *   V8
 
-Additional reserved storage (64MB) is required for Evergreen binaries. We expect
+Additional reserved storage (96MB) is required for Evergreen binaries. We expect
 Evergreen implementations to have an initial Cobalt preloaded on the device and
 an additional reserved space for additional Cobalt update storage.
 
 *   Initial Cobalt binary deployment - 64MB
-*   Additional Cobalt update storage - 64MB
+*   Additional Cobalt update storage - 96MB
     *   Required for 2 update slots under `kSbSystemPathStorageDirectory`
 
 As Cobalt Evergreen is intended to be updated from Google Cloud architecture
diff --git a/starboard/evergreen/shared/gyp_configuration.py b/starboard/evergreen/shared/gyp_configuration.py
index c5f355c..ca85820 100644
--- a/starboard/evergreen/shared/gyp_configuration.py
+++ b/starboard/evergreen/shared/gyp_configuration.py
@@ -27,11 +27,7 @@
 
   def GetTestTargets(self):
     tests = super().GetTestTargets()
-    tests.extend({
-        'cobalt_slot_management_test',
-        'crx_file_test',
-        'updater_test',
-    })
+    tests.extend({'cobalt_slot_management_test', 'updater_test'})
     return [test for test in tests if test not in self.__FORBIDDEN_TESTS]
 
   __FORBIDDEN_TESTS = [  # pylint: disable=invalid-name
diff --git a/starboard/evergreen/shared/launcher.py b/starboard/evergreen/shared/launcher.py
index 41143bd..cad9fbe 100644
--- a/starboard/evergreen/shared/launcher.py
+++ b/starboard/evergreen/shared/launcher.py
@@ -119,7 +119,7 @@
         target_params=target_command_line_params,
         output_file=self.output_file,
         out_directory=self.staging_directory,
-        coverage_file_path=self.coverage_file_path,
+        coverage_directory=self.coverage_directory,
         env_variables=self.env_variables,
         log_targets=False)
 
diff --git a/starboard/evergreen/shared/platform_configuration/BUILD.gn b/starboard/evergreen/shared/platform_configuration/BUILD.gn
index 6b154a0..ddaf279 100644
--- a/starboard/evergreen/shared/platform_configuration/BUILD.gn
+++ b/starboard/evergreen/shared/platform_configuration/BUILD.gn
@@ -74,17 +74,6 @@
     # By default, <EGL/eglplatform.h> pulls in some X11 headers that have some
     # nasty macros (|Status|, for example) that conflict with Chromium base.
     "MESA_EGL_NO_X11_HEADERS",
-
-    # During Evergreen updates the CRX package is kept in-memory, instead of
-    # on the file system, before getting unpacked.
-    # TODO(b/158043520): we need to make significant customizations to Chromium
-    # code to implement this feature and this macro allows us to switch back
-    # to the legacy behavior during development to verify the customizations
-    # are made cleanly. Once the feature is complete we may want to remove
-    # existing customizations that enable the legacy behavior for Cobalt builds,
-    # change IN_MEMORY_UPDATES references to USE_COBALT_CUSTOMIZATIONS
-    # references, and remove this macro.
-    "IN_MEMORY_UPDATES",
   ]
 
   if (is_debug) {
diff --git a/starboard/evergreen/testing/run_all_tests.sh b/starboard/evergreen/testing/run_all_tests.sh
index 0894d29..90a171b 100755
--- a/starboard/evergreen/testing/run_all_tests.sh
+++ b/starboard/evergreen/testing/run_all_tests.sh
@@ -24,7 +24,8 @@
 USE_COMPRESSED_SYSTEM_IMAGE="false"
 SYSTEM_IMAGE_EXTENSION=".so"
 
-DISABLE_TESTS="false"
+# TODO: Enable once the Starboard 16 binaries are published for Evergreen.
+DISABLE_TESTS="true"
 
 while getopts "d:a:c" o; do
     case "${o}" in
diff --git a/starboard/linux/shared/launcher.py b/starboard/linux/shared/launcher.py
index 812b87c..c433ffb 100644
--- a/starboard/linux/shared/launcher.py
+++ b/starboard/linux/shared/launcher.py
@@ -79,12 +79,14 @@
     # Ensure that if the binary has code coverage or profiling instrumentation,
     # the output will be written to a file in the coverage_directory named as
     # the target_name with '.profraw' postfixed.
-    if self.coverage_file_path:
-      env.update({"LLVM_PROFILE_FILE": self.coverage_file_path})
+    if self.coverage_directory:
+      target_profraw = os.path.join(self.coverage_directory,
+                                    target_name + ".profraw")
+      env.update({"LLVM_PROFILE_FILE": target_profraw})
 
       # Remove any stale profraw file that may already exist.
       try:
-        os.remove(self.coverage_file_path)
+        os.remove(target_profraw)
       except OSError as e:
         if e.errno != errno.ENOENT:
           raise
diff --git a/starboard/linux/shared/test_filters.py b/starboard/linux/shared/test_filters.py
index 43a454d..42e89ea 100644
--- a/starboard/linux/shared/test_filters.py
+++ b/starboard/linux/shared/test_filters.py
@@ -36,10 +36,7 @@
 }
 
 _FILTERED_TESTS = {
-    'nplb': [
-        # TODO(b/286249595): This test crashes when coverage is enabled.
-        'SbMemoryMapTest.CanChangeMemoryProtection'
-    ],
+    'nplb': [],
 }
 if os.getenv('MODULAR_BUILD', '0') == '1':
   _FILTERED_TESTS = _MODULAR_BUILD_FILTERED_TESTS
diff --git a/starboard/shared/starboard/player/filter/testing/test_util.cc b/starboard/shared/starboard/player/filter/testing/test_util.cc
index 39ae17d..25ad281 100644
--- a/starboard/shared/starboard/player/filter/testing/test_util.cc
+++ b/starboard/shared/starboard/player/filter/testing/test_util.cc
@@ -181,32 +181,14 @@
       const auto& video_stream_info = dmp_reader.video_stream_info();
       const std::string video_mime = dmp_reader.video_mime_type();
       const MimeType video_mime_type(video_mime.c_str());
-      // SbMediaIsVideoSupported may return false for gpu based decoder that in
-      // fact supports av1 or/and vp9 because the system can make async
-      // initialization at startup.
-      // To minimize probability of false negative we check result few times
-      static bool decoder_has_been_checked_once = false;
-      int counter = 5;
-      const SbMediaVideoCodec video_codec = dmp_reader.video_codec();
-      bool need_to_check_with_wait = video_codec == kSbMediaVideoCodecAv1 ||
-                                     video_codec == kSbMediaVideoCodecVp9;
-      do {
-        if (SbMediaIsVideoSupported(
-                video_codec, video_mime.size() > 0 ? &video_mime_type : nullptr,
-                -1, -1, 8, kSbMediaPrimaryIdUnspecified,
-                kSbMediaTransferIdUnspecified, kSbMediaMatrixIdUnspecified,
-                video_stream_info.frame_width, video_stream_info.frame_height,
-                dmp_reader.video_bitrate(), dmp_reader.video_fps(), false)) {
-          test_params.push_back(std::make_tuple(filename, output_mode));
-          break;
-        } else if (need_to_check_with_wait && !decoder_has_been_checked_once) {
-          SbThreadSleep(kSbTimeSecond);
-        } else {
-          break;
-        }
-      } while (--counter);
-      if (need_to_check_with_wait) {
-        decoder_has_been_checked_once = true;
+      if (SbMediaIsVideoSupported(
+              dmp_reader.video_codec(),
+              video_mime.size() > 0 ? &video_mime_type : nullptr, -1, -1, 8,
+              kSbMediaPrimaryIdUnspecified, kSbMediaTransferIdUnspecified,
+              kSbMediaMatrixIdUnspecified, video_stream_info.frame_width,
+              video_stream_info.frame_height, dmp_reader.video_bitrate(),
+              dmp_reader.video_fps(), false)) {
+        test_params.push_back(std::make_tuple(filename, output_mode));
       }
     }
   }
diff --git a/starboard/tools/abstract_launcher.py b/starboard/tools/abstract_launcher.py
index ff361cf..ff0ad61 100644
--- a/starboard/tools/abstract_launcher.py
+++ b/starboard/tools/abstract_launcher.py
@@ -113,7 +113,7 @@
     if not out_directory:
       out_directory = paths.BuildOutputDirectory(platform_name, config)
     self.out_directory = out_directory
-    self.coverage_file_path = kwargs.get("coverage_file_path", None)
+    self.coverage_directory = kwargs.get("coverage_directory", out_directory)
 
     output_file = kwargs.get("output_file", None)
     if not output_file:
diff --git a/starboard/tools/testing/test_coverage.py b/starboard/tools/testing/test_coverage.py
deleted file mode 100644
index fd05bf6..0000000
--- a/starboard/tools/testing/test_coverage.py
+++ /dev/null
@@ -1,186 +0,0 @@
-#!/usr/bin/python
-#
-# 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.
-#
-"""Generate coverage reports from llvm coverage data."""
-
-import argparse
-import glob
-import logging
-import os
-import pathlib
-import subprocess
-
-from starboard.build import clang
-from starboard.tools import build
-from starboard.tools.util import SetupDefaultLoggingConfig
-
-SRC_DIR = os.path.join(
-    os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)
-
-
-def _get_raw_reports(test_binaries, coverage_dir):
-  """Get a list of raw coverage reports from the coverage directory."""
-  raw_reports = []
-  missing_reports = []
-  for target in test_binaries:
-    # Find coverage reports from the supplied test binaries.
-    profraw_file = os.path.join(coverage_dir, target + '.profraw')
-    if not os.path.isfile(profraw_file):
-      missing_reports.append(profraw_file)
-    else:
-      raw_reports.append(profraw_file)
-  else:
-    # If no test binaries were supplied we use all coverage data we find.
-    raw_reports = glob.glob(os.path.join(coverage_dir, '*.profraw'))
-
-  if missing_reports:
-    logging.error('Unable to find the following coverage files: %s.',
-                  ', '.join(missing_reports))
-  return raw_reports
-
-
-def _get_test_binary_paths(binaries_dir, test_binaries, coverage_dir):
-  if not test_binaries:
-    # Use the coverage files to deduce the paths if no binaries were specified.
-    raw_reports = _get_raw_reports(test_binaries, coverage_dir)
-    test_binaries = [pathlib.Path(report).stem for report in raw_reports]
-  return [os.path.join(binaries_dir, binary) for binary in test_binaries]
-
-
-def _get_exclude_opts():
-  """Get list of exclude options."""
-  # Get a list of all directories in the src root and exclude all but our code.
-  all_dirs = next(os.walk(SRC_DIR))[1]
-  include_dirs = ['cobalt', 'starboard']
-  exclude_dirs = [d for d in all_dirs if d not in include_dirs]
-  return [
-      r'--ignore-filename-regex=.*_test\.h$',
-      r'--ignore-filename-regex=.*_test\.cc$',
-      r'--ignore-filename-regex=.*_tests\.h$',
-      r'--ignore-filename-regex=.*_tests\.cc$',
-      r'--ignore-filename-regex=.*_test_fixture\.h$',
-      r'--ignore-filename-regex=.*_test_fixture\.cc$',
-      r'--ignore-filename-regex=.*_test_internal\.h$',
-      r'--ignore-filename-regex=.*_test_internal\.cc$',
-      r'--ignore-filename-regex=.*_test_util\.h$',
-      r'--ignore-filename-regex=.*_test_util\.cc$',
-      r'--ignore-filename-regex=.*_unittest\.h$',
-      r'--ignore-filename-regex=.*_unittest\.cc$',
-  ] + [f'--ignore-filename-regex={d}/.*' for d in exclude_dirs]
-
-
-def _generate_reports(target_paths, merged_data_path, report_dir, report_type):
-  """Generate reports based on the combined coverage data."""
-  toolchain_dir = build.GetClangBinPath(clang.GetClangSpecification())
-  llvm_cov = os.path.join(toolchain_dir, 'llvm-cov')
-
-  exclude_opts = _get_exclude_opts()
-  test_binary_opts = [target_paths[0]] + \
-    ['-object=' + target for target in target_paths[1:]]
-
-  if report_type in ['html', 'both']:
-    logging.info('Creating html coverage report')
-    show_cmd_list = [
-        llvm_cov, 'show', *exclude_opts, '-instr-profile=' + merged_data_path,
-        '-format=html', '-output-dir=' + os.path.join(report_dir, 'html'),
-        *test_binary_opts
-    ]
-    subprocess.check_call(show_cmd_list, text=True)
-
-  if report_type in ['gcov', 'both']:
-    logging.info('Creating gcov coverage report')
-    report_cmd_list = [
-        llvm_cov, 'show', *exclude_opts, '-instr-profile=' + merged_data_path,
-        '-format=text', *test_binary_opts
-    ]
-    with open(os.path.join(report_dir, 'report.txt'), 'wb') as out:
-      subprocess.check_call(report_cmd_list, stdout=out, text=True)
-
-
-def _merge_coverage_data(targets, coverage_dir):
-  """Create a combined coverage file from the raw reports."""
-  toolchain_dir = build.GetClangBinPath(clang.GetClangSpecification())
-  llvm_profdata = os.path.join(toolchain_dir, 'llvm-profdata')
-
-  raw_reports = _get_raw_reports(targets, coverage_dir)
-  if not raw_reports:
-    raise RuntimeError('No coverage data found in \'coverage_dir\'')
-
-  logging.info('Merging coverage files')
-  merged_data_path = os.path.join(coverage_dir, 'report.profdata')
-  merge_cmd_list = [
-      llvm_profdata, 'merge', '-sparse=true', '-o', merged_data_path
-  ] + raw_reports
-  subprocess.check_call(merge_cmd_list, text=True)
-
-  return merged_data_path
-
-
-def create_report(binary_dir, test_binaries, coverage_dir, report_type='both'):
-  """Generate source code coverage reports."""
-  merged_data_path = _merge_coverage_data(test_binaries, coverage_dir)
-
-  report_dir = coverage_dir
-  target_paths = _get_test_binary_paths(binary_dir, test_binaries, coverage_dir)
-  _generate_reports(target_paths, merged_data_path, report_dir, report_type)
-
-
-def main():
-  SetupDefaultLoggingConfig()
-
-  arg_parser = argparse.ArgumentParser()
-  arg_parser.add_argument(
-      '--binaries_dir',
-      required=True,
-      help='Directory where the test binaries are located.')
-  arg_parser.add_argument(
-      '--coverage_dir',
-      required=True,
-      help='Directory where coverage files are located and where the reports '
-      'will be generated.')
-  arg_parser.add_argument(
-      '--test_binaries',
-      nargs='+',
-      default=[],
-      help='List of test binaries that should be used for the coverage report. '
-      'If not passed all coverage files in --coverage_dir will be used.')
-
-  report_type_group = arg_parser.add_mutually_exclusive_group()
-  report_type_group.add_argument(
-      '--html_only',
-      action='store_true',
-      help='If passed only the html report will be created. Can not be used '
-      'with --gcov_only.')
-  report_type_group.add_argument(
-      '--gcov_only',
-      action='store_true',
-      help='If passed only the gcov report will be created. Can not be used '
-      'with --html_only.')
-
-  args = arg_parser.parse_args()
-
-  report_type = 'both'
-  if args.html_only:
-    report_type = 'html'
-  elif args.gcov_only:
-    report_type = 'gcov'
-
-  create_report(args.binaries_dir, args.test_binaries, args.coverage_dir,
-                report_type)
-
-
-if __name__ == '__main__':
-  main()
diff --git a/starboard/tools/testing/test_runner.py b/starboard/tools/testing/test_runner.py
index 97addf1..923fe87 100755
--- a/starboard/tools/testing/test_runner.py
+++ b/starboard/tools/testing/test_runner.py
@@ -27,6 +27,7 @@
 import traceback
 
 from six.moves import cStringIO as StringIO
+from starboard.build import clang
 from starboard.tools import abstract_launcher
 from starboard.tools import build
 from starboard.tools import command_line
@@ -34,7 +35,6 @@
 from starboard.tools.testing import build_tests
 from starboard.tools.testing import test_filter
 from starboard.tools.testing.test_sharding import ShardingTestConfig
-from starboard.tools.testing.test_coverage import create_report
 from starboard.tools.util import SetupDefaultLoggingConfig
 
 # pylint: disable=consider-using-f-string
@@ -223,8 +223,7 @@
                xml_output_dir=None,
                log_xml_results=False,
                shard_index=None,
-               launcher_args=None,
-               coverage_directory=None):
+               launcher_args=None):
     self.platform = platform
     self.config = config
     self.loader_platform = loader_platform
@@ -238,7 +237,7 @@
     if not self.out_directory:
       self.out_directory = paths.BuildOutputDirectory(self.platform,
                                                       self.config)
-    self.coverage_directory = coverage_directory
+    self.coverage_directory = os.path.join(self.out_directory, "coverage")
     if (not self.loader_out_directory and self.loader_platform and
         self.loader_config):
       self.loader_out_directory = paths.BuildOutputDirectory(
@@ -440,9 +439,6 @@
     # For on-device testing, this is w.r.t on device storage.
     test_result_xml_path = None
 
-    # Path to coverage file to generate.
-    coverage_file_path = None
-
     def MakeLauncher():
       logging.info("MakeLauncher(): %s", test_result_xml_path)
       return abstract_launcher.LauncherFactory(
@@ -453,7 +449,7 @@
           target_params=test_params,
           output_file=write_pipe,
           out_directory=self.out_directory,
-          coverage_file_path=coverage_file_path,
+          coverage_directory=self.coverage_directory,
           env_variables=env,
           test_result_xml_path=test_result_xml_path,
           loader_platform=self.loader_platform,
@@ -490,12 +486,6 @@
       test_params.append(f"--gtest_output=xml:{test_result_xml_path}")
     logging.info("XML test result path: %s", test_result_xml_path)
 
-    if self.coverage_directory:
-      coverage_file_path = os.path.join(self.coverage_directory,
-                                        target_name + ".profraw")
-      logging.info("Coverage data will be saved to %s",
-                   os.path.relpath(coverage_file_path))
-
     # Turn off color codes from output to make it easy to parse
     test_params.append("--gtest_color=no")
 
@@ -840,6 +830,51 @@
         results.append(self._RunTest(test_target))
     return self._ProcessAllTestResults(results)
 
+  def GenerateCoverageReport(self):
+    """Generate the source code coverage report."""
+    available_profraw_files = []
+    available_targets = []
+    for target in sorted(self.test_targets.keys()):
+      profraw_file = os.path.join(self.coverage_directory, target + ".profraw")
+      if os.path.isfile(profraw_file):
+        available_profraw_files.append(profraw_file)
+        available_targets.append(target)
+
+    # If there are no profraw files, then there is no work to do.
+    if not available_profraw_files:
+      return
+
+    toolchain_dir = build.GetClangBinPath(clang.GetClangSpecification())
+
+    report_name = "report"
+    profdata_name = os.path.join(self.coverage_directory,
+                                 report_name + ".profdata")
+    merge_cmd_list = [
+        os.path.join(toolchain_dir, "llvm-profdata"), "merge", "-sparse=true",
+        "-o", profdata_name
+    ]
+    merge_cmd_list += available_profraw_files
+
+    self._Exec(merge_cmd_list)
+    show_cmd_list = [
+        os.path.join(toolchain_dir, "llvm-cov"), "show",
+        "-instr-profile=" + profdata_name, "-format=html",
+        "-output-dir=" + os.path.join(self.coverage_directory, "html"),
+        available_targets[0]
+    ]
+    show_cmd_list += ["-object=" + target for target in available_targets[1:]]
+    self._Exec(show_cmd_list)
+
+    report_cmd_list = [
+        os.path.join(toolchain_dir, "llvm-cov"), "report",
+        "-instr-profile=" + profdata_name, available_targets[0]
+    ]
+    report_cmd_list += ["-object=" + target for target in available_targets[1:]]
+    self._Exec(
+        report_cmd_list,
+        output_file=os.path.join(self.coverage_directory, report_name + ".txt"))
+    return
+
 
 def main():
   SetupDefaultLoggingConfig()
@@ -906,17 +941,6 @@
       type=int,
       help="The index of the test shard to run. This selects a subset of tests "
       "to run based on the configuration per platform, if it exists.")
-  arg_parser.add_argument(
-      "--coverage_dir",
-      help="If defined, coverage data will be collected in the directory "
-      "specified. This requires the files to have been compiled with clang "
-      "coverage.")
-  arg_parser.add_argument(
-      "--coverage_report",
-      action="store_true",
-      help="If set, a coverage report will be generated if there is available "
-      "coverage information in the directory specified by --coverage_dir."
-      "If --coverage_dir is not passed this parameter is ignored.")
   args = arg_parser.parse_args()
 
   if (args.loader_platform and not args.loader_config or
@@ -937,19 +961,13 @@
   if args.dry_run:
     launcher_args.append(abstract_launcher.ARG_DRYRUN)
 
-  coverage_directory = None
-  if args.coverage_dir:
-    # Clang needs an absolute path to generate coverage reports.
-    coverage_directory = os.path.abspath(args.coverage_dir)
-
   logging.info("Initializing test runner")
   runner = TestRunner(args.platform, args.config, args.loader_platform,
                       args.loader_config, args.device_id, args.target_name,
                       target_params, args.out_directory,
                       args.loader_out_directory, args.platform_tests_only,
                       args.application_name, args.dry_run, args.xml_output_dir,
-                      args.log_xml_results, args.shard_index, launcher_args,
-                      coverage_directory)
+                      args.log_xml_results, args.shard_index, launcher_args)
   logging.info("Test runner initialized")
 
   def Abort(signum, frame):
@@ -980,15 +998,7 @@
   if args.run:
     run_success = runner.RunAllTests()
 
-  if args.coverage_report and coverage_directory:
-    if run_success:
-      try:
-        create_report(runner.out_directory, runner.test_targets.keys(),
-                      coverage_directory)
-      except Exception as e:  # pylint: disable=broad-except
-        logging.error("Failed to generate coverage report: %s", e)
-    else:
-      logging.warning("Test run failed, skipping code coverage report.")
+  runner.GenerateCoverageReport()
 
   # If either step has failed, count the whole test run as failed.
   if not build_success or not run_success:
diff --git a/third_party/v8/METADATA b/third_party/v8/METADATA
index 4347fff..a886829 100644
--- a/third_party/v8/METADATA
+++ b/third_party/v8/METADATA
@@ -3,18 +3,18 @@
   "v8 is Google's JavaScript engine developed for The Chromium Project. Cobalt "
   "uses v8 as its primary JavaScript engine. v8 works closely with Cobalt's "
   "script interface and templated bindings code."
-  "Current V8 version: 8.8.278.17"
+  "Current V8 version: 8.8.278.8"
 
 third_party {
   url {
     type: GIT
     value: "https://chromium.googlesource.com/v8/v8"
   }
-  version: "f7f4327516912dfca0d7401044e890ca0ce885ad"
+  version: "2dbcdc105b963ee2501c82139eef7e0603977ff0"
   last_upgrade_date {
-    year: 2023
-    month: 6
-    day: 16
+    year: 2019
+    month: 8
+    day: 26
   }
   license_type: NOTICE
 }
diff --git a/third_party/v8/include/v8-version.h b/third_party/v8/include/v8-version.h
index 8ecd81c..0be46fc 100644
--- a/third_party/v8/include/v8-version.h
+++ b/third_party/v8/include/v8-version.h
@@ -11,7 +11,7 @@
 #define V8_MAJOR_VERSION 8
 #define V8_MINOR_VERSION 8
 #define V8_BUILD_NUMBER 278
-#define V8_PATCH_LEVEL 17
+#define V8_PATCH_LEVEL 8
 
 // Use 1 for candidates and 0 otherwise.
 // (Boolean macro values are not supported by all preprocessors.)
diff --git a/third_party/v8/infra/testing/PRESUBMIT.py b/third_party/v8/infra/testing/PRESUBMIT.py
index 46ae051..178ba9f 100644
--- a/third_party/v8/infra/testing/PRESUBMIT.py
+++ b/third_party/v8/infra/testing/PRESUBMIT.py
@@ -29,7 +29,6 @@
   'cpu',
   'device_os',
   'device_type',
-  'gpu',
   'os',
   'pool',
 ]
diff --git a/third_party/v8/infra/testing/builders.pyl b/third_party/v8/infra/testing/builders.pyl
index 9414e17..bff4284 100644
--- a/third_party/v8/infra/testing/builders.pyl
+++ b/third_party/v8/infra/testing/builders.pyl
@@ -639,8 +639,7 @@
   'v8_mac64_asan_rel_ng_triggered': {
     'swarming_dimensions' : {
       'cpu': 'x86-64',
-      'os': 'Mac-10.15',
-      'gpu': 'none',
+      'os': 'Mac-10.13',
     },
     'tests': [
       {'name': 'v8testing', 'shards': 4},
@@ -649,8 +648,7 @@
   'v8_mac64_dbg_ng_triggered': {
     'swarming_dimensions' : {
       'cpu': 'x86-64',
-      'os': 'Mac-10.15',
-      'gpu': 'none',
+      'os': 'Mac-10.13',
     },
     'tests': [
       {'name': 'mozilla'},
@@ -662,8 +660,7 @@
   'v8_mac64_gc_stress_dbg_ng_triggered': {
     'swarming_dimensions' : {
       'cpu': 'x86-64',
-      'os': 'Mac-10.15',
-      'gpu': 'none',
+      'os': 'Mac-10.13',
     },
     'tests': [
       {'name': 'd8testing', 'test_args': ['--gc-stress'], 'shards': 4},
@@ -672,8 +669,7 @@
   'v8_mac64_rel_ng_triggered': {
     'swarming_dimensions' : {
       'cpu': 'x86-64',
-      'os': 'Mac-10.15',
-      'gpu': 'none',
+      'os': 'Mac-10.13',
     },
     'tests': [
       {'name': 'mozilla'},
@@ -715,8 +711,7 @@
   'v8_mac_arm64_sim_rel_ng_triggered': {
     'swarming_dimensions' : {
       'cpu': 'x86-64',
-      'os': 'Mac-10.15',
-      'gpu': 'none',
+      'os': 'Mac-10.13',
     },
     'tests': [
       {'name': 'v8testing', 'shards': 8},
@@ -725,8 +720,7 @@
   'v8_mac_arm64_sim_dbg_ng_triggered': {
     'swarming_dimensions' : {
       'cpu': 'x86-64',
-      'os': 'Mac-10.15',
-      'gpu': 'none',
+      'os': 'Mac-10.13',
     },
     'tests': [
       {'name': 'v8testing', 'shards': 8},
@@ -735,8 +729,7 @@
   'v8_mac_arm64_sim_nodcheck_rel_ng_triggered': {
     'swarming_dimensions' : {
       'cpu': 'x86-64',
-      'os': 'Mac-10.15',
-      'gpu': 'none',
+      'os': 'Mac-10.13',
     },
     'tests': [
       {'name': 'v8testing', 'shards': 8},
@@ -1305,8 +1298,7 @@
   'V8 Mac64': {
     'swarming_dimensions': {
       'cpu': 'x86-64',
-      'os': 'Mac-10.15',
-      'gpu': 'none',
+      'os': 'Mac-10.13',
     },
     'tests': [
       {'name': 'mozilla'},
@@ -1318,8 +1310,7 @@
   'V8 Mac64 - debug': {
     'swarming_dimensions': {
       'cpu': 'x86-64',
-      'os': 'Mac-10.15',
-      'gpu': 'none',
+      'os': 'Mac-10.13',
     },
     'tests': [
       {'name': 'mozilla'},
@@ -1331,8 +1322,7 @@
   'V8 Mac64 ASAN': {
     'swarming_dimensions': {
       'cpu': 'x86-64',
-      'os': 'Mac-10.15',
-      'gpu': 'none',
+      'os': 'Mac-10.13',
     },
     'tests': [
       {'name': 'v8testing', 'shards': 5},
@@ -1341,8 +1331,7 @@
   'V8 Mac64 GC Stress': {
     'swarming_dimensions': {
       'cpu': 'x86-64',
-      'os': 'Mac-10.15',
-      'gpu': 'none',
+      'os': 'Mac-10.13',
     },
     'tests': [
       {'name': 'd8testing', 'test_args': ['--gc-stress'], 'shards': 4},
@@ -1367,8 +1356,7 @@
   'V8 Mac - arm64 - sim - debug': {
     'swarming_dimensions' : {
       'cpu': 'x86-64',
-      'os': 'Mac-10.15',
-      'gpu': 'none',
+      'os': 'Mac-10.13',
     },
     'swarming_task_attrs': {
       'expiration': 14400,
@@ -1382,8 +1370,7 @@
   'V8 Mac - arm64 - sim - release': {
     'swarming_dimensions' : {
       'cpu': 'x86-64',
-      'os': 'Mac-10.15',
-      'gpu': 'none',
+      'os': 'Mac-10.13',
     },
     'swarming_task_attrs': {
       'expiration': 14400,
diff --git a/third_party/v8/src/common/message-template.h b/third_party/v8/src/common/message-template.h
index c8ff902..f0f4b61 100644
--- a/third_party/v8/src/common/message-template.h
+++ b/third_party/v8/src/common/message-template.h
@@ -580,8 +580,6 @@
   T(DataCloneErrorOutOfMemory, "Data cannot be cloned, out of memory.")        \
   T(DataCloneErrorDetachedArrayBuffer,                                         \
     "An ArrayBuffer is detached and could not be cloned.")                     \
-  T(DataCloneErrorNonDetachableArrayBuffer,                                    \
-    "ArrayBuffer is not detachable and could not be cloned.")                  \
   T(DataCloneErrorSharedArrayBufferTransferred,                                \
     "A SharedArrayBuffer could not be cloned. SharedArrayBuffer must not be "  \
     "transferred.")                                                            \
diff --git a/third_party/v8/src/compiler/backend/ia32/code-generator-ia32.cc b/third_party/v8/src/compiler/backend/ia32/code-generator-ia32.cc
index 1820e39..18c0ed4 100644
--- a/third_party/v8/src/compiler/backend/ia32/code-generator-ia32.cc
+++ b/third_party/v8/src/compiler/backend/ia32/code-generator-ia32.cc
@@ -2869,7 +2869,7 @@
     case kAVXI16x8SConvertI32x4: {
       CpuFeatureScope avx_scope(tasm(), AVX);
       __ vpackssdw(i.OutputSimd128Register(), i.InputSimd128Register(0),
-                   i.InputOperand(1));
+                   i.InputSimd128Register(1));
       break;
     }
     case kSSEI16x8Add: {
diff --git a/third_party/v8/src/compiler/operator-properties.cc b/third_party/v8/src/compiler/operator-properties.cc
index a8e2941..c77249f 100644
--- a/third_party/v8/src/compiler/operator-properties.cc
+++ b/third_party/v8/src/compiler/operator-properties.cc
@@ -193,17 +193,16 @@
     case IrOpcode::kJSCloneObject:
 
     // Property access operations
-    case IrOpcode::kJSDeleteProperty:
-    case IrOpcode::kJSLoadGlobal:
     case IrOpcode::kJSLoadNamed:
     case IrOpcode::kJSLoadNamedFromSuper:
-    case IrOpcode::kJSLoadProperty:
-    case IrOpcode::kJSStoreDataPropertyInLiteral:
-    case IrOpcode::kJSStoreInArrayLiteral:
-    case IrOpcode::kJSStoreGlobal:
     case IrOpcode::kJSStoreNamed:
-    case IrOpcode::kJSStoreNamedOwn:
+    case IrOpcode::kJSLoadProperty:
     case IrOpcode::kJSStoreProperty:
+    case IrOpcode::kJSLoadGlobal:
+    case IrOpcode::kJSStoreGlobal:
+    case IrOpcode::kJSStoreNamedOwn:
+    case IrOpcode::kJSStoreDataPropertyInLiteral:
+    case IrOpcode::kJSDeleteProperty:
 
     // Conversions
     case IrOpcode::kJSToLength:
diff --git a/third_party/v8/src/compiler/simd-scalar-lowering.cc b/third_party/v8/src/compiler/simd-scalar-lowering.cc
index 36d590b..06673c3 100644
--- a/third_party/v8/src/compiler/simd-scalar-lowering.cc
+++ b/third_party/v8/src/compiler/simd-scalar-lowering.cc
@@ -511,20 +511,11 @@
   int num_lanes = NumLanes(type);
   int lane_width = kSimd128Size / num_lanes;
   int laneIndex = kLaneOffsets[0] / lane_width;
-
-  Node* rep = index;
-
-  if (HasReplacement(0, index)) {
-    // Index nodes are lowered to scalar nodes.
-    DCHECK_EQ(1, ReplacementCount(index));
-    rep = GetReplacements(index)[0];
-  }
-
-  new_indices[laneIndex] = rep;
+  new_indices[laneIndex] = index;
   for (int i = 1; i < num_lanes; ++i) {
     laneIndex = kLaneOffsets[i * lane_width] / lane_width;
     new_indices[laneIndex] = graph()->NewNode(
-        machine()->Int32Add(), rep,
+        machine()->Int32Add(), index,
         graph()->NewNode(
             common()->Int32Constant(static_cast<int>(i) * lane_width)));
   }
@@ -672,13 +663,9 @@
       }
     } else {
       // Load splat, load from the same index for every lane.
-      Node* rep = HasReplacement(0, index) ? GetReplacements(index)[0] : index;
-
-      // Replace first node, we only called ChangeOp above.
-      reps[0]->ReplaceInput(1, rep);
       for (int i = num_lanes - 1; i > 0; --i) {
         reps[i] =
-            graph()->NewNode(load_op, base, rep, effect_input, control_input);
+            graph()->NewNode(load_op, base, index, effect_input, control_input);
         effect_input = reps[i];
       }
     }
diff --git a/third_party/v8/src/deoptimizer/deoptimizer.cc b/third_party/v8/src/deoptimizer/deoptimizer.cc
index 63b4431..4ae51bf 100644
--- a/third_party/v8/src/deoptimizer/deoptimizer.cc
+++ b/third_party/v8/src/deoptimizer/deoptimizer.cc
@@ -267,7 +267,6 @@
           SafepointEntry safepoint = code.GetSafepointEntry(it.frame()->pc());
           int trampoline_pc = safepoint.trampoline_pc();
           DCHECK_IMPLIES(code == topmost_, safe_to_deopt_);
-          CHECK_GE(trampoline_pc, 0);
           // Replace the current pc on the stack with the trampoline.
           // TODO(v8:10026): avoid replacing a signed pointer.
           Address* pc_addr = it.frame()->pc_address();
diff --git a/third_party/v8/src/flags/flag-definitions.h b/third_party/v8/src/flags/flag-definitions.h
index ad3354c..1e1b1f3 100644
--- a/third_party/v8/src/flags/flag-definitions.h
+++ b/third_party/v8/src/flags/flag-definitions.h
@@ -1503,7 +1503,7 @@
 DEFINE_BOOL(trace_experimental_regexp_engine, false,
             "trace execution of experimental regexp engine")
 
-DEFINE_BOOL(enable_experimental_regexp_engine_on_excessive_backtracks, false,
+DEFINE_BOOL(enable_experimental_regexp_engine_on_excessive_backtracks, true,
             "fall back to a breadth-first regexp engine on excessive "
             "backtracking")
 DEFINE_UINT(regexp_backtracks_before_fallback, 50000,
diff --git a/third_party/v8/src/interpreter/bytecode-generator.cc b/third_party/v8/src/interpreter/bytecode-generator.cc
index 6757154..0e1c45c 100644
--- a/third_party/v8/src/interpreter/bytecode-generator.cc
+++ b/third_party/v8/src/interpreter/bytecode-generator.cc
@@ -4926,9 +4926,8 @@
       Property* property = chain->expression()->AsProperty();
       BuildOptionalChain([&]() {
         VisitAndPushIntoRegisterList(property->obj(), &args);
-        VisitPropertyLoad(args.last_register(), property);
+        VisitPropertyLoadForRegister(args.last_register(), property, callee);
       });
-      builder()->StoreAccumulatorInRegister(callee);
       break;
     }
     case Call::SUPER_CALL:
diff --git a/third_party/v8/src/objects/value-serializer.cc b/third_party/v8/src/objects/value-serializer.cc
index 1ce01ab..d5119cf 100644
--- a/third_party/v8/src/objects/value-serializer.cc
+++ b/third_party/v8/src/objects/value-serializer.cc
@@ -865,11 +865,6 @@
     WriteVarint(index.FromJust());
     return ThrowIfOutOfMemory();
   }
-  if (!array_buffer->is_detachable()) {
-    ThrowDataCloneError(
-        MessageTemplate::kDataCloneErrorNonDetachableArrayBuffer);
-    return Nothing<bool>();
-  }
 
   uint32_t* transfer_entry = array_buffer_transfer_map_.Find(array_buffer);
   if (transfer_entry) {
diff --git a/third_party/v8/src/wasm/baseline/liftoff-assembler.cc b/third_party/v8/src/wasm/baseline/liftoff-assembler.cc
index ec7e0cb..956291f 100644
--- a/third_party/v8/src/wasm/baseline/liftoff-assembler.cc
+++ b/third_party/v8/src/wasm/baseline/liftoff-assembler.cc
@@ -38,7 +38,6 @@
 
   struct RegisterLoad {
     enum LoadKind : uint8_t {
-      kNop,           // no-op, used for high fp of a fp pair.
       kConstant,      // load a constant value into a register.
       kStack,         // fill a register from a stack slot.
       kLowHalfStack,  // fill a register from the low half of a stack slot.
@@ -65,10 +64,6 @@
       return {half == kLowWord ? kLowHalfStack : kHighHalfStack, kWasmI32,
               offset};
     }
-    static RegisterLoad Nop() {
-      // ValueType does not matter.
-      return {kNop, kWasmI32, 0};
-    }
 
    private:
     RegisterLoad(LoadKind kind, ValueType type, int32_t value)
@@ -225,11 +220,11 @@
           RegisterLoad::HalfStack(stack_offset, kHighWord);
     } else if (dst.is_fp_pair()) {
       DCHECK_EQ(kWasmS128, type);
-      // Only need register_load for low_gp since we load 128 bits at one go.
-      // Both low and high need to be set in load_dst_regs_ but when iterating
-      // over it, both low and high will be cleared, so we won't load twice.
+      // load_dst_regs_.set above will set both low and high fp regs.
+      // But unlike gp_pair, we load a kWasm128 in one go in ExecuteLoads.
+      // So unset the top fp register to skip loading it.
+      load_dst_regs_.clear(dst.high());
       *register_load(dst.low()) = RegisterLoad::Stack(stack_offset, type);
-      *register_load(dst.high()) = RegisterLoad::Nop();
     } else {
       *register_load(dst) = RegisterLoad::Stack(stack_offset, type);
     }
@@ -326,8 +321,6 @@
     for (LiftoffRegister dst : load_dst_regs_) {
       RegisterLoad* load = register_load(dst);
       switch (load->kind) {
-        case RegisterLoad::kNop:
-          break;
         case RegisterLoad::kConstant:
           asm_->LoadConstant(dst, load->type == kWasmI64
                                       ? WasmValue(int64_t{load->value})
diff --git a/third_party/v8/src/wasm/baseline/liftoff-compiler.cc b/third_party/v8/src/wasm/baseline/liftoff-compiler.cc
index 1ead202..5066468 100644
--- a/third_party/v8/src/wasm/baseline/liftoff-compiler.cc
+++ b/third_party/v8/src/wasm/baseline/liftoff-compiler.cc
@@ -4138,7 +4138,10 @@
       dead_breakpoint);
   decoder.Decode();
   LiftoffCompiler* compiler = &decoder.interface();
-  if (decoder.failed()) compiler->OnFirstError(&decoder);
+  if (decoder.failed()) {
+    compiler->OnFirstError(&decoder);
+    return WasmCompilationResult{};
+  }
 
   if (counters) {
     // Check that the histogram for the bailout reasons has the correct size.
diff --git a/third_party/v8/test/mjsunit/mjsunit.status b/third_party/v8/test/mjsunit/mjsunit.status
index 398e7c1..ecf8f43 100644
--- a/third_party/v8/test/mjsunit/mjsunit.status
+++ b/third_party/v8/test/mjsunit/mjsunit.status
@@ -77,10 +77,6 @@
   # https://crbug.com/1129854
   'tools/log': ['arch == arm or arch == arm64', SKIP],
 
-  # crbug.com/1161357
-  # TODO(solanes): Remove this entry once the underlying issue is fixed.
-  'regress/regress-1161357': [PASS, FAIL],
-
   ##############################################################################
   # Tests where variants make no sense.
   'd8/enable-tracing': [PASS, NO_VARIANTS],
diff --git a/third_party/v8/test/mjsunit/regress/regress-1161357.js b/third_party/v8/test/mjsunit/regress/regress-1161357.js
deleted file mode 100644
index b6f03b9..0000000
--- a/third_party/v8/test/mjsunit/regress/regress-1161357.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2020 the V8 project authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-for (let i = 0; i < 3; i++) {
-  for (let j = 0; j < 32767; j++) {
-    Number;
-  }
-  for (let j = 0; j < 2335; j++) {
-    Number;
-  }
-  var arr = [, ...(new Int16Array(0xffff)), 4294967296];
-  arr.concat(Number, arr)
-}
-eval(``);
diff --git a/third_party/v8/test/mjsunit/regress/regress-crbug-1038178.js b/third_party/v8/test/mjsunit/regress/regress-crbug-1038178.js
index 3a84066..0362f69 100644
--- a/third_party/v8/test/mjsunit/regress/regress-crbug-1038178.js
+++ b/third_party/v8/test/mjsunit/regress/regress-crbug-1038178.js
@@ -15,7 +15,7 @@
     (((function(){})())?.v)()
 }
 %PrepareFunctionForOptimization(opt)
-assertThrows(() => opt());
-assertThrows(() => opt());
+assertThrows(opt());
+assertThrows(opt());
 %OptimizeFunctionOnNextCall(opt)
-assertThrows(() => opt());
+assertThrows(opt());
diff --git a/third_party/v8/test/mjsunit/regress/regress-crbug-1171954.js b/third_party/v8/test/mjsunit/regress/regress-crbug-1171954.js
deleted file mode 100644
index 94fbb32..0000000
--- a/third_party/v8/test/mjsunit/regress/regress-crbug-1171954.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 the V8 project authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Flags: --always-opt
-
-// This causes the register used by the call in the later try-catch block to be
-// used by the ToName conversion for null which causes a DCHECK fail when
-// compiling. If register allocation changes, this test may no longer reproduce
-// the crash but it is not easy write a proper test because it is linked to
-// register allocation. This test should always work, so shouldn't cause any
-// flakes.
-try {
-  var { [null]: __v_12, } = {};
-} catch (e) {}
-
-try {
-  assertEquals((__v_40?.o?.m)().p);
-} catch (e) {}
diff --git a/third_party/v8/test/mjsunit/regress/wasm/regress-1161654.js b/third_party/v8/test/mjsunit/regress/wasm/regress-1161654.js
deleted file mode 100644
index 93f2c3b..0000000
--- a/third_party/v8/test/mjsunit/regress/wasm/regress-1161654.js
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2021 the V8 project authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Flags: --wasm-staging
-
-// This is a fuzzer-generated test case that exposed a bug in Liftoff that only
-// affects ARM, where the fp register aliasing is different from other archs.
-// We were inncorrectly clearing the the high fp register in a LiftoffRegList
-// indicating registers to load, hitting a DCHECK.
-load('test/mjsunit/wasm/wasm-module-builder.js');
-
-const builder = new WasmModuleBuilder();
-builder.addMemory(19, 32, false);
-builder.addGlobal(kWasmI32, 0);
-builder.addType(makeSig([], []));
-builder.addType(makeSig([kWasmI64, kWasmS128, kWasmF32], [kWasmI32]));
-// Generate function 1 (out of 5).
-builder.addFunction(undefined, 0 /* sig */)
-  .addBodyWithEnd([
-// signature: v_v
-// body:
-kExprI32Const, 0x05,  // i32.const
-kExprReturn,  // return
-kExprUnreachable,  // unreachable
-kExprEnd,  // end @5
-]);
-// Generate function 4 (out of 5).
-builder.addFunction(undefined, 1 /* sig */)
-  .addBodyWithEnd([
-// signature: i_lsf
-// body:
-kExprLocalGet, 0x01,  // local.get
-kExprLocalGet, 0x01,  // local.get
-kExprGlobalGet, 0x00,  // global.get
-kExprDrop,  // drop
-kExprLoop, kWasmStmt,  // loop @8
-  kExprLoop, 0x00,  // loop @10
-    kExprI32Const, 0x01,  // i32.const
-    kExprMemoryGrow, 0x00,  // memory.grow
-    kExprI64LoadMem8U, 0x00, 0x70,  // i64.load8_u
-    kExprLoop, 0x00,  // loop @19
-      kExprCallFunction, 0x00,  // call function #0: v_v
-      kExprEnd,  // end @23
-    kExprI64Const, 0xf1, 0x24,  // i64.const
-    kExprGlobalGet, 0x00,  // global.get
-    kExprDrop,  // drop
-    kExprBr, 0x00,  // br depth=0
-    kExprEnd,  // end @32
-  kExprEnd,  // end @33
-kExprI32Const, 0x5b,  // i32.const
-kExprReturn,  // return
-kExprEnd,  // end @37
-]);
-// Instantiation is enough to cause a crash.
-const instance = builder.instantiate();
diff --git a/third_party/v8/test/mjsunit/wasm/worker-memory.js b/third_party/v8/test/mjsunit/wasm/worker-memory.js
index bf5430f..c5b99ed 100644
--- a/third_party/v8/test/mjsunit/wasm/worker-memory.js
+++ b/third_party/v8/test/mjsunit/wasm/worker-memory.js
@@ -11,13 +11,6 @@
   assertThrows(() => worker.postMessage(memory), Error);
 })();
 
-(function TestPostMessageUnsharedMemoryBuffer() {
-  let worker = new Worker('', {type: 'string'});
-  let memory = new WebAssembly.Memory({initial: 1, maximum: 2});
-
-  assertThrows(() => worker.postMessage(memory.buffer), Error);
-})();
-
 // Can't use assert in a worker.
 let workerHelpers =
   `function assertTrue(value, msg) {
diff --git a/third_party/v8/tools/clusterfuzz/js_fuzzer/package-lock.json b/third_party/v8/tools/clusterfuzz/js_fuzzer/package-lock.json
index c7a46b1..85eb89d 100644
--- a/third_party/v8/tools/clusterfuzz/js_fuzzer/package-lock.json
+++ b/third_party/v8/tools/clusterfuzz/js_fuzzer/package-lock.json
@@ -1969,9 +1969,9 @@
       }
     },
     "lodash": {
-      "version": "4.17.12",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.12.tgz",
-      "integrity": "sha512-+CiwtLnsJhX03p20mwXuvhoebatoh5B3tt+VvYlrPgZC1g36y+RRbkufX95Xa+X4I59aWEacDFYwnJZiyBh9gA=="
+      "version": "4.17.11",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
+      "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
     },
     "lodash._baseassign": {
       "version": "3.2.0",
diff --git a/third_party/zlib/google/zip.cc b/third_party/zlib/google/zip.cc
index 54196a6..1559ad9 100644
--- a/third_party/zlib/google/zip.cc
+++ b/third_party/zlib/google/zip.cc
@@ -176,13 +176,6 @@
   return UnzipWithFilterCallback(
       src_file, dest_dir, base::BindRepeating(&ExcludeNoFilesFilter), true);
 }
-#if defined(IN_MEMORY_UPDATES)
-bool Unzip(const std::string& src_str, const base::FilePath& dest_dir) {
-  return UnzipWithFilterCallback(
-      src_str, dest_dir, base::BindRepeating(&ExcludeNoFilesFilter), true);
-}
-#endif
-
 
 bool UnzipWithFilterCallback(const base::FilePath& src_file,
                              const base::FilePath& dest_dir,
@@ -207,24 +200,17 @@
 #endif
 }
 
-#if defined(IN_MEMORY_UPDATES)
-bool UnzipWithFilterCallback(const std::string& src_str,
-                             const base::FilePath& dest_dir,
-                             const FilterCallback& filter_cb,
-                             bool log_skipped_files) {
-  return UnzipWithFilterAndWriters(
-      src_str, base::BindRepeating(&CreateFilePathWriterDelegate, dest_dir),
-      base::BindRepeating(&CreateDirectory, dest_dir), filter_cb,
-      log_skipped_files);
-}
-#endif
-
 #if defined(STARBOARD)
-bool UnzipWithFilterAndWriters(ZipReader& reader,
+bool UnzipWithFilterAndWriters(const base::FilePath& src_file,
                                const WriterFactory& writer_factory,
                                const DirectoryCreator& directory_creator,
                                const FilterCallback& filter_cb,
                                bool log_skipped_files) {
+  ZipReader reader;
+  if (!reader.Open(src_file)) {
+    DLOG(WARNING) << "Failed to open src_file " << src_file;
+    return false;
+  }
   while (reader.HasMore()) {
     if (!reader.OpenCurrentEntryInZip()) {
       DLOG(WARNING) << "Failed to open the current file in zip";
@@ -258,44 +244,7 @@
   }
   return true;
 }
-
-bool UnzipWithFilterAndWriters(const base::FilePath& src_file,
-                               const WriterFactory& writer_factory,
-                               const DirectoryCreator& directory_creator,
-                               const FilterCallback& filter_cb,
-                               bool log_skipped_files) {
-  ZipReader reader;
-  if (!reader.Open(src_file)) {
-    DLOG(WARNING) << "Failed to open src_file " << src_file;
-    return false;
-  }
-  return UnzipWithFilterAndWriters(reader,
-                                   writer_factory,
-                                   directory_creator,
-                                   filter_cb,
-                                   log_skipped_files);
-}
-
-#if defined(IN_MEMORY_UPDATES)
-bool UnzipWithFilterAndWriters(const std::string& src_str,
-                               const WriterFactory& writer_factory,
-                               const DirectoryCreator& directory_creator,
-                               const FilterCallback& filter_cb,
-                               bool log_skipped_files) {
-  ZipReader reader;
-  if (!reader.OpenFromString(src_str)) {
-    DLOG(WARNING) << "Failed to open src_str";
-    return false;
-  }
-  return UnzipWithFilterAndWriters(reader,
-                                   writer_factory,
-                                   directory_creator,
-                                   filter_cb,
-                                   log_skipped_files);
-}
-#endif  // defined(IN_MEMORY_UPDATES)
-#else  // defined(STARBOARD)
-
+#else
 bool UnzipWithFilterAndWriters(const base::PlatformFile& src_file,
                                const WriterFactory& writer_factory,
                                const DirectoryCreator& directory_creator,
@@ -339,7 +288,7 @@
   }
   return true;
 }
-#endif  // STARBOARD
+#endif
 
 bool ZipWithFilterCallback(const base::FilePath& src_dir,
                            const base::FilePath& dest_file,
diff --git a/third_party/zlib/google/zip.h b/third_party/zlib/google/zip.h
index 6c4154e..dad3f41 100644
--- a/third_party/zlib/google/zip.h
+++ b/third_party/zlib/google/zip.h
@@ -158,14 +158,6 @@
                              const FilterCallback& filter_cb,
                              bool log_skipped_files);
 
-#if defined(IN_MEMORY_UPDATES)
-// An overload for a zip represented as a string.
-bool UnzipWithFilterCallback(const std::string& zip_str,
-                             const base::FilePath& dest_dir,
-                             const FilterCallback& filter_cb,
-                             bool log_skipped_files);
-#endif
-
 // Unzip the contents of zip_file, using the writers provided by writer_factory.
 // For each file in zip_file, include it only if the callback |filter_cb|
 // returns true. Otherwise omit it.
@@ -182,28 +174,16 @@
                                const DirectoryCreator& directory_creator,
                                const FilterCallback& filter_cb,
                                bool log_skipped_files);
-#if defined(IN_MEMORY_UPDATES)
-// An overload for a zip represented as a string.
-bool UnzipWithFilterAndWriters(const std::string& zip_str,
-                               const WriterFactory& writer_factory,
-                               const DirectoryCreator& directory_creator,
-                               const FilterCallback& filter_cb,
-                               bool log_skipped_files);
-#endif  // defined(IN_MEMORY_UPDATES)
-#else  // defined(STARBOARD)
+#else
 bool UnzipWithFilterAndWriters(const base::PlatformFile& zip_file,
                                const WriterFactory& writer_factory,
                                const DirectoryCreator& directory_creator,
                                const FilterCallback& filter_cb,
                                bool log_skipped_files);
-#endif  // defined(STARBOARD)
+#endif
 
 // Unzip the contents of zip_file into dest_dir.
 bool Unzip(const base::FilePath& zip_file, const base::FilePath& dest_dir);
-#if defined(IN_MEMORY_UPDATES)
-// Unzip the contents of zip_str into dest_dir.
-bool Unzip(const std::string& zip_str, const base::FilePath& dest_dir);
-#endif
 
 }  // namespace zip
 
diff --git a/third_party/zlib/google/zip_unittest.cc b/third_party/zlib/google/zip_unittest.cc
index 69763a0..b92c650 100644
--- a/third_party/zlib/google/zip_unittest.cc
+++ b/third_party/zlib/google/zip_unittest.cc
@@ -196,12 +196,6 @@
     ASSERT_TRUE(base::PathExists(path)) << "no file " << path.value();
     ASSERT_TRUE(zip::Unzip(path, test_dir_));
 
-#if defined(STARBOARD)
-    VerifyUnzippedContents(expect_hidden_files);
-  }
-  void VerifyUnzippedContents(bool expect_hidden_files) {
-#endif
-
     base::FilePath original_dir;
     ASSERT_TRUE(GetTestDataDirectory(&original_dir));
     original_dir = original_dir.AppendASCII("test");
@@ -244,21 +238,6 @@
     EXPECT_EQ(expected_count, count);
   }
 
-#if defined(IN_MEMORY_UPDATES)
-  void ReadFileToString(const base::FilePath::StringType& filename,
-                        std::string* contents) {
-    base::FilePath test_dir;
-    ASSERT_TRUE(GetTestDataDirectory(&test_dir));
-    ASSERT_TRUE(base::ReadFileToString(test_dir.Append(filename), contents));
-  }
-
-  void TestUnzipString(const std::string& zip_src, bool expect_hidden_files) {
-    ASSERT_TRUE(zip::Unzip(zip_src, test_dir_));
-
-    VerifyUnzippedContents(expect_hidden_files);
-  }
-#endif
-
   // This function does the following:
   // 1) Creates a test.txt file with the given last modification timestamp
   // 2) Zips test.txt and extracts it back into a different location.
@@ -332,28 +311,10 @@
   TestUnzipFile(FILE_PATH_LITERAL("test.zip"), true);
 }
 
-#if defined(IN_MEMORY_UPDATES)
-TEST_F(ZipTest, UnzipFromString) {
-  std::string zip_str;
-  ReadFileToString(FILE_PATH_LITERAL("test.zip"), &zip_str);
-
-  TestUnzipString(zip_str, true);
-}
-#endif
-
 TEST_F(ZipTest, UnzipUncompressed) {
   TestUnzipFile(FILE_PATH_LITERAL("test_nocompress.zip"), true);
 }
 
-#if defined(IN_MEMORY_UPDATES)
-TEST_F(ZipTest, UnzipFromStringUncompressed) {
-  std::string zip_str;
-  ReadFileToString(FILE_PATH_LITERAL("test_nocompress.zip"), &zip_str);
-
-  TestUnzipString(zip_str, true);
-}
-#endif
-
 TEST_F(ZipTest, UnzipEvil) {
   base::FilePath path;
   ASSERT_TRUE(GetTestDataDirectory(&path));
@@ -369,24 +330,6 @@
   ASSERT_FALSE(base::PathExists(evil_file));
 }
 
-#if defined(IN_MEMORY_UPDATES)
-TEST_F(ZipTest, UnzipFromStringEvil) {
-  std::string zip_str;
-  ReadFileToString(FILE_PATH_LITERAL("evil.zip"), &zip_str);
-
-  // Unzip the zip file into a sub directory of test_dir_ so evil.zip
-  // won't create a persistent file outside test_dir_ in case of a
-  // failure.
-  base::FilePath output_dir = test_dir_.AppendASCII("out");
-  ASSERT_FALSE(zip::Unzip(zip_str, output_dir));
-
-  base::FilePath evil_file = output_dir;
-  evil_file = evil_file.AppendASCII(
-      "../levilevilevilevilevilevilevilevilevilevilevilevil");
-  ASSERT_FALSE(base::PathExists(evil_file));
-}
-#endif
-
 TEST_F(ZipTest, UnzipEvil2) {
   base::FilePath path;
   ASSERT_TRUE(GetTestDataDirectory(&path));
@@ -402,24 +345,6 @@
   ASSERT_FALSE(base::PathExists(evil_file));
 }
 
-#if defined(IN_MEMORY_UPDATES)
-TEST_F(ZipTest, UnzipFromStringEvil2) {
-  std::string zip_str;
-  // The zip file contains an evil file with invalid UTF-8 in its file
-  // name.
-  ReadFileToString(FILE_PATH_LITERAL("evil_via_invalid_utf8.zip"), &zip_str);
-
-  // See the comment at UnzipEvil() for why we do this.
-  base::FilePath output_dir = test_dir_.AppendASCII("out");
-  // This should fail as it contains an evil file.
-  ASSERT_FALSE(zip::Unzip(zip_str, output_dir));
-
-  base::FilePath evil_file = output_dir;
-  evil_file = evil_file.AppendASCII("../evil.txt");
-  ASSERT_FALSE(base::PathExists(evil_file));
-}
-#endif
-
 TEST_F(ZipTest, UnzipWithFilter) {
   auto filter = base::BindRepeating([](const base::FilePath& path) {
     return path.BaseName().MaybeAsASCII() == "foo.txt";
@@ -456,46 +381,6 @@
   ASSERT_EQ(0, extracted_count);
 }
 
-#if defined(IN_MEMORY_UPDATES)
-TEST_F(ZipTest, UnzipFromStringWithFilter) {
-  auto filter = base::BindRepeating([](const base::FilePath& path) {
-    return path.BaseName().MaybeAsASCII() == "foo.txt";
-  });
-  std::string zip_str;
-  ReadFileToString(FILE_PATH_LITERAL("test.zip"), &zip_str);
-
-  ASSERT_TRUE(zip::UnzipWithFilterCallback(zip_str,
-                                           test_dir_, filter, false));
-
-  // Only foo.txt should have been extracted. The following paths should not
-  // be extracted:
-  //   foo/
-  //   foo/bar.txt
-  //   foo/bar/
-  //   foo/bar/.hidden
-  //   foo/bar/baz.txt
-  //   foo/bar/quux.txt
-  ASSERT_TRUE(base::PathExists(test_dir_.AppendASCII("foo.txt")));
-  base::FileEnumerator extractedFiles(
-      test_dir_,
-      false,  // Do not enumerate recursively - the file must be in the root.
-      base::FileEnumerator::FileType::FILES);
-  int extracted_count = 0;
-  while (!extractedFiles.Next().empty())
-    ++extracted_count;
-  ASSERT_EQ(1, extracted_count);
-
-  base::FileEnumerator extractedDirs(
-      test_dir_,
-      false,  // Do not enumerate recursively - we require zero directories.
-      base::FileEnumerator::FileType::DIRECTORIES);
-  extracted_count = 0;
-  while (!extractedDirs.Next().empty())
-    ++extracted_count;
-  ASSERT_EQ(0, extracted_count);
-}
-#endif
-
 TEST_F(ZipTest, UnzipWithDelegates) {
   auto filter =
       base::BindRepeating([](const base::FilePath& path) { return true; });
@@ -534,41 +419,6 @@
   ASSERT_TRUE(base::PathExists(dir_foo_bar.AppendASCII("quux.txt")));
 }
 
-#if defined(IN_MEMORY_UPDATES)
-TEST_F(ZipTest, UnzipFromStringWithDelegates) {
-  auto filter =
-       base::BindRepeating([](const base::FilePath& path) { return true; });
-  auto dir_creator = base::BindRepeating(
-      [](const base::FilePath& extract_dir, const base::FilePath& entry_path) {
-        return base::CreateDirectory(extract_dir.Append(entry_path));
-      },
-      test_dir_);
-  auto writer = base::BindRepeating(
-      [](const base::FilePath& extract_dir, const base::FilePath& entry_path)
-          -> std::unique_ptr<zip::WriterDelegate> {
-        return std::make_unique<zip::FilePathWriterDelegate>(
-            extract_dir.Append(entry_path));
-      },
-      test_dir_);
-  std::string zip_str;
-  ReadFileToString(FILE_PATH_LITERAL("test.zip"), &zip_str);
-
-  ASSERT_TRUE(zip::UnzipWithFilterAndWriters(
-      zip_str, writer, dir_creator, filter, false));
-
-  base::FilePath dir = test_dir_;
-  base::FilePath dir_foo = dir.AppendASCII("foo");
-  base::FilePath dir_foo_bar = dir_foo.AppendASCII("bar");
-  ASSERT_TRUE(base::PathExists(dir.AppendASCII("foo.txt")));
-  ASSERT_TRUE(base::PathExists(dir_foo));
-  ASSERT_TRUE(base::PathExists(dir_foo.AppendASCII("bar.txt")));
-  ASSERT_TRUE(base::PathExists(dir_foo_bar));
-  ASSERT_TRUE(base::PathExists(dir_foo_bar.AppendASCII(".hidden")));
-  ASSERT_TRUE(base::PathExists(dir_foo_bar.AppendASCII("baz.txt")));
-  ASSERT_TRUE(base::PathExists(dir_foo_bar.AppendASCII("quux.txt")));
-}
-#endif
-
 TEST_F(ZipTest, Zip) {
   base::FilePath src_dir;
   ASSERT_TRUE(GetTestDataDirectory(&src_dir));
diff --git a/tools/update_required_branch_checks.py b/tools/update_required_branch_checks.py
deleted file mode 100644
index fa660df..0000000
--- a/tools/update_required_branch_checks.py
+++ /dev/null
@@ -1,127 +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.
-"""Updates the requires status checks for a branch."""
-
-import argparse
-from github import Github
-from typing import List
-
-YOUR_GITHUB_TOKEN = ''
-assert YOUR_GITHUB_TOKEN != '', 'YOUR_GITHUB_TOKEN must be set.'
-
-TARGET_REPO = 'youtube/cobalt'
-
-EXCLUDED_CHECK_PATTERNS = [
-    'feedback/copybara',
-    '_on_device_',
-    'codecov',
-    'prepare_branch_list',
-    'cherry_pick',
-    # Excludes templated check names.
-    '${{'
-]
-
-# Exclude rc_11 and COBALT_9 releases.
-MINIMUM_LTS_RELEASE_NUMBER = 19
-LATEST_LTS_RELEASE_NUMBER = 24
-
-
-def get_protected_branches() -> List[str]:
-  branches = ['main']
-  for i in range(MINIMUM_LTS_RELEASE_NUMBER, LATEST_LTS_RELEASE_NUMBER + 1):
-    branches.append(f'{i}.lts.1+')
-  return branches
-
-
-def initialize_repo_connection():
-  g = Github(YOUR_GITHUB_TOKEN)
-  return g.get_repo(TARGET_REPO)
-
-
-def get_checks_for_branch(repo, branch: str) -> None:
-  prs = repo.get_pulls(
-      state='closed', sort='updated', base=branch, direction='desc')
-
-  latest_pr = None
-  for pr in prs:
-    if pr.merged:
-      latest_pr = pr
-      break
-
-  latest_pr_commit = repo.get_commit(latest_pr.head.sha)
-  checks = latest_pr_commit.get_check_runs()
-  return checks
-
-
-def should_include_run(check_run) -> bool:
-  for pattern in EXCLUDED_CHECK_PATTERNS:
-    if pattern in check_run.name:
-      return False
-  return True
-
-
-def get_required_checks_for_branch(repo, branch: str) -> List[str]:
-  checks = get_checks_for_branch(repo, branch)
-  filtered_check_runs = [run for run in checks if should_include_run(run)]
-  check_names = set(run.name for run in filtered_check_runs)
-  return list(check_names)
-
-
-def print_checks(branch: str, check_names: List[str]) -> None:
-  print(f'Checks for {branch}:')
-  for check_name in check_names:
-    print(check_name)
-  print()
-
-
-def update_protection_for_branch(repo, branch: str,
-                                 check_names: List[str]) -> None:
-  branch = repo.get_branch(branch)
-  branch.edit_required_status_checks(contexts=check_names)
-
-
-def parse_args() -> None:
-  parser = argparse.ArgumentParser()
-  parser.add_argument(
-      '-b',
-      '--branch',
-      action='append',
-      help='Branch to update. Can be repeated to update multiple branches.'
-      ' Defaults to all protected branches.')
-  parser.add_argument(
-      '--dry_run',
-      action='store_true',
-      default=False,
-      help='Only print protection updates.')
-  args = parser.parse_args()
-
-  if not args.branch:
-    args.branch = get_protected_branches()
-
-  return args
-
-
-def main() -> None:
-  args = parse_args()
-  repo = initialize_repo_connection()
-  for branch in args.branch:
-    required_checks = get_required_checks_for_branch(repo, branch)
-    if args.dry_run:
-      print_checks(branch, required_checks)
-    else:
-      update_protection_for_branch(repo, branch, required_checks)
-
-
-if __name__ == '__main__':
-  main()