Import Cobalt 23.master.0.308193
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2ee0961..08dba15 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -43,8 +43,9 @@
                --ignore-words-list, atleast]
         exclude: |
             (?x)^(
-                cobalt/content/i18n/platform/|
-                cobalt/content/licenses/
+                starboard/[^/]+/i18n/|
+                cobalt/content/licenses/|
+                cobalt/fetch/embedded_scripts
             )
 
 -   repo: local
@@ -79,14 +80,12 @@
         language: system
         types: [python]
         args: [-i, -vv]
-        exclude: '\.gypi?$'
     -   id: pylint
         name: pylint
         entry: pylint
         language: system
         types: [python]
         args: [-d W0201]
-        exclude: '\.gypi?$'
     -   id: google-java-format
         name: google-java-format
         entry: python ./precommit_hooks/google_java_format_wrapper.py
@@ -175,9 +174,3 @@
         entry: gn format
         language: system
         files: '.*\.gni?$'
-    -   id: gyp-changes
-        name: Disallow GYP Changes
-        entry: echo -e "GYP is deprecated and will not be included past Cobalt 22. Use GN.\n"
-        language: system
-        files: '\.gypi?$'
-        verbose: true
diff --git a/base/files/important_file_writer_unittest.cc b/base/files/important_file_writer_unittest.cc
index d444eed..4c96b06 100644
--- a/base/files/important_file_writer_unittest.cc
+++ b/base/files/important_file_writer_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
+#include "base/metrics/statistics_recorder.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/test/metrics/histogram_tester.h"
@@ -115,7 +116,8 @@
 
 class ImportantFileWriterTest : public testing::Test {
  public:
-  ImportantFileWriterTest() = default;
+  ImportantFileWriterTest() :
+    recorder_for_testing_(StatisticsRecorder::CreateTemporaryForTesting()) {}
   void SetUp() override {
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
     file_ = temp_dir_.GetPath().AppendASCII("test-file");
@@ -128,6 +130,8 @@
 
  private:
   ScopedTempDir temp_dir_;
+
+  std::unique_ptr<StatisticsRecorder> recorder_for_testing_;
 };
 
 TEST_F(ImportantFileWriterTest, Basic) {
diff --git a/base/metrics/persistent_histogram_storage_unittest.cc b/base/metrics/persistent_histogram_storage_unittest.cc
index 0b9b1ce..9db4391 100644
--- a/base/metrics/persistent_histogram_storage_unittest.cc
+++ b/base/metrics/persistent_histogram_storage_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/metrics/statistics_recorder.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -25,7 +26,8 @@
 
 class PersistentHistogramStorageTest : public testing::Test {
  protected:
-  PersistentHistogramStorageTest() = default;
+  PersistentHistogramStorageTest() :
+    recorder_for_testing_(StatisticsRecorder::CreateTemporaryForTesting()) {}
   ~PersistentHistogramStorageTest() override = default;
 
   // Creates a unique temporary directory, and sets the test storage directory.
@@ -47,6 +49,8 @@
   // The directory into which metrics files are written.
   FilePath test_storage_dir_;
 
+  std::unique_ptr<StatisticsRecorder> recorder_for_testing_;
+
   DISALLOW_COPY_AND_ASSIGN(PersistentHistogramStorageTest);
 };
 
diff --git a/base/task/sequence_manager/task_queue_selector_unittest.cc b/base/task/sequence_manager/task_queue_selector_unittest.cc
index d691230..fd5b9f6 100644
--- a/base/task/sequence_manager/task_queue_selector_unittest.cc
+++ b/base/task/sequence_manager/task_queue_selector_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/bind.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
+#include "base/metrics/persistent_histogram_allocator.h"
 #include "base/metrics/statistics_recorder.h"
 #include "base/pending_task.h"
 #include "base/task/sequence_manager/task_queue_impl.h"
@@ -96,7 +97,9 @@
   TaskQueueSelectorTest()
       : test_closure_(BindRepeating(&TaskQueueSelectorTest::TestFunction)),
         associated_thread_(AssociatedThreadId::CreateBound()),
-        selector_(associated_thread_) {}
+        selector_(associated_thread_) {
+            GlobalHistogramAllocator::ReleaseForTesting();
+        }
   ~TaskQueueSelectorTest() override = default;
 
   TaskQueueSelectorForTest::PrioritizingSelector* prioritizing_selector() {
@@ -164,6 +167,7 @@
           << i;
       queue_to_index_map_.insert(std::make_pair(task_queues_[i].get(), i));
     }
+    recorder_for_testing_.reset(StatisticsRecorder::CreateTemporaryForTesting().get());
     histogram_tester_.reset(new HistogramTester());
   }
 
@@ -175,6 +179,7 @@
       selector_.RemoveQueue(task_queue.get());
       task_queue->UnregisterTaskQueue();
     }
+    recorder_for_testing_.release();
   }
 
   std::unique_ptr<TaskQueueImpl> NewTaskQueueWithBlockReporting() {
@@ -183,6 +188,7 @@
   }
 
   const size_t kTaskQueueCount = 5;
+  std::unique_ptr<StatisticsRecorder> recorder_for_testing_;
   RepeatingClosure test_closure_;
   scoped_refptr<AssociatedThreadId> associated_thread_;
   TaskQueueSelectorForTest selector_;
diff --git a/base/test/metrics/histogram_tester_unittest.cc b/base/test/metrics/histogram_tester_unittest.cc
index f8f52d7..6b0f12d 100644
--- a/base/test/metrics/histogram_tester_unittest.cc
+++ b/base/test/metrics/histogram_tester_unittest.cc
@@ -9,6 +9,8 @@
 
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/histogram_samples.h"
+#include "base/metrics/persistent_histogram_allocator.h"
+#include "base/metrics/statistics_recorder.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -28,7 +30,12 @@
 
 namespace base {
 
-typedef testing::Test HistogramTesterTest;
+class HistogramTesterTest : public testing::Test {
+  std::unique_ptr<StatisticsRecorder> recorder_for_testing_;
+public:
+  HistogramTesterTest()
+    : recorder_for_testing_(StatisticsRecorder::CreateTemporaryForTesting()) {}
+};
 
 TEST_F(HistogramTesterTest, Scope) {
   // Record a histogram before the creation of the recorder.
diff --git a/cobalt/BUILD.gn b/cobalt/BUILD.gn
index 2a4e0b6..d025533 100644
--- a/cobalt/BUILD.gn
+++ b/cobalt/BUILD.gn
@@ -20,7 +20,6 @@
     "//cobalt/browser:browser_test",
     "//cobalt/cssom:cssom_test",
     "//cobalt/dom:dom_test",
-    "//cobalt/dom/testing:dom_testing",
     "//cobalt/dom_parser:dom_parser_test",
     "//cobalt/encoding:text_encoding_test",
     "//cobalt/extension:extension_test",
@@ -38,9 +37,11 @@
     "//cobalt/renderer/sandbox:renderer_sandbox",
     "//cobalt/renderer/sandbox:scaling_text_sandbox",
     "//cobalt/speech/sandbox:speech_sandbox",
+    "//cobalt/web:web_test",
     "//cobalt/web_animations:web_animations_test",
     "//cobalt/webdriver:webdriver_test",
     "//cobalt/websocket:websocket_test",
+    "//cobalt/worker:worker_test",
     "//cobalt/xhr:xhr_test",
   ]
 
diff --git a/cobalt/audio/BUILD.gn b/cobalt/audio/BUILD.gn
index 6e9c962..3cc95a7 100644
--- a/cobalt/audio/BUILD.gn
+++ b/cobalt/audio/BUILD.gn
@@ -57,9 +57,9 @@
 
   deps = [
     "//cobalt/base",
-    "//cobalt/dom:dom_exception",
     "//cobalt/media",
     "//cobalt/script",
+    "//cobalt/web:dom_exception",
     "//starboard:starboard_headers_only",
   ]
   deps += cobalt_platform_dependencies
diff --git a/cobalt/audio/audio_buffer.cc b/cobalt/audio/audio_buffer.cc
index 0a4c203..6a3c169 100644
--- a/cobalt/audio/audio_buffer.cc
+++ b/cobalt/audio/audio_buffer.cc
@@ -12,14 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "cobalt/audio/audio_buffer.h"
+
 #include <algorithm>
 #include <limits>
 #include <memory>
-
-#include "cobalt/audio/audio_buffer.h"
+#include <utility>
 
 #include "cobalt/audio/audio_helpers.h"
-#include "cobalt/dom/dom_exception.h"
+#include "cobalt/web/dom_exception.h"
 #include "starboard/memory.h"
 
 namespace cobalt {
@@ -37,7 +38,7 @@
     uint32 start_in_channel, script::ExceptionState* exception_state) {
   if (channel_number >= audio_bus_->channels() ||
       start_in_channel > audio_bus_->frames()) {
-    dom::DOMException::Raise(dom::DOMException::kIndexSizeErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kIndexSizeErr, exception_state);
     return;
   }
 
diff --git a/cobalt/audio/audio_buffer_source_node.cc b/cobalt/audio/audio_buffer_source_node.cc
index 79e4bdc..3d1153a 100644
--- a/cobalt/audio/audio_buffer_source_node.cc
+++ b/cobalt/audio/audio_buffer_source_node.cc
@@ -85,13 +85,13 @@
   AudioLock::AutoLock lock(audio_lock());
 
   if (when != 0 || offset != 0 || duration != 0) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              exception_state);
     return;
   }
 
   if (state_ != kNone) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              exception_state);
     return;
   }
@@ -103,13 +103,13 @@
   AudioLock::AutoLock lock(audio_lock());
 
   if (when != 0) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              exception_state);
     return;
   }
 
   if (state_ != kStarted) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              exception_state);
     return;
   }
@@ -247,7 +247,7 @@
 
 void AudioBufferSourceNode::RemoveBufferSource() {
   context()->RemoveBufferSource(base::WrapRefCounted(this));
-  DispatchEvent(new dom::Event(base::Tokens::ended()));
+  DispatchEvent(new web::Event(base::Tokens::ended()));
 }
 
 }  // namespace audio
diff --git a/cobalt/audio/audio_context.cc b/cobalt/audio/audio_context.cc
index fae4b53..0e7ae9d 100644
--- a/cobalt/audio/audio_context.cc
+++ b/cobalt/audio/audio_context.cc
@@ -26,7 +26,7 @@
 namespace audio {
 
 AudioContext::AudioContext(script::EnvironmentSettings* settings)
-    : dom::EventTarget(settings),
+    : web::EventTarget(settings),
       global_environment_(
           base::polymorphic_downcast<web::EnvironmentSettings*>(settings)
               ->context()
@@ -103,7 +103,7 @@
 }
 
 void AudioContext::TraceMembers(script::Tracer* tracer) {
-  dom::EventTarget::TraceMembers(tracer);
+  web::EventTarget::TraceMembers(tracer);
 
   tracer->Trace(destination_.get());
   for (const auto& source : buffer_sources_) {
diff --git a/cobalt/audio/audio_context.h b/cobalt/audio/audio_context.h
index 01e7b6f..f7c8677 100644
--- a/cobalt/audio/audio_context.h
+++ b/cobalt/audio/audio_context.h
@@ -29,13 +29,13 @@
 #include "cobalt/audio/audio_buffer_source_node.h"
 #include "cobalt/audio/audio_destination_node.h"
 #include "cobalt/audio/audio_helpers.h"
-#include "cobalt/dom/dom_exception.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/script/array_buffer.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/script_value.h"
+#include "cobalt/web/dom_exception.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace audio {
@@ -77,7 +77,7 @@
 // together. In most user cases, only a single AudioContext is used per
 // document.
 //   https://www.w3.org/TR/webaudio/#AudioContext-section
-class AudioContext : public dom::EventTarget {
+class AudioContext : public web::EventTarget {
  public:
   // Type for decode success and error callbacks on JS side.
   //
diff --git a/cobalt/audio/audio_file_reader_wav.cc b/cobalt/audio/audio_file_reader_wav.cc
index 30caa8f..c81ef88 100644
--- a/cobalt/audio/audio_file_reader_wav.cc
+++ b/cobalt/audio/audio_file_reader_wav.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include <memory>
+#include <sstream>
+#include <string>
 
 #include "cobalt/audio/audio_file_reader_wav.h"
 
@@ -44,6 +46,15 @@
 uint32 kWAVRIFFChunkHeaderSize = 12;
 uint32 kWAVfmtChunkHeaderSize = 16;
 
+std::string Generate4CC(int num) {
+  std::stringstream ss;
+  ss << char((num >> 24) & 0xff);
+  ss << char((num >> 16) & 0xff);
+  ss << char((num >> 8) & 0xff);
+  ss << char(num & 0xff);
+  return ss.str();
+}
+
 }  // namespace
 
 // static
@@ -99,11 +110,15 @@
 void AudioFileReaderWAV::ParseChunks(const uint8* data, size_t size) {
   uint32 offset = kWAVRIFFChunkHeaderSize;
   bool is_src_sample_in_float = false;
-  // If the WAV file is PCM format, it has two sub-chunks: first one is "fmt"
-  // and the second one is "data".
+  // If the WAV file is PCM format, it has at least two sub-chunks:
+  // first one is "fmt" and the second one is "data".
   // TODO: support the cases that the WAV file is non-PCM format and the
   // WAV file is extensible format.
-  for (int i = 0; i < 2; ++i) {
+
+  bool have_fmt_chunk = false;
+  const int chunk_info_size = 8;  // 4 bytes for chunk_id and 4 for chunk_size
+
+  while (offset < (size - chunk_info_size)) {
     // Sub chunk id.
     uint32 chunk_id = load_uint32_big_endian(data + offset);
     offset += 4;
@@ -116,16 +131,18 @@
       break;
     }
 
-    if (chunk_id == kWAVChunkID_fmt && i == 0) {
+    if (chunk_id == kWAVChunkID_fmt && !have_fmt_chunk) {
       if (!ParseWAV_fmt(data, offset, chunk_size, &is_src_sample_in_float)) {
         DLOG(WARNING) << "Parse fmt chunk failed.";
         break;
       }
-    } else if (chunk_id == kWAVChunkID_data && i == 1) {
+      have_fmt_chunk = true;
+    } else if (chunk_id == kWAVChunkID_data && have_fmt_chunk) {
       ParseWAV_data(data, offset, chunk_size, is_src_sample_in_float);
-    } else {
-      DLOG(WARNING) << "Malformed audio chunk.";
       break;
+    } else {
+      DLOG(INFO) << "Ignoring unknown audio chunk (" << Generate4CC(chunk_id)
+                 << "). (Only looking for 'fmt' and 'data')";
     }
 
     offset += chunk_size;
diff --git a/cobalt/audio/audio_node.cc b/cobalt/audio/audio_node.cc
index 49ca8e6..d99dac1 100644
--- a/cobalt/audio/audio_node.cc
+++ b/cobalt/audio/audio_node.cc
@@ -15,14 +15,14 @@
 #include "cobalt/audio/audio_node.h"
 
 #include "cobalt/audio/audio_context.h"
-#include "cobalt/dom/dom_exception.h"
+#include "cobalt/web/dom_exception.h"
 
 namespace cobalt {
 namespace audio {
 
 AudioNode::AudioNode(script::EnvironmentSettings* settings,
                      AudioContext* context)
-    : EventTarget(settings),
+    : web::EventTarget(settings),
       audio_context_(context),
       audio_lock_(context->audio_lock()),
       channel_count_(2),
@@ -49,7 +49,7 @@
   // If this value is set to zero, the implementation MUST throw a
   // NOT_SUPPORTED_ERR exception.
   if (channel_count == 0) {
-    dom::DOMException::Raise(dom::DOMException::kNotSupportedErr,
+    web::DOMException::Raise(web::DOMException::kNotSupportedErr,
                              "Audio node channel count must be non-zero.",
                              exception_state);
     return;
@@ -84,21 +84,21 @@
 
   // The destination parameter is the AudioNode to connect to.
   if (!destination) {
-    dom::DOMException::Raise(dom::DOMException::kSyntaxErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, exception_state);
     return;
   }
   // The output parameter is an index describing which output of the AudioNode
   // from which to connect. If this parameter is out-of-bound, an INDEX_SIZE_ERR
   // exception MUST be thrown.
   if (output >= number_of_outputs()) {
-    dom::DOMException::Raise(dom::DOMException::kIndexSizeErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kIndexSizeErr, exception_state);
     return;
   }
   // The input parameter is an index describing which input of the destination
   // AudioNode to connect to. If this parameter is out-of-bound, an
   // INDEX_SIZE_ERR exception MUST be thrown.
   if (input >= destination->number_of_inputs()) {
-    dom::DOMException::Raise(dom::DOMException::kIndexSizeErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kIndexSizeErr, exception_state);
     return;
   }
 
@@ -122,7 +122,7 @@
   // to disconnect. If the output parameter is out-of-bounds, an INDEX_SIZE_ERR
   // exception MUST be thrown.
   if (output >= number_of_outputs()) {
-    dom::DOMException::Raise(dom::DOMException::kIndexSizeErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kIndexSizeErr, exception_state);
     return;
   }
 
@@ -189,7 +189,7 @@
 }
 
 void AudioNode::TraceMembers(script::Tracer* tracer) {
-  EventTarget::TraceMembers(tracer);
+  web::EventTarget::TraceMembers(tracer);
 
   tracer->Trace(audio_context_);
 }
diff --git a/cobalt/audio/audio_node.h b/cobalt/audio/audio_node.h
index 925356c..fd24872 100644
--- a/cobalt/audio/audio_node.h
+++ b/cobalt/audio/audio_node.h
@@ -25,10 +25,10 @@
 #include "cobalt/audio/audio_node_channel_interpretation.h"
 #include "cobalt/audio/audio_node_input.h"
 #include "cobalt/audio/audio_node_output.h"
-#include "cobalt/dom/dom_exception.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/media/base/audio_bus.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/web/dom_exception.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace audio {
@@ -48,7 +48,7 @@
 // process its inputs (if it has any), and generate audio for its outputs
 // (if it has any).
 //   https://www.w3.org/TR/webaudio/#AudioNode-section
-class AudioNode : public dom::EventTarget {
+class AudioNode : public web::EventTarget {
   typedef media::AudioBus AudioBus;
 
  public:
diff --git a/cobalt/base/tokens.h b/cobalt/base/tokens.h
index 954b441..5629893 100644
--- a/cobalt/base/tokens.h
+++ b/cobalt/base/tokens.h
@@ -29,6 +29,7 @@
     MacroOpWithNameOnly(addsourcebuffer)                             \
     MacroOpWithNameOnly(addtrack)                                    \
     MacroOpWithNameOnly(abort)                                       \
+    MacroOpWithNameOnly(activate)                                    \
     MacroOpWithNameOnly(additions)                                   \
     MacroOpWithNameOnly(all)                                         \
     MacroOpWithNameOnly(alt)                                         \
@@ -54,6 +55,7 @@
     MacroOpWithNameOnly(end)                                         \
     MacroOpWithNameOnly(ended)                                       \
     MacroOpWithNameOnly(error)                                       \
+    MacroOpWithNameOnly(fetch)                                       \
     MacroOpWithNameOnly(focus)                                       \
     MacroOpWithNameOnly(focusin)                                     \
     MacroOpWithNameOnly(focusout)                                    \
@@ -62,6 +64,7 @@
     MacroOpWithNameOnly(hashchange)                                  \
     MacroOpWithNameOnly(hide)                                        \
     MacroOpWithNameOnly(input)                                       \
+    MacroOpWithNameOnly(install)                                     \
     MacroOpWithNameOnly(keydown)                                     \
     MacroOpWithNameOnly(keypress)                                    \
     MacroOpWithNameOnly(keystatuseschange)                           \
diff --git a/cobalt/bindings/templates/dictionary.h.template b/cobalt/bindings/templates/dictionary.h.template
index f03856c..b31fb2c 100644
--- a/cobalt/bindings/templates/dictionary.h.template
+++ b/cobalt/bindings/templates/dictionary.h.template
@@ -52,6 +52,10 @@
 #include "{{include}}"
 {% endfor %}
 
+{% for component in components %}
+namespace {{component}} {
+{% endfor %}
+
 {% for used_class in forward_declarations %}
 {% if used_class.conditional %}
 #if defined({{used_class.conditional}})
@@ -62,10 +66,6 @@
 {% endif %}
 {% endfor %}
 
-{% for component in components %}
-namespace {{component}} {
-{% endfor %}
-
 {% if parent %}
 class {{class_name}} : public {{parent}} {
 {% else %}
diff --git a/cobalt/black_box_tests/black_box_tests.py b/cobalt/black_box_tests/black_box_tests.py
index e80713f..e9808f3 100644
--- a/cobalt/black_box_tests/black_box_tests.py
+++ b/cobalt/black_box_tests/black_box_tests.py
@@ -63,6 +63,9 @@
     'allow_eval',
     'compression_test',
     'disable_eval_with_csp',
+    # http_cache is disabled due to flakiness. Planned update to make use of
+    # transferSize rather than load timings to make it more consistent.
+    # 'http_cache',
     'persistent_cookie',
     'soft_mic_platform_service_test',
     'web_debugger',
diff --git a/cobalt/black_box_tests/testdata/http_cache.html b/cobalt/black_box_tests/testdata/http_cache.html
new file mode 100644
index 0000000..cad147f
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/http_cache.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+
+<head>
+  <title>Cobalt HTTP Cache Test</title>
+  <script src="black_box_js_test_utils.js"></script>
+  <script>
+    var NUMBER_OF_CACHE_ELEMENTS = Number.MAX_SAFE_INTEGER;
+    var loadTimes = new Map();
+    var cacheSize = 0;
+
+    let url = window.location.search.substring(1);
+    let urlVars = url.split('&');
+    for (var i = 0; i < urlVars.length; i++) {
+      if (urlVars[i].includes('=')) {
+        var param = urlVars[i].split('=');
+        loadTimes.set(param[0], param[1]);
+      }
+    }
+
+    var initialLoadTime = loadTimes.has('initialLoadTime') ?
+      loadTimes.get('initialLoadTime') : new Date().getTime();
+    var reloadUrl = 'http_cache.html?initialLoadTime=' + initialLoadTime;
+
+    function measureLoadTime(element, initialTime) {
+      cacheSize++;
+      onLoadTime = new Date().getTime() - initialTime;
+      if (loadTimes.has(element)) {
+        // TODO: change to using transferSize once its implemented (b/231475964)
+        // Load times might give false positives, but should rarely give false
+        // negatives (i.e. this may pass when caching didn't actually occur,
+        // but will likely never fail when caching does occur).
+        console.log(element + ' first load time: ' + loadTimes.get(element));
+        console.log(element + ' second load time: ' + onLoadTime);
+        assertTrue(onLoadTime <= loadTimes.get(element));
+        if (cacheSize == NUMBER_OF_CACHE_ELEMENTS) {
+          onEndTest();
+        }
+      } else {
+        reloadUrl += '&' + element + '=' + onLoadTime;
+        if (cacheSize == NUMBER_OF_CACHE_ELEMENTS) {
+          setTimeout(() => { window.location.href = reloadUrl; }, 100);
+        }
+      }
+    }
+
+    // Add elements after window loads. In Cobalt, adding the onload property
+    // directly to an html tag doesn't execute.
+    window.onload = function () {
+      NUMBER_OF_CACHE_ELEMENTS = document.getElementsByTagName('div').length;
+      // Append the timestamp string to each resource url to force reload the
+      // files the first time the page is accessed each test run. The load time
+      // is recorded on the first run and passed to the second run to ensure
+      // the same url is used both times.
+      let timestampString = '?t=' + initialLoadTime;
+
+      let initialJsTime = new Date().getTime();
+      let script = document.createElement('script');
+      script.onload = () => {
+        measureLoadTime('javascript', initialJsTime);
+      };
+      script.onerror = () => {
+        notReached();
+      };
+      script.src = 'http_cache_test_resources/http_cache.js' + timestampString;
+      document.getElementById('js_div').appendChild(script);
+
+      let initialImgTime = new Date().getTime();
+      let image = document.createElement('img');
+      image.onload = () => {
+        measureLoadTime('image', initialImgTime);
+      };
+      image.onerror = (e) => {
+        notReached();
+      };
+      image.src = 'http_cache_test_resources/cobalt_logo.png' + timestampString;
+      image.alt = 'falied to load image';
+      document.getElementById('image_div').appendChild(image);
+
+      let initialCssTime = new Date().getTime();
+      let css = document.createElement('link');
+      css.onload = () => {
+        measureLoadTime('css', initialCssTime);
+      };
+      css.onerror = (e) => {
+        notReached();
+      };
+      css.rel = 'stylesheet';
+      css.href = 'http_cache_test_resources/http_cache.css' + timestampString;
+      document.getElementById('css_div').appendChild(css);
+    }
+
+  </script>
+</head>
+
+<body>
+  <h1>HTTP CACHE TEST</h1>
+  <div id="js_div">
+    <h2>Loading JS Script</h2>
+  </div>
+  <div id="image_div">
+    <h2>Loading image file (png)</h2>
+  </div>
+  <div id="css_div">
+    <h2>Loading CSS file</h2>
+  </div>
+</body>
diff --git a/cobalt/black_box_tests/testdata/http_cache_test_resources/cobalt_logo.png b/cobalt/black_box_tests/testdata/http_cache_test_resources/cobalt_logo.png
new file mode 100644
index 0000000..443a571
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/http_cache_test_resources/cobalt_logo.png
Binary files differ
diff --git a/cobalt/black_box_tests/testdata/http_cache_test_resources/http_cache.css b/cobalt/black_box_tests/testdata/http_cache_test_resources/http_cache.css
new file mode 100644
index 0000000..f951366
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/http_cache_test_resources/http_cache.css
@@ -0,0 +1,16 @@
+@font-face {
+  font-family: 'networkfont';
+  src: url('../test_font.ttf');
+}
+
+body {
+  background-color: white;
+  color: navy;
+  font-family: 'networkfont';
+}
+
+img {
+  width: 500px;
+  height: 500px;
+  background-color: black;
+}
diff --git a/cobalt/black_box_tests/testdata/http_cache_test_resources/http_cache.js b/cobalt/black_box_tests/testdata/http_cache_test_resources/http_cache.js
new file mode 100644
index 0000000..f02c922
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/http_cache_test_resources/http_cache.js
@@ -0,0 +1 @@
+console.log('loaded js file');
diff --git a/cobalt/black_box_tests/testdata/service_worker_test.html b/cobalt/black_box_tests/testdata/service_worker_test.html
index fc52dbd..4e90a28 100644
--- a/cobalt/black_box_tests/testdata/service_worker_test.html
+++ b/cobalt/black_box_tests/testdata/service_worker_test.html
@@ -27,10 +27,11 @@
 <body>
     <script>
         var message_event_count = 0;
-        console.log('running');
+        console.log('Starting tests');
         if ('serviceWorker' in navigator) {
             navigator.serviceWorker.register('..:..')
                 .then(function (registration) {
+                    console.log(`(Unexpected) : ${registration}`);
                     notReached();
                 }, function (error) {
                     console.log(`(Expected) Test invalid script URL: ${error}`);
@@ -39,6 +40,7 @@
                 });
             navigator.serviceWorker.register('arg:service_worker_test.js')
                 .then(function (registration) {
+                    console.log(`(Unexpected) : ${registration}`);
                     notReached();
                 }, function (error) {
                     console.log(`(Expected) Test script URL that is not http or https: ${error}`);
@@ -47,6 +49,7 @@
                 });
             navigator.serviceWorker.register('http:%2fservice_worker_test.js')
                 .then(function (registration) {
+                    console.log(`(Unexpected) : ${registration}`);
                     notReached();
                 }, function (error) {
                     console.log(`(Expected) Test script URL with escaped slash: ${error}`);
@@ -57,6 +60,7 @@
                 scope: '..:..',
             })
                 .then(function (registration) {
+                    console.log(`(Unexpected) : ${registration}`);
                     notReached();
                 }, function (error) {
                     console.log(`(Expected) Test invalid scope URL: ${error}`);
@@ -67,6 +71,7 @@
                 scope: 'arg:/',
             })
                 .then(function (registration) {
+                    console.log(`(Unexpected) : ${registration}`);
                     notReached();
                 }, function (error) {
                     console.log(`(Expected) Test scope URL that is not http or https: ${error}`);
@@ -77,6 +82,7 @@
                 scope: '/%5c',
             })
                 .then(function (registration) {
+                    console.log(`(Unexpected) : ${registration}`);
                     notReached();
                 }, function (error) {
                     console.log(`(Expected) Test scope URL with escaped slash: ${error}`);
@@ -89,6 +95,7 @@
                     scope: '/',
                 })
                     .then(function (registration) {
+                        console.log(`(Unexpected) : ${registration}`);
                         notReached();
                     }, function (error) {
                         console.log(`(Expected) Test Script URL is not trusted: ${error}`);
@@ -96,53 +103,91 @@
                         message_event_count += 1;
                     });
             }
+            navigator.serviceWorker.register('http://127.0.0.100:2345/')
+                .then(function (registration) {
+                    console.log(`(Unexpected) : ${registration}`);
+                    notReached();
+                }, function (error) {
+                    console.log(`(Expected) Test script URL with different origin: ${error}`);
+                    assertEqual("SecurityError: Service Worker Register failed: Script URL and referrer origin are not the same.", `${error}`);
+                    message_event_count += 1;
+                });
+
+            navigator.serviceWorker.register('service_worker_test.js', {
+                scope: 'http://127.0.0.100:2345/',
+            })
+                .then(function (registration) {
+                    console.log(`(Unexpected) : ${registration}`);
+                    notReached();
+                }, function (error) {
+                    console.log(`(Expected) Test scope URL with different origin: ${error}`);
+                    assertEqual("SecurityError: Service Worker Register failed: Scope URL and referrer origin are not the same.", `${error}`);
+                    message_event_count += 1;
+                });
+
+            // Finally, test a succeeding registration.
+            navigator.serviceWorker.register('service_worker_test.js', {
+                scope: '/registration/scope',
+            })
+                .then(function (registration) {
+                    console.log(`(Expected) Registration Succeeded: ${registration}`);
+                    assertNotEqual(null, registration);
+                    // The default value for RegistrationOptions.type is 'imports'.
+                    assertEqual('imports', registration.updateViaCache);
+                    assertTrue(registration.scope.endsWith('/registration/scope'));
+                    message_event_count += 1;
+
+                    registration.onupdatefound = function (event) {
+                        console.log("Got onupdatefound event", event.target.scope);
+                        assertTrue(event.target.scope.endsWith('/registration/scope'));
+                        message_event_count += 1;
+                    }
+                    assertNotEqual(null, registration.installing);
+                    assertEqual('installing', registration.installing.state);
+                    registration.installing.onstatechange = function (event) {
+                        console.log("Got onstatechange event: ", event.target.state);
+                        message_event_count += 1;
+                    }
+                    assertEqual(null, registration.waiting);
+                    assertEqual(null, registration.active);
+
+                    // Test getRegistration for a non-registered scope.
+                    navigator.serviceWorker.getRegistration('/bo/gus')
+                        .then(function (registration) {
+                            console.log(`(Expected) getRegistration Succeeded: ${registration}`);
+                            assertEqual(null, registration);
+                            message_event_count += 1;
+                        }, function (error) {
+                            console.log(`(Unexpected) : ${error}`);
+                            notReached();
+                        });
+
+                    // Finally, test getRegistration for a registered scope.
+                    navigator.serviceWorker.getRegistration('/registration/scope')
+                        .then(function (registration) {
+                            console.log(`(Expected) getRegistration Succeeded: ${registration}`);
+                            assertNotEqual(null, registration);
+                            assertEqual('imports', registration.updateViaCache);
+                            assertTrue(registration.scope.endsWith('/registration/scope'));
+                            message_event_count += 1;
+                        }, function (error) {
+                            console.log(`(Unexpected) : ${error}`);
+                            notReached();
+                        });
+
+                }, function (error) {
+                    console.log(`(Unexpected) : ${error}`);
+                    notReached();
+                });
         }
-        navigator.serviceWorker.register('http://127.0.0.100:2345/')
-            .then(function (registration) {
-                notReached();
-            }, function (error) {
-                console.log(`(Expected) Test script URL with different origin: ${error}`);
-                assertEqual("SecurityError: Service Worker Register failed: Script URL and referrer origin are not the same.", `${error}`);
-                message_event_count += 1;
-            });
 
-        navigator.serviceWorker.register('service_worker_test.js', {
-            scope: 'http://127.0.0.100:2345/',
-        })
-            .then(function (registration) {
-                notReached();
-            }, function (error) {
-                console.log(`(Expected) Test scope URL with different origin: ${error}`);
-                assertEqual("SecurityError: Service Worker Register failed: Scope URL and referrer origin are not the same.", `${error}`);
-                message_event_count += 1;
-            });
-
-        // Finally, test a succeeding registration.
-        navigator.serviceWorker.register('service_worker_test.js', {
-            scope: '/registration/scope',
-        })
-            .then(function (registration) {
-                console.log(`(Expected) Registration Succeeded: ${registration}`);
-                assertNotEqual(null, registration);
-                assertEqual(null, registration.installing);
-                assertEqual(null, registration.waiting);
-                assertEqual(null, registration.active);
-
-                // The default value for RegistrationOptions.type is 'imports'.
-                assertEqual('imports', registration.updateViaCache);
-                assertTrue(registration.scope.endsWith('/registration/scope'));
-                message_event_count += 1;
-            }, function (error) {
-                notReached();
-            });
-
+        console.log('Done starting tests');
         window.setTimeout(
             () => {
-                assertEqual(24, message_event_count);
                 console.log("Events: ", message_event_count)
+                assertEqual(29, message_event_count);
                 onEndTest();
             }, 500);
 
-        console.log('end');
     </script>
 </body>
diff --git a/cobalt/dom/blob_property_bag.idl b/cobalt/black_box_tests/testdata/service_worker_test.js
similarity index 76%
copy from cobalt/dom/blob_property_bag.idl
copy to cobalt/black_box_tests/testdata/service_worker_test.js
index 88f477f..044a770 100644
--- a/cobalt/dom/blob_property_bag.idl
+++ b/cobalt/black_box_tests/testdata/service_worker_test.js
@@ -1,4 +1,4 @@
-// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+// Copyright 2022 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.
@@ -12,7 +12,5 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// https://www.w3.org/TR/FileAPI/#dfn-BlobPropertyBag
-dictionary BlobPropertyBag {
-  DOMString type = "";
-};
\ No newline at end of file
+console.log('foo');
+this.oninstall = function(e) { console.log('bar', e); }
diff --git a/cobalt/black_box_tests/testdata/web_worker_test.html b/cobalt/black_box_tests/testdata/web_worker_test.html
index 9839984..f69537b 100644
--- a/cobalt/black_box_tests/testdata/web_worker_test.html
+++ b/cobalt/black_box_tests/testdata/web_worker_test.html
@@ -56,10 +56,10 @@
         console.log('window got onerror');
         notReached();
     };
-    console.log('posting');
+    console.log('posting', data);
     worker.postMessage(data);
     window.setTimeout(
-        () => { console.log('posting'); worker.postMessage(data); }, 250);
+        () => { console.log('posting', data); worker.postMessage(data); }, 250);
     console.log('end');
 </script>
 </body>
diff --git a/cobalt/black_box_tests/testdata/web_worker_test.js b/cobalt/black_box_tests/testdata/web_worker_test.js
index 9d84a22..c330f2e 100644
--- a/cobalt/black_box_tests/testdata/web_worker_test.js
+++ b/cobalt/black_box_tests/testdata/web_worker_test.js
@@ -12,15 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-var data = "worker data";
+const data = "worker data";
 this.postMessage(data);
 this.onmessage = function (event) {
+    let message = 'worker received wrong message';
     if (event.data === 'window data') {
-        this.postMessage('worker received correct message');
-    } else {
-        this.postMessage('worker received wrong message')
+        message = 'worker received correct message';
     }
-    var data = event.data.toUpperCase();
-    this.postMessage(data);
+    console.log(message);
+    this.postMessage(message);
+    const uppercase_data = event.data.toUpperCase();
+    this.postMessage(uppercase_data);
 };
 this
diff --git a/cobalt/black_box_tests/tests/http_cache.py b/cobalt/black_box_tests/tests/http_cache.py
new file mode 100644
index 0000000..3b24fc5
--- /dev/null
+++ b/cobalt/black_box_tests/tests/http_cache.py
@@ -0,0 +1,53 @@
+# Copyright 2022 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests if Cobalt properly caches resources that were previously loaded."""
+
+import os
+from six.moves import SimpleHTTPServer
+from six.moves.urllib.parse import urlparse
+import time
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import MakeRequestHandlerClass
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+
+# The base path of the requested assets is the parent directory.
+_SERVER_ROOT_PATH = os.path.join(os.path.dirname(__file__), os.pardir)
+
+
+class DelayedHttpRequestHandler(MakeRequestHandlerClass(_SERVER_ROOT_PATH)):
+  """Handles HTTP requests but adds a delay before serving a response."""
+
+  def do_GET(self):  # pylint: disable=invalid-name
+    """Handles HTTP GET requests for resources."""
+
+    parsed_path = urlparse(self.path)
+
+    if parsed_path.path.startswith('/testdata/http_cache_test_resources/'):
+      time.sleep(10)
+
+    return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
+
+
+class HttpCacheTest(black_box_tests.BlackBoxTestCase):
+  """Load resources, then reload the page and verify."""
+
+  def test_simple(self):
+
+    with ThreadedWebServer(
+        binding_address=self.GetBindingAddress(),
+        handler=DelayedHttpRequestHandler) as server:
+      url = server.GetURL(file_name='testdata/http_cache.html')
+      with self.CreateCobaltRunner(url=url) as runner:
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn
index 899f2c5..de6c2b7 100644
--- a/cobalt/browser/BUILD.gn
+++ b/cobalt/browser/BUILD.gn
@@ -143,6 +143,8 @@
   deps = [
     ":bindings",
     ":browser_switches",
+    ":generated_bindings",
+    ":generated_types",
     "//cobalt/account",
     "//cobalt/audio",
     "//cobalt/base",
diff --git a/cobalt/browser/application.cc b/cobalt/browser/application.cc
index 9cd3339..9d64007 100644
--- a/cobalt/browser/application.cc
+++ b/cobalt/browser/application.cc
@@ -804,7 +804,7 @@
 
   network_module_options.https_requirement = security_flags.https_requirement;
   options.web_module_options.require_csp = security_flags.csp_header_policy;
-  options.web_module_options.csp_enforcement_mode = dom::kCspEnforcementEnable;
+  options.web_module_options.csp_enforcement_mode = web::kCspEnforcementEnable;
 
   options.requested_viewport_size = requested_viewport_size;
 
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc
index 75e3351..9028c80 100644
--- a/cobalt/browser/browser_module.cc
+++ b/cobalt/browser/browser_module.cc
@@ -40,19 +40,19 @@
 #include "cobalt/browser/switches.h"
 #include "cobalt/configuration/configuration.h"
 #include "cobalt/cssom/viewport_size.h"
-#include "cobalt/dom/csp_delegate_factory.h"
 #include "cobalt/dom/input_event_init.h"
 #include "cobalt/dom/keyboard_event_init.h"
 #include "cobalt/dom/keycode.h"
 #include "cobalt/dom/mutation_observer_task_manager.h"
 #include "cobalt/dom/navigator.h"
-#include "cobalt/dom/navigator_ua_data.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/extension/graphics.h"
 #include "cobalt/h5vcc/h5vcc.h"
 #include "cobalt/input/input_device_manager_fuzzer.h"
 #include "cobalt/math/matrix3_f.h"
 #include "cobalt/overlay_info/overlay_info_registry.h"
+#include "cobalt/web/csp_delegate_factory.h"
+#include "cobalt/web/navigator_ua_data.h"
 #include "nb/memory_scope.h"
 #include "starboard/atomic.h"
 #include "starboard/common/string.h"
@@ -463,6 +463,9 @@
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
   SbCoreDumpUnregisterHandler(BrowserModule::CoreDumpHandler, this);
 #endif
+
+  // Make sure the WebModule is destroyed before the ServiceWorkerRegistry
+  web_module_.reset();
 }
 
 void BrowserModule::Navigate(const GURL& url_reference) {
@@ -561,7 +564,7 @@
 // Create new WebModule.
 #if !defined(COBALT_FORCE_CSP)
   options_.web_module_options.csp_insecure_allowed_token =
-      dom::CspDelegateFactory::GetInsecureAllowedToken();
+      web::CspDelegateFactory::GetInsecureAllowedToken();
 #endif
   WebModule::Options options(options_.web_module_options);
   options.splash_screen_cache = splash_screen_cache_.get();
@@ -610,6 +613,8 @@
       base::Bind(&BrowserModule::OnMaybeFreeze, base::Unretained(this));
 
   options.web_options.network_module = network_module_;
+  options.web_options.service_worker_jobs =
+      service_worker_registry_.service_worker_jobs();
 
   web_module_.reset(new WebModule(
       url, application_state_,
@@ -619,8 +624,7 @@
       base::Bind(&BrowserModule::OnWindowClose, base::Unretained(this)),
       base::Bind(&BrowserModule::OnWindowMinimize, base::Unretained(this)),
       can_play_type_handler_.get(), media_module_.get(), viewport_size,
-      GetResourceProvider(), kLayoutMaxRefreshFrequencyInHz,
-      service_worker_registry_.service_worker_jobs(), options));
+      GetResourceProvider(), kLayoutMaxRefreshFrequencyInHz, options));
   lifecycle_observers_.AddObserver(web_module_.get());
 
   if (system_window_) {
@@ -1428,6 +1432,12 @@
   DCHECK(application_state_ == base::kApplicationStateStarted);
   application_state_ = base::kApplicationStateBlurred;
   FOR_EACH_OBSERVER(LifecycleObserver, lifecycle_observers_, Blur(timestamp));
+
+  // The window is about to lose focus, and may be destroyed.
+  if (media_module_) {
+    DCHECK(system_window_);
+    window_size_ = system_window_->GetWindowSize();
+  }
 }
 
 void BrowserModule::Conceal(SbTimeMonotonic timestamp) {
@@ -1721,10 +1731,8 @@
 
   ResetResources();
 
-  // Suspend media module and update system window and resource provider.
+  // Suspend media module and update resource provider.
   if (media_module_) {
-    DCHECK(system_window_);
-    window_size_ = system_window_->GetWindowSize();
 #if SB_API_VERSION >= 13
     // This needs to be done before destroying the renderer module as it
     // may use the renderer module to release assets during the update.
diff --git a/cobalt/browser/debug_console.cc b/cobalt/browser/debug_console.cc
index 2414afb..41bf29a 100644
--- a/cobalt/browser/debug_console.cc
+++ b/cobalt/browser/debug_console.cc
@@ -21,10 +21,10 @@
 #include "cobalt/base/cobalt_paths.h"
 #include "cobalt/base/source_location.h"
 #include "cobalt/browser/switches.h"
-#include "cobalt/dom/csp_delegate_factory.h"
 #include "cobalt/dom/mutation_observer_task_manager.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/web/context.h"
+#include "cobalt/web/csp_delegate_factory.h"
 
 namespace cobalt {
 namespace browser {
@@ -119,9 +119,9 @@
   web_module_options.image_cache_capacity = 0;
   // Disable CSP for the Debugger's WebModule. This will also allow eval() in
   // javascript.
-  web_module_options.csp_enforcement_mode = dom::kCspEnforcementDisable;
+  web_module_options.csp_enforcement_mode = web::kCspEnforcementDisable;
   web_module_options.csp_insecure_allowed_token =
-      dom::CspDelegateFactory::GetInsecureAllowedToken();
+      web::CspDelegateFactory::GetInsecureAllowedToken();
 
   // Since the debug console is intended to be overlaid on top of the main
   // web module contents, make sure blending is enabled for its background.
@@ -148,7 +148,7 @@
                     base::Closure(),            /* window_minimize_callback */
                     NULL /* can_play_type_handler */, NULL /* media_module */,
                     window_dimensions, resource_provider, layout_refresh_rate,
-                    NULL /* service_worker_jobs */, web_module_options));
+                    web_module_options));
 }
 
 DebugConsole::~DebugConsole() {}
diff --git a/cobalt/browser/idl_files.gni b/cobalt/browser/idl_files.gni
index 9f9b2aa..296d9ac 100644
--- a/cobalt/browser/idl_files.gni
+++ b/cobalt/browser/idl_files.gni
@@ -44,21 +44,17 @@
   "//cobalt/dom/attr.idl",
   "//cobalt/dom/audio_track.idl",
   "//cobalt/dom/audio_track_list.idl",
-  "//cobalt/dom/blob.idl",
   "//cobalt/dom/c_val_view.idl",
   "//cobalt/dom/camera_3d.idl",
   "//cobalt/dom/captions/system_caption_settings.idl",
   "//cobalt/dom/cdata_section.idl",
   "//cobalt/dom/character_data.idl",
-  "//cobalt/dom/cobalt_ua_data_values_interface.idl",
   "//cobalt/dom/comment.idl",
   "//cobalt/dom/crypto.idl",
-  "//cobalt/dom/custom_event.idl",
   "//cobalt/dom/device_orientation_event.idl",
   "//cobalt/dom/document.idl",
   "//cobalt/dom/document_timeline.idl",
   "//cobalt/dom/document_type.idl",
-  "//cobalt/dom/dom_exception.idl",
   "//cobalt/dom/dom_implementation.idl",
   "//cobalt/dom/dom_parser.idl",
   "//cobalt/dom/dom_rect.idl",
@@ -73,10 +69,6 @@
   "//cobalt/dom/eme/media_key_status_map.idl",
   "//cobalt/dom/eme/media_key_system_access.idl",
   "//cobalt/dom/eme/media_keys.idl",
-  "//cobalt/dom/error_event.idl",
-  "//cobalt/dom/event.idl",
-  "//cobalt/dom/event_listener.idl",
-  "//cobalt/dom/event_target.idl",
   "//cobalt/dom/focus_event.idl",
   "//cobalt/dom/history.idl",
   "//cobalt/dom/html_anchor_element.idl",
@@ -118,10 +110,8 @@
   "//cobalt/dom/mutation_record.idl",
   "//cobalt/dom/named_node_map.idl",
   "//cobalt/dom/navigator.idl",
-  "//cobalt/dom/navigator_ua_data.idl",
   "//cobalt/dom/node.idl",
   "//cobalt/dom/node_list.idl",
-  "//cobalt/dom/on_error_event_listener.idl",
   "//cobalt/dom/on_screen_keyboard.idl",
   "//cobalt/dom/performance.idl",
   "//cobalt/dom/performance_entry.idl",
@@ -138,7 +128,6 @@
   "//cobalt/dom/progress_event.idl",
   "//cobalt/dom/screen.idl",
   "//cobalt/dom/screenshot.idl",
-  "//cobalt/dom/security_policy_violation_event.idl",
   "//cobalt/dom/source_buffer.idl",
   "//cobalt/dom/source_buffer_list.idl",
   "//cobalt/dom/storage.idl",
@@ -151,7 +140,6 @@
   "//cobalt/dom/track_event.idl",
   "//cobalt/dom/transition_event.idl",
   "//cobalt/dom/ui_event.idl",
-  "//cobalt/dom/url.idl",
   "//cobalt/dom/video_playback_quality.idl",
   "//cobalt/dom/video_track.idl",
   "//cobalt/dom/video_track_list.idl",
@@ -214,6 +202,19 @@
   "//cobalt/subtlecrypto/crypto_key.idl",
   "//cobalt/subtlecrypto/subtle_crypto.idl",
 
+  "//cobalt/web/blob.idl",
+  "//cobalt/web/cobalt_ua_data_values_interface.idl",
+  "//cobalt/web/custom_event.idl",
+  "//cobalt/web/dom_exception.idl",
+  "//cobalt/web/error_event.idl",
+  "//cobalt/web/event.idl",
+  "//cobalt/web/event_listener.idl",
+  "//cobalt/web/event_target.idl",
+  "//cobalt/web/navigator_ua_data.idl",
+  "//cobalt/web/on_error_event_listener.idl",
+  "//cobalt/web/security_policy_violation_event.idl",
+  "//cobalt/web/url.idl",
+
   "//cobalt/web_animations/animatable.idl",
   "//cobalt/web_animations/animation.idl",
   "//cobalt/web_animations/animation_effect_read_only.idl",
@@ -234,9 +235,12 @@
   "//cobalt/worker/navigation_preload_manager.idl",
   "//cobalt/worker/service_worker.idl",
   "//cobalt/worker/service_worker_container.idl",
+  "//cobalt/worker/service_worker_global_scope.idl",
   "//cobalt/worker/service_worker_registration.idl",
   "//cobalt/worker/worker.idl",
   "//cobalt/worker/worker_global_scope.idl",
+  "//cobalt/worker/worker_location.idl",
+  "//cobalt/worker/worker_navigator.idl",
 
   "//cobalt/xhr/xml_http_request.idl",
   "//cobalt/xhr/xml_http_request_event_target.idl",
@@ -259,15 +263,12 @@
   "//cobalt/audio/audio_node_channel_interpretation.idl",
   "//cobalt/debug/console/console_command.idl",
   "//cobalt/debug/console/debug_console_mode.idl",
-  "//cobalt/dom/blob_property_bag.idl",
   "//cobalt/dom/captions/caption_character_edge_style.idl",
   "//cobalt/dom/captions/caption_color.idl",
   "//cobalt/dom/captions/caption_font_family.idl",
   "//cobalt/dom/captions/caption_font_size_percentage.idl",
   "//cobalt/dom/captions/caption_opacity_percentage.idl",
   "//cobalt/dom/captions/caption_state.idl",
-  "//cobalt/dom/cobalt_ua_data_values.idl",
-  "//cobalt/dom/custom_event_init.idl",
   "//cobalt/dom/device_orientation_event_init.idl",
   "//cobalt/dom/document_ready_state.idl",
   "//cobalt/dom/dom_parser_supported_type.idl",
@@ -279,8 +280,6 @@
   "//cobalt/dom/eme/media_key_system_configuration.idl",
   "//cobalt/dom/eme/media_key_system_media_capability.idl",
   "//cobalt/dom/eme/media_keys_requirement.idl",
-  "//cobalt/dom/error_event_init.idl",
-  "//cobalt/dom/event_init.idl",
   "//cobalt/dom/event_modifier_init.idl",
   "//cobalt/dom/focus_event_init.idl",
   "//cobalt/dom/input_event_init.idl",
@@ -293,14 +292,11 @@
   "//cobalt/dom/mouse_event_init.idl",
   "//cobalt/dom/mutation_observer_init.idl",
   "//cobalt/dom/navigation_type.idl",
-  "//cobalt/dom/navigator_ua_brand_version.idl",
   "//cobalt/dom/performance_observer_callback_options.idl",
   "//cobalt/dom/performance_observer_init.idl",
   "//cobalt/dom/pointer_event_init.idl",
   "//cobalt/dom/source_buffer_append_mode.idl",
   "//cobalt/dom/track_default_type.idl",
-  "//cobalt/dom/ua_data_values.idl",
-  "//cobalt/dom/ua_low_entropy_json.idl",
   "//cobalt/dom/ui_event_init.idl",
   "//cobalt/dom/visibility_state.idl",
   "//cobalt/dom/wheel_event_init.idl",
@@ -308,6 +304,8 @@
   "//cobalt/encoding/text_decoder_options.idl",
   "//cobalt/encoding/text_encoder_encode_into_result.idl",
   "//cobalt/h5vcc/h5vcc_crash_type.idl",
+  "//cobalt/h5vcc/h5vcc_storage_write_test_dictionary.idl",
+  "//cobalt/h5vcc/h5vcc_storage_verify_test_dictionary.idl",
   "//cobalt/h5vcc/watchdog_state.idl",
   "//cobalt/h5vcc/watchdog_replace.idl",
   "//cobalt/media_capture/blob_event_init.idl",
@@ -330,6 +328,14 @@
   "//cobalt/subtlecrypto/key_format.idl",
   "//cobalt/subtlecrypto/key_type.idl",
   "//cobalt/subtlecrypto/key_usage.idl",
+  "//cobalt/web/blob_property_bag.idl",
+  "//cobalt/web/cobalt_ua_data_values.idl",
+  "//cobalt/web/custom_event_init.idl",
+  "//cobalt/web/error_event_init.idl",
+  "//cobalt/web/event_init.idl",
+  "//cobalt/web/navigator_ua_brand_version.idl",
+  "//cobalt/web/ua_data_values.idl",
+  "//cobalt/web/ua_low_entropy_json.idl",
   "//cobalt/web_animations/animation_fill_mode.idl",
   "//cobalt/web_animations/animation_playback_direction.idl",
   "//cobalt/websocket/close_event_init.idl",
@@ -349,7 +355,6 @@
 dependency_idl_files = [
   "//cobalt/cssom/link_style.idl",
 
-  "//cobalt/dom/buffer_source.idl",
   "//cobalt/dom/captions/navigator_system_caption_settings.idl",
   "//cobalt/dom/document_cobalt.idl",
   "//cobalt/dom/document_cssom.idl",
@@ -367,13 +372,9 @@
   "//cobalt/dom/global_event_handlers.idl",
   "//cobalt/dom/html_element_cssom_view.idl",
   "//cobalt/dom/mouse_event_cssom_view.idl",
-  "//cobalt/dom/navigator_id.idl",
-  "//cobalt/dom/navigator_language.idl",
   "//cobalt/dom/navigator_licenses.idl",
-  "//cobalt/dom/navigator_online.idl",
   "//cobalt/dom/navigator_plugins.idl",
   "//cobalt/dom/navigator_storage_utils.idl",
-  "//cobalt/dom/navigator_ua.idl",
   "//cobalt/dom/non_document_type_child_node.idl",
   "//cobalt/dom/non_element_parent_node.idl",
   "//cobalt/dom/parent_node.idl",
@@ -381,7 +382,6 @@
   "//cobalt/dom/performance_high_resolution_time.idl",
   "//cobalt/dom/speech_synthesis_getter.idl",
   "//cobalt/dom/url_mse.idl",
-  "//cobalt/dom/url_utils.idl",
   "//cobalt/dom/window_animation_timing.idl",
   "//cobalt/dom/window_cssom.idl",
   "//cobalt/dom/window_cssom_view.idl",
@@ -393,6 +393,12 @@
   "//cobalt/dom/window_timers.idl",
   "//cobalt/media_capture/navigator.idl",
   "//cobalt/media_session/navigator_media_session.idl",
+  "//cobalt/web/buffer_source.idl",
+  "//cobalt/web/navigator_id.idl",
+  "//cobalt/web/navigator_language.idl",
+  "//cobalt/web/navigator_online.idl",
+  "//cobalt/web/navigator_ua.idl",
+  "//cobalt/web/url_utils.idl",
   "//cobalt/worker/abstract_worker.idl",
   "//cobalt/worker/navigator.idl",
 ]
diff --git a/cobalt/browser/service_worker_registry.cc b/cobalt/browser/service_worker_registry.cc
index 71a3a4f..c0a4e00 100644
--- a/cobalt/browser/service_worker_registry.cc
+++ b/cobalt/browser/service_worker_registry.cc
@@ -33,7 +33,7 @@
 }  // namespace
 
 void ServiceWorkerRegistry::WillDestroyCurrentMessageLoop() {
-  // TODO clear all member variables allocated form the thread.
+  // Clear all member variables allocated form the thread.
   service_worker_jobs_.reset();
 }
 
diff --git a/cobalt/browser/splash_screen.cc b/cobalt/browser/splash_screen.cc
index bd642dc..3173ccd 100644
--- a/cobalt/browser/splash_screen.cc
+++ b/cobalt/browser/splash_screen.cc
@@ -109,7 +109,7 @@
       base::Closure(),  // window_minimize_callback
       NULL /* can_play_type_handler */, NULL /* media_module */,
       window_dimensions, resource_provider, layout_refresh_rate,
-      NULL /* service_worker_jobs */, web_module_options));
+      web_module_options));
 }
 
 SplashScreen::~SplashScreen() {
diff --git a/cobalt/browser/switches.cc b/cobalt/browser/switches.cc
index 0312eaf..d9e9f90 100644
--- a/cobalt/browser/switches.cc
+++ b/cobalt/browser/switches.cc
@@ -80,6 +80,10 @@
     "will disable any cenc and cbcs DRM playbacks. Accepted values: \"cenc\", "
     "\"cbcs\", \"cbcs-1-9\".";
 
+const char kDisableOnScreenKeyboard[] = "disable_on_screen_keyboard";
+const char kDisableOnScreenKeyboardHelp[] =
+    "Disable the on screen keyboard for testing.";
+
 const char kDisableRasterizerCaching[] = "disable_rasterizer_caching";
 const char kDisableRasterizerCachingHelp[] =
     "Disables caching of rasterized render tree nodes; caching improves "
@@ -208,14 +212,16 @@
     "speech synthesis API. If the platform doesn't have speech synthesis, "
     "TTSLogger will be used instead.";
 
+const char kWatchdog[] = "watchdog";
+const char kWatchdogHelp[] =
+    "Watchdog debug delay settings that correspond to "
+    "delay_name_,delay_wait_time_microseconds_,delay_sleep_time_microseconds_ "
+    "separated by commas (ex. \"renderer,100000,1000\").";
+
 const char kWebDriverPort[] = "webdriver_port";
 const char kWebDriverPortHelp[] =
     "Port that the WebDriver server should be listening on.";
 
-const char kDisableOnScreenKeyboard[] = "disable_on_screen_keyboard";
-const char kDisableOnScreenKeyboardHelp[] =
-    "Disable the on screen keyboard for testing.";
-
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
 const char kCompressUpdate[] = "compress_update";
@@ -439,6 +445,7 @@
         {kForceDeterministicRendering, kForceDeterministicRenderingHelp},
         {kDisableMediaCodecs, kDisableMediaCodecsHelp},
         {kDisableMediaEncryptionSchemes, kDisableMediaEncryptionSchemesHelp},
+        {kDisableOnScreenKeyboard, kDisableOnScreenKeyboardHelp},
         {kDisableRasterizerCaching, kDisableRasterizerCachingHelp},
         {kDisableSignIn, kDisableSignInHelp},
         {kDisableSplashScreenOnReloads, kDisableSplashScreenOnReloadsHelp},
@@ -457,8 +464,8 @@
         {kUserAgent, kUserAgentHelp},
         {kUserAgentClientHints, kUserAgentClientHintsHelp},
         {kUserAgentOsNameVersion, kUserAgentOsNameVersionHelp},
-        {kUseTTS, kUseTTSHelp}, {kWebDriverPort, kWebDriverPortHelp},
-        {kDisableOnScreenKeyboard, kDisableOnScreenKeyboardHelp},
+        {kUseTTS, kUseTTSHelp}, {kWatchdog, kWatchdogHelp},
+        {kWebDriverPort, kWebDriverPortHelp},
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
         {kCompressUpdate, kCompressUpdateHelp},
         {kDisableJavaScriptJit, kDisableJavaScriptJitHelp},
diff --git a/cobalt/browser/switches.h b/cobalt/browser/switches.h
index 8348964..166ddd9 100644
--- a/cobalt/browser/switches.h
+++ b/cobalt/browser/switches.h
@@ -43,6 +43,8 @@
 extern const char kDisableMediaCodecsHelp[];
 extern const char kDisableMediaEncryptionSchemes[];
 extern const char kDisableMediaEncryptionSchemesHelp[];
+extern const char kDisableOnScreenKeyboard[];
+extern const char kDisableOnScreenKeyboardHelp[];
 extern const char kDisableRasterizerCaching[];
 extern const char kDisableSignIn[];
 extern const char kDisableSignInHelp[];
@@ -84,10 +86,10 @@
 extern const char kUserAgentOsNameVersionHelp[];
 extern const char kUseTTS[];
 extern const char kUseTTSHelp[];
+extern const char kWatchdog[];
+extern const char kWatchdogHelp[];
 extern const char kWebDriverPort[];
 extern const char kWebDriverPortHelp[];
-extern const char kDisableOnScreenKeyboard[];
-extern const char kDisableOnScreenKeyboardHelp[];
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
 extern const char kCompressUpdate[];
diff --git a/cobalt/browser/user_agent_platform_info.cc b/cobalt/browser/user_agent_platform_info.cc
index a862c3c..1264c06 100644
--- a/cobalt/browser/user_agent_platform_info.cc
+++ b/cobalt/browser/user_agent_platform_info.cc
@@ -108,7 +108,7 @@
 
 struct DeviceTypeName {
   SbSystemDeviceType device_type;
-  char device_type_string[8];
+  char device_type_string[10];
 };
 
 const DeviceTypeName kDeviceTypeStrings[] = {
@@ -119,6 +119,9 @@
     {kSbSystemDeviceTypeTV, "TV"},
     {kSbSystemDeviceTypeAndroidTV, "ATV"},
     {kSbSystemDeviceTypeDesktopPC, "DESKTOP"},
+#if SB_API_VERSION >= 14
+    {kSbSystemDeviceTypeVideoProjector, "PROJECTOR"},
+#endif  // SB_API_VERSION >= 14
     {kSbSystemDeviceTypeUnknown, "UNKNOWN"}};
 
 std::string CreateDeviceTypeString(SbSystemDeviceType device_type) {
diff --git a/cobalt/browser/user_agent_platform_info.h b/cobalt/browser/user_agent_platform_info.h
index 7922671..3c13ea7 100644
--- a/cobalt/browser/user_agent_platform_info.h
+++ b/cobalt/browser/user_agent_platform_info.h
@@ -18,7 +18,7 @@
 #include <map>
 #include <string>
 
-#include "cobalt/dom/user_agent_platform_info.h"
+#include "cobalt/web/user_agent_platform_info.h"
 
 namespace cobalt {
 namespace browser {
@@ -27,7 +27,7 @@
     const std::string& user_agent_input,
     std::map<std::string, std::string>& user_agent_input_map);
 
-class UserAgentPlatformInfo : public dom::UserAgentPlatformInfo {
+class UserAgentPlatformInfo : public web::UserAgentPlatformInfo {
  public:
   UserAgentPlatformInfo();
   ~UserAgentPlatformInfo() override{};
diff --git a/cobalt/browser/web_module.cc b/cobalt/browser/web_module.cc
index 0da0b70..56fef29 100644
--- a/cobalt/browser/web_module.cc
+++ b/cobalt/browser/web_module.cc
@@ -40,10 +40,7 @@
 #include "cobalt/browser/web_module_stat_tracker.h"
 #include "cobalt/configuration/configuration.h"
 #include "cobalt/css_parser/parser.h"
-#include "cobalt/dom/blob.h"
-#include "cobalt/dom/csp_delegate_factory.h"
 #include "cobalt/dom/element.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/html_script_element.h"
 #include "cobalt/dom/input_event.h"
@@ -57,7 +54,6 @@
 #include "cobalt/dom/pointer_event.h"
 #include "cobalt/dom/storage.h"
 #include "cobalt/dom/ui_event.h"
-#include "cobalt/dom/url.h"
 #include "cobalt/dom/visibility_state.h"
 #include "cobalt/dom/wheel_event.h"
 #include "cobalt/dom/window.h"
@@ -70,8 +66,12 @@
 #include "cobalt/media/media_module.h"
 #include "cobalt/media_session/media_session_client.h"
 #include "cobalt/storage/storage_manager.h"
+#include "cobalt/web/blob.h"
 #include "cobalt/web/context.h"
+#include "cobalt/web/csp_delegate_factory.h"
 #include "cobalt/web/environment_settings.h"
+#include "cobalt/web/event.h"
+#include "cobalt/web/url.h"
 #include "starboard/accessibility.h"
 #include "starboard/common/log.h"
 #include "starboard/gles.h"
@@ -323,7 +323,7 @@
 
   // Inject the DOM event object into the window or the element.
   void InjectInputEvent(scoped_refptr<dom::Element> element,
-                        const scoped_refptr<dom::Event>& event);
+                        const scoped_refptr<web::Event>& event);
 
   // Handle queued pointer events. Called by LayoutManager on_layout callback.
   void HandlePointerEvents();
@@ -331,8 +331,8 @@
   // Initializes the ResourceProvider and dependent resources.
   void SetResourceProvider(render_tree::ResourceProvider* resource_provider);
 
-  void OnStartDispatchEvent(const scoped_refptr<dom::Event>& event);
-  void OnStopDispatchEvent(const scoped_refptr<dom::Event>& event);
+  void OnStartDispatchEvent(const scoped_refptr<web::Event>& event);
+  void OnStopDispatchEvent(const scoped_refptr<web::Event>& event);
 
   // Thread checker ensures all calls to the WebModule are made from the same
   // thread that it is created in.
@@ -586,7 +586,7 @@
   web_context_->setup_environment_settings(new dom::DOMSettings(
       debugger_hooks_, kDOMMaxElementDepth, media_source_registry_.get(),
       data.can_play_type_handler, memory_info, &mutation_observer_task_manager_,
-      data.service_worker_jobs, data.options.dom_settings_options));
+      data.options.dom_settings_options));
   DCHECK(web_context_->environment_settings());
 
   system_caption_settings_ = new cobalt::dom::captions::SystemCaptionSettings(
@@ -673,6 +673,13 @@
 
   web_context_->global_environment()->CreateGlobalObject(
       window_, web_context_->environment_settings());
+  DCHECK(web_context_->GetWindowOrWorkerGlobalScope()->IsWindow());
+  DCHECK(!web_context_->GetWindowOrWorkerGlobalScope()->IsDedicatedWorker());
+  DCHECK(!web_context_->GetWindowOrWorkerGlobalScope()->IsServiceWorker());
+  DCHECK(web_context_->GetWindowOrWorkerGlobalScope()->GetWrappableType() ==
+         base::GetTypeId<dom::Window>());
+  DCHECK_EQ(window_, base::polymorphic_downcast<dom::Window*>(
+                         web_context_->GetWindowOrWorkerGlobalScope()));
 
   render_tree_produced_callback_ = data.render_tree_produced_callback;
   DCHECK(!render_tree_produced_callback_.is_null());
@@ -700,7 +707,7 @@
   DCHECK(layout_manager_);
 
 #if !defined(COBALT_FORCE_CSP)
-  if (data.options.csp_enforcement_mode == dom::kCspEnforcementDisable) {
+  if (data.options.csp_enforcement_mode == web::kCspEnforcementDisable) {
     // If CSP is disabled, enable eval(). Otherwise, it will be enabled by
     // a CSP directive.
     web_context_->global_environment()->EnableEval();
@@ -708,7 +715,7 @@
 #endif
 
   web_context_->global_environment()->SetReportEvalCallback(
-      base::Bind(&dom::CspDelegate::ReportEval,
+      base::Bind(&web::CspDelegate::ReportEval,
                  base::Unretained(window_->document()->csp_delegate())));
 
   web_context_->global_environment()->SetReportErrorCallback(
@@ -744,7 +751,7 @@
   // Collect document's unload event start time.
   base::TimeTicks unload_event_start_time = base::TimeTicks::Now();
 
-  window_->DispatchEvent(new dom::Event(base::Tokens::unload()));
+  window_->DispatchEvent(new web::Event(base::Tokens::unload()));
 
   // Collect document's unload event end time.
   base::TimeTicks unload_event_end_time = base::TimeTicks::Now();
@@ -788,7 +795,7 @@
 }
 
 void WebModule::Impl::InjectInputEvent(scoped_refptr<dom::Element> element,
-                                       const scoped_refptr<dom::Event>& event) {
+                                       const scoped_refptr<web::Event>& event) {
   TRACE_EVENT1("cobalt::browser", "WebModule::Impl::InjectInputEvent()",
                "event", TRACE_STR_COPY(event->type().c_str()));
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -1120,12 +1127,12 @@
 }
 
 void WebModule::Impl::OnStartDispatchEvent(
-    const scoped_refptr<dom::Event>& event) {
+    const scoped_refptr<web::Event>& event) {
   web_module_stat_tracker_->OnStartDispatchEvent(event);
 }
 
 void WebModule::Impl::OnStopDispatchEvent(
-    const scoped_refptr<dom::Event>& event) {
+    const scoped_refptr<web::Event>& event) {
   web_module_stat_tracker_->OnStopDispatchEvent(
       event, window_->HasPendingAnimationFrameCallbacks(),
       layout_manager_->IsRenderTreePending());
@@ -1257,7 +1264,7 @@
 void WebModule::Impl::InjectBeforeUnloadEvent() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (window_ && window_->HasEventListener(base::Tokens::beforeunload())) {
-    window_->DispatchEvent(new dom::Event(base::Tokens::beforeunload()));
+    window_->DispatchEvent(new web::Event(base::Tokens::beforeunload()));
   } else if (!on_before_unload_fired_but_not_handled_.is_null()) {
     on_before_unload_fired_but_not_handled_.Run();
   }
@@ -1298,7 +1305,7 @@
 void WebModule::Impl::HandlePointerEvents() {
   TRACE_EVENT0("cobalt::browser", "WebModule::Impl::HandlePointerEvents");
   const scoped_refptr<dom::Document>& document = window_->document();
-  scoped_refptr<dom::Event> event;
+  scoped_refptr<web::Event> event;
   do {
     event = document->pointer_state()->GetNextQueuedPointerEvent();
     if (event) {
@@ -1330,7 +1337,7 @@
     media::CanPlayTypeHandler* can_play_type_handler,
     media::MediaModule* media_module, const ViewportSize& window_dimensions,
     render_tree::ResourceProvider* resource_provider, float layout_refresh_rate,
-    worker::ServiceWorkerJobs* service_worker_jobs, const Options& options)
+    const Options& options)
     : ui_nav_root_(new ui_navigation::NavItem(
           ui_navigation::kNativeItemTypeContainer,
           // Currently, events do not need to be processed for the root item.
@@ -1343,7 +1350,7 @@
 #if defined(ENABLE_DEBUGGER)
       &waiting_for_web_debugger_,
 #endif  // defined(ENABLE_DEBUGGER)
-      &synchronous_loader_interrupt_, service_worker_jobs, options);
+      &synchronous_loader_interrupt_, options);
 
   web_agent_.reset(
       new web::Agent(options.web_options,
diff --git a/cobalt/browser/web_module.h b/cobalt/browser/web_module.h
index 4c42f19..d1cb1c2 100644
--- a/cobalt/browser/web_module.h
+++ b/cobalt/browser/web_module.h
@@ -33,8 +33,6 @@
 #include "cobalt/browser/user_agent_platform_info.h"
 #include "cobalt/css_parser/parser.h"
 #include "cobalt/cssom/viewport_size.h"
-#include "cobalt/dom/blob.h"
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/input_event_init.h"
 #include "cobalt/dom/keyboard_event_init.h"
@@ -55,9 +53,10 @@
 #include "cobalt/render_tree/resource_provider.h"
 #include "cobalt/ui_navigation/nav_item.h"
 #include "cobalt/web/agent.h"
+#include "cobalt/web/blob.h"
 #include "cobalt/web/context.h"
+#include "cobalt/web/csp_delegate.h"
 #include "cobalt/webdriver/session_driver.h"
-#include "cobalt/worker/service_worker_jobs.h"
 #include "starboard/atomic.h"
 #include "url/gurl.h"
 
@@ -142,7 +141,7 @@
     bool enable_map_to_mesh = true;
 
     // Content Security Policy enforcement mode for this web module.
-    dom::CspEnforcementType csp_enforcement_mode = dom::kCspEnforcementEnable;
+    web::CspEnforcementType csp_enforcement_mode = web::kCspEnforcementEnable;
 
     // Token obtained from CSP to allow creation of insecure delegates.
     int csp_insecure_allowed_token = 0;
@@ -259,9 +258,7 @@
             media::MediaModule* media_module,
             const cssom::ViewportSize& window_dimensions,
             render_tree::ResourceProvider* resource_provider,
-            float layout_refresh_rate,
-            worker::ServiceWorkerJobs* service_worker_jobs,
-            const Options& options);
+            float layout_refresh_rate, const Options& options);
   ~WebModule();
 
   // Injects an on screen keyboard input event into the web module. The value
@@ -411,7 +408,6 @@
                      starboard::atomic_bool* waiting_for_web_debugger,
 #endif  // defined(ENABLE_DEBUGGER)
                      base::WaitableEvent* synchronous_loader_interrupt,
-                     worker::ServiceWorkerJobs* service_worker_jobs,
                      const Options& options)
         : initial_url(initial_url),
           initial_application_state(initial_application_state),
@@ -430,7 +426,6 @@
           waiting_for_web_debugger(waiting_for_web_debugger),
 #endif  // defined(ENABLE_DEBUGGER)
           synchronous_loader_interrupt(synchronous_loader_interrupt),
-          service_worker_jobs(service_worker_jobs),
           options(options) {
     }
 
@@ -451,7 +446,6 @@
     starboard::atomic_bool* waiting_for_web_debugger;
 #endif  // defined(ENABLE_DEBUGGER)
     base::WaitableEvent* synchronous_loader_interrupt;
-    worker::ServiceWorkerJobs* service_worker_jobs;
     Options options;
   };
 
diff --git a/cobalt/browser/web_module_stat_tracker.cc b/cobalt/browser/web_module_stat_tracker.cc
index 67d4b2c..7f56e28 100644
--- a/cobalt/browser/web_module_stat_tracker.cc
+++ b/cobalt/browser/web_module_stat_tracker.cc
@@ -20,7 +20,7 @@
 
 #include "base/strings/stringprintf.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/event.h"
+#include "cobalt/web/event.h"
 #if defined(ENABLE_WEBDRIVER)
 #include "cobalt/dom/global_stats.h"
 #endif  // ENABLE_WEBDRIVER
@@ -57,7 +57,7 @@
 }
 
 void WebModuleStatTracker::OnStartDispatchEvent(
-    const scoped_refptr<dom::Event>& event) {
+    const scoped_refptr<web::Event>& event) {
   if (!should_track_event_stats_) {
     return;
   }
@@ -99,7 +99,7 @@
 }
 
 void WebModuleStatTracker::OnStopDispatchEvent(
-    const scoped_refptr<dom::Event>& event,
+    const scoped_refptr<web::Event>& event,
     bool are_animation_frame_callbacks_pending,
     bool is_new_render_tree_pending) {
   // Verify that this dispatched event is the one currently being tracked.
diff --git a/cobalt/browser/web_module_stat_tracker.h b/cobalt/browser/web_module_stat_tracker.h
index 36d98c1..4ec69e9 100644
--- a/cobalt/browser/web_module_stat_tracker.h
+++ b/cobalt/browser/web_module_stat_tracker.h
@@ -23,8 +23,8 @@
 #include "cobalt/base/c_val.h"
 #include "cobalt/base/stop_watch.h"
 #include "cobalt/dom/dom_stat_tracker.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/layout/layout_stat_tracker.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace browser {
@@ -46,12 +46,12 @@
 
   // |OnStartDispatchEvent| starts event stat tracking if
   // |should_track_dispatched_events_| is true. Otherwise, it does nothing.
-  void OnStartDispatchEvent(const scoped_refptr<dom::Event>& event);
+  void OnStartDispatchEvent(const scoped_refptr<web::Event>& event);
 
   // |OnStopDispatchEvent| notifies the event stat tracking that |event| has
   // finished being dispatched. If this is the event currently being tracked
   // and nothing is pending, then it also ends tracking of the event.
-  void OnStopDispatchEvent(const scoped_refptr<dom::Event>& event,
+  void OnStopDispatchEvent(const scoped_refptr<web::Event>& event,
                            bool are_animation_frame_callbacks_pending,
                            bool is_new_render_tree_pending);
 
@@ -157,7 +157,7 @@
   EventType current_event_type_;
   // Raw pointer to the current event. This is used to verify that the event in
   // |OnStopDispatchEvent| is the dispatched event being tracked.
-  dom::Event* current_event_dispatched_event_;
+  web::Event* current_event_dispatched_event_;
   base::TimeTicks current_event_start_time_;
   base::TimeTicks current_event_render_tree_produced_time_;
 
diff --git a/cobalt/build/cobalt_configuration.py b/cobalt/build/cobalt_configuration.py
index 1201c9d..c70dfdf 100644
--- a/cobalt/build/cobalt_configuration.py
+++ b/cobalt/build/cobalt_configuration.py
@@ -101,6 +101,12 @@
         ('fetch/WebPlatformTest.Run/'
          'fetch_api_request_request_cache_no_store_html'),
 
+        # RequestCache "no-cache" mode revalidates fresh responses found in
+        # the cache.
+        # Disabled because of: Caching bug.
+        ('fetch/WebPlatformTest.Run/'
+         'fetch_api_request_request_cache_no_cache_html'),
+
         # Check response clone use structureClone for teed ReadableStreams
         # (DataViewchunk).
         # Disabled because of: Timeout.
@@ -166,9 +172,11 @@
         'renderer_test',
         'storage_test',
         'text_encoding_test',
+        'web_test',
         'web_animations_test',
         'webdriver_test',
         'websocket_test',
+        'worker_test',
         'xhr_test',
         'zip_unittests',
     ]
diff --git a/starboard/sabi/sabi.py b/cobalt/cache/BUILD.gn
similarity index 68%
copy from starboard/sabi/sabi.py
copy to cobalt/cache/BUILD.gn
index 5dca3b9..c81fa0a 100644
--- a/starboard/sabi/sabi.py
+++ b/cobalt/cache/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2020 The Cobalt Authors. All Rights Reserved.
+# Copyright 2022 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.
@@ -11,6 +11,15 @@
 # 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.
-"""Source of truth of the default Starboard API version."""
-
-SB_API_VERSION = 14
+static_library("cache") {
+  sources = [
+    "cache.cc",
+    "cache.h",
+  ]
+  deps = [
+    "//base",
+    "//cobalt/base",
+    "//net",
+    "//starboard:starboard_headers_only",
+  ]
+}
diff --git a/cobalt/cache/cache.cc b/cobalt/cache/cache.cc
new file mode 100644
index 0000000..5ab81ab
--- /dev/null
+++ b/cobalt/cache/cache.cc
@@ -0,0 +1,221 @@
+// Copyright 2022 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/cache/cache.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/files/file_util.h"
+#include "base/memory/singleton.h"
+#include "base/strings/string_number_conversions.h"
+#include "cobalt/extension/javascript_cache.h"
+#include "starboard/configuration_constants.h"
+#include "starboard/directory.h"
+#include "starboard/system.h"
+
+namespace {
+
+base::Optional<base::FilePath> CacheDirectory(
+    disk_cache::ResourceType resource_type) {
+  std::vector<char> path(kSbFileMaxPath, 0);
+  if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path.data(),
+                       kSbFileMaxPath)) {
+    return base::nullopt;
+  }
+  // TODO: make subdirectory determined by type of cache data.
+  std::string cache_subdirectory;
+  switch (resource_type) {
+    case disk_cache::ResourceType::kCompiledScript:
+      cache_subdirectory = "compiled_js";
+      break;
+    default:
+      return base::nullopt;
+  }
+  auto cache_directory = base::FilePath(path.data()).Append(cache_subdirectory);
+  SbDirectoryCreate(cache_directory.value().c_str());
+  return cache_directory;
+}
+
+base::Optional<base::FilePath> FilePathFromKey(
+    uint32_t key, disk_cache::ResourceType resource_type) {
+  auto cache_directory = CacheDirectory(resource_type);
+  if (!cache_directory) {
+    return base::nullopt;
+  }
+  return cache_directory->Append(base::UintToString(key));
+}
+
+base::Optional<uint32_t> GetMaxStorageInBytes(
+    disk_cache::ResourceType resource_type) {
+  switch (resource_type) {
+    case disk_cache::ResourceType::kCompiledScript:
+      return 5 << 20;  // 5MiB
+    default:
+      return base::nullopt;
+  }
+}
+
+const CobaltExtensionJavaScriptCacheApi* GetJavaScriptCacheExtension() {
+  const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension =
+      static_cast<const CobaltExtensionJavaScriptCacheApi*>(
+          SbSystemGetExtension(kCobaltExtensionJavaScriptCacheName));
+  if (javascript_cache_extension &&
+      strcmp(javascript_cache_extension->name,
+             kCobaltExtensionJavaScriptCacheName) == 0 &&
+      javascript_cache_extension->version >= 1) {
+    return javascript_cache_extension;
+  }
+  return nullptr;
+}
+
+}  // namespace
+
+namespace cobalt {
+namespace cache {
+
+namespace {
+
+struct FileInfoComparer {
+  bool operator()(const Cache::FileInfo& left, const Cache::FileInfo& right) {
+    return left.GetLastModifiedTime() > right.GetLastModifiedTime();
+  }
+};
+
+}  // namespace
+
+Cache::FileInfo::FileInfo(base::FilePath root_path,
+                          base::FileEnumerator::FileInfo file_info)
+    : file_path_(root_path.Append(file_info.GetName())),
+      last_modified_time_(file_info.GetLastModifiedTime()),
+      size_(static_cast<uint32_t>(file_info.GetSize())) {}
+
+Cache::FileInfo::FileInfo(base::FilePath file_path,
+                          base::Time last_modified_time, uint32_t size)
+    : file_path_(file_path),
+      last_modified_time_(last_modified_time),
+      size_(size) {}
+
+base::Time Cache::FileInfo::GetLastModifiedTime() const {
+  return last_modified_time_;
+}
+
+Cache* Cache::GetInstance() {
+  return base::Singleton<Cache, base::LeakySingletonTraits<Cache>>::get();
+}
+
+base::Optional<std::vector<uint8_t>> Cache::Retrieve(
+    uint32_t key, disk_cache::ResourceType resource_type) {
+  const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension =
+      GetJavaScriptCacheExtension();
+  if (javascript_cache_extension) {
+    const uint8_t* cache_data_buf = nullptr;
+    int cache_data_size = -1;
+    if (javascript_cache_extension->GetCachedScript(key, 0, &cache_data_buf,
+                                                    &cache_data_size)) {
+      auto data = std::vector<uint8_t>(cache_data_buf,
+                                       cache_data_buf + cache_data_size);
+      javascript_cache_extension->ReleaseCachedScriptData(cache_data_buf);
+      return data;
+    }
+  }
+  // TODO: check if caching should be disabled.
+  auto file_path = FilePathFromKey(key, resource_type);
+  if (!file_path || !base::PathExists(file_path.value())) {
+    return base::nullopt;
+  }
+  int64 size;
+  base::GetFileSize(file_path.value(), &size);
+  std::vector<uint8_t> data(static_cast<size_t>(size));
+  int bytes_read =
+      base::ReadFile(file_path.value(), reinterpret_cast<char*>(data.data()),
+                     static_cast<int>(size));
+  if (bytes_read != size) {
+    return base::nullopt;
+  }
+  return data;
+}
+
+bool Cache::Store(uint32_t key, disk_cache::ResourceType resource_type,
+                  const std::vector<uint8_t>& data) {
+  const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension =
+      GetJavaScriptCacheExtension();
+  if (javascript_cache_extension &&
+      javascript_cache_extension->StoreCachedScript(key, 0, data.data(),
+                                                    data.size())) {
+    return true;
+  }
+  // TODO: check if caching should be disabled.
+  base::AutoLock auto_lock(lock_);
+  auto stored = GetStored(resource_type);
+  if (!stored) {
+    return false;
+  }
+  auto file_path = FilePathFromKey(key, resource_type);
+  if (!file_path) {
+    return false;
+  }
+  auto max_storage_in_bytes = GetMaxStorageInBytes(resource_type);
+  if (!max_storage_in_bytes) {
+    return false;
+  }
+  uint32_t new_entry_size = static_cast<uint32_t>(data.size());
+  while (stored_size_by_resource_type_[resource_type] + new_entry_size >
+         max_storage_in_bytes.value()) {
+    std::pop_heap(stored->begin(), stored->end(), FileInfoComparer());
+    auto removed = stored->back();
+    stored_size_by_resource_type_[resource_type] -= removed.size_;
+    base::DeleteFile(removed.file_path_, false);
+    stored->pop_back();
+  }
+  int bytes_written =
+      base::WriteFile(file_path.value(),
+                      reinterpret_cast<const char*>(data.data()), data.size());
+  if (bytes_written != data.size()) {
+    base::DeleteFile(file_path.value(), false);
+    return false;
+  }
+  stored_size_by_resource_type_[resource_type] += new_entry_size;
+  stored->push_back(
+      FileInfo(file_path.value(), base::Time::Now(), new_entry_size));
+  stored_by_resource_type_[resource_type] = stored.value();
+  return true;
+}
+
+base::Optional<std::vector<Cache::FileInfo>> Cache::GetStored(
+    disk_cache::ResourceType resource_type) {
+  auto it = stored_by_resource_type_.find(resource_type);
+  if (it != stored_by_resource_type_.end()) {
+    return it->second;
+  }
+  auto cache_directory = CacheDirectory(resource_type);
+  if (!cache_directory) {
+    return base::nullopt;
+  }
+  base::FileEnumerator file_enumerator(cache_directory.value(), false,
+                                       base::FileEnumerator::FILES);
+  std::vector<FileInfo> stored;
+  while (!file_enumerator.Next().empty()) {
+    auto file_info =
+        Cache::FileInfo(cache_directory.value(), file_enumerator.GetInfo());
+    stored.push_back(file_info);
+    stored_size_by_resource_type_[resource_type] += file_info.size_;
+  }
+  std::make_heap(stored.begin(), stored.end(), FileInfoComparer());
+  stored_by_resource_type_[resource_type] = stored;
+  return stored;
+}
+
+}  // namespace cache
+}  // namespace cobalt
diff --git a/cobalt/cache/cache.h b/cobalt/cache/cache.h
new file mode 100644
index 0000000..93bff5e
--- /dev/null
+++ b/cobalt/cache/cache.h
@@ -0,0 +1,81 @@
+// Copyright 2022 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_CACHE_CACHE_H_
+#define COBALT_CACHE_CACHE_H_
+
+#include <map>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/synchronization/lock.h"
+#include "net/disk_cache/cobalt/resource_type.h"
+
+namespace base {
+template <typename T>
+struct DefaultSingletonTraits;
+}
+
+namespace cobalt {
+namespace cache {
+
+class Cache {
+ public:
+  class FileInfo {
+   public:
+    FileInfo(base::FilePath root_path,
+             base::FileEnumerator::FileInfo file_info);
+    FileInfo(base::FilePath file_path, base::Time last_modified_time,
+             uint32_t size);
+
+    base::Time GetLastModifiedTime() const;
+
+   private:
+    friend class Cache;
+
+    base::FilePath file_path_;
+    base::Time last_modified_time_;
+    uint32_t size_;
+  };  // class FileInfo
+
+  static Cache* GetInstance();
+  base::Optional<std::vector<uint8_t>> Retrieve(
+      uint32_t key, disk_cache::ResourceType resource_type);
+  bool Store(uint32_t key, disk_cache::ResourceType resource_type,
+             const std::vector<uint8_t>& data);
+
+ private:
+  friend struct base::DefaultSingletonTraits<Cache>;
+  Cache() = default;
+  base::Optional<std::vector<FileInfo>> GetStored(
+      disk_cache::ResourceType resource_type);
+
+  mutable base::Lock lock_;
+  // The following maps are only used when the JavaScript cache extension is
+  // neither present nor valid.
+  std::map<disk_cache::ResourceType, uint32_t> stored_size_by_resource_type_;
+  std::map<disk_cache::ResourceType, std::vector<FileInfo>>
+      stored_by_resource_type_;
+
+  DISALLOW_COPY_AND_ASSIGN(Cache);
+};  // class Cache
+
+}  // namespace cache
+}  // namespace cobalt
+
+#endif  // COBALT_CACHE_CACHE_H_
diff --git a/cobalt/content/i18n/platform/linux/en-US.xlb b/cobalt/content/i18n/platform/linux/en-US.xlb
deleted file mode 100644
index 52f9c42..0000000
--- a/cobalt/content/i18n/platform/linux/en-US.xlb
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<localizationbundle locale="en">
-  <messages>
-    <msg name="AGE_RESTRICTED" desc="This message is shown to users who are under 13 years of age.">YouTube is not intended for use by children under 13.</msg>
-    <msg name="UNABLE_TO_CONTACT_YOUTUBE" desc="This error message is shown to the user when the software detects that YouTube's servers are repeatedly returning error messages. The message is shown in the center of the TV screen in white text over a dark background.">Unable to contact YouTube.</msg>
-  </messages>
-</localizationbundle>
diff --git a/cobalt/cssom/BUILD.gn b/cobalt/cssom/BUILD.gn
index 6e77b8f..b313c59 100644
--- a/cobalt/cssom/BUILD.gn
+++ b/cobalt/cssom/BUILD.gn
@@ -248,11 +248,11 @@
   deps = [
     ":cssom_embed_resources_as_header_files",
     "//cobalt/base",
-    "//cobalt/dom:dom_exception",
     "//cobalt/math",
     "//cobalt/network",
     "//cobalt/script",
     "//cobalt/ui_navigation",
+    "//cobalt/web:dom_exception",
     "//url",
   ]
 }
diff --git a/cobalt/cssom/css_computed_style_declaration.cc b/cobalt/cssom/css_computed_style_declaration.cc
index b4c25f0..6245987 100644
--- a/cobalt/cssom/css_computed_style_declaration.cc
+++ b/cobalt/cssom/css_computed_style_declaration.cc
@@ -15,7 +15,7 @@
 #include "cobalt/cssom/css_computed_style_declaration.h"
 
 #include "cobalt/cssom/css_declared_style_data.h"
-#include "cobalt/dom/dom_exception.h"
+#include "cobalt/web/dom_exception.h"
 
 namespace cobalt {
 namespace cssom {
@@ -30,7 +30,7 @@
 
 void CSSComputedStyleDeclaration::set_css_text(
     const std::string& css_text, script::ExceptionState* exception_state) {
-  dom::DOMException::Raise(dom::DOMException::kInvalidAccessErr,
+  web::DOMException::Raise(web::DOMException::kInvalidAccessErr,
                            exception_state);
 }
 
@@ -78,14 +78,14 @@
 void CSSComputedStyleDeclaration::SetPropertyValue(
     const std::string& property_name, const std::string& property_value,
     script::ExceptionState* exception_state) {
-  dom::DOMException::Raise(dom::DOMException::kInvalidAccessErr,
+  web::DOMException::Raise(web::DOMException::kInvalidAccessErr,
                            exception_state);
 }
 
 void CSSComputedStyleDeclaration::SetProperty(
     const std::string& property_name, const std::string& property_value,
     const std::string& priority, script::ExceptionState* exception_state) {
-  dom::DOMException::Raise(dom::DOMException::kInvalidAccessErr,
+  web::DOMException::Raise(web::DOMException::kInvalidAccessErr,
                            exception_state);
 }
 
diff --git a/cobalt/cssom/css_computed_style_declaration_test.cc b/cobalt/cssom/css_computed_style_declaration_test.cc
index fd267c8..ac53074 100644
--- a/cobalt/cssom/css_computed_style_declaration_test.cc
+++ b/cobalt/cssom/css_computed_style_declaration_test.cc
@@ -37,7 +37,7 @@
 
   const std::string css_text = "font-size: 100px; color: #0047ab;";
   style->set_css_text(css_text, &exception_state);
-  EXPECT_EQ(dom::DOMException::kInvalidAccessErr,
+  EXPECT_EQ(web::DOMException::kInvalidAccessErr,
             exception_state.GetExceptionCode());
 }
 
@@ -65,7 +65,7 @@
 
   style->SetPropertyValue(GetPropertyName(kBackgroundProperty), background,
                           &exception_state);
-  EXPECT_EQ(dom::DOMException::kInvalidAccessErr,
+  EXPECT_EQ(web::DOMException::kInvalidAccessErr,
             exception_state.GetExceptionCode());
 }
 
@@ -78,7 +78,7 @@
 
   style->SetProperty(GetPropertyName(kBackgroundProperty), background,
                      &exception_state);
-  EXPECT_EQ(dom::DOMException::kInvalidAccessErr,
+  EXPECT_EQ(web::DOMException::kInvalidAccessErr,
             exception_state.GetExceptionCode());
 }
 
@@ -91,7 +91,7 @@
 
   style->SetProperty(GetPropertyName(kBackgroundProperty), background,
                      std::string(), &exception_state);
-  EXPECT_EQ(dom::DOMException::kInvalidAccessErr,
+  EXPECT_EQ(web::DOMException::kInvalidAccessErr,
             exception_state.GetExceptionCode());
 }
 
@@ -107,7 +107,7 @@
   FakeExceptionState exception_state;
 
   style->RemoveProperty(GetPropertyName(kDisplayProperty), &exception_state);
-  EXPECT_EQ(dom::DOMException::kInvalidAccessErr,
+  EXPECT_EQ(web::DOMException::kInvalidAccessErr,
             exception_state.GetExceptionCode());
 }
 
diff --git a/cobalt/cssom/css_style_sheet.cc b/cobalt/cssom/css_style_sheet.cc
index 5a399e1..6695d36 100644
--- a/cobalt/cssom/css_style_sheet.cc
+++ b/cobalt/cssom/css_style_sheet.cc
@@ -25,7 +25,7 @@
 #include "cobalt/cssom/css_style_rule.h"
 #include "cobalt/cssom/style_sheet_list.h"
 #include "cobalt/cssom/viewport_size.h"
-#include "cobalt/dom/dom_exception.h"
+#include "cobalt/web/dom_exception.h"
 
 namespace cobalt {
 namespace cssom {
@@ -87,9 +87,9 @@
 const scoped_refptr<CSSRuleList>& CSSStyleSheet::css_rules(
     script::ExceptionState* exception_state) {
   if (!origin_clean_) {
-    dom::DOMException::Raise(
-        dom::DOMException::kSecurityErr,
-        "Website trys to access css rules from a CSSStyleSheet "
+    web::DOMException::Raise(
+        web::DOMException::kSecurityErr,
+        "Website tries to access css rules from a CSSStyleSheet "
         "fetched from another origin.",
         exception_state);
     DCHECK(!null_css_rule_list_);
@@ -111,9 +111,9 @@
     const std::string& rule, unsigned int index,
     script::ExceptionState* exception_state) {
   if (!origin_clean_) {
-    dom::DOMException::Raise(
-        dom::DOMException::kSecurityErr,
-        "Website trys to insert css rule to a CSSStyleSheet fetched"
+    web::DOMException::Raise(
+        web::DOMException::kSecurityErr,
+        "Website tries to insert css rule to a CSSStyleSheet fetched"
         "from another origin.",
         exception_state);
     return 0;
diff --git a/cobalt/debug/BUILD.gn b/cobalt/debug/BUILD.gn
index a329978..91c5586 100644
--- a/cobalt/debug/BUILD.gn
+++ b/cobalt/debug/BUILD.gn
@@ -61,6 +61,7 @@
   deps = [
     ":console_command_manager",
     "//cobalt/base",
+    "//cobalt/browser:generated_types",
     "//cobalt/cssom",
     "//cobalt/debug/backend/content:copy_backend_web_files",
     "//cobalt/debug/console/content:copy_console_web_files",
@@ -71,6 +72,7 @@
     "//cobalt/render_tree",
     "//cobalt/script",
     "//cobalt/speech",
+    "//cobalt/web",
     "//net",
     "//net:http_server",
     "//starboard",
diff --git a/cobalt/debug/backend/debug_dispatcher.h b/cobalt/debug/backend/debug_dispatcher.h
index 99660c9..2ce8afe 100644
--- a/cobalt/debug/backend/debug_dispatcher.h
+++ b/cobalt/debug/backend/debug_dispatcher.h
@@ -32,11 +32,11 @@
 #include "cobalt/debug/command.h"
 #include "cobalt/debug/debug_client.h"
 #include "cobalt/debug/json_object.h"
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/script_debugger.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/value_handle.h"
+#include "cobalt/web/csp_delegate.h"
 
 namespace cobalt {
 namespace debug {
diff --git a/cobalt/debug/backend/debug_module.cc b/cobalt/debug/backend/debug_module.cc
index b597002..982f8f3 100644
--- a/cobalt/debug/backend/debug_module.cc
+++ b/cobalt/debug/backend/debug_module.cc
@@ -89,7 +89,7 @@
 }
 
 DebugModule::~DebugModule() {
-  debugger_hooks_->DetachDebugger();
+  if (debugger_hooks_) debugger_hooks_->DetachDebugger();
   if (!is_frozen_) {
     // Shutting down without navigating. Give everything a chance to cleanup by
     // freezing, but throw away the state.
@@ -117,16 +117,13 @@
 void DebugModule::BuildInternal(const ConstructionData& data) {
   DCHECK(base::MessageLoop::current() == data.message_loop);
   DCHECK(data.global_environment);
-  DCHECK(data.render_overlay);
-  DCHECK(data.resource_provider);
-  DCHECK(data.window);
 
   // Create the backend objects supporting the debugger agents.
   script_debugger_ =
       script::ScriptDebugger::CreateDebugger(data.global_environment, this);
-  script_runner_.reset(
-      new DebugScriptRunner(data.global_environment, script_debugger_.get(),
-                            data.window->document()->csp_delegate()));
+  script_runner_.reset(new DebugScriptRunner(
+      data.global_environment, script_debugger_.get(),
+      data.window ? data.window->document()->csp_delegate() : nullptr));
   debug_dispatcher_.reset(
       new DebugDispatcher(script_debugger_.get(), script_runner_.get()));
   debug_backend_ = WrapRefCounted(new DebugBackend(
@@ -134,16 +131,8 @@
       base::Bind(&DebugModule::SendEvent, base::Unretained(this))));
 
   debugger_hooks_ = data.debugger_hooks;
-  debugger_hooks_->AttachDebugger(script_debugger_.get());
+  if (debugger_hooks_) debugger_hooks_->AttachDebugger(script_debugger_.get());
 
-  // Create render layers for the agents that need them and chain them
-  // together. Ownership will be passed to the agent that uses each layer.
-  // The layers will be painted in the reverse order they are listed here.
-  std::unique_ptr<RenderLayer> page_render_layer(new RenderLayer(base::Bind(
-      &RenderOverlay::SetOverlay, base::Unretained(data.render_overlay))));
-
-  std::unique_ptr<RenderLayer> overlay_render_layer(new RenderLayer(
-      base::Bind(&RenderLayer::SetBackLayer, page_render_layer->AsWeakPtr())));
 
   // Create the agents that implement the various devtools protocol domains by
   // handling commands and sending event notifications. The script debugger
@@ -154,11 +143,22 @@
   log_agent_.reset(new LogAgent(debug_dispatcher_.get()));
   dom_agent_.reset(new DOMAgent(debug_dispatcher_.get()));
   css_agent_ = WrapRefCounted(new CSSAgent(debug_dispatcher_.get()));
-  overlay_agent_.reset(new OverlayAgent(debug_dispatcher_.get(),
-                                        std::move(overlay_render_layer)));
-  page_agent_.reset(new PageAgent(debug_dispatcher_.get(), data.window,
-                                  std::move(page_render_layer),
-                                  data.resource_provider));
+  if (data.render_overlay && data.resource_provider && data.window) {
+    // Create render layers for the agents that need them and chain them
+    // together. Ownership will be passed to the agent that uses each layer.
+    // The layers will be painted in the reverse order they are listed here.
+    std::unique_ptr<RenderLayer> page_render_layer(new RenderLayer(base::Bind(
+        &RenderOverlay::SetOverlay, base::Unretained(data.render_overlay))));
+
+    std::unique_ptr<RenderLayer> overlay_render_layer(
+        new RenderLayer(base::Bind(&RenderLayer::SetBackLayer,
+                                   page_render_layer->AsWeakPtr())));
+    overlay_agent_.reset(new OverlayAgent(debug_dispatcher_.get(),
+                                          std::move(overlay_render_layer)));
+    page_agent_.reset(new PageAgent(debug_dispatcher_.get(), data.window,
+                                    std::move(page_render_layer),
+                                    data.resource_provider));
+  }
   tracing_agent_.reset(
       new TracingAgent(debug_dispatcher_.get(), script_debugger_.get()));
 
@@ -183,8 +183,10 @@
   log_agent_->Thaw(RemoveAgentState(kLogAgent, agents_state));
   dom_agent_->Thaw(RemoveAgentState(kDomAgent, agents_state));
   css_agent_->Thaw(RemoveAgentState(kCssAgent, agents_state));
-  overlay_agent_->Thaw(RemoveAgentState(kOverlayAgent, agents_state));
-  page_agent_->Thaw(RemoveAgentState(kPageAgent, agents_state));
+  if (overlay_agent_)
+    overlay_agent_->Thaw(RemoveAgentState(kOverlayAgent, agents_state));
+  if (page_agent_)
+    page_agent_->Thaw(RemoveAgentState(kPageAgent, agents_state));
   tracing_agent_->Thaw(RemoveAgentState(kTracingAgent, agents_state));
 
   is_frozen_ = false;
@@ -203,8 +205,10 @@
   StoreAgentState(agents_state, kLogAgent, log_agent_->Freeze());
   StoreAgentState(agents_state, kDomAgent, dom_agent_->Freeze());
   StoreAgentState(agents_state, kCssAgent, css_agent_->Freeze());
-  StoreAgentState(agents_state, kOverlayAgent, overlay_agent_->Freeze());
-  StoreAgentState(agents_state, kPageAgent, page_agent_->Freeze());
+  if (overlay_agent_)
+    StoreAgentState(agents_state, kOverlayAgent, overlay_agent_->Freeze());
+  if (page_agent_)
+    StoreAgentState(agents_state, kPageAgent, page_agent_->Freeze());
   StoreAgentState(agents_state, kTracingAgent, tracing_agent_->Freeze());
 
   // Take the clients from the dispatcher last so they still get events that the
diff --git a/cobalt/debug/backend/debug_script_runner.cc b/cobalt/debug/backend/debug_script_runner.cc
index 36fa38b..a6df5c7 100644
--- a/cobalt/debug/backend/debug_script_runner.cc
+++ b/cobalt/debug/backend/debug_script_runner.cc
@@ -33,7 +33,7 @@
 DebugScriptRunner::DebugScriptRunner(
     script::GlobalEnvironment* global_environment,
     script::ScriptDebugger* script_debugger,
-    const dom::CspDelegate* csp_delegate)
+    const web::CspDelegate* csp_delegate)
     : global_environment_(global_environment),
       script_debugger_(script_debugger),
       csp_delegate_(csp_delegate) {}
@@ -97,7 +97,7 @@
   }
 
   global_environment_->SetReportEvalCallback(base::Bind(
-      &dom::CspDelegate::ReportEval, base::Unretained(csp_delegate_)));
+      &web::CspDelegate::ReportEval, base::Unretained(csp_delegate_)));
 }
 
 }  // namespace backend
diff --git a/cobalt/debug/backend/debug_script_runner.h b/cobalt/debug/backend/debug_script_runner.h
index e41d7b3..91481bd 100644
--- a/cobalt/debug/backend/debug_script_runner.h
+++ b/cobalt/debug/backend/debug_script_runner.h
@@ -17,9 +17,9 @@
 
 #include <string>
 
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/script_debugger.h"
+#include "cobalt/web/csp_delegate.h"
 
 namespace cobalt {
 namespace debug {
@@ -30,7 +30,7 @@
  public:
   DebugScriptRunner(script::GlobalEnvironment* global_environment,
                     script::ScriptDebugger* script_debugger,
-                    const dom::CspDelegate* csp_delegate);
+                    const web::CspDelegate* csp_delegate);
 
   // Runs |method| on the JavaScript |debugBackend| object, passing in
   // |json_params|. If |json_result| is non-NULL it receives the result.
@@ -61,7 +61,7 @@
   script::ScriptDebugger* script_debugger_;
 
   // Non-owned reference to let this object query whether CSP allows eval.
-  const dom::CspDelegate* csp_delegate_;
+  const web::CspDelegate* csp_delegate_;
 };
 
 }  // namespace backend
diff --git a/cobalt/demos/content/splash_screen/redirect_server.py b/cobalt/demos/content/splash_screen/redirect_server.py
index cdb977e..fe83ac4 100644
--- a/cobalt/demos/content/splash_screen/redirect_server.py
+++ b/cobalt/demos/content/splash_screen/redirect_server.py
@@ -56,15 +56,16 @@
 the Cobalt logo splash screen on the next navigation to
 link_splash_screen.html?redirect=yes.
 """
-import SimpleHTTPServer
-import SocketServer
+from six.moves import SimpleHTTPServer
+from six.moves import socketserver
 
 
 class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+  """Class to redirect requests to the splash screen."""
 
-  def do_GET(self):
+  def do_GET(self):  # pylint: disable=invalid-name
     path = self.path
-    print 'path = ' + path
+    print('path = ' + path)
     redirect_from = 'link_splash_screen.html?redirect=yes'
     redirect_to = 'redirected.html'
     if redirect_from in path:
@@ -75,12 +76,7 @@
       return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
 
 
-port = 8000
-while True:
-  try:
-    handler = SocketServer.TCPServer(('', port), Handler)
-    print 'seving port ' + str(port)
-    handler.serve_forever()
-  except SocketServer.socket.error as exc:
-    port += 1
-    print 'trying port ' + str(port)
+if __name__ == '__main__':
+  handler = socketserver.TCPServer(('', 0), Handler)
+  print('serving port ' + str(handler.server_address[1]))
+  handler.serve_forever()
diff --git a/cobalt/demos/content/watchdog-demo/index.html b/cobalt/demos/content/watchdog-demo/index.html
new file mode 100644
index 0000000..3ab90bf
--- /dev/null
+++ b/cobalt/demos/content/watchdog-demo/index.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <style>
+    body {
+      background-color: white;
+      font-family: Roboto;
+      font-size: 2em;
+    }
+
+    .highlight {
+      background-color: gray;
+    }
+  </style>
+</head>
+
+<body>
+  <div id="menu"></div>
+  <script type="text/javascript">
+    const watchdogFunctions = {
+      register: 'Register',
+      unregister: 'Unregister',
+      ping: 'Ping',
+      getWatchdogViolations: 'Get Watchdog Violations',
+      getCanTriggerCrash: 'Get Can Trigger Crash',
+      setCanTriggerCrashTrue: 'Set Can Trigger Crash True',
+      setCanTriggerCrashFalse: 'Set Can Trigger Crash False',
+    }
+
+    window.onload = loadMenu();
+
+    function loadMenu() {
+      let menu = document.getElementById('menu');
+      for (const key in watchdogFunctions) {
+        let el = document.createElement('div');
+        el.id = key;
+        el.textContent = watchdogFunctions[key];
+        menu.appendChild(el);
+      }
+      menu = document.getElementById('menu').children;
+
+      let index = 0;
+
+      document.addEventListener('keydown', navigateMenu);
+
+      refreshMenuHighlights();
+
+      function navigateMenu(e) {
+        if ([37, 38, 32782, 32780].includes(e.keyCode)) { // left, up, android left, up
+          index--;
+        } else if ([39, 40, 32781, 32783].includes(e.keyCode)) { // right, down, android right, down
+          index++;
+        } else if ([13, 32768].includes(e.keyCode)) { // enter, android enter
+          let watchdogFunction = menu[index].id;
+          let ret = 'void';
+
+          if (watchdogFunction == 'register') {
+            ret = h5vcc.crashLog.register('test-name', 'test-description', 'started', 5000000, 0, 'none');
+          } else if (watchdogFunction == 'unregister') {
+            ret = h5vcc.crashLog.unregister('test-name');
+          } else if (watchdogFunction == 'ping') {
+            ret = h5vcc.crashLog.ping('test-name', `test-ping`);
+          } else if (watchdogFunction == 'getWatchdogViolations') {
+            ret = h5vcc.crashLog.getWatchdogViolations(true);
+          } else if (watchdogFunction == 'getCanTriggerCrash') {
+            ret = h5vcc.crashLog.getCanTriggerCrash();
+          } else if (watchdogFunction == 'setCanTriggerCrashTrue') {
+            h5vcc.crashLog.setCanTriggerCrash(true);
+          } else if (watchdogFunction == 'setCanTriggerCrashFalse') {
+            h5vcc.crashLog.setCanTriggerCrash(false);
+          }
+
+          let el = document.getElementById(watchdogFunction);
+          el.textContent = watchdogFunctions[watchdogFunction] + ' (' + ret + ')';
+        }
+        index = (index + menu.length) % menu.length;
+        refreshMenuHighlights();
+      }
+
+      function refreshMenuHighlights() {
+        for (let i = 0; i < menu.length; i++) {
+          if (i == index) {
+            menu[i].classList.add('highlight');
+          } else {
+            menu[i].classList.remove('highlight');
+          }
+        }
+      }
+    }
+  </script>
+</body>
+
+</html>
diff --git a/cobalt/demos/readme.md b/cobalt/demos/readme.md
index 11d511c..26e67bb 100644
--- a/cobalt/demos/readme.md
+++ b/cobalt/demos/readme.md
@@ -1,16 +1,9 @@
 # Cobalt HTML Demos
 
-## How to build
-
-`ninja cobalt_with_demos`
-
 ## How to run
 
-These html pages can be executed by running cobalt and pointing the `--url`
-parameter to the file in question. For example:
+These html pages can be executed by first running a basic server
+`python3 -m http.server` in the `cobalt_src` directory and then running
+Cobalt and pointing the `--url` parameter to the file in question. For example:
 
-`out/.../cobalt --url=file:///demos/transparent-animated-webp-demo/index.html`
-
-Note that `file:///demos` maps to the `src/out/<PLATFORM>/content/test/demos`
-directory, whose contents are copied from the `src/cobalt/demos/content` source
-directory when `ninja cobalt_with_demos` is run.
+`out/.../cobalt --url=http://0.0.0.0:8000/cobalt/demos/content/watchdog-demo/index.html`
diff --git a/cobalt/doc/voice_search.md b/cobalt/doc/voice_search.md
index 3153553..d4bd6ec 100644
--- a/cobalt/doc/voice_search.md
+++ b/cobalt/doc/voice_search.md
@@ -69,7 +69,7 @@
 
 In `starboard/linux/shared/soft_mic_platform_service.cc` there is an example
 stub implementation of the SoftMicPlatformService. Platforms can optionally
-implement this [CobaltPlatformService](https://cobalt.dev/gen/cobalt/doc/
+implement this [CobaltPlatformService](https://cobalt.dev/gen/cobalt/doc/\
 platform_services.html) to specify if they support the `soft mic` and/or `hard mic`
 for voice search. The `soft mic` refers to the software activation of the microphone
 for voice search through the UI microphone button on the Youtube Web Application
diff --git a/cobalt/dom/BUILD.gn b/cobalt/dom/BUILD.gn
index f8a437c..e7985aa 100644
--- a/cobalt/dom/BUILD.gn
+++ b/cobalt/dom/BUILD.gn
@@ -39,10 +39,6 @@
     "base64.h",
     "benchmark_stat_names.cc",
     "benchmark_stat_names.h",
-    "blob.cc",
-    "blob.h",
-    "buffer_source.cc",
-    "buffer_source.h",
     "c_val_view.cc",
     "c_val_view.h",
     "camera_3d.cc",
@@ -53,23 +49,14 @@
     "cdata_section.h",
     "character_data.cc",
     "character_data.h",
-    "cobalt_ua_data_values_interface.cc",
-    "cobalt_ua_data_values_interface.h",
     "comment.cc",
     "comment.h",
     "crypto.cc",
     "crypto.h",
-    "csp_delegate.cc",
-    "csp_delegate.h",
-    "csp_delegate_factory.cc",
-    "csp_delegate_factory.h",
-    "csp_violation_reporter.cc",
-    "csp_violation_reporter.h",
     "css_animations_adapter.cc",
     "css_animations_adapter.h",
     "css_transitions_adapter.cc",
     "css_transitions_adapter.h",
-    "custom_event.h",
     "device_orientation_event.cc",
     "device_orientation_event.h",
     "directionality.h",
@@ -114,16 +101,9 @@
     "eme/media_key_system_access.h",
     "eme/media_keys.cc",
     "eme/media_keys.h",
-    "error_event.h",
-    "event.cc",
-    "event.h",
     "event_listener.h",
     "event_queue.cc",
     "event_queue.h",
-    "event_target.cc",
-    "event_target.h",
-    "event_target_listener_info.cc",
-    "event_target_listener_info.h",
     "focus_event.cc",
     "focus_event.h",
     "font_cache.cc",
@@ -229,8 +209,6 @@
     "named_node_map.h",
     "navigator.cc",
     "navigator.h",
-    "navigator_ua_data.cc",
-    "navigator_ua_data.h",
     "node.cc",
     "node.h",
     "node_children_iterator.h",
@@ -239,8 +217,6 @@
     "node_list.h",
     "node_list_live.cc",
     "node_list_live.h",
-    "on_error_event_listener.cc",
-    "on_error_event_listener.h",
     "on_screen_keyboard.cc",
     "on_screen_keyboard.h",
     "on_screen_keyboard_bridge.h",
@@ -287,8 +263,6 @@
     "screenshot.h",
     "screenshot_manager.cc",
     "screenshot_manager.h",
-    "security_policy_violation_event.cc",
-    "security_policy_violation_event.h",
     "serializer.cc",
     "serializer.h",
     "source_buffer.cc",
@@ -316,20 +290,13 @@
     "ui_event.h",
     "ui_event_with_key_state.cc",
     "ui_event_with_key_state.h",
-    "url.cc",
-    "url.h",
-    "url_registry.h",
-    "url_utils.cc",
-    "url_utils.h",
-    "user_agent_platform_info.h",
+    "url_media_source.cc",
     "video_track.h",
     "video_track_list.h",
     "wheel_event.cc",
     "wheel_event.h",
     "window.cc",
     "window.h",
-    "window_timers.cc",
-    "window_timers.h",
     "xml_document.h",
     "xml_serializer.cc",
     "xml_serializer.h",
@@ -340,12 +307,12 @@
   }
 
   public_deps = [
+    ":window_timers",
     "//cobalt/browser:generated_types",
     "//cobalt/web",
   ]
 
   deps = [
-    ":dom_exception",
     ":licenses",
     "//base:i18n",
     "//cobalt/base",
@@ -368,8 +335,10 @@
     "//cobalt/system_window",
     "//cobalt/ui_navigation",
     "//cobalt/web",
+    "//cobalt/web:dom_exception",
     "//cobalt/web_animations",
     "//cobalt/worker",
+    "//cobalt/xhr:global_stats",
     "//crypto",
     "//nb",
     "//net",
@@ -381,15 +350,23 @@
   ]
 }
 
-static_library("dom_exception") {
+static_library("window_timers") {
   has_pedantic_warnings = true
 
   sources = [
-    "dom_exception.cc",
-    "dom_exception.h",
+    # TODO (b/219103808): Move window_timers to /cobalt/web.
+    "window_timers.cc",
+    "window_timers.h",
   ]
 
-  deps = [ "//cobalt/script" ]
+  public_deps = [ "//cobalt/web:stat_tracker" ]
+
+  deps = [
+    "//cobalt/base",
+    "//cobalt/script",
+    "//cobalt/web:web_events",
+    "//nb",
+  ]
 }
 
 copy("licenses") {
@@ -407,11 +384,8 @@
 
   sources = [
     "application_lifecycle_state_test.cc",
-    "blob_test.cc",
     "comment_test.cc",
     "crypto_test.cc",
-    "csp_delegate_test.cc",
-    "custom_event_test.cc",
     "document_test.cc",
     "document_type_test.cc",
     "dom_implementation_test.cc",
@@ -420,10 +394,7 @@
     "dom_string_map_test.cc",
     "dom_token_list_test.cc",
     "element_test.cc",
-    "error_event_test.cc",
     "event_queue_test.cc",
-    "event_target_test.cc",
-    "event_test.cc",
     "font_cache_test.cc",
     "html_element_factory_test.cc",
     "html_element_test.cc",
@@ -449,7 +420,6 @@
     "storage_area_test.cc",
     "text_test.cc",
     "time_ranges_test.cc",
-    "url_utils_test.cc",
     "user_agent_data_test.cc",
     "window_test.cc",
     "window_timers_test.cc",
@@ -458,7 +428,7 @@
 
   deps = [
     ":dom",
-    ":dom_exception",
+    ":window_timers",
     "//cobalt/base",
     "//cobalt/browser:browser",
     "//cobalt/browser:generated_bindings",
@@ -472,13 +442,13 @@
     "//cobalt/loader",
     "//cobalt/media_session",
     "//cobalt/network_bridge",
-    "//cobalt/network_bridge",
     "//cobalt/render_tree",
     "//cobalt/script",
     "//cobalt/script/v8c:engine",
     "//cobalt/storage",
     "//cobalt/storage/store:memory_store",
     "//cobalt/test:run_all_unittests",
+    "//cobalt/web:dom_exception",
     "//nb",
     "//net:test_support",
     "//testing/gmock",
diff --git a/cobalt/dom/abort_signal.cc b/cobalt/dom/abort_signal.cc
index f313df3..ba9414e 100644
--- a/cobalt/dom/abort_signal.cc
+++ b/cobalt/dom/abort_signal.cc
@@ -14,7 +14,7 @@
 
 #include "cobalt/dom/abort_signal.h"
 
-#include "cobalt/dom/event.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace dom {
@@ -61,7 +61,7 @@
   following_signals_.clear();
 
   // 5. Fire an event named abort at signal.
-  DispatchEvent(new Event(base::Tokens::abort()));
+  DispatchEvent(new web::Event(base::Tokens::abort()));
 }
 
 void AbortSignal::TraceMembers(script::Tracer* tracer) {
diff --git a/cobalt/dom/abort_signal.h b/cobalt/dom/abort_signal.h
index e544e31..9084f15 100644
--- a/cobalt/dom/abort_signal.h
+++ b/cobalt/dom/abort_signal.h
@@ -18,18 +18,18 @@
 #include <vector>
 
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace dom {
 
 // This represents the DOM AbortSignal object.
 //    https://dom.spec.whatwg.org/#interface-AbortSignal
-class AbortSignal : public EventTarget {
+class AbortSignal : public web::EventTarget {
  public:
   explicit AbortSignal(script::EnvironmentSettings* settings)
-      : EventTarget(settings) {}
+      : web::EventTarget(settings) {}
 
   // Web API: AbortSignal
   bool aborted() const { return aborted_; }
diff --git a/cobalt/dom/animation_event.h b/cobalt/dom/animation_event.h
index 6cb16fa..6c537c0 100644
--- a/cobalt/dom/animation_event.h
+++ b/cobalt/dom/animation_event.h
@@ -19,14 +19,14 @@
 
 #include "cobalt/base/token.h"
 #include "cobalt/cssom/property_definitions.h"
-#include "cobalt/dom/event.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace dom {
 
 // The completion of a CSS Animation generates a corresponding DOM Event.
 //   https://www.w3.org/TR/2013/WD-css3-animations-20130219/#animation-events
-class AnimationEvent : public Event {
+class AnimationEvent : public web::Event {
  public:
   explicit AnimationEvent(const std::string& type)
       : Event(base::Token(type), kBubbles, kNotCancelable),
diff --git a/cobalt/dom/camera_3d.cc b/cobalt/dom/camera_3d.cc
index 7c2b3bb..8d75e92 100644
--- a/cobalt/dom/camera_3d.cc
+++ b/cobalt/dom/camera_3d.cc
@@ -43,7 +43,7 @@
 void Camera3D::Reset() { impl_->Reset(); }
 
 void Camera3D::StartOrientationEvents(
-    const base::WeakPtr<EventTarget>& target) {
+    const base::WeakPtr<web::EventTarget>& target) {
   if (!impl()) {
     return;
   }
@@ -71,7 +71,7 @@
 
 }  // namespace
 
-void Camera3D::FireOrientationEvent(base::WeakPtr<EventTarget> target) {
+void Camera3D::FireOrientationEvent(base::WeakPtr<web::EventTarget> target) {
   glm::dquat quaternion = glm::normalize(
       glm::dquat(impl()->GetOrientation()) *
       // The API assumes a different initial orientation (straight down instead
diff --git a/cobalt/dom/camera_3d.h b/cobalt/dom/camera_3d.h
index f24bf84..b0337d4 100644
--- a/cobalt/dom/camera_3d.h
+++ b/cobalt/dom/camera_3d.h
@@ -19,9 +19,9 @@
 
 #include "base/basictypes.h"
 #include "base/timer/timer.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/input/camera_3d.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace dom {
@@ -69,13 +69,13 @@
   // Custom, not in any spec.
   scoped_refptr<input::Camera3D> impl() { return impl_; }
 
-  void StartOrientationEvents(const base::WeakPtr<EventTarget>& target);
+  void StartOrientationEvents(const base::WeakPtr<web::EventTarget>& target);
   void StopOrientationEvents();
 
   DEFINE_WRAPPABLE_TYPE(Camera3D);
 
  private:
-  void FireOrientationEvent(const base::WeakPtr<EventTarget> target);
+  void FireOrientationEvent(const base::WeakPtr<web::EventTarget> target);
 
   // We delegate all calls to the implementation of Camera3D so that all camera
   // state is stored within an object that is *not* a script::Wrappable. This
diff --git a/cobalt/dom/captions/system_caption_settings.cc b/cobalt/dom/captions/system_caption_settings.cc
index 3a78d88..fa07b56 100644
--- a/cobalt/dom/captions/system_caption_settings.cc
+++ b/cobalt/dom/captions/system_caption_settings.cc
@@ -26,7 +26,7 @@
 #include "cobalt/dom/captions/caption_opacity_percentage.h"
 #include "cobalt/dom/captions/caption_state.h"
 #include "cobalt/dom/captions/system_caption_settings.h"
-#include "cobalt/dom/event_target.h"
+#include "cobalt/web/event_target.h"
 
 #include "starboard/accessibility.h"
 #include "starboard/memory.h"
@@ -501,15 +501,15 @@
   return (success) ? caption_settings.supports_override : false;
 }
 
-const EventTarget::EventListenerScriptValue* SystemCaptionSettings::onchanged()
-    const {
-  return EventTarget::GetAttributeEventListener(base::Tokens::change());
+const web::EventTarget::EventListenerScriptValue*
+SystemCaptionSettings::onchanged() const {
+  return web::EventTarget::GetAttributeEventListener(base::Tokens::change());
 }
 
 void SystemCaptionSettings::set_onchanged(
-    const EventTarget::EventListenerScriptValue& event_listener) {
-  EventTarget::SetAttributeEventListener(base::Tokens::change(),
-                                         event_listener);
+    const web::EventTarget::EventListenerScriptValue& event_listener) {
+  web::EventTarget::SetAttributeEventListener(base::Tokens::change(),
+                                              event_listener);
 }
 
 const char* SystemCaptionSettings::CaptionCharacterEdgeStyleToString(
diff --git a/cobalt/dom/captions/system_caption_settings.h b/cobalt/dom/captions/system_caption_settings.h
index f48c6f8..afcdd71 100644
--- a/cobalt/dom/captions/system_caption_settings.h
+++ b/cobalt/dom/captions/system_caption_settings.h
@@ -26,17 +26,17 @@
 #include "cobalt/dom/captions/caption_font_size_percentage.h"
 #include "cobalt/dom/captions/caption_opacity_percentage.h"
 #include "cobalt/dom/captions/caption_state.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace dom {
 namespace captions {
 
-class SystemCaptionSettings : public EventTarget {
+class SystemCaptionSettings : public web::EventTarget {
  public:
   explicit SystemCaptionSettings(script::EnvironmentSettings* settings)
-      : EventTarget(settings) {}
+      : web::EventTarget(settings) {}
 
   base::Optional<std::string> background_color();
   CaptionState background_color_state();
diff --git a/cobalt/dom/comment_test.cc b/cobalt/dom/comment_test.cc
index 7dee707..b68580d 100644
--- a/cobalt/dom/comment_test.cc
+++ b/cobalt/dom/comment_test.cc
@@ -18,8 +18,8 @@
 #include "cobalt/dom/element.h"
 #include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/html_element_context.h"
-#include "cobalt/dom/testing/gtest_workarounds.h"
 #include "cobalt/dom/text.h"
+#include "cobalt/web/testing/gtest_workarounds.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
diff --git a/cobalt/dom/crypto.cc b/cobalt/dom/crypto.cc
index a93cc8a..a800e58 100644
--- a/cobalt/dom/crypto.cc
+++ b/cobalt/dom/crypto.cc
@@ -14,7 +14,7 @@
 
 #include "cobalt/dom/crypto.h"
 
-#include "cobalt/dom/dom_exception.h"
+#include "cobalt/web/dom_exception.h"
 #include "crypto/random.h"
 
 namespace cobalt {
@@ -38,12 +38,14 @@
   if (array.IsEmpty()) {
     // TODO: Also throw exception if element type of the array is
     // not one of the integer types.
-    DOMException::Raise(DOMException::kTypeMismatchErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kTypeMismatchErr,
+                             exception_state);
     // Return value should be ignored.
     return script::Handle<script::ArrayBufferView>();
   }
   if (array->ByteLength() > kMaxArrayLengthInBytes) {
-    DOMException::Raise(DOMException::kQuotaExceededErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kQuotaExceededErr,
+                             exception_state);
     // Return value should be ignored.
     return script::Handle<script::ArrayBufferView>();
   }
diff --git a/cobalt/dom/crypto_test.cc b/cobalt/dom/crypto_test.cc
index f629920..3ce0a0a 100644
--- a/cobalt/dom/crypto_test.cc
+++ b/cobalt/dom/crypto_test.cc
@@ -18,7 +18,6 @@
 
 #include "base/test/scoped_task_environment.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/script/array_buffer_view.h"
 #include "cobalt/script/global_environment.h"
@@ -26,6 +25,7 @@
 #include "cobalt/script/script_exception.h"
 #include "cobalt/script/testing/mock_exception_state.h"
 #include "cobalt/script/typed_arrays.h"
+#include "cobalt/web/dom_exception.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
@@ -109,8 +109,9 @@
       crypto->GetRandomValues(empty_handle, &exception_state);
   EXPECT_TRUE(result.IsEmpty());
   ASSERT_TRUE(exception);
-  EXPECT_EQ(DOMException::kTypeMismatchErr,
-            base::polymorphic_downcast<DOMException*>(exception.get())->code());
+  EXPECT_EQ(
+      web::DOMException::kTypeMismatchErr,
+      base::polymorphic_downcast<web::DOMException*>(exception.get())->code());
 }
 
 TEST(CryptoTest, LargeArray) {
@@ -137,8 +138,9 @@
       crypto->GetRandomValues(casted_array65537, &exception_state);
   EXPECT_TRUE(result.IsEmpty());
   ASSERT_TRUE(exception);
-  EXPECT_EQ(DOMException::kQuotaExceededErr,
-            base::polymorphic_downcast<DOMException*>(exception.get())->code());
+  EXPECT_EQ(
+      web::DOMException::kQuotaExceededErr,
+      base::polymorphic_downcast<web::DOMException*>(exception.get())->code());
 
   // Also ensure that we can work with array whose length is exactly 65536.
   script::Handle<script::Uint8Array> array65536 =
diff --git a/cobalt/dom/custom_event_test.cc b/cobalt/dom/custom_event_test.cc
deleted file mode 100644
index 4a3c1e7..0000000
--- a/cobalt/dom/custom_event_test.cc
+++ /dev/null
@@ -1,206 +0,0 @@
-// Copyright 2017 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "cobalt/dom/custom_event.h"
-
-#include <memory>
-#include <string>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/optional.h"
-#include "base/threading/platform_thread.h"
-#include "cobalt/css_parser/parser.h"
-#include "cobalt/cssom/viewport_size.h"
-#include "cobalt/dom/custom_event_init.h"
-#include "cobalt/dom/local_storage_database.h"
-#include "cobalt/dom/testing/gtest_workarounds.h"
-#include "cobalt/dom/testing/stub_environment_settings.h"
-#include "cobalt/dom/window.h"
-#include "cobalt/dom_parser/parser.h"
-#include "cobalt/loader/fetcher_factory.h"
-#include "cobalt/loader/loader_factory.h"
-#include "cobalt/script/global_environment.h"
-#include "cobalt/script/javascript_engine.h"
-#include "cobalt/script/source_code.h"
-#include "cobalt/script/testing/fake_script_value.h"
-#include "cobalt/script/value_handle.h"
-#include "cobalt/script/wrappable.h"
-#include "nb/pointer_arithmetic.h"
-#include "starboard/window.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using cobalt::cssom::ViewportSize;
-using cobalt::script::testing::FakeScriptValue;
-
-namespace cobalt {
-namespace dom {
-
-class MockErrorCallback
-    : public base::Callback<void(const base::Optional<std::string>&)> {
- public:
-  MOCK_METHOD1(Run, void(const base::Optional<std::string>&));
-};
-
-namespace {
-class CustomEventTest : public ::testing::Test {
- public:
-  CustomEventTest()
-      : message_loop_(base::MessageLoop::TYPE_DEFAULT),
-        environment_settings_(new testing::StubEnvironmentSettings),
-        css_parser_(css_parser::Parser::Create()),
-        dom_parser_(new dom_parser::Parser(mock_error_callback_)),
-        fetcher_factory_(new loader::FetcherFactory(NULL)),
-        loader_factory_(new loader::LoaderFactory(
-            "Test", fetcher_factory_.get(), NULL, null_debugger_hooks_, 0,
-            base::ThreadPriority::DEFAULT)),
-        local_storage_database_(NULL),
-        url_("about:blank") {
-    engine_ = script::JavaScriptEngine::CreateEngine();
-    global_environment_ = engine_->CreateGlobalEnvironment();
-    window_ = new Window(
-        environment_settings_.get(), ViewportSize(1920, 1080),
-        base::kApplicationStateStarted, css_parser_.get(), dom_parser_.get(),
-        fetcher_factory_.get(), loader_factory_.get(), NULL, NULL, NULL, NULL,
-        NULL, NULL, &local_storage_database_, NULL, NULL, NULL, NULL,
-        global_environment_->script_value_factory(), NULL, NULL, url_, "", NULL,
-        "en-US", "en", base::Callback<void(const GURL&)>(),
-        base::Bind(&MockErrorCallback::Run,
-                   base::Unretained(&mock_error_callback_)),
-        NULL, network_bridge::PostSender(), csp::kCSPRequired,
-        kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
-        base::Closure() /* ran_animation_frame_callbacks */,
-        dom::Window::CloseCallback() /* window_close */,
-        base::Closure() /* window_minimize */, NULL, NULL,
-        dom::Window::OnStartDispatchEventCallback(),
-        dom::Window::OnStopDispatchEventCallback(),
-        dom::ScreenshotManager::ProvideScreenshotFunctionCallback(), NULL);
-    global_environment_->CreateGlobalObject(window_,
-                                            environment_settings_.get());
-  }
-
-  bool EvaluateScript(const std::string& js_code, std::string* result);
-
- private:
-  base::MessageLoop message_loop_;
-  base::NullDebuggerHooks null_debugger_hooks_;
-  std::unique_ptr<script::JavaScriptEngine> engine_;
-  const std::unique_ptr<testing::StubEnvironmentSettings> environment_settings_;
-  scoped_refptr<script::GlobalEnvironment> global_environment_;
-  MockErrorCallback mock_error_callback_;
-  std::unique_ptr<css_parser::Parser> css_parser_;
-  std::unique_ptr<dom_parser::Parser> dom_parser_;
-  std::unique_ptr<loader::FetcherFactory> fetcher_factory_;
-  std::unique_ptr<loader::LoaderFactory> loader_factory_;
-  dom::LocalStorageDatabase local_storage_database_;
-  GURL url_;
-  scoped_refptr<Window> window_;
-};
-
-bool CustomEventTest::EvaluateScript(const std::string& js_code,
-                                     std::string* result) {
-  DCHECK(global_environment_);
-  DCHECK(result);
-  scoped_refptr<script::SourceCode> source_code =
-      script::SourceCode::CreateSourceCode(
-          js_code, base::SourceLocation(__FILE__, __LINE__, 1));
-
-  global_environment_->EnableEval();
-  global_environment_->SetReportEvalCallback(base::Closure());
-  bool succeeded = global_environment_->EvaluateScript(source_code, result);
-  return succeeded;
-}
-}  // namespace
-
-TEST_F(CustomEventTest, ConstructorWithEventTypeString) {
-  scoped_refptr<CustomEvent> event = new CustomEvent("mytestevent");
-
-  EXPECT_EQ("mytestevent", event->type());
-  EXPECT_EQ(NULL, event->target().get());
-  EXPECT_EQ(NULL, event->current_target().get());
-  EXPECT_EQ(Event::kNone, event->event_phase());
-  EXPECT_FALSE(event->bubbles());
-  EXPECT_FALSE(event->cancelable());
-  EXPECT_FALSE(event->default_prevented());
-  EXPECT_FALSE(event->IsBeingDispatched());
-  EXPECT_FALSE(event->propagation_stopped());
-  EXPECT_FALSE(event->immediate_propagation_stopped());
-  EXPECT_EQ(NULL, event->detail());
-}
-
-TEST_F(CustomEventTest, ConstructorWithEventTypeAndDefaultInitDict) {
-  CustomEventInit init;
-  scoped_refptr<CustomEvent> event = new CustomEvent("mytestevent", init);
-
-  EXPECT_EQ("mytestevent", event->type());
-  EXPECT_EQ(NULL, event->target().get());
-  EXPECT_EQ(NULL, event->current_target().get());
-  EXPECT_EQ(Event::kNone, event->event_phase());
-  EXPECT_FALSE(event->bubbles());
-  EXPECT_FALSE(event->cancelable());
-  EXPECT_FALSE(event->default_prevented());
-  EXPECT_FALSE(event->IsBeingDispatched());
-  EXPECT_FALSE(event->propagation_stopped());
-  EXPECT_FALSE(event->immediate_propagation_stopped());
-  EXPECT_EQ(NULL, event->detail());
-}
-
-TEST_F(CustomEventTest, ConstructorWithEventTypeAndCustomInitDict) {
-  std::string result;
-  bool success = EvaluateScript(
-      "var event = new CustomEvent('dog', "
-      "    {'bubbles':true, "
-      "     'cancelable':true, "
-      "     'detail':{'cobalt':'rulez'}});"
-      "if (event.type == 'dog' &&"
-      "    event.bubbles == true &&"
-      "    event.cancelable == true) "
-      "    event.detail.cobalt;",
-      &result);
-  EXPECT_EQ("rulez", result);
-
-  if (!success) {
-    DLOG(ERROR) << "Failed to evaluate test: "
-                << "\"" << result << "\"";
-  } else {
-    LOG(INFO) << "Test result : "
-              << "\"" << result << "\"";
-  }
-}
-
-TEST_F(CustomEventTest, InitCustomEvent) {
-  std::string result;
-  bool success = EvaluateScript(
-      "var event = new CustomEvent('cat');\n"
-      "event.initCustomEvent('dog', true, true, {cobalt:'rulez'});"
-      "if (event.type == 'dog' &&"
-      "    event.detail &&"
-      "    event.bubbles == true &&"
-      "    event.cancelable == true) "
-      "    event.detail.cobalt;",
-      &result);
-  EXPECT_EQ("rulez", result);
-
-  if (!success) {
-    DLOG(ERROR) << "Failed to evaluate test: "
-                << "\"" << result << "\"";
-  } else {
-    LOG(INFO) << "Test result : "
-              << "\"" << result << "\"";
-  }
-}
-
-}  // namespace dom
-}  // namespace cobalt
diff --git a/cobalt/dom/device_orientation_event.h b/cobalt/dom/device_orientation_event.h
index 0379f63..fe81d7b 100644
--- a/cobalt/dom/device_orientation_event.h
+++ b/cobalt/dom/device_orientation_event.h
@@ -21,8 +21,8 @@
 
 #include "base/optional.h"
 #include "cobalt/dom/device_orientation_event_init.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace dom {
@@ -33,7 +33,7 @@
 // meaning each of the rotations is done on the axis of the rotated system
 // resulting of the previous rotation.
 //   https://www.w3.org/TR/2016/CR-orientation-event-20160818/
-class DeviceOrientationEvent : public Event {
+class DeviceOrientationEvent : public web::Event {
  public:
   DeviceOrientationEvent();
   explicit DeviceOrientationEvent(const std::string& type);
diff --git a/cobalt/dom/document.cc b/cobalt/dom/document.cc
index e2cdd67..92e9a1f 100644
--- a/cobalt/dom/document.cc
+++ b/cobalt/dom/document.cc
@@ -35,10 +35,6 @@
 #include "cobalt/cssom/viewport_size.h"
 #include "cobalt/dom/benchmark_stat_names.h"
 #include "cobalt/dom/comment.h"
-#include "cobalt/dom/csp_delegate.h"
-#include "cobalt/dom/csp_delegate_factory.h"
-#include "cobalt/dom/custom_event.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_implementation.h"
 #include "cobalt/dom/element.h"
 #include "cobalt/dom/font_cache.h"
@@ -65,6 +61,10 @@
 #include "cobalt/dom/wheel_event.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/script/global_environment.h"
+#include "cobalt/web/csp_delegate.h"
+#include "cobalt/web/csp_delegate_factory.h"
+#include "cobalt/web/custom_event.h"
+#include "cobalt/web/dom_exception.h"
 #include "nb/memory_scope.h"
 
 using cobalt::cssom::ViewportSize;
@@ -113,9 +113,9 @@
     SetViewport(*options.viewport_size);
   }
 
-  std::unique_ptr<CspViolationReporter> violation_reporter(
-      new CspViolationReporter(this, options.post_sender));
-  csp_delegate_ = CspDelegateFactory::GetInstance()->Create(
+  std::unique_ptr<web::CspViolationReporter> violation_reporter(
+      new web::CspViolationReporter(this, options.post_sender));
+  csp_delegate_ = web::CspDelegateFactory::GetInstance()->Create(
       options.csp_enforcement_mode, std::move(violation_reporter), options.url,
       options.require_csp, options.csp_policy_changed_callback,
       options.csp_insecure_allowed_token);
@@ -124,8 +124,9 @@
 
   location_ = new Location(
       options.url, options.hashchange_callback, options.navigation_callback,
-      base::Bind(&CspDelegate::CanLoad, base::Unretained(csp_delegate_.get()),
-                 CspDelegate::kLocation),
+      base::Bind(&web::CspDelegate::CanLoad,
+                 base::Unretained(csp_delegate_.get()),
+                 web::CspDelegate::kLocation),
       base::Bind(&Document::SetNavigationType, base::Unretained(this)));
 
   font_cache_.reset(new FontCache(
@@ -137,15 +138,15 @@
   if (HasBrowsingContext()) {
     if (html_element_context_->remote_typeface_cache()) {
       html_element_context_->remote_typeface_cache()->set_security_callback(
-          base::Bind(&CspDelegate::CanLoad,
+          base::Bind(&web::CspDelegate::CanLoad,
                      base::Unretained(csp_delegate_.get()),
-                     CspDelegate::kFont));
+                     web::CspDelegate::kFont));
     }
 
     if (html_element_context_->image_cache()) {
       html_element_context_->image_cache()->set_security_callback(base::Bind(
-          &CspDelegate::CanLoad, base::Unretained(csp_delegate_.get()),
-          CspDelegate::kImage));
+          &web::CspDelegate::CanLoad, base::Unretained(csp_delegate_.get()),
+          web::CspDelegate::kImage));
     }
 
     ready_state_ = kDocumentReadyStateLoading;
@@ -232,7 +233,7 @@
   return new Comment(this, data);
 }
 
-scoped_refptr<Event> Document::CreateEvent(
+scoped_refptr<web::Event> Document::CreateEvent(
     const std::string& interface_name,
     script::ExceptionState* exception_state) {
   TRACK_MEMORY_SCOPE("DOM");
@@ -241,28 +242,28 @@
   if (base::strcasecmp(interface_name.c_str(), "event") == 0 ||
       base::strcasecmp(interface_name.c_str(), "events") == 0 ||
       base::strcasecmp(interface_name.c_str(), "htmlevents") == 0) {
-    return new Event(Event::Uninitialized);
+    return new web::Event(web::Event::Uninitialized);
   } else if (base::strcasecmp(interface_name.c_str(), "keyboardevent") == 0 ||
              base::strcasecmp(interface_name.c_str(), "keyevents") == 0) {
-    return new KeyboardEvent(Event::Uninitialized);
+    return new KeyboardEvent(web::Event::Uninitialized);
   } else if (base::strcasecmp(interface_name.c_str(), "messageevent") == 0) {
-    return new MessageEvent(Event::Uninitialized);
+    return new MessageEvent(web::Event::Uninitialized);
   } else if (base::strcasecmp(interface_name.c_str(), "mouseevent") == 0 ||
              base::strcasecmp(interface_name.c_str(), "mouseevents") == 0) {
-    return new MouseEvent(Event::Uninitialized);
+    return new MouseEvent(web::Event::Uninitialized);
   } else if (base::strcasecmp(interface_name.c_str(), "uievent") == 0 ||
              base::strcasecmp(interface_name.c_str(), "uievents") == 0) {
-    return new UIEvent(Event::Uninitialized);
+    return new UIEvent(web::Event::Uninitialized);
   } else if (base::strcasecmp(interface_name.c_str(), "wheelevent") == 0) {
     // This not in the spec, but commonly implemented to create a WheelEvent.
     //   https://www.w3.org/TR/2016/WD-uievents-20160804/#interface-wheelevent
-    return new WheelEvent(Event::Uninitialized);
+    return new WheelEvent(web::Event::Uninitialized);
   } else if (base::strcasecmp(interface_name.c_str(), "customevent") == 0) {
-    return new CustomEvent(Event::Uninitialized);
+    return new web::CustomEvent(web::Event::Uninitialized);
   }
 
-  DOMException::Raise(
-      DOMException::kNotSupportedErr,
+  web::DOMException::Raise(
+      web::DOMException::kNotSupportedErr,
       "document.createEvent does not support \"" + interface_name + "\".",
       exception_state);
 
@@ -445,9 +446,9 @@
     return;
   }
   if (location_->GetOriginAsObject().is_opaque()) {
-    DOMException::Raise(DOMException::kSecurityErr,
-                        "Document origin is opaque, cookie setting failed",
-                        exception_state);
+    web::DOMException::Raise(web::DOMException::kSecurityErr,
+                             "Document origin is opaque, cookie setting failed",
+                             exception_state);
     return;
   }
   if (cookie_jar_) {
@@ -463,9 +464,9 @@
     return "";
   }
   if (location_->GetOriginAsObject().is_opaque()) {
-    DOMException::Raise(DOMException::kSecurityErr,
-                        "Document origin is opaque, cookie getting failed",
-                        exception_state);
+    web::DOMException::Raise(web::DOMException::kSecurityErr,
+                             "Document origin is opaque, cookie getting failed",
+                             exception_state);
     return "";
   }
   if (cookie_jar_) {
@@ -1024,8 +1025,9 @@
 }
 
 void Document::OnVisibilityStateChanged(VisibilityState visibility_state) {
-  DispatchEvent(new Event(base::Tokens::visibilitychange(), Event::kBubbles,
-                          Event::kNotCancelable));
+  DispatchEvent(new web::Event(base::Tokens::visibilitychange(),
+                               web::Event::kBubbles,
+                               web::Event::kNotCancelable));
 
   // Refocus the previously-focused UI navigation item (if any).
   if (visibility_state == kVisibilityStateVisible) {
@@ -1071,8 +1073,8 @@
   frozenness_ = true;
 
   // 2. Fire an event named freeze at doc.
-  DispatchEvent(new Event(base::Tokens::freeze(), Event::kBubbles,
-                          Event::kNotCancelable));
+  DispatchEvent(new web::Event(base::Tokens::freeze(), web::Event::kBubbles,
+                               web::Event::kNotCancelable));
 
   // 3. Let elements be all media elements that are shadow-including
   //    documents of doc, in shadow-including tree order.
@@ -1120,8 +1122,8 @@
   }
 
   // 3. Fire an event named resume at doc.
-  DispatchEvent(new Event(base::Tokens::resume(), Event::kBubbles,
-                          Event::kNotCancelable));
+  DispatchEvent(new web::Event(base::Tokens::resume(), web::Event::kBubbles,
+                               web::Event::kNotCancelable));
 
   // 4. Set doc's frozeness state to false.
   frozenness_ = false;
@@ -1246,7 +1248,7 @@
 
   // Dispatch the readystatechange event (before the load event), since we
   // have changed the document ready state.
-  DispatchEvent(new Event(base::Tokens::readystatechange()));
+  DispatchEvent(new web::Event(base::Tokens::readystatechange()));
 
   // Set document load timing info's load event start time before user agent
   // dispatch the load event for the document.
@@ -1255,7 +1257,7 @@
   }
 
   // Dispatch the document's onload event.
-  DispatchEvent(new Event(base::Tokens::load()));
+  DispatchEvent(new web::Event(base::Tokens::load()));
 
   // Set document load timing info's load event end time after user agent
   // completes handling the load event for the document.
diff --git a/cobalt/dom/document.h b/cobalt/dom/document.h
index 6bf0ee6..b50a8d9 100644
--- a/cobalt/dom/document.h
+++ b/cobalt/dom/document.h
@@ -40,11 +40,9 @@
 #include "cobalt/cssom/style_sheet_list.h"
 #include "cobalt/cssom/viewport_size.h"
 #include "cobalt/dom/application_lifecycle_state.h"
-#include "cobalt/dom/csp_delegate_type.h"
 #include "cobalt/dom/document_load_timing_info.h"
 #include "cobalt/dom/document_ready_state.h"
 #include "cobalt/dom/document_timeline.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/dom/html_element_context.h"
 #include "cobalt/dom/intersection_observer_task_manager.h"
 #include "cobalt/dom/location.h"
@@ -57,6 +55,9 @@
 #include "cobalt/network_bridge/net_poster.h"
 #include "cobalt/script/exception_state.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/csp_delegate.h"
+#include "cobalt/web/csp_delegate_type.h"
+#include "cobalt/web/event.h"
 #include "starboard/time.h"
 #include "url/gurl.h"
 
@@ -64,7 +65,6 @@
 namespace dom {
 
 class Comment;
-class CspDelegate;
 class DOMImplementation;
 class Element;
 class FontCache;
@@ -108,12 +108,12 @@
     Options()
         : window(NULL),
           cookie_jar(NULL),
-          csp_enforcement_mode(kCspEnforcementEnable) {}
+          csp_enforcement_mode(web::kCspEnforcementEnable) {}
     explicit Options(const GURL& url_value)
         : url(url_value),
           window(NULL),
           cookie_jar(NULL),
-          csp_enforcement_mode(kCspEnforcementEnable) {}
+          csp_enforcement_mode(web::kCspEnforcementEnable) {}
     Options(const GURL& url_value, Window* window,
             const base::Closure& hashchange_callback,
             const scoped_refptr<base::BasicClock>& navigation_start_clock_value,
@@ -123,7 +123,7 @@
             network_bridge::CookieJar* cookie_jar,
             const network_bridge::PostSender& post_sender,
             csp::CSPHeaderPolicy require_csp,
-            CspEnforcementType csp_enforcement_mode,
+            web::CspEnforcementType csp_enforcement_mode,
             const base::Closure& csp_policy_changed_callback,
             int csp_insecure_allowed_token = 0, int dom_max_element_depth = 0)
         : url(url_value),
@@ -151,7 +151,7 @@
     network_bridge::CookieJar* cookie_jar;
     network_bridge::PostSender post_sender;
     csp::CSPHeaderPolicy require_csp;
-    CspEnforcementType csp_enforcement_mode;
+    web::CspEnforcementType csp_enforcement_mode;
     base::Closure csp_policy_changed_callback;
     int csp_insecure_allowed_token;
     int dom_max_element_depth;
@@ -188,8 +188,9 @@
   scoped_refptr<Text> CreateTextNode(const std::string& data);
   scoped_refptr<Comment> CreateComment(const std::string& data);
 
-  scoped_refptr<Event> CreateEvent(const std::string& interface_name,
-                                   script::ExceptionState* exception_state);
+  scoped_refptr<web::Event> CreateEvent(
+      const std::string& interface_name,
+      script::ExceptionState* exception_state);
 
   // Web API: NonElementParentNode (implements)
   //   https://www.w3.org/TR/2014/WD-dom-20140710/#interface-nonelementparentnode
@@ -385,7 +386,7 @@
     return navigation_start_clock_;
   }
 
-  CspDelegate* csp_delegate() const { return csp_delegate_.get(); }
+  web::CspDelegate* csp_delegate() const { return csp_delegate_.get(); }
 
   // Triggers a synchronous layout.
   scoped_refptr<render_tree::Node> DoSynchronousLayoutAndGetRenderTree();
@@ -594,7 +595,7 @@
   // Viewport size.
   base::Optional<cssom::ViewportSize> viewport_size_;
   // Content Security Policy enforcement for this document.
-  std::unique_ptr<CspDelegate> csp_delegate_;
+  std::unique_ptr<web::CspDelegate> csp_delegate_;
   network_bridge::CookieJar* cookie_jar_;
   // Associated location object.
   scoped_refptr<Location> location_;
diff --git a/cobalt/dom/document_test.cc b/cobalt/dom/document_test.cc
index 6144fc9..bedb598 100644
--- a/cobalt/dom/document_test.cc
+++ b/cobalt/dom/document_test.cc
@@ -21,8 +21,6 @@
 #include "cobalt/cssom/css_style_sheet.h"
 #include "cobalt/dom/attr.h"
 #include "cobalt/dom/comment.h"
-#include "cobalt/dom/custom_event.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_implementation.h"
 #include "cobalt/dom/dom_stat_tracker.h"
 #include "cobalt/dom/element.h"
@@ -37,12 +35,14 @@
 #include "cobalt/dom/message_event.h"
 #include "cobalt/dom/mouse_event.h"
 #include "cobalt/dom/node_list.h"
-#include "cobalt/dom/testing/gtest_workarounds.h"
 #include "cobalt/dom/testing/html_collection_testing.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
 #include "cobalt/dom/text.h"
 #include "cobalt/dom/ui_event.h"
 #include "cobalt/script/testing/mock_exception_state.h"
+#include "cobalt/web/custom_event.h"
+#include "cobalt/web/dom_exception.h"
+#include "cobalt/web/testing/gtest_workarounds.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
@@ -73,11 +73,11 @@
 DocumentTest::DocumentTest()
     : css_parser_(css_parser::Parser::Create()),
       dom_stat_tracker_(new DomStatTracker("DocumentTest")),
-      html_element_context_(
-          &environment_settings_, NULL, NULL, css_parser_.get(), NULL, NULL,
-          NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-          dom_stat_tracker_.get(), "", base::kApplicationStateStarted, NULL,
-          NULL) {
+      html_element_context_(&environment_settings_, NULL, NULL,
+                            css_parser_.get(), NULL, NULL, NULL, NULL, NULL,
+                            NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                            dom_stat_tracker_.get(), "",
+                            base::kApplicationStateStarted, NULL, NULL) {
   EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
 }
 
@@ -162,7 +162,8 @@
   scoped_refptr<Document> document = new Document(&html_element_context_);
 
   // Create an Event, the name is case insensitive.
-  scoped_refptr<Event> event = document->CreateEvent("EvEnT", &exception_state);
+  scoped_refptr<web::Event> event =
+      document->CreateEvent("EvEnT", &exception_state);
   EXPECT_TRUE(event);
   EXPECT_FALSE(event->initialized_flag());
 
@@ -185,11 +186,11 @@
   scoped_refptr<Document> document = new Document(&html_element_context_);
 
   // Create an Event, the name is case insensitive.
-  scoped_refptr<Event> event =
+  scoped_refptr<web::Event> event =
       document->CreateEvent("CuStOmEvEnT", &exception_state);
   EXPECT_TRUE(event);
   EXPECT_FALSE(event->initialized_flag());
-  EXPECT_TRUE(base::polymorphic_downcast<CustomEvent*>(event.get()));
+  EXPECT_TRUE(base::polymorphic_downcast<web::CustomEvent*>(event.get()));
 }
 
 TEST_F(DocumentTest, CreateEventUIEvent) {
@@ -198,7 +199,7 @@
   scoped_refptr<Document> document = new Document(&html_element_context_);
 
   // Create an Event, the name is case insensitive.
-  scoped_refptr<Event> event =
+  scoped_refptr<web::Event> event =
       document->CreateEvent("UiEvEnT", &exception_state);
   EXPECT_TRUE(event);
   EXPECT_FALSE(event->initialized_flag());
@@ -216,7 +217,7 @@
   scoped_refptr<Document> document = new Document(&html_element_context_);
 
   // Create an Event, the name is case insensitive.
-  scoped_refptr<Event> event =
+  scoped_refptr<web::Event> event =
       document->CreateEvent("KeYbOaRdEvEnT", &exception_state);
   EXPECT_TRUE(event);
   EXPECT_FALSE(event->initialized_flag());
@@ -234,7 +235,7 @@
   scoped_refptr<Document> document = new Document(&html_element_context_);
 
   // Create an Event, the name is case insensitive.
-  scoped_refptr<Event> event =
+  scoped_refptr<web::Event> event =
       document->CreateEvent("MeSsAgEeVeNt", &exception_state);
   EXPECT_TRUE(event);
   EXPECT_FALSE(event->initialized_flag());
@@ -247,7 +248,7 @@
   scoped_refptr<Document> document = new Document(&html_element_context_);
 
   // Create an Event, the name is case insensitive.
-  scoped_refptr<Event> event =
+  scoped_refptr<web::Event> event =
       document->CreateEvent("MoUsEeVeNt", &exception_state);
   EXPECT_TRUE(event);
   EXPECT_FALSE(event->initialized_flag());
@@ -266,13 +267,14 @@
 
   EXPECT_CALL(exception_state, SetException(_))
       .WillOnce(SaveArg<0>(&exception));
-  scoped_refptr<Event> event =
+  scoped_refptr<web::Event> event =
       document->CreateEvent("Event Not Supported", &exception_state);
 
   EXPECT_FALSE(event);
   ASSERT_TRUE(exception);
-  EXPECT_EQ(DOMException::kNotSupportedErr,
-            base::polymorphic_downcast<DOMException*>(exception.get())->code());
+  EXPECT_EQ(
+      web::DOMException::kNotSupportedErr,
+      base::polymorphic_downcast<web::DOMException*>(exception.get())->code());
 }
 
 TEST_F(DocumentTest, GetElementsByClassName) {
diff --git a/cobalt/dom/dom_animatable.h b/cobalt/dom/dom_animatable.h
index 4c2c8b9..346c93e 100644
--- a/cobalt/dom/dom_animatable.h
+++ b/cobalt/dom/dom_animatable.h
@@ -17,9 +17,9 @@
 
 #include "base/compiler_specific.h"
 #include "cobalt/dom/document.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/dom/html_element.h"
 #include "cobalt/dom/pseudo_element.h"
+#include "cobalt/web/event.h"
 #include "cobalt/web_animations/animatable.h"
 
 namespace cobalt {
diff --git a/cobalt/dom/dom_parser_test.cc b/cobalt/dom/dom_parser_test.cc
index 25fad98..fb1601a 100644
--- a/cobalt/dom/dom_parser_test.cc
+++ b/cobalt/dom/dom_parser_test.cc
@@ -22,10 +22,10 @@
 #include "cobalt/dom/html_element_context.h"
 #include "cobalt/dom/testing/stub_css_parser.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
-#include "cobalt/dom/testing/stub_script_runner.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/loader_factory.h"
+#include "cobalt/script/testing/stub_script_runner.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
@@ -42,7 +42,7 @@
   loader::LoaderFactory loader_factory_;
   testing::StubCSSParser stub_css_parser_;
   std::unique_ptr<dom_parser::Parser> dom_parser_parser_;
-  testing::StubScriptRunner stub_script_runner_;
+  script::testing::StubScriptRunner stub_script_runner_;
   HTMLElementContext html_element_context_;
   scoped_refptr<DOMParser> dom_parser_;
 };
@@ -65,8 +65,7 @@
           NULL /* remote_typeface_cache */, NULL /* mesh_cache */,
           NULL /* dom_stat_tracker */, "" /* language */,
           base::kApplicationStateStarted,
-          NULL /* synchronous_loader_interrupt */,
-          NULL /* performance */),
+          NULL /* synchronous_loader_interrupt */, NULL /* performance */),
       dom_parser_(new DOMParser(&html_element_context_)) {}
 
 TEST_F(DOMParserTest, ParsesXML) {
diff --git a/cobalt/dom/dom_settings.cc b/cobalt/dom/dom_settings.cc
index 43e614d..a8a2171 100644
--- a/cobalt/dom/dom_settings.cc
+++ b/cobalt/dom/dom_settings.cc
@@ -15,8 +15,8 @@
 #include "cobalt/dom/dom_settings.h"
 
 #include "cobalt/dom/document.h"
-#include "cobalt/dom/url_utils.h"
 #include "cobalt/dom/window.h"
+#include "cobalt/web/url_utils.h"
 
 namespace cobalt {
 namespace dom {
@@ -27,15 +27,14 @@
     media::CanPlayTypeHandler* can_play_type_handler,
     const media::DecoderBufferMemoryInfo* decoder_buffer_memory_info,
     MutationObserverTaskManager* mutation_observer_task_manager,
-    worker::ServiceWorkerJobs* service_worker_jobs, const Options& options)
+    const Options& options)
     : EnvironmentSettings(debugger_hooks),
       max_dom_element_depth_(max_dom_element_depth),
       microphone_options_(options.microphone_options),
       media_source_registry_(media_source_registry),
       can_play_type_handler_(can_play_type_handler),
       decoder_buffer_memory_info_(decoder_buffer_memory_info),
-      mutation_observer_task_manager_(mutation_observer_task_manager),
-      service_worker_jobs_(service_worker_jobs) {}
+      mutation_observer_task_manager_(mutation_observer_task_manager) {}
 
 DOMSettings::~DOMSettings() {}
 
diff --git a/cobalt/dom/dom_settings.h b/cobalt/dom/dom_settings.h
index 75a74f4..0c81c39 100644
--- a/cobalt/dom/dom_settings.h
+++ b/cobalt/dom/dom_settings.h
@@ -18,13 +18,12 @@
 #include "base/memory/ref_counted.h"
 #include "cobalt/base/debugger_hooks.h"
 #include "cobalt/dom/mutation_observer_task_manager.h"
-#include "cobalt/dom/url_registry.h"
-#include "cobalt/dom/url_utils.h"
 #include "cobalt/media/can_play_type_handler.h"
 #include "cobalt/media/decoder_buffer_memory_info.h"
 #include "cobalt/speech/microphone.h"
 #include "cobalt/web/environment_settings.h"
-#include "cobalt/worker/service_worker_jobs.h"
+#include "cobalt/web/url_registry.h"
+#include "cobalt/web/url_utils.h"
 
 namespace cobalt {
 
@@ -46,7 +45,7 @@
 // that ask for it in their IDL custom attributes.
 class DOMSettings : public web::EnvironmentSettings {
  public:
-  typedef UrlRegistry<MediaSource> MediaSourceRegistry;
+  typedef web::UrlRegistry<MediaSource> MediaSourceRegistry;
   // Hold optional settings for DOMSettings.
   struct Options {
     // Microphone options.
@@ -59,7 +58,6 @@
               media::CanPlayTypeHandler* can_play_type_handler,
               const media::DecoderBufferMemoryInfo* decoder_buffer_memory_info,
               MutationObserverTaskManager* mutation_observer_task_manager,
-              worker::ServiceWorkerJobs* service_worker_jobs,
               const Options& options = Options());
   ~DOMSettings() override;
 
@@ -74,21 +72,6 @@
   MediaSourceRegistry* media_source_registry() const {
     return media_source_registry_;
   }
-  std::size_t media_source_size_limit() const {
-    return decoder_buffer_memory_info_
-               ? decoder_buffer_memory_info_->GetMaximumMemoryCapacity()
-               : 0;
-  }
-  std::size_t total_media_source_size() const {
-    return decoder_buffer_memory_info_
-               ? decoder_buffer_memory_info_->GetCurrentMemoryCapacity()
-               : 0;
-  }
-  std::size_t used_media_source_memory_size() const {
-    return decoder_buffer_memory_info_
-               ? decoder_buffer_memory_info_->GetAllocatedMemory()
-               : 0;
-  }
   void set_decoder_buffer_memory_info(
       const media::DecoderBufferMemoryInfo* decoder_buffer_memory_info) {
     decoder_buffer_memory_info_ = decoder_buffer_memory_info;
@@ -96,13 +79,13 @@
   media::CanPlayTypeHandler* can_play_type_handler() const {
     return can_play_type_handler_;
   }
+  const media::DecoderBufferMemoryInfo* decoder_buffer_memory_info() {
+    return decoder_buffer_memory_info_;
+  }
   MutationObserverTaskManager* mutation_observer_task_manager() const {
     return mutation_observer_task_manager_;
   }
 
-  worker::ServiceWorkerJobs* service_worker_jobs() const {
-    return service_worker_jobs_;
-  }
   // Return's document's origin.
   loader::Origin document_origin() const;
 
@@ -114,7 +97,6 @@
   media::CanPlayTypeHandler* can_play_type_handler_;
   const media::DecoderBufferMemoryInfo* decoder_buffer_memory_info_;
   MutationObserverTaskManager* mutation_observer_task_manager_;
-  worker::ServiceWorkerJobs* service_worker_jobs_;
   DISALLOW_COPY_AND_ASSIGN(DOMSettings);
 };
 
diff --git a/cobalt/dom/dom_stat_tracker.cc b/cobalt/dom/dom_stat_tracker.cc
index 586f8a3..093889f 100644
--- a/cobalt/dom/dom_stat_tracker.cc
+++ b/cobalt/dom/dom_stat_tracker.cc
@@ -20,27 +20,17 @@
 namespace dom {
 
 DomStatTracker::DomStatTracker(const std::string& name)
-    : count_html_element_(
+    : web::StatTracker(name),
+      count_html_element_(
           base::StringPrintf("Count.%s.DOM.HtmlElement", name.c_str()), 0,
           "Total number of HTML elements."),
       count_html_element_document_(
           base::StringPrintf("Count.%s.DOM.HtmlElement.Document", name.c_str()),
           0, "Number of HTML elements in the document."),
-      count_window_timers_interval_(
-          base::StringPrintf("Count.%s.DOM.WindowTimers.Interval",
-                             name.c_str()),
-          0, "Number of active WindowTimer Intervals."),
-      count_window_timers_timeout_(
-          base::StringPrintf("Count.%s.DOM.WindowTimers.Timeout", name.c_str()),
-          0, "Number of active WindowTimer Timeouts."),
       count_html_element_created_(0),
       count_html_element_destroyed_(0),
       count_html_element_document_added_(0),
       count_html_element_document_removed_(0),
-      count_window_timers_interval_created_(0),
-      count_window_timers_interval_destroyed_(0),
-      count_window_timers_timeout_created_(0),
-      count_window_timers_timeout_destroyed_(0),
       script_element_execute_count_(
           base::StringPrintf("Count.%s.DOM.HtmlScriptElement.Execute",
                              name.c_str()),
@@ -80,8 +70,6 @@
   // destroyed.
   DCHECK_EQ(count_html_element_, 0);
   DCHECK_EQ(count_html_element_document_, 0);
-  DCHECK_EQ(count_window_timers_interval_, 0);
-  DCHECK_EQ(count_window_timers_timeout_, 0);
 
   event_video_start_delay_stop_watch_.Stop();
 }
@@ -153,26 +141,19 @@
 }
 
 void DomStatTracker::FlushPeriodicTracking() {
+  web::StatTracker::FlushPeriodicTracking();
+
   // Update the CVals before clearing the periodic values.
   count_html_element_ +=
       count_html_element_created_ - count_html_element_destroyed_;
   count_html_element_document_ +=
       count_html_element_document_added_ - count_html_element_document_removed_;
 
-  count_window_timers_interval_ += count_window_timers_interval_created_ -
-                                   count_window_timers_interval_destroyed_;
-  count_window_timers_timeout_ += count_window_timers_timeout_created_ -
-                                  count_window_timers_timeout_destroyed_;
   // Now clear the values.
   count_html_element_created_ = 0;
   count_html_element_destroyed_ = 0;
   count_html_element_document_added_ = 0;
   count_html_element_document_removed_ = 0;
-
-  count_window_timers_interval_created_ = 0;
-  count_window_timers_interval_destroyed_ = 0;
-  count_window_timers_timeout_created_ = 0;
-  count_window_timers_timeout_destroyed_ = 0;
 }
 
 void DomStatTracker::StartTrackingEvent() {
diff --git a/cobalt/dom/dom_stat_tracker.h b/cobalt/dom/dom_stat_tracker.h
index 288444e..bb49c0e 100644
--- a/cobalt/dom/dom_stat_tracker.h
+++ b/cobalt/dom/dom_stat_tracker.h
@@ -20,12 +20,13 @@
 
 #include "cobalt/base/c_val.h"
 #include "cobalt/base/stop_watch.h"
+#include "cobalt/web/stat_tracker.h"
 
 namespace cobalt {
 namespace dom {
 
 // This class handles tracking DOM stats and is intended for single thread use.
-class DomStatTracker : public base::StopWatchOwner {
+class DomStatTracker : public base::StopWatchOwner, public web::StatTracker {
  public:
   enum StopWatchType {
     kStopWatchTypeRunAnimationFrameCallbacks,
@@ -37,19 +38,6 @@
   explicit DomStatTracker(const std::string& name);
   ~DomStatTracker();
 
-  void OnWindowTimersIntervalCreated() {
-    ++count_window_timers_interval_created_;
-  }
-  void OnWindowTimersIntervalDestroyed() {
-    ++count_window_timers_interval_destroyed_;
-  }
-  void OnWindowTimersTimeoutCreated() {
-    ++count_window_timers_timeout_created_;
-  }
-  void OnWindowTimersTimeoutDestroyed() {
-    ++count_window_timers_timeout_destroyed_;
-  }
-
   void OnHtmlElementCreated();
   void OnHtmlElementDestroyed();
   void OnHtmlElementInsertedIntoDocument();
@@ -107,19 +95,12 @@
   base::CVal<int, base::CValPublic> count_html_element_;
   base::CVal<int, base::CValPublic> count_html_element_document_;
 
-  base::CVal<int, base::CValPublic> count_window_timers_interval_;
-  base::CVal<int, base::CValPublic> count_window_timers_timeout_;
-
   // Periodic counts. The counts are cleared after the CVals are updated in
   // |FlushPeriodicTracking|.
   int count_html_element_created_;
   int count_html_element_destroyed_;
   int count_html_element_document_added_;
   int count_html_element_document_removed_;
-  int count_window_timers_interval_created_;
-  int count_window_timers_interval_destroyed_;
-  int count_window_timers_timeout_created_;
-  int count_window_timers_timeout_destroyed_;
 
   // Count of HtmlScriptElement::Execute() calls, their total size in bytes, and
   // the time of last call.
diff --git a/cobalt/dom/element.cc b/cobalt/dom/element.cc
index 7a279a6..c2119dc 100644
--- a/cobalt/dom/element.cc
+++ b/cobalt/dom/element.cc
@@ -15,6 +15,7 @@
 #include "cobalt/dom/element.h"
 
 #include <algorithm>
+#include <memory>
 
 #include "base/lazy_instance.h"
 #include "base/strings/string_util.h"
@@ -23,7 +24,6 @@
 #include "cobalt/cssom/css_style_rule.h"
 #include "cobalt/cssom/selector.h"
 #include "cobalt/dom/document.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_rect.h"
 #include "cobalt/dom/dom_rect_list.h"
 #include "cobalt/dom/dom_token_list.h"
@@ -39,6 +39,7 @@
 #include "cobalt/dom/serializer.h"
 #include "cobalt/dom/text.h"
 #include "cobalt/math/rect_f.h"
+#include "cobalt/web/dom_exception.h"
 #include "nb/memory_scope.h"
 
 namespace cobalt {
@@ -107,7 +108,7 @@
 }
 
 bool Element::Matches(const std::string& selectors,
-                             script::ExceptionState* exception_state) {
+                      script::ExceptionState* exception_state) {
   TRACK_MEMORY_SCOPE("DOM");
   // Referenced from:
   // https://dom.spec.whatwg.org/#dom-element-matches
@@ -118,15 +119,15 @@
   scoped_refptr<cssom::CSSRule> css_rule =
       css_parser->ParseRule(selectors + " {}", this->GetInlineSourceLocation());
 
-  // 2. If s is failure, throw a "SyntaxError" DOMException.
+  // 2. If s is failure, throw a "SyntaxError" web::DOMException.
   if (!css_rule) {
-    DOMException::Raise(dom::DOMException::kSyntaxErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, exception_state);
     return false;
   }
   scoped_refptr<cssom::CSSStyleRule> css_style_rule =
       css_rule->AsCSSStyleRule();
   if (!css_style_rule) {
-    DOMException::Raise(dom::DOMException::kSyntaxErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, exception_state);
     return false;
   }
 
@@ -196,8 +197,8 @@
 //   https://www.w3.org/TR/2014/WD-dom-20140710/#dom-element-setattribute
 void Element::SetAttribute(const std::string& name, const std::string& value) {
   TRACK_MEMORY_SCOPE("DOM");
-  TRACE_EVENT2("cobalt::dom", "Element::SetAttribute",
-               "name", name, "value", value);
+  TRACE_EVENT2("cobalt::dom", "Element::SetAttribute", "name", name, "value",
+               value);
   Document* document = node_document();
 
   // 1. Not needed by Cobalt.
@@ -544,10 +545,11 @@
     return;
   }
 
-  // 3. If parent is a Document, throw a DOMException with name
+  // 3. If parent is a Document, throw a web::DOMException with name
   // "NoModificationAllowedError" exception.
   if (parent->IsDocument()) {
-    DOMException::Raise(dom::DOMException::kInvalidAccessErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidAccessErr,
+                             exception_state);
     return;
   }
 
diff --git a/cobalt/dom/element.h b/cobalt/dom/element.h
index 2db79f0..d922fb4 100644
--- a/cobalt/dom/element.h
+++ b/cobalt/dom/element.h
@@ -15,7 +15,9 @@
 #ifndef COBALT_DOM_ELEMENT_H_
 #define COBALT_DOM_ELEMENT_H_
 
+#include <memory>
 #include <string>
+#include <unordered_map>
 
 #include "base/containers/hash_tables.h"
 #include "base/containers/small_map.h"
@@ -24,11 +26,11 @@
 #include "base/strings/string_piece.h"
 #include "cobalt/base/token.h"
 #include "cobalt/cssom/style_sheet_list.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/element_intersection_observer_module.h"
 #include "cobalt/dom/intersection_observer.h"
 #include "cobalt/dom/node.h"
 #include "cobalt/script/exception_state.h"
+#include "cobalt/web/dom_exception.h"
 #include "cobalt/web_animations/animation_set.h"
 
 namespace cobalt {
@@ -177,8 +179,7 @@
   // by javascript.
   // opening_tag_location points to ">" of opening tag.
   virtual void OnParserStartTag(
-      const base::SourceLocation& opening_tag_location) {
-  }
+      const base::SourceLocation& opening_tag_location) {}
   virtual void OnParserEndTag() {}
 
   // Used to ensure that the style attribute value reflects the style
diff --git a/cobalt/dom/element_test.cc b/cobalt/dom/element_test.cc
index 48f542b..0128e48 100644
--- a/cobalt/dom/element_test.cc
+++ b/cobalt/dom/element_test.cc
@@ -31,12 +31,12 @@
 #include "cobalt/dom/html_element_context.h"
 #include "cobalt/dom/named_node_map.h"
 #include "cobalt/dom/node_list.h"
-#include "cobalt/dom/testing/gtest_workarounds.h"
 #include "cobalt/dom/testing/html_collection_testing.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
 #include "cobalt/dom/text.h"
 #include "cobalt/dom/xml_document.h"
 #include "cobalt/dom_parser/parser.h"
+#include "cobalt/web/testing/gtest_workarounds.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
diff --git a/cobalt/dom/eme/eme_helpers.h b/cobalt/dom/eme/eme_helpers.h
index bc3b4b9..8d87e9e 100644
--- a/cobalt/dom/eme/eme_helpers.h
+++ b/cobalt/dom/eme/eme_helpers.h
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "cobalt/script/script_value_factory.h"
+#include "cobalt/web/dom_exception.h"
 #include "starboard/drm.h"
 
 namespace cobalt {
@@ -37,20 +38,20 @@
       promise_reference->value().Reject(script::kTypeError);
       break;
     case kSbDrmStatusNotSupportedError:
-      promise_reference->value().Reject(
-          new DOMException(DOMException::kNotSupportedErr, error_message));
+      promise_reference->value().Reject(new web::DOMException(
+          web::DOMException::kNotSupportedErr, error_message));
       break;
     case kSbDrmStatusInvalidStateError:
-      promise_reference->value().Reject(
-          new DOMException(DOMException::kInvalidStateErr, error_message));
+      promise_reference->value().Reject(new web::DOMException(
+          web::DOMException::kInvalidStateErr, error_message));
       break;
     case kSbDrmStatusQuotaExceededError:
-      promise_reference->value().Reject(
-          new DOMException(DOMException::kQuotaExceededErr, error_message));
+      promise_reference->value().Reject(new web::DOMException(
+          web::DOMException::kQuotaExceededErr, error_message));
       break;
     case kSbDrmStatusUnknownError:
       promise_reference->value().Reject(
-          new DOMException(DOMException::kNone, error_message));
+          new web::DOMException(web::DOMException::kNone, error_message));
       break;
   }
 }
diff --git a/cobalt/dom/eme/media_encrypted_event.h b/cobalt/dom/eme/media_encrypted_event.h
index 6aa11ae..0f16091 100644
--- a/cobalt/dom/eme/media_encrypted_event.h
+++ b/cobalt/dom/eme/media_encrypted_event.h
@@ -20,9 +20,9 @@
 
 #include "base/memory/ref_counted.h"
 #include "cobalt/dom/eme/media_encrypted_event_init.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/script/array_buffer.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace dom {
@@ -30,7 +30,7 @@
 
 // The MediaEncryptedEvent object is used for the encrypted event.
 //   https://www.w3.org/TR/encrypted-media/#mediaencryptedevent
-class MediaEncryptedEvent : public Event {
+class MediaEncryptedEvent : public web::Event {
  public:
   // Web API: MediaEncryptedEvent
   //
diff --git a/cobalt/dom/eme/media_key_message_event.h b/cobalt/dom/eme/media_key_message_event.h
index e661219..4fe613c 100644
--- a/cobalt/dom/eme/media_key_message_event.h
+++ b/cobalt/dom/eme/media_key_message_event.h
@@ -20,9 +20,9 @@
 #include "base/memory/ref_counted.h"
 #include "cobalt/dom/eme/media_key_message_event_init.h"
 #include "cobalt/dom/eme/media_key_message_type.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/script/array_buffer.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace dom {
@@ -30,7 +30,7 @@
 
 // The MediaKeyMessageEvent object is used for the message event.
 //   https://www.w3.org/TR/encrypted-media/#mediakeymessageevent
-class MediaKeyMessageEvent : public Event {
+class MediaKeyMessageEvent : public web::Event {
  public:
   // Web IDL: MediaKeyMessageEvent
   //
diff --git a/cobalt/dom/eme/media_key_session.cc b/cobalt/dom/eme/media_key_session.cc
index bf5c16b..c36eff2 100644
--- a/cobalt/dom/eme/media_key_session.cc
+++ b/cobalt/dom/eme/media_key_session.cc
@@ -17,8 +17,8 @@
 #include <memory>
 #include <type_traits>
 
+#include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/eme/eme_helpers.h"
 #include "cobalt/dom/eme/media_key_message_event.h"
 #include "cobalt/dom/eme/media_key_message_event_init.h"
@@ -27,6 +27,7 @@
 #include "cobalt/script/array_buffer_view.h"
 #include "cobalt/script/script_value_factory.h"
 #include "cobalt/web/context.h"
+#include "cobalt/web/dom_exception.h"
 #include "cobalt/web/environment_settings.h"
 
 namespace cobalt {
@@ -40,7 +41,7 @@
     const scoped_refptr<media::DrmSystem>& drm_system,
     script::ScriptValueFactory* script_value_factory,
     const ClosedCallback& closed_callback)
-    : EventTarget(settings),
+    : web::EventTarget(settings),
       ALLOW_THIS_IN_INITIALIZER_LIST(event_queue_(this)),
       drm_system_(drm_system),
       drm_system_session_(drm_system->CreateSession(
@@ -74,7 +75,7 @@
   return key_status_map_;
 }
 
-const EventTarget::EventListenerScriptValue*
+const web::EventTarget::EventListenerScriptValue*
 MediaKeySession::onkeystatuseschange() const {
   return GetAttributeEventListener(base::Tokens::keystatuseschange());
 }
@@ -84,7 +85,7 @@
   SetAttributeEventListener(base::Tokens::keystatuseschange(), event_listener);
 }
 
-const EventTarget::EventListenerScriptValue* MediaKeySession::onmessage()
+const web::EventTarget::EventListenerScriptValue* MediaKeySession::onmessage()
     const {
   return GetAttributeEventListener(base::Tokens::message());
 }
@@ -98,7 +99,9 @@
 // https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-generaterequest.
 script::Handle<script::Promise<void>> MediaKeySession::GenerateRequest(
     script::EnvironmentSettings* settings, const std::string& init_data_type,
-    const BufferSource& init_data) {
+    const web::BufferSource& init_data) {
+  TRACE_EVENT1("cobalt::dom::eme", "MediaKeySession::GenerateRequest()",
+               "init_data_type", init_data_type);
   script::Handle<script::Promise<void>> promise =
       script_value_factory_->CreateBasicPromise<void>();
 
@@ -107,7 +110,7 @@
   // 2. If this object's uninitialized value is false, return a promise rejected
   //    with an InvalidStateError.
   if (drm_system_session_->is_closed() || !uninitialized_) {
-    promise->Reject(new DOMException(DOMException::kInvalidStateErr));
+    promise->Reject(new web::DOMException(web::DOMException::kInvalidStateErr));
     return promise;
   }
 
@@ -116,7 +119,7 @@
 
   const uint8* init_data_buffer;
   int init_data_buffer_size;
-  GetBufferAndSize(init_data, &init_data_buffer, &init_data_buffer_size);
+  web::GetBufferAndSize(init_data, &init_data_buffer, &init_data_buffer_size);
 
   // 4. If initDataType is the empty string, return a promise rejected with
   //    a newly created TypeError.
@@ -149,7 +152,8 @@
 
 // See https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-update.
 script::Handle<script::Promise<void>> MediaKeySession::Update(
-    const BufferSource& response) {
+    const web::BufferSource& response) {
+  TRACE_EVENT0("cobalt::dom::eme", "MediaKeySession::Update()");
   script::Handle<script::Promise<void>> promise =
       script_value_factory_->CreateBasicPromise<void>();
 
@@ -158,13 +162,13 @@
   // 2. If this object's callable value is false, return a promise rejected
   //    with an InvalidStateError.
   if (drm_system_session_->is_closed() || !callable_) {
-    promise->Reject(new DOMException(DOMException::kInvalidStateErr));
+    promise->Reject(new web::DOMException(web::DOMException::kInvalidStateErr));
     return promise;
   }
 
   const uint8* response_buffer;
   int response_buffer_size;
-  GetBufferAndSize(response, &response_buffer, &response_buffer_size);
+  web::GetBufferAndSize(response, &response_buffer, &response_buffer_size);
 
   // 3. If response is an empty array, return a promise rejected with a newly
   //    created TypeError.
@@ -192,6 +196,7 @@
 
 // See https://www.w3.org/TR/encrypted-media/#dom-mediakeysession-close.
 script::Handle<script::Promise<void>> MediaKeySession::Close() {
+  TRACE_EVENT0("cobalt::dom::eme", "MediaKeySession::Close()");
   script::Handle<script::Promise<void>> promise =
       script_value_factory_->CreateBasicPromise<void>();
 
@@ -204,7 +209,7 @@
   // 3. If session's callable value is false, return a promise rejected with
   //    an InvalidStateError.
   if (!callable_) {
-    promise->Reject(new DOMException(DOMException::kInvalidStateErr));
+    promise->Reject(new web::DOMException(web::DOMException::kInvalidStateErr));
     return promise;
   }
 
@@ -217,7 +222,7 @@
 }
 
 void MediaKeySession::TraceMembers(script::Tracer* tracer) {
-  EventTarget::TraceMembers(tracer);
+  web::EventTarget::TraceMembers(tracer);
 
   tracer->Trace(event_queue_);
   tracer->Trace(key_status_map_);
@@ -383,7 +388,7 @@
   //    session.
   LOG(INFO) << "Fired 'keystatuseschange' event on MediaKeySession with "
             << key_status_map_->size() << " keys.";
-  event_queue_.Enqueue(new Event(base::Tokens::keystatuseschange()));
+  event_queue_.Enqueue(new web::Event(base::Tokens::keystatuseschange()));
 
   // 6. Queue a task to run the Attempt to Resume Playback If Necessary
   //    algorithm on each of the media element(s) whose mediaKeys attribute is
diff --git a/cobalt/dom/eme/media_key_session.h b/cobalt/dom/eme/media_key_session.h
index f9886b5..0de0834 100644
--- a/cobalt/dom/eme/media_key_session.h
+++ b/cobalt/dom/eme/media_key_session.h
@@ -21,15 +21,15 @@
 
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
-#include "cobalt/dom/buffer_source.h"
 #include "cobalt/dom/eme/media_key_status.h"
 #include "cobalt/dom/eme/media_key_status_map.h"
 #include "cobalt/dom/event_queue.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/media/base/drm_system.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/script/script_value_factory.h"
+#include "cobalt/web/buffer_source.h"
+#include "cobalt/web/event_target.h"
 #include "starboard/drm.h"
 
 namespace cobalt {
@@ -41,7 +41,7 @@
 //   https://www.w3.org/TR/encrypted-media/#key-session
 //
 // Also see https://www.w3.org/TR/encrypted-media/#mediakeysession-interface.
-class MediaKeySession : public EventTarget {
+class MediaKeySession : public web::EventTarget {
  public:
   typedef script::ScriptValue<script::Promise<void>> VoidPromiseValue;
   typedef base::Callback<void(MediaKeySession* session)> ClosedCallback;
@@ -62,8 +62,9 @@
   void set_onmessage(const EventListenerScriptValue& event_listener);
   script::Handle<script::Promise<void>> GenerateRequest(
       script::EnvironmentSettings* settings, const std::string& init_data_type,
-      const BufferSource& init_data);
-  script::Handle<script::Promise<void>> Update(const BufferSource& response);
+      const web::BufferSource& init_data);
+  script::Handle<script::Promise<void>> Update(
+      const web::BufferSource& response);
   script::Handle<script::Promise<void>> Close();
 
   DEFINE_WRAPPABLE_TYPE(MediaKeySession);
diff --git a/cobalt/dom/eme/media_key_status_map.cc b/cobalt/dom/eme/media_key_status_map.cc
index 3423bce..3a52234 100644
--- a/cobalt/dom/eme/media_key_status_map.cc
+++ b/cobalt/dom/eme/media_key_status_map.cc
@@ -59,8 +59,8 @@
   return "internal-error";
 }
 
-BufferSource ConvertStringToBufferSource(script::EnvironmentSettings* settings,
-                                         const std::string& str) {
+web::BufferSource ConvertStringToBufferSource(
+    script::EnvironmentSettings* settings, const std::string& str) {
   DCHECK(settings);
   auto* global_environment =
       base::polymorphic_downcast<web::EnvironmentSettings*>(settings)
@@ -69,7 +69,7 @@
   DCHECK(global_environment);
   script::Handle<script::ArrayBuffer> array_buffer =
       script::ArrayBuffer::New(global_environment, str.data(), str.size());
-  return BufferSource(array_buffer);
+  return web::BufferSource(array_buffer);
 }
 
 }  // namespace
@@ -83,6 +83,7 @@
 
 void MediaKeyStatusMap::ForEach(script::EnvironmentSettings* settings,
                                 const ForEachCallbackArg& callback) {
+  TRACE_EVENT0("cobalt::dom::eme", "MediaKeyStatusMap::ForEach()");
   ForEachCallbackArg::Reference reference(this, callback);
 
   for (auto& key_status : key_statuses_) {
diff --git a/cobalt/dom/eme/media_key_status_map.h b/cobalt/dom/eme/media_key_status_map.h
index 9e8ada5..b8e55b7 100644
--- a/cobalt/dom/eme/media_key_status_map.h
+++ b/cobalt/dom/eme/media_key_status_map.h
@@ -19,12 +19,13 @@
 #include <string>
 
 #include "base/memory/ref_counted.h"
-#include "cobalt/dom/buffer_source.h"
+#include "base/trace_event/trace_event.h"
 #include "cobalt/dom/eme/media_key_status.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/buffer_source.h"
 
 namespace cobalt {
 namespace dom {
@@ -36,7 +37,7 @@
 class MediaKeyStatusMap : public script::Wrappable {
  public:
   typedef script::CallbackFunction<void(
-      const std::string&, const BufferSource&,
+      const std::string&, const web::BufferSource&,
       const scoped_refptr<MediaKeyStatusMap>&)>
       ForEachCallback;
   typedef script::ScriptValue<ForEachCallback> ForEachCallbackArg;
@@ -52,7 +53,9 @@
   // Web IDL: MediaKeyStatusMap.
   //
   uint32_t size() const { return static_cast<uint32_t>(key_statuses_.size()); }
-  MediaKeyStatus Get(const BufferSource& key_id) const {
+  MediaKeyStatus Get(const web::BufferSource& key_id) const {
+    TRACE_EVENT1("cobalt::dom::eme", "MediaKeyStatusMap::Get()", "key_id",
+                 GetStringFromBufferSource(key_id));
     std::string key_id_copy = GetStringFromBufferSource(key_id);
     const auto& iter = key_statuses_.find(key_id_copy);
     // TODO: Return "undefined" if `key_id` cannot be found.
@@ -60,7 +63,9 @@
                                        : iter->second;
   }
 
-  bool Has(const BufferSource& key_id) const {
+  bool Has(const web::BufferSource& key_id) const {
+    TRACE_EVENT1("cobalt::dom::eme", "MediaKeyStatusMap::Has()", "key_id",
+                 GetStringFromBufferSource(key_id));
     std::string key_id_copy = GetStringFromBufferSource(key_id);
     return key_statuses_.find(key_id_copy) != key_statuses_.end();
   }
@@ -72,11 +77,11 @@
 
  private:
   static std::string GetStringFromBufferSource(
-      const BufferSource& buffer_source) {
+      const web::BufferSource& buffer_source) {
     const uint8* buffer;
     int buffer_size;
 
-    GetBufferAndSize(buffer_source, &buffer, &buffer_size);
+    web::GetBufferAndSize(buffer_source, &buffer, &buffer_size);
 
     DCHECK(buffer);
     DCHECK_GE(buffer_size, 0);
diff --git a/cobalt/dom/eme/media_key_system_access.cc b/cobalt/dom/eme/media_key_system_access.cc
index 988fe43..bc6fd9c 100644
--- a/cobalt/dom/eme/media_key_system_access.cc
+++ b/cobalt/dom/eme/media_key_system_access.cc
@@ -16,10 +16,10 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_refptr.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/eme/media_keys.h"
 #include "cobalt/media/base/drm_system.h"
 #include "cobalt/script/script_value_factory.h"
+#include "cobalt/web/dom_exception.h"
 
 namespace cobalt {
 namespace dom {
@@ -38,6 +38,7 @@
 script::Handle<MediaKeySystemAccess::InterfacePromise>
 MediaKeySystemAccess::CreateMediaKeys(
     script::EnvironmentSettings* settings) const {
+  TRACE_EVENT0("cobalt::dom::eme", "MediaKeySystemAccess::CreateMediaKeys()");
   // 1. Let promise be a new promise.
   script::Handle<MediaKeySystemAccess::InterfacePromise> promise =
       script_value_factory_->CreateInterfacePromise<scoped_refptr<MediaKeys>>();
@@ -51,8 +52,8 @@
   // DOMException whose name is the appropriate error name.
   if (!drm_system->is_valid()) {
     drm_system.reset();
-    promise->Reject(new DOMException(
-        DOMException::kNotSupportedErr,
+    promise->Reject(new web::DOMException(
+        web::DOMException::kNotSupportedErr,
         "Failed to load and initialize the Key System implementation."));
     return promise;
   }
diff --git a/cobalt/dom/eme/media_key_system_access.h b/cobalt/dom/eme/media_key_system_access.h
index 5849374..31591bd 100644
--- a/cobalt/dom/eme/media_key_system_access.h
+++ b/cobalt/dom/eme/media_key_system_access.h
@@ -17,6 +17,7 @@
 
 #include <string>
 
+#include "base/trace_event/trace_event.h"
 #include "cobalt/dom/eme/media_key_system_configuration.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/promise.h"
@@ -41,6 +42,8 @@
   // Web API: MediaKeySystemAccess.
   const std::string& key_system() const { return key_system_; }
   const MediaKeySystemConfiguration& GetConfiguration() const {
+    TRACE_EVENT0("cobalt::dom::eme",
+                 "MediaKeySystemAccess::GetConfiguration()");
     return configuration_;
   }
   script::Handle<InterfacePromise> CreateMediaKeys(
diff --git a/cobalt/dom/eme/media_keys.cc b/cobalt/dom/eme/media_keys.cc
index c29c300..e902445 100644
--- a/cobalt/dom/eme/media_keys.cc
+++ b/cobalt/dom/eme/media_keys.cc
@@ -15,12 +15,13 @@
 #include "cobalt/dom/eme/media_keys.h"
 
 #include "base/bind.h"
+#include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/eme/eme_helpers.h"
 #include "cobalt/dom/eme/media_key_session.h"
 #include "cobalt/web/context.h"
+#include "cobalt/web/dom_exception.h"
 
 namespace cobalt {
 namespace dom {
@@ -39,10 +40,13 @@
 // See https://www.w3.org/TR/encrypted-media/#dom-mediakeys-createsession.
 scoped_refptr<MediaKeySession> MediaKeys::CreateSession(
     MediaKeySessionType session_type, script::ExceptionState* exception_state) {
+  TRACE_EVENT1("cobalt::dom::eme", "MediaKeys::CreateSession()", "session_type",
+               session_type);
   // 1. If this object's supported session types value does not contain
   //    sessionType, throw a NotSupportedError.
   if (session_type != kMediaKeySessionTypeTemporary) {
-    DOMException::Raise(DOMException::kNotSupportedErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kNotSupportedErr,
+                             exception_state);
     return NULL;
   }
 
@@ -58,7 +62,8 @@
 }
 
 MediaKeys::BoolPromiseHandle MediaKeys::SetServerCertificate(
-    const BufferSource& server_certificate) {
+    const web::BufferSource& server_certificate) {
+  TRACE_EVENT0("cobalt::dom::eme", "MediaKeys::SetServerCertificate()");
   BoolPromiseHandle promise = script_value_factory_->CreateBasicPromise<bool>();
 
   // 1. If the Key System implementation represented by this object's cdm
@@ -73,8 +78,8 @@
   //    newly created TypeError.
   const uint8* server_certificate_buffer;
   int server_certificate_buffer_size = 0;
-  GetBufferAndSize(server_certificate, &server_certificate_buffer,
-                   &server_certificate_buffer_size);
+  web::GetBufferAndSize(server_certificate, &server_certificate_buffer,
+                        &server_certificate_buffer_size);
 
   if (server_certificate_buffer_size == 0) {
     promise->Reject(script::kTypeError);
@@ -100,13 +105,15 @@
 
 script::Handle<script::Uint8Array> MediaKeys::GetMetrics(
     script::ExceptionState* exception_state) {
+  TRACE_EVENT0("cobalt::dom::eme", "MediaKeys::GetMetrics()");
   std::vector<uint8_t> metrics;
   if (drm_system_->GetMetrics(&metrics)) {
     return script::Uint8Array::New(
         dom_settings_->context()->global_environment(), metrics.data(),
         metrics.size());
   }
-  DOMException::Raise(DOMException::kNotSupportedErr, exception_state);
+  web::DOMException::Raise(web::DOMException::kNotSupportedErr,
+                           exception_state);
   return script::Handle<script::Uint8Array>();
 }
 
diff --git a/cobalt/dom/eme/media_keys.h b/cobalt/dom/eme/media_keys.h
index 67e773e..942cf9c 100644
--- a/cobalt/dom/eme/media_keys.h
+++ b/cobalt/dom/eme/media_keys.h
@@ -20,7 +20,6 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-#include "cobalt/dom/buffer_source.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/eme/media_key_session.h"
 #include "cobalt/dom/eme/media_key_session_type.h"
@@ -29,6 +28,8 @@
 #include "cobalt/script/script_value_factory.h"
 #include "cobalt/script/typed_arrays.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/buffer_source.h"
+#include "cobalt/web/dom_exception.h"
 #include "starboard/drm.h"
 
 namespace cobalt {
@@ -57,7 +58,7 @@
       MediaKeySessionType session_type,
       script::ExceptionState* exception_state);
   BoolPromiseHandle SetServerCertificate(
-      const BufferSource& server_certificate);
+      const web::BufferSource& server_certificate);
   script::Handle<script::Uint8Array> GetMetrics(
       script::ExceptionState* exception_state);
 
diff --git a/cobalt/dom/error_event_test.cc b/cobalt/dom/error_event_test.cc
deleted file mode 100644
index 8cbfdec..0000000
--- a/cobalt/dom/error_event_test.cc
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright 2017 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "cobalt/dom/error_event.h"
-
-#include <memory>
-#include <string>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/optional.h"
-#include "base/threading/platform_thread.h"
-#include "cobalt/css_parser/parser.h"
-#include "cobalt/cssom/viewport_size.h"
-#include "cobalt/dom/error_event_init.h"
-#include "cobalt/dom/local_storage_database.h"
-#include "cobalt/dom/testing/gtest_workarounds.h"
-#include "cobalt/dom/testing/stub_environment_settings.h"
-#include "cobalt/dom/window.h"
-#include "cobalt/dom_parser/parser.h"
-#include "cobalt/loader/fetcher_factory.h"
-#include "cobalt/loader/loader_factory.h"
-#include "cobalt/script/global_environment.h"
-#include "cobalt/script/javascript_engine.h"
-#include "cobalt/script/source_code.h"
-#include "cobalt/script/testing/fake_script_value.h"
-#include "cobalt/script/value_handle.h"
-#include "cobalt/script/wrappable.h"
-#include "nb/pointer_arithmetic.h"
-#include "starboard/window.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using cobalt::cssom::ViewportSize;
-using cobalt::script::testing::FakeScriptValue;
-
-namespace cobalt {
-namespace dom {
-
-class MockLoadCompleteCallback
-    : public base::Callback<void(const base::Optional<std::string>&)> {
- public:
-  MOCK_METHOD1(Run, void(const base::Optional<std::string>&));
-};
-
-namespace {
-class ErrorEventTest : public ::testing::Test {
- public:
-  ErrorEventTest()
-      : message_loop_(base::MessageLoop::TYPE_DEFAULT),
-        environment_settings_(new testing::StubEnvironmentSettings),
-        css_parser_(css_parser::Parser::Create()),
-        dom_parser_(new dom_parser::Parser(mock_load_complete_callback_)),
-        fetcher_factory_(new loader::FetcherFactory(NULL)),
-        loader_factory_(new loader::LoaderFactory(
-            "Test", fetcher_factory_.get(), NULL, null_debugger_hooks_, 0,
-            base::ThreadPriority::DEFAULT)),
-        local_storage_database_(NULL),
-        url_("about:blank") {
-    engine_ = script::JavaScriptEngine::CreateEngine();
-    global_environment_ = engine_->CreateGlobalEnvironment();
-
-    ViewportSize view_size(1920, 1080);
-    window_ = new Window(
-        environment_settings_.get(), view_size, base::kApplicationStateStarted,
-        css_parser_.get(), dom_parser_.get(), fetcher_factory_.get(),
-        loader_factory_.get(), NULL, NULL, NULL, NULL, NULL, NULL,
-        &local_storage_database_, NULL, NULL, NULL, NULL,
-        global_environment_->script_value_factory(), NULL, NULL, url_, "", NULL,
-        "en-US", "en", base::Callback<void(const GURL&)>(),
-        base::Bind(&MockLoadCompleteCallback::Run,
-                   base::Unretained(&mock_load_complete_callback_)),
-        NULL, network_bridge::PostSender(), csp::kCSPRequired,
-        kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
-        base::Closure() /* ran_animation_frame_callbacks */,
-        dom::Window::CloseCallback() /* window_close */,
-        base::Closure() /* window_minimize */, NULL, NULL,
-        dom::Window::OnStartDispatchEventCallback(),
-        dom::Window::OnStopDispatchEventCallback(),
-        dom::ScreenshotManager::ProvideScreenshotFunctionCallback(), NULL);
-
-    global_environment_->CreateGlobalObject(window_,
-                                            environment_settings_.get());
-  }
-
-  bool EvaluateScript(const std::string& js_code, std::string* result);
-
- private:
-  base::MessageLoop message_loop_;
-  base::NullDebuggerHooks null_debugger_hooks_;
-  std::unique_ptr<script::JavaScriptEngine> engine_;
-  const std::unique_ptr<testing::StubEnvironmentSettings> environment_settings_;
-  scoped_refptr<script::GlobalEnvironment> global_environment_;
-  MockLoadCompleteCallback mock_load_complete_callback_;
-  std::unique_ptr<css_parser::Parser> css_parser_;
-  std::unique_ptr<dom_parser::Parser> dom_parser_;
-  std::unique_ptr<loader::FetcherFactory> fetcher_factory_;
-  std::unique_ptr<loader::LoaderFactory> loader_factory_;
-  dom::LocalStorageDatabase local_storage_database_;
-  GURL url_;
-  scoped_refptr<Window> window_;
-};
-
-bool ErrorEventTest::EvaluateScript(const std::string& js_code,
-                                    std::string* result) {
-  DCHECK(global_environment_);
-  DCHECK(result);
-  scoped_refptr<script::SourceCode> source_code =
-      script::SourceCode::CreateSourceCode(
-          js_code, base::SourceLocation(__FILE__, __LINE__, 1));
-
-  global_environment_->EnableEval();
-  global_environment_->SetReportEvalCallback(base::Closure());
-  bool succeeded = global_environment_->EvaluateScript(source_code, result);
-  return succeeded;
-}
-}  // namespace
-
-TEST_F(ErrorEventTest, ConstructorWithEventTypeString) {
-  scoped_refptr<ErrorEvent> event = new ErrorEvent("mytestevent");
-
-  EXPECT_EQ("mytestevent", event->type());
-  EXPECT_EQ(NULL, event->target().get());
-  EXPECT_EQ(NULL, event->current_target().get());
-  EXPECT_EQ(Event::kNone, event->event_phase());
-  EXPECT_FALSE(event->bubbles());
-  EXPECT_FALSE(event->cancelable());
-  EXPECT_FALSE(event->default_prevented());
-  EXPECT_FALSE(event->IsBeingDispatched());
-  EXPECT_FALSE(event->propagation_stopped());
-  EXPECT_FALSE(event->immediate_propagation_stopped());
-  EXPECT_EQ("", event->message());
-  EXPECT_EQ("", event->filename());
-  EXPECT_EQ(0, event->lineno());
-  EXPECT_EQ(0, event->colno());
-  EXPECT_EQ(NULL, event->error());
-}
-
-TEST_F(ErrorEventTest, ConstructorWithEventTypeAndDefaultInitDict) {
-  ErrorEventInit init;
-  scoped_refptr<ErrorEvent> event = new ErrorEvent("mytestevent", init);
-
-  EXPECT_EQ("mytestevent", event->type());
-  EXPECT_EQ(NULL, event->target().get());
-  EXPECT_EQ(NULL, event->current_target().get());
-  EXPECT_EQ(Event::kNone, event->event_phase());
-  EXPECT_FALSE(event->bubbles());
-  EXPECT_FALSE(event->cancelable());
-  EXPECT_FALSE(event->default_prevented());
-  EXPECT_FALSE(event->IsBeingDispatched());
-  EXPECT_FALSE(event->propagation_stopped());
-  EXPECT_FALSE(event->immediate_propagation_stopped());
-  EXPECT_EQ("", event->message());
-  EXPECT_EQ("", event->filename());
-  EXPECT_EQ(0, event->lineno());
-  EXPECT_EQ(0, event->colno());
-  EXPECT_EQ(NULL, event->error());
-}
-
-TEST_F(ErrorEventTest, ConstructorWithEventTypeAndErrorInitDict) {
-  std::string result;
-  bool success = EvaluateScript(
-      "var event = new ErrorEvent('dog', "
-      "    {'cancelable':true, "
-      "     'message':'error_message', "
-      "     'filename':'error_filename', "
-      "     'lineno':100, "
-      "     'colno':50, "
-      "     'error':{'cobalt':'rulez'}});"
-      "if (event.type == 'dog' &&"
-      "    event.bubbles == false &&"
-      "    event.cancelable == true &&"
-      "    event.message == 'error_message' &&"
-      "    event.filename == 'error_filename' &&"
-      "    event.lineno == 100 &&"
-      "    event.colno == 50) "
-      "    event.error.cobalt;",
-      &result);
-  EXPECT_EQ("rulez", result);
-
-  if (!success) {
-    DLOG(ERROR) << "Failed to evaluate test: "
-                << "\"" << result << "\"";
-  } else {
-    LOG(INFO) << "Test result : "
-              << "\"" << result << "\"";
-  }
-}
-
-}  // namespace dom
-}  // namespace cobalt
diff --git a/cobalt/dom/event_queue.cc b/cobalt/dom/event_queue.cc
index 69e5a3e..1bd5282 100644
--- a/cobalt/dom/event_queue.cc
+++ b/cobalt/dom/event_queue.cc
@@ -20,14 +20,14 @@
 namespace cobalt {
 namespace dom {
 
-EventQueue::EventQueue(EventTarget* event_target)
+EventQueue::EventQueue(web::EventTarget* event_target)
     : event_target_(event_target),
       message_loop_(base::MessageLoop::current()->task_runner()) {
   DCHECK(event_target_);
   DCHECK(message_loop_);
 }
 
-void EventQueue::Enqueue(const scoped_refptr<Event>& event) {
+void EventQueue::Enqueue(const scoped_refptr<web::Event>& event) {
   DCHECK(message_loop_->BelongsToCurrentThread());
 
   if (events_.empty()) {
@@ -61,14 +61,14 @@
 
   // Make sure that the event_target_ stays alive for the duration of
   // all event dispatches.
-  scoped_refptr<EventTarget> keep_alive_reference(event_target_);
+  scoped_refptr<web::EventTarget> keep_alive_reference(event_target_);
 
   firing_events_.swap(events_);
 
   for (Events::iterator iter = firing_events_.begin();
        iter != firing_events_.end(); ++iter) {
-    scoped_refptr<Event>& event = *iter;
-    EventTarget* target =
+    scoped_refptr<web::Event>& event = *iter;
+    web::EventTarget* target =
         event->target() ? event->target().get() : event_target_;
     target->DispatchEvent(event);
   }
diff --git a/cobalt/dom/event_queue.h b/cobalt/dom/event_queue.h
index 967c9f8..624bd8e 100644
--- a/cobalt/dom/event_queue.h
+++ b/cobalt/dom/event_queue.h
@@ -20,8 +20,8 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/message_loop/message_loop.h"
-#include "cobalt/dom/event.h"
-#include "cobalt/dom/event_target.h"
+#include "cobalt/web/event.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace dom {
@@ -41,17 +41,17 @@
  public:
   // The EventTarget is guaranteed to be valid during the life time and should
   // usually be the owner.
-  explicit EventQueue(EventTarget* event_target);
-  void Enqueue(const scoped_refptr<Event>& event);
+  explicit EventQueue(web::EventTarget* event_target);
+  void Enqueue(const scoped_refptr<web::Event>& event);
   void CancelAllEvents();
 
   void TraceMembers(script::Tracer* tracer) override;
 
  private:
-  typedef std::vector<scoped_refptr<Event> > Events;
+  typedef std::vector<scoped_refptr<web::Event> > Events;
   void DispatchEvents();
 
-  EventTarget* event_target_;
+  web::EventTarget* event_target_;
   scoped_refptr<base::SingleThreadTaskRunner> message_loop_;
   Events events_;
   // Events that are currently being fired. We need this as local variable for
diff --git a/cobalt/dom/event_queue_test.cc b/cobalt/dom/event_queue_test.cc
index 625cafb..e8fc35f 100644
--- a/cobalt/dom/event_queue_test.cc
+++ b/cobalt/dom/event_queue_test.cc
@@ -17,11 +17,11 @@
 #include <memory>
 
 #include "base/message_loop/message_loop.h"
-#include "cobalt/dom/event.h"
-#include "cobalt/dom/event_target.h"
-#include "cobalt/dom/testing/mock_event_listener.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
 #include "cobalt/script/testing/fake_script_value.h"
+#include "cobalt/web/event.h"
+#include "cobalt/web/event_target.h"
+#include "cobalt/web/testing/mock_event_listener.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using ::testing::_;
@@ -33,19 +33,19 @@
 namespace dom {
 
 using ::cobalt::script::testing::FakeScriptValue;
-using testing::MockEventListener;
+using web::testing::MockEventListener;
 
 class EventQueueTest : public ::testing::Test {
  protected:
   void ExpectHandleEventCallWithEventAndTarget(
-      const MockEventListener* listener, const scoped_refptr<Event>& event,
-      const scoped_refptr<EventTarget>& target) {
+      const MockEventListener* listener, const scoped_refptr<web::Event>& event,
+      const scoped_refptr<web::EventTarget>& target) {
     // Note that we must pass the raw pointer to avoid reference counting issue.
     EXPECT_CALL(
         *listener,
         HandleEvent(_,
-                    AllOf(Eq(event.get()),
-                          Pointee(Property(&Event::target, Eq(target.get())))),
+                    AllOf(Eq(event.get()), Pointee(Property(&web::Event::target,
+                                                            Eq(target.get())))),
                     _))
         .RetiresOnSaturation();
   }
@@ -54,15 +54,16 @@
 };
 
 TEST_F(EventQueueTest, EventWithoutTargetTest) {
-  scoped_refptr<EventTarget> event_target =
-      new EventTarget(&environment_settings_);
-  scoped_refptr<Event> event = new Event(base::Token("event"));
+  scoped_refptr<web::EventTarget> event_target =
+      new web::EventTarget(&environment_settings_);
+  scoped_refptr<web::Event> event = new web::Event(base::Token("event"));
   std::unique_ptr<MockEventListener> event_listener =
       MockEventListener::Create();
   EventQueue event_queue(event_target.get());
 
   event_target->AddEventListener(
-      "event", FakeScriptValue<EventListener>(event_listener.get()), false);
+      "event", FakeScriptValue<web::EventListener>(event_listener.get()),
+      false);
   ExpectHandleEventCallWithEventAndTarget(event_listener.get(), event,
                                           event_target);
 
@@ -71,16 +72,17 @@
 }
 
 TEST_F(EventQueueTest, EventWithTargetTest) {
-  scoped_refptr<EventTarget> event_target =
-      new EventTarget(&environment_settings_);
-  scoped_refptr<Event> event = new Event(base::Token("event"));
+  scoped_refptr<web::EventTarget> event_target =
+      new web::EventTarget(&environment_settings_);
+  scoped_refptr<web::Event> event = new web::Event(base::Token("event"));
   std::unique_ptr<MockEventListener> event_listener =
       MockEventListener::Create();
   EventQueue event_queue(event_target.get());
 
   event->set_target(event_target);
   event_target->AddEventListener(
-      "event", FakeScriptValue<EventListener>(event_listener.get()), false);
+      "event", FakeScriptValue<web::EventListener>(event_listener.get()),
+      false);
   ExpectHandleEventCallWithEventAndTarget(event_listener.get(), event,
                                           event_target);
 
@@ -89,16 +91,17 @@
 }
 
 TEST_F(EventQueueTest, CancelAllEventsTest) {
-  scoped_refptr<EventTarget> event_target =
-      new EventTarget(&environment_settings_);
-  scoped_refptr<Event> event = new Event(base::Token("event"));
+  scoped_refptr<web::EventTarget> event_target =
+      new web::EventTarget(&environment_settings_);
+  scoped_refptr<web::Event> event = new web::Event(base::Token("event"));
   std::unique_ptr<MockEventListener> event_listener =
       MockEventListener::Create();
   EventQueue event_queue(event_target.get());
 
   event->set_target(event_target);
   event_target->AddEventListener(
-      "event", FakeScriptValue<EventListener>(event_listener.get()), false);
+      "event", FakeScriptValue<web::EventListener>(event_listener.get()),
+      false);
   event_listener->ExpectNoHandleEventCall();
 
   event_queue.Enqueue(event);
@@ -110,11 +113,11 @@
 // correctness of event propagation like capturing or bubbling are tested in
 // the unit tests of EventTarget.
 TEST_F(EventQueueTest, EventWithDifferentTargetTest) {
-  scoped_refptr<EventTarget> event_target_1 =
-      new EventTarget(&environment_settings_);
-  scoped_refptr<EventTarget> event_target_2 =
-      new EventTarget(&environment_settings_);
-  scoped_refptr<Event> event = new Event(base::Token("event"));
+  scoped_refptr<web::EventTarget> event_target_1 =
+      new web::EventTarget(&environment_settings_);
+  scoped_refptr<web::EventTarget> event_target_2 =
+      new web::EventTarget(&environment_settings_);
+  scoped_refptr<web::Event> event = new web::Event(base::Token("event"));
   std::unique_ptr<MockEventListener> event_listener =
       MockEventListener::Create();
 
@@ -122,7 +125,8 @@
 
   event->set_target(event_target_2);
   event_target_2->AddEventListener(
-      "event", FakeScriptValue<EventListener>(event_listener.get()), false);
+      "event", FakeScriptValue<web::EventListener>(event_listener.get()),
+      false);
   ExpectHandleEventCallWithEventAndTarget(event_listener.get(), event,
                                           event_target_2);
 
diff --git a/cobalt/dom/focus_event.cc b/cobalt/dom/focus_event.cc
index d9def1f..664aedd 100644
--- a/cobalt/dom/focus_event.cc
+++ b/cobalt/dom/focus_event.cc
@@ -21,7 +21,7 @@
     : UIEvent(type), related_target_(NULL) {}
 FocusEvent::FocusEvent(base::Token type, Bubbles bubbles, Cancelable cancelable,
                        const scoped_refptr<Window>& view,
-                       const scoped_refptr<EventTarget>& related_target)
+                       const scoped_refptr<web::EventTarget>& related_target)
     : UIEvent(type, bubbles, cancelable, view),
       related_target_(related_target) {}
 
diff --git a/cobalt/dom/focus_event.h b/cobalt/dom/focus_event.h
index f1a3ff4..b7dfb37 100644
--- a/cobalt/dom/focus_event.h
+++ b/cobalt/dom/focus_event.h
@@ -18,8 +18,8 @@
 #include <string>
 
 #include "cobalt/base/token.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/dom/ui_event.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace dom {
@@ -33,11 +33,11 @@
 
   FocusEvent(base::Token type, Bubbles bubbles, Cancelable cancelable,
              const scoped_refptr<Window>& view,
-             const scoped_refptr<EventTarget>& related_target);
+             const scoped_refptr<web::EventTarget>& related_target);
 
   // Web API: FocusEvent
   //
-  const scoped_refptr<EventTarget>& related_target() const {
+  const scoped_refptr<web::EventTarget>& related_target() const {
     return related_target_;
   }
 
@@ -45,7 +45,7 @@
   void TraceMembers(script::Tracer* tracer) override;
 
  protected:
-  scoped_refptr<EventTarget> related_target_;
+  scoped_refptr<web::EventTarget> related_target_;
 };
 
 }  // namespace dom
diff --git a/cobalt/dom/global_stats.cc b/cobalt/dom/global_stats.cc
index 00db059..eccae4c 100644
--- a/cobalt/dom/global_stats.cc
+++ b/cobalt/dom/global_stats.cc
@@ -31,8 +31,6 @@
                            "Total number of currently active string maps."),
       num_dom_token_lists_("Count.DOM.TokenLists", 0,
                            "Total number of currently active token lists."),
-      num_event_listeners_("Count.DOM.EventListeners", 0,
-                           "Total number of currently active event listeners."),
       num_html_collections_(
           "Count.DOM.HtmlCollections", 0,
           "Total number of currently active HTML collections."),
@@ -42,11 +40,6 @@
                  "Total number of currently active nodes."),
       num_node_lists_("Count.DOM.NodeLists", 0,
                       "Total number of currently active node lists."),
-      num_active_java_script_events_(
-          "Count.DOM.ActiveJavaScriptEvents", 0,
-          "Total number of currently active JavaScript events."),
-      num_xhrs_("Count.XHR", 0, "Total number of currently active XHRs."),
-      xhr_memory_("Memory.XHR", 0, "Memory allocated by XHRs in bytes."),
       total_font_request_time_(
           "Time.MainWebModule.DOM.FontCache.TotalFontRequestTime", 0,
           "The time it takes for all fonts requests to complete") {}
@@ -54,12 +47,11 @@
 GlobalStats::~GlobalStats() {}
 
 bool GlobalStats::CheckNoLeaks() {
-  return num_attrs_ == 0 && num_dom_string_maps_ == 0 &&
-         num_dom_token_lists_ == 0 && num_event_listeners_ == 0 &&
+  return web::GlobalStats::GetInstance()->CheckNoLeaks() &&
+         xhr::GlobalStats::GetInstance()->CheckNoLeaks() && num_attrs_ == 0 &&
+         num_dom_string_maps_ == 0 && num_dom_token_lists_ == 0 &&
          num_html_collections_ == 0 && num_named_node_maps_ == 0 &&
-         num_nodes_ == 0 && num_node_lists_ == 0 &&
-         num_active_java_script_events_ == 0 && num_xhrs_ == 0 &&
-         xhr_memory_ == 0;
+         num_nodes_ == 0 && num_node_lists_ == 0;
 }
 
 void GlobalStats::Add(Attr* object) { ++num_attrs_; }
@@ -76,8 +68,6 @@
 
 void GlobalStats::Add(NodeList* object) { ++num_node_lists_; }
 
-void GlobalStats::AddEventListener() { ++num_event_listeners_; }
-
 void GlobalStats::Remove(Attr* object) { --num_attrs_; }
 
 void GlobalStats::Remove(DOMStringMap* object) { --num_dom_string_maps_; }
@@ -92,23 +82,6 @@
 
 void GlobalStats::Remove(NodeList* object) { --num_node_lists_; }
 
-void GlobalStats::RemoveEventListener() { --num_event_listeners_; }
-
-void GlobalStats::StartJavaScriptEvent() { ++num_active_java_script_events_; }
-
-void GlobalStats::StopJavaScriptEvent() { --num_active_java_script_events_; }
-
-void GlobalStats::Add(xhr::XMLHttpRequest* object) { ++num_xhrs_; }
-
-void GlobalStats::Remove(xhr::XMLHttpRequest* object) { --num_xhrs_; }
-
-void GlobalStats::IncreaseXHRMemoryUsage(size_t delta) { xhr_memory_ += delta; }
-
-void GlobalStats::DecreaseXHRMemoryUsage(size_t delta) {
-  DCHECK_GE(xhr_memory_.value(), delta);
-  xhr_memory_ -= delta;
-}
-
 void GlobalStats::OnFontRequestComplete(int64 start_time) {
   total_font_request_time_ +=
       base::TimeTicks::Now().ToInternalValue() - start_time;
diff --git a/cobalt/dom/global_stats.h b/cobalt/dom/global_stats.h
index 7a2babf..69fc206 100644
--- a/cobalt/dom/global_stats.h
+++ b/cobalt/dom/global_stats.h
@@ -18,13 +18,10 @@
 #include "base/compiler_specific.h"
 #include "base/memory/singleton.h"
 #include "cobalt/base/c_val.h"
+#include "cobalt/web/global_stats.h"
+#include "cobalt/xhr/global_stats.h"
 
 namespace cobalt {
-
-namespace xhr {
-class XMLHttpRequest;
-}  // namespace xhr
-
 namespace dom {
 
 class Attr;
@@ -38,6 +35,8 @@
 // This singleton class is used to track DOM-related statistics.
 class GlobalStats {
  public:
+  GlobalStats(const GlobalStats&) = delete;
+  GlobalStats& operator=(const GlobalStats&) = delete;
   static GlobalStats* GetInstance();
 
   bool CheckNoLeaks();
@@ -49,7 +48,9 @@
   void Add(NamedNodeMap* object);
   void Add(Node* object);
   void Add(NodeList* object);
-  void AddEventListener();
+  void AddEventListener() {
+    web::GlobalStats::GetInstance()->AddEventListener();
+  }
   void Remove(Attr* object);
   void Remove(DOMStringMap* object);
   void Remove(DOMTokenList* object);
@@ -57,18 +58,21 @@
   void Remove(NamedNodeMap* object);
   void Remove(Node* object);
   void Remove(NodeList* object);
-  void RemoveEventListener();
+  void RemoveEventListener() {
+    web::GlobalStats::GetInstance()->RemoveEventListener();
+  }
 
-  int GetNumEventListeners() const { return num_event_listeners_; }
+  int GetNumEventListeners() const {
+    return web::GlobalStats::GetInstance()->GetNumEventListeners();
+  }
   int GetNumNodes() const { return num_nodes_; }
 
-  void StartJavaScriptEvent();
-  void StopJavaScriptEvent();
-
-  void Add(xhr::XMLHttpRequest* object);
-  void Remove(xhr::XMLHttpRequest* object);
-  void IncreaseXHRMemoryUsage(size_t delta);
-  void DecreaseXHRMemoryUsage(size_t delta);
+  void StartJavaScriptEvent() {
+    web::GlobalStats::GetInstance()->StartJavaScriptEvent();
+  }
+  void StopJavaScriptEvent() {
+    web::GlobalStats::GetInstance()->StopJavaScriptEvent();
+  }
 
   void OnFontRequestComplete(int64 start_time);
 
@@ -80,20 +84,12 @@
   base::CVal<int> num_attrs_;
   base::CVal<int> num_dom_string_maps_;
   base::CVal<int> num_dom_token_lists_;
-  base::CVal<int, base::CValPublic> num_event_listeners_;
   base::CVal<int> num_html_collections_;
   base::CVal<int> num_named_node_maps_;
   base::CVal<int, base::CValPublic> num_nodes_;
   base::CVal<int> num_node_lists_;
 
-  base::CVal<int> num_active_java_script_events_;
-
-  // XHR-related tracking
-  base::CVal<int> num_xhrs_;
-  base::CVal<base::cval::SizeInBytes> xhr_memory_;
-
   friend struct base::DefaultSingletonTraits<GlobalStats>;
-  DISALLOW_COPY_AND_ASSIGN(GlobalStats);
 
   // Font-related tracking
   base::CVal<int64, base::CValPublic> total_font_request_time_;
diff --git a/cobalt/dom/html_anchor_element.h b/cobalt/dom/html_anchor_element.h
index 0f78121..21fb3d2 100644
--- a/cobalt/dom/html_anchor_element.h
+++ b/cobalt/dom/html_anchor_element.h
@@ -20,7 +20,7 @@
 #include "base/bind.h"
 #include "base/compiler_specific.h"
 #include "cobalt/dom/html_element.h"
-#include "cobalt/dom/url_utils.h"
+#include "cobalt/web/url_utils.h"
 
 namespace cobalt {
 namespace dom {
@@ -91,7 +91,7 @@
 
   void UpdateSteps(const std::string& value);
 
-  URLUtils url_utils_;
+  web::URLUtils url_utils_;
 };
 
 }  // namespace dom
diff --git a/cobalt/dom/html_element.cc b/cobalt/dom/html_element.cc
index a8c44e5..aa38f18 100644
--- a/cobalt/dom/html_element.cc
+++ b/cobalt/dom/html_element.cc
@@ -33,7 +33,6 @@
 #include "cobalt/cssom/property_list_value.h"
 #include "cobalt/cssom/selector_tree.h"
 #include "cobalt/cssom/viewport_size.h"
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/dom_animatable.h"
 #include "cobalt/dom/dom_string_map.h"
@@ -63,6 +62,7 @@
 #include "cobalt/dom/text.h"
 #include "cobalt/loader/image/animated_image_tracker.h"
 #include "cobalt/loader/resource_cache.h"
+#include "cobalt/web/csp_delegate.h"
 #include "third_party/icu/source/common/unicode/uchar.h"
 #include "third_party/icu/source/common/unicode/utf8.h"
 
@@ -834,10 +834,10 @@
 
 void HTMLElement::SetStyleAttribute(const std::string& value) {
   Document* document = node_document();
-  CspDelegate* csp_delegate = document->csp_delegate();
+  web::CspDelegate* csp_delegate = document->csp_delegate();
   if (value.empty() ||
       csp_delegate->AllowInline(
-          CspDelegate::kStyle,
+          web::CspDelegate::kStyle,
           base::SourceLocation(GetSourceLocationName(), 1, 1), value)) {
     style_->set_css_text(value, NULL);
     Element::SetStyleAttribute(value);
@@ -1221,8 +1221,8 @@
 void HTMLElement::OnUiNavScroll(SbTimeMonotonic /* time */) {
   Document* document = node_document();
   scoped_refptr<Window> window(document ? document->window() : nullptr);
-  DispatchEvent(new UIEvent(base::Tokens::scroll(), Event::kBubbles,
-                            Event::kNotCancelable, window));
+  DispatchEvent(new UIEvent(base::Tokens::scroll(), web::Event::kBubbles,
+                            web::Event::kNotCancelable, window));
 }
 
 HTMLElement::HTMLElement(Document* document, base::Token local_name)
@@ -1380,8 +1380,8 @@
   // to receive focus. This event type is similar to focus, but is dispatched
   // before focus is shifted, and does bubble.
   //   https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-focusin
-  DispatchEvent(new FocusEvent(base::Tokens::focusin(), Event::kBubbles,
-                               Event::kNotCancelable, document->window(),
+  DispatchEvent(new FocusEvent(base::Tokens::focusin(), web::Event::kBubbles,
+                               web::Event::kNotCancelable, document->window(),
                                this));
 
   // 3. Make the element the currently focused element in its top-level browsing
@@ -1396,8 +1396,8 @@
   // event type. This event type is similar to focusin, but is dispatched after
   // focus is shifted, and does not bubble.
   //   https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-focus
-  DispatchEvent(new FocusEvent(base::Tokens::focus(), Event::kNotBubbles,
-                               Event::kNotCancelable, document->window(),
+  DispatchEvent(new FocusEvent(base::Tokens::focus(), web::Event::kNotBubbles,
+                               web::Event::kNotCancelable, document->window(),
                                this));
 
   // Custom, not in any spec.
@@ -1417,8 +1417,8 @@
   //   https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-focusout
   Document* document = node_document();
   scoped_refptr<Window> window(document ? document->window() : NULL);
-  DispatchEvent(new FocusEvent(base::Tokens::focusout(), Event::kBubbles,
-                               Event::kNotCancelable, window, this));
+  DispatchEvent(new FocusEvent(base::Tokens::focusout(), web::Event::kBubbles,
+                               web::Event::kNotCancelable, window, this));
 
   // 2. Unfocus the element.
   if (document && document->active_element() == this->AsElement()) {
@@ -1431,8 +1431,8 @@
   // event type. This event type is similar to focusout, but is dispatched after
   // focus is shifted, and does not bubble.
   //   https://www.w3.org/TR/2016/WD-uievents-20160804/#event-type-blur
-  DispatchEvent(new FocusEvent(base::Tokens::blur(), Event::kNotBubbles,
-                               Event::kNotCancelable, document->window(),
+  DispatchEvent(new FocusEvent(base::Tokens::blur(), web::Event::kNotBubbles,
+                               web::Event::kNotCancelable, document->window(),
                                this));
 
   // Custom, not in any spec.
diff --git a/cobalt/dom/html_element_context.h b/cobalt/dom/html_element_context.h
index cb4183d..4f4ff40 100644
--- a/cobalt/dom/html_element_context.h
+++ b/cobalt/dom/html_element_context.h
@@ -26,7 +26,6 @@
 #include "cobalt/dom/dom_stat_tracker.h"
 #include "cobalt/dom/parser.h"
 #include "cobalt/dom/performance.h"
-#include "cobalt/dom/url_registry.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/font/remote_typeface_cache.h"
 #include "cobalt/loader/image/animated_image_tracker.h"
@@ -37,6 +36,7 @@
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/script_runner.h"
 #include "cobalt/script/script_value_factory.h"
+#include "cobalt/web/url_registry.h"
 
 namespace cobalt {
 namespace dom {
@@ -49,7 +49,7 @@
 // HTML elements.
 class HTMLElementContext {
  public:
-  typedef UrlRegistry<MediaSource> MediaSourceRegistry;
+  typedef web::UrlRegistry<MediaSource> MediaSourceRegistry;
 
 #if !defined(COBALT_BUILD_TYPE_GOLD)
   // No-args constructor for tests.
diff --git a/cobalt/dom/html_element_factory_test.cc b/cobalt/dom/html_element_factory_test.cc
index 1c8907e..3ef2db6 100644
--- a/cobalt/dom/html_element_factory_test.cc
+++ b/cobalt/dom/html_element_factory_test.cc
@@ -43,10 +43,10 @@
 #include "cobalt/dom/lottie_player.h"
 #include "cobalt/dom/testing/stub_css_parser.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
-#include "cobalt/dom/testing/stub_script_runner.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/loader_factory.h"
+#include "cobalt/script/testing/stub_script_runner.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
@@ -74,8 +74,7 @@
             NULL /* remote_typeface_cache */, NULL /* mesh_cache */,
             dom_stat_tracker_.get(), "" /* language */,
             base::kApplicationStateStarted,
-            NULL /* synchronous_loader_interrupt */,
-            NULL /* performance */),
+            NULL /* synchronous_loader_interrupt */, NULL /* performance */),
         document_(new Document(&html_element_context_)) {}
   ~HTMLElementFactoryTest() override {}
 
@@ -85,7 +84,7 @@
   loader::LoaderFactory loader_factory_;
   std::unique_ptr<Parser> dom_parser_;
   testing::StubCSSParser stub_css_parser_;
-  testing::StubScriptRunner stub_script_runner_;
+  script::testing::StubScriptRunner stub_script_runner_;
   std::unique_ptr<DomStatTracker> dom_stat_tracker_;
   HTMLElementContext html_element_context_;
   scoped_refptr<Document> document_;
diff --git a/cobalt/dom/html_image_element.cc b/cobalt/dom/html_image_element.cc
index f931af3..2cbc162 100644
--- a/cobalt/dom/html_image_element.cc
+++ b/cobalt/dom/html_image_element.cc
@@ -21,12 +21,12 @@
 #include "base/message_loop/message_loop.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/html_element_context.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/script/global_environment.h"
+#include "cobalt/web/csp_delegate.h"
 #include "url/gurl.h"
 
 namespace cobalt {
@@ -239,8 +239,8 @@
   const GURL selected_source = base_url.Resolve(src);
 
   html_element_context()->performance()->CreatePerformanceResourceTiming(
-      cached_image_loaded_callback_handler_->GetLoadTimingInfo(),
-      kTagName, selected_source.spec());
+      cached_image_loaded_callback_handler_->GetLoadTimingInfo(), kTagName,
+      selected_source.spec());
 }
 
 }  // namespace dom
diff --git a/cobalt/dom/html_link_element.cc b/cobalt/dom/html_link_element.cc
index 906822d..b51d697 100644
--- a/cobalt/dom/html_link_element.cc
+++ b/cobalt/dom/html_link_element.cc
@@ -24,10 +24,10 @@
 #include "base/trace_event/trace_event.h"
 #include "cobalt/cssom/css_parser.h"
 #include "cobalt/cssom/css_style_sheet.h"
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/html_element_context.h"
 #include "cobalt/dom/window.h"
+#include "cobalt/web/csp_delegate.h"
 #include "nb/memory_scope.h"
 #include "url/gurl.h"
 
@@ -59,14 +59,15 @@
   return is_valid_format;
 }
 
-CspDelegate::ResourceType GetCspResourceTypeForRel(const std::string& rel) {
+web::CspDelegate::ResourceType GetCspResourceTypeForRel(
+    const std::string& rel) {
   if (rel == "stylesheet") {
-    return CspDelegate::kStyle;
+    return web::CspDelegate::kStyle;
   } else if (IsValidSplashScreenFormat(rel)) {
-    return CspDelegate::kLocation;
+    return web::CspDelegate::kLocation;
   } else {
     NOTIMPLEMENTED();
-    return CspDelegate::kImage;
+    return web::CspDelegate::kImage;
   }
 }
 
@@ -191,7 +192,7 @@
   // attribute, the origin being the origin of the link element's Document, and
   // the default origin behaviour set to taint.
   csp::SecurityCallback csp_callback = base::Bind(
-      &CspDelegate::CanLoad, base::Unretained(document->csp_delegate()),
+      &web::CspDelegate::CanLoad, base::Unretained(document->csp_delegate()),
       GetCspResourceTypeForRel(rel()));
 
   fetched_last_url_origin_ = loader::Origin();
diff --git a/cobalt/dom/html_link_element.h b/cobalt/dom/html_link_element.h
index 981abac..620d34c 100644
--- a/cobalt/dom/html_link_element.h
+++ b/cobalt/dom/html_link_element.h
@@ -23,10 +23,10 @@
 #include "base/threading/thread_checker.h"
 #include "cobalt/cssom/style_sheet.h"
 #include "cobalt/dom/html_element.h"
-#include "cobalt/dom/url_utils.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/loader.h"
 #include "cobalt/loader/text_decoder.h"
+#include "cobalt/web/url_utils.h"
 
 namespace cobalt {
 namespace dom {
diff --git a/cobalt/dom/html_media_element.cc b/cobalt/dom/html_media_element.cc
index 7539d91..91bb221 100644
--- a/cobalt/dom/html_media_element.cc
+++ b/cobalt/dom/html_media_element.cc
@@ -30,10 +30,7 @@
 #include "cobalt/base/instance_counter.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/cssom/map_to_mesh_function.h"
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/document.h"
-#include "cobalt/dom/dom_exception.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/dom/html_element_context.h"
 #include "cobalt/dom/html_video_element.h"
 #include "cobalt/dom/media_source.h"
@@ -42,6 +39,9 @@
 #include "cobalt/media/fetcher_buffered_data_source.h"
 #include "cobalt/media/web_media_player_factory.h"
 #include "cobalt/script/script_value_factory.h"
+#include "cobalt/web/csp_delegate.h"
+#include "cobalt/web/dom_exception.h"
+#include "cobalt/web/event.h"
 
 #include "cobalt/dom/eme/media_encrypted_event.h"
 #include "cobalt/dom/eme/media_encrypted_event_init.h"
@@ -237,6 +237,8 @@
 
 std::string HTMLMediaElement::CanPlayType(const std::string& mime_type,
                                           const std::string& key_system) {
+  TRACE_EVENT2("cobalt::dom", "HTMLMediaElement::CanPlayType()", "mime_type",
+               mime_type, "key_system", key_system);
   DCHECK(html_element_context()->can_play_type_handler());
 
   DLOG_IF(ERROR, !key_system.empty())
@@ -263,8 +265,8 @@
   return result;
 }
 
-const EventTarget::EventListenerScriptValue* HTMLMediaElement::onencrypted()
-    const {
+const web::EventTarget::EventListenerScriptValue*
+HTMLMediaElement::onencrypted() const {
   return GetAttributeEventListener(base::Tokens::encrypted());
 }
 
@@ -363,7 +365,8 @@
   // exception.
   if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing || !player_) {
     LOG(ERROR) << "invalid state error";
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
   Seek(time);
@@ -559,7 +562,7 @@
                                   script::ExceptionState* exception_state) {
   LOG(INFO) << "Change volume from " << volume_ << " to " << volume << ".";
   if (volume < 0.0f || volume > 1.0f) {
-    DOMException::Raise(DOMException::kIndexSizeErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kIndexSizeErr, exception_state);
     return;
   }
 
@@ -628,7 +631,7 @@
   }
 }
 
-void HTMLMediaElement::ScheduleEvent(const scoped_refptr<Event>& event) {
+void HTMLMediaElement::ScheduleEvent(const scoped_refptr<web::Event>& event) {
   TRACE_EVENT0("cobalt::dom", "HTMLMediaElement::ScheduleEvent()");
   MLOG() << "Schedule event " << event->type() << ".";
   event_queue_.Enqueue(event);
@@ -831,7 +834,7 @@
   DCHECK(!media_source_);
   if (url.SchemeIs(kMediaSourceUrlProtocol)) {
     // Check whether url is allowed by security policy.
-    if (!node_document()->csp_delegate()->CanLoad(CspDelegate::kMedia, url,
+    if (!node_document()->csp_delegate()->CanLoad(web::CspDelegate::kMedia, url,
                                                   false)) {
       DLOG(INFO) << "URL " << url << " is rejected by security policy.";
       NoneSupported("URL is rejected by security policy.");
@@ -877,9 +880,10 @@
   if (url.spec() == SourceURL()) {
     player_->LoadMediaSource();
   } else {
-    csp::SecurityCallback csp_callback = base::Bind(
-        &CspDelegate::CanLoad,
-        base::Unretained(node_document()->csp_delegate()), CspDelegate::kMedia);
+    csp::SecurityCallback csp_callback =
+        base::Bind(&web::CspDelegate::CanLoad,
+                   base::Unretained(node_document()->csp_delegate()),
+                   web::CspDelegate::kMedia);
     request_mode_ = GetRequestMode(GetAttribute("crossOrigin"));
     DCHECK(node_document()->location());
     std::unique_ptr<BufferedDataSource> data_source(
@@ -1060,8 +1064,8 @@
   LOG_IF(INFO, event_name == base::Tokens::error())
       << "onerror event fired with error " << (error_ ? error_->code() : 0);
   MLOG() << event_name;
-  scoped_refptr<Event> event =
-      new Event(event_name, Event::kNotBubbles, Event::kCancelable);
+  scoped_refptr<web::Event> event = new web::Event(
+      event_name, web::Event::kNotBubbles, web::Event::kCancelable);
   event->set_target(this);
 
   ScheduleEvent(event);
@@ -1612,8 +1616,6 @@
   EndProcessingMediaPlayerCallback();
 }
 
-void HTMLMediaElement::SawUnsupportedTracks() { NOTIMPLEMENTED(); }
-
 float HTMLMediaElement::Volume() const { return muted_ ? 0 : volume(NULL); }
 
 void HTMLMediaElement::SourceOpened(ChunkDemuxer* chunk_demuxer) {
@@ -1716,7 +1718,8 @@
     script::ExceptionState* exception_state) {
   if (GetAttribute("src").value_or("").length() > 0) {
     LOG(WARNING) << "Cannot set maximum capabilities after src is defined.";
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
   max_video_capabilities_ = max_video_capabilities;
diff --git a/cobalt/dom/html_media_element.h b/cobalt/dom/html_media_element.h
index 6d708fd..a8ff601 100644
--- a/cobalt/dom/html_media_element.h
+++ b/cobalt/dom/html_media_element.h
@@ -144,7 +144,7 @@
 
   // Let other objects add event to the EventQueue of HTMLMediaElement.  This
   // function won't modify the target of the |event| passed in.
-  void ScheduleEvent(const scoped_refptr<Event>& event);
+  void ScheduleEvent(const scoped_refptr<web::Event>& event);
 
   // Set max video capabilities.
   void SetMaxVideoCapabilities(const std::string& max_video_capabilities,
@@ -230,7 +230,6 @@
   void OutputModeChanged() override;
   void ContentSizeChanged() override;
   void PlaybackStateChanged() override;
-  void SawUnsupportedTracks() override;
   float Volume() const override;
   void SourceOpened(ChunkDemuxer* chunk_demuxer) override;
   std::string SourceURL() const override;
diff --git a/cobalt/dom/html_meta_element.cc b/cobalt/dom/html_meta_element.cc
index faf39d2..3852a5a 100644
--- a/cobalt/dom/html_meta_element.cc
+++ b/cobalt/dom/html_meta_element.cc
@@ -17,10 +17,10 @@
 #include "base/memory/ref_counted.h"
 #include "base/strings/string_util.h"
 #include "cobalt/csp/content_security_policy.h"
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/html_head_element.h"
 #include "cobalt/dom/node.h"
+#include "cobalt/web/csp_delegate.h"
 
 namespace cobalt {
 namespace dom {
diff --git a/cobalt/dom/html_script_element.cc b/cobalt/dom/html_script_element.cc
index 0261bb6..a036d7c 100644
--- a/cobalt/dom/html_script_element.cc
+++ b/cobalt/dom/html_script_element.cc
@@ -24,7 +24,6 @@
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/console_log.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/html_element_context.h"
@@ -34,6 +33,7 @@
 #include "cobalt/loader/text_decoder.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/script_runner.h"
+#include "cobalt/web/csp_delegate.h"
 #include "nb/memory_scope.h"
 #include "url/gurl.h"
 
@@ -288,19 +288,19 @@
 
   // https://www.w3.org/TR/CSP2/#directive-script-src
 
-  CspDelegate* csp_delegate = document_->csp_delegate();
+  web::CspDelegate* csp_delegate = document_->csp_delegate();
   // If the script element has a valid nonce, we always permit it, regardless
   // of its URL or inline nature.
   const bool bypass_csp =
-      csp_delegate->IsValidNonce(CspDelegate::kScript, nonce());
+      csp_delegate->IsValidNonce(web::CspDelegate::kScript, nonce());
 
   csp::SecurityCallback csp_callback;
   if (bypass_csp) {
     csp_callback = base::Bind(&PermitAnyURL);
   } else {
     csp_callback =
-        base::Bind(&CspDelegate::CanLoad, base::Unretained(csp_delegate),
-                   CspDelegate::kScript);
+        base::Bind(&web::CspDelegate::CanLoad, base::Unretained(csp_delegate),
+                   web::CspDelegate::kScript);
   }
 
   // Clear fetched resource's origin before start.
@@ -417,7 +417,7 @@
       base::Optional<std::string> content = text_content();
       const std::string& text = content.value_or(base::EmptyString());
       if (bypass_csp || text.empty() ||
-          csp_delegate->AllowInline(CspDelegate::kScript,
+          csp_delegate->AllowInline(web::CspDelegate::kScript,
                                     inline_script_location_, text)) {
         fetched_last_url_origin_ = document_->location()->GetOriginAsObject();
         ExecuteInternal();
@@ -601,7 +601,7 @@
     // 404 error)
     // Executing the script block must just consist of firing a simple event
     // named error at the element.
-    DispatchEvent(new Event(base::Tokens::error()));
+    DispatchEvent(new web::Event(base::Tokens::error()));
   } else {
     DCHECK(content_);
     Execute(*content_, base::SourceLocation(url_.spec(), 1, 1), true);
@@ -668,8 +668,8 @@
   // named load at the script element.
   // TODO: Remove the firing of readystatechange once we support Promise.
   if (is_external) {
-    DispatchEvent(new Event(base::Tokens::load()));
-    DispatchEvent(new Event(base::Tokens::readystatechange()));
+    DispatchEvent(new web::Event(base::Tokens::load()));
+    DispatchEvent(new web::Event(base::Tokens::readystatechange()));
   } else {
     PreventGarbageCollectionAndPostToDispatchEvent(
         FROM_HERE, base::Tokens::load(),
diff --git a/cobalt/dom/html_script_element.h b/cobalt/dom/html_script_element.h
index 7c5fa14..3c9740e 100644
--- a/cobalt/dom/html_script_element.h
+++ b/cobalt/dom/html_script_element.h
@@ -24,9 +24,9 @@
 #include "base/threading/thread_checker.h"
 #include "cobalt/base/source_location.h"
 #include "cobalt/dom/html_element.h"
-#include "cobalt/dom/url_utils.h"
 #include "cobalt/loader/loader.h"
 #include "cobalt/script/global_environment.h"
+#include "cobalt/web/url_utils.h"
 
 namespace cobalt {
 namespace dom {
diff --git a/cobalt/dom/html_style_element.cc b/cobalt/dom/html_style_element.cc
index aa80cde..1706b3f 100644
--- a/cobalt/dom/html_style_element.cc
+++ b/cobalt/dom/html_style_element.cc
@@ -17,9 +17,9 @@
 #include <string>
 
 #include "cobalt/cssom/css_parser.h"
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/html_element_context.h"
+#include "cobalt/web/csp_delegate.h"
 
 namespace cobalt {
 namespace dom {
@@ -72,14 +72,14 @@
     return;
   }
 
-  CspDelegate* csp_delegate = document->csp_delegate();
+  web::CspDelegate* csp_delegate = document->csp_delegate();
   // If the style element has a valid nonce, we always permit it.
   const bool bypass_csp = csp_delegate->IsValidNonce(
-      CspDelegate::kStyle, GetAttribute("nonce").value_or(""));
+      web::CspDelegate::kStyle, GetAttribute("nonce").value_or(""));
 
   base::Optional<std::string> content = text_content();
   const std::string& text = content.value_or(base::EmptyString());
-  if (bypass_csp || csp_delegate->AllowInline(CspDelegate::kStyle,
+  if (bypass_csp || csp_delegate->AllowInline(web::CspDelegate::kStyle,
                                               inline_style_location_, text)) {
     scoped_refptr<cssom::CSSStyleSheet> css_style_sheet =
         document->html_element_context()->css_parser()->ParseStyleSheet(
diff --git a/cobalt/dom/html_video_element.cc b/cobalt/dom/html_video_element.cc
index cc14883..de2e639 100644
--- a/cobalt/dom/html_video_element.cc
+++ b/cobalt/dom/html_video_element.cc
@@ -16,6 +16,7 @@
 
 #include "base/logging.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/trace_event/trace_event.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/performance.h"
 #include "cobalt/dom/window.h"
@@ -78,6 +79,7 @@
 
 scoped_refptr<VideoPlaybackQuality> HTMLVideoElement::GetVideoPlaybackQuality(
     script::EnvironmentSettings* environment_settings) const {
+  TRACE_EVENT0("cobalt::dom", "HTMLVideoElement::GetVideoPlaybackQuality()");
   DOMSettings* dom_settings =
       base::polymorphic_downcast<DOMSettings*>(environment_settings);
   DCHECK(dom_settings);
diff --git a/cobalt/dom/location.cc b/cobalt/dom/location.cc
index e34db7c..f77b30c 100644
--- a/cobalt/dom/location.cc
+++ b/cobalt/dom/location.cc
@@ -17,6 +17,7 @@
 #include "base/bind.h"
 #include "base/compiler_specific.h"
 #include "base/logging.h"
+#include "cobalt/web/location_base.h"
 
 namespace cobalt {
 namespace dom {
@@ -26,27 +27,29 @@
                    const csp::SecurityCallback& security_callback,
                    const base::Callback<void(NavigationType type)>&
                        set_navigation_type_callback)
-    : ALLOW_THIS_IN_INITIALIZER_LIST(url_utils_(
-          url, base::Bind(&Location::Replace, base::Unretained(this)))),
+    : web::LocationBase(url),
       hashchange_callback_(hashchange_callback),
       navigation_callback_(navigation_callback),
       security_callback_(security_callback),
-      set_navigation_type_callback_(set_navigation_type_callback) {}
+      set_navigation_type_callback_(set_navigation_type_callback) {
+  set_update_steps_callback(
+      base::Bind(&Location::Replace, base::Unretained(this)));
+}
 
 // Algorithm for Replace:
 //   https://www.w3.org/TR/html50/browsers.html#dom-location-replace
-void Location::Replace(const std::string& url) {
+void Location::Replace(const std::string& new_url_string) {
   // When the replace(url) method is invoked, the UA must resolve the argument,
   // relative to the API base URL specified by the entry settings object, and if
   // that is successful, navigate the browsing context to the specified url with
   // replacement enabled and exceptions enabled.
 
-  GURL new_url = url_utils_.url().Resolve(url);
+  GURL new_url = url().Resolve(new_url_string);
   if (!new_url.is_valid()) {
     DLOG(WARNING) << "New url is invalid, aborting the navigation.";
     return;
   }
-  const GURL& old_url = url_utils_.url();
+  const GURL& old_url = url();
 
   // The following codes correspond to navigating the browsing context in HTML5.
   //   https://www.w3.org/TR/html50/browsers.html#navigate
@@ -73,7 +76,7 @@
   if (new_url.ReplaceComponents(replacements) ==
           old_url.ReplaceComponents(replacements) &&
       new_url.has_ref() && new_url.ref() != old_url.ref()) {
-    url_utils_.set_url(new_url);
+    set_url(new_url);
     if (!hashchange_callback_.is_null()) {
       hashchange_callback_.Run();
     }
diff --git a/cobalt/dom/location.h b/cobalt/dom/location.h
index b103683..8eccc90 100644
--- a/cobalt/dom/location.h
+++ b/cobalt/dom/location.h
@@ -20,8 +20,8 @@
 #include "base/callback.h"
 #include "cobalt/csp/content_security_policy.h"
 #include "cobalt/dom/navigation_type.h"
-#include "cobalt/dom/url_utils.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/location_base.h"
 #include "url/gurl.h"
 
 namespace cobalt {
@@ -32,15 +32,18 @@
 // the browsing context's session history to be changed, by adding or replacing
 // entries in the history object.
 //   https://www.w3.org/TR/html50/browsers.html#the-location-interface
-class Location : public script::Wrappable {
+class Location : public web::LocationBase {
  public:
   // If any navigation is triggered, all these callbacks should be provided,
   // otherwise they can be empty.
+  explicit Location(const GURL& url) : web::LocationBase(url) {}
   Location(const GURL& url, const base::Closure& hashchange_callback,
            const base::Callback<void(const GURL&)>& navigation_callback,
            const csp::SecurityCallback& security_callback,
            const base::Callback<void(NavigationType type)>&
                set_navigation_type_callback);
+  Location(const Location&) = delete;
+  Location& operator=(const Location&) = delete;
 
   // Web API: Location
   //
@@ -52,62 +55,15 @@
 
   void Reload();
 
-  // Web API: URLUtils (implements)
-  //
-  std::string href() const { return url_utils_.href(); }
-  void set_href(const std::string& href) { url_utils_.set_href(href); }
-
-  std::string protocol() const { return url_utils_.protocol(); }
-  void set_protocol(const std::string& protocol) {
-    url_utils_.set_protocol(protocol);
-  }
-
-  std::string host() const { return url_utils_.host(); }
-  void set_host(const std::string& host) { url_utils_.set_host(host); }
-
-  std::string hostname() const { return url_utils_.hostname(); }
-  void set_hostname(const std::string& hostname) {
-    url_utils_.set_hostname(hostname);
-  }
-
-  std::string port() const { return url_utils_.port(); }
-  void set_port(const std::string& port) { url_utils_.set_port(port); }
-
-  std::string pathname() const { return url_utils_.pathname(); }
-  void set_pathname(const std::string& pathname) {
-    url_utils_.set_pathname(pathname);
-  }
-
-  std::string hash() const { return url_utils_.hash(); }
-  void set_hash(const std::string& hash) { url_utils_.set_hash(hash); }
-
-  std::string search() const { return url_utils_.search(); }
-  void set_search(const std::string& search) { url_utils_.set_search(search); }
-
-  std::string origin() const { return url_utils_.origin(); }
-
-  // Custom, not in any spec.
-  //
-  // Gets and sets the URL without doing any navigation.
-  const GURL& url() const { return url_utils_.url(); }
-  void set_url(const GURL& url) { url_utils_.set_url(url); }
-
-  loader::Origin GetOriginAsObject() const {
-    return url_utils_.GetOriginAsObject();
-  }
-
   DEFINE_WRAPPABLE_TYPE(Location);
 
  private:
   ~Location() override {}
 
-  URLUtils url_utils_;
   base::Closure hashchange_callback_;
   base::Callback<void(const GURL&)> navigation_callback_;
   csp::SecurityCallback security_callback_;
   const base::Callback<void(NavigationType)> set_navigation_type_callback_;
-
-  DISALLOW_COPY_AND_ASSIGN(Location);
 };
 
 }  // namespace dom
diff --git a/cobalt/dom/location.idl b/cobalt/dom/location.idl
index 0b96c5a..bb9c1a0 100644
--- a/cobalt/dom/location.idl
+++ b/cobalt/dom/location.idl
@@ -14,7 +14,7 @@
 
 // https://www.w3.org/TR/html50/browsers.html#the-location-interface
 
-[Unforgeable] interface Location {
+[Exposed = Window, Unforgeable] interface Location {
   void assign(DOMString url);
   void replace(DOMString url);
   void reload();
diff --git a/cobalt/dom/location_test.cc b/cobalt/dom/location_test.cc
index a63cab6..9e4caf2 100644
--- a/cobalt/dom/location_test.cc
+++ b/cobalt/dom/location_test.cc
@@ -146,8 +146,111 @@
   location_->set_protocol("http");
 }
 
-INSTANTIATE_TEST_CASE_P(SecurityTests, LocationTestWithParams,
+INSTANTIATE_TEST_CASE_P(LocationTests, LocationTestWithParams,
                         ::testing::Bool());
 
+// Note: The following tests duplicate the tests in URLUtilsTest, because
+// Location implements URLUtils.
+
+TEST(LocationTest, GettersShouldReturnExpectedFormat) {
+  scoped_refptr<Location> location(
+      new Location(GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+
+  EXPECT_EQ("https://user:pass@google.com:99/foo;bar?q=a#ref",
+            location->href());
+  EXPECT_EQ("https:", location->protocol());
+  EXPECT_EQ("google.com:99", location->host());
+  EXPECT_EQ("google.com", location->hostname());
+  EXPECT_EQ("99", location->port());
+  EXPECT_EQ("/foo;bar", location->pathname());
+  EXPECT_EQ("#ref", location->hash());
+  EXPECT_EQ("?q=a", location->search());
+}
+
+TEST(LocationTest, SetHref) {
+  scoped_refptr<Location> location(
+      new Location(GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  location->set_href("http://www.youtube.com");
+  EXPECT_TRUE(location->url().is_valid());
+  EXPECT_EQ("http://www.youtube.com/", location->href());
+}
+
+TEST(LocationTest, SetProtocolShouldWorkAsExpected) {
+  scoped_refptr<Location> location(
+      new Location(GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  location->set_protocol("http");
+  EXPECT_TRUE(location->url().is_valid());
+  EXPECT_EQ("http://user:pass@google.com:99/foo;bar?q=a#ref", location->href());
+}
+
+TEST(LocationTest, SetHostShouldWorkAsExpected) {
+  scoped_refptr<Location> location(
+      new Location(GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  location->set_host("youtube.com");
+  EXPECT_TRUE(location->url().is_valid());
+  EXPECT_EQ("https://user:pass@youtube.com:99/foo;bar?q=a#ref",
+            location->href());
+
+  location->set_host("google.com:100");
+  EXPECT_TRUE(location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:100/foo;bar?q=a#ref",
+            location->href());
+}
+
+TEST(LocationTest, SetHostnameShouldWorkAsExpected) {
+  scoped_refptr<Location> location(
+      new Location(GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  location->set_hostname("youtube.com");
+  EXPECT_TRUE(location->url().is_valid());
+  EXPECT_EQ("https://user:pass@youtube.com:99/foo;bar?q=a#ref",
+            location->href());
+}
+
+TEST(LocationTest, SetPortShouldWorkAsExpected) {
+  scoped_refptr<Location> location(
+      new Location(GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  location->set_port("100");
+  EXPECT_TRUE(location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:100/foo;bar?q=a#ref",
+            location->href());
+}
+
+TEST(LocationTest, SetPathnameShouldWorkAsExpected) {
+  scoped_refptr<Location> location(
+      new Location(GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  location->set_pathname("baz");
+  EXPECT_TRUE(location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:99/baz?q=a#ref", location->href());
+}
+
+TEST(LocationTest, SetHashShouldWorkAsExpected) {
+  scoped_refptr<Location> location(
+      new Location(GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  location->set_hash("hash");
+  EXPECT_TRUE(location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:99/foo;bar?q=a#hash",
+            location->href());
+
+  location->set_hash("#hash2");
+  EXPECT_TRUE(location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:99/foo;bar?q=a#hash2",
+            location->href());
+}
+
+TEST(LocationTest, SetSearchShouldWorkAsExpected) {
+  scoped_refptr<Location> location(
+      new Location(GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  location->set_search("b=c");
+  EXPECT_TRUE(location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:99/foo;bar?b=c#ref",
+            location->href());
+
+  location->set_search("?d=e");
+  EXPECT_TRUE(location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:99/foo;bar?d=e#ref",
+            location->href());
+}
+
+
 }  // namespace dom
 }  // namespace cobalt
diff --git a/cobalt/dom/lottie_frame_custom_event.h b/cobalt/dom/lottie_frame_custom_event.h
index d2cd62c..fca5aca 100644
--- a/cobalt/dom/lottie_frame_custom_event.h
+++ b/cobalt/dom/lottie_frame_custom_event.h
@@ -18,8 +18,8 @@
 #include <memory>
 #include <string>
 
-#include "cobalt/dom/event.h"
 #include "cobalt/dom/lottie_frame_custom_event_detail.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace dom {
@@ -27,10 +27,11 @@
 // Lottie frame events carry custom data about the frame rendered and how far
 // the animation has played.
 //   https://lottiefiles.github.io/lottie-player/events.html
-class LottieFrameCustomEvent : public Event {
+class LottieFrameCustomEvent : public web::Event {
  public:
   explicit LottieFrameCustomEvent(const std::string& type) : Event(type) {}
-  LottieFrameCustomEvent(const std::string& type, const EventInit& init_dict)
+  LottieFrameCustomEvent(const std::string& type,
+                         const web::EventInit& init_dict)
       : Event(type, init_dict) {}
 
   void set_detail(const LottieFrameCustomEventDetail& detail) {
diff --git a/cobalt/dom/lottie_player.cc b/cobalt/dom/lottie_player.cc
index 57ee5de..93baab8 100644
--- a/cobalt/dom/lottie_player.cc
+++ b/cobalt/dom/lottie_player.cc
@@ -21,13 +21,13 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/html_element_context.h"
 #include "cobalt/dom/lottie_frame_custom_event.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/script/global_environment.h"
+#include "cobalt/web/csp_delegate.h"
 #include "url/gurl.h"
 
 namespace cobalt {
@@ -434,7 +434,7 @@
 
 void LottiePlayer::ScheduleEvent(base::Token event_name) {
   // https://github.com/LottieFiles/lottie-player#events
-  scoped_refptr<Event> event = new Event(event_name);
+  scoped_refptr<web::Event> event = new web::Event(event_name);
   event->set_target(this);
   event_queue_.Enqueue(event);
 }
diff --git a/cobalt/dom/media_source.cc b/cobalt/dom/media_source.cc
index 3707c32..8a2553e 100644
--- a/cobalt/dom/media_source.cc
+++ b/cobalt/dom/media_source.cc
@@ -54,9 +54,9 @@
 #include "base/logging.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_settings.h"
-#include "cobalt/dom/event.h"
+#include "cobalt/web/dom_exception.h"
+#include "cobalt/web/event.h"
 #include "starboard/media.h"
 #include "third_party/chromium/media/base/pipeline_status.h"
 
@@ -69,7 +69,7 @@
 using ::media::PipelineStatus;
 
 MediaSource::MediaSource(script::EnvironmentSettings* settings)
-    : EventTarget(settings),
+    : web::EventTarget(settings),
       chunk_demuxer_(NULL),
       ready_state_(kMediaSourceReadyStateClosed),
       ALLOW_THIS_IN_INITIALIZER_LIST(event_queue_(this)),
@@ -101,11 +101,12 @@
 void MediaSource::set_duration(double duration,
                                script::ExceptionState* exception_state) {
   if (duration < 0.0 || std::isnan(duration)) {
-    DOMException::Raise(DOMException::kIndexSizeErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kIndexSizeErr, exception_state);
     return;
   }
   if (!IsOpen() || IsUpdating()) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -122,7 +123,8 @@
   }
 
   if (duration < highest_buffered_presentation_timestamp) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -154,18 +156,21 @@
   DLOG(INFO) << "add SourceBuffer with type " << type;
 
   if (type.empty()) {
-    DOMException::Raise(DOMException::kInvalidAccessErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidAccessErr,
+                             exception_state);
     // Return value should be ignored.
     return NULL;
   }
 
   if (!IsTypeSupported(settings, type)) {
-    DOMException::Raise(DOMException::kNotSupportedErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kNotSupportedErr,
+                             exception_state);
     return NULL;
   }
 
   if (!IsOpen()) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return NULL;
   }
 
@@ -178,10 +183,12 @@
           new SourceBuffer(settings, guid, this, chunk_demuxer_, &event_queue_);
       break;
     case ChunkDemuxer::kNotSupported:
-      DOMException::Raise(DOMException::kNotSupportedErr, exception_state);
+      web::DOMException::Raise(web::DOMException::kNotSupportedErr,
+                               exception_state);
       return NULL;
     case ChunkDemuxer::kReachedIdLimit:
-      DOMException::Raise(DOMException::kQuotaExceededErr, exception_state);
+      web::DOMException::Raise(web::DOMException::kQuotaExceededErr,
+                               exception_state);
       return NULL;
   }
 
@@ -195,13 +202,14 @@
     script::ExceptionState* exception_state) {
   TRACE_EVENT0("cobalt::dom", "MediaSource::RemoveSourceBuffer()");
   if (source_buffer.get() == NULL) {
-    DOMException::Raise(DOMException::kInvalidAccessErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidAccessErr,
+                             exception_state);
     return;
   }
 
   if (source_buffers_->length() == 0 ||
       !source_buffers_->Contains(source_buffer)) {
-    DOMException::Raise(DOMException::kNotFoundErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kNotFoundErr, exception_state);
     return;
   }
 
@@ -234,7 +242,8 @@
                               script::ExceptionState* exception_state) {
   TRACE_EVENT1("cobalt::dom", "MediaSource::EndOfStream()", "error", error);
   if (!IsOpen() || IsUpdating()) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
   EndOfStreamAlgorithm(error);
@@ -242,13 +251,16 @@
 
 void MediaSource::SetLiveSeekableRange(
     double start, double end, script::ExceptionState* exception_state) {
+  TRACE_EVENT2("cobalt::dom", "MediaSource::SetLiveSeekableRange()", "start",
+               start, "end", end);
   if (!IsOpen()) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
   if (start < 0 || start > end) {
-    DOMException::Raise(DOMException::kIndexSizeErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kIndexSizeErr, exception_state);
     return;
   }
 
@@ -257,8 +269,10 @@
 
 void MediaSource::ClearLiveSeekableRange(
     script::ExceptionState* exception_state) {
+  TRACE_EVENT0("cobalt::dom", "MediaSource::ClearLiveSeekableRange()");
   if (!IsOpen()) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -471,7 +485,7 @@
 }
 
 void MediaSource::TraceMembers(script::Tracer* tracer) {
-  EventTarget::TraceMembers(tracer);
+  web::EventTarget::TraceMembers(tracer);
 
   tracer->Trace(event_queue_);
   tracer->Trace(attached_element_);
@@ -530,7 +544,7 @@
 }
 
 void MediaSource::ScheduleEvent(base::Token event_name) {
-  scoped_refptr<Event> event = new Event(event_name);
+  scoped_refptr<web::Event> event = new web::Event(event_name);
   event->set_target(this);
   event_queue_.Enqueue(event);
 }
diff --git a/cobalt/dom/media_source.h b/cobalt/dom/media_source.h
index 0fb691c..003d6f8 100644
--- a/cobalt/dom/media_source.h
+++ b/cobalt/dom/media_source.h
@@ -52,17 +52,17 @@
 #include "cobalt/base/token.h"
 #include "cobalt/dom/audio_track.h"
 #include "cobalt/dom/event_queue.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/dom/html_media_element.h"
 #include "cobalt/dom/media_source_end_of_stream_error.h"
 #include "cobalt/dom/media_source_ready_state.h"
 #include "cobalt/dom/source_buffer.h"
 #include "cobalt/dom/source_buffer_list.h"
 #include "cobalt/dom/time_ranges.h"
-#include "cobalt/dom/url_registry.h"
 #include "cobalt/dom/video_track.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/exception_state.h"
+#include "cobalt/web/event_target.h"
+#include "cobalt/web/url_registry.h"
 #include "third_party/chromium/media/filters/chunk_demuxer.h"
 
 namespace cobalt {
@@ -71,10 +71,10 @@
 // The MediaSource interface exposes functionalities for playing adaptive
 // streams.
 //   https://www.w3.org/TR/2016/CR-media-source-20160705/#idl-def-MediaSource
-class MediaSource : public EventTarget {
+class MediaSource : public web::EventTarget {
  public:
   typedef ::media::ChunkDemuxer ChunkDemuxer;
-  typedef UrlRegistry<MediaSource> Registry;
+  typedef web::UrlRegistry<MediaSource> Registry;
 
   // Custom, not in any spec.
   //
diff --git a/cobalt/dom/memory_info.cc b/cobalt/dom/memory_info.cc
index 401ec9f..d687545 100644
--- a/cobalt/dom/memory_info.cc
+++ b/cobalt/dom/memory_info.cc
@@ -22,6 +22,16 @@
 
 namespace cobalt {
 namespace dom {
+namespace {
+
+const media::DecoderBufferMemoryInfo* GetDecoderBufferMemoryInfo(
+    script::EnvironmentSettings* environment_settings) {
+  DOMSettings* dom_settings =
+      base::polymorphic_downcast<DOMSettings*>(environment_settings);
+  return dom_settings ? dom_settings->decoder_buffer_memory_info() : nullptr;
+}
+
+}  // namespace
 
 uint32 MemoryInfo::total_js_heap_size(
     script::EnvironmentSettings* environment_settings) const {
@@ -53,27 +63,20 @@
 
 uint32 MemoryInfo::media_source_size_limit(
     script::EnvironmentSettings* environment_settings) const {
-  DOMSettings* settings =
-      base::polymorphic_downcast<DOMSettings*>(environment_settings);
-  return settings ? static_cast<uint32>(settings->media_source_size_limit())
-                  : 0u;
+  auto memory_info = GetDecoderBufferMemoryInfo(environment_settings);
+  return memory_info ? memory_info->GetMaximumMemoryCapacity() : 0u;
 }
 
 uint32 MemoryInfo::total_media_source_size(
     script::EnvironmentSettings* environment_settings) const {
-  DOMSettings* settings =
-      base::polymorphic_downcast<DOMSettings*>(environment_settings);
-  return settings ? static_cast<uint32>(settings->total_media_source_size())
-                  : 0u;
+  auto memory_info = GetDecoderBufferMemoryInfo(environment_settings);
+  return memory_info ? memory_info->GetCurrentMemoryCapacity() : 0u;
 }
 
 uint32 MemoryInfo::used_media_source_memory_size(
     script::EnvironmentSettings* environment_settings) const {
-  DOMSettings* settings =
-      base::polymorphic_downcast<DOMSettings*>(environment_settings);
-  return settings
-             ? static_cast<uint32>(settings->used_media_source_memory_size())
-             : 0u;
+  auto memory_info = GetDecoderBufferMemoryInfo(environment_settings);
+  return memory_info ? memory_info->GetAllocatedMemory() : 0u;
 }
 
 }  // namespace dom
diff --git a/cobalt/dom/message_event.cc b/cobalt/dom/message_event.cc
index 434ce9c..157151d 100644
--- a/cobalt/dom/message_event.cc
+++ b/cobalt/dom/message_event.cc
@@ -82,7 +82,7 @@
       return ResponseType(string_response);
     }
     case kBlob: {
-      scoped_refptr<dom::Blob> blob = new dom::Blob(settings, response_buffer);
+      scoped_refptr<web::Blob> blob = new web::Blob(settings, response_buffer);
       return ResponseType(blob);
     }
     case kArrayBuffer: {
diff --git a/cobalt/dom/message_event.h b/cobalt/dom/message_event.h
index bd55426..7923a6b 100644
--- a/cobalt/dom/message_event.h
+++ b/cobalt/dom/message_event.h
@@ -21,19 +21,19 @@
 #include "base/memory/ref_counted.h"
 #include "base/strings/string_piece.h"
 #include "cobalt/base/token.h"
-#include "cobalt/dom/blob.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/script/array_buffer.h"
 #include "cobalt/script/union_type.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/blob.h"
+#include "cobalt/web/event.h"
 #include "net/base/io_buffer.h"
 
 namespace cobalt {
 namespace dom {
 
-class MessageEvent : public dom::Event {
+class MessageEvent : public web::Event {
  public:
-  typedef script::UnionType3<std::string, scoped_refptr<dom::Blob>,
+  typedef script::UnionType3<std::string, scoped_refptr<web::Blob>,
                              script::Handle<script::ArrayBuffer> >
       ResponseType;
   // These response codes are ordered in the likelihood of being used.
diff --git a/cobalt/dom/mouse_event.cc b/cobalt/dom/mouse_event.cc
index bf02aa0..6449dc2 100644
--- a/cobalt/dom/mouse_event.cc
+++ b/cobalt/dom/mouse_event.cc
@@ -79,7 +79,7 @@
     const scoped_refptr<Window>& view, int32 detail, int32 screen_x,
     int32 screen_y, int32 client_x, int32 client_y, bool ctrl_key, bool alt_key,
     bool shift_key, bool meta_key, uint16 button,
-    const scoped_refptr<EventTarget>& related_target) {
+    const scoped_refptr<web::EventTarget>& related_target) {
   InitUIEventWithKeyState(type, bubbles, cancelable, view, detail, ctrl_key,
                           alt_key, shift_key, meta_key);
   screen_x_ = screen_x;
@@ -96,7 +96,7 @@
     const scoped_refptr<Window>& view, int32 detail, int32 screen_x,
     int32 screen_y, int32 client_x, int32 client_y,
     const std::string& modifierslist, uint16 button,
-    const scoped_refptr<EventTarget>& related_target) {
+    const scoped_refptr<web::EventTarget>& related_target) {
   InitUIEventWithKeyState(type, bubbles, cancelable, view, detail,
                           modifierslist);
   screen_x_ = screen_x;
diff --git a/cobalt/dom/mouse_event.h b/cobalt/dom/mouse_event.h
index 6f09811..61672dd 100644
--- a/cobalt/dom/mouse_event.h
+++ b/cobalt/dom/mouse_event.h
@@ -18,9 +18,9 @@
 #include <string>
 
 #include "cobalt/base/token.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/dom/mouse_event_init.h"
 #include "cobalt/dom/ui_event_with_key_state.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace dom {
@@ -46,14 +46,14 @@
                       int32 screen_x, int32 screen_y, int32 client_x,
                       int32 client_y, bool ctrl_key, bool alt_key,
                       bool shift_key, bool meta_key, uint16 button,
-                      const scoped_refptr<EventTarget>& related_target);
+                      const scoped_refptr<web::EventTarget>& related_target);
 
   void InitMouseEvent(const std::string& type, bool bubbles, bool cancelable,
                       const scoped_refptr<Window>& view, int32 detail,
                       int32 screen_x, int32 screen_y, int32 client_x,
                       int32 client_y, const std::string& modifierslist,
                       uint16 button,
-                      const scoped_refptr<EventTarget>& related_target);
+                      const scoped_refptr<web::EventTarget>& related_target);
 
   float screen_x() const { return screen_x_; }
   float screen_y() const { return screen_y_; }
@@ -74,11 +74,11 @@
   int16_t button() const { return button_; }
   uint16_t buttons() const { return buttons_; }
 
-  void set_related_target(const scoped_refptr<EventTarget>& target) {
+  void set_related_target(const scoped_refptr<web::EventTarget>& target) {
     related_target_ = target;
   }
 
-  const scoped_refptr<EventTarget>& related_target() const {
+  const scoped_refptr<web::EventTarget>& related_target() const {
     return related_target_;
   }
 
@@ -101,7 +101,7 @@
   int16_t button_;
   uint16_t buttons_;
 
-  scoped_refptr<EventTarget> related_target_;
+  scoped_refptr<web::EventTarget> related_target_;
 };
 
 }  // namespace dom
diff --git a/cobalt/dom/navigator.cc b/cobalt/dom/navigator.cc
index b82cd66..15ba01e 100644
--- a/cobalt/dom/navigator.cc
+++ b/cobalt/dom/navigator.cc
@@ -18,20 +18,20 @@
 #include <vector>
 
 #include "base/optional.h"
+#include "base/trace_event/trace_event.h"
 #include "cobalt/dom/captions/system_caption_settings.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/eme/media_key_system_access.h"
 #include "cobalt/media_capture/media_devices.h"
 #include "cobalt/media_session/media_session_client.h"
 #include "cobalt/script/script_value_factory.h"
+#include "cobalt/web/dom_exception.h"
+#include "cobalt/web/navigator_base.h"
 #include "cobalt/worker/service_worker_container.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/file.h"
 #include "starboard/media.h"
 
-using cobalt::media_session::MediaSession;
-
 namespace cobalt {
 namespace dom {
 namespace {
@@ -144,32 +144,19 @@
 
 }  // namespace
 
-Navigator::Navigator(
-    script::EnvironmentSettings* settings, const std::string& user_agent,
-    UserAgentPlatformInfo* platform_info, const std::string& language,
-    scoped_refptr<cobalt::dom::captions::SystemCaptionSettings> captions,
-    script::ScriptValueFactory* script_value_factory)
-    : user_agent_(user_agent),
-      user_agent_data_(
-          new NavigatorUAData(platform_info, script_value_factory)),
-      language_(language),
+Navigator::Navigator(script::EnvironmentSettings* settings,
+                     const std::string& user_agent,
+                     web::UserAgentPlatformInfo* platform_info,
+                     const std::string& language,
+                     scoped_refptr<captions::SystemCaptionSettings> captions,
+                     script::ScriptValueFactory* script_value_factory)
+    : web::NavigatorBase(settings, user_agent, platform_info, language,
+                         script_value_factory),
       mime_types_(new MimeTypeArray()),
       plugins_(new PluginArray()),
       media_devices_(
           new media_capture::MediaDevices(settings, script_value_factory)),
-      service_worker_(new worker::ServiceWorkerContainer(
-          settings, base::polymorphic_downcast<DOMSettings*>(settings)
-                        ->service_worker_jobs())),
-      system_caption_settings_(captions),
-      script_value_factory_(script_value_factory) {}
-
-const std::string& Navigator::language() const { return language_; }
-
-script::Sequence<std::string> Navigator::languages() const {
-  script::Sequence<std::string> languages;
-  languages.push_back(language_);
-  return languages;
-}
+      system_caption_settings_(captions) {}
 
 base::Optional<std::string> GetFilenameForLicenses() {
   const size_t kBufferSize = kSbFileMaxPath + 1;
@@ -216,32 +203,14 @@
   return file_contents;
 }
 
-const std::string& Navigator::user_agent() const { return user_agent_; }
-
-const scoped_refptr<NavigatorUAData>& Navigator::user_agent_data() const {
-  return user_agent_data_;
-}
-
 bool Navigator::java_enabled() const { return false; }
 
 bool Navigator::cookie_enabled() const { return false; }
 
-bool Navigator::on_line() const {
-#if SB_API_VERSION >= 13
-  return !SbSystemNetworkIsDisconnected();
-#else
-  return true;
-#endif
-}
-
 scoped_refptr<media_capture::MediaDevices> Navigator::media_devices() {
   return media_devices_;
 }
 
-scoped_refptr<worker::ServiceWorkerContainer> Navigator::service_worker() {
-  return service_worker_;
-}
-
 const scoped_refptr<MimeTypeArray>& Navigator::mime_types() const {
   return mime_types_;
 }
@@ -252,8 +221,8 @@
 
 const scoped_refptr<media_session::MediaSession>& Navigator::media_session() {
   if (media_session_ == nullptr) {
-    media_session_ =
-        scoped_refptr<media_session::MediaSession>(new MediaSession());
+    media_session_ = scoped_refptr<media_session::MediaSession>(
+        new media_session::MediaSession());
 
     if (media_player_factory_ != nullptr) {
       media_session_->EnsureMediaSessionClient();
@@ -271,20 +240,21 @@
 //       with the order of declaration.
 // See
 // https://www.w3.org/TR/encrypted-media/#get-supported-capabilities-for-audio-video-type.
-base::Optional<script::Sequence<MediaKeySystemMediaCapability>>
+base::Optional<script::Sequence<eme::MediaKeySystemMediaCapability>>
 Navigator::TryGetSupportedCapabilities(
     const media::CanPlayTypeHandler& can_play_type_handler,
     const std::string& key_system,
-    const script::Sequence<MediaKeySystemMediaCapability>&
+    const script::Sequence<eme::MediaKeySystemMediaCapability>&
         requested_media_capabilities) {
   // 2. Let supported media capabilities be an empty sequence of
   //    MediaKeySystemMediaCapability dictionaries.
-  script::Sequence<MediaKeySystemMediaCapability> supported_media_capabilities;
+  script::Sequence<eme::MediaKeySystemMediaCapability>
+      supported_media_capabilities;
   // 3. For each requested media capability in requested media capabilities:
   for (std::size_t media_capability_index = 0;
        media_capability_index < requested_media_capabilities.size();
        ++media_capability_index) {
-    const MediaKeySystemMediaCapability& requested_media_capability =
+    const eme::MediaKeySystemMediaCapability& requested_media_capability =
         requested_media_capabilities.at(media_capability_index);
     // 3.1. Let content type be requested media capability's contentType member.
     const std::string& content_type = requested_media_capability.content_type();
@@ -360,7 +330,7 @@
       !candidate_configuration.video_capabilities().empty()) {
     // 16.1. Let video capabilities be the result of executing the "Get
     //       Supported Capabilities for Audio/Video Type" algorithm.
-    base::Optional<script::Sequence<MediaKeySystemMediaCapability>>
+    base::Optional<script::Sequence<eme::MediaKeySystemMediaCapability>>
         maybe_video_capabilities = TryGetSupportedCapabilities(
             can_play_type_handler, key_system,
             candidate_configuration.video_capabilities());
@@ -375,7 +345,7 @@
     // Otherwise: set the videoCapabilities member of accumulated configuration
     // to an empty sequence.
     accumulated_configuration.set_video_capabilities(
-        script::Sequence<MediaKeySystemMediaCapability>());
+        script::Sequence<eme::MediaKeySystemMediaCapability>());
   }
 
   // 17. If the audioCapabilities member in candidate configuration is
@@ -384,7 +354,7 @@
       !candidate_configuration.audio_capabilities().empty()) {
     // 17.1. Let audio capabilities be the result of executing the "Get
     //       Supported Capabilities for Audio/Video Type" algorithm.
-    base::Optional<script::Sequence<MediaKeySystemMediaCapability>>
+    base::Optional<script::Sequence<eme::MediaKeySystemMediaCapability>>
         maybe_audio_capabilities = TryGetSupportedCapabilities(
             can_play_type_handler, key_system,
             candidate_configuration.audio_capabilities());
@@ -399,7 +369,7 @@
     // Otherwise: set the audioCapabilities member of accumulated configuration
     // to an empty sequence.
     accumulated_configuration.set_audio_capabilities(
-        script::Sequence<MediaKeySystemMediaCapability>());
+        script::Sequence<eme::MediaKeySystemMediaCapability>());
   }
 
   // 23. Return accumulated configuration.
@@ -413,12 +383,14 @@
     script::EnvironmentSettings* settings, const std::string& key_system,
     const script::Sequence<eme::MediaKeySystemConfiguration>&
         supported_configurations) {
+  TRACE_EVENT1("cobalt::dom", "Navigator::RequestMediaKeySystemAccess()",
+               "key_system", key_system);
   DCHECK(settings);
   DOMSettings* dom_settings =
       base::polymorphic_downcast<DOMSettings*>(settings);
   DCHECK(dom_settings->can_play_type_handler());
   script::Handle<InterfacePromise> promise =
-      script_value_factory_
+      script_value_factory()
           ->CreateInterfacePromise<scoped_refptr<eme::MediaKeySystemAccess>>();
 
 #if !defined(COBALT_BUILD_TYPE_GOLD)
@@ -450,7 +422,7 @@
       scoped_refptr<eme::MediaKeySystemAccess> media_key_system_access(
           new eme::MediaKeySystemAccess(key_system,
                                         *maybe_supported_configuration,
-                                        script_value_factory_));
+                                        script_value_factory()));
 #if !defined(COBALT_BUILD_TYPE_GOLD)
       LOG(INFO) << "Navigator.RequestMediaKeySystemAccess() resolved with '"
                 << media_key_system_access->key_system() << "', and\n"
@@ -463,11 +435,11 @@
   }
 
   // 6.4. Reject promise with a NotSupportedError.
-  promise->Reject(new DOMException(DOMException::kNotSupportedErr));
+  promise->Reject(new web::DOMException(web::DOMException::kNotSupportedErr));
   return promise;
 }
 
-const scoped_refptr<cobalt::dom::captions::SystemCaptionSettings>&
+const scoped_refptr<captions::SystemCaptionSettings>&
 Navigator::system_caption_settings() const {
   return system_caption_settings_;
 }
@@ -483,7 +455,7 @@
 bool Navigator::CanPlayWithCapability(
     const media::CanPlayTypeHandler& can_play_type_handler,
     const std::string& key_system,
-    const MediaKeySystemMediaCapability& media_capability) {
+    const eme::MediaKeySystemMediaCapability& media_capability) {
   const std::string& content_type = media_capability.content_type();
 
   // There is no encryption scheme specified, check directly.
diff --git a/cobalt/dom/navigator.h b/cobalt/dom/navigator.h
index 3fdb68e..6acfe0e 100644
--- a/cobalt/dom/navigator.h
+++ b/cobalt/dom/navigator.h
@@ -22,7 +22,6 @@
 #include "cobalt/dom/captions/system_caption_settings.h"
 #include "cobalt/dom/eme/media_key_system_configuration.h"
 #include "cobalt/dom/mime_type_array.h"
-#include "cobalt/dom/navigator_ua_data.h"
 #include "cobalt/dom/plugin_array.h"
 #include "cobalt/media/web_media_player_factory.h"
 #include "cobalt/media_capture/media_devices.h"
@@ -31,7 +30,8 @@
 #include "cobalt/script/script_value_factory.h"
 #include "cobalt/script/sequence.h"
 #include "cobalt/script/wrappable.h"
-#include "cobalt/worker/service_worker_container.h"
+#include "cobalt/web/navigator_base.h"
+#include "cobalt/web/navigator_ua_data.h"
 
 namespace cobalt {
 namespace dom {
@@ -40,23 +40,16 @@
 // client), and allows Web pages to register themselves as potential protocol
 // and content handlers.
 // https://www.w3.org/TR/html50/webappapis.html#navigator
-class Navigator : public script::Wrappable {
+class Navigator : public web::NavigatorBase {
  public:
-  Navigator(
-      script::EnvironmentSettings* settings, const std::string& user_agent,
-      UserAgentPlatformInfo* platform_info, const std::string& language,
-      scoped_refptr<cobalt::dom::captions::SystemCaptionSettings> captions,
-      script::ScriptValueFactory* script_value_factory);
-
-  // Web API: NavigatorID
-  const std::string& user_agent() const;
-
-  // Web API: NavigatorUA
-  const scoped_refptr<NavigatorUAData>& user_agent_data() const;
-
-  // Web API: NavigatorLanguage
-  const std::string& language() const;
-  script::Sequence<std::string> languages() const;
+  Navigator(script::EnvironmentSettings* settings,
+            const std::string& user_agent,
+            web::UserAgentPlatformInfo* platform_info,
+            const std::string& language,
+            scoped_refptr<captions::SystemCaptionSettings> captions,
+            script::ScriptValueFactory* script_value_factory);
+  Navigator(const Navigator&) = delete;
+  Navigator& operator=(const Navigator&) = delete;
 
   // Web API: NavigatorLicenses
   const std::string licenses() const;
@@ -67,14 +60,9 @@
   // Web API: NavigatorPlugins
   bool cookie_enabled() const;
 
-  bool on_line() const;
-
   // Web API: MediaDevices
   scoped_refptr<media_capture::MediaDevices> media_devices();
 
-  // Web API: ServiceWorker
-  scoped_refptr<worker::ServiceWorkerContainer> service_worker();
-
   const scoped_refptr<MimeTypeArray>& mime_types() const;
   const scoped_refptr<PluginArray>& plugins() const;
 
@@ -96,7 +84,7 @@
       const script::Sequence<eme::MediaKeySystemConfiguration>&
           supported_configurations);
 
-  const scoped_refptr<cobalt::dom::captions::SystemCaptionSettings>&
+  const scoped_refptr<captions::SystemCaptionSettings>&
   system_caption_settings() const;
 
   DEFINE_WRAPPABLE_TYPE(Navigator);
@@ -105,11 +93,11 @@
  private:
   ~Navigator() override {}
 
-  base::Optional<script::Sequence<MediaKeySystemMediaCapability>>
+  base::Optional<script::Sequence<eme::MediaKeySystemMediaCapability>>
   TryGetSupportedCapabilities(
       const media::CanPlayTypeHandler& can_play_type_handler,
       const std::string& key_system,
-      const script::Sequence<MediaKeySystemMediaCapability>&
+      const script::Sequence<eme::MediaKeySystemMediaCapability>&
           requested_media_capabilities);
 
   base::Optional<eme::MediaKeySystemConfiguration> TryGetSupportedConfiguration(
@@ -120,7 +108,7 @@
   bool CanPlayWithCapability(
       const media::CanPlayTypeHandler& can_play_type_handler,
       const std::string& key_system,
-      const MediaKeySystemMediaCapability& media_capability);
+      const eme::MediaKeySystemMediaCapability& media_capability);
 
   bool CanPlayWithoutAttributes(
       const media::CanPlayTypeHandler& can_play_type_handler,
@@ -132,23 +120,15 @@
       const std::string& content_type, const std::string& key_system,
       const std::string& encryption_scheme);
 
-  std::string user_agent_;
-  scoped_refptr<NavigatorUAData> user_agent_data_;
-  std::string language_;
   scoped_refptr<MimeTypeArray> mime_types_;
   scoped_refptr<PluginArray> plugins_;
-  scoped_refptr<cobalt::media_session::MediaSession> media_session_;
-  scoped_refptr<cobalt::media_capture::MediaDevices> media_devices_;
-  scoped_refptr<cobalt::worker::ServiceWorkerContainer> service_worker_;
-  scoped_refptr<cobalt::dom::captions::SystemCaptionSettings>
-      system_caption_settings_;
-  script::ScriptValueFactory* script_value_factory_;
+  scoped_refptr<media_session::MediaSession> media_session_;
+  scoped_refptr<media_capture::MediaDevices> media_devices_;
+  scoped_refptr<captions::SystemCaptionSettings> system_caption_settings_;
   base::Optional<bool> key_system_with_attributes_supported_;
 
   base::Closure maybe_freeze_callback_;
   const media::WebMediaPlayerFactory* media_player_factory_ = nullptr;
-
-  DISALLOW_COPY_AND_ASSIGN(Navigator);
 };
 
 }  // namespace dom
diff --git a/cobalt/dom/node.cc b/cobalt/dom/node.cc
index 0b39848..bdd6209 100644
--- a/cobalt/dom/node.cc
+++ b/cobalt/dom/node.cc
@@ -27,7 +27,6 @@
 #include "cobalt/dom/comment.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/document_type.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/element.h"
 #include "cobalt/dom/global_stats.h"
@@ -40,6 +39,7 @@
 #include "cobalt/dom/rule_matching.h"
 #include "cobalt/dom/text.h"
 #include "cobalt/dom/window.h"
+#include "cobalt/web/dom_exception.h"
 #if defined(STARBOARD)
 #include "starboard/configuration.h"
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
@@ -53,7 +53,7 @@
 
 // Diagram for DispatchEvent:
 //  https://www.w3.org/TR/DOM-Level-3-Events/#event-flow
-bool Node::DispatchEvent(const scoped_refptr<Event>& event) {
+bool Node::DispatchEvent(const scoped_refptr<web::Event>& event) {
   DCHECK(event);
   DCHECK(!event->IsBeingDispatched());
   DCHECK(event->initialized_flag());
@@ -90,7 +90,7 @@
   // The capture phase: The event object propagates through the target's
   // ancestors from the Window to the target's parent. This phase is also known
   // as the capturing phase.
-  event->set_event_phase(Event::kCapturingPhase);
+  event->set_event_phase(web::Event::kCapturingPhase);
   if (window) {
     window->FireEventOnListeners(event);
   }
@@ -104,7 +104,7 @@
   if (!event->propagation_stopped()) {
     // The target phase: The event object arrives at the event object's event
     // target. This phase is also known as the at-target phase.
-    event->set_event_phase(Event::kAtTarget);
+    event->set_event_phase(web::Event::kAtTarget);
     FireEventOnListeners(event);
   }
 
@@ -115,7 +115,7 @@
       // The bubble phase: The event object propagates through the target's
       // ancestors in reverse order, starting with the target's parent and
       // ending with the Window. This phase is also known as the bubbling phase.
-      event->set_event_phase(Event::kBubblingPhase);
+      event->set_event_phase(web::Event::kBubblingPhase);
       for (Ancestors::iterator iter = ancestors.begin();
            iter != ancestors.end() && !event->propagation_stopped(); ++iter) {
         (*iter)->FireEventOnListeners(event);
@@ -126,7 +126,7 @@
     }
   }
 
-  event->set_event_phase(Event::kNone);
+  event->set_event_phase(web::Event::kNone);
 
   if (window) {
     window->OnStopDispatchEvent(event);
@@ -446,7 +446,7 @@
 }
 
 void Node::TraceMembers(script::Tracer* tracer) {
-  EventTarget::TraceMembers(tracer);
+  web::EventTarget::TraceMembers(tracer);
 
   tracer->Trace(node_document_);
   tracer->Trace(parent_);
@@ -462,7 +462,7 @@
     : Node(document->html_element_context(), document) {}
 
 Node::Node(HTMLElementContext* html_element_context, Document* document)
-    : EventTarget(html_element_context->environment_settings()),
+    : web::EventTarget(html_element_context->environment_settings()),
       node_document_(base::AsWeakPtr(document)),
       parent_(NULL),
       previous_sibling_(NULL),
diff --git a/cobalt/dom/node.h b/cobalt/dom/node.h
index 92c5a23..7cf13aa 100644
--- a/cobalt/dom/node.h
+++ b/cobalt/dom/node.h
@@ -24,10 +24,10 @@
 #include "base/optional.h"
 #include "cobalt/base/debugger_hooks.h"
 #include "cobalt/base/token.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/dom/mutation_observer.h"
 #include "cobalt/dom/mutation_observer_init.h"
 #include "cobalt/dom/registered_observer_list.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace dom {
@@ -90,7 +90,7 @@
 // As a result, any JavaScript reference to a node will keep the whole DOM tree
 // that the node belongs to alive, which is a conforming behavior.
 //
-class Node : public EventTarget {
+class Node : public web::EventTarget {
  public:
   // Web API: Node
   // NodeType values as defined by Web API Node.nodeType.
@@ -115,7 +115,7 @@
 
   // Web API: EventTarget
   //
-  bool DispatchEvent(const scoped_refptr<Event>& event) override;
+  bool DispatchEvent(const scoped_refptr<web::Event>& event) override;
 
   // Web API: Node
   //
diff --git a/cobalt/dom/node_dispatch_event_test.cc b/cobalt/dom/node_dispatch_event_test.cc
index 22be6e4..9ecb4e0 100644
--- a/cobalt/dom/node_dispatch_event_test.cc
+++ b/cobalt/dom/node_dispatch_event_test.cc
@@ -20,8 +20,8 @@
 #include "cobalt/dom/element.h"
 #include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/html_element_context.h"
-#include "cobalt/dom/testing/mock_event_listener.h"
 #include "cobalt/script/testing/fake_script_value.h"
+#include "cobalt/web/testing/mock_event_listener.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using ::testing::AllOf;
@@ -37,7 +37,7 @@
 namespace dom {
 
 using ::cobalt::script::testing::FakeScriptValue;
-using testing::MockEventListener;
+using web::testing::MockEventListener;
 
 //////////////////////////////////////////////////////////////////////////
 // NodeDispatchEventTest
@@ -70,23 +70,26 @@
   event_listener_bubbling_ = MockEventListener::Create();
 
   grand_parent_->AddEventListener(
-      "fired", FakeScriptValue<EventListener>(event_listener_bubbling_.get()),
+      "fired",
+      FakeScriptValue<web::EventListener>(event_listener_bubbling_.get()),
       false);
   grand_parent_->AddEventListener(
-      "fired", FakeScriptValue<EventListener>(event_listener_capture_.get()),
-      true);
+      "fired",
+      FakeScriptValue<web::EventListener>(event_listener_capture_.get()), true);
   parent_->AddEventListener(
-      "fired", FakeScriptValue<EventListener>(event_listener_bubbling_.get()),
+      "fired",
+      FakeScriptValue<web::EventListener>(event_listener_bubbling_.get()),
       false);
   parent_->AddEventListener(
-      "fired", FakeScriptValue<EventListener>(event_listener_capture_.get()),
-      true);
+      "fired",
+      FakeScriptValue<web::EventListener>(event_listener_capture_.get()), true);
   child_->AddEventListener(
-      "fired", FakeScriptValue<EventListener>(event_listener_bubbling_.get()),
+      "fired",
+      FakeScriptValue<web::EventListener>(event_listener_bubbling_.get()),
       false);
   child_->AddEventListener(
-      "fired", FakeScriptValue<EventListener>(event_listener_capture_.get()),
-      true);
+      "fired",
+      FakeScriptValue<web::EventListener>(event_listener_capture_.get()), true);
 }
 
 NodeDispatchEventTest::~NodeDispatchEventTest() {
@@ -106,132 +109,133 @@
 //////////////////////////////////////////////////////////////////////////
 
 TEST_F(NodeDispatchEventTest, NonBubblingEventPropagation) {
-  scoped_refptr<Event> event = new Event(
-      base::Token("fired"), Event::kNotBubbles, Event::kNotCancelable);
+  scoped_refptr<web::Event> event =
+      new web::Event(base::Token("fired"), web::Event::kNotBubbles,
+                     web::Event::kNotCancelable);
 
   InSequence in_sequence;
   event_listener_capture_->ExpectHandleEventCall(event, child_, grand_parent_,
-                                                 Event::kCapturingPhase);
+                                                 web::Event::kCapturingPhase);
   event_listener_capture_->ExpectHandleEventCall(event, child_, parent_,
-                                                 Event::kCapturingPhase);
+                                                 web::Event::kCapturingPhase);
   // The event listeners called at target are always in the order of append.
   event_listener_bubbling_->ExpectHandleEventCall(event, child_, child_,
-                                                  Event::kAtTarget);
+                                                  web::Event::kAtTarget);
   event_listener_capture_->ExpectHandleEventCall(event, child_, child_,
-                                                 Event::kAtTarget);
+                                                 web::Event::kAtTarget);
   EXPECT_TRUE(child_->DispatchEvent(event));
 }
 
 TEST_F(NodeDispatchEventTest, BubblingEventPropagation) {
-  scoped_refptr<Event> event =
-      new Event(base::Token("fired"), Event::kBubbles, Event::kNotCancelable);
+  scoped_refptr<web::Event> event = new web::Event(
+      base::Token("fired"), web::Event::kBubbles, web::Event::kNotCancelable);
 
   InSequence in_sequence;
   event_listener_capture_->ExpectHandleEventCall(event, child_, grand_parent_,
-                                                 Event::kCapturingPhase);
+                                                 web::Event::kCapturingPhase);
   event_listener_capture_->ExpectHandleEventCall(event, child_, parent_,
-                                                 Event::kCapturingPhase);
+                                                 web::Event::kCapturingPhase);
   // The event listeners called at target are always in the order of append.
   event_listener_bubbling_->ExpectHandleEventCall(event, child_, child_,
-                                                  Event::kAtTarget);
+                                                  web::Event::kAtTarget);
   event_listener_capture_->ExpectHandleEventCall(event, child_, child_,
-                                                 Event::kAtTarget);
+                                                 web::Event::kAtTarget);
   event_listener_bubbling_->ExpectHandleEventCall(event, child_, parent_,
-                                                  Event::kBubblingPhase);
+                                                  web::Event::kBubblingPhase);
   event_listener_bubbling_->ExpectHandleEventCall(event, child_, grand_parent_,
-                                                  Event::kBubblingPhase);
+                                                  web::Event::kBubblingPhase);
   EXPECT_TRUE(child_->DispatchEvent(event));
 }
 
 TEST_F(NodeDispatchEventTest, StopPropagationAtCapturePhase) {
-  scoped_refptr<Event> event =
-      new Event(base::Token("fired"), Event::kBubbles, Event::kNotCancelable);
+  scoped_refptr<web::Event> event = new web::Event(
+      base::Token("fired"), web::Event::kBubbles, web::Event::kNotCancelable);
 
   InSequence in_sequence;
   event_listener_capture_->ExpectHandleEventCall(event, child_, grand_parent_,
-                                                 Event::kCapturingPhase);
+                                                 web::Event::kCapturingPhase);
   event_listener_capture_->ExpectHandleEventCall(
-      event, child_, parent_, Event::kCapturingPhase,
+      event, child_, parent_, web::Event::kCapturingPhase,
       &MockEventListener::StopPropagation);
   EXPECT_TRUE(child_->DispatchEvent(event));
 }
 
 TEST_F(NodeDispatchEventTest, StopPropagationAtAtTargetPhase) {
-  scoped_refptr<Event> event =
-      new Event(base::Token("fired"), Event::kBubbles, Event::kNotCancelable);
+  scoped_refptr<web::Event> event = new web::Event(
+      base::Token("fired"), web::Event::kBubbles, web::Event::kNotCancelable);
 
   InSequence in_sequence;
   event_listener_capture_->ExpectHandleEventCall(event, child_, grand_parent_,
-                                                 Event::kCapturingPhase);
+                                                 web::Event::kCapturingPhase);
   event_listener_capture_->ExpectHandleEventCall(event, child_, parent_,
-                                                 Event::kCapturingPhase);
+                                                 web::Event::kCapturingPhase);
   event_listener_bubbling_->ExpectHandleEventCall(
-      event, child_, child_, Event::kAtTarget,
+      event, child_, child_, web::Event::kAtTarget,
       &MockEventListener::StopPropagation);
   event_listener_capture_->ExpectHandleEventCall(event, child_, child_,
-                                                 Event::kAtTarget);
+                                                 web::Event::kAtTarget);
   EXPECT_TRUE(child_->DispatchEvent(event));
 }
 
 TEST_F(NodeDispatchEventTest, StopPropagationAtBubblingPhase) {
-  scoped_refptr<Event> event =
-      new Event(base::Token("fired"), Event::kBubbles, Event::kNotCancelable);
+  scoped_refptr<web::Event> event = new web::Event(
+      base::Token("fired"), web::Event::kBubbles, web::Event::kNotCancelable);
 
   InSequence in_sequence;
   event_listener_capture_->ExpectHandleEventCall(event, child_, grand_parent_,
-                                                 Event::kCapturingPhase);
+                                                 web::Event::kCapturingPhase);
   event_listener_capture_->ExpectHandleEventCall(event, child_, parent_,
-                                                 Event::kCapturingPhase);
+                                                 web::Event::kCapturingPhase);
   // The event listeners called at target are always in the order of append.
   event_listener_bubbling_->ExpectHandleEventCall(event, child_, child_,
-                                                  Event::kAtTarget);
+                                                  web::Event::kAtTarget);
   event_listener_capture_->ExpectHandleEventCall(event, child_, child_,
-                                                 Event::kAtTarget);
+                                                 web::Event::kAtTarget);
   event_listener_bubbling_->ExpectHandleEventCall(
-      event, child_, parent_, Event::kBubblingPhase,
+      event, child_, parent_, web::Event::kBubblingPhase,
       &MockEventListener::StopPropagation);
   EXPECT_TRUE(child_->DispatchEvent(event));
 }
 
 TEST_F(NodeDispatchEventTest, StopImmediatePropagation) {
-  scoped_refptr<Event> event =
-      new Event(base::Token("fired"), Event::kBubbles, Event::kNotCancelable);
+  scoped_refptr<web::Event> event = new web::Event(
+      base::Token("fired"), web::Event::kBubbles, web::Event::kNotCancelable);
 
   InSequence in_sequence;
   event_listener_capture_->ExpectHandleEventCall(event, child_, grand_parent_,
-                                                 Event::kCapturingPhase);
+                                                 web::Event::kCapturingPhase);
   event_listener_capture_->ExpectHandleEventCall(event, child_, parent_,
-                                                 Event::kCapturingPhase);
+                                                 web::Event::kCapturingPhase);
   event_listener_bubbling_->ExpectHandleEventCall(
-      event, child_, child_, Event::kAtTarget,
+      event, child_, child_, web::Event::kAtTarget,
       &MockEventListener::StopImmediatePropagation);
   EXPECT_TRUE(child_->DispatchEvent(event));
 }
 
 TEST_F(NodeDispatchEventTest, PreventDefaultOnNonCancelableEvent) {
-  scoped_refptr<Event> event =
-      new Event(base::Token("fired"), Event::kBubbles, Event::kNotCancelable);
+  scoped_refptr<web::Event> event = new web::Event(
+      base::Token("fired"), web::Event::kBubbles, web::Event::kNotCancelable);
 
   InSequence in_sequence;
   event_listener_capture_->ExpectHandleEventCall(
-      event, child_, grand_parent_, Event::kCapturingPhase,
+      event, child_, grand_parent_, web::Event::kCapturingPhase,
       &MockEventListener::PreventDefault);
   event_listener_capture_->ExpectHandleEventCall(
-      event, child_, parent_, Event::kCapturingPhase,
+      event, child_, parent_, web::Event::kCapturingPhase,
       &MockEventListener::StopPropagation);
   EXPECT_TRUE(child_->DispatchEvent(event));
 }
 
 TEST_F(NodeDispatchEventTest, PreventDefaultOnCancelableEvent) {
-  scoped_refptr<Event> event =
-      new Event(base::Token("fired"), Event::kBubbles, Event::kCancelable);
+  scoped_refptr<web::Event> event = new web::Event(
+      base::Token("fired"), web::Event::kBubbles, web::Event::kCancelable);
 
   InSequence in_sequence;
   event_listener_capture_->ExpectHandleEventCall(
-      event, child_, grand_parent_, Event::kCapturingPhase,
+      event, child_, grand_parent_, web::Event::kCapturingPhase,
       &MockEventListener::PreventDefault);
   event_listener_capture_->ExpectHandleEventCall(
-      event, child_, parent_, Event::kCapturingPhase,
+      event, child_, parent_, web::Event::kCapturingPhase,
       &MockEventListener::StopPropagation);
   EXPECT_FALSE(child_->DispatchEvent(event));
 }
diff --git a/cobalt/dom/node_test.cc b/cobalt/dom/node_test.cc
index 22a0780..e1825fa 100644
--- a/cobalt/dom/node_test.cc
+++ b/cobalt/dom/node_test.cc
@@ -22,8 +22,8 @@
 #include "cobalt/dom/html_collection.h"
 #include "cobalt/dom/html_element_context.h"
 #include "cobalt/dom/node_list.h"
-#include "cobalt/dom/testing/gtest_workarounds.h"
 #include "cobalt/dom/text.h"
+#include "cobalt/web/testing/gtest_workarounds.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
@@ -36,7 +36,7 @@
 // Helper function that validates that the children of the given node are
 // identical to the provided expected array.
 template <int N>
-void ExpectNodeChildrenEq(scoped_refptr<Node>(&children)[N],
+void ExpectNodeChildrenEq(scoped_refptr<Node> (&children)[N],
                           const scoped_refptr<Node>& node) {
   // Make sure that we have at least one child.
   COMPILE_ASSERT(N >= 1, expect_node_children_has_no_child);
diff --git a/cobalt/dom/on_screen_keyboard.cc b/cobalt/dom/on_screen_keyboard.cc
index 72d1ca0..54c2242 100644
--- a/cobalt/dom/on_screen_keyboard.cc
+++ b/cobalt/dom/on_screen_keyboard.cc
@@ -18,8 +18,8 @@
 
 #include "base/callback.h"
 #include "base/compiler_specific.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/dom/window.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace dom {
@@ -27,7 +27,7 @@
 OnScreenKeyboard::OnScreenKeyboard(
     script::EnvironmentSettings* settings, OnScreenKeyboardBridge* bridge,
     script::ScriptValueFactory* script_value_factory)
-    : EventTarget(settings),
+    : web::EventTarget(settings),
       bridge_(bridge),
       script_value_factory_(script_value_factory),
       next_ticket_(0) {
@@ -114,7 +114,8 @@
   return promise;
 }
 
-const EventTarget::EventListenerScriptValue* OnScreenKeyboard::onshow() const {
+const web::EventTarget::EventListenerScriptValue* OnScreenKeyboard::onshow()
+    const {
   return GetAttributeEventListener(base::Tokens::show());
 }
 void OnScreenKeyboard::set_onshow(
@@ -122,7 +123,8 @@
   SetAttributeEventListener(base::Tokens::show(), event_listener);
 }
 
-const EventTarget::EventListenerScriptValue* OnScreenKeyboard::onfocus() const {
+const web::EventTarget::EventListenerScriptValue* OnScreenKeyboard::onfocus()
+    const {
   return GetAttributeEventListener(base::Tokens::focus());
 }
 void OnScreenKeyboard::set_onfocus(
@@ -130,7 +132,8 @@
   SetAttributeEventListener(base::Tokens::focus(), event_listener);
 }
 
-const EventTarget::EventListenerScriptValue* OnScreenKeyboard::onblur() const {
+const web::EventTarget::EventListenerScriptValue* OnScreenKeyboard::onblur()
+    const {
   return GetAttributeEventListener(base::Tokens::blur());
 }
 void OnScreenKeyboard::set_onblur(
@@ -138,7 +141,8 @@
   SetAttributeEventListener(base::Tokens::blur(), event_listener);
 }
 
-const EventTarget::EventListenerScriptValue* OnScreenKeyboard::onhide() const {
+const web::EventTarget::EventListenerScriptValue* OnScreenKeyboard::onhide()
+    const {
   return GetAttributeEventListener(base::Tokens::hide());
 }
 void OnScreenKeyboard::set_onhide(
@@ -146,7 +150,8 @@
   SetAttributeEventListener(base::Tokens::hide(), event_listener);
 }
 
-const EventTarget::EventListenerScriptValue* OnScreenKeyboard::oninput() const {
+const web::EventTarget::EventListenerScriptValue* OnScreenKeyboard::oninput()
+    const {
   return GetAttributeEventListener(base::Tokens::input());
 }
 
@@ -168,7 +173,7 @@
 
 void OnScreenKeyboard::DispatchHideEvent(int ticket) {
   if (ResolvePromise(ticket, &ticket_to_hide_promise_map_)) {
-    DispatchEvent(new dom::Event(base::Tokens::hide()));
+    DispatchEvent(new web::Event(base::Tokens::hide()));
   } else {
     LOG(ERROR)
         << "No promise matching ticket for OnScreenKeyboardHidden event.";
@@ -177,7 +182,7 @@
 
 void OnScreenKeyboard::DispatchShowEvent(int ticket) {
   if (ResolvePromise(ticket, &ticket_to_show_promise_map_)) {
-    DispatchEvent(new dom::Event(base::Tokens::show()));
+    DispatchEvent(new web::Event(base::Tokens::show()));
   } else {
     LOG(ERROR) << "No promise matching ticket for OnScreenKeyboardShown event.";
   }
@@ -185,7 +190,7 @@
 
 void OnScreenKeyboard::DispatchFocusEvent(int ticket) {
   if (ResolvePromise(ticket, &ticket_to_focus_promise_map_)) {
-    DispatchEvent(new dom::Event(base::Tokens::focus()));
+    DispatchEvent(new web::Event(base::Tokens::focus()));
   } else {
     LOG(ERROR)
         << "No promise matching ticket for OnScreenKeyboardFocused event.";
@@ -194,7 +199,7 @@
 
 void OnScreenKeyboard::DispatchBlurEvent(int ticket) {
   if (ResolvePromise(ticket, &ticket_to_blur_promise_map_)) {
-    DispatchEvent(new dom::Event(base::Tokens::blur()));
+    DispatchEvent(new web::Event(base::Tokens::blur()));
   } else {
     LOG(ERROR)
         << "No promise matching ticket for OnScreenKeyboardBlurred event.";
@@ -203,7 +208,7 @@
 
 void OnScreenKeyboard::DispatchSuggestionsUpdatedEvent(int ticket) {
   if (ResolvePromise(ticket, &ticket_to_update_suggestions_promise_map_)) {
-    DispatchEvent(new dom::Event(base::Tokens::suggestionsUpdated()));
+    DispatchEvent(new web::Event(base::Tokens::suggestionsUpdated()));
   } else {
     LOG(ERROR) << "No promise matching ticket for "
                   "OnScreenKeyboardSuggestionsUpdated event.";
diff --git a/cobalt/dom/on_screen_keyboard.h b/cobalt/dom/on_screen_keyboard.h
index bc781f9..8690c95 100644
--- a/cobalt/dom/on_screen_keyboard.h
+++ b/cobalt/dom/on_screen_keyboard.h
@@ -22,13 +22,13 @@
 #include "base/callback.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/dom_rect.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/dom/on_screen_keyboard_bridge.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/script/sequence.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event_target.h"
 #include "starboard/window.h"
 
 namespace cobalt {
@@ -37,7 +37,7 @@
 class Window;
 class OnScreenKeyboardMockBridge;
 
-class OnScreenKeyboard : public EventTarget {
+class OnScreenKeyboard : public web::EventTarget {
  public:
   typedef script::ScriptValue<script::Promise<void>> VoidPromiseValue;
 
diff --git a/cobalt/dom/on_screen_keyboard_test.cc b/cobalt/dom/on_screen_keyboard_test.cc
index 095595d..fe74cb8 100644
--- a/cobalt/dom/on_screen_keyboard_test.cc
+++ b/cobalt/dom/on_screen_keyboard_test.cc
@@ -24,7 +24,6 @@
 #include "cobalt/cssom/viewport_size.h"
 #include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/local_storage_database.h"
-#include "cobalt/dom/testing/gtest_workarounds.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/dom_parser/parser.h"
@@ -33,6 +32,7 @@
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/script/source_code.h"
+#include "cobalt/web/testing/gtest_workarounds.h"
 #include "starboard/window.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -217,7 +217,8 @@
             base::Bind(&MockErrorCallback::Run,
                        base::Unretained(&mock_error_callback_)),
             NULL, network_bridge::PostSender(), csp::kCSPRequired,
-            kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
+            web::kCspEnforcementEnable,
+            base::Closure() /* csp_policy_changed */,
             base::Closure() /* ran_animation_frame_callbacks */,
             dom::Window::CloseCallback() /* window_close */,
             base::Closure() /* window_minimize */,
@@ -235,7 +236,7 @@
     global_environment_->SetReportEvalCallback(base::Closure());
     global_environment_->SetReportErrorCallback(
         script::GlobalEnvironment::ReportErrorCallback());
-    window_->DispatchEvent(new dom::Event(base::Tokens::unload()));
+    window_->DispatchEvent(new web::Event(base::Tokens::unload()));
 
     // TODO: figure out how to destruct OSK before global environment.
     window_->ReleaseOnScreenKeyboard();
diff --git a/cobalt/dom/performance.cc b/cobalt/dom/performance.cc
index d1298ce..68b8990 100644
--- a/cobalt/dom/performance.cc
+++ b/cobalt/dom/performance.cc
@@ -19,11 +19,11 @@
 #include "base/time/default_clock.h"
 #include "base/time/time.h"
 #include "cobalt/browser/stack_size_constants.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/memory_info.h"
 #include "cobalt/dom/performance_entry.h"
 #include "cobalt/dom/performance_mark.h"
 #include "cobalt/dom/performance_measure.h"
+#include "cobalt/web/dom_exception.h"
 
 namespace cobalt {
 namespace dom {
@@ -60,7 +60,7 @@
   // Note that we only support navigationStart in the PerformanceTiming
   // interface. We return 0.0 instead of the result of subtracting
   // startTime from endTime.
-  dom::DOMException::Raise(dom::DOMException::kSyntaxErr,
+  web::DOMException::Raise(web::DOMException::kSyntaxErr,
                            "Cannot convert a name that is not a public "
                            "attribute of PerformanceTiming to a timestamp",
                            exception_state);
@@ -71,7 +71,7 @@
 
 Performance::Performance(script::EnvironmentSettings* settings,
                          const scoped_refptr<base::BasicClock>& clock)
-    : EventTarget(settings),
+    : web::EventTarget(settings),
       time_origin_(base::TimeTicks::Now()),
       tick_clock_(base::DefaultTickClock::GetInstance()),
       timing_(new PerformanceTiming(
@@ -153,8 +153,8 @@
   // as a read only attribute in the PerformanceTiming interface, throw a
   // SyntaxError.
   if (IsNamePerformanceTimingAttribute(mark_name)) {
-    dom::DOMException::Raise(
-        dom::DOMException::kSyntaxErr,
+    web::DOMException::Raise(
+        web::DOMException::kSyntaxErr,
         "Cannot create a mark with the same name as a read-only attribute in "
         "the PerformanceTiming interface",
         exception_state);
@@ -226,8 +226,8 @@
     // entry is found, throw a SyntaxError.
     PerformanceEntryList list = GetEntriesByName(end_mark, "mark");
     if (list.empty()) {
-      dom::DOMException::Raise(
-          dom::DOMException::kSyntaxErr,
+      web::DOMException::Raise(
+          web::DOMException::kSyntaxErr,
           "Cannot create measure; no mark found with name: " + end_mark + ".",
           exception_state);
       return;
@@ -252,8 +252,8 @@
     // matching entry is found, throw a SyntaxError.
     PerformanceEntryList list = GetEntriesByName(start_mark, "mark");
     if (list.empty()) {
-      dom::DOMException::Raise(
-          dom::DOMException::kSyntaxErr,
+      web::DOMException::Raise(
+          web::DOMException::kSyntaxErr,
           "Cannot create measure; no mark found with name: " + start_mark + ".",
           exception_state);
       return;
@@ -441,12 +441,12 @@
 }
 
 void Performance::set_onresourcetimingbufferfull(
-    const EventTarget::EventListenerScriptValue& event_listener) {
+    const web::EventTarget::EventListenerScriptValue& event_listener) {
   SetAttributeEventListener(base::Tokens::resourcetimingbufferfull(),
                             event_listener);
 }
 
-const EventTarget::EventListenerScriptValue*
+const web::EventTarget::EventListenerScriptValue*
 Performance::onresourcetimingbufferfull() const {
   return GetAttributeEventListener(base::Tokens::resourcetimingbufferfull());
 }
@@ -463,7 +463,7 @@
     // 1.2 If can add resource timing entry returns false, then fire an event
     // named resourcetimingbufferfull at the Performance object.
     if (!CanAddResourceTimingEntry()) {
-      DispatchEvent(new Event(base::Tokens::resourcetimingbufferfull()));
+      DispatchEvent(new web::Event(base::Tokens::resourcetimingbufferfull()));
     }
     // 1.3 Run copy secondary buffer.
     CopySecondaryBuffer();
diff --git a/cobalt/dom/performance.h b/cobalt/dom/performance.h
index 50a754b..eb720c8 100644
--- a/cobalt/dom/performance.h
+++ b/cobalt/dom/performance.h
@@ -25,7 +25,6 @@
 #include "base/time/default_tick_clock.h"
 #include "cobalt/base/application_state.h"
 #include "cobalt/base/clock.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/dom/performance_entry_list_impl.h"
 #include "cobalt/dom/performance_high_resolution_time.h"
 #include "cobalt/dom/performance_lifecycle_timing.h"
@@ -36,6 +35,7 @@
 #include "cobalt/dom/performance_timing.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/exception_state.h"
+#include "cobalt/web/event_target.h"
 #include "net/base/load_timing_info.h"
 
 namespace cobalt {
@@ -46,7 +46,7 @@
 // Implements the Performance IDL interface, an instance of which is created
 // and owned by the Window object.
 //   https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#sec-window.performance-attribute
-class Performance : public EventTarget {
+class Performance : public web::EventTarget {
  public:
   // Ensure that the timer resolution is at the lowest 20 microseconds in
   // order to mitigate potential Spectre-related attacks.  This is following
@@ -99,8 +99,8 @@
   bool CanAddResourceTimingEntry();
   void CopySecondaryBuffer();
   void set_onresourcetimingbufferfull(
-      const EventTarget::EventListenerScriptValue& event_listener);
-  const EventTarget::EventListenerScriptValue* onresourcetimingbufferfull()
+      const web::EventTarget::EventListenerScriptValue& event_listener);
+  const web::EventTarget::EventListenerScriptValue* onresourcetimingbufferfull()
       const;
   void FireResourceTimingBufferFullEvent();
   void AddPerformanceResourceTimingEntry(
diff --git a/cobalt/dom/performance_observer.cc b/cobalt/dom/performance_observer.cc
index f6caf30..8c97fe0 100644
--- a/cobalt/dom/performance_observer.cc
+++ b/cobalt/dom/performance_observer.cc
@@ -17,11 +17,11 @@
 #include <unordered_set>
 
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/performance.h"
 #include "cobalt/dom/performance_entry.h"
 #include "cobalt/dom/window.h"
+#include "cobalt/web/dom_exception.h"
 
 namespace cobalt {
 namespace dom {
@@ -106,14 +106,14 @@
   bool has_entry_types = options.has_entry_types();
   bool has_type = options.has_type();
   if (!has_entry_types && !has_type) {
-    DOMException::Raise(DOMException::kSyntaxErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, exception_state);
   }
   // 4.  If options's entryTypes is present and any other member is also
   // present, then throw a SyntaxError.
   bool entry_types_present = has_entry_types && !options.entry_types().empty();
   bool type_present = has_type && !options.type().empty();
   if (entry_types_present && type_present) {
-    DOMException::Raise(DOMException::kSyntaxErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, exception_state);
   }
   // 5.  Update or check observer's observer type by running these steps:
   // 5.1   If observer's observer type is "undefined":
@@ -133,12 +133,14 @@
   // member is present, then throw an InvalidModificationError.
   if (observer_type_ == PerformanceObserverType::kSingle &&
       entry_types_present) {
-    DOMException::Raise(DOMException::kInvalidModificationErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidModificationErr,
+                             exception_state);
   }
   // 5.3  If observer's observer type is "multiple" and options's type member
   // is present, then throw an InvalidModificationError.
   if (observer_type_ == PerformanceObserverType::kMultiple && type_present) {
-    DOMException::Raise(DOMException::kInvalidModificationErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidModificationErr,
+                             exception_state);
   }
   // 6  If observer's observer type is "multiple", run the following steps:
   if (observer_type_ == PerformanceObserverType::kMultiple) {
diff --git a/cobalt/dom/performance_observer_test.cc b/cobalt/dom/performance_observer_test.cc
index 7a4a3c7..49f7d38 100644
--- a/cobalt/dom/performance_observer_test.cc
+++ b/cobalt/dom/performance_observer_test.cc
@@ -12,15 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "cobalt/dom/performance_observer.h"
+
 #include "cobalt/base/clock.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/performance.h"
 #include "cobalt/dom/performance_entry.h"
 #include "cobalt/dom/performance_high_resolution_time.h"
-#include "cobalt/dom/performance_observer.h"
 #include "cobalt/dom/performance_observer_init.h"
 #include "cobalt/dom/performance_resource_timing.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
+#include "cobalt/web/dom_exception.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -40,9 +41,9 @@
       const NativePerformanceObserverCallback& native_callback,
       const scoped_refptr<Performance>& performance);
 
-   bool IsRegistered() { return is_registered_; }
-   size_t NumOfPerformanceEntries() { return observer_buffer_.size(); }
-   DOMHighResTimeStamp Now() { return performance_->Now(); }
+  bool IsRegistered() { return is_registered_; }
+  size_t NumOfPerformanceEntries() { return observer_buffer_.size(); }
+  DOMHighResTimeStamp Now() { return performance_->Now(); }
 };
 
 MockPerformanceObserver::MockPerformanceObserver(
@@ -64,11 +65,12 @@
 
 PerformanceObserverTest::PerformanceObserverTest()
     : clock_(new base::SystemMonotonicClock()),
-    performance_(new Performance(&environment_settings_, clock_)),
-    observer_(new MockPerformanceObserver(
-        base::Bind(&PerformanceObserverCallbackMock::NativePerformanceObserverCallback,
-                   base::Unretained(&callback_mock_)),
-                   performance_)) {}
+      performance_(new Performance(&environment_settings_, clock_)),
+      observer_(new MockPerformanceObserver(
+          base::Bind(&PerformanceObserverCallbackMock::
+                         NativePerformanceObserverCallback,
+                     base::Unretained(&callback_mock_)),
+          performance_)) {}
 
 TEST_F(PerformanceObserverTest, Observe) {
   DCHECK(observer_);
@@ -86,10 +88,8 @@
   DCHECK(observer_);
   EXPECT_EQ(0, observer_->NumOfPerformanceEntries());
 
-  scoped_refptr<PerformanceResourceTiming> entry(
-      new PerformanceResourceTiming("resource",
-                                    observer_->Now(),
-                                    observer_->Now()));
+  scoped_refptr<PerformanceResourceTiming> entry(new PerformanceResourceTiming(
+      "resource", observer_->Now(), observer_->Now()));
 
   observer_->EnqueuePerformanceEntry(entry);
   EXPECT_EQ(1, observer_->NumOfPerformanceEntries());
diff --git a/cobalt/dom/performance_resource_timing.cc b/cobalt/dom/performance_resource_timing.cc
index 4f7bba5..f3bb1e8 100644
--- a/cobalt/dom/performance_resource_timing.cc
+++ b/cobalt/dom/performance_resource_timing.cc
@@ -17,15 +17,15 @@
 
 namespace cobalt {
 namespace dom {
-
 namespace {
 const char kPerformanceResourceTimingCacheMode[] = "local";
-}
+const uint64_t kPerformanceResourceTimingHeaderSize = 300;
+}  // namespace
 
 PerformanceResourceTiming::PerformanceResourceTiming(
     const std::string& name, DOMHighResTimeStamp start_time,
     DOMHighResTimeStamp end_time)
-    : PerformanceEntry(name, start_time, end_time), transfer_size_(0) {}
+    : PerformanceEntry(name, start_time, end_time) {}
 
 PerformanceResourceTiming::PerformanceResourceTiming(
     const net::LoadTimingInfo& timing_info, const std::string& initiator_type,
@@ -38,7 +38,6 @@
                            time_origin, timing_info.receive_headers_end)),
       initiator_type_(initiator_type),
       cache_mode_(kPerformanceResourceTimingCacheMode),
-      transfer_size_(0),
       timing_info_(timing_info),
       time_origin_(time_origin),
       timing_info_response_end_(performance->Now()) {}
@@ -116,7 +115,17 @@
 }
 
 uint64_t PerformanceResourceTiming::transfer_size() const {
-  return transfer_size_;
+  // Cobalt does not support cache mode.
+  // The transferSize getter steps are to perform the following steps:
+  //    https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-transfersize
+  // 1. If this's cache mode is "local", then return 0.
+  // 2. If this's cache mode is "validated", then return 300.
+  // 3. Return this's timing info's encoded body size plus 300.
+  return timing_info_.encoded_body_size + kPerformanceResourceTimingHeaderSize;
+}
+
+uint64_t PerformanceResourceTiming::encoded_body_size() const {
+  return timing_info_.encoded_body_size;
 }
 
 void PerformanceResourceTiming::SetResourceTimingEntry(
diff --git a/cobalt/dom/performance_resource_timing.h b/cobalt/dom/performance_resource_timing.h
index b9ed763..c80e45f 100644
--- a/cobalt/dom/performance_resource_timing.h
+++ b/cobalt/dom/performance_resource_timing.h
@@ -56,6 +56,7 @@
   // of loading completes instead.
   DOMHighResTimeStamp response_end() const;
   uint64_t transfer_size() const;
+  uint64_t encoded_body_size() const;
 
   std::string entry_type() const override { return "resource"; }
   PerformanceEntryType EntryTypeEnum() const override {
@@ -72,7 +73,6 @@
  private:
   std::string initiator_type_;
   std::string cache_mode_;
-  uint64_t transfer_size_;
   std::string requested_url_;
   net::LoadTimingInfo timing_info_;
   base::TimeTicks time_origin_;
diff --git a/cobalt/dom/performance_resource_timing.idl b/cobalt/dom/performance_resource_timing.idl
index c1c0bd3..fe2875f 100644
--- a/cobalt/dom/performance_resource_timing.idl
+++ b/cobalt/dom/performance_resource_timing.idl
@@ -27,5 +27,5 @@
   readonly  attribute DOMHighResTimeStamp responseStart;
   readonly  attribute DOMHighResTimeStamp responseEnd;
   readonly  attribute unsigned long long  transferSize;
+  readonly  attribute unsigned long long  encodedBodySize;
 };
-
diff --git a/cobalt/dom/pointer_state.cc b/cobalt/dom/pointer_state.cc
index 49ae842..5dec62c 100644
--- a/cobalt/dom/pointer_state.cc
+++ b/cobalt/dom/pointer_state.cc
@@ -26,7 +26,7 @@
 namespace cobalt {
 namespace dom {
 
-void PointerState::QueuePointerEvent(const scoped_refptr<Event>& event) {
+void PointerState::QueuePointerEvent(const scoped_refptr<web::Event>& event) {
   TRACE_EVENT1("cobalt::dom", "PointerState::QueuePointerEvent()", "event",
                TRACE_STR_COPY(event->type().c_str()));
 
@@ -38,7 +38,7 @@
 }
 
 namespace {
-int32_t GetPointerIdFromEvent(const scoped_refptr<Event>& event) {
+int32_t GetPointerIdFromEvent(const scoped_refptr<web::Event>& event) {
   if (event->GetWrappableType() == base::GetTypeId<PointerEvent>()) {
     const PointerEvent* const pointer_event =
         base::polymorphic_downcast<const PointerEvent* const>(event.get());
@@ -48,15 +48,15 @@
 }
 }  // namespace
 
-scoped_refptr<Event> PointerState::GetNextQueuedPointerEvent() {
-  scoped_refptr<Event> event;
+scoped_refptr<web::Event> PointerState::GetNextQueuedPointerEvent() {
+  scoped_refptr<web::Event> event;
   if (pointer_events_.empty()) {
     return event;
   }
 
   // Ignore pointer move events when they are succeeded by additional pointer
   // move events with the same pointerId.
-  scoped_refptr<Event> front_event(pointer_events_.front());
+  scoped_refptr<web::Event> front_event(pointer_events_.front());
   bool next_event_is_move_event =
       front_event->type() == base::Tokens::pointermove() ||
       front_event->type() == base::Tokens::mousemove();
@@ -120,9 +120,9 @@
   if (override != target_override_.end() && override->second &&
       (pending_override == pending_target_override_.end() ||
        pending_override->second != override->second)) {
-    override->second->DispatchEvent(
-        new PointerEvent(base::Tokens::lostpointercapture(), Event::kBubbles,
-                         Event::kNotCancelable, view, *event_init));
+    override->second->DispatchEvent(new PointerEvent(
+        base::Tokens::lostpointercapture(), web::Event::kBubbles,
+        web::Event::kNotCancelable, view, *event_init));
   }
 
   // 2. If the pending pointer capture target override for this pointer is set
@@ -133,9 +133,9 @@
       pending_override->second &&
       (override == target_override_.end() ||
        pending_override->second != override->second)) {
-    pending_override->second->DispatchEvent(
-        new PointerEvent(base::Tokens::gotpointercapture(), Event::kBubbles,
-                         Event::kNotCancelable, view, *event_init));
+    pending_override->second->DispatchEvent(new PointerEvent(
+        base::Tokens::gotpointercapture(), web::Event::kBubbles,
+        web::Event::kNotCancelable, view, *event_init));
   }
 
   // 3. Set the pointer capture target override to the pending pointer capture
@@ -161,11 +161,11 @@
   //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#setting-pointer-capture
 
   // 1. If the pointerId provided as the method's argument does not match any of
-  // the active pointers, then throw a DOMException with the name
+  // the active pointers, then throw a web::DOMException with the name
   // InvalidPointerId.
   if (active_pointers_.find(pointer_id) == active_pointers_.end()) {
-    DOMException::Raise(dom::DOMException::kInvalidPointerIdErr,
-                        exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidPointerIdErr,
+                             exception_state);
     return;
   }
 
@@ -173,7 +173,8 @@
   // its ownerDocument's tree, throw an exception with the name
   // InvalidStateError.
   if (!element || !element->owner_document()) {
-    DOMException::Raise(dom::DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -197,11 +198,11 @@
 
   // 1. If the pointerId provided as the method's argument does not match any of
   // the active pointers and these steps are not being invoked as a result of
-  // the implicit release of pointer capture, then throw a DOMException with the
-  // name InvalidPointerId.
+  // the implicit release of pointer capture, then throw a web::DOMException
+  // with the name InvalidPointerId.
   if (active_pointers_.find(pointer_id) == active_pointers_.end()) {
-    DOMException::Raise(dom::DOMException::kInvalidPointerIdErr,
-                        exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidPointerIdErr,
+                             exception_state);
     return;
   }
 
@@ -261,7 +262,7 @@
 }
 
 // static
-bool PointerState::CanQueueEvent(const scoped_refptr<Event>& event) {
+bool PointerState::CanQueueEvent(const scoped_refptr<web::Event>& event) {
   return event->GetWrappableType() == base::GetTypeId<PointerEvent>() ||
          event->GetWrappableType() == base::GetTypeId<MouseEvent>() ||
          event->GetWrappableType() == base::GetTypeId<WheelEvent>();
diff --git a/cobalt/dom/pointer_state.h b/cobalt/dom/pointer_state.h
index 30f3d50..15ed507 100644
--- a/cobalt/dom/pointer_state.h
+++ b/cobalt/dom/pointer_state.h
@@ -22,11 +22,11 @@
 #include "base/basictypes.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-#include "cobalt/dom/dom_exception.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/dom/html_element.h"
 #include "cobalt/dom/pointer_event_init.h"
 #include "cobalt/math/vector2d_f.h"
+#include "cobalt/web/dom_exception.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace dom {
@@ -47,10 +47,10 @@
   //
 
   // Queue up pointer related events.
-  void QueuePointerEvent(const scoped_refptr<Event>& event);
+  void QueuePointerEvent(const scoped_refptr<web::Event>& event);
 
   // Get the next queued pointer event.
-  scoped_refptr<Event> GetNextQueuedPointerEvent();
+  scoped_refptr<web::Event> GetNextQueuedPointerEvent();
 
   // Set the pending pointer capture target override for a pointer.
   void SetPendingPointerCaptureTargetOverride(int32_t pointer_id,
@@ -77,11 +77,11 @@
   // shutdown.
   void ClearForShutdown();
 
-  static bool CanQueueEvent(const scoped_refptr<Event>& event);
+  static bool CanQueueEvent(const scoped_refptr<web::Event>& event);
 
  private:
   // Stores pointer events until they are handled after a layout.
-  std::queue<scoped_refptr<Event> > pointer_events_;
+  std::queue<scoped_refptr<web::Event> > pointer_events_;
 
   // This stores the elements with target overrides
   //   https://www.w3.org/TR/2015/REC-pointerevents-20150224/#pointer-capture
diff --git a/cobalt/dom/progress_event.h b/cobalt/dom/progress_event.h
index 6e36cb8..bfa569a 100644
--- a/cobalt/dom/progress_event.h
+++ b/cobalt/dom/progress_event.h
@@ -18,14 +18,14 @@
 #include <string>
 
 #include "cobalt/base/token.h"
-#include "cobalt/dom/event.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace dom {
 
 // Events using the ProgressEvent interface indicate some kind of progression.
 //   https://www.w3.org/TR/progress-events/#interface-progressevent
-class ProgressEvent : public Event {
+class ProgressEvent : public web::Event {
  public:
   explicit ProgressEvent(const std::string& type);
   explicit ProgressEvent(base::Token type);
diff --git a/cobalt/dom/source_buffer.cc b/cobalt/dom/source_buffer.cc
index ea5a50d..dfc5dfa 100644
--- a/cobalt/dom/source_buffer.cc
+++ b/cobalt/dom/source_buffer.cc
@@ -50,9 +50,11 @@
 #include "base/logging.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
+#include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/dom_exception.h"
+#include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/media_source.h"
+#include "cobalt/web/dom_exception.h"
 #include "third_party/chromium/media/base/ranges.h"
 #include "third_party/chromium/media/base/timestamp_constants.h"
 
@@ -82,13 +84,24 @@
   return base::TimeDelta::FromSecondsD(time);
 }
 
+size_t GetEvictExtraInBytes(script::EnvironmentSettings* settings) {
+  DOMSettings* dom_settings =
+      base::polymorphic_downcast<DOMSettings*>(settings);
+  if (dom_settings && dom_settings->decoder_buffer_memory_info()) {
+    return dom_settings->decoder_buffer_memory_info()
+        ->GetSourceBufferEvictExtraInBytes();
+  }
+  return 0;
+}
+
 }  // namespace
 
 SourceBuffer::SourceBuffer(script::EnvironmentSettings* settings,
                            const std::string& id, MediaSource* media_source,
                            ChunkDemuxer* chunk_demuxer, EventQueue* event_queue)
-    : EventTarget(settings),
+    : web::EventTarget(settings),
       id_(id),
+      evict_extra_in_bytes_(GetEvictExtraInBytes(settings)),
       chunk_demuxer_(chunk_demuxer),
       media_source_(media_source),
       event_queue_(event_queue),
@@ -114,18 +127,21 @@
 void SourceBuffer::set_mode(SourceBufferAppendMode mode,
                             script::ExceptionState* exception_state) {
   if (media_source_ == NULL) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
   if (updating_) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
   media_source_->OpenIfInEndedState();
 
   if (chunk_demuxer_->IsParsingMediaSegment(id_)) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -137,7 +153,8 @@
 scoped_refptr<TimeRanges> SourceBuffer::buffered(
     script::ExceptionState* exception_state) const {
   if (media_source_ == NULL) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return NULL;
   }
 
@@ -153,18 +170,21 @@
 void SourceBuffer::set_timestamp_offset(
     double offset, script::ExceptionState* exception_state) {
   if (media_source_ == NULL) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
   if (updating_) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
   media_source_->OpenIfInEndedState();
 
   if (chunk_demuxer_->IsParsingMediaSegment(id_)) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -177,11 +197,13 @@
 void SourceBuffer::set_append_window_start(
     double start, script::ExceptionState* exception_state) {
   if (media_source_ == NULL) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
   if (updating_) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -196,11 +218,13 @@
 void SourceBuffer::set_append_window_end(
     double end, script::ExceptionState* exception_state) {
   if (media_source_ == NULL) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
   if (updating_) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -239,17 +263,20 @@
   TRACE_EVENT0("cobalt::dom", "SourceBuffer::Abort()");
 
   if (media_source_ == NULL) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
   if (!media_source_->IsOpen()) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
   if (pending_remove_start_ != -1) {
     DCHECK(updating_);
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -268,12 +295,16 @@
 
 void SourceBuffer::Remove(double start, double end,
                           script::ExceptionState* exception_state) {
+  TRACE_EVENT2("cobalt::dom", "SourceBuffer::Remove()", "start", start, "end",
+               end);
   if (media_source_ == NULL) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
   if (updating_) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -304,11 +335,13 @@
     const scoped_refptr<TrackDefaultList>& track_defaults,
     script::ExceptionState* exception_state) {
   if (media_source_ == NULL) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
   if (updating_) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -350,7 +383,7 @@
 }
 
 void SourceBuffer::TraceMembers(script::Tracer* tracer) {
-  EventTarget::TraceMembers(tracer);
+  web::EventTarget::TraceMembers(tracer);
 
   tracer->Trace(event_queue_);
   tracer->Trace(media_source_);
@@ -369,32 +402,38 @@
 }
 
 void SourceBuffer::ScheduleEvent(base::Token event_name) {
-  scoped_refptr<Event> event = new Event(event_name);
+  scoped_refptr<web::Event> event = new web::Event(event_name);
   event->set_target(this);
   event_queue_->Enqueue(event);
 }
 
 bool SourceBuffer::PrepareAppend(size_t new_data_size,
                                  script::ExceptionState* exception_state) {
+  TRACE_EVENT1("cobalt::dom", "SourceBuffer::PrepareAppend()", "new_data_size",
+               new_data_size);
   if (media_source_ == NULL) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return false;
   }
   if (updating_) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return false;
   }
 
   DCHECK(media_source_->GetMediaElement());
   if (media_source_->GetMediaElement()->error()) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return false;
   }
 
   media_source_->OpenIfInEndedState();
 
   if (!EvictCodedFrames(new_data_size)) {
-    DOMException::Raise(DOMException::kQuotaExceededErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kQuotaExceededErr,
+                             exception_state);
     return false;
   }
 
@@ -406,12 +445,15 @@
   DCHECK(media_source_->GetMediaElement());
   double current_time = media_source_->GetMediaElement()->current_time(NULL);
   return chunk_demuxer_->EvictCodedFrames(
-      id_, base::TimeDelta::FromSecondsD(current_time), new_data_size);
+      id_, base::TimeDelta::FromSecondsD(current_time),
+      new_data_size + evict_extra_in_bytes_);
 }
 
 void SourceBuffer::AppendBufferInternal(
     const unsigned char* data, size_t size,
     script::ExceptionState* exception_state) {
+  TRACE_EVENT1("cobalt::dom", "SourceBuffer::AppendBufferInternal()", "size",
+               size);
   if (!PrepareAppend(size, exception_state)) {
     return;
   }
@@ -498,6 +540,7 @@
 }
 
 void SourceBuffer::OnRemoveTimer() {
+  TRACE_EVENT0("cobalt::dom", "SourceBuffer::OnRemoveTimer()");
   DCHECK(updating_);
   DCHECK_GE(pending_remove_start_, 0);
   DCHECK_LT(pending_remove_start_, pending_remove_end_);
diff --git a/cobalt/dom/source_buffer.h b/cobalt/dom/source_buffer.h
index 56dba93..1f4f4b6 100644
--- a/cobalt/dom/source_buffer.h
+++ b/cobalt/dom/source_buffer.h
@@ -57,7 +57,6 @@
 #include "cobalt/base/token.h"
 #include "cobalt/dom/audio_track_list.h"
 #include "cobalt/dom/event_queue.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/dom/source_buffer_append_mode.h"
 #include "cobalt/dom/time_ranges.h"
 #include "cobalt/dom/track_default_list.h"
@@ -66,6 +65,7 @@
 #include "cobalt/script/array_buffer_view.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/exception_state.h"
+#include "cobalt/web/event_target.h"
 #include "third_party/chromium/media/base/media_tracks.h"
 #include "third_party/chromium/media/filters/chunk_demuxer.h"
 
@@ -77,7 +77,7 @@
 // The SourceBuffer interface exposes the media source buffer so its user can
 // append media data to.
 //   https://www.w3.org/TR/2016/CR-media-source-20160705/#sourcebuffer
-class SourceBuffer : public dom::EventTarget {
+class SourceBuffer : public web::EventTarget {
  public:
   typedef ::media::ChunkDemuxer ChunkDemuxer;
 
@@ -166,6 +166,7 @@
       const std::string& byte_stream_track_id) const;
 
   const std::string id_;
+  const size_t evict_extra_in_bytes_;
   ChunkDemuxer* chunk_demuxer_;
   MediaSource* media_source_;
   scoped_refptr<TrackDefaultList> track_defaults_ = new TrackDefaultList(NULL);
diff --git a/cobalt/dom/source_buffer_list.cc b/cobalt/dom/source_buffer_list.cc
index 2431228..3a67e42 100644
--- a/cobalt/dom/source_buffer_list.cc
+++ b/cobalt/dom/source_buffer_list.cc
@@ -50,7 +50,7 @@
 #include "base/logging.h"
 #include "base/strings/stringprintf.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/event.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace dom {
@@ -62,7 +62,7 @@
 
 SourceBufferList::SourceBufferList(script::EnvironmentSettings* settings,
                                    EventQueue* event_queue)
-    : EventTarget(settings), event_queue_(event_queue) {
+    : web::EventTarget(settings), event_queue_(event_queue) {
   DCHECK(event_queue_);
   source_buffers_.reserve(kSizeOfSourceBufferToReserveInitially);
 }
@@ -124,7 +124,7 @@
 }
 
 void SourceBufferList::TraceMembers(script::Tracer* tracer) {
-  EventTarget::TraceMembers(tracer);
+  web::EventTarget::TraceMembers(tracer);
 
   if (event_queue_) {
     event_queue_->TraceMembers(tracer);
@@ -135,7 +135,7 @@
 }
 
 void SourceBufferList::ScheduleEvent(base::Token event_name) {
-  scoped_refptr<Event> event = new Event(event_name);
+  scoped_refptr<web::Event> event = new web::Event(event_name);
   event->set_target(this);
   event_queue_->Enqueue(event);
 }
diff --git a/cobalt/dom/source_buffer_list.h b/cobalt/dom/source_buffer_list.h
index 4e1da23..0c348cc 100644
--- a/cobalt/dom/source_buffer_list.h
+++ b/cobalt/dom/source_buffer_list.h
@@ -50,10 +50,10 @@
 
 #include "base/memory/ref_counted.h"
 #include "cobalt/dom/event_queue.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/dom/source_buffer.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace dom {
@@ -62,7 +62,7 @@
 // to access all the SourceBuffers and the active SourceBuffers of a MediaSource
 // object.
 //   https://www.w3.org/TR/2016/CR-media-source-20160705/#sourcebufferlist
-class SourceBufferList : public EventTarget {
+class SourceBufferList : public web::EventTarget {
  public:
   // Custom, not in any spec.
   //
diff --git a/cobalt/dom/storage_event.h b/cobalt/dom/storage_event.h
index 606aef7..f0809b3 100644
--- a/cobalt/dom/storage_event.h
+++ b/cobalt/dom/storage_event.h
@@ -18,14 +18,14 @@
 #include <string>
 
 #include "base/optional.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace dom {
 class Storage;
 
-class StorageEvent : public Event {
+class StorageEvent : public web::Event {
  public:
   StorageEvent();
   explicit StorageEvent(const std::string& type);
diff --git a/cobalt/dom/testing/BUILD.gn b/cobalt/dom/testing/BUILD.gn
index 5d92349..ea55f62 100644
--- a/cobalt/dom/testing/BUILD.gn
+++ b/cobalt/dom/testing/BUILD.gn
@@ -17,15 +17,11 @@
   has_pedantic_warnings = true
 
   sources = [
-    "gtest_workarounds.h",
     "html_collection_testing.h",
-    "mock_event_listener.h",
     "mock_layout_boxes.h",
     "stub_css_parser.cc",
     "stub_css_parser.h",
     "stub_environment_settings.h",
-    "stub_script_runner.cc",
-    "stub_script_runner.h",
     "stub_window.h",
   ]
 
@@ -38,13 +34,16 @@
     "//cobalt/css_parser",
     "//cobalt/cssom",
     "//cobalt/dom",
-    "//cobalt/dom:dom_exception",
     "//cobalt/dom_parser",
     "//cobalt/loader",
     "//cobalt/script",
     "//cobalt/web",
+    "//cobalt/web:dom_exception",
+    "//cobalt/web/testing:web_testing",
     "//testing/gmock",
     "//testing/gtest",
     "//url",
   ]
+
+  public_deps = [ "//cobalt/web/testing:web_testing" ]
 }
diff --git a/cobalt/dom/testing/fake_exception_state.h b/cobalt/dom/testing/fake_exception_state.h
index 9c88bc9..34a0304 100644
--- a/cobalt/dom/testing/fake_exception_state.h
+++ b/cobalt/dom/testing/fake_exception_state.h
@@ -18,7 +18,7 @@
 #include "cobalt/script/exception_state.h"
 
 #include "base/memory/ref_counted.h"
-#include "cobalt/dom/dom_exception.h"
+#include "cobalt/web/dom_exception.h"
 
 namespace cobalt {
 namespace dom {
@@ -29,20 +29,20 @@
   void SetException(
       const scoped_refptr<script::ScriptException>& exception) override {
     dom_exception_ = base::WrapRefCounted(
-        base::polymorphic_downcast<dom::DOMException*>(exception.get()));
+        base::polymorphic_downcast<web::DOMException*>(exception.get()));
   }
   void SetSimpleExceptionVA(script::SimpleExceptionType type,
-                            const char* format, va_list & args) override {
+                            const char* format, va_list& args) override {
     // no-op
   }
-  dom::DOMException::ExceptionCode GetExceptionCode() {
-    return dom_exception_ ? static_cast<dom::DOMException::ExceptionCode>(
+  web::DOMException::ExceptionCode GetExceptionCode() {
+    return dom_exception_ ? static_cast<web::DOMException::ExceptionCode>(
                                 dom_exception_->code())
-                          : dom::DOMException::kNone;
+                          : web::DOMException::kNone;
   }
 
  private:
-  scoped_refptr<dom::DOMException> dom_exception_;
+  scoped_refptr<web::DOMException> dom_exception_;
 };
 
 }  // namespace testing
diff --git a/cobalt/dom/testing/stub_environment_settings.h b/cobalt/dom/testing/stub_environment_settings.h
index a70eef3..01d01f3 100644
--- a/cobalt/dom/testing/stub_environment_settings.h
+++ b/cobalt/dom/testing/stub_environment_settings.h
@@ -26,7 +26,7 @@
  public:
   explicit StubEnvironmentSettings(const Options& options = Options())
       : DOMSettings(null_debugger_hooks_, 0, nullptr, nullptr, nullptr, nullptr,
-                    nullptr, options) {}
+                    options) {}
   ~StubEnvironmentSettings() override {}
 
  private:
diff --git a/cobalt/dom/testing/stub_window.h b/cobalt/dom/testing/stub_window.h
index 197191c..e4bad1e 100644
--- a/cobalt/dom/testing/stub_window.h
+++ b/cobalt/dom/testing/stub_window.h
@@ -38,7 +38,7 @@
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/environment_settings.h"
-#include "cobalt/web/stub_web_context.h"
+#include "cobalt/web/testing/stub_web_context.h"
 #include "starboard/window.h"
 #include "url/gurl.h"
 
@@ -57,7 +57,7 @@
             new dom_parser::Parser(base::Bind(&StubLoadCompleteCallback))),
         local_storage_database_(NULL),
         dom_stat_tracker_(new dom::DomStatTracker("StubWindow")) {
-    web_context_.reset(new web::test::StubWebContext());
+    web_context_.reset(new web::testing::StubWebContext());
     web_context_->setup_environment_settings(
         new dom::testing::StubEnvironmentSettings(options));
     web_context_->environment_settings()->set_base_url(GURL("about:blank"));
@@ -78,7 +78,7 @@
             "en", base::Callback<void(const GURL&)>(),
             base::Bind(&StubLoadCompleteCallback), NULL,
             network_bridge::PostSender(), csp::kCSPRequired,
-            dom::kCspEnforcementEnable,
+            web::kCspEnforcementEnable,
             base::Closure() /* csp_policy_changed */,
             base::Closure() /* ran_animation_frame_callbacks */,
             dom::Window::CloseCallback() /* window_close */,
@@ -99,7 +99,7 @@
     return web_context_->global_environment();
   }
   css_parser::Parser* css_parser() { return css_parser_.get(); }
-  script::EnvironmentSettings* environment_settings() {
+  web::EnvironmentSettings* environment_settings() {
     return web_context_->environment_settings();
   }
 
diff --git a/cobalt/dom/text_test.cc b/cobalt/dom/text_test.cc
index ec02d45..dd46e80 100644
--- a/cobalt/dom/text_test.cc
+++ b/cobalt/dom/text_test.cc
@@ -18,7 +18,7 @@
 #include "cobalt/dom/element.h"
 #include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/html_element_context.h"
-#include "cobalt/dom/testing/gtest_workarounds.h"
+#include "cobalt/web/testing/gtest_workarounds.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
diff --git a/cobalt/dom/time_ranges.cc b/cobalt/dom/time_ranges.cc
index 6d9795f..2b5431c 100644
--- a/cobalt/dom/time_ranges.cc
+++ b/cobalt/dom/time_ranges.cc
@@ -18,7 +18,7 @@
 #include <limits>
 
 #include "base/logging.h"
-#include "cobalt/dom/dom_exception.h"
+#include "cobalt/web/dom_exception.h"
 
 namespace cobalt {
 namespace dom {
@@ -36,8 +36,8 @@
 double TimeRanges::Start(uint32 index,
                          script::ExceptionState* exception_state) const {
   if (index >= ranges_.size()) {
-    exception_state->SetException(
-        base::WrapRefCounted(new DOMException(DOMException::kIndexSizeErr)));
+    exception_state->SetException(base::WrapRefCounted(
+        new web::DOMException(web::DOMException::kIndexSizeErr)));
     // Return value should be ignored.
     return 0.0;
   }
@@ -47,8 +47,8 @@
 double TimeRanges::End(uint32 index,
                        script::ExceptionState* exception_state) const {
   if (index >= ranges_.size()) {
-    exception_state->SetException(
-        base::WrapRefCounted(new DOMException(DOMException::kIndexSizeErr)));
+    exception_state->SetException(base::WrapRefCounted(
+        new web::DOMException(web::DOMException::kIndexSizeErr)));
     // Return value should be ignored.
     return 0.0;
   }
diff --git a/cobalt/dom/time_ranges_test.cc b/cobalt/dom/time_ranges_test.cc
index a94c50a..e0f1095 100644
--- a/cobalt/dom/time_ranges_test.cc
+++ b/cobalt/dom/time_ranges_test.cc
@@ -180,12 +180,14 @@
   {
     FakeExceptionState exception_state;
     time_ranges->Start(2, &exception_state);
-    EXPECT_EQ(DOMException::kIndexSizeErr, exception_state.GetExceptionCode());
+    EXPECT_EQ(web::DOMException::kIndexSizeErr,
+              exception_state.GetExceptionCode());
   }
   {
     FakeExceptionState exception_state;
     time_ranges->End(2, &exception_state);
-    EXPECT_EQ(DOMException::kIndexSizeErr, exception_state.GetExceptionCode());
+    EXPECT_EQ(web::DOMException::kIndexSizeErr,
+              exception_state.GetExceptionCode());
   }
 }
 
diff --git a/cobalt/dom/track_default_list.h b/cobalt/dom/track_default_list.h
index 23fca9c..55e2f6f 100644
--- a/cobalt/dom/track_default_list.h
+++ b/cobalt/dom/track_default_list.h
@@ -20,11 +20,11 @@
 #include <utility>
 
 #include "base/compiler_specific.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/track_default.h"
 #include "cobalt/script/exception_state.h"
 #include "cobalt/script/sequence.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/dom_exception.h"
 
 namespace cobalt {
 namespace dom {
@@ -35,8 +35,7 @@
  public:
   // Web API: TrackDefaultList
   //
-  explicit TrackDefaultList(script::ExceptionState* exception_state) {
-  }
+  explicit TrackDefaultList(script::ExceptionState* exception_state) {}
   TrackDefaultList(
       const script::Sequence<scoped_refptr<TrackDefault> >& track_defaults,
       script::ExceptionState* exception_state) {
@@ -50,7 +49,8 @@
       TypeAndId type_and_id(track_default->type(),
                             track_default->byte_stream_track_id());
       if (!type_and_ids.insert(type_and_id).second) {
-        DOMException::Raise(DOMException::kInvalidAccessErr, exception_state);
+        web::DOMException::Raise(web::DOMException::kInvalidAccessErr,
+                                 exception_state);
         return;
       }
     }
diff --git a/cobalt/dom/track_event.h b/cobalt/dom/track_event.h
index 76bbf43..575be96 100644
--- a/cobalt/dom/track_event.h
+++ b/cobalt/dom/track_event.h
@@ -19,9 +19,9 @@
 
 #include "cobalt/base/token.h"
 #include "cobalt/dom/audio_track.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/dom/video_track.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace dom {
@@ -29,7 +29,7 @@
 // The TrackEvent provides specific contextual information associated with
 // events fired on AudioTrack, VideoTrack, and TextTrack objects.
 //   https://www.w3.org/TR/html51/semantics-embedded-content.html#trackevent-trackevent
-class TrackEvent : public Event {
+class TrackEvent : public web::Event {
  public:
   // Custom, not in any spec.
   //
diff --git a/cobalt/dom/track_list_base.h b/cobalt/dom/track_list_base.h
index 37c83b1..217bbb8 100644
--- a/cobalt/dom/track_list_base.h
+++ b/cobalt/dom/track_list_base.h
@@ -21,23 +21,24 @@
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
+#include "base/trace_event/trace_event.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/event.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/dom/html_media_element.h"
 #include "cobalt/dom/track_event.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/web/event.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace dom {
 
 // This class contains common operations for AudioTrackList and VideoTrackList.
 template <typename TrackType>
-class TrackListBase : public EventTarget {
+class TrackListBase : public web::EventTarget {
  public:
   TrackListBase(script::EnvironmentSettings* settings,
                 HTMLMediaElement* media_element)
-      : EventTarget(settings) {
+      : web::EventTarget(settings) {
     DCHECK(media_element);
     media_element_ = base::AsWeakPtr(media_element);
   }
@@ -49,6 +50,7 @@
   }
 
   scoped_refptr<TrackType> GetTrackById(const std::string& id) const {
+    TRACE_EVENT1("cobalt::dom", "TrackListBase::GetTrackById()", "id", id);
     for (size_t i = 0; i < tracks_.size(); ++i) {
       if (tracks_[i]->id() == id) {
         return tracks_[i];
@@ -87,11 +89,11 @@
   }
 
   void ScheduleChangeEvent() {
-    ScheduleEvent(new Event(base::Tokens::change()));
+    ScheduleEvent(new web::Event(base::Tokens::change()));
   }
 
  private:
-  void ScheduleEvent(const scoped_refptr<Event>& event) {
+  void ScheduleEvent(const scoped_refptr<web::Event>& event) {
     event->set_target(this);
     media_element_->ScheduleEvent(event);
   }
diff --git a/cobalt/dom/transition_event.h b/cobalt/dom/transition_event.h
index b9cd825..f2b70f7 100644
--- a/cobalt/dom/transition_event.h
+++ b/cobalt/dom/transition_event.h
@@ -19,14 +19,14 @@
 
 #include "cobalt/base/token.h"
 #include "cobalt/cssom/property_definitions.h"
-#include "cobalt/dom/event.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace dom {
 
 // The completion of a CSS Transition generates a corresponding DOM Event.
 //   https://www.w3.org/TR/2013/WD-css3-transitions-20131119/#transition-events
-class TransitionEvent : public Event {
+class TransitionEvent : public web::Event {
  public:
   explicit TransitionEvent(const std::string& type)
       : Event(base::Token(type), kBubbles, kCancelable),
diff --git a/cobalt/dom/ui_event.h b/cobalt/dom/ui_event.h
index 55225b9..464c13d 100644
--- a/cobalt/dom/ui_event.h
+++ b/cobalt/dom/ui_event.h
@@ -18,10 +18,10 @@
 #include <string>
 
 #include "cobalt/dom/document.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/dom/ui_event_init.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace dom {
@@ -29,7 +29,7 @@
 // The UIEvent provides specific contextual information associated with User
 // Interface events.
 //   https://www.w3.org/TR/2016/WD-uievents-20160804/#events-uievents
-class UIEvent : public Event {
+class UIEvent : public web::Event {
  public:
   explicit UIEvent(const std::string& type);
   UIEvent(const std::string& type, const UIEventInit& init_dict);
diff --git a/cobalt/dom/url.cc b/cobalt/dom/url.cc
deleted file mode 100644
index c088a43..0000000
--- a/cobalt/dom/url.cc
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2015 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "cobalt/dom/url.h"
-
-#include "base/guid.h"
-#include "base/logging.h"
-#include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/dom_settings.h"
-#include "cobalt/dom/media_source.h"
-#include "cobalt/web/context.h"
-#include "cobalt/web/environment_settings.h"
-#include "url/gurl.h"
-
-namespace cobalt {
-namespace dom {
-
-namespace {
-
-const char kBlobUrlProtocol[] = "blob";
-
-}  // namespace
-
-// static
-std::string URL::CreateObjectURL(
-    script::EnvironmentSettings* environment_settings,
-    const scoped_refptr<MediaSource>& media_source) {
-  DOMSettings* dom_settings =
-      base::polymorphic_downcast<DOMSettings*>(environment_settings);
-  DCHECK(dom_settings);
-  DCHECK(dom_settings->media_source_registry());
-  if (!media_source) {
-    return "";
-  }
-
-  std::string blob_url = kBlobUrlProtocol;
-  blob_url += ':' + base::GenerateGUID();
-  dom_settings->media_source_registry()->Register(blob_url, media_source);
-  return blob_url;
-}
-
-// static
-std::string URL::CreateObjectURL(
-    script::EnvironmentSettings* environment_settings,
-    const scoped_refptr<Blob>& blob) {
-  web::EnvironmentSettings* web_settings =
-      base::polymorphic_downcast<web::EnvironmentSettings*>(
-          environment_settings);
-  DCHECK(web_settings);
-  DCHECK(web_settings->context()->blob_registry());
-  if (!blob) {
-    return "";
-  }
-
-  std::string blob_url = kBlobUrlProtocol;
-  blob_url += ':' + base::GenerateGUID();
-  web_settings->context()->blob_registry()->Register(blob_url, blob);
-  return blob_url;
-}
-
-// static
-void URL::RevokeObjectURL(script::EnvironmentSettings* environment_settings,
-                          const std::string& url) {
-  DOMSettings* dom_settings =
-      base::polymorphic_downcast<DOMSettings*>(environment_settings);
-  DCHECK(dom_settings);
-  DCHECK(dom_settings->media_source_registry());
-
-  // 1. If the url refers to a Blob that has a readability state of CLOSED OR if
-  // the value provided for the url argument is not a Blob URL, OR if the value
-  // provided for the url argument does not have an entry in the Blob URL Store,
-  // this method call does nothing. User agents may display a message on the
-  // error console.
-  if (!GURL(url).SchemeIs(kBlobUrlProtocol)) {
-    LOG(WARNING) << "URL is not a Blob URL.";
-    return;
-  }
-
-  // 2. Otherwise, user agents must remove the entry from the Blob URL Store for
-  // url.
-  if (!dom_settings->media_source_registry()->Unregister(url) &&
-      !dom_settings->context()->blob_registry()->Unregister(url)) {
-    DLOG(WARNING) << "Cannot find object for blob url " << url;
-  }
-}
-
-bool URL::BlobResolver(dom::Blob::Registry* registry, const GURL& url,
-                       const char** data, size_t* size) {
-  DCHECK(data);
-  DCHECK(size);
-
-  *size = 0;
-  *data = NULL;
-
-  dom::Blob* blob = registry->Retrieve(url.spec()).get();
-
-  if (blob) {
-    *size = static_cast<size_t>(blob->size());
-
-    if (*size > 0) {
-      *data = reinterpret_cast<const char*>(blob->data());
-    }
-
-    return true;
-  } else {
-    return false;
-  }
-}
-
-}  // namespace dom
-}  // namespace cobalt
diff --git a/cobalt/dom/url_media_source.cc b/cobalt/dom/url_media_source.cc
new file mode 100644
index 0000000..83e9c86
--- /dev/null
+++ b/cobalt/dom/url_media_source.cc
@@ -0,0 +1,52 @@
+// Copyright 2015 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/web/url.h"
+
+#include "base/guid.h"
+#include "base/logging.h"
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/dom/dom_settings.h"
+#include "cobalt/dom/media_source.h"
+#include "cobalt/web/context.h"
+#include "cobalt/web/environment_settings.h"
+#include "url/gurl.h"
+
+namespace cobalt {
+namespace dom {
+
+// extern
+void RegisterMediaSourceObjectURL(
+    script::EnvironmentSettings* environment_settings,
+    const std::string& blob_url,
+    const scoped_refptr<dom::MediaSource>& media_source) {
+  dom::DOMSettings* dom_settings =
+      base::polymorphic_downcast<dom::DOMSettings*>(environment_settings);
+  DCHECK(dom_settings);
+  DCHECK(dom_settings->media_source_registry());
+  dom_settings->media_source_registry()->Register(blob_url, media_source);
+}
+
+// extern
+bool UnregisterMediaSourceObjectURL(
+    script::EnvironmentSettings* environment_settings, const std::string& url) {
+  dom::DOMSettings* dom_settings =
+      base::polymorphic_downcast<dom::DOMSettings*>(environment_settings);
+  DCHECK(dom_settings);
+  DCHECK(dom_settings->media_source_registry());
+  return dom_settings->media_source_registry()->Unregister(url);
+}
+
+}  // namespace dom
+}  // namespace cobalt
diff --git a/cobalt/dom/user_agent_data_test.cc b/cobalt/dom/user_agent_data_test.cc
index d6f0a58..53d9255 100644
--- a/cobalt/dom/user_agent_data_test.cc
+++ b/cobalt/dom/user_agent_data_test.cc
@@ -24,8 +24,6 @@
 #include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/local_storage_database.h"
 #include "cobalt/dom/navigator.h"
-#include "cobalt/dom/navigator_ua_data.h"
-#include "cobalt/dom/testing/gtest_workarounds.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/dom_parser/parser.h"
@@ -35,6 +33,8 @@
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/script/source_code.h"
+#include "cobalt/web/navigator_ua_data.h"
+#include "cobalt/web/testing/gtest_workarounds.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using cobalt::cssom::ViewportSize;
@@ -69,8 +69,8 @@
         NULL, NULL, url_, "", platform_info_.get(), "en-US", "en",
         base::Callback<void(const GURL&)>(),
         base::Bind(&StubLoadCompleteCallback), NULL,
-        network_bridge::PostSender(), csp::kCSPRequired, kCspEnforcementEnable,
-        base::Closure() /* csp_policy_changed */,
+        network_bridge::PostSender(), csp::kCSPRequired,
+        web::kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
         base::Closure() /* ran_animation_frame_callbacks */,
         dom::Window::CloseCallback() /* window_close */,
         base::Closure() /* window_minimize */, NULL, NULL,
@@ -101,7 +101,7 @@
     global_environment_->SetReportEvalCallback(base::Closure());
     global_environment_->SetReportErrorCallback(
         script::GlobalEnvironment::ReportErrorCallback());
-    window_->DispatchEvent(new dom::Event(base::Tokens::unload()));
+    window_->DispatchEvent(new web::Event(base::Tokens::unload()));
 
     window_ = nullptr;
     global_environment_ = nullptr;
diff --git a/cobalt/dom/wheel_event.cc b/cobalt/dom/wheel_event.cc
index 609e479..2a1d54c 100644
--- a/cobalt/dom/wheel_event.cc
+++ b/cobalt/dom/wheel_event.cc
@@ -53,7 +53,7 @@
     const std::string& type, bool bubbles, bool cancelable,
     const scoped_refptr<Window>& view, int32 detail, int32 screen_x,
     int32 screen_y, int32 client_x, int32 client_y, uint16 button,
-    const scoped_refptr<EventTarget>& related_target,
+    const scoped_refptr<web::EventTarget>& related_target,
     const std::string& modifierslist, double delta_x, double delta_y,
     double delta_z, uint32 delta_mode) {
   InitMouseEvent(type, bubbles, cancelable, view, detail, screen_x, screen_y,
diff --git a/cobalt/dom/wheel_event.h b/cobalt/dom/wheel_event.h
index 33d3b7f..1f2e981 100644
--- a/cobalt/dom/wheel_event.h
+++ b/cobalt/dom/wheel_event.h
@@ -53,7 +53,7 @@
                       const scoped_refptr<Window>& view, int32 detail,
                       int32 screen_x, int32 screen_y, int32 client_x,
                       int32 client_y, uint16 button,
-                      const scoped_refptr<EventTarget>& related_target,
+                      const scoped_refptr<web::EventTarget>& related_target,
                       const std::string& modifierslist, double delta_x,
                       double delta_y, double delta_z, uint32 delta_mode);
 
diff --git a/cobalt/dom/window.cc b/cobalt/dom/window.cc
index 71df65b..bcbc0e5 100644
--- a/cobalt/dom/window.cc
+++ b/cobalt/dom/window.cc
@@ -30,9 +30,6 @@
 #include "cobalt/dom/device_orientation_event.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/element.h"
-#include "cobalt/dom/error_event.h"
-#include "cobalt/dom/error_event_init.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/dom/history.h"
 #include "cobalt/dom/html_element.h"
 #include "cobalt/dom/html_element_context.h"
@@ -57,6 +54,10 @@
 #include "cobalt/speech/speech_synthesis.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/environment_settings.h"
+#include "cobalt/web/error_event.h"
+#include "cobalt/web/error_event_init.h"
+#include "cobalt/web/event.h"
+#include "cobalt/web/window_or_worker_global_scope.h"
 #include "starboard/file.h"
 
 using cobalt::cssom::ViewportSize;
@@ -104,13 +105,14 @@
     script::ScriptValueFactory* script_value_factory,
     MediaSource::Registry* media_source_registry,
     DomStatTracker* dom_stat_tracker, const GURL& url,
-    const std::string& user_agent, UserAgentPlatformInfo* platform_info,
+    const std::string& user_agent, web::UserAgentPlatformInfo* platform_info,
     const std::string& language, const std::string& font_language_script,
     const base::Callback<void(const GURL&)> navigation_callback,
     const loader::Decoder::OnCompleteFunction& load_complete_callback,
     network_bridge::CookieJar* cookie_jar,
     const network_bridge::PostSender& post_sender,
-    csp::CSPHeaderPolicy require_csp, CspEnforcementType csp_enforcement_mode,
+    csp::CSPHeaderPolicy require_csp,
+    web::CspEnforcementType csp_enforcement_mode,
     const base::Closure& csp_policy_changed_callback,
     const base::Closure& ran_animation_frame_callbacks_callback,
     CloseCallback window_close_callback, base::Closure window_minimize_callback,
@@ -130,7 +132,7 @@
     bool log_tts)
     // 'window' object EventTargets require special handling for onerror events,
     // see EventTarget constructor for more details.
-    : EventTarget(settings, kUnpackOnErrorEvents),
+    : web::WindowOrWorkerGlobalScope(settings),
       viewport_size_(view_size),
       is_resize_event_pending_(false),
       is_reporting_script_error_(false),
@@ -165,8 +167,7 @@
       ALLOW_THIS_IN_INITIALIZER_LIST(
           relay_on_load_event_(new RelayLoadEvent(this))),
       ALLOW_THIS_IN_INITIALIZER_LIST(window_timers_(
-          new WindowTimers(this, dom_stat_tracker, debugger_hooks(),
-                           initial_application_state))),
+          this, dom_stat_tracker, debugger_hooks(), initial_application_state)),
       ALLOW_THIS_IN_INITIALIZER_LIST(animation_frame_request_callback_list_(
           new AnimationFrameRequestCallbackList(this, debugger_hooks()))),
       crypto_(new Crypto()),
@@ -375,7 +376,8 @@
          "compatible with old versions of Cobalt if you use btoa.";
   auto output = ForgivingBase64Encode(string_to_encode);
   if (!output) {
-    DOMException::Raise(DOMException::kInvalidCharacterErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidCharacterErr,
+                             exception_state);
     return std::string();
   }
   return *output;
@@ -386,7 +388,8 @@
   TRACE_EVENT0("cobalt::dom", "Window::Atob()");
   auto output = ForgivingBase64Decode(encoded_string);
   if (!output) {
-    DOMException::Raise(DOMException::kInvalidCharacterErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidCharacterErr,
+                             exception_state);
     return {};
   }
   return *output;
@@ -394,53 +397,19 @@
 
 int Window::SetTimeout(const WindowTimers::TimerCallbackArg& handler,
                        int timeout) {
-  LOG_IF(WARNING, timeout < 0)
-      << "Window::SetTimeout received negative timeout: " << timeout;
-  timeout = std::max(timeout, 0);
-
-  int return_value = 0;
-  if (window_timers_) {
-    return_value = window_timers_->SetTimeout(handler, timeout);
-  } else {
-    LOG(WARNING) << "window_timers_ does not exist.  Already destroyed?";
-  }
-
-  return return_value;
+  return window_timers_.SetTimeout(handler, timeout);
 }
 
-void Window::ClearTimeout(int handle) {
-  if (window_timers_) {
-    window_timers_->ClearTimeout(handle);
-  } else {
-    LOG(WARNING) << "window_timers_ does not exist.  Already destroyed?";
-  }
-}
+void Window::ClearTimeout(int handle) { window_timers_.ClearTimeout(handle); }
 
 int Window::SetInterval(const WindowTimers::TimerCallbackArg& handler,
                         int timeout) {
-  LOG_IF(WARNING, timeout < 0)
-      << "Window::SetInterval received negative interval: " << timeout;
-  timeout = std::max(timeout, 0);
-
-  int return_value = 0;
-  if (window_timers_) {
-    return_value = window_timers_->SetInterval(handler, timeout);
-  } else {
-    LOG(WARNING) << "window_timers_ does not exist.  Already destroyed?";
-  }
-
-  return return_value;
+  return window_timers_.SetInterval(handler, timeout);
 }
 
-void Window::ClearInterval(int handle) {
-  if (window_timers_) {
-    window_timers_->ClearInterval(handle);
-  } else {
-    DLOG(WARNING) << "window_timers_ does not exist.  Already destroyed?";
-  }
-}
+void Window::ClearInterval(int handle) { window_timers_.ClearInterval(handle); }
 
-void Window::DestroyTimers() { window_timers_->DisableCallbacks(); }
+void Window::DestroyTimers() { window_timers_.DisableCallbacks(); }
 
 scoped_refptr<Storage> Window::local_storage() const { return local_storage_; }
 
@@ -506,7 +475,7 @@
   return animation_frame_request_callback_list_->HasPendingCallbacks();
 }
 
-void Window::InjectEvent(const scoped_refptr<Event>& event) {
+void Window::InjectEvent(const scoped_refptr<web::Event>& event) {
   TRACE_EVENT1("cobalt::dom", "Window::InjectEvent()", "event",
                TRACE_STR_COPY(event->type().c_str()));
 
@@ -538,7 +507,7 @@
       state);
   if (timestamp == 0) return;
   performance_->SetApplicationState(state, timestamp);
-  window_timers_->SetApplicationState(state);
+  window_timers_.SetApplicationState(state);
 }
 
 bool Window::ReportScriptError(const script::ErrorReport& error_report) {
@@ -559,7 +528,7 @@
   // 7. Let event be a new trusted ErrorEvent object that does not bubble but is
   //    cancelable, and which has the event name error.
   // NOTE: Cobalt does not currently support trusted events.
-  ErrorEventInit error_event_init;
+  web::ErrorEventInit error_event_init;
   error_event_init.set_bubbles(false);
   error_event_init.set_cancelable(true);
 
@@ -586,8 +555,8 @@
                                                   : NULL);
   }
 
-  scoped_refptr<ErrorEvent> error_event(
-      new ErrorEvent(base::Tokens::error(), error_event_init));
+  scoped_refptr<web::ErrorEvent> error_event(
+      new web::ErrorEvent(base::Tokens::error(), error_event_init));
 
   // 13. Dispatch event at target.
   DispatchEvent(error_event);
@@ -622,7 +591,7 @@
   if (html_element_context()
           ->application_lifecycle_state()
           ->GetVisibilityState() == kVisibilityStateVisible) {
-    DispatchEvent(new Event(base::Tokens::resize()));
+    DispatchEvent(new web::Event(base::Tokens::resize()));
   } else {
     is_resize_event_pending_ = true;
   }
@@ -641,13 +610,13 @@
 
 void Window::OnWindowFocusChanged(bool has_focus) {
   DispatchEvent(
-      new Event(has_focus ? base::Tokens::focus() : base::Tokens::blur()));
+      new web::Event(has_focus ? base::Tokens::focus() : base::Tokens::blur()));
 }
 
 void Window::OnVisibilityStateChanged(VisibilityState visibility_state) {
   if (is_resize_event_pending_ && visibility_state == kVisibilityStateVisible) {
     is_resize_event_pending_ = false;
-    DispatchEvent(new Event(base::Tokens::resize()));
+    DispatchEvent(new web::Event(base::Tokens::resize()));
   }
 }
 
@@ -669,20 +638,20 @@
 }
 
 void Window::OnWindowOnOnlineEvent() {
-  DispatchEvent(new Event(base::Tokens::online()));
+  DispatchEvent(new web::Event(base::Tokens::online()));
 }
 
 void Window::OnWindowOnOfflineEvent() {
-  DispatchEvent(new Event(base::Tokens::offline()));
+  DispatchEvent(new web::Event(base::Tokens::offline()));
 }
 
-void Window::OnStartDispatchEvent(const scoped_refptr<dom::Event>& event) {
+void Window::OnStartDispatchEvent(const scoped_refptr<web::Event>& event) {
   if (!on_start_dispatch_event_callback_.is_null()) {
     on_start_dispatch_event_callback_.Run(event);
   }
 }
 
-void Window::OnStopDispatchEvent(const scoped_refptr<dom::Event>& event) {
+void Window::OnStopDispatchEvent(const scoped_refptr<web::Event>& event) {
   if (!on_stop_dispatch_event_callback_.is_null()) {
     on_stop_dispatch_event_callback_.Run(event);
   }
@@ -693,7 +662,7 @@
 }
 
 void Window::TraceMembers(script::Tracer* tracer) {
-  EventTarget::TraceMembers(tracer);
+  web::EventTarget::TraceMembers(tracer);
 
 #if defined(ENABLE_TEST_RUNNER)
   tracer->Trace(test_runner_);
diff --git a/cobalt/dom/window.h b/cobalt/dom/window.h
index 254d0ec..6671405 100644
--- a/cobalt/dom/window.h
+++ b/cobalt/dom/window.h
@@ -34,9 +34,7 @@
 #include "cobalt/dom/application_lifecycle_state.h"
 #include "cobalt/dom/captions/system_caption_settings.h"
 #include "cobalt/dom/crypto.h"
-#include "cobalt/dom/csp_delegate_type.h"
 #include "cobalt/dom/dom_stat_tracker.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/dom/html_element_context.h"
 #include "cobalt/dom/location.h"
 #include "cobalt/dom/media_query_list.h"
@@ -47,8 +45,6 @@
 #if defined(ENABLE_TEST_RUNNER)
 #include "cobalt/dom/test_runner.h"
 #endif  // ENABLE_TEST_RUNNER
-#include "cobalt/dom/url_registry.h"
-#include "cobalt/dom/user_agent_platform_info.h"
 #include "cobalt/dom/window_timers.h"
 #include "cobalt/input/camera_3d.h"
 #include "cobalt/loader/cors_preflight_cache.h"
@@ -72,9 +68,15 @@
 #include "cobalt/script/script_runner.h"
 #include "cobalt/script/script_value_factory.h"
 #include "cobalt/ui_navigation/nav_item.h"
+#include "cobalt/web/csp_delegate_type.h"
+#include "cobalt/web/event_target.h"
+#include "cobalt/web/url_registry.h"
+#include "cobalt/web/user_agent_platform_info.h"
+#include "cobalt/web/window_or_worker_global_scope.h"
 #include "starboard/window.h"
 #include "url/gurl.h"
 
+
 namespace cobalt {
 namespace media_session {
 class MediaSession;
@@ -82,15 +84,14 @@
 namespace speech {
 class SpeechSynthesis;
 }  // namespace speech
-}  // namespace cobalt
-
-namespace cobalt {
+namespace web {
+class Event;
+}  // namespace web
 namespace dom {
 
 class Camera3D;
 class Document;
 class Element;
-class Event;
 class History;
 class LocalStorageDatabase;
 class Location;
@@ -106,21 +107,22 @@
 //   https://www.w3.org/TR/html50/browsers.html#the-window-object
 //
 // TODO: Properly handle viewport resolution change event.
-class Window : public EventTarget, public ApplicationLifecycleState::Observer {
+class Window : public web::WindowOrWorkerGlobalScope,
+               public ApplicationLifecycleState::Observer {
  public:
   typedef AnimationFrameRequestCallbackList::FrameRequestCallback
       FrameRequestCallback;
   typedef WindowTimers::TimerCallback TimerCallback;
-  typedef base::Callback<void(const scoped_refptr<dom::Event>& event)>
+  typedef base::Callback<void(const scoped_refptr<web::Event>& event)>
       OnStartDispatchEventCallback;
-  typedef base::Callback<void(const scoped_refptr<dom::Event>& event)>
+  typedef base::Callback<void(const scoped_refptr<web::Event>& event)>
       OnStopDispatchEventCallback;
 
   // Callback that will be called when window.close() is called.  The
   // base::TimeDelta parameter will contain the document's timeline time when
   // close() was called.
   typedef base::Callback<void(base::TimeDelta)> CloseCallback;
-  typedef UrlRegistry<MediaSource> MediaSourceRegistry;
+  typedef web::UrlRegistry<MediaSource> MediaSourceRegistry;
   typedef base::Callback<void(const std::string&,
                               const base::Optional<std::string>&)>
       CacheCallback;
@@ -153,14 +155,14 @@
       script::ScriptValueFactory* script_value_factory,
       MediaSourceRegistry* media_source_registry,
       DomStatTracker* dom_stat_tracker, const GURL& url,
-      const std::string& user_agent, UserAgentPlatformInfo* platform_info,
+      const std::string& user_agent, web::UserAgentPlatformInfo* platform_info,
       const std::string& language, const std::string& font_language_script,
       const base::Callback<void(const GURL&)> navigation_callback,
       const loader::Decoder::OnCompleteFunction& load_complete_callback,
       network_bridge::CookieJar* cookie_jar,
       const network_bridge::PostSender& post_sender,
       csp::CSPHeaderPolicy require_csp,
-      dom::CspEnforcementType csp_enforcement_mode,
+      web::CspEnforcementType csp_enforcement_mode,
       const base::Closure& csp_policy_changed_callback,
       const base::Closure& ran_animation_frame_callbacks_callback,
       CloseCallback window_close_callback,
@@ -334,7 +336,7 @@
 
   // Call this to inject an event into the window which will ultimately make
   // its way to the appropriate object in DOM.
-  void InjectEvent(const scoped_refptr<Event>& event);
+  void InjectEvent(const scoped_refptr<web::Event>& event);
 
   scoped_refptr<Window> opener() const { return NULL; }
 
@@ -392,8 +394,8 @@
   const scoped_refptr<OnScreenKeyboard>& on_screen_keyboard() const;
   void ReleaseOnScreenKeyboard();
 
-  void OnStartDispatchEvent(const scoped_refptr<dom::Event>& event);
-  void OnStopDispatchEvent(const scoped_refptr<dom::Event>& event);
+  void OnStartDispatchEvent(const scoped_refptr<web::Event>& event);
+  void OnStopDispatchEvent(const scoped_refptr<web::Event>& event);
 
   // |PointerState| will in general create reference cycles back to us, which is
   // ok, as they are cleared over time.  During shutdown however, since no
@@ -456,7 +458,7 @@
   scoped_refptr<Navigator> navigator_;
   std::unique_ptr<RelayLoadEvent> relay_on_load_event_;
   scoped_refptr<Camera3D> camera_3d_;
-  std::unique_ptr<WindowTimers> window_timers_;
+  WindowTimers window_timers_;
   std::unique_ptr<AnimationFrameRequestCallbackList>
       animation_frame_request_callback_list_;
 
diff --git a/cobalt/dom/window_test.cc b/cobalt/dom/window_test.cc
index b7df5f7..ead13db 100644
--- a/cobalt/dom/window_test.cc
+++ b/cobalt/dom/window_test.cc
@@ -26,7 +26,6 @@
 #include "cobalt/cssom/viewport_size.h"
 #include "cobalt/dom/local_storage_database.h"
 #include "cobalt/dom/screen.h"
-#include "cobalt/dom/testing/mock_event_listener.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
@@ -34,6 +33,7 @@
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/script/testing/fake_script_value.h"
+#include "cobalt/web/testing/mock_event_listener.h"
 #include "starboard/window.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -45,7 +45,7 @@
 namespace dom {
 
 using ::cobalt::script::testing::FakeScriptValue;
-using testing::MockEventListener;
+using web::testing::MockEventListener;
 
 class MockErrorCallback
     : public base::Callback<void(const base::Optional<std::string> &)> {
@@ -77,7 +77,7 @@
         base::Bind(&MockErrorCallback::Run,
                    base::Unretained(&mock_error_callback_)),
         NULL, network_bridge::PostSender(), csp::kCSPRequired,
-        kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
+        web::kCspEnforcementEnable, base::Closure() /* csp_policy_changed */,
         base::Closure() /* ran_animation_frame_callbacks */,
         dom::Window::CloseCallback() /* window_close */,
         base::Closure() /* window_minimize */, NULL, NULL,
@@ -132,7 +132,7 @@
 // corresponding online and offline events are fired to listeners.
 TEST_F(WindowTest, OnlineEvent) {
   window_->AddEventListener(
-      "online", FakeScriptValue<EventListener>(fake_event_listener_.get()),
+      "online", FakeScriptValue<web::EventListener>(fake_event_listener_.get()),
       true);
   fake_event_listener_->ExpectHandleEventCall("online", window_);
   window_->OnWindowOnOnlineEvent();
@@ -140,8 +140,8 @@
 
 TEST_F(WindowTest, OfflineEvent) {
   window_->AddEventListener(
-      "offline", FakeScriptValue<EventListener>(fake_event_listener_.get()),
-      true);
+      "offline",
+      FakeScriptValue<web::EventListener>(fake_event_listener_.get()), true);
   fake_event_listener_->ExpectHandleEventCall("offline", window_);
   window_->OnWindowOnOfflineEvent();
 }
diff --git a/cobalt/dom/window_timers.cc b/cobalt/dom/window_timers.cc
index 951f7ee..d78cd15 100644
--- a/cobalt/dom/window_timers.cc
+++ b/cobalt/dom/window_timers.cc
@@ -14,6 +14,7 @@
 
 #include "cobalt/dom/window_timers.h"
 
+#include <algorithm>
 #include <limits>
 #include <memory>
 
@@ -23,7 +24,7 @@
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/application_state.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/global_stats.h"
+#include "cobalt/web/global_stats.h"
 #include "nb/memory_scope.h"
 
 namespace cobalt {
@@ -41,7 +42,7 @@
 
   if (callbacks_active_) {
     scoped_refptr<Timer> timer =
-        new Timer(type, owner_, dom_stat_tracker_, debugger_hooks_, handler,
+        new Timer(type, owner_, stat_tracker_, debugger_hooks_, handler,
                   timeout, handle, this);
     if (application_state_ != base::kApplicationStateFrozen) {
       timer->StartOrResume();
@@ -55,6 +56,10 @@
 }
 
 int WindowTimers::SetTimeout(const TimerCallbackArg& handler, int timeout) {
+  LOG_IF(WARNING, timeout < 0)
+      << "WindowTimers::SetTimeout received negative timeout: " << timeout;
+  timeout = std::max(timeout, 0);
+
   TRACK_MEMORY_SCOPE("DOM");
   return TryAddNewTimer(Timer::kOneShot, handler, timeout);
 }
@@ -71,6 +76,10 @@
 }
 
 int WindowTimers::SetInterval(const TimerCallbackArg& handler, int timeout) {
+  LOG_IF(WARNING, timeout < 0)
+      << "WindowTimers::SetInterval received negative interval: " << timeout;
+  timeout = std::max(timeout, 0);
+
   TRACK_MEMORY_SCOPE("DOM");
   return TryAddNewTimer(Timer::kRepeating, handler, timeout);
 }
@@ -132,13 +141,13 @@
 }
 
 WindowTimers::Timer::Timer(TimerType type, script::Wrappable* const owner,
-                           DomStatTracker* dom_stat_tracker,
+                           web::StatTracker* stat_tracker,
                            const base::DebuggerHooks& debugger_hooks,
                            const TimerCallbackArg& callback, int timeout,
                            int handle, WindowTimers* window_timers)
     : type_(type),
       callback_(owner, callback),
-      dom_stat_tracker_(dom_stat_tracker),
+      stat_tracker_(stat_tracker),
       debugger_hooks_(debugger_hooks),
       timeout_(timeout),
       handle_(handle),
@@ -149,24 +158,28 @@
       type == Timer::kOneShot
           ? base::DebuggerHooks::AsyncTaskFrequency::kOneshot
           : base::DebuggerHooks::AsyncTaskFrequency::kRecurring);
-  switch (type) {
-    case Timer::kOneShot:
-      dom_stat_tracker_->OnWindowTimersTimeoutCreated();
-      break;
-    case Timer::kRepeating:
-      dom_stat_tracker_->OnWindowTimersIntervalCreated();
-      break;
+  if (stat_tracker_) {
+    switch (type) {
+      case Timer::kOneShot:
+        stat_tracker_->OnWindowTimersTimeoutCreated();
+        break;
+      case Timer::kRepeating:
+        stat_tracker_->OnWindowTimersIntervalCreated();
+        break;
+    }
   }
 }
 
 WindowTimers::Timer::~Timer() {
-  switch (type_) {
-    case Timer::kOneShot:
-      dom_stat_tracker_->OnWindowTimersTimeoutDestroyed();
-      break;
-    case Timer::kRepeating:
-      dom_stat_tracker_->OnWindowTimersIntervalDestroyed();
-      break;
+  if (stat_tracker_) {
+    switch (type_) {
+      case Timer::kOneShot:
+        stat_tracker_->OnWindowTimersTimeoutDestroyed();
+        break;
+      case Timer::kRepeating:
+        stat_tracker_->OnWindowTimersIntervalDestroyed();
+        break;
+    }
   }
   debugger_hooks_.AsyncTaskCanceled(this);
 }
@@ -177,7 +190,7 @@
   }
 
   // The callback is now being run. Track it in the global stats.
-  GlobalStats::GetInstance()->StartJavaScriptEvent();
+  web::GlobalStats::GetInstance()->StartJavaScriptEvent();
 
   {
     base::ScopedAsyncTask async_task(debugger_hooks_, this);
@@ -190,7 +203,7 @@
   }
 
   // The callback has finished running. Stop tracking it in the global stats.
-  GlobalStats::GetInstance()->StopJavaScriptEvent();
+  web::GlobalStats::GetInstance()->StopJavaScriptEvent();
 }
 
 void WindowTimers::Timer::Pause() {
diff --git a/cobalt/dom/window_timers.h b/cobalt/dom/window_timers.h
index fc81784..3b62eb6 100644
--- a/cobalt/dom/window_timers.h
+++ b/cobalt/dom/window_timers.h
@@ -26,10 +26,10 @@
 #include "base/timer/timer.h"
 #include "cobalt/base/application_state.h"
 #include "cobalt/base/debugger_hooks.h"
-#include "cobalt/dom/dom_stat_tracker.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/stat_tracker.h"
 
 namespace cobalt {
 namespace dom {
@@ -39,11 +39,11 @@
   typedef script::CallbackFunction<void()> TimerCallback;
   typedef script::ScriptValue<TimerCallback> TimerCallbackArg;
   explicit WindowTimers(script::Wrappable* const owner,
-                        DomStatTracker* dom_stat_tracker,
+                        web::StatTracker* stat_tracker,
                         const base::DebuggerHooks& debugger_hooks,
                         base::ApplicationState application_state)
       : owner_(owner),
-        dom_stat_tracker_(dom_stat_tracker),
+        stat_tracker_(stat_tracker),
         debugger_hooks_(debugger_hooks),
         application_state_(application_state) {}
   ~WindowTimers() { DisableCallbacks(); }
@@ -70,7 +70,7 @@
     enum TimerType { kOneShot, kRepeating };
 
     Timer(TimerType type, script::Wrappable* const owner,
-          DomStatTracker* dom_stat_tracker,
+          web::StatTracker* stat_tracker,
           const base::DebuggerHooks& debugger_hooks,
           const TimerCallbackArg& callback, int timeout, int handle,
           WindowTimers* window_timers);
@@ -96,7 +96,7 @@
     TimerType type_;
     std::unique_ptr<base::internal::TimerBase> timer_;
     TimerCallbackArg::Reference callback_;
-    DomStatTracker* const dom_stat_tracker_;
+    web::StatTracker* const stat_tracker_;
     const base::DebuggerHooks& debugger_hooks_;
     int timeout_;
     int handle_;
@@ -122,7 +122,7 @@
   Timers timers_;
   int current_timer_index_ = 0;
   script::Wrappable* const owner_;
-  DomStatTracker* const dom_stat_tracker_;
+  web::StatTracker* const stat_tracker_;
   const base::DebuggerHooks& debugger_hooks_;
 
   // Set to false when we're about to shutdown, to ensure that no new JavaScript
diff --git a/cobalt/dom/window_timers.idl b/cobalt/dom/window_timers.idl
index 9e7b435..562e5cb 100644
--- a/cobalt/dom/window_timers.idl
+++ b/cobalt/dom/window_timers.idl
@@ -29,5 +29,6 @@
   // spec regarding "unsafe-eval".
 };
 Window implements WindowTimers;
+WorkerGlobalScope implements WindowTimers;
 
 callback TimerCallback = void();
diff --git a/cobalt/dom/window_timers_test.cc b/cobalt/dom/window_timers_test.cc
index 57b233e..37bb80c 100644
--- a/cobalt/dom/window_timers_test.cc
+++ b/cobalt/dom/window_timers_test.cc
@@ -21,11 +21,11 @@
 #include "base/test/scoped_task_environment.h"
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
-#include "cobalt/dom/dom_stat_tracker.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/script/testing/fake_script_value.h"
+#include "cobalt/web/stat_tracker.h"
 #include "net/test/test_with_scoped_task_environment.h"
 
 #include "testing/gmock/include/gmock/gmock.h"
@@ -86,18 +86,18 @@
   WindowTimersTest()
       : WithScopedTaskEnvironment(
             base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME),
-        dom_stat_tracker_("WindowTimersTest"),
+        stat_tracker_("WindowTimersTest"),
         callback_(&mock_timer_callback_) {
     script::Wrappable* foo = nullptr;
     timers_.reset(
-        new WindowTimers(foo, &dom_stat_tracker_, hooks_,
+        new WindowTimers(foo, &stat_tracker_, hooks_,
                          base::ApplicationState::kApplicationStateStarted));
   }
 
   ~WindowTimersTest() override {}
 
   testing::MockDebuggerHooks hooks_;
-  DomStatTracker dom_stat_tracker_;
+  web::StatTracker stat_tracker_;
   std::unique_ptr<WindowTimers> timers_;
   testing::MockTimerCallback mock_timer_callback_;
   FakeScriptValue<WindowTimers::TimerCallback> callback_;
@@ -237,7 +237,7 @@
   timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
   timers_->SetTimeout(callback_, kTimerDelayInMilliseconds * 3);
 
-  dom_stat_tracker_.FlushPeriodicTracking();
+  stat_tracker_.FlushPeriodicTracking();
   EXPECT_EQ("0", base::CValManager::GetInstance()
                      ->GetValueAsString(
                          "Count.WindowTimersTest.DOM.WindowTimers.Interval")
@@ -255,7 +255,7 @@
       base::TimeDelta::FromMilliseconds(3 * kTimerDelayInMilliseconds));
   EXPECT_EQ(GetPendingMainThreadTaskCount(), 0);
 
-  dom_stat_tracker_.FlushPeriodicTracking();
+  stat_tracker_.FlushPeriodicTracking();
   EXPECT_EQ("0", base::CValManager::GetInstance()
                      ->GetValueAsString(
                          "Count.WindowTimersTest.DOM.WindowTimers.Interval")
@@ -391,7 +391,7 @@
   timers_->SetInterval(callback_, kTimerDelayInMilliseconds);
   timers_->SetInterval(callback_, kTimerDelayInMilliseconds * 3);
 
-  dom_stat_tracker_.FlushPeriodicTracking();
+  stat_tracker_.FlushPeriodicTracking();
   EXPECT_EQ("2", base::CValManager::GetInstance()
                      ->GetValueAsString(
                          "Count.WindowTimersTest.DOM.WindowTimers.Interval")
@@ -410,7 +410,7 @@
   RunUntilIdle();
   EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
 
-  dom_stat_tracker_.FlushPeriodicTracking();
+  stat_tracker_.FlushPeriodicTracking();
   EXPECT_EQ("2", base::CValManager::GetInstance()
                      ->GetValueAsString(
                          "Count.WindowTimersTest.DOM.WindowTimers.Interval")
@@ -433,7 +433,7 @@
   timers_->SetTimeout(callback_, kTimerDelayInMilliseconds);
   timers_->SetTimeout(callback_, kTimerDelayInMilliseconds * 3);
 
-  dom_stat_tracker_.FlushPeriodicTracking();
+  stat_tracker_.FlushPeriodicTracking();
   EXPECT_EQ("2", base::CValManager::GetInstance()
                      ->GetValueAsString(
                          "Count.WindowTimersTest.DOM.WindowTimers.Interval")
@@ -452,7 +452,7 @@
   RunUntilIdle();
   EXPECT_EQ(GetPendingMainThreadTaskCount(), 2);
 
-  dom_stat_tracker_.FlushPeriodicTracking();
+  stat_tracker_.FlushPeriodicTracking();
   EXPECT_EQ("2", base::CValManager::GetInstance()
                      ->GetValueAsString(
                          "Count.WindowTimersTest.DOM.WindowTimers.Interval")
diff --git a/cobalt/dom_parser/html_decoder.cc b/cobalt/dom_parser/html_decoder.cc
index b662528..742d4db 100644
--- a/cobalt/dom_parser/html_decoder.cc
+++ b/cobalt/dom_parser/html_decoder.cc
@@ -15,9 +15,9 @@
 #include "cobalt/dom_parser/html_decoder.h"
 
 #include "cobalt/csp/content_security_policy.h"
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom_parser/libxml_html_parser_wrapper.h"
 #include "cobalt/loader/net_fetcher.h"
+#include "cobalt/web/csp_delegate.h"
 
 namespace cobalt {
 namespace dom_parser {
@@ -65,7 +65,8 @@
     return loader::kLoadResponseContinue;
   } else {
     LOG(ERROR) << "Failure receiving Content Security Policy headers "
-                  "for URL: " << url_fetcher->GetURL() << ".";
+                  "for URL: "
+               << url_fetcher->GetURL() << ".";
     LOG(ERROR) << "The server *must* send CSP headers or Cobalt will not "
                   "load the page.";
     return loader::kLoadResponseAbort;
diff --git a/cobalt/dom_parser/html_decoder_test.cc b/cobalt/dom_parser/html_decoder_test.cc
index 94992ac..78dd0da 100644
--- a/cobalt/dom_parser/html_decoder_test.cc
+++ b/cobalt/dom_parser/html_decoder_test.cc
@@ -29,11 +29,11 @@
 #include "cobalt/dom/named_node_map.h"
 #include "cobalt/dom/testing/stub_css_parser.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
-#include "cobalt/dom/testing/stub_script_runner.h"
 #include "cobalt/dom/text.h"
 #include "cobalt/dom_parser/parser.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/loader_factory.h"
+#include "cobalt/script/testing/stub_script_runner.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -59,7 +59,7 @@
   loader::LoaderFactory loader_factory_;
   std::unique_ptr<Parser> dom_parser_;
   dom::testing::StubCSSParser stub_css_parser_;
-  dom::testing::StubScriptRunner stub_script_runner_;
+  script::testing::StubScriptRunner stub_script_runner_;
   std::unique_ptr<dom::DomStatTracker> dom_stat_tracker_;
   dom::HTMLElementContext html_element_context_;
   scoped_refptr<dom::Document> document_;
diff --git a/cobalt/encoding/BUILD.gn b/cobalt/encoding/BUILD.gn
index 96ce2b0..e1f0665 100644
--- a/cobalt/encoding/BUILD.gn
+++ b/cobalt/encoding/BUILD.gn
@@ -43,7 +43,8 @@
   deps = [
     ":text_encoding",
     "//cobalt/base",
-    "//cobalt/dom",
+    "//cobalt/browser:generated_bindings",
+    "//cobalt/debug",
     "//cobalt/dom/testing:dom_testing",
     "//cobalt/script",
     "//cobalt/test:run_all_unittests",
diff --git a/cobalt/encoding/text_decoder.cc b/cobalt/encoding/text_decoder.cc
index 1118c0f..8f1a48c 100644
--- a/cobalt/encoding/text_decoder.cc
+++ b/cobalt/encoding/text_decoder.cc
@@ -107,7 +107,7 @@
   return Decode(default_options, exception_state);
 }
 
-std::string TextDecoder::Decode(const dom::BufferSource& input,
+std::string TextDecoder::Decode(const web::BufferSource& input,
                                 script::ExceptionState* exception_state) {
   const TextDecodeOptions default_options;
   return Decode(input, default_options, exception_state);
@@ -120,13 +120,13 @@
   return result;
 }
 
-std::string TextDecoder::Decode(const dom::BufferSource& input,
+std::string TextDecoder::Decode(const web::BufferSource& input,
                                 const TextDecodeOptions& options,
                                 script::ExceptionState* exception_state) {
   int32_t size;
   const uint8* buffer;
   std::string result;
-  dom::GetBufferAndSize(input, &buffer, &size);
+  web::GetBufferAndSize(input, &buffer, &size);
   Decode(reinterpret_cast<const char*>(buffer), size, options, exception_state,
          &result);
   return result;
diff --git a/cobalt/encoding/text_decoder.h b/cobalt/encoding/text_decoder.h
index 7412de6..5edbd46 100644
--- a/cobalt/encoding/text_decoder.h
+++ b/cobalt/encoding/text_decoder.h
@@ -17,10 +17,10 @@
 
 #include <string>
 
-#include "cobalt/dom/buffer_source.h"
 #include "cobalt/encoding/text_decode_options.h"
 #include "cobalt/encoding/text_decoder_options.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/buffer_source.h"
 
 typedef struct UConverter UConverter;
 
@@ -42,9 +42,9 @@
   bool ignore_bom() const { return ignore_bom_; }
 
   std::string Decode(script::ExceptionState*);
-  std::string Decode(const dom::BufferSource&, script::ExceptionState*);
+  std::string Decode(const web::BufferSource&, script::ExceptionState*);
   std::string Decode(const TextDecodeOptions&, script::ExceptionState*);
-  std::string Decode(const dom::BufferSource&, const TextDecodeOptions&,
+  std::string Decode(const web::BufferSource&, const TextDecodeOptions&,
                      script::ExceptionState*);
 
   DEFINE_WRAPPABLE_TYPE(TextDecoder);
diff --git a/cobalt/encoding/text_decoder_test.cc b/cobalt/encoding/text_decoder_test.cc
index 956a277..ca1c3b0 100644
--- a/cobalt/encoding/text_decoder_test.cc
+++ b/cobalt/encoding/text_decoder_test.cc
@@ -26,6 +26,7 @@
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/testing/mock_exception_state.h"
 #include "cobalt/script/typed_arrays.h"
+#include "cobalt/web/testing/stub_web_context.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -107,7 +108,7 @@
     EXPECT_CALL(exception_state_,
                 SetSimpleExceptionVA(script::kRangeError, _, _))
         .Times(0);
-    std::string got = text_decoder_->Decode(dom::BufferSource(array_buffer),
+    std::string got = text_decoder_->Decode(web::BufferSource(array_buffer),
                                             &exception_state_);
     EXPECT_EQ(got, want);
   }
@@ -137,7 +138,7 @@
     EXPECT_CALL(exception_state_,
                 SetSimpleExceptionVA(script::kRangeError, _, _))
         .Times(0);
-    std::string got = text_decoder_->Decode(dom::BufferSource(array_buffer),
+    std::string got = text_decoder_->Decode(web::BufferSource(array_buffer),
                                             &exception_state_);
     EXPECT_EQ(got, want);
   }
@@ -204,7 +205,7 @@
     EXPECT_CALL(exception_state_,
                 SetSimpleExceptionVA(script::kTypeError, _, _))
         .Times(1);
-    std::string got = text_decoder_->Decode(dom::BufferSource(array_buffer),
+    std::string got = text_decoder_->Decode(web::BufferSource(array_buffer),
                                             &exception_state_);
     EXPECT_EQ(got, want);
   }
@@ -240,7 +241,7 @@
       EXPECT_CALL(exception_state_,
                   SetSimpleExceptionVA(script::kRangeError, _, _))
           .Times(0);
-      std::string got = text_decoder_->Decode(dom::BufferSource(array_buffer),
+      std::string got = text_decoder_->Decode(web::BufferSource(array_buffer),
                                               &exception_state_);
       std::string want = kBOM + kABC;
       EXPECT_EQ(got, want);
@@ -259,7 +260,7 @@
       EXPECT_CALL(exception_state_,
                   SetSimpleExceptionVA(script::kRangeError, _, _))
           .Times(0);
-      std::string got = text_decoder_->Decode(dom::BufferSource(array_buffer),
+      std::string got = text_decoder_->Decode(web::BufferSource(array_buffer),
                                               &exception_state_);
       std::string want = kABC;
       EXPECT_EQ(got, want);
@@ -277,7 +278,7 @@
       EXPECT_CALL(exception_state_,
                   SetSimpleExceptionVA(script::kRangeError, _, _))
           .Times(0);
-      std::string got = text_decoder_->Decode(dom::BufferSource(array_buffer),
+      std::string got = text_decoder_->Decode(web::BufferSource(array_buffer),
                                               &exception_state_);
       std::string want = kABC;
       EXPECT_EQ(got, want);
@@ -330,7 +331,7 @@
                     SetSimpleExceptionVA(script::kRangeError, _, _))
             .Times(0);
         // decoding with {stream: true}
-        got += text_decoder_->Decode(dom::BufferSource(chunk), stream_option,
+        got += text_decoder_->Decode(web::BufferSource(chunk), stream_option,
                                      &exception_state_);
       }
       EXPECT_CALL(exception_state_,
@@ -360,7 +361,7 @@
     EXPECT_CALL(exception_state_,
                 SetSimpleExceptionVA(script::kRangeError, _, _))
         .Times(0);
-    std::string got = text_decoder_->Decode(dom::BufferSource(array_buffer),
+    std::string got = text_decoder_->Decode(web::BufferSource(array_buffer),
                                             &exception_state_);
     EXPECT_EQ(got, want);
   }
@@ -384,7 +385,7 @@
     EXPECT_CALL(exception_state_,
                 SetSimpleExceptionVA(script::kRangeError, _, _))
         .Times(0);
-    std::string got = text_decoder_->Decode(dom::BufferSource(array_buffer),
+    std::string got = text_decoder_->Decode(web::BufferSource(array_buffer),
                                             &exception_state_);
     EXPECT_EQ(got, want);
   }
diff --git a/cobalt/evergreen_tests/evergreen_tests.py b/cobalt/evergreen_tests/evergreen_tests.py
index 74eafb0..b0e2385 100644
--- a/cobalt/evergreen_tests/evergreen_tests.py
+++ b/cobalt/evergreen_tests/evergreen_tests.py
@@ -28,6 +28,7 @@
 from starboard.tools.paths import REPOSITORY_ROOT
 
 _DEFAULT_PLATFORM_UNDER_TEST = 'linux'
+_DEFAULT_TEST_TYPE = 'functional'
 
 
 def _Exec(cmd, env=None):
@@ -57,6 +58,10 @@
       '--platform_under_test',
       default=_DEFAULT_PLATFORM_UNDER_TEST,
       help='The platform to run the tests on (e.g., linux or raspi).')
+  arg_parser.add_argument(
+      '--test_type',
+      default=_DEFAULT_TEST_TYPE,
+      help='The type of tests to run: functional or performance.')
   authentication_method = arg_parser.add_mutually_exclusive_group()
   authentication_method.add_argument(
       '--public-key-auth',
@@ -118,6 +123,9 @@
     command.append('-a')
     command.append('password')
 
+  command.append('-t')
+  command.append(args.test_type)
+
   command.append(args.platform_under_test)
 
   return _Exec(command, env)
diff --git a/cobalt/fetch/fetch_internal.cc b/cobalt/fetch/fetch_internal.cc
index c098897..bc9f0bb 100644
--- a/cobalt/fetch/fetch_internal.cc
+++ b/cobalt/fetch/fetch_internal.cc
@@ -16,8 +16,8 @@
 
 #include "base/strings/string_util.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/blob.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/web/blob.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/environment_settings.h"
 #include "url/gurl.h"
@@ -74,7 +74,7 @@
 // static
 script::Handle<script::ArrayBuffer> FetchInternal::BlobToArrayBuffer(
     script::EnvironmentSettings* settings,
-    const scoped_refptr<dom::Blob>& blob) {
+    const scoped_refptr<web::Blob>& blob) {
   // Create a copy of the data so that the caller cannot modify the Blob.
   return script::ArrayBuffer::New(
       base::polymorphic_downcast<web::EnvironmentSettings*>(settings)
diff --git a/cobalt/fetch/fetch_internal.h b/cobalt/fetch/fetch_internal.h
index b4ff63a..86b4258 100644
--- a/cobalt/fetch/fetch_internal.h
+++ b/cobalt/fetch/fetch_internal.h
@@ -18,11 +18,11 @@
 #include <string>
 
 #include "base/memory/ref_counted.h"
-#include "cobalt/dom/blob.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/exception_state.h"
 #include "cobalt/script/typed_arrays.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/blob.h"
 
 namespace cobalt {
 namespace fetch {
@@ -50,7 +50,7 @@
   // Translate a dom Blob to ArrayBuffer.
   static script::Handle<script::ArrayBuffer> BlobToArrayBuffer(
       script::EnvironmentSettings* settings,
-      const scoped_refptr<dom::Blob>& blob);
+      const scoped_refptr<web::Blob>& blob);
 
   DEFINE_WRAPPABLE_TYPE(FetchInternal);
 };
diff --git a/cobalt/h5vcc/BUILD.gn b/cobalt/h5vcc/BUILD.gn
index 0469041..b5b0a34 100644
--- a/cobalt/h5vcc/BUILD.gn
+++ b/cobalt/h5vcc/BUILD.gn
@@ -18,7 +18,7 @@
 }
 
 config("h5vcc_external_config") {
-  defines = [ "COBALT_ENABLE_CRASH_LOG" ]
+  defines = []
   if (enable_account_manager) {
     defines += [ "COBALT_ENABLE_ACCOUNT_MANAGER" ]
   }
@@ -78,7 +78,6 @@
     "//cobalt/build:cobalt_build_id",
     "//cobalt/configuration",
     "//cobalt/dom",
-    "//cobalt/dom:dom_exception",
     "//cobalt/media",
     "//cobalt/network",
     "//cobalt/script",
@@ -87,6 +86,7 @@
     "//cobalt/storage",
     "//cobalt/trace_event",
     "//cobalt/watchdog",
+    "//cobalt/web:dom_exception",
     "//net",
     "//net:http_server",
     "//starboard",
diff --git a/cobalt/h5vcc/h5vcc.h b/cobalt/h5vcc/h5vcc.h
index ea37396..601ba48 100644
--- a/cobalt/h5vcc/h5vcc.h
+++ b/cobalt/h5vcc/h5vcc.h
@@ -61,7 +61,7 @@
 #endif
     account::AccountManager* account_manager;
     base::EventDispatcher* event_dispatcher;
-    dom::NavigatorUAData* user_agent_data;
+    web::NavigatorUAData* user_agent_data;
     script::GlobalEnvironment* global_environment;
   };
 
diff --git a/cobalt/h5vcc/h5vcc.idl b/cobalt/h5vcc/h5vcc.idl
index eb10236..4428dda 100644
--- a/cobalt/h5vcc/h5vcc.idl
+++ b/cobalt/h5vcc/h5vcc.idl
@@ -34,8 +34,7 @@
   readonly attribute H5vccAccessibility accessibility;
   readonly attribute H5vccAccountInfo accountInfo;
   readonly attribute H5vccAudioConfigArray audioConfig;
-  [Conditional=COBALT_ENABLE_CRASH_LOG]
-      readonly attribute H5vccCrashLog crashLog;
+  readonly attribute H5vccCrashLog crashLog;
   readonly attribute CValView cVal;
   readonly attribute H5vccRuntime runtime;
   readonly attribute H5vccSettings settings;
diff --git a/cobalt/h5vcc/h5vcc_crash_log.cc b/cobalt/h5vcc/h5vcc_crash_log.cc
index 41dad67..208297d 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.cc
+++ b/cobalt/h5vcc/h5vcc_crash_log.cc
@@ -186,5 +186,16 @@
   return "";
 }
 
+bool H5vccCrashLog::GetCanTriggerCrash() {
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+  if (watchdog) return watchdog->GetCanTriggerCrash();
+  return false;
+}
+
+void H5vccCrashLog::SetCanTriggerCrash(bool can_trigger_crash) {
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+  if (watchdog) watchdog->SetCanTriggerCrash(can_trigger_crash);
+}
+
 }  // namespace h5vcc
 }  // namespace cobalt
diff --git a/cobalt/h5vcc/h5vcc_crash_log.h b/cobalt/h5vcc/h5vcc_crash_log.h
index 3e44f11..5bb0526 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.h
+++ b/cobalt/h5vcc/h5vcc_crash_log.h
@@ -44,6 +44,10 @@
 
   std::string GetWatchdogViolations(bool current);
 
+  bool GetCanTriggerCrash();
+
+  void SetCanTriggerCrash(bool can_trigger_crash);
+
   DEFINE_WRAPPABLE_TYPE(H5vccCrashLog);
 
  private:
diff --git a/cobalt/h5vcc/h5vcc_crash_log.idl b/cobalt/h5vcc/h5vcc_crash_log.idl
index 45b1353..626934e 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.idl
+++ b/cobalt/h5vcc/h5vcc_crash_log.idl
@@ -18,9 +18,6 @@
 // repository that can be examined later.
 // Platforms typically have maximum crash log data sizes on the order of 16KB,
 // but it varies per platform.
-[
-  Conditional=COBALT_ENABLE_CRASH_LOG
-]
 interface H5vccCrashLog {
   // Returns true if the string was successfully set in the crash log, and false
   // if for some reason the string could not be set in the crash log.
@@ -60,4 +57,12 @@
   // previous file containing violations from previous app starts and since the
   // last call (up to a limit) is returned.
   DOMString getWatchdogViolations(boolean current);
+
+  // Gets a persistent Watchdog setting that determines whether or not a
+  // Watchdog violation will trigger a crash.
+  boolean getCanTriggerCrash();
+
+  // Sets a persistent Watchdog setting that determines whether or not a
+  // Watchdog violation will trigger a crash.
+  void setCanTriggerCrash(boolean can_trigger_crash);
 };
diff --git a/cobalt/h5vcc/h5vcc_platform_service.cc b/cobalt/h5vcc/h5vcc_platform_service.cc
index 4cbf743..27f7510 100644
--- a/cobalt/h5vcc/h5vcc_platform_service.cc
+++ b/cobalt/h5vcc/h5vcc_platform_service.cc
@@ -97,7 +97,7 @@
     const script::Handle<script::ArrayBuffer>& data,
     script::ExceptionState* exception_state) {
   if (!IsOpen()) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              "Closed service should not Send.",
                              exception_state);
     return script::ArrayBuffer::New(environment_, 0);
@@ -120,7 +120,7 @@
   void* output_data = platform_service_api_->Send(
       ext_service_, data_ptr, data_length, &output_length, &invalid_state);
   if (invalid_state) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              "Service unable to accept data currently.",
                              exception_state);
     SbMemoryDeallocate(output_data);
diff --git a/cobalt/h5vcc/h5vcc_platform_service.h b/cobalt/h5vcc/h5vcc_platform_service.h
index 62ac3e1..6fd41ef 100644
--- a/cobalt/h5vcc/h5vcc_platform_service.h
+++ b/cobalt/h5vcc/h5vcc_platform_service.h
@@ -22,12 +22,12 @@
 #include "base/message_loop/message_loop.h"
 #include "base/optional.h"
 #include "base/single_thread_task_runner.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/extension/platform_service.h"
 #include "cobalt/script/array_buffer.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/dom_exception.h"
 
 namespace cobalt {
 namespace h5vcc {
diff --git a/cobalt/h5vcc/h5vcc_settings.cc b/cobalt/h5vcc/h5vcc_settings.cc
index 83bf08b..3417192 100644
--- a/cobalt/h5vcc/h5vcc_settings.cc
+++ b/cobalt/h5vcc/h5vcc_settings.cc
@@ -24,7 +24,7 @@
 #if SB_IS(EVERGREEN)
                              cobalt::updater::UpdaterModule* updater_module,
 #endif
-                             dom::NavigatorUAData* user_agent_data,
+                             web::NavigatorUAData* user_agent_data,
                              script::GlobalEnvironment* global_environment)
     : media_module_(media_module),
       network_module_(network_module),
diff --git a/cobalt/h5vcc/h5vcc_settings.h b/cobalt/h5vcc/h5vcc_settings.h
index 086a524..2257d6b 100644
--- a/cobalt/h5vcc/h5vcc_settings.h
+++ b/cobalt/h5vcc/h5vcc_settings.h
@@ -17,11 +17,11 @@
 
 #include <string>
 
-#include "cobalt/dom/navigator_ua_data.h"
 #include "cobalt/media/media_module.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/navigator_ua_data.h"
 
 #if SB_IS(EVERGREEN)
 #include "cobalt/updater/updater_module.h"
@@ -40,7 +40,7 @@
 #if SB_IS(EVERGREEN)
                          cobalt::updater::UpdaterModule* updater_module,
 #endif
-                         dom::NavigatorUAData* user_agent_data,
+                         web::NavigatorUAData* user_agent_data,
                          script::GlobalEnvironment* global_environment);
 
   // Returns true when the setting is set successfully or if the setting has
@@ -56,7 +56,7 @@
 #if SB_IS(EVERGREEN)
   cobalt::updater::UpdaterModule* updater_module_ = nullptr;
 #endif
-  dom::NavigatorUAData* user_agent_data_;
+  web::NavigatorUAData* user_agent_data_;
   script::GlobalEnvironment* global_environment_;
 
   DISALLOW_COPY_AND_ASSIGN(H5vccSettings);
diff --git a/cobalt/h5vcc/h5vcc_storage.cc b/cobalt/h5vcc/h5vcc_storage.cc
index 8bc8c5d..952f925 100644
--- a/cobalt/h5vcc/h5vcc_storage.cc
+++ b/cobalt/h5vcc/h5vcc_storage.cc
@@ -12,14 +12,47 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/h5vcc/h5vcc_storage.h"
+#include <algorithm>
+#include <string>
+#include <vector>
 
+#include "base/files/file_util.h"
+#include "cobalt/h5vcc/h5vcc_storage.h"
 #include "cobalt/storage/storage_manager.h"
-#include "net/url_request/url_request_context.h"
+
+#include "starboard/common/file.h"
+#include "starboard/common/string.h"
 
 namespace cobalt {
 namespace h5vcc {
 
+namespace {
+const char kTestFileName[] = "cache_test_file.json";
+
+const uint32 kWriteBufferSize = 1024 * 1024;
+
+const uint32 kReadBufferSize = 1024 * 1024;
+
+H5vccStorageWriteTestDictionary WriteTestMessage(std::string error = "",
+                                                 uint32 bytes_written = 0) {
+  H5vccStorageWriteTestDictionary message;
+  message.set_error(error);
+  message.set_bytes_written(bytes_written);
+  return message;
+}
+
+H5vccStorageVerifyTestDictionary VerifyTestMessage(std::string error = "",
+                                                   bool verified = false,
+                                                   uint32 bytes_read = 0) {
+  H5vccStorageVerifyTestDictionary message;
+  message.set_error(error);
+  message.set_verified(verified);
+  message.set_bytes_read(bytes_read);
+  return message;
+}
+
+}  // namespace
+
 H5vccStorage::H5vccStorage(network::NetworkModule* network_module)
     : network_module_(network_module) {}
 
@@ -50,5 +83,111 @@
   network_module_->network_delegate()->set_cookies_enabled(enabled);
 }
 
+H5vccStorageWriteTestDictionary H5vccStorage::WriteTest(
+    uint32 test_size, std::string test_string) {
+  // Get cache_dir path.
+  std::vector<char> cache_dir(kSbFileMaxPath + 1, 0);
+  SbSystemGetPath(kSbSystemPathCacheDirectory, cache_dir.data(),
+                  kSbFileMaxPath);
+
+  // Delete the contents of cache_dir.
+  starboard::SbFileDeleteRecursive(cache_dir.data(), true);
+
+  // Try to Create the test_file.
+  std::string test_file_path =
+      std::string(cache_dir.data()) + kSbFileSepString + kTestFileName;
+  SbFileError test_file_error;
+  starboard::ScopedFile test_file(test_file_path.c_str(),
+                                  kSbFileOpenAlways | kSbFileWrite, NULL,
+                                  &test_file_error);
+
+  if (test_file_error != kSbFileOk) {
+    return WriteTestMessage(
+        starboard::FormatString("SbFileError: %d while opening ScopedFile: %s",
+                                test_file_error, test_file_path.c_str()));
+  }
+
+  // Repeatedly write test_string to test_size bytes of write_buffer.
+  std::string write_buf;
+  int iterations = test_size / test_string.length();
+  for (int i = 0; i < iterations; ++i) {
+    write_buf.append(test_string);
+  }
+  write_buf.append(test_string.substr(0, test_size % test_string.length()));
+
+  // Incremental Writes of test_data, copies SbWriteAll, using a maximum
+  // kWriteBufferSize per write.
+  uint32 total_bytes_written = 0;
+
+  do {
+    auto bytes_written = test_file.Write(
+        write_buf.data() + total_bytes_written,
+        std::min(kWriteBufferSize, test_size - total_bytes_written));
+    if (bytes_written <= 0) {
+      SbFileDelete(test_file_path.c_str());
+      return WriteTestMessage("SbWrite -1 return value error");
+    }
+    total_bytes_written += bytes_written;
+  } while (total_bytes_written < test_size);
+
+  test_file.Flush();
+
+  return WriteTestMessage("", total_bytes_written);
+}
+
+H5vccStorageVerifyTestDictionary H5vccStorage::VerifyTest(
+    uint32 test_size, std::string test_string) {
+  std::vector<char> cache_dir(kSbFileMaxPath + 1, 0);
+  SbSystemGetPath(kSbSystemPathCacheDirectory, cache_dir.data(),
+                  kSbFileMaxPath);
+
+  std::string test_file_path =
+      std::string(cache_dir.data()) + kSbFileSepString + kTestFileName;
+  SbFileError test_file_error;
+  starboard::ScopedFile test_file(test_file_path.c_str(),
+                                  kSbFileOpenOnly | kSbFileRead, NULL,
+                                  &test_file_error);
+
+  if (test_file_error != kSbFileOk) {
+    return VerifyTestMessage(
+        starboard::FormatString("SbFileError: %d while opening ScopedFile: %s",
+                                test_file_error, test_file_path.c_str()));
+  }
+
+  // Incremental Reads of test_data, copies SbReadAll, using a maximum
+  // kReadBufferSize per write.
+  uint32 total_bytes_read = 0;
+
+  do {
+    char read_buf[kReadBufferSize];
+    auto bytes_read = test_file.Read(
+        read_buf, std::min(kReadBufferSize, test_size - total_bytes_read));
+    if (bytes_read <= 0) {
+      SbFileDelete(test_file_path.c_str());
+      return VerifyTestMessage("SbRead -1 return value error");
+    }
+
+    // Verify read_buf equivalent to a repeated test_string.
+    for (auto i = 0; i < bytes_read; ++i) {
+      if (read_buf[i] !=
+          test_string[(total_bytes_read + i) % test_string.size()]) {
+        return VerifyTestMessage(
+            "File test data does not match with test data string");
+      }
+    }
+
+    total_bytes_read += bytes_read;
+  } while (total_bytes_read < test_size);
+
+  if (total_bytes_read != test_size) {
+    SbFileDelete(test_file_path.c_str());
+    return VerifyTestMessage(
+        "File test data size does not match kTestDataSize");
+  }
+
+  SbFileDelete(test_file_path.c_str());
+  return VerifyTestMessage("", true, total_bytes_read);
+}
+
 }  // namespace h5vcc
 }  // namespace cobalt
diff --git a/cobalt/h5vcc/h5vcc_storage.h b/cobalt/h5vcc/h5vcc_storage.h
index 613f024..beec434 100644
--- a/cobalt/h5vcc/h5vcc_storage.h
+++ b/cobalt/h5vcc/h5vcc_storage.h
@@ -15,7 +15,11 @@
 #ifndef COBALT_H5VCC_H5VCC_STORAGE_H_
 #define COBALT_H5VCC_H5VCC_STORAGE_H_
 
+#include <string>
+
 #include "base/optional.h"
+#include "cobalt/h5vcc/h5vcc_storage_verify_test_dictionary.h"
+#include "cobalt/h5vcc/h5vcc_storage_write_test_dictionary.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/script/wrappable.h"
 
@@ -30,6 +34,15 @@
   bool GetCookiesEnabled();
   void SetCookiesEnabled(bool enabled);
 
+  // Write test_size bytes of a repeating test_string to a test_file in the
+  // kSbSystemPathCacheDirectory.
+  H5vccStorageWriteTestDictionary WriteTest(uint32 test_size,
+                                            std::string test_string);
+  // Read the test_file and verify the file data matches the repeating
+  // test_string and is at least test_size bytes.
+  H5vccStorageVerifyTestDictionary VerifyTest(uint32 test_size,
+                                              std::string test_string);
+
   DEFINE_WRAPPABLE_TYPE(H5vccStorage);
 
  private:
diff --git a/cobalt/h5vcc/h5vcc_storage.idl b/cobalt/h5vcc/h5vcc_storage.idl
index 7a30dec..eb77fb4 100644
--- a/cobalt/h5vcc/h5vcc_storage.idl
+++ b/cobalt/h5vcc/h5vcc_storage.idl
@@ -18,4 +18,7 @@
   void flush(optional boolean sync = false);
   boolean getCookiesEnabled();
   void setCookiesEnabled(boolean value);
+
+  H5vccStorageWriteTestDictionary writeTest(unsigned long test_size, DOMString test_string);
+  H5vccStorageVerifyTestDictionary verifyTest(unsigned long test_size, DOMString test_string);
 };
diff --git a/cobalt/dom/blob_property_bag.idl b/cobalt/h5vcc/h5vcc_storage_verify_test_dictionary.idl
similarity index 76%
copy from cobalt/dom/blob_property_bag.idl
copy to cobalt/h5vcc/h5vcc_storage_verify_test_dictionary.idl
index 88f477f..fb2b269 100644
--- a/cobalt/dom/blob_property_bag.idl
+++ b/cobalt/h5vcc/h5vcc_storage_verify_test_dictionary.idl
@@ -1,4 +1,4 @@
-// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+// Copyright 2022 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.
@@ -12,7 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// https://www.w3.org/TR/FileAPI/#dfn-BlobPropertyBag
-dictionary BlobPropertyBag {
-  DOMString type = "";
-};
\ No newline at end of file
+dictionary H5vccStorageVerifyTestDictionary {
+  boolean verified;
+  long bytes_read;
+  DOMString error;
+};
diff --git a/cobalt/dom/blob_property_bag.idl b/cobalt/h5vcc/h5vcc_storage_write_test_dictionary.idl
similarity index 76%
copy from cobalt/dom/blob_property_bag.idl
copy to cobalt/h5vcc/h5vcc_storage_write_test_dictionary.idl
index 88f477f..451baf9 100644
--- a/cobalt/dom/blob_property_bag.idl
+++ b/cobalt/h5vcc/h5vcc_storage_write_test_dictionary.idl
@@ -1,4 +1,4 @@
-// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+// Copyright 2022 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.
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// https://www.w3.org/TR/FileAPI/#dfn-BlobPropertyBag
-dictionary BlobPropertyBag {
-  DOMString type = "";
-};
\ No newline at end of file
+dictionary H5vccStorageWriteTestDictionary {
+  long bytes_written;
+  DOMString error;
+};
diff --git a/cobalt/input/input_device_manager_desktop.cc b/cobalt/input/input_device_manager_desktop.cc
index 2446fc7..f86c46f 100644
--- a/cobalt/input/input_device_manager_desktop.cc
+++ b/cobalt/input/input_device_manager_desktop.cc
@@ -20,7 +20,6 @@
 #include "base/time/time.h"
 #include "cobalt/base/token.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/dom/input_event.h"
 #include "cobalt/dom/input_event_init.h"
 #include "cobalt/dom/keyboard_event.h"
@@ -33,17 +32,19 @@
 #include "cobalt/input/input_poller_impl.h"
 #include "cobalt/overlay_info/overlay_info_registry.h"
 #include "cobalt/system_window/input_event.h"
+#include "cobalt/web/event.h"
+#include "cobalt/web/event_init.h"
 
 namespace cobalt {
 namespace input {
 
 namespace {
 void UpdateEventInit(const system_window::InputEvent* input_event,
-                     EventInit* event) {
+                     web::EventInit* event) {
   if (input_event->timestamp() != 0) {
     // Convert SbTimeMonotonic to DOMTimeStamp.
     event->set_time_stamp(
-        cobalt::dom::Event::GetEventTime(input_event->timestamp()));
+        cobalt::web::Event::GetEventTime(input_event->timestamp()));
   }
 }
 }  // namespace
@@ -110,8 +111,8 @@
                Mismatched_modifier_enums);
 
 void UpdateEventModifierInit(const system_window::InputEvent* input_event,
-                             EventModifierInit* event) {
-  const uint32 modifiers = input_event->modifiers();
+                             dom::EventModifierInit* event) {
+  const uint32_t modifiers = input_event->modifiers();
   event->set_ctrl_key(modifiers & system_window::InputEvent::kCtrlKey);
   event->set_shift_key(modifiers & system_window::InputEvent::kShiftKey);
   event->set_alt_key(modifiers & system_window::InputEvent::kAltKey);
@@ -119,7 +120,7 @@
 }
 
 void UpdateMouseEventInitButtons(const system_window::InputEvent* input_event,
-                                 MouseEventInit* event) {
+                                 dom::MouseEventInit* event) {
   // The value of the button attribute MUST be as follows:
   //  https://www.w3.org/TR/uievents/#ref-for-dom-mouseevent-button-1
   switch (input_event->key_code()) {
diff --git a/cobalt/layout/topmost_event_target.cc b/cobalt/layout/topmost_event_target.cc
index 06ab6fd..c4a1b72 100644
--- a/cobalt/layout/topmost_event_target.cc
+++ b/cobalt/layout/topmost_event_target.cc
@@ -20,7 +20,6 @@
 #include "cobalt/base/tokens.h"
 #include "cobalt/cssom/keyword_value.h"
 #include "cobalt/dom/document.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/dom/html_element.h"
 #include "cobalt/dom/html_html_element.h"
 #include "cobalt/dom/lottie_player.h"
@@ -33,6 +32,7 @@
 #include "cobalt/dom/wheel_event.h"
 #include "cobalt/math/vector2d.h"
 #include "cobalt/math/vector2d_f.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace layout {
@@ -188,8 +188,8 @@
                element = element->parent_element()) {
             DCHECK(element->AsHTMLElement()->IsDesignated());
             element->DispatchEvent(new dom::PointerEvent(
-                base::Tokens::pointerleave(), dom::Event::kNotBubbles,
-                dom::Event::kNotCancelable, view, *event_init));
+                base::Tokens::pointerleave(), web::Event::kNotBubbles,
+                web::Event::kNotCancelable, view, *event_init));
           }
         }
       }
@@ -205,8 +205,8 @@
              element = element->parent_element()) {
           DCHECK(element->AsHTMLElement()->IsDesignated());
           element->DispatchEvent(new dom::MouseEvent(
-              base::Tokens::mouseleave(), dom::Event::kNotBubbles,
-              dom::Event::kNotCancelable, view, *event_init));
+              base::Tokens::mouseleave(), web::Event::kNotBubbles,
+              web::Event::kNotCancelable, view, *event_init));
         }
 
         if (!target_element ||
@@ -242,8 +242,8 @@
              element && element != nearest_common_ancestor;
              element = element->parent_element()) {
           element->DispatchEvent(new dom::PointerEvent(
-              base::Tokens::pointerenter(), dom::Event::kNotBubbles,
-              dom::Event::kNotCancelable, view, *event_init));
+              base::Tokens::pointerenter(), web::Event::kNotBubbles,
+              web::Event::kNotCancelable, view, *event_init));
         }
       }
 
@@ -255,8 +255,8 @@
            element && element != nearest_common_ancestor;
            element = element->parent_element()) {
         element->DispatchEvent(new dom::MouseEvent(
-            base::Tokens::mouseenter(), dom::Event::kNotBubbles,
-            dom::Event::kNotCancelable, view, *event_init));
+            base::Tokens::mouseenter(), web::Event::kNotBubbles,
+            web::Event::kNotCancelable, view, *event_init));
       }
     }
   }
@@ -264,7 +264,7 @@
 
 void SendCompatibilityMappingMouseEvent(
     const scoped_refptr<dom::HTMLElement>& target_element,
-    const scoped_refptr<dom::Event>& event,
+    const scoped_refptr<web::Event>& event,
     const dom::PointerEvent* pointer_event,
     const dom::PointerEventInit& event_init,
     std::set<std::string>* mouse_event_prevent_flags) {
@@ -343,7 +343,7 @@
 }  // namespace
 
 void TopmostEventTarget::MaybeSendPointerEvents(
-    const scoped_refptr<dom::Event>& event) {
+    const scoped_refptr<web::Event>& event) {
   TRACE_EVENT0("cobalt::layout",
                "TopmostEventTarget::MaybeSendPointerEvents()");
 
diff --git a/cobalt/layout/topmost_event_target.h b/cobalt/layout/topmost_event_target.h
index 0224ea6..078e179 100644
--- a/cobalt/layout/topmost_event_target.h
+++ b/cobalt/layout/topmost_event_target.h
@@ -20,12 +20,12 @@
 
 #include "base/memory/weak_ptr.h"
 #include "cobalt/dom/document.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/dom/html_element.h"
 #include "cobalt/layout/box.h"
 #include "cobalt/layout/layout_boxes.h"
 #include "cobalt/math/vector2d.h"
 #include "cobalt/math/vector2d_f.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace layout {
@@ -34,7 +34,7 @@
  public:
   TopmostEventTarget() {}
 
-  void MaybeSendPointerEvents(const scoped_refptr<dom::Event>& event);
+  void MaybeSendPointerEvents(const scoped_refptr<web::Event>& event);
 
  private:
   scoped_refptr<dom::HTMLElement> FindTopmostEventTarget(
diff --git a/cobalt/layout_tests/layout_snapshot.cc b/cobalt/layout_tests/layout_snapshot.cc
index 13573c7..57e3769 100644
--- a/cobalt/layout_tests/layout_snapshot.cc
+++ b/cobalt/layout_tests/layout_snapshot.cc
@@ -106,8 +106,7 @@
       browser::WebModule::CloseCallback() /* window_close_callback */,
       base::Closure() /* window_minimize_callback */,
       NULL /* can_play_type_handler */, NULL /* web_media_player_factory */,
-      viewport_size, resource_provider, 60.0f, NULL /* service_worker_jobs */,
-      web_module_options);
+      viewport_size, resource_provider, 60.0f, web_module_options);
 
   run_loop.Run();
 
diff --git a/cobalt/layout_tests/testdata/web-platform-tests/cors/web_platform_tests.txt b/cobalt/layout_tests/testdata/web-platform-tests/cors/web_platform_tests.txt
index b09222b..0bb5658 100644
--- a/cobalt/layout_tests/testdata/web-platform-tests/cors/web_platform_tests.txt
+++ b/cobalt/layout_tests/testdata/web-platform-tests/cors/web_platform_tests.txt
@@ -3,7 +3,7 @@
 #
 
 # Cobalt does not support HTTP cache.
-304.htm,FAIL
+304.htm,PASS
 allow-headers.htm,PASS
 basic.htm,PASS
 credentials-flag.htm,PASS
@@ -23,4 +23,3 @@
 status-async.htm,PASS
 status-preflight.htm,PASS
 status.htm,PASS
-
diff --git a/cobalt/layout_tests/testdata/web-platform-tests/workers/web_platform_tests.txt b/cobalt/layout_tests/testdata/web-platform-tests/workers/web_platform_tests.txt
index 8058d56..89b530d 100644
--- a/cobalt/layout_tests/testdata/web-platform-tests/workers/web_platform_tests.txt
+++ b/cobalt/layout_tests/testdata/web-platform-tests/workers/web_platform_tests.txt
@@ -70,6 +70,7 @@
 postMessage_ports_readonly_array.htm, DISABLE
 postMessage_target_source.htm, DISABLE
 
+# b/229767497, b/229128880
 constructors/Worker/AbstractWorker.onerror.html, DISABLE
 constructors/Worker/Blob-url.html, DISABLE
 constructors/Worker/ctor-1.html, PASS
@@ -79,7 +80,7 @@
 constructors/Worker/no-arguments-ctor.html, PASS
 constructors/Worker/resolve-empty-string.html, PASS
 constructors/Worker/same-origin.html, DISABLE
-constructors/Worker/terminate.html, PASS
+constructors/Worker/terminate.html, DISABLE
 constructors/Worker/unexpected-self-properties.html, DISABLE
 constructors/Worker/unresolvable-url.html, DISABLE
 
@@ -109,6 +110,13 @@
 
 interfaces/WorkerGlobalScope/self.html, DISABLE
 
+# b/229767497, b/229128880
+interfaces/WorkerUtils/WindowTimers/001.html, PASS
+interfaces/WorkerUtils/WindowTimers/002.html, PASS
+interfaces/WorkerUtils/WindowTimers/003.html, DISABLE
+interfaces/WorkerUtils/WindowTimers/004.html, PASS
+interfaces/WorkerUtils/WindowTimers/005.html, DISABLE
+
 non-automated/application-cache-dedicated.html, DISABLE
 
 semantics/encodings/001.html,PASS
diff --git a/cobalt/layout_tests/web_platform_tests.cc b/cobalt/layout_tests/web_platform_tests.cc
index 3b2f9c1..520ed72 100644
--- a/cobalt/layout_tests/web_platform_tests.cc
+++ b/cobalt/layout_tests/web_platform_tests.cc
@@ -27,13 +27,13 @@
 #include "base/values.h"
 #include "cobalt/browser/web_module.h"
 #include "cobalt/cssom/viewport_size.h"
-#include "cobalt/dom/csp_delegate_factory.h"
 #include "cobalt/layout_tests/test_utils.h"
 #include "cobalt/layout_tests/web_platform_test_parser.h"
 #include "cobalt/math/size.h"
 #include "cobalt/media/media_module.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/render_tree/resource_provider_stub.h"
+#include "cobalt/web/csp_delegate_factory.h"
 #include "starboard/window.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -48,20 +48,20 @@
 // A CspDelegate that behaves more like a "standard" one. i.e. it
 // is permissive by default. This is for testing our compliance
 // with the web-platform-test suite.
-class CspDelegatePermissive : public dom::CspDelegateSecure {
+class CspDelegatePermissive : public web::CspDelegateSecure {
  public:
   CspDelegatePermissive(
-      std::unique_ptr<dom::CspViolationReporter> violation_reporter,
+      std::unique_ptr<web::CspViolationReporter> violation_reporter,
       const GURL& url, csp::CSPHeaderPolicy require_csp,
       const base::Closure& policy_changed_callback)
-      : dom::CspDelegateSecure(std::move(violation_reporter), url, require_csp,
+      : web::CspDelegateSecure(std::move(violation_reporter), url, require_csp,
                                policy_changed_callback) {
     // Lies, but some checks in our parent require this.
     was_header_received_ = true;
   }
 
   static CspDelegate* Create(
-      std::unique_ptr<dom::CspViolationReporter> violation_reporter,
+      std::unique_ptr<web::CspViolationReporter> violation_reporter,
       const GURL& url, csp::CSPHeaderPolicy require_csp,
       const base::Closure& policy_changed_callback,
       int insecure_allowed_token) {
@@ -205,8 +205,8 @@
   std::unique_ptr<media::CanPlayTypeHandler> can_play_type_handler(
       media::MediaModule::CreateCanPlayTypeHandler());
 
-  dom::CspDelegateFactory::GetInstance()->OverrideCreator(
-      dom::kCspEnforcementEnable, CspDelegatePermissive::Create);
+  web::CspDelegateFactory::GetInstance()->OverrideCreator(
+      web::kCspEnforcementEnable, CspDelegatePermissive::Create);
   // Use test runner mode to allow the content itself to dictate when it is
   // ready for layout should be performed.  See cobalt/dom/test_runner.h.
   browser::WebModule::Options web_module_options("RunWebPlatformTest");
@@ -230,8 +230,7 @@
       base::Bind(&WindowCloseCallback, &run_loop, base::MessageLoop::current()),
       base::Closure() /* window_minimize_callback */,
       can_play_type_handler.get(), media_module.get(), kDefaultViewportSize,
-      &resource_provider, 60.0f, NULL /* service_worker_jobs */,
-      web_module_options);
+      &resource_provider, 60.0f, web_module_options);
   run_loop.Run();
   const std::string extract_results =
       "document.getElementById(\"__testharness__results__\").textContent;";
diff --git a/cobalt/loader/BUILD.gn b/cobalt/loader/BUILD.gn
index 23e6f8a..9378b97 100644
--- a/cobalt/loader/BUILD.gn
+++ b/cobalt/loader/BUILD.gn
@@ -15,9 +15,6 @@
 static_library("loader") {
   has_pedantic_warnings = true
 
-  # Includes cobalt/dom/url_utils.h from dom that depends on this target.
-  check_includes = false
-
   sources = [
     "blob_fetcher.cc",
     "blob_fetcher.h",
@@ -115,6 +112,7 @@
     "//cobalt/network",
     "//cobalt/render_tree",
     "//cobalt/renderer/test/png_utils",
+    "//cobalt/web",
     "//nb",
     "//third_party/libjpeg-turbo:libjpeg",
     "//third_party/libpng",
diff --git a/cobalt/loader/decoder.h b/cobalt/loader/decoder.h
index 4c03208..e7d7a7c 100644
--- a/cobalt/loader/decoder.h
+++ b/cobalt/loader/decoder.h
@@ -19,9 +19,9 @@
 #include <string>
 
 #include "base/optional.h"
-#include "cobalt/dom/url_utils.h"
 #include "cobalt/loader/loader_types.h"
 #include "cobalt/render_tree/resource_provider.h"
+#include "cobalt/web/url_utils.h"
 #include "net/http/http_response_headers.h"
 #include "url/gurl.h"
 
diff --git a/cobalt/loader/fetcher.h b/cobalt/loader/fetcher.h
index 3702679..19be76d 100644
--- a/cobalt/loader/fetcher.h
+++ b/cobalt/loader/fetcher.h
@@ -19,8 +19,8 @@
 #include <string>
 
 #include "base/callback.h"
-#include "cobalt/dom/url_utils.h"
 #include "cobalt/loader/loader_types.h"
+#include "cobalt/web/url_utils.h"
 #include "net/base/load_timing_info.h"
 #include "net/http/http_response_headers.h"
 #include "url/gurl.h"
diff --git a/cobalt/loader/loader_test.cc b/cobalt/loader/loader_test.cc
index 81df3b4..fc0b3b6 100644
--- a/cobalt/loader/loader_test.cc
+++ b/cobalt/loader/loader_test.cc
@@ -22,9 +22,9 @@
 #include "base/optional.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
-#include "cobalt/dom/url_utils.h"
 #include "cobalt/loader/file_fetcher.h"
 #include "cobalt/loader/text_decoder.h"
+#include "cobalt/web/url_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/cobalt/loader/net_fetcher.cc b/cobalt/loader/net_fetcher.cc
index d29c906..5c55904 100644
--- a/cobalt/loader/net_fetcher.cc
+++ b/cobalt/loader/net_fetcher.cc
@@ -19,6 +19,7 @@
 #include <utility>
 
 #include "base/strings/stringprintf.h"
+#include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/loader/cors_preflight.h"
 #include "cobalt/loader/url_fetcher_string_writer.h"
@@ -135,6 +136,8 @@
 void NetFetcher::Start() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   const GURL& original_url = url_fetcher_->GetOriginalURL();
+  TRACE_EVENT1("cobalt::loader", "NetFetcher::Start", "url",
+               original_url.spec());
   if (security_callback_.is_null() ||
       security_callback_.Run(original_url, false /* did not redirect */)) {
     url_fetcher_->Start();
@@ -147,6 +150,8 @@
 
 void NetFetcher::OnURLFetchResponseStarted(const net::URLFetcher* source) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  TRACE_EVENT1("cobalt::loader", "NetFetcher::OnURLFetchResponseStarted", "url",
+               url_fetcher_->GetOriginalURL().spec());
   if (source->GetURL() != source->GetOriginalURL()) {
     // A redirect occurred. Re-check the security policy.
     if (!security_callback_.is_null() &&
@@ -198,6 +203,8 @@
 
 void NetFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  TRACE_EVENT1("cobalt::loader", "NetFetcher::OnURLFetchComplete", "url",
+               url_fetcher_->GetOriginalURL().spec());
   const net::URLRequestStatus& status = source->GetStatus();
   const int response_code = source->GetResponseCode();
   if (status.is_success() && IsResponseCodeSuccess(response_code)) {
diff --git a/cobalt/loader/script_loader_factory.h b/cobalt/loader/script_loader_factory.h
index 0097f1e..5fcc8a4 100644
--- a/cobalt/loader/script_loader_factory.h
+++ b/cobalt/loader/script_loader_factory.h
@@ -38,7 +38,8 @@
 class ScriptLoaderFactory {
  public:
   ScriptLoaderFactory(const char* name, FetcherFactory* fetcher_factory,
-                      base::ThreadPriority loader_thread_priority);
+                      base::ThreadPriority loader_thread_priority =
+                          base::ThreadPriority::NORMAL);
 
   // Creates a loader that fetches and decodes a Javascript resource.
   std::unique_ptr<Loader> CreateScriptLoader(
diff --git a/cobalt/loader/text_decoder.h b/cobalt/loader/text_decoder.h
index d25992c..07a2492 100644
--- a/cobalt/loader/text_decoder.h
+++ b/cobalt/loader/text_decoder.h
@@ -24,8 +24,8 @@
 #include "base/compiler_specific.h"
 #include "base/logging.h"
 #include "base/threading/thread_checker.h"
-#include "cobalt/dom/url_utils.h"
 #include "cobalt/loader/decoder.h"
+#include "cobalt/web/url_utils.h"
 
 namespace cobalt {
 namespace loader {
diff --git a/cobalt/media/BUILD.gn b/cobalt/media/BUILD.gn
index bb2d46c..50a4629 100644
--- a/cobalt/media/BUILD.gn
+++ b/cobalt/media/BUILD.gn
@@ -45,8 +45,6 @@
     "base/sbplayer_set_bounds_helper.h",
     "base/starboard_player.cc",
     "base/starboard_player.h",
-    "base/starboard_utils.cc",
-    "base/starboard_utils.h",
     "base/video_frame_provider.h",
     "decoder_buffer_allocator.cc",
     "decoder_buffer_allocator.h",
diff --git a/cobalt/media/base/sbplayer_pipeline.cc b/cobalt/media/base/sbplayer_pipeline.cc
index 2c245c7..d0ae591 100644
--- a/cobalt/media/base/sbplayer_pipeline.cc
+++ b/cobalt/media/base/sbplayer_pipeline.cc
@@ -1490,6 +1490,7 @@
                  "SbPlayerPipeline::ResumeTask failed to create a valid "
                  "StarboardPlayer - " +
                      time_information + " \'" + error_message + "\'");
+      done_event->Signal();
       return;
     }
   }
diff --git a/cobalt/media/base/starboard_player.cc b/cobalt/media/base/starboard_player.cc
index 1037e3f..ecc1ab4 100644
--- a/cobalt/media/base/starboard_player.cc
+++ b/cobalt/media/base/starboard_player.cc
@@ -25,11 +25,11 @@
 #include "base/logging.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/media/base/format_support_query_metrics.h"
-#include "cobalt/media/base/starboard_utils.h"
 #include "starboard/common/media.h"
 #include "starboard/common/string.h"
 #include "starboard/configuration.h"
 #include "starboard/memory.h"
+#include "third_party/chromium/media/base/starboard_utils.h"
 
 namespace cobalt {
 namespace media {
@@ -219,8 +219,9 @@
       static_cast<int>(video_config_.natural_size().height());
   video_sample_info_.codec =
       MediaVideoCodecToSbMediaVideoCodec(video_config_.codec());
-  video_sample_info_.color_metadata = MediaToSbMediaColorMetadata(
-      video_config_.color_space_info(), video_config_.hdr_metadata());
+  video_sample_info_.color_metadata =
+      MediaToSbMediaColorMetadata(video_config_.color_space_info(),
+                                  video_config_.hdr_metadata(), mime_type);
   video_mime_type_ = mime_type;
   video_sample_info_.mime = video_mime_type_.c_str();
   video_sample_info_.max_video_capabilities = max_video_capabilities_.c_str();
@@ -784,7 +785,8 @@
   }
 
   if (state_ == kResuming) {
-    DemuxerStream::Type stream_type = SbMediaTypeToDemuxerStreamType(type);
+    DemuxerStream::Type stream_type =
+        ::media::SbMediaTypeToDemuxerStreamType(type);
     if (decoder_buffer_cache_.GetBuffer(stream_type)) {
       WriteNextBufferFromCache(stream_type);
       return;
@@ -795,7 +797,7 @@
     }
   }
 
-  host_->OnNeedData(SbMediaTypeToDemuxerStreamType(type));
+  host_->OnNeedData(::media::SbMediaTypeToDemuxerStreamType(type));
 }
 
 void StarboardPlayer::OnPlayerStatus(SbPlayer player, SbPlayerState state,
diff --git a/cobalt/media/base/starboard_utils.h b/cobalt/media/base/starboard_utils.h
deleted file mode 100644
index d7ec2a2..0000000
--- a/cobalt/media/base/starboard_utils.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2016 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_MEDIA_BASE_STARBOARD_UTILS_H_
-#define COBALT_MEDIA_BASE_STARBOARD_UTILS_H_
-
-#include "starboard/drm.h"
-#include "starboard/media.h"
-#include "third_party/chromium/media/base/audio_codecs.h"
-#include "third_party/chromium/media/base/audio_decoder_config.h"
-#include "third_party/chromium/media/base/decoder_buffer.h"
-#include "third_party/chromium/media/base/demuxer_stream.h"
-#include "third_party/chromium/media/base/video_codecs.h"
-#include "third_party/chromium/media/base/video_decoder_config.h"
-#include "third_party/chromium/media/cobalt/third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/chromium/media/cobalt/ui/gfx/hdr_metadata.h"
-
-namespace cobalt {
-namespace media {
-
-SbMediaAudioCodec MediaAudioCodecToSbMediaAudioCodec(::media::AudioCodec codec);
-SbMediaVideoCodec MediaVideoCodecToSbMediaVideoCodec(::media::VideoCodec codec);
-
-SbMediaAudioSampleInfo MediaAudioConfigToSbMediaAudioSampleInfo(
-    const ::media::AudioDecoderConfig& audio_decoder_config,
-    const char* mime_type);
-
-::media::DemuxerStream::Type SbMediaTypeToDemuxerStreamType(SbMediaType type);
-SbMediaType DemuxerStreamTypeToSbMediaType(::media::DemuxerStream::Type type);
-
-void FillDrmSampleInfo(const scoped_refptr<::media::DecoderBuffer>& buffer,
-                       SbDrmSampleInfo* drm_info,
-                       SbDrmSubSampleMapping* subsample_mapping);
-
-SbMediaColorMetadata MediaToSbMediaColorMetadata(
-    const ::media::VideoColorSpace& color_space,
-    const absl::optional<gfx::HDRMetadata>& hdr_metadata);
-
-}  // namespace media
-}  // namespace cobalt
-
-#endif  // COBALT_MEDIA_BASE_STARBOARD_UTILS_H_
diff --git a/cobalt/media/decoder_buffer_allocator.cc b/cobalt/media/decoder_buffer_allocator.cc
index be9b8e6..6f3a1a1 100644
--- a/cobalt/media/decoder_buffer_allocator.cc
+++ b/cobalt/media/decoder_buffer_allocator.cc
@@ -14,27 +14,30 @@
 
 #include "cobalt/media/decoder_buffer_allocator.h"
 
+#include <algorithm>
 #include <vector>
 
 #include "cobalt/math/size.h"
-#include "cobalt/media/base/starboard_utils.h"
 #include "nb/allocator.h"
 #include "nb/memory_scope.h"
 #include "starboard/configuration.h"
 #include "starboard/media.h"
 #include "starboard/memory.h"
+#include "third_party/chromium/media/base/starboard_utils.h"
 
 namespace cobalt {
 namespace media {
 
 namespace {
 
+using starboard::ScopedLock;
+
 const bool kEnableAllocationLog = false;
 
-const std::size_t kAllocationRecordGranularity = 512 * 1024;
+const size_t kAllocationRecordGranularity = 512 * 1024;
 // Used to determine if the memory allocated is large. The underlying logic can
 // be different.
-const std::size_t kSmallAllocationThreshold = 512;
+const size_t kSmallAllocationThreshold = 512;
 
 }  // namespace
 
@@ -58,11 +61,8 @@
 
   TRACK_MEMORY_SCOPE("Media");
 
-  // We cannot call SbMediaGetMaxBufferCapacity because |video_codec_| is not
-  // set yet. Use 0 (unbounded) until |video_codec_| is updated in
-  // UpdateVideoConfig().
-  starboard::ScopedLock scoped_lock(mutex_);
-  CreateReuseAllocator(0);
+  ScopedLock scoped_lock(mutex_);
+  EnsureReuseAllocatorIsCreated();
   Allocator::Set(this);
 }
 
@@ -75,7 +75,7 @@
 
   TRACK_MEMORY_SCOPE("Media");
 
-  starboard::ScopedLock scoped_lock(mutex_);
+  ScopedLock scoped_lock(mutex_);
 
   if (reuse_allocator_) {
     DCHECK_EQ(reuse_allocator_->GetAllocated(), 0);
@@ -90,7 +90,7 @@
 
   TRACK_MEMORY_SCOPE("Media");
 
-  starboard::ScopedLock scoped_lock(mutex_);
+  ScopedLock scoped_lock(mutex_);
 
   if (reuse_allocator_ && reuse_allocator_->GetAllocated() == 0) {
     DLOG(INFO) << "Freed " << reuse_allocator_->GetCapacity()
@@ -106,11 +106,8 @@
 
   TRACK_MEMORY_SCOPE("Media");
 
-  starboard::ScopedLock scoped_lock(mutex_);
-
-  if (!reuse_allocator_) {
-    CreateReuseAllocator(0);
-  }
+  ScopedLock scoped_lock(mutex_);
+  EnsureReuseAllocatorIsCreated();
 }
 
 void* DecoderBufferAllocator::Allocate(size_t size, size_t alignment) {
@@ -118,32 +115,20 @@
 
   if (!using_memory_pool_) {
     sbmemory_bytes_used_.fetch_add(size);
-    return SbMemoryAllocateAligned(alignment, size);
-  }
-
-  starboard::ScopedLock scoped_lock(mutex_);
-
-  if (!reuse_allocator_) {
-    DCHECK(is_memory_pool_allocated_on_demand_);
-
-    int max_capacity = 0;
-    if (video_codec_ != kSbMediaVideoCodecNone) {
-      DCHECK_GT(resolution_width_, 0);
-      DCHECK_GT(resolution_height_, 0);
-
-      max_capacity = SbMediaGetMaxBufferCapacity(
-          video_codec_, resolution_width_, resolution_height_, bits_per_pixel_);
-    }
-    CreateReuseAllocator(max_capacity);
-  }
-
-  void* p = reuse_allocator_->Allocate(size, alignment);
-  if (!p) {
+    auto p = SbMemoryAllocateAligned(alignment, size);
+    CHECK(p);
     return p;
   }
+
+  ScopedLock scoped_lock(mutex_);
+
+  EnsureReuseAllocatorIsCreated();
+
+  void* p = reuse_allocator_->Allocate(size, alignment);
+  CHECK(p);
+
   LOG_IF(INFO, kEnableAllocationLog)
       << "Media Allocation Log " << p << " " << size << " " << alignment << " ";
-  UpdateAllocationRecord();
   return p;
 }
 
@@ -161,7 +146,7 @@
     return;
   }
 
-  starboard::ScopedLock scoped_lock(mutex_);
+  ScopedLock scoped_lock(mutex_);
 
   DCHECK(reuse_allocator_);
 
@@ -177,109 +162,48 @@
   }
 }
 
-void DecoderBufferAllocator::UpdateVideoConfig(
-    const ::media::VideoDecoderConfig& config) {
-  if (!using_memory_pool_) {
-    return;
-  }
-
-  starboard::ScopedLock scoped_lock(mutex_);
-
-  if (!reuse_allocator_) {
-    return;
-  }
-
-  video_codec_ = MediaVideoCodecToSbMediaVideoCodec(config.codec());
-  resolution_width_ = config.visible_rect().size().width();
-  resolution_height_ = config.visible_rect().size().height();
-  // TODO(b/230799815): Consider infer |bits_per_pixel_| from the video codec,
-  // or deprecate it completely.
-  bits_per_pixel_ = 0;
-
-  reuse_allocator_->IncreaseMaxCapacityIfNecessary(SbMediaGetMaxBufferCapacity(
-      video_codec_, resolution_width_, resolution_height_, bits_per_pixel_));
-  DLOG(INFO) << "Max capacity of decoder buffer allocator after increasing is "
-             << reuse_allocator_->GetCapacity();
-}
-
-std::size_t DecoderBufferAllocator::GetAllocatedMemory() const {
+size_t DecoderBufferAllocator::GetAllocatedMemory() const {
   if (!using_memory_pool_) {
     return sbmemory_bytes_used_.load();
   }
-  starboard::ScopedLock scoped_lock(mutex_);
+  ScopedLock scoped_lock(mutex_);
   return reuse_allocator_ ? reuse_allocator_->GetAllocated() : 0;
 }
 
-std::size_t DecoderBufferAllocator::GetCurrentMemoryCapacity() const {
+size_t DecoderBufferAllocator::GetCurrentMemoryCapacity() const {
   if (!using_memory_pool_) {
     return sbmemory_bytes_used_.load();
   }
-  starboard::ScopedLock scoped_lock(mutex_);
+  ScopedLock scoped_lock(mutex_);
   return reuse_allocator_ ? reuse_allocator_->GetCapacity() : 0;
 }
 
-std::size_t DecoderBufferAllocator::GetMaximumMemoryCapacity() const {
-  starboard::ScopedLock scoped_lock(mutex_);
-  if (video_codec_ == kSbMediaVideoCodecNone) {
-    return 0;
+size_t DecoderBufferAllocator::GetMaximumMemoryCapacity() const {
+  ScopedLock scoped_lock(mutex_);
+
+  if (reuse_allocator_) {
+    return std::max<size_t>(reuse_allocator_->max_capacity(),
+                            max_buffer_capacity_);
   }
-  if (!using_memory_pool_) {
-    return SbMediaGetMaxBufferCapacity(video_codec_, resolution_width_,
-                                       resolution_height_, bits_per_pixel_);
-  }
-  return reuse_allocator_
-             ? reuse_allocator_->max_capacity()
-             : SbMediaGetMaxBufferCapacity(video_codec_, resolution_width_,
-                                           resolution_height_, bits_per_pixel_);
+  return max_buffer_capacity_;
 }
 
-void DecoderBufferAllocator::CreateReuseAllocator(int max_capacity) {
+size_t DecoderBufferAllocator::GetSourceBufferEvictExtraInBytes() const {
+  return source_buffer_evict_extra_in_bytes_;
+}
+
+void DecoderBufferAllocator::EnsureReuseAllocatorIsCreated() {
   mutex_.DCheckAcquired();
 
+  if (reuse_allocator_) {
+    return;
+  }
+
   reuse_allocator_.reset(new nb::BidirectionalFitReuseAllocator(
       &fallback_allocator_, initial_capacity_, kSmallAllocationThreshold,
-      allocation_unit_, max_capacity));
+      allocation_unit_, 0));
   DLOG(INFO) << "Allocated " << initial_capacity_
-             << " bytes for media buffer pool, with max capacity set to "
-             << max_capacity;
-}
-
-bool DecoderBufferAllocator::UpdateAllocationRecord() const {
-#if !defined(COBALT_BUILD_TYPE_GOLD)
-  // This code is not quite multi-thread safe but is safe enough for tracking
-  // purposes.
-  static std::size_t max_allocated = initial_capacity_ / 2;
-  static std::size_t max_capacity = initial_capacity_;
-
-  bool new_max_reached = false;
-  if (reuse_allocator_->GetAllocated() >
-      max_allocated + kAllocationRecordGranularity) {
-    max_allocated = reuse_allocator_->GetAllocated();
-    new_max_reached = true;
-  }
-  if (reuse_allocator_->GetCapacity() >
-      max_capacity + kAllocationRecordGranularity) {
-    max_capacity = reuse_allocator_->GetCapacity();
-    new_max_reached = true;
-  }
-  if (new_max_reached) {
-    SB_LOG(INFO) << "New Media Buffer Allocation Record: "
-                 << "Max Allocated: " << max_allocated
-                 << "  Max Capacity: " << max_capacity;
-    // TODO: Enable the following line once PrintAllocations() accepts max line
-    // as a parameter.
-    // reuse_allocator_->PrintAllocations();
-  }
-#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
-
-  if (reuse_allocator_->CapacityExceeded()) {
-    SB_LOG(WARNING) << "Cobalt media buffer capacity "
-                    << reuse_allocator_->GetCapacity()
-                    << " exceeded max capacity "
-                    << reuse_allocator_->max_capacity();
-    return false;
-  }
-  return true;
+             << " bytes for media buffer pool.";
 }
 
 }  // namespace media
diff --git a/cobalt/media/decoder_buffer_allocator.h b/cobalt/media/decoder_buffer_allocator.h
index 274561d..2e08a9f 100644
--- a/cobalt/media/decoder_buffer_allocator.h
+++ b/cobalt/media/decoder_buffer_allocator.h
@@ -47,15 +47,14 @@
   size_t GetAllocatedMemory() const override;
   size_t GetCurrentMemoryCapacity() const override;
   size_t GetMaximumMemoryCapacity() const override;
+  size_t GetSourceBufferEvictExtraInBytes() const override;
 
-  void UpdateVideoConfig(const ::media::VideoDecoderConfig& video_config);
+  void SetSourceBufferEvictExtraInBytes(size_t evict_extra_in_bytes) {
+    source_buffer_evict_extra_in_bytes_ = evict_extra_in_bytes;
+  }
 
  private:
-  void CreateReuseAllocator(int max_capacity);
-
-  // Update the Allocation record, and return false if allocation exceeds the
-  // max buffer capacity, or true otherwise.
-  bool UpdateAllocationRecord() const;
+  void EnsureReuseAllocatorIsCreated();
 
   const bool using_memory_pool_;
   const bool is_memory_pool_allocated_on_demand_;
@@ -66,10 +65,8 @@
   nb::StarboardMemoryAllocator fallback_allocator_;
   std::unique_ptr<nb::BidirectionalFitReuseAllocator> reuse_allocator_;
 
-  SbMediaVideoCodec video_codec_ = kSbMediaVideoCodecNone;
-  int resolution_width_ = -1;
-  int resolution_height_ = -1;
-  int bits_per_pixel_ = -1;
+  int max_buffer_capacity_ = 0;
+  size_t source_buffer_evict_extra_in_bytes_ = 0;
 
   // Monitor memory allocation and use when |using_memory_pool_| is false
   starboard::atomic_int32_t sbmemory_bytes_used_;
diff --git a/cobalt/media/decoder_buffer_memory_info.h b/cobalt/media/decoder_buffer_memory_info.h
index af79574..2e4813b 100644
--- a/cobalt/media/decoder_buffer_memory_info.h
+++ b/cobalt/media/decoder_buffer_memory_info.h
@@ -28,15 +28,24 @@
   virtual size_t GetAllocatedMemory() const = 0;
   virtual size_t GetCurrentMemoryCapacity() const = 0;
   virtual size_t GetMaximumMemoryCapacity() const = 0;
+  // The return value will be used in `SourceBuffer::EvictCodedFrames()` so
+  // it will evict extra data from the SourceBuffer, to reduce the overall
+  // memory the underlying Demuxer implementation may use.
+  // NOTE: This is not the best place to pass such information.  Since it is a
+  // tentative workaround that will be reverted once we confirm the new
+  // DecoderBufferAllocator implementation works in production, adding this
+  // here allows us to reduce interfaces passed from media to dom.
+  virtual size_t GetSourceBufferEvictExtraInBytes() const = 0;
 };
 
 class StubDecoderBufferMemoryInfo : public DecoderBufferMemoryInfo {
-  public:
-   ~StubDecoderBufferMemoryInfo() override {}
+ public:
+  ~StubDecoderBufferMemoryInfo() override {}
 
-   size_t GetAllocatedMemory() const override { return 0; }
-   size_t GetCurrentMemoryCapacity() const override { return 0; }
-   size_t GetMaximumMemoryCapacity() const override { return 0; }
+  size_t GetAllocatedMemory() const override { return 0; }
+  size_t GetCurrentMemoryCapacity() const override { return 0; }
+  size_t GetMaximumMemoryCapacity() const override { return 0; }
+  size_t GetSourceBufferEvictExtraInBytes() const override { return 0; }
 };
 
 }  // namespace media
diff --git a/cobalt/media/media_module.cc b/cobalt/media/media_module.cc
index 89e251b..2eb8ff0 100644
--- a/cobalt/media/media_module.cc
+++ b/cobalt/media/media_module.cc
@@ -29,6 +29,7 @@
 #include "starboard/common/string.h"
 #include "starboard/media.h"
 #include "starboard/window.h"
+#include "third_party/chromium/media/base/mime_util.h"
 
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
 #include "cobalt/browser/switches.h"
@@ -59,9 +60,7 @@
       continue;
     }
     if (name_and_value[0] == "codecs") {
-      // TODO(b/230888580): Revive ParseCodecString() to enable returning of
-      // `codecs`.
-      //   ParseCodecString(name_and_value[1], &codecs, /* strip= */ false);
+      ::media::SplitCodecs(name_and_value[1], &codecs);
       return codecs;
     }
   }
@@ -184,6 +183,14 @@
 
 }  // namespace
 
+bool MediaModule::SetConfiguration(const std::string& name, int32 value) {
+  if (name == "source_buffer_evict_extra_in_bytes" && value >= 0) {
+    decoder_buffer_allocator_.SetSourceBufferEvictExtraInBytes(value);
+    return true;
+  }
+  return false;
+}
+
 std::unique_ptr<WebMediaPlayer> MediaModule::CreateWebMediaPlayer(
     WebMediaPlayerClient* client) {
   TRACK_MEMORY_SCOPE("Media");
diff --git a/cobalt/media/media_module.h b/cobalt/media/media_module.h
index 94cc140..0a90162 100644
--- a/cobalt/media/media_module.h
+++ b/cobalt/media/media_module.h
@@ -64,7 +64,7 @@
   // Returns true when the setting is set successfully or if the setting has
   // already been set to the expected value.  Returns false when the setting is
   // invalid or not set to the expected value.
-  bool SetConfiguration(const std::string& name, int32 value) { return false; }
+  bool SetConfiguration(const std::string& name, int32 value);
 
   const DecoderBufferAllocator* GetDecoderBufferAllocator() const {
     return &decoder_buffer_allocator_;
diff --git a/cobalt/media/player/mime_util_certificate_type_list.h b/cobalt/media/player/mime_util_certificate_type_list.h
deleted file mode 100644
index c3d2947..0000000
--- a/cobalt/media/player/mime_util_certificate_type_list.h
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// This file intentionally does not have header guards, it's included
-// inside a macro to generate enum.
-
-// This file contains the list of certificate MIME types.
-
-CERTIFICATE_MIME_TYPE(UNKNOWN, 0)
-CERTIFICATE_MIME_TYPE(X509_USER_CERT, 1)
-CERTIFICATE_MIME_TYPE(X509_CA_CERT, 2)
-CERTIFICATE_MIME_TYPE(PKCS12_ARCHIVE, 3)
diff --git a/cobalt/media/player/web_media_player.h b/cobalt/media/player/web_media_player.h
index 53a9999..d3b7abd 100644
--- a/cobalt/media/player/web_media_player.h
+++ b/cobalt/media/player/web_media_player.h
@@ -108,10 +108,7 @@
   // Playback controls.
   virtual void Play() = 0;
   virtual void Pause() = 0;
-  virtual bool SupportsFullscreen() const = 0;
-  virtual bool SupportsSave() const = 0;
   virtual void Seek(float seconds) = 0;
-  virtual void SetEndTime(float seconds) = 0;
   virtual void SetRate(float rate) = 0;
   virtual void SetVolume(float volume) = 0;
   virtual void SetVisible(bool visible) = 0;
@@ -215,9 +212,6 @@
   virtual void OutputModeChanged() = 0;
   virtual void ContentSizeChanged() = 0;
   virtual void PlaybackStateChanged() = 0;
-  // TODO: Revisit the necessity of the following function.
-  virtual void SetOpaque(bool opaque) {}
-  virtual void SawUnsupportedTracks() = 0;
   virtual float Volume() const = 0;
   virtual void SourceOpened(::media::ChunkDemuxer* chunk_demuxer) = 0;
   virtual std::string SourceURL() const = 0;
@@ -235,9 +229,6 @@
   virtual void EncryptedMediaInitDataEncountered(const char* init_data_type,
                                                  const unsigned char* init_data,
                                                  unsigned init_data_length) = 0;
-  // TODO: Revisit the necessity of the following functions.
-  virtual void CloseHelperPlugin() { NOTREACHED(); }
-  virtual void DisableAcceleratedCompositing() {}
 
  protected:
   ~WebMediaPlayerClient() {}
diff --git a/cobalt/media/player/web_media_player_impl.cc b/cobalt/media/player/web_media_player_impl.cc
index bba2962..62d242a 100644
--- a/cobalt/media/player/web_media_player_impl.cc
+++ b/cobalt/media/player/web_media_player_impl.cc
@@ -122,9 +122,7 @@
       allow_resume_after_suspend_(allow_resume_after_suspend),
       proxy_(new WebMediaPlayerProxy(main_loop_->task_runner(), this)),
       media_log_(media_log),
-      incremented_externally_allocated_memory_(false),
       is_local_source_(false),
-      supports_save_(true),
       suppress_destruction_errors_(false),
       drm_system_(NULL),
       window_(window) {
@@ -252,7 +250,6 @@
           &WebMediaPlayerImpl::OnEncryptedMediaInitDataEncounteredWrapper),
       media_log_));
 
-  supports_save_ = false;
   state_.is_media_source = true;
   StartPipeline(chunk_demuxer_.get());
 }
@@ -310,16 +307,6 @@
   media_log_->AddEvent<::media::MediaLogEvent::kPause>();
 }
 
-bool WebMediaPlayerImpl::SupportsFullscreen() const {
-  DCHECK_EQ(main_loop_, base::MessageLoop::current());
-  return true;
-}
-
-bool WebMediaPlayerImpl::SupportsSave() const {
-  DCHECK_EQ(main_loop_, base::MessageLoop::current());
-  return supports_save_;
-}
-
 void WebMediaPlayerImpl::Seek(float seconds) {
   DCHECK_EQ(main_loop_, base::MessageLoop::current());
 
@@ -360,13 +347,6 @@
                   BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineSeek));
 }
 
-void WebMediaPlayerImpl::SetEndTime(float seconds) {
-  DCHECK_EQ(main_loop_, base::MessageLoop::current());
-
-  // TODO(hclam): add method call when it has been implemented.
-  return;
-}
-
 void WebMediaPlayerImpl::SetRate(float rate) {
   DCHECK_EQ(main_loop_, base::MessageLoop::current());
 
@@ -781,12 +761,6 @@
   GetClient()->SourceOpened(chunk_demuxer_.get());
 }
 
-void WebMediaPlayerImpl::SetOpaque(bool opaque) {
-  DCHECK_EQ(main_loop_, base::MessageLoop::current());
-
-  GetClient()->SetOpaque(opaque);
-}
-
 void WebMediaPlayerImpl::OnDownloadingStatusChanged(bool is_downloading) {
   if (!is_downloading && network_state_ == WebMediaPlayer::kNetworkStateLoading)
     SetNetworkState(WebMediaPlayer::kNetworkStateIdle);
@@ -857,7 +831,6 @@
 
   if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing &&
       state >= WebMediaPlayer::kReadyStateHaveMetadata) {
-    if (!HasVideo()) GetClient()->DisableAcceleratedCompositing();
   } else if (state == WebMediaPlayer::kReadyStateHaveEnoughData) {
     if (is_local_source_ &&
         network_state_ == WebMediaPlayer::kNetworkStateLoading) {
diff --git a/cobalt/media/player/web_media_player_impl.h b/cobalt/media/player/web_media_player_impl.h
index 09ffa6c..c53b434 100644
--- a/cobalt/media/player/web_media_player_impl.h
+++ b/cobalt/media/player/web_media_player_impl.h
@@ -124,10 +124,7 @@
   // Playback controls.
   void Play() override;
   void Pause() override;
-  bool SupportsFullscreen() const override;
-  bool SupportsSave() const override;
   void Seek(float seconds) override;
-  void SetEndTime(float seconds) override;
   void SetRate(float rate) override;
   void SetVolume(float volume) override;
   void SetVisible(bool visible) override;
@@ -198,7 +195,6 @@
                        const std::string& message);
   void OnPipelineBufferingState(Pipeline::BufferingState buffering_state);
   void OnDemuxerOpened();
-  void SetOpaque(bool);
 
  private:
   // Called when the data source is downloading or paused.
@@ -293,19 +289,16 @@
     bool is_media_source;
   } state_;
 
-  WebMediaPlayerClient* client_;
-  WebMediaPlayerDelegate* delegate_;
-  bool allow_resume_after_suspend_;
+  WebMediaPlayerClient* const client_;
+  WebMediaPlayerDelegate* const delegate_;
+  const bool allow_resume_after_suspend_;
   scoped_refptr<VideoFrameProvider> video_frame_provider_;
 
   scoped_refptr<WebMediaPlayerProxy> proxy_;
 
   ::media::MediaLog* const media_log_;
 
-  bool incremented_externally_allocated_memory_;
-
   bool is_local_source_;
-  bool supports_save_;
 
   std::unique_ptr<::media::Demuxer> progressive_demuxer_;
   std::unique_ptr<::media::ChunkDemuxer> chunk_demuxer_;
@@ -315,9 +308,6 @@
   // unimportant.
   bool suppress_destruction_errors_;
 
-  base::Callback<void(base::TimeDelta*, bool*)>
-      media_time_and_seeking_state_cb_;
-
   DrmSystemReadyCB drm_system_ready_cb_;
   scoped_refptr<DrmSystem> drm_system_;
 
diff --git a/cobalt/media/player/web_media_player_proxy.cc b/cobalt/media/player/web_media_player_proxy.cc
index 9bfa385..350d71a 100644
--- a/cobalt/media/player/web_media_player_proxy.cc
+++ b/cobalt/media/player/web_media_player_proxy.cc
@@ -11,11 +11,6 @@
 namespace cobalt {
 namespace media {
 
-// Limits the maximum outstanding repaints posted on render thread.
-// This number of 50 is a guess, it does not take too much memory on the task
-// queue but gives up a pretty good latency on repaint.
-static const int kMaxOutstandingRepaints = 50;
-
 WebMediaPlayerProxy::WebMediaPlayerProxy(
     const scoped_refptr<base::SingleThreadTaskRunner>& render_loop,
     WebMediaPlayerImpl* webmediaplayer)
diff --git a/cobalt/media/progressive/progressive_demuxer.cc b/cobalt/media/progressive/progressive_demuxer.cc
index 36769ca..8599a31 100644
--- a/cobalt/media/progressive/progressive_demuxer.cc
+++ b/cobalt/media/progressive/progressive_demuxer.cc
@@ -27,9 +27,9 @@
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/media/base/data_source.h"
-#include "cobalt/media/base/starboard_utils.h"
 #include "starboard/types.h"
 #include "third_party/chromium/media/base/bind_to_current_loop.h"
+#include "third_party/chromium/media/base/starboard_utils.h"
 #include "third_party/chromium/media/base/timestamp_constants.h"
 
 namespace cobalt {
diff --git a/cobalt/media/sandbox/web_media_player_helper.cc b/cobalt/media/sandbox/web_media_player_helper.cc
index ad1fa4f..9de31c7 100644
--- a/cobalt/media/sandbox/web_media_player_helper.cc
+++ b/cobalt/media/sandbox/web_media_player_helper.cc
@@ -45,8 +45,7 @@
   void OutputModeChanged() override {}
   void ContentSizeChanged() override {}
   void PlaybackStateChanged() override {}
-  void SawUnsupportedTracks() override {}
-  float Volume() const override { return 1.f; }
+  float Volume() const override { return 1.0f; }
   void SourceOpened(::media::ChunkDemuxer* chunk_demuxer) override {
     DCHECK(!chunk_demuxer_open_cb_.is_null());
     chunk_demuxer_open_cb_.Run(chunk_demuxer);
diff --git a/cobalt/media_capture/BUILD.gn b/cobalt/media_capture/BUILD.gn
index 5334dbc..9e7ffc9 100644
--- a/cobalt/media_capture/BUILD.gn
+++ b/cobalt/media_capture/BUILD.gn
@@ -43,12 +43,12 @@
   deps = [
     "//base",
     "//cobalt/base",
-    "//cobalt/dom:dom_exception",
     "//cobalt/media",
     "//cobalt/media_stream",
     "//cobalt/script",
     "//cobalt/script:engine",
     "//cobalt/speech",
+    "//cobalt/web:dom_exception",
     "//starboard",
   ]
 }
@@ -65,12 +65,12 @@
   deps = [
     ":media_capture",
     "//cobalt/dom",
-    "//cobalt/dom:dom_exception",
     "//cobalt/dom/testing:dom_testing",
     "//cobalt/media_stream",
     "//cobalt/media_stream:media_stream_test_headers",
     "//cobalt/script",
     "//cobalt/test:run_all_unittests",
+    "//cobalt/web:dom_exception",
     "//testing/gmock",
     "//testing/gtest",
   ]
diff --git a/cobalt/media_capture/blob_event.h b/cobalt/media_capture/blob_event.h
index 0cca2e4..221d1ab 100644
--- a/cobalt/media_capture/blob_event.h
+++ b/cobalt/media_capture/blob_event.h
@@ -18,42 +18,42 @@
 #include <limits>
 #include <string>
 
-#include "cobalt/dom/blob.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/media_capture/blob_event_init.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/blob.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace media_capture {
 
 // Implements the WebAPI defined at
 // https://w3c.github.io/mediacapture-record/#blob-event
-class BlobEvent : public dom::Event {
+class BlobEvent : public web::Event {
  public:
   explicit BlobEvent(base::Token type,
                      const media_capture::BlobEventInit& blob_event_init)
-      : dom::Event(type) {
+      : web::Event(type) {
     Init(blob_event_init);
   }
   explicit BlobEvent(const std::string& type,
                      const media_capture::BlobEventInit& blob_event_init)
-      : dom::Event(type) {
+      : web::Event(type) {
     Init(blob_event_init);
   }
-  explicit BlobEvent(base::Token type, const scoped_refptr<dom::Blob>& blob,
+  explicit BlobEvent(base::Token type, const scoped_refptr<web::Blob>& blob,
                      double timecode)
-      : dom::Event(type), blob_(blob), timecode_(timecode) {}
+      : web::Event(type), blob_(blob), timecode_(timecode) {}
   explicit BlobEvent(const std::string& type,
-                     const scoped_refptr<dom::Blob>& blob, double timecode)
-      : dom::Event(type), blob_(blob), timecode_(timecode) {}
+                     const scoped_refptr<web::Blob>& blob, double timecode)
+      : web::Event(type), blob_(blob), timecode_(timecode) {}
 
-  scoped_refptr<dom::Blob> data() const { return blob_; }
+  scoped_refptr<web::Blob> data() const { return blob_; }
   double timecode() const { return timecode_; }
 
   DEFINE_WRAPPABLE_TYPE(BlobEvent);
 
   void TraceMembers(script::Tracer* tracer) override {
-    dom::Event::TraceMembers(tracer);
+    web::Event::TraceMembers(tracer);
     tracer->Trace(blob_);
   }
 
@@ -67,7 +67,7 @@
     }
   }
 
-  scoped_refptr<dom::Blob> blob_;
+  scoped_refptr<web::Blob> blob_;
   double timecode_ = std::numeric_limits<double>::quiet_NaN();
 };
 
diff --git a/cobalt/media_capture/media_devices.cc b/cobalt/media_capture/media_devices.cc
index f6cfb1e..c5ff88c 100644
--- a/cobalt/media_capture/media_devices.cc
+++ b/cobalt/media_capture/media_devices.cc
@@ -16,9 +16,9 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/media_capture/media_device_info.h"
 #include "cobalt/media_stream/media_stream.h"
 #include "cobalt/media_stream/media_track_settings.h"
@@ -26,6 +26,7 @@
 #include "cobalt/speech/microphone.h"
 #include "cobalt/speech/microphone_fake.h"
 #include "cobalt/speech/microphone_starboard.h"
+#include "cobalt/web/dom_exception.h"
 #include "starboard/common/string.h"
 
 namespace cobalt {
@@ -66,7 +67,7 @@
 
 MediaDevices::MediaDevices(script::EnvironmentSettings* settings,
                            script::ScriptValueFactory* script_value_factory)
-    : dom::EventTarget(settings),
+    : web::EventTarget(settings),
       settings_(base::polymorphic_downcast<dom::DOMSettings*>(settings)),
       script_value_factory_(script_value_factory),
       javascript_message_loop_(base::MessageLoop::current()),
@@ -184,7 +185,7 @@
 
   for (auto& promise : pending_microphone_promises_) {
     promise->value().Reject(
-        new dom::DOMException(dom::DOMException::kNotAllowedErr));
+        new web::DOMException(web::DOMException::kNotAllowedErr));
   }
   pending_microphone_promises_.clear();
 }
diff --git a/cobalt/media_capture/media_devices.h b/cobalt/media_capture/media_devices.h
index 4ec1d39..8ecd735 100644
--- a/cobalt/media_capture/media_devices.h
+++ b/cobalt/media_capture/media_devices.h
@@ -25,7 +25,6 @@
 #include "base/threading/thread_checker.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/dom/dom_settings.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/media_capture/media_device_info.h"
 #include "cobalt/media_stream/media_stream_audio_source.h"
 #include "cobalt/media_stream/media_stream_constraints.h"
@@ -37,6 +36,7 @@
 #include "cobalt/script/wrappable.h"
 #include "cobalt/speech/microphone.h"
 #include "cobalt/speech/microphone_manager.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace media_capture {
@@ -44,7 +44,7 @@
 // The MediaDevices object is the entry point to the API used to examine and
 // get access to media devices available to the User Agent.
 //   https://www.w3.org/TR/mediacapture-streams/#mediadevices
-class MediaDevices : public dom::EventTarget {
+class MediaDevices : public web::EventTarget {
  public:
   using MediaInfoSequence = script::Sequence<scoped_refptr<script::Wrappable>>;
   using MediaInfoSequencePromise = script::Promise<MediaInfoSequence>;
diff --git a/cobalt/media_capture/media_recorder.cc b/cobalt/media_capture/media_recorder.cc
index 11d6d86..7e868a5 100644
--- a/cobalt/media_capture/media_recorder.cc
+++ b/cobalt/media_capture/media_recorder.cc
@@ -27,8 +27,6 @@
 #include "base/strings/string_util_starboard.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/blob.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/media_capture/blob_event.h"
 #include "cobalt/media_capture/encoders/flac_audio_encoder.h"
 #include "cobalt/media_capture/encoders/linear16_audio_encoder.h"
@@ -37,7 +35,9 @@
 #include "cobalt/media_stream/media_stream_track.h"
 #include "cobalt/media_stream/media_track_settings.h"
 #include "cobalt/script/array_buffer.h"
+#include "cobalt/web/blob.h"
 #include "cobalt/web/context.h"
+#include "cobalt/web/dom_exception.h"
 #include "cobalt/web/environment_settings.h"
 
 namespace {
@@ -95,7 +95,7 @@
 
   // Step #3
   if (recording_state_ != kRecordingStateInactive) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              "Recording state must be inactive.",
                              exception_state);
     return;
@@ -107,7 +107,7 @@
   recording_state_ = kRecordingStateRecording;
 
   // Step 5.1.
-  DispatchEvent(new dom::Event(base::Tokens::start()));
+  DispatchEvent(new web::Event(base::Tokens::start()));
 
   // Steps 5.2-5.3, not needed for Cobalt.
 
@@ -155,7 +155,7 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   if (recording_state_ == kRecordingStateInactive) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              "Recording state must NOT be inactive.",
                              exception_state);
     return;
@@ -229,7 +229,7 @@
     const scoped_refptr<media_stream::MediaStream>& stream,
     const MediaRecorderOptions& options,
     script::ExceptionState* exception_state)
-    : dom::EventTarget(settings),
+    : web::EventTarget(settings),
       settings_(settings),
       stream_(stream),
       javascript_message_loop_(base::MessageLoop::current()->task_runner()),
@@ -242,7 +242,7 @@
 
   if (options.has_mime_type()) {
     if (!IsTypeSupported(options.mime_type())) {
-      dom::DOMException::Raise(dom::DOMException::kNotSupportedErr,
+      web::DOMException::Raise(web::DOMException::kNotSupportedErr,
                                exception_state);
       return;
     }
@@ -284,7 +284,7 @@
 
   timeslice_ = base::TimeDelta::FromSeconds(0);
   timeslice_unspecified_ = false;
-  DispatchEvent(new dom::Event(base::Tokens::stop()));
+  DispatchEvent(new web::Event(base::Tokens::stop()));
 }
 
 void MediaRecorder::UnsubscribeFromTrack() {
@@ -322,7 +322,7 @@
       buffer_.data(), buffer_.size());
 
   auto blob = base::WrapRefCounted(
-      new dom::Blob(settings_, array_buffer, blob_options_));
+      new web::Blob(settings_, array_buffer, blob_options_));
 
   DispatchEvent(new media_capture::BlobEvent(base::Tokens::dataavailable(),
                                              blob, timecode.ToInternalValue()));
diff --git a/cobalt/media_capture/media_recorder.h b/cobalt/media_capture/media_recorder.h
index 5883af8..56784d2 100644
--- a/cobalt/media_capture/media_recorder.h
+++ b/cobalt/media_capture/media_recorder.h
@@ -26,8 +26,6 @@
 #include "base/strings/string_piece.h"
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
-#include "cobalt/dom/blob_property_bag.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/media_capture/encoders/audio_encoder.h"
 #include "cobalt/media_capture/media_recorder_options.h"
 #include "cobalt/media_capture/recording_state.h"
@@ -37,6 +35,8 @@
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/exception_state.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/blob_property_bag.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace media_capture {
@@ -45,7 +45,7 @@
 // https://www.w3.org/TR/mediastream-recording/#mediarecorder-api
 class MediaRecorder : public media_stream::MediaStreamAudioSink,
                       public encoders::AudioEncoder::Listener,
-                      public dom::EventTarget {
+                      public web::EventTarget {
  public:
   // Constructors.
   explicit MediaRecorder(script::EnvironmentSettings* settings,
@@ -152,7 +152,7 @@
 
   script::EnvironmentSettings* settings_;
   std::string mime_type_;
-  dom::BlobPropertyBag blob_options_;
+  web::BlobPropertyBag blob_options_;
   scoped_refptr<media_stream::MediaStream> stream_;
 
   scoped_refptr<base::SingleThreadTaskRunner> javascript_message_loop_;
diff --git a/cobalt/media_capture/media_recorder_test.cc b/cobalt/media_capture/media_recorder_test.cc
index 46fab4a..92760e1 100644
--- a/cobalt/media_capture/media_recorder_test.cc
+++ b/cobalt/media_capture/media_recorder_test.cc
@@ -16,8 +16,6 @@
 
 #include <memory>
 
-#include "cobalt/dom/dom_exception.h"
-#include "cobalt/dom/testing/mock_event_listener.h"
 #include "cobalt/dom/testing/stub_window.h"
 #include "cobalt/media_capture/media_recorder_options.h"
 #include "cobalt/media_stream/media_stream.h"
@@ -26,10 +24,13 @@
 #include "cobalt/media_stream/testing/mock_media_stream_audio_track.h"
 #include "cobalt/script/testing/fake_script_value.h"
 #include "cobalt/script/testing/mock_exception_state.h"
+#include "cobalt/web/dom_exception.h"
+#include "cobalt/web/event_listener.h"
+#include "cobalt/web/testing/mock_event_listener.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using cobalt::dom::EventListener;
-using cobalt::dom::testing::MockEventListener;
+using cobalt::web::EventListener;
+using cobalt::web::testing::MockEventListener;
 using cobalt::script::testing::FakeScriptValue;
 using cobalt::script::testing::MockExceptionState;
 using ::testing::_;
@@ -142,9 +143,9 @@
   media_recorder_->Start(&exception_state_);
 
   ASSERT_TRUE(exception.get());
-  dom::DOMException& dom_exception(
-      *base::polymorphic_downcast<dom::DOMException*>(exception.get()));
-  EXPECT_EQ(dom::DOMException::kInvalidStateErr, dom_exception.code());
+  web::DOMException& dom_exception(
+      *base::polymorphic_downcast<web::DOMException*>(exception.get()));
+  EXPECT_EQ(web::DOMException::kInvalidStateErr, dom_exception.code());
   EXPECT_EQ(dom_exception.message(), "Recording state must be inactive.");
   EXPECT_CALL(*audio_track_, Stop());
 }
@@ -161,19 +162,19 @@
   media_recorder_->Stop(&exception_state_);
 
   ASSERT_TRUE(exception.get());
-  dom::DOMException& dom_exception(
-      *base::polymorphic_downcast<dom::DOMException*>(exception.get()));
-  EXPECT_EQ(dom::DOMException::kInvalidStateErr, dom_exception.code());
+  web::DOMException& dom_exception(
+      *base::polymorphic_downcast<web::DOMException*>(exception.get()));
+  EXPECT_EQ(web::DOMException::kInvalidStateErr, dom_exception.code());
   EXPECT_EQ(dom_exception.message(), "Recording state must NOT be inactive.");
 }
 
 TEST_F(MediaRecorderTest, RecordL16Frames) {
   std::unique_ptr<MockEventListener> listener = MockEventListener::Create();
-  FakeScriptValue<EventListener> script_object(listener.get());
+  FakeScriptValue<web::EventListener> script_object(listener.get());
   media_recorder_->set_ondataavailable(script_object);
   EXPECT_CALL(*listener,
               HandleEvent(Eq(media_recorder_),
-                          Pointee(Property(&dom::Event::type,
+                          Pointee(Property(&web::Event::type,
                                            base::Tokens::dataavailable())),
                           _));
 
@@ -206,11 +207,11 @@
 
 TEST_F(MediaRecorderTest, DifferentThreadForAudioSource) {
   std::unique_ptr<MockEventListener> listener = MockEventListener::Create();
-  FakeScriptValue<EventListener> script_object(listener.get());
+  FakeScriptValue<web::EventListener> script_object(listener.get());
   media_recorder_->set_ondataavailable(script_object);
   EXPECT_CALL(*listener,
               HandleEvent(Eq(media_recorder_),
-                          Pointee(Property(&dom::Event::type,
+                          Pointee(Property(&web::Event::type,
                                            base::Tokens::dataavailable())),
                           _));
 
@@ -239,12 +240,12 @@
 
 TEST_F(MediaRecorderTest, StartEvent) {
   std::unique_ptr<MockEventListener> listener = MockEventListener::Create();
-  FakeScriptValue<EventListener> script_object(listener.get());
+  FakeScriptValue<web::EventListener> script_object(listener.get());
   media_recorder_->set_onstart(script_object);
   EXPECT_CALL(
       *listener,
       HandleEvent(Eq(media_recorder_),
-                  Pointee(Property(&dom::Event::type, base::Tokens::start())),
+                  Pointee(Property(&web::Event::type, base::Tokens::start())),
                   _));
 
   media_recorder_->Start(&exception_state_);
@@ -253,14 +254,14 @@
 
 TEST_F(MediaRecorderTest, StopEvent) {
   std::unique_ptr<MockEventListener> listener = MockEventListener::Create();
-  FakeScriptValue<EventListener> script_object(listener.get());
+  FakeScriptValue<web::EventListener> script_object(listener.get());
   media_recorder_->Start(&exception_state_);
   media_recorder_->set_onstop(script_object);
 
   EXPECT_CALL(
       *listener,
       HandleEvent(Eq(media_recorder_),
-                  Pointee(Property(&dom::Event::type, base::Tokens::stop())),
+                  Pointee(Property(&web::Event::type, base::Tokens::stop())),
                   _));
 
   EXPECT_CALL(*audio_track_, Stop());
diff --git a/cobalt/media_integration_tests/functionality/general_playback.py b/cobalt/media_integration_tests/functionality/general_playback.py
index 46d633d..95b1e1a 100644
--- a/cobalt/media_integration_tests/functionality/general_playback.py
+++ b/cobalt/media_integration_tests/functionality/general_playback.py
@@ -16,7 +16,7 @@
 import logging
 
 from cobalt.media_integration_tests.test_case import TestCase
-from cobalt.media_integration_tests.test_util import MimeStrings, PlaybackUrls
+from cobalt.media_integration_tests.test_util import PlaybackUrls
 
 
 class GeneralPlaybackTest(TestCase):
@@ -40,18 +40,19 @@
 
 TEST_PARAMETERS = [
     ('H264', PlaybackUrls.H264_ONLY, None),
-    ('PROGRESSIVE', PlaybackUrls.PROGRESSIVE, None),
-    ('ENCRYPTED', PlaybackUrls.ENCRYPTED, None),
-    ('VR', PlaybackUrls.VR, None),
-    ('VP9', PlaybackUrls.VP9, MimeStrings.VP9),
-    ('VP9_HFR', PlaybackUrls.VP9_HFR, MimeStrings.VP9_HFR),
-    ('AV1', PlaybackUrls.AV1, MimeStrings.AV1),
-    ('AV1_HFR', PlaybackUrls.AV1_HFR, MimeStrings.AV1_HFR),
-    ('VERTICAL', PlaybackUrls.VERTICAL, None),
-    ('SHORT', PlaybackUrls.SHORT, None),
-    ('VP9_HDR_HLG', PlaybackUrls.VP9_HDR_HLG, MimeStrings.VP9_HDR_HLG),
-    ('VP9_HDR_PQ', PlaybackUrls.VP9_HDR_PQ, MimeStrings.VP9_HDR_PQ),
-    ('HDR_PQ_HFR', PlaybackUrls.HDR_PQ_HFR, MimeStrings.VP9_HDR_PQ_HFR),
+    # TODO(b/223856877) -- renable these when tests are stable.
+    #('PROGRESSIVE', PlaybackUrls.PROGRESSIVE, None),
+    #('ENCRYPTED', PlaybackUrls.ENCRYPTED, None),
+    #('VR', PlaybackUrls.VR, None),
+    #('VP9', PlaybackUrls.VP9, MimeStrings.VP9),
+    #('VP9_HFR', PlaybackUrls.VP9_HFR, MimeStrings.VP9_HFR),
+    #('AV1', PlaybackUrls.AV1, MimeStrings.AV1),
+    #('AV1_HFR', PlaybackUrls.AV1_HFR, MimeStrings.AV1_HFR),
+    #('VERTICAL', PlaybackUrls.VERTICAL, None),
+    #('SHORT', PlaybackUrls.SHORT, None),
+    #('VP9_HDR_HLG', PlaybackUrls.VP9_HDR_HLG, MimeStrings.VP9_HDR_HLG),
+    #('VP9_HDR_PQ', PlaybackUrls.VP9_HDR_PQ, MimeStrings.VP9_HDR_PQ),
+    #('HDR_PQ_HFR', PlaybackUrls.HDR_PQ_HFR, MimeStrings.VP9_HDR_PQ_HFR),
 ]
 
 for name, playback_url, mime_str in TEST_PARAMETERS:
diff --git a/cobalt/media_integration_tests/test_app.py b/cobalt/media_integration_tests/test_app.py
index a0f1b02..ba65c1d 100644
--- a/cobalt/media_integration_tests/test_app.py
+++ b/cobalt/media_integration_tests/test_app.py
@@ -56,9 +56,8 @@
   if value_type in (int, float) and default_value_type in (int, float):
     return value
 
-  # CVal returns unicode, so we need to decode them before using.
   if default_value_type in (bool, int, float):
-    if value_type in (str, unicode):  # pylint: disable=undefined-variable
+    if isinstance(value, str):
       try:
         # Try to parse numbers and booleans.
         parsed_value = json.loads(value)
@@ -67,10 +66,6 @@
 
       return parsed_value
 
-  elif default_value_type is str:
-    if value_type == unicode:  # pylint: disable=undefined-variable
-      return value.encode('utf-8')
-
   raise NotImplementedError('Convertion from (%s) to (%s) is not supported.' %
                             (value_type, default_value_type))
 
@@ -121,7 +116,7 @@
 
 
 class ApplicationStateHandler():
-  """The handler of applicatoin state."""
+  """The handler of application state."""
 
   def __init__(self, app):
     self.app = app
@@ -209,7 +204,8 @@
     # If |pipeline_status| is not 0, it indicates there's an error
     # in the pipeline.
     self.pipeline_status = GetValueFromQueryResult(query_result,
-                                                   'pipeline_status', 0)
+                                                   'pipeline_status',
+                                                   'PIPELINE_OK')
     self.error_message = GetValueFromQueryResult(query_result, 'error_message',
                                                  '')
 
diff --git a/cobalt/media_session/media_session_client.cc b/cobalt/media_session/media_session_client.cc
index f62411e..e668cae 100644
--- a/cobalt/media_session/media_session_client.cc
+++ b/cobalt/media_session/media_session_client.cc
@@ -20,14 +20,15 @@
 #include <string>
 
 #include "base/logging.h"
+#include "cobalt/media_session/media_image.h"
 #include "cobalt/script/sequence.h"
 #include "starboard/time.h"
 
-using MediaImageSequence = ::cobalt::script::Sequence<MediaImage>;
-
 namespace cobalt {
 namespace media_session {
 
+using MediaImageSequence = ::cobalt::script::Sequence<MediaImage>;
+
 namespace {
 
 // Delay to re-query position state after an action has been invoked.
diff --git a/cobalt/media_stream/media_stream.h b/cobalt/media_stream/media_stream.h
index b0c658c..46243c9 100644
--- a/cobalt/media_stream/media_stream.h
+++ b/cobalt/media_stream/media_stream.h
@@ -15,27 +15,29 @@
 #ifndef COBALT_MEDIA_STREAM_MEDIA_STREAM_H_
 #define COBALT_MEDIA_STREAM_MEDIA_STREAM_H_
 
+#include <utility>
+
 #include "base/memory/ref_counted.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/media_stream/media_stream_track.h"
 #include "cobalt/script/sequence.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace media_stream {
 
 // This class represents a MediaStream, and implements the specification at:
 // https://www.w3.org/TR/mediacapture-streams/#dom-mediastream
-class MediaStream : public dom::EventTarget {
+class MediaStream : public web::EventTarget {
  public:
   using TrackSequences = script::Sequence<scoped_refptr<MediaStreamTrack>>;
 
   // Constructors.
   explicit MediaStream(script::EnvironmentSettings* settings)
-      : dom::EventTarget(settings) {}
+      : web::EventTarget(settings) {}
 
   MediaStream(script::EnvironmentSettings* settings, TrackSequences tracks)
-      : dom::EventTarget(settings), tracks_(std::move(tracks)) {}
+      : web::EventTarget(settings), tracks_(std::move(tracks)) {}
 
   // Functions.
   script::Sequence<scoped_refptr<MediaStreamTrack>>& GetAudioTracks() {
@@ -47,7 +49,7 @@
   }
 
   void TraceMembers(script::Tracer* tracer) override {
-    EventTarget::TraceMembers(tracer);
+    web::EventTarget::TraceMembers(tracer);
     tracer->TraceItems(tracks_);
   }
 
diff --git a/cobalt/media_stream/media_stream_audio_track.h b/cobalt/media_stream/media_stream_audio_track.h
index f1b9b7d..3f82ff5 100644
--- a/cobalt/media_stream/media_stream_audio_track.h
+++ b/cobalt/media_stream/media_stream_audio_track.h
@@ -18,7 +18,6 @@
 #include "base/callback.h"
 #include "base/strings/string_piece.h"
 #include "base/threading/thread_checker.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/media/base/audio_bus.h"
 #include "cobalt/media_stream/audio_parameters.h"
 #include "cobalt/media_stream/media_stream_audio_deliverer.h"
@@ -26,6 +25,7 @@
 #include "cobalt/media_stream/media_stream_track.h"
 #include "cobalt/media_stream/media_track_settings.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 
diff --git a/cobalt/media_stream/media_stream_track.h b/cobalt/media_stream/media_stream_track.h
index 8b27a6d..4dce9bd 100644
--- a/cobalt/media_stream/media_stream_track.h
+++ b/cobalt/media_stream/media_stream_track.h
@@ -17,9 +17,9 @@
 
 #include "base/callback.h"
 #include "base/strings/string_piece.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/media_stream/media_track_settings.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/web/event_target.h"
 #include "starboard/common/mutex.h"
 
 namespace cobalt {
@@ -27,7 +27,7 @@
 
 // This class represents a MediaStreamTrack, and implements the specification
 // at: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack
-class MediaStreamTrack : public dom::EventTarget {
+class MediaStreamTrack : public web::EventTarget {
  public:
   enum ReadyState {
     kReadyStateLive,
@@ -35,7 +35,7 @@
   };
 
   explicit MediaStreamTrack(script::EnvironmentSettings* settings)
-      : EventTarget(settings) {}
+      : web::EventTarget(settings) {}
 
   // Function exposed to JavaScript via IDL.
   const MediaTrackSettings& GetSettings() const {
diff --git a/cobalt/renderer/pipeline.cc b/cobalt/renderer/pipeline.cc
index ebe5e14..4815da9 100644
--- a/cobalt/renderer/pipeline.cc
+++ b/cobalt/renderer/pipeline.cc
@@ -211,7 +211,7 @@
 Pipeline::~Pipeline() {
   // Unregisters Pipeline as a watchdog client.
   watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
-  if (watchdog) watchdog->Unregister(std::string(kWatchdogName));
+  if (watchdog) watchdog->Unregister(kWatchdogName);
 
   TRACE_EVENT0("cobalt::renderer", "Pipeline::~Pipeline()");
 
@@ -337,9 +337,9 @@
   // Registers Pipeline as a watchdog client.
   watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
   if (watchdog)
-    watchdog->Register(std::string(kWatchdogName),
-                       base::kApplicationStateStarted, kWatchdogTimeInterval,
-                       kWatchdogTimeWait, watchdog::PING);
+    watchdog->Register(kWatchdogName, base::kApplicationStateStarted,
+                       kWatchdogTimeInterval, kWatchdogTimeWait,
+                       watchdog::PING);
 
   // If a time fence is active, save the submission to be queued only after
   // we pass the time fence.  Overwrite any existing waiting submission in this
@@ -390,7 +390,7 @@
 
   // Unregisters Pipeline as a watchdog client.
   watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
-  if (watchdog) watchdog->Unregister(std::string(kWatchdogName));
+  if (watchdog) watchdog->Unregister(kWatchdogName);
 
   ResetSubmissionQueue();
   rasterize_timer_ = base::nullopt;
@@ -403,7 +403,13 @@
 
   // Pings watchdog.
   watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
-  if (watchdog) watchdog->Ping(std::string(kWatchdogName));
+  if (watchdog) {
+#if defined(_DEBUG)
+    // Injects delay based off of environment variables for watchdog debugging.
+    watchdog->MaybeInjectDebugDelay(kWatchdogName);
+#endif  // defined(_DEBUG)
+    watchdog->Ping(kWatchdogName);
+  }
 
   base::TimeTicks start_rasterize_time = base::TimeTicks::Now();
   Submission submission =
@@ -668,10 +674,14 @@
   render_tree::ColorRGBA clear_color;
   if (render_target_ && clear_on_shutdown_mode_ == kClearAccordingToPlatform &&
       ShouldClearFrameOnShutdown(&clear_color)) {
-    rasterizer_->Submit(
-        new render_tree::ClearRectNode(math::RectF(render_target_->GetSize()),
-                                       clear_color),
-        render_target_);
+    // Only clear the frame if anything was previoously rendered. Otherwise,
+    // this call may unnecessarily initialize hardware rasterizer components.
+    if (!last_rasterize_time_.is_null()) {
+      rasterizer_->Submit(
+          new render_tree::ClearRectNode(math::RectF(render_target_->GetSize()),
+                                         clear_color),
+          render_target_);
+    }
   }
 
   // This potential reference to a render tree whose animations may have ended
diff --git a/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc b/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
index 84b5a71..6b7a91e 100644
--- a/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
+++ b/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
@@ -203,9 +203,9 @@
       }
     } break;
     case kSbDecodeTargetFormat3Plane10BitYUVI420:
-#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+#if SB_API_VERSION >= 14
     case kSbDecodeTargetFormat3Plane10BitYUVI420Compact:
-#endif  // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+#endif  // SB_API_VERSION >= 14
     case kSbDecodeTargetFormat3PlaneYUVI420: {
       DCHECK_LT(plane, 3);
 #if defined(GL_RED_EXT)
@@ -234,11 +234,11 @@
     case kSbDecodeTargetFormat3Plane10BitYUVI420: {
       return render_tree::kMultiPlaneImageFormatYUV3Plane10BitBT2020;
     } break;
-#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+#if SB_API_VERSION >= 14
     case kSbDecodeTargetFormat3Plane10BitYUVI420Compact: {
       return render_tree::kMultiPlaneImageFormatYUV3Plane10BitCompactedBT2020;
     }
-#endif  // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+#endif  // SB_API_VERSION >= 14
     default: { NOTREACHED(); }
   }
   return render_tree::kMultiPlaneImageFormatYUV2PlaneBT709;
diff --git a/cobalt/script/environment_settings.h b/cobalt/script/environment_settings.h
index 15a6dd4..9a28715 100644
--- a/cobalt/script/environment_settings.h
+++ b/cobalt/script/environment_settings.h
@@ -42,6 +42,9 @@
   //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#concept-environment-creation-url
   const GURL& creation_url() const { return base_url(); }
 
+  // https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-origin
+  const GURL GetOrigin() const { return creation_url().GetOrigin(); }
+
   const base::DebuggerHooks& debugger_hooks() const { return debugger_hooks_; }
 
  protected:
diff --git a/cobalt/script/fake_global_environment.h b/cobalt/script/fake_global_environment.h
index 312cbc8..d1d1cfb 100644
--- a/cobalt/script/fake_global_environment.h
+++ b/cobalt/script/fake_global_environment.h
@@ -26,6 +26,8 @@
 class FakeGlobalEnvironment : public GlobalEnvironment {
  public:
   void CreateGlobalObject() override {}
+  Wrappable* global_wrappable() const override { return nullptr; };
+
   bool EvaluateScript(const scoped_refptr<SourceCode>& script_utf8,
                       std::string* out_result) override {
     return false;
diff --git a/cobalt/script/global_environment.h b/cobalt/script/global_environment.h
index 97b7093..209a2ae 100644
--- a/cobalt/script/global_environment.h
+++ b/cobalt/script/global_environment.h
@@ -56,6 +56,9 @@
   // Create a new global object with no bindings.
   virtual void CreateGlobalObject() = 0;
 
+  // Returns the global object if it is a wrappable.
+  virtual Wrappable* global_wrappable() const = 0;
+
   // Evaluate the JavaScript source code. Returns true on success,
   // false if there is an exception.
   // If out_result is non-NULL, it will be set to hold the result of the script
diff --git a/cobalt/script/script_value_factory_instantiations.h b/cobalt/script/script_value_factory_instantiations.h
index dc0cab0..ad37696 100644
--- a/cobalt/script/script_value_factory_instantiations.h
+++ b/cobalt/script/script_value_factory_instantiations.h
@@ -24,7 +24,7 @@
 namespace cobalt {
 namespace script {
 
-// TODO: Move dom::BufferSource to script::BufferSource and include here
+// TODO: Move web::BufferSource to script::BufferSource and include here
 using BufferSource = script::UnionType2<script::Handle<script::ArrayBufferView>,
                                         script::Handle<script::ArrayBuffer>>;
 
diff --git a/starboard/sabi/sabi.py b/cobalt/script/testing/BUILD.gn
similarity index 60%
copy from starboard/sabi/sabi.py
copy to cobalt/script/testing/BUILD.gn
index 5dca3b9..ea566c3 100644
--- a/starboard/sabi/sabi.py
+++ b/cobalt/script/testing/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2020 The Cobalt Authors. All Rights Reserved.
+# Copyright 2021 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.
@@ -11,6 +11,21 @@
 # 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.
-"""Source of truth of the default Starboard API version."""
 
-SB_API_VERSION = 14
+static_library("script_testing") {
+  testonly = true
+  has_pedantic_warnings = true
+
+  sources = [
+    "stub_script_runner.cc",
+    "stub_script_runner.h",
+  ]
+
+  deps = [
+    "//base",
+    "//base/test:test_support",
+    "//cobalt/script",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
diff --git a/cobalt/dom/testing/stub_script_runner.cc b/cobalt/script/testing/stub_script_runner.cc
similarity index 90%
rename from cobalt/dom/testing/stub_script_runner.cc
rename to cobalt/script/testing/stub_script_runner.cc
index 992bf2b..5c06012 100644
--- a/cobalt/dom/testing/stub_script_runner.cc
+++ b/cobalt/script/testing/stub_script_runner.cc
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/testing/stub_script_runner.h"
+#include "cobalt/script/testing/stub_script_runner.h"
 
 namespace cobalt {
-namespace dom {
+namespace script {
 namespace testing {
 
 std::string StubScriptRunner::Execute(
@@ -28,5 +28,5 @@
 }
 
 }  // namespace testing
-}  // namespace dom
+}  // namespace script
 }  // namespace cobalt
diff --git a/cobalt/dom/testing/stub_script_runner.h b/cobalt/script/testing/stub_script_runner.h
similarity index 83%
rename from cobalt/dom/testing/stub_script_runner.h
rename to cobalt/script/testing/stub_script_runner.h
index f1c7077..bcdec70 100644
--- a/cobalt/dom/testing/stub_script_runner.h
+++ b/cobalt/script/testing/stub_script_runner.h
@@ -12,15 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_TESTING_STUB_SCRIPT_RUNNER_H_
-#define COBALT_DOM_TESTING_STUB_SCRIPT_RUNNER_H_
+#ifndef COBALT_SCRIPT_TESTING_STUB_SCRIPT_RUNNER_H_
+#define COBALT_SCRIPT_TESTING_STUB_SCRIPT_RUNNER_H_
 
 #include <string>
 
 #include "cobalt/script/script_runner.h"
 
 namespace cobalt {
-namespace dom {
+namespace script {
 namespace testing {
 
 class StubScriptRunner : public script::ScriptRunner {
@@ -31,7 +31,7 @@
 };
 
 }  // namespace testing
-}  // namespace dom
+}  // namespace script
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_TESTING_STUB_SCRIPT_RUNNER_H_
+#endif  // COBALT_SCRIPT_TESTING_STUB_SCRIPT_RUNNER_H_
diff --git a/cobalt/script/v8c/BUILD.gn b/cobalt/script/v8c/BUILD.gn
index 230516a..74add0b 100644
--- a/cobalt/script/v8c/BUILD.gn
+++ b/cobalt/script/v8c/BUILD.gn
@@ -124,6 +124,7 @@
 
   deps = [
     ":embed_v8c_resources_as_header_files",
+    "//cobalt/cache",
     "//cobalt/configuration",
     "//cobalt/script",
     "//nb",
diff --git a/cobalt/script/v8c/union_type_conversion_impl.h b/cobalt/script/v8c/union_type_conversion_impl.h
index 32602fa..87f2b1b 100644
--- a/cobalt/script/v8c/union_type_conversion_impl.h
+++ b/cobalt/script/v8c/union_type_conversion_impl.h
@@ -57,7 +57,7 @@
   // https://www.w3.org/TR/WebIDL-1/#es-union
   // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
+  // TODO: Support Date, RegExp, web::DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -122,8 +122,8 @@
   //      the object V.
   // Not implemented
 
-  // 5. If V is a DOMException platform object, then:
-  // 1. If types includes DOMException or Error, then return the result of
+  // 5. If V is a web::DOMException platform object, then:
+  // 1. If types includes web::DOMException or Error, then return the result of
   // converting
   //      V to that type.
   // Not implemented
@@ -350,7 +350,7 @@
   // https://www.w3.org/TR/WebIDL-1/#es-union
   // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
+  // TODO: Support Date, RegExp, web::DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -425,8 +425,8 @@
   //      the object V.
   // Not implemented
 
-  // 5. If V is a DOMException platform object, then:
-  // 1. If types includes DOMException or Error, then return the result of
+  // 5. If V is a web::DOMException platform object, then:
+  // 1. If types includes web::DOMException or Error, then return the result of
   // converting
   //      V to that type.
   // Not implemented
@@ -711,7 +711,7 @@
   // https://www.w3.org/TR/WebIDL-1/#es-union
   // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
+  // TODO: Support Date, RegExp, web::DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -796,8 +796,8 @@
   //      the object V.
   // Not implemented
 
-  // 5. If V is a DOMException platform object, then:
-  // 1. If types includes DOMException or Error, then return the result of
+  // 5. If V is a web::DOMException platform object, then:
+  // 1. If types includes web::DOMException or Error, then return the result of
   // converting
   //      V to that type.
   // Not implemented
@@ -1140,7 +1140,7 @@
   // https://www.w3.org/TR/WebIDL-1/#es-union
   // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
+  // TODO: Support Date, RegExp, web::DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -1235,8 +1235,8 @@
   //      the object V.
   // Not implemented
 
-  // 5. If V is a DOMException platform object, then:
-  // 1. If types includes DOMException or Error, then return the result of
+  // 5. If V is a web::DOMException platform object, then:
+  // 1. If types includes web::DOMException or Error, then return the result of
   // converting
   //      V to that type.
   // Not implemented
diff --git a/cobalt/script/v8c/union_type_conversion_impl.h.pump b/cobalt/script/v8c/union_type_conversion_impl.h.pump
index 8060890..ad6f1bf 100644
--- a/cobalt/script/v8c/union_type_conversion_impl.h.pump
+++ b/cobalt/script/v8c/union_type_conversion_impl.h.pump
@@ -69,7 +69,7 @@
   // https://www.w3.org/TR/WebIDL-1/#es-union
   // ( Draft: http://heycam.github.io/webidl/#es-union )
 
-  // TODO: Support Date, RegExp, DOMException, Error, callback functions.
+  // TODO: Support Date, RegExp, web::DOMException, Error, callback functions.
 
   // 1. If the union type includes a nullable type and |V| is null or undefined,
   // then return the IDL value null.
@@ -133,8 +133,8 @@
   //      the object V.
   // Not implemented
 
-  // 5. If V is a DOMException platform object, then:
-  //   1. If types includes DOMException or Error, then return the result of converting
+  // 5. If V is a web::DOMException platform object, then:
+  //   1. If types includes web::DOMException or Error, then return the result of converting
   //      V to that type.
   // Not implemented
 
diff --git a/cobalt/script/v8c/v8c_global_environment.cc b/cobalt/script/v8c/v8c_global_environment.cc
index 1e790d0..16ae2f1 100644
--- a/cobalt/script/v8c/v8c_global_environment.cc
+++ b/cobalt/script/v8c/v8c_global_environment.cc
@@ -21,6 +21,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/cache/cache.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/script/v8c/embedded_resources.h"
 #include "cobalt/script/v8c/entry_scope.h"
@@ -421,10 +422,33 @@
 
   // Note that we expect an |EntryScope| and |v8::TryCatch| to have been set
   // up by our caller.
+
+  v8::Local<v8::Script> script;
+  if (!CompileWithCaching(source_code).ToLocal(&script)) {
+    return {};
+  }
+
+  TRACE_EVENT0("cobalt::script", "v8::Script::Run()");
+  return script->Run(isolate_->GetCurrentContext());
+}
+
+v8::MaybeLocal<v8::Script> V8cGlobalEnvironment::CompileWithCaching(
+    const scoped_refptr<SourceCode>& source_code) {
+  TRACE_EVENT0("cobalt::script", "V8cGlobalEnvironment::CompileWithCaching()");
+  v8::Local<v8::Context> context = isolate_->GetCurrentContext();
   V8cSourceCode* v8c_source_code =
       base::polymorphic_downcast<V8cSourceCode*>(source_code.get());
   const base::SourceLocation& source_location = v8c_source_code->location();
 
+  v8::Local<v8::String> source;
+  if (!v8::String::NewFromUtf8(isolate_, v8c_source_code->source_utf8().c_str(),
+                               v8::NewStringType::kNormal,
+                               v8c_source_code->source_utf8().length())
+           .ToLocal(&source)) {
+    LOG(WARNING) << "Failed to convert source code to V8 UTF-8 string.";
+    return {};
+  }
+
   v8::Local<v8::String> resource_name;
   if (!v8::String::NewFromUtf8(isolate_, source_location.file_path.c_str(),
                                v8::NewStringType::kNormal)
@@ -447,66 +471,7 @@
       /*resource_is_shared_cross_origin=*/
       v8::Boolean::New(isolate_, v8c_source_code->is_muted()));
 
-  v8::Local<v8::String> source;
-  if (!v8::String::NewFromUtf8(isolate_, v8c_source_code->source_utf8().c_str(),
-                               v8::NewStringType::kNormal,
-                               v8c_source_code->source_utf8().length())
-           .ToLocal(&source)) {
-    LOG(WARNING) << "Failed to convert source code to V8 UTF-8 string.";
-    return {};
-  }
-
-  v8::Local<v8::Context> context = isolate_->GetCurrentContext();
-  v8::Local<v8::Script> script;
-
-  bool used_cache = false;
-  const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension =
-      static_cast<const CobaltExtensionJavaScriptCacheApi*>(
-          SbSystemGetExtension(kCobaltExtensionJavaScriptCacheName));
-  if (javascript_cache_extension &&
-      strcmp(javascript_cache_extension->name,
-             kCobaltExtensionJavaScriptCacheName) == 0 &&
-      javascript_cache_extension->version >= 1) {
-    TRACE_EVENT0("cobalt::script",
-                 "V8cGlobalEnvironment::CompileWithCaching()");
-    if (CompileWithCaching(javascript_cache_extension, context, v8c_source_code,
-                           source, &script_origin)
-            .ToLocal(&script)) {
-      used_cache = true;
-    } else {
-      LOG(WARNING) << "Failed to compile script.";
-      return {};
-    }
-  }
-  if (!used_cache) {
-    TRACE_EVENT0("cobalt::script", "v8::Script::Compile()");
-    if (!v8::Script::Compile(context, source, &script_origin)
-             .ToLocal(&script)) {
-      LOG(WARNING) << "Failed to compile script.";
-      return {};
-    }
-  }
-
-  v8::Local<v8::Value> result;
-  {
-    TRACE_EVENT0("cobalt::script", "v8::Script::Run()");
-    if (!script->Run(context).ToLocal(&result)) {
-      LOG(WARNING) << "Failed to run script.";
-      return {};
-    }
-  }
-
-  return result;
-}
-
-v8::MaybeLocal<v8::Script> V8cGlobalEnvironment::CompileWithCaching(
-    const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension,
-    v8::Local<v8::Context> context, V8cSourceCode* v8c_source_code,
-    v8::Local<v8::String> source, v8::ScriptOrigin* script_origin) {
-  const base::SourceLocation& source_location = v8c_source_code->location();
-
-  DLOG(INFO) << "CompileWithCaching: " << source_location.file_path.c_str()
-             << " " << v8c_source_code->location().file_path;
+  DLOG(INFO) << "CompileWithCaching: " << v8c_source_code->location().file_path;
 
   bool successful_cache = false;
 
@@ -515,19 +480,18 @@
   uint32_t javascript_cache_key = CreateJavaScriptCacheKey(
       javascript_engine_version, v8::ScriptCompiler::CachedDataVersionTag(),
       v8c_source_code->source_utf8(), source_location.file_path);
-  const uint8_t* cache_data_buf = nullptr;
-  int cache_data_size = -1;
+  base::Optional<std::vector<uint8_t>> cached_data =
+      cobalt::cache::Cache::GetInstance()->Retrieve(
+          javascript_cache_key, disk_cache::ResourceType::kCompiledScript);
   v8::Local<v8::Script> script;
-  if (javascript_cache_extension->GetCachedScript(
-          javascript_cache_key, v8c_source_code->source_utf8().size(),
-          &cache_data_buf, &cache_data_size)) {
-    DLOG(INFO) << "CompileWithCaching:  Using cached resource";
+  if (cached_data) {
+    DLOG(INFO) << "CompileWithCaching: Using cached resource";
     v8::ScriptCompiler::CachedData* cached_code =
         new v8::ScriptCompiler::CachedData(
-            cache_data_buf, cache_data_size,
+            cached_data->data(), cached_data->size(),
             v8::ScriptCompiler::CachedData::BufferNotOwned);
     // The script_source owns the cached_code object.
-    v8::ScriptCompiler::Source script_source(source, *script_origin,
+    v8::ScriptCompiler::Source script_source(source, script_origin,
                                              cached_code);
 
     {
@@ -545,16 +509,11 @@
     }
   }
 
-  if (cache_data_buf != nullptr) {
-    javascript_cache_extension->ReleaseCachedScriptData(cache_data_buf);
-    cache_data_buf = nullptr;
-  }
-
   if (!successful_cache) {
     DLOG(INFO) << "CompileWithCaching: compile";
     {
       TRACE_EVENT0("cobalt::script", "v8::Script::Compile()");
-      if (!v8::Script::Compile(context, source, script_origin)
+      if (!v8::Script::Compile(context, source, &script_origin)
                .ToLocal(&script)) {
         LOG(WARNING) << "CompileWithCaching: Failed to compile script.";
         return {};
@@ -562,9 +521,10 @@
     }
     std::unique_ptr<v8::ScriptCompiler::CachedData> cached_data(
         v8::ScriptCompiler::CreateCodeCache(script->GetUnboundScript()));
-    if (!javascript_cache_extension->StoreCachedScript(
-            javascript_cache_key, v8c_source_code->source_utf8().size(),
-            cached_data->data, cached_data->length)) {
+    if (!cobalt::cache::Cache::GetInstance()->Store(
+            javascript_cache_key, disk_cache::ResourceType::kCompiledScript,
+            std::vector<uint8_t>(cached_data->data,
+                                 cached_data->data + cached_data->length))) {
       LOG(WARNING) << "CompileWithCaching: Failed to store cached script";
     }
   }
diff --git a/cobalt/script/v8c/v8c_global_environment.h b/cobalt/script/v8c/v8c_global_environment.h
index c0f6a81..fd4b772 100644
--- a/cobalt/script/v8c/v8c_global_environment.h
+++ b/cobalt/script/v8c/v8c_global_environment.h
@@ -26,7 +26,6 @@
 #include "base/optional.h"
 #include "base/stl_util.h"
 #include "base/threading/thread_checker.h"
-#include "cobalt/extension/javascript_cache.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/script/v8c/v8c_heap_tracer.h"
@@ -123,7 +122,9 @@
 
   WrapperFactory* wrapper_factory() const { return wrapper_factory_.get(); }
 
-  Wrappable* global_wrappable() const { return global_wrappable_.get(); }
+  Wrappable* global_wrappable() const override {
+    return global_wrappable_.get();
+  }
 
   EnvironmentSettings* GetEnvironmentSettings() const {
     return environment_settings_;
@@ -152,9 +153,7 @@
       const scoped_refptr<SourceCode>& source_code);
 
   v8::MaybeLocal<v8::Script> CompileWithCaching(
-      const CobaltExtensionJavaScriptCacheApi* javascript_cache_extension,
-      v8::Local<v8::Context> context, V8cSourceCode* v8c_source_code,
-      v8::Local<v8::String> source, v8::ScriptOrigin* script_origin);
+      const scoped_refptr<SourceCode>& source_code);
 
   void EvaluateEmbeddedScript(const unsigned char* data, size_t size,
                               const char* filename);
diff --git a/cobalt/site/docs/development/setup-linux.md b/cobalt/site/docs/development/setup-linux.md
index be9f564..498b5f3 100644
--- a/cobalt/site/docs/development/setup-linux.md
+++ b/cobalt/site/docs/development/setup-linux.md
@@ -67,6 +67,18 @@
     $ git clone https://cobalt.googlesource.com/cobalt
     ```
 
+1.  Set `PYTHONPATH` environment variable to include the full path to the
+    top-level `cobalt` directory from the previous step. Add the following to
+    the end of your ~/.bash_profile (replacing `fullpathto` with the actual
+    path where you cloned the repo):
+
+    ```
+    export PYTHONPATH="/fullpathto/cobalt:${PYTHONPATH}"
+    ```
+
+    You should also run the above command in your termainal so it's available
+    immediately, rather than when you next login.
+
 ### Set up Developer Tools
 
 1.  Enter your new `cobalt` directory:
diff --git a/cobalt/speech/BUILD.gn b/cobalt/speech/BUILD.gn
index 8251374..199796a 100644
--- a/cobalt/speech/BUILD.gn
+++ b/cobalt/speech/BUILD.gn
@@ -101,11 +101,11 @@
 
   deps = [
     "//cobalt/base",
-    "//cobalt/dom:dom_exception",
     "//cobalt/loader",
     "//cobalt/media",
     "//cobalt/network",
     "//cobalt/script",
+    "//cobalt/web:dom_exception",
     "//content/browser/speech",
     "//starboard",
     "//third_party/flac",
diff --git a/cobalt/speech/cobalt_speech_recognizer.cc b/cobalt/speech/cobalt_speech_recognizer.cc
index 0d61e70..d7527af 100644
--- a/cobalt/speech/cobalt_speech_recognizer.cc
+++ b/cobalt/speech/cobalt_speech_recognizer.cc
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <memory>
-
 #include "cobalt/speech/cobalt_speech_recognizer.h"
 
+#include <memory>
+#include <utility>
+
 #include "base/bind.h"
 #include "cobalt/base/tokens.h"
 #if defined(ENABLE_FAKE_MICROPHONE)
@@ -74,7 +75,6 @@
     const Microphone::Options& microphone_options,
     const EventCallback& event_callback)
     : SpeechRecognizer(event_callback), endpointer_delegate_(kSampleRate) {
-
   GoogleSpeechService::URLFetcherCreator url_fetcher_creator =
       base::Bind(&CreateURLFetcher);
   MicrophoneManager::MicrophoneCreator microphone_creator =
@@ -119,13 +119,13 @@
   endpointer_delegate_.Stop();
   microphone_manager_->Close();
   service_->Stop();
-  RunEventCallback(new dom::Event(base::Tokens::soundend()));
+  RunEventCallback(new web::Event(base::Tokens::soundend()));
 }
 
 void CobaltSpeechRecognizer::OnDataReceived(
     std::unique_ptr<AudioBus> audio_bus) {
   if (endpointer_delegate_.IsFirstTimeSoundStarted(*audio_bus)) {
-    RunEventCallback(new dom::Event(base::Tokens::soundstart()));
+    RunEventCallback(new web::Event(base::Tokens::soundstart()));
   }
   service_->RecognizeAudio(std::move(audio_bus), false);
 }
@@ -142,13 +142,13 @@
 }
 
 void CobaltSpeechRecognizer::OnRecognizerEvent(
-    const scoped_refptr<dom::Event>& event) {
+    const scoped_refptr<web::Event>& event) {
   RunEventCallback(event);
 }
 
 void CobaltSpeechRecognizer::OnMicrophoneError(
     MicrophoneManager::MicrophoneError error, std::string error_message) {
-  // An error is occured in Mic, so stop the energy endpointer and recognizer.
+  // An error is occurred in Mic, so stop the energy endpointer and recognizer.
 
   SpeechRecognitionErrorCode speech_error_code =
       kSpeechRecognitionErrorCodeAborted;
@@ -160,7 +160,7 @@
       speech_error_code = kSpeechRecognitionErrorCodeAudioCapture;
       break;
   }
-  scoped_refptr<dom::Event> event(
+  scoped_refptr<web::Event> event(
       new SpeechRecognitionError(speech_error_code, error_message));
   endpointer_delegate_.Stop();
   service_->Stop();
diff --git a/cobalt/speech/cobalt_speech_recognizer.h b/cobalt/speech/cobalt_speech_recognizer.h
index d67ab5e..2e201a8 100644
--- a/cobalt/speech/cobalt_speech_recognizer.h
+++ b/cobalt/speech/cobalt_speech_recognizer.h
@@ -57,7 +57,7 @@
                          std::string error_message);
 
   // Callbacks from recognizer.
-  void OnRecognizerEvent(const scoped_refptr<dom::Event>& event);
+  void OnRecognizerEvent(const scoped_refptr<web::Event>& event);
 
   EventCallback event_callback_;
   std::unique_ptr<GoogleSpeechService> service_;
diff --git a/cobalt/speech/google_speech_service.cc b/cobalt/speech/google_speech_service.cc
index 0bd11ac..1cb7e36 100644
--- a/cobalt/speech/google_speech_service.cc
+++ b/cobalt/speech/google_speech_service.cc
@@ -121,7 +121,7 @@
     proto::SpeechRecognitionEvent event,
     GoogleSpeechService::EventCallback event_callback) {
   // This method handles wrappables and should run on the MainWebModule thread.
-  scoped_refptr<dom::Event> error_event;
+  scoped_refptr<web::Event> error_event;
   switch (event.status()) {
     case proto::SpeechRecognitionEvent::STATUS_SUCCESS:
       NOTREACHED();
diff --git a/cobalt/speech/google_speech_service.h b/cobalt/speech/google_speech_service.h
index a1ea1e5..e254eff 100644
--- a/cobalt/speech/google_speech_service.h
+++ b/cobalt/speech/google_speech_service.h
@@ -44,7 +44,7 @@
 class GoogleSpeechService : public net::URLFetcherDelegate {
  public:
   typedef media::AudioBus AudioBus;
-  typedef base::Callback<void(const scoped_refptr<dom::Event>&)> EventCallback;
+  typedef base::Callback<void(const scoped_refptr<web::Event>&)> EventCallback;
   typedef SpeechRecognitionResultList::SpeechRecognitionResults
       SpeechRecognitionResults;
   typedef base::Callback<std::unique_ptr<net::URLFetcher>(
diff --git a/cobalt/speech/microphone_manager.h b/cobalt/speech/microphone_manager.h
index 602f6bf..0bb3903 100644
--- a/cobalt/speech/microphone_manager.h
+++ b/cobalt/speech/microphone_manager.h
@@ -24,9 +24,9 @@
 #include "base/optional.h"
 #include "base/threading/thread.h"
 #include "base/timer/timer.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/media/base/audio_bus.h"
 #include "cobalt/speech/microphone.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace speech {
diff --git a/cobalt/speech/sandbox/BUILD.gn b/cobalt/speech/sandbox/BUILD.gn
index 7a51260..7853ac1 100644
--- a/cobalt/speech/sandbox/BUILD.gn
+++ b/cobalt/speech/sandbox/BUILD.gn
@@ -27,6 +27,7 @@
   deps = [
     "//cobalt/audio",
     "//cobalt/base",
+    "//cobalt/browser",
     "//cobalt/debug:console_command_manager",
     "//cobalt/dom",
     "//cobalt/loader",
diff --git a/cobalt/speech/sandbox/speech_sandbox.cc b/cobalt/speech/sandbox/speech_sandbox.cc
index 78e3353..56f46bd 100644
--- a/cobalt/speech/sandbox/speech_sandbox.cc
+++ b/cobalt/speech/sandbox/speech_sandbox.cc
@@ -77,7 +77,7 @@
     const dom::DOMSettings::Options& dom_settings_options) {
   std::unique_ptr<script::EnvironmentSettings> environment_settings(
       new dom::DOMSettings(null_debugger_hooks_, kDOMMaxElementDepth, NULL,
-                           NULL, NULL, NULL, NULL, dom_settings_options));
+                           NULL, NULL, NULL, dom_settings_options));
   DCHECK(environment_settings);
 
   speech_recognition_ = new SpeechRecognition(environment_settings.get());
diff --git a/cobalt/speech/speech_recognition.cc b/cobalt/speech/speech_recognition.cc
index 38d60f3..a8066ae 100644
--- a/cobalt/speech/speech_recognition.cc
+++ b/cobalt/speech/speech_recognition.cc
@@ -28,7 +28,7 @@
 // interim results: false.
 // max alternatives: 1.
 SpeechRecognition::SpeechRecognition(script::EnvironmentSettings* settings)
-    : dom::EventTarget(settings),
+    : web::EventTarget(settings),
       ALLOW_THIS_IN_INITIALIZER_LIST(manager_(
           base::polymorphic_downcast<web::EnvironmentSettings*>(settings)
               ->context()
@@ -49,7 +49,7 @@
 void SpeechRecognition::Abort() { manager_.Abort(); }
 
 bool SpeechRecognition::OnEventAvailable(
-    const scoped_refptr<dom::Event>& event) {
+    const scoped_refptr<web::Event>& event) {
   return DispatchEvent(event);
 }
 
diff --git a/cobalt/speech/speech_recognition.h b/cobalt/speech/speech_recognition.h
index e1e440d..cdcea6f 100644
--- a/cobalt/speech/speech_recognition.h
+++ b/cobalt/speech/speech_recognition.h
@@ -17,10 +17,10 @@
 
 #include <string>
 
-#include "cobalt/dom/event_target.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/speech/speech_recognition_config.h"
 #include "cobalt/speech/speech_recognition_manager.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace speech {
@@ -28,7 +28,7 @@
 // The speech recognition interface is the scripted web API for controlling a
 // given recognition.
 //   https://dvcs.w3.org/hg/speech-api/raw-file/9a0075d25326/speechapi.html#speechreco-section
-class SpeechRecognition : public dom::EventTarget {
+class SpeechRecognition : public web::EventTarget {
  public:
   explicit SpeechRecognition(script::EnvironmentSettings* settings);
 
@@ -106,7 +106,7 @@
   ~SpeechRecognition() override {}
 
   // Callback from recognition manager.
-  bool OnEventAvailable(const scoped_refptr<dom::Event>& event);
+  bool OnEventAvailable(const scoped_refptr<web::Event>& event);
 
   // Handles main operations of speech recognition including audio encoding,
   // mic audio retrieving and audio data recognizing.
diff --git a/cobalt/speech/speech_recognition_config.h b/cobalt/speech/speech_recognition_config.h
index 69859b5..97b5910 100644
--- a/cobalt/speech/speech_recognition_config.h
+++ b/cobalt/speech/speech_recognition_config.h
@@ -17,13 +17,15 @@
 
 #include <string>
 
+#include "starboard/types.h"
+
 namespace cobalt {
 namespace speech {
 
 // Speech recognition parameters.
 struct SpeechRecognitionConfig {
   SpeechRecognitionConfig(const std::string& lang, bool continuous,
-                          bool interim_results, uint32 max_alternatives)
+                          bool interim_results, uint32_t max_alternatives)
       : lang(lang),
         continuous(continuous),
         interim_results(interim_results),
@@ -41,7 +43,7 @@
   bool interim_results;
   // This attribute will set the maximum number of SpeechRecognitionAlternatives
   // per result.
-  uint32 max_alternatives;
+  uint32_t max_alternatives;
 };
 
 }  // namespace speech
diff --git a/cobalt/speech/speech_recognition_error.cc b/cobalt/speech/speech_recognition_error.cc
index 9089823..8cf380a 100644
--- a/cobalt/speech/speech_recognition_error.cc
+++ b/cobalt/speech/speech_recognition_error.cc
@@ -21,7 +21,7 @@
 
 SpeechRecognitionError::SpeechRecognitionError(
     SpeechRecognitionErrorCode error_code, const std::string& message)
-    : dom::Event(base::Tokens::error()),
+    : web::Event(base::Tokens::error()),
       error_code_(error_code),
       message_(message) {
   LOG(ERROR) << "Created SpeechRecognitionError code " << error_code
diff --git a/cobalt/speech/speech_recognition_error.h b/cobalt/speech/speech_recognition_error.h
index fd6c274..7876108 100644
--- a/cobalt/speech/speech_recognition_error.h
+++ b/cobalt/speech/speech_recognition_error.h
@@ -19,16 +19,16 @@
 
 #include "base/basictypes.h"
 #include "base/memory/ref_counted.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/script/wrappable.h"
 #include "cobalt/speech/speech_recognition_error_code.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace speech {
 
 // The SpeechRecognitionError event is the interface used for the error event.
 //   https://dvcs.w3.org/hg/speech-api/raw-file/9a0075d25326/speechapi.html#speechreco-error
-class SpeechRecognitionError : public dom::Event {
+class SpeechRecognitionError : public web::Event {
  public:
   SpeechRecognitionError(SpeechRecognitionErrorCode error_code,
                          const std::string& message);
diff --git a/cobalt/speech/speech_recognition_event.cc b/cobalt/speech/speech_recognition_event.cc
index c2bb36e..ac63453 100644
--- a/cobalt/speech/speech_recognition_event.cc
+++ b/cobalt/speech/speech_recognition_event.cc
@@ -36,12 +36,12 @@
     Type type, uint32 result_index,
     const scoped_refptr<SpeechRecognitionResultList>&
         speech_recognition_result_list)
-    : dom::Event(TypeEnumToToken(type)),
+    : web::Event(TypeEnumToToken(type)),
       result_index_(result_index),
       speech_recognition_result_list_(speech_recognition_result_list) {}
 
 void SpeechRecognitionEvent::TraceMembers(script::Tracer* tracer) {
-  dom::Event::TraceMembers(tracer);
+  web::Event::TraceMembers(tracer);
 
   tracer->Trace(speech_recognition_result_list_);
 }
diff --git a/cobalt/speech/speech_recognition_event.h b/cobalt/speech/speech_recognition_event.h
index 1faf875..8aab431 100644
--- a/cobalt/speech/speech_recognition_event.h
+++ b/cobalt/speech/speech_recognition_event.h
@@ -17,9 +17,9 @@
 
 #include "base/basictypes.h"
 #include "base/memory/ref_counted.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/script/wrappable.h"
 #include "cobalt/speech/speech_recognition_result_list.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace speech {
@@ -27,7 +27,7 @@
 // The SpeechRecognitionEvent is the event that is raised each time there are
 // any changes to interim or final results.
 //   https://dvcs.w3.org/hg/speech-api/raw-file/9a0075d25326/speechapi.html#speechreco-event
-class SpeechRecognitionEvent : public dom::Event {
+class SpeechRecognitionEvent : public web::Event {
  public:
   // Result event and nomatch event MUST use the SpeechRecognitionEvent
   // interface.
diff --git a/cobalt/speech/speech_recognition_manager.cc b/cobalt/speech/speech_recognition_manager.cc
index ef98728..218346a 100644
--- a/cobalt/speech/speech_recognition_manager.cc
+++ b/cobalt/speech/speech_recognition_manager.cc
@@ -15,9 +15,9 @@
 #include "cobalt/speech/speech_recognition_manager.h"
 
 #include "base/bind.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/speech/speech_configuration.h"
 #include "cobalt/speech/speech_recognition_error.h"
+#include "cobalt/web/dom_exception.h"
 #if defined(SB_USE_SB_SPEECH_RECOGNIZER)
 #include "cobalt/speech/starboard_speech_recognizer.h"
 #endif
@@ -59,7 +59,7 @@
   // If the start method is called on an already started object, the user agent
   // MUST throw an InvalidStateError exception and ignore the call.
   if (state_ == kStarted) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              exception_state);
     return;
   }
@@ -104,7 +104,7 @@
 }
 
 void SpeechRecognitionManager::OnEventAvailable(
-    const scoped_refptr<dom::Event>& event) {
+    const scoped_refptr<web::Event>& event) {
   if (!main_message_loop_task_runner_->BelongsToCurrentThread()) {
     // Called from recognizer. |event_callback_| is required to be run on
     // the |main_message_loop_task_runner_|.
diff --git a/cobalt/speech/speech_recognition_manager.h b/cobalt/speech/speech_recognition_manager.h
index acdbfad..e50842d 100644
--- a/cobalt/speech/speech_recognition_manager.h
+++ b/cobalt/speech/speech_recognition_manager.h
@@ -34,7 +34,7 @@
 // class would encode the audio data, then send it to recognition service.
 class SpeechRecognitionManager {
  public:
-  typedef base::Callback<bool(const scoped_refptr<dom::Event>&)> EventCallback;
+  typedef base::Callback<bool(const scoped_refptr<web::Event>&)> EventCallback;
 
   SpeechRecognitionManager(network::NetworkModule* network_module,
                            const EventCallback& event_callback,
@@ -54,7 +54,7 @@
     kAborted,
   };
 
-  void OnEventAvailable(const scoped_refptr<dom::Event>& event);
+  void OnEventAvailable(const scoped_refptr<web::Event>& event);
 
   base::WeakPtrFactory<SpeechRecognitionManager> weak_ptr_factory_;
   // We construct a WeakPtr upon SpeechRecognitionManager's construction in
diff --git a/cobalt/speech/speech_recognizer.cc b/cobalt/speech/speech_recognizer.cc
index 10595b7..15af254 100644
--- a/cobalt/speech/speech_recognizer.cc
+++ b/cobalt/speech/speech_recognizer.cc
@@ -21,7 +21,7 @@
     : event_callback_(event_callback) {}
 
 void SpeechRecognizer::RunEventCallback(
-    const scoped_refptr<dom::Event>& event) {
+    const scoped_refptr<web::Event>& event) {
   event_callback_.Run(event);
 }
 
diff --git a/cobalt/speech/speech_recognizer.h b/cobalt/speech/speech_recognizer.h
index 1f24fb0..276ba82 100644
--- a/cobalt/speech/speech_recognizer.h
+++ b/cobalt/speech/speech_recognizer.h
@@ -16,8 +16,8 @@
 #define COBALT_SPEECH_SPEECH_RECOGNIZER_H_
 
 #include "base/callback.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/speech/speech_recognition_config.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace speech {
@@ -26,7 +26,7 @@
 // classes to run event callback.
 class SpeechRecognizer {
  public:
-  typedef base::Callback<void(const scoped_refptr<dom::Event>&)> EventCallback;
+  typedef base::Callback<void(const scoped_refptr<web::Event>&)> EventCallback;
 
   explicit SpeechRecognizer(const EventCallback& event_callback);
 
@@ -36,7 +36,7 @@
   virtual void Stop() = 0;
 
  protected:
-  void RunEventCallback(const scoped_refptr<dom::Event>& event);
+  void RunEventCallback(const scoped_refptr<web::Event>& event);
 
  private:
   // Callback for sending dom events if available.
diff --git a/cobalt/speech/speech_synthesis.cc b/cobalt/speech/speech_synthesis.cc
index 11526dc..88c7750 100644
--- a/cobalt/speech/speech_synthesis.cc
+++ b/cobalt/speech/speech_synthesis.cc
@@ -29,7 +29,7 @@
 SpeechSynthesis::SpeechSynthesis(script::EnvironmentSettings* settings,
                                  const scoped_refptr<dom::Navigator>& navigator,
                                  bool log_output)
-    : dom::EventTarget(settings),
+    : web::EventTarget(settings),
       log_output_(log_output),
       paused_(false),
       navigator_(navigator) {
@@ -50,7 +50,7 @@
     const EventListenerScriptValue& event_listener) {
   base::Token event_name = base::Tokens::voiceschanged();
   SetAttributeEventListener(event_name, event_listener);
-  DispatchEvent(new dom::Event(event_name));
+  DispatchEvent(new web::Event(event_name));
 }
 
 void SpeechSynthesis::Cancel() {
@@ -76,7 +76,7 @@
 }
 
 void SpeechSynthesis::TraceMembers(script::Tracer* tracer) {
-  dom::EventTarget::TraceMembers(tracer);
+  web::EventTarget::TraceMembers(tracer);
 
   tracer->TraceItems(utterances_);
   tracer->TraceItems(voices_);
diff --git a/cobalt/speech/speech_synthesis.h b/cobalt/speech/speech_synthesis.h
index 3ff910c..d4b9a71 100644
--- a/cobalt/speech/speech_synthesis.h
+++ b/cobalt/speech/speech_synthesis.h
@@ -19,12 +19,12 @@
 
 #include "base/basictypes.h"
 #include "base/memory/ref_counted.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/sequence.h"
 #include "cobalt/script/wrappable.h"
 #include "cobalt/speech/speech_synthesis_utterance.h"
 #include "cobalt/speech/speech_synthesis_voice.h"
+#include "cobalt/web/event_target.h"
 #include "starboard/system.h"
 
 namespace cobalt {
@@ -40,7 +40,7 @@
 // The speech synthesis interface is the scripted web API for controlling a
 // given speech synthesis.
 //   https://dvcs.w3.org/hg/speech-api/raw-file/4f41ea1126bb/webspeechapi.html#tts-section
-class SpeechSynthesis : public dom::EventTarget {
+class SpeechSynthesis : public web::EventTarget {
  public:
   typedef script::Sequence<scoped_refptr<SpeechSynthesisVoice> >
       SpeechSynthesisVoiceSequence;
diff --git a/cobalt/speech/speech_synthesis_error_event.h b/cobalt/speech/speech_synthesis_error_event.h
index e5da656..be011fe 100644
--- a/cobalt/speech/speech_synthesis_error_event.h
+++ b/cobalt/speech/speech_synthesis_error_event.h
@@ -16,10 +16,10 @@
 #define COBALT_SPEECH_SPEECH_SYNTHESIS_ERROR_EVENT_H_
 
 #include "base/basictypes.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/script/wrappable.h"
 #include "cobalt/speech/speech_synthesis_error_code.h"
 #include "cobalt/speech/speech_synthesis_event.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace speech {
diff --git a/cobalt/speech/speech_synthesis_event.cc b/cobalt/speech/speech_synthesis_event.cc
index 099a367..8caa703 100644
--- a/cobalt/speech/speech_synthesis_event.cc
+++ b/cobalt/speech/speech_synthesis_event.cc
@@ -22,7 +22,7 @@
 SpeechSynthesisEvent::SpeechSynthesisEvent(
     base::Token event_name,
     const scoped_refptr<SpeechSynthesisUtterance>& utterance)
-    : Event(event_name, Event::kBubbles, Event::kNotCancelable),
+    : Event(event_name, web::Event::kBubbles, web::Event::kNotCancelable),
       utterance_(utterance) {}
 
 SpeechSynthesisEvent::~SpeechSynthesisEvent() {}
diff --git a/cobalt/speech/speech_synthesis_event.h b/cobalt/speech/speech_synthesis_event.h
index 48cc744..c2bd086 100644
--- a/cobalt/speech/speech_synthesis_event.h
+++ b/cobalt/speech/speech_synthesis_event.h
@@ -20,8 +20,8 @@
 #include "base/basictypes.h"
 #include "base/memory/ref_counted.h"
 #include "base/optional.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
 namespace speech {
@@ -31,7 +31,7 @@
 // The speech synthesis event interface is the scripted web API for defining a
 // speech synthesis event.
 //   https://dvcs.w3.org/hg/speech-api/raw-file/9a0075d25326/speechapi.html#speechsynthesisevent
-class SpeechSynthesisEvent : public dom::Event {
+class SpeechSynthesisEvent : public web::Event {
  public:
   SpeechSynthesisEvent(
       base::Token event_name,
diff --git a/cobalt/speech/speech_synthesis_utterance.cc b/cobalt/speech/speech_synthesis_utterance.cc
index 373e7ce..d97f37e 100644
--- a/cobalt/speech/speech_synthesis_utterance.cc
+++ b/cobalt/speech/speech_synthesis_utterance.cc
@@ -19,7 +19,7 @@
 
 SpeechSynthesisUtterance::SpeechSynthesisUtterance(
     script::EnvironmentSettings* settings)
-    : dom::EventTarget(settings),
+    : web::EventTarget(settings),
       settings_(settings),
       volume_(1.0f),
       rate_(1.0f),
@@ -27,7 +27,7 @@
       pending_speak_(false) {}
 SpeechSynthesisUtterance::SpeechSynthesisUtterance(
     script::EnvironmentSettings* settings, const std::string& text)
-    : dom::EventTarget(settings),
+    : web::EventTarget(settings),
       settings_(settings),
       text_(text),
       volume_(1.0f),
@@ -37,7 +37,7 @@
 
 SpeechSynthesisUtterance::SpeechSynthesisUtterance(
     const scoped_refptr<SpeechSynthesisUtterance>& utterance)
-    : dom::EventTarget(utterance->settings_),
+    : web::EventTarget(utterance->settings_),
       text_(utterance->text_),
       lang_(utterance->lang_),
       voice_(utterance->voice_),
@@ -47,7 +47,7 @@
       pending_speak_(false) {
   using ListenerSetterFunctionType = base::Callback<void(
       SpeechSynthesisUtterance*,
-      const cobalt::dom::EventTarget::EventListenerScriptValue&)>;
+      const cobalt::web::EventTarget::EventListenerScriptValue&)>;
   auto ListenerCopyHelper = [this](
                                 const EventListenerScriptValue* script_value,
                                 const ListenerSetterFunctionType& setter_func) {
@@ -96,7 +96,7 @@
 }
 
 void SpeechSynthesisUtterance::TraceMembers(script::Tracer* tracer) {
-  dom::EventTarget::TraceMembers(tracer);
+  web::EventTarget::TraceMembers(tracer);
 
   tracer->Trace(voice_);
 }
diff --git a/cobalt/speech/speech_synthesis_utterance.h b/cobalt/speech/speech_synthesis_utterance.h
index aaec826..13169bd 100644
--- a/cobalt/speech/speech_synthesis_utterance.h
+++ b/cobalt/speech/speech_synthesis_utterance.h
@@ -17,11 +17,11 @@
 
 #include <string>
 
-#include "cobalt/dom/event_target.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/wrappable.h"
 #include "cobalt/speech/speech_synthesis_error_event.h"
 #include "cobalt/speech/speech_synthesis_voice.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace speech {
@@ -29,7 +29,7 @@
 // The speech synthesis voice interface is the scripted web API for defining a
 // speech synthesis voice.
 //   https://dvcs.w3.org/hg/speech-api/raw-file/4f41ea1126bb/webspeechapi.html#speechsynthesisvoice
-class SpeechSynthesisUtterance : public dom::EventTarget {
+class SpeechSynthesisUtterance : public web::EventTarget {
  public:
   explicit SpeechSynthesisUtterance(script::EnvironmentSettings* settings);
   SpeechSynthesisUtterance(
diff --git a/cobalt/speech/starboard_speech_recognizer.cc b/cobalt/speech/starboard_speech_recognizer.cc
index 73905c0..cc37dcc 100644
--- a/cobalt/speech/starboard_speech_recognizer.cc
+++ b/cobalt/speech/starboard_speech_recognizer.cc
@@ -86,7 +86,7 @@
   speech_recognizer_ = SbSpeechRecognizerCreate(&handler);
 
   if (!SbSpeechRecognizerIsValid(speech_recognizer_)) {
-    scoped_refptr<dom::Event> error_event(new SpeechRecognitionError(
+    scoped_refptr<web::Event> error_event(new SpeechRecognitionError(
         kSpeechRecognitionErrorCodeServiceNotAllowed, ""));
     RunEventCallback(error_event);
   }
@@ -117,14 +117,14 @@
 }
 
 void StarboardSpeechRecognizer::OnRecognizerSpeechDetected(bool detected) {
-  scoped_refptr<dom::Event> event(new dom::Event(
+  scoped_refptr<web::Event> event(new web::Event(
       detected ? base::Tokens::soundstart() : base::Tokens::soundend()));
   RunEventCallback(event);
 }
 
 void StarboardSpeechRecognizer::OnRecognizerError(
     SbSpeechRecognizerError error) {
-  scoped_refptr<dom::Event> error_event;
+  scoped_refptr<web::Event> error_event;
   switch (error) {
     case kSbNoSpeechError:
       error_event =
diff --git a/cobalt/subtlecrypto/BUILD.gn b/cobalt/subtlecrypto/BUILD.gn
index 7097523..a3698ea 100644
--- a/cobalt/subtlecrypto/BUILD.gn
+++ b/cobalt/subtlecrypto/BUILD.gn
@@ -40,9 +40,9 @@
     "//cobalt/base",
     "//cobalt/browser:generated_types",
     "//cobalt/dom",
-    "//cobalt/dom:dom_exception",
     "//cobalt/script",
     "//cobalt/script:engine",
+    "//cobalt/web:dom_exception",
   ]
 }
 
diff --git a/cobalt/subtlecrypto/subtle_crypto.cc b/cobalt/subtlecrypto/subtle_crypto.cc
index 5fd6edf..e9bf7eb 100644
--- a/cobalt/subtlecrypto/subtle_crypto.cc
+++ b/cobalt/subtlecrypto/subtle_crypto.cc
@@ -28,10 +28,10 @@
 
 namespace {
 
-const ByteVector to_vector(const dom::BufferSource &data) {
+const ByteVector to_vector(const web::BufferSource &data) {
   const uint8_t *buff;
   int buf_len;
-  cobalt::dom::GetBufferAndSize(data, &buff, &buf_len);
+  web::GetBufferAndSize(data, &buff, &buf_len);
   return ByteVector(buff, buff + buf_len);
 }
 
@@ -64,13 +64,13 @@
 }
 
 template <typename Promise>
-Promise reject(Promise &&promise, dom::DOMException::ExceptionCode error) {
-  return reject(promise, new dom::DOMException(error));
+Promise reject(Promise &&promise, web::DOMException::ExceptionCode error) {
+  return reject(promise, new web::DOMException(error));
 }
 
 template <typename Promise>
 Promise reject(Promise &&promise, const std::string &dom_error) {
-  return reject(promise, new dom::DOMException(dom_error, dom_error));
+  return reject(promise, new web::DOMException(dom_error, dom_error));
 }
 
 }  // namespace
@@ -95,16 +95,16 @@
       ->CreateInterfacePromise<scoped_refptr<CryptoKeyPtr>>();
 }
 
-dom::BufferSource SubtleCrypto::CreateBufferSource(const ByteVector &input) {
+web::BufferSource SubtleCrypto::CreateBufferSource(const ByteVector &input) {
   DCHECK(global_env_);
   auto arrayBuffer =
       script::ArrayBuffer::New(global_env_, input.data(), input.size());
-  return dom::BufferSource(arrayBuffer);
+  return web::BufferSource(arrayBuffer);
 }
 
 PromiseArray SubtleCrypto::Decrypt(EncryptionAlgorithm algorithm,
                                    const CryptoKeyPtr &key,
-                                   const BufferSource &data) {
+                                   const web::BufferSource &data) {
   // 1. Let algorithm and key be the algorithm and key parameters passed to the
   // decrypt method, respectively.
   // 2. Let data be the result of getting a copy of the bytes held by the data
@@ -117,20 +117,20 @@
   // 4. If an error occurred, return a Promise rejected with
   // normalizedAlgorithm.
   if (normalizedAlgorithm != key->get_algorithm()) {
-    return reject(promise, DOMException::kInvalidAccessErr);
+    return reject(promise, web::DOMException::kInvalidAccessErr);
   }
   // 9. If the [[usages]] internal slot of key does not contain an entry that is
   // "decrypt", then throw an InvalidAccessError.
   if (!key->usage(KeyUsage::kKeyUsageDecrypt)) {
-    return reject(promise, DOMException::kInvalidAccessErr);
+    return reject(promise, web::DOMException::kInvalidAccessErr);
   }
   if (key->get_key().size() == 0) {
-    return reject(promise, DOMException::kInvalidAccessErr);
+    return reject(promise, web::DOMException::kInvalidAccessErr);
   }
   if (normalizedAlgorithm == "AES-CTR") {
     auto ctr_params = algorithm.AsType<AesCtrParams>();
     if (!(ctr_params.has_length() && ctr_params.has_counter())) {
-      return reject(promise, DOMException::kValidationErr);
+      return reject(promise, web::DOMException::kValidationErr);
     }
     auto iv = to_vector(ctr_params.counter());
     auto counter_len = ctr_params.length();
@@ -153,7 +153,7 @@
   } else {
     // 7. If the following steps or referenced procedures say to throw an error,
     // reject promise with the returned error and then terminate the algorithm.
-    return reject(promise, DOMException::kNotSupportedErr);
+    return reject(promise, web::DOMException::kNotSupportedErr);
   }
   // 6. Return promise and asynchronously perform the remaining steps.
   return promise;
@@ -163,7 +163,7 @@
 // very similar for symmetric algos.
 PromiseArray SubtleCrypto::Encrypt(EncryptionAlgorithm algorithm,
                                    const CryptoKeyPtr &key,
-                                   const BufferSource &data) {
+                                   const web::BufferSource &data) {
   // 1. Let algorithm and key be the algorithm and key parameters passed to the
   // encrypt method, respectively.
   // 2. Let data be the result of getting a copy of the bytes held by the data
@@ -176,20 +176,20 @@
   // 4. If an error occurred, return a Promise rejected with
   // normalizedAlgorithm.
   if (normalizedAlgorithm != key->get_algorithm()) {
-    return reject(promise, DOMException::kInvalidAccessErr);
+    return reject(promise, web::DOMException::kInvalidAccessErr);
   }
   // 9. If the [[usages]] internal slot of key does not contain an entry that is
   // "encrypt", then throw an InvalidAccessError.
   if (!key->usage(KeyUsage::kKeyUsageEncrypt)) {
-    return reject(promise, DOMException::kInvalidAccessErr);
+    return reject(promise, web::DOMException::kInvalidAccessErr);
   }
   if (key->get_key().size() == 0) {
-    return reject(promise, DOMException::kInvalidAccessErr);
+    return reject(promise, web::DOMException::kInvalidAccessErr);
   }
   if (normalizedAlgorithm == "AES-CTR") {
     auto ctr_params = algorithm.AsType<AesCtrParams>();
     if (!(ctr_params.has_length() && ctr_params.has_counter())) {
-      return reject(promise, DOMException::kValidationErr);
+      return reject(promise, web::DOMException::kValidationErr);
     }
     auto iv = to_vector(ctr_params.counter());
     auto counter_len = ctr_params.length();
@@ -212,7 +212,7 @@
   } else {
     // 7. If the following steps or referenced procedures say to throw an error,
     // reject promise with the returned error and then terminate the algorithm.
-    return reject(promise, DOMException::kNotSupportedErr);
+    return reject(promise, web::DOMException::kNotSupportedErr);
   }
   // 6. Return promise and asynchronously perform the remaining steps.
   return promise;
@@ -220,7 +220,7 @@
 
 PromiseArray SubtleCrypto::Sign(AlgorithmIdentifier algorithm,
                                 const CryptoKeyPtr &key,
-                                const BufferSource &data) {
+                                const web::BufferSource &data) {
   // 1. Let algorithm and key be the algorithm and key parameters passed to the
   // sign method, respectively.
   // 2. Let data be the result of getting a copy of the bytes held by the data
@@ -235,7 +235,7 @@
     // 9. If the [[usages]] internal slot of key does not contain an entry that
     // is "sign", then throw an InvalidAccessError.
     if (!key->usage(KeyUsage::kKeyUsageSign) || !hash) {
-      return reject(promise, DOMException::kInvalidAccessErr);
+      return reject(promise, web::DOMException::kInvalidAccessErr);
     }
     // 10. Let result be the result of performing the sign operation specified
     // by normalizedAlgorithm using key and algorithm and with data as message.
@@ -245,7 +245,7 @@
   } else {
     // 7. If the following steps or referenced procedures say to throw an error,
     // reject promise with the returned error and then terminate the algorithm.
-    return reject(promise, DOMException::kNotSupportedErr);
+    return reject(promise, web::DOMException::kNotSupportedErr);
   }
   // 6. Return promise and asynchronously perform the remaining steps.
   return promise;
@@ -253,8 +253,8 @@
 
 PromiseBool SubtleCrypto::Verify(AlgorithmIdentifier algorithm,
                                  const CryptoKeyPtr &key,
-                                 const BufferSource &signature,
-                                 const BufferSource &data) {
+                                 const web::BufferSource &signature,
+                                 const web::BufferSource &data) {
   // 1. Let algorithm and key be the algorithm and key parameters passed to the
   // verify method, respectively.
   // 2. Let signature be the result of getting a copy of the bytes held by the
@@ -271,7 +271,7 @@
     // 10. If the [[usages]] internal slot of key does not contain an entry that
     // is "verify", then throw an InvalidAccessError.
     if (!key->usage(KeyUsage::kKeyUsageVerify) || !hash) {
-      return reject(promise, DOMException::kInvalidAccessErr);
+      return reject(promise, web::DOMException::kInvalidAccessErr);
     }
     // 11. Let result be the result of performing the verify operation
     // specified by normalizedAlgorithm using key, algorithm and signature
@@ -283,14 +283,14 @@
   } else {
     // 8. If the following steps or referenced procedures say to throw an error,
     // reject promise with the returned error and then terminate the algorithm.
-    return reject(promise, DOMException::kNotSupportedErr);
+    return reject(promise, web::DOMException::kNotSupportedErr);
   }
   // 7. Return promise and asynchronously perform the remaining steps.
   return promise;
 }
 
 PromiseArray SubtleCrypto::Digest(AlgorithmIdentifier algorithm,
-                                  const BufferSource &data) {
+                                  const web::BufferSource &data) {
   // 1. Let algorithm be the algorithm parameter passed to the digest method.
   // 2. Let data be the result of getting a copy of the bytes held by the data
   // parameter passed to the digest method.
@@ -303,7 +303,7 @@
   if (!hash) {
     // 4. If an error occurred, return a Promise rejected with
     // normalizedAlgorithm.
-    return reject(promise, DOMException::kNotSupportedErr);
+    return reject(promise, web::DOMException::kNotSupportedErr);
   }
   // 8. Let result be the result of performing the digest operation specified by
   // normalizedAlgorithm using algorithm, with data as message.
@@ -319,7 +319,7 @@
                                        bool extractable,
                                        const KeyUsages &keyUsages) {
   NOTIMPLEMENTED();
-  return reject(CreatePromise(), DOMException::kNotSupportedErr);
+  return reject(CreatePromise(), web::DOMException::kNotSupportedErr);
 }
 
 PromiseArray SubtleCrypto::DeriveKey(AlgorithmIdentifier algorithm,
@@ -328,18 +328,18 @@
                                      bool extractable,
                                      const KeyUsages &keyUsages) {
   NOTIMPLEMENTED();
-  return reject(CreatePromise(), DOMException::kNotSupportedErr);
+  return reject(CreatePromise(), web::DOMException::kNotSupportedErr);
 }
 
 PromiseArray SubtleCrypto::DeriveBits(AlgorithmIdentifier algorithm,
                                       const CryptoKeyPtr &key,
                                       const uint32_t length) {
   NOTIMPLEMENTED();
-  return reject(CreatePromise(), DOMException::kNotSupportedErr);
+  return reject(CreatePromise(), web::DOMException::kNotSupportedErr);
 }
 
 PromiseWrappable SubtleCrypto::ImportKey(
-    KeyFormat format, const BufferSource &keyData,
+    KeyFormat format, const web::BufferSource &keyData,
     script::UnionType2<ImportKeyAlgorithmParams, std::string> algorithm,
     bool extractable, const KeyUsages &keyUsages) {
   // 1. Let format, algorithm, extractable and usages, be the format, algorithm,
@@ -356,11 +356,13 @@
 
   if (format != kKeyFormatRaw) {
     NOTIMPLEMENTED();
-    return reject(promise, new DOMException(DOMException::kNotSupportedErr));
+    return reject(promise,
+                  new web::DOMException(web::DOMException::kNotSupportedErr));
   }
 
   if (!(normalizedAlgorithm == "AES-CTR" || normalizedAlgorithm == "HMAC")) {
-    return reject(promise, new DOMException(DOMException::kNotSupportedErr));
+    return reject(promise,
+                  new web::DOMException(web::DOMException::kNotSupportedErr));
   }
 
   // 8. Let result be the CryptoKey object that results from performing the
@@ -371,7 +373,7 @@
   // 11. Set the [[usages]] internal slot of result to the normalized value of
   // usages.
   if (!cryptoKey->set_usages(keyUsages)) {
-    return reject(promise, new DOMException("Invalid key", "not allowed"));
+    return reject(promise, new web::DOMException("Invalid key", "not allowed"));
   }
   // 25.7.1 Let data be the octet string contained in keyData.
   cryptoKey->set_key(to_vector(keyData));
@@ -392,23 +394,23 @@
 PromiseArray SubtleCrypto::ExportKey(KeyFormat format,
                                      const CryptoKeyPtr &key) {
   NOTIMPLEMENTED();
-  return reject(CreatePromise(), DOMException::kNotSupportedErr);
+  return reject(CreatePromise(), web::DOMException::kNotSupportedErr);
 }
 
 PromiseArray SubtleCrypto::WrapKey(KeyFormat format, const CryptoKeyPtr &key,
                                    const CryptoKeyPtr &wrappingKey,
                                    AlgorithmIdentifier algorithm) {
   NOTIMPLEMENTED();
-  return reject(CreatePromise(), DOMException::kNotSupportedErr);
+  return reject(CreatePromise(), web::DOMException::kNotSupportedErr);
 }
 
 PromiseWrappable SubtleCrypto::UnwrapKey(
-    KeyFormat format, const BufferSource &wrappedKey,
+    KeyFormat format, const web::BufferSource &wrappedKey,
     const CryptoKeyPtr &unwrappingKey, AlgorithmIdentifier unwrapAlgorithm,
     AlgorithmIdentifier unwrappedKeyAlgorithm, bool extractacble,
     const KeyUsages &keyUsages) {
   NOTIMPLEMENTED();
-  return reject(CreateKeyPromise(), DOMException::kNotSupportedErr);
+  return reject(CreateKeyPromise(), web::DOMException::kNotSupportedErr);
 }
 
 }  // namespace subtlecrypto
diff --git a/cobalt/subtlecrypto/subtle_crypto.h b/cobalt/subtlecrypto/subtle_crypto.h
index 62f2714..90bc319 100644
--- a/cobalt/subtlecrypto/subtle_crypto.h
+++ b/cobalt/subtlecrypto/subtle_crypto.h
@@ -18,12 +18,12 @@
 #include <string>
 #include <vector>
 
-#include "cobalt/dom/buffer_source.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/exception_state.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/promise.h"
+#include "cobalt/web/buffer_source.h"
+#include "cobalt/web/dom_exception.h"
 
 #include "cobalt/subtlecrypto/aes_ctr_params.h"
 #include "cobalt/subtlecrypto/algorithm.h"
@@ -43,10 +43,8 @@
   using AlgorithmIdentifier = script::UnionType2<Algorithm, std::string>;
   using EncryptionAlgorithm = script::UnionType2<AesCtrParams, Algorithm>;
   using CryptoKeyPtr = scoped_refptr<CryptoKey>;
-  using BufferSource = dom::BufferSource;
-  using DOMException = dom::DOMException;
 
-  using PromiseArray = script::Handle<script::Promise<dom::BufferSource>>;
+  using PromiseArray = script::Handle<script::Promise<web::BufferSource>>;
   using PromiseBool = script::Handle<script::Promise<bool>>;
   using PromiseWrappable =
       script::Handle<script::Promise<scoped_refptr<script::Wrappable>>>;
@@ -55,16 +53,16 @@
   ~SubtleCrypto() override;
 
   PromiseArray Decrypt(EncryptionAlgorithm algorithm, const CryptoKeyPtr& key,
-                       const dom::BufferSource& data);
+                       const web::BufferSource& data);
   PromiseArray Encrypt(EncryptionAlgorithm algorithm, const CryptoKeyPtr& key,
-                       const dom::BufferSource& data);
+                       const web::BufferSource& data);
   PromiseArray Sign(AlgorithmIdentifier algorithm, const CryptoKeyPtr& key,
-                    const dom::BufferSource& data);
+                    const web::BufferSource& data);
   PromiseBool Verify(AlgorithmIdentifier algorithm, const CryptoKeyPtr& key,
-                     const dom::BufferSource& signature,
-                     const dom::BufferSource& data);
+                     const web::BufferSource& signature,
+                     const web::BufferSource& data);
   PromiseArray Digest(AlgorithmIdentifier algorithm,
-                      const dom::BufferSource& data);
+                      const web::BufferSource& data);
   PromiseArray GenerateKey(AlgorithmIdentifier algorithm, bool extractable,
                            const KeyUsages& keyUsages);
   PromiseArray DeriveKey(AlgorithmIdentifier algorithm, const CryptoKeyPtr& key,
@@ -73,7 +71,7 @@
   PromiseArray DeriveBits(AlgorithmIdentifier algorithm,
                           const CryptoKeyPtr& key, const uint32_t length);
   PromiseWrappable ImportKey(
-      KeyFormat format, const dom::BufferSource& keyData,
+      KeyFormat format, const web::BufferSource& keyData,
       script::UnionType2<ImportKeyAlgorithmParams, std::string> algorithm,
       bool extractable, const KeyUsages& keyUsages);
   PromiseArray ExportKey(KeyFormat format, const CryptoKeyPtr& key);
@@ -81,7 +79,7 @@
                        const CryptoKeyPtr& wrappingKey,
                        AlgorithmIdentifier algorithm);
   PromiseWrappable UnwrapKey(KeyFormat format,
-                             const dom::BufferSource& wrappedKey,
+                             const web::BufferSource& wrappedKey,
                              const CryptoKeyPtr& unwrappingKey,
                              AlgorithmIdentifier unwrapAlgorithm,
                              AlgorithmIdentifier unwrappedKeyAlgorithm,
@@ -96,14 +94,14 @@
   cobalt::script::GlobalEnvironment* global_env_ = nullptr;
   cobalt::script::ScriptValueFactory* script_value_factory_ = nullptr;
 
-  template <typename Promise = PromiseArray, typename T = dom::BufferSource>
+  template <typename Promise = PromiseArray, typename T = web::BufferSource>
   Promise CreatePromise() {
     DCHECK(script_value_factory_);
     return script_value_factory_->CreateBasicPromise<T>();
   }
 
   PromiseWrappable CreateKeyPromise();
-  dom::BufferSource CreateBufferSource(const std::vector<uint8_t>& input);
+  web::BufferSource CreateBufferSource(const std::vector<uint8_t>& input);
 };
 
 }  // namespace subtlecrypto
diff --git a/cobalt/system_window/system_window.cc b/cobalt/system_window/system_window.cc
index df86f86..f2edc9b 100644
--- a/cobalt/system_window/system_window.cc
+++ b/cobalt/system_window/system_window.cc
@@ -28,7 +28,7 @@
 namespace system_window {
 namespace {
 
-SystemWindow* g_the_window = NULL;
+SystemWindow* g_the_window = nullptr;
 
 int Round(const float f) {
   double d(f + 0.5f);
@@ -43,7 +43,7 @@
       window_(kSbWindowInvalid),
       key_down_(false) {
   if (!window_size) {
-    window_ = SbWindowCreate(NULL);
+    window_ = SbWindowCreate(nullptr);
   } else {
     SbWindowOptions options;
     SbWindowSetDefaultOptions(&options);
@@ -60,7 +60,7 @@
   DCHECK_EQ(this, g_the_window);
 
   if (g_the_window == this) {
-    g_the_window = NULL;
+    g_the_window = nullptr;
   }
   SbWindowDestroy(window_);
 }
@@ -277,7 +277,11 @@
   }
 
   DCHECK(g_the_window);
-  g_the_window->HandleInputEvent(event);
+  if (g_the_window != nullptr) {
+    g_the_window->HandleInputEvent(event);
+  } else {
+    SB_LOG(ERROR) << "Missing SystemWindow";
+  }
   return;
 }
 
diff --git a/cobalt/tools/gyp_to_gn.py b/cobalt/tools/gyp_to_gn.py
deleted file mode 100755
index 23f4a59..0000000
--- a/cobalt/tools/gyp_to_gn.py
+++ /dev/null
@@ -1,799 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# Modifications Copyright 2017 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Tries to translate a GYP file into a GN file.
-
-Output from this script should be piped to ``gn format --stdin``. It most likely
-needs to be manually fixed after that as well.
-"""
-
-import argparse
-import ast
-import collections
-import itertools
-import os
-import os.path
-import re
-import sys
-
-sys.path.append(
-    os.path.abspath(
-        os.path.join(__file__, os.pardir, os.pardir, os.pardir)))
-import cobalt.tools.paths  # pylint: disable=g-import-not-at-top
-
-VariableRewrite = collections.namedtuple('VariableRewrite',
-                                         ['name', 'value_rewrites'])
-
-# The directory containing the input file, as a repository-absolute (//foo/bar)
-# path
-repo_abs_input_file_dir = ''
-deps_substitutions = {}
-variable_rewrites = {}
-
-
-class GNException(Exception):
-  pass
-
-
-class GYPCondToGNNodeVisitor(ast.NodeVisitor):
-  """An AST NodeVisitor which translates GYP conditions to GN strings.
-
-  Given a GYP condition as an AST with mode eval, outputs a string containing
-  the GN equivalent of that condition. Simplifies conditions involving the
-  variables OS and os_posix, and performs variable substitutions.
-
-  Example:
-  (Assume arm_neon is renamed to arm_use_neon and converted from 0/1 to
-  true/false):
-
-  >>> g = GYPCondToGNNodeVisitor()
-  >>> g.visit(ast.parse('arm_neon and target_arch=="raspi-2"', mode='eval'))
-  '(arm_use_neon && target_cpu == "raspi-2")'
-  >>> g.visit(ast.parse('use_system_libjpeg and target_arch=="raspi-2"',
-  ...                   mode='eval'))
-  '(use_system_libjpeg && target_cpu == "raspi-2")'
-  >>> g.visit(ast.parse('arm_neon == 1', mode='eval'))
-  'arm_use_neon == true'
-  >>> g.visit(ast.parse('1', mode='eval'))
-  'true'
-  >>> g.visit(ast.parse('0', mode='eval'))
-  'false'
-  >>> g.visit(ast.parse('arm_neon != 0 and target_arch != "raspi-2" and
-  use_system_libjpeg or enable_doom_melon', mode='eval'))
-  '((arm_use_neon != false && target_cpu != "raspi-2" && use_system_libjpeg) ||
-  enable_doom_melon)'
-  >>> g.visit(ast.parse('arm_neon != 0 and target_arch != "raspi-2" or
-  use_system_libjpeg and enable_doom_melon', mode='eval'))
-  '((arm_use_neon != false && target_cpu != "raspi-2") || (use_system_libjpeg &&
-  enable_doom_melon))'
-  """
-
-  def visit_Expression(self, expr):  # pylint: disable=invalid-name
-    return self.visit(expr.body)
-
-  def visit_Num(self, num):  # pylint: disable=invalid-name
-    # A number that doesn't occur inside a Compare is taken in boolean context
-    return GYPValueToGNString(bool(num.n))
-
-  def visit_Str(self, string):  # pylint: disable=invalid-name
-    return GYPValueToGNString(string.s)
-
-  def visit_Name(self, name):  # pylint: disable=invalid-name
-    if name.id in variable_rewrites:
-      return variable_rewrites[name.id].name
-    else:
-      return name.id
-
-  def visit_BoolOp(self, boolop):  # pylint: disable=invalid-name
-    glue = ' && ' if isinstance(boolop.op, ast.And) else ' || '
-    return '(' + glue.join(itertools.imap(self.visit, boolop.values)) + ')'
-
-  def visit_Compare(self, compare):  # pylint: disable=invalid-name
-    if len(compare.ops) != 1:
-      raise GNException("This script doesn't support multiple operators in "
-                        'comparisons')
-
-    if isinstance(compare.ops[0], ast.Eq):
-      op = ' == '
-    elif isinstance(compare.ops[0], ast.NotEq):
-      op = ' != '
-    else:
-      raise GNException('Operator ' + str(compare.ops[0]) + ' not supported')
-
-    if isinstance(compare.left, ast.Name):
-      if isinstance(compare.comparators[0], ast.Name):  # var1 == var2
-        left = self.visit_Name(compare.left)
-        right = self.visit_Name(compare.comparators[0])
-      elif isinstance(compare.comparators[0], ast.Num):  # var1 == 42
-        left, right = TransformVariable(compare.left.id,
-                                        compare.comparators[0].n)
-      elif isinstance(compare.comparators[0], ast.Str):  # var1 == "some string"
-        left, right = TransformVariable(compare.left.id,
-                                        compare.comparators[0].s)
-      else:
-        raise GNException('Unknown RHS type ' + str(compare.comparators[0]))
-    else:
-      raise GNException('Non-variables on LHS of comparison are not supported')
-
-    if right == 'true' and op == ' == ' or right == 'false' and op == ' != ':
-      return left
-    elif right == 'false' and op == ' == ' or right == 'true' and op == ' != ':
-      return '!(' + left + ')'
-    else:
-      return left + op + right
-
-  def generic_visit(self, node):
-    raise GNException("I don't know how to convert node " + str(node))
-
-
-class OSComparisonRewriter(ast.NodeTransformer):
-
-  def visit_Compare(self, compare):  # pylint: disable=invalid-name
-    """Substitute instances of comparisons involving the OS and os_posix
-    variables with their known values in Compare nodes.
-
-    Examples:
-      ``OS == "starboard"`` -> ``True``
-      ``OS != "starboard"`` -> ``False``
-      ``OS == "linux"`` -> ``False``
-      ``os_posix == 1`` -> ``False``
-      ``os_bsd == 1`` -> ``False``
-    """
-    if len(compare.ops) != 1:
-      raise GNException("This script doesn't support multiple operators in "
-                        'comparisons')
-
-    if not isinstance(compare.left, ast.Name):
-      return compare
-
-    elif compare.left.id == 'OS':
-      if (isinstance(compare.comparators[0], ast.Str) and
-          compare.comparators[0].s == 'starboard'):
-        # OS == "starboard" -> True, OS != "starboard" -> False
-        new_node = ast.Num(1 if isinstance(compare.ops[0], ast.Eq) else 0)
-      else:
-        # OS == "something else" -> False, OS != "something else" -> True
-        new_node = ast.Num(0 if isinstance(compare.ops[0], ast.Eq) else 1)
-
-      return ast.copy_location(new_node, compare)
-
-    elif compare.left.id in {'os_posix', 'os_bsd'}:
-      if (isinstance(compare.comparators[0], ast.Num) and
-          compare.comparators[0].n == 0):
-        # os_posix == 0 -> True, os_posix != 0 -> False
-        # ditto for os_bsd
-        new_node = ast.Num(1 if isinstance(compare.ops[0], ast.Eq) else 0)
-      else:
-        # os_posix == (not 0) -> False, os_posix != (not 0) -> True
-        # ditto for os_bsd
-        new_node = ast.Num(0 if isinstance(compare.ops[0], ast.Eq) else 1)
-
-      return ast.copy_location(new_node, compare)
-
-    else:
-      return compare
-
-  def visit_BoolOp(self, boolop):  # pylint: disable=invalid-name
-    """Simplify BoolOp nodes by weeding out Falses and Trues resulting from
-    the elimination of OS comparisons.
-    """
-    doing_and = isinstance(boolop.op, ast.And)
-    new_values = map(self.visit, boolop.values)
-    new_values_filtered = []
-
-    for v in new_values:
-      if isinstance(v, ast.Num):
-        # "x and False", or "y or True" - short circuit
-        if (doing_and and v.n == 0) or (not doing_and and v.n == 1):
-          new_values_filtered = None
-          break
-        # "x and True", or "y or False" - skip the redundant value
-        elif (doing_and and v.n == 1) or (not doing_and and v.n == 0):
-          pass
-        else:
-          new_values_filtered.append(v)
-      else:
-        new_values_filtered.append(v)
-
-    if new_values_filtered is None:
-      new_node = ast.Num(0 if doing_and else 1)
-    elif len(new_values_filtered) == 0:
-      new_node = ast.Num(1 if doing_and else 0)
-    elif len(new_values_filtered) == 1:
-      new_node = new_values_filtered[0]
-    else:
-      new_node = ast.BoolOp(boolop.op, new_values_filtered)
-
-    return ast.copy_location(new_node, boolop)
-
-
-def Warn(msg):
-  print >> sys.stderr, '\x1b[1;33mWarning:\x1b[0m', msg
-
-
-def WarnAboutRemainingKeys(dic):
-  for key in dic:
-    Warn("I don't know what {} is".format(key))
-
-
-def TransformVariable(var, value):
-  """Given a variable and value, substitutes the variable name/value with
-  the new name/new value from the variable_rewrites dict, if applicable.
-
-  Example:
-    ``('arm_neon', 1)`` -> ``('arm_use_neon', 'true')``
-    ``('gl_type', 'none')`` -> ``('gl_type', 'none')``
-  """
-  if var in variable_rewrites:
-    var, value_rewrites = variable_rewrites[var]
-    if value_rewrites is not None:
-      value = value_rewrites[value]
-
-  return var, GYPValueToGNString(value)
-
-
-def GYPValueToGNString(value, allow_dicts=True):
-  """Returns a stringified GN equivalent of the Python value.
-
-  allow_dicts indicates if this function will allow converting dictionaries
-  to GN scopes. This is only possible at the top level, you can't nest a
-  GN scope in a list, so this should be set to False for recursive calls."""
-
-  if isinstance(value, str):
-    if value.find('\n') >= 0:
-      raise GNException("GN strings don't support newlines")
-    # Escape characters
-    ret = value.replace('\\', r'\\').replace('"', r'\"') \
-               .replace('$', r'\$')
-    # Convert variable substitutions
-    ret = re.sub(r'[<>]\((\w+)\)', r'$\1', ret)
-    return '"' + ret + '"'
-
-  if isinstance(value, unicode):
-    if value.find(u'\n') >= 0:
-      raise GNException("GN strings don't support newlines")
-    # Escape characters
-    ret = value.replace(u'\\', ur'\\').replace(u'"', ur'\"') \
-               .replace(u'$', ur'\$')
-    # Convert variable substitutions
-    ret = re.sub(ur'[<>]\((\w+)\)', ur'$\1', ret)
-    return (u'"' + ret + u'"').encode('utf-8')
-
-  if isinstance(value, bool):
-    if value:
-      return 'true'
-    return 'false'
-
-  if isinstance(value, list):
-    return '[ %s ]' % ', '.join(GYPValueToGNString(v) for v in value)
-
-  if isinstance(value, dict):
-    if not allow_dicts:
-      raise GNException('Attempting to recursively print a dictionary.')
-    result = ''
-    for key in sorted(value):
-      if not isinstance(key, basestring):
-        raise GNException('Dictionary key is not a string.')
-      result += '%s = %s\n' % (key, GYPValueToGNString(value[key], False))
-    return result
-
-  if isinstance(value, int):
-    return str(value)
-
-  raise GNException('Unsupported type when printing to GN.')
-
-
-def GYPTargetToGNString(target_dict):
-  """Given a target dict, output the GN equivalent."""
-
-  target_header_text = ''
-
-  target_name = target_dict.pop('target_name')
-  target_type = target_dict.pop('type')
-  if target_type in {'executable', 'shared_library', 'static_library'}:
-    if target_type == 'static_library':
-      Warn('converting static library, check to see if it should be a '
-           'source_set')
-    target_header_text += '{}("{}") {{\n'.format(target_type, target_name)
-  elif target_type == 'none':
-    target_header_text += 'group("{}") {{\n'.format(target_name)
-  elif target_type == '<(gtest_target_type)':
-    target_header_text += 'test("{}") {{\n'.format(target_name)
-  elif target_type == '<(final_executable_type)':
-    target_header_text += 'final_executable("{}") {{\n'.format(target_name)
-  elif target_type == '<(component)' or target_type == '<(library)':
-    Warn('converting static library, check to see if it should be a '
-         'source_set')
-    target_header_text += 'static_library("{}") {{\n'.format(target_name)
-  else:
-    raise GNException("I don't understand target type {}".format(target_type))
-
-  target_body_text, configs_text = \
-      GYPTargetToGNString_Inner(target_dict, target_name)
-
-  return configs_text + target_header_text + target_body_text + '}\n\n'
-
-
-def ProcessIncludeExclude(dic, param):
-  """Translate dic[param] and dic[param + '!'] lists to GN.
-
-  Example input:
-    {
-      'sources': [ 'foo.cc', 'bar.cc', 'baz.cc' ],
-      'sources!': [ 'bar.cc' ]
-    }
-  Example output:
-    sources = [ 'foo.cc', 'bar.cc', 'baz.cc' ]
-    sources -= [ 'bar.cc' ]
-  """
-  ret = ''
-  if param in dic:
-    value = dic.pop(param)
-    ret += '{} = [ {} ]\n'.format(param, ', '.join(
-        GYPValueToGNString(s) for s in value))
-  if param + '!' in dic:
-    value = dic.pop(param + '!')
-    ret += '{} -= [ {} ]\n'.format(param, ', '.join(
-        GYPValueToGNString(s) for s in value))
-  ret += '\n'
-  return ret
-
-
-def ProcessDependentSettings(dependent_settings_dict, target_dict, param,
-                             renamed_param, config_name):
-  """Translates direct_dependent_settings and all_dependent_settings blocks
-  to their GN equivalents. This is done by creating a new GN config, putting
-  the settings in that config, and adding the config to the target's
-  public_configs/all_dependent_configs. Returns a tuple of
-  (target_text, config_text).
-
-  Also eliminates the translated settings from the target_dict so they aren't
-  translated twice.
-
-  Example input:
-    {
-      'target_name': 'abc',
-      'direct_dependent_settings': {
-        'defines': [ "FOO" ]
-      }
-    }
-
-  Example target text output:
-    public_configs = [ ":abc_direct_config" ]
-
-  Example config text output:
-    config("abc_direct_config") {
-      defines = [ "FOO" ]
-    }
-  """
-  ret_target = ''
-  ret_config = 'config("{}") {{\n'.format(config_name)
-
-  def FilterList(key):
-    if key in target_dict and key in dependent_settings_dict:
-      filtered_list = sorted(
-          set(target_dict[key]) - set(dependent_settings_dict[key]))
-      if filtered_list:
-        target_dict[key] = filtered_list
-      else:
-        del target_dict[key]
-
-  for inner_param in [
-      'include_dirs', 'defines', 'asmflags', 'cflags', 'cflags_c', 'cflags_cc',
-      'cflags_objc', 'cflags_objcc', 'ldflags'
-  ]:
-    FilterList(inner_param)
-    FilterList(inner_param + '!')
-    ret_config += ProcessIncludeExclude(dependent_settings_dict, inner_param)
-
-  if 'variables' in dependent_settings_dict:
-    Warn("variables block inside {}. You'll need to handle that manually."
-         .format(param))
-    del dependent_settings_dict['variables']
-
-  if 'conditions' in dependent_settings_dict:
-    for i, cond_block in enumerate(dependent_settings_dict['conditions']):
-      cond_config_name = '{}_{}'.format(config_name, i)
-      t, c = GYPConditionToGNString(cond_block,
-               lambda dsd: ProcessDependentSettings(dsd, target_dict, param,
-                                                    renamed_param,
-                                                    cond_config_name))
-      ret_config += c
-      ret_target += t
-    del dependent_settings_dict['conditions']
-
-  if 'target_conditions' in dependent_settings_dict:
-    for i, cond_block in \
-        enumerate(dependent_settings_dict['target_conditions']):
-      cond_config_name = '{}_t{}'.format(config_name, i)
-      t, c = GYPConditionToGNString(cond_block,
-               lambda dsd: ProcessDependentSettings(dsd, target_dict, param,
-                                                    renamed_param,
-                                                    cond_config_name))
-      ret_config += c
-      ret_target += t
-    del dependent_settings_dict['target_conditions']
-
-  ret_config += '}\n\n'
-  ret_target += '{} = [ ":{}" ]\n\n'.format(renamed_param, config_name)
-  WarnAboutRemainingKeys(dependent_settings_dict)
-  return ret_target, ret_config
-
-
-def GYPTargetToGNString_Inner(target_dict, target_name):
-  """Translates the body of a GYP target into a GN string."""
-  configs_text = ''
-  target_text = ''
-  dependent_text = ''
-
-  if 'variables' in target_dict:
-    target_text += GYPVariablesToGNString(target_dict['variables'])
-    del target_dict['variables']
-
-  target_text += ProcessIncludeExclude(target_dict, 'sources')
-
-  for param, renamed_param, config_name_elem in \
-      [('direct_dependent_settings', 'public_configs', 'direct'),
-       ('all_dependent_settings', 'all_dependent_configs', 'dependent')]:
-    if param in target_dict:
-      config_name = '{}_{}_config'.format(target_name, config_name_elem)
-      # Append dependent_text to target_text after include_dirs/defines/etc.
-      dependent_text, c = ProcessDependentSettings(
-          target_dict[param], target_dict, param, renamed_param, config_name)
-      configs_text += c
-      del target_dict[param]
-
-  for param in [
-      'include_dirs', 'defines', 'asmflags', 'cflags', 'cflags_c', 'cflags_cc',
-      'cflags_objc', 'cflags_objcc', 'ldflags'
-  ]:
-    target_text += ProcessIncludeExclude(target_dict, param)
-
-  target_text += dependent_text
-
-  if 'export_dependent_settings' in target_dict:
-    target_text += GYPDependenciesToGNString(
-        'public_deps', target_dict['export_dependent_settings'])
-
-    # Remove dependencies covered here from the ordinary dependencies list
-    target_dict['dependencies'] = sorted(
-        set(target_dict['dependencies']) -
-        set(target_dict['export_dependent_settings']))
-    if not target_dict['dependencies']:
-      del target_dict['dependencies']
-
-    del target_dict['export_dependent_settings']
-
-  if 'dependencies' in target_dict:
-    target_text += GYPDependenciesToGNString('deps',
-                                             target_dict['dependencies'])
-    del target_dict['dependencies']
-
-  if 'conditions' in target_dict:
-    for cond_block in target_dict['conditions']:
-      t, c = GYPConditionToGNString(
-          cond_block, lambda td: GYPTargetToGNString_Inner(td, target_name))
-      configs_text += c
-      target_text += t
-    del target_dict['conditions']
-
-  if 'target_conditions' in target_dict:
-    for cond_block in target_dict['target_conditions']:
-      t, c = GYPConditionToGNString(
-          cond_block, lambda td: GYPTargetToGNString_Inner(td, target_name))
-      configs_text += c
-      target_text += t
-    del target_dict['target_conditions']
-
-  WarnAboutRemainingKeys(target_dict)
-  return target_text, configs_text
-
-
-def GYPDependenciesToGNString(param, dep_list):
-  r"""Translates a GYP dependency into a GN dependency. Tries to intelligently
-  perform target renames as according to GN style conventions.
-
-  Examples:
-  (Note that <(DEPTH) has already been translated into // by the time this
-  function is called)
-  >>> GYPDependenciesToGNString('deps', ['//cobalt/math/math.gyp:math'])
-  'deps = [ "//cobalt/math" ]\n\n'
-  >>> GYPDependenciesToGNString('public_deps',
-  ...                           ['//testing/gtest.gyp:gtest'])
-  'public_deps = [ "//testing/gtest" ]\n\n'
-  >>> GYPDependenciesToGNString('deps', ['//third_party/icu/icu.gyp:icui18n'])
-  'deps = [ "//third_party/icu:icui18n" ]\n\n'
-  >>> GYPDependenciesToGNString('deps',
-  ...     ['//cobalt/browser/browser_bindings_gen.gyp:generated_types'])
-  'deps = [ "//cobalt/browser/browser_bindings_gen:generated_types" ]\n\n'
-  >>> GYPDependenciesToGNString('deps', ['allocator'])
-  'deps = [ ":allocator" ]\n\n'
-  >>> # Suppose the input file is in //cobalt/foo/bar/
-  >>> GYPDependenciesToGNString('deps', ['../baz.gyp:quux'])
-  'deps = [ "//cobalt/foo/baz:quux" ]\n\n'
-  """
-  new_dep_list = []
-  for dep in dep_list:
-    if dep in deps_substitutions:
-      new_dep_list.append(deps_substitutions[dep])
-    else:
-      m1 = re.match(r'(.*/)?(\w+)/\2\.gyp:\2$', dep)
-      m2 = re.match(r'(.*/)?(\w+)\.gyp:\2$', dep)
-      m3 = re.match(r'(.*/)?(\w+)/\2.gyp:(\w+)$', dep)
-      m4 = re.match(r'(.*)\.gyp:(\w+)$', dep)
-      m5 = re.match(r'\w+', dep)
-      if m1:
-        new_dep = '{}{}'.format(m1.group(1) or '', m1.group(2))
-      elif m2:
-        new_dep = '{}{}'.format(m2.group(1) or '', m2.group(2))
-      elif m3:
-        new_dep = '{}{}:{}'.format(m3.group(1) or '', m3.group(2), m3.group(3))
-      elif m4:
-        new_dep = '{}:{}'.format(m4.group(1), m4.group(2))
-      elif m5:
-        new_dep = ':' + dep
-      else:
-        Warn("I don't know how to translate dependency " + dep)
-        continue
-
-      if not (new_dep.startswith('//') or new_dep.startswith(':') or
-              os.path.isabs(new_dep)):
-        # Rebase new_dep to be repository-absolute
-        new_dep = os.path.normpath(repo_abs_input_file_dir + new_dep)
-
-      new_dep_list.append(new_dep)
-
-  quoted_deps = ['"{}"'.format(d) for d in new_dep_list]
-  return '{} = [ {} ]\n\n'.format(param, ', '.join(quoted_deps))
-
-
-def GYPVariablesToGNString(varblock):
-  """Translates a GYP variables block into its GN equivalent, performing
-  variable substitutions as necessary."""
-  ret = ''
-
-  if 'variables' in varblock:
-    # Recursively flatten nested variables dicts.
-    ret += GYPVariablesToGNString(varblock['variables'])
-
-  for k, v in varblock.viewitems():
-    if k in {'variables', 'conditions'}:
-      continue
-
-    if k.endswith('%'):
-      k = k[:-1]
-
-    if not k.endswith('!'):
-      ret += '{} = {}\n'.format(*TransformVariable(k, v))
-    else:
-      ret += '{} -= {}\n'.format(*TransformVariable(k[:-1], v))
-
-  if 'conditions' in varblock:
-    for cond_block in varblock['conditions']:
-      ret += GYPConditionToGNString(cond_block, GYPVariablesToGNString)[0]
-
-  ret += '\n'
-  return ret
-
-
-def GYPConditionToGNString(cond_block, recursive_translate):
-  """Translates a GYP condition block into a GN string. The recursive_translate
-  param is a function that is called with the true subdict and the false
-  subdict if applicable. The recursive_translate function returns either a
-  single string that should go inside the if/else block, or a tuple of
-  (target_text, config_text), in which the target_text goes inside the if/else
-  block and the config_text goes outside.
-
-  Returns a tuple (target_text, config_text), where config_text is the empty
-  string if recursive_translate only returns one string.
-  """
-  cond = cond_block[0]
-  iftrue = cond_block[1]
-  iffalse = cond_block[2] if len(cond_block) == 3 else None
-
-  ast_cond = ast.parse(cond, mode='eval')
-  ast_cond = OSComparisonRewriter().visit(ast_cond)
-  translated_cond = GYPCondToGNNodeVisitor().visit(ast_cond)
-
-  if translated_cond == 'false' and iffalse is None:
-    # if (false) { ... } -> nothing
-    # Special cased to avoid printing warnings from the unnecessary translation
-    # of the true clause
-    return '', ''
-
-  translated_iftrue = recursive_translate(iftrue)
-  if isinstance(translated_iftrue, tuple):
-    iftrue_text, aux_iftrue_text = translated_iftrue
-  else:
-    iftrue_text, aux_iftrue_text = translated_iftrue, ''
-
-  if translated_cond == 'true':  # Tautology - just return the body
-    return iftrue_text, aux_iftrue_text
-
-  elif iffalse is None:  # Non-tautology, non-contradiction, no else clause
-    return ('if (' + translated_cond + ') {\n' + iftrue_text + '}\n\n',
-            aux_iftrue_text)
-
-  else:  # Non-tautology, else clause present
-    translated_iffalse = recursive_translate(iffalse)
-    if isinstance(translated_iffalse, tuple):
-      iffalse_text, aux_iffalse_text = translated_iffalse
-    else:
-      iffalse_text, aux_iffalse_text = translated_iffalse, ''
-
-    if translated_cond == 'false':  # if (false) { blah } else { ... } -> ...
-      return iffalse_text, aux_iffalse_text
-
-    else:
-      return ('if (' + translated_cond + ') {\n' + iftrue_text + '} else {\n' +
-              iffalse_text + '}\n\n', aux_iftrue_text + aux_iffalse_text)
-
-
-def ToplevelGYPToGNString(toplevel_dict):
-  """Translates a toplevel GYP dict to GN. This is the main function which is
-  called to perform the GYP to GN translation.
-  """
-  ret = ''
-
-  if 'variables' in toplevel_dict:
-    ret += GYPVariablesToGNString(toplevel_dict['variables'])
-    del toplevel_dict['variables']
-
-  if 'targets' in toplevel_dict:
-    for t in toplevel_dict['targets']:
-      ret += GYPTargetToGNString(t)
-    del toplevel_dict['targets']
-
-  if 'conditions' in toplevel_dict:
-    for cond_block in toplevel_dict['conditions']:
-      ret += GYPConditionToGNString(cond_block, ToplevelGYPToGNString)[0]
-    del toplevel_dict['conditions']
-
-  if 'target_conditions' in toplevel_dict:
-    for cond_block in toplevel_dict['target_conditions']:
-      ret += GYPConditionToGNString(cond_block, ToplevelGYPToGNString)[0]
-    del toplevel_dict['target_conditions']
-
-  WarnAboutRemainingKeys(toplevel_dict)
-  return ret
-
-
-def LoadPythonDictionary(path):
-  with open(path, 'r') as fin:
-    file_string = fin.read()
-  try:
-    file_data = eval(file_string, {'__builtins__': None}, None)  # pylint: disable=eval-used
-  except SyntaxError, e:
-    e.filename = path
-    raise
-  except Exception, e:
-    raise Exception('Unexpected error while reading %s: %s' % (path, str(e)))
-
-  assert isinstance(file_data, dict), '%s does not eval to a dictionary' % path
-
-  return file_data
-
-
-def ReplaceSubstrings(values, search_for, replace_with):
-  """Recursively replaces substrings in a value.
-
-  Replaces all substrings of the "search_for" with "replace_with" for all
-  strings occurring in "values". This is done by recursively iterating into
-  lists as well as the keys and values of dictionaries.
-  """
-  if isinstance(values, str):
-    return values.replace(search_for, replace_with)
-
-  if isinstance(values, list):
-    return [ReplaceSubstrings(v, search_for, replace_with) for v in values]
-
-  if isinstance(values, dict):
-    # For dictionaries, do the search for both the key and values.
-    result = {}
-    for key, value in values.viewitems():
-      new_key = ReplaceSubstrings(key, search_for, replace_with)
-      new_value = ReplaceSubstrings(value, search_for, replace_with)
-      result[new_key] = new_value
-    return result
-
-  # Assume everything else is unchanged.
-  return values
-
-
-def CalculatePaths(filename):
-  global repo_abs_input_file_dir
-
-  abs_input_file_dir = os.path.abspath(os.path.dirname(filename))
-  abs_repo_root = cobalt.tools.paths.REPOSITORY_ROOT + '/'
-  if not abs_input_file_dir.startswith(abs_repo_root):
-    raise ValueError('Input file is not in repository')
-
-  repo_abs_input_file_dir = '//' + abs_input_file_dir[len(abs_repo_root):] + '/'
-
-
-def LoadDepsSubstitutions():
-  dirname = os.path.dirname(__file__)
-  if dirname:
-    dirname += '/'
-
-  with open(dirname + 'deps_substitutions.txt', 'r') as f:
-    for line in itertools.ifilter(lambda lin: lin.strip(), f):
-      dep, new_dep = line.split()
-      deps_substitutions[dep.replace('<(DEPTH)/', '//')] = new_dep
-
-
-def LoadVariableRewrites():
-  dirname = os.path.dirname(__file__)
-  if dirname:
-    dirname += '/'
-
-  vr = LoadPythonDictionary(dirname + 'variable_rewrites.dict')
-
-  if 'rewrites' in vr:
-    for old_name, rewrite in vr['rewrites'].viewitems():
-      variable_rewrites[old_name] = VariableRewrite(*rewrite)
-
-  if 'renames' in vr:
-    for old_name, new_name in vr['renames'].viewitems():
-      variable_rewrites[old_name] = VariableRewrite(new_name, None)
-
-  if 'booleans' in vr:
-    bool_mapping = {0: False, 1: True}
-    for v in vr['booleans']:
-      if v in variable_rewrites:
-        variable_rewrites[v] = \
-            variable_rewrites[v]._replace(value_rewrites=bool_mapping)
-      else:
-        variable_rewrites[v] = VariableRewrite(v, bool_mapping)
-
-
-def main():
-  parser = argparse.ArgumentParser(description='Convert a subset of GYP to GN.')
-  parser.add_argument(
-      '-r',
-      '--replace',
-      action='append',
-      help='Replaces substrings. If passed a=b, replaces all '
-      'substrs a with b.')
-  parser.add_argument('file', help='The GYP file to read.')
-  args = parser.parse_args()
-
-  CalculatePaths(args.file)
-
-  data = LoadPythonDictionary(args.file)
-  if args.replace:
-    # Do replacements for all specified patterns.
-    for replace in args.replace:
-      split = replace.split('=')
-      # Allow 'foo=' to replace with nothing.
-      if len(split) == 1:
-        split.append('')
-      assert len(split) == 2, "Replacement must be of the form 'key=value'."
-      data = ReplaceSubstrings(data, split[0], split[1])
-  # Also replace <(DEPTH)/ with //
-  data = ReplaceSubstrings(data, '<(DEPTH)/', '//')
-
-  LoadDepsSubstitutions()
-  LoadVariableRewrites()
-
-  print ToplevelGYPToGNString(data)
-
-
-if __name__ == '__main__':
-  main()
diff --git a/cobalt/tools/variable_rewrites.dict b/cobalt/tools/variable_rewrites.dict
deleted file mode 100644
index f7a6342..0000000
--- a/cobalt/tools/variable_rewrites.dict
+++ /dev/null
@@ -1,38 +0,0 @@
-{
-  # Variables which need to be renamed, and whose values need to be
-  # simultaneously changed
-  "rewrites": {
-  },
-
-  # Variables which only need to be renamed
-  "renames": {
-    "cobalt_fastbuild": "cobalt_use_fastbuild",
-    "cobalt_version": "cobalt_build_id",
-    "in_app_dial": "enable_in_app_dial",
-    "sb_allows_memory_tracking": "sb_allow_memory_tracking",
-    "target_arch": "target_cpu",
-    "tizen_os": "is_tizen_os",
-  },
-
-  # List of variables which are actually booleans
-  # Note that we use the old names here!
-  "booleans": [
-    "abort_on_allocation_failure",
-    "cobalt_copy_test_data",
-    "cobalt_fastbuild",
-    "enable_about_scheme",
-    "enable_account_manager",
-    "enable_configure_request_job_factory",
-    "enable_crash_log",
-    "enable_debugger",
-    "enable_fake_microphone",
-    "enable_file_scheme",
-    "enable_network_logging",
-    "enable_webdriver",
-    "enable_spdy",
-    "enable_xhr_header_filtering",
-    "in_app_dial",
-    "sb_allows_memory_tracking",
-    "tizen_os",
-  ]
-}
diff --git a/cobalt/updater/win/installer/create_installer_archive.py b/cobalt/updater/win/installer/create_installer_archive.py
deleted file mode 100644
index cbbfd26..0000000
--- a/cobalt/updater/win/installer/create_installer_archive.py
+++ /dev/null
@@ -1,373 +0,0 @@
-# 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.
-"""Script to create the Chrome Updater Installer archive.
-
-  This script is used to create an archive of all the files required for a
-  Chrome Updater install in appropriate directory structure. It reads
-  updater.release file as input, creates updater.7z uncompressed archive, and
-  generates the updater.packed.7z compressed archive.
-
-"""
-
-import ConfigParser
-import glob
-import optparse
-import os
-import shutil
-import subprocess
-import sys
-
-# Directory name inside the uncompressed archive where all the files are.
-UPDATER_DIR = "bin"
-
-# Suffix to uncompressed full archive file, appended to options.output_name.
-ARCHIVE_SUFFIX = ".7z"
-
-# compressed full archive suffix, will be prefixed by options.output_name.
-COMPRESSED_ARCHIVE_SUFFIX = ".packed.7z"
-TEMP_ARCHIVE_DIR = "temp_installer_archive"
-
-g_archive_inputs = []
-
-
-def CompressUsingLZMA(build_dir, compressed_file, input_file, verbose):
-  lzma_exec = GetLZMAExec(build_dir)
-  cmd = [
-      lzma_exec,
-      'a',
-      '-t7z',
-      # Flags equivalent to -mx9 (ultra) but with the bcj2 turned on (exe
-      # pre-filter). These arguments are the similar to what the Chrome mini
-      # installer is using.
-      '-m0=BCJ2',
-      '-m1=LZMA:d27:fb128',
-      '-m2=LZMA:d22:fb128:mf=bt2',
-      '-m3=LZMA:d22:fb128:mf=bt2',
-      '-mb0:1',
-      '-mb0s1:2',
-      '-mb0s2:3',
-      os.path.abspath(compressed_file),
-      os.path.abspath(input_file),
-  ]
-  if os.path.exists(compressed_file):
-    os.remove(compressed_file)
-  RunSystemCommand(cmd, verbose)
-
-
-def CopyAllFilesToStagingDir(config, staging_dir, build_dir):
-  """Copies the files required for installer archive.
-  """
-  CopySectionFilesToStagingDir(config, 'GENERAL', staging_dir, build_dir)
-
-
-def CopySectionFilesToStagingDir(config, section, staging_dir, src_dir):
-  """Copies installer archive files specified in section from src_dir to
-  staging_dir. This method reads section from config and copies all the
-  files specified from src_dir to staging dir.
-  """
-  for option in config.options(section):
-    src_subdir = option.replace('\\', os.sep)
-    dst_dir = os.path.join(staging_dir, config.get(section, option))
-    dst_dir = dst_dir.replace('\\', os.sep)
-    src_paths = glob.glob(os.path.join(src_dir, src_subdir))
-    if src_paths and not os.path.exists(dst_dir):
-      os.makedirs(dst_dir)
-    for src_path in src_paths:
-      print(src_path)
-      dst_path = os.path.join(dst_dir, os.path.basename(src_path))
-      if not os.path.exists(dst_path):
-        g_archive_inputs.append(src_path)
-        print('paths src_path={0}, dest_dir={1}'.format(src_path, dst_dir))
-        shutil.copy(src_path, dst_dir)
-
-
-def GetLZMAExec(build_dir):
-  if sys.platform == 'win32':
-    lzma_exec = os.path.join(build_dir, "..", "..", "third_party", "lzma_sdk",
-                             "Executable", "7za.exe")
-  else:
-    lzma_exec = '7zr'  # Use system 7zr.
-  return lzma_exec
-
-
-def MakeStagingDirectory(staging_dir):
-  """Creates a staging path for installer archive. If directory exists already,
-  deletes the existing directory.
-  """
-  file_path = os.path.join(staging_dir, TEMP_ARCHIVE_DIR)
-  if os.path.exists(file_path):
-    shutil.rmtree(file_path)
-  os.makedirs(file_path)
-  return file_path
-
-
-def Readconfig(input_file):
-  """Reads config information from input file after setting default value of
-  global variables.
-  """
-  variables = {}
-  variables['UpdaterDir'] = UPDATER_DIR
-  config = ConfigParser.SafeConfigParser(variables)
-  config.read(input_file)
-  return config
-
-
-def RunSystemCommand(cmd, verbose):
-  """Runs |cmd|, prints the |cmd| and its output if |verbose|; otherwise
-  captures its output and only emits it on failure.
-  """
-  if verbose:
-    print 'Running', cmd
-
-  try:
-    # Run |cmd|, redirecting stderr to stdout in order for captured errors to be
-    # inline with corresponding stdout.
-    output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
-    if verbose:
-      print output
-  except subprocess.CalledProcessError as e:
-    raise Exception("Error while running cmd: %s\n"
-                    "Exit code: %s\n"
-                    "Command output:\n%s" % (e.cmd, e.returncode, e.output))
-
-
-def CreateArchiveFile(options, staging_dir):
-  """Creates a new installer archive file after deleting any existing old file.
-  """
-  # First create an uncompressed archive file for the current build (updater.7z)
-  lzma_exec = GetLZMAExec(options.build_dir)
-  archive_file = os.path.join(options.output_dir,
-                              options.output_name + ARCHIVE_SUFFIX)
-
-  if options.depfile:
-    # If a depfile was requested, do the glob of the staging dir and generate
-    # a list of dependencies in .d format. We list the files that were copied
-    # into the staging dir, not the files that are actually in the staging dir
-    # because the ones in the staging dir will never be edited, and we want
-    # to have the build be triggered when the thing-that-was-copied-there
-    # changes.
-
-    def PathFixup(path):
-      """Fixes path for depfile format: backslash to forward slash, and
-      backslash escaping for spaces."""
-      return path.replace('\\', '/').replace(' ', '\\ ')
-
-    # Gather the list of files in the staging dir that will be zipped up. We
-    # only gather this list to make sure that g_archive_inputs is complete (i.e.
-    # that there's not file copies that got missed).
-    staging_contents = []
-    for root, files in os.walk(os.path.join(staging_dir, UPDATER_DIR)):
-      for filename in files:
-        staging_contents.append(PathFixup(os.path.join(root, filename)))
-
-    # Make sure there's an archive_input for each staging dir file.
-    for staging_file in staging_contents:
-      for archive_input in g_archive_inputs:
-        archive_rel = PathFixup(archive_input)
-        if (os.path.basename(staging_file).lower() == os.path.basename(
-            archive_rel).lower()):
-          break
-      else:
-        raise Exception('Did not find an archive input file for "%s"' %
-                        staging_file)
-
-    # Finally, write the depfile referencing the inputs.
-    with open(options.depfile, 'wb') as f:
-      f.write(
-          PathFixup(os.path.relpath(archive_file, options.build_dir)) +
-          ': \\\n')
-      f.write('  ' + ' \\\n  '.join(PathFixup(x) for x in g_archive_inputs))
-
-  # It is important to use abspath to create the path to the directory because
-  # if you use a relative path without any .. sequences then 7za.exe uses the
-  # entire relative path as part of the file paths in the archive. If you have
-  # a .. sequence or an absolute path then only the last directory is stored as
-  # part of the file paths in the archive, which is what we want.
-  cmd = [
-      lzma_exec,
-      'a',
-      '-t7z',
-      archive_file,
-      os.path.abspath(os.path.join(staging_dir, UPDATER_DIR)),
-      '-mx0',
-  ]
-  # There does not seem to be any way in 7za.exe to override existing file so
-  # we always delete before creating a new one.
-  if not os.path.exists(archive_file):
-    RunSystemCommand(cmd, options.verbose)
-  elif options.skip_rebuild_archive != "true":
-    os.remove(archive_file)
-    RunSystemCommand(cmd, options.verbose)
-
-  # Do not compress the archive when skip_archive_compression is specified.
-  if options.skip_archive_compression:
-    compressed_file = os.path.join(
-        options.output_dir, options.output_name + COMPRESSED_ARCHIVE_SUFFIX)
-    if os.path.exists(compressed_file):
-      os.remove(compressed_file)
-    return os.path.basename(archive_file)
-
-  compressed_archive_file = options.output_name + COMPRESSED_ARCHIVE_SUFFIX
-  compressed_archive_file_path = os.path.join(options.output_dir,
-                                              compressed_archive_file)
-  CompressUsingLZMA(options.build_dir, compressed_archive_file_path,
-                    archive_file, options.verbose)
-
-  return compressed_archive_file
-
-
-_RESOURCE_FILE_HEADER = """\
-// This file is automatically generated by create_installer_archive.py.
-// It contains the resource entries that are going to be linked inside the exe.
-// For each file to be linked there should be two lines:
-// - The first line contains the output filename (without path) and the
-// type of the resource ('BN' - not compressed , 'BL' - LZ compressed,
-// 'B7' - LZMA compressed)
-// - The second line contains the path to the input file. Uses '/' to
-// separate path components.
-"""
-
-
-def CreateResourceInputFile(output_dir, archive_file, resource_file_path,
-                            component_build, staging_dir):
-  """Creates resource input file for installer target."""
-
-  # An array of (file, type, path) tuples of the files to be included.
-  resources = [(archive_file, 'B7', os.path.join(output_dir, archive_file))]
-
-  with open(resource_file_path, 'w') as f:
-    f.write(_RESOURCE_FILE_HEADER)
-    for (file, type, path) in resources:
-      f.write('\n%s  %s\n    "%s"\n' % (file, type, path.replace("\\", "/")))
-
-
-def ParseDLLsFromDeps(build_dir, runtime_deps_file):
-  """Parses the runtime_deps file and returns the set of DLLs in it, relative
-  to build_dir."""
-  build_dlls = set()
-  args = open(runtime_deps_file).read()
-  for l in args.splitlines():
-    if os.path.splitext(l)[1] == ".dll":
-      build_dlls.add(os.path.join(build_dir, l))
-  return build_dlls
-
-
-# Copies component build DLLs for the setup to be able to find those DLLs at
-# run-time.
-# This is meant for developer builds only and should never be used to package
-# an official build.
-def DoComponentBuildTasks(staging_dir, build_dir, setup_runtime_deps):
-  installer_dir = os.path.join(staging_dir, UPDATER_DIR)
-  if not os.path.exists(installer_dir):
-    os.mkdir(installer_dir)
-
-  setup_component_dlls = ParseDLLsFromDeps(build_dir, setup_runtime_deps)
-
-  for setup_component_dll in setup_component_dlls:
-    g_archive_inputs.append(setup_component_dll)
-    shutil.copy(setup_component_dll, installer_dir)
-
-
-def main(options):
-  """Main method that reads input file, creates archive file and writes
-  resource input file.
-  """
-  config = Readconfig(options.input_file)
-
-  staging_dir = MakeStagingDirectory(options.staging_dir)
-
-  # Copy the files from the build dir.
-  CopyAllFilesToStagingDir(config, staging_dir, options.build_dir)
-
-  if options.component_build == '1':
-    DoComponentBuildTasks(staging_dir, options.build_dir,
-                          options.setup_runtime_deps)
-
-  # Name of the archive file built (for example - updater.7z)
-  archive_file = CreateArchiveFile(options, staging_dir)
-  CreateResourceInputFile(options.output_dir, archive_file,
-                          options.resource_file_path,
-                          options.component_build == '1', staging_dir)
-
-
-def _ParseOptions():
-  parser = optparse.OptionParser()
-  parser.add_option(
-      '-i',
-      '--input_file',
-      help='Input file describing which files to archive.')
-  parser.add_option(
-      '-b',
-      '--build_dir',
-      help='Build directory. The paths in input_file are relative to this.')
-  parser.add_option(
-      '--staging_dir',
-      help='Staging directory where intermediate files and directories '
-      'will be created')
-  parser.add_option(
-      '-o',
-      '--output_dir',
-      help='The output directory where the archives will be written. '
-      'Defaults to the build_dir.')
-  parser.add_option(
-      '--resource_file_path',
-      help='The path where the resource file will be output. ')
-  parser.add_option(
-      '-s',
-      '--skip_rebuild_archive',
-      default="False",
-      help='Skip re-building updater.7z archive if it exists.')
-  parser.add_option(
-      '-n',
-      '--output_name',
-      default='updater',
-      help='Name used to prefix names of generated archives.')
-  parser.add_option(
-      '--component_build',
-      default='0',
-      help='Whether this archive is packaging a component build.')
-  parser.add_option(
-      '--skip_archive_compression',
-      action='store_true',
-      default=False,
-      help='Turn off compression of updater.7z into updater.packed.7z and '
-      'helpfully delete any old updater.packed.7z in |output_dir|.')
-  parser.add_option(
-      '--depfile',
-      help='Generate a depfile with the given name listing the implicit inputs '
-      'to the archive process that can be used with a build system.')
-  parser.add_option(
-      '--setup_runtime_deps',
-      help='A file listing runtime dependencies for setup.exe. This will be '
-      'used to get a list of DLLs to archive in a component build.')
-  parser.add_option(
-      '-v', '--verbose', action='store_true', dest='verbose', default=False)
-
-  options, _ = parser.parse_args()
-  if not options.build_dir:
-    parser.error('You must provide a build dir.')
-
-  options.build_dir = os.path.normpath(options.build_dir)
-
-  if not options.staging_dir:
-    parser.error('You must provide a staging dir.')
-
-  if not options.input_file:
-    parser.error('You must provide an input file')
-
-  is_component_build = options.component_build == '1'
-  if is_component_build and not options.setup_runtime_deps:
-    parser.error("updater_runtime_deps must be specified for a component build")
-
-  if not options.output_dir:
-    options.output_dir = options.build_dir
-
-  return options
-
-
-if '__main__' == __name__:
-  options = _ParseOptions()
-  if options.verbose:
-    print sys.argv
-  sys.exit(main(options))
diff --git a/cobalt/watchdog/BUILD.gn b/cobalt/watchdog/BUILD.gn
index e560f78..fc1b84a 100644
--- a/cobalt/watchdog/BUILD.gn
+++ b/cobalt/watchdog/BUILD.gn
@@ -18,5 +18,9 @@
     "watchdog.h",
   ]
 
-  deps = [ "//starboard/common" ]
+  deps = [
+    "//base",
+    "//cobalt/browser:browser_switches",
+    "//starboard/common",
+  ]
 }
diff --git a/cobalt/watchdog/README.md b/cobalt/watchdog/README.md
index 7e250ba..82872c2 100644
--- a/cobalt/watchdog/README.md
+++ b/cobalt/watchdog/README.md
@@ -42,3 +42,18 @@
 To implement liveness check behavior monitoring, a Javascript adapter layer
 will need to be implemented that handles the periodic pinging and callbacks
 required.
+
+### III. Debugging
+
+Watchdog also provides a function MaybeInjectDelay() that can be used to
+artificially inject thread sleeps to create Watchdog violations. This is
+especially useful for testing and should be placed near where Ping() calls are
+made.
+
+The delay works by reading the watchdog command line switch which takes in a
+comma separated string with settings that correspond to
+delay_name_,delay_wait_time_microseconds_,delay_sleep_time_microseconds_
+(ex. "renderer,100000,1000"). The delay name is the name of the Watchdog client
+to inject delay, the delay wait is the time in between delays (periodic), and
+the delay sleep is the delay duration. They have default values of empty, 0,
+and 0 respectively.
diff --git a/cobalt/watchdog/watchdog.cc b/cobalt/watchdog/watchdog.cc
index f63558c..d6a2380 100644
--- a/cobalt/watchdog/watchdog.cc
+++ b/cobalt/watchdog/watchdog.cc
@@ -17,11 +17,16 @@
 #include <utility>
 #include <vector>
 
+#include "base/command_line.h"
 #include "cobalt/watchdog/watchdog.h"
 #include "starboard/common/file.h"
 #include "starboard/common/log.h"
 #include "starboard/configuration_constants.h"
 
+#if defined(_DEBUG)
+#include "cobalt/browser/switches.h"
+#endif  // defined(_DEBUG)
+
 namespace cobalt {
 namespace watchdog {
 
@@ -44,6 +49,31 @@
   SB_CHECK(SbMutexCreate(&mutex_));
   smallest_time_interval_ = kWatchdogSmallestTimeInterval;
 
+#if defined(_DEBUG)
+  // Sets Watchdog delay settings from command line switch.
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  if (command_line->HasSwitch(browser::switches::kWatchdog)) {
+    std::string watchdog_settings =
+        command_line->GetSwitchValueASCII(browser::switches::kWatchdog);
+    std::istringstream ss(watchdog_settings);
+
+    std::string delay_name;
+    std::getline(ss, delay_name, ',');
+    delay_name_ = delay_name;
+
+    std::string delay_wait_time_microseconds;
+    std::getline(ss, delay_wait_time_microseconds, ',');
+    if (delay_wait_time_microseconds != "")
+      delay_wait_time_microseconds_ = std::stoll(delay_wait_time_microseconds);
+
+    std::string delay_sleep_time_microseconds;
+    std::getline(ss, delay_sleep_time_microseconds, ',');
+    if (delay_sleep_time_microseconds != "")
+      delay_sleep_time_microseconds_ =
+          std::stoll(delay_sleep_time_microseconds);
+  }
+#endif  // defined(_DEBUG)
+
   // Starts monitor thread.
   is_monitoring_ = true;
   SB_DCHECK(!SbThreadIsValid(watchdog_thread_));
@@ -254,6 +284,15 @@
       kSbFileCreateAlways | kSbFileWrite);
   watchdog_file.WriteAll(watchdog_json.c_str(),
                          static_cast<int>(watchdog_json.size()));
+
+  MaybeTriggerCrash(context);
+}
+
+void Watchdog::MaybeTriggerCrash(void* context) {
+  if (static_cast<Watchdog*>(context)->can_trigger_crash_) {
+    SB_LOG(ERROR) << "Triggering Watchdog Violation Crash!";
+    CHECK(false);
+  }
 }
 
 bool Watchdog::Register(std::string name, base::ApplicationState monitor_state,
@@ -352,8 +391,8 @@
     it->second->time_last_pinged_microseconds = SbTimeGetMonotonicNow();
     if (info != "") {
       int64_t current_time = SbTimeToPosix(SbTimeGetNow());
-      it->second->ping_infos.push(std::to_string(current_time) + "\n" + info +
-                                  "\n");
+      it->second->ping_infos.push(std::to_string(current_time) + "\\n" + info +
+                                  "\\n");
       if (it->second->ping_infos.size() > kWatchdogMaxPingInfos)
         it->second->ping_infos.pop();
     }
@@ -389,5 +428,35 @@
   }
 }
 
+bool Watchdog::GetCanTriggerCrash() { return can_trigger_crash_; }
+
+void Watchdog::SetCanTriggerCrash(bool can_trigger_crash) {
+  // Sets a persistent Watchdog setting that determines whether or not a
+  // Watchdog violation will trigger a crash. TODO
+  can_trigger_crash_ = can_trigger_crash;
+}
+
+#if defined(_DEBUG)
+// Sleeps threads based off of environment variables for Watchdog debugging.
+void Watchdog::MaybeInjectDebugDelay(const std::string& name) {
+  // Watchdog stub
+  if (is_stub_) return;
+
+  starboard::ScopedLock scoped_lock(delay_lock_);
+
+  if (name != delay_name_) return;
+
+  SbTimeMonotonic current_time = SbTimeGetMonotonicNow();
+  if (time_last_delayed_microseconds_ == 0)
+    time_last_delayed_microseconds_ = current_time;
+
+  if (current_time >
+      time_last_delayed_microseconds_ + delay_wait_time_microseconds_) {
+    SbThreadSleep(delay_sleep_time_microseconds_);
+    time_last_delayed_microseconds_ = SbTimeGetMonotonicNow();
+  }
+}
+#endif  // defined(_DEBUG)
+
 }  // namespace watchdog
 }  // namespace cobalt
diff --git a/cobalt/watchdog/watchdog.h b/cobalt/watchdog/watchdog.h
index 5b51b98..409bea6 100644
--- a/cobalt/watchdog/watchdog.h
+++ b/cobalt/watchdog/watchdog.h
@@ -22,6 +22,7 @@
 
 #include "cobalt/base/application_state.h"
 #include "cobalt/watchdog/singleton.h"
+#include "starboard/common/mutex.h"
 #include "starboard/mutex.h"
 #include "starboard/thread.h"
 #include "starboard/time.h"
@@ -95,6 +96,13 @@
   bool Ping(const std::string& name);
   bool Ping(const std::string& name, const std::string& info);
   std::string GetWatchdogViolations(bool current = false);
+  bool GetCanTriggerCrash();
+  void SetCanTriggerCrash(bool can_trigger_crash);
+
+#if defined(_DEBUG)
+  // Sleeps threads based off of environment variables for Watchdog debugging.
+  void MaybeInjectDebugDelay(const std::string& name);
+#endif  // defined(_DEBUG)
 
  private:
   std::string GetWatchdogFilePaths(bool current);
@@ -102,6 +110,7 @@
   static void* Monitor(void* context);
   std::string GetSerializedWatchdogIndex();
   static void SerializeWatchdogViolations(void* context);
+  static void MaybeTriggerCrash(void* context);
 
   // Current Watchdog violations file path.
   std::string watchdog_file_;
@@ -130,6 +139,20 @@
   bool is_stub_ = false;
   // Flag to stop monitor thread.
   bool is_monitoring_;
+  // Flag to control whether or not crashes can be triggered.
+  bool can_trigger_crash_ = false;
+
+#if defined(_DEBUG)
+  starboard::Mutex delay_lock_;
+  // name of the client to inject delay
+  std::string delay_name_ = "";
+  // since (relative)
+  SbTimeMonotonic time_last_delayed_microseconds_ = 0;
+  // time in between delays (periodic)
+  int64_t delay_wait_time_microseconds_ = 0;
+  // delay duration
+  int64_t delay_sleep_time_microseconds_ = 0;
+#endif
 };
 
 }  // namespace watchdog
diff --git a/cobalt/web/BUILD.gn b/cobalt/web/BUILD.gn
index ed5fb7c..be21aac 100644
--- a/cobalt/web/BUILD.gn
+++ b/cobalt/web/BUILD.gn
@@ -12,6 +12,43 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+static_library("web_events") {
+  sources = [
+    "custom_event.h",
+    "environment_settings.h",
+    "error_event.h",
+    "event.cc",
+    "event.h",
+    "event_target.cc",
+    "event_target.h",
+    "event_target_listener_info.cc",
+    "event_target_listener_info.h",
+    "global_stats.cc",
+    "global_stats.h",
+    "on_error_event_listener.cc",
+    "on_error_event_listener.h",
+  ]
+
+  deps = [
+    ":dom_exception",
+    "//cobalt/base",
+    "//cobalt/browser:generated_bindings",
+    "//cobalt/script",
+    "//cobalt/script:engine",
+    "//cobalt/script/v8c:engine",
+    "//nb",
+    "//url",
+  ]
+
+  public_deps = [
+    # Additionally, ensure that the include directories for generated
+    # headers are put on the include directories for targets that depend
+    # on this one.
+    ":dom_exception",
+    "//cobalt/browser:generated_types",
+  ]
+}
+
 static_library("web") {
   # Creates cycle with //cobalt/dom
   check_includes = false
@@ -19,23 +56,114 @@
   sources = [
     "agent.cc",
     "agent.h",
+    "blob.cc",
+    "blob.h",
+    "buffer_source.cc",
+    "buffer_source.h",
+    "cobalt_ua_data_values_interface.cc",
+    "cobalt_ua_data_values_interface.h",
     "context.h",
-    "environment_settings.h",
-    "stub_web_context.h",
+    "csp_delegate.cc",
+    "csp_delegate.h",
+    "csp_delegate_factory.cc",
+    "csp_delegate_factory.h",
+    "csp_violation_reporter.cc",
+    "csp_violation_reporter.h",
+    "location_base.h",
+    "navigator_base.cc",
+    "navigator_base.h",
+    "navigator_ua_data.cc",
+    "navigator_ua_data.h",
+    "security_policy_violation_event.cc",
+    "security_policy_violation_event.h",
+    "url.cc",
+    "url.h",
+    "url_registry.h",
+    "url_utils.cc",
+    "url_utils.h",
+    "user_agent_platform_info.h",
+    "window_or_worker_global_scope.cc",
+    "window_or_worker_global_scope.h",
   ]
 
   deps = [
+    ":web_events",
     "//cobalt/base",
-    "//cobalt/loader",
+    "//cobalt/browser:generated_bindings",
+    "//cobalt/csp",
+    "//cobalt/network",
+    "//cobalt/network_bridge",
     "//cobalt/script",
     "//cobalt/script:engine",
     "//cobalt/script/v8c:engine",
+    "//url",
   ]
 
   public_deps = [
     # Additionally, ensure that the include directories for generated
     # headers are put on the include directories for targets that depend
     # on this one.
+    ":web_events",
     "//cobalt/browser:generated_types",
   ]
 }
+
+static_library("stat_tracker") {
+  has_pedantic_warnings = true
+
+  sources = [
+    "stat_tracker.cc",
+    "stat_tracker.h",
+  ]
+
+  deps = [ "//cobalt/base" ]
+}
+
+static_library("dom_exception") {
+  has_pedantic_warnings = true
+
+  sources = [
+    "dom_exception.cc",
+    "dom_exception.h",
+  ]
+
+  deps = [ "//cobalt/script" ]
+}
+
+target(gtest_target_type, "web_test") {
+  testonly = true
+  has_pedantic_warnings = true
+
+  sources = [
+    "blob_test.cc",
+    "csp_delegate_test.cc",
+    "custom_event_test.cc",
+    "error_event_test.cc",
+    "event_target_test.cc",
+    "event_test.cc",
+    "url_test.cc",
+    "url_utils_test.cc",
+  ]
+
+  deps = [
+    ":web",
+    "//cobalt/base",
+    "//cobalt/browser:browser",
+    "//cobalt/browser:generated_bindings",
+    "//cobalt/browser:generated_types",
+    "//cobalt/css_parser",
+    "//cobalt/cssom",
+    "//cobalt/dom/testing:dom_testing",
+    "//cobalt/dom_parser",
+    "//cobalt/loader",
+    "//cobalt/script",
+    "//cobalt/script/v8c:engine",
+    "//cobalt/test:run_all_unittests",
+    "//cobalt/web/testing:web_testing",
+    "//nb",
+    "//net:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//url",
+  ]
+}
diff --git a/cobalt/web/agent.cc b/cobalt/web/agent.cc
index 4111950..bafb7b8 100644
--- a/cobalt/web/agent.cc
+++ b/cobalt/web/agent.cc
@@ -19,8 +19,6 @@
 #include <utility>
 
 #include "base/trace_event/trace_event.h"
-#include "cobalt/dom/blob.h"
-#include "cobalt/dom/url.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/script_loader_factory.h"
 #include "cobalt/script/environment_settings.h"
@@ -30,9 +28,13 @@
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/script/script_runner.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/blob.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/environment_settings.h"
+#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_jobs.h"
 #include "cobalt/worker/service_worker_object.h"
 #include "cobalt/worker/service_worker_registration.h"
 #include "cobalt/worker/service_worker_registration_object.h"
@@ -77,13 +79,14 @@
   script::ScriptRunner* script_runner() const final {
     return script_runner_.get();
   }
-  dom::Blob::Registry* blob_registry() const final {
-    return blob_registry_.get();
-  }
+  Blob::Registry* blob_registry() const final { return blob_registry_.get(); }
   network::NetworkModule* network_module() const final {
     DCHECK(fetcher_factory_);
     return fetcher_factory_->network_module();
   }
+  worker::ServiceWorkerJobs* service_worker_jobs() const final {
+    return service_worker_jobs_;
+  }
 
   const std::string& name() const final { return name_; };
   void setup_environment_settings(
@@ -92,24 +95,31 @@
     if (environment_settings_) environment_settings_->set_context(this);
   }
 
-  web::EnvironmentSettings* environment_settings() const final {
+  EnvironmentSettings* environment_settings() const final {
     DCHECK_EQ(environment_settings_->context(), this);
     return environment_settings_.get();
   }
 
+  scoped_refptr<worker::ServiceWorkerRegistration>
+  LookupServiceWorkerRegistration(
+      worker::ServiceWorkerRegistrationObject* registration) final;
   // https://w3c.github.io/ServiceWorker/#service-worker-registration-creation
   scoped_refptr<worker::ServiceWorkerRegistration> GetServiceWorkerRegistration(
       worker::ServiceWorkerRegistrationObject* registration) final;
 
+  scoped_refptr<worker::ServiceWorker> LookupServiceWorker(
+      worker::ServiceWorkerObject* worker) final;
+  // https://w3c.github.io/ServiceWorker/#get-the-service-worker-object
+  scoped_refptr<worker::ServiceWorker> GetServiceWorker(
+      worker::ServiceWorkerObject* worker) final;
+
+  WindowOrWorkerGlobalScope* GetWindowOrWorkerGlobalScope() final;
+
  private:
   // Injects a list of attributes into the Web Context's global object.
   void InjectGlobalObjectAttributes(
       const Agent::Options::InjectedGlobalObjectAttributes& attributes);
 
-  // https://w3c.github.io/ServiceWorker/#get-the-service-worker-object
-  scoped_refptr<worker::ServiceWorker> GetServiceWorker(
-      worker::ServiceWorkerObject* worker);
-
   // Thread checker ensures all calls to the Context are made from the same
   // thread that it is created in.
   THREAD_CHECKER(thread_checker_);
@@ -141,10 +151,10 @@
   std::unique_ptr<script::ScriptRunner> script_runner_;
 
   // Object to register and retrieve Blob objects with a string key.
-  std::unique_ptr<dom::Blob::Registry> blob_registry_;
+  std::unique_ptr<Blob::Registry> blob_registry_;
 
   // Environment Settings object
-  std::unique_ptr<web::EnvironmentSettings> environment_settings_;
+  std::unique_ptr<EnvironmentSettings> environment_settings_;
 
   // The service worker registration object map.
   //   https://w3c.github.io/ServiceWorker/#environment-settings-object-service-worker-registration-object-map
@@ -156,15 +166,18 @@
   //   https://w3c.github.io/ServiceWorker/#environment-settings-object-service-worker-object-map
   std::map<worker::ServiceWorkerObject*, scoped_refptr<worker::ServiceWorker>>
       service_worker_object_map_;
+
+  worker::ServiceWorkerJobs* service_worker_jobs_;
 };
 
 Impl::Impl(const Agent::Options& options) : name_(options.name) {
   TRACE_EVENT0("cobalt::web", "Agent::Impl::Impl()");
-  blob_registry_.reset(new dom::Blob::Registry);
+  service_worker_jobs_ = options.service_worker_jobs;
+  blob_registry_.reset(new Blob::Registry);
 
   fetcher_factory_.reset(new loader::FetcherFactory(
       options.network_module, options.extra_web_file_dir,
-      dom::URL::MakeBlobResolverCallback(blob_registry_.get()),
+      URL::MakeBlobResolverCallback(blob_registry_.get()),
       options.read_cache_callback));
   DCHECK(fetcher_factory_);
 
@@ -198,11 +211,24 @@
         base::Bind(&Impl::InjectGlobalObjectAttributes, base::Unretained(this),
                    options.injected_global_object_attributes));
   }
+
+  if (service_worker_jobs_) {
+    service_worker_jobs_->RegisterWebContext(this);
+  }
 }
 
 void Impl::ShutDownJavaScriptEngine() {
   // TODO: Disentangle shutdown of the JS engine with the various tracking and
   // caching in the WebModule.
+
+  service_worker_object_map_.clear();
+  service_worker_registration_object_map_.clear();
+
+  if (service_worker_jobs_) {
+    service_worker_jobs_->UnregisterWebContext(this);
+    service_worker_jobs_ = nullptr;
+  }
+
   if (global_environment_) {
     global_environment_->SetReportEvalCallback(base::Closure());
     global_environment_->SetReportErrorCallback(
@@ -234,6 +260,20 @@
   }
 }
 
+scoped_refptr<worker::ServiceWorkerRegistration>
+Impl::LookupServiceWorkerRegistration(
+    worker::ServiceWorkerRegistrationObject* registration) {
+  scoped_refptr<worker::ServiceWorkerRegistration> worker_registration;
+  if (!registration) {
+    return worker_registration;
+  }
+  auto registration_lookup =
+      service_worker_registration_object_map_.find(registration);
+  if (registration_lookup != service_worker_registration_object_map_.end()) {
+    worker_registration = registration_lookup->second;
+  }
+  return worker_registration;
+}
 
 scoped_refptr<worker::ServiceWorkerRegistration>
 Impl::GetServiceWorkerRegistration(
@@ -242,7 +282,7 @@
   //   https://w3c.github.io/ServiceWorker/#get-the-service-worker-registration-object
   scoped_refptr<worker::ServiceWorkerRegistration> worker_registration;
   if (!registration) {
-    NOTREACHED();
+    // Return undefined when registration is null.
     return worker_registration;
   }
 
@@ -298,12 +338,38 @@
   return worker_registration;
 }
 
+
+scoped_refptr<worker::ServiceWorker> Impl::LookupServiceWorker(
+    worker::ServiceWorkerObject* worker) {
+  // Algorithm for 'get the service worker object':
+  //   https://w3c.github.io/ServiceWorker/#get-the-service-worker-object
+  scoped_refptr<worker::ServiceWorker> service_worker;
+
+  if (!worker) {
+    // Return undefined when worker is null.
+    return service_worker;
+  }
+
+  // 1. Let objectMap be environment’s service worker object map.
+  // 2. If objectMap[serviceWorker] does not exist, then:
+  auto worker_lookup = service_worker_object_map_.find(worker);
+  if (worker_lookup != service_worker_object_map_.end()) {
+    service_worker = worker_lookup->second;
+  }
+  return service_worker;
+}
+
 scoped_refptr<worker::ServiceWorker> Impl::GetServiceWorker(
     worker::ServiceWorkerObject* worker) {
   // Algorithm for 'get the service worker object':
   //   https://w3c.github.io/ServiceWorker/#get-the-service-worker-object
   scoped_refptr<worker::ServiceWorker> service_worker;
 
+  if (!worker) {
+    // Return undefined when worker is null.
+    return service_worker;
+  }
+
   // 1. Let objectMap be environment’s service worker object map.
   // 2. If objectMap[serviceWorker] does not exist, then:
   auto worker_lookup = service_worker_object_map_.find(worker);
@@ -323,6 +389,20 @@
   return service_worker;
 }
 
+WindowOrWorkerGlobalScope* Impl::GetWindowOrWorkerGlobalScope() {
+  script::Wrappable* global_wrappable =
+      global_environment()->global_wrappable();
+  if (!global_wrappable) {
+    return nullptr;
+  }
+  DCHECK(global_wrappable->IsWrappable());
+  DCHECK_EQ(script::Wrappable::JSObjectType::kObject,
+            global_wrappable->GetJSObjectType());
+
+  return base::polymorphic_downcast<WindowOrWorkerGlobalScope*>(
+      global_wrappable);
+}
+
 // Signals the given WaitableEvent.
 void SignalWaitableEvent(base::WaitableEvent* event) { event->Signal(); }
 }  // namespace
diff --git a/cobalt/web/agent.h b/cobalt/web/agent.h
index 3026e59..ddd4edd 100644
--- a/cobalt/web/agent.h
+++ b/cobalt/web/agent.h
@@ -30,6 +30,9 @@
 #include "cobalt/web/environment_settings.h"
 
 namespace cobalt {
+namespace worker {
+class ServiceWorkerJobs;
+}
 namespace web {
 
 class Agent : public base::MessageLoop::DestructionObserver {
@@ -68,6 +71,8 @@
     // Optional callback for fetching from cache via h5vcc-cache://.
     base::Callback<int(const std::string&, std::unique_ptr<char[]>*)>
         read_cache_callback;
+
+    worker::ServiceWorkerJobs* service_worker_jobs = nullptr;
   };
 
   typedef base::Callback<void(const script::HeapStatistics&)>
diff --git a/cobalt/dom/blob.cc b/cobalt/web/blob.cc
similarity index 80%
rename from cobalt/dom/blob.cc
rename to cobalt/web/blob.cc
index 6199841..c59345f 100644
--- a/cobalt/dom/blob.cc
+++ b/cobalt/web/blob.cc
@@ -12,16 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/blob.h"
+#include "cobalt/web/blob.h"
 
 #include "base/lazy_instance.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/blob_property_bag.h"
+#include "cobalt/web/blob_property_bag.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/environment_settings.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 namespace {
 
@@ -81,19 +81,18 @@
            const script::Handle<script::ArrayBuffer>& buffer,
            const BlobPropertyBag& options)
     : buffer_reference_(
-          this, buffer.IsEmpty()
-                    ? script::ArrayBuffer::New(
-                          base::polymorphic_downcast<web::EnvironmentSettings*>(
-                              settings)
-                              ->context()
-                              ->global_environment(),
-                          0)
-                    : script::ArrayBuffer::New(
-                          base::polymorphic_downcast<web::EnvironmentSettings*>(
-                              settings)
-                              ->context()
-                              ->global_environment(),
-                          buffer->Data(), buffer->ByteLength())) {
+          this,
+          buffer.IsEmpty()
+              ? script::ArrayBuffer::New(
+                    base::polymorphic_downcast<EnvironmentSettings*>(settings)
+                        ->context()
+                        ->global_environment(),
+                    0)
+              : script::ArrayBuffer::New(
+                    base::polymorphic_downcast<EnvironmentSettings*>(settings)
+                        ->context()
+                        ->global_environment(),
+                    buffer->Data(), buffer->ByteLength())) {
   DCHECK(settings);
   if (options.has_type()) {
     type_ = options.type();
@@ -107,12 +106,11 @@
            const script::Sequence<BlobPart>& blob_parts,
            const BlobPropertyBag& options)
     : buffer_reference_(
-          this,
-          script::ArrayBuffer::New(
-              base::polymorphic_downcast<web::EnvironmentSettings*>(settings)
-                  ->context()
-                  ->global_environment(),
-              TotalDataLength(blob_parts))),
+          this, script::ArrayBuffer::New(
+                    base::polymorphic_downcast<EnvironmentSettings*>(settings)
+                        ->context()
+                        ->global_environment(),
+                    TotalDataLength(blob_parts))),
       type_(options.type()) {
   DCHECK(settings);
   uint8* destination = static_cast<uint8*>(buffer_reference_.value().Data());
@@ -132,5 +130,5 @@
   return empty_blob_property_bag.Get();
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/blob.h b/cobalt/web/blob.h
similarity index 94%
rename from cobalt/dom/blob.h
rename to cobalt/web/blob.h
index 19f04b1..898e17b 100644
--- a/cobalt/dom/blob.h
+++ b/cobalt/web/blob.h
@@ -12,14 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_BLOB_H_
-#define COBALT_DOM_BLOB_H_
+#ifndef COBALT_WEB_BLOB_H_
+#define COBALT_WEB_BLOB_H_
 
 #include <algorithm>
 #include <string>
 #include <vector>
 
-#include "cobalt/dom/url_registry.h"
 #include "cobalt/script/array_buffer.h"
 #include "cobalt/script/array_buffer_view.h"
 #include "cobalt/script/data_view.h"
@@ -27,10 +26,11 @@
 #include "cobalt/script/sequence.h"
 #include "cobalt/script/union_type.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/url_registry.h"
 #include "url/gurl.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // This ensures this header can be included without depending on generated
 // dictionaries.
@@ -83,7 +83,7 @@
   DISALLOW_COPY_AND_ASSIGN(Blob);
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_BLOB_H_
+#endif  // COBALT_WEB_BLOB_H_
diff --git a/cobalt/dom/blob.idl b/cobalt/web/blob.idl
similarity index 100%
rename from cobalt/dom/blob.idl
rename to cobalt/web/blob.idl
diff --git a/cobalt/dom/blob_property_bag.idl b/cobalt/web/blob_property_bag.idl
similarity index 99%
rename from cobalt/dom/blob_property_bag.idl
rename to cobalt/web/blob_property_bag.idl
index 88f477f..992a76e9 100644
--- a/cobalt/dom/blob_property_bag.idl
+++ b/cobalt/web/blob_property_bag.idl
@@ -15,4 +15,4 @@
 // https://www.w3.org/TR/FileAPI/#dfn-BlobPropertyBag
 dictionary BlobPropertyBag {
   DOMString type = "";
-};
\ No newline at end of file
+};
diff --git a/cobalt/dom/blob_test.cc b/cobalt/web/blob_test.cc
similarity index 95%
rename from cobalt/dom/blob_test.cc
rename to cobalt/web/blob_test.cc
index 996396a..32ac89e 100644
--- a/cobalt/dom/blob_test.cc
+++ b/cobalt/web/blob_test.cc
@@ -12,18 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/blob.h"
+#include "cobalt/web/blob.h"
 
 #include <algorithm>
 
-#include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/testing/stub_window.h"
 #include "cobalt/script/data_view.h"
+#include "cobalt/script/environment_settings.h"
 #include "cobalt/script/testing/mock_exception_state.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 namespace {
 
 using script::testing::MockExceptionState;
@@ -33,7 +33,7 @@
 
 TEST(BlobTest, Constructors) {
   // Initialize JavaScriptEngine and its environment.
-  testing::StubWindow stub_window;
+  dom::testing::StubWindow stub_window;
   script::GlobalEnvironment* global_environment =
       stub_window.global_environment();
   script::EnvironmentSettings* environment_settings =
@@ -89,7 +89,7 @@
 
 TEST(BlobTest, HasOwnBuffer) {
   // Initialize JavaScriptEngine and its environment.
-  testing::StubWindow stub_window;
+  dom::testing::StubWindow stub_window;
   script::GlobalEnvironment* global_environment =
       stub_window.global_environment();
   script::EnvironmentSettings* environment_settings =
@@ -119,5 +119,5 @@
 }
 
 }  // namespace
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/buffer_source.cc b/cobalt/web/buffer_source.cc
similarity index 95%
rename from cobalt/dom/buffer_source.cc
rename to cobalt/web/buffer_source.cc
index 74cf56a..53b6f24 100644
--- a/cobalt/dom/buffer_source.cc
+++ b/cobalt/web/buffer_source.cc
@@ -12,12 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/buffer_source.h"
+#include "cobalt/web/buffer_source.h"
 
 #include "base/logging.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 void GetBufferAndSize(const BufferSource& buffer_source, const uint8** buffer,
                       int* buffer_size) {
@@ -44,5 +44,5 @@
   }
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/buffer_source.h b/cobalt/web/buffer_source.h
similarity index 87%
rename from cobalt/dom/buffer_source.h
rename to cobalt/web/buffer_source.h
index 7ef4aaa..c0f0b43 100644
--- a/cobalt/dom/buffer_source.h
+++ b/cobalt/web/buffer_source.h
@@ -12,15 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_BUFFER_SOURCE_H_
-#define COBALT_DOM_BUFFER_SOURCE_H_
+#ifndef COBALT_WEB_BUFFER_SOURCE_H_
+#define COBALT_WEB_BUFFER_SOURCE_H_
 
 #include "cobalt/script/array_buffer.h"
 #include "cobalt/script/array_buffer_view.h"
 #include "cobalt/script/union_type.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 typedef script::UnionType2<script::Handle<script::ArrayBufferView>,
                            script::Handle<script::ArrayBuffer> >
@@ -29,7 +29,7 @@
 void GetBufferAndSize(const BufferSource& buffer_source, const uint8** buffer,
                       int* buffer_size);
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_BUFFER_SOURCE_H_
+#endif  // COBALT_WEB_BUFFER_SOURCE_H_
diff --git a/cobalt/dom/buffer_source.idl b/cobalt/web/buffer_source.idl
similarity index 100%
rename from cobalt/dom/buffer_source.idl
rename to cobalt/web/buffer_source.idl
diff --git a/cobalt/dom/cobalt_ua_data_values.idl b/cobalt/web/cobalt_ua_data_values.idl
similarity index 100%
rename from cobalt/dom/cobalt_ua_data_values.idl
rename to cobalt/web/cobalt_ua_data_values.idl
diff --git a/cobalt/dom/cobalt_ua_data_values_interface.cc b/cobalt/web/cobalt_ua_data_values_interface.cc
similarity index 96%
rename from cobalt/dom/cobalt_ua_data_values_interface.cc
rename to cobalt/web/cobalt_ua_data_values_interface.cc
index 586e086..35504a8 100644
--- a/cobalt/dom/cobalt_ua_data_values_interface.cc
+++ b/cobalt/web/cobalt_ua_data_values_interface.cc
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/cobalt_ua_data_values_interface.h"
+#include "cobalt/web/cobalt_ua_data_values_interface.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 CobaltUADataValuesInterface::CobaltUADataValuesInterface(
     const CobaltUADataValues& init_dict) {
@@ -90,5 +90,5 @@
   }
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/cobalt_ua_data_values_interface.h b/cobalt/web/cobalt_ua_data_values_interface.h
similarity index 92%
rename from cobalt/dom/cobalt_ua_data_values_interface.h
rename to cobalt/web/cobalt_ua_data_values_interface.h
index cf0093b..91dff3a 100644
--- a/cobalt/dom/cobalt_ua_data_values_interface.h
+++ b/cobalt/web/cobalt_ua_data_values_interface.h
@@ -12,16 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_COBALT_UA_DATA_VALUES_INTERFACE_H_
-#define COBALT_DOM_COBALT_UA_DATA_VALUES_INTERFACE_H_
+#ifndef COBALT_WEB_COBALT_UA_DATA_VALUES_INTERFACE_H_
+#define COBALT_WEB_COBALT_UA_DATA_VALUES_INTERFACE_H_
 
 #include <string>
 
-#include "cobalt/dom/cobalt_ua_data_values.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/cobalt_ua_data_values.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 class CobaltUADataValuesInterface : public script::Wrappable {
  public:
@@ -87,7 +87,7 @@
   DISALLOW_COPY_AND_ASSIGN(CobaltUADataValuesInterface);
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_COBALT_UA_DATA_VALUES_INTERFACE_H_
+#endif  // COBALT_WEB_COBALT_UA_DATA_VALUES_INTERFACE_H_
diff --git a/cobalt/dom/cobalt_ua_data_values_interface.idl b/cobalt/web/cobalt_ua_data_values_interface.idl
similarity index 100%
rename from cobalt/dom/cobalt_ua_data_values_interface.idl
rename to cobalt/web/cobalt_ua_data_values_interface.idl
diff --git a/cobalt/web/context.h b/cobalt/web/context.h
index 8b5bf0e..e605064 100644
--- a/cobalt/web/context.h
+++ b/cobalt/web/context.h
@@ -17,7 +17,6 @@
 
 #include <string>
 
-#include "cobalt/dom/blob.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/script_loader_factory.h"
 #include "cobalt/network/network_module.h"
@@ -26,15 +25,20 @@
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/script/script_runner.h"
+#include "cobalt/script/wrappable.h"
+#include "cobalt/web/blob.h"
 #include "cobalt/web/environment_settings.h"
-#include "cobalt/worker/service_worker_registration_object.h"
 
 namespace cobalt {
 namespace worker {
 class ServiceWorkerRegistration;
 class ServiceWorkerRegistrationObject;
+class ServiceWorker;
+class ServiceWorkerJobs;
+class ServiceWorkerObject;
 }  // namespace worker
 namespace web {
+class WindowOrWorkerGlobalScope;
 
 class Context {
  public:
@@ -48,17 +52,29 @@
   virtual script::GlobalEnvironment* global_environment() const = 0;
   virtual script::ExecutionState* execution_state() const = 0;
   virtual script::ScriptRunner* script_runner() const = 0;
-  virtual dom::Blob::Registry* blob_registry() const = 0;
+  virtual Blob::Registry* blob_registry() const = 0;
   virtual network::NetworkModule* network_module() const = 0;
+  virtual worker::ServiceWorkerJobs* service_worker_jobs() const = 0;
 
   virtual const std::string& name() const = 0;
   virtual void setup_environment_settings(EnvironmentSettings* settings) = 0;
   virtual EnvironmentSettings* environment_settings() const = 0;
 
+  virtual scoped_refptr<worker::ServiceWorkerRegistration>
+  LookupServiceWorkerRegistration(
+      worker::ServiceWorkerRegistrationObject* registration) = 0;
   // https://w3c.github.io/ServiceWorker/#get-the-service-worker-registration-object
   virtual scoped_refptr<worker::ServiceWorkerRegistration>
   GetServiceWorkerRegistration(
       worker::ServiceWorkerRegistrationObject* registration) = 0;
+
+  virtual scoped_refptr<worker::ServiceWorker> LookupServiceWorker(
+      worker::ServiceWorkerObject* worker) = 0;
+  // https://w3c.github.io/ServiceWorker/#get-the-service-worker-object
+  virtual scoped_refptr<worker::ServiceWorker> GetServiceWorker(
+      worker::ServiceWorkerObject* worker) = 0;
+
+  virtual WindowOrWorkerGlobalScope* GetWindowOrWorkerGlobalScope() = 0;
 };
 
 }  // namespace web
diff --git a/cobalt/dom/csp_delegate.cc b/cobalt/web/csp_delegate.cc
similarity index 98%
rename from cobalt/dom/csp_delegate.cc
rename to cobalt/web/csp_delegate.cc
index 4bf8fd2..8b1cbb9 100644
--- a/cobalt/dom/csp_delegate.cc
+++ b/cobalt/web/csp_delegate.cc
@@ -12,15 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <memory>
+#include "cobalt/web/csp_delegate.h"
 
-#include "cobalt/dom/csp_delegate.h"
+#include <memory>
+#include <utility>
 
 #include "base/bind.h"
 #include "url/gurl.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 CspDelegate::CspDelegate() {}
 CspDelegate::~CspDelegate() {}
@@ -180,5 +181,5 @@
   }
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/csp_delegate.h b/cobalt/web/csp_delegate.h
similarity index 96%
rename from cobalt/dom/csp_delegate.h
rename to cobalt/web/csp_delegate.h
index 3f3c6bd..0a74804 100644
--- a/cobalt/dom/csp_delegate.h
+++ b/cobalt/web/csp_delegate.h
@@ -12,18 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_CSP_DELEGATE_H_
-#define COBALT_DOM_CSP_DELEGATE_H_
+#ifndef COBALT_WEB_CSP_DELEGATE_H_
+#define COBALT_WEB_CSP_DELEGATE_H_
 
 #include <memory>
 #include <string>
 
 #include "cobalt/base/source_location.h"
 #include "cobalt/csp/content_security_policy.h"
-#include "cobalt/dom/csp_violation_reporter.h"
+#include "cobalt/web/csp_violation_reporter.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // Object that represents a Content Security Policy for a particular document.
 // Owned by the Document. Objects wishing to enforce CSP need to query the
@@ -161,7 +161,7 @@
   DISALLOW_COPY_AND_ASSIGN(CspDelegateSecure);
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_CSP_DELEGATE_H_
+#endif  // COBALT_WEB_CSP_DELEGATE_H_
diff --git a/cobalt/dom/csp_delegate_factory.cc b/cobalt/web/csp_delegate_factory.cc
similarity index 95%
rename from cobalt/dom/csp_delegate_factory.cc
rename to cobalt/web/csp_delegate_factory.cc
index ad30781..89d546c 100644
--- a/cobalt/dom/csp_delegate_factory.cc
+++ b/cobalt/web/csp_delegate_factory.cc
@@ -13,17 +13,18 @@
 // limitations under the License.
 
 #include <memory>
+#include <utility>
 
-#include "cobalt/dom/csp_delegate_factory.h"
+#include "cobalt/web/csp_delegate_factory.h"
 
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/threading/thread_local.h"
 
-#include "cobalt/dom/csp_delegate.h"
+#include "cobalt/web/csp_delegate.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 namespace {
 #if !defined(COBALT_FORCE_CSP)
@@ -104,5 +105,5 @@
 }
 #endif  // !defined(COBALT_FORCE_CSP)
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/csp_delegate_factory.h b/cobalt/web/csp_delegate_factory.h
similarity index 92%
rename from cobalt/dom/csp_delegate_factory.h
rename to cobalt/web/csp_delegate_factory.h
index 0e1541c..aa10ac9 100644
--- a/cobalt/dom/csp_delegate_factory.h
+++ b/cobalt/web/csp_delegate_factory.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_CSP_DELEGATE_FACTORY_H_
-#define COBALT_DOM_CSP_DELEGATE_FACTORY_H_
+#ifndef COBALT_WEB_CSP_DELEGATE_FACTORY_H_
+#define COBALT_WEB_CSP_DELEGATE_FACTORY_H_
 
 #include <memory>
 #include <string>
@@ -22,7 +22,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/memory/singleton.h"
 #include "cobalt/csp/content_security_policy.h"
-#include "cobalt/dom/csp_delegate_type.h"
+#include "cobalt/web/csp_delegate_type.h"
 #include "url/gurl.h"
 
 namespace cobalt {
@@ -33,7 +33,7 @@
 class SplashScreen;
 }  // namespace browser
 
-namespace dom {
+namespace web {
 
 class CspDelegate;
 class CspViolationReporter;
@@ -83,7 +83,7 @@
   DISALLOW_COPY_AND_ASSIGN(CspDelegateFactory);
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_CSP_DELEGATE_FACTORY_H_
+#endif  // COBALT_WEB_CSP_DELEGATE_FACTORY_H_
diff --git a/cobalt/dom/csp_delegate_test.cc b/cobalt/web/csp_delegate_test.cc
similarity index 97%
rename from cobalt/dom/csp_delegate_test.cc
rename to cobalt/web/csp_delegate_test.cc
index 4089dc2..837d504 100644
--- a/cobalt/dom/csp_delegate_test.cc
+++ b/cobalt/web/csp_delegate_test.cc
@@ -13,11 +13,12 @@
 // limitations under the License.
 
 #include <memory>
+#include <utility>
 
 #include "base/strings/stringprintf.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/csp_delegate.h"
-#include "cobalt/dom/csp_delegate_factory.h"
+#include "cobalt/web/csp_delegate.h"
+#include "cobalt/web/csp_delegate_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -28,7 +29,7 @@
 using ::testing::ValuesIn;
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 namespace {
 
@@ -169,5 +170,5 @@
   EXPECT_TRUE(delegate != NULL);
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/csp_delegate_type.h b/cobalt/web/csp_delegate_type.h
similarity index 87%
rename from cobalt/dom/csp_delegate_type.h
rename to cobalt/web/csp_delegate_type.h
index 804e2e9..74e552f 100644
--- a/cobalt/dom/csp_delegate_type.h
+++ b/cobalt/web/csp_delegate_type.h
@@ -12,11 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_CSP_DELEGATE_TYPE_H_
-#define COBALT_DOM_CSP_DELEGATE_TYPE_H_
+#ifndef COBALT_WEB_CSP_DELEGATE_TYPE_H_
+#define COBALT_WEB_CSP_DELEGATE_TYPE_H_
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 enum CspEnforcementType {
   // Require CSP policy to be delivered in HTTP headers, otherwise consider
@@ -31,7 +31,7 @@
   kCspEnforcementCount,
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_CSP_DELEGATE_TYPE_H_
+#endif  // COBALT_WEB_CSP_DELEGATE_TYPE_H_
diff --git a/cobalt/dom/csp_violation_reporter.cc b/cobalt/web/csp_violation_reporter.cc
similarity index 95%
rename from cobalt/dom/csp_violation_reporter.cc
rename to cobalt/web/csp_violation_reporter.cc
index 6561ecb..268ac95 100644
--- a/cobalt/dom/csp_violation_reporter.cc
+++ b/cobalt/web/csp_violation_reporter.cc
@@ -13,21 +13,22 @@
 // limitations under the License.
 
 #include <memory>
+#include <utility>
 
-#include "cobalt/dom/csp_violation_reporter.h"
+#include "cobalt/web/csp_violation_reporter.h"
 
 #include "base/hash.h"
 #include "base/json/json_writer.h"
 #include "base/values.h"
 #include "cobalt/dom/document.h"
 #include "cobalt/dom/html_element_context.h"
-#include "cobalt/dom/security_policy_violation_event.h"
 #include "cobalt/network/net_poster.h"
 #include "cobalt/script/global_environment.h"
+#include "cobalt/web/security_policy_violation_event.h"
 #include "url/gurl.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 namespace {
 
@@ -82,7 +83,7 @@
 }
 
 void GatherSecurityPolicyViolationEventData(
-    const Document* document, const csp::ViolationInfo& violation_info,
+    const dom::Document* document, const csp::ViolationInfo& violation_info,
     ViolationEvent* event_data) {
   event_data->document_uri =
       StripUrlForUseInReport(document->url_as_gurl(), document->url_as_gurl());
@@ -112,7 +113,7 @@
 }  // namespace
 
 CspViolationReporter::CspViolationReporter(
-    Document* document, const network_bridge::PostSender& post_sender)
+    dom::Document* document, const network_bridge::PostSender& post_sender)
     : post_sender_(post_sender),
       message_loop_(base::MessageLoop::current()),
       document_(document) {}
@@ -197,5 +198,5 @@
   }
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/csp_violation_reporter.h b/cobalt/web/csp_violation_reporter.h
similarity index 90%
rename from cobalt/dom/csp_violation_reporter.h
rename to cobalt/web/csp_violation_reporter.h
index a81abda..c7aa1a5 100644
--- a/cobalt/dom/csp_violation_reporter.h
+++ b/cobalt/web/csp_violation_reporter.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_CSP_VIOLATION_REPORTER_H_
-#define COBALT_DOM_CSP_VIOLATION_REPORTER_H_
+#ifndef COBALT_WEB_CSP_VIOLATION_REPORTER_H_
+#define COBALT_WEB_CSP_VIOLATION_REPORTER_H_
 
 #include <string>
 #include <vector>
@@ -26,8 +26,10 @@
 
 namespace cobalt {
 namespace dom {
-
 class Document;
+}  // namespace dom
+
+namespace web {
 
 // Responsible for reporting CSP violations, i.e. posting JSON
 // reports to any reporting endpoints described by the policy.
@@ -35,7 +37,7 @@
 // and passed in to its constructor. Generally it should not be called directly.
 class CspViolationReporter {
  public:
-  CspViolationReporter(Document* document,
+  CspViolationReporter(dom::Document* document,
                        const network_bridge::PostSender& post_sender);
   virtual ~CspViolationReporter();
 
@@ -61,12 +63,12 @@
   // We must send violations on the document's message loop.
   base::MessageLoop* message_loop_;
 
-  Document* document_;
+  dom::Document* document_;
 
   DISALLOW_COPY_AND_ASSIGN(CspViolationReporter);
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_CSP_VIOLATION_REPORTER_H_
+#endif  // COBALT_WEB_CSP_VIOLATION_REPORTER_H_
diff --git a/cobalt/dom/custom_event.h b/cobalt/web/custom_event.h
similarity index 88%
rename from cobalt/dom/custom_event.h
rename to cobalt/web/custom_event.h
index 8e598f0..ca9d46e 100644
--- a/cobalt/dom/custom_event.h
+++ b/cobalt/web/custom_event.h
@@ -12,22 +12,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_CUSTOM_EVENT_H_
-#define COBALT_DOM_CUSTOM_EVENT_H_
+#ifndef COBALT_WEB_CUSTOM_EVENT_H_
+#define COBALT_WEB_CUSTOM_EVENT_H_
 
 #include <memory>
 #include <string>
 
-#include "cobalt/dom/custom_event_init.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/script/value_handle.h"
+#include "cobalt/web/custom_event_init.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // Events using the CustomEvent interface can be used to carry custom data.
 //   https://www.w3.org/TR/2015/REC-dom-20151119/#customevent
-class CustomEvent : public Event {
+class CustomEvent : public web::Event {
  public:
   explicit CustomEvent(const std::string& type) : Event(type) {}
   CustomEvent(const std::string& type, const CustomEventInit& init_dict)
@@ -71,7 +71,7 @@
   std::unique_ptr<script::ValueHandleHolder::Reference> detail_;
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_CUSTOM_EVENT_H_
+#endif  // COBALT_WEB_CUSTOM_EVENT_H_
diff --git a/cobalt/dom/custom_event.idl b/cobalt/web/custom_event.idl
similarity index 100%
rename from cobalt/dom/custom_event.idl
rename to cobalt/web/custom_event.idl
diff --git a/cobalt/dom/custom_event_init.idl b/cobalt/web/custom_event_init.idl
similarity index 100%
rename from cobalt/dom/custom_event_init.idl
rename to cobalt/web/custom_event_init.idl
diff --git a/cobalt/web/custom_event_test.cc b/cobalt/web/custom_event_test.cc
new file mode 100644
index 0000000..995781f
--- /dev/null
+++ b/cobalt/web/custom_event_test.cc
@@ -0,0 +1,140 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/web/custom_event.h"
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/optional.h"
+#include "cobalt/dom/testing/stub_window.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/source_code.h"
+#include "cobalt/web/custom_event_init.h"
+#include "cobalt/web/testing/gtest_workarounds.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace web {
+
+namespace {
+class CustomEventTest : public ::testing::Test {
+ public:
+  CustomEventTest() {}
+
+  bool EvaluateScript(const std::string& js_code, std::string* result);
+
+ private:
+  cobalt::dom::testing::StubWindow stub_window_;
+};
+
+bool CustomEventTest::EvaluateScript(const std::string& js_code,
+                                     std::string* result) {
+  DCHECK(stub_window_.global_environment());
+  DCHECK(result);
+  scoped_refptr<script::SourceCode> source_code =
+      script::SourceCode::CreateSourceCode(
+          js_code, base::SourceLocation(__FILE__, __LINE__, 1));
+
+  stub_window_.global_environment()->EnableEval();
+  stub_window_.global_environment()->SetReportEvalCallback(base::Closure());
+  bool succeeded =
+      stub_window_.global_environment()->EvaluateScript(source_code, result);
+  return succeeded;
+}
+}  // namespace
+
+TEST_F(CustomEventTest, ConstructorWithEventTypeString) {
+  scoped_refptr<CustomEvent> event = new CustomEvent("mytestevent");
+
+  EXPECT_EQ("mytestevent", event->type());
+  EXPECT_EQ(NULL, event->target().get());
+  EXPECT_EQ(NULL, event->current_target().get());
+  EXPECT_EQ(web::Event::kNone, event->event_phase());
+  EXPECT_FALSE(event->bubbles());
+  EXPECT_FALSE(event->cancelable());
+  EXPECT_FALSE(event->default_prevented());
+  EXPECT_FALSE(event->IsBeingDispatched());
+  EXPECT_FALSE(event->propagation_stopped());
+  EXPECT_FALSE(event->immediate_propagation_stopped());
+  EXPECT_EQ(NULL, event->detail());
+}
+
+TEST_F(CustomEventTest, ConstructorWithEventTypeAndDefaultInitDict) {
+  CustomEventInit init;
+  scoped_refptr<CustomEvent> event = new CustomEvent("mytestevent", init);
+
+  EXPECT_EQ("mytestevent", event->type());
+  EXPECT_EQ(NULL, event->target().get());
+  EXPECT_EQ(NULL, event->current_target().get());
+  EXPECT_EQ(web::Event::kNone, event->event_phase());
+  EXPECT_FALSE(event->bubbles());
+  EXPECT_FALSE(event->cancelable());
+  EXPECT_FALSE(event->default_prevented());
+  EXPECT_FALSE(event->IsBeingDispatched());
+  EXPECT_FALSE(event->propagation_stopped());
+  EXPECT_FALSE(event->immediate_propagation_stopped());
+  EXPECT_EQ(NULL, event->detail());
+}
+
+TEST_F(CustomEventTest, ConstructorWithEventTypeAndCustomInitDict) {
+  std::string result;
+  bool success = EvaluateScript(
+      "var event = new CustomEvent('dog', "
+      "    {'bubbles':true, "
+      "     'cancelable':true, "
+      "     'detail':{'cobalt':'rulez'}});"
+      "if (event.type == 'dog' &&"
+      "    event.bubbles == true &&"
+      "    event.cancelable == true) "
+      "    event.detail.cobalt;",
+      &result);
+  EXPECT_EQ("rulez", result);
+
+  if (!success) {
+    DLOG(ERROR) << "Failed to evaluate test: "
+                << "\"" << result << "\"";
+  } else {
+    LOG(INFO) << "Test result : "
+              << "\"" << result << "\"";
+  }
+}
+
+TEST_F(CustomEventTest, InitCustomEvent) {
+  std::string result;
+  bool success = EvaluateScript(
+      "var event = new CustomEvent('cat');\n"
+      "event.initCustomEvent('dog', true, true, {cobalt:'rulez'});"
+      "if (event.type == 'dog' &&"
+      "    event.detail &&"
+      "    event.bubbles == true &&"
+      "    event.cancelable == true) "
+      "    event.detail.cobalt;",
+      &result);
+  EXPECT_EQ("rulez", result);
+
+  if (!success) {
+    DLOG(ERROR) << "Failed to evaluate test: "
+                << "\"" << result << "\"";
+  } else {
+    LOG(INFO) << "Test result : "
+              << "\"" << result << "\"";
+  }
+}
+
+}  // namespace web
+}  // namespace cobalt
diff --git a/cobalt/dom/dom_exception.cc b/cobalt/web/dom_exception.cc
similarity index 77%
rename from cobalt/dom/dom_exception.cc
rename to cobalt/web/dom_exception.cc
index 85cc3a7..149900e 100644
--- a/cobalt/dom/dom_exception.cc
+++ b/cobalt/web/dom_exception.cc
@@ -12,47 +12,47 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/dom_exception.h"
+#include "cobalt/web/dom_exception.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 namespace {
 
 // https://heycam.github.io/webidl/#dfn-error-names-table
 const char* kCodeToErrorName[] = {
-  "",
-  "IndexSizeError",
-  "DOMStringSizeError",
-  "HierarchyRequestError",
-  "WrongDocumentError",
-  "InvalidCharacterError",
-  "NoDataAllowedError",
-  "NoModificationAllowedError",
-  "NotFoundError",
-  "NotSupportedError",
-  "InUseAttributeError",
-  "InvalidStateError",
-  "SyntaxError",
-  "InvalidModificationError",
-  "NamespaceError",
-  "InvalidAccessError",
-  "ValidationError",
-  "TypeMismatchError",
-  "SecurityError",
-  "NetworkError",
-  "AbortError",
-  "URLMismatchError",
-  "QuotaExceededError",
-  "TimeoutError",
-  "InvalidNodeTypeError",
-  "DataCloneError",
+    "",
+    "IndexSizeError",
+    "DOMStringSizeError",
+    "HierarchyRequestError",
+    "WrongDocumentError",
+    "InvalidCharacterError",
+    "NoDataAllowedError",
+    "NoModificationAllowedError",
+    "NotFoundError",
+    "NotSupportedError",
+    "InUseAttributeError",
+    "InvalidStateError",
+    "SyntaxError",
+    "InvalidModificationError",
+    "NamespaceError",
+    "InvalidAccessError",
+    "ValidationError",
+    "TypeMismatchError",
+    "SecurityError",
+    "NetworkError",
+    "AbortError",
+    "URLMismatchError",
+    "QuotaExceededError",
+    "TimeoutError",
+    "InvalidNodeTypeError",
+    "DataCloneError",
 };
 
 const char* GetErrorName(DOMException::ExceptionCode code) {
   // Use the table for errors that have a corresponding code.
-  static_assert(arraysize(kCodeToErrorName) ==
-                DOMException::kHighestErrCodeValue + 1,
-                "Mismatch number of entries in error table");
+  static_assert(
+      arraysize(kCodeToErrorName) == DOMException::kHighestErrCodeValue + 1,
+      "Mismatch number of entries in error table");
   if (code <= DOMException::kHighestErrCodeValue) {
     return kCodeToErrorName[code];
   }
@@ -103,9 +103,7 @@
       message_(message) {}
 
 DOMException::DOMException(const std::string& message, const std::string& name)
-    : code_(GetErrorCode(name)),
-      name_(name),
-      message_(message) {}
+    : code_(GetErrorCode(name)), name_(name), message_(message) {}
 
 // static
 void DOMException::Raise(ExceptionCode code,
@@ -126,5 +124,5 @@
   }
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/dom_exception.h b/cobalt/web/dom_exception.h
similarity index 88%
rename from cobalt/dom/dom_exception.h
rename to cobalt/web/dom_exception.h
index 28a8778..01585ce 100644
--- a/cobalt/dom/dom_exception.h
+++ b/cobalt/web/dom_exception.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_DOM_EXCEPTION_H_
-#define COBALT_DOM_DOM_EXCEPTION_H_
+#ifndef COBALT_WEB_DOM_EXCEPTION_H_
+#define COBALT_WEB_DOM_EXCEPTION_H_
 
 #include <string>
 
@@ -21,7 +21,7 @@
 #include "cobalt/script/script_exception.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 class DOMException : public script::ScriptException {
  public:
@@ -32,12 +32,12 @@
     kNone = 0,
 
     // These errors have legacy code values (corresponding to the enum).
-    kIndexSizeErr,                // Deprecated. Use RangeError instead.
-    kDomstringSizeErr,            // Deprecated. Use RangeError instead.
+    kIndexSizeErr,      // Deprecated. Use RangeError instead.
+    kDomstringSizeErr,  // Deprecated. Use RangeError instead.
     kHierarchyRequestErr,
     kWrongDocumentErr,
     kInvalidCharacterErr,
-    kNoDataAllowedErr,            // Deprecated.
+    kNoDataAllowedErr,  // Deprecated.
     kNoModificationAllowedErr,
     kNotFoundErr,
     kNotSupportedErr,
@@ -50,7 +50,7 @@
     // "NotSupportedError" DOMException for unsupported operations, and
     // "NotAllowedError" DOMException for denied requests instead.
     kInvalidAccessErr,
-    kValidationErr,               // Deprecated.
+    kValidationErr,  // Deprecated.
     // Note that TypeMismatchErr is replaced by TypeError but we keep using it
     // to be in sync with Chrome.
     kTypeMismatchErr,
@@ -94,7 +94,7 @@
   std::string message_;
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_DOM_EXCEPTION_H_
+#endif  // COBALT_WEB_DOM_EXCEPTION_H_
diff --git a/cobalt/dom/dom_exception.idl b/cobalt/web/dom_exception.idl
similarity index 100%
rename from cobalt/dom/dom_exception.idl
rename to cobalt/web/dom_exception.idl
diff --git a/cobalt/dom/error_event.h b/cobalt/web/error_event.h
similarity index 84%
rename from cobalt/dom/error_event.h
rename to cobalt/web/error_event.h
index dba71ee..c99ab38 100644
--- a/cobalt/dom/error_event.h
+++ b/cobalt/web/error_event.h
@@ -12,18 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_ERROR_EVENT_H_
-#define COBALT_DOM_ERROR_EVENT_H_
+#ifndef COBALT_WEB_ERROR_EVENT_H_
+#define COBALT_WEB_ERROR_EVENT_H_
 
 #include <memory>
 #include <string>
 
-#include "cobalt/dom/error_event_init.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/script/value_handle.h"
+#include "cobalt/web/error_event_init.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // Whenever an uncaught runtime script error occurs in one of the scripts
 // associated with a Document, the user agent must report the error for the
@@ -33,7 +33,7 @@
  public:
   explicit ErrorEvent(const std::string& type)
       : Event(type), lineno_(0), colno_(0) {}
-  ErrorEvent(const std::string& type, const ErrorEventInit& init_dict)
+  ErrorEvent(const std::string& type, const web::ErrorEventInit& init_dict)
       : Event(type, init_dict),
         message_(init_dict.message()),
         filename_(init_dict.filename()),
@@ -41,7 +41,7 @@
         colno_(init_dict.colno()) {
     InitError(init_dict);
   }
-  ErrorEvent(base::Token type, const ErrorEventInit& init_dict)
+  ErrorEvent(base::Token type, const web::ErrorEventInit& init_dict)
       : Event(type, init_dict),
         message_(init_dict.message()),
         filename_(init_dict.filename()),
@@ -70,7 +70,7 @@
   ~ErrorEvent() override {}
 
  private:
-  void InitError(const ErrorEventInit& init_dict) {
+  void InitError(const web::ErrorEventInit& init_dict) {
     const script::ValueHandleHolder* error = init_dict.error();
     if (error) {
       error_.reset(new script::ValueHandleHolder::Reference(this, *error));
@@ -84,7 +84,7 @@
   std::unique_ptr<script::ValueHandleHolder::Reference> error_;
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_ERROR_EVENT_H_
+#endif  // COBALT_WEB_ERROR_EVENT_H_
diff --git a/cobalt/dom/error_event.idl b/cobalt/web/error_event.idl
similarity index 100%
rename from cobalt/dom/error_event.idl
rename to cobalt/web/error_event.idl
diff --git a/cobalt/dom/error_event_init.idl b/cobalt/web/error_event_init.idl
similarity index 100%
rename from cobalt/dom/error_event_init.idl
rename to cobalt/web/error_event_init.idl
diff --git a/cobalt/web/error_event_test.cc b/cobalt/web/error_event_test.cc
new file mode 100644
index 0000000..adc0516
--- /dev/null
+++ b/cobalt/web/error_event_test.cc
@@ -0,0 +1,132 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/web/error_event.h"
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "cobalt/dom/testing/stub_window.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/source_code.h"
+#include "cobalt/web/error_event_init.h"
+#include "cobalt/web/testing/gtest_workarounds.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace web {
+
+namespace {
+class ErrorEventTest : public ::testing::Test {
+ public:
+  ErrorEventTest() {}
+
+  bool EvaluateScript(const std::string& js_code, std::string* result);
+
+ private:
+  cobalt::dom::testing::StubWindow stub_window_;
+};
+
+bool ErrorEventTest::EvaluateScript(const std::string& js_code,
+                                    std::string* result) {
+  DCHECK(stub_window_.global_environment());
+  DCHECK(result);
+  scoped_refptr<script::SourceCode> source_code =
+      script::SourceCode::CreateSourceCode(
+          js_code, base::SourceLocation(__FILE__, __LINE__, 1));
+
+  stub_window_.global_environment()->EnableEval();
+  stub_window_.global_environment()->SetReportEvalCallback(base::Closure());
+  bool succeeded =
+      stub_window_.global_environment()->EvaluateScript(source_code, result);
+  return succeeded;
+}
+}  // namespace
+
+TEST_F(ErrorEventTest, ConstructorWithEventTypeString) {
+  scoped_refptr<ErrorEvent> event = new ErrorEvent("mytestevent");
+
+  EXPECT_EQ("mytestevent", event->type());
+  EXPECT_EQ(NULL, event->target().get());
+  EXPECT_EQ(NULL, event->current_target().get());
+  EXPECT_EQ(Event::kNone, event->event_phase());
+  EXPECT_FALSE(event->bubbles());
+  EXPECT_FALSE(event->cancelable());
+  EXPECT_FALSE(event->default_prevented());
+  EXPECT_FALSE(event->IsBeingDispatched());
+  EXPECT_FALSE(event->propagation_stopped());
+  EXPECT_FALSE(event->immediate_propagation_stopped());
+  EXPECT_EQ("", event->message());
+  EXPECT_EQ("", event->filename());
+  EXPECT_EQ(0, event->lineno());
+  EXPECT_EQ(0, event->colno());
+  EXPECT_EQ(NULL, event->error());
+}
+
+TEST_F(ErrorEventTest, ConstructorWithEventTypeAndDefaultInitDict) {
+  ErrorEventInit init;
+  scoped_refptr<ErrorEvent> event = new ErrorEvent("mytestevent", init);
+
+  EXPECT_EQ("mytestevent", event->type());
+  EXPECT_EQ(NULL, event->target().get());
+  EXPECT_EQ(NULL, event->current_target().get());
+  EXPECT_EQ(Event::kNone, event->event_phase());
+  EXPECT_FALSE(event->bubbles());
+  EXPECT_FALSE(event->cancelable());
+  EXPECT_FALSE(event->default_prevented());
+  EXPECT_FALSE(event->IsBeingDispatched());
+  EXPECT_FALSE(event->propagation_stopped());
+  EXPECT_FALSE(event->immediate_propagation_stopped());
+  EXPECT_EQ("", event->message());
+  EXPECT_EQ("", event->filename());
+  EXPECT_EQ(0, event->lineno());
+  EXPECT_EQ(0, event->colno());
+  EXPECT_EQ(NULL, event->error());
+}
+
+TEST_F(ErrorEventTest, ConstructorWithEventTypeAndErrorInitDict) {
+  std::string result;
+  bool success = EvaluateScript(
+      "var event = new ErrorEvent('dog', "
+      "    {'cancelable':true, "
+      "     'message':'error_message', "
+      "     'filename':'error_filename', "
+      "     'lineno':100, "
+      "     'colno':50, "
+      "     'error':{'cobalt':'rulez'}});"
+      "if (event.type == 'dog' &&"
+      "    event.bubbles == false &&"
+      "    event.cancelable == true &&"
+      "    event.message == 'error_message' &&"
+      "    event.filename == 'error_filename' &&"
+      "    event.lineno == 100 &&"
+      "    event.colno == 50) "
+      "    event.error.cobalt;",
+      &result);
+  EXPECT_EQ("rulez", result);
+
+  if (!success) {
+    DLOG(ERROR) << "Failed to evaluate test: "
+                << "\"" << result << "\"";
+  } else {
+    LOG(INFO) << "Test result : "
+              << "\"" << result << "\"";
+  }
+}
+
+}  // namespace web
+}  // namespace cobalt
diff --git a/cobalt/dom/event.cc b/cobalt/web/event.cc
similarity index 97%
rename from cobalt/dom/event.cc
rename to cobalt/web/event.cc
index 1643e35..f791fef 100644
--- a/cobalt/dom/event.cc
+++ b/cobalt/web/event.cc
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/event.h"
+#include "cobalt/web/event.h"
 
 #include "base/compiler_specific.h"
 #include "base/time/time.h"
-#include "cobalt/dom/event_target.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 Event::Event(UninitializedFlag uninitialized_flag)
     : event_phase_(kNone), time_stamp_(GetEventTime(SbTimeGetMonotonicNow())) {
@@ -114,5 +114,5 @@
   return static_cast<uint64>(base_time.ToJsTime());
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/event.h b/cobalt/web/event.h
similarity index 96%
rename from cobalt/dom/event.h
rename to cobalt/web/event.h
index c4fd725..a909e54 100644
--- a/cobalt/dom/event.h
+++ b/cobalt/web/event.h
@@ -12,18 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_EVENT_H_
-#define COBALT_DOM_EVENT_H_
+#ifndef COBALT_WEB_EVENT_H_
+#define COBALT_WEB_EVENT_H_
 
 #include <string>
 
 #include "base/memory/weak_ptr.h"
 #include "cobalt/base/token.h"
-#include "cobalt/dom/event_init.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event_init.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // The forward declaration is necessary for breaking the bi-directional
 // dependency between Event and EventTarget.
@@ -155,7 +155,7 @@
   uint64 time_stamp_;
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_EVENT_H_
+#endif  // COBALT_WEB_EVENT_H_
diff --git a/cobalt/dom/event.idl b/cobalt/web/event.idl
similarity index 100%
rename from cobalt/dom/event.idl
rename to cobalt/web/event.idl
diff --git a/cobalt/dom/event_init.idl b/cobalt/web/event_init.idl
similarity index 100%
rename from cobalt/dom/event_init.idl
rename to cobalt/web/event_init.idl
diff --git a/cobalt/dom/event_listener.h b/cobalt/web/event_listener.h
similarity index 87%
rename from cobalt/dom/event_listener.h
rename to cobalt/web/event_listener.h
index 2e3914b..a1f79ef 100644
--- a/cobalt/dom/event_listener.h
+++ b/cobalt/web/event_listener.h
@@ -12,16 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_EVENT_LISTENER_H_
-#define COBALT_DOM_EVENT_LISTENER_H_
+#ifndef COBALT_WEB_EVENT_LISTENER_H_
+#define COBALT_WEB_EVENT_LISTENER_H_
 
 #include "base/memory/ref_counted.h"
 #include "base/optional.h"
-#include "cobalt/dom/event.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // The EventListener interface represents a callable object that will be called
 // when an event is fired.
@@ -37,7 +37,7 @@
       const scoped_refptr<Event>& event, bool* had_exception) const = 0;
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_EVENT_LISTENER_H_
+#endif  // COBALT_WEB_EVENT_LISTENER_H_
diff --git a/cobalt/dom/event_listener.idl b/cobalt/web/event_listener.idl
similarity index 100%
rename from cobalt/dom/event_listener.idl
rename to cobalt/web/event_listener.idl
diff --git a/cobalt/dom/event_target.cc b/cobalt/web/event_target.cc
similarity index 93%
rename from cobalt/dom/event_target.cc
rename to cobalt/web/event_target.cc
index bd5aa1c..8d40e9c 100644
--- a/cobalt/dom/event_target.cc
+++ b/cobalt/web/event_target.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/event_target.h"
+#include "cobalt/web/event_target.h"
 
 #include <memory>
 #include <utility>
@@ -23,18 +23,28 @@
 #include "base/message_loop/message_loop.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/dom_exception.h"
-#include "cobalt/dom/global_stats.h"
 #include "cobalt/script/environment_settings.h"
-#include "cobalt/xhr/xml_http_request_event_target.h"
+#include "cobalt/web/dom_exception.h"
+#include "cobalt/web/global_stats.h"
 #include "nb/memory_scope.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
+// Since EventTarget is always in a web environment, we store the environment
+// settings as web environment settings, and make it easily available to all
+// derived classes with the environment_settings() accessor.
 EventTarget::EventTarget(
     script::EnvironmentSettings* settings,
     UnpackOnErrorEventsBool onerror_event_parameter_handling)
+    : environment_settings_(
+          base::polymorphic_downcast<web::EnvironmentSettings*>(settings)),
+      unpack_onerror_events_(onerror_event_parameter_handling ==
+                             kUnpackOnErrorEvents) {}
+
+EventTarget::EventTarget(
+    web::EnvironmentSettings* settings,
+    UnpackOnErrorEventsBool onerror_event_parameter_handling)
     : environment_settings_(settings),
       unpack_onerror_events_(onerror_event_parameter_handling ==
                              kUnpackOnErrorEvents) {}
@@ -311,5 +321,5 @@
   return false;
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/event_target.h b/cobalt/web/event_target.h
similarity index 96%
rename from cobalt/dom/event_target.h
rename to cobalt/web/event_target.h
index 7dc5fee..899a65b 100644
--- a/cobalt/dom/event_target.h
+++ b/cobalt/web/event_target.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_EVENT_TARGET_H_
-#define COBALT_DOM_EVENT_TARGET_H_
+#ifndef COBALT_WEB_EVENT_TARGET_H_
+#define COBALT_WEB_EVENT_TARGET_H_
 
 #include <memory>
 #include <string>
@@ -27,17 +27,18 @@
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/token.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/event.h"
-#include "cobalt/dom/event_listener.h"
-#include "cobalt/dom/event_target_listener_info.h"
-#include "cobalt/dom/on_error_event_listener.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/exception_state.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/environment_settings.h"
+#include "cobalt/web/event.h"
+#include "cobalt/web/event_listener.h"
+#include "cobalt/web/event_target_listener_info.h"
+#include "cobalt/web/on_error_event_listener.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // The EventTarget interface represents an object that holds event listeners
 // and possibly generates events.
@@ -65,6 +66,10 @@
       script::EnvironmentSettings* settings,
       UnpackOnErrorEventsBool onerror_event_parameter_handling =
           kDoNotUnpackOnErrorEvents);
+  explicit EventTarget(
+      web::EnvironmentSettings* settings,
+      UnpackOnErrorEventsBool onerror_event_parameter_handling =
+          kDoNotUnpackOnErrorEvents);
 
   typedef script::ScriptValue<EventListener> EventListenerScriptValue;
   typedef script::ScriptValue<OnErrorEventListener>
@@ -484,7 +489,7 @@
   const base::DebuggerHooks& debugger_hooks() const {
     return environment_settings_->debugger_hooks();
   }
-  script::EnvironmentSettings* environment_settings() const {
+  web::EnvironmentSettings* environment_settings() const {
     return environment_settings_;
   }
 
@@ -502,7 +507,7 @@
 
   EventListenerInfos event_listener_infos_;
 
-  script::EnvironmentSettings* environment_settings_;
+  web::EnvironmentSettings* environment_settings_;
 
   // Tracks whether this current event listener should unpack the onerror
   // event object when calling its callback.  This is needed to implement
@@ -510,7 +515,7 @@
   bool unpack_onerror_events_;
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_EVENT_TARGET_H_
+#endif  // COBALT_WEB_EVENT_TARGET_H_
diff --git a/cobalt/dom/event_target.idl b/cobalt/web/event_target.idl
similarity index 100%
rename from cobalt/dom/event_target.idl
rename to cobalt/web/event_target.idl
diff --git a/cobalt/dom/event_target_listener_info.cc b/cobalt/web/event_target_listener_info.cc
similarity index 96%
rename from cobalt/dom/event_target_listener_info.cc
rename to cobalt/web/event_target_listener_info.cc
index 1f668d6..933703a 100644
--- a/cobalt/dom/event_target_listener_info.cc
+++ b/cobalt/web/event_target_listener_info.cc
@@ -12,15 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/event_target_listener_info.h"
+#include "cobalt/web/event_target_listener_info.h"
 
 #include "base/trace_event/trace_event.h"
-#include "cobalt/dom/event.h"
-#include "cobalt/dom/event_target.h"
-#include "cobalt/dom/global_stats.h"
+#include "cobalt/web/event.h"
+#include "cobalt/web/event_target.h"
+#include "cobalt/web/global_stats.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 EventTargetListenerInfo::EventTargetListenerInfo(
     script::Wrappable* wrappable, base::Token type, AttachMethod attach,
@@ -144,5 +144,5 @@
           on_error_event_listener_reference_->referenced_value().IsNull());
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/event_target_listener_info.h b/cobalt/web/event_target_listener_info.h
similarity index 95%
rename from cobalt/dom/event_target_listener_info.h
rename to cobalt/web/event_target_listener_info.h
index 0323236..5eb6594 100644
--- a/cobalt/dom/event_target_listener_info.h
+++ b/cobalt/web/event_target_listener_info.h
@@ -12,19 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_EVENT_TARGET_LISTENER_INFO_H_
-#define COBALT_DOM_EVENT_TARGET_LISTENER_INFO_H_
+#ifndef COBALT_WEB_EVENT_TARGET_LISTENER_INFO_H_
+#define COBALT_WEB_EVENT_TARGET_LISTENER_INFO_H_
 
 #include <memory>
 
 #include "base/memory/ref_counted.h"
-#include "cobalt/dom/event_listener.h"
-#include "cobalt/dom/on_error_event_listener.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event_listener.h"
+#include "cobalt/web/on_error_event_listener.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // Holds the event listener for an EventTarget, along with metadata describing
 // in what manner the listener was attached to the EventTarget.
@@ -129,7 +129,7 @@
       on_error_event_listener_reference_;
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_EVENT_TARGET_LISTENER_INFO_H_
+#endif  // COBALT_WEB_EVENT_TARGET_LISTENER_INFO_H_
diff --git a/cobalt/dom/event_target_test.cc b/cobalt/web/event_target_test.cc
similarity index 98%
rename from cobalt/dom/event_target_test.cc
rename to cobalt/web/event_target_test.cc
index 68eb284..9633018 100644
--- a/cobalt/dom/event_target_test.cc
+++ b/cobalt/web/event_target_test.cc
@@ -12,22 +12,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/event_target.h"
+#include "cobalt/web/event_target.h"
 
 #include <memory>
 
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/global_stats.h"
-#include "cobalt/dom/testing/mock_event_listener.h"
 #include "cobalt/script/testing/fake_script_value.h"
 #include "cobalt/script/testing/mock_exception_state.h"
 #include "cobalt/test/mock_debugger_hooks.h"
+#include "cobalt/web/dom_exception.h"
 #include "cobalt/web/environment_settings.h"
+#include "cobalt/web/testing/mock_event_listener.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 namespace {
 
 using script::testing::FakeScriptValue;
@@ -57,7 +57,7 @@
   }
 
   StrictMock<test::MockDebuggerHooks> debugger_hooks_;
-  web::EnvironmentSettings environment_settings_;
+  EnvironmentSettings environment_settings_;
 };
 
 base::Optional<bool> DispatchEventOnCurrentTarget(
@@ -551,5 +551,5 @@
 }
 
 }  // namespace
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/event_test.cc b/cobalt/web/event_test.cc
similarity index 96%
rename from cobalt/dom/event_test.cc
rename to cobalt/web/event_test.cc
index 53659b4..177bdca 100644
--- a/cobalt/dom/event_test.cc
+++ b/cobalt/web/event_test.cc
@@ -12,21 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/event.h"
+#include "cobalt/web/event.h"
 
-#include "cobalt/dom/global_stats.h"
 #include "base/time/time.h"
-#include "cobalt/dom/testing/gtest_workarounds.h"
+#include "cobalt/dom/global_stats.h"
+#include "cobalt/web/testing/gtest_workarounds.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 class EventTest : public ::testing::Test {
  protected:
-  EventTest(){
-    EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
-  }
+  EventTest() { EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks()); }
   ~EventTest() override {
     EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
   }
@@ -147,5 +145,5 @@
   EXPECT_FALSE(event->IsBeingDispatched());
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/web/global_stats.cc b/cobalt/web/global_stats.cc
new file mode 100644
index 0000000..97980c4
--- /dev/null
+++ b/cobalt/web/global_stats.cc
@@ -0,0 +1,50 @@
+// Copyright 2014 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/web/global_stats.h"
+
+#include "base/memory/singleton.h"
+#include "cobalt/base/c_val.h"
+
+namespace cobalt {
+namespace web {
+
+// static
+GlobalStats* GlobalStats::GetInstance() {
+  return base::Singleton<GlobalStats>::get();
+}
+
+GlobalStats::GlobalStats()
+    : num_event_listeners_("Count.WEB.EventListeners", 0,
+                           "Total number of currently active event listeners."),
+      num_active_java_script_events_(
+          "Count.WEB.ActiveJavaScriptEvents", 0,
+          "Total number of currently active JavaScript events.") {}
+
+GlobalStats::~GlobalStats() {}
+
+bool GlobalStats::CheckNoLeaks() {
+  return num_event_listeners_ == 0 && num_active_java_script_events_ == 0;
+}
+
+void GlobalStats::AddEventListener() { ++num_event_listeners_; }
+
+void GlobalStats::RemoveEventListener() { --num_event_listeners_; }
+
+void GlobalStats::StartJavaScriptEvent() { ++num_active_java_script_events_; }
+
+void GlobalStats::StopJavaScriptEvent() { --num_active_java_script_events_; }
+
+}  // namespace web
+}  // namespace cobalt
diff --git a/cobalt/web/global_stats.h b/cobalt/web/global_stats.h
new file mode 100644
index 0000000..ac3b021
--- /dev/null
+++ b/cobalt/web/global_stats.h
@@ -0,0 +1,58 @@
+// Copyright 2014 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_WEB_GLOBAL_STATS_H_
+#define COBALT_WEB_GLOBAL_STATS_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/singleton.h"
+#include "cobalt/base/c_val.h"
+
+namespace cobalt {
+namespace web {
+
+// This singleton class is used to track DOM-related statistics.
+class GlobalStats {
+ public:
+  GlobalStats(const GlobalStats&) = delete;
+  GlobalStats& operator=(const GlobalStats&) = delete;
+
+  static GlobalStats* GetInstance();
+
+  bool CheckNoLeaks();
+
+  void AddEventListener();
+  void RemoveEventListener();
+
+  int GetNumEventListeners() const { return num_event_listeners_; }
+
+  void StartJavaScriptEvent();
+  void StopJavaScriptEvent();
+
+ private:
+  GlobalStats();
+  ~GlobalStats();
+
+  // Web-related tracking
+  base::CVal<int, base::CValPublic> num_event_listeners_;
+
+  base::CVal<int> num_active_java_script_events_;
+
+  friend struct base::DefaultSingletonTraits<GlobalStats>;
+};
+
+}  // namespace web
+}  // namespace cobalt
+
+#endif  // COBALT_WEB_GLOBAL_STATS_H_
diff --git a/cobalt/web/location_base.h b/cobalt/web/location_base.h
new file mode 100644
index 0000000..7a036ac
--- /dev/null
+++ b/cobalt/web/location_base.h
@@ -0,0 +1,96 @@
+// Copyright 2022 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_WEB_LOCATION_BASE_H_
+#define COBALT_WEB_LOCATION_BASE_H_
+
+#include <string>
+
+#include "cobalt/script/wrappable.h"
+#include "cobalt/web/url_utils.h"
+#include "url/gurl.h"
+
+namespace cobalt {
+namespace web {
+
+// LocationBase objects provide a representation of the address of the active
+// script of the web context.
+//   https://www.w3.org/TR/html50/workers.html#worker-locations
+class LocationBase : public script::Wrappable {
+ public:
+  // If any navigation is triggered, all these callbacks should be provided,
+  // otherwise they can be empty.
+  explicit LocationBase(const GURL& url) : url_utils_(url) {}
+  LocationBase(const LocationBase&) = delete;
+  LocationBase& operator=(const LocationBase&) = delete;
+
+  // Web API: URLUtils (implements)
+  //
+  std::string href() const { return url_utils_.href(); }
+  void set_href(const std::string& href) { url_utils_.set_href(href); }
+
+  std::string protocol() const { return url_utils_.protocol(); }
+  void set_protocol(const std::string& protocol) {
+    url_utils_.set_protocol(protocol);
+  }
+
+  std::string host() const { return url_utils_.host(); }
+  void set_host(const std::string& host) { url_utils_.set_host(host); }
+
+  std::string hostname() const { return url_utils_.hostname(); }
+  void set_hostname(const std::string& hostname) {
+    url_utils_.set_hostname(hostname);
+  }
+
+  std::string port() const { return url_utils_.port(); }
+  void set_port(const std::string& port) { url_utils_.set_port(port); }
+
+  std::string pathname() const { return url_utils_.pathname(); }
+  void set_pathname(const std::string& pathname) {
+    url_utils_.set_pathname(pathname);
+  }
+
+  std::string hash() const { return url_utils_.hash(); }
+  void set_hash(const std::string& hash) { url_utils_.set_hash(hash); }
+
+  std::string search() const { return url_utils_.search(); }
+  void set_search(const std::string& search) { url_utils_.set_search(search); }
+
+  std::string origin() const { return url_utils_.origin(); }
+
+  // Custom, not in any spec.
+  //
+  // Gets and sets the URL without doing any navigation.
+  const GURL& url() const { return url_utils_.url(); }
+  void set_url(const GURL& url) { url_utils_.set_url(url); }
+
+  void set_update_steps_callback(
+      const URLUtils::UpdateStepsCallback& update_steps) {
+    url_utils_.set_update_steps_callback(update_steps);
+  }
+
+  loader::Origin GetOriginAsObject() const {
+    return url_utils_.GetOriginAsObject();
+  }
+
+  DEFINE_WRAPPABLE_TYPE(LocationBase);
+
+ private:
+  web::URLUtils url_utils_;
+};
+
+}  // namespace web
+}  // namespace cobalt
+
+#endif  // COBALT_WEB_LOCATION_BASE_H_
diff --git a/cobalt/web/navigator_base.cc b/cobalt/web/navigator_base.cc
new file mode 100644
index 0000000..ae0f020
--- /dev/null
+++ b/cobalt/web/navigator_base.cc
@@ -0,0 +1,68 @@
+// Copyright 2022 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/web/navigator_base.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/optional.h"
+#include "cobalt/script/script_value_factory.h"
+#include "cobalt/web/navigator_ua_data.h"
+#include "cobalt/worker/service_worker_container.h"
+#include "starboard/configuration_constants.h"
+
+namespace cobalt {
+namespace web {
+
+NavigatorBase::NavigatorBase(script::EnvironmentSettings* settings,
+                             const std::string& user_agent,
+                             web::UserAgentPlatformInfo* platform_info,
+                             const std::string& language,
+                             script::ScriptValueFactory* script_value_factory)
+    : user_agent_(user_agent),
+      user_agent_data_(
+          new NavigatorUAData(platform_info, script_value_factory)),
+      language_(language),
+      service_worker_(new worker::ServiceWorkerContainer(settings)),
+      script_value_factory_(script_value_factory) {}
+
+const std::string& NavigatorBase::language() const { return language_; }
+
+script::Sequence<std::string> NavigatorBase::languages() const {
+  script::Sequence<std::string> languages;
+  languages.push_back(language_);
+  return languages;
+}
+
+const std::string& NavigatorBase::user_agent() const { return user_agent_; }
+
+const scoped_refptr<NavigatorUAData>& NavigatorBase::user_agent_data() const {
+  return user_agent_data_;
+}
+
+bool NavigatorBase::on_line() const {
+#if SB_API_VERSION >= 13
+  return !SbSystemNetworkIsDisconnected();
+#else
+  return true;
+#endif
+}
+
+scoped_refptr<worker::ServiceWorkerContainer> NavigatorBase::service_worker() {
+  return service_worker_;
+}
+
+}  // namespace web
+}  // namespace cobalt
diff --git a/cobalt/web/navigator_base.h b/cobalt/web/navigator_base.h
new file mode 100644
index 0000000..55f3eec
--- /dev/null
+++ b/cobalt/web/navigator_base.h
@@ -0,0 +1,80 @@
+// Copyright 2022 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_WEB_NAVIGATOR_BASE_H_
+#define COBALT_WEB_NAVIGATOR_BASE_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "cobalt/script/script_value_factory.h"
+#include "cobalt/script/wrappable.h"
+#include "cobalt/web/navigator_ua_data.h"
+#include "cobalt/worker/service_worker_container.h"
+
+namespace cobalt {
+namespace web {
+
+// The NavigatorBase object contains the shared logic between Navigator and
+// WorkerNavigator.
+// https://html.spec.whatwg.org/commit-snapshots/9fe65ccaf193c8c0d93fd3638b4c8e935fc28213/#the-navigatorbase-object
+// https://www.w3.org/TR/html50/webappapis.html#navigator
+
+class NavigatorBase : public script::Wrappable {
+ public:
+  NavigatorBase(script::EnvironmentSettings* settings,
+                const std::string& user_agent,
+                UserAgentPlatformInfo* platform_info,
+                const std::string& language,
+                script::ScriptValueFactory* script_value_factory);
+
+  // Web API: NavigatorID
+  const std::string& user_agent() const;
+
+  // Web API: NavigatorUA
+  const scoped_refptr<NavigatorUAData>& user_agent_data() const;
+
+  // Web API: NavigatorLanguage
+  const std::string& language() const;
+  script::Sequence<std::string> languages() const;
+
+  // Web API: NavigatorOnline
+  bool on_line() const;
+
+  // Web API: ServiceWorker
+  scoped_refptr<worker::ServiceWorkerContainer> service_worker();
+
+  // Set maybe freeze callback.
+  void set_maybefreeze_callback(const base::Closure& maybe_freeze_callback) {
+    maybe_freeze_callback_ = maybe_freeze_callback;
+  }
+
+  script::ScriptValueFactory* script_value_factory() {
+    return script_value_factory_;
+  }
+
+ private:
+  std::string user_agent_;
+  scoped_refptr<NavigatorUAData> user_agent_data_;
+  std::string language_;
+  scoped_refptr<worker::ServiceWorkerContainer> service_worker_;
+  script::ScriptValueFactory* script_value_factory_;
+
+  base::Closure maybe_freeze_callback_;
+};
+
+}  // namespace web
+}  // namespace cobalt
+
+#endif  // COBALT_WEB_NAVIGATOR_BASE_H_
diff --git a/cobalt/dom/navigator_id.idl b/cobalt/web/navigator_id.idl
similarity index 100%
rename from cobalt/dom/navigator_id.idl
rename to cobalt/web/navigator_id.idl
diff --git a/cobalt/dom/navigator_language.idl b/cobalt/web/navigator_language.idl
similarity index 100%
rename from cobalt/dom/navigator_language.idl
rename to cobalt/web/navigator_language.idl
diff --git a/cobalt/dom/navigator_online.idl b/cobalt/web/navigator_online.idl
similarity index 100%
rename from cobalt/dom/navigator_online.idl
rename to cobalt/web/navigator_online.idl
diff --git a/cobalt/dom/navigator_ua.idl b/cobalt/web/navigator_ua.idl
similarity index 100%
rename from cobalt/dom/navigator_ua.idl
rename to cobalt/web/navigator_ua.idl
diff --git a/cobalt/dom/navigator_ua_brand_version.idl b/cobalt/web/navigator_ua_brand_version.idl
similarity index 100%
rename from cobalt/dom/navigator_ua_brand_version.idl
rename to cobalt/web/navigator_ua_brand_version.idl
diff --git a/cobalt/dom/navigator_ua_data.cc b/cobalt/web/navigator_ua_data.cc
similarity index 98%
rename from cobalt/dom/navigator_ua_data.cc
rename to cobalt/web/navigator_ua_data.cc
index 46df53c..391815f 100644
--- a/cobalt/dom/navigator_ua_data.cc
+++ b/cobalt/web/navigator_ua_data.cc
@@ -12,12 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/navigator_ua_data.h"
+#include "cobalt/web/navigator_ua_data.h"
 
 #include "base/strings/stringprintf.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 NavigatorUAData::NavigatorUAData(
     UserAgentPlatformInfo* platform_info,
@@ -158,5 +158,5 @@
   return promise;
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/navigator_ua_data.h b/cobalt/web/navigator_ua_data.h
similarity index 84%
rename from cobalt/dom/navigator_ua_data.h
rename to cobalt/web/navigator_ua_data.h
index 8b2a7b5..1dfd873 100644
--- a/cobalt/dom/navigator_ua_data.h
+++ b/cobalt/web/navigator_ua_data.h
@@ -12,23 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_NAVIGATOR_UA_DATA_H_
-#define COBALT_DOM_NAVIGATOR_UA_DATA_H_
+#ifndef COBALT_WEB_NAVIGATOR_UA_DATA_H_
+#define COBALT_WEB_NAVIGATOR_UA_DATA_H_
 
 #include <string>
 
-#include "cobalt/dom/cobalt_ua_data_values_interface.h"
-#include "cobalt/dom/navigator_ua_brand_version.h"
-#include "cobalt/dom/ua_low_entropy_json.h"
-#include "cobalt/dom/user_agent_platform_info.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/script_value_factory.h"
 #include "cobalt/script/sequence.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/cobalt_ua_data_values_interface.h"
+#include "cobalt/web/navigator_ua_brand_version.h"
+#include "cobalt/web/ua_low_entropy_json.h"
+#include "cobalt/web/user_agent_platform_info.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // The NavigatorUAData object holds the User-Agent Client Hints information.
 // https://wicg.github.io/ua-client-hints/#navigatoruadata
@@ -65,7 +65,7 @@
   DISALLOW_COPY_AND_ASSIGN(NavigatorUAData);
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_NAVIGATOR_UA_DATA_H_
+#endif  // COBALT_WEB_NAVIGATOR_UA_DATA_H_
diff --git a/cobalt/dom/navigator_ua_data.idl b/cobalt/web/navigator_ua_data.idl
similarity index 100%
rename from cobalt/dom/navigator_ua_data.idl
rename to cobalt/web/navigator_ua_data.idl
diff --git a/cobalt/dom/on_error_event_listener.cc b/cobalt/web/on_error_event_listener.cc
similarity index 91%
rename from cobalt/dom/on_error_event_listener.cc
rename to cobalt/web/on_error_event_listener.cc
index a56a55b..88ae6c4 100644
--- a/cobalt/dom/on_error_event_listener.cc
+++ b/cobalt/web/on_error_event_listener.cc
@@ -12,18 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/on_error_event_listener.h"
+#include "cobalt/web/on_error_event_listener.h"
 
 #include <string>
 
 #include "base/logging.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/error_event.h"
-#include "cobalt/dom/event_target.h"
+#include "cobalt/web/error_event.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 base::Optional<bool> OnErrorEventListener::HandleEvent(
     const scoped_refptr<script::Wrappable>& callback_this,
@@ -45,5 +45,5 @@
   }
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/on_error_event_listener.h b/cobalt/web/on_error_event_listener.h
similarity index 88%
rename from cobalt/dom/on_error_event_listener.h
rename to cobalt/web/on_error_event_listener.h
index 5ed11cc..18754ca 100644
--- a/cobalt/dom/on_error_event_listener.h
+++ b/cobalt/web/on_error_event_listener.h
@@ -12,20 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_ON_ERROR_EVENT_LISTENER_H_
-#define COBALT_DOM_ON_ERROR_EVENT_LISTENER_H_
+#ifndef COBALT_WEB_ON_ERROR_EVENT_LISTENER_H_
+#define COBALT_WEB_ON_ERROR_EVENT_LISTENER_H_
 
 #include <string>
 
 #include "base/memory/ref_counted.h"
 #include "base/optional.h"
-#include "cobalt/dom/event.h"
-#include "cobalt/dom/event_listener.h"
 #include "cobalt/script/union_type.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event.h"
+#include "cobalt/web/event_listener.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // This interface exists to support the special case OnErrorEventHandler:
 //   https://html.spec.whatwg.org/#onerroreventhandler
@@ -50,7 +50,7 @@
       bool* had_exception) const = 0;
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_ON_ERROR_EVENT_LISTENER_H_
+#endif  // COBALT_WEB_ON_ERROR_EVENT_LISTENER_H_
diff --git a/cobalt/dom/on_error_event_listener.idl b/cobalt/web/on_error_event_listener.idl
similarity index 100%
rename from cobalt/dom/on_error_event_listener.idl
rename to cobalt/web/on_error_event_listener.idl
diff --git a/cobalt/dom/security_policy_violation_event.cc b/cobalt/web/security_policy_violation_event.cc
similarity index 94%
rename from cobalt/dom/security_policy_violation_event.cc
rename to cobalt/web/security_policy_violation_event.cc
index b32e2e9..aa0c0ca 100644
--- a/cobalt/dom/security_policy_violation_event.cc
+++ b/cobalt/web/security_policy_violation_event.cc
@@ -12,13 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/security_policy_violation_event.h"
+#include "cobalt/web/security_policy_violation_event.h"
 
 #include "cobalt/base/token.h"
 #include "cobalt/base/tokens.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 SecurityPolicyViolationEvent::SecurityPolicyViolationEvent(
     const std::string& type)
@@ -42,5 +42,5 @@
       line_number_(line_number),
       column_number_(column_number) {}
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/security_policy_violation_event.h b/cobalt/web/security_policy_violation_event.h
similarity index 88%
rename from cobalt/dom/security_policy_violation_event.h
rename to cobalt/web/security_policy_violation_event.h
index e36ba90..d09a254 100644
--- a/cobalt/dom/security_policy_violation_event.h
+++ b/cobalt/web/security_policy_violation_event.h
@@ -12,18 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_SECURITY_POLICY_VIOLATION_EVENT_H_
-#define COBALT_DOM_SECURITY_POLICY_VIOLATION_EVENT_H_
+#ifndef COBALT_WEB_SECURITY_POLICY_VIOLATION_EVENT_H_
+#define COBALT_WEB_SECURITY_POLICY_VIOLATION_EVENT_H_
 
 #include <string>
 
-#include "cobalt/dom/event.h"
+#include "cobalt/web/event.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // https://www.w3.org/TR/CSP/#securitypolicyviolationevent-interface
-class SecurityPolicyViolationEvent : public Event {
+class SecurityPolicyViolationEvent : public web::Event {
  public:
   explicit SecurityPolicyViolationEvent(const std::string& type);
 
@@ -64,7 +64,7 @@
   int column_number_;
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_SECURITY_POLICY_VIOLATION_EVENT_H_
+#endif  // COBALT_WEB_SECURITY_POLICY_VIOLATION_EVENT_H_
diff --git a/cobalt/dom/security_policy_violation_event.idl b/cobalt/web/security_policy_violation_event.idl
similarity index 100%
rename from cobalt/dom/security_policy_violation_event.idl
rename to cobalt/web/security_policy_violation_event.idl
diff --git a/cobalt/web/stat_tracker.cc b/cobalt/web/stat_tracker.cc
new file mode 100644
index 0000000..edead88
--- /dev/null
+++ b/cobalt/web/stat_tracker.cc
@@ -0,0 +1,57 @@
+// Copyright 2022 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/web/stat_tracker.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace cobalt {
+namespace web {
+StatTracker::StatTracker(const std::string& name)
+    : count_window_timers_interval_(
+          base::StringPrintf("Count.%s.DOM.WindowTimers.Interval",
+                             name.c_str()),
+          0, "Number of active WindowTimer Intervals."),
+      count_window_timers_timeout_(
+          base::StringPrintf("Count.%s.DOM.WindowTimers.Timeout", name.c_str()),
+          0, "Number of active WindowTimer Timeouts."),
+      count_window_timers_interval_created_(0),
+      count_window_timers_interval_destroyed_(0),
+      count_window_timers_timeout_created_(0),
+      count_window_timers_timeout_destroyed_(0) {}
+
+StatTracker::~StatTracker() {
+  FlushPeriodicTracking();
+
+  // Verify that all of the elements were removed from the document and
+  // destroyed.
+  DCHECK_EQ(count_window_timers_interval_, 0);
+  DCHECK_EQ(count_window_timers_timeout_, 0);
+}
+
+void StatTracker::FlushPeriodicTracking() {
+  // Update the CVals before clearing the periodic values.
+  count_window_timers_interval_ += count_window_timers_interval_created_ -
+                                   count_window_timers_interval_destroyed_;
+  count_window_timers_timeout_ += count_window_timers_timeout_created_ -
+                                  count_window_timers_timeout_destroyed_;
+
+  // Now clear the values.
+  count_window_timers_interval_created_ = 0;
+  count_window_timers_interval_destroyed_ = 0;
+  count_window_timers_timeout_created_ = 0;
+  count_window_timers_timeout_destroyed_ = 0;
+}
+}  // namespace web
+}  // namespace cobalt
diff --git a/cobalt/web/stat_tracker.h b/cobalt/web/stat_tracker.h
new file mode 100644
index 0000000..3e9edc8
--- /dev/null
+++ b/cobalt/web/stat_tracker.h
@@ -0,0 +1,61 @@
+// Copyright 2022 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_WEB_STAT_TRACKER_H_
+#define COBALT_WEB_STAT_TRACKER_H_
+
+#include <string>
+
+#include "cobalt/base/c_val.h"
+
+namespace cobalt {
+namespace web {
+
+// Tracks stats for web resources.
+class StatTracker {
+ public:
+  explicit StatTracker(const std::string& name);
+  ~StatTracker();
+
+  void OnWindowTimersIntervalCreated() {
+    ++count_window_timers_interval_created_;
+  }
+  void OnWindowTimersIntervalDestroyed() {
+    ++count_window_timers_interval_destroyed_;
+  }
+  void OnWindowTimersTimeoutCreated() {
+    ++count_window_timers_timeout_created_;
+  }
+  void OnWindowTimersTimeoutDestroyed() {
+    ++count_window_timers_timeout_destroyed_;
+  }
+
+  void FlushPeriodicTracking();
+
+ private:
+  // Count cvals that are updated when the periodic tracking is flushed.
+  base::CVal<int, base::CValPublic> count_window_timers_interval_;
+  base::CVal<int, base::CValPublic> count_window_timers_timeout_;
+
+  // Periodic counts. The counts are cleared after the CVals are updated in
+  // |FlushPeriodicTracking|.
+  int count_window_timers_interval_created_;
+  int count_window_timers_interval_destroyed_;
+  int count_window_timers_timeout_created_;
+  int count_window_timers_timeout_destroyed_;
+};
+}  // namespace web
+}  // namespace cobalt
+
+#endif  // COBALT_WEB_STAT_TRACKER_H_
diff --git a/cobalt/web/testing/BUILD.gn b/cobalt/web/testing/BUILD.gn
new file mode 100644
index 0000000..3b785ca
--- /dev/null
+++ b/cobalt/web/testing/BUILD.gn
@@ -0,0 +1,41 @@
+# Copyright 2021 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.
+
+source_set("web_testing") {
+  testonly = true
+  has_pedantic_warnings = true
+
+  sources = [
+    "gtest_workarounds.h",
+    "mock_event_listener.h",
+    "stub_environment_settings.h",
+    "stub_web_context.h",
+  ]
+
+  deps = [
+    "//base",
+    "//base/test:test_support",
+    "//cobalt/base",
+    "//cobalt/network",
+    "//cobalt/script",
+    "//cobalt/script/testing:script_testing",
+    "//cobalt/web",
+    "//cobalt/worker",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//url",
+  ]
+
+  public_deps = [ "//cobalt/script/testing:script_testing" ]
+}
diff --git a/cobalt/dom/testing/gtest_workarounds.h b/cobalt/web/testing/gtest_workarounds.h
similarity index 89%
rename from cobalt/dom/testing/gtest_workarounds.h
rename to cobalt/web/testing/gtest_workarounds.h
index 79ab69c..b13d03b 100644
--- a/cobalt/dom/testing/gtest_workarounds.h
+++ b/cobalt/web/testing/gtest_workarounds.h
@@ -12,13 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_TESTING_GTEST_WORKAROUNDS_H_
-#define COBALT_DOM_TESTING_GTEST_WORKAROUNDS_H_
+#ifndef COBALT_WEB_TESTING_GTEST_WORKAROUNDS_H_
+#define COBALT_WEB_TESTING_GTEST_WORKAROUNDS_H_
 
 #include "base/memory/ref_counted.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // The following set of operators is needed to support EXPECT_* expressions
 // where a scoped_refptr object is compared to NULL.
@@ -54,7 +54,7 @@
   return p != NULL;
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_TESTING_GTEST_WORKAROUNDS_H_
+#endif  // COBALT_WEB_TESTING_GTEST_WORKAROUNDS_H_
diff --git a/cobalt/dom/testing/mock_event_listener.h b/cobalt/web/testing/mock_event_listener.h
similarity index 95%
rename from cobalt/dom/testing/mock_event_listener.h
rename to cobalt/web/testing/mock_event_listener.h
index 1129410..7d0e5ee 100644
--- a/cobalt/dom/testing/mock_event_listener.h
+++ b/cobalt/web/testing/mock_event_listener.h
@@ -12,12 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_TESTING_MOCK_EVENT_LISTENER_H_
-#define COBALT_DOM_TESTING_MOCK_EVENT_LISTENER_H_
+#ifndef COBALT_WEB_TESTING_MOCK_EVENT_LISTENER_H_
+#define COBALT_WEB_TESTING_MOCK_EVENT_LISTENER_H_
 
 #include <memory>
+#include <string>
 
-#include "cobalt/dom/event_listener.h"
+#include "cobalt/web/event_listener.h"
 
 #include "base/memory/ref_counted.h"
 #include "base/optional.h"
@@ -27,7 +28,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 namespace testing {
 
 class MockEventListener : public EventListener {
@@ -163,7 +164,7 @@
   }
 };
 }  // namespace testing
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_TESTING_MOCK_EVENT_LISTENER_H_
+#endif  // COBALT_WEB_TESTING_MOCK_EVENT_LISTENER_H_
diff --git a/cobalt/web/testing/stub_environment_settings.h b/cobalt/web/testing/stub_environment_settings.h
new file mode 100644
index 0000000..e984c8f
--- /dev/null
+++ b/cobalt/web/testing/stub_environment_settings.h
@@ -0,0 +1,38 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_WEB_TESTING_STUB_ENVIRONMENT_SETTINGS_H_
+#define COBALT_WEB_TESTING_STUB_ENVIRONMENT_SETTINGS_H_
+
+#include "cobalt/base/debugger_hooks.h"
+#include "cobalt/web/environment_settings.h"
+
+namespace cobalt {
+namespace web {
+namespace testing {
+
+class StubEnvironmentSettings : public EnvironmentSettings {
+ public:
+  StubEnvironmentSettings() : EnvironmentSettings(null_debugger_hooks_) {}
+  ~StubEnvironmentSettings() override {}
+
+ private:
+  base::NullDebuggerHooks null_debugger_hooks_;
+};
+
+}  // namespace testing
+}  // namespace web
+}  // namespace cobalt
+
+#endif  // COBALT_WEB_TESTING_STUB_ENVIRONMENT_SETTINGS_H_
diff --git a/cobalt/web/stub_web_context.h b/cobalt/web/testing/stub_web_context.h
similarity index 70%
rename from cobalt/web/stub_web_context.h
rename to cobalt/web/testing/stub_web_context.h
index 8903fce..8db6621 100644
--- a/cobalt/web/stub_web_context.h
+++ b/cobalt/web/testing/stub_web_context.h
@@ -12,24 +12,29 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_WEB_STUB_WEB_CONTEXT_H_
-#define COBALT_WEB_STUB_WEB_CONTEXT_H_
+#ifndef COBALT_WEB_TESTING_STUB_WEB_CONTEXT_H_
+#define COBALT_WEB_TESTING_STUB_WEB_CONTEXT_H_
 
 #include <memory>
 #include <string>
 
-#include "cobalt/loader/fetcher_factory.h"
-#include "cobalt/loader/script_loader_factory.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
+#include "cobalt/script/wrappable.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/environment_settings.h"
+#include "cobalt/worker/service_worker.h"
+#include "cobalt/worker/service_worker_registration.h"
 #include "url/gurl.h"
 
 namespace cobalt {
+namespace loader {
+class FetcherFactory;
+class ScriptLoaderFactory;
+}  // namespace loader
 namespace web {
-namespace test {
+namespace testing {
 
 class StubSettings : public EnvironmentSettings {
  public:
@@ -38,9 +43,9 @@
  private:
 };
 
-class StubWebContext : public web::Context {
+class StubWebContext : public Context {
  public:
-  StubWebContext() : web::Context(), name_("StubWebInstance") {
+  StubWebContext() : Context(), name_("StubWebInstance") {
     javascript_engine_ = script::JavaScriptEngine::CreateEngine();
     global_environment_ = javascript_engine_->CreateGlobalEnvironment();
   }
@@ -82,7 +87,7 @@
     NOTREACHED();
     return nullptr;
   }
-  dom::Blob::Registry* blob_registry() const final {
+  Blob::Registry* blob_registry() const final {
     NOTREACHED();
     return nullptr;
   }
@@ -94,22 +99,50 @@
     return network_module_.get();
   }
 
+  worker::ServiceWorkerJobs* service_worker_jobs() const final {
+    NOTREACHED();
+    return nullptr;
+  }
+
+
   const std::string& name() const final { return name_; };
   void setup_environment_settings(
       EnvironmentSettings* environment_settings) final {
     environment_settings_.reset(environment_settings);
     if (environment_settings_) environment_settings_->set_context(this);
   }
-  web::EnvironmentSettings* environment_settings() const final {
+  EnvironmentSettings* environment_settings() const final {
     return environment_settings_.get();
   }
 
+  scoped_refptr<worker::ServiceWorkerRegistration>
+  LookupServiceWorkerRegistration(
+      worker::ServiceWorkerRegistrationObject* registration) final {
+    NOTIMPLEMENTED();
+    return scoped_refptr<worker::ServiceWorkerRegistration>();
+  }
   scoped_refptr<worker::ServiceWorkerRegistration> GetServiceWorkerRegistration(
       worker::ServiceWorkerRegistrationObject* registration) final {
     NOTIMPLEMENTED();
     return scoped_refptr<worker::ServiceWorkerRegistration>();
   }
 
+  scoped_refptr<worker::ServiceWorker> LookupServiceWorker(
+      worker::ServiceWorkerObject* worker) final {
+    NOTIMPLEMENTED();
+    return scoped_refptr<worker::ServiceWorker>();
+  }
+  scoped_refptr<worker::ServiceWorker> GetServiceWorker(
+      worker::ServiceWorkerObject* worker) final {
+    NOTIMPLEMENTED();
+    return scoped_refptr<worker::ServiceWorker>();
+  }
+
+  WindowOrWorkerGlobalScope* GetWindowOrWorkerGlobalScope() final {
+    NOTIMPLEMENTED();
+    return nullptr;
+  }
+
   // Other
  private:
   // Name of the web instance.
@@ -122,11 +155,11 @@
 
   std::unique_ptr<network::NetworkModule> network_module_;
   // Environment Settings object
-  std::unique_ptr<web::EnvironmentSettings> environment_settings_;
+  std::unique_ptr<EnvironmentSettings> environment_settings_;
 };
 
-}  // namespace test
+}  // namespace testing
 }  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_WEB_STUB_WEB_CONTEXT_H_
+#endif  // COBALT_WEB_TESTING_STUB_WEB_CONTEXT_H_
diff --git a/cobalt/dom/ua_data_values.idl b/cobalt/web/ua_data_values.idl
similarity index 100%
rename from cobalt/dom/ua_data_values.idl
rename to cobalt/web/ua_data_values.idl
diff --git a/cobalt/dom/ua_low_entropy_json.idl b/cobalt/web/ua_low_entropy_json.idl
similarity index 100%
rename from cobalt/dom/ua_low_entropy_json.idl
rename to cobalt/web/ua_low_entropy_json.idl
diff --git a/cobalt/web/url.cc b/cobalt/web/url.cc
new file mode 100644
index 0000000..87092e7
--- /dev/null
+++ b/cobalt/web/url.cc
@@ -0,0 +1,145 @@
+// Copyright 2015 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/web/url.h"
+
+#include "base/guid.h"
+#include "base/logging.h"
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/script/exception_message.h"
+#include "cobalt/script/exception_state.h"
+#include "cobalt/web/context.h"
+#include "cobalt/web/environment_settings.h"
+#include "url/gurl.h"
+
+namespace cobalt {
+namespace web {
+
+namespace {
+const char kBlobUrlProtocol[] = "blob";
+}  // namespace
+
+URL::URL(const std::string& url, const std::string& base,
+         script::ExceptionState* exception_state)
+    : URLUtils() {
+  // Algorithm for URL constructor:
+  //   https://www.w3.org/TR/2014/WD-url-1-20141209/#dom-url-url
+  // 1. Let parsedBase be the result of running the basic URL parser on base.
+  GURL parsed_base(base);
+
+  // 2. If parsedBase is failure, throw a TypeError exception.
+  if (parsed_base.is_empty()) {
+    exception_state->SetSimpleException(script::kSimpleTypeError);
+    return;
+  }
+  // 3. Set parsedURL to the result of running the basic URL parser on url
+  //    with parsedBase.
+  GURL parsed_url = parsed_base.Resolve(url);
+
+  // 4. If parsedURL is failure, throw a TypeError exception.
+  if (parsed_url.is_empty()) {
+    exception_state->SetSimpleException(script::kSimpleTypeError);
+    return;
+  }
+
+  // 5. Let result be a new URL object.
+  // 6. Let result’s get the base return parsedBase.
+  // 7. Run result’s set the input given the empty string and parsedURL.
+  set_url(parsed_url);
+
+  // 8. Return result.
+}
+
+// static
+std::string URL::CreateObjectURL(
+    script::EnvironmentSettings* environment_settings,
+    const scoped_refptr<dom::MediaSource>& media_source) {
+  if (!media_source) {
+    return "";
+  }
+
+  std::string blob_url = kBlobUrlProtocol;
+  blob_url += ':' + base::GenerateGUID();
+  dom::RegisterMediaSourceObjectURL(environment_settings, blob_url,
+                                    media_source);
+  return blob_url;
+}
+
+// static
+std::string URL::CreateObjectURL(
+    script::EnvironmentSettings* environment_settings,
+    const scoped_refptr<Blob>& blob) {
+  EnvironmentSettings* web_settings =
+      base::polymorphic_downcast<EnvironmentSettings*>(environment_settings);
+  DCHECK(web_settings);
+  DCHECK(web_settings->context()->blob_registry());
+  if (!blob) {
+    return "";
+  }
+
+  std::string blob_url = kBlobUrlProtocol;
+  blob_url += ':' + base::GenerateGUID();
+  web_settings->context()->blob_registry()->Register(blob_url, blob);
+  return blob_url;
+}
+
+// static
+void URL::RevokeObjectURL(script::EnvironmentSettings* environment_settings,
+                          const std::string& url) {
+  // 1. If the url refers to a Blob that has a readability state of CLOSED OR if
+  // the value provided for the url argument is not a Blob URL, OR if the value
+  // provided for the url argument does not have an entry in the Blob URL Store,
+  // this method call does nothing. User agents may display a message on the
+  // error console.
+  if (!GURL(url).SchemeIs(kBlobUrlProtocol)) {
+    LOG(WARNING) << "URL is not a Blob URL.";
+    return;
+  }
+
+  // 2. Otherwise, user agents must remove the entry from the Blob URL Store for
+  // url.
+  if (!dom::UnregisterMediaSourceObjectURL(environment_settings, url) &&
+      !base::polymorphic_downcast<EnvironmentSettings*>(environment_settings)
+           ->context()
+           ->blob_registry()
+           ->Unregister(url)) {
+    DLOG(WARNING) << "Cannot find object for blob url " << url;
+  }
+}
+
+bool URL::BlobResolver(Blob::Registry* registry, const GURL& url,
+                       const char** data, size_t* size) {
+  DCHECK(data);
+  DCHECK(size);
+
+  *size = 0;
+  *data = NULL;
+
+  web::Blob* blob = registry->Retrieve(url.spec()).get();
+
+  if (blob) {
+    *size = static_cast<size_t>(blob->size());
+
+    if (*size > 0) {
+      *data = reinterpret_cast<const char*>(blob->data());
+    }
+
+    return true;
+  } else {
+    return false;
+  }
+}
+
+}  // namespace web
+}  // namespace cobalt
diff --git a/cobalt/dom/url.h b/cobalt/web/url.h
similarity index 68%
rename from cobalt/dom/url.h
rename to cobalt/web/url.h
index e3bb795..4eddb32 100644
--- a/cobalt/dom/url.h
+++ b/cobalt/web/url.h
@@ -12,34 +12,47 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_URL_H_
-#define COBALT_DOM_URL_H_
+#ifndef COBALT_WEB_URL_H_
+#define COBALT_WEB_URL_H_
 
 #include <string>
 
-#include "cobalt/dom/blob.h"
 #include "cobalt/loader/blob_fetcher.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/script/exception_state.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/blob.h"
 
 namespace cobalt {
 namespace dom {
-
 class MediaSource;
 
+extern void RegisterMediaSourceObjectURL(
+    script::EnvironmentSettings* environment_settings,
+    const std::string& blob_url,
+    const scoped_refptr<dom::MediaSource>& media_source);
+
+extern bool UnregisterMediaSourceObjectURL(
+    script::EnvironmentSettings* environment_settings, const std::string& url);
+
+
+}  // namespace dom
+namespace web {
 // The URL interface contains static functions CreateObjectURL() and
 // RevokeObjectURL().  The former is used to create urls in string from
 // JavaScript blob objects.  The latter is used to revoke the url.
 //   https://www.w3.org/TR/2015/WD-FileAPI-20150421/#URL-object
 //
-// The Media Source Extension extends it to create an url from a MediaSource
-// object so we can assign it to HTMLMediaElement.src.
+// The Media Source Extension extends it to create an url from a
+// dom::MediaSource object so we can assign it to HTMLMediaElement.src.
 //   https://rawgit.com/w3c/media-source/cfb1b3d4309a6e6e2c01bd87e048758172a86e4b/media-source.html#dom-createobjecturl
-class URL : public script::Wrappable {
+class URL : public URLUtils, public script::Wrappable {
  public:
+  URL(const std::string& url, const std::string& base,
+      script::ExceptionState* exception_state);
   static std::string CreateObjectURL(
       script::EnvironmentSettings* environment_settings,
-      const scoped_refptr<MediaSource>& media_source);
+      const scoped_refptr<dom::MediaSource>& media_source);
   static std::string CreateObjectURL(
       script::EnvironmentSettings* environment_settings,
       const scoped_refptr<Blob>& blob);
@@ -47,7 +60,7 @@
                               const std::string& url);
 
   static loader::BlobFetcher::ResolverCallback MakeBlobResolverCallback(
-      dom::Blob::Registry* blob_registry) {
+      Blob::Registry* blob_registry) {
     DCHECK(blob_registry);
     return base::Bind(&BlobResolver, blob_registry);
   }
@@ -55,11 +68,11 @@
   DEFINE_WRAPPABLE_TYPE(URL);
 
  private:
-  static bool BlobResolver(dom::Blob::Registry* registry, const GURL& url,
+  static bool BlobResolver(Blob::Registry* registry, const GURL& url,
                            const char** data, size_t* size);
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_URL_H_
+#endif  // COBALT_WEB_URL_H_
diff --git a/cobalt/dom/url.idl b/cobalt/web/url.idl
similarity index 84%
rename from cobalt/dom/url.idl
rename to cobalt/web/url.idl
index aee3eb8..eed2f23 100644
--- a/cobalt/dom/url.idl
+++ b/cobalt/web/url.idl
@@ -13,9 +13,10 @@
 // limitations under the License.
 
 // https://www.w3.org/TR/2014/WD-url-1-20141209/#dom-url
-
-interface URL {
+[RaisesException=Constructor, Constructor(
+     USVString url, optional USVString base = "about:blank")] interface URL {
   [CallWith=EnvironmentSettings] static DOMString
       createObjectURL(Blob blob);
   [CallWith=EnvironmentSettings] static void revokeObjectURL(DOMString url);
 };
+URL implements URLUtils;
diff --git a/cobalt/dom/url_registry.h b/cobalt/web/url_registry.h
similarity index 94%
rename from cobalt/dom/url_registry.h
rename to cobalt/web/url_registry.h
index eec21bd..870ed50 100644
--- a/cobalt/dom/url_registry.h
+++ b/cobalt/web/url_registry.h
@@ -12,17 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_URL_REGISTRY_H_
-#define COBALT_DOM_URL_REGISTRY_H_
+#ifndef COBALT_WEB_URL_REGISTRY_H_
+#define COBALT_WEB_URL_REGISTRY_H_
 
 #include <string>
+#include <utility>
 
 #include "base/containers/hash_tables.h"
 #include "base/memory/ref_counted.h"
 #include "cobalt/script/tracer.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // This class manages a registry of objects.  Its user can associate a
 // object to a blob url as well as to retrieve a object by a blob url.
@@ -81,7 +82,7 @@
   return true;
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_URL_REGISTRY_H_
+#endif  // COBALT_WEB_URL_REGISTRY_H_
diff --git a/cobalt/web/url_test.cc b/cobalt/web/url_test.cc
new file mode 100644
index 0000000..d38cdc1
--- /dev/null
+++ b/cobalt/web/url_test.cc
@@ -0,0 +1,208 @@
+// Copyright 2022 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/web/url.h"
+
+#include <memory>
+#include <string>
+
+#include "base/logging.h"
+#include "cobalt/dom/testing/stub_window.h"
+#include "cobalt/script/exception_message.h"
+#include "cobalt/script/exception_state.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/source_code.h"
+#include "cobalt/script/testing/mock_exception_state.h"
+#include "cobalt/web/testing/gtest_workarounds.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using cobalt::script::testing::MockExceptionState;
+using ::testing::_;
+using ::testing::SaveArg;
+using ::testing::StrictMock;
+
+namespace cobalt {
+namespace web {
+
+namespace {
+class URLTest : public ::testing::Test {
+ public:
+  URLTest() {}
+
+  StrictMock<MockExceptionState>* exception_state() {
+    return &exception_state_;
+  }
+
+ private:
+  StrictMock<MockExceptionState> exception_state_;
+};
+
+class URLTestWithJavaScript : public ::testing::Test {
+ public:
+  URLTestWithJavaScript() {}
+
+  bool EvaluateScript(const std::string& js_code, std::string* result) {
+    DCHECK(stub_window_.global_environment());
+    DCHECK(result);
+    scoped_refptr<script::SourceCode> source_code =
+        script::SourceCode::CreateSourceCode(
+            js_code, base::SourceLocation(__FILE__, __LINE__, 1));
+
+    stub_window_.global_environment()->EnableEval();
+    stub_window_.global_environment()->SetReportEvalCallback(base::Closure());
+    bool succeeded =
+        stub_window_.global_environment()->EvaluateScript(source_code, result);
+    return succeeded;
+  }
+
+  StrictMock<MockExceptionState>* exception_state() {
+    return &exception_state_;
+  }
+
+ private:
+  cobalt::dom::testing::StubWindow stub_window_;
+  StrictMock<MockExceptionState> exception_state_;
+};
+}  // namespace
+
+TEST_F(URLTest, ConstructorWithValidURL) {
+  scoped_refptr<script::ScriptException> exception;
+  EXPECT_CALL(*exception_state(), SetException(_)).Times(0);
+  scoped_refptr<URL> url =
+      new URL("https://user:password@www.example.com:1234/foo/bar?baz#qux",
+              "about:blank", exception_state());
+
+  EXPECT_EQ("https://user:password@www.example.com:1234/foo/bar?baz#qux",
+            url->href());
+  EXPECT_EQ("https:", url->protocol());
+  EXPECT_EQ("www.example.com:1234", url->host());
+  EXPECT_EQ("www.example.com", url->hostname());
+  EXPECT_EQ("1234", url->port());
+  EXPECT_EQ("/foo/bar", url->pathname());
+  EXPECT_EQ("#qux", url->hash());
+  EXPECT_EQ("?baz", url->search());
+}
+
+TEST_F(URLTest, ConstructorWithInvalidBase) {
+  EXPECT_CALL(*exception_state(),
+              SetSimpleExceptionVA(script::kTypeError, _, _));
+  scoped_refptr<URL> url =
+      new URL("https://www.example.com", "", exception_state());
+
+  EXPECT_TRUE(url->href().empty());
+}
+
+TEST_F(URLTest, ConstructorWithInvalidURL) {
+  EXPECT_CALL(*exception_state(),
+              SetSimpleExceptionVA(script::kTypeError, _, _));
+  scoped_refptr<URL> url = new URL("...:...", "about:blank", exception_state());
+
+  EXPECT_TRUE(url->href().empty());
+}
+
+TEST_F(URLTest, ConstructorWithValidURLAndBase) {
+  scoped_refptr<script::ScriptException> exception;
+  EXPECT_CALL(*exception_state(), SetException(_)).Times(0);
+  scoped_refptr<URL> url = new URL(
+      "quux", "https://user:password@www.example.com:1234/foo/bar?baz#qux",
+      exception_state());
+
+  EXPECT_EQ("https://user:password@www.example.com:1234/foo/quux", url->href());
+  EXPECT_EQ("https:", url->protocol());
+  EXPECT_EQ("www.example.com:1234", url->host());
+  EXPECT_EQ("www.example.com", url->hostname());
+  EXPECT_EQ("1234", url->port());
+  EXPECT_EQ("/foo/quux", url->pathname());
+  EXPECT_EQ("", url->hash());
+  EXPECT_EQ("", url->search());
+}
+
+TEST_F(URLTestWithJavaScript, ConstructorWithValidURL) {
+  std::string result;
+  bool success = EvaluateScript(
+      "var url = new "
+      "URL('https://user:password@www.example.com:1234/foo/bar?baz#qux');"
+      "if (url.href == "
+      "'https://user:password@www.example.com:1234/foo/bar?baz#qux' &&"
+      "    url.protocol == 'https:' &&"
+      "    url.host == 'www.example.com:1234' &&"
+      "    url.hostname == 'www.example.com' &&"
+      "    url.port == '1234' &&"
+      "    url.pathname == '/foo/bar' &&"
+      "    url.hash == '#qux' &&"
+      "    url.search == '?baz') "
+      "    url.href;",
+      &result);
+  EXPECT_TRUE(success);
+  EXPECT_EQ("https://user:password@www.example.com:1234/foo/bar?baz#qux",
+            result);
+
+  if (success) {
+    LOG(INFO) << "Test result : "
+              << "\"" << result << "\"";
+  } else {
+    DLOG(ERROR) << "Failed to evaluate test: "
+                << "\"" << result << "\"";
+  }
+}
+
+TEST_F(URLTestWithJavaScript, ConstructorWithInvalidBase) {
+  std::string result;
+  bool success = EvaluateScript(
+      "let result = 'unknown';"
+      "try {"
+      "  var url = new URL('https://www.example.com', '');"
+      "} catch (e) {"
+      "  result = e.name;"
+      "}"
+      "if (!url) result;",
+      &result);
+  EXPECT_TRUE(success);
+  EXPECT_EQ("TypeError", result);
+
+  if (success) {
+    LOG(INFO) << "Test result : "
+              << "\"" << result << "\"";
+  } else {
+    DLOG(ERROR) << "Failed to evaluate test: "
+                << "\"" << result << "\"";
+  }
+}
+
+TEST_F(URLTestWithJavaScript, ConstructorWithInvalidURL) {
+  std::string result;
+  bool success = EvaluateScript(
+      "let result = 'unknown';"
+      "try {"
+      "  var url = new URL('...:...');"
+      "} catch (e) {"
+      "  result = e.name;"
+      "}"
+      "if (!url) result;",
+      &result);
+  EXPECT_TRUE(success);
+  EXPECT_EQ("TypeError", result);
+
+  if (success) {
+    LOG(INFO) << "Test result : "
+              << "\"" << result << "\"";
+  } else {
+    DLOG(ERROR) << "Failed to evaluate test: "
+                << "\"" << result << "\"";
+  }
+}
+
+}  // namespace web
+}  // namespace cobalt
diff --git a/cobalt/dom/url_utils.cc b/cobalt/web/url_utils.cc
similarity index 94%
rename from cobalt/dom/url_utils.cc
rename to cobalt/web/url_utils.cc
index 0cf8dfa..462c6f4 100644
--- a/cobalt/dom/url_utils.cc
+++ b/cobalt/web/url_utils.cc
@@ -12,14 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/url_utils.h"
+#include "cobalt/web/url_utils.h"
+
+#include <string>
+
+#include "cobalt/loader/origin.h"
+#include "url/gurl.h"
 
 namespace cobalt {
-namespace dom {
-
-URLUtils::URLUtils(const GURL& url) : url_(url) {}
-URLUtils::URLUtils(const GURL& url, const UpdateStepsCallback& update_steps)
-    : url_(url), update_steps_(update_steps) {}
+namespace web {
 
 std::string URLUtils::href() const { return url_.possibly_invalid_spec(); }
 
@@ -145,5 +146,5 @@
   return loader::Origin(url_);
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/url_utils.h b/cobalt/web/url_utils.h
similarity index 88%
rename from cobalt/dom/url_utils.h
rename to cobalt/web/url_utils.h
index 610f906..09d7fdd 100644
--- a/cobalt/dom/url_utils.h
+++ b/cobalt/web/url_utils.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_URL_UTILS_H_
-#define COBALT_DOM_URL_UTILS_H_
+#ifndef COBALT_WEB_URL_UTILS_H_
+#define COBALT_WEB_URL_UTILS_H_
 
 #include <string>
 
@@ -22,7 +22,7 @@
 #include "url/gurl.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // Utility methods and properties that help work with URL.
 //   https://www.w3.org/TR/2014/WD-url-1-20141209/#dom-urlutils
@@ -38,10 +38,10 @@
  public:
   typedef base::Callback<void(const std::string&)> UpdateStepsCallback;
 
-  explicit URLUtils(const GURL& url);
+  URLUtils() {}
+  explicit URLUtils(const GURL& url) : url_(url) {}
   explicit URLUtils(const UpdateStepsCallback& update_steps)
       : update_steps_(update_steps) {}
-  URLUtils(const GURL& url, const UpdateStepsCallback& update_steps);
 
   // From the spec: URLUtils.
   //
@@ -76,6 +76,10 @@
   const GURL& url() const { return url_; }
   void set_url(const GURL& url) { url_ = url; }
 
+  void set_update_steps_callback(const UpdateStepsCallback& update_steps) {
+    update_steps_ = update_steps;
+  }
+
   loader::Origin GetOriginAsObject() const;
 
  private:
@@ -86,7 +90,7 @@
   UpdateStepsCallback update_steps_;
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_URL_UTILS_H_
+#endif  // COBALT_WEB_URL_UTILS_H_
diff --git a/cobalt/dom/url_utils.idl b/cobalt/web/url_utils.idl
similarity index 100%
rename from cobalt/dom/url_utils.idl
rename to cobalt/web/url_utils.idl
diff --git a/cobalt/dom/url_utils_test.cc b/cobalt/web/url_utils_test.cc
similarity index 98%
rename from cobalt/dom/url_utils_test.cc
rename to cobalt/web/url_utils_test.cc
index fbbaf2d..b0e75b9 100644
--- a/cobalt/dom/url_utils_test.cc
+++ b/cobalt/web/url_utils_test.cc
@@ -12,13 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/dom/url_utils.h"
+#include "cobalt/web/url_utils.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 TEST(URLUtilsTest, GettersShouldReturnExpectedFormat) {
   URLUtils url_utils(GURL("https://user:pass@google.com:99/foo;bar?q=a#ref"));
@@ -110,5 +110,5 @@
             url_utils.href());
 }
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/dom/user_agent_platform_info.h b/cobalt/web/user_agent_platform_info.h
similarity index 92%
rename from cobalt/dom/user_agent_platform_info.h
rename to cobalt/web/user_agent_platform_info.h
index 2b288b5..b1300de 100644
--- a/cobalt/dom/user_agent_platform_info.h
+++ b/cobalt/web/user_agent_platform_info.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_DOM_USER_AGENT_PLATFORM_INFO_H_
-#define COBALT_DOM_USER_AGENT_PLATFORM_INFO_H_
+#ifndef COBALT_WEB_USER_AGENT_PLATFORM_INFO_H_
+#define COBALT_WEB_USER_AGENT_PLATFORM_INFO_H_
 
 #include <string>
 
@@ -21,7 +21,7 @@
 #include "starboard/system.h"
 
 namespace cobalt {
-namespace dom {
+namespace web {
 
 // This class gives the DOM an interface to the user agent platform info. This
 // allows NavigatorUAData to access user agent string info.
@@ -55,7 +55,7 @@
   UserAgentPlatformInfo() {}
 };
 
-}  // namespace dom
+}  // namespace web
 }  // namespace cobalt
 
-#endif  // COBALT_DOM_USER_AGENT_PLATFORM_INFO_H_
+#endif  // COBALT_WEB_USER_AGENT_PLATFORM_INFO_H_
diff --git a/cobalt/web/window_or_worker_global_scope.cc b/cobalt/web/window_or_worker_global_scope.cc
new file mode 100644
index 0000000..5f43865
--- /dev/null
+++ b/cobalt/web/window_or_worker_global_scope.cc
@@ -0,0 +1,51 @@
+// Copyright 2022 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/web/window_or_worker_global_scope.h"
+
+#include "cobalt/script/environment_settings.h"
+#include "cobalt/web/event_target.h"
+
+namespace cobalt {
+namespace dom {
+class Window;
+}  // namespace dom
+namespace worker {
+class DedicatedWorkerGlobalScope;
+class ServiceWorkerGlobalScope;
+}  // namespace worker
+namespace web {
+WindowOrWorkerGlobalScope::WindowOrWorkerGlobalScope(
+    script::EnvironmentSettings* settings)
+    // Global scope object EventTargets require special handling for onerror
+    // events, see EventTarget constructor for more details.
+    : EventTarget(settings, kUnpackOnErrorEvents) {}
+
+bool WindowOrWorkerGlobalScope::IsWindow() {
+  return GetWrappableType() == base::GetTypeId<dom::Window>();
+}
+
+bool WindowOrWorkerGlobalScope::IsDedicatedWorker() {
+  return GetWrappableType() ==
+         base::GetTypeId<worker::DedicatedWorkerGlobalScope>();
+}
+
+bool WindowOrWorkerGlobalScope::IsServiceWorker() {
+  return GetWrappableType() ==
+         base::GetTypeId<worker::ServiceWorkerGlobalScope>();
+}
+
+
+}  // namespace web
+}  // namespace cobalt
diff --git a/cobalt/web/window_or_worker_global_scope.h b/cobalt/web/window_or_worker_global_scope.h
new file mode 100644
index 0000000..ecd21fe
--- /dev/null
+++ b/cobalt/web/window_or_worker_global_scope.h
@@ -0,0 +1,54 @@
+// Copyright 2022 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_WEB_WINDOW_OR_WORKER_GLOBAL_SCOPE_H_
+#define COBALT_WEB_WINDOW_OR_WORKER_GLOBAL_SCOPE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "cobalt/script/environment_settings.h"
+#include "cobalt/web/event_target.h"
+#include "cobalt/web/event_target_listener_info.h"
+
+namespace cobalt {
+namespace web {
+
+// Implementation of the logic common to both Window WorkerGlobalScope
+// interfaces.
+class WindowOrWorkerGlobalScope : public EventTarget {
+ public:
+  explicit WindowOrWorkerGlobalScope(script::EnvironmentSettings* settings);
+  WindowOrWorkerGlobalScope(const WindowOrWorkerGlobalScope&) = delete;
+  WindowOrWorkerGlobalScope& operator=(const WindowOrWorkerGlobalScope&) =
+      delete;
+
+  DEFINE_WRAPPABLE_TYPE(WindowOrWorkerGlobalScope);
+
+  bool IsWindow();
+  bool IsDedicatedWorker();
+  bool IsServiceWorker();
+
+ protected:
+  virtual ~WindowOrWorkerGlobalScope() {}
+
+ private:
+};
+
+}  // namespace web
+}  // namespace cobalt
+
+#endif  // COBALT_WEB_WINDOW_OR_WORKER_GLOBAL_SCOPE_H_
diff --git a/cobalt/webdriver/BUILD.gn b/cobalt/webdriver/BUILD.gn
index f5aee54..abb1b08 100644
--- a/cobalt/webdriver/BUILD.gn
+++ b/cobalt/webdriver/BUILD.gn
@@ -145,6 +145,7 @@
     "//cobalt/dom/testing:dom_testing",
     "//cobalt/script",
     "//cobalt/test:run_all_unittests",
+    "//cobalt/web/testing:web_testing",
     "//testing/gmock",
     "//testing/gtest",
     "//third_party/devtools:devtools_all_files",
diff --git a/cobalt/webdriver/web_driver_module.cc b/cobalt/webdriver/web_driver_module.cc
index b34860d..3a4d360 100644
--- a/cobalt/webdriver/web_driver_module.cc
+++ b/cobalt/webdriver/web_driver_module.cc
@@ -16,6 +16,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/base64.h"
diff --git a/cobalt/websocket/BUILD.gn b/cobalt/websocket/BUILD.gn
index 28a2a84..0758f2d 100644
--- a/cobalt/websocket/BUILD.gn
+++ b/cobalt/websocket/BUILD.gn
@@ -31,9 +31,9 @@
     "//cobalt/base",
     "//cobalt/browser:generated_type_conversion",
     "//cobalt/dom",
-    "//cobalt/dom:dom_exception",
     "//cobalt/network",
     "//cobalt/script",
+    "//cobalt/web:dom_exception",
     "//net",
     "//starboard",
     "//third_party/protobuf:protobuf_lite",
@@ -53,11 +53,12 @@
     ":websocket",
     "//cobalt/base",
     "//cobalt/dom",
-    "//cobalt/dom:dom_exception",
     "//cobalt/dom/testing:dom_testing",
     "//cobalt/network",
     "//cobalt/script",
     "//cobalt/test:run_all_unittests",
+    "//cobalt/web:dom_exception",
+    "//cobalt/web/testing:web_testing",
     "//testing/gmock",
     "//testing/gtest",
     "//url",
diff --git a/cobalt/websocket/close_event.h b/cobalt/websocket/close_event.h
index d715a8f..cc638cb 100644
--- a/cobalt/websocket/close_event.h
+++ b/cobalt/websocket/close_event.h
@@ -17,14 +17,14 @@
 #include <string>
 
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/event.h"
+#include "cobalt/web/event.h"
 #include "cobalt/websocket/close_event_init.h"
 #include "net/websockets/websocket_errors.h"
 
 namespace cobalt {
 namespace websocket {
 
-class CloseEvent : public dom::Event {
+class CloseEvent : public web::Event {
  public:
   explicit CloseEvent(const base::Token type)
       : Event(type), was_clean_(true), code_(net::kWebSocketNormalClosure) {}
diff --git a/cobalt/websocket/web_socket.cc b/cobalt/websocket/web_socket.cc
index 768a77b..dd1aff3 100644
--- a/cobalt/websocket/web_socket.cc
+++ b/cobalt/websocket/web_socket.cc
@@ -172,7 +172,7 @@
 WebSocket::WebSocket(script::EnvironmentSettings* settings,
                      const std::string& url,
                      script::ExceptionState* exception_state)
-    : dom::EventTarget(settings), require_network_module_(true) {
+    : web::EventTarget(settings), require_network_module_(true) {
   const std::vector<std::string> empty{};
   Initialize(settings, url, empty, exception_state);
 }
@@ -181,7 +181,7 @@
                      const std::string& url,
                      const std::vector<std::string>& sub_protocols,
                      script::ExceptionState* exception_state)
-    : dom::EventTarget(settings), require_network_module_(true) {
+    : web::EventTarget(settings), require_network_module_(true) {
   Initialize(settings, url, sub_protocols, exception_state);
 }
 
@@ -194,7 +194,7 @@
 std::string WebSocket::binary_type(script::ExceptionState* exception_state) {
   if (!IsValidBinaryType(binary_type_)) {
     NOTREACHED() << "Invalid binary_type_";
-    dom::DOMException::Raise(dom::DOMException::kNone, exception_state);
+    web::DOMException::Raise(web::DOMException::kNone, exception_state);
     return std::string();
   }
   return dom::MessageEvent::GetResponseTypeAsString(binary_type_);
@@ -205,7 +205,7 @@
                      const std::string& url,
                      const std::string& sub_protocol_list,
                      script::ExceptionState* exception_state)
-    : dom::EventTarget(settings), require_network_module_(true) {
+    : web::EventTarget(settings), require_network_module_(true) {
   std::vector<std::string> sub_protocols =
       base::SplitString(sub_protocol_list, kComma, base::KEEP_WHITESPACE,
                         base::SPLIT_WANT_NONEMPTY);
@@ -223,7 +223,7 @@
   dom::MessageEvent::ResponseTypeCode response_code =
       dom::MessageEvent::GetResponseTypeCode(binary_type_string_piece);
   if (!IsValidBinaryType(response_code)) {
-    dom::DOMException::Raise(dom::DOMException::kSyntaxErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, exception_state);
   } else {
     binary_type_ = response_code;
   }
@@ -258,13 +258,13 @@
     DLOG(ERROR) << "Reason specified in WebSocket::Close must be less than "
                 << kMaxControlPayloadSizeInBytes << " bytes.";
 
-    dom::DOMException::Raise(dom::DOMException::kSyntaxErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, exception_state);
     return;
   }
 
   if (net::WebSocketErrorToNetError(static_cast<net::WebSocketError>(code)) ==
       net::ERR_UNEXPECTED) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidAccessErr,
+    web::DOMException::Raise(web::DOMException::kInvalidAccessErr,
                              exception_state);
     return;
   }
@@ -294,7 +294,7 @@
   // "If the readyState attribute is CONNECTING, it must throw an
   // InvalidStateError exception"
   if (ready_state() == kConnecting) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              "readyState is not in CONNECTING state",
                              exception_state);
     return false;
@@ -320,14 +320,14 @@
 }
 
 // Implements spec at https://www.w3.org/TR/websockets/#dom-websocket-send.
-void WebSocket::Send(const scoped_refptr<dom::Blob>& data,
+void WebSocket::Send(const scoped_refptr<web::Blob>& data,
                      script::ExceptionState* exception_state) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(impl_);
   if (!CheckReadyState(exception_state)) {
     return;
   }
-  dom::Blob* blob(data.get());
+  web::Blob* blob(data.get());
   if (!blob) {
     return;
   }
@@ -401,7 +401,7 @@
              << "]";
   protocol_ = selected_subprotocol;
   SetReadyState(kOpen);
-  this->DispatchEvent(new dom::Event(base::Tokens::open()));
+  this->DispatchEvent(new web::Event(base::Tokens::open()));
 }
 
 void WebSocket::OnDisconnected(bool was_clean, uint16 code,
@@ -441,8 +441,8 @@
 
   settings_ = base::polymorphic_downcast<web::EnvironmentSettings*>(settings);
   if (!settings_) {
-    dom::DOMException::Raise(
-        dom::DOMException::kNone,
+    web::DOMException::Raise(
+        web::DOMException::kNone,
         "Internal error: Unable to get Web Environment Settings.",
         exception_state);
     NOTREACHED() << "Unable to get DOM settings.";
@@ -450,7 +450,7 @@
   }
 
   if (require_network_module_ && !settings_->context()->network_module()) {
-    dom::DOMException::Raise(dom::DOMException::kNone,
+    web::DOMException::Raise(web::DOMException::kNone,
                              "Internal error: Unable to get network module.",
                              exception_state);
     NOTREACHED() << "Unable to get network module.";
@@ -458,8 +458,8 @@
   }
 
   if (!settings_->base_url().is_valid()) {
-    dom::DOMException::Raise(
-        dom::DOMException::kNone,
+    web::DOMException::Raise(
+        web::DOMException::kNone,
         "Internal error: base_url (the url of the entry script) must be valid.",
         exception_state);
     return;
@@ -477,13 +477,13 @@
 
   resolved_url_ = settings_->base_url().Resolve(url);
   if (resolved_url_.is_empty()) {
-    dom::DOMException::Raise(dom::DOMException::kSyntaxErr, "url is empty",
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, "url is empty",
                              exception_state);
     return;
   }
 
   if (!resolved_url_.is_valid()) {
-    dom::DOMException::Raise(dom::DOMException::kSyntaxErr, "url is invalid",
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, "url is invalid",
                              exception_state);
     return;
   }
@@ -493,7 +493,7 @@
   if (!is_absolute) {
     std::string error_message = "Only relative URLs are supported.  [" + url +
                                 "] is not an absolute URL.";
-    dom::DOMException::Raise(dom::DOMException::kSyntaxErr, error_message,
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, error_message,
                              exception_state);
     return;
   }
@@ -509,7 +509,7 @@
                                 "].  Only " + std::string(url::kWsScheme) +
                                 ", and " + std::string(url::kWssScheme) +
                                 " schemes are supported.";
-    dom::DOMException::Raise(dom::DOMException::kSyntaxErr, error_message,
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, error_message,
                              exception_state);
     return;
   }
@@ -520,22 +520,22 @@
     std::string error_message =
         "URL has a fragment '" + fragment +
         "'.  Fragments are not are supported in websocket URLs.";
-    dom::DOMException::Raise(dom::DOMException::kSyntaxErr, error_message,
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, error_message,
                              exception_state);
     return;
   }
 
-  dom::CspDelegate* csp = csp_delegate();
+  web::CspDelegate* csp = csp_delegate();
   if (csp &&
-      !csp->CanLoad(dom::CspDelegate::kWebSocket, resolved_url_, false)) {
-    dom::DOMException::Raise(dom::DOMException::kSecurityErr, exception_state);
+      !csp->CanLoad(web::CspDelegate::kWebSocket, resolved_url_, false)) {
+    web::DOMException::Raise(web::DOMException::kSecurityErr, exception_state);
     return;
   }
 
   if (!net::IsPortAllowedForScheme(GetPort(), resolved_url_.scheme())) {
     std::string error_message = "Connecting to port " + GetPortAsString() +
                                 " using websockets is not allowed.";
-    dom::DOMException::Raise(dom::DOMException::kSecurityErr, error_message,
+    web::DOMException::Raise(web::DOMException::kSecurityErr, error_message,
                              exception_state);
     return;
   }
@@ -546,14 +546,14 @@
                                 "].  Subprotocols' characters must be in valid "
                                 "range and not have separating characters.  "
                                 "See RFC 2616 for details.";
-    dom::DOMException::Raise(dom::DOMException::kSyntaxErr, error_message,
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, error_message,
                              exception_state);
     return;
   }
 
   if (!AreSubProtocolsUnique(sub_protocols)) {
     std::string error_message = "Subprotocol values must be unique.";
-    dom::DOMException::Raise(dom::DOMException::kSyntaxErr, error_message,
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, error_message,
                              exception_state);
     return;
   }
@@ -561,7 +561,7 @@
   Connect(resolved_url_, sub_protocols);
 }
 
-dom::CspDelegate* WebSocket::csp_delegate() const {
+web::CspDelegate* WebSocket::csp_delegate() const {
   DCHECK(settings_);
   if (!settings_) {
     return NULL;
@@ -595,7 +595,7 @@
                      const std::string& url,
                      script::ExceptionState* exception_state,
                      const bool require_network_module)
-    : dom::EventTarget(settings),
+    : web::EventTarget(settings),
       require_network_module_(require_network_module) {
   const std::vector<std::string> empty{};
   Initialize(settings, url, empty, exception_state);
@@ -605,7 +605,7 @@
                      const std::string& url, const std::string& sub_protocol,
                      script::ExceptionState* exception_state,
                      const bool require_network_module)
-    : dom::EventTarget(settings),
+    : web::EventTarget(settings),
       require_network_module_(require_network_module) {
   std::vector<std::string> sub_protocols;
   sub_protocols.push_back(sub_protocol);
@@ -617,7 +617,7 @@
                      const std::vector<std::string>& sub_protocols,
                      script::ExceptionState* exception_state,
                      const bool require_network_module)
-    : dom::EventTarget(settings),
+    : web::EventTarget(settings),
       require_network_module_(require_network_module) {
   Initialize(settings, url, sub_protocols, exception_state);
 }
diff --git a/cobalt/websocket/web_socket.h b/cobalt/websocket/web_socket.h
index 70940c8..2733087 100644
--- a/cobalt/websocket/web_socket.h
+++ b/cobalt/websocket/web_socket.h
@@ -24,17 +24,17 @@
 #include "base/optional.h"
 #include "cobalt/base/compiler.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/blob.h"
-#include "cobalt/dom/csp_delegate.h"
-#include "cobalt/dom/dom_exception.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/dom/message_event.h"
 #include "cobalt/script/array_buffer.h"
 #include "cobalt/script/array_buffer_view.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/blob.h"
+#include "cobalt/web/csp_delegate.h"
+#include "cobalt/web/dom_exception.h"
 #include "cobalt/web/environment_settings.h"
+#include "cobalt/web/event_target.h"
 #include "cobalt/websocket/web_socket_impl.h"
 
 namespace cobalt {
@@ -43,7 +43,7 @@
 // This class represents a WebSocket.  It abides by RFC 6455 "The WebSocket
 // Protocol", and implements the The WebSocket API spec at
 // https://www.w3.org/TR/websockets/ (as of Jan 2017).
-class WebSocket : public dom::EventTarget {
+class WebSocket : public web::EventTarget {
  public:
   // Constants.
   static const uint16 kConnecting = 0;
@@ -84,7 +84,7 @@
              script::ExceptionState* exception_state);
 
   void Send(const std::string& data, script::ExceptionState* exception_state);
-  void Send(const scoped_refptr<dom::Blob>& data,
+  void Send(const scoped_refptr<web::Blob>& data,
             script::ExceptionState* exception_state);
   void Send(const script::Handle<script::ArrayBuffer>& data,
             script::ExceptionState* exception_state);
@@ -101,7 +101,7 @@
                       scoped_refptr<net::IOBufferWithSize> data);
   void OnWriteDone(uint64_t bytes_written);
 
-  void OnError() { this->DispatchEvent(new dom::Event(base::Tokens::error())); }
+  void OnError() { this->DispatchEvent(new web::Event(base::Tokens::error())); }
 
   // EventHandlers.
   const EventListenerScriptValue* onclose() const {
@@ -146,7 +146,7 @@
   int GetPort() const { return resolved_url_.EffectiveIntPort(); }
   std::string GetPortAsString() const;
 
-  dom::CspDelegate* csp_delegate() const;
+  web::CspDelegate* csp_delegate() const;
 
   DEFINE_WRAPPABLE_TYPE(WebSocket)
 
diff --git a/cobalt/websocket/web_socket_impl_test.cc b/cobalt/websocket/web_socket_impl_test.cc
index f6477af..646ac8e 100644
--- a/cobalt/websocket/web_socket_impl_test.cc
+++ b/cobalt/websocket/web_socket_impl_test.cc
@@ -21,7 +21,6 @@
 #include "base/memory/ref_counted.h"
 #include "base/test/scoped_task_environment.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
 #include "cobalt/dom/window.h"
@@ -29,7 +28,8 @@
 #include "cobalt/script/script_exception.h"
 #include "cobalt/script/testing/mock_exception_state.h"
 #include "cobalt/web/context.h"
-#include "cobalt/web/stub_web_context.h"
+#include "cobalt/web/dom_exception.h"
+#include "cobalt/web/testing/stub_web_context.h"
 #include "cobalt/websocket/mock_websocket_channel.h"
 #include "cobalt/websocket/web_socket.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -64,7 +64,7 @@
   }
 
  protected:
-  WebSocketImplTest() : web_context_(new web::test::StubWebContext()) {
+  WebSocketImplTest() : web_context_(new web::testing::StubWebContext()) {
     web_context_->set_network_module(new network::NetworkModule());
     web_context_->setup_environment_settings(
         new dom::testing::StubEnvironmentSettings());
@@ -110,7 +110,7 @@
 
   base::test::ScopedTaskEnvironment env_;
 
-  std::unique_ptr<web::test::StubWebContext> web_context_;
+  std::unique_ptr<web::testing::StubWebContext> web_context_;
   scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
   scoped_refptr<WebSocketImpl> websocket_impl_;
   MockWebSocketChannel* mock_channel_;
diff --git a/cobalt/websocket/web_socket_test.cc b/cobalt/websocket/web_socket_test.cc
index a130bee..efd6cb9 100644
--- a/cobalt/websocket/web_socket_test.cc
+++ b/cobalt/websocket/web_socket_test.cc
@@ -20,13 +20,13 @@
 #include "base/memory/ref_counted.h"
 #include "base/test/scoped_task_environment.h"
 #include "cobalt/base/polymorphic_downcast.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/script/script_exception.h"
 #include "cobalt/script/testing/mock_exception_state.h"
 #include "cobalt/web/context.h"
-#include "cobalt/web/stub_web_context.h"
+#include "cobalt/web/dom_exception.h"
+#include "cobalt/web/testing/stub_web_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using ::testing::_;
@@ -44,7 +44,7 @@
   }
 
  protected:
-  WebSocketTest() : web_context_(new web::test::StubWebContext()) {
+  WebSocketTest() : web_context_(new web::testing::StubWebContext()) {
     web_context_->set_network_module(new network::NetworkModule());
     web_context_->setup_environment_settings(
         new dom::testing::StubEnvironmentSettings());
@@ -54,7 +54,7 @@
 
   base::test::ScopedTaskEnvironment env_;
 
-  std::unique_ptr<web::test::StubWebContext> web_context_;
+  std::unique_ptr<web::testing::StubWebContext> web_context_;
   StrictMock<MockExceptionState> exception_state_;
 };
 
@@ -70,9 +70,9 @@
       new WebSocket(settings(), "ws://example.com", &exception_state_, false));
 
   ASSERT_TRUE(exception.get());
-  dom::DOMException& dom_exception(
-      *base::polymorphic_downcast<dom::DOMException*>(exception.get()));
-  EXPECT_EQ(dom::DOMException::kNone, dom_exception.code());
+  web::DOMException& dom_exception(
+      *base::polymorphic_downcast<web::DOMException*>(exception.get()));
+  EXPECT_EQ(web::DOMException::kNone, dom_exception.code());
   EXPECT_EQ(
       dom_exception.message(),
       "Internal error: base_url (the url of the entry script) must be valid.");
@@ -116,11 +116,11 @@
   scoped_refptr<WebSocket> ws(new WebSocket(
       settings(), "badscheme://example.com", &exception_state_, false));
 
-  dom::DOMException& dom_exception(
-      *base::polymorphic_downcast<dom::DOMException*>(exception.get()));
+  web::DOMException& dom_exception(
+      *base::polymorphic_downcast<web::DOMException*>(exception.get()));
 
   ASSERT_TRUE(exception.get());
-  EXPECT_EQ(dom::DOMException::kSyntaxErr, dom_exception.code());
+  EXPECT_EQ(web::DOMException::kSyntaxErr, dom_exception.code());
   EXPECT_EQ(
       dom_exception.message(),
       "Invalid scheme [badscheme].  Only ws, and wss schemes are supported.");
@@ -159,10 +159,10 @@
   scoped_refptr<WebSocket> ws(
       new WebSocket(settings(), "relative_url", &exception_state_, false));
 
-  dom::DOMException& dom_exception(
-      *base::polymorphic_downcast<dom::DOMException*>(exception.get()));
+  web::DOMException& dom_exception(
+      *base::polymorphic_downcast<web::DOMException*>(exception.get()));
   ASSERT_TRUE(exception.get());
-  EXPECT_EQ(dom::DOMException::kSyntaxErr, dom_exception.code());
+  EXPECT_EQ(web::DOMException::kSyntaxErr, dom_exception.code());
   EXPECT_EQ(dom_exception.message(),
             "Only relative URLs are supported.  [relative_url] is not an "
             "absolute URL.");
@@ -177,10 +177,10 @@
   scoped_refptr<WebSocket> ws(new WebSocket(
       settings(), "wss://example.com/#fragment", &exception_state_, false));
 
-  dom::DOMException& dom_exception(
-      *base::polymorphic_downcast<dom::DOMException*>(exception.get()));
+  web::DOMException& dom_exception(
+      *base::polymorphic_downcast<web::DOMException*>(exception.get()));
   ASSERT_TRUE(exception.get());
-  EXPECT_EQ(dom::DOMException::kSyntaxErr, dom_exception.code());
+  EXPECT_EQ(web::DOMException::kSyntaxErr, dom_exception.code());
   EXPECT_EQ(dom_exception.message(),
             "URL has a fragment 'fragment'.  Fragments are not are supported "
             "in websocket URLs.");
@@ -268,10 +268,10 @@
   scoped_refptr<WebSocket> ws(new WebSocket(settings(), "ws://example.com:22",
                                             &exception_state_, false));
 
-  dom::DOMException& dom_exception(
-      *base::polymorphic_downcast<dom::DOMException*>(exception.get()));
+  web::DOMException& dom_exception(
+      *base::polymorphic_downcast<web::DOMException*>(exception.get()));
   ASSERT_TRUE(exception.get());
-  EXPECT_EQ(dom::DOMException::kSecurityErr, dom_exception.code());
+  EXPECT_EQ(web::DOMException::kSecurityErr, dom_exception.code());
   EXPECT_EQ(dom_exception.message(),
             "Connecting to port 22 using websockets is not allowed.");
 }
@@ -280,19 +280,19 @@
   struct InvalidSubProtocolCase {
     const char* sub_protocol;
     bool exception_thrown;
-    dom::DOMException::ExceptionCode exception_code;
+    web::DOMException::ExceptionCode exception_code;
     const char* error_message;
   } invalid_subprotocol_cases[] = {
-      {"a,b", true, dom::DOMException::kSyntaxErr,
+      {"a,b", true, web::DOMException::kSyntaxErr,
        "Invalid subprotocol [a,b].  Subprotocols' characters must be in valid "
        "range and not have separating characters.  See RFC 2616 for details."},
-      {"a b", true, dom::DOMException::kSyntaxErr,
+      {"a b", true, web::DOMException::kSyntaxErr,
        "Invalid subprotocol [a b].  Subprotocols' characters must be in valid "
        "range and not have separating characters.  See RFC 2616 for details."},
-      {"? b", true, dom::DOMException::kSyntaxErr,
+      {"? b", true, web::DOMException::kSyntaxErr,
        "Invalid subprotocol [? b].  Subprotocols' characters must be in valid "
        "range and not have separating characters.  See RFC 2616 for details."},
-      {" b", true, dom::DOMException::kSyntaxErr,
+      {" b", true, web::DOMException::kSyntaxErr,
        "Invalid subprotocol [ b].  Subprotocols' characters must be in valid "
        "range and not have separating characters.  See RFC 2616 for details."},
   };
@@ -312,8 +312,8 @@
                                               &exception_state_, false));
 
     if (test_case.exception_thrown) {
-      dom::DOMException& dom_exception(
-          *base::polymorphic_downcast<dom::DOMException*>(exception.get()));
+      web::DOMException& dom_exception(
+          *base::polymorphic_downcast<web::DOMException*>(exception.get()));
       ASSERT_TRUE(exception.get());
       EXPECT_EQ(dom_exception.code(), test_case.exception_code);
       EXPECT_EQ(dom_exception.message(), test_case.error_message);
@@ -357,10 +357,10 @@
 
   ASSERT_TRUE(exception.get());
 
-  dom::DOMException& dom_exception(
-      *base::polymorphic_downcast<dom::DOMException*>(exception.get()));
+  web::DOMException& dom_exception(
+      *base::polymorphic_downcast<web::DOMException*>(exception.get()));
   ASSERT_TRUE(exception.get());
-  EXPECT_EQ(dom_exception.code(), dom::DOMException::kSyntaxErr);
+  EXPECT_EQ(dom_exception.code(), web::DOMException::kSyntaxErr);
   EXPECT_EQ(dom_exception.message(), "Subprotocol values must be unique.");
 }
 
diff --git a/cobalt/worker/BUILD.gn b/cobalt/worker/BUILD.gn
index 2cffcec..4c6aa3d 100644
--- a/cobalt/worker/BUILD.gn
+++ b/cobalt/worker/BUILD.gn
@@ -30,24 +30,65 @@
     "service_worker.h",
     "service_worker_container.cc",
     "service_worker_container.h",
+    "service_worker_global_scope.cc",
+    "service_worker_global_scope.h",
     "service_worker_jobs.cc",
     "service_worker_jobs.h",
+    "service_worker_object.cc",
+    "service_worker_object.h",
     "service_worker_registration.cc",
     "service_worker_registration.h",
+    "service_worker_registration_map.cc",
+    "service_worker_registration_map.h",
     "service_worker_registration_object.cc",
     "service_worker_registration_object.h",
     "worker.cc",
     "worker.h",
     "worker_global_scope.cc",
     "worker_global_scope.h",
+    "worker_location.h",
+    "worker_navigator.cc",
+    "worker_navigator.h",
     "worker_settings.cc",
     "worker_settings.h",
   ]
 
+  deps = [ "//url" ]
+
   public_deps = [
     # Additionally, ensure that the include directories for generated
     # headers are put on the include directories for targets that depend
     # on this one.
     "//cobalt/browser:generated_types",
+    "//cobalt/dom:window_timers",
+  ]
+}
+
+target(gtest_target_type, "worker_test") {
+  testonly = true
+  has_pedantic_warnings = true
+
+  sources = [ "worker_location_test.cc" ]
+
+  deps = [
+    ":worker",
+    "//cobalt/base",
+    "//cobalt/browser:browser",
+    "//cobalt/browser:generated_bindings",
+    "//cobalt/browser:generated_types",
+    "//cobalt/css_parser",
+    "//cobalt/cssom",
+    "//cobalt/dom/testing:dom_testing",
+    "//cobalt/dom_parser",
+    "//cobalt/loader",
+    "//cobalt/script",
+    "//cobalt/script/v8c:engine",
+    "//cobalt/test:run_all_unittests",
+    "//cobalt/web/testing:web_testing",
+    "//nb",
+    "//net:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//url",
   ]
 }
diff --git a/cobalt/worker/abstract_worker.h b/cobalt/worker/abstract_worker.h
index 0c53445..6c573fd 100644
--- a/cobalt/worker/abstract_worker.h
+++ b/cobalt/worker/abstract_worker.h
@@ -15,9 +15,9 @@
 #ifndef COBALT_WORKER_ABSTRACT_WORKER_H_
 #define COBALT_WORKER_ABSTRACT_WORKER_H_
 
-#include "cobalt/dom/event_target.h"
-#include "cobalt/dom/event_target_listener_info.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/web/event_target.h"
+#include "cobalt/web/event_target_listener_info.h"
 
 namespace cobalt {
 namespace worker {
@@ -27,10 +27,10 @@
 
 class AbstractWorker {
  public:
-  virtual const dom::EventTargetListenerInfo::EventListenerScriptValue*
+  virtual const web::EventTargetListenerInfo::EventListenerScriptValue*
   onerror() const = 0;
   virtual void set_onerror(
-      const dom::EventTargetListenerInfo::EventListenerScriptValue&
+      const web::EventTargetListenerInfo::EventListenerScriptValue&
           event_listener) = 0;
 
  protected:
diff --git a/cobalt/worker/dedicated_worker.cc b/cobalt/worker/dedicated_worker.cc
index 20e5ae3..316e2f9 100644
--- a/cobalt/worker/dedicated_worker.cc
+++ b/cobalt/worker/dedicated_worker.cc
@@ -18,8 +18,8 @@
 #include <string>
 
 #include "cobalt/browser/stack_size_constants.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/web/event_target.h"
 #include "cobalt/worker/message_port.h"
 #include "cobalt/worker/worker.h"
 #include "cobalt/worker/worker_options.h"
@@ -34,14 +34,14 @@
 
 DedicatedWorker::DedicatedWorker(script::EnvironmentSettings* settings,
                                  const std::string& scriptURL)
-    : EventTarget(settings), settings_(settings), script_url_(scriptURL) {
+    : web::EventTarget(settings), settings_(settings), script_url_(scriptURL) {
   Initialize();
 }
 
 DedicatedWorker::DedicatedWorker(script::EnvironmentSettings* settings,
                                  const std::string& scriptURL,
                                  const WorkerOptions& worker_options)
-    : EventTarget(settings),
+    : web::EventTarget(settings),
       settings_(settings),
       script_url_(scriptURL),
       worker_options_(worker_options) {
@@ -52,7 +52,8 @@
   // Algorithm for the Worker constructor.
   //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#dom-worker
 
-  // 1. The user agent may throw a "SecurityError" DOMException if the request
+  // 1. The user agent may throw a "SecurityError" web::DOMException if the
+  // request
   //    violates a policy decision (e.g. if the user agent is configured to
   //    not
   //    allow the page to start dedicated workers).
@@ -65,7 +66,7 @@
   LOG_IF(WARNING, !options.url.is_valid())
       << script_url_ << " cannot be resolved based on " << base_url << ".";
 
-  // 4. If this fails, throw a "SyntaxError" DOMException.
+  // 4. If this fails, throw a "SyntaxError" web::DOMException.
   // 5. Let worker URL be the resulting URL record.
   options.web_options.stack_size = cobalt::browser::kWorkerStackSize;
   options.web_options.network_module =
@@ -80,7 +81,8 @@
   // 9. Run this step in parallel:
   //    1. Run a worker given worker, worker URL, outside settings, outside
   //    port, and options.
-  options.outside_settings = settings_;
+  options.outside_settings =
+      base::polymorphic_downcast<web::EnvironmentSettings*>(settings_);
   options.outside_port = outside_port_.get();
   options.options = worker_options_;
 
diff --git a/cobalt/worker/dedicated_worker.h b/cobalt/worker/dedicated_worker.h
index f35fc34..fdc12aa 100644
--- a/cobalt/worker/dedicated_worker.h
+++ b/cobalt/worker/dedicated_worker.h
@@ -19,9 +19,9 @@
 #include <string>
 
 #include "base/memory/scoped_refptr.h"
-#include "cobalt/dom/event_target.h"
-#include "cobalt/dom/event_target_listener_info.h"
-#include "cobalt/script/environment_settings.h"
+#include "cobalt/web/environment_settings.h"
+#include "cobalt/web/event_target.h"
+#include "cobalt/web/event_target_listener_info.h"
 #include "cobalt/worker/abstract_worker.h"
 #include "cobalt/worker/message_port.h"
 #include "cobalt/worker/worker.h"
@@ -32,12 +32,14 @@
 
 // Implementation of Dedicated workers and the Worker interface.
 //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#dedicated-workers-and-the-worker-interface
-class DedicatedWorker : public AbstractWorker, public dom::EventTarget {
+class DedicatedWorker : public AbstractWorker, public web::EventTarget {
  public:
   DedicatedWorker(script::EnvironmentSettings* settings,
                   const std::string& scriptURL);
   DedicatedWorker(script::EnvironmentSettings* settings,
                   const std::string& scriptURL, const WorkerOptions& options);
+  DedicatedWorker(const DedicatedWorker&) = delete;
+  DedicatedWorker& operator=(const DedicatedWorker&) = delete;
 
   // Web API: Worker
   //
@@ -77,7 +79,6 @@
  private:
   ~DedicatedWorker() override;
   void Initialize();
-  DISALLOW_COPY_AND_ASSIGN(DedicatedWorker);
 
   script::EnvironmentSettings* settings_;
   const std::string script_url_;
diff --git a/cobalt/worker/dedicated_worker_global_scope.h b/cobalt/worker/dedicated_worker_global_scope.h
index 0252f27..e3eafe6 100644
--- a/cobalt/worker/dedicated_worker_global_scope.h
+++ b/cobalt/worker/dedicated_worker_global_scope.h
@@ -18,12 +18,12 @@
 #include <string>
 
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/event_target.h"
-#include "cobalt/dom/event_target_listener_info.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/sequence.h"
 #include "cobalt/script/value_handle.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event_target.h"
+#include "cobalt/web/event_target_listener_info.h"
 #include "cobalt/worker/worker_global_scope.h"
 
 namespace cobalt {
@@ -37,31 +37,34 @@
   explicit DedicatedWorkerGlobalScope(
       script::EnvironmentSettings* settings,
       bool parent_cross_origin_isolated_capability);
+  DedicatedWorkerGlobalScope(const DedicatedWorkerGlobalScope&) = delete;
+  DedicatedWorkerGlobalScope& operator=(const DedicatedWorkerGlobalScope&) =
+      delete;
+
+  void Initialize() override;
 
   // Web API: DedicatedWorkerGlobalScope
   //
   void set_name(const std::string& name) { name_ = name; }
   std::string name() { return name_; }
 
-  void Initialize() override;
-
   void PostMessage(const std::string& message);
   void Close() {}
 
-  const dom::EventTargetListenerInfo::EventListenerScriptValue* onmessage() {
+  const web::EventTargetListenerInfo::EventListenerScriptValue* onmessage() {
     return GetAttributeEventListener(base::Tokens::message());
   }
   void set_onmessage(
-      const dom::EventTargetListenerInfo::EventListenerScriptValue&
+      const web::EventTargetListenerInfo::EventListenerScriptValue&
           event_listener) {
     SetAttributeEventListener(base::Tokens::message(), event_listener);
   }
-  const dom::EventTargetListenerInfo::EventListenerScriptValue*
+  const web::EventTargetListenerInfo::EventListenerScriptValue*
   onmessageerror() {
     return GetAttributeEventListener(base::Tokens::messageerror());
   }
   void set_onmessageerror(
-      const dom::EventTargetListenerInfo::EventListenerScriptValue&
+      const web::EventTargetListenerInfo::EventListenerScriptValue&
           event_listener) {
     SetAttributeEventListener(base::Tokens::messageerror(), event_listener);
   }
@@ -72,7 +75,6 @@
   ~DedicatedWorkerGlobalScope() override {}
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(DedicatedWorkerGlobalScope);
   bool cross_origin_isolated_capability_;
 
   std::string name_;
diff --git a/cobalt/worker/message_port.cc b/cobalt/worker/message_port.cc
index 8f77b63..5940a9c 100644
--- a/cobalt/worker/message_port.cc
+++ b/cobalt/worker/message_port.cc
@@ -22,15 +22,15 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop.h"
-#include "cobalt/dom/event.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/dom/message_event.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/web/event.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace worker {
 
-MessagePort::MessagePort(dom::EventTarget* event_target,
+MessagePort::MessagePort(web::EventTarget* event_target,
                          script::EnvironmentSettings* settings)
     : event_target_(event_target),
       message_loop_(base::MessageLoop::current()),
diff --git a/cobalt/worker/message_port.h b/cobalt/worker/message_port.h
index 0fabc1b..128dc93 100644
--- a/cobalt/worker/message_port.h
+++ b/cobalt/worker/message_port.h
@@ -20,13 +20,13 @@
 #include "base/memory/weak_ptr.h"
 #include "base/message_loop/message_loop.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/event_target.h"
-#include "cobalt/dom/event_target_listener_info.h"
 #include "cobalt/dom/message_event.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/sequence.h"
 #include "cobalt/script/value_handle.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event_target.h"
+#include "cobalt/web/event_target_listener_info.h"
 
 namespace cobalt {
 namespace worker {
@@ -34,9 +34,11 @@
 class MessagePort : public script::Wrappable,
                     public base::SupportsWeakPtr<MessagePort> {
  public:
-  MessagePort(dom::EventTarget* event_target,
+  MessagePort(web::EventTarget* event_target,
               script::EnvironmentSettings* settings);
   ~MessagePort();
+  MessagePort(const MessagePort&) = delete;
+  MessagePort& operator=(const MessagePort&) = delete;
 
   // This may help for adding support of 'object'
   // void postMessage(any message, object transfer);
@@ -47,28 +49,28 @@
   void Start() {}
   void Close() {}
 
-  const dom::EventTargetListenerInfo::EventListenerScriptValue* onmessage()
+  const web::EventTargetListenerInfo::EventListenerScriptValue* onmessage()
       const {
     return event_target_ ? event_target_->GetAttributeEventListener(
                                base::Tokens::message())
                          : nullptr;
   }
   void set_onmessage(
-      const dom::EventTargetListenerInfo::EventListenerScriptValue&
+      const web::EventTargetListenerInfo::EventListenerScriptValue&
           event_listener) {
     if (event_target_)
       event_target_->SetAttributeEventListener(base::Tokens::message(),
                                                event_listener);
   }
 
-  const dom::EventTargetListenerInfo::EventListenerScriptValue* onmessageerror()
+  const web::EventTargetListenerInfo::EventListenerScriptValue* onmessageerror()
       const {
     return event_target_ ? event_target_->GetAttributeEventListener(
                                base::Tokens::messageerror())
                          : nullptr;
   }
   void set_onmessageerror(
-      const dom::EventTargetListenerInfo::EventListenerScriptValue&
+      const web::EventTargetListenerInfo::EventListenerScriptValue&
           event_listener) {
     if (event_target_)
       event_target_->SetAttributeEventListener(base::Tokens::messageerror(),
@@ -80,10 +82,8 @@
   DEFINE_WRAPPABLE_TYPE(MessagePort);
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(MessagePort);
-
   // The event target to dispatch events to.
-  dom::EventTarget* event_target_ = nullptr;
+  web::EventTarget* event_target_ = nullptr;
   // The message loop for posting event dispatches to.
   base::MessageLoop* message_loop_ = nullptr;
   // EnvironmentSettings of the event target.
diff --git a/cobalt/worker/navigation_preload_manager.cc b/cobalt/worker/navigation_preload_manager.cc
index d8ec15e..e29da18 100644
--- a/cobalt/worker/navigation_preload_manager.cc
+++ b/cobalt/worker/navigation_preload_manager.cc
@@ -16,7 +16,7 @@
 
 #include <vector>
 
-#include "cobalt/dom/dom_exception.h"
+#include "cobalt/web/dom_exception.h"
 #include "cobalt/worker/service_worker_registration.h"
 
 namespace cobalt {
@@ -42,7 +42,7 @@
       script_value_factory_->CreateBasicPromise<void>();
 
   if (registration_->active() == nullptr) {
-    promise->Reject(new dom::DOMException(dom::DOMException::kInvalidStateErr));
+    promise->Reject(new web::DOMException(web::DOMException::kInvalidStateErr));
     return promise;
   }
 
@@ -56,7 +56,7 @@
   script::Handle<script::Promise<void>> promise =
       script_value_factory_->CreateBasicPromise<void>();
   if (registration_->active() == nullptr) {
-    promise->Reject(new dom::DOMException(dom::DOMException::kInvalidStateErr));
+    promise->Reject(new web::DOMException(web::DOMException::kInvalidStateErr));
     return promise;
   }
   registration_->EnableNavigationPreload(enable);
diff --git a/cobalt/worker/service_worker.cc b/cobalt/worker/service_worker.cc
index 471d60a..3cc3e67 100644
--- a/cobalt/worker/service_worker.cc
+++ b/cobalt/worker/service_worker.cc
@@ -13,17 +13,22 @@
 // limitations under the License.
 
 #include "cobalt/worker/service_worker.h"
-#include "cobalt/worker/service_worker_object.h"
 
 #include <memory>
 #include <utility>
 
+#include "cobalt/script/environment_settings.h"
+#include "cobalt/worker/service_worker_object.h"
+#include "cobalt/worker/service_worker_state.h"
+
 namespace cobalt {
 namespace worker {
 
 ServiceWorker::ServiceWorker(script::EnvironmentSettings* settings,
                              worker::ServiceWorkerObject* worker)
-    : dom::EventTarget(settings), worker_(worker), state_(worker->state()) {}
+    : web::EventTarget(settings),
+      worker_(worker),
+      state_(kServiceWorkerStateParsed) {}
 
 }  // namespace worker
 }  // namespace cobalt
diff --git a/cobalt/worker/service_worker.h b/cobalt/worker/service_worker.h
index 8896c73..2085280 100644
--- a/cobalt/worker/service_worker.h
+++ b/cobalt/worker/service_worker.h
@@ -19,10 +19,10 @@
 #include <string>
 #include <utility>
 
-#include "cobalt/dom/event_target.h"
-#include "cobalt/dom/event_target_listener_info.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event_target.h"
+#include "cobalt/web/event_target_listener_info.h"
 #include "cobalt/worker/abstract_worker.h"
 #include "cobalt/worker/service_worker_object.h"
 #include "cobalt/worker/service_worker_state.h"
@@ -33,14 +33,19 @@
 // The ServiceWorker interface represents a service worker within a service
 // worker client realm.
 //   https://w3c.github.io/ServiceWorker/#serviceworker-interface
-class ServiceWorker : public AbstractWorker, public dom::EventTarget {
+class ServiceWorker : public AbstractWorker, public web::EventTarget {
  public:
   ServiceWorker(script::EnvironmentSettings* settings,
                 worker::ServiceWorkerObject* worker);
+  ServiceWorker(const ServiceWorker&) = delete;
+  ServiceWorker& operator=(const ServiceWorker&) = delete;
 
   // The scriptURL getter steps are to return the
   // service worker's serialized script url.
-  std::string script_url() const { return script_url_; }
+  std::string script_url() const { return worker_->script_url().spec(); }
+
+  // https://w3c.github.io/ServiceWorker/#dom-serviceworker-state
+  void set_state(ServiceWorkerState state) { state_ = state; }
   ServiceWorkerState state() const { return state_; }
 
   const EventListenerScriptValue* onstatechange() const {
@@ -63,11 +68,8 @@
 
  private:
   ~ServiceWorker() override = default;
-  DISALLOW_COPY_AND_ASSIGN(ServiceWorker);
 
   worker::ServiceWorkerObject* worker_;
-
-  const std::string script_url_;
   ServiceWorkerState state_;
 };
 
diff --git a/cobalt/worker/service_worker_container.cc b/cobalt/worker/service_worker_container.cc
index 22fe76f..c6e1417 100644
--- a/cobalt/worker/service_worker_container.cc
+++ b/cobalt/worker/service_worker_container.cc
@@ -17,12 +17,13 @@
 #include <string>
 #include <utility>
 
+#include "base/logging.h"
 #include "base/optional.h"
 #include "base/trace_event/trace_event.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/web/context.h"
+#include "cobalt/web/dom_exception.h"
 #include "cobalt/web/environment_settings.h"
 #include "cobalt/worker/registration_options.h"
 #include "cobalt/worker/service_worker_update_via_cache.h"
@@ -33,9 +34,8 @@
 namespace worker {
 
 ServiceWorkerContainer::ServiceWorkerContainer(
-    script::EnvironmentSettings* settings,
-    worker::ServiceWorkerJobs* service_worker_jobs)
-    : dom::EventTarget(settings) {}
+    script::EnvironmentSettings* settings)
+    : web::EventTarget(settings) {}
 
 // TODO: Implement the service worker ready algorithm. b/219972966
 script::Handle<script::PromiseWrappable> ServiceWorkerContainer::ready() {
@@ -72,6 +72,11 @@
 script::Handle<script::PromiseWrappable> ServiceWorkerContainer::Register(
     const std::string& url, const RegistrationOptions& options) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerContainer::Register()");
+  DCHECK_EQ(base::MessageLoop::current(),
+            base::polymorphic_downcast<web::EnvironmentSettings*>(
+                environment_settings())
+                ->context()
+                ->message_loop());
   // https://w3c.github.io/ServiceWorker/#navigator-service-worker-register
   // 1. Let p be a promise.
   script::HandlePromiseWrappable promise =
@@ -101,19 +106,12 @@
   }
   // 6. Invoke Start Register with scopeURL, scriptURL, p, client, client’s
   //    creation URL, options["type"], and options["updateViaCache"].
-  base::polymorphic_downcast<web::EnvironmentSettings*>(environment_settings())
-      ->context()
-      ->message_loop()
-      ->task_runner()
-      ->PostTask(
-          FROM_HERE,
-          base::BindOnce(
-              &ServiceWorkerJobs::StartRegister,
-              base::Unretained(base::polymorphic_downcast<dom::DOMSettings*>(
-                                   environment_settings())
-                                   ->service_worker_jobs()),
-              scope_url, script_url, std::move(promise_reference), client,
-              options.type(), options.update_via_cache()));
+  base::MessageLoop::current()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&ServiceWorkerJobs::StartRegister,
+                     base::Unretained(client->context()->service_worker_jobs()),
+                     scope_url, script_url, std::move(promise_reference),
+                     client, options.type(), options.update_via_cache()));
   // 7. Return p.
   return promise;
 }
@@ -121,17 +119,16 @@
 script::Handle<script::PromiseWrappable>
 ServiceWorkerContainer::GetRegistration(const std::string& url) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerContainer::GetRegistration()");
-  // https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistration
-  // 1. Let client be this's service worker client.
-  web::EnvironmentSettings* client =
-      base::polymorphic_downcast<web::EnvironmentSettings*>(
-          environment_settings());
-  // 2. Let clientURL be the result of parsing clientURL with this's relevant
-  //    settings object’s API base URL.
-  const GURL& base_url = environment_settings()->base_url();
-  GURL client_url = base_url.Resolve(url);
-
-  // 3. If clientURL is failure, return a promise rejected with a TypeError.
+  DCHECK_EQ(base::MessageLoop::current(),
+            base::polymorphic_downcast<web::EnvironmentSettings*>(
+                environment_settings())
+                ->context()
+                ->message_loop());
+  // Algorithm for 'ServiceWorkerContainer.getRegistration()':
+  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistration
+  // Let promise be a new promise.
+  // Perform the rest of the steps in a task, because the promise has to be
+  // returned before we can safely reject or resolve it.
   auto promise =
       base::polymorphic_downcast<web::EnvironmentSettings*>(
           environment_settings())
@@ -139,39 +136,70 @@
           ->global_environment()
           ->script_value_factory()
           ->CreateInterfacePromise<scoped_refptr<ServiceWorkerRegistration>>();
+  std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference(
+      new script::ValuePromiseWrappable::Reference(this, promise));
+  base::MessageLoop::current()->task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&ServiceWorkerContainer::GetRegistrationTask,
+                                base::Unretained(this), url,
+                                std::move(promise_reference)));
+  // 9. Return promise.
+  return promise;
+}
+
+void ServiceWorkerContainer::GetRegistrationTask(
+    const std::string& url,
+    std::unique_ptr<script::ValuePromiseWrappable::Reference>
+        promise_reference) {
+  TRACE_EVENT0("cobalt::worker",
+               "ServiceWorkerContainer::GetRegistrationTask()");
+  DCHECK_EQ(base::MessageLoop::current(),
+            base::polymorphic_downcast<web::EnvironmentSettings*>(
+                environment_settings())
+                ->context()
+                ->message_loop());
+  // Algorithm for 'ServiceWorkerContainer.getRegistration()':
+  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistration
+  // 1. Let client be this's service worker client.
+  web::EnvironmentSettings* client =
+      base::polymorphic_downcast<web::EnvironmentSettings*>(
+          environment_settings());
+
+  // 2. Let storage key be the result of running obtain a storage key given
+  //    client.
+  url::Origin storage_key = url::Origin::Create(client->creation_url());
+
+  // 3. Let clientURL be the result of parsing clientURL with this's relevant
+  //    settings object’s API base URL.
+  const GURL& base_url = environment_settings()->base_url();
+  GURL client_url = base_url.Resolve(url);
+
+  // 4. If clientURL is failure, return a promise rejected with a TypeError.
   if (client_url.is_empty()) {
-    promise->Reject(script::kTypeError);
-    return promise;
+    promise_reference->value().Reject(script::kTypeError);
+    return;
   }
 
-  // 4. Set clientURL’s fragment to null.
+  // 5. Set clientURL’s fragment to null.
   url::Replacements<char> replacements;
   replacements.ClearRef();
   client_url = client_url.ReplaceComponents(replacements);
   DCHECK(!client_url.has_ref() || client_url.ref().empty());
 
-  // 5. If the origin of clientURL is not client’s origin, return a promise
-  //    rejected with a "SecurityError" DOMException.
+  // 6. If the origin of clientURL is not client’s origin, return a promise
+  //    rejected with a "SecurityError" web::DOMException.
   if (client_url.GetOrigin() != base_url.GetOrigin()) {
-    promise->Reject(new dom::DOMException(dom::DOMException::kSecurityErr));
-    return promise;
+    promise_reference->value().Reject(
+        new web::DOMException(web::DOMException::kSecurityErr));
+    return;
   }
 
-  // 6. Let promise be a new promise.
-  // 7. Run the following substeps in parallel:
-  //    1. Let registration be the result of running Match Service Worker
-  //       Registration algorithm with clientURL as its argument.
-  // TODO handle parallelism, and add callback to resolve the promise.
-  base::polymorphic_downcast<dom::DOMSettings*>(environment_settings())
-      ->service_worker_jobs()
-      ->MatchServiceWorkerRegistration(client_url);
-  //    2. If registration is null, resolve promise with undefined and abort
-  //       these steps.
-  //    3. Resolve promise with the result of getting the service worker
-  //       registration object that represents registration in promise’s
-  //       relevant settings object.
-  // 8. Return promise.
-  return promise;
+  // 7. Let promise be a new promise.
+  // 8. Run the following substeps in parallel:
+  worker::ServiceWorkerJobs* jobs = client->context()->service_worker_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::Handle<script::PromiseSequenceWrappable>
diff --git a/cobalt/worker/service_worker_container.h b/cobalt/worker/service_worker_container.h
index 5092a96..b43c288 100644
--- a/cobalt/worker/service_worker_container.h
+++ b/cobalt/worker/service_worker_container.h
@@ -18,13 +18,13 @@
 #include <memory>
 #include <string>
 
-#include "cobalt/dom/event_target.h"
-#include "cobalt/dom/event_target_listener_info.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/script_value_factory.h"
 #include "cobalt/script/sequence.h"
 #include "cobalt/script/wrappable.h"
+#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_jobs.h"
 #include "cobalt/worker/service_worker_registration.h"
@@ -35,10 +35,9 @@
 // The ServiceWorkerContainer interface represents the interface to register and
 // access service workers from a service worker client realm.
 //   https://w3c.github.io/ServiceWorker/#serviceworkercontainer-interface
-class ServiceWorkerContainer : public dom::EventTarget {
+class ServiceWorkerContainer : public web::EventTarget {
  public:
-  ServiceWorkerContainer(script::EnvironmentSettings* settings,
-                         worker::ServiceWorkerJobs* service_worker_jobs);
+  explicit ServiceWorkerContainer(script::EnvironmentSettings* settings);
 
   scoped_refptr<ServiceWorker> controller() { return controller_; }
   script::Handle<script::PromiseWrappable> ready();
@@ -76,6 +75,11 @@
   DEFINE_WRAPPABLE_TYPE(ServiceWorkerContainer);
 
  private:
+  void GetRegistrationTask(
+      const std::string& url,
+      std::unique_ptr<script::ValuePromiseWrappable::Reference>
+          promise_reference);
+
   scoped_refptr<ServiceWorker> controller_;
   scoped_refptr<ServiceWorker> ready_;
 
diff --git a/cobalt/worker/service_worker_global_scope.cc b/cobalt/worker/service_worker_global_scope.cc
new file mode 100644
index 0000000..565aec4
--- /dev/null
+++ b/cobalt/worker/service_worker_global_scope.cc
@@ -0,0 +1,41 @@
+// Copyright 2022 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_global_scope.h"
+
+#include "base/logging.h"
+#include "cobalt/web/environment_settings.h"
+#include "cobalt/worker/worker_settings.h"
+
+namespace cobalt {
+namespace worker {
+ServiceWorkerGlobalScope::ServiceWorkerGlobalScope(
+    web::EnvironmentSettings* settings)
+    : WorkerGlobalScope(settings) {}
+
+void ServiceWorkerGlobalScope::Initialize() {}
+
+script::Handle<script::Promise<void>> ServiceWorkerGlobalScope::SkipWaiting() {
+  auto promise = base::polymorphic_downcast<web::EnvironmentSettings*>(
+                     environment_settings())
+                     ->context()
+                     ->global_environment()
+                     ->script_value_factory()
+                     ->CreateBasicPromise<void>();
+  NOTIMPLEMENTED();
+  return promise;
+}
+
+}  // namespace worker
+}  // namespace cobalt
diff --git a/cobalt/worker/service_worker_global_scope.h b/cobalt/worker/service_worker_global_scope.h
new file mode 100644
index 0000000..177fddd
--- /dev/null
+++ b/cobalt/worker/service_worker_global_scope.h
@@ -0,0 +1,115 @@
+// Copyright 2022 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_GLOBAL_SCOPE_H_
+#define COBALT_WORKER_SERVICE_WORKER_GLOBAL_SCOPE_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "cobalt/base/tokens.h"
+#include "cobalt/script/promise.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/script/wrappable.h"
+#include "cobalt/web/environment_settings.h"
+#include "cobalt/web/event_target.h"
+#include "cobalt/web/event_target_listener_info.h"
+#include "cobalt/worker/service_worker.h"
+#include "cobalt/worker/service_worker_registration.h"
+#include "cobalt/worker/worker_global_scope.h"
+
+namespace cobalt {
+namespace worker {
+
+// Implementation of Service Worker Global Scope.
+//   https://w3c.github.io/ServiceWorker/#serviceworkerglobalscope-interface
+
+class ServiceWorkerGlobalScope : public WorkerGlobalScope {
+ public:
+  explicit ServiceWorkerGlobalScope(web::EnvironmentSettings* settings);
+  ServiceWorkerGlobalScope(const ServiceWorkerGlobalScope&) = delete;
+  ServiceWorkerGlobalScope& operator=(const ServiceWorkerGlobalScope&) = delete;
+
+  void Initialize() override;
+
+  // Web API: ServiceWorkerGlobalScope
+  //
+  scoped_refptr<ServiceWorkerRegistration> registration() const {
+    return registration_;
+  }
+  scoped_refptr<ServiceWorker> service_worker() const {
+    return service_worker_;
+  }
+
+  script::Handle<script::Promise<void>> SkipWaiting();
+
+  const web::EventTargetListenerInfo::EventListenerScriptValue* oninstall() {
+    return GetAttributeEventListener(base::Tokens::install());
+  }
+  void set_oninstall(
+      const web::EventTargetListenerInfo::EventListenerScriptValue&
+          event_listener) {
+    SetAttributeEventListener(base::Tokens::install(), event_listener);
+  }
+
+  const web::EventTargetListenerInfo::EventListenerScriptValue* onactivate() {
+    return GetAttributeEventListener(base::Tokens::activate());
+  }
+  void set_onactivate(
+      const web::EventTargetListenerInfo::EventListenerScriptValue&
+          event_listener) {
+    SetAttributeEventListener(base::Tokens::activate(), event_listener);
+  }
+
+  const web::EventTargetListenerInfo::EventListenerScriptValue* onfetch() {
+    return GetAttributeEventListener(base::Tokens::fetch());
+  }
+  void set_onfetch(const web::EventTargetListenerInfo::EventListenerScriptValue&
+                       event_listener) {
+    SetAttributeEventListener(base::Tokens::fetch(), event_listener);
+  }
+
+  const web::EventTargetListenerInfo::EventListenerScriptValue* onmessage() {
+    return GetAttributeEventListener(base::Tokens::message());
+  }
+  void set_onmessage(
+      const web::EventTargetListenerInfo::EventListenerScriptValue&
+          event_listener) {
+    SetAttributeEventListener(base::Tokens::message(), event_listener);
+  }
+  const web::EventTargetListenerInfo::EventListenerScriptValue*
+  onmessageerror() {
+    return GetAttributeEventListener(base::Tokens::messageerror());
+  }
+  void set_onmessageerror(
+      const web::EventTargetListenerInfo::EventListenerScriptValue&
+          event_listener) {
+    SetAttributeEventListener(base::Tokens::messageerror(), event_listener);
+  }
+
+  DEFINE_WRAPPABLE_TYPE(ServiceWorkerGlobalScope);
+
+ protected:
+  ~ServiceWorkerGlobalScope() override {}
+
+ private:
+  scoped_refptr<ServiceWorkerRegistration> registration_;
+  scoped_refptr<ServiceWorker> service_worker_;
+};
+
+}  // namespace worker
+}  // namespace cobalt
+
+#endif  // COBALT_WORKER_SERVICE_WORKER_GLOBAL_SCOPE_H_
diff --git a/cobalt/worker/service_worker_global_scope.idl b/cobalt/worker/service_worker_global_scope.idl
new file mode 100644
index 0000000..58dd61d
--- /dev/null
+++ b/cobalt/worker/service_worker_global_scope.idl
@@ -0,0 +1,30 @@
+// Copyright 2022 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.
+
+// https://html.spec.whatwg.org/multipage/workers.html#dedicated-workers-and-the-dedicatedworkerglobalscope-interface
+
+[Global] interface ServiceWorkerGlobalScope : WorkerGlobalScope {
+  // readonly attribute Clients clients;
+  readonly attribute ServiceWorkerRegistration registration;
+  readonly attribute ServiceWorker serviceWorker;
+
+  Promise<void> skipWaiting();
+
+  attribute EventHandler onactivate;
+  attribute EventHandler onfetch;
+  attribute EventHandler oninstall;
+
+  attribute EventHandler onmessage;
+  attribute EventHandler onmessageerror;
+};
diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc
index 9c98b6f..771329f 100644
--- a/cobalt/worker/service_worker_jobs.cc
+++ b/cobalt/worker/service_worker_jobs.cc
@@ -14,6 +14,7 @@
 
 #include "cobalt/worker/service_worker_jobs.h"
 
+#include <list>
 #include <map>
 #include <memory>
 #include <queue>
@@ -21,21 +22,27 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/logging.h"
 #include "base/message_loop/message_loop.h"
 #include "base/synchronization/lock.h"
 #include "base/trace_event/trace_event.h"
-#include "cobalt/dom/dom_exception.h"
+#include "cobalt/base/tokens.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/worker/service_worker.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/worker_type.h"
 #include "net/base/url_util.h"
+#include "starboard/atomic.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -52,7 +59,7 @@
 }
 
 bool IsOriginPotentiallyTrustworthy(const GURL& url) {
-  // Algorithm for 'potentially trustworthy origin'.
+  // Algorithm for potentially trustworthy origin:
   //   https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-origin
 
   const url::Origin origin(url::Origin::Create(url));
@@ -92,22 +99,29 @@
   return false;
 }
 
-// Returns the serialized URL excluding the fragment.
-std::string SerializeExcludingFragment(const GURL& url) {
-  url::Replacements<char> replacements;
-  replacements.ClearRef();
-  GURL no_fragment_url = url.ReplaceComponents(replacements);
-  DCHECK(!no_fragment_url.has_ref() || no_fragment_url.ref().empty());
-  DCHECK(!no_fragment_url.is_empty());
-  return no_fragment_url.spec();
-}
+bool PermitAnyURL(const GURL&, bool) { return true; }
 }  // namespace
 
 ServiceWorkerJobs::ServiceWorkerJobs(network::NetworkModule* network_module,
                                      base::MessageLoop* message_loop)
-    : network_module_(network_module), message_loop_(message_loop) {}
+    : network_module_(network_module), message_loop_(message_loop) {
+  fetcher_factory_.reset(new loader::FetcherFactory(network_module));
+  DCHECK(fetcher_factory_);
 
-ServiceWorkerJobs::~ServiceWorkerJobs() {}
+  script_loader_factory_.reset(new loader::ScriptLoaderFactory(
+      "ServiceWorkerJobs", fetcher_factory_.get()));
+  DCHECK(script_loader_factory_);
+}
+
+ServiceWorkerJobs::~ServiceWorkerJobs() {
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  if (!web_context_registrations_.empty()) {
+    DLOG(INFO) << "Waiting for web context registrations to be cleared. "
+               << web_context_registrations_.size();
+    // Wait for web context registrations to be cleared.
+    web_context_registrations_cleared_.Wait();
+  }
+}
 
 void ServiceWorkerJobs::StartRegister(
     const base::Optional<GURL>& maybe_scope_url,
@@ -116,7 +130,9 @@
     web::EnvironmentSettings* client, const WorkerType& type,
     const ServiceWorkerUpdateViaCache& update_via_cache) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::StartRegister()");
-  // Algorithm for "Start Register":
+  DCHECK_NE(message_loop(), base::MessageLoop::current());
+  DCHECK_EQ(client->context()->message_loop(), base::MessageLoop::current());
+  // Algorithm for Start Register:
   //   https://w3c.github.io/ServiceWorker/#start-register-algorithm
   // 1. If scriptURL is failure, reject promise with a TypeError and abort these
   // steps.
@@ -200,24 +216,17 @@
 
   // 15. Invoke Schedule Job with job.
   message_loop()->task_runner()->PostTask(
-      FROM_HERE, base::Bind(&ServiceWorkerJobs::ScheduleJob,
-                            base::Unretained(this), base::Passed(&job)));
+      FROM_HERE, base::BindOnce(&ServiceWorkerJobs::ScheduleJob,
+                                base::Unretained(this), std::move(job)));
   DCHECK(!job.get());
 }
 
-void ServiceWorkerJobs::MatchServiceWorkerRegistration(const GURL& client_url) {
-  TRACE_EVENT0("cobalt::worker",
-               "ServiceWorkerJobs::MatchServiceWorkerRegistration()");
-  // TODO this need a callback to return the matching registration.
-  NOTIMPLEMENTED();
-}
-
 void ServiceWorkerJobs::PromiseErrorData::Reject(
     std::unique_ptr<JobPromiseType> promise) const {
   if (message_type_ != script::kNoError) {
     promise->Reject(GetSimpleExceptionType(message_type_));
   } else {
-    promise->Reject(new dom::DOMException(exception_code_, message_));
+    promise->Reject(new web::DOMException(exception_code_, message_));
   }
 }
 
@@ -226,7 +235,7 @@
     const GURL& script_url, std::unique_ptr<JobPromiseType> promise,
     web::EnvironmentSettings* client) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::CreateJob()");
-  // Algorithm for "Create Job":
+  // Algorithm for Create Job:
   //   https://w3c.github.io/ServiceWorker/#create-job
   // 1. Let job be a new job.
   // 2. Set job’s job type to jobType.
@@ -236,7 +245,7 @@
   // 6. Set job’s job promise to promise.
   // 7. Set job’s client to client.
   std::unique_ptr<Job> job(new Job(kRegister, storage_key, scope_url,
-                                   script_url, std::move(promise), client));
+                                   script_url, client, std::move(promise)));
   // 8. If client is not null, set job’s referrer to client’s creation URL.
   if (client) {
     job->referrer = client->creation_url();
@@ -247,9 +256,9 @@
 
 void ServiceWorkerJobs::ScheduleJob(std::unique_ptr<Job> job) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::ScheduleJob()");
-  DCHECK_EQ(base::MessageLoop::current(), message_loop());
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
   DCHECK(job);
-  // Algorithm for "Schedule Job":
+  // Algorithm for Schedule Job:
   //   https://w3c.github.io/ServiceWorker/#schedule-job
   // 1. Let jobQueue be null.
 
@@ -305,7 +314,7 @@
 }
 
 bool ServiceWorkerJobs::EquivalentJobs(Job* one, Job* two) {
-  // Algorithm for 'Two jobs are equivalent'
+  // Algorithm for Two jobs are equivalent:
   //   https://w3c.github.io/ServiceWorker/#dfn-job-equivalent
   DCHECK(one);
   DCHECK(two);
@@ -333,8 +342,8 @@
 
 void ServiceWorkerJobs::RunJob(JobQueue* job_queue) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::RunJob()");
-  DCHECK_EQ(base::MessageLoop::current(), message_loop());
-  // Algorithm for "Run Job":
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Algorithm for Run Job:
   //   https://w3c.github.io/ServiceWorker/#run-job-algorithm
 
   // 1. Assert: jobQueue is not empty.
@@ -345,8 +354,8 @@
 
   // 2. Queue a task to run these steps:
   message_loop()->task_runner()->PostTask(
-      FROM_HERE, base::Bind(&ServiceWorkerJobs::RunJobTask,
-                            base::Unretained(this), job_queue));
+      FROM_HERE, base::BindOnce(&ServiceWorkerJobs::RunJobTask,
+                                base::Unretained(this), job_queue));
 }
 
 void ServiceWorkerJobs::RunJobTask(JobQueue* job_queue) {
@@ -387,7 +396,7 @@
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Register()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   DCHECK(job);
-  // Algorithm for "Register"
+  // Algorithm for Register:
   //   https://w3c.github.io/ServiceWorker/#register-algorithm
 
   // 1. If the result of running potentially trustworthy origin with the origin
@@ -396,7 +405,7 @@
     // 1.1. Invoke Reject Job Promise with job and "SecurityError" DOMException.
     RejectJobPromise(
         job, PromiseErrorData(
-                 dom::DOMException::kSecurityErr,
+                 web::DOMException::kSecurityErr,
                  "Service Worker Register failed: Script URL is Not Trusted."));
     // 1.2. Invoke Finish Job with job and abort these steps.
     FinishJob(job);
@@ -411,7 +420,7 @@
     // 2.1. Invoke Reject Job Promise with job and "SecurityError" DOMException.
     RejectJobPromise(
         job, PromiseErrorData(
-                 dom::DOMException::kSecurityErr,
+                 web::DOMException::kSecurityErr,
                  "Service Worker Register failed: Script URL and referrer "
                  "origin are not the same."));
     // 2.2. Invoke Finish Job with job and abort these steps.
@@ -426,7 +435,7 @@
     // 3.1. Invoke Reject Job Promise with job and "SecurityError" DOMException.
     RejectJobPromise(
         job, PromiseErrorData(
-                 dom::DOMException::kSecurityErr,
+                 web::DOMException::kSecurityErr,
                  "Service Worker Register failed: Scope URL and referrer "
                  "origin are not the same."));
 
@@ -438,13 +447,14 @@
   // 4. Let registration be the result of running Get Registration given job’s
   // storage key and job’s scope url.
   ServiceWorkerRegistrationObject* registration =
-      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) {
     // 5.1 Let newestWorker be the result of running the Get Newest Worker
     // algorithm passing registration as the argument.
-    ServiceWorker* newest_worker = GetNewestWorker(registration);
+    ServiceWorkerObject* newest_worker = registration->GetNewestWorker();
 
     // 5.2 If newestWorker is not null, job’s script url equals newestWorker’s
     // script url, job’s worker type equals newestWorker’s type, and job’s
@@ -463,8 +473,8 @@
 
     // 6.1 Invoke Set Registration algorithm with job’s storage key, job’s scope
     // url, and job’s update via cache mode.
-    registration = SetRegistration(job->storage_key, job->scope_url,
-                                   job->update_via_cache);
+    registration = scope_to_registration_map_.SetRegistration(
+        job->storage_key, job->scope_url, job->update_via_cache);
   }
 
   // 7. Invoke Update algorithm passing job as the argument.
@@ -475,13 +485,14 @@
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Update()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   DCHECK(job);
-  // Algorithm for "Update"
+  // Algorithm for Update:
   //   https://w3c.github.io/ServiceWorker/#update-algorithm
 
   // 1. Let registration be the result of running Get Registration given job’s
-  // storage key and job’s scope url.
+  //    storage key and job’s scope url.
   ServiceWorkerRegistrationObject* registration =
-      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) {
@@ -492,14 +503,649 @@
     FinishJob(job);
     return;
   }
+  // 3. Let newestWorker be the result of running Get Newest Worker algorithm
+  //    passing registration as the argument.
+  ServiceWorkerObject* newest_worker = registration->GetNewestWorker();
+
+  // 4. If job’s job type is update, and newestWorker is not null and its script
+  //    url does not equal job’s script url, then:
+  if ((job->type == kUpdate) && newest_worker &&
+      (newest_worker->script_url() != job->script_url)) {
+    // 4.1 Invoke Reject Job Promise with job and TypeError.
+    RejectJobPromise(job, PromiseErrorData(script::kSimpleTypeError));
+
+    // 4.2 Invoke Finish Job with job and abort these steps.
+    FinishJob(job);
+    return;
+  }
+
+  auto state(
+      base::MakeRefCounted<UpdateJobState>(job, registration, newest_worker));
+
+  // 5. Let referrerPolicy be the empty string.
+  // 6. Let hasUpdatedResources be false.
+  state->has_updated_resources = false;
+  // 7. Let updatedResourceMap be an ordered map where the keys are URLs and the
+  //    values are responses.
+  // That is located in job->updated_resource_map.
+
+  // 8. Switching on job’s worker type, run these substeps with the following
+  //    options:
+  //    - "classic"
+  //        Fetch a classic worker script given job’s serialized script url,
+  //        job’s client, "serviceworker", and the to-be-created environment
+  //        settings object for this service worker.
+  //    - "module"
+  //        Fetch a module worker script graph given job’s serialized script
+  //        url, job’s client, "serviceworker", "omit", and the to-be-created
+  //        environment settings object for this service worker.
+  // To perform the fetch given request, run the following steps:
+  //   8.1.  Append `Service-Worker`/`script` to request’s header list.
+  //   8.2.  Set request’s cache mode to "no-cache" if any of the following are
+  //         true:
+  //          - registration’s update via cache mode is not "all".
+  //          - job’s force bypass cache flag is set.
+  //          - newestWorker is not null and registration is stale.
+  //   8.3.  Set request’s service-workers mode to "none".
+  //   8.4.  If the is top-level flag is unset, then return the result of
+  //         fetching request.
+  //   8.5.  Set request’s redirect mode to "error".
+  //   8.6.  Fetch request, and asynchronously wait to run the remaining steps
+  //         as part of fetch’s process response for the response response.
+  //   8.7.  Extract a MIME type from the response’s header list. If this MIME
+  //         type (ignoring parameters) is not a JavaScript MIME type, then:
+  //   8.7.1. Invoke Reject Job Promise with job and "SecurityError"
+  //          DOMException.
+  //   8.7.2. Asynchronously complete these steps with a network error.
+  //   8.8.  Let serviceWorkerAllowed be the result of extracting header list
+  //         values given `Service-Worker-Allowed` and response’s header list.
+  //   8.9.  Set policyContainer to the result of creating a policy container
+  //         from a fetch response given response.
+  //   8.10. If serviceWorkerAllowed is failure, then:
+  //   8.10.1  Asynchronously complete these steps with a network error.
+  //   8.11. Let scopeURL be registration’s scope url.
+  //   8.12. Let maxScopeString be null.
+  //   8.13. If serviceWorkerAllowed is null, then:
+  //   8.13.1. Let resolvedScope be the result of parsing "./" using job’s
+  //           script url as the base URL.
+  //   8.13.2. Set maxScopeString to "/", followed by the strings in
+  //           resolvedScope’s path (including empty strings), separated from
+  //           each other by "/".
+  //   8.14. Else:
+  //   8.14.1. Let maxScope be the result of parsing serviceWorkerAllowed using
+  //           job’s script url as the base URL.
+  //   8.14.2. If maxScope’s origin is job’s script url's origin, then:
+  //   8.14.2.1. Set maxScopeString to "/", followed by the strings in
+  //             maxScope’s path (including empty strings), separated from each
+  //             other by "/".
+  //   8.15. Let scopeString be "/", followed by the strings in scopeURL’s path
+  //         (including empty strings), separated from each other by "/".
+  //   8.16. If maxScopeString is null or scopeString does not start with
+  //         maxScopeString, then:
+  //   8.16.1. Invoke Reject Job Promise with job and "SecurityError"
+  //           DOMException.
+  //   8.16.2. Asynchronously complete these steps with a network error.
+
+  // TODO(b/225037465): Implement CSP check.
+  csp::SecurityCallback csp_callback = base::Bind(&PermitAnyURL);
+  loader::Origin origin = loader::Origin(job->script_url.GetOrigin());
+  job->loader = script_loader_factory_->CreateScriptLoader(
+      job->script_url, origin, csp_callback,
+      base::Bind(&ServiceWorkerJobs::UpdateOnContentProduced,
+                 base::Unretained(this), state),
+      base::Bind(&ServiceWorkerJobs::UpdateOnLoadingComplete,
+                 base::Unretained(this), state));
+}
+
+void ServiceWorkerJobs::UpdateOnContentProduced(
+    scoped_refptr<UpdateJobState> state, const loader::Origin& last_url_origin,
+    std::unique_ptr<std::string> content) {
+  TRACE_EVENT0("cobalt::worker",
+               "ServiceWorkerJobs::UpdateOnContentProduced()");
+  // Note: There seems to be missing handling of network errors here.
+  //   8.17. Let url be request’s url.
+  //   8.18. Set updatedResourceMap[url] to response.
+  state->updated_resource_map.insert(
+      std::make_pair(state->job->script_url, std::move(content)));
+  //   8.19. If response’s cache state is not "local", set registration’s last
+  //         update check time to the current time.
+  //   8.20. Set hasUpdatedResources to true if any of the following are true:
+  //          - newestWorker is null.
+  //          - newestWorker’s script url is not url or newestWorker’s type is
+  //            not job’s worker type.
+  // Note: Cobalt only supports 'classic' worker type.
+  //          - newestWorker’s script resource map[url]'s body is not
+  //            byte-for-byte identical with response’s body.
+  if (state->newest_worker == nullptr) {
+    state->has_updated_resources = true;
+  } else {
+    if (state->newest_worker->script_url() != state->job->script_url) {
+      state->has_updated_resources = true;
+    } else {
+      std::string* script_resource =
+          state->newest_worker->LookupScriptResource(state->job->script_url);
+      if (script_resource && content && (*script_resource != *content)) {
+        state->has_updated_resources = true;
+      }
+    }
+  }
+
+  // TODO(b/228900516): The logic below is needed for importScripts().
+  //   8.21. If hasUpdatedResources is false and newestWorker’s classic
+  //         scripts imported flag is set, then:
+  //   8.21.1. For each importUrl → storedResponse of newestWorker’s script
+  //           resource map:
+  //   8.21.1.1. If importUrl is url, then continue.
+  //   8.21.1.2. Let importRequest be a new request whose url is importUrl,
+  //             client is job’s client, destination is "script", parser
+  //             metadata is "not parser-inserted", synchronous flag is set,
+  //             and whose use-URL-credentials flag is set.
+  //   8.21.1.3. Set importRequest’s cache mode to "no-cache" if any of the
+  //             following are true:
+  //               - registration’s update via cache mode is "none".
+  //               - job’s force bypass cache flag is set.
+  //               - registration is stale.
+  //   8.21.1.4. Let fetchedResponse be the result of fetching importRequest.
+  //   8.21.1.5. Set updatedResourceMap[importRequest’s url] to
+  //             fetchedResponse.
+  //   8.21.1.6. Set fetchedResponse to fetchedResponse’s unsafe response.
+  //   8.21.1.7. If fetchedResponse’s cache state is not
+  //             "local", set registration’s last update check time to the
+  //             current time.
+  //   8.21.1.8. If fetchedResponse is a bad import script response, continue.
+  //   8.21.1.9. If fetchedResponse’s body is not byte-for-byte identical with
+  //             storedResponse’s unsafe response's body, set
+  //             hasUpdatedResources to true.
+  //   8.22. Asynchronously complete these steps with response.
+}
+
+void ServiceWorkerJobs::UpdateOnLoadingComplete(
+    scoped_refptr<UpdateJobState> state,
+    const base::Optional<std::string>& error) {
+  TRACE_EVENT0("cobalt::worker",
+               "ServiceWorkerJobs::UpdateOnLoadingComplete()");
+  // TODO: This shouldn't run until the load for each script from the script
+  // resource map completes (step 8.22).
+
+  // When the algorithm asynchronously completes, continue the rest of these
+  // steps, with script being the asynchronous completion value.
+  auto entry = state->updated_resource_map.find(state->job->script_url);
+  auto* script = entry != state->updated_resource_map.end()
+                     ? entry->second.get()
+                     : nullptr;
+  // 9. If script is null or Is Async Module with script’s record, script’s
+  //    base URL, and {} it true, then:
+  if (script == nullptr) {
+    // 9.1. Invoke Reject Job Promise with job and TypeError.
+    RejectJobPromise(state->job, PromiseErrorData(script::kSimpleTypeError));
+
+    // 9.2. If newestWorker is null, then remove registration
+    //      map[(registration’s storage key, serialized scopeURL)].
+    if (state->newest_worker == nullptr) {
+      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);
+    return;
+  }
+
+  // 10. If hasUpdatedResources is false, then:
+  if (!state->has_updated_resources) {
+    // 10.1. Set registration’s update via cache mode to job’s update via cache
+    //       mode.
+    state->registration->set_update_via_cache_mode(
+        state->job->update_via_cache);
+
+    // 10.2. Invoke Resolve Job Promise with job and registration.
+    ResolveJobPromise(state->job, state->registration);
+
+    // 10.3. Invoke Finish Job with job and abort these steps.
+    FinishJob(state->job);
+    return;
+  }
+
+  // 11. Let worker be a new service worker.
+  ServiceWorkerObject::Options options(
+      "ServiceWorker", state->job->client->context()->network_module());
+  std::unique_ptr<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
+  //     script resource map to updatedResourceMap.
+  // -> The worker's script resource is set in the resource map at step 8.18.
+  worker->set_script_url(state->job->script_url);
+  worker->set_script_resource_map(std::move(state->updated_resource_map));
+  // 13. Append url to worker’s set of used scripts.
+  // -> The script resource map contains the used scripts.
+  // 14. Set worker’s script resource’s policy container to policyContainer.
+  // 15. Let forceBypassCache be true if job’s force bypass cache flag is
+  //     set, and false otherwise.
+  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 = RunServiceWorker(worker.get(), force_bypass_cache);
+  // 17. If runResult is failure or an abrupt completion, then:
+  if (!run_result) {
+    // 17.1. Invoke Reject Job Promise with job and TypeError.
+    RejectJobPromise(state->job, PromiseErrorData(script::kSimpleTypeError));
+    // 17.2. If newestWorker is null, then remove registration
+    //       map[(registration’s storage key, serialized scopeURL)].
+    if (state->newest_worker == nullptr) {
+      scope_to_registration_map_.RemoveRegistration(state->job->storage_key,
+                                                    state->job->scope_url);
+    }
+    // 17.3. Invoke Finish Job with job.
+    FinishJob(state->job);
+  } else {
+    // 18. Else, invoke Install algorithm with job, worker, and registration
+    //     as its arguments.
+    Install(state->job, worker.get(), state->registration);
+  }
+}
+
+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);
+  // return worker->Run(force_bypass_cache);
+  // Algorithm for "Run Service Worker"
+  //   https://w3c.github.io/ServiceWorker/#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.
+  std::string* script = worker->LookupScriptResource();
+  // 6. Assert: script is not null.
+  DCHECK(script != nullptr);
+  // 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();
+}
+
+void ServiceWorkerJobs::Install(Job* job, ServiceWorkerObject* worker,
+                                ServiceWorkerRegistrationObject* registration) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Install()");
+  DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Algorithm for Install:
+  //   https://w3c.github.io/ServiceWorker/#installation-algorithm
+
+  // 1. Let installFailed be false.
+  starboard::atomic_bool install_failed(false);
+
+  // 2. Let newestWorker be the result of running Get Newest Worker algorithm
+  //    passing registration as its argument.
+  ServiceWorkerObject* newest_worker = registration->GetNewestWorker();
+
+  // 3. Set registration’s update via cache mode to job’s update via cache mode.
+  registration->set_update_via_cache_mode(job->update_via_cache);
+
+  // 4. Run the Update Registration State algorithm passing registration,
+  //    "installing" and worker as the arguments.
+  UpdateRegistrationState(registration, kInstalling, worker);
+
+  // 5. Run the Update Worker State algorithm passing registration’s installing
+  //    worker and "installing" as the arguments.
+  UpdateWorkerState(registration->installing_worker(),
+                    kServiceWorkerStateInstalling);
+  // 6. Assert: job’s job promise is not null.
+  DCHECK(job->promise.get() != nullptr);
+  // 7. Invoke Resolve Job Promise with job and registration.
   ResolveJobPromise(job, registration);
+  // 8. Let settingsObjects be all environment settings objects whose origin is
+  //    registration’s scope url's origin.
+  auto registration_origin = registration->scope_url().GetOrigin();
+  // 9. For each settingsObject of settingsObjects...
+  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:
+      context->message_loop()->task_runner()->PostTask(
+          FROM_HERE,
+          base::BindOnce(
+              [](web::Context* context,
+                 ServiceWorkerRegistrationObject* registration) {
+                // 9.1. Let registrationObjects be every
+                // ServiceWorkerRegistration
+                //      object in settingsObject’s realm, whose service worker
+                //      registration is registration.
+
+                // There is at most one per web context, stored in the service
+                // worker registration object map of the web context.
+
+                // 9.2. For each registrationObject of registrationObjects, fire
+                // an
+                //      event on registrationObject named updatefound.
+                auto registration_object =
+                    context->LookupServiceWorkerRegistration(registration);
+                if (registration_object) {
+                  context->message_loop()->task_runner()->PostTask(
+                      FROM_HERE,
+                      base::BindOnce(
+                          [](scoped_refptr<worker::ServiceWorkerRegistration>
+                                 registration_object) {
+                            registration_object->DispatchEvent(
+                                new web::Event(base::Tokens::updatefound()));
+                          },
+                          registration_object));
+                }
+              },
+              context, registration));
+    }
+  }
+  // 10. Let installingWorker be registration’s installing worker.
+  ServiceWorkerObject* installing_worker = registration->installing_worker();
+  // 11. If the result of running the Should Skip Event algorithm with
+  //     installingWorker and "install" is false, then:
+  if (!ShouldSkipEvent(base::Tokens::install(), installing_worker)) {
+    // 11.1. Let forceBypassCache be true if job’s force bypass cache flag is
+    //       set, and false otherwise.
+    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 = RunServiceWorker(installing_worker, force_bypass_cache);
+    if (!run_result) {
+      // 11.2.1. Set installFailed to true.
+      install_failed.store(true);
+      // 11.3. Else:
+    } else {
+      // 11.3.1. Queue a task task on installingWorker’s event loop using the
+      //         DOM manipulation task source to run the following steps:
+      installing_worker->web_agent()
+          ->context()
+          ->message_loop()
+          ->task_runner()
+          ->PostBlockingTask(
+              FROM_HERE,
+              base::Bind(
+                  [](ServiceWorkerObject* installing_worker) {
+                    // 11.3.1.1. Let e be the result of creating an event with
+                    //           ExtendableEvent.
+                    // TODO: implement this as ExtendableEvent.
+                    // 11.3.1.2. Initialize e’s type attribute to install.
+                    // 11.3.1.3. Dispatch e at installingWorker’s global object.
+                    installing_worker->worker_global_scope()->DispatchEvent(
+                        new web::Event(base::Tokens::install()));
+                    // 11.3.1.4. WaitForAsynchronousExtensions: Run the
+                    //           following substeps in parallel:
+                    // 11.3.1.4.1. Wait until e is not active.
+                    // 11.3.1.4.2. If e’s timed out flag is set, set
+                    //             installFailed to true.
+                    // 11.3.1.4.3. Let p be the result of getting a promise to
+                    //             wait for all of e’s extend lifetime promises.
+                    // 11.3.1.4.4. Upon rejection of p, set installFailed to
+                    //             true.
+                    //         If task is discarded, set installFailed to true.
+                  },
+                  installing_worker));
+      // 11.3.2. Wait for task to have executed or been discarded.
+      // Waiting is done inside PostBlockingTask above.
+      // 11.3.3. Wait for the step labeled WaitForAsynchronousExtensions to
+      //         complete.
+      NOTIMPLEMENTED();
+    }
+  }
+  // 12. If installFailed is true, then:
+  if (install_failed.load()) {
+    // 12.1. Run the Update Worker State algorithm passing registration’s
+    //       installing worker and "redundant" as the arguments.
+    UpdateWorkerState(registration->installing_worker(),
+                      kServiceWorkerStateRedundant);
+    // 12.2. Run the Update Registration State algorithm passing registration,
+    //       "installing" and null as the arguments.
+    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) {
+      scope_to_registration_map_.RemoveRegistration(registration->storage_key(),
+                                                    registration->scope_url());
+    }
+    // 12.4. Invoke Finish Job with job and abort these steps.
+    FinishJob(job);
+    return;
+  }
+  // Note: The logic below is for scripts added with importScript().
+  // 13. Let map be registration’s installing worker's script resource map.
+  // 14. Let usedSet be registration’s installing worker's set of used scripts.
+  // 15. For each url of map:
+  // 15.1. If usedSet does not contain url, then remove map[url].
+  // 16. If registration’s waiting worker is not null, then:
+  if (registration->waiting_worker()) {
+    // 16.1. Terminate registration’s waiting worker.
+    TerminateServiceWorker(registration->waiting_worker());
+    // 16.2. Run the Update Worker State algorithm passing registration’s
+    //       waiting worker and "redundant" as the arguments.
+    UpdateWorkerState(registration->waiting_worker(),
+                      kServiceWorkerStateRedundant);
+  }
+  // 17. Run the Update Registration State algorithm passing registration,
+  //     "waiting" and registration’s installing worker as the arguments.
+  UpdateRegistrationState(registration, kWaiting,
+                          registration->installing_worker());
+  // 18. Run the Update Registration State algorithm passing registration,
+  //     "installing" and null as the arguments.
+  UpdateRegistrationState(registration, kInstalling, nullptr);
+  // 19. Run the Update Worker State algorithm passing registration’s waiting
+  //     worker and "installed" as the arguments.
+  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: Wait for tasks.
+  // 22. Invoke Try Activate with registration.
+  TryActivate(registration);
+}
+
+void ServiceWorkerJobs::TryActivate(
+    ServiceWorkerRegistrationObject* registration) {
+  // Algorithm for Try Activate:
+  //   https://w3c.github.io/ServiceWorker/#try-activate-algorithm
+  NOTIMPLEMENTED();
+}
+
+void ServiceWorkerJobs::UpdateRegistrationState(
+    ServiceWorkerRegistrationObject* registration, RegistrationState target,
+    ServiceWorkerObject* source) {
+  TRACE_EVENT0("cobalt::worker",
+               "ServiceWorkerJobs::UpdateRegistrationState()");
+  DCHECK(registration);
+  // Algorithm for Update Registration State:
+  //   https://w3c.github.io/ServiceWorker/#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()->PostTask(
+            FROM_HERE,
+            base::BindOnce(
+                [](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->GetServiceWorkerRegistration(registration);
+                  if (registration_object) {
+                    registration_object->set_installing(
+                        context->GetServiceWorker(
+                            registration->installing_worker()));
+                  }
+                },
+                context, 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()->PostTask(
+            FROM_HERE,
+            base::BindOnce(
+                [](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, 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()->PostTask(
+            FROM_HERE,
+            base::BindOnce(
+                [](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, registration));
+      }
+      break;
+    }
+    default:
+      NOTREACHED();
+  }
+}
+
+void ServiceWorkerJobs::UpdateWorkerState(ServiceWorkerObject* worker,
+                                          ServiceWorkerState state) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::UpdateWorkerState()");
+  DCHECK(worker);
+  if (!worker) {
+    return;
+  }
+  // Algorithm for Update Worker State:
+  //   https://w3c.github.io/ServiceWorker/#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 = worker->script_url().GetOrigin();
+  // 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()->PostTask(
+          FROM_HERE,
+          base::BindOnce(
+              [](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.
+                  context->message_loop()->task_runner()->PostTask(
+                      FROM_HERE,
+                      base::BindOnce(
+                          [](scoped_refptr<worker::ServiceWorker> worker_obj) {
+                            worker_obj->DispatchEvent(
+                                new web::Event(base::Tokens::statechange()));
+                          },
+                          worker_obj));
+                }
+              },
+              context, worker, state));
+    }
+  }
+}
+
+bool ServiceWorkerJobs::ShouldSkipEvent(base::Token event_name,
+                                        ServiceWorkerObject* worker) {
+  // Algorithm for Should Skip Event:
+  //   https://w3c.github.io/ServiceWorker/#should-skip-event-algorithm
+  NOTIMPLEMENTED();
+  return false;
+}
+
+void ServiceWorkerJobs::TerminateServiceWorker(ServiceWorkerObject* worker) {
+  TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::TerminateServiceWorker()");
+  // Algorithm for Terminate Service Worker:
+  //   https://w3c.github.io/ServiceWorker/#terminate-service-worker
   NOTIMPLEMENTED();
 }
 
 void ServiceWorkerJobs::Unregister(Job* job) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Unregister()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  // Algorithm for "Unregister"
+  // Algorithm for Unregister:
   //   https://w3c.github.io/ServiceWorker/#unregister-algorithm
   NOTIMPLEMENTED();
 }
@@ -508,12 +1154,12 @@
                                          const PromiseErrorData& error_data) {
   TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::RejectJobPromise()");
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
-  // Algorithm for "Reject Job Promise"
+  // Algorithm for Reject Job Promise:
   //   https://w3c.github.io/ServiceWorker/#reject-job-promise
   // 1. If job’s client is not null, queue a task, on job’s client's responsible
-  // event loop using the DOM manipulation task source, to reject job’s job
-  // promise with a new exception with errorData and a user agent-defined
-  // message, in job’s client's Realm.
+  //    event loop using the DOM manipulation task source, to reject job’s job
+  //    promise with a new exception with errorData and a user agent-defined
+  //    message, in job’s client's Realm.
 
   auto reject_task = [](std::unique_ptr<JobPromiseType> promise,
                         const PromiseErrorData& error_data) {
@@ -524,7 +1170,7 @@
     base::AutoLock lock(job->equivalent_jobs_promise_mutex);
     job->client->context()->message_loop()->task_runner()->PostTask(
         FROM_HERE,
-        base::Bind(reject_task, base::Passed(&job->promise), error_data));
+        base::BindOnce(reject_task, std::move(job->promise), error_data));
     // Ensure that the promise is cleared, so that equivalent jobs won't get
     // added from this point on.
     CHECK(!job->promise);
@@ -537,16 +1183,16 @@
     // 2.1. If equivalentJob’s client is null, continue.
     if (equivalent_job->client) {
       // 2.2. Queue a task, on equivalentJob’s client's responsible event loop
-      // using the DOM manipulation task source, to reject equivalentJob’s job
-      // promise with a new exception with errorData and a user agent-defined
-      // message, in equivalentJob’s client's Realm.
+      //      using the DOM manipulation task source, to reject equivalentJob’s
+      //      job promise with a new exception with errorData and a user
+      //      agent-defined message, in equivalentJob’s client's Realm.
       equivalent_job->client->context()
           ->message_loop()
           ->task_runner()
-          ->PostTask(FROM_HERE,
-                     base::BindOnce(reject_task,
-                                    base::Passed(&equivalent_job->promise),
-                                    error_data));
+          ->PostTask(
+              FROM_HERE,
+              base::BindOnce(reject_task, std::move(equivalent_job->promise),
+                             error_data));
       // Check that the promise is cleared.
       CHECK(!equivalent_job->promise);
     }
@@ -560,18 +1206,38 @@
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
   DCHECK(job);
   DCHECK(value);
-  // Algorithm for "Resolve Job Promise".
+  // Algorithm for Resolve Job Promise:
   //   https://w3c.github.io/ServiceWorker/#resolve-job-promise-algorithm
 
   // 1. If job’s client is not null, queue a task, on job’s client's responsible
-  // event loop using the DOM manipulation task source, to run the following
-  // substeps:
+  //    event loop using the DOM manipulation task source, to run the following
+  //    substeps:
+  auto resolve_task = [](JobType type, web::EnvironmentSettings* client,
+                         std::unique_ptr<JobPromiseType> promise,
+                         ServiceWorkerRegistrationObject* value) {
+    // 1.1./2.2.1. Let convertedValue be null.
+    // 1.2./2.2.2. If job’s job type is either register or update, set
+    //             convertedValue to the result of getting the service worker
+    //             registration object that represents value in job’s client.
+    if (type == kRegister || type == kUpdate) {
+      scoped_refptr<cobalt::script::Wrappable> converted_value =
+          client->context()->GetServiceWorkerRegistration(value);
+      // 1.4./2.2.4.  Resolve job’s job promise with convertedValue.
+      promise->Resolve(converted_value);
+    } else {
+      DCHECK_EQ(kUnregister, type);
+      // 1.3./2.2.3. Else, set convertedValue to value, in job’s client's Realm.
+      bool converted_value = value != nullptr;
+      // 1.4./2.2.4.  Resolve job’s job promise with convertedValue.
+      promise->Resolve(converted_value);
+    }
+  };
+
   if (job->client) {
     base::AutoLock lock(job->equivalent_jobs_promise_mutex);
     job->client->context()->message_loop()->task_runner()->PostTask(
-        FROM_HERE, base::Bind(&ServiceWorkerJobs::ResolveJobPromiseTask,
-                              base::Unretained(this), job->type,
-                              base::Passed(&job->promise), job->client, value));
+        FROM_HERE, base::BindOnce(resolve_task, job->type, job->client,
+                                  std::move(job->promise), value));
     // Ensure that the promise is cleared, so that equivalent jobs won't get
     // added from this point on.
     CHECK(!job->promise);
@@ -583,18 +1249,18 @@
     DCHECK(equivalent_job->equivalent_jobs.empty());
 
     // 2.1. If equivalentJob’s client is null, continue to the next iteration of
-    // the loop.
+    //      the loop.
     if (equivalent_job->client) {
       // 2.2. Queue a task, on equivalentJob’s client's responsible event loop
-      // using the DOM manipulation task source, to run the following substeps:
+      //      using the DOM manipulation task source, to run the following
+      //      substeps:
       equivalent_job->client->context()
           ->message_loop()
           ->task_runner()
           ->PostTask(FROM_HERE,
-                     base::Bind(&ServiceWorkerJobs::ResolveJobPromiseTask,
-                                base::Unretained(this), equivalent_job->type,
-                                base::Passed(&equivalent_job->promise),
-                                equivalent_job->client, value));
+                     base::BindOnce(resolve_task, equivalent_job->type,
+                                    equivalent_job->client,
+                                    std::move(equivalent_job->promise), value));
       // Check that the promise is cleared.
       CHECK(!equivalent_job->promise);
     }
@@ -602,29 +1268,6 @@
   job->equivalent_jobs.clear();
 }
 
-void ServiceWorkerJobs::ResolveJobPromiseTask(
-    JobType type, std::unique_ptr<JobPromiseType> promise,
-    web::EnvironmentSettings* client, ServiceWorkerRegistrationObject* value) {
-  DCHECK_NE(message_loop(), base::MessageLoop::current());
-  DCHECK_EQ(base::MessageLoop::current(), client->context()->message_loop());
-  // 1.1./2.2.1. Let convertedValue be null.
-  // 1.2./2.2.2. If job’s job type is either register or update, set
-  // convertedValue to the result of getting the service worker registration
-  // object that represents value in job’s client.
-  if (type == kRegister || type == kUpdate) {
-    auto converted_value =
-        client->context()->GetServiceWorkerRegistration(value);
-    // 1.4./2.2.4.  Resolve job’s job promise with convertedValue.
-    promise->Resolve(converted_value);
-  } else {
-    DCHECK_EQ(type, kUnregister);
-    // 1.3./2.2.3. Else, set convertedValue to value, in job’s client's Realm.
-    bool converted_value = value != nullptr;
-    // 1.4./2.2.4.  Resolve job’s job promise with convertedValue.
-    promise->Resolve(converted_value);
-  }
-}
-
 // https://w3c.github.io/ServiceWorker/#finish-job-algorithm
 void ServiceWorkerJobs::FinishJob(Job* job) {
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
@@ -632,7 +1275,7 @@
   JobQueue* job_queue = job->containing_job_queue;
 
   // 2. Assert: the first item in jobQueue is job.
-  DCHECK_EQ(job_queue->FirstItem(), job);
+  DCHECK_EQ(job, job_queue->FirstItem());
 
   // 3. Dequeue from jobQueue.
   job_queue->Dequeue();
@@ -643,74 +1286,69 @@
   }
 }
 
-ServiceWorkerRegistrationObject* ServiceWorkerJobs::GetRegistration(
-    const url::Origin& storage_key, const GURL& scope) {
-  // Algorithm for 'Get Registration':
-  //   https://w3c.github.io/ServiceWorker/#get-registration-algorithm
+void ServiceWorkerJobs::GetRegistrationSubSteps(
+    const url::Origin& storage_key, const GURL& client_url,
+    web::EnvironmentSettings* client,
+    std::unique_ptr<script::ValuePromiseWrappable::Reference>
+        promise_reference) {
   DCHECK_EQ(message_loop(), base::MessageLoop::current());
+  // Algorithm for Sub steps of ServiceWorkerContainer.getRegistration():
+  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistration
 
-  // 1. Run the following steps atomically.
-  base::AutoLock lock(registration_map_mutex_);
-
-  // 2. Let scopeString be the empty string.
-  std::string scope_string;
-
-  // 3. If scope is not null, set scopeString to serialized scope with the
-  // exclude fragment flag set.
-  if (!scope.is_empty()) {
-    scope_string = SerializeExcludingFragment(scope);
-  }
-
-  RegistrationKey registration_key(storage_key, scope_string);
-  // 4. For each (entry storage key, entry scope) → registration of registration
-  // map:
-  for (const auto& entry : registration_map_) {
-    // 4.1. If storage key equals entry storage key and scopeString matches
-    // entry scope, then return registration.
-    if (entry.first == registration_key) {
-      return entry.second.get();
-    }
-  }
-
-  // 5. Return null.
-  return nullptr;
+  // 1. Let registration be the result of running Match Service Worker
+  //    Registration algorithm with clientURL as its argument.
+  worker::ServiceWorkerRegistrationObject* registration =
+      scope_to_registration_map_.MatchServiceWorkerRegistration(storage_key,
+                                                                client_url);
+  // 2. If registration is null, resolve promise with undefined and abort
+  //    these steps.
+  // 3. Resolve promise with the result of getting the service worker
+  //    registration object that represents registration in promise’s
+  //    relevant settings object.
+  client->context()->message_loop()->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](web::EnvironmentSettings* settings,
+             std::unique_ptr<script::ValuePromiseWrappable::Reference> promise,
+             ServiceWorkerRegistrationObject* registration) {
+            promise->value().Resolve(
+                settings->context()->GetServiceWorkerRegistration(
+                    registration));
+          },
+          client, std::move(promise_reference), registration));
 }
 
-ServiceWorkerRegistrationObject* ServiceWorkerJobs::SetRegistration(
-    const url::Origin& storage_key, const GURL& scope,
-    const ServiceWorkerUpdateViaCache& update_via_cache) {
-  // Algorithm for 'Set Registration':
-  //   https://w3c.github.io/ServiceWorker/#set-registration-algorithm
+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());
-
-  // 1. Run the following steps atomically.
-  base::AutoLock lock(registration_map_mutex_);
-
-  // 2. Let scopeString be serialized scope with the exclude fragment flag set.
-  std::string scope_string = SerializeExcludingFragment(scope);
-
-  // 3. Let registration be a new service worker registration whose storage key
-  // is set to storage key, scope url is set to scope, and update via cache mode
-  // is set to updateViaCache.
-  ServiceWorkerRegistrationObject* registration(
-      new ServiceWorkerRegistrationObject(storage_key, scope,
-                                          update_via_cache));
-
-  // 4. Set registration map[(storage key, scopeString)] to registration.
-  RegistrationKey registration_key(storage_key, scope_string);
-  registration_map_.insert(std::make_pair(
-      registration_key,
-      std::unique_ptr<ServiceWorkerRegistrationObject>(registration)));
-
-  // 5. Return registration.
-  return registration;
+  DCHECK_EQ(0, web_context_registrations_.count(context));
+  web_context_registrations_.insert(context);
 }
 
-// https://w3c.github.io/ServiceWorker/#get-newest-worker
-ServiceWorker* ServiceWorkerJobs::GetNewestWorker(
-    ServiceWorkerRegistrationObject* registration) {
-  NOTIMPLEMENTED();
-  return 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);
+  if (web_context_registrations_.empty()) {
+    web_context_registrations_cleared_.Signal();
+  }
 }
 
 ServiceWorkerJobs::JobPromiseType::JobPromiseType(
@@ -726,7 +1364,7 @@
 }
 
 void ServiceWorkerJobs::JobPromiseType::Resolve(
-    const scoped_refptr<ServiceWorkerRegistration>& result) {
+    const scoped_refptr<cobalt::script::Wrappable>& result) {
   DCHECK(promise_wrappable_reference_);
   promise_wrappable_reference_->value().Resolve(result);
 }
diff --git a/cobalt/worker/service_worker_jobs.h b/cobalt/worker/service_worker_jobs.h
index a97b0e4..fa3c4fd 100644
--- a/cobalt/worker/service_worker_jobs.h
+++ b/cobalt/worker/service_worker_jobs.h
@@ -19,20 +19,28 @@
 #include <map>
 #include <memory>
 #include <queue>
+#include <set>
 #include <string>
 #include <utility>
 
 #include "base/memory/ref_counted.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/dom/dom_exception.h"
+#include "cobalt/loader/fetcher_factory.h"
+#include "cobalt/loader/script_loader_factory.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/script/exception_message.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/script/script_value.h"
+#include "cobalt/web/dom_exception.h"
 #include "cobalt/web/environment_settings.h"
 #include "cobalt/worker/service_worker.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"
@@ -60,8 +68,17 @@
                      web::EnvironmentSettings* client, const WorkerType& type,
                      const ServiceWorkerUpdateViaCache& update_via_cache);
 
-  // https://w3c.github.io/ServiceWorker/#scope-match-algorithm
-  void MatchServiceWorkerRegistration(const GURL& client_url);
+  // Sub steps (8) of 'ServiceWorkerContainer.getRegistration()'.
+  //   https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistration
+  void GetRegistrationSubSteps(
+      const url::Origin& storage_key, const GURL& client_url,
+      web::EnvironmentSettings* client,
+      std::unique_ptr<script::ValuePromiseWrappable::Reference>
+          promise_reference);
+
+  // Registration of web contexts that may have service workers.
+  void RegisterWebContext(web::Context* context);
+  void UnregisterWebContext(web::Context* context);
 
  private:
   // https://w3c.github.io/ServiceWorker/#dfn-job-type
@@ -87,7 +104,7 @@
     }
 
     void Resolve(const bool result);
-    void Resolve(const scoped_refptr<ServiceWorkerRegistration>& result);
+    void Resolve(const scoped_refptr<cobalt::script::Wrappable>& result);
     void Reject(script::SimpleExceptionType exception);
     void Reject(const scoped_refptr<script::ScriptException>& result);
 
@@ -103,32 +120,59 @@
   // https://w3c.github.io/ServiceWorker/#dfn-job
   struct Job {
     Job(JobType type, const url::Origin& storage_key, const GURL& scope_url,
-        const GURL& script_url, std::unique_ptr<JobPromiseType> promise,
-        web::EnvironmentSettings* client)
+        const GURL& script_url, web::EnvironmentSettings* client,
+        std::unique_ptr<JobPromiseType> promise)
         : type(type),
           storage_key(storage_key),
           scope_url(scope_url),
           script_url(script_url),
-          promise(std::move(promise)),
-          client(client) {}
+          client(client),
+          promise(std::move(promise)) {}
     ~Job() {
       client = nullptr;
       containing_job_queue = nullptr;
     }
+
+    // Job properties from the spec.
+    //
     JobType type;
     url::Origin storage_key;
     GURL scope_url;
     GURL script_url;
-    std::unique_ptr<JobPromiseType> promise;
-    web::EnvironmentSettings* client;
     ServiceWorkerUpdateViaCache update_via_cache;
+    web::EnvironmentSettings* client;
+    GURL referrer;
+    std::unique_ptr<JobPromiseType> promise;
     JobQueue* containing_job_queue = nullptr;
     std::deque<std::unique_ptr<Job>> equivalent_jobs;
-    GURL referrer;
+    bool force_bypass_cache_flag = false;
+
+    // Custom, not in the spec.
+    //
 
     // This lock is for the list of equivalent jobs. It should also be held when
     // resolving the promise.
     base::Lock equivalent_jobs_promise_mutex;
+
+    // The loader that is used for asynchronous loads.
+    std::unique_ptr<loader::Loader> loader;
+  };
+
+  // State used for the 'Update' algorithm.
+  struct UpdateJobState : public base::RefCounted<UpdateJobState> {
+    UpdateJobState(Job* job, ServiceWorkerRegistrationObject* registration,
+                   ServiceWorkerObject* newest_worker)
+        : job(job), registration(registration), newest_worker(newest_worker) {}
+    Job* job;
+    ServiceWorkerRegistrationObject* registration;
+    ServiceWorkerObject* newest_worker;
+
+    // map of content or resources for the worker.
+    ServiceWorkerObject::ScriptResourceMap updated_resource_map;
+
+    // This represents hasUpdatedResources of the Update algorithm.
+    // True if any of the resources has changed since last cached.
+    bool has_updated_resources = false;
   };
 
   // https://w3c.github.io/ServiceWorker/#dfn-job-queue
@@ -176,8 +220,8 @@
    public:
     explicit PromiseErrorData(const script::MessageType& message_type)
         : message_type_(message_type),
-          exception_code_(dom::DOMException::kNone) {}
-    PromiseErrorData(const dom::DOMException::ExceptionCode& code,
+          exception_code_(web::DOMException::kNone) {}
+    PromiseErrorData(const web::DOMException::ExceptionCode& code,
                      const std::string& message)
         : message_type_(script::kNoError),
           exception_code_(code),
@@ -189,10 +233,11 @@
     // Use script::MessageType because it can hold kNoError value to distinguish
     // between simple exceptions and DOM exceptions.
     script::MessageType message_type_;
-    const dom::DOMException::ExceptionCode exception_code_;
+    const web::DOMException::ExceptionCode exception_code_;
     const std::string message_;
   };
 
+  enum RegistrationState { kInstalling, kWaiting, kActive };
 
   // https://w3c.github.io/ServiceWorker/#create-job
   std::unique_ptr<Job> CreateJob(JobType type, const url::Origin& storage_key,
@@ -218,6 +263,12 @@
   // https://w3c.github.io/ServiceWorker/#update-algorithm
   void Update(Job* job);
 
+  void UpdateOnContentProduced(scoped_refptr<UpdateJobState> state,
+                               const loader::Origin& last_url_origin,
+                               std::unique_ptr<std::string> content);
+  void UpdateOnLoadingComplete(scoped_refptr<UpdateJobState> state,
+                               const base::Optional<std::string>& error);
+
   // https://w3c.github.io/ServiceWorker/#unregister-algorithm
   void Unregister(Job* job);
 
@@ -226,40 +277,58 @@
 
   // https://w3c.github.io/ServiceWorker/#resolve-job-promise-algorithm
   void ResolveJobPromise(Job* job, ServiceWorkerRegistrationObject* value);
-  void ResolveJobPromiseTask(JobType type,
-                             std::unique_ptr<JobPromiseType> promise,
-                             web::EnvironmentSettings* client,
-                             ServiceWorkerRegistrationObject* value);
 
   // https://w3c.github.io/ServiceWorker/#finish-job-algorithm
   void FinishJob(Job* job);
 
-  // https://w3c.github.io/ServiceWorker/#get-registration-algorithm
-  ServiceWorkerRegistrationObject* GetRegistration(
-      const url::Origin& storage_key, const GURL& scope);
-
-  // https://w3c.github.io/ServiceWorker/#set-registration-algorithm
-  ServiceWorkerRegistrationObject* SetRegistration(
-      const url::Origin& storage_key, const GURL& scope,
-      const ServiceWorkerUpdateViaCache& update_via_cache);
-
   // https://w3c.github.io/ServiceWorker/#get-newest-worker
   ServiceWorker* GetNewestWorker(ServiceWorkerRegistrationObject* registration);
 
+  // https://w3c.github.io/ServiceWorker/#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);
+
+  // https://w3c.github.io/ServiceWorker/#installation-algorithm
+  void Install(Job* job, ServiceWorkerObject* worker,
+               ServiceWorkerRegistrationObject* registration);
+
+  // https://w3c.github.io/ServiceWorker/#try-activate-algorithm
+  void TryActivate(ServiceWorkerRegistrationObject* registration);
+
+
+  // https://w3c.github.io/ServiceWorker/#update-registration-state-algorithm
+  void UpdateRegistrationState(ServiceWorkerRegistrationObject* registration,
+                               RegistrationState target,
+                               ServiceWorkerObject* source);
+
+  // https://w3c.github.io/ServiceWorker/#update-state-algorithm
+  void UpdateWorkerState(ServiceWorkerObject* worker, ServiceWorkerState state);
+
+  // https://w3c.github.io/ServiceWorker/#should-skip-event-algorithm
+  bool ShouldSkipEvent(base::Token event_name, ServiceWorkerObject* worker);
+
+  // https://w3c.github.io/ServiceWorker/#terminate-service-worker
+  void TerminateServiceWorker(ServiceWorkerObject* worker);
+
+  // 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_;
   network::NetworkModule* network_module_;
   base::MessageLoop* message_loop_;
 
   JobQueueMap job_queue_map_;
+  ServiceWorkerRegistrationMap scope_to_registration_map_;
 
-  // A registration map is an ordered map where the keys are (storage key,
-  // serialized scope urls) and the values are service worker registrations.
-  //   https://w3c.github.io/ServiceWorker/#dfn-scope-to-registration-map
-  using RegistrationKey = std::pair<url::Origin, std::string>;
-  std::map<RegistrationKey, std::unique_ptr<ServiceWorkerRegistrationObject>>
-      registration_map_;
+  std::set<web::Context*> web_context_registrations_;
 
-  // This lock is to allow atomic operations on the registration map.
-  base::Lock registration_map_mutex_;
+  base::WaitableEvent web_context_registrations_cleared_ = {
+      base::WaitableEvent::ResetPolicy::MANUAL,
+      base::WaitableEvent::InitialState::NOT_SIGNALED};
 };
 
 }  // namespace worker
diff --git a/cobalt/worker/service_worker_object.cc b/cobalt/worker/service_worker_object.cc
index aee3dc6..1cb3a83 100644
--- a/cobalt/worker/service_worker_object.cc
+++ b/cobalt/worker/service_worker_object.cc
@@ -14,10 +14,183 @@
 
 #include "cobalt/worker/service_worker_object.h"
 
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/trace_event/trace_event.h"
+#include "cobalt/script/environment_settings.h"
+#include "cobalt/script/execution_state.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/javascript_engine.h"
+#include "cobalt/script/script_runner.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/script/wrappable.h"
+#include "cobalt/web/agent.h"
+#include "cobalt/web/context.h"
+#include "cobalt/worker/service_worker_global_scope.h"
+#include "cobalt/worker/service_worker_state.h"
+#include "cobalt/worker/worker_global_scope.h"
+#include "cobalt/worker/worker_settings.h"
+#include "url/gurl.h"
+
 namespace cobalt {
 namespace worker {
 
-ServiceWorkerObject::ServiceWorkerObject() {}
+ServiceWorkerObject::ServiceWorkerObject(const Options& options)
+    : state_(kServiceWorkerStateParsed), options_(options) {}
+
+ServiceWorkerObject::~ServiceWorkerObject() {
+  if (web_agent_) {
+    DCHECK(message_loop());
+    web_agent_->WaitUntilDone();
+    web_agent_.reset();
+  }
+}
+
+std::string* ServiceWorkerObject::LookupScriptResource() const {
+  return LookupScriptResource(script_url_);
+}
+
+std::string* ServiceWorkerObject::LookupScriptResource(const GURL& url) const {
+  auto entry = script_resource_map_.find(url);
+  return entry != script_resource_map_.end() ? entry->second.get() : nullptr;
+}
+
+void ServiceWorkerObject::WillDestroyCurrentMessageLoop() {
+// Destroy members that were constructed in the worker thread.
+#if defined(ENABLE_DEBUGGER)
+  debug_module_.reset();
+#endif  // ENABLE_DEBUGGER
+
+  worker_global_scope_ = nullptr;
+}
+
+void ServiceWorkerObject::ObtainWebAgentAndWaitUntilDone() {
+  web_agent_.reset(new web::Agent(
+      options_.web_options,
+      base::Bind(&ServiceWorkerObject::Initialize, base::Unretained(this)),
+      this));
+  web_agent_->WaitUntilDone();
+}
+
+void ServiceWorkerObject::Initialize(web::Context* context) {
+  // Algorithm for "Run Service Worker"
+  // https://w3c.github.io/ServiceWorker/#run-service-worker-algorithm
+
+  // 8.1. Let realmExecutionContext be the result of creating a new JavaScript
+  //      realm given agent and the following customizations:
+  //        For the global object, create a new ServiceWorkerGlobalScope object.
+  //        Let workerGlobalScope be the created object.
+  web_context_ = context;
+  // 8.2. Set serviceWorker’s global object to workerGlobalScope.
+  // 8.3. Let settingsObject be a new environment settings object whose
+  //      algorithms are defined as follows:
+  //      The realm execution context
+  //        Return realmExecutionContext.
+  //      The module map
+  //        Return workerGlobalScope’s module map.
+  //      The API URL character encoding
+  //        Return UTF-8.
+  //      The API base URL
+  //        Return serviceWorker’s script url.
+  //      The origin
+  //        Return its registering service worker client's origin.
+  //      The policy container
+  //        Return workerGlobalScope’s policy container.
+  //      The time origin
+  //        Return the result of coarsening unsafeCreationTime given
+  //        workerGlobalScope’s cross-origin isolated capability.
+  // 8.4. Set settingsObject’s id to a new unique opaque string, creation URL to
+  //      serviceWorker’s script url, top-level creation URL to null, top-level
+  //      origin to an implementation-defined value, target browsing context to
+  //      null, and active service worker to null.
+  web_context_->setup_environment_settings(new WorkerSettings(script_url_));
+  scoped_refptr<ServiceWorkerGlobalScope> service_worker_global_scope =
+      new ServiceWorkerGlobalScope(web_context_->environment_settings());
+  worker_global_scope_ = service_worker_global_scope;
+  web_context_->global_environment()->CreateGlobalObject(
+      service_worker_global_scope, web_context_->environment_settings());
+  DCHECK(!web_context_->GetWindowOrWorkerGlobalScope()->IsWindow());
+  DCHECK(!web_context_->GetWindowOrWorkerGlobalScope()->IsDedicatedWorker());
+  DCHECK(web_context_->GetWindowOrWorkerGlobalScope()->IsServiceWorker());
+  DCHECK(web_context_->GetWindowOrWorkerGlobalScope()->GetWrappableType() ==
+         base::GetTypeId<ServiceWorkerGlobalScope>());
+  DCHECK_EQ(service_worker_global_scope,
+            base::polymorphic_downcast<ServiceWorkerGlobalScope*>(
+                web_context_->GetWindowOrWorkerGlobalScope()));
+
+#if defined(ENABLE_DEBUGGER)
+  debug_module_.reset(new debug::backend::DebugModule(
+      nullptr /* debugger_hooks */, web_context_->global_environment(),
+      nullptr /* render_overlay */, nullptr /* resource_provider */,
+      nullptr /* window */, nullptr /* debugger_state */));
+#endif  // ENABLE_DEBUGGER
+
+  // 8.5. Set workerGlobalScope’s url to serviceWorker’s script url.
+  worker_global_scope_->set_url(
+      web_context_->environment_settings()->base_url());
+  // 8.6. Set workerGlobalScope’s policy container to serviceWorker’s script
+  //      resource’s policy container.
+  // 8.7. Set workerGlobalScope’s type to serviceWorker’s type.
+  // 8.8. Set workerGlobalScope’s force bypass cache for import scripts flag if
+  //      forceBypassCache is true.
+  // 8.9. Create a new WorkerLocation object and associate it with
+  //      workerGlobalScope.
+  // 8.10. If the run CSP initialization for a global object algorithm returns
+  //       "Blocked" when executed upon workerGlobalScope, set startFailed to
+  //       true and abort these steps.
+  // 8.11. If serviceWorker is an active worker, and there are any tasks queued
+  //       in serviceWorker’s containing service worker registration’s task
+  //       queues, queue them to serviceWorker’s event loop’s task queues in the
+  //       same order using their original task sources.
+  // 8.12. Let evaluationStatus be null.
+  // 8.13. If script is a classic script, then:
+  // 8.13.1. Set evaluationStatus to the result of running the classic script
+  //         script.
+
+  bool mute_errors = false;
+  bool succeeded = false;
+  auto* content = LookupScriptResource();
+  DCHECK(content);
+  base::SourceLocation script_location(script_url().spec(), 1, 1);
+  std::string retval = web_context_->script_runner()->Execute(
+      *content, script_location, mute_errors, &succeeded);
+  // 8.13.2. If evaluationStatus.[[Value]] is empty, this means the script was
+  //         not evaluated. Set startFailed to true and abort these steps.
+  // We don't actually have access to an 'evaluationStatus' from ScriptRunner,
+  // so here we have to use the returned 'succeeded' boolean as a proxy for this
+  // step.
+  if (!succeeded) {
+    store_start_failed(true);
+    return;
+  }
+  // 8.14. Otherwise, if script is a module script, then:
+  // 8.14.1. Let evaluationPromise be the result of running the module script
+  //         script, with report errors set to false.
+  // 8.14.2. Assert: evaluationPromise.[[PromiseState]] is not "pending".
+  // 8.14.3. If evaluationPromise.[[PromiseState]] is "rejected":
+  // 8.14.3.1. Set evaluationStatus to
+  //           ThrowCompletion(evaluationPromise.[[PromiseResult]]).
+  // 8.14.4. Otherwise:
+  // 8.14.4.1. Set evaluationStatus to NormalCompletion(undefined).
+  // 8.15. If the script was aborted by the Terminate Service Worker algorithm,
+  //       set startFailed to true and abort these steps.
+  // 8.16. Set serviceWorker’s start status to evaluationStatus.
+  start_status_.reset(new std::string(retval));
+  // 8.17. If script’s has ever been evaluated flag is unset, then:
+  // 8.17.1. For each eventType of settingsObject’s global object's associated
+  //         list of event listeners' event types:
+  // 8.17.1.1. Append eventType to workerGlobalScope’s associated service
+  //           worker's set of event types to handle.
+  // 8.17.1.2. Set script’s has ever been evaluated flag.
+  // 8.18. Run the responsible event loop specified by settingsObject until it
+  //       is destroyed.
+  // 8.19. Empty workerGlobalScope’s list of active timers.
+}
 
 }  // namespace worker
 }  // namespace cobalt
diff --git a/cobalt/worker/service_worker_object.h b/cobalt/worker/service_worker_object.h
index 5ca5e12..80020aa 100644
--- a/cobalt/worker/service_worker_object.h
+++ b/cobalt/worker/service_worker_object.h
@@ -15,9 +15,25 @@
 #ifndef COBALT_WORKER_SERVICE_WORKER_OBJECT_H_
 #define COBALT_WORKER_SERVICE_WORKER_OBJECT_H_
 
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/message_loop/message_loop_current.h"
+#include "cobalt/web/agent.h"
+#include "cobalt/web/context.h"
 #include "cobalt/worker/service_worker_state.h"
+#include "cobalt/worker/worker_global_scope.h"
+#include "starboard/atomic.h"
 #include "url/gurl.h"
 
+#if defined(ENABLE_DEBUGGER)
+#include "cobalt/debug/backend/debug_module.h"  // nogncheck
+#endif                                          // defined(ENABLE_DEBUGGER)
+
 namespace cobalt {
 namespace worker {
 
@@ -30,15 +46,93 @@
 // object. A user agent may terminate service workers at any time it has no
 // event to handle, or detects abnormal operation.
 //   https://w3c.github.io/ServiceWorker/#service-worker-lifetime
-class ServiceWorkerObject {
+class ServiceWorkerObject : public base::MessageLoop::DestructionObserver {
  public:
-  ServiceWorkerObject();
-  ~ServiceWorkerObject() {}
+  // Worker Options needed at thread run time.
+  struct Options {
+    explicit Options(const std::string& name,
+                     network::NetworkModule* network_module)
+        : web_options(name) {
+      web_options.network_module = network_module;
+    }
 
-  ServiceWorkerState state() { return state_; }
+    web::Agent::Options web_options;
+  };
+
+  using ScriptResourceMap = std::map<GURL, std::unique_ptr<std::string>>;
+
+  explicit ServiceWorkerObject(const Options& options);
+  ~ServiceWorkerObject();
+  ServiceWorkerObject(const ServiceWorkerObject&) = delete;
+  ServiceWorkerObject& operator=(const ServiceWorkerObject&) = delete;
+
+  void set_script_url(const GURL& script_url) { script_url_ = script_url; }
+  const GURL& script_url() const { return script_url_; }
+  void set_state(ServiceWorkerState state) { state_ = state; }
+  ServiceWorkerState state() const { return state_; }
+
+  void set_script_resource_map(ScriptResourceMap&& resource_map) {
+    script_resource_map_ = std::move(resource_map);
+  }
+
+  std::string* LookupScriptResource() const;
+  std::string* LookupScriptResource(const GURL& url) const;
+
+  bool is_running() { return web_agent_.get() != nullptr; }
+  web::Agent* web_agent() const { return web_agent_.get(); }
+
+  std::string* start_status() const { return start_status_.get(); }
+
+  scoped_refptr<WorkerGlobalScope> worker_global_scope() {
+    return worker_global_scope_;
+  }
+
+  // From base::MessageLoop::DestructionObserver.
+  void WillDestroyCurrentMessageLoop() override;
+
+  void store_start_failed(bool value) { start_failed_.store(value); }
+  bool load_start_failed() { return start_failed_.load(); }
+
+  void ObtainWebAgentAndWaitUntilDone();
 
  private:
+  // Called by ObtainWebAgentAndWaitUntilDone to perform initialization required
+  // on the dedicated thread.
+  //   https://w3c.github.io/ServiceWorker/#run-service-worker-algorithm
+  void Initialize(web::Context* context);
+
+  // The message loop this object is running on.
+  base::MessageLoop* message_loop() const {
+    return web_agent_ ? web_agent_->message_loop() : nullptr;
+  }
+
+#if defined(ENABLE_DEBUGGER)
+  // The core of the debugging system.
+  std::unique_ptr<debug::backend::DebugModule> debug_module_;
+#endif  // defined(ENABLE_DEBUGGER)
+
+  // The Web Context includes the Script Agent and Realm.
+  std::unique_ptr<web::Agent> web_agent_;
+
+  web::Context* web_context_;
+
+  Options options_;
+
+  // https://w3c.github.io/ServiceWorker/#dfn-state
   ServiceWorkerState state_;
+
+  // https://w3c.github.io/ServiceWorker/#dfn-script-url
+  GURL script_url_;
+
+  // map of content or resources for the worker.
+  ScriptResourceMap script_resource_map_;
+
+  // https://w3c.github.io/ServiceWorker/#service-worker-start-status
+  std::unique_ptr<std::string> start_status_;
+
+  starboard::atomic_bool start_failed_;
+
+  scoped_refptr<WorkerGlobalScope> worker_global_scope_;
 };
 
 }  // namespace worker
diff --git a/cobalt/worker/service_worker_registration.cc b/cobalt/worker/service_worker_registration.cc
index 0d1ebd9..4d711b0 100644
--- a/cobalt/worker/service_worker_registration.cc
+++ b/cobalt/worker/service_worker_registration.cc
@@ -22,10 +22,11 @@
 namespace cobalt {
 namespace worker {
 
+
 ServiceWorkerRegistration::ServiceWorkerRegistration(
     script::EnvironmentSettings* settings,
     worker::ServiceWorkerRegistrationObject* registration)
-    : dom::EventTarget(settings), registration_(registration) {}
+    : web::EventTarget(settings), registration_(registration) {}
 
 script::Handle<script::Promise<void>> ServiceWorkerRegistration::Update() {
   // Todo: Add logic for update()
diff --git a/cobalt/worker/service_worker_registration.h b/cobalt/worker/service_worker_registration.h
index 7ccfd4c..b9c21f5 100644
--- a/cobalt/worker/service_worker_registration.h
+++ b/cobalt/worker/service_worker_registration.h
@@ -20,11 +20,11 @@
 #include <utility>
 
 #include "base/macros.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/script_value_factory.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event_target.h"
 #include "cobalt/worker/navigation_preload_manager.h"
 #include "cobalt/worker/service_worker.h"
 #include "cobalt/worker/service_worker_registration_object.h"
@@ -38,7 +38,7 @@
 // The ServiceWorkerRegistration interface represents a service worker
 // registration within a service worker client realm.
 //   https://w3c.github.io/ServiceWorker/#serviceworker-interface
-class ServiceWorkerRegistration : public dom::EventTarget {
+class ServiceWorkerRegistration : public web::EventTarget {
  public:
   ServiceWorkerRegistration(
       script::EnvironmentSettings* settings,
diff --git a/cobalt/worker/service_worker_registration_map.cc b/cobalt/worker/service_worker_registration_map.cc
new file mode 100644
index 0000000..3f1429e
--- /dev/null
+++ b/cobalt/worker/service_worker_registration_map.cc
@@ -0,0 +1,195 @@
+// Copyright 2022 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_registration_map.h"
+
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "base/trace_event/trace_event.h"
+#include "cobalt/script/exception_message.h"
+#include "cobalt/script/promise.h"
+#include "cobalt/script/script_value.h"
+#include "cobalt/web/context.h"
+#include "cobalt/web/environment_settings.h"
+#include "cobalt/worker/service_worker_registration_object.h"
+#include "cobalt/worker/service_worker_update_via_cache.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+
+namespace cobalt {
+namespace worker {
+
+namespace {
+// Returns the serialized URL excluding the fragment.
+std::string SerializeExcludingFragment(const GURL& url) {
+  url::Replacements<char> replacements;
+  replacements.ClearRef();
+  GURL no_fragment_url = url.ReplaceComponents(replacements);
+  DCHECK(!no_fragment_url.has_ref() || no_fragment_url.ref().empty());
+  DCHECK(!no_fragment_url.is_empty());
+  return no_fragment_url.spec();
+}
+}  // namespace
+
+worker::ServiceWorkerRegistrationObject*
+ServiceWorkerRegistrationMap::MatchServiceWorkerRegistration(
+    const url::Origin& storage_key, const GURL& client_url) {
+  TRACE_EVENT0(
+      "cobalt::worker",
+      "ServiceWorkerRegistrationMap::MatchServiceWorkerRegistration()");
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  // Algorithm for Match Service Worker Registration:
+  //   https://w3c.github.io/ServiceWorker/#scope-match-algorithm
+  GURL matching_scope;
+
+  // 1. Run the following steps atomically.
+  {
+    base::AutoLock lock(mutex_);
+
+    // 2. Let clientURLString be serialized clientURL.
+    std::string client_url_string(client_url.spec());
+
+    // 3. Let matchingScopeString be the empty string.
+    std::string matching_scope_string;
+
+    // 4. Let scopeStringSet be an empty list.
+    std::list<std::string> scope_string_set;
+
+    // 5. For each (entry storage key, entry scope) → registration of
+    // registration map:
+    for (const auto& entry : registration_map_) {
+      // 5.1. If storage key equals entry storage key, then append entry scope
+      // to the end of scopeStringSet.
+      if (entry.first.first == storage_key) {
+        scope_string_set.push_back(entry.first.second);
+      }
+    }
+
+    // 6. Set matchingScopeString to the longest value in scopeStringSet which
+    // the value of clientURLString starts with, if it exists.
+    for (const auto& scope_string : scope_string_set) {
+      bool starts_with =
+          client_url_string.substr(0, scope_string.length()) == scope_string;
+      if (starts_with &&
+          (scope_string.length() > matching_scope_string.length())) {
+        matching_scope_string = scope_string;
+      }
+    }
+
+    // 7. Let matchingScope be null.
+    // 8. If matchingScopeString is not the empty string, then:
+    if (!matching_scope_string.empty()) {
+      // 8.1. Set matchingScope to the result of parsing matchingScopeString.
+      matching_scope = GURL(matching_scope_string);
+
+      // 8.2. Assert: matchingScope’s origin and clientURL’s origin are same
+      // origin.
+      DCHECK_EQ(url::Origin::Create(matching_scope),
+                url::Origin::Create(client_url));
+    }
+  }
+
+  // 9. Return the result of running Get Registration given storage key and
+  // matchingScope.
+  return GetRegistration(storage_key, matching_scope);
+}
+
+ServiceWorkerRegistrationObject* ServiceWorkerRegistrationMap::GetRegistration(
+    const url::Origin& storage_key, const GURL& scope) {
+  TRACE_EVENT0("cobalt::worker",
+               "ServiceWorkerRegistrationMap::GetRegistration()");
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  // Algorithm for Get Registration:
+  //   https://w3c.github.io/ServiceWorker/#get-registration-algorithm
+
+  // 1. Run the following steps atomically.
+  base::AutoLock lock(mutex_);
+
+  // 2. Let scopeString be the empty string.
+  std::string scope_string;
+
+  // 3. If scope is not null, set scopeString to serialized scope with the
+  // exclude fragment flag set.
+  if (!scope.is_empty()) {
+    scope_string = SerializeExcludingFragment(scope);
+  }
+
+  Key registration_key(storage_key, scope_string);
+  // 4. For each (entry storage key, entry scope) → registration of registration
+  // map:
+  for (const auto& entry : registration_map_) {
+    // 4.1. If storage key equals entry storage key and scopeString matches
+    // entry scope, then return registration.
+    if (entry.first == registration_key) {
+      return entry.second.get();
+    }
+  }
+
+  // 5. Return null.
+  return nullptr;
+}
+
+ServiceWorkerRegistrationObject* ServiceWorkerRegistrationMap::SetRegistration(
+    const url::Origin& storage_key, const GURL& scope,
+    const ServiceWorkerUpdateViaCache& update_via_cache) {
+  TRACE_EVENT0("cobalt::worker",
+               "ServiceWorkerRegistrationMap::SetRegistration()");
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  // Algorithm for Set Registration:
+  //   https://w3c.github.io/ServiceWorker/#set-registration-algorithm
+
+  // 1. Run the following steps atomically.
+  base::AutoLock lock(mutex_);
+
+  // 2. Let scopeString be serialized scope with the exclude fragment flag set.
+  std::string scope_string = SerializeExcludingFragment(scope);
+
+  // 3. Let registration be a new service worker registration whose storage key
+  // is set to storage key, scope url is set to scope, and update via cache mode
+  // is set to updateViaCache.
+  ServiceWorkerRegistrationObject* registration(
+      new ServiceWorkerRegistrationObject(storage_key, scope,
+                                          update_via_cache));
+
+  // 4. Set registration map[(storage key, scopeString)] to registration.
+  Key registration_key(storage_key, scope_string);
+  registration_map_.insert(std::make_pair(
+      registration_key,
+      std::unique_ptr<ServiceWorkerRegistrationObject>(registration)));
+
+  // 5. Return registration.
+  return registration;
+}
+
+void ServiceWorkerRegistrationMap::RemoveRegistration(
+    const url::Origin& storage_key, const GURL& scope) {
+  std::string scope_string = SerializeExcludingFragment(scope);
+  Key registration_key(storage_key, scope_string);
+  auto entry = registration_map_.find(registration_key);
+  DCHECK(entry != registration_map_.end());
+  if (entry != registration_map_.end()) {
+    registration_map_.erase(entry);
+  }
+}
+
+}  // namespace worker
+}  // namespace cobalt
diff --git a/cobalt/worker/service_worker_registration_map.h b/cobalt/worker/service_worker_registration_map.h
new file mode 100644
index 0000000..e6603a6
--- /dev/null
+++ b/cobalt/worker/service_worker_registration_map.h
@@ -0,0 +1,77 @@
+// Copyright 2022 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_REGISTRATION_MAP_H_
+#define COBALT_WORKER_SERVICE_WORKER_REGISTRATION_MAP_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_checker.h"
+#include "cobalt/script/exception_message.h"
+#include "cobalt/script/promise.h"
+#include "cobalt/script/script_value.h"
+#include "cobalt/script/script_value_factory.h"
+#include "cobalt/web/environment_settings.h"
+#include "cobalt/worker/service_worker_registration_object.h"
+#include "cobalt/worker/service_worker_update_via_cache.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace cobalt {
+namespace worker {
+
+// Algorithms for the service worker scope to registration map.
+//   https://w3c.github.io/ServiceWorker/#dfn-scope-to-registration-map
+class ServiceWorkerRegistrationMap {
+ public:
+  using Key = std::pair<url::Origin, std::string>;
+
+  // https://w3c.github.io/ServiceWorker/#get-registration-algorithm
+  ServiceWorkerRegistrationObject* GetRegistration(
+      const url::Origin& storage_key, const GURL& scope);
+
+  // https://w3c.github.io/ServiceWorker/#set-registration-algorithm
+  ServiceWorkerRegistrationObject* SetRegistration(
+      const url::Origin& storage_key, const GURL& scope,
+      const ServiceWorkerUpdateViaCache& update_via_cache);
+
+  // https://w3c.github.io/ServiceWorker/#scope-match-algorithm
+  ServiceWorkerRegistrationObject* MatchServiceWorkerRegistration(
+      const url::Origin& storage_key, const GURL& client_url);
+
+  void RemoveRegistration(const url::Origin& storage_key, const GURL& scope);
+
+ private:
+  // ThreadChecker for use by the methods operating on the registration map.
+  THREAD_CHECKER(thread_checker_);
+
+  // A registration map is an ordered map where the keys are (storage key,
+  // serialized scope urls) and the values are service worker registrations.
+  //   https://w3c.github.io/ServiceWorker/#dfn-scope-to-registration-map
+  std::map<Key, std::unique_ptr<ServiceWorkerRegistrationObject>>
+      registration_map_;
+
+  // This lock is to allow atomic operations on the registration map.
+  base::Lock mutex_;
+};
+
+}  // namespace worker
+}  // namespace cobalt
+
+#endif  // COBALT_WORKER_SERVICE_WORKER_REGISTRATION_MAP_H_
diff --git a/cobalt/worker/service_worker_registration_object.cc b/cobalt/worker/service_worker_registration_object.cc
index f0081ec..921eb8f 100644
--- a/cobalt/worker/service_worker_registration_object.cc
+++ b/cobalt/worker/service_worker_registration_object.cc
@@ -14,6 +14,8 @@
 
 #include "cobalt/worker/service_worker_registration_object.h"
 
+#include <string>
+
 #include "cobalt/worker/service_worker_update_via_cache.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -28,5 +30,32 @@
       scope_url_(scope_url),
       update_via_cache_mode_(update_via_cache_mode) {}
 
+ServiceWorkerObject* ServiceWorkerRegistrationObject::GetNewestWorker() {
+  // Algorithm for Get Newest Worker:
+  //   https://w3c.github.io/ServiceWorker/#get-newest-worker
+  // 1. Run the following steps atomically.
+  base::AutoLock lock(mutex_);
+
+  // 2. Let newestWorker be null.
+  ServiceWorkerObject* newest_worker = nullptr;
+
+  // 3. If registration’s installing worker is not null, set newestWorker to
+  // registration’s installing worker.
+  if (installing_worker_) {
+    newest_worker = installing_worker_;
+    // 4. Else if registration’s waiting worker is not null, set newestWorker to
+    // registration’s waiting worker.
+  } else if (waiting_worker_) {
+    newest_worker = waiting_worker_;
+    // 5. Else if registration’s active worker is not null, set newestWorker to
+    // registration’s active worker.
+  } else if (active_worker_) {
+    newest_worker = active_worker_;
+  }
+
+  // 6. Return newestWorker.
+  return newest_worker;
+}
+
 }  // namespace worker
 }  // namespace cobalt
diff --git a/cobalt/worker/service_worker_registration_object.h b/cobalt/worker/service_worker_registration_object.h
index 9980ead..5b218a4 100644
--- a/cobalt/worker/service_worker_registration_object.h
+++ b/cobalt/worker/service_worker_registration_object.h
@@ -15,6 +15,9 @@
 #ifndef COBALT_WORKER_SERVICE_WORKER_REGISTRATION_OBJECT_H_
 #define COBALT_WORKER_SERVICE_WORKER_REGISTRATION_OBJECT_H_
 
+#include <memory>
+
+#include "base/synchronization/lock.h"
 #include "cobalt/worker/service_worker_object.h"
 #include "cobalt/worker/service_worker_update_via_cache.h"
 #include "url/gurl.h"
@@ -40,18 +43,37 @@
 
   const url::Origin& storage_key() const { return storage_key_; }
   const GURL& scope_url() const { return scope_url_; }
+  void set_update_via_cache_mode(
+      const ServiceWorkerUpdateViaCache& update_via_cache_mode) {
+    update_via_cache_mode_ = update_via_cache_mode;
+  }
   const ServiceWorkerUpdateViaCache& update_via_cache_mode() const {
     return update_via_cache_mode_;
   }
 
+  void set_installing_worker(ServiceWorkerObject* worker) {
+    installing_worker_ = worker;
+  }
   ServiceWorkerObject* installing_worker() const { return installing_worker_; }
+  void set_waiting_worker(ServiceWorkerObject* worker) {
+    waiting_worker_ = worker;
+  }
   ServiceWorkerObject* waiting_worker() const { return waiting_worker_; }
+  void set_active_worker(ServiceWorkerObject* worker) {
+    active_worker_ = worker;
+  }
   ServiceWorkerObject* active_worker() const { return active_worker_; }
 
+  // https://w3c.github.io/ServiceWorker/#get-newest-worker
+  ServiceWorkerObject* GetNewestWorker();
+
  private:
-  const url::Origin& storage_key_;
-  const GURL& scope_url_;
-  const ServiceWorkerUpdateViaCache& update_via_cache_mode_;
+  // This lock is to allow atomic operations on the registration object.
+  base::Lock mutex_;
+
+  url::Origin storage_key_;
+  GURL scope_url_;
+  ServiceWorkerUpdateViaCache update_via_cache_mode_;
   ServiceWorkerObject* installing_worker_ = nullptr;
   ServiceWorkerObject* waiting_worker_ = nullptr;
   ServiceWorkerObject* active_worker_ = nullptr;
diff --git a/cobalt/worker/worker.cc b/cobalt/worker/worker.cc
index faf3dc4..b9f04d1 100644
--- a/cobalt/worker/worker.cc
+++ b/cobalt/worker/worker.cc
@@ -17,6 +17,8 @@
 #include <string>
 #include <utility>
 
+#include "base/location.h"
+#include "base/logging.h"
 #include "base/message_loop/message_loop.h"
 #include "base/threading/thread.h"
 #include "cobalt/script/environment_settings.h"
@@ -60,6 +62,9 @@
 }
 
 void Worker::WillDestroyCurrentMessageLoop() {
+#if defined(ENABLE_DEBUGGER)
+  debug_module_.reset();
+#endif  // ENABLE_DEBUGGER
   // Destroy members that were constructed in the worker thread.
   loader_.reset();
   worker_global_scope_ = nullptr;
@@ -69,10 +74,8 @@
 }
 
 void Worker::Initialize(const Options& options, web::Context* context) {
-  LOG(INFO) << "Look at me, I'm running a worker thread";
-
   // 7. Let realm execution context be the result of creating a new
-  // JavaScript realm given agent and the following customizations:
+  //    JavaScript realm given agent and the following customizations:
   web_context_ = context;
   //    . For the global object, if is shared is true, create a new
   //      SharedWorkerGlobalScope object. Otherwise, create a new
@@ -80,7 +83,7 @@
   // TODO: Actual type here should depend on derived class (e.g. dedicated,
   // shared, service)
   web_context_->setup_environment_settings(
-      new WorkerSettings(options.outside_port, options.url));
+      new WorkerSettings(options.url, options.outside_port));
   // 8. Let worker global scope be the global object of realm execution
   //    context's Realm component.
   scoped_refptr<DedicatedWorkerGlobalScope> dedicated_worker_global_scope =
@@ -92,6 +95,21 @@
   //    inside settings be the result.
   web_context_->global_environment()->CreateGlobalObject(
       dedicated_worker_global_scope, web_context_->environment_settings());
+  DCHECK(!web_context_->GetWindowOrWorkerGlobalScope()->IsWindow());
+  DCHECK(web_context_->GetWindowOrWorkerGlobalScope()->IsDedicatedWorker());
+  DCHECK(!web_context_->GetWindowOrWorkerGlobalScope()->IsServiceWorker());
+  DCHECK(web_context_->GetWindowOrWorkerGlobalScope()->GetWrappableType() ==
+         base::GetTypeId<DedicatedWorkerGlobalScope>());
+  DCHECK_EQ(dedicated_worker_global_scope,
+            base::polymorphic_downcast<DedicatedWorkerGlobalScope*>(
+                web_context_->GetWindowOrWorkerGlobalScope()));
+
+#if defined(ENABLE_DEBUGGER)
+  debug_module_.reset(new debug::backend::DebugModule(
+      nullptr /* debugger_hooks */, web_context_->global_environment(),
+      nullptr /* render_overlay */, nullptr /* resource_provider */,
+      nullptr /* window */, nullptr /* debugger_state */));
+#endif  // ENABLE_DEBUGGER
 
   // 10. Set worker global scope's name to the value of options's name member.
   dedicated_worker_global_scope->set_name(options.web_options.name);
@@ -107,6 +125,7 @@
   // 13. Let destination be "sharedworker" if is shared is true, and
   // "worker" otherwise.
   // 14. Obtain script
+
   Obtain();
 }
 
@@ -168,7 +187,9 @@
 
 void Worker::OnReadyToExecute() {
   DCHECK(content_);
-  Execute(*content_, base::SourceLocation("[object Worker::RunLoop]", 1, 1));
+  Execute(*content_,
+          base::SourceLocation(
+              web_context_->environment_settings()->base_url().spec(), 1, 1));
   content_.reset();
 }
 
@@ -198,11 +219,8 @@
 
   bool mute_errors = false;
   bool succeeded = false;
-  LOG(INFO) << "Script Executing \"" << content << "\".";
   std::string retval = web_context_->script_runner()->Execute(
       content, script_location, mute_errors, &succeeded);
-  LOG(INFO) << "Script Executed " << (succeeded ? "and" : ", but not")
-            << " succeeded: \"" << retval << "\"";
 
   // 24. Enable outside port's port message queue.
   // 25. If is shared is false, enable the port message queue of the worker's
@@ -223,6 +241,7 @@
 
 Worker::~Worker() {
   // 29. Clear the worker global scope's map of active timers.
+  worker_global_scope_->DestroyTimers();
   // 30. Disentangle all the ports in the list of the worker's ports.
   // 31. Empty worker global scope's owner set.
   if (web_agent_) {
diff --git a/cobalt/worker/worker.h b/cobalt/worker/worker.h
index 96b9730..f4fe538 100644
--- a/cobalt/worker/worker.h
+++ b/cobalt/worker/worker.h
@@ -19,9 +19,13 @@
 #include <string>
 
 #include "base/bind.h"
+#include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/message_loop/message_loop_current.h"
+#include "base/synchronization/waitable_event.h"
 #include "base/threading/thread.h"
 #include "cobalt/csp/content_security_policy.h"
+#include "cobalt/loader/script_loader_factory.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/execution_state.h"
 #include "cobalt/script/global_environment.h"
@@ -31,6 +35,7 @@
 #include "cobalt/script/wrappable.h"
 #include "cobalt/web/agent.h"
 #include "cobalt/web/context.h"
+#include "cobalt/web/environment_settings.h"
 #include "cobalt/worker/dedicated_worker_global_scope.h"
 #include "cobalt/worker/message_port.h"
 #include "cobalt/worker/worker_global_scope.h"
@@ -38,6 +43,10 @@
 #include "cobalt/worker/worker_settings.h"
 #include "url/gurl.h"
 
+#if defined(ENABLE_DEBUGGER)
+#include "cobalt/debug/backend/debug_module.h"  // nogncheck
+#endif                                          // defined(ENABLE_DEBUGGER)
+
 namespace cobalt {
 namespace worker {
 
@@ -55,13 +64,15 @@
     // Parameters from 'Run a worker' step 9.1 in the spec.
     //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#dom-worker
     GURL url;
-    script::EnvironmentSettings* outside_settings;
+    web::EnvironmentSettings* outside_settings;
     MessagePort* outside_port;
     WorkerOptions options;
   };
 
   Worker();
   ~Worker();
+  Worker(const Worker&) = delete;
+  Worker& operator=(const Worker&) = delete;
 
   // Start the worker thread. Returns true if successful.
   bool Run(const Options& options);
@@ -73,7 +84,9 @@
   MessagePort* message_port() const { return message_port_.get(); }
 
   // The message loop this object is running on.
-  base::MessageLoop* message_loop() const { return web_agent_->message_loop(); }
+  base::MessageLoop* message_loop() const {
+    return web_agent_ ? web_agent_->message_loop() : nullptr;
+  }
 
   void PostMessage(const std::string& message);
 
@@ -81,7 +94,6 @@
   void WillDestroyCurrentMessageLoop() override;
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(Worker);
   // Called by |Run| to perform initialization required on the dedicated
   // thread.
   void Initialize(const Options& options, web::Context* context);
@@ -97,6 +109,11 @@
 
   web::Agent* web_agent() const { return web_agent_.get(); }
 
+#if defined(ENABLE_DEBUGGER)
+  // The core of the debugging system.
+  std::unique_ptr<debug::backend::DebugModule> debug_module_;
+#endif  // defined(ENABLE_DEBUGGER)
+
   // The Web Context includes the Script Agent and Realm.
   std::unique_ptr<web::Agent> web_agent_;
 
diff --git a/cobalt/worker/worker_global_scope.cc b/cobalt/worker/worker_global_scope.cc
index 6fd52ec..6ddcf69 100644
--- a/cobalt/worker/worker_global_scope.cc
+++ b/cobalt/worker/worker_global_scope.cc
@@ -14,10 +14,38 @@
 
 #include "cobalt/worker/worker_global_scope.h"
 
+#include "cobalt/script/environment_settings.h"
+#include "cobalt/web/window_or_worker_global_scope.h"
+
 namespace cobalt {
 namespace worker {
 WorkerGlobalScope::WorkerGlobalScope(script::EnvironmentSettings* settings)
-    : EventTarget(settings) {}
+    : web::WindowOrWorkerGlobalScope(settings),
+      ALLOW_THIS_IN_INITIALIZER_LIST(
+          window_timers_(this, /*dom_stat_tracker=*/NULL, debugger_hooks(),
+                         // TODO (b/233788170): once application state is
+                         // available, update this to use the actual state.
+                         base::ApplicationState::kApplicationStateStarted)) {}
+
+int WorkerGlobalScope::SetTimeout(
+    const dom::WindowTimers::TimerCallbackArg& handler, int timeout) {
+  return window_timers_.SetTimeout(handler, timeout);
+}
+
+void WorkerGlobalScope::ClearTimeout(int handle) {
+  window_timers_.ClearTimeout(handle);
+}
+
+int WorkerGlobalScope::SetInterval(
+    const dom::WindowTimers::TimerCallbackArg& handler, int timeout) {
+  return window_timers_.SetInterval(handler, timeout);
+}
+
+void WorkerGlobalScope::ClearInterval(int handle) {
+  window_timers_.ClearInterval(handle);
+}
+
+void WorkerGlobalScope::DestroyTimers() { window_timers_.DisableCallbacks(); }
 
 }  // namespace worker
 }  // namespace cobalt
diff --git a/cobalt/worker/worker_global_scope.h b/cobalt/worker/worker_global_scope.h
index bfdc46f..662361f 100644
--- a/cobalt/worker/worker_global_scope.h
+++ b/cobalt/worker/worker_global_scope.h
@@ -20,24 +20,31 @@
 
 #include "base/memory/ref_counted.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/event_target.h"
-#include "cobalt/dom/event_target_listener_info.h"
+#include "cobalt/dom/window_timers.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/script/sequence.h"
 #include "cobalt/script/value_handle.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event_target.h"
+#include "cobalt/web/event_target_listener_info.h"
+#include "cobalt/web/window_or_worker_global_scope.h"
 #include "net/url_request/url_request.h"
 #include "url/gurl.h"
 
 namespace cobalt {
 namespace worker {
-
 // Implementation of the WorkerGlobalScope common interface.
 //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#dedicated-workers-and-the-workerglobalscope-interface
 
-class WorkerGlobalScope : public dom::EventTarget {
+class WorkerGlobalScope : public web::WindowOrWorkerGlobalScope {
  public:
+  typedef dom::WindowTimers::TimerCallback TimerCallback;
+
   explicit WorkerGlobalScope(script::EnvironmentSettings* settings);
+  WorkerGlobalScope(const WorkerGlobalScope&) = delete;
+  WorkerGlobalScope& operator=(const WorkerGlobalScope&) = delete;
+
+  virtual void Initialize() = 0;
 
   // Web API: WorkerGlobalScope
   //
@@ -45,53 +52,75 @@
 
   void ImportScripts(const std::vector<std::string>& urls) {}
 
-  virtual void Initialize() = 0;
-
   void set_url(const GURL& url) { url_ = url; }
 
   const GURL Url() const { return url_; }
 
-  const dom::EventTargetListenerInfo::EventListenerScriptValue*
+  const web::EventTargetListenerInfo::EventListenerScriptValue*
   onlanguagechange() {
     return GetAttributeEventListener(base::Tokens::languagechange());
   }
   void set_onlanguagechange(
-      const dom::EventTargetListenerInfo::EventListenerScriptValue&
+      const web::EventTargetListenerInfo::EventListenerScriptValue&
           event_listener) {
     SetAttributeEventListener(base::Tokens::languagechange(), event_listener);
   }
 
-  const dom::EventTargetListenerInfo::EventListenerScriptValue*
+  const web::EventTargetListenerInfo::EventListenerScriptValue*
   onrejectionhandled() {
     return GetAttributeEventListener(base::Tokens::rejectionhandled());
   }
   void set_onrejectionhandled(
-      const dom::EventTargetListenerInfo::EventListenerScriptValue&
+      const web::EventTargetListenerInfo::EventListenerScriptValue&
           event_listener) {
     SetAttributeEventListener(base::Tokens::rejectionhandled(), event_listener);
   }
-  const dom::EventTargetListenerInfo::EventListenerScriptValue*
+  const web::EventTargetListenerInfo::EventListenerScriptValue*
   onunhandledrejection() {
     return GetAttributeEventListener(base::Tokens::unhandledrejection());
   }
   void set_onunhandledrejection(
-      const dom::EventTargetListenerInfo::EventListenerScriptValue&
+      const web::EventTargetListenerInfo::EventListenerScriptValue&
           event_listener) {
     SetAttributeEventListener(base::Tokens::unhandledrejection(),
                               event_listener);
   }
 
+  // Web API: WindowTimers (implements)
+  //   https://www.w3.org/TR/html50/webappapis.html#timers
+  //
+  int SetTimeout(const dom::WindowTimers::TimerCallbackArg& handler) {
+    return SetTimeout(handler, 0);
+  }
+
+  int SetTimeout(const dom::WindowTimers::TimerCallbackArg& handler,
+                 int timeout);
+
+  void ClearTimeout(int handle);
+
+  int SetInterval(const dom::WindowTimers::TimerCallbackArg& handler) {
+    return SetInterval(handler, 0);
+  }
+
+  int SetInterval(const dom::WindowTimers::TimerCallbackArg& handler,
+                  int timeout);
+
+  void ClearInterval(int handle);
+
+  void DestroyTimers();
+
+
   DEFINE_WRAPPABLE_TYPE(WorkerGlobalScope);
 
  protected:
   virtual ~WorkerGlobalScope() {}
 
  private:
-  DISALLOW_COPY_AND_ASSIGN(WorkerGlobalScope);
-
   // WorkerGlobalScope Attribute
   // https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#concept-workerglobalscope-url
   GURL url_;
+
+  dom::WindowTimers window_timers_;
 };
 
 }  // namespace worker
diff --git a/cobalt/worker/worker_location.h b/cobalt/worker/worker_location.h
new file mode 100644
index 0000000..584df37
--- /dev/null
+++ b/cobalt/worker/worker_location.h
@@ -0,0 +1,47 @@
+// Copyright 2022 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_WORKER_LOCATION_H_
+#define COBALT_WORKER_WORKER_LOCATION_H_
+
+#include <string>
+
+#include "cobalt/script/wrappable.h"
+#include "cobalt/web/location_base.h"
+#include "url/gurl.h"
+
+namespace cobalt {
+namespace worker {
+
+// WorkerLocation objects provide a representation of the address of the active
+// script of the web context.
+//   https://www.w3.org/TR/html50/workers.html#worker-locations
+class WorkerLocation : public web::LocationBase {
+ public:
+  // If any navigation is triggered, all these callbacks should be provided,
+  // otherwise they can be empty.
+  explicit WorkerLocation(const GURL& url) : web::LocationBase(url) {}
+  WorkerLocation(const WorkerLocation&) = delete;
+  WorkerLocation& operator=(const WorkerLocation&) = delete;
+
+  DEFINE_WRAPPABLE_TYPE(WorkerLocation);
+
+ private:
+  ~WorkerLocation() override {}
+};
+
+}  // namespace worker
+}  // namespace cobalt
+
+#endif  // COBALT_WORKER_WORKER_LOCATION_H_
diff --git a/cobalt/dom/blob_property_bag.idl b/cobalt/worker/worker_location.idl
similarity index 71%
copy from cobalt/dom/blob_property_bag.idl
copy to cobalt/worker/worker_location.idl
index 88f477f..43d2594 100644
--- a/cobalt/dom/blob_property_bag.idl
+++ b/cobalt/worker/worker_location.idl
@@ -1,4 +1,4 @@
-// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+// Copyright 2022 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.
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// https://www.w3.org/TR/FileAPI/#dfn-BlobPropertyBag
-dictionary BlobPropertyBag {
-  DOMString type = "";
-};
\ No newline at end of file
+// https://www.w3.org/TR/html50/workers.html#worker-locations
+
+[Exposed = Worker, Unforgeable] interface WorkerLocation {};
+WorkerLocation implements URLUtils;
diff --git a/cobalt/worker/worker_location_test.cc b/cobalt/worker/worker_location_test.cc
new file mode 100644
index 0000000..dd767be
--- /dev/null
+++ b/cobalt/worker/worker_location_test.cc
@@ -0,0 +1,128 @@
+// Copyright 2016 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/worker_location.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace cobalt {
+namespace worker {
+
+// Note: The following tests duplicate the tests in URLUtilsTest, because
+// WorkerLocation implements URLUtils.
+
+TEST(WorkerLocationTest, GettersShouldReturnExpectedFormat) {
+  scoped_refptr<WorkerLocation> worker_location(new WorkerLocation(
+      GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+
+  EXPECT_EQ("https://user:pass@google.com:99/foo;bar?q=a#ref",
+            worker_location->href());
+  EXPECT_EQ("https:", worker_location->protocol());
+  EXPECT_EQ("google.com:99", worker_location->host());
+  EXPECT_EQ("google.com", worker_location->hostname());
+  EXPECT_EQ("99", worker_location->port());
+  EXPECT_EQ("/foo;bar", worker_location->pathname());
+  EXPECT_EQ("#ref", worker_location->hash());
+  EXPECT_EQ("?q=a", worker_location->search());
+}
+
+TEST(WorkerLocationTest, SetHref) {
+  scoped_refptr<WorkerLocation> worker_location(new WorkerLocation(
+      GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  worker_location->set_href("http://www.youtube.com");
+  EXPECT_TRUE(worker_location->url().is_valid());
+  EXPECT_EQ("http://www.youtube.com/", worker_location->href());
+}
+
+TEST(WorkerLocationTest, SetProtocolShouldWorkAsExpected) {
+  scoped_refptr<WorkerLocation> worker_location(new WorkerLocation(
+      GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  worker_location->set_protocol("http");
+  EXPECT_TRUE(worker_location->url().is_valid());
+  EXPECT_EQ("http://user:pass@google.com:99/foo;bar?q=a#ref",
+            worker_location->href());
+}
+
+TEST(WorkerLocationTest, SetHostShouldWorkAsExpected) {
+  scoped_refptr<WorkerLocation> worker_location(new WorkerLocation(
+      GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  worker_location->set_host("youtube.com");
+  EXPECT_TRUE(worker_location->url().is_valid());
+  EXPECT_EQ("https://user:pass@youtube.com:99/foo;bar?q=a#ref",
+            worker_location->href());
+
+  worker_location->set_host("google.com:100");
+  EXPECT_TRUE(worker_location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:100/foo;bar?q=a#ref",
+            worker_location->href());
+}
+
+TEST(WorkerLocationTest, SetHostnameShouldWorkAsExpected) {
+  scoped_refptr<WorkerLocation> worker_location(new WorkerLocation(
+      GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  worker_location->set_hostname("youtube.com");
+  EXPECT_TRUE(worker_location->url().is_valid());
+  EXPECT_EQ("https://user:pass@youtube.com:99/foo;bar?q=a#ref",
+            worker_location->href());
+}
+
+TEST(WorkerLocationTest, SetPortShouldWorkAsExpected) {
+  scoped_refptr<WorkerLocation> worker_location(new WorkerLocation(
+      GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  worker_location->set_port("100");
+  EXPECT_TRUE(worker_location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:100/foo;bar?q=a#ref",
+            worker_location->href());
+}
+
+TEST(WorkerLocationTest, SetPathnameShouldWorkAsExpected) {
+  scoped_refptr<WorkerLocation> worker_location(new WorkerLocation(
+      GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  worker_location->set_pathname("baz");
+  EXPECT_TRUE(worker_location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:99/baz?q=a#ref",
+            worker_location->href());
+}
+
+TEST(WorkerLocationTest, SetHashShouldWorkAsExpected) {
+  scoped_refptr<WorkerLocation> worker_location(new WorkerLocation(
+      GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  worker_location->set_hash("hash");
+  EXPECT_TRUE(worker_location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:99/foo;bar?q=a#hash",
+            worker_location->href());
+
+  worker_location->set_hash("#hash2");
+  EXPECT_TRUE(worker_location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:99/foo;bar?q=a#hash2",
+            worker_location->href());
+}
+
+TEST(WorkerLocationTest, SetSearchShouldWorkAsExpected) {
+  scoped_refptr<WorkerLocation> worker_location(new WorkerLocation(
+      GURL("https://user:pass@google.com:99/foo;bar?q=a#ref")));
+  worker_location->set_search("b=c");
+  EXPECT_TRUE(worker_location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:99/foo;bar?b=c#ref",
+            worker_location->href());
+
+  worker_location->set_search("?d=e");
+  EXPECT_TRUE(worker_location->url().is_valid());
+  EXPECT_EQ("https://user:pass@google.com:99/foo;bar?d=e#ref",
+            worker_location->href());
+}
+
+}  // namespace worker
+}  // namespace cobalt
diff --git a/cobalt/worker/worker_navigator.cc b/cobalt/worker/worker_navigator.cc
new file mode 100644
index 0000000..5f515ac
--- /dev/null
+++ b/cobalt/worker/worker_navigator.cc
@@ -0,0 +1,37 @@
+// Copyright 2022 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/worker_navigator.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/optional.h"
+#include "cobalt/script/script_value_factory.h"
+#include "cobalt/web/navigator_base.h"
+#include "cobalt/web/navigator_ua_data.h"
+#include "starboard/configuration_constants.h"
+
+namespace cobalt {
+namespace worker {
+
+WorkerNavigator::WorkerNavigator(
+    script::EnvironmentSettings* settings, const std::string& user_agent,
+    web::UserAgentPlatformInfo* platform_info, const std::string& language,
+    script::ScriptValueFactory* script_value_factory)
+    : web::NavigatorBase(settings, user_agent, platform_info, language,
+                         script_value_factory) {}
+
+}  // namespace worker
+}  // namespace cobalt
diff --git a/cobalt/worker/worker_navigator.h b/cobalt/worker/worker_navigator.h
new file mode 100644
index 0000000..0aec328
--- /dev/null
+++ b/cobalt/worker/worker_navigator.h
@@ -0,0 +1,52 @@
+// Copyright 2022 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_WORKER_NAVIGATOR_H_
+#define COBALT_WORKER_WORKER_NAVIGATOR_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "cobalt/script/promise.h"
+#include "cobalt/script/wrappable.h"
+#include "cobalt/web/navigator_base.h"
+#include "cobalt/web/navigator_ua_data.h"
+
+namespace cobalt {
+namespace worker {
+
+// The WorkerNavigator object represents the identity and state of the user
+// agent (the client).
+// https://html.spec.whatwg.org/commit-snapshots/9fe65ccaf193c8c0d93fd3638b4c8e935fc28213/#the-workernavigator-object
+
+class WorkerNavigator : public web::NavigatorBase {
+ public:
+  WorkerNavigator(script::EnvironmentSettings* settings,
+                  const std::string& user_agent,
+                  web::UserAgentPlatformInfo* platform_info,
+                  const std::string& language,
+                  script::ScriptValueFactory* script_value_factory);
+  WorkerNavigator(const WorkerNavigator&) = delete;
+  WorkerNavigator& operator=(const WorkerNavigator&) = delete;
+
+  DEFINE_WRAPPABLE_TYPE(WorkerNavigator);
+
+ private:
+  ~WorkerNavigator() override {}
+};
+
+}  // namespace worker
+}  // namespace cobalt
+
+#endif  // COBALT_WORKER_WORKER_NAVIGATOR_H_
diff --git a/cobalt/worker/worker_navigator.idl b/cobalt/worker/worker_navigator.idl
new file mode 100644
index 0000000..21fbab4
--- /dev/null
+++ b/cobalt/worker/worker_navigator.idl
@@ -0,0 +1,26 @@
+// Copyright 2022 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.
+
+// https://html.spec.whatwg.org/multipage/workers.html#the-workernavigator-object
+
+interface WorkerNavigator {
+  // From partial interface definition:
+  // https://w3c.github.io/ServiceWorker/#navigator-serviceworker
+  readonly attribute ServiceWorkerContainer serviceWorker;
+};
+
+WorkerNavigator implements NavigatorID;
+WorkerNavigator implements NavigatorUA;
+WorkerNavigator implements NavigatorLanguage;
+WorkerNavigator implements NavigatorOnline;
diff --git a/cobalt/worker/worker_settings.cc b/cobalt/worker/worker_settings.cc
index 3a0ae01..a094d08 100644
--- a/cobalt/worker/worker_settings.cc
+++ b/cobalt/worker/worker_settings.cc
@@ -23,8 +23,8 @@
 namespace cobalt {
 namespace worker {
 // TODO: Only use NullDebuggerHooks if !ENABLE_DEBUGGER.
-WorkerSettings::WorkerSettings(worker::MessagePort* message_port,
-                               const GURL& base)
+WorkerSettings::WorkerSettings(const GURL& base,
+                               worker::MessagePort* message_port)
     : web::EnvironmentSettings(), message_port_(message_port) {
   set_base_url(base);
 }
diff --git a/cobalt/worker/worker_settings.h b/cobalt/worker/worker_settings.h
index 2c633eb..cc185c4 100644
--- a/cobalt/worker/worker_settings.h
+++ b/cobalt/worker/worker_settings.h
@@ -28,7 +28,8 @@
 
 class WorkerSettings : public web::EnvironmentSettings {
  public:
-  WorkerSettings(worker::MessagePort* message_port, const GURL& base);
+  explicit WorkerSettings(const GURL& base,
+                          worker::MessagePort* message_port = nullptr);
 
   worker::MessagePort* message_port() const { return message_port_; }
 
diff --git a/cobalt/xhr/BUILD.gn b/cobalt/xhr/BUILD.gn
index 4c5532f..75a2c80 100644
--- a/cobalt/xhr/BUILD.gn
+++ b/cobalt/xhr/BUILD.gn
@@ -13,6 +13,9 @@
 # limitations under the License.
 
 static_library("xhr") {
+  # Creates cycle with //cobalt/dom through xml_document.h
+  check_includes = false
+
   has_pedantic_warnings = true
   sources = [
     "url_fetcher_buffer_writer.cc",
@@ -24,12 +27,12 @@
   ]
 
   deps = [
+    ":global_stats",
     "//cobalt/base",
-    "//cobalt/dom",
-    "//cobalt/dom:dom_exception",
     "//cobalt/dom_parser",
     "//cobalt/loader",
     "//cobalt/script",
+    "//cobalt/web",
     "//nb",
     "//net",
     "//starboard",
@@ -44,6 +47,16 @@
   }
 }
 
+static_library("global_stats") {
+  has_pedantic_warnings = true
+  sources = [
+    "global_stats.cc",
+    "global_stats.h",
+  ]
+
+  deps = [ "//cobalt/base" ]
+}
+
 target(gtest_target_type, "xhr_test") {
   testonly = true
   has_pedantic_warnings = true
@@ -51,10 +64,12 @@
   deps = [
     ":xhr",
     "//cobalt/base",
-    "//cobalt/dom",
-    "//cobalt/dom:dom_exception",
+    "//cobalt/browser",
+    "//cobalt/debug",
     "//cobalt/dom/testing:dom_testing",
     "//cobalt/test:run_all_unittests",
+    "//cobalt/web:dom_exception",
+    "//cobalt/web/testing:web_testing",
     "//testing/gmock",
     "//testing/gtest",
     "//url",
diff --git a/cobalt/xhr/global_stats.cc b/cobalt/xhr/global_stats.cc
new file mode 100644
index 0000000..d6dd0f7
--- /dev/null
+++ b/cobalt/xhr/global_stats.cc
@@ -0,0 +1,48 @@
+// Copyright 2014 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/xhr/global_stats.h"
+
+#include "base/memory/singleton.h"
+#include "cobalt/base/c_val.h"
+
+namespace cobalt {
+namespace xhr {
+
+// static
+GlobalStats* GlobalStats::GetInstance() {
+  return base::Singleton<GlobalStats>::get();
+}
+
+GlobalStats::GlobalStats()
+    : num_xhrs_("Count.XHR", 0, "Total number of currently active XHRs."),
+      xhr_memory_("Memory.XHR", 0, "Memory allocated by XHRs in bytes.") {}
+
+GlobalStats::~GlobalStats() {}
+
+bool GlobalStats::CheckNoLeaks() { return num_xhrs_ == 0 && xhr_memory_ == 0; }
+
+void GlobalStats::Add(xhr::XMLHttpRequest* object) { ++num_xhrs_; }
+
+void GlobalStats::Remove(xhr::XMLHttpRequest* object) { --num_xhrs_; }
+
+void GlobalStats::IncreaseXHRMemoryUsage(size_t delta) { xhr_memory_ += delta; }
+
+void GlobalStats::DecreaseXHRMemoryUsage(size_t delta) {
+  DCHECK_GE(xhr_memory_.value(), delta);
+  xhr_memory_ -= delta;
+}
+
+}  // namespace xhr
+}  // namespace cobalt
diff --git a/cobalt/xhr/global_stats.h b/cobalt/xhr/global_stats.h
new file mode 100644
index 0000000..915656b
--- /dev/null
+++ b/cobalt/xhr/global_stats.h
@@ -0,0 +1,55 @@
+// Copyright 2014 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_XHR_GLOBAL_STATS_H_
+#define COBALT_XHR_GLOBAL_STATS_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/singleton.h"
+#include "cobalt/base/c_val.h"
+
+namespace cobalt {
+namespace xhr {
+class XMLHttpRequest;
+
+// This singleton class is used to track DOM-related statistics.
+class GlobalStats {
+ public:
+  GlobalStats(const GlobalStats&) = delete;
+  GlobalStats& operator=(const GlobalStats&) = delete;
+
+  static GlobalStats* GetInstance();
+
+  bool CheckNoLeaks();
+
+  void Add(xhr::XMLHttpRequest* object);
+  void Remove(xhr::XMLHttpRequest* object);
+  void IncreaseXHRMemoryUsage(size_t delta);
+  void DecreaseXHRMemoryUsage(size_t delta);
+
+ private:
+  GlobalStats();
+  ~GlobalStats();
+
+  // XHR-related tracking
+  base::CVal<int> num_xhrs_;
+  base::CVal<base::cval::SizeInBytes> xhr_memory_;
+
+  friend struct base::DefaultSingletonTraits<GlobalStats>;
+};
+
+}  // namespace xhr
+}  // namespace cobalt
+
+#endif  // COBALT_XHR_GLOBAL_STATS_H_
diff --git a/cobalt/xhr/xml_http_request.cc b/cobalt/xhr/xml_http_request.cc
index acacfd2..831b5cb 100644
--- a/cobalt/xhr/xml_http_request.cc
+++ b/cobalt/xhr/xml_http_request.cc
@@ -27,9 +27,7 @@
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/base/source_location.h"
 #include "cobalt/base/tokens.h"
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/dom_settings.h"
-#include "cobalt/dom/global_stats.h"
 #include "cobalt/dom/performance.h"
 #include "cobalt/dom/progress_event.h"
 #include "cobalt/dom/window.h"
@@ -41,7 +39,9 @@
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/javascript_engine.h"
 #include "cobalt/web/context.h"
+#include "cobalt/web/csp_delegate.h"
 #include "cobalt/web/environment_settings.h"
+#include "cobalt/xhr/global_stats.h"
 #include "cobalt/xhr/xhr_modify_headers.h"
 #include "nb/memory_scope.h"
 #include "net/http/http_util.h"
@@ -49,7 +49,7 @@
 namespace cobalt {
 namespace xhr {
 
-using dom::DOMException;
+using web::DOMException;
 
 namespace {
 
@@ -192,7 +192,7 @@
       redirect_times_(0),
       is_data_url_(false) {
   DCHECK(settings_);
-  dom::GlobalStats::GetInstance()->Add(this);
+  xhr::GlobalStats::GetInstance()->Add(this);
   xhr_id_ = ++s_xhr_sequence_num_;
 }
 
@@ -231,31 +231,32 @@
 
   if (!async) {
     DLOG(ERROR) << "synchronous XHR is not supported";
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
   base_url_ = settings_->base_url();
 
   if (IsForbiddenMethod(method)) {
-    DOMException::Raise(DOMException::kSecurityErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kSecurityErr, exception_state);
     return;
   }
 
   if (!MethodNameToRequestType(method, &method_)) {
-    DOMException::Raise(DOMException::kSyntaxErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, exception_state);
     return;
   }
 
   request_url_ = base_url_.Resolve(url);
   if (!request_url_.is_valid()) {
-    DOMException::Raise(DOMException::kSyntaxErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, exception_state);
     return;
   }
 
-  dom::CspDelegate* csp = csp_delegate();
-  if (csp && !csp->CanLoad(dom::CspDelegate::kXhr, request_url_, false)) {
-    DOMException::Raise(DOMException::kSecurityErr, exception_state);
+  web::CspDelegate* csp = csp_delegate();
+  if (csp && !csp->CanLoad(web::CspDelegate::kXhr, request_url_, false)) {
+    web::DOMException::Raise(web::DOMException::kSecurityErr, exception_state);
     return;
   }
 
@@ -279,7 +280,8 @@
   TRACK_MEMORY_SCOPE("XHR");
   // https://www.w3.org/TR/2014/WD-XMLHttpRequest-20140130/#dom-xmlhttprequest-setrequestheader
   if (state_ != kOpened || sent_) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -310,7 +312,8 @@
   // https://www.w3.org/TR/2014/WD-XMLHttpRequest-20140130/#dom-xmlhttprequest-overridemimetype
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (state_ == kLoading || state_ == kDone) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -323,7 +326,7 @@
   net::HttpUtil::ParseContentType(override_mime, &mime_type, &charset,
                                   &had_charset, NULL);
   if (!mime_type.length()) {
-    DOMException::Raise(DOMException::kSyntaxErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kSyntaxErr, exception_state);
     return;
   }
   mime_type_override_ = mime_type;
@@ -340,12 +343,14 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   // Step 1
   if (state_ != kOpened) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
   // Step 2
   if (sent_) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
 
@@ -484,7 +489,7 @@
     script::ExceptionState* exception_state) {
   // https://www.w3.org/TR/2014/WD-XMLHttpRequest-20140130/#the-responsetext-attribute
   if (response_type_ != kDefault && response_type_ != kText) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              exception_state);
   }
   if (error_ || (state_ != kLoading && state_ != kDone)) {
@@ -508,7 +513,7 @@
   // 1. If responseType is not the empty string or "document", throw an
   // "InvalidStateError" exception.
   if (response_type_ != kDefault && response_type_ != kDocument) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              exception_state);
     return NULL;
   }
@@ -573,7 +578,7 @@
 void XMLHttpRequest::set_response_type(
     const std::string& response_type, script::ExceptionState* exception_state) {
   if (state_ == kLoading || state_ == kDone) {
-    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
                              exception_state);
     return;
   }
@@ -618,7 +623,8 @@
     bool with_credentials, script::ExceptionState* exception_state) {
   // https://www.w3.org/TR/2014/WD-XMLHttpRequest-20140130/#the-withcredentials-attribute
   if ((state_ != kUnsent && state_ != kOpened) || sent_) {
-    DOMException::Raise(DOMException::kInvalidStateErr, exception_state);
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
     return;
   }
   with_credentials_ = with_credentials;
@@ -894,7 +900,7 @@
     return;
   }
   // This is a redirect. Re-check the CSP.
-  if (!csp_delegate()->CanLoad(dom::CspDelegate::kXhr, new_url,
+  if (!csp_delegate()->CanLoad(web::CspDelegate::kXhr, new_url,
                                true /* is_redirect */)) {
     HandleRequestError(kNetworkError);
     return;
@@ -940,10 +946,10 @@
 
 XMLHttpRequest::~XMLHttpRequest() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  dom::GlobalStats::GetInstance()->Remove(this);
+  xhr::GlobalStats::GetInstance()->Remove(this);
 }
 
-dom::CspDelegate* XMLHttpRequest::csp_delegate() const {
+web::CspDelegate* XMLHttpRequest::csp_delegate() const {
   DCHECK(settings_);
   if (settings_->window() && settings_->window()->document()) {
     return settings_->window()->document()->csp_delegate();
@@ -1024,7 +1030,7 @@
 
   state_ = new_state;
   if (state_ != kUnsent) {
-    DispatchEvent(new dom::Event(base::Tokens::readystatechange()));
+    DispatchEvent(new web::Event(base::Tokens::readystatechange()));
   }
 }
 
diff --git a/cobalt/xhr/xml_http_request.h b/cobalt/xhr/xml_http_request.h
index d39aa5d..2c26720 100644
--- a/cobalt/xhr/xml_http_request.h
+++ b/cobalt/xhr/xml_http_request.h
@@ -23,9 +23,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/optional.h"
 #include "base/timer/timer.h"
-#include "cobalt/dom/csp_delegate.h"
 #include "cobalt/dom/document.h"
-#include "cobalt/dom/dom_exception.h"
 #include "cobalt/loader/cors_preflight.h"
 #include "cobalt/loader/net_fetcher.h"
 #include "cobalt/script/array_buffer.h"
@@ -34,6 +32,8 @@
 #include "cobalt/script/global_environment.h"
 #include "cobalt/script/typed_arrays.h"
 #include "cobalt/script/union_type.h"
+#include "cobalt/web/csp_delegate.h"
+#include "cobalt/web/dom_exception.h"
 #include "cobalt/xhr/url_fetcher_buffer_writer.h"
 #include "cobalt/xhr/xml_http_request_event_target.h"
 #include "cobalt/xhr/xml_http_request_upload.h"
@@ -200,7 +200,7 @@
 
   // Return the CSP delegate from the Settings object.
   // virtual for use by tests.
-  virtual dom::CspDelegate* csp_delegate() const;
+  virtual web::CspDelegate* csp_delegate() const;
 
  private:
   FRIEND_TEST_ALL_PREFIXES(XhrTest, GetResponseHeader);
diff --git a/cobalt/xhr/xml_http_request_event_target.cc b/cobalt/xhr/xml_http_request_event_target.cc
index 526d8cd..35a0197 100644
--- a/cobalt/xhr/xml_http_request_event_target.cc
+++ b/cobalt/xhr/xml_http_request_event_target.cc
@@ -21,40 +21,40 @@
 
 XMLHttpRequestEventTarget::XMLHttpRequestEventTarget(
     script::EnvironmentSettings* settings)
-    : EventTarget(settings) {}
+    : web::EventTarget(settings) {}
 XMLHttpRequestEventTarget::~XMLHttpRequestEventTarget() {}
 
-const dom::EventTarget::EventListenerScriptValue*
+const web::EventTarget::EventListenerScriptValue*
 XMLHttpRequestEventTarget::onabort() const {
   return onabort_listener_ ? &onabort_listener_.value().referenced_value()
                            : NULL;
 }
-const dom::EventTarget::EventListenerScriptValue*
+const web::EventTarget::EventListenerScriptValue*
 XMLHttpRequestEventTarget::onerror() const {
   return onerror_listener_ ? &onerror_listener_.value().referenced_value()
                            : NULL;
 }
-const dom::EventTarget::EventListenerScriptValue*
+const web::EventTarget::EventListenerScriptValue*
 XMLHttpRequestEventTarget::onload() const {
   return onload_listener_ ? &onload_listener_.value().referenced_value() : NULL;
 }
-const dom::EventTarget::EventListenerScriptValue*
+const web::EventTarget::EventListenerScriptValue*
 XMLHttpRequestEventTarget::onloadend() const {
   return onloadend_listener_ ? &onloadend_listener_.value().referenced_value()
                              : NULL;
 }
-const dom::EventTarget::EventListenerScriptValue*
+const web::EventTarget::EventListenerScriptValue*
 XMLHttpRequestEventTarget::onloadstart() const {
   return onloadstart_listener_
              ? &onloadstart_listener_.value().referenced_value()
              : NULL;
 }
-const dom::EventTarget::EventListenerScriptValue*
+const web::EventTarget::EventListenerScriptValue*
 XMLHttpRequestEventTarget::onprogress() const {
   return onprogress_listener_ ? &onprogress_listener_.value().referenced_value()
                               : NULL;
 }
-const dom::EventTarget::EventListenerScriptValue*
+const web::EventTarget::EventListenerScriptValue*
 XMLHttpRequestEventTarget::ontimeout() const {
   return ontimeout_listener_ ? &ontimeout_listener_.value().referenced_value()
                              : NULL;
@@ -125,7 +125,7 @@
 }
 
 void XMLHttpRequestEventTarget::TraceMembers(script::Tracer* tracer) {
-  dom::EventTarget::TraceMembers(tracer);
+  web::EventTarget::TraceMembers(tracer);
 }
 
 }  // namespace xhr
diff --git a/cobalt/xhr/xml_http_request_event_target.h b/cobalt/xhr/xml_http_request_event_target.h
index ab82bbd..fe37a3b 100644
--- a/cobalt/xhr/xml_http_request_event_target.h
+++ b/cobalt/xhr/xml_http_request_event_target.h
@@ -18,13 +18,13 @@
 #include <string>
 
 #include "base/optional.h"
-#include "cobalt/dom/event_target.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/web/event_target.h"
 
 namespace cobalt {
 namespace xhr {
 
-class XMLHttpRequestEventTarget : public dom::EventTarget {
+class XMLHttpRequestEventTarget : public web::EventTarget {
  public:
   explicit XMLHttpRequestEventTarget(script::EnvironmentSettings* settings);
 
diff --git a/cobalt/xhr/xml_http_request_test.cc b/cobalt/xhr/xml_http_request_test.cc
index f1e023c..e83e21a 100644
--- a/cobalt/xhr/xml_http_request_test.cc
+++ b/cobalt/xhr/xml_http_request_test.cc
@@ -17,17 +17,18 @@
 #include <memory>
 
 #include "base/logging.h"
-#include "cobalt/dom/dom_exception.h"
-#include "cobalt/dom/testing/mock_event_listener.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/script/testing/fake_script_value.h"
 #include "cobalt/script/testing/mock_exception_state.h"
+#include "cobalt/web/dom_exception.h"
+#include "cobalt/web/event_listener.h"
+#include "cobalt/web/testing/mock_event_listener.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
-using cobalt::dom::EventListener;
-using cobalt::dom::testing::MockEventListener;
+using cobalt::web::EventListener;
+using cobalt::web::testing::MockEventListener;
 using cobalt::script::testing::FakeScriptValue;
 using cobalt::script::testing::MockExceptionState;
 using ::testing::_;
@@ -94,11 +95,11 @@
   FakeSettings() { set_base_url(GURL("http://example.com")); }
 };
 
-class MockCspDelegate : public dom::CspDelegateInsecure {
+class MockCspDelegate : public web::CspDelegateInsecure {
  public:
   MockCspDelegate() {}
   MOCK_CONST_METHOD3(CanLoad,
-                     bool(dom::CspDelegate::ResourceType, const GURL&, bool));
+                     bool(web::CspDelegate::ResourceType, const GURL&, bool));
 };
 
 // Derive from XMLHttpRequest in order to override its csp_delegate.
@@ -106,12 +107,12 @@
 class FakeXmlHttpRequest : public XMLHttpRequest {
  public:
   FakeXmlHttpRequest(script::EnvironmentSettings* settings,
-                     dom::CspDelegate* csp_delegate)
+                     web::CspDelegate* csp_delegate)
       : XMLHttpRequest(settings), csp_delegate_(csp_delegate) {}
-  dom::CspDelegate* csp_delegate() const override { return csp_delegate_; }
+  web::CspDelegate* csp_delegate() const override { return csp_delegate_; }
 
  private:
-  dom::CspDelegate* csp_delegate_;
+  web::CspDelegate* csp_delegate_;
 };
 
 }  // namespace
@@ -139,17 +140,17 @@
   EXPECT_CALL(exception_state_, SetException(_))
       .WillOnce(SaveArg<0>(&exception));
   xhr_->Open("INVALID_METHOD", "fake_url", &exception_state_);
-  EXPECT_EQ(dom::DOMException::kSyntaxErr,
-            dynamic_cast<dom::DOMException*>(exception.get())->code());
+  EXPECT_EQ(web::DOMException::kSyntaxErr,
+            dynamic_cast<web::DOMException*>(exception.get())->code());
 }
 
 TEST_F(XhrTest, Open) {
   std::unique_ptr<MockEventListener> listener = MockEventListener::Create();
-  FakeScriptValue<EventListener> script_object(listener.get());
+  FakeScriptValue<web::EventListener> script_object(listener.get());
   xhr_->set_onreadystatechange(script_object);
   EXPECT_CALL(*listener,
               HandleEvent(Eq(xhr_),
-                          Pointee(Property(&dom::Event::type,
+                          Pointee(Property(&web::Event::type,
                                            base::Token("readystatechange"))),
                           _))
       .Times(1);
@@ -171,8 +172,8 @@
   EXPECT_CALL(csp_delegate, CanLoad(_, _, _)).WillOnce(Return(false));
   xhr_->Open("GET", "https://www.google.com", &exception_state_);
 
-  EXPECT_EQ(dom::DOMException::kSecurityErr,
-            dynamic_cast<dom::DOMException*>(exception.get())->code());
+  EXPECT_EQ(web::DOMException::kSecurityErr,
+            dynamic_cast<web::DOMException*>(exception.get())->code());
   EXPECT_EQ(XMLHttpRequest::kUnsent, xhr_->ready_state());
 }
 
@@ -184,8 +185,8 @@
 
   xhr_->OverrideMimeType("invalidmimetype", &exception_state_);
   EXPECT_EQ("", xhr_->mime_type_override());
-  EXPECT_EQ(dom::DOMException::kSyntaxErr,
-            dynamic_cast<dom::DOMException*>(exception.get())->code());
+  EXPECT_EQ(web::DOMException::kSyntaxErr,
+            dynamic_cast<web::DOMException*>(exception.get())->code());
 
   xhr_->OverrideMimeType("text/xml", &exception_state_);
   EXPECT_EQ("text/xml", xhr_->mime_type_override());
@@ -208,8 +209,8 @@
   EXPECT_CALL(exception_state_, SetException(_))
       .WillOnce(SaveArg<0>(&exception));
   xhr_->SetRequestHeader("Foo", "bar", &exception_state_);
-  EXPECT_EQ(dom::DOMException::kInvalidStateErr,
-            dynamic_cast<dom::DOMException*>(exception.get())->code());
+  EXPECT_EQ(web::DOMException::kInvalidStateErr,
+            dynamic_cast<web::DOMException*>(exception.get())->code());
 }
 
 TEST_F(XhrTest, SetRequestHeader) {
diff --git a/docker-compose.yml b/docker-compose.yml
index 0a82474..1f5f4e3 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -40,6 +40,23 @@
   IS_DOCKER: 1
   PYTHONPATH: /code
 
+x-shared-unittest-definitions: &shared-unittest-definitions
+  stdin_open: true
+  tty: true
+  build:
+    context: ./docker/linux
+    dockerfile: unittest/Dockerfile
+  image: cobalt-unittest
+  environment:
+    - PLATFORM=${PLATFORM:-linux-x64x11}
+    - CONFIG=${CONFIG:-devel}
+  volumes:
+    - ${COBALT_SRC:-.}/out/${PLATFORM:-linux-x64x11}_${CONFIG:-devel}:/out
+    - ${COBALT_SRC:-.}/out/${PLATFORM:-linux-x64x11}_${CONFIG:-devel}/testoutput:/tmp/testoutput
+  # TODO: Get NPLB unittests to run with IPv6 without using the host network.
+  network_mode: "host"
+  depends_on: [ base ]
+
 services:
 #### Tools
   pre-commit:
@@ -353,16 +370,30 @@
   # 3. Run the unittests for that target.
   # PLATFORM=linux-x64x11 CONFIG=devel TARGET=all docker-compose run unittest
   unittest:
-    <<: *common-definitions
-    build:
-      context: ./docker/linux
-      dockerfile: unittest/Dockerfile
-    image: cobalt-unittest
-    environment:
-      - PLATFORM=${PLATFORM:-linux-x64x11}
-      - CONFIG=${CONFIG:-debug}
-    volumes:
-      - ${COBALT_SRC:-.}/out/${PLATFORM:-linux-x64x11}_${CONFIG:-debug}:/out
-    # TODO: Get NPLB unittests to run with IPv6 without using the host network.
-    network_mode: "host"
-    depends_on: [ base ]
+    <<: *shared-unittest-definitions
+
+  # Stub container that launches all shard containers in parallel.
+  unittest-parallel:
+    image: cobalt-base
+    entrypoint: ["/bin/sh", "-c", "echo", "done"]
+    depends_on:
+      - unittest-parallel-0
+      - unittest-parallel-1
+      - unittest-parallel-2
+      - unittest-parallel-3
+
+  unittest-parallel-0:
+    <<: *shared-unittest-definitions
+    entrypoint: ["python3", "/unittest_docker_launcher.py", "0"]
+
+  unittest-parallel-1:
+    <<: *shared-unittest-definitions
+    entrypoint: ["python3", "/unittest_docker_launcher.py", "1"]
+
+  unittest-parallel-2:
+    <<: *shared-unittest-definitions
+    entrypoint: ["python3", "/unittest_docker_launcher.py", "2"]
+
+  unittest-parallel-3:
+    <<: *shared-unittest-definitions
+    entrypoint: ["python3", "/unittest_docker_launcher.py", "3"]
diff --git a/docker/linux/unittest/Dockerfile b/docker/linux/unittest/Dockerfile
index d3bff35..b915a10 100644
--- a/docker/linux/unittest/Dockerfile
+++ b/docker/linux/unittest/Dockerfile
@@ -17,11 +17,16 @@
 RUN apt update -qqy \
     && apt install -qqy --no-install-recommends \
         libasound2 \
+        libatomic1 \
+        libavcodec58 \
+        libavformat58 \
+        libavutil56 \
         libegl1-mesa \
         libgl1-mesa-dri \
         libgles2-mesa \
         libx11-6 \
         libxcomposite1 \
+        libxi6 \
         libxrender1 \
         unzip \
         xauth \
@@ -34,7 +39,9 @@
 
 RUN mkdir -p /app_launcher_out
 
-CMD unzip -q /out/app_launcher -d /app_launcher_out && \
-    xvfb-run --server-args="-screen 0 1920x1080x24 +render +extension GLX -noreset" \
-    python /app_launcher_out/starboard/tools/testing/test_runner.py --run \
-           -o /out --platform $PLATFORM --config $CONFIG
+ENV PYTHONPATH "/app_launcher_out"
+
+COPY unittest/unittest_docker_launcher.py /unittest_docker_launcher.py
+
+# Without arguments, it assumes no sharding and runs all tests in the container.
+CMD python3 /unittest_docker_launcher.py
diff --git a/docker/linux/unittest/unittest_docker_launcher.py b/docker/linux/unittest/unittest_docker_launcher.py
new file mode 100644
index 0000000..2a035bb
--- /dev/null
+++ b/docker/linux/unittest/unittest_docker_launcher.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+# Copyright 2022 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.
+"""Entrypoint for running unit-tests shards in docker"""
+
+import json
+import logging
+import os
+import subprocess
+import sys
+import time
+
+log = logging.getLogger(__name__)
+
+
+def exc(cmd, check=False):
+  log.info(cmd)
+  try:
+    output = subprocess.check_output(cmd, shell=True)
+    log.info(output)
+  except subprocess.CalledProcessError:
+    if check:
+      raise
+
+
+def main(argv):
+  shard_index = None
+  if len(argv) >= 2:
+    shard_index = int(argv[1])
+
+  out_dir = '/out'
+
+  exc('unzip -q {}/app_launcher -d /app_launcher_out'.format(out_dir))
+
+  xvfb_prefix = ('xvfb-run -a --server-args="-screen 0 1920x1080x24'
+                 '+render +extension GLX -noreset"')
+  env_platform = os.getenv('PLATFORM')
+  env_config = os.getenv('CONFIG')
+  test_command = [
+      'python', '/app_launcher_out/starboard/tools/testing/test_runner.py',
+      '--run', '-o', out_dir, '-p', env_platform, '-c', env_config, '-l'
+  ]
+
+  if shard_index is not None:
+    test_command.append('-s')
+    test_command.append(str(shard_index))
+
+  test_command = ' '.join(test_command)
+
+  start_t = time.time()
+  exc('{} {}'.format(xvfb_prefix, test_command))
+  end_t = time.time()
+
+  # Output shard timing information to file.
+  duration_t = (end_t - start_t) / 60.0
+  shard_timing_json = '{}/shard_timing.json'.format(out_dir)
+  try:
+    with open(shard_timing_json, 'r') as f:
+      json_content = json.loads(f.read())
+  except FileNotFoundError:
+    json_content = {}
+  json_content[str(shard_index)] = '{:.2f}'.format(duration_t)
+  with open(shard_timing_json, 'w+') as f:
+    json.dump(json_content, f, sort_keys=True, indent=2)
+
+
+if __name__ == '__main__':
+  logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
+  main(sys.argv)
diff --git a/docker/windows/base/build/Dockerfile b/docker/windows/base/build/Dockerfile
new file mode 100644
index 0000000..658ec30
--- /dev/null
+++ b/docker/windows/base/build/Dockerfile
@@ -0,0 +1,91 @@
+# escape=`
+
+# Copyright 2021 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.
+ARG FROM_IMAGE
+FROM ${FROM_IMAGE}
+
+SHELL ["powershell", "-ExecutionPolicy", "unrestricted", "-Command"]
+
+# Helper script for quick delete operations in windows
+COPY ./fast-win-rmdir.cmd /fast-win-rmdir.cmd
+
+# Helper script to run an looping command to persist the container instead of
+# running the main command.
+COPY ./spin.ps1 /spin.ps1
+
+# Helper script to output all the running python processes showing the details
+# of the execution, i.e. the full invocation string.
+COPY ./list_python_processes.py /list_python_processes.py
+
+# Helper file to ensure python2 is invoked correctly from command line.
+COPY ./python2.bat /python2/python2.bat
+
+# Install deps via chocolatey.
+RUN iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'));`
+    mkdir C:\choco-cache;`
+    # Install Python 3 before Python 2, so the default on the path is python2.
+    choco install -y -c C:\choco-cache python3 -ia '/quiet InstallAllUsers=1 PrependPath=1 TargetDir="C:\Python3"';`
+    choco install -y -c C:\choco-cache python2 --params '/InstallDir:C:\python2';`
+    choco install -y -c C:\choco-cache winflexbison3 --params '/InstallDir:C:\bison';`
+    choco install -y -c C:\choco-cache ninja;`
+    choco install -y -c C:\choco-cache nodejs-lts;`
+    choco install -y -c C:\choco-cache git;`
+    choco install -y -c C:\choco-cache cmake --installargs 'ADD_CMAKE_TO_PATH=System';`
+    Write-Host ('Deleting the chocolately download cache');`
+    Remove-Item -Force -Recurse $env:TEMP\*;`
+    C:\fast-win-rmdir.cmd C:\choco-cache;`
+    # Create version specific copy of each python executable.
+    Copy-Item C:\Python3\python.exe C:\Python3\python3.exe;`
+    Copy-Item C:\python2\python.exe C:\python2\python2.exe
+
+# Install python2 packages via PIP.
+# Additionally do the same for python3, and set various configurations.
+RUN mkdir C:\pip-cache;`
+    python2 -m pip install pypiwin32 six --cache-dir C:\pip-cache;`
+    C:\fast-win-rmdir.cmd C:\pip-cache;`
+    # Install python3 packages via PIP.
+    mkdir C:\pip-cache;`
+    python3 -m pip install six --cache-dir C:\pip-cache;`
+    C:\fast-win-rmdir.cmd C:\pip-cache;`
+    # Configure git global settings.
+    git config --global core.autocrlf false;`
+    git config --global core.symlinks false;`
+    git config --global core.filemode false;`
+    git config --global core.preloadindex true;`
+    git config --global core.fscache true;`
+    # Registry changes to enable long filesystem paths.
+    reg add 'HKLM\SYSTEM\CurrentControlSet\Control\FileSystem' /v LongPathsEnabled /t REG_DWORD /reg:64 /d 1 /f;`
+    reg add 'HKLM\SYSTEM\CurrentControlSet\Control\FileSystem' /v LongPathsEnabled /t REG_DWORD /d 1 /f;`
+    # Environment variables
+    # This fixes a bug running python scripts "LookupError: unknown encoding: cp0".
+    cmd /S /C setx /M PYTHONIOENCODING 'UTF-8';`
+    # This ensures GYP does not generate PDB files, or create symlinks during build.
+    cmd /S /C setx /M IS_DOCKER '1'
+
+# Setup Sccache
+ADD https://storage.googleapis.com/cobalt-docker-resources/sccache-0.2.16-alpha.0.exe /sccache/sccache.exe
+RUN mkdir C:\root\sccache;`
+    setx /M PATH $($Env:PATH + ';C:\sccache')
+
+# Set up GN
+RUN (New-Object Net.WebClient).DownloadFile(`
+    'https://chrome-infra-packages.appspot.com/dl/gn/gn/windows-amd64/+/c7arrb3NphJV3Hzo5oKj94VeQgVYY6AoatHY39wlWAEC',`
+    'C:\gn.zip') ; `
+    Expand-Archive -Force C:\gn.zip C:\gn\ ; `
+    Remove-Item -Path C:\gn.zip ; `
+    setx /M PATH $($Env:PATH + ';C:\gn')
+
+# Configure common env vars.
+ENV NINJA_STATUS="[%e sec | %f/%t %u remaining | %c/sec | j%r] "
diff --git a/docker/windows/base/build/fast-win-rmdir.cmd b/docker/windows/base/build/fast-win-rmdir.cmd
new file mode 100644
index 0000000..ba85628
--- /dev/null
+++ b/docker/windows/base/build/fast-win-rmdir.cmd
@@ -0,0 +1,5 @@
+set DIRECTORY=%1
+
+DEL /F /Q /S %DIRECTORY%\*.* > NUL
+
+RMDIR /Q /S %DIRECTORY%
diff --git a/docker/windows/base/build/list_python_processes.py b/docker/windows/base/build/list_python_processes.py
new file mode 100644
index 0000000..acf000f
--- /dev/null
+++ b/docker/windows/base/build/list_python_processes.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+"""
+Convenience script to debug the list of running python processes on windows
+without relying on 3rd party GUI programs such as Process Explorer.
+"""
+
+import subprocess
+
+wmic_cmd = """wmic process where "name='python.exe' or name='pythonw.exe'" get commandline,processid"""
+wmic_prc = subprocess.Popen(wmic_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+wmic_out, _ = wmic_prc.communicate()
+
+py_processes = [item.rsplit(None, 1) for item in wmic_out.splitlines() if item][1:]
+py_processes = [[cmdline, int(pid)] for [cmdline, pid] in py_processes]
+
+print('Running python processes:')
+for p in py_processes:
+  print(p[1]) # Prints the full python invocation.
+  print(p[0]) # Prints the pid of the process.
+  print
diff --git a/docker/windows/base/build/python2.bat b/docker/windows/base/build/python2.bat
new file mode 100644
index 0000000..9e2101e
--- /dev/null
+++ b/docker/windows/base/build/python2.bat
@@ -0,0 +1,3 @@
+@echo off

+setlocal

+c:\python2\python.exe %*

diff --git a/docker/windows/base/build/spin.ps1 b/docker/windows/base/build/spin.ps1
new file mode 100644
index 0000000..339c0df
--- /dev/null
+++ b/docker/windows/base/build/spin.ps1
@@ -0,0 +1,8 @@
+# Helper script to run an placeholder process on windows.
+# This serves to keep windows containers running, so we can shell into it to
+# administer it.
+
+while ($true) {
+  test-connection google.com -Count 1;
+  sleep 120;
+}
diff --git a/glimp/shaders/path_conversion.py b/glimp/shaders/path_conversion.py
deleted file mode 100644
index 45a8819..0000000
--- a/glimp/shaders/path_conversion.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2014 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Modifies a list of input paths to produce a list of output paths.
-
-The user can specify a list of paths as well as specific path transformations
-(e.g. replace directory or replace extension) and this script will return a
-list of processed output paths.
-"""
-
-import argparse
-import os
-import sys
-import textwrap
-
-def ConvertPaths(input_paths, output_directory, output_prefix,
-                 output_extension, forward_slashes):
-  """The script's core function."""
-  def ConvertPath(path):
-    output_path = path
-
-    if output_directory:
-      output_path = os.path.join(output_directory,
-                                 os.path.basename(output_path))
-    if output_prefix:
-      output_path = os.path.join(os.path.dirname(output_path),
-                                 output_prefix + os.path.basename(output_path))
-    if output_extension:
-      output_path = (os.path.splitext(output_path)[0] +
-                     os.extsep + output_extension)
-    if forward_slashes:
-      output_path = output_path.replace('\\', '/')
-
-    return output_path
-
-  # If an element in the list of input paths contains a space, treat that as
-  # a separater for more input paths and expand the list of input paths.
-  # Gyp does not make it easy to marshall paramaters into a Python script so
-  # this functionality is for working around that problem.
-  expanded_input_paths = []
-  for x in input_paths:
-    expanded_input_paths += x.split(' ')
-
-  return [ConvertPath(x) for x in expanded_input_paths]
-
-def DoMain(argv):
-  parser = argparse.ArgumentParser(
-      formatter_class=argparse.RawDescriptionHelpFormatter,
-      description=textwrap.dedent(__doc__))
-  parser.add_argument('-d', '--output_directory',
-                      type=str,
-                      help=('If specified, will replace all input path '
-                            'directories with the one specified by this '
-                            'parameter.'))
-  parser.add_argument('-e', '--output_extension',
-                      type=str,
-                      help=('If specified, will replace all input path '
-                            'extensions with the one specified by this '
-                            'parameter.'))
-  parser.add_argument('-p', '--output_prefix',
-                      type=str,
-                      help=('If specified, will prepend the string '
-                            'specified in the parameter to the filename.'))
-  parser.add_argument('-s', '--forward_slashes',
-                      action='store_true',
-                      help=('Set this flag if you would like all output '
-                            'paths to contain forward slashes, even on '
-                            'Windows.  This is useful if you are calling this '
-                            'from Gyp which expects forward slashes.'))
-  parser.add_argument('input_paths',
-                      help=('A list of paths that will be modified as '
-                            'specified by the other options.'),
-                      nargs='*')
-  args = parser.parse_args(argv)
-
-  return ' '.join(ConvertPaths(args.input_paths,
-                               args.output_directory,
-                               args.output_prefix,
-                               args.output_extension,
-                               args.forward_slashes))
-
-if __name__ == '__main__':
-  print DoMain(sys.argv[1:])
-  sys.exit(0)
diff --git a/nb/BUILD.gn b/nb/BUILD.gn
index bdcc0ef..c4cdde4 100644
--- a/nb/BUILD.gn
+++ b/nb/BUILD.gn
@@ -70,7 +70,7 @@
     ]
 
     if (defined(has_nb_platform) && has_nb_platform) {
-      deps += [ "//nb/${target_platform}:nb_platform" ]
+      deps += [ "//starboard/${target_platform}/nb:nb_platform" ]
     }
   }
 }
diff --git a/net/BUILD.gn b/net/BUILD.gn
index c8c35f6..258af0d 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -384,8 +384,8 @@
 
     # disk_cache is not supported yet. They are required for http cache.
     # Maybe we will need it in the future.
-    # "disk_cache/backend_cleanup_tracker.cc",
-    # "disk_cache/backend_cleanup_tracker.h",
+    "disk_cache/backend_cleanup_tracker.cc",
+    "disk_cache/backend_cleanup_tracker.h",
     # "disk_cache/blockfile/addr.cc",
     # "disk_cache/blockfile/addr.h",
     # "disk_cache/blockfile/backend_impl.cc",
@@ -429,51 +429,49 @@
     # "disk_cache/blockfile/trace.h",
     # "disk_cache/blockfile/webfonts_histogram.cc",
     # "disk_cache/blockfile/webfonts_histogram.h",
-    # "disk_cache/cache_util.cc",
-    # "disk_cache/cache_util.h",
-    # "disk_cache/cache_util_starboard.cc",
+    "disk_cache/cache_util.cc",
+    "disk_cache/cache_util.h",
+    "disk_cache/cache_util_starboard.cc",
     # "disk_cache/disk_cache.cc",
     "disk_cache/disk_cache.h",
-    # "disk_cache/memory/mem_backend_impl.cc",
-    # "disk_cache/memory/mem_backend_impl.h",
-    # "disk_cache/memory/mem_entry_impl.cc",
-    # "disk_cache/memory/mem_entry_impl.h",
+    "disk_cache/memory/mem_backend_impl.cc",
+    "disk_cache/memory/mem_backend_impl.h",
+    "disk_cache/memory/mem_entry_impl.cc",
+    "disk_cache/memory/mem_entry_impl.h",
     "disk_cache/cobalt/cobalt_disk_cache.cc",
     "disk_cache/cobalt/cobalt_backend_impl.cc",
     "disk_cache/cobalt/cobalt_backend_impl.h",
-    "disk_cache/cobalt/cobalt_entry_impl.cc",
-    "disk_cache/cobalt/cobalt_entry_impl.h",
     "disk_cache/cobalt/resource_type.h",
     "disk_cache/net_log_parameters.cc",
     "disk_cache/net_log_parameters.h",
-    # "disk_cache/simple/simple_backend_impl.cc",
-    # "disk_cache/simple/simple_backend_impl.h",
-    # "disk_cache/simple/simple_backend_version.h",
-    # "disk_cache/simple/simple_entry_format.cc",
-    # "disk_cache/simple/simple_entry_format.h",
-    # "disk_cache/simple/simple_entry_format_history.h",
-    # "disk_cache/simple/simple_entry_impl.cc",
-    # "disk_cache/simple/simple_entry_impl.h",
-    # "disk_cache/simple/simple_entry_operation.cc",
-    # "disk_cache/simple/simple_entry_operation.h",
-    # "disk_cache/simple/simple_file_tracker.cc",
-    # "disk_cache/simple/simple_file_tracker.h",
-    # "disk_cache/simple/simple_histogram_macros.h",
-    # "disk_cache/simple/simple_index.cc",
-    # "disk_cache/simple/simple_index.h",
-    # "disk_cache/simple/simple_index_delegate.h",
-    # "disk_cache/simple/simple_index_file.cc",
-    # "disk_cache/simple/simple_index_file.h",
-    # "disk_cache/simple/simple_index_file_starboard.cc",
-    # "disk_cache/simple/simple_net_log_parameters.cc",
-    # "disk_cache/simple/simple_net_log_parameters.h",
-    # "disk_cache/simple/simple_synchronous_entry.cc",
-    # "disk_cache/simple/simple_synchronous_entry.h",
-    # "disk_cache/simple/simple_util.cc",
-    # "disk_cache/simple/simple_util.h",
-    # "disk_cache/simple/simple_util_starboard.cc",
-    # "disk_cache/simple/simple_version_upgrade.cc",
-    # "disk_cache/simple/simple_version_upgrade.h",
+    "disk_cache/simple/simple_backend_impl.cc",
+    "disk_cache/simple/simple_backend_impl.h",
+    "disk_cache/simple/simple_backend_version.h",
+    "disk_cache/simple/simple_entry_format.cc",
+    "disk_cache/simple/simple_entry_format.h",
+    "disk_cache/simple/simple_entry_format_history.h",
+    "disk_cache/simple/simple_entry_impl.cc",
+    "disk_cache/simple/simple_entry_impl.h",
+    "disk_cache/simple/simple_entry_operation.cc",
+    "disk_cache/simple/simple_entry_operation.h",
+    "disk_cache/simple/simple_file_tracker.cc",
+    "disk_cache/simple/simple_file_tracker.h",
+    "disk_cache/simple/simple_histogram_macros.h",
+    "disk_cache/simple/simple_index.cc",
+    "disk_cache/simple/simple_index.h",
+    "disk_cache/simple/simple_index_delegate.h",
+    "disk_cache/simple/simple_index_file.cc",
+    "disk_cache/simple/simple_index_file.h",
+    "disk_cache/simple/simple_index_file_starboard.cc",
+    "disk_cache/simple/simple_net_log_parameters.cc",
+    "disk_cache/simple/simple_net_log_parameters.h",
+    "disk_cache/simple/simple_synchronous_entry.cc",
+    "disk_cache/simple/simple_synchronous_entry.h",
+    "disk_cache/simple/simple_util.cc",
+    "disk_cache/simple/simple_util.h",
+    "disk_cache/simple/simple_util_starboard.cc",
+    "disk_cache/simple/simple_version_upgrade.cc",
+    "disk_cache/simple/simple_version_upgrade.h",
     "dns/dns_util.cc",
     "dns/dns_util.h",
     "dns/host_cache.cc",
diff --git a/net/base/load_timing_info.h b/net/base/load_timing_info.h
index 4d518d7..aacaac0 100644
--- a/net/base/load_timing_info.h
+++ b/net/base/load_timing_info.h
@@ -160,6 +160,10 @@
   // is not closed by the server.
   base::TimeTicks push_start;
   base::TimeTicks push_end;
+
+#if defined(STARBOARD)
+  uint64_t encoded_body_size;
+#endif  // defined(STARBOARD)
 };
 
 }  // namespace net
diff --git a/net/disk_cache/cobalt/cobalt_backend_impl.cc b/net/disk_cache/cobalt/cobalt_backend_impl.cc
index 2835797..ec25ce8 100644
--- a/net/disk_cache/cobalt/cobalt_backend_impl.cc
+++ b/net/disk_cache/cobalt/cobalt_backend_impl.cc
@@ -14,33 +14,83 @@
 
 #include "net/disk_cache/cobalt/cobalt_backend_impl.h"
 
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
 #include "base/threading/sequenced_task_runner_handle.h"
+#include "net/disk_cache/backend_cleanup_tracker.h"
 
 using base::Time;
 
 namespace disk_cache {
 
-CobaltBackendImpl::CobaltBackendImpl(net::NetLog* net_log)
-    : weak_factory_(this) {}
+namespace {
+
+void CompletionOnceCallbackHandler(
+    scoped_refptr<CobaltBackendImpl::RefCountedRunner> runner,
+    int result) {
+  // Save most recent non-success result.
+  if (result != net::OK) {
+    runner->set_callback_result(result);
+  }
+}
+
+ResourceType GetType(const std::string& key) {
+  // The type is stored at the front of the key, as an int that maps to the
+  // ResourceType enum. This is done in HttpCache::GenerateCacheKey.
+  DCHECK(!key.empty());
+  char type_hint = key[0];
+  if (isdigit(type_hint)) {
+    // ASCII '0' = 48
+    return (disk_cache::ResourceType)(static_cast<int>(type_hint) - 48);
+  }
+  return kOther;
+}
+
+}  // namespace
+
+CobaltBackendImpl::CobaltBackendImpl(
+    const base::FilePath& path,
+    scoped_refptr<BackendCleanupTracker> cleanup_tracker,
+    int64_t max_bytes,
+    net::CacheType cache_type,
+    net::NetLog* net_log)
+    : weak_factory_(this) {
+  // Initialize disk backend for each resource type.
+  int64_t total_size = 0;
+  for (int i = 0; i < kTypeCount; i++) {
+    base::FilePath dir =
+        path.Append(FILE_PATH_LITERAL(kTypeMetadata[i].directory));
+    int64_t bucket_size = kTypeMetadata[i].max_size_mb * 1024 * 1024;
+    total_size += bucket_size;
+    SimpleBackendImpl* simple_backend = new SimpleBackendImpl(
+        dir, cleanup_tracker, /* file_tracker = */ nullptr, bucket_size,
+        cache_type, net_log);
+    simple_backend_map_[(ResourceType)i] = simple_backend;
+  }
+
+  // Must be at least enough space for each backend.
+  DCHECK(total_size <= max_bytes);
+}
 
 CobaltBackendImpl::~CobaltBackendImpl() {
-  if (!post_cleanup_callback_.is_null())
-    base::SequencedTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, std::move(post_cleanup_callback_));
+  for (int i = 0; i < kTypeCount; i++) {
+    delete simple_backend_map_[(ResourceType)i];
+  }
+  simple_backend_map_.clear();
 }
 
-// static
-std::unique_ptr<CobaltBackendImpl> CobaltBackendImpl::CreateBackend(
-    int64_t max_bytes,
-    net::NetLog* net_log) {
-  std::unique_ptr<CobaltBackendImpl> cache(
-      std::make_unique<CobaltBackendImpl>(net_log));
-  return cache;
-}
-
-void CobaltBackendImpl::SetPostCleanupCallback(base::OnceClosure cb) {
-  DCHECK(post_cleanup_callback_.is_null());
-  post_cleanup_callback_ = std::move(cb);
+net::Error CobaltBackendImpl::Init(CompletionOnceCallback completion_callback) {
+  auto closure_runner =
+      base::MakeRefCounted<RefCountedRunner>(std::move(completion_callback));
+  for (auto const& backend : simple_backend_map_) {
+    CompletionOnceCallback delegate =
+        base::BindOnce(&CompletionOnceCallbackHandler, closure_runner);
+    backend.second->Init(base::BindOnce(std::move(delegate)));
+  }
+  return net::ERR_IO_PENDING;
 }
 
 net::CacheType CobaltBackendImpl::GetCacheType() const {
@@ -48,52 +98,76 @@
 }
 
 int32_t CobaltBackendImpl::GetEntryCount() const {
-  // TODO: Implement
-  return 0;
+  // Return total number of entries both on disk and in memory.
+  int32_t count = 0;
+  for (auto const& backend : simple_backend_map_) {
+    count += backend.second->GetEntryCount();
+  }
+  return count;
 }
 
 net::Error CobaltBackendImpl::OpenEntry(const std::string& key,
                                         net::RequestPriority request_priority,
                                         Entry** entry,
-                                        std::string type,
                                         CompletionOnceCallback callback) {
-  // TODO: Implement
-  return net::ERR_FAILED;
+  SimpleBackendImpl* simple_backend = simple_backend_map_[GetType(key)];
+  return simple_backend->OpenEntry(key, request_priority, entry,
+                                   std::move(callback));
 }
 
 net::Error CobaltBackendImpl::CreateEntry(const std::string& key,
                                           net::RequestPriority request_priority,
                                           Entry** entry,
                                           CompletionOnceCallback callback) {
-  // TODO: Implement
-  return net::ERR_FAILED;
+  SimpleBackendImpl* simple_backend = simple_backend_map_[GetType(key)];
+  return simple_backend->CreateEntry(key, request_priority, entry,
+                                     std::move(callback));
 }
 
 net::Error CobaltBackendImpl::DoomEntry(const std::string& key,
                                         net::RequestPriority priority,
                                         CompletionOnceCallback callback) {
-  // TODO: Implement
-  return net::OK;
+  SimpleBackendImpl* simple_backend = simple_backend_map_[GetType(key)];
+  return simple_backend->DoomEntry(key, priority, std::move(callback));
 }
 
 net::Error CobaltBackendImpl::DoomAllEntries(CompletionOnceCallback callback) {
-  // TODO: Implement
-  return net::OK;
+  auto closure_runner =
+      base::MakeRefCounted<RefCountedRunner>(std::move(callback));
+  for (auto const& backend : simple_backend_map_) {
+    CompletionOnceCallback delegate =
+        base::BindOnce(&CompletionOnceCallbackHandler, closure_runner);
+    backend.second->DoomAllEntries(std::move(delegate));
+  }
+  return net::ERR_IO_PENDING;
 }
 
 net::Error CobaltBackendImpl::DoomEntriesBetween(
     Time initial_time,
     Time end_time,
     CompletionOnceCallback callback) {
-  // TODO: Implement
-  return net::OK;
+  auto closure_runner =
+      base::MakeRefCounted<RefCountedRunner>(std::move(callback));
+  for (auto const& backend : simple_backend_map_) {
+    CompletionOnceCallback delegate =
+        base::BindOnce(&CompletionOnceCallbackHandler, closure_runner);
+    backend.second->DoomEntriesBetween(initial_time, end_time,
+                                       std::move(delegate));
+  }
+  return net::ERR_IO_PENDING;
 }
 
 net::Error CobaltBackendImpl::DoomEntriesSince(
     Time initial_time,
     CompletionOnceCallback callback) {
-  // TODO: Implement
-  return net::OK;
+  auto closure_runner =
+      base::MakeRefCounted<RefCountedRunner>(std::move(callback));
+  for (auto const& backend : simple_backend_map_) {
+    CompletionOnceCallback delegate =
+        base::BindOnce(&CompletionOnceCallbackHandler, closure_runner);
+    backend.second->DoomEntriesSince(initial_time, std::move(delegate));
+  }
+  return net::ERR_IO_PENDING;
 }
 
 int64_t CobaltBackendImpl::CalculateSizeOfAllEntries(
@@ -131,14 +205,20 @@
 }
 
 void CobaltBackendImpl::OnExternalCacheHit(const std::string& key) {
-  // TODO: Implement
+  SimpleBackendImpl* simple_backend = simple_backend_map_[GetType(key)];
+  simple_backend->OnExternalCacheHit(key);
 }
 
 size_t CobaltBackendImpl::DumpMemoryStats(
     base::trace_event::ProcessMemoryDump* pmd,
     const std::string& parent_absolute_name) const {
-  // TODO: Implement
-  return 0;
+  // Dump memory stats for all backends.
+  std::string name = parent_absolute_name + "/cobalt_backend";
+  size_t size = 0;
+  for (auto const& backend : simple_backend_map_) {
+    size += backend.second->DumpMemoryStats(pmd, name);
+  }
+  return size;
 }
 
 }  // namespace disk_cache
diff --git a/net/disk_cache/cobalt/cobalt_backend_impl.h b/net/disk_cache/cobalt/cobalt_backend_impl.h
index 171f952..c2b82f7 100644
--- a/net/disk_cache/cobalt/cobalt_backend_impl.h
+++ b/net/disk_cache/cobalt/cobalt_backend_impl.h
@@ -17,7 +17,17 @@
 #ifndef NET_DISK_CACHE_COBALT_COBALT_BACKEND_IMPL_H_
 #define NET_DISK_CACHE_COBALT_COBALT_BACKEND_IMPL_H_
 
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/callback_helpers.h"
+#include "net/base/completion_once_callback.h"
+#include "net/disk_cache/cobalt/resource_type.h"
 #include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/memory/mem_backend_impl.h"
+#include "net/disk_cache/simple/simple_backend_impl.h"
 
 namespace disk_cache {
 
@@ -25,15 +35,17 @@
 // the operations of the cache without writing to disk.
 class NET_EXPORT_PRIVATE CobaltBackendImpl final : public Backend {
  public:
-  explicit CobaltBackendImpl(net::NetLog* net_log);
+  explicit CobaltBackendImpl(
+      const base::FilePath& path,
+      scoped_refptr<BackendCleanupTracker> cleanup_tracker,
+      int64_t max_bytes,
+      net::CacheType cache_type,
+      net::NetLog* net_log);
+  CobaltBackendImpl(const CobaltBackendImpl&) = delete;
+  CobaltBackendImpl& operator=(const CobaltBackendImpl&) = delete;
   ~CobaltBackendImpl() override;
 
-  static std::unique_ptr<CobaltBackendImpl> CreateBackend(int64_t max_bytes,
-                                                          net::NetLog* net_log);
-
-  // Sets a callback to be posted after we are destroyed. Should be called at
-  // most once.
-  void SetPostCleanupCallback(base::OnceClosure cb);
+  net::Error Init(CompletionOnceCallback completion_callback);
 
   // Backend interface.
   net::CacheType GetCacheType() const override;
@@ -41,7 +53,6 @@
   net::Error OpenEntry(const std::string& key,
                        net::RequestPriority request_priority,
                        Entry** entry,
-                       std::string type,
                        CompletionOnceCallback callback) override;
   net::Error CreateEntry(const std::string& key,
                          net::RequestPriority request_priority,
@@ -69,15 +80,35 @@
       base::trace_event::ProcessMemoryDump* pmd,
       const std::string& parent_absolute_name) const override;
 
+  // A refcounted class that runs a CompletionOnceCallback once it's destroyed.
+  class RefCountedRunner : public base::RefCounted<RefCountedRunner> {
+   public:
+    explicit RefCountedRunner(CompletionOnceCallback completion_callback)
+        : destruction_callback_(
+              base::BindOnce(&RefCountedRunner::CompletionCallback,
+                             base::Unretained(this),
+                             std::move(completion_callback))) {}
+    void set_callback_result(int result) { callback_result_ = result; }
+
+   private:
+    friend class base::RefCounted<RefCountedRunner>;
+    ~RefCountedRunner() = default;
+
+    void CompletionCallback(CompletionOnceCallback callback) {
+      std::move(callback).Run(callback_result_);
+    }
+
+    base::ScopedClosureRunner destruction_callback_;
+    int callback_result_ = net::OK;
+  };
+
  private:
   class CobaltIterator;
   friend class CobaltIterator;
 
-  base::OnceClosure post_cleanup_callback_;
-
   base::WeakPtrFactory<CobaltBackendImpl> weak_factory_;
 
-  DISALLOW_COPY_AND_ASSIGN(CobaltBackendImpl);
+  std::map<ResourceType, SimpleBackendImpl*> simple_backend_map_;
 };
 
 }  // namespace disk_cache
diff --git a/net/disk_cache/cobalt/cobalt_disk_cache.cc b/net/disk_cache/cobalt/cobalt_disk_cache.cc
index 243a69a..42f79f2 100644
--- a/net/disk_cache/cobalt/cobalt_disk_cache.cc
+++ b/net/disk_cache/cobalt/cobalt_disk_cache.cc
@@ -1,24 +1,157 @@
-// Copyright 2022 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.
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
 
+#include "base/files/file_path.h"
 #include "base/metrics/field_trial.h"
+#include "base/task/task_scheduler/task_scheduler.h"
+#include "net/base/cache_type.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/backend_cleanup_tracker.h"
+#include "net/disk_cache/cache_util.h"
 #include "net/disk_cache/cobalt/cobalt_backend_impl.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/disk_cache/memory/mem_backend_impl.h"
+
+namespace {
+
+// Builds an instance of the backend depending on platform, type, experiments
+// etc. Takes care of the retry state. This object will self-destroy when
+// finished.
+class CacheCreator {
+ public:
+  CacheCreator(const base::FilePath& path,
+               bool force,
+               int64_t max_bytes,
+               net::CacheType type,
+               net::NetLog* net_log,
+               std::unique_ptr<disk_cache::Backend>* backend,
+               base::OnceClosure post_cleanup_callback,
+               net::CompletionOnceCallback callback);
+
+  net::Error TryCreateCleanupTrackerAndRun();
+
+  // Creates the backend, the cleanup context for it having been already
+  // established... or purposefully left as null.
+  net::Error Run();
+
+ private:
+  ~CacheCreator();
+
+  void DoCallback(int result);
+
+  void OnIOComplete(int result);
+
+  const base::FilePath path_;
+  bool force_;
+  bool retry_;
+  int64_t max_bytes_;
+  net::CacheType type_;
+  std::unique_ptr<disk_cache::Backend>* backend_;
+  base::OnceClosure post_cleanup_callback_;
+  net::CompletionOnceCallback callback_;
+  std::unique_ptr<disk_cache::Backend> created_cache_;
+  net::NetLog* net_log_;
+  scoped_refptr<disk_cache::BackendCleanupTracker> cleanup_tracker_;
+
+  DISALLOW_COPY_AND_ASSIGN(CacheCreator);
+};
+
+CacheCreator::CacheCreator(const base::FilePath& path,
+                           bool force,
+                           int64_t max_bytes,
+                           net::CacheType type,
+                           net::NetLog* net_log,
+                           std::unique_ptr<disk_cache::Backend>* backend,
+                           base::OnceClosure post_cleanup_callback,
+                           net::CompletionOnceCallback callback)
+    : path_(path),
+      force_(force),
+      retry_(false),
+      max_bytes_(max_bytes),
+      type_(type),
+      backend_(backend),
+      post_cleanup_callback_(std::move(post_cleanup_callback)),
+      callback_(std::move(callback)),
+      net_log_(net_log) {}
+
+CacheCreator::~CacheCreator() = default;
+
+net::Error CacheCreator::Run() {
+  disk_cache::CobaltBackendImpl* cobalt_cache =
+      new disk_cache::CobaltBackendImpl(path_, cleanup_tracker_.get(),
+                                        max_bytes_, type_, net_log_);
+  created_cache_.reset(cobalt_cache);
+  return cobalt_cache->Init(
+      base::Bind(&CacheCreator::OnIOComplete, base::Unretained(this)));
+}
+
+net::Error CacheCreator::TryCreateCleanupTrackerAndRun() {
+  // Before creating a cache Backend, a BackendCleanupTracker object is needed
+  // so there is a place to keep track of outstanding I/O even after the backend
+  // object itself is destroyed, so that further use of the directory
+  // doesn't race with those outstanding disk I/O ops.
+
+  // This method's purpose it to grab exlusive ownership of a fresh
+  // BackendCleanupTracker for the cache path, and then move on to Run(),
+  // which will take care of creating the actual cache backend. It's possible
+  // that something else is currently making use of the directory, in which
+  // case BackendCleanupTracker::TryCreate will fail, but will just have
+  // TryCreateCleanupTrackerAndRun run again at an opportune time to make
+  // another attempt.
+
+  // The resulting BackendCleanupTracker is stored into a scoped_refptr member
+  // so that it's kept alive while |this| CacheCreator exists , so that in the
+  // case Run() needs to retry Backend creation the same BackendCleanupTracker
+  // is used for both attempts, and |post_cleanup_callback_| gets called after
+  // the second try, not the first one.
+  cleanup_tracker_ = disk_cache::BackendCleanupTracker::TryCreate(
+      path_, base::BindOnce(base::IgnoreResult(
+                                &CacheCreator::TryCreateCleanupTrackerAndRun),
+                            base::Unretained(this)));
+  if (!cleanup_tracker_)
+    return net::ERR_IO_PENDING;
+  if (!post_cleanup_callback_.is_null())
+    cleanup_tracker_->AddPostCleanupCallback(std::move(post_cleanup_callback_));
+  return Run();
+}
+
+void CacheCreator::DoCallback(int result) {
+  DCHECK_NE(net::ERR_IO_PENDING, result);
+  if (result == net::OK) {
+    *backend_ = std::move(created_cache_);
+  } else {
+    LOG(ERROR) << "Unable to create cache";
+    created_cache_.reset();
+  }
+  std::move(callback_).Run(result);
+  delete this;
+}
+
+// If the initialization of the cache fails, and |force| is true, we will
+// discard the whole cache and create a new one.
+void CacheCreator::OnIOComplete(int result) {
+  if (result == net::OK || !force_ || retry_)
+    return DoCallback(result);
+
+  // This is a failure and we are supposed to try again, so delete the object,
+  // delete all the files, and try again.
+  retry_ = true;
+  created_cache_.reset();
+  if (!disk_cache::DelayedCacheCleanup(path_))
+    return DoCallback(result);
+
+  // The worker thread will start deleting files soon, but the original folder
+  // is not there anymore... let's create a new set of files.
+  int rv = Run();
+  DCHECK_EQ(net::ERR_IO_PENDING, rv);
+}
+
+}  // namespace
 
 namespace disk_cache {
 
 net::Error CreateCacheBackendImpl(net::CacheType type,
-                                  net::BackendType backend_type,
                                   const base::FilePath& path,
                                   int64_t max_bytes,
                                   bool force,
@@ -28,19 +161,32 @@
                                   net::CompletionOnceCallback callback) {
   DCHECK(!callback.is_null());
 
-  std::unique_ptr<CobaltBackendImpl> cobalt_backend_impl =
-      disk_cache::CobaltBackendImpl::CreateBackend(max_bytes, net_log);
-  if (cobalt_backend_impl) {
-    cobalt_backend_impl->SetPostCleanupCallback(
-        std::move(post_cleanup_callback));
-    *backend = std::move(cobalt_backend_impl);
-    return net::OK;
+  if (type == net::MEMORY_CACHE) {
+    std::unique_ptr<MemBackendImpl> mem_backend_impl =
+        disk_cache::MemBackendImpl::CreateBackend(max_bytes, net_log);
+    if (mem_backend_impl) {
+      mem_backend_impl->SetPostCleanupCallback(
+          std::move(post_cleanup_callback));
+      *backend = std::move(mem_backend_impl);
+      return net::OK;
+    } else {
+      if (!post_cleanup_callback.is_null())
+        base::SequencedTaskRunnerHandle::Get()->PostTask(
+            FROM_HERE, std::move(post_cleanup_callback));
+      return net::ERR_FAILED;
+    }
   }
 
-  if (!post_cleanup_callback.is_null())
-    base::SequencedTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, std::move(post_cleanup_callback));
-  return net::ERR_FAILED;
+  bool had_post_cleanup_callback = !post_cleanup_callback.is_null();
+  CacheCreator* creator =
+      new CacheCreator(path, force, max_bytes, type, net_log, backend,
+                       std::move(post_cleanup_callback), std::move(callback));
+  if (type == net::DISK_CACHE || type == net::MEDIA_CACHE) {
+    DCHECK(!had_post_cleanup_callback);
+    return creator->Run();
+  }
+
+  return creator->TryCreateCleanupTrackerAndRun();
 }
 
 net::Error CreateCacheBackend(net::CacheType type,
@@ -51,9 +197,8 @@
                               net::NetLog* net_log,
                               std::unique_ptr<Backend>* backend,
                               net::CompletionOnceCallback callback) {
-  return CreateCacheBackendImpl(type, backend_type, path, max_bytes, force,
-                                net_log, backend, base::OnceClosure(),
-                                std::move(callback));
+  return CreateCacheBackendImpl(type, path, max_bytes, force, net_log, backend,
+                                base::OnceClosure(), std::move(callback));
 }
 
 net::Error CreateCacheBackend(net::CacheType type,
@@ -65,9 +210,15 @@
                               std::unique_ptr<Backend>* backend,
                               base::OnceClosure post_cleanup_callback,
                               net::CompletionOnceCallback callback) {
-  return CreateCacheBackendImpl(
-      type, backend_type, path, max_bytes, force, net_log, backend,
-      std::move(post_cleanup_callback), std::move(callback));
+  return CreateCacheBackendImpl(type, path, max_bytes, force, net_log, backend,
+                                std::move(post_cleanup_callback),
+                                std::move(callback));
+}
+
+void FlushCacheThreadForTesting() {
+  // For simple backend.
+  SimpleBackendImpl::FlushWorkerPoolForTesting();
+  base::TaskScheduler::GetInstance()->FlushForTesting();
 }
 
 int64_t Backend::CalculateSizeOfEntriesBetween(
@@ -83,4 +234,4 @@
 
 void Backend::SetEntryInMemoryData(const std::string& key, uint8_t data) {}
 
-}  // namespace disk_cache
\ No newline at end of file
+}  // namespace disk_cache
diff --git a/net/disk_cache/cobalt/cobalt_entry_impl.cc b/net/disk_cache/cobalt/cobalt_entry_impl.cc
deleted file mode 100644
index 33bd3ae..0000000
--- a/net/disk_cache/cobalt/cobalt_entry_impl.cc
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2022 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 "net/disk_cache/cobalt/cobalt_entry_impl.h"
-
-using base::Time;
-
-namespace disk_cache {
-
-CobaltEntryImpl::CobaltEntryImpl(base::WeakPtr<CobaltBackendImpl> backend,
-                                 const std::string& key,
-                                 net::NetLog* net_log)
-    : CobaltEntryImpl(backend,
-                      key,
-                      0,        // child_id
-                      nullptr,  // parent
-                      net_log) {}
-
-CobaltEntryImpl::CobaltEntryImpl(base::WeakPtr<CobaltBackendImpl> backend,
-                                 int child_id,
-                                 CobaltEntryImpl* parent,
-                                 net::NetLog* net_log)
-    : CobaltEntryImpl(backend,
-                      std::string(),  // key
-                      child_id,
-                      parent,
-                      net_log) {
-  (*parent_->children_)[child_id] = this;
-}
-
-CobaltEntryImpl::CobaltEntryImpl(base::WeakPtr<CobaltBackendImpl> backend,
-                                 const ::std::string& key,
-                                 int child_id,
-                                 CobaltEntryImpl* parent,
-                                 net::NetLog* net_log)
-    : key_(key),
-      parent_(parent),
-      last_modified_(Time::Now()),
-      last_used_(last_modified_) {}
-
-void CobaltEntryImpl::Doom() {
-  // TODO: Implement
-}
-
-void CobaltEntryImpl::Close() {
-  // TODO: Implement
-}
-
-std::string CobaltEntryImpl::GetKey() const {
-  // A child entry doesn't have key so this method should not be called.
-  DCHECK_EQ(PARENT_ENTRY, type());
-  return key_;
-}
-
-Time CobaltEntryImpl::GetLastUsed() const {
-  return last_used_;
-}
-
-Time CobaltEntryImpl::GetLastModified() const {
-  return last_modified_;
-}
-
-int32_t CobaltEntryImpl::GetDataSize(int index) const {
-  // TODO: Implement
-  return 0;
-}
-
-int CobaltEntryImpl::ReadData(int index,
-                              int offset,
-                              IOBuffer* buf,
-                              int buf_len,
-                              CompletionOnceCallback callback) {
-  // TODO: Implement
-  return net::ERR_FAILED;
-}
-
-int CobaltEntryImpl::WriteData(int index,
-                               int offset,
-                               IOBuffer* buf,
-                               int buf_len,
-                               CompletionOnceCallback callback,
-                               bool truncate) {
-  // TODO: Implement
-  return net::ERR_FAILED;
-}
-
-int CobaltEntryImpl::ReadSparseData(int64_t offset,
-                                    IOBuffer* buf,
-                                    int buf_len,
-                                    CompletionOnceCallback callback) {
-  // TODO: Implement
-  return net::ERR_FAILED;
-}
-
-int CobaltEntryImpl::WriteSparseData(int64_t offset,
-                                     IOBuffer* buf,
-                                     int buf_len,
-                                     CompletionOnceCallback callback) {
-  // TODO: Implement
-  return net::ERR_FAILED;
-}
-
-int CobaltEntryImpl::GetAvailableRange(int64_t offset,
-                                       int len,
-                                       int64_t* start,
-                                       CompletionOnceCallback callback) {
-  // TODO: Implement
-  return 0;
-}
-
-bool CobaltEntryImpl::CouldBeSparse() const {
-  DCHECK_EQ(PARENT_ENTRY, type());
-  return (children_.get() != nullptr);
-}
-
-net::Error CobaltEntryImpl::ReadyForSparseIO(CompletionOnceCallback callback) {
-  return net::OK;
-}
-
-void CobaltEntryImpl::SetLastUsedTimeForTest(base::Time time) {
-  last_used_ = time;
-}
-}  // namespace disk_cache
diff --git a/net/disk_cache/cobalt/cobalt_entry_impl.h b/net/disk_cache/cobalt/cobalt_entry_impl.h
deleted file mode 100644
index 6206040..0000000
--- a/net/disk_cache/cobalt/cobalt_entry_impl.h
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2022 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 NET_DISK_CACHE_COBALT_COBALT_ENTRY_IMPL_H_
-#define NET_DISK_CACHE_COBALT_COBALT_ENTRY_IMPL_H_
-
-#include "base/trace_event/memory_usage_estimator.h"
-#include "net/disk_cache/disk_cache.h"
-
-namespace disk_cache {
-
-class CobaltBackendImpl;
-
-class NET_EXPORT_PRIVATE CobaltEntryImpl final
-    : public Entry,
-      public base::LinkNode<CobaltEntryImpl> {
- public:
-  enum EntryType {
-    PARENT_ENTRY,
-    CHILD_ENTRY,
-  };
-
-  // Constructor for parent entries.
-  CobaltEntryImpl(base::WeakPtr<CobaltBackendImpl> backend,
-                  const std::string& key,
-                  net::NetLog* net_log);
-
-  // Constructor for child entries.
-  CobaltEntryImpl(base::WeakPtr<CobaltBackendImpl> backend,
-                  int child_id,
-                  CobaltEntryImpl* parent,
-                  net::NetLog* net_log);
-
-  EntryType type() const { return parent_ ? CHILD_ENTRY : PARENT_ENTRY; }
-
-  // From disk_cache::Entry:
-  void Doom() override;
-  void Close() override;
-  std::string GetKey() const override;
-  base::Time GetLastUsed() const override;
-  base::Time GetLastModified() const override;
-  int32_t GetDataSize(int index) const override;
-  int ReadData(int index,
-               int offset,
-               IOBuffer* buf,
-               int buf_len,
-               CompletionOnceCallback callback) override;
-  int WriteData(int index,
-                int offset,
-                IOBuffer* buf,
-                int buf_len,
-                CompletionOnceCallback callback,
-                bool truncate) override;
-  int ReadSparseData(int64_t offset,
-                     IOBuffer* buf,
-                     int buf_len,
-                     CompletionOnceCallback callback) override;
-  int WriteSparseData(int64_t offset,
-                      IOBuffer* buf,
-                      int buf_len,
-                      CompletionOnceCallback callback) override;
-  int GetAvailableRange(int64_t offset,
-                        int len,
-                        int64_t* start,
-                        CompletionOnceCallback callback) override;
-  bool CouldBeSparse() const override;
-  void CancelSparseIO() override {}
-  net::Error ReadyForSparseIO(CompletionOnceCallback callback) override;
-  void SetLastUsedTimeForTest(base::Time time) override;
-
- private:
-  CobaltEntryImpl(base::WeakPtr<CobaltBackendImpl> backend,
-                  const std::string& key,
-                  int child_id,
-                  CobaltEntryImpl* parent,
-                  net::NetLog* net_log);
-
-  using EntryMap = std::map<int, CobaltEntryImpl*>;
-
-  std::string key_;
-
-  // Pointer to the parent entry, or nullptr if this entry is a parent entry.
-  CobaltEntryImpl* parent_ = nullptr;
-  std::unique_ptr<EntryMap> children_ = nullptr;
-
-  base::Time last_modified_;
-  base::Time last_used_;
-
-  DISALLOW_COPY_AND_ASSIGN(CobaltEntryImpl);
-};
-
-}  // namespace disk_cache
-
-#endif  // NET_DISK_CACHE_COBALT_COBALT_ENTRY_IMPL_H_
diff --git a/net/disk_cache/cobalt/resource_type.h b/net/disk_cache/cobalt/resource_type.h
index 13f2518..72af7d7 100644
--- a/net/disk_cache/cobalt/resource_type.h
+++ b/net/disk_cache/cobalt/resource_type.h
@@ -17,6 +17,7 @@
 
 namespace disk_cache {
 
+/* Note: If adding a new resource type, add corresponding metadata below. */
 enum ResourceType {
   kOther = 0,
   kHTML = 1,
@@ -29,6 +30,17 @@
   kTypeCount = 8
 };
 
+struct ResourceTypeMetadata {
+  std::string directory;
+  int64_t max_size_mb;
+};
+
+// TODO: Store sizes on disk.
+static ResourceTypeMetadata kTypeMetadata[] = {
+    {"other", 3}, {"html", 3},   {"css", 3},           {"image", 3},
+    {"font", 3},  {"splash", 3}, {"uncompiled_js", 3}, {"compiled_js", 3},
+};
+
 }  // namespace disk_cache
 
 #endif  // RESOURCE_TYPE_H_
diff --git a/net/disk_cache/disk_cache.h b/net/disk_cache/disk_cache.h
index 572bf0c..98f00cc 100644
--- a/net/disk_cache/disk_cache.h
+++ b/net/disk_cache/disk_cache.h
@@ -165,18 +165,10 @@
   // will be invoked when the entry is available. The pointer to receive the
   // |entry| must remain valid until the operation completes. The |priority|
   // of the entry determines its priority in the background worker pools.
-#ifdef STARBOARD
-  virtual net::Error OpenEntry(const std::string& key,
-                               net::RequestPriority priority,
-                               Entry** entry,
-                               std::string type,
-                               CompletionOnceCallback callback) = 0;
-#else
   virtual net::Error OpenEntry(const std::string& key,
                                net::RequestPriority priority,
                                Entry** entry,
                                CompletionOnceCallback callback) = 0;
-#endif
 
   // Creates a new entry. Upon success, the out param holds a pointer to an
   // Entry object representing the newly created disk cache entry. When the
diff --git a/net/disk_cache/simple/simple_backend_impl.cc b/net/disk_cache/simple/simple_backend_impl.cc
index d909eae..fe2d236 100644
--- a/net/disk_cache/simple/simple_backend_impl.cc
+++ b/net/disk_cache/simple/simple_backend_impl.cc
@@ -116,7 +116,7 @@
 SimpleCacheConsistencyResult FileStructureConsistent(
     const base::FilePath& path) {
   if (!base::PathExists(path) && !base::CreateDirectory(path)) {
-    LOG(ERROR) << "Failed to create directory: " << path.LossyDisplayName();
+    LOG(ERROR) << "Failed to create directory: " << path.LossyDisplayName().c_str();
     return SimpleCacheConsistencyResult::kCreateDirectoryFailed;
   }
   return disk_cache::UpgradeSimpleCacheOnDisk(path);
@@ -724,7 +724,7 @@
   if (consistency != SimpleCacheConsistencyResult::kOK) {
     LOG(ERROR) << "Simple Cache Backend: wrong file structure on disk: "
                << static_cast<int>(consistency)
-               << " path: " << path.LossyDisplayName();
+               << " path: " << path.LossyDisplayName().c_str();
     result.net_error = net::ERR_FAILED;
   } else {
     bool mtime_result =
diff --git a/net/disk_cache/simple/simple_index_file.cc b/net/disk_cache/simple/simple_index_file.cc
index 68b3ee9..0afa575 100644
--- a/net/disk_cache/simple/simple_index_file.cc
+++ b/net/disk_cache/simple/simple_index_file.cc
@@ -24,6 +24,7 @@
 #include "net/disk_cache/simple/simple_index.h"
 #include "net/disk_cache/simple/simple_synchronous_entry.h"
 #include "net/disk_cache/simple/simple_util.h"
+#include "starboard/file.h"
 
 namespace disk_cache {
 namespace {
@@ -323,18 +324,20 @@
     return;
   }
   SerializeFinalData(cache_dir_mtime, pickle.get());
+#if defined(STARBOARD)
+  const char* data = static_cast<const char*>(pickle.get()->data());
+  if (!SbFileAtomicReplace(index_filename.value().c_str(), data, pickle.get()->size()))
+    return;
+#else
   if (!WritePickleFile(pickle.get(), temp_index_filename)) {
     LOG(ERROR) << "Failed to write the temporary index file";
     return;
   }
 
-#if defined(STARBOARD)
-  NOTIMPLEMENTED() << "Starboard does not support file replacement.";
-  return;
-#else
   // Atomically rename the temporary index file to become the real one.
   if (!base::ReplaceFile(temp_index_filename, index_filename, NULL))
     return;
+#endif
 
   if (app_on_background) {
     SIMPLE_CACHE_UMA(TIMES,
@@ -345,7 +348,6 @@
                      "IndexWriteToDiskTime.Foreground", cache_type,
                      (base::TimeTicks::Now() - start_time));
   }
-#endif
 }
 
 bool SimpleIndexFile::IndexMetadata::CheckIndexMetadata() {
diff --git a/net/disk_cache/simple/simple_index_file_starboard.cc b/net/disk_cache/simple/simple_index_file_starboard.cc
index bba6cd4..47e0764 100644
--- a/net/disk_cache/simple/simple_index_file_starboard.cc
+++ b/net/disk_cache/simple/simple_index_file_starboard.cc
@@ -31,6 +31,7 @@
   SbFileError error;
   SbDirectory dir = SbDirectoryOpen(cache_path.value().c_str(), &error);
   if (dir == kSbDirectoryInvalid) {
+    SbDirectoryClose(dir);
     PLOG(ERROR) << "opendir " << cache_path.value() << ", erron: " << error;
     return false;
   }
@@ -39,8 +40,7 @@
 
   while (true) {
     if (!SbDirectoryGetNext(dir, entry.data(), entry.size())) {
-      PLOG(ERROR) << "readdir " << cache_path.value();
-      return false;
+      break;
     }
 
     const std::string file_name(entry.data());
@@ -58,6 +58,7 @@
                             file_info.last_modified, file_info.size);
   }
 
+  SbDirectoryClose(dir);
   return true;
 }
 
diff --git a/net/disk_cache/simple/simple_synchronous_entry.cc b/net/disk_cache/simple/simple_synchronous_entry.cc
index e4cd4c3..c8ca20a 100644
--- a/net/disk_cache/simple/simple_synchronous_entry.cc
+++ b/net/disk_cache/simple/simple_synchronous_entry.cc
@@ -1403,6 +1403,7 @@
   std::unique_ptr<char[]> prefetch_buf;
   base::StringPiece file_0_prefetch;
 
+#if !defined(STARBOARD)
   if (file_size > GetSimpleCachePrefetchSize()) {
     RecordWhetherOpenDidPrefetch(cache_type_, false);
   } else {
@@ -1412,6 +1413,7 @@
       return net::ERR_FAILED;
     file_0_prefetch.set(prefetch_buf.get(), file_size);
   }
+#endif
 
   // Read stream 0 footer first --- it has size/feature info required to figure
   // out file 0's layout.
diff --git a/net/disk_cache/simple/simple_version_upgrade.cc b/net/disk_cache/simple/simple_version_upgrade.cc
index dfff9f7..e1419f8 100644
--- a/net/disk_cache/simple/simple_version_upgrade.cc
+++ b/net/disk_cache/simple/simple_version_upgrade.cc
@@ -45,7 +45,7 @@
                                  sizeof(file_contents));
   if (bytes_written != sizeof(file_contents)) {
     LOG(ERROR) << "Failed to write fake index file: "
-               << file_name.LossyDisplayName();
+               << file_name.LossyDisplayName().c_str();
     return false;
   }
   return true;
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc
index ea19ca9..08346a8 100644
--- a/net/http/http_cache.cc
+++ b/net/http/http_cache.cc
@@ -37,6 +37,7 @@
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
 #include "net/base/upload_data_stream.h"
+#include "net/disk_cache/cobalt/resource_type.h"
 #include "net/disk_cache/disk_cache.h"
 #include "net/http/http_cache_lookup_manager.h"
 #include "net/http/http_cache_transaction.h"
@@ -593,6 +594,13 @@
                base::StringPrintf("%" PRId64 "/",
                                   request->upload_data_stream->identifier()));
   }
+#if defined(STARBOARD)
+  if (request->extra_headers.HasHeader(HttpRequestHeaders::kResourceType)) {
+    std::string type = std::to_string(disk_cache::kOther);
+    request->extra_headers.GetHeader(HttpRequestHeaders::kResourceType, &type);
+    url.insert(0, type + "/");
+  }
+#endif
   return url;
 }
 
@@ -776,18 +784,10 @@
   DCHECK(pending_op->pending_queue.empty());
 
   pending_op->writer = std::move(item);
-
-#ifdef STARBOARD
-  int rv = disk_cache_->OpenEntry(
-      key, trans->priority(), &(pending_op->disk_entry),  trans->type(),
-      base::BindOnce(&HttpCache::OnPendingOpComplete, GetWeakPtr(),
-                     pending_op));
-#else
   int rv =
       disk_cache_->OpenEntry(key, trans->priority(), &(pending_op->disk_entry),
                              base::BindOnce(&HttpCache::OnPendingOpComplete,
                                             GetWeakPtr(), pending_op));
-#endif
 
   if (rv == ERR_IO_PENDING) {
     pending_op->callback_will_delete = true;
diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc
index 13123c8..da934ee 100644
--- a/net/http/http_cache_transaction.cc
+++ b/net/http/http_cache_transaction.cc
@@ -2350,10 +2350,6 @@
 
   if (request_->extra_headers.HasHeader(HttpRequestHeaders::kRange))
     range_found = true;
-#if defined(STARBOARD)
-  if (request_->extra_headers.HasHeader(HttpRequestHeaders::kResourceType))
-    request_->extra_headers.GetHeader(HttpRequestHeaders::kResourceType, &type_);
-#endif
 
   for (size_t i = 0; i < arraysize(kSpecialHeaders); ++i) {
     if (HeaderMatches(request_->extra_headers, kSpecialHeaders[i].search)) {
diff --git a/net/http/http_cache_transaction.h b/net/http/http_cache_transaction.h
index 1012b39..57063c7 100644
--- a/net/http/http_cache_transaction.h
+++ b/net/http/http_cache_transaction.h
@@ -195,11 +195,6 @@
   // value has been set to something other than PARALLEL_WRITING_NONE.
   void MaybeSetParallelWritingPatternForMetrics(ParallelWritingPattern pattern);
 
-#if defined(STARBOARD)
-  // Returns resource type of request, if set.
-  std::string type() { return type_; }
-#endif
-
  private:
   static const size_t kNumValidationHeaders = 2;
   // Helper struct to pair a header name with its value, for
@@ -587,10 +582,6 @@
   // If extra_headers specified a "if-modified-since" or "if-none-match",
   // |external_validation_| contains the value of those headers.
   ValidationHeaders external_validation_;
-#if defined(STARBOARD)
-  // If extra_headers specified a resource type.
-  std::string type_;
-#endif
   base::WeakPtr<HttpCache> cache_;
   HttpCache::ActiveEntry* entry_;
   HttpCache::ActiveEntry* new_entry_;
diff --git a/net/http/mock_http_cache.cc b/net/http/mock_http_cache.cc
index 3e90275..7383d2b 100644
--- a/net/http/mock_http_cache.cc
+++ b/net/http/mock_http_cache.cc
@@ -420,7 +420,6 @@
 net::Error MockDiskCache::OpenEntry(const std::string& key,
                                     net::RequestPriority request_priority,
                                     disk_cache::Entry** entry,
-                                    std::string type,
                                     CompletionOnceCallback callback) {
   DCHECK(!callback.is_null());
   if (fail_requests_)
@@ -705,7 +704,7 @@
 bool MockHttpCache::OpenBackendEntry(const std::string& key,
                                      disk_cache::Entry** entry) {
   TestCompletionCallback cb;
-  int rv = backend()->OpenEntry(key, net::HIGHEST, entry, "0", cb.callback());
+  int rv = backend()->OpenEntry(key, net::HIGHEST, entry, cb.callback());
   return (cb.GetResult(rv) == OK);
 }
 
diff --git a/net/http/mock_http_cache.h b/net/http/mock_http_cache.h
index 70a928a..bfd0822 100644
--- a/net/http/mock_http_cache.h
+++ b/net/http/mock_http_cache.h
@@ -151,7 +151,6 @@
   net::Error OpenEntry(const std::string& key,
                        net::RequestPriority request_priority,
                        disk_cache::Entry** entry,
-                       std::string type,
                        CompletionOnceCallback callback) override;
   net::Error CreateEntry(const std::string& key,
                          net::RequestPriority request_priority,
diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc
index 11e37d2..b4a9ab4 100644
--- a/net/url_request/url_request.cc
+++ b/net/url_request/url_request.cc
@@ -1157,6 +1157,7 @@
 
     ConvertRealLoadTimesToBlockingTimes(&load_timing_info_);
 #if defined (STARBOARD)
+    load_timing_info_.encoded_body_size = static_cast<uint64_t>(GetTotalReceivedBytes());
     if (!load_timing_info_callback_.is_null()) {
       load_timing_info_callback_.Run(load_timing_info_);
     }
diff --git a/starboard/BUILD.gn b/starboard/BUILD.gn
index 741b336..156a37e 100644
--- a/starboard/BUILD.gn
+++ b/starboard/BUILD.gn
@@ -26,6 +26,7 @@
     "//starboard/loader_app:app_key_files_test",
     "//starboard/nplb",
     "//starboard/nplb/nplb_evergreen_compat_tests",
+    "//starboard/tools/testing:copy_sharding_configuration",
   ]
 
   if (gl_type != "none") {
diff --git a/starboard/CHANGELOG.md b/starboard/CHANGELOG.md
index 0dfdc47..ab75b28 100644
--- a/starboard/CHANGELOG.md
+++ b/starboard/CHANGELOG.md
@@ -14,16 +14,43 @@
 can be found in the comments of the "Experimental Feature Defines" section of
 [configuration.h](configuration.h).
 
-### Deprecate the usage of SB_HAS_PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT.
+## Version 14
+### Add kSbSystemDeviceTypeVideoProjector type to Starboard devices.
+This adds a video projector type to Starboard devices.
+
+### Deprecated SystemPathTestOutputDirectory.
+This duplicated SystemPathDebugOutputDirectory without a useful distinction.
+
+### Introduce a new format kSbDecodeTargetFormat3Plane10BitYUVI420Compact.
+A decoder target format consisting of 10bit Y, U, and V planes.
+
+### Deprecated the usage of SB_HAS_PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT.
 The improvements on player creation and output mode query (like
 `SbPlayerCreationParam` and `SbPlayerGetPreferredOutputMode()`) are always
 enabled.  This change also deprecates `SbPlayerOutputModeSupported()`.
 
-### Deprecate the usage of SB_HAS_MEDIA_IS_VIDEO_SUPPORTED_REFINEMENT
+### Deprecated the usage of SB_HAS_MEDIA_IS_VIDEO_SUPPORTED_REFINEMENT
 The extra parameters (like `profile` and `level`) on `SbMediaIsVideoSupported()`
 are always enabled.  This change also deprecated
 `SbMediaIsTransferCharacteristicsSupported()`.
 
+### Deprecated kSbMediaMatrixIdUnknown and added kSbMediaMatrixIdInvalid.
+`kSbMediaMatrixIdUnknown` has been deprecated. `kSbMediaMatrixIdInvalid`
+was added.
+
+### Deprecated `SbMediaType type` parameter to some SbMedia buffer functions.
+`SbMediaGetBufferAlignment()` and `SbMediaGetBufferPadding()` no longer accept
+`SbMediaType type` as parameters.  The implementation has to return the same
+values for both audio and video streams.
+
+### Made `starboard::QueueApplication::DispatchAndDelete` private
+Since `starboard::shared::starboard::QueueApplication` has an event queue,
+`QueueApplication::Inject` should be used if an event just needs to be added to
+the queue or `QueueApplication::InjectAndProcess` if the event needs to be
+handled before returning. `QueueApplication::DispatchAndDelete` jumps the event
+queue and could lead to bugs due to the event (especially lifecycle events)
+being handled out of order.
+
 ## Version 13
 ### Changed lifecycle events to add support for a concealed state.
 
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ResourceOverlay.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ResourceOverlay.java
index f14abec..69e08ea 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ResourceOverlay.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ResourceOverlay.java
@@ -28,13 +28,21 @@
   @UsedByNative
   public final boolean supports_spherical_videos;
 
+  @SuppressWarnings("MemberName")
+  @UsedByNative
   public final int max_video_buffer_budget;
 
+  @SuppressWarnings("MemberName")
+  @UsedByNative
+  public final int min_audio_sink_buffer_size_in_frames;
+
   public ResourceOverlay(Context context) {
     // Load the values for all Overlay variables.
     this.supports_spherical_videos =
         context.getResources().getBoolean(R.bool.supports_spherical_videos);
     this.max_video_buffer_budget =
         context.getResources().getInteger(R.integer.max_video_buffer_budget);
+    this.min_audio_sink_buffer_size_in_frames =
+        context.getResources().getInteger(R.integer.min_audio_sink_buffer_size_in_frames);
   }
 }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/VideoDecoderCache.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/VideoDecoderCache.java
index fe17de3..abeffe6 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/VideoDecoderCache.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/VideoDecoderCache.java
@@ -100,7 +100,9 @@
       if (isExpired(cacheTtlOverride)) {
         refreshDecoders();
       }
-      return cache.getOrDefault(mimeType, Collections.<CachedDecoder>emptyList());
+      return cache.containsKey(mimeType)
+          ? cache.get(mimeType)
+          : Collections.<CachedDecoder>emptyList();
     }
   }
 }
diff --git a/starboard/android/apk/app/src/main/res/values/overlayable.xml b/starboard/android/apk/app/src/main/res/values/overlayable.xml
index ec87d0c..a968b69 100644
--- a/starboard/android/apk/app/src/main/res/values/overlayable.xml
+++ b/starboard/android/apk/app/src/main/res/values/overlayable.xml
@@ -26,6 +26,12 @@
         to this value in MB. Set to 0 to use default values.
       -->
       <item type="integer" name="max_video_buffer_budget" />
+      <!--
+        When set, the return value of SbAudioSinkGetMinBufferSizeInFrames()
+        would be greater or equal to this value. Set to 0 to use default
+        values.
+      -->
+      <item type="integer" name="min_audio_sink_buffer_size_in_frames" />
     </policy>
   </overlayable>
 </resources>
diff --git a/starboard/android/apk/app/src/main/res/values/rro_variables.xml b/starboard/android/apk/app/src/main/res/values/rro_variables.xml
index f97a99c..c237a07 100644
--- a/starboard/android/apk/app/src/main/res/values/rro_variables.xml
+++ b/starboard/android/apk/app/src/main/res/values/rro_variables.xml
@@ -24,4 +24,11 @@
   -->
   <integer name="max_video_buffer_budget"
            tools:ignore="DuplicateDefinition">0</integer>
+  <!--
+     When set, the return value of SbAudioSinkGetMinBufferSizeInFrames()
+     would be greater or equal to this value. Set to 0 to use default values.
+  -->
+  <integer name="min_audio_sink_buffer_size_in_frames"
+           tools:ignore="DuplicateDefinition">0</integer>
+
 </resources>
diff --git a/starboard/android/arm64/cobalt/__init__.py b/starboard/android/arm64/cobalt/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/starboard/android/arm64/cobalt/__init__.py
+++ /dev/null
diff --git a/starboard/android/arm64/cobalt/configuration.py b/starboard/android/arm64/cobalt/configuration.py
deleted file mode 100644
index 63995c0..0000000
--- a/starboard/android/arm64/cobalt/configuration.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2022 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.
-"""Starboard Android arm64 Cobalt configuration."""
-
-from starboard.android.shared.cobalt import configuration
-from starboard.tools.testing import test_filter
-
-
-class CobaltAndroidARM64Configuration(configuration.CobaltAndroidConfiguration):
-  """Starboard Android arm64 Cobalt configuration."""
-
-  def GetTestFilters(self):
-    filters = super(CobaltAndroidARM64Configuration, self).GetTestFilters()
-    for target, tests in self.__FILTERED_TESTS.items():
-      filters.extend(test_filter.TestFilter(target, test) for test in tests)
-    return filters
-
-  # pylint:disable=line-too-long
-  # A map of failing or crashing tests per target
-  __FILTERED_TESTS = {  # pylint: disable=invalid-name
-      'net_unittests': [
-          # NetlinkConnection::SendRequest() fails, send() failed, errno=13.
-          'DialHttpServerTest.*',
-      ],
-  }
diff --git a/starboard/android/arm64/test_filters.py b/starboard/android/arm64/test_filters.py
index 3b301de..e0454ca 100644
--- a/starboard/android/arm64/test_filters.py
+++ b/starboard/android/arm64/test_filters.py
@@ -14,22 +14,6 @@
 """Starboard Android ARM-64 Platform Test Filters."""
 
 from starboard.android.shared import test_filters as shared_test_filters
-from starboard.tools.testing import test_filter
-
-# pylint:disable=line-too-long
-# A map of failing or crashing tests per target.
-_FILTERED_TESTS = {
-    'nplb': [
-        # NetlinkConnection::SendRequest() fails, send() failed, errno=13.
-        'SbSocketBindTest.SunnyDayLocalInterface',
-        'SbSocketGetInterfaceAddressTest.SunnyDay',
-        'SbSocketGetInterfaceAddressTest.SunnyDayNullDestination',
-        'SbSocketGetLocalAddressTest.SunnyDayBoundSpecified',
-        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDayDestination/*',
-        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceForDestination/*',
-        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceNotLoopback/*',
-    ],
-}
 
 
 def CreateTestFilters():
@@ -41,6 +25,4 @@
 
   def GetTestFilters(self):
     filters = super(AndroidArm64TestFilters, self).GetTestFilters()
-    for target, tests in _FILTERED_TESTS.items():
-      filters.extend(test_filter.TestFilter(target, test) for test in tests)
     return filters
diff --git a/starboard/android/examples/overlays/res/xml/overlays.xml b/starboard/android/examples/overlays/res/xml/overlays.xml
index 7670021..ae898a2 100644
--- a/starboard/android/examples/overlays/res/xml/overlays.xml
+++ b/starboard/android/examples/overlays/res/xml/overlays.xml
@@ -23,4 +23,9 @@
     this value in MB. Set to 0 to use default values.
   -->
   <item target="integer/max_video_buffer_budget" value="0" />
+  <!--
+    When set, the return value of SbAudioSinkGetMinBufferSizeInFrames() would
+    be greater or equal to this value. Set to 0 to use default values.
+  -->
+  <item target="integer/min_audio_sink_buffer_size_in_frames" value="0" />
 </overlay>
diff --git a/starboard/android/shared/application_android.cc b/starboard/android/shared/application_android.cc
index b2eecf4..fc79766 100644
--- a/starboard/android/shared/application_android.cc
+++ b/starboard/android/shared/application_android.cc
@@ -253,15 +253,17 @@
     // (rather than to the Activity lifecycle) since Cobalt can't do anything at
     // all if it doesn't have a window surface to draw on.
     case AndroidCommand::kNativeWindowCreated: {
-      ScopedLock lock(android_command_mutex_);
-      native_window_ = static_cast<ANativeWindow*>(cmd.data);
-      if (window_) {
-        window_->native_window = native_window_;
+      {
+        ScopedLock lock(android_command_mutex_);
+        native_window_ = static_cast<ANativeWindow*>(cmd.data);
+        if (window_) {
+          window_->native_window = native_window_;
+        }
+        // Now that we have the window, signal that the Android UI thread can
+        // continue, before we start or resume the Starboard app.
+        android_command_condition_.Signal();
       }
-      // Now that we have the window, signal that the Android UI thread can
-      // continue, before we start or resume the Starboard app.
-      android_command_condition_.Signal();
-    }
+
       if (state() == kStateUnstarted) {
         // This is the initial launch, so we have to start Cobalt now that we
         // have a window.
@@ -277,20 +279,19 @@
         sync_state = activity_state_;
       }
       break;
-    case AndroidCommand::kNativeWindowDestroyed:
+    }
+    case AndroidCommand::kNativeWindowDestroyed: {
       // No need to JNI call StarboardBridge.beforeSuspend() since we did it
       // early in SendAndroidCommand().
       {
         ScopedLock lock(android_command_mutex_);
-// Cobalt can't keep running without a window, even if the Activity
-// hasn't stopped yet. DispatchAndDelete() will inject events as needed
-// if we're not already paused.
-#if SB_API_VERSION >= 13
-        DispatchAndDelete(new Event(kSbEventTypeConceal,
-                                    SbTimeGetMonotonicNow(), NULL, NULL));
-#else   // SB_API_VERSION >= 13
-        DispatchAndDelete(new Event(kSbEventTypeConceal, NULL, NULL));
-#endif  // SB_API_VERSION >= 13
+        // Cobalt can't keep running without a window, even if the Activity
+        // hasn't stopped yet. Block until conceal event has been processed.
+
+        // Only process injected events -- don't check system events since
+        // that may try to acquire the already-locked android_command_mutex_.
+        InjectAndProcess(kSbEventTypeConceal, /* checkSystemEvents */ false);
+
         if (window_) {
           window_->native_window = NULL;
         }
@@ -300,7 +301,7 @@
         android_command_condition_.Signal();
       }
       break;
-
+    }
     case AndroidCommand::kWindowFocusLost:
       break;
     case AndroidCommand::kWindowFocusGained: {
@@ -319,8 +320,7 @@
                      settings.is_high_contrast_text_enabled;
 
       if (enabled != last_is_accessibility_high_contrast_text_enabled_) {
-        DispatchAndDelete(
-            new Event(kSbEventTypeAccessibilitySettingsChanged, NULL, NULL));
+        Inject(new Event(kSbEventTypeAccessibilitySettingsChanged, NULL, NULL));
       }
       last_is_accessibility_high_contrast_text_enabled_ = enabled;
       break;
@@ -333,7 +333,7 @@
     case AndroidCommand::kStop:
       sync_state = activity_state_ = cmd.type;
       break;
-    case AndroidCommand::kDeepLink:
+    case AndroidCommand::kDeepLink: {
       char* deep_link = static_cast<char*>(cmd.data);
       SB_LOG(INFO) << "AndroidCommand::kDeepLink: deep_link=" << deep_link
                    << " state=" << state();
@@ -353,63 +353,28 @@
         }
       }
       break;
+    }
   }
 
-// If there's a window, sync the app state to the Activity lifecycle, letting
-// DispatchAndDelete() inject events as needed if we missed a state.
-#if SB_API_VERSION >= 13
+  // If there's a window, sync the app state to the Activity lifecycle.
   if (native_window_) {
     switch (sync_state) {
       case AndroidCommand::kStart:
-        DispatchAndDelete(
-            new Event(kSbEventTypeReveal, SbTimeGetMonotonicNow(), NULL, NULL));
+        Inject(new Event(kSbEventTypeReveal, NULL, NULL));
         break;
       case AndroidCommand::kResume:
-        DispatchAndDelete(
-            new Event(kSbEventTypeFocus, SbTimeGetMonotonicNow(), NULL, NULL));
+        Inject(new Event(kSbEventTypeFocus, NULL, NULL));
         break;
       case AndroidCommand::kPause:
-        DispatchAndDelete(
-            new Event(kSbEventTypeBlur, SbTimeGetMonotonicNow(), NULL, NULL));
+        Inject(new Event(kSbEventTypeBlur, NULL, NULL));
         break;
       case AndroidCommand::kStop:
-        if (state() != kStateConcealed && state() != kStateFrozen) {
-          // We usually conceal when losing the window above, but if the window
-          // wasn't destroyed (e.g. when Daydream starts) then we still have to
-          // conceal when the Activity is stopped.
-          DispatchAndDelete(new Event(kSbEventTypeConceal,
-                                      SbTimeGetMonotonicNow(), NULL, NULL));
-        }
+        Inject(new Event(kSbEventTypeConceal, NULL, NULL));
         break;
       default:
         break;
     }
   }
-#else   // SB_API_VERSION >= 13
-  if (native_window_) {
-    switch (sync_state) {
-      case AndroidCommand::kStart:
-        DispatchAndDelete(new Event(kSbEventTypeReveal, NULL, NULL));
-        break;
-      case AndroidCommand::kResume:
-        DispatchAndDelete(new Event(kSbEventTypeFocus, NULL, NULL));
-        break;
-      case AndroidCommand::kPause:
-        DispatchAndDelete(new Event(kSbEventTypeBlur, NULL, NULL));
-        break;
-      case AndroidCommand::kStop:
-        if (state() != kStateConcealed && state() != kStateFrozen) {
-          // We usually conceal when losing the window above, but if the window
-          // wasn't destroyed (e.g. when Daydream starts) then we still have to
-          // conceal when the Activity is stopped.
-          DispatchAndDelete(new Event(kSbEventTypeConceal, NULL, NULL));
-        }
-        break;
-      default:
-        break;
-    }
-  }
-#endif  // SB_API_VERSION >= 13
 }
 
 void ApplicationAndroid::SendAndroidCommand(AndroidCommand::CommandType type,
@@ -453,7 +418,7 @@
     bool handled = input_events_generator_->CreateInputEventsFromAndroidEvent(
         android_event, &app_events);
     for (int i = 0; i < app_events.size(); ++i) {
-      DispatchAndDelete(app_events[i].release());
+      Inject(app_events[i].release());
     }
     AInputQueue_finishEvent(input_queue_, android_event, handled);
   }
@@ -471,7 +436,7 @@
   InputEventsGenerator::Events app_events;
   input_events_generator_->CreateInputEventsFromSbKey(key, &app_events);
   for (int i = 0; i < app_events.size(); ++i) {
-    DispatchAndDelete(app_events[i].release());
+    Inject(app_events[i].release());
   }
 }
 
diff --git a/starboard/android/shared/audio_sink_get_min_buffer_size_in_frames.cc b/starboard/android/shared/audio_sink_get_min_buffer_size_in_frames.cc
index c6af790..14afd52 100644
--- a/starboard/android/shared/audio_sink_get_min_buffer_size_in_frames.cc
+++ b/starboard/android/shared/audio_sink_get_min_buffer_size_in_frames.cc
@@ -14,6 +14,7 @@
 
 #include "starboard/audio_sink.h"
 
+#include "starboard/android/shared/application_android.h"
 #include "starboard/android/shared/audio_track_audio_sink_type.h"
 #include "starboard/common/log.h"
 
@@ -34,6 +35,15 @@
     return -1;
   }
 
-  return starboard::android::shared::AudioTrackAudioSinkType::
+  int min_buffer_size = starboard::android::shared::AudioTrackAudioSinkType::
       GetMinBufferSizeInFrames(channels, sample_type, sampling_frequency_hz);
+
+  int overlayed_min_buffer_size =
+      starboard::android::shared::ApplicationAndroid::Get()
+          ->GetOverlayedIntValue("min_audio_sink_buffer_size_in_frames");
+  if (overlayed_min_buffer_size != 0 &&
+      overlayed_min_buffer_size > min_buffer_size) {
+    min_buffer_size = overlayed_min_buffer_size;
+  }
+  return min_buffer_size;
 }
diff --git a/starboard/android/shared/launcher.py b/starboard/android/shared/launcher.py
index d7b2214..79d2e67 100644
--- a/starboard/android/shared/launcher.py
+++ b/starboard/android/shared/launcher.py
@@ -112,7 +112,7 @@
 
   def _Run(self):
     while True:
-      line = CleanLine(self.process.stdout.readline())
+      line = CleanLine(self.process.stdout.readline().decode())
       if not line:
         return
       # Show the crash lines reported by "am monitor".
@@ -187,7 +187,7 @@
 
     names = []
     for device in result:
-      name_info = device.split('\t')
+      name_info = device.decode().split('\t')
       # Some devices may not have authorization for USB debugging.
       try:
         if 'unauthorized' not in name_info[1]:
@@ -280,8 +280,8 @@
     # TODO: Need to wait until cobalt fully shutdown. Otherwise, it may get
     # dirty logs from previous test, and logs like "***Application Stopped***"
     # will cause unexpected errors.
-    # Simply wait 2s as a temporary solution.
-    time.sleep(2)
+    # Simply wait 5s as a temporary solution.
+    time.sleep(5)
     # Clear logcat
     self._CheckCallAdb('logcat', '-c')
 
@@ -355,7 +355,7 @@
 
         # Note we cannot use "for line in logcat_process.stdout" because
         # that uses a large buffer which will cause us to deadlock.
-        line = CleanLine(logcat_process.stdout.readline())
+        line = CleanLine(logcat_process.stdout.readline().decode())
 
         # Some crashes are not caught by the am_monitor thread, but they do
         # produce the following string in logcat before they exit.
@@ -439,7 +439,8 @@
         'forward', 'tcp:0', 'tcp:{}'.format(port), stdout=subprocess.PIPE)
     forward_p.wait()
 
-    self.local_port = CleanLine(forward_p.stdout.readline()).rstrip('\n')
+    self.local_port = CleanLine(
+        forward_p.stdout.readline().decode()).rstrip('\n')
     sys.stderr.write('ADB forward local port {} '
                      '=> device port {}\n'.format(self.local_port, port))
     # pylint: disable=g-socket-gethostbyname
diff --git a/starboard/android/shared/socket_get_interface_address.cc b/starboard/android/shared/socket_get_interface_address.cc
index ce871bd..fc12f06 100644
--- a/starboard/android/shared/socket_get_interface_address.cc
+++ b/starboard/android/shared/socket_get_interface_address.cc
@@ -67,6 +67,12 @@
       "getLocalInterfaceAddressAndNetmask", "(Z)Landroid/util/Pair;",
       want_ipv6);
 
+  if (!pair) {
+    SB_LOG(ERROR) << "Null value returned from JNI call to "
+                     "getLocalInterfaceAddressAndNetmask.";
+    return false;
+  }
+
   jobject field;
   field = env->GetObjectFieldOrAbort(pair, "first", "Ljava/lang/Object;");
   if (!CopySocketAddress(static_cast<jbyteArray>(field), out_source_address)) {
diff --git a/starboard/android/shared/system_get_path.cc b/starboard/android/shared/system_get_path.cc
index a290a56..2ca0815 100644
--- a/starboard/android/shared/system_get_path.cc
+++ b/starboard/android/shared/system_get_path.cc
@@ -79,7 +79,7 @@
       break;
     }
 
-#if SB_API_VERSION < SB_SYSTEM_PATH_TEST_OUTPUT_DIRECTORY_DEPRECATED
+#if SB_API_VERSION < 14
     case kSbSystemPathTestOutputDirectory: {
       return SbSystemGetPath(kSbSystemPathDebugOutputDirectory, out_path,
                              path_size);
diff --git a/starboard/build/config/base_configuration.gni b/starboard/build/config/base_configuration.gni
index fdf5cf5..ac3c932 100644
--- a/starboard/build/config/base_configuration.gni
+++ b/starboard/build/config/base_configuration.gni
@@ -25,7 +25,7 @@
 
   # The Starboard API version of the current build configuration. The default
   # value is meant to be overridden by a Starboard ABI file.
-  sb_api_version = 14
+  sb_api_version = 15
 
   # Enables embedding Cobalt as a shared library within another app. This
   # requires a 'lib' starboard implementation for the corresponding platform.
diff --git a/starboard/build/copy_data.py b/starboard/build/copy_data.py
deleted file mode 100644
index b0d88e3..0000000
--- a/starboard/build/copy_data.py
+++ /dev/null
@@ -1,171 +0,0 @@
-#!/usr/bin/python
-# Copyright 2014 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# This file is based on build/copy_test_data_ios.py
-"""Copies data files or directories into a given output directory.
-
-Since the output of this script is intended to be use by GYP, all resulting
-paths are using Unix-style forward flashes.
-"""
-
-import argparse
-import logging
-import os
-import posixpath
-import shutil
-import sys
-if os.name == 'nt':
-  import pywintypes
-  import win32api
-
-# The name of an environment variable that when set to |'1'|, signals to us
-# that we should log all output directories that we have staged a copy to to
-# our stderr output. This output could then, for example, be captured and
-# analyzed by an external tool that is interested in these directories.
-_ShouldLogEnvKey = 'STARBOARD_GYP_SHOULD_LOG_COPIES'
-
-
-class WrongNumberOfArgumentsException(Exception):
-  pass
-
-
-def EscapePath(path):
-  """Returns a path with spaces escaped."""
-  return path.replace(' ', '\\ ')
-
-
-def ListFilesForPath(path):
-  """Returns a list of all the files under a given path."""
-  output = []
-  # Ignore revision control metadata directories.
-  if (os.path.basename(path).startswith('.git') or
-      os.path.basename(path).startswith('.svn')):
-    return output
-
-  # Files get returned without modification.
-  if not os.path.isdir(path):
-    output.append(path)
-    return output
-
-  # Directories get recursively expanded.
-  contents = os.listdir(path)
-  for item in contents:
-    full_path = posixpath.join(path, item)
-    output.extend(ListFilesForPath(full_path))
-  return output
-
-
-def CalcInputs(inputs):
-  """Computes the full list of input files. The output is a list of
-  (filepath, filepath relative to input dir)"""
-  # |inputs| is a list of paths, which may be directories.
-  output = []
-  for input_file in inputs:
-    file_list = ListFilesForPath(input_file)
-    dirname = posixpath.dirname(input_file)
-    output.extend([(x, posixpath.relpath(x, dirname)) for x in file_list])
-  return output
-
-
-def CopyFiles(files_to_copy, output_basedir):
-  """Copies files to the given output directory."""
-  for (filename, relative_filename) in files_to_copy:
-    if os.name == 'nt':
-      # Some of the files (especially for layout tests) result in very long
-      # paths (especially on build machines) such that shutil.copy fails.
-      try:
-        filename = win32api.GetShortPathName(filename)
-        output_basedir = win32api.GetShortPathName(output_basedir)
-      except pywintypes.error:
-        logging.error('Failed to get ShortPathName for filename: \"%s\" and output_basedir: \"%s\"', filename, output_basedir)
-        raise
-    # In certain cases, files would fail to open on windows if relative paths
-    # were provided.  Using absolute paths fixes this.
-    filename = os.path.abspath(filename)
-    output_basedir = os.path.abspath(output_basedir)
-    output_filename = os.path.abspath(os.path.join(output_basedir,
-                                                   relative_filename))
-    output_dir = os.path.dirname(output_filename)
-
-    # In cases where a directory has turned into a file or vice versa, delete it
-    # before copying it below.
-    if os.path.exists(output_dir) and not os.path.isdir(output_dir):
-      os.remove(output_dir)
-    if os.path.exists(output_filename) and os.path.isdir(output_filename):
-      shutil.rmtree(output_filename)
-
-    if not os.path.exists(output_dir):
-      os.makedirs(output_dir)
-
-    shutil.copy(filename, output_filename)
-
-
-def DoMain(argv):
-  """Called by GYP using pymod_do_main."""
-  parser = argparse.ArgumentParser()
-  parser.add_argument('-o', dest='output_dir', help='output directory')
-  parser.add_argument(
-      '--inputs',
-      action='store_true',
-      dest='list_inputs',
-      help='prints a list of all input files')
-  parser.add_argument(
-      '--outputs',
-      action='store_true',
-      dest='list_outputs',
-      help='prints a list of all output files')
-  parser.add_argument(
-      'input_paths',
-      metavar='path',
-      nargs='+',
-      help='path to an input file or directory')
-  options = parser.parse_args(argv)
-
-  if os.environ.get(_ShouldLogEnvKey, None) == '1':
-    if options.output_dir is not None:
-      print >> sys.stderr, 'COPY_LOG:', options.output_dir
-
-  files_to_copy = CalcInputs(options.input_paths)
-  if options.list_inputs:
-    return '\n'.join([EscapePath(x[0]) for x in files_to_copy])
-
-  if not options.output_dir:
-    raise WrongNumberOfArgumentsException('-o required.')
-
-  if options.list_outputs:
-    outputs = [posixpath.join(options.output_dir, x[1]) for x in files_to_copy]
-    return '\n'.join(outputs)
-
-  CopyFiles(files_to_copy, options.output_dir)
-  return
-
-
-def main(argv):
-  logging_format = '[%(levelname)s:%(filename)s:%(lineno)s] %(message)s'
-  logging.basicConfig(
-      level=logging.INFO, format=logging_format, datefmt='%H:%M:%S')
-
-  try:
-    result = DoMain(argv[1:])
-  except WrongNumberOfArgumentsException, e:
-    print >> sys.stderr, e
-    return 1
-  if result:
-    print result
-  return 0
-
-
-if __name__ == '__main__':
-  sys.exit(main(sys.argv))
diff --git a/starboard/build/doc/migrating_gyp_to_gn.md b/starboard/build/doc/migrating_gyp_to_gn.md
index 43d009b..9f80b40 100644
--- a/starboard/build/doc/migrating_gyp_to_gn.md
+++ b/starboard/build/doc/migrating_gyp_to_gn.md
@@ -12,6 +12,12 @@
 There are a few ways to get a binary. Follow the instructions for whichever way
 you prefer [here][gn_getting_a_binary].
 
+### Setup your workstation and developer tools
+
+Some environment settings and tools have changed with the GN migration. Please
+go through the [setup guide][dev_setup_linux] to make sure your development
+environment is properly configured.
+
 ### Read the Docs
 
 Most of the documentation for GN is located [here][gn_doc_home].
@@ -258,6 +264,7 @@
 able to be built for your platform.
 
 [cobalt_porting_guide]: https://cobalt.dev/starboard/porting.html
+[dev_setup_linux]: https://cobalt.dev/development/setup-linux.html
 [gn_check_tool]: https://cobalt.googlesource.com/third_party/gn/+/refs/heads/main/docs/reference.md#cmd_check
 [gn_doc_home]: https://cobalt.googlesource.com/third_party/gn/+/refs/heads/main/docs
 [gn_format_tool]: https://cobalt.googlesource.com/third_party/gn/+/refs/heads/main/docs/reference.md#cmd_format
diff --git a/starboard/common/BUILD.gn b/starboard/common/BUILD.gn
index 439c12b..62f7974 100644
--- a/starboard/common/BUILD.gn
+++ b/starboard/common/BUILD.gn
@@ -87,6 +87,7 @@
 target(gtest_target_type, "common_test") {
   testonly = true
   sources = [
+    "media_test.cc",
     "memory_test.cc",
     "socket_test.cc",
     "test_main.cc",
diff --git a/starboard/common/media.cc b/starboard/common/media.cc
index fe378da..283d0ff 100644
--- a/starboard/common/media.cc
+++ b/starboard/common/media.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include <algorithm>
+#include <cctype>
+#include <string>
 
 #include "starboard/common/media.h"
 
@@ -22,6 +24,559 @@
 
 namespace starboard {
 
+namespace {
+
+int hex_to_int(char hex) {
+  if (hex >= '0' && hex <= '9') {
+    return hex - '0';
+  }
+  if (hex >= 'A' && hex <= 'F') {
+    return hex - 'A' + 10;
+  }
+  if (hex >= 'a' && hex <= 'f') {
+    return hex - 'a' + 10;
+  }
+  SB_NOTREACHED();
+  return 0;
+}
+
+// Read one digit hex from |str| and store into |output|. Return false if
+// the character is not a digit, return true otherwise.
+template <typename T>
+bool ReadOneDigitHex(const char* str, T* output) {
+  SB_DCHECK(str);
+
+  if (str[0] >= 'A' && str[0] <= 'F') {
+    *output = static_cast<T>((str[0] - 'A' + 10));
+    return true;
+  }
+  if (str[0] >= 'a' && str[0] <= 'f') {
+    *output = static_cast<T>((str[0] - 'a' + 10));
+    return true;
+  }
+
+  if (!isdigit(str[0])) {
+    return false;
+  }
+
+  *output = static_cast<T>((str[0] - '0'));
+  return true;
+}
+
+// Read multi digit decimal from |str| until the next '.' character or end of
+// string, and store into |output|. Return false if one of the character is not
+// a digit, return true otherwise.
+template <typename T>
+bool ReadDecimalUntilDot(const char* str, T* output) {
+  SB_DCHECK(str);
+
+  *output = 0;
+  while (*str != 0 && *str != '.') {
+    if (!isdigit(*str)) {
+      return false;
+    }
+    *output = *output * 10 + (*str - '0');
+    ++str;
+  }
+
+  return true;
+}
+
+// Read two digit decimal from |str| and store into |output|. Return false if
+// any character is not a digit, return true otherwise.
+template <typename T>
+bool ReadTwoDigitDecimal(const char* str, T* output) {
+  SB_DCHECK(str);
+
+  if (!isdigit(str[0]) || !isdigit(str[1])) {
+    return false;
+  }
+
+  *output = static_cast<T>((str[0] - '0') * 10 + (str[1] - '0'));
+  return true;
+}
+
+// Verify the format against a reference using the following rules:
+// 1. They must have the same size.
+// 2. If reference[i] is a letter, format[i] must contain the *same* letter.
+// 3. If reference[i] is a number, format[i] can contain *any* number.
+// 4. If reference[i] is '.', format[i] must also be '.'.
+// 5. If reference[i] is ?, format[i] can contain *any* character.
+// 6. If |format| is longer than |reference|, then |format[reference_size]| can
+//    not be '.' or digit.
+// For example, both "av01.0.05M.08" and "av01.0.05M.08****" match reference
+// "av01.0.05?.08", but "vp09.0.05M.08" or "vp09.0.05M.08." don't.
+// The function returns true when |format| matches |reference|.
+bool VerifyFormat(const char* format, const char* reference) {
+  auto format_size = strlen(format);
+  auto reference_size = strlen(reference);
+  if (format_size < reference_size) {
+    return false;
+  }
+  for (size_t i = 0; i < reference_size; ++i) {
+    if (isdigit(reference[i])) {
+      if (!isdigit(format[i])) {
+        return false;
+      }
+    } else if (std::isalpha(reference[i])) {
+      if (reference[i] != format[i]) {
+        return false;
+      }
+    } else if (reference[i] == '.') {
+      if (format[i] != '.') {
+        return false;
+      }
+    } else if (reference[i] != '?') {
+      return false;
+    }
+  }
+  if (format_size == reference_size) {
+    return true;
+  }
+  return format[reference_size] != '.' && !isdigit(format[reference_size]);
+}
+
+// It works exactly the same as the above function, except that the size of
+// |format| has to be exactly the same as the size of |reference|.
+bool VerifyFormatStrictly(const char* format, const char* reference) {
+  if (strlen(format) != strlen(reference)) {
+    return false;
+  }
+  return VerifyFormat(format, reference);
+}
+
+// This function parses an av01 codec in the form of "av01.0.05M.08" or
+// "av01.0.04M.10.0.110.09.16.09.0" as
+// specified by https://aomediacodec.github.io/av1-isobmff/#codecsparam.
+//
+// Note that the spec also supports of different chroma subsamplings but the
+// implementation always assume that it is 4:2:0 and returns false when it
+// isn't.
+bool ParseAv1Info(std::string codec,
+                  int* profile,
+                  int* level,
+                  int* bit_depth,
+                  SbMediaPrimaryId* primary_id,
+                  SbMediaTransferId* transfer_id,
+                  SbMediaMatrixId* matrix_id) {
+  // The codec can only in one of the following formats:
+  //   Full: av01.0.04M.10.0.110.09.16.09.0
+  //   Short: av01.0.05M.08
+  // When short format is used, it is assumed that the omitted parts are
+  // "0.110.01.01.01.0".
+  // All fields are fixed size and leading zero cannot be omitted, so the
+  // expected sizes are known.
+  const char kShortFormReference[] = "av01.0.05M.08";
+  const char kLongFormReference[] = "av01.0.04M.10.0.110.09.16.09.0";
+  const size_t kShortFormSize = strlen(kShortFormReference);
+  const size_t kLongFormSize = strlen(kLongFormReference);
+
+  // 1. Sanity check the format.
+  if (strncmp(codec.c_str(), "av01.", 5) != 0) {
+    return false;
+  }
+  if (VerifyFormat(codec.c_str(), kLongFormReference)) {
+    codec.resize(kLongFormSize);
+  } else if (VerifyFormat(codec.c_str(), kShortFormReference)) {
+    codec.resize(kShortFormSize);
+  } else {
+    return false;
+  }
+
+  // 2. Parse profile, which can only be 0, 1, or 2.
+  if (codec[5] < '0' || codec[5] > '2') {
+    return false;
+  }
+  *profile = codec[5] - '0';
+
+  // 3. Parse level, which is two digit value from 0 to 23, maps to level 2.0,
+  //    2.1, 2.2, 2.3, 3.0, 3.1, 3.2, 3.3, ..., 7.0, 7.1, 7.2, 7.3.
+  int level_value;
+  if (!ReadTwoDigitDecimal(codec.c_str() + 7, &level_value)) {
+    return false;
+  }
+
+  if (level_value > 23) {
+    return false;
+  }
+  // Level x.y is represented by integer |xy|, for example, 23 means level 2.3.
+  *level = (level_value / 4 + 2) * 10 + (level_value % 4);
+
+  // 4. Parse tier, which can only be 'M' or 'H'
+  if (codec[9] != 'M' && codec[9] != 'H') {
+    return false;
+  }
+
+  // 5. Parse bit depth, which can be value 08, 10, or 12.
+  if (!ReadTwoDigitDecimal(codec.c_str() + 11, bit_depth)) {
+    return false;
+  }
+  if (*bit_depth != 8 && *bit_depth != 10 && *bit_depth != 12) {
+    return false;
+  }
+
+  // 6. Return now if it is a well-formed short form codec string.
+  *primary_id = kSbMediaPrimaryIdBt709;
+  *transfer_id = kSbMediaTransferIdBt709;
+  *matrix_id = kSbMediaMatrixIdBt709;
+
+  if (codec.size() == kShortFormSize) {
+    return true;
+  }
+
+  // 7. Parse monochrome, which can only be 0 or 1.
+  // Note that this value is not returned.
+  if (codec[14] != '0' && codec[14] != '1') {
+    return false;
+  }
+
+  // 8. Parse chroma subsampling, which we only support 110.
+  // Note that this value is not returned.
+  if (strncmp(codec.c_str() + 16, "110", 3) != 0) {
+    return false;
+  }
+
+  // 9. Parse color primaries, which can be 1 to 12, and 22 (EBU Tech. 3213-E).
+  //    Note that 22 is not currently supported by Cobalt.
+  if (!ReadTwoDigitDecimal(codec.c_str() + 20, primary_id)) {
+    return false;
+  }
+  SB_LOG_IF(WARNING, *primary_id == 22)
+      << codec << " uses primary id 22 (EBU Tech. 3213-E)."
+      << " It is rejected because Cobalt doesn't support primary id 22.";
+  if (*primary_id < 1 || *primary_id > 12) {
+    return false;
+  }
+
+  // 10. Parse transfer characteristics, which can be 0 to 18.
+  if (!ReadTwoDigitDecimal(codec.c_str() + 23, transfer_id)) {
+    return false;
+  }
+  if (*transfer_id > 18) {
+    return false;
+  }
+
+  // 11. Parse matrix coefficients, which can be 0 to 14.
+  //     Note that 12, 13, and 14 are not currently supported by Cobalt.
+  if (!ReadTwoDigitDecimal(codec.c_str() + 26, matrix_id)) {
+    return false;
+  }
+  if (*matrix_id > 11) {
+    return false;
+  }
+
+  // 12. Parse color range, which can only be 0 or 1.
+  if (codec[29] != '0' && codec[29] != '1') {
+    return false;
+  }
+
+  // 13. Return
+  return true;
+}
+
+// This function parses an h264 codec in the form of {avc1|avc3}.PPCCLL as
+// specified by https://tools.ietf.org/html/rfc6381#section-3.3.
+//
+// Note that the leading codec is not necessarily to be "avc1" or "avc3" per
+// spec but this function only parses "avc1" and "avc3".  This function returns
+// false when |codec| doesn't contain a valid codec string.
+bool ParseH264Info(const char* codec, int* profile, int* level) {
+  if (strncmp(codec, "avc1.", 5) != 0 && strncmp(codec, "avc3.", 5) != 0) {
+    return false;
+  }
+
+  if (strlen(codec) != 11 || !isxdigit(codec[9]) || !isxdigit(codec[10])) {
+    return false;
+  }
+
+  *profile = hex_to_int(codec[5]) * 16 + hex_to_int(codec[6]);
+  *level = hex_to_int(codec[9]) * 16 + hex_to_int(codec[10]);
+  return true;
+}
+
+// This function parses an h265 codec as specified by ISO IEC 14496-15 dated
+// 2012 or newer in the Annex E.3.  The codec will be in the form of:
+//   hvc1.PPP.PCPCPCPC.TLLL.CB.CB.CB.CB.CB.CB, where
+// PPP: 0 or 1 byte general_profile_space ('', 'A', 'B', or 'C') +
+//        up to two bytes profile idc.
+// PCPCPCPC: Profile compatibility, up to 32 bits hex, with leading 0 omitted.
+// TLLL: One byte tier ('L' or 'H') + up to three bytes level.
+// CB: Up to 6 constraint bytes, separated by '.'.
+// Note that the above level in decimal = 30 * real level, i.e. 93 means level
+// 3.1, 120 mean level 4.
+// Please see the comment in the code for interactions between the various
+// parts.
+bool ParseH265Info(const char* codec, int* profile, int* level) {
+  if (strncmp(codec, "hev1.", 5) != 0 && strncmp(codec, "hvc1.", 5) != 0) {
+    return false;
+  }
+
+  codec += 5;
+
+  // Read profile space
+  if (codec[0] == 'A' || codec[0] == 'B' || codec[0] == 'C') {
+    ++codec;
+  }
+
+  if (strlen(codec) < 3) {
+    return false;
+  }
+
+  // Read profile
+  int general_profile_idc;
+  if (codec[1] == '.') {
+    if (!ReadDecimalUntilDot(codec, &general_profile_idc)) {
+      return false;
+    }
+    codec += 2;
+  } else if (codec[2] == '.') {
+    if (!ReadDecimalUntilDot(codec, &general_profile_idc)) {
+      return false;
+    }
+    codec += 3;
+  } else {
+    return false;
+  }
+
+  // Read profile compatibility, up to 32 bits hex.
+  const char* dot = strchr(codec, '.');
+  if (dot == NULL || dot - codec == 0 || dot - codec > 8) {
+    return false;
+  }
+
+  uint32_t general_profile_compatibility_flags = 0;
+  for (int i = 0; i < 9; ++i) {
+    if (codec[0] == '.') {
+      ++codec;
+      break;
+    }
+    uint32_t hex = 0;
+    if (!ReadOneDigitHex(codec, &hex)) {
+      return false;
+    }
+    general_profile_compatibility_flags *= 16;
+    general_profile_compatibility_flags += hex;
+    ++codec;
+  }
+
+  *profile = -1;
+  if (general_profile_idc == 3 || (general_profile_compatibility_flags & 4)) {
+    *profile = 3;
+  }
+  if (general_profile_idc == 2 || (general_profile_compatibility_flags & 2)) {
+    *profile = 2;
+  }
+  if (general_profile_idc == 1 || (general_profile_compatibility_flags & 1)) {
+    *profile = 1;
+  }
+  if (*profile == -1) {
+    return false;
+  }
+
+  // Parse tier
+  if (codec[0] != 'L' && codec[0] != 'H') {
+    return false;
+  }
+  ++codec;
+
+  // Parse level in 2 or 3 digits decimal.
+  if (strlen(codec) < 2) {
+    return false;
+  }
+  if (!ReadDecimalUntilDot(codec, level)) {
+    return false;
+  }
+  if (*level % 3 != 0) {
+    return false;
+  }
+  *level /= 3;
+  if (codec[2] == 0 || codec[2] == '.') {
+    codec += 2;
+  } else if (codec[3] == 0 || codec[3] == '.') {
+    codec += 3;
+  } else {
+    return false;
+  }
+
+  // Parse up to 6 constraint flags in the form of ".HH".
+  for (int i = 0; i < 6; ++i) {
+    if (codec[0] == 0) {
+      return true;
+    }
+    if (codec[0] != '.' || !isxdigit(codec[1]) || !isxdigit(codec[2])) {
+      return false;
+    }
+    codec += 3;
+  }
+
+  return *codec == 0;
+}
+
+// This function parses an vp09 codec in the form of "vp09.00.41.08" or
+// "vp09.02.10.10.01.09.16.09.01" as specified by
+// https://www.webmproject.org/vp9/mp4/.  YouTube also uses the long form
+// without the last part (color range), so we also support it.
+//
+// Note that the spec also supports of different chroma subsamplings but the
+// implementation always assume that it is 4:2:0 and returns false when it
+// isn't.
+bool ParseVp09Info(const char* codec,
+                   int* profile,
+                   int* level,
+                   int* bit_depth,
+                   SbMediaPrimaryId* primary_id,
+                   SbMediaTransferId* transfer_id,
+                   SbMediaMatrixId* matrix_id) {
+  // The codec can only in one of the following formats:
+  //   Full: vp09.02.10.10.01.09.16.09.01
+  //   Short: vp09.00.41.08
+  // Note that currently the player also uses the following form:
+  //   Medium: vp09.02.10.10.01.09.16.09
+  // When short format is used, it is assumed that the omitted parts are
+  // "01.01.01.01.00".  When medium format is used, the omitted part is "00".
+  // All fields are fixed size and leading zero cannot be omitted, so the
+  // expected sizes are known.
+  const char kShortFormReference[] = "vp09.00.41.08";
+  const char kMediumFormReference[] = "vp09.02.10.10.01.09.16.09";
+  const char kLongFormReference[] = "vp09.02.10.10.01.09.16.09.01";
+  const size_t kShortFormSize = strlen(kShortFormReference);
+  const size_t kMediumFormSize = strlen(kMediumFormReference);
+  const size_t kLongFormSize = strlen(kLongFormReference);
+
+  // 1. Sanity check the format.
+  if (strncmp(codec, "vp09.", 5) != 0) {
+    return false;
+  }
+  if (!VerifyFormatStrictly(codec, kLongFormReference) &&
+      !VerifyFormatStrictly(codec, kMediumFormReference) &&
+      !VerifyFormatStrictly(codec, kShortFormReference)) {
+    return false;
+  }
+
+  // 2. Parse profile, which can only be 00, 01, 02, or 03.
+  if (!ReadTwoDigitDecimal(codec + 5, profile)) {
+    return false;
+  }
+  if (*profile < 0 || *profile > 3) {
+    return false;
+  }
+
+  // 3. Parse level, which is two digit value in the following list:
+  const int kLevels[] = {10, 11, 20, 21, 30, 31, 40,
+                         41, 50, 51, 52, 60, 61, 62};
+  if (!ReadTwoDigitDecimal(codec + 8, level)) {
+    return false;
+  }
+  auto end = kLevels + SB_ARRAY_SIZE(kLevels);
+  if (std::find(kLevels, end, *level) == end) {
+    return false;
+  }
+
+  // 4. Parse bit depth, which can be value 08, 10, or 12.
+  if (!ReadTwoDigitDecimal(codec + 11, bit_depth)) {
+    return false;
+  }
+  if (*bit_depth != 8 && *bit_depth != 10 && *bit_depth != 12) {
+    return false;
+  }
+
+  // 5. Return now if it is a well-formed short form codec string.
+  *primary_id = kSbMediaPrimaryIdBt709;
+  *transfer_id = kSbMediaTransferIdBt709;
+  *matrix_id = kSbMediaMatrixIdBt709;
+
+  if (strlen(codec) == kShortFormSize) {
+    return true;
+  }
+
+  // 6. Parse chroma subsampling, which we only support 00 and 01.
+  // Note that this value is not returned.
+  int chroma;
+  if (!ReadTwoDigitDecimal(codec + 14, &chroma) ||
+      (chroma != 0 && chroma != 1)) {
+    return false;
+  }
+
+  // 7. Parse color primaries, which can be 1 to 12, and 22 (EBU Tech. 3213-E).
+  //    Note that 22 is not currently supported by Cobalt.
+  if (!ReadTwoDigitDecimal(codec + 17, primary_id)) {
+    return false;
+  }
+  if (*primary_id < 1 || *primary_id > 12) {
+    return false;
+  }
+
+  // 8. Parse transfer characteristics, which can be 0 to 18.
+  if (!ReadTwoDigitDecimal(codec + 20, transfer_id)) {
+    return false;
+  }
+  if (*transfer_id > 18) {
+    return false;
+  }
+
+  // 9. Parse matrix coefficients, which can be 0 to 14.
+  //     Note that 12, 13, and 14 are not currently supported by Cobalt.
+  if (!ReadTwoDigitDecimal(codec + 23, matrix_id)) {
+    return false;
+  }
+  if (*matrix_id > 11) {
+    return false;
+  }
+
+  // 10. Return now if it is a well-formed medium form codec string.
+  if (strlen(codec) == kMediumFormSize) {
+    return true;
+  }
+
+  // 11. Parse color range, which can only be 0 or 1.
+  int color_range;
+  if (!ReadTwoDigitDecimal(codec + 26, &color_range)) {
+    return false;
+  }
+  if (color_range != 0 && color_range != 1) {
+    return false;
+  }
+
+  // 12. Return
+  return true;
+}
+
+// This function parses a vp9 codec in the form of "vp9", "vp9.0", "vp9.1",
+// "vp9.2", or "vp9.3".
+bool ParseVp9Info(const char* codec, int* profile, int* bit_depth) {
+  SB_DCHECK(profile);
+  SB_DCHECK(bit_depth);
+
+  if (strcmp(codec, "vp9") == 0) {
+    *profile = -1;
+    *bit_depth = 8;
+    return true;
+  }
+  if (strcmp(codec, "vp9.0") == 0) {
+    *profile = 0;
+    *bit_depth = 8;
+    return true;
+  }
+  if (strcmp(codec, "vp9.1") == 0) {
+    *profile = 1;
+    *bit_depth = 8;
+    return true;
+  }
+  if (strcmp(codec, "vp9.2") == 0) {
+    *profile = 2;
+    *bit_depth = 10;
+    return true;
+  }
+  if (strcmp(codec, "vp9.3") == 0) {
+    *profile = 3;
+    *bit_depth = 10;
+    return true;
+  }
+  return false;
+}
+
+}  // namespace
+
 const char* GetMediaAudioCodecName(SbMediaAudioCodec codec) {
   switch (codec) {
     case kSbMediaAudioCodecNone:
@@ -194,8 +749,13 @@
       return "Bt2020ConstantLuminance";
     case kSbMediaMatrixIdYDzDx:
       return "YDzDx";
+#if SB_API_VERSION >= 14
+    case kSbMediaMatrixIdInvalid:
+      return "Invalid";
+#else   // SB_API_VERSION >= 14
     case kSbMediaMatrixIdUnknown:
       return "Unknown";
+#endif  // SB_API_VERSION >= 14
   }
   SB_NOTREACHED();
   return "Invalid";
@@ -216,6 +776,63 @@
   return "Invalid";
 }
 
+bool ParseVideoCodec(const char* codec_string,
+                     SbMediaVideoCodec* codec,
+                     int* profile,
+                     int* level,
+                     int* bit_depth,
+                     SbMediaPrimaryId* primary_id,
+                     SbMediaTransferId* transfer_id,
+                     SbMediaMatrixId* matrix_id) {
+  SB_DCHECK(codec_string);
+  SB_DCHECK(codec);
+  SB_DCHECK(profile);
+  SB_DCHECK(level);
+  SB_DCHECK(bit_depth);
+  SB_DCHECK(primary_id);
+  SB_DCHECK(transfer_id);
+  SB_DCHECK(matrix_id);
+
+  *codec = kSbMediaVideoCodecNone;
+  *profile = -1;
+  *level = -1;
+  *bit_depth = 8;
+  *primary_id = kSbMediaPrimaryIdUnspecified;
+  *transfer_id = kSbMediaTransferIdUnspecified;
+  *matrix_id = kSbMediaMatrixIdUnspecified;
+
+  if (strncmp(codec_string, "av01.", 5) == 0) {
+    *codec = kSbMediaVideoCodecAv1;
+    return ParseAv1Info(codec_string, profile, level, bit_depth, primary_id,
+                        transfer_id, matrix_id);
+  }
+  if (strncmp(codec_string, "avc1.", 5) == 0 ||
+      strncmp(codec_string, "avc3.", 5) == 0) {
+    *codec = kSbMediaVideoCodecH264;
+    return ParseH264Info(codec_string, profile, level);
+  }
+  if (strncmp(codec_string, "hev1.", 5) == 0 ||
+      strncmp(codec_string, "hvc1.", 5) == 0) {
+    *codec = kSbMediaVideoCodecH265;
+    return ParseH265Info(codec_string, profile, level);
+  }
+  if (strncmp(codec_string, "vp09.", 5) == 0) {
+    *codec = kSbMediaVideoCodecVp9;
+    return ParseVp09Info(codec_string, profile, level, bit_depth, primary_id,
+                         transfer_id, matrix_id);
+  }
+  if (strncmp(codec_string, "vp8", 3) == 0) {
+    *codec = kSbMediaVideoCodecVp8;
+    return true;
+  }
+  if (strncmp(codec_string, "vp9", 3) == 0) {
+    *codec = kSbMediaVideoCodecVp9;
+    return ParseVp9Info(codec_string, profile, bit_depth);
+  }
+
+  return false;
+}
+
 }  // namespace starboard
 
 std::ostream& operator<<(std::ostream& os,
diff --git a/starboard/common/media.h b/starboard/common/media.h
index bd23a1a..a51641e 100644
--- a/starboard/common/media.h
+++ b/starboard/common/media.h
@@ -28,6 +28,26 @@
 const char* GetMediaMatrixIdName(SbMediaMatrixId matrix_id);
 const char* GetMediaRangeIdName(SbMediaRangeId range_id);
 
+// This function parses the video codec string and returns a codec.  All fields
+// will be filled with information parsed from the codec string when possible,
+// otherwise they will have the following default values:
+//            profile: -1
+//              level: -1
+//          bit_depth: 8
+//         primary_id: kSbMediaPrimaryIdUnspecified
+//        transfer_id: kSbMediaTransferIdUnspecified
+//          matrix_id: kSbMediaMatrixIdUnspecified
+// It returns true when |codec| contains a well-formed codec string, otherwise
+// it returns false.
+bool ParseVideoCodec(const char* codec_string,
+                     SbMediaVideoCodec* codec,
+                     int* profile,
+                     int* level,
+                     int* bit_depth,
+                     SbMediaPrimaryId* primary_id,
+                     SbMediaTransferId* transfer_id,
+                     SbMediaMatrixId* matrix_id);
+
 }  // namespace starboard
 
 // For logging use only.
diff --git a/starboard/common/media_test.cc b/starboard/common/media_test.cc
new file mode 100644
index 0000000..dae1d1b
--- /dev/null
+++ b/starboard/common/media_test.cc
@@ -0,0 +1,197 @@
+// Copyright 2022 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 "starboard/common/media.h"
+
+#include "starboard/common/log.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace {
+
+class ParseVideoCodecTest : public ::testing::Test {
+ protected:
+  bool Parse(const char* codec_string) {
+    return ParseVideoCodec(codec_string, &codec_, &profile_, &level_,
+                           &bit_depth_, &color_primary_id_, &transfer_id_,
+                           &matrix_id_);
+  }
+
+  SbMediaVideoCodec codec_;
+  int profile_;
+  int level_;
+  int bit_depth_;
+  SbMediaPrimaryId color_primary_id_;
+  SbMediaTransferId transfer_id_;
+  SbMediaMatrixId matrix_id_;
+};
+
+TEST_F(ParseVideoCodecTest, SimpleCodecs) {
+  const char* kCodecStrings[] = {"vp8", "vp9"};
+  const SbMediaVideoCodec kVideoCodecs[] = {kSbMediaVideoCodecVp8,
+                                            kSbMediaVideoCodecVp9};
+  for (size_t i = 0; i < SB_ARRAY_SIZE(kCodecStrings); ++i) {
+    ASSERT_TRUE(Parse(kCodecStrings[i]));
+    EXPECT_EQ(codec_, kVideoCodecs[i]);
+    EXPECT_EQ(profile_, -1);
+    EXPECT_EQ(level_, -1);
+    EXPECT_EQ(bit_depth_, 8);
+    EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdUnspecified);
+    EXPECT_EQ(transfer_id_, kSbMediaTransferIdUnspecified);
+    EXPECT_EQ(matrix_id_, kSbMediaMatrixIdUnspecified);
+  }
+}
+
+TEST_F(ParseVideoCodecTest, EmptyString) {
+  ASSERT_FALSE(Parse(""));
+}
+
+TEST_F(ParseVideoCodecTest, ShortFormAv1) {
+  ASSERT_TRUE(Parse("av01.0.01M.08"));
+  EXPECT_EQ(codec_, kSbMediaVideoCodecAv1);
+  EXPECT_EQ(profile_, 0);
+  EXPECT_EQ(level_, 21);
+  EXPECT_EQ(bit_depth_, 8);
+  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdBt709);
+  EXPECT_EQ(transfer_id_, kSbMediaTransferIdBt709);
+  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdBt709);
+}
+
+TEST_F(ParseVideoCodecTest, LongFormAv1) {
+  ASSERT_TRUE(Parse("av01.0.04M.10.0.110.09.16.09.0"));
+  EXPECT_EQ(codec_, kSbMediaVideoCodecAv1);
+  EXPECT_EQ(profile_, 0);
+  EXPECT_EQ(level_, 30);
+  EXPECT_EQ(bit_depth_, 10);
+  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdBt2020);
+  EXPECT_EQ(transfer_id_, kSbMediaTransferIdSmpteSt2084);
+  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdBt2020NonconstantLuminance);
+}
+
+TEST_F(ParseVideoCodecTest, InvalidAv1) {
+  EXPECT_FALSE(Parse("av01.0.04M.10.0.110.9.16.9.0"));
+  EXPECT_FALSE(Parse("av01.0.04M.10.0.110.09.16.09"));
+  EXPECT_FALSE(Parse("av01.0.04M.10.0.110.09.16"));
+  EXPECT_FALSE(Parse("av01.0.04M.10.0.110.09"));
+  EXPECT_FALSE(Parse("av01.0.04M.10.0.110"));
+  EXPECT_FALSE(Parse("av01.0.04M.10.0"));
+  EXPECT_FALSE(Parse("av01.0.04M"));
+  EXPECT_FALSE(Parse("av01.0"));
+  EXPECT_FALSE(Parse("av01"));
+
+  EXPECT_FALSE(Parse("av02.0.04M.10.0.110.09.16.09.0"));
+  EXPECT_FALSE(Parse("av01.0.04X.10.0.110.09.16.09.0"));
+  EXPECT_FALSE(Parse("av01.0.04M.10.0.110.09.16.09.2"));
+}
+
+TEST_F(ParseVideoCodecTest, Avc) {
+  ASSERT_TRUE(Parse("avc1.640028"));
+  EXPECT_EQ(codec_, kSbMediaVideoCodecH264);
+  EXPECT_EQ(profile_, 100);
+  EXPECT_EQ(level_, 40);
+  EXPECT_EQ(bit_depth_, 8);
+  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdUnspecified);
+  EXPECT_EQ(transfer_id_, kSbMediaTransferIdUnspecified);
+  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdUnspecified);
+
+  ASSERT_TRUE(Parse("avc3.640028"));
+  EXPECT_EQ(codec_, kSbMediaVideoCodecH264);
+  EXPECT_EQ(profile_, 100);
+  EXPECT_EQ(level_, 40);
+  EXPECT_EQ(bit_depth_, 8);
+  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdUnspecified);
+  EXPECT_EQ(transfer_id_, kSbMediaTransferIdUnspecified);
+  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdUnspecified);
+}
+
+TEST_F(ParseVideoCodecTest, InvalidAvc) {
+  EXPECT_FALSE(Parse("avc1.64002"));
+  EXPECT_FALSE(Parse("avc2.640028"));
+  EXPECT_FALSE(Parse("avc3.640028.1"));
+}
+
+TEST_F(ParseVideoCodecTest, H265) {
+  ASSERT_TRUE(Parse("hvc1.1.2.L93.B0"));
+  EXPECT_EQ(codec_, kSbMediaVideoCodecH265);
+  EXPECT_EQ(profile_, 1);
+  EXPECT_EQ(level_, 31);
+  ASSERT_TRUE(Parse("hev1.A4.41.H120.B0.12.34.56.78.90"));
+  EXPECT_EQ(codec_, kSbMediaVideoCodecH265);
+  EXPECT_EQ(profile_, 1);
+  EXPECT_EQ(level_, 40);
+
+  EXPECT_TRUE(Parse("hvc1.1.2.H93.B0"));
+  EXPECT_TRUE(Parse("hvc1.A1.2.H93.B0"));
+  EXPECT_TRUE(Parse("hvc1.B1.2.H93.B0"));
+  EXPECT_TRUE(Parse("hvc1.C1.2.H93.B0"));
+  EXPECT_TRUE(Parse("hvc1.C1.2.H93"));
+  EXPECT_TRUE(Parse("hvc1.C1.ABCDEF01.H93.B0"));
+}
+
+TEST_F(ParseVideoCodecTest, InvalidH265) {
+  EXPECT_FALSE(Parse("hvc2.1.2.L93.B0"));
+  EXPECT_FALSE(Parse("hvc1.D1.2.L93.B0"));
+  EXPECT_FALSE(Parse("hvc1.A111.2.L93.B0"));
+  EXPECT_FALSE(Parse("hvc1.111.2.L93.B0"));
+  EXPECT_FALSE(Parse("hvc1.1.ABCDEF012.L93.B0"));
+  EXPECT_FALSE(Parse("hvc1.1.2.L92.B0"));
+  EXPECT_FALSE(Parse("hvc1.1.2.P93.B0"));
+  EXPECT_FALSE(Parse("hvc1.1.2.L93.B0.B1.B2.B3.B4.B5.B6"));
+}
+
+TEST_F(ParseVideoCodecTest, ShortFormVp9) {
+  ASSERT_TRUE(Parse("vp09.00.41.08"));
+  EXPECT_EQ(codec_, kSbMediaVideoCodecVp9);
+  EXPECT_EQ(profile_, 0);
+  EXPECT_EQ(level_, 41);
+  EXPECT_EQ(bit_depth_, 8);
+  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdBt709);
+  EXPECT_EQ(transfer_id_, kSbMediaTransferIdBt709);
+  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdBt709);
+}
+
+TEST_F(ParseVideoCodecTest, MediumFormVp9) {
+  ASSERT_TRUE(Parse("vp09.02.10.10.01.09.16.09"));
+  EXPECT_EQ(codec_, kSbMediaVideoCodecVp9);
+  EXPECT_EQ(profile_, 2);
+  EXPECT_EQ(level_, 10);
+  EXPECT_EQ(bit_depth_, 10);
+  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdBt2020);
+  EXPECT_EQ(transfer_id_, kSbMediaTransferIdSmpteSt2084);
+  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdBt2020NonconstantLuminance);
+}
+
+TEST_F(ParseVideoCodecTest, LongFormVp9) {
+  ASSERT_TRUE(Parse("vp09.02.10.10.01.09.16.09.01"));
+  EXPECT_EQ(codec_, kSbMediaVideoCodecVp9);
+  EXPECT_EQ(profile_, 2);
+  EXPECT_EQ(level_, 10);
+  EXPECT_EQ(bit_depth_, 10);
+  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdBt2020);
+  EXPECT_EQ(transfer_id_, kSbMediaTransferIdSmpteSt2084);
+  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdBt2020NonconstantLuminance);
+}
+
+TEST_F(ParseVideoCodecTest, InvalidVp9) {
+  EXPECT_FALSE(Parse("vp09.02.10.10.01.9.16.9"));
+  EXPECT_FALSE(Parse("vp09.02.10.10.01.09.16"));
+  EXPECT_FALSE(Parse("vp09.02.10.10.01.09"));
+  EXPECT_FALSE(Parse("vp09.02.10.10.01"));
+  EXPECT_FALSE(Parse("vp09.02.10"));
+  EXPECT_FALSE(Parse("vp09.02"));
+  EXPECT_FALSE(Parse("vp09"));
+}
+
+}  // namespace
+}  // namespace starboard
diff --git a/starboard/configuration.h b/starboard/configuration.h
index 4d0095a..b00d129 100644
--- a/starboard/configuration.h
+++ b/starboard/configuration.h
@@ -39,12 +39,12 @@
 
 // The maximum API version allowed by this version of the Starboard headers,
 // inclusive.
-#define SB_MAXIMUM_API_VERSION 14
+#define SB_MAXIMUM_API_VERSION 15
 
 // The API version that is currently open for changes, and therefore is not
 // stable or frozen. Production-oriented ports should avoid declaring that they
 // implement the experimental Starboard API version.
-#define SB_EXPERIMENTAL_API_VERSION 14
+#define SB_EXPERIMENTAL_API_VERSION 15
 
 // The next API version to be frozen, but is still subject to emergency
 // changes. It is reasonable to base a port on the Release Candidate API
@@ -65,16 +65,6 @@
 //   //   exposes functionality for my new feature.
 //   #define SB_MY_EXPERIMENTAL_FEATURE_VERSION SB_EXPERIMENTAL_API_VERSION
 
-// Introduce a new format kSbDecodeTargetFormat3Plane10BitYUVI420Compact.
-//   A decoder target format consisting of 10bit Y, U, and V planes.
-#define SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION \
-  SB_EXPERIMENTAL_API_VERSION
-
-#define SB_SYSTEM_PATH_TEST_OUTPUT_DIRECTORY_DEPRECATED \
-  SB_EXPERIMENTAL_API_VERSION
-
-#define SB_SYSTEM_DEVICE_PROJECTOR_ADDED SB_EXPERIMENTAL_API_VERSION
-
 // --- Release Candidate Feature Defines -------------------------------------
 
 // --- Common Detected Features ----------------------------------------------
diff --git a/starboard/decode_target.h b/starboard/decode_target.h
index 9d24eff..b347070 100644
--- a/starboard/decode_target.h
+++ b/starboard/decode_target.h
@@ -134,12 +134,12 @@
   // order. Each pixel is stored in 16 bits.
   kSbDecodeTargetFormat3Plane10BitYUVI420,
 
-#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+#if SB_API_VERSION >= 14
   // A decoder target format consisting of 10bit Y, U, and V planes, in that
   // order. The plane data is stored in a compact format. Every three 10-bit
   // pixels are packed into 32 bits.
   kSbDecodeTargetFormat3Plane10BitYUVI420Compact,
-#endif  // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+#endif  // SB_API_VERSION >= 14
 
   // A decoder target format consisting of a single plane with pixels laid out
   // in the format UYVY.  Since there are two Y values per sample, but only one
@@ -324,9 +324,9 @@
     case kSbDecodeTargetFormat2PlaneYUVNV12:
       return 2;
     case kSbDecodeTargetFormat3Plane10BitYUVI420:
-#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+#if SB_API_VERSION >= 14
     case kSbDecodeTargetFormat3Plane10BitYUVI420Compact:
-#endif  // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+#endif  // SB_API_VERSION >= 14
     case kSbDecodeTargetFormat3PlaneYUVI420:
       return 3;
     default:
diff --git a/starboard/elf_loader/elf_loader_impl.cc b/starboard/elf_loader/elf_loader_impl.cc
index fc7c635..ca37c37 100644
--- a/starboard/elf_loader/elf_loader_impl.cc
+++ b/starboard/elf_loader/elf_loader_impl.cc
@@ -17,8 +17,10 @@
 #include <string>
 
 #include "starboard/common/log.h"
+#include "starboard/common/scoped_ptr.h"
 #include "starboard/elf_loader/elf.h"
 #include "starboard/elf_loader/elf_loader_constants.h"
+#include "starboard/elf_loader/file.h"
 #include "starboard/elf_loader/file_impl.h"
 #include "starboard/elf_loader/log.h"
 #include "starboard/elf_loader/lz4_file_impl.h"
@@ -54,17 +56,19 @@
                   << " Compression is not supported with memory mapped files.";
     return false;
   }
+
+  scoped_ptr<File> elf_file;
   if (use_compression && EndsWith(name, kCompressionSuffix)) {
-    elf_file_.reset(new LZ4FileImpl());
+    elf_file.reset(new LZ4FileImpl());
     SB_LOG(INFO) << "Loading " << name << " using compression";
   } else {
     SB_LOG(INFO) << "Loading " << name;
-    elf_file_.reset(new FileImpl());
+    elf_file.reset(new FileImpl());
   }
-  elf_file_->Open(name);
+  elf_file->Open(name);
 
   elf_header_loader_.reset(new ElfHeader());
-  if (!elf_header_loader_->LoadElfHeader(elf_file_.get())) {
+  if (!elf_header_loader_->LoadElfHeader(elf_file.get())) {
     SB_LOG(ERROR) << "Failed to load ELF header";
     return false;
   }
@@ -89,7 +93,7 @@
   }
 
   program_table_->LoadProgramHeader(elf_header_loader_->GetHeader(),
-                                    elf_file_.get());
+                                    elf_file.get());
 
   SB_DLOG(INFO) << "Loaded Program header";
 
@@ -100,7 +104,7 @@
 
   SB_DLOG(INFO) << "Reserved address space";
 
-  if (!program_table_->LoadSegments(elf_file_.get())) {
+  if (!program_table_->LoadSegments(elf_file.get())) {
     SB_LOG(ERROR) << "Failed to load segments";
     return false;
   }
diff --git a/starboard/elf_loader/elf_loader_impl.h b/starboard/elf_loader/elf_loader_impl.h
index 02ee7a3..5077bc5 100644
--- a/starboard/elf_loader/elf_loader_impl.h
+++ b/starboard/elf_loader/elf_loader_impl.h
@@ -21,7 +21,6 @@
 #include "starboard/elf_loader/elf_hash_table.h"
 #include "starboard/elf_loader/elf_header.h"
 #include "starboard/elf_loader/exported_symbols.h"
-#include "starboard/elf_loader/file.h"
 #include "starboard/elf_loader/gnu_hash_table.h"
 #include "starboard/elf_loader/program_table.h"
 #include "starboard/elf_loader/relocations.h"
@@ -40,7 +39,6 @@
   ~ElfLoaderImpl();
 
  private:
-  scoped_ptr<File> elf_file_;
   scoped_ptr<ElfHeader> elf_header_loader_;
   scoped_ptr<ProgramTable> program_table_;
   scoped_ptr<DynamicSection> dynamic_section_;
diff --git a/starboard/evergreen/testing/performance_tests/baseline_update_test.sh b/starboard/evergreen/testing/performance_tests/baseline_update_test.sh
new file mode 100644
index 0000000..de3a2f6
--- /dev/null
+++ b/starboard/evergreen/testing/performance_tests/baseline_update_test.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="BaselineUpdatePerformance"
+
+function run_test() {
+  source $(dirname "$0")/performance_tests/run_update_trial.sh
+
+  for i in {1..3}; do
+    run_update_trial "$i";
+    result=$?
+    if [[ "${result}" -ne 0 ]]; then
+      return 1
+    fi
+  done
+
+  return 0
+}
diff --git a/starboard/evergreen/testing/performance_tests/compression_update_test.sh b/starboard/evergreen/testing/performance_tests/compression_update_test.sh
new file mode 100644
index 0000000..4b1d4ef
--- /dev/null
+++ b/starboard/evergreen/testing/performance_tests/compression_update_test.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="CompressionUpdatePerformance"
+
+function run_test() {
+  source $(dirname "$0")/performance_tests/run_update_trial.sh
+
+  for i in {1..3}; do
+    run_update_trial "$i" "--compress_update" "--loader_use_compression";
+    result=$?
+    if [[ "${result}" -ne 0 ]]; then
+      return 1
+    fi
+  done
+
+  return 0
+}
diff --git a/starboard/evergreen/testing/performance_tests/run_update_trial.sh b/starboard/evergreen/testing/performance_tests/run_update_trial.sh
new file mode 100644
index 0000000..7188c4a
--- /dev/null
+++ b/starboard/evergreen/testing/performance_tests/run_update_trial.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+#
+# Copyright 2022 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.
+
+# Runs an update cycle to generate performance logs.
+#
+# The size of the update is reported by this function directly, while memory
+# usage and shared library load time are logged by the loader app process.
+#
+# Args:
+#   Trial number, extra arguments for the loader app run to install an update,
+#   extra arguments for the loader app run to load the update.
+function run_update_trial() {
+  clear_storage
+
+  log "info" "Running trial ${1}"
+  # Run the loader app once until an update has been installed.
+  cycle_cobalt "https://www.youtube.com/tv" "${TEST_NAME}.0.log" "PingSender::SendPingComplete" "--loader_track_memory=100 ${2}"
+
+  run_command "ls -l \"${STORAGE_DIR}/installation_1/lib\""
+
+  # And run the loader app once more until it loads the installed update. The
+  # memory tracker is configured with a shorter period to capture more data
+  # during loading of the shared library.
+  cycle_cobalt "https://www.youtube.com/tv" "${TEST_NAME}.1.log" "Loading took" "--loader_track_memory=10 ${3}"
+
+  if grep -Eq "content/app/cobalt/lib/" "${LOG_PATH}/${TEST_NAME}.1.log"; then
+    log "error" "The system image was loaded instead of the update, which must have failed"
+    return 1
+  fi
+
+  return 0
+}
diff --git a/starboard/evergreen/testing/run_all_tests.sh b/starboard/evergreen/testing/run_all_tests.sh
index 8317a55..e9c67eb 100755
--- a/starboard/evergreen/testing/run_all_tests.sh
+++ b/starboard/evergreen/testing/run_all_tests.sh
@@ -21,7 +21,8 @@
 DIR="$(dirname "${0}")"
 
 AUTH_METHOD="public-key"
-while getopts "d:a:" o; do
+TEST_TYPE="functional"
+while getopts "d:a:t:" o; do
     case "${o}" in
         d)
             DEVICE_ID=${OPTARG}
@@ -29,6 +30,9 @@
         a)
             AUTH_METHOD=${OPTARG}
             ;;
+        t)
+            TEST_TYPE=${OPTARG}
+            ;;
     esac
 done
 shift $((OPTIND-1))
@@ -41,8 +45,14 @@
 source $DIR/setup.sh
 
 # Find all of the test files within the 'test' subdirectory.
-TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'"))
-
+if [[ "${TEST_TYPE}" == "functional" ]]; then
+  TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'"))
+elif [[ "${TEST_TYPE}" == "performance" ]]; then
+  TESTS=($(eval "find ${DIR}/performance_tests -maxdepth 1 -name '*_test.sh'"))
+else
+  echo "Only functional and performance tests are supported"
+  exit 1
+fi
 COUNT=0
 FAILED=()
 PASSED=()
diff --git a/cobalt/content/i18n/platform/win/en-US.xlb b/starboard/linux/i18n/en-US.xlb
similarity index 100%
rename from cobalt/content/i18n/platform/win/en-US.xlb
rename to starboard/linux/i18n/en-US.xlb
diff --git a/starboard/linux/shared/cobalt/configuration.py b/starboard/linux/shared/cobalt/configuration.py
index 72532e9..62d27f5 100644
--- a/starboard/linux/shared/cobalt/configuration.py
+++ b/starboard/linux/shared/cobalt/configuration.py
@@ -16,6 +16,49 @@
 from cobalt.build import cobalt_configuration
 from starboard.tools.testing import test_filter
 
+_FILTERED_TESTS = {
+    'base_unittests': [
+        # Fails when run in a sharded configuration: b/233108722, b/216774170
+        'TaskQueueSelectorTest.TestHighestPriority',
+        'TaskQueueSelectorTest.TestHighPriority',
+        'TaskQueueSelectorTest.TestLowPriority',
+        'TaskQueueSelectorTest.TestBestEffortPriority',
+        'TaskQueueSelectorTest.TestControlPriority',
+        'TaskQueueSelectorTest.TestObserverWithEnabledQueue',
+        'TaskQueueSelectorTest.TestObserverWithSetQueuePriorityAndQueueAlreadyEnabled',  #pylint: disable=line-too-long
+        'TaskQueueSelectorTest.TestDisableEnable',
+        'TaskQueueSelectorTest.TestDisableChangePriorityThenEnable',
+        'TaskQueueSelectorTest.TestEmptyQueues',
+        'TaskQueueSelectorTest.TestAge',
+        'TaskQueueSelectorTest.TestControlStarvesOthers',
+        'TaskQueueSelectorTest.TestHighestPriorityDoesNotStarveHigh',
+        'TaskQueueSelectorTest.TestHighestPriorityDoesNotStarveHighOrNormal',
+        'TaskQueueSelectorTest.TestHighestPriorityDoesNotStarveHighOrNormalOrLow',  #pylint: disable=line-too-long
+        'TaskQueueSelectorTest.TestHighPriorityDoesNotStarveNormal',
+        'TaskQueueSelectorTest.TestHighPriorityDoesNotStarveNormalOrLow',
+        'TaskQueueSelectorTest.TestNormalPriorityDoesNotStarveLow',
+        'TaskQueueSelectorTest.TestBestEffortGetsStarved',
+        'TaskQueueSelectorTest.TestHighPriorityStarvationScoreIncreasedOnlyWhenTasksArePresent',  #pylint: disable=line-too-long
+        'TaskQueueSelectorTest.TestNormalPriorityStarvationScoreIncreasedOnllWhenTasksArePresent',  #pylint: disable=line-too-long
+        'TaskQueueSelectorTest.TestLowPriorityTaskStarvationOnlyIncreasedWhenTasksArePresent',  #pylint: disable=line-too-long
+        'TaskQueueSelectorTest.AllEnabledWorkQueuesAreEmpty',
+        'TaskQueueSelectorTest.AllEnabledWorkQueuesAreEmpty_ControlPriority',
+        'TaskQueueSelectorTest.ChooseOldestWithPriority_Empty',
+        'TaskQueueSelectorTest.ChooseOldestWithPriority_OnlyDelayed',
+        'TaskQueueSelectorTest.ChooseOldestWithPriority_OnlyImmediate',
+        'TaskQueueSelectorTest.TestObserverWithOneBlockedQueue',
+        'TaskQueueSelectorTest.TestObserverWithTwoBlockedQueues',
+        'HistogramTesterTest.GetHistogramSamplesSinceCreationNotNull',
+        'HistogramTesterTest.TestUniqueSample',
+        'HistogramTesterTest.TestBucketsSample',
+        'HistogramTesterTest.TestBucketsSampleWithScope',
+        'HistogramTesterTest.TestGetAllSamples',
+        'HistogramTesterTest.TestGetAllSamples_NoSamples',
+        'HistogramTesterTest.TestGetTotalCountsForPrefix',
+        'HistogramTesterTest.TestGetAllChangedHistograms',
+    ],
+}
+
 
 class CobaltLinuxConfiguration(cobalt_configuration.CobaltConfiguration):
   """Starboard Linux Cobalt shared configuration."""
@@ -31,6 +74,8 @@
 
   def GetTestFilters(self):
     filters = super(CobaltLinuxConfiguration, self).GetTestFilters()
+    for target, tests in _FILTERED_TESTS.items():
+      filters.extend(test_filter.TestFilter(target, test) for test in tests)
     return filters
 
   def GetWebPlatformTestFilters(self):
diff --git a/starboard/linux/shared/launcher.py b/starboard/linux/shared/launcher.py
index 867ada8..0e90bc9 100644
--- a/starboard/linux/shared/launcher.py
+++ b/starboard/linux/shared/launcher.py
@@ -200,4 +200,4 @@
 
   def GetDeviceOutputPath(self):
     """Writable path where test targets can output files"""
-    return "/tmp"
+    return "/tmp/testoutput"
diff --git a/starboard/linux/shared/system_get_path.cc b/starboard/linux/shared/system_get_path.cc
index 1f73113..0f70002 100644
--- a/starboard/linux/shared/system_get_path.cc
+++ b/starboard/linux/shared/system_get_path.cc
@@ -227,11 +227,11 @@
       SbDirectoryCreate(path.data());
       break;
 
-#if SB_API_VERSION < SB_SYSTEM_PATH_TEST_OUTPUT_DIRECTORY_DEPRECATED
+#if SB_API_VERSION < 14
     case kSbSystemPathTestOutputDirectory:
       return SbSystemGetPath(kSbSystemPathDebugOutputDirectory, out_path,
                              path_size);
-#endif  // #if SB_API_VERSION < SB_SYSTEM_PATH_TEST_OUTPUT_DIRECTORY_DEPRECATED
+#endif  // #if SB_API_VERSION < 14
 
     case kSbSystemPathExecutableFile:
       return GetExecutablePath(out_path, path_size);
diff --git a/starboard/linux/x64x11/system_get_property_impl.cc b/starboard/linux/x64x11/system_get_property_impl.cc
index 416d1b0..7f2abeb 100644
--- a/starboard/linux/x64x11/system_get_property_impl.cc
+++ b/starboard/linux/x64x11/system_get_property_impl.cc
@@ -28,22 +28,13 @@
 const char kPlatformName[] = "X11; Linux x86_64";
 const char kSystemIntegratorName[] = "SystemIntegratorName";
 
-#if SB_API_VERSION >= 13
+#if SB_API_VERSION >= 14
+const char kModelYear[] = "2023";
+#elif SB_API_VERSION >= 13
 const char kModelYear[] = "2022";
 #elif SB_API_VERSION >= 12
 const char kModelYear[] = "2021";
-#elif SB_API_VERSION >= 11
-const char kModelYear[] = "2020";
-#elif SB_API_VERSION >= 10
-const char kModelYear[] = "2019";
-#elif SB_API_VERSION >= 6
-const char kModelYear[] = "2018";
-#elif SB_API_VERSION >= 2
-const char kModelYear[] = "2017";
-#else
-const char kModelYear[] = "2016";
 #endif  // SB_API_VERSION
-
 }  // namespace
 
 // Omit namespace linux due to symbol name conflict.
diff --git a/starboard/media.h b/starboard/media.h
index 1655696..d7c39f9 100644
--- a/starboard/media.h
+++ b/starboard/media.h
@@ -256,10 +256,14 @@
   kSbMediaMatrixIdYDzDx = 11,
 
   kSbMediaMatrixIdLastStandardValue = kSbMediaMatrixIdYDzDx,
-
-  // Chrome-specific values start at 1000
+#if SB_API_VERSION >= 14
+  kSbMediaMatrixIdInvalid = 255,
+  kSbMediaMatrixIdLast = kSbMediaMatrixIdInvalid,
+#else   // SB_API_VERSION >= 14
   kSbMediaMatrixIdUnknown = 1000,
   kSbMediaMatrixIdLast = kSbMediaMatrixIdUnknown,
+#endif  // SB_API_VERSION >= 14
+
 } SbMediaMatrixId;
 
 // This corresponds to the WebM Range enum which is part of WebM color data (see
@@ -585,8 +589,12 @@
 // The media buffer will be allocated using the returned alignment.  Set this to
 // a larger value may increase the memory consumption of media buffers.
 //
+#if SB_API_VERSION >= 14
+SB_EXPORT int SbMediaGetBufferAlignment();
+#else   // SB_API_VERSION >= 14
 // |type|: the media type of the stream (audio or video).
 SB_EXPORT int SbMediaGetBufferAlignment(SbMediaType type);
+#endif  // SB_API_VERSION >= 14
 
 // When the media stack needs more memory to store media buffers, it will
 // allocate extra memory in units returned by SbMediaGetBufferAllocationUnit.
@@ -621,6 +629,11 @@
 
 // The maximum amount of memory that will be used to store media buffers. This
 // must be larger than sum of the video budget and audio budget.
+// This is a soft limit and the app will continue to allocate media buffers even
+// if the accumulated memory used by the media buffers exceeds the maximum
+// buffer capacity.  The allocation of media buffers may only fail when there is
+// not enough memory in the system to fulfill the request, under which case the
+// app will be terminated as under other OOM situations.
 //
 // |codec|: the video codec associated with the buffer.
 // |resolution_width|: the width of the video resolution.
@@ -636,8 +649,12 @@
 // can be use optimally by specific instructions like SIMD.  Set to 0 to remove
 // any padding.
 //
+#if SB_API_VERSION >= 14
+SB_EXPORT int SbMediaGetBufferPadding();
+#else   // SB_API_VERSION >= 14
 // |type|: the media type of the stream (audio or video).
 SB_EXPORT int SbMediaGetBufferPadding(SbMediaType type);
+#endif  // SB_API_VERSION >= 14
 
 // When either SbMediaGetInitialBufferCapacity or SbMediaGetBufferAllocationUnit
 // isn't zero, media buffers will be allocated using a memory pool.  Set the
diff --git a/starboard/nplb/media_buffer_test.cc b/starboard/nplb/media_buffer_test.cc
index b1f40f3..ed05240 100644
--- a/starboard/nplb/media_buffer_test.cc
+++ b/starboard/nplb/media_buffer_test.cc
@@ -117,7 +117,13 @@
 
 TEST(SbMediaBufferTest, Alignment) {
   for (auto type : kMediaTypes) {
+#if SB_API_VERSION >= 14
+    // The test will be run more than once, it's redundant but allows us to keep
+    // the test logic in one place.
+    int alignment = SbMediaGetBufferAlignment();
+#else  // SB_API_VERSION >= 14
     int alignment = SbMediaGetBufferAlignment(type);
+#endif  // SB_API_VERSION >= 14
     EXPECT_GE(alignment, 1);
     EXPECT_EQ(alignment & (alignment - 1), 0)
         << "Alignment must always be a power of 2";
@@ -141,7 +147,13 @@
 
   if (!HasNonfatalFailure()) {
     for (SbMediaType type : kMediaTypes) {
+#if SB_API_VERSION >= 14
+      // The test will be run more than once, it's redundant but allows us to
+      // keep the test logic in one place.
+      int alignment = SbMediaGetBufferAlignment();
+#else  // SB_API_VERSION >= 14
       int alignment = SbMediaGetBufferAlignment(type);
+#endif  // SB_API_VERSION >= 14
       EXPECT_EQ(alignment & (alignment - 1), 0)
           << "Alignment must always be a power of 2";
       if (HasNonfatalFailure()) {
@@ -201,9 +213,13 @@
 }
 
 TEST(SbMediaBufferTest, Padding) {
+#if SB_API_VERSION >= 14
+  EXPECT_GE(SbMediaGetBufferPadding(), 0);
+#else   // SB_API_VERSION >= 14
   for (auto type : kMediaTypes) {
     EXPECT_GE(SbMediaGetBufferPadding(type), 0);
   }
+#endif  // SB_API_VERSION >= 14
 }
 
 TEST(SbMediaBufferTest, PoolAllocateOnDemand) {
@@ -268,10 +284,17 @@
   TEST_PERF_FUNCNOARGS_DEFAULT(SbMediaGetBufferStorageType);
   TEST_PERF_FUNCNOARGS_DEFAULT(SbMediaIsBufferUsingMemoryPool);
 
+#if SB_API_VERSION >= 14
+  for (auto type : kMediaTypes) {
+    TEST_PERF_FUNCNOARGS_DEFAULT(SbMediaGetBufferAlignment);
+    TEST_PERF_FUNCNOARGS_DEFAULT(SbMediaGetBufferPadding);
+  }
+#else   // SB_API_VERSION >= 14
   for (auto type : kMediaTypes) {
     TEST_PERF_FUNCWITHARGS_DEFAULT(SbMediaGetBufferAlignment, type);
     TEST_PERF_FUNCWITHARGS_DEFAULT(SbMediaGetBufferPadding, type);
   }
+#endif  // SB_API_VERSION >= 14
 
   for (auto resolution : kVideoResolutions) {
     for (auto bits_per_pixel : kBitsPerPixelValues) {
diff --git a/starboard/nplb/nplb_evergreen_compat_tests/sabi_test.cc b/starboard/nplb/nplb_evergreen_compat_tests/sabi_test.cc
index e4c93da..f5b8a21 100644
--- a/starboard/nplb/nplb_evergreen_compat_tests/sabi_test.cc
+++ b/starboard/nplb/nplb_evergreen_compat_tests/sabi_test.cc
@@ -208,6 +208,66 @@
     "\"target_arch\":\"x64\",\"target_arch_sub\":\"\",\"word_size\":64}";
 #endif  // SB_API_VERSION == 14
 
+#if SB_API_VERSION == 15
+const char* kSabiJsonIdArmHardfp =
+    "{\"alignment_char\":1,\"alignment_double\":8,\"alignment_float\":4,"
+    "\"alignment_int\":4,\"alignment_llong\":8,\"alignment_long\":4,"
+    "\"alignment_pointer\":4,\"alignment_short\":2,\"calling_convention\":"
+    "\"eabi\",\"endianness\":\"little\",\"floating_point_abi\":\"hard\","
+    "\"floating_point_fpu\":\"vfpv3\",\"sb_api_version\":15,\"signedness_of_"
+    "char\":\"signed\",\"signedness_of_enum\":\"signed\",\"size_of_char\":1,"
+    "\"size_of_double\":8,\"size_of_enum\":4,\"size_of_float\":4,\"size_of_"
+    "int\":4,\"size_of_llong\":8,\"size_of_long\":4,\"size_of_pointer\":4,"
+    "\"size_of_short\":2,\"target_arch\":\"arm\",\"target_arch_sub\":\"v7a\","
+    "\"word_size\":32}";
+
+const char* kSabiJsonIdArmSoftfp =
+    "{\"alignment_char\":1,\"alignment_double\":8,\"alignment_float\":4,"
+    "\"alignment_int\":4,\"alignment_llong\":8,\"alignment_long\":4,"
+    "\"alignment_pointer\":4,\"alignment_short\":2,\"calling_convention\":"
+    "\"eabi\",\"endianness\":\"little\",\"floating_point_abi\":\"softfp\","
+    "\"floating_point_fpu\":\"vfpv3\",\"sb_api_version\":15,\"signedness_of_"
+    "char\":\"signed\",\"signedness_of_enum\":\"signed\",\"size_of_char\":1,"
+    "\"size_of_double\":8,\"size_of_enum\":4,\"size_of_float\":4,\"size_of_"
+    "int\":4,\"size_of_llong\":8,\"size_of_long\":4,\"size_of_pointer\":4,"
+    "\"size_of_short\":2,\"target_arch\":\"arm\",\"target_arch_sub\":\"v7a\","
+    "\"word_size\":32}";
+
+const char* kSabiJsonIdArm64 =
+    "{\"alignment_char\":1,\"alignment_double\":8,\"alignment_float\":4,"
+    "\"alignment_int\":4,\"alignment_llong\":8,\"alignment_long\":8,"
+    "\"alignment_pointer\":8,\"alignment_short\":2,\"calling_convention\":"
+    "\"aarch64\",\"endianness\":\"little\",\"floating_point_abi\":\"\","
+    "\"floating_point_fpu\":\"\",\"sb_api_version\":15,\"signedness_of_char\":"
+    "\"signed\",\"signedness_of_enum\":\"signed\",\"size_of_char\":1,\"size_of_"
+    "double\":8,\"size_of_enum\":4,\"size_of_float\":4,\"size_of_int\":4,"
+    "\"size_of_llong\":8,\"size_of_long\":8,\"size_of_pointer\":8,\"size_of_"
+    "short\":2,\"target_arch\":\"arm64\",\"target_arch_sub\":\"v8a\",\"word_"
+    "size\":64}";
+
+const char* kSabiJsonIdX86 =
+    "{\"alignment_char\":1,\"alignment_double\":8,\"alignment_float\":4,"
+    "\"alignment_int\":4,\"alignment_llong\":8,\"alignment_long\":4,"
+    "\"alignment_pointer\":4,\"alignment_short\":2,\"calling_convention\":"
+    "\"sysv\",\"endianness\":\"little\",\"floating_point_abi\":\"\",\"floating_"
+    "point_fpu\":\"\",\"sb_api_version\":15,\"signedness_of_char\":\"signed\","
+    "\"signedness_of_enum\":\"signed\",\"size_of_char\":1,\"size_of_double\":8,"
+    "\"size_of_enum\":4,\"size_of_float\":4,\"size_of_int\":4,\"size_of_"
+    "llong\":8,\"size_of_long\":4,\"size_of_pointer\":4,\"size_of_short\":2,"
+    "\"target_arch\":\"x86\",\"target_arch_sub\":\"\",\"word_size\":32}";
+
+const char* kSabiJsonIdX64Sysv =
+    "{\"alignment_char\":1,\"alignment_double\":8,\"alignment_float\":4,"
+    "\"alignment_int\":4,\"alignment_llong\":8,\"alignment_long\":8,"
+    "\"alignment_pointer\":8,\"alignment_short\":2,\"calling_convention\":"
+    "\"sysv\",\"endianness\":\"little\",\"floating_point_abi\":\"\",\"floating_"
+    "point_fpu\":\"\",\"sb_api_version\":15,\"signedness_of_char\":\"signed\","
+    "\"signedness_of_enum\":\"signed\",\"size_of_char\":1,\"size_of_double\":8,"
+    "\"size_of_enum\":4,\"size_of_float\":4,\"size_of_int\":4,\"size_of_"
+    "llong\":8,\"size_of_long\":8,\"size_of_pointer\":8,\"size_of_short\":2,"
+    "\"target_arch\":\"x64\",\"target_arch_sub\":\"\",\"word_size\":64}";
+#endif  // SB_API_VERSION == 15
+
 class SabiTest : public ::testing::Test {
  protected:
   SabiTest() {}
diff --git a/starboard/nplb/system_get_path_test.cc b/starboard/nplb/system_get_path_test.cc
index 7167746..e320737 100644
--- a/starboard/nplb/system_get_path_test.cc
+++ b/starboard/nplb/system_get_path_test.cc
@@ -94,9 +94,9 @@
 TEST(SbSystemGetPathTest, DoesNotBlowUpForDefinedIds) {
   BasicTest(kSbSystemPathDebugOutputDirectory, false, false, __LINE__);
   BasicTest(kSbSystemPathTempDirectory, false, false, __LINE__);
-#if SB_API_VERSION < SB_SYSTEM_PATH_TEST_OUTPUT_DIRECTORY_DEPRECATED
+#if SB_API_VERSION < 14
   BasicTest(kSbSystemPathTestOutputDirectory, false, false, __LINE__);
-#endif  // #if SB_API_VERSION < SB_SYSTEM_PATH_TEST_OUTPUT_DIRECTORY_DEPRECATED
+#endif  // #if SB_API_VERSION < 14
   BasicTest(kSbSystemPathCacheDirectory, false, false, __LINE__);
   BasicTest(kSbSystemPathFontDirectory, false, false, __LINE__);
   BasicTest(kSbSystemPathFontConfigurationDirectory, false, false, __LINE__);
@@ -105,9 +105,9 @@
 TEST(SbSystemGetPathTest, DoesNotTouchOutputBufferOnFailureForDefinedIds) {
   UnmodifiedOnFailureTest(kSbSystemPathDebugOutputDirectory, __LINE__);
   UnmodifiedOnFailureTest(kSbSystemPathTempDirectory, __LINE__);
-#if SB_API_VERSION < SB_SYSTEM_PATH_TEST_OUTPUT_DIRECTORY_DEPRECATED
+#if SB_API_VERSION < 14
   UnmodifiedOnFailureTest(kSbSystemPathTestOutputDirectory, __LINE__);
-#endif  // #if SB_API_VERSION < SB_SYSTEM_PATH_TEST_OUTPUT_DIRECTORY_DEPRECATED
+#endif  // #if SB_API_VERSION < 14
   UnmodifiedOnFailureTest(kSbSystemPathCacheDirectory, __LINE__);
   UnmodifiedOnFailureTest(kSbSystemPathFontDirectory, __LINE__);
   UnmodifiedOnFailureTest(kSbSystemPathFontConfigurationDirectory, __LINE__);
diff --git a/starboard/sabi/arm/hardfp/sabi-v15.json b/starboard/sabi/arm/hardfp/sabi-v15.json
new file mode 100644
index 0000000..e95251f
--- /dev/null
+++ b/starboard/sabi/arm/hardfp/sabi-v15.json
@@ -0,0 +1,31 @@
+{
+  "variables": {
+    "sb_api_version": 15,
+    "target_arch": "arm",
+    "target_arch_sub": "v7a",
+    "word_size": 32,
+    "endianness": "little",
+    "calling_convention": "eabi",
+    "floating_point_abi": "hard",
+    "floating_point_fpu": "vfpv3",
+    "signedness_of_char": "signed",
+    "signedness_of_enum": "signed",
+    "alignment_char":    1,
+    "alignment_double":  8,
+    "alignment_float":   4,
+    "alignment_int":     4,
+    "alignment_llong":   8,
+    "alignment_long":    4,
+    "alignment_pointer": 4,
+    "alignment_short":   2,
+    "size_of_char":    1,
+    "size_of_double":  8,
+    "size_of_enum":    4,
+    "size_of_float":   4,
+    "size_of_int":     4,
+    "size_of_llong":   8,
+    "size_of_long":    4,
+    "size_of_pointer": 4,
+    "size_of_short":   2
+  }
+}
diff --git a/starboard/sabi/arm/softfp/sabi-v15.json b/starboard/sabi/arm/softfp/sabi-v15.json
new file mode 100644
index 0000000..262b5d5
--- /dev/null
+++ b/starboard/sabi/arm/softfp/sabi-v15.json
@@ -0,0 +1,31 @@
+{
+  "variables": {
+    "sb_api_version": 15,
+    "target_arch": "arm",
+    "target_arch_sub": "v7a",
+    "word_size": 32,
+    "endianness": "little",
+    "calling_convention": "eabi",
+    "floating_point_abi": "softfp",
+    "floating_point_fpu": "vfpv3",
+    "signedness_of_char": "signed",
+    "signedness_of_enum": "signed",
+    "alignment_char":    1,
+    "alignment_double":  8,
+    "alignment_float":   4,
+    "alignment_int":     4,
+    "alignment_llong":   8,
+    "alignment_long":    4,
+    "alignment_pointer": 4,
+    "alignment_short":   2,
+    "size_of_char":    1,
+    "size_of_double":  8,
+    "size_of_enum":    4,
+    "size_of_float":   4,
+    "size_of_int":     4,
+    "size_of_llong":   8,
+    "size_of_long":    4,
+    "size_of_pointer": 4,
+    "size_of_short":   2
+  }
+}
diff --git a/starboard/sabi/arm64/sabi-v15.json b/starboard/sabi/arm64/sabi-v15.json
new file mode 100644
index 0000000..fe44d33
--- /dev/null
+++ b/starboard/sabi/arm64/sabi-v15.json
@@ -0,0 +1,31 @@
+{
+  "variables": {
+    "sb_api_version": 15,
+    "target_arch": "arm64",
+    "target_arch_sub": "v8a",
+    "word_size": 64,
+    "endianness": "little",
+    "calling_convention": "aarch64",
+    "floating_point_abi": "",
+    "floating_point_fpu": "",
+    "signedness_of_char": "signed",
+    "signedness_of_enum": "signed",
+    "alignment_char":    1,
+    "alignment_double":  8,
+    "alignment_float":   4,
+    "alignment_int":     4,
+    "alignment_llong":   8,
+    "alignment_long":    8,
+    "alignment_pointer": 8,
+    "alignment_short":   2,
+    "size_of_char":    1,
+    "size_of_double":  8,
+    "size_of_enum":    4,
+    "size_of_float":   4,
+    "size_of_int":     4,
+    "size_of_llong":   8,
+    "size_of_long":    8,
+    "size_of_pointer": 8,
+    "size_of_short":   2
+  }
+}
diff --git a/starboard/sabi/schema/sabi-v15.schema.json b/starboard/sabi/schema/sabi-v15.schema.json
new file mode 100644
index 0000000..65d082a
--- /dev/null
+++ b/starboard/sabi/schema/sabi-v15.schema.json
@@ -0,0 +1,172 @@
+{
+  "title": "Starboard ABI Schema",
+  "description": "This schema validates that a Starboard ABI file contains the required keys, no extras, and that the corresponding values are valid.",
+  "type": "object",
+  "properties": {
+    "sb_api_version": {
+      "type": "integer",
+      "description": "The Starboard API version.",
+      "enum": [15]
+    },
+    "target_arch": {
+      "type": "string",
+      "description": "The targeted architecture.",
+      "enum": ["arm", "arm64", "x86", "x64"]
+    },
+    "target_arch_sub": {
+      "type": "string",
+      "description": "The targeted sub-architecture.",
+      "enum": ["v7a", "v8a", ""]
+    },
+    "word_size": {
+      "type": "integer",
+      "description": "The word size.",
+      "enum": [32, 64]
+    },
+    "endianness": {
+      "type": "string",
+      "description": "The endianness.",
+      "enum": ["big", "little"]
+    },
+    "calling_convention": {
+      "type": "string",
+      "description": "The calling convention.",
+      "enum": ["sysv", "eabi", "windows", "aarch64"]
+    },
+    "floating_point_abi": {
+      "type": "string",
+      "description": "The floating-point ABI.",
+      "enum": ["hard", "softfp", ""]
+    },
+    "floating_point_fpu": {
+      "type": "string",
+      "description": "The floating-point hardware, if available.",
+      "enum": ["vfpv2", "vfpv3", ""]
+    },
+    "signedness_of_char": {
+      "type": "string",
+      "description": "The signedness of the 'char' data type.",
+      "enum": ["signed", "unsigned"]
+    },
+    "signedness_of_enum": {
+      "type": "string",
+      "description": "The signedness of the 'enum' data type.",
+      "enum": ["signed", "unsigned"]
+    },
+    "alignment_char": {
+      "type": "integer",
+      "description": "The alignment of the 'char' data type.",
+      "enum": [1]
+    },
+    "alignment_double": {
+      "type": "integer",
+      "description": "The alignment of the 'double' data type.",
+      "enum": [8]
+    },
+    "alignment_float": {
+      "type": "integer",
+      "description": "The alignment of the 'float' data type.",
+      "enum": [4]
+    },
+    "alignment_int": {
+      "type": "integer",
+      "description": "The alignment of the 'int' data type.",
+      "enum": [4]
+    },
+    "alignment_llong": {
+      "type": "integer",
+      "description": "The alignment of the 'long long' data type.",
+      "enum": [8]
+    },
+    "alignment_long": {
+      "type": "integer",
+      "description": "The alignment of the 'long' data type.",
+      "enum": [4, 8]
+    },
+    "alignment_pointer": {
+      "type": "integer",
+      "description": "The alignment of pointers.",
+      "enum": [4, 8]
+    },
+    "alignment_short": {
+      "type": "integer",
+      "description": "The alignment of the 'short' data type.",
+      "enum": [2]
+    },
+    "size_of_char": {
+      "type": "integer",
+      "description": "The size of the 'char' data type.",
+      "enum": [1]
+    },
+    "size_of_double": {
+      "type": "integer",
+      "description": "The size of the 'double' data type.",
+      "enum": [8]
+    },
+    "size_of_enum": {
+      "type": "integer",
+      "description": "The size of the 'enum' data type.",
+      "enum": [4]
+    },
+    "size_of_float": {
+      "type": "integer",
+      "description": "The size of the 'float' data type.",
+      "enum": [4]
+    },
+    "size_of_int": {
+      "type": "integer",
+      "description": "The size of the 'int' data type.",
+      "enum": [4]
+    },
+    "size_of_llong": {
+      "type": "integer",
+      "description": "The size of the 'long long' data type.",
+      "enum": [8]
+    },
+    "size_of_long": {
+      "type": "integer",
+      "description": "The size of the 'long' data type.",
+      "enum": [4, 8]
+    },
+    "size_of_pointer": {
+      "type": "integer",
+      "description": "The size of pointers.",
+      "enum": [4, 8]
+    },
+    "size_of_short": {
+      "type": "integer",
+      "description": "The size of the 'short' data type.",
+      "enum": [2]
+    }
+  },
+  "required": [
+    "sb_api_version",
+    "target_arch",
+    "target_arch_sub",
+    "word_size",
+    "endianness",
+    "calling_convention",
+    "floating_point_abi",
+    "floating_point_fpu",
+    "signedness_of_char",
+    "signedness_of_enum",
+    "alignment_char",
+    "alignment_double",
+    "alignment_float",
+    "alignment_int",
+    "alignment_llong",
+    "alignment_long",
+    "alignment_pointer",
+    "alignment_short",
+    "size_of_char",
+    "size_of_enum",
+    "size_of_double",
+    "size_of_float",
+    "size_of_int",
+    "size_of_llong",
+    "size_of_long",
+    "size_of_pointer",
+    "size_of_short"
+  ],
+  "additionalProperties": false
+}
diff --git a/starboard/sabi/x64/sysv/sabi-v15.json b/starboard/sabi/x64/sysv/sabi-v15.json
new file mode 100644
index 0000000..b7ac82d
--- /dev/null
+++ b/starboard/sabi/x64/sysv/sabi-v15.json
@@ -0,0 +1,31 @@
+{
+  "variables": {
+    "sb_api_version": 15,
+    "target_arch": "x64",
+    "target_arch_sub": "",
+    "word_size": 64,
+    "endianness": "little",
+    "calling_convention": "sysv",
+    "floating_point_abi": "",
+    "floating_point_fpu": "",
+    "signedness_of_char": "signed",
+    "signedness_of_enum": "signed",
+    "alignment_char":    1,
+    "alignment_double":  8,
+    "alignment_float":   4,
+    "alignment_int":     4,
+    "alignment_llong":   8,
+    "alignment_long":    8,
+    "alignment_pointer": 8,
+    "alignment_short":   2,
+    "size_of_char":    1,
+    "size_of_double":  8,
+    "size_of_enum":    4,
+    "size_of_float":   4,
+    "size_of_int":     4,
+    "size_of_llong":   8,
+    "size_of_long":    8,
+    "size_of_pointer": 8,
+    "size_of_short":   2
+  }
+}
diff --git a/starboard/sabi/x64/windows/sabi-v15.json b/starboard/sabi/x64/windows/sabi-v15.json
new file mode 100644
index 0000000..837a30c
--- /dev/null
+++ b/starboard/sabi/x64/windows/sabi-v15.json
@@ -0,0 +1,31 @@
+{
+  "variables": {
+    "sb_api_version": 15,
+    "target_arch": "x64",
+    "target_arch_sub": "",
+    "word_size": 64,
+    "endianness": "little",
+    "calling_convention": "windows",
+    "floating_point_abi": "",
+    "floating_point_fpu": "",
+    "signedness_of_char": "signed",
+    "signedness_of_enum": "signed",
+    "alignment_char":    1,
+    "alignment_double":  8,
+    "alignment_float":   4,
+    "alignment_int":     4,
+    "alignment_llong":   8,
+    "alignment_long":    4,
+    "alignment_pointer": 8,
+    "alignment_short":   2,
+    "size_of_char":    1,
+    "size_of_double":  8,
+    "size_of_enum":    4,
+    "size_of_float":   4,
+    "size_of_int":     4,
+    "size_of_llong":   8,
+    "size_of_long":    4,
+    "size_of_pointer": 8,
+    "size_of_short":   2
+  }
+}
diff --git a/starboard/sabi/x86/sabi-v15.json b/starboard/sabi/x86/sabi-v15.json
new file mode 100644
index 0000000..1fd2b2e
--- /dev/null
+++ b/starboard/sabi/x86/sabi-v15.json
@@ -0,0 +1,31 @@
+{
+  "variables": {
+    "sb_api_version": 15,
+    "target_arch": "x86",
+    "target_arch_sub": "",
+    "word_size": 32,
+    "endianness": "little",
+    "calling_convention": "sysv",
+    "floating_point_abi": "",
+    "floating_point_fpu": "",
+    "signedness_of_char": "signed",
+    "signedness_of_enum": "signed",
+    "alignment_char":    1,
+    "alignment_double":  8,
+    "alignment_float":   4,
+    "alignment_int":     4,
+    "alignment_llong":   8,
+    "alignment_long":    4,
+    "alignment_pointer": 4,
+    "alignment_short":   2,
+    "size_of_char":    1,
+    "size_of_double":  8,
+    "size_of_enum":    4,
+    "size_of_float":   4,
+    "size_of_int":     4,
+    "size_of_llong":   8,
+    "size_of_long":    4,
+    "size_of_pointer": 4,
+    "size_of_short":   2
+  }
+}
diff --git a/starboard/shared/deviceauth/deviceauth_internal.cc b/starboard/shared/deviceauth/deviceauth_internal.cc
index 416e270..c0cde20 100644
--- a/starboard/shared/deviceauth/deviceauth_internal.cc
+++ b/starboard/shared/deviceauth/deviceauth_internal.cc
@@ -12,20 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/system.h"
+#include "starboard/shared/deviceauth/deviceauth_internal.h"
 
 #include <string.h>
 #include <string>
 
 #include "starboard/common/log.h"
 #include "starboard/linux/x64x11/system_properties.h"
+#include "starboard/system.h"
 #include "third_party/boringssl/src/include/openssl/hmac.h"
 #include "third_party/modp_b64/modp_b64.h"
-
-#include "starboard/shared/deviceauth/deviceauth_internal.h"
-
-#if SB_API_VERSION >= 11
-
 namespace {
 bool Base64Decode(const std::string& input, std::string* output) {
   std::string temp;
@@ -82,10 +78,6 @@
   return result == digest;
 }
 
-} // namespace deviceauth
-
-} // namespace shared
-
-} // namespace starboard
-
-#endif  // SB_API_VERSION >= 11
+}  // namespace deviceauth
+}  // namespace shared
+}  // namespace starboard
diff --git a/starboard/shared/deviceauth/deviceauth_internal.h b/starboard/shared/deviceauth/deviceauth_internal.h
index c2f7b62..9aab66e 100644
--- a/starboard/shared/deviceauth/deviceauth_internal.h
+++ b/starboard/shared/deviceauth/deviceauth_internal.h
@@ -17,7 +17,6 @@
 
 #include <cstddef>
 #include <cstdint>
-
 namespace starboard {
 namespace shared {
 namespace deviceauth {
diff --git a/starboard/shared/libdav1d/dav1d_video_decoder.cc b/starboard/shared/libdav1d/dav1d_video_decoder.cc
index 1f793ac..08623b0 100644
--- a/starboard/shared/libdav1d/dav1d_video_decoder.cc
+++ b/starboard/shared/libdav1d/dav1d_video_decoder.cc
@@ -325,13 +325,6 @@
       return nullptr;
     }
 
-    SB_DCHECK(dav1d_picture.stride[kDav1dPlaneY] ==
-              dav1d_picture.stride[kDav1dPlaneU] * 2);
-    SB_DCHECK(dav1d_picture.data[kDav1dPlaneY] <
-              dav1d_picture.data[kDav1dPlaneU]);
-    SB_DCHECK(dav1d_picture.data[kDav1dPlaneU] <
-              dav1d_picture.data[kDav1dPlaneV]);
-
     if (dav1d_picture.stride[kDav1dPlaneY] !=
             dav1d_picture.stride[kDav1dPlaneU] * 2 ||
         dav1d_picture.data[kDav1dPlaneY] >= dav1d_picture.data[kDav1dPlaneU] ||
diff --git a/starboard/shared/libjpeg/jpeg_image_decoder.cc b/starboard/shared/libjpeg/jpeg_image_decoder.cc
index d81a735..3a79422d 100644
--- a/starboard/shared/libjpeg/jpeg_image_decoder.cc
+++ b/starboard/shared/libjpeg/jpeg_image_decoder.cc
@@ -165,9 +165,9 @@
       case kSbDecodeTargetFormat2PlaneYUVNV12:
       case kSbDecodeTargetFormat3PlaneYUVI420:
       case kSbDecodeTargetFormat3Plane10BitYUVI420:
-#if SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+#if SB_API_VERSION >= 14
       case kSbDecodeTargetFormat3Plane10BitYUVI420Compact:
-#endif  // SB_API_VERSION >= SB_DECODE_TARGET_FORMAT_YUVI420_COMPACT_API_VERSION
+#endif  // SB_API_VERSION >= 14
       case kSbDecodeTargetFormat1PlaneUYVY:
       case kSbDecodeTargetFormatInvalid:
         SB_NOTREACHED();
diff --git a/starboard/shared/starboard/application.cc b/starboard/shared/starboard/application.cc
index 65bc7ab..fd60437 100644
--- a/starboard/shared/starboard/application.cc
+++ b/starboard/shared/starboard/application.cc
@@ -274,10 +274,13 @@
   // Ensure the event is deleted unless it is released.
   scoped_ptr<Event> scoped_event(event);
 
+// Ensure that we go through the the appropriate lifecycle events based on
+// the current state. If intermediate events need to be processed, use
+// HandleEventAndUpdateState() rather than Inject() for the intermediate events
+// because there may already be other lifecycle events in the queue.
+
 #if SB_API_VERSION >= 13
   SbTimeMonotonic timestamp = scoped_event->event->timestamp;
-  // Ensure that we go through the the appropriate lifecycle events based on the
-  // current state.
   switch (scoped_event->event->type) {
     case kSbEventTypePreload:
       if (state() != kStateUnstarted) {
@@ -286,7 +289,8 @@
       break;
     case kSbEventTypeStart:
       if (state() != kStateUnstarted && state() != kStateStarted) {
-        Inject(new Event(kSbEventTypeFocus, timestamp, NULL, NULL));
+        HandleEventAndUpdateState(
+            new Event(kSbEventTypeFocus, timestamp, NULL, NULL));
         return true;
       }
       break;
@@ -300,11 +304,13 @@
         case kStateStopped:
           return true;
         case kStateFrozen:
-          Inject(new Event(kSbEventTypeUnfreeze, timestamp, NULL, NULL));
+          HandleEventAndUpdateState(
+              new Event(kSbEventTypeUnfreeze, timestamp, NULL, NULL));
         // The fall-through is intentional.
         case kStateConcealed:
-          Inject(new Event(kSbEventTypeReveal, timestamp, NULL, NULL));
-          Inject(scoped_event.release());
+          HandleEventAndUpdateState(
+              new Event(kSbEventTypeReveal, timestamp, NULL, NULL));
+          HandleEventAndUpdateState(scoped_event.release());
           return true;
         case kStateBlurred:
           break;
@@ -318,8 +324,9 @@
         case kStateUnstarted:
           return true;
         case kStateStarted:
-          Inject(new Event(kSbEventTypeBlur, timestamp, NULL, NULL));
-          Inject(scoped_event.release());
+          HandleEventAndUpdateState(
+              new Event(kSbEventTypeBlur, timestamp, NULL, NULL));
+          HandleEventAndUpdateState(scoped_event.release());
           return true;
         case kStateBlurred:
           break;
@@ -334,8 +341,9 @@
         case kStateStopped:
           return true;
         case kStateFrozen:
-          Inject(new Event(kSbEventTypeUnfreeze, timestamp, NULL, NULL));
-          Inject(scoped_event.release());
+          HandleEventAndUpdateState(
+              new Event(kSbEventTypeUnfreeze, timestamp, NULL, NULL));
+          HandleEventAndUpdateState(scoped_event.release());
           return true;
         case kStateConcealed:
           break;
@@ -350,11 +358,13 @@
         case kStateUnstarted:
           return true;
         case kStateStarted:
-          Inject(new Event(kSbEventTypeBlur, timestamp, NULL, NULL));
+          HandleEventAndUpdateState(
+              new Event(kSbEventTypeBlur, timestamp, NULL, NULL));
         // The fall-through is intentional
         case kStateBlurred:
-          Inject(new Event(kSbEventTypeConceal, timestamp, NULL, NULL));
-          Inject(scoped_event.release());
+          HandleEventAndUpdateState(
+              new Event(kSbEventTypeConceal, timestamp, NULL, NULL));
+          HandleEventAndUpdateState(scoped_event.release());
           return true;
         case kStateConcealed:
           OnSuspend();
@@ -383,13 +393,24 @@
         case kStateUnstarted:
           return true;
         case kStateStarted:
-          Inject(new Event(kSbEventTypeBlur, timestamp, NULL, NULL));
+          HandleEventAndUpdateState(
+              new Event(kSbEventTypeBlur, timestamp, NULL, NULL));
         // The fall-through is intentional.
         case kStateBlurred:
-          Inject(new Event(kSbEventTypeConceal, timestamp, NULL, NULL));
+          HandleEventAndUpdateState(
+              new Event(kSbEventTypeConceal, timestamp, NULL, NULL));
         // The fall-through is intentional.
         case kStateConcealed:
-          Inject(new Event(kSbEventTypeFreeze, timestamp, NULL, NULL));
+          HandleEventAndUpdateState(
+              new Event(kSbEventTypeFreeze, timestamp, NULL, NULL));
+          // There is a race condition with kSbEventTypeStop processing and
+          // timed events currently in use. Processing the intermediate events
+          // takes time, so makes it more likely that a timed event will be due
+          // immediately and processed immediately afterward. The event(s) need
+          // to be fixed to behave better after kSbEventTypeStop has been
+          // handled. In the meantime, continue to use Inject() to preserve the
+          // current timing. This bug can still happen with Inject(), but it is
+          // less likely than if HandleEventAndUpdateState() were used.
           Inject(scoped_event.release());
           return true;
         case kStateFrozen:
@@ -409,8 +430,6 @@
       break;
   }
 #else
-  // Ensure that we go through the the appropriate lifecycle events based on the
-  // current state.
   switch (scoped_event->event->type) {
     case kSbEventTypePreload:
       if (state() != kStateUnstarted) {
@@ -439,8 +458,8 @@
       }
 
       if (state() == kStateSuspended) {
-        Inject(new Event(kSbEventTypeResume, NULL, NULL));
-        Inject(scoped_event.release());
+        HandleEventAndUpdateState(new Event(kSbEventTypeResume, NULL, NULL));
+        HandleEventAndUpdateState(scoped_event.release());
         return true;
       }
       break;
@@ -454,8 +473,8 @@
       }
 
       if (state() == kStateStarted) {
-        Inject(new Event(kSbEventTypePause, NULL, NULL));
-        Inject(scoped_event.release());
+        HandleEventAndUpdateState(new Event(kSbEventTypePause, NULL, NULL));
+        HandleEventAndUpdateState(scoped_event.release());
         return true;
       }
       break;
@@ -468,15 +487,23 @@
       }
       break;
     case kSbEventTypeStop:
+      // There is a race condition with kSbEventTypeStop processing and
+      // timed events currently in use. Processing the intermediate events
+      // takes time, so makes it more likely that a timed event will be due
+      // immediately and processed immediately afterward. The event(s) need
+      // to be fixed to behave better after kSbEventTypeStop has been
+      // handled. In the meantime, continue to use Inject() to preserve the
+      // current timing. This bug can still happen with Inject(), but it is
+      // less likely than if HandleEventAndUpdateState() were used.
       if (state() == kStateStarted) {
-        Inject(new Event(kSbEventTypePause, NULL, NULL));
-        Inject(new Event(kSbEventTypeSuspend, NULL, NULL));
+        HandleEventAndUpdateState(new Event(kSbEventTypePause, NULL, NULL));
+        HandleEventAndUpdateState(new Event(kSbEventTypeSuspend, NULL, NULL));
         Inject(scoped_event.release());
         return true;
       }
 
       if (state() == kStatePaused || state() == kStatePreloading) {
-        Inject(new Event(kSbEventTypeSuspend, NULL, NULL));
+        HandleEventAndUpdateState(new Event(kSbEventTypeSuspend, NULL, NULL));
         Inject(scoped_event.release());
         return true;
       }
@@ -493,6 +520,13 @@
   }
 #endif  // SB_API_VERSION >= 13
 
+  return HandleEventAndUpdateState(scoped_event.release());
+}
+
+bool Application::HandleEventAndUpdateState(Application::Event* event) {
+  // Ensure the event is deleted unless it is released.
+  scoped_ptr<Event> scoped_event(event);
+
   SbEventHandle(scoped_event->event);
 #if SB_API_VERSION >= 13
   switch (scoped_event->event->type) {
diff --git a/starboard/shared/starboard/application.h b/starboard/shared/starboard/application.h
index eaa8f6c..04bc51c 100644
--- a/starboard/shared/starboard/application.h
+++ b/starboard/shared/starboard/application.h
@@ -523,6 +523,10 @@
   // Internal workhorse of the main run loop.
   int RunLoop();
 
+  // Do the actual processing of an event. This should only be called by
+  // DispatchAndDelete().
+  bool HandleEventAndUpdateState(Application::Event* event);
+
   // The single application instance.
   static Application* g_instance;
 
diff --git a/starboard/shared/starboard/media/codec_util.cc b/starboard/shared/starboard/media/codec_util.cc
index bd3e8ba..cb331b4 100644
--- a/starboard/shared/starboard/media/codec_util.cc
+++ b/starboard/shared/starboard/media/codec_util.cc
@@ -28,549 +28,6 @@
 namespace starboard {
 namespace media {
 
-namespace {
-
-int hex_to_int(char hex) {
-  if (hex >= '0' && hex <= '9') {
-    return hex - '0';
-  }
-  if (hex >= 'A' && hex <= 'F') {
-    return hex - 'A' + 10;
-  }
-  if (hex >= 'a' && hex <= 'f') {
-    return hex - 'a' + 10;
-  }
-  SB_NOTREACHED();
-  return 0;
-}
-
-// Read one digit hex from |str| and store into |output|. Return false if
-// the character is not a digit, return true otherwise.
-template <typename T>
-bool ReadOneDigitHex(const char* str, T* output) {
-  SB_DCHECK(str);
-
-  if (str[0] >= 'A' && str[0] <= 'F') {
-    *output = static_cast<T>((str[0] - 'A' + 10));
-    return true;
-  }
-  if (str[0] >= 'a' && str[0] <= 'f') {
-    *output = static_cast<T>((str[0] - 'a' + 10));
-    return true;
-  }
-
-  if (!isdigit(str[0])) {
-    return false;
-  }
-
-  *output = static_cast<T>((str[0] - '0'));
-  return true;
-}
-
-// Read multi digit decimal from |str| until the next '.' character or end of
-// string, and store into |output|. Return false if one of the character is not
-// a digit, return true otherwise.
-template <typename T>
-bool ReadDecimalUntilDot(const char* str, T* output) {
-  SB_DCHECK(str);
-
-  *output = 0;
-  while (*str != 0 && *str != '.') {
-    if (!isdigit(*str)) {
-      return false;
-    }
-    *output = *output * 10 + (*str - '0');
-    ++str;
-  }
-
-  return true;
-}
-
-// Read two digit decimal from |str| and store into |output|. Return false if
-// any character is not a digit, return true otherwise.
-template <typename T>
-bool ReadTwoDigitDecimal(const char* str, T* output) {
-  SB_DCHECK(str);
-
-  if (!isdigit(str[0]) || !isdigit(str[1])) {
-    return false;
-  }
-
-  *output = static_cast<T>((str[0] - '0') * 10 + (str[1] - '0'));
-  return true;
-}
-
-// Verify the format against a reference using the following rules:
-// 1. They must have the same size.
-// 2. If reference[i] is a letter, format[i] must contain the *same* letter.
-// 3. If reference[i] is a number, format[i] can contain *any* number.
-// 4. If reference[i] is '.', format[i] must also be '.'.
-// 5. If reference[i] is ?, format[i] can contain *any* character.
-// 6. If |format| is longer than |reference|, then |format[reference_size]| can
-//    not be '.' or digit.
-// For example, both "av01.0.05M.08" and "av01.0.05M.08****" match reference
-// "av01.0.05?.08", but "vp09.0.05M.08" or "vp09.0.05M.08." don't.
-// The function returns true when |format| matches |reference|.
-bool VerifyFormat(const char* format, const char* reference) {
-  auto format_size = strlen(format);
-  auto reference_size = strlen(reference);
-  if (format_size < reference_size) {
-    return false;
-  }
-  for (size_t i = 0; i < reference_size; ++i) {
-    if (isdigit(reference[i])) {
-      if (!isdigit(format[i])) {
-        return false;
-      }
-    } else if (std::isalpha(reference[i])) {
-      if (reference[i] != format[i]) {
-        return false;
-      }
-    } else if (reference[i] == '.') {
-      if (format[i] != '.') {
-        return false;
-      }
-    } else if (reference[i] != '?') {
-      return false;
-    }
-  }
-  if (format_size == reference_size) {
-    return true;
-  }
-  return format[reference_size] != '.' && !isdigit(format[reference_size]);
-}
-
-// It works exactly the same as the above function, except that the size of
-// |format| has to be exactly the same as the size of |reference|.
-bool VerifyFormatStrictly(const char* format, const char* reference) {
-  if (strlen(format) != strlen(reference)) {
-    return false;
-  }
-  return VerifyFormat(format, reference);
-}
-
-// This function parses an av01 codec in the form of "av01.0.05M.08" or
-// "av01.0.04M.10.0.110.09.16.09.0" as
-// specified by https://aomediacodec.github.io/av1-isobmff/#codecsparam.
-//
-// Note that the spec also supports of different chroma subsamplings but the
-// implementation always assume that it is 4:2:0 and returns false when it
-// isn't.
-bool ParseAv1Info(std::string codec,
-                  int* profile,
-                  int* level,
-                  int* bit_depth,
-                  SbMediaPrimaryId* primary_id,
-                  SbMediaTransferId* transfer_id,
-                  SbMediaMatrixId* matrix_id) {
-  // The codec can only in one of the following formats:
-  //   Full: av01.0.04M.10.0.110.09.16.09.0
-  //   Short: av01.0.05M.08
-  // When short format is used, it is assumed that the omitted parts are
-  // "0.110.01.01.01.0".
-  // All fields are fixed size and leading zero cannot be omitted, so the
-  // expected sizes are known.
-  const char kShortFormReference[] = "av01.0.05M.08";
-  const char kLongFormReference[] = "av01.0.04M.10.0.110.09.16.09.0";
-  const size_t kShortFormSize = strlen(kShortFormReference);
-  const size_t kLongFormSize = strlen(kLongFormReference);
-
-  // 1. Sanity check the format.
-  if (strncmp(codec.c_str(), "av01.", 5) != 0) {
-    return false;
-  }
-  if (VerifyFormat(codec.c_str(), kLongFormReference)) {
-    codec.resize(kLongFormSize);
-  } else if (VerifyFormat(codec.c_str(), kShortFormReference)) {
-    codec.resize(kShortFormSize);
-  } else {
-    return false;
-  }
-
-  // 2. Parse profile, which can only be 0, 1, or 2.
-  if (codec[5] < '0' || codec[5] > '2') {
-    return false;
-  }
-  *profile = codec[5] - '0';
-
-  // 3. Parse level, which is two digit value from 0 to 23, maps to level 2.0,
-  //    2.1, 2.2, 2.3, 3.0, 3.1, 3.2, 3.3, ..., 7.0, 7.1, 7.2, 7.3.
-  int level_value;
-  if (!ReadTwoDigitDecimal(codec.c_str() + 7, &level_value)) {
-    return false;
-  }
-
-  if (level_value > 23) {
-    return false;
-  }
-  // Level x.y is represented by integer |xy|, for example, 23 means level 2.3.
-  *level = (level_value / 4 + 2) * 10 + (level_value % 4);
-
-  // 4. Parse tier, which can only be 'M' or 'H'
-  if (codec[9] != 'M' && codec[9] != 'H') {
-    return false;
-  }
-
-  // 5. Parse bit depth, which can be value 08, 10, or 12.
-  if (!ReadTwoDigitDecimal(codec.c_str() + 11, bit_depth)) {
-    return false;
-  }
-  if (*bit_depth != 8 && *bit_depth != 10 && *bit_depth != 12) {
-    return false;
-  }
-
-  // 6. Return now if it is a well-formed short form codec string.
-  *primary_id = kSbMediaPrimaryIdBt709;
-  *transfer_id = kSbMediaTransferIdBt709;
-  *matrix_id = kSbMediaMatrixIdBt709;
-
-  if (codec.size() == kShortFormSize) {
-    return true;
-  }
-
-  // 7. Parse monochrome, which can only be 0 or 1.
-  // Note that this value is not returned.
-  if (codec[14] != '0' && codec[14] != '1') {
-    return false;
-  }
-
-  // 8. Parse chroma subsampling, which we only support 110.
-  // Note that this value is not returned.
-  if (strncmp(codec.c_str() + 16, "110", 3) != 0) {
-    return false;
-  }
-
-  // 9. Parse color primaries, which can be 1 to 12, and 22 (EBU Tech. 3213-E).
-  //    Note that 22 is not currently supported by Cobalt.
-  if (!ReadTwoDigitDecimal(codec.c_str() + 20, primary_id)) {
-    return false;
-  }
-  SB_LOG_IF(WARNING, *primary_id == 22)
-      << codec << " uses primary id 22 (EBU Tech. 3213-E)."
-      << " It is rejected because Cobalt doesn't support primary id 22.";
-  if (*primary_id < 1 || *primary_id > 12) {
-    return false;
-  }
-
-  // 10. Parse transfer characteristics, which can be 0 to 18.
-  if (!ReadTwoDigitDecimal(codec.c_str() + 23, transfer_id)) {
-    return false;
-  }
-  if (*transfer_id > 18) {
-    return false;
-  }
-
-  // 11. Parse matrix coefficients, which can be 0 to 14.
-  //     Note that 12, 13, and 14 are not currently supported by Cobalt.
-  if (!ReadTwoDigitDecimal(codec.c_str() + 26, matrix_id)) {
-    return false;
-  }
-  if (*matrix_id > 11) {
-    return false;
-  }
-
-  // 12. Parse color range, which can only be 0 or 1.
-  if (codec[29] != '0' && codec[29] != '1') {
-    return false;
-  }
-
-  // 13. Return
-  return true;
-}
-
-// This function parses an h264 codec in the form of {avc1|avc3}.PPCCLL as
-// specified by https://tools.ietf.org/html/rfc6381#section-3.3.
-//
-// Note that the leading codec is not necessarily to be "avc1" or "avc3" per
-// spec but this function only parses "avc1" and "avc3".  This function returns
-// false when |codec| doesn't contain a valid codec string.
-bool ParseH264Info(const char* codec, int* profile, int* level) {
-  if (strncmp(codec, "avc1.", 5) != 0 && strncmp(codec, "avc3.", 5) != 0) {
-    return false;
-  }
-
-  if (strlen(codec) != 11 || !isxdigit(codec[9]) || !isxdigit(codec[10])) {
-    return false;
-  }
-
-  *profile = hex_to_int(codec[5]) * 16 + hex_to_int(codec[6]);
-  *level = hex_to_int(codec[9]) * 16 + hex_to_int(codec[10]);
-  return true;
-}
-
-// This function parses an h265 codec as specified by ISO IEC 14496-15 dated
-// 2012 or newer in the Annex E.3.  The codec will be in the form of:
-//   hvc1.PPP.PCPCPCPC.TLLL.CB.CB.CB.CB.CB.CB, where
-// PPP: 0 or 1 byte general_profile_space ('', 'A', 'B', or 'C') +
-//        up to two bytes profile idc.
-// PCPCPCPC: Profile compatibility, up to 32 bits hex, with leading 0 omitted.
-// TLLL: One byte tier ('L' or 'H') + up to three bytes level.
-// CB: Up to 6 constraint bytes, separated by '.'.
-// Note that the above level in decimal = 30 * real level, i.e. 93 means level
-// 3.1, 120 mean level 4.
-// Please see the comment in the code for interactions between the various
-// parts.
-bool ParseH265Info(const char* codec, int* profile, int* level) {
-  if (strncmp(codec, "hev1.", 5) != 0 && strncmp(codec, "hvc1.", 5) != 0) {
-    return false;
-  }
-
-  codec += 5;
-
-  // Read profile space
-  if (codec[0] == 'A' || codec[0] == 'B' || codec[0] == 'C') {
-    ++codec;
-  }
-
-  if (strlen(codec) < 3) {
-    return false;
-  }
-
-  // Read profile
-  int general_profile_idc;
-  if (codec[1] == '.') {
-    if (!ReadDecimalUntilDot(codec, &general_profile_idc)) {
-      return false;
-    }
-    codec += 2;
-  } else if (codec[2] == '.') {
-    if (!ReadDecimalUntilDot(codec, &general_profile_idc)) {
-      return false;
-    }
-    codec += 3;
-  } else {
-    return false;
-  }
-
-  // Read profile compatibility, up to 32 bits hex.
-  const char* dot = strchr(codec, '.');
-  if (dot == NULL || dot - codec == 0 || dot - codec > 8) {
-    return false;
-  }
-
-  uint32_t general_profile_compatibility_flags = 0;
-  for (int i = 0; i < 9; ++i) {
-    if (codec[0] == '.') {
-      ++codec;
-      break;
-    }
-    uint32_t hex = 0;
-    if (!ReadOneDigitHex(codec, &hex)) {
-      return false;
-    }
-    general_profile_compatibility_flags *= 16;
-    general_profile_compatibility_flags += hex;
-    ++codec;
-  }
-
-  *profile = -1;
-  if (general_profile_idc == 3 || (general_profile_compatibility_flags & 4)) {
-    *profile = 3;
-  }
-  if (general_profile_idc == 2 || (general_profile_compatibility_flags & 2)) {
-    *profile = 2;
-  }
-  if (general_profile_idc == 1 || (general_profile_compatibility_flags & 1)) {
-    *profile = 1;
-  }
-  if (*profile == -1) {
-    return false;
-  }
-
-  // Parse tier
-  if (codec[0] != 'L' && codec[0] != 'H') {
-    return false;
-  }
-  ++codec;
-
-  // Parse level in 2 or 3 digits decimal.
-  if (strlen(codec) < 2) {
-    return false;
-  }
-  if (!ReadDecimalUntilDot(codec, level)) {
-    return false;
-  }
-  if (*level % 3 != 0) {
-    return false;
-  }
-  *level /= 3;
-  if (codec[2] == 0 || codec[2] == '.') {
-    codec += 2;
-  } else if (codec[3] == 0 || codec[3] == '.') {
-    codec += 3;
-  } else {
-    return false;
-  }
-
-  // Parse up to 6 constraint flags in the form of ".HH".
-  for (int i = 0; i < 6; ++i) {
-    if (codec[0] == 0) {
-      return true;
-    }
-    if (codec[0] != '.' || !isxdigit(codec[1]) || !isxdigit(codec[2])) {
-      return false;
-    }
-    codec += 3;
-  }
-
-  return *codec == 0;
-}
-
-// This function parses an vp09 codec in the form of "vp09.00.41.08" or
-// "vp09.02.10.10.01.09.16.09.01" as specified by
-// https://www.webmproject.org/vp9/mp4/.  YouTube also uses the long form
-// without the last part (color range), so we also support it.
-//
-// Note that the spec also supports of different chroma subsamplings but the
-// implementation always assume that it is 4:2:0 and returns false when it
-// isn't.
-bool ParseVp09Info(const char* codec,
-                   int* profile,
-                   int* level,
-                   int* bit_depth,
-                   SbMediaPrimaryId* primary_id,
-                   SbMediaTransferId* transfer_id,
-                   SbMediaMatrixId* matrix_id) {
-  // The codec can only in one of the following formats:
-  //   Full: vp09.02.10.10.01.09.16.09.01
-  //   Short: vp09.00.41.08
-  // Note that currently the player also uses the following form:
-  //   Medium: vp09.02.10.10.01.09.16.09
-  // When short format is used, it is assumed that the omitted parts are
-  // "01.01.01.01.00".  When medium format is used, the omitted part is "00".
-  // All fields are fixed size and leading zero cannot be omitted, so the
-  // expected sizes are known.
-  const char kShortFormReference[] = "vp09.00.41.08";
-  const char kMediumFormReference[] = "vp09.02.10.10.01.09.16.09";
-  const char kLongFormReference[] = "vp09.02.10.10.01.09.16.09.01";
-  const size_t kShortFormSize = strlen(kShortFormReference);
-  const size_t kMediumFormSize = strlen(kMediumFormReference);
-  const size_t kLongFormSize = strlen(kLongFormReference);
-
-  // 1. Sanity check the format.
-  if (strncmp(codec, "vp09.", 5) != 0) {
-    return false;
-  }
-  if (!VerifyFormatStrictly(codec, kLongFormReference) &&
-      !VerifyFormatStrictly(codec, kMediumFormReference) &&
-      !VerifyFormatStrictly(codec, kShortFormReference)) {
-    return false;
-  }
-
-  // 2. Parse profile, which can only be 00, 01, 02, or 03.
-  if (!ReadTwoDigitDecimal(codec + 5, profile)) {
-    return false;
-  }
-  if (*profile < 0 || *profile > 3) {
-    return false;
-  }
-
-  // 3. Parse level, which is two digit value in the following list:
-  const int kLevels[] = {10, 11, 20, 21, 30, 31, 40,
-                         41, 50, 51, 52, 60, 61, 62};
-  if (!ReadTwoDigitDecimal(codec + 8, level)) {
-    return false;
-  }
-  auto end = kLevels + SB_ARRAY_SIZE(kLevels);
-  if (std::find(kLevels, end, *level) == end) {
-    return false;
-  }
-
-  // 4. Parse bit depth, which can be value 08, 10, or 12.
-  if (!ReadTwoDigitDecimal(codec + 11, bit_depth)) {
-    return false;
-  }
-  if (*bit_depth != 8 && *bit_depth != 10 && *bit_depth != 12) {
-    return false;
-  }
-
-  // 5. Return now if it is a well-formed short form codec string.
-  *primary_id = kSbMediaPrimaryIdBt709;
-  *transfer_id = kSbMediaTransferIdBt709;
-  *matrix_id = kSbMediaMatrixIdBt709;
-
-  if (strlen(codec) == kShortFormSize) {
-    return true;
-  }
-
-  // 6. Parse chroma subsampling, which we only support 00 and 01.
-  // Note that this value is not returned.
-  int chroma;
-  if (!ReadTwoDigitDecimal(codec + 14, &chroma) ||
-      (chroma != 0 && chroma != 1)) {
-    return false;
-  }
-
-  // 7. Parse color primaries, which can be 1 to 12, and 22 (EBU Tech. 3213-E).
-  //    Note that 22 is not currently supported by Cobalt.
-  if (!ReadTwoDigitDecimal(codec + 17, primary_id)) {
-    return false;
-  }
-  if (*primary_id < 1 || *primary_id > 12) {
-    return false;
-  }
-
-  // 8. Parse transfer characteristics, which can be 0 to 18.
-  if (!ReadTwoDigitDecimal(codec + 20, transfer_id)) {
-    return false;
-  }
-  if (*transfer_id > 18) {
-    return false;
-  }
-
-  // 9. Parse matrix coefficients, which can be 0 to 14.
-  //     Note that 12, 13, and 14 are not currently supported by Cobalt.
-  if (!ReadTwoDigitDecimal(codec + 23, matrix_id)) {
-    return false;
-  }
-  if (*matrix_id > 11) {
-    return false;
-  }
-
-  // 10. Return now if it is a well-formed medium form codec string.
-  if (strlen(codec) == kMediumFormSize) {
-    return true;
-  }
-
-  // 11. Parse color range, which can only be 0 or 1.
-  int color_range;
-  if (!ReadTwoDigitDecimal(codec + 26, &color_range)) {
-    return false;
-  }
-  if (color_range != 0 && color_range != 1) {
-    return false;
-  }
-
-  // 12. Return
-  return true;
-}
-
-// This function parses an vp9 codec in the form of "vp9", "vp9.0", "vp9.1", or
-// "vp9.2".
-bool ParseVp9Info(const char* codec, int* profile) {
-  SB_DCHECK(profile);
-
-  if (strcmp(codec, "vp9") == 0) {
-    *profile = -1;
-    return true;
-  }
-  if (strcmp(codec, "vp9.0") == 0) {
-    *profile = 0;
-    return true;
-  }
-  if (strcmp(codec, "vp9.1") == 0) {
-    *profile = 1;
-    return true;
-  }
-  if (strcmp(codec, "vp9.2") == 0) {
-    *profile = 2;
-    return true;
-  }
-  return false;
-}
-
-}  // namespace
-
 VideoConfig::VideoConfig(SbMediaVideoCodec video_codec,
                          int width,
                          int height,
@@ -629,72 +86,15 @@
       return kSbMediaAudioCodecEac3;
     }
   }
-  if (strncmp(codec, "opus", 4) == 0) {
+  if (strcmp(codec, "opus") == 0) {
     return kSbMediaAudioCodecOpus;
   }
-  if (strncmp(codec, "vorbis", 6) == 0) {
+  if (strcmp(codec, "vorbis") == 0) {
     return kSbMediaAudioCodecVorbis;
   }
   return kSbMediaAudioCodecNone;
 }
 
-bool ParseVideoCodec(const char* codec_string,
-                     SbMediaVideoCodec* codec,
-                     int* profile,
-                     int* level,
-                     int* bit_depth,
-                     SbMediaPrimaryId* primary_id,
-                     SbMediaTransferId* transfer_id,
-                     SbMediaMatrixId* matrix_id) {
-  SB_DCHECK(codec_string);
-  SB_DCHECK(codec);
-  SB_DCHECK(profile);
-  SB_DCHECK(level);
-  SB_DCHECK(bit_depth);
-  SB_DCHECK(primary_id);
-  SB_DCHECK(transfer_id);
-  SB_DCHECK(matrix_id);
-
-  *codec = kSbMediaVideoCodecNone;
-  *profile = -1;
-  *level = -1;
-  *bit_depth = 8;
-  *primary_id = kSbMediaPrimaryIdUnspecified;
-  *transfer_id = kSbMediaTransferIdUnspecified;
-  *matrix_id = kSbMediaMatrixIdUnspecified;
-
-  if (strncmp(codec_string, "av01.", 5) == 0) {
-    *codec = kSbMediaVideoCodecAv1;
-    return ParseAv1Info(codec_string, profile, level, bit_depth, primary_id,
-                        transfer_id, matrix_id);
-  }
-  if (strncmp(codec_string, "avc1.", 5) == 0 ||
-      strncmp(codec_string, "avc3.", 5) == 0) {
-    *codec = kSbMediaVideoCodecH264;
-    return ParseH264Info(codec_string, profile, level);
-  }
-  if (strncmp(codec_string, "hev1.", 5) == 0 ||
-      strncmp(codec_string, "hvc1.", 5) == 0) {
-    *codec = kSbMediaVideoCodecH265;
-    return ParseH265Info(codec_string, profile, level);
-  }
-  if (strncmp(codec_string, "vp09.", 5) == 0) {
-    *codec = kSbMediaVideoCodecVp9;
-    return ParseVp09Info(codec_string, profile, level, bit_depth, primary_id,
-                         transfer_id, matrix_id);
-  }
-  if (strncmp(codec_string, "vp8", 3) == 0) {
-    *codec = kSbMediaVideoCodecVp8;
-    return true;
-  }
-  if (strncmp(codec_string, "vp9", 3) == 0) {
-    *codec = kSbMediaVideoCodecVp9;
-    return ParseVp9Info(codec_string, profile);
-  }
-
-  return false;
-}
-
 }  // namespace media
 }  // namespace starboard
 }  // namespace shared
diff --git a/starboard/shared/starboard/media/codec_util.h b/starboard/shared/starboard/media/codec_util.h
index eefbc1a..fed5710 100644
--- a/starboard/shared/starboard/media/codec_util.h
+++ b/starboard/shared/starboard/media/codec_util.h
@@ -65,26 +65,6 @@
 
 SbMediaAudioCodec GetAudioCodecFromString(const char* codec);
 
-// This function parses the video codec string and returns a codec.  All fields
-// will be filled with information parsed from the codec string when possible,
-// otherwise they will have the following default values:
-//            profile: -1
-//              level: -1
-//          bit_depth: 8
-//         primary_id: kSbMediaPrimaryIdUnspecified
-//        transfer_id: kSbMediaTransferIdUnspecified
-//          matrix_id: kSbMediaMatrixIdUnspecified
-// It returns true when |codec| contains a well-formed codec string, otherwise
-// it returns false.
-bool ParseVideoCodec(const char* codec_string,
-                     SbMediaVideoCodec* codec,
-                     int* profile,
-                     int* level,
-                     int* bit_depth,
-                     SbMediaPrimaryId* primary_id,
-                     SbMediaTransferId* transfer_id,
-                     SbMediaMatrixId* matrix_id);
-
 }  // namespace media
 }  // namespace starboard
 }  // namespace shared
diff --git a/starboard/shared/starboard/media/codec_util_test.cc b/starboard/shared/starboard/media/codec_util_test.cc
index 545c748..a319697 100644
--- a/starboard/shared/starboard/media/codec_util_test.cc
+++ b/starboard/shared/starboard/media/codec_util_test.cc
@@ -214,175 +214,6 @@
   EXPECT_FALSE(config_h264 == config_vp9);
 }
 
-class ParseVideoCodecTest : public ::testing::Test {
- protected:
-  bool Parse(const char* codec_string) {
-    return ParseVideoCodec(codec_string, &codec_, &profile_, &level_,
-                           &bit_depth_, &color_primary_id_, &transfer_id_,
-                           &matrix_id_);
-  }
-
-  SbMediaVideoCodec codec_;
-  int profile_;
-  int level_;
-  int bit_depth_;
-  SbMediaPrimaryId color_primary_id_;
-  SbMediaTransferId transfer_id_;
-  SbMediaMatrixId matrix_id_;
-};
-
-TEST_F(ParseVideoCodecTest, SimpleCodecs) {
-  const char* kCodecStrings[] = {"vp8", "vp9"};
-  const SbMediaVideoCodec kVideoCodecs[] = {kSbMediaVideoCodecVp8,
-                                            kSbMediaVideoCodecVp9};
-  for (size_t i = 0; i < SB_ARRAY_SIZE(kCodecStrings); ++i) {
-    ASSERT_TRUE(Parse(kCodecStrings[i]));
-    EXPECT_EQ(codec_, kVideoCodecs[i]);
-    EXPECT_EQ(profile_, -1);
-    EXPECT_EQ(level_, -1);
-    EXPECT_EQ(bit_depth_, 8);
-    EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdUnspecified);
-    EXPECT_EQ(transfer_id_, kSbMediaTransferIdUnspecified);
-    EXPECT_EQ(matrix_id_, kSbMediaMatrixIdUnspecified);
-  }
-}
-
-TEST_F(ParseVideoCodecTest, ShortFormAv1) {
-  ASSERT_TRUE(Parse("av01.0.01M.08"));
-  EXPECT_EQ(codec_, kSbMediaVideoCodecAv1);
-  EXPECT_EQ(profile_, 0);
-  EXPECT_EQ(level_, 21);
-  EXPECT_EQ(bit_depth_, 8);
-  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdBt709);
-  EXPECT_EQ(transfer_id_, kSbMediaTransferIdBt709);
-  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdBt709);
-}
-
-TEST_F(ParseVideoCodecTest, LongFormAv1) {
-  ASSERT_TRUE(Parse("av01.0.04M.10.0.110.09.16.09.0"));
-  EXPECT_EQ(codec_, kSbMediaVideoCodecAv1);
-  EXPECT_EQ(profile_, 0);
-  EXPECT_EQ(level_, 30);
-  EXPECT_EQ(bit_depth_, 10);
-  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdBt2020);
-  EXPECT_EQ(transfer_id_, kSbMediaTransferIdSmpteSt2084);
-  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdBt2020NonconstantLuminance);
-}
-
-TEST_F(ParseVideoCodecTest, InvalidAv1) {
-  EXPECT_FALSE(Parse("av01.0.04M.10.0.110.9.16.9.0"));
-  EXPECT_FALSE(Parse("av01.0.04M.10.0.110.09.16.09"));
-  EXPECT_FALSE(Parse("av01.0.04M.10.0.110.09.16"));
-  EXPECT_FALSE(Parse("av01.0.04M.10.0.110.09"));
-  EXPECT_FALSE(Parse("av01.0.04M.10.0.110"));
-  EXPECT_FALSE(Parse("av01.0.04M.10.0"));
-  EXPECT_FALSE(Parse("av01.0.04M"));
-  EXPECT_FALSE(Parse("av01.0"));
-  EXPECT_FALSE(Parse("av01"));
-
-  EXPECT_FALSE(Parse("av02.0.04M.10.0.110.09.16.09.0"));
-  EXPECT_FALSE(Parse("av01.0.04X.10.0.110.09.16.09.0"));
-  EXPECT_FALSE(Parse("av01.0.04M.10.0.110.09.16.09.2"));
-}
-
-TEST_F(ParseVideoCodecTest, Avc) {
-  ASSERT_TRUE(Parse("avc1.640028"));
-  EXPECT_EQ(codec_, kSbMediaVideoCodecH264);
-  EXPECT_EQ(profile_, 100);
-  EXPECT_EQ(level_, 40);
-  EXPECT_EQ(bit_depth_, 8);
-  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdUnspecified);
-  EXPECT_EQ(transfer_id_, kSbMediaTransferIdUnspecified);
-  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdUnspecified);
-
-  ASSERT_TRUE(Parse("avc3.640028"));
-  EXPECT_EQ(codec_, kSbMediaVideoCodecH264);
-  EXPECT_EQ(profile_, 100);
-  EXPECT_EQ(level_, 40);
-  EXPECT_EQ(bit_depth_, 8);
-  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdUnspecified);
-  EXPECT_EQ(transfer_id_, kSbMediaTransferIdUnspecified);
-  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdUnspecified);
-}
-
-TEST_F(ParseVideoCodecTest, InvalidAvc) {
-  EXPECT_FALSE(Parse("avc1.64002"));
-  EXPECT_FALSE(Parse("avc2.640028"));
-  EXPECT_FALSE(Parse("avc3.640028.1"));
-}
-
-TEST_F(ParseVideoCodecTest, H265) {
-  ASSERT_TRUE(Parse("hvc1.1.2.L93.B0"));
-  EXPECT_EQ(codec_, kSbMediaVideoCodecH265);
-  EXPECT_EQ(profile_, 1);
-  EXPECT_EQ(level_, 31);
-  ASSERT_TRUE(Parse("hev1.A4.41.H120.B0.12.34.56.78.90"));
-  EXPECT_EQ(codec_, kSbMediaVideoCodecH265);
-  EXPECT_EQ(profile_, 1);
-  EXPECT_EQ(level_, 40);
-
-  EXPECT_TRUE(Parse("hvc1.1.2.H93.B0"));
-  EXPECT_TRUE(Parse("hvc1.A1.2.H93.B0"));
-  EXPECT_TRUE(Parse("hvc1.B1.2.H93.B0"));
-  EXPECT_TRUE(Parse("hvc1.C1.2.H93.B0"));
-  EXPECT_TRUE(Parse("hvc1.C1.2.H93"));
-  EXPECT_TRUE(Parse("hvc1.C1.ABCDEF01.H93.B0"));
-}
-
-TEST_F(ParseVideoCodecTest, InvalidH265) {
-  EXPECT_FALSE(Parse("hvc2.1.2.L93.B0"));
-  EXPECT_FALSE(Parse("hvc1.D1.2.L93.B0"));
-  EXPECT_FALSE(Parse("hvc1.A111.2.L93.B0"));
-  EXPECT_FALSE(Parse("hvc1.111.2.L93.B0"));
-  EXPECT_FALSE(Parse("hvc1.1.ABCDEF012.L93.B0"));
-  EXPECT_FALSE(Parse("hvc1.1.2.L92.B0"));
-  EXPECT_FALSE(Parse("hvc1.1.2.P93.B0"));
-  EXPECT_FALSE(Parse("hvc1.1.2.L93.B0.B1.B2.B3.B4.B5.B6"));
-}
-
-TEST_F(ParseVideoCodecTest, ShortFormVp9) {
-  ASSERT_TRUE(Parse("vp09.00.41.08"));
-  EXPECT_EQ(codec_, kSbMediaVideoCodecVp9);
-  EXPECT_EQ(profile_, 0);
-  EXPECT_EQ(level_, 41);
-  EXPECT_EQ(bit_depth_, 8);
-  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdBt709);
-  EXPECT_EQ(transfer_id_, kSbMediaTransferIdBt709);
-  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdBt709);
-}
-
-TEST_F(ParseVideoCodecTest, MediumFormVp9) {
-  ASSERT_TRUE(Parse("vp09.02.10.10.01.09.16.09"));
-  EXPECT_EQ(codec_, kSbMediaVideoCodecVp9);
-  EXPECT_EQ(profile_, 2);
-  EXPECT_EQ(level_, 10);
-  EXPECT_EQ(bit_depth_, 10);
-  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdBt2020);
-  EXPECT_EQ(transfer_id_, kSbMediaTransferIdSmpteSt2084);
-  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdBt2020NonconstantLuminance);
-}
-
-TEST_F(ParseVideoCodecTest, LongFormVp9) {
-  ASSERT_TRUE(Parse("vp09.02.10.10.01.09.16.09.01"));
-  EXPECT_EQ(codec_, kSbMediaVideoCodecVp9);
-  EXPECT_EQ(profile_, 2);
-  EXPECT_EQ(level_, 10);
-  EXPECT_EQ(bit_depth_, 10);
-  EXPECT_EQ(color_primary_id_, kSbMediaPrimaryIdBt2020);
-  EXPECT_EQ(transfer_id_, kSbMediaTransferIdSmpteSt2084);
-  EXPECT_EQ(matrix_id_, kSbMediaMatrixIdBt2020NonconstantLuminance);
-}
-
-TEST_F(ParseVideoCodecTest, InvalidVp9) {
-  EXPECT_FALSE(Parse("vp09.02.10.10.01.9.16.9"));
-  EXPECT_FALSE(Parse("vp09.02.10.10.01.09.16"));
-  EXPECT_FALSE(Parse("vp09.02.10.10.01.09"));
-  EXPECT_FALSE(Parse("vp09.02.10.10.01"));
-  EXPECT_FALSE(Parse("vp09.02.10"));
-  EXPECT_FALSE(Parse("vp09.02"));
-  EXPECT_FALSE(Parse("vp09"));
-}
-
 }  // namespace
 }  // namespace media
 }  // namespace starboard
diff --git a/starboard/shared/starboard/media/media_get_buffer_alignment.cc b/starboard/shared/starboard/media/media_get_buffer_alignment.cc
index 282c482..5f7e50a 100644
--- a/starboard/shared/starboard/media/media_get_buffer_alignment.cc
+++ b/starboard/shared/starboard/media/media_get_buffer_alignment.cc
@@ -14,6 +14,10 @@
 
 #include "starboard/media.h"
 
+#if SB_API_VERSION >= 14
+int SbMediaGetBufferAlignment() {
+#else  // SB_API_VERSION >= 14
 int SbMediaGetBufferAlignment(SbMediaType type) {
+#endif  // SB_API_VERSION >= 14
   return 1;
 }
diff --git a/starboard/shared/starboard/media/media_get_buffer_padding.cc b/starboard/shared/starboard/media/media_get_buffer_padding.cc
index 6c4ead1..71d0d0e 100644
--- a/starboard/shared/starboard/media/media_get_buffer_padding.cc
+++ b/starboard/shared/starboard/media/media_get_buffer_padding.cc
@@ -14,6 +14,10 @@
 
 #include "starboard/media.h"
 
+#if SB_API_VERSION >= 14
+int SbMediaGetBufferPadding() {
+#else   // SB_API_VERSION >= 14
 int SbMediaGetBufferPadding(SbMediaType type) {
+#endif  // SB_API_VERSION >= 14
   return 0;
 }
diff --git a/starboard/shared/starboard/media/media_util.cc b/starboard/shared/starboard/media/media_util.cc
index 3081d34..40fe9b1 100644
--- a/starboard/shared/starboard/media/media_util.cc
+++ b/starboard/shared/starboard/media/media_util.cc
@@ -18,6 +18,7 @@
 
 #include "starboard/character.h"
 #include "starboard/common/log.h"
+#include "starboard/common/media.h"
 #include "starboard/common/string.h"
 #include "starboard/log.h"
 #include "starboard/memory.h"
diff --git a/starboard/shared/starboard/player/filter/testing/player_components_test.cc b/starboard/shared/starboard/player/filter/testing/player_components_test.cc
index 0e9ce1c..11f880a 100644
--- a/starboard/shared/starboard/player/filter/testing/player_components_test.cc
+++ b/starboard/shared/starboard/player/filter/testing/player_components_test.cc
@@ -28,7 +28,6 @@
 #include "starboard/shared/starboard/player/video_dmp_reader.h"
 #include "starboard/testing/fake_graphics_context_provider.h"
 #include "testing/gtest/include/gtest/gtest.h"
-
 namespace starboard {
 namespace shared {
 namespace starboard {
@@ -426,30 +425,16 @@
   scoped_refptr<InputBuffer> GetAudioInputBuffer(size_t index) const {
     auto player_sample_info =
         audio_reader_->GetPlayerSampleInfo(kSbMediaTypeAudio, index);
-#if SB_API_VERSION >= 11
     auto input_buffer = new InputBuffer(StubDeallocateSampleFunc, nullptr,
                                         nullptr, player_sample_info);
-#else   // SB_API_VERSION >= 11
-    SbMediaAudioSampleInfo audio_sample_info =
-        audio_reader_->GetAudioSampleInfo(index);
-    auto input_buffer =
-        new InputBuffer(kSbMediaTypeAudio, StubDeallocateSampleFunc, nullptr,
-                        nullptr, player_sample_info, &audio_sample_info);
-#endif  // SB_API_VERSION >= 11
     return input_buffer;
   }
 
   scoped_refptr<InputBuffer> GetVideoInputBuffer(size_t index) const {
     auto video_sample_info =
         video_reader_->GetPlayerSampleInfo(kSbMediaTypeVideo, index);
-#if SB_API_VERSION >= 11
     auto input_buffer = new InputBuffer(StubDeallocateSampleFunc, NULL, NULL,
                                         video_sample_info);
-#else   // SB_API_VERSION >= 11
-    auto input_buffer =
-        new InputBuffer(kSbMediaTypeVideo, StubDeallocateSampleFunc, NULL, NULL,
-                        video_sample_info, NULL);
-#endif  // SB_API_VERSION >= 11
     return input_buffer;
   }
 
@@ -733,7 +718,6 @@
 INSTANTIATE_TEST_CASE_P(PlayerComponentsTests,
                         PlayerComponentsTest,
                         ValuesIn(GetSupportedCreationParameters()));
-
 }  // namespace
 }  // namespace testing
 }  // namespace filter
diff --git a/starboard/shared/starboard/player/tools/compare_video_hashes.py b/starboard/shared/starboard/player/tools/compare_video_hashes.py
deleted file mode 100644
index 10c29ee..0000000
--- a/starboard/shared/starboard/player/tools/compare_video_hashes.py
+++ /dev/null
@@ -1,118 +0,0 @@
-# Copyright 2020 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Tool to verify video input hashes dump by --dump_video_input_hash."""
-
-import sys
-
-
-class VideoHashes(object):
-  """Manages the timestamp to frame hashes.
-
-     The timestamp to frame hashes are loaded from log file generated using
-     --dump_video_input_hash command line parameter.
-  """
-
-  def __init__(self, filename):
-    """Loads the timestamp to frame hashes dictionary from log file."""
-    # A typical log line looks like:
-    #   ... filter_based_player_worker_handler.cc(...)] Dump clear audio input \
-    #       hash @ 23219: 2952221532
-    with open(filename, 'r') as f:
-      lines = [
-          line for line in f.readlines() if line.find('input hash') != -1 and
-          line.find('filter_based_player_worker_handler') != -1
-      ]
-    is_audio_encrypted = False
-    is_video_encrypted = False
-    for line in lines:
-      if line.find('encrypted audio') != -1:
-        is_audio_encrypted = True
-        continue
-      if line.find('encrypted video') != -1:
-        is_video_encrypted = True
-        continue
-
-      tokens = line.split('Dump clear')
-      assert len(tokens) > 1
-
-      tokens = tokens[-1].split()
-      assert len(tokens) == 6
-
-      if tokens[0] == 'audio':
-        self._audio_timestamp_to_hashes[tokens[4]] = [tokens[5]]
-      else:
-        assert tokens[0] == 'video'
-        self._video_timestamp_to_hashes[tokens[4]] = [tokens[5]]
-
-    print '{} contains {} audio samples and {} video samples,'.format(
-        filename, len(self._audio_timestamp_to_hashes),
-        len(self._video_timestamp_to_hashes))
-    print '  its audio stream is {}, its video stream is {}.'.format(
-        'encrypted' if is_audio_encrypted else 'clear',
-        'encrypted' if is_video_encrypted else 'clear')
-
-  def AreHashesMatching(self, other):
-    """Compare if two objects match each other."""
-    self._AreHashesMatching('audio', self._audio_timestamp_to_hashes,
-                            other.GetAudioTimestampToHashes())
-    self._AreHashesMatching('video', self._video_timestamp_to_hashes,
-                            other.GetVideoTimestampToHashes())
-
-  def _AreHashesMatching(self, media_type, timestamp_to_hashes1,
-                         timestamp_to_hashes2):
-    """Compare if two timestamp to frame hashes dictionary match each other."""
-    hashes_to_check = min(len(timestamp_to_hashes1), len(timestamp_to_hashes2))
-
-    all_matches = True
-
-    for timestamp in timestamp_to_hashes1:
-      hashes_to_check = hashes_to_check - 1
-      if hashes_to_check < 0:
-        break
-
-      if timestamp not in timestamp_to_hashes2:
-        print media_type, 'frame @ timestamp', timestamp, ('in file 1 isn\'t in'
-                                                           ' file 2')
-        all_matches = False
-        continue
-      if timestamp_to_hashes1[timestamp] != timestamp_to_hashes2[timestamp]:
-        print media_type, 'frame @ timestamp', timestamp, 'hash mismatches'
-        all_matches = False
-        continue
-
-    if all_matches:
-      print 'All', media_type, 'inputs match'
-
-  def GetAudioTimestampToHashes(self):
-    return self._audio_timestamp_to_hashes
-
-  def GetVideoTimestampToHashes(self):
-    return self._video_timestamp_to_hashes
-
-  _audio_timestamp_to_hashes = {}
-  _video_timestamp_to_hashes = {}
-
-
-def CompareVideoHashes(filename1, filename2):
-  hashes1 = VideoHashes(filename1)
-  hashes2 = VideoHashes(filename2)
-
-  hashes1.AreHashesMatching(hashes2)
-
-
-if len(sys.argv) != 3:
-  print('USAGE: compare_video_timestamp_to_hashes.py <input_file_1>',
-        '<input_file_2>')
-else:
-  CompareVideoHashes(sys.argv[1], sys.argv[2])
diff --git a/starboard/shared/starboard/queue_application.cc b/starboard/shared/starboard/queue_application.cc
index e3111ba..08550a8 100644
--- a/starboard/shared/starboard/queue_application.cc
+++ b/starboard/shared/starboard/queue_application.cc
@@ -24,6 +24,14 @@
 namespace shared {
 namespace starboard {
 
+namespace {
+void SetTrueWhenCalled(void* flag) {
+  volatile bool* bool_flag =
+      const_cast<volatile bool*>(static_cast<bool*>(flag));
+  *bool_flag = true;
+}
+}  // namespace
+
 void QueueApplication::Wake() {
   if (IsCurrentThread()) {
     return;
@@ -90,6 +98,21 @@
   // and go back to sleep.
 }
 
+void QueueApplication::InjectAndProcess(SbEventType type,
+                                        bool checkSystemEvents) {
+  volatile bool event_processed = false;
+  Event* flagged_event =
+      new Event(type, const_cast<bool*>(&event_processed), &SetTrueWhenCalled);
+  Inject(flagged_event);
+  while (!event_processed) {
+    if (checkSystemEvents) {
+      DispatchAndDelete(GetNextEvent());
+    } else {
+      DispatchAndDelete(GetNextInjectedEvent());
+    }
+  }
+}
+
 Application::TimedEvent* QueueApplication::GetNextDueTimedEvent() {
   return timed_event_queue_.Get();
 }
diff --git a/starboard/shared/starboard/queue_application.h b/starboard/shared/starboard/queue_application.h
index fdf35f2..8f9ffb7 100644
--- a/starboard/shared/starboard/queue_application.h
+++ b/starboard/shared/starboard/queue_application.h
@@ -50,6 +50,14 @@
   TimedEvent* GetNextDueTimedEvent() override;
   SbTimeMonotonic GetNextTimedEventTargetTime() override;
 
+  // Add the given event onto the event queue, then process the queue until the
+  // event is handled. This is similar to DispatchAndDelete but will process
+  // the queue in order until the new event is handled rather than processing
+  // the event out of order. If the caller is part of system event handling,
+  // then consider passing |checkSystemEvents| = false to avoid recursion if
+  // needed.
+  void InjectAndProcess(SbEventType type, bool checkSystemEvents);
+
   // Returns true if it is valid to poll/query for system events.
   virtual bool MayHaveSystemEvents() = 0;
 
@@ -68,6 +76,12 @@
   virtual void WakeSystemEventWait() = 0;
 
  private:
+#if SB_API_VERSION >= 14
+  // Use Inject() or InjectAndProcess(). DispatchAndDelete() ignores the event
+  // queue and processes the event out of order which can lead to bugs.
+  using Application::DispatchAndDelete;
+#endif
+
   // Specialization of Queue for starboard events.  It differs in that it has
   // the responsibility of deleting heap allocated starboard events in its
   // destructor.  Note the non-virtual destructor, which is intentional and
diff --git a/starboard/shared/stub/media_get_buffer_alignment.cc b/starboard/shared/stub/media_get_buffer_alignment.cc
index 607d895..32bfad7 100644
--- a/starboard/shared/stub/media_get_buffer_alignment.cc
+++ b/starboard/shared/stub/media_get_buffer_alignment.cc
@@ -14,6 +14,10 @@
 
 #include "starboard/media.h"
 
+#if SB_API_VERSION >= 14
+int SbMediaGetBufferAlignment() {
+#else  // SB_API_VERSION >= 14
 int SbMediaGetBufferAlignment(SbMediaType type) {
+#endif  // SB_API_VERSION >= 14
   return 0;
 }
diff --git a/starboard/shared/stub/media_get_buffer_padding.cc b/starboard/shared/stub/media_get_buffer_padding.cc
index 6c4ead1..71d0d0e 100644
--- a/starboard/shared/stub/media_get_buffer_padding.cc
+++ b/starboard/shared/stub/media_get_buffer_padding.cc
@@ -14,6 +14,10 @@
 
 #include "starboard/media.h"
 
+#if SB_API_VERSION >= 14
+int SbMediaGetBufferPadding() {
+#else   // SB_API_VERSION >= 14
 int SbMediaGetBufferPadding(SbMediaType type) {
+#endif  // SB_API_VERSION >= 14
   return 0;
 }
diff --git a/starboard/shared/win32/application_win32.cc b/starboard/shared/win32/application_win32.cc
index 4290730..b816897 100644
--- a/starboard/shared/win32/application_win32.cc
+++ b/starboard/shared/win32/application_win32.cc
@@ -241,7 +241,7 @@
       if (window_.get()) {
         // Freeze the application first so we can do some cleanup before the
         // window is destroyed (e.g. stopping rasterization).
-        DispatchAndDelete(new Event(kSbEventTypeFreeze, NULL, NULL));
+        InjectAndProcess(kSbEventTypeFreeze, /* checkSystemEvents */ false);
         PostQuitMessage(0);
       }
       break;
diff --git a/starboard/system.h b/starboard/system.h
index ebd1005..87e1734 100644
--- a/starboard/system.h
+++ b/starboard/system.h
@@ -58,10 +58,10 @@
   // Path to a directory where temporary files can be written.
   kSbSystemPathTempDirectory,
 
-#if SB_API_VERSION < SB_SYSTEM_PATH_TEST_OUTPUT_DIRECTORY_DEPRECATED
+#if SB_API_VERSION < 14
   // Path to a directory where test results can be written.
   kSbSystemPathTestOutputDirectory,
-#endif  // #if SB_API_VERSION < SB_SYSTEM_PATH_TEST_OUTPUT_DIRECTORY_DEPRECATED
+#endif  // #if SB_API_VERSION < 14
 
   // Full path to the executable file.
   kSbSystemPathExecutableFile,
@@ -153,10 +153,10 @@
   // An Android TV Device.
   kSbSystemDeviceTypeAndroidTV,
 
-#if SB_API_VERSION >= SB_SYSTEM_DEVICE_PROJECTOR_ADDED
+#if SB_API_VERSION >= 14
   // A wall video projector.
   kSbSystemDeviceTypeVideoProjector,
-#endif  // SB_API_VERSION >= SB_SYSTEM_DEVICE_PROJECTOR_ADDED
+#endif  // SB_API_VERSION >= 14
 
   // Unknown device.
   kSbSystemDeviceTypeUnknown,
diff --git a/starboard/tools/media/create_sha1_from_dmp_files.py b/starboard/tools/media/create_sha1_from_dmp_files.py
deleted file mode 100644
index 456d077..0000000
--- a/starboard/tools/media/create_sha1_from_dmp_files.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# Copyright 2019 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Helper script to create formatted ready-to-commit files from raw DMP files.
-
-This script will produce two files for each DMP:
-- A copy of the DMP, renamed with the sha1 of the file (with no extension).
-- A placeholder file, with the same name (with .dmp.sha1 extension), with the
-  contents of the file being the sha1 of the DMP.
-
-The DMP copy is to be uploaded to static storage.
-
-The placeholder copy is to be committed to test files folder.
-"""
-
-import hashlib
-import os
-import shutil
-import sys
-
-
-def main(argv):
-  dmp_dir = argv[1]
-
-  dmp_sha1_dir = os.path.join(dmp_dir, "dmp_sha1_files")
-  gs_dir = os.path.join(dmp_dir, "gs_files")
-
-  if not os.path.exists(dmp_sha1_dir):
-    os.mkdir(dmp_sha1_dir)
-  if not os.path.exists(gs_dir):
-    os.mkdir(gs_dir)
-
-  for filename in os.listdir(dmp_dir):
-    _, ext = os.path.splitext(filename)
-    if ext == ".dmp":
-      print "*"
-      print "File: " + filename
-      # Compute the sha1 of the file
-      filepath = os.path.join(dmp_dir, filename)
-      with open(filepath, "rb") as filehandle:
-        sha1 = hashlib.sha1()
-        buf = filehandle.read(65536)
-        while buf:
-          sha1.update(buf)
-          buf = filehandle.read(65536)
-        sha1sum = sha1.hexdigest()
-
-        print "SHA1: " + sha1sum
-
-        # Rename the original DMP to its |SHA1| with no extension.
-        gs_out = os.path.join(gs_dir, sha1sum)
-        shutil.copyfile(filepath, gs_out)
-        print "Copied \'" + filepath + "\' => \'" + gs_out + "\'"
-
-        # Create a new file with the |SHA1| of the DMP as its contents.
-        dmp_sha1_filename = os.path.join(dmp_sha1_dir, filename + ".sha1")
-        with open(dmp_sha1_filename, "w") as dmp_sha1_file:
-          dmp_sha1_file.write(sha1sum)
-          dmp_sha1_file.close()
-          print "Created \'" + dmp_sha1_filename + "\'"
-
-
-if __name__ == "__main__":
-  sys.exit(main(sys.argv))
diff --git a/starboard/tools/net_args.py b/starboard/tools/net_args.py
index 1948633..b2235db 100644
--- a/starboard/tools/net_args.py
+++ b/starboard/tools/net_args.py
@@ -27,7 +27,7 @@
 # Example:
 #  TryConnectAndSendNetArgs('1.2.3.4', '1234', ['--argument', '--switch=value'])
 def TryConnectAndSendNetArgs(host, port, arg_list):
-  arg_string = '\n'.join(arg_list)
+  arg_string = '\n'.join(arg_list).encode()
   try:
     server_socket = socket.create_connection((host, port), timeout=.5)
     server_socket.sendall(arg_string)
diff --git a/starboard/tools/net_log.py b/starboard/tools/net_log.py
index aaa9797..7fb8a61 100644
--- a/starboard/tools/net_log.py
+++ b/starboard/tools/net_log.py
@@ -59,7 +59,7 @@
       ready_list, _, _ = select.select([self.server_socket], [], [])
       if not ready_list:
         return ''
-      result = self.server_socket.recv(1024)
+      result = self.server_socket.recv(1024).decode('utf-8')
       # An empty string is a flag that the connection has closed.
       if len(result) == 0:
         return None
diff --git a/starboard/sabi/sabi.py b/starboard/tools/testing/BUILD.gn
similarity index 72%
rename from starboard/sabi/sabi.py
rename to starboard/tools/testing/BUILD.gn
index 5dca3b9..17e03e7 100644
--- a/starboard/sabi/sabi.py
+++ b/starboard/tools/testing/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2020 The Cobalt Authors. All Rights Reserved.
+# Copyright 2022 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.
@@ -11,6 +11,8 @@
 # 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.
-"""Source of truth of the default Starboard API version."""
 
-SB_API_VERSION = 14
+copy("copy_sharding_configuration") {
+  sources = [ "sharding_configuration.json" ]
+  outputs = [ "$root_out_dir/sharding_configuration.json" ]
+}
diff --git a/starboard/tools/testing/sharding_configuration.json b/starboard/tools/testing/sharding_configuration.json
new file mode 100644
index 0000000..8f4bce5
--- /dev/null
+++ b/starboard/tools/testing/sharding_configuration.json
@@ -0,0 +1,60 @@
+{
+  "linux-x64x11": [
+    {
+      "renderer_test":"*",
+      "bindings_test":"*",
+      "graphics_system_test":"*",
+      "installation_manager_test":"*",
+      "dom_test":"*",
+      "webdriver_test":"*",
+      "crypto_unittests":"*",
+      "media_capture_test":"*",
+      "websocket_test":"*",
+      "text_encoding_test":"*",
+      "zip_unittests":"*",
+      "audio_test":"*",
+      "slot_management_test":"*",
+      "media_session_test":"*",
+      "base_test":"*",
+      "nb_test":"*",
+      "loader_test":"*",
+      "dom_parser_test":"*",
+      "storage_test":"*",
+      "css_parser_test":"*",
+      "cssom_test":"*",
+      "starboard_platform_tests":"*",
+      "browser_test":"*",
+      "elf_loader_test":"*",
+      "drain_file_test":"*",
+      "xhr_test":"*",
+      "csp_test":"*",
+      "layout_test":"*",
+      "math_test":"*",
+      "media_stream_test":"*",
+      "network_test":"*",
+      "render_tree_test":"*",
+      "memory_store_test":"*",
+      "web_animations_test":"*",
+      "app_key_files_test":"*",
+      "eztime_test":"*",
+      "app_key_test":"*",
+      "cwrappers_test":"*",
+      "extension_test":"*",
+      "poem_unittests":"*",
+      "net_unittests": [1, 2]
+    },
+    {
+      "base_unittests": [1, 2],
+      "layout_tests": "*"
+    },
+    {
+      "nplb":"*",
+      "player_filter_tests": [1, 2]
+    },
+    {
+      "base_unittests": [2, 2],
+      "net_unittests": [2, 2],
+      "player_filter_tests": [2, 2]
+    }
+  ]
+}
diff --git a/starboard/tools/testing/test_runner.py b/starboard/tools/testing/test_runner.py
index 0a721d5..a300dc8 100755
--- a/starboard/tools/testing/test_runner.py
+++ b/starboard/tools/testing/test_runner.py
@@ -17,6 +17,7 @@
 """Cross-platform unit test runner."""
 
 import argparse
+import json
 import logging
 import os
 import re
@@ -219,6 +220,7 @@
                dry_run=False,
                xml_output_dir=None,
                log_xml_results=False,
+               shard_index=None,
                launcher_args=None):
     self.platform = platform
     self.config = config
@@ -228,6 +230,7 @@
     self.target_params = target_params
     self.out_directory = out_directory
     self.loader_out_directory = loader_out_directory
+    self.shard_index = shard_index
     self.launcher_args = launcher_args
     if not self.out_directory:
       self.out_directory = paths.BuildOutputDirectory(self.platform,
@@ -276,6 +279,17 @@
     logging.info("Got test targets")
     self.test_env_vars = self._GetAllTestEnvVariables()
 
+    # Read the sharding configuration from deployed sharding configuration json.
+    # Create subset of test targets, and launch params per target.
+    self.shard_config = None
+    if self.shard_index is not None:
+      with open(
+          os.path.join(self.out_directory, "sharding_configuration.json"),
+          "r") as f:
+        shard_json = json.loads(f.read())
+      full_config = shard_json[self.platform]
+      self.shard_config = full_config[self.shard_index]
+
   def _Exec(self, cmd_list, output_file=None):
     """Execute a command in a subprocess."""
     try:
@@ -380,7 +394,11 @@
         env_variables[test] = test_env
     return env_variables
 
-  def _RunTest(self, target_name, test_name=None):
+  def _RunTest(self,
+               target_name,
+               test_name=None,
+               shard_index=None,
+               shard_count=None):
     """Runs a specific target or test and collects the output.
 
     Args:
@@ -414,6 +432,10 @@
     if gtest_filter_value:
       test_params.append("--gtest_filter=" + gtest_filter_value)
 
+    if shard_index and shard_count:
+      test_params.append("--gtest_total_shards={}".format(shard_count))
+      test_params.append("--gtest_shard_index={}".format(shard_index))
+
     def MakeLauncher():
       return abstract_launcher.LauncherFactory(
           self.platform,
@@ -763,8 +785,28 @@
     results = []
     # Sort the targets so they are run in alphabetical order
     for test_target in sorted(self.test_targets.keys()):
-      results.append(self._RunTest(test_target))
-
+      if self.shard_config:
+        if test_target in self.shard_config.keys():
+          test_shard_config = self.shard_config[test_target]
+          if test_shard_config == "*":
+            logging.info("SHARD %d RUNS TEST %s (full)", self.shard_index,
+                         test_target)
+            results.append(self._RunTest(test_target))
+          else:
+            sub_shard_index = test_shard_config[0] - 1
+            sub_shard_count = test_shard_config[1]
+            logging.info("SHARD %d RUNS TEST %s (%d of %d)", self.shard_index,
+                         test_target, sub_shard_index + 1, sub_shard_count)
+            results.append(
+                self._RunTest(
+                    test_target,
+                    shard_index=sub_shard_index,
+                    shard_count=sub_shard_count))
+        else:
+          logging.info("SHARD %d SKIP TEST %s", self.shard_index, test_target)
+      else:
+        # Run all tests and cases serially. No sharding enabled.
+        results.append(self._RunTest(test_target))
     return self._ProcessAllTestResults(results)
 
   def GenerateCoverageReport(self):
@@ -868,6 +910,12 @@
       "Arguments are platform specific and may not be implemented for all "
       "platforms. Common arguments are:\n\t'noinstall' - skip install steps "
       "before running the test\n\t'systools' - use system-installed tools.")
+  arg_parser.add_argument(
+      "-s",
+      "--shard_index",
+      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.")
   args = arg_parser.parse_args()
 
   if (args.loader_platform and not args.loader_config or
@@ -894,7 +942,7 @@
                       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, launcher_args)
+                      args.log_xml_results, args.shard_index, launcher_args)
   logging.info("Test runner initialized")
 
   def Abort(signum, frame):
diff --git a/cobalt/content/i18n/platform/win/en-US.xlb b/starboard/win/i18n/en-US.xlb
similarity index 100%
copy from cobalt/content/i18n/platform/win/en-US.xlb
copy to starboard/win/i18n/en-US.xlb
diff --git a/starboard/win/shared/BUILD.gn b/starboard/win/shared/BUILD.gn
index 662e967..0a7e0f7 100644
--- a/starboard/win/shared/BUILD.gn
+++ b/starboard/win/shared/BUILD.gn
@@ -367,10 +367,10 @@
   ]
 
   deps = [
-    "//cobalt/content/i18n/platform/xb1",
     "//starboard/egl_and_gles",
     "//starboard/shared/starboard/media:media_util",
     "//starboard/shared/starboard/player/filter:filter_based_player_sources",
+    "//starboard/xb1/i18n",
     "//third_party/opus",
   ]
 
diff --git a/starboard/win/shared/platform_configuration/configuration.gni b/starboard/win/shared/platform_configuration/configuration.gni
index 9fac923..3176613 100644
--- a/starboard/win/shared/platform_configuration/configuration.gni
+++ b/starboard/win/shared/platform_configuration/configuration.gni
@@ -28,6 +28,6 @@
 
 cobalt_platform_dependencies = [ "//starboard/egl_and_gles" ]
 
-platform_i18n_config_path = "//cobalt/content/i18n/platform/xb1:xb1"
+platform_i18n_config_path = "//starboard/xb1/i18n:i18n"
 
 sb_enable_cpp17_audit = false
diff --git a/testing/gmock/METADATA b/testing/gmock/METADATA
index 8f87f92..2c70453 100644
--- a/testing/gmock/METADATA
+++ b/testing/gmock/METADATA
@@ -4,12 +4,8 @@
 
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/external/gmock_mirror"
-  }
-  url {
     type: GIT
-    value: "https://github.com/google/googlemock.git"
+    value: "https://chromium.googlesource.com/external/googlemock"
   }
   version: "38513a8bb154f0b6d0a4088814fe92552696d465"
   last_upgrade_date {
diff --git a/testing/gtest-parallel/.gitignore b/testing/gtest-parallel/.gitignore
deleted file mode 100644
index 2f5184c..0000000
--- a/testing/gtest-parallel/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-.*.swp
-.DS_Store
diff --git a/testing/gtest-parallel/LICENSE b/testing/gtest-parallel/LICENSE
deleted file mode 100644
index d645695..0000000
--- a/testing/gtest-parallel/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   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.
diff --git a/testing/gtest-parallel/METADATA b/testing/gtest-parallel/METADATA
deleted file mode 100644
index e43837d..0000000
--- a/testing/gtest-parallel/METADATA
+++ /dev/null
@@ -1,20 +0,0 @@
-name: "gtest-parallel"
-description:
-  "Subtree at testing/gtest-parallel."
-
-third_party {
-  url {
-    type: LOCAL_SOURCE
-    value: "/testing/gtest-parallel_mirror"
-  }
-  url {
-    type: GIT
-    value: "https://github.com/google/gtest-parallel.git"
-  }
-  version: "d0cebaba01c5dbf3af8a1c89b64eed7596c2b56c"
-  last_upgrade_date {
-    year: 2016
-    month: 8
-    day: 3
-  }
-}
diff --git a/testing/gtest-parallel/README.cobalt b/testing/gtest-parallel/README.cobalt
deleted file mode 100644
index 0d388eb..0000000
--- a/testing/gtest-parallel/README.cobalt
+++ /dev/null
@@ -1,59 +0,0 @@
-Name: gtest-parallel
-URL: https://github.com/google/gtest-parallel
-Version: 0
-License: Apache 2.0
-Security Critical: no
-
-Description
-    This directory contains the source code of a python project that helps run
-    gtest binaries in parallel using multiple processes.
-
-How to use
-    Pre-reqs: Python (2)
-    1.  ./gtest-parallel <name-of-gtest-binary>
-
-    for additional options, see ./gtest-parallel --help
-
-    If all the tests pass, you will be see one line on the screen get updated.
-    If a test fails, it will print out the test result and scroll a little bit.
-
-    Example: $ ./gtest-parallel ~/cobalt/src/out/linux-x64x11_debug/layout_tests
-    [436/436] BidiLayoutTests/LayoutTest.LayoutTest/1 (1285 ms)
-
-Troubleshooting
-    1.  Make sure that the gtest-parallel script is executable:
-      chmod +x ./gtest-parallel
-
-Source
-1.  a) The external repo was imported into our git server on lbshell-internal,
-       repo name is gtest-parallel
-    b) A new branch was created with the date of last commit
-    c) Then it was pushed to the COBALT branch
-
-    Steps for step 1.b:
-    $ git clone https://lbshell-internal.googlesource.com/testing/gtest-parallel
-    $ cd gtest-parallel/
-    $ git remote add upstream https://github.com/google/gtest-parallel.git
-    $ git fetch upstream
-    $ git checkout -b 20160803 upstream/master
-    $ git push origin
-
-    You will likely need admin permissions on the repo to execute the git push.
-
-2. A patch was applied since gtest now prints some things to stderr instead of
-   stdout.
-
-    $ patch < patches/stderr_out.patch
-
-FAQ
-1.  Do the tests need to be thread safe?
-
-    No, the script first asks the binary for the list of tests, and then splits
-    them across multiple processes.
-
-Patches
-1.  patches/stderr_out.patch
-
-    Starboard redirects some output from stdout to stderr.
-    Therefore, we needed to modify the the gtest-parallel script to parse stderr
-    instead of stdout.
diff --git a/testing/gtest-parallel/gtest-parallel b/testing/gtest-parallel/gtest-parallel
deleted file mode 100755
index a258a87..0000000
--- a/testing/gtest-parallel/gtest-parallel
+++ /dev/null
@@ -1,411 +0,0 @@
-#!/usr/bin/env python2
-# Copyright 2013 Google Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import cPickle
-import errno
-import gzip
-import multiprocessing
-import optparse
-import os
-import signal
-import subprocess
-import sys
-import tempfile
-import thread
-import threading
-import time
-import zlib
-
-# An object that catches SIGINT sent to the Python process and notices
-# if processes passed to wait() die by SIGINT (we need to look for
-# both of those cases, because pressing Ctrl+C can result in either
-# the main process or one of the subprocesses getting the signal).
-#
-# Before a SIGINT is seen, wait(p) will simply call p.wait() and
-# return the result. Once a SIGINT has been seen (in the main process
-# or a subprocess, including the one the current call is waiting for),
-# wait(p) will call p.terminate() and raise ProcessWasInterrupted.
-class SigintHandler(object):
-  class ProcessWasInterrupted(Exception): pass
-  sigint_returncodes = {-signal.SIGINT,  # Unix
-                        -1073741510,     # Windows
-                        }
-  def __init__(self):
-    self.__lock = threading.Lock()
-    self.__processes = set()
-    self.__got_sigint = False
-    signal.signal(signal.SIGINT, self.__sigint_handler)
-  def __on_sigint(self):
-    self.__got_sigint = True
-    while self.__processes:
-      try:
-        self.__processes.pop().terminate()
-      except OSError:
-        pass
-  def __sigint_handler(self, signal_num, frame):
-    with self.__lock:
-      self.__on_sigint()
-  def got_sigint(self):
-    with self.__lock:
-      return self.__got_sigint
-  def wait(self, p):
-    with self.__lock:
-      if self.__got_sigint:
-        p.terminate()
-      self.__processes.add(p)
-    code = p.wait()
-    with self.__lock:
-      self.__processes.discard(p)
-      if code in self.sigint_returncodes:
-        self.__on_sigint()
-      if self.__got_sigint:
-        raise self.ProcessWasInterrupted
-    return code
-sigint_handler = SigintHandler()
-
-# Return the width of the terminal, or None if it couldn't be
-# determined (e.g. because we're not being run interactively).
-def term_width(out):
-  if not out.isatty():
-    return None
-  try:
-    p = subprocess.Popen(["stty", "size"],
-                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    (out, err) = p.communicate()
-    if p.returncode != 0 or err:
-      return None
-    return int(out.split()[1])
-  except (IndexError, OSError, ValueError):
-    return None
-
-# Output transient and permanent lines of text. If several transient
-# lines are written in sequence, the new will overwrite the old. We
-# use this to ensure that lots of unimportant info (tests passing)
-# won't drown out important info (tests failing).
-class Outputter(object):
-  def __init__(self, out_file):
-    self.__out_file = out_file
-    self.__previous_line_was_transient = False
-    self.__width = term_width(out_file)  # Line width, or None if not a tty.
-  def transient_line(self, msg):
-    if self.__width is None:
-      self.__out_file.write(msg + "\n")
-    else:
-      self.__out_file.write("\r" + msg[:self.__width].ljust(self.__width))
-      self.__previous_line_was_transient = True
-  def flush_transient_output(self):
-    if self.__previous_line_was_transient:
-      self.__out_file.write("\n")
-      self.__previous_line_was_transient = False
-  def permanent_line(self, msg):
-    self.flush_transient_output()
-    self.__out_file.write(msg + "\n")
-
-stdout_lock = threading.Lock()
-
-class FilterFormat:
-  if sys.stdout.isatty():
-    # stdout needs to be unbuffered since the output is interactive.
-    sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
-
-  out = Outputter(sys.stdout)
-  total_tests = 0
-  finished_tests = 0
-
-  tests = {}
-  outputs = {}
-  failures = []
-
-  def print_test_status(self, last_finished_test, time_ms):
-    self.out.transient_line("[%d/%d] %s (%d ms)"
-                            % (self.finished_tests, self.total_tests,
-                               last_finished_test, time_ms))
-
-  def handle_meta(self, job_id, args):
-    (command, arg) = args.split(' ', 1)
-    if command == "TEST":
-      (binary, test) = arg.split(' ', 1)
-      self.tests[job_id] = (binary, test.strip())
-    elif command == "EXIT":
-      (exit_code, time_ms) = [int(x) for x in arg.split(' ', 1)]
-      self.finished_tests += 1
-      (binary, test) = self.tests[job_id]
-      self.print_test_status(test, time_ms)
-      if exit_code != 0:
-        self.failures.append(self.tests[job_id])
-        with open(self.outputs[job_id]) as f:
-          for line in f.readlines():
-            self.out.permanent_line(line.rstrip())
-        self.out.permanent_line(
-          "[%d/%d] %s returned/aborted with exit code %d (%d ms)"
-          % (self.finished_tests, self.total_tests, test, exit_code, time_ms))
-    elif command == "TESTCNT":
-      self.total_tests = int(arg.split(' ', 1)[1])
-      self.out.transient_line("[0/%d] Running tests..." % self.total_tests)
-
-  def logfile(self, job_id, name):
-    self.outputs[job_id] = name
-
-  def log(self, line):
-    stdout_lock.acquire()
-    (prefix, output) = line.split(' ', 1)
-
-    assert prefix[-1] == ':'
-    self.handle_meta(int(prefix[:-1]), output)
-    stdout_lock.release()
-
-  def end(self):
-    if self.failures:
-      self.out.permanent_line("FAILED TESTS (%d/%d):"
-                              % (len(self.failures), self.total_tests))
-      for (binary, test) in self.failures:
-        self.out.permanent_line(" " + binary + ": " + test)
-    self.out.flush_transient_output()
-
-class RawFormat:
-  def log(self, line):
-    stdout_lock.acquire()
-    sys.stdout.write(line + "\n")
-    sys.stdout.flush()
-    stdout_lock.release()
-  def logfile(self, job_id, name):
-    with open(name) as f:
-      for line in f.readlines():
-        self.log(str(job_id) + '> ' + line.rstrip())
-  def end(self):
-    pass
-
-# Record of test runtimes. Has built-in locking.
-class TestTimes(object):
-  def __init__(self, save_file):
-    "Create new object seeded with saved test times from the given file."
-    self.__times = {}  # (test binary, test name) -> runtime in ms
-
-    # Protects calls to record_test_time(); other calls are not
-    # expected to be made concurrently.
-    self.__lock = threading.Lock()
-
-    try:
-      with gzip.GzipFile(save_file, "rb") as f:
-        times = cPickle.load(f)
-    except (EOFError, IOError, cPickle.UnpicklingError, zlib.error):
-      # File doesn't exist, isn't readable, is malformed---whatever.
-      # Just ignore it.
-      return
-
-    # Discard saved times if the format isn't right.
-    if type(times) is not dict:
-      return
-    for ((test_binary, test_name), runtime) in times.items():
-      if (type(test_binary) is not str or type(test_name) is not str
-          or type(runtime) not in {int, long, type(None)}):
-        return
-
-    self.__times = times
-
-  def get_test_time(self, binary, testname):
-    """Return the last duration for the given test as an integer number of
-    milliseconds, or None if the test failed or if there's no record for it."""
-    return self.__times.get((binary, testname), None)
-
-  def record_test_time(self, binary, testname, runtime_ms):
-    """Record that the given test ran in the specified number of
-    milliseconds. If the test failed, runtime_ms should be None."""
-    with self.__lock:
-      self.__times[(binary, testname)] = runtime_ms
-
-  def write_to_file(self, save_file):
-    "Write all the times to file."
-    try:
-      with open(save_file, "wb") as f:
-        with gzip.GzipFile("", "wb", 9, f) as gzf:
-          cPickle.dump(self.__times, gzf, cPickle.HIGHEST_PROTOCOL)
-    except IOError:
-      pass  # ignore errors---saving the times isn't that important
-
-# Remove additional arguments (anything after --).
-additional_args = []
-
-for i in range(len(sys.argv)):
-  if sys.argv[i] == '--':
-    additional_args = sys.argv[i+1:]
-    sys.argv = sys.argv[:i]
-    break
-
-parser = optparse.OptionParser(
-    usage = 'usage: %prog [options] binary [binary ...] -- [additional args]')
-
-parser.add_option('-d', '--output_dir', type='string',
-                  default=os.path.join(tempfile.gettempdir(), "gtest-parallel"),
-                  help='output directory for test logs')
-parser.add_option('-r', '--repeat', type='int', default=1,
-                  help='repeat tests')
-parser.add_option('--failed', action='store_true', default=False,
-                  help='run only failed and new tests')
-parser.add_option('-w', '--workers', type='int',
-                  default=multiprocessing.cpu_count(),
-                  help='number of workers to spawn')
-parser.add_option('--gtest_color', type='string', default='yes',
-                  help='color output')
-parser.add_option('--gtest_filter', type='string', default='',
-                  help='test filter')
-parser.add_option('--gtest_also_run_disabled_tests', action='store_true',
-                  default=False, help='run disabled tests too')
-parser.add_option('--format', type='string', default='filter',
-                  help='output format (raw,filter)')
-parser.add_option('--print_test_times', action='store_true', default=False,
-                  help='When done, list the run time of each test')
-
-(options, binaries) = parser.parse_args()
-
-if binaries == []:
-  parser.print_usage()
-  sys.exit(1)
-
-logger = RawFormat()
-if options.format == 'raw':
-  pass
-elif options.format == 'filter':
-  logger = FilterFormat()
-else:
-  sys.exit("Unknown output format: " + options.format)
-
-# Find tests.
-save_file = os.path.join(os.path.expanduser("~"), ".gtest-parallel-times")
-times = TestTimes(save_file)
-tests = []
-for test_binary in binaries:
-  command = [test_binary]
-  if options.gtest_also_run_disabled_tests:
-    command += ['--gtest_also_run_disabled_tests']
-
-  list_command = list(command)
-  if options.gtest_filter != '':
-    list_command += ['--gtest_filter=' + options.gtest_filter]
-
-  try:
-    test_list = subprocess.Popen(list_command + ['--gtest_list_tests'],
-                                 stderr=subprocess.PIPE).communicate()[1]
-  except OSError as e:
-    sys.exit("%s: %s" % (test_binary, str(e)))
-
-  command += additional_args
-
-  test_group = ''
-  for line in test_list.split('\n'):
-    if not line.strip():
-      continue
-    if line[0] != " ":
-      # Remove comments for typed tests and strip whitespace.
-      test_group = line.split('#')[0].strip()
-      continue
-    # Remove comments for parameterized tests and strip whitespace.
-    line = line.split('#')[0].strip()
-    if not line:
-      continue
-
-    test = test_group + line
-    if not options.gtest_also_run_disabled_tests and 'DISABLED_' in test:
-      continue
-    tests.append((times.get_test_time(test_binary, test),
-                  test_binary, test, command))
-
-if options.failed:
-  # The first element of each entry is the runtime of the most recent
-  # run if it was successful, or None if the test is new or the most
-  # recent run failed.
-  tests = [x for x in tests if x[0] is None]
-
-# Sort tests by falling runtime (with None, which is what we get for
-# new and failing tests, being considered larger than any real
-# runtime).
-tests.sort(reverse=True, key=lambda x: ((1 if x[0] is None else 0), x))
-
-# Repeat tests (-r flag).
-tests *= options.repeat
-test_lock = threading.Lock()
-job_id = 0
-logger.log(str(-1) + ': TESTCNT ' + ' ' + str(len(tests)))
-
-exit_code = 0
-
-# Create directory for test log output.
-try:
-  os.makedirs(options.output_dir)
-except OSError as e:
-  # Ignore errors if this directory already exists.
-  if e.errno != errno.EEXIST or not os.path.isdir(options.output_dir):
-    raise e
-# Remove files from old test runs.
-for logfile in os.listdir(options.output_dir):
-  os.remove(os.path.join(options.output_dir, logfile))
-
-# Run the specified job. Return the elapsed time in milliseconds if
-# the job succeeds, or None if the job fails. (This ensures that
-# failing tests will run first the next time.)
-def run_job((command, job_id, test)):
-  begin = time.time()
-
-  with tempfile.NamedTemporaryFile(dir=options.output_dir, delete=False) as log:
-    sub = subprocess.Popen(command + ['--gtest_filter=' + test] +
-                             ['--gtest_color=' + options.gtest_color],
-                           stdout=log.file,
-                           stderr=log.file)
-    try:
-      code = sigint_handler.wait(sub)
-    except sigint_handler.ProcessWasInterrupted:
-      thread.exit()
-    runtime_ms = int(1000 * (time.time() - begin))
-    logger.logfile(job_id, log.name)
-
-  logger.log("%s: EXIT %s %d" % (job_id, code, runtime_ms))
-  if code == 0:
-    return runtime_ms
-  global exit_code
-  exit_code = code
-  return None
-
-def worker():
-  global job_id
-  while True:
-    job = None
-    test_lock.acquire()
-    if job_id < len(tests):
-      (_, test_binary, test, command) = tests[job_id]
-      logger.log(str(job_id) + ': TEST ' + test_binary + ' ' + test)
-      job = (command, job_id, test)
-    job_id += 1
-    test_lock.release()
-    if job is None:
-      return
-    times.record_test_time(test_binary, test, run_job(job))
-
-def start_daemon(func):
-  t = threading.Thread(target=func)
-  t.daemon = True
-  t.start()
-  return t
-
-workers = [start_daemon(worker) for i in range(options.workers)]
-
-[t.join() for t in workers]
-logger.end()
-times.write_to_file(save_file)
-if options.print_test_times:
-  ts = sorted((times.get_test_time(test_binary, test), test_binary, test)
-              for (_, test_binary, test, _) in tests
-              if times.get_test_time(test_binary, test) is not None)
-  for (time_ms, test_binary, test) in ts:
-    print "%8s %s" % ("%dms" % time_ms, test)
-sys.exit(-signal.SIGINT if sigint_handler.got_sigint() else exit_code)
diff --git a/testing/gtest-parallel/patches/stderr_out.patch b/testing/gtest-parallel/patches/stderr_out.patch
deleted file mode 100644
index ddaf9df..0000000
--- a/testing/gtest-parallel/patches/stderr_out.patch
+++ /dev/null
@@ -1,19 +0,0 @@
-commit 0dec4f46db1b072591cb6703c68a2ebb0279026d
-Author: Navreet Gill <navreetgill@google.com>
-Date:   Wed Aug 24 11:32:47 2016 -0700
-
-    test
-
-diff --git a/gtest-parallel b/gtest-parallel
-index 4e80928..a258a87 100755
---- a/gtest-parallel
-+++ b/gtest-parallel
-@@ -296,7 +296,7 @@ for test_binary in binaries:
- 
-   try:
-     test_list = subprocess.Popen(list_command + ['--gtest_list_tests'],
--                                 stdout=subprocess.PIPE).communicate()[0]
-+                                 stderr=subprocess.PIPE).communicate()[1]
-   except OSError as e:
-     sys.exit("%s: %s" % (test_binary, str(e)))
- 
diff --git a/testing/gtest/METADATA b/testing/gtest/METADATA
index 6bbeaa9..fbb8d59 100644
--- a/testing/gtest/METADATA
+++ b/testing/gtest/METADATA
@@ -4,12 +4,8 @@
 
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/external/gtest_mirror"
-  }
-  url {
     type: GIT
-    value: "https://github.com/google/googletest.git"
+    value: "https://chromium.googlesource.com/chromium/src/third_party/googletest"
   }
   version: "a1c4b46bc2c12ea7c61108f001a5b5eb4a8ccad0"
   last_upgrade_date {
diff --git a/third_party/angle/METADATA b/third_party/angle/METADATA
index d133ef0..d20e493 100644
--- a/third_party/angle/METADATA
+++ b/third_party/angle/METADATA
@@ -4,10 +4,6 @@
 
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/external/angle_mirror"
-  }
-  url {
     type: GIT
     value: "https://chromium.googlesource.com/angle/angle"
   }
diff --git a/third_party/aom_includes/METADATA b/third_party/aom_includes/METADATA
index e1ea361..c870f2b 100644
--- a/third_party/aom_includes/METADATA
+++ b/third_party/aom_includes/METADATA
@@ -2,6 +2,7 @@
 description:
   "libaom is an AV1 codec library. aom_includes contains the header files "
   "necessary for the library to be dynamically linked into crosstool builds."
+
 third_party {
   url {
     type: GIT
diff --git a/third_party/blink/METADATA b/third_party/blink/METADATA
index cf0800e..cdf5467 100644
--- a/third_party/blink/METADATA
+++ b/third_party/blink/METADATA
@@ -2,6 +2,7 @@
 description:
   "Blink is a browser engine used by Chromium. Cobalt uses Blink for its IDL "
   "parser and bindings generation."
+
 third_party {
   url {
     type: GIT
diff --git a/third_party/chromium/media/BUILD.gn b/third_party/chromium/media/BUILD.gn
index ce95f56..e914124 100644
--- a/third_party/chromium/media/BUILD.gn
+++ b/third_party/chromium/media/BUILD.gn
@@ -91,6 +91,8 @@
     "base/ranges.h",
     "base/sample_format.cc",
     "base/sample_format.h",
+    "base/starboard_utils.cc",
+    "base/starboard_utils.h",
     "base/stream_parser.cc",
     "base/stream_parser.h",
     "base/stream_parser_buffer.cc",
@@ -249,3 +251,23 @@
     "//url",
   ]
 }
+
+target(gtest_target_type, "chromium_media_test") {
+  testonly = true
+
+  sources = [
+    "base/starboard_utils_test.cc",
+    "test/run_all_unittests.cc",
+  ]
+
+  configs -= [ "//starboard/build/config:size" ]
+  configs += [ "//starboard/build/config:speed" ]
+
+  deps = [
+    ":media",
+    "//base",
+    "//base/test:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
diff --git a/third_party/chromium/media/METADATA b/third_party/chromium/media/METADATA
index 65c61b0..0158fa1 100644
--- a/third_party/chromium/media/METADATA
+++ b/third_party/chromium/media/METADATA
@@ -4,12 +4,8 @@
 
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/third_party/chromium/media_mirror"
-  }
-  url {
     type: GIT
-    value: "https://chromium.googlesource.com/chromium/src.git"
+    value: "https://chromium.googlesource.com/chromium/src/media"
   }
   version: "4dc2af87b3a5ff1c1489ec642491b59e9872ac8b"
   last_upgrade_date {
diff --git a/third_party/chromium/media/base/decoder_buffer.cc b/third_party/chromium/media/base/decoder_buffer.cc
index 7f8d096..6060c9b 100644
--- a/third_party/chromium/media/base/decoder_buffer.cc
+++ b/third_party/chromium/media/base/decoder_buffer.cc
@@ -95,12 +95,20 @@
 #if defined(STARBOARD)
   DCHECK(s_allocator);
   DCHECK(!data_);
-  // TODO(b/230887703): Consider deprecate type parameter to
-  // `SbMediaGetBufferAlignment()`.
-  data_ = static_cast<uint8_t*>(
-              s_allocator->Allocate(
-                  size_, SbMediaGetBufferAlignment(kSbMediaTypeVideo)));
-  allocated_size_ = size_;
+
+#if SB_API_VERSION >= 14
+  int alignment = SbMediaGetBufferAlignment();
+  int padding = SbMediaGetBufferPadding();
+#else  // SB_API_VERSION >= 14
+  int alignment = std::max(SbMediaGetBufferAlignment(kSbMediaTypeAudio),
+                           SbMediaGetBufferAlignment(kSbMediaTypeVideo));
+  int padding = std::max(SbMediaGetBufferPadding(kSbMediaTypeAudio),
+                         SbMediaGetBufferPadding(kSbMediaTypeVideo));
+#endif  // SB_API_VERSION >= 14
+  allocated_size_ = size_ + padding;
+  data_ = static_cast<uint8_t*>(s_allocator->Allocate(allocated_size_,
+                                                      alignment));
+  memset(data_ + size_, 0, padding);
 #else  // defined(STARBOARD)
   data_.reset(new uint8_t[size_]);
 #endif  // defined(STARBOARD)
diff --git a/third_party/chromium/media/base/demuxer_memory_limit.h b/third_party/chromium/media/base/demuxer_memory_limit.h
index 2492eca..0cfcec9 100644
--- a/third_party/chromium/media/base/demuxer_memory_limit.h
+++ b/third_party/chromium/media/base/demuxer_memory_limit.h
@@ -19,9 +19,17 @@
 // particular type of stream.
 MEDIA_EXPORT size_t
 GetDemuxerStreamAudioMemoryLimit(const AudioDecoderConfig* audio_config);
+
+#if defined(STARBOARD)
+MEDIA_EXPORT size_t
+GetDemuxerStreamVideoMemoryLimit(Demuxer::DemuxerTypes demuxer_type,
+                                 const VideoDecoderConfig* video_config,
+                                 const std::string& mime_type);
+#else  // defined(STARBOARD)
 MEDIA_EXPORT size_t
 GetDemuxerStreamVideoMemoryLimit(Demuxer::DemuxerTypes demuxer_type,
                                  const VideoDecoderConfig* video_config);
+#endif  // defined(STARBOARD)
 
 // The maximum amount of data (in bytes) a demuxer can keep in memory overall.
 MEDIA_EXPORT size_t GetDemuxerMemoryLimit(Demuxer::DemuxerTypes demuxer_type);
diff --git a/third_party/chromium/media/base/demuxer_memory_limit_starboard.cc b/third_party/chromium/media/base/demuxer_memory_limit_starboard.cc
index 0a622e5..19fca6b 100644
--- a/third_party/chromium/media/base/demuxer_memory_limit_starboard.cc
+++ b/third_party/chromium/media/base/demuxer_memory_limit_starboard.cc
@@ -13,43 +13,13 @@
 // limitations under the License.
 
 #include "media/base/demuxer_memory_limit.h"
+
+#include "media/base/starboard_utils.h"
 #include "media/base/video_codecs.h"
 #include "base/logging.h"
 #include "starboard/media.h"
 
 namespace media {
-namespace {
-
-// TODO(b/231375871): Move this to video_codecs.h.  Also consider remove
-// starboard_utils.* and move the functions to individual files.
-SbMediaVideoCodec MediaVideoCodecToSbMediaVideoCodec(VideoCodec codec) {
-  switch (codec) {
-    case VideoCodec::kH264:
-      return kSbMediaVideoCodecH264;
-    case VideoCodec::kVC1:
-      return kSbMediaVideoCodecVc1;
-    case VideoCodec::kMPEG2:
-      return kSbMediaVideoCodecMpeg2;
-    case VideoCodec::kTheora:
-      return kSbMediaVideoCodecTheora;
-    case VideoCodec::kVP8:
-      return kSbMediaVideoCodecVp8;
-    case VideoCodec::kVP9:
-      return kSbMediaVideoCodecVp9;
-    case VideoCodec::kHEVC:
-      return kSbMediaVideoCodecH265;
-    case VideoCodec::kAV1:
-      return kSbMediaVideoCodecAv1;
-    default:
-      // Cobalt only supports a subset of video codecs defined by Chromium.
-      DLOG(ERROR) << "Unsupported video codec " << GetCodecName(codec);
-      return kSbMediaVideoCodecNone;
-  }
-  NOTREACHED();
-  return kSbMediaVideoCodecNone;
-}
-
-}  // namespace
 
 size_t GetDemuxerStreamAudioMemoryLimit(
     const AudioDecoderConfig* /*audio_config*/) {
@@ -58,25 +28,15 @@
 
 size_t GetDemuxerStreamVideoMemoryLimit(
     Demuxer::DemuxerTypes /*demuxer_type*/,
-    const VideoDecoderConfig* video_config) {
-  if (!video_config) {
-    return static_cast<size_t>(
-        SbMediaGetVideoBufferBudget(kSbMediaVideoCodecH264, 1920, 1080, 8));
-  }
-
-  auto width = video_config->visible_rect().size().width();
-  auto height = video_config->visible_rect().size().height();
-  // TODO(b/230799815_): Ensure |bits_per_pixel| always contains a valid value,
-  // or consider deprecating it from `SbMediaGetVideoBufferBudget()`.
-  auto bits_per_pixel = 0;
-  auto codec = MediaVideoCodecToSbMediaVideoCodec(video_config->codec());
+    const VideoDecoderConfig* video_config,
+    const std::string& mime_type) {
   return static_cast<size_t>(
-      SbMediaGetVideoBufferBudget(codec, width, height, bits_per_pixel));
+      GetSbMediaVideoBufferBudget(video_config, mime_type));
 }
 
 size_t GetDemuxerMemoryLimit(Demuxer::DemuxerTypes demuxer_type) {
   return GetDemuxerStreamAudioMemoryLimit(nullptr) +
-         GetDemuxerStreamVideoMemoryLimit(demuxer_type, nullptr);
+         GetDemuxerStreamVideoMemoryLimit(demuxer_type, nullptr, "");
 }
 
 }  // namespace media
diff --git a/cobalt/media/base/starboard_utils.cc b/third_party/chromium/media/base/starboard_utils.cc
similarity index 78%
rename from cobalt/media/base/starboard_utils.cc
rename to third_party/chromium/media/base/starboard_utils.cc
index fe7e27d..658bb2c 100644
--- a/cobalt/media/base/starboard_utils.cc
+++ b/third_party/chromium/media/base/starboard_utils.cc
@@ -12,25 +12,48 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/media/base/starboard_utils.h"
+#include "media/base/starboard_utils.h"
 
 #include <algorithm>
 
 #include "base/logging.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "starboard/common/media.h"
 #include "starboard/configuration.h"
 #include "starboard/memory.h"
-#include "third_party/chromium/media/base/decrypt_config.h"
+#include "media/base/decrypt_config.h"
 
 using base::Time;
 using base::TimeDelta;
 
-namespace cobalt {
 namespace media {
 namespace {
 
-using ::media::AudioCodec;
-using ::media::VideoCodec;
-using ::media::VideoColorSpace;
+int GetBitsPerPixel(const std::string& mime_type) {
+  auto codecs = ExtractCodecs(mime_type);
+  SbMediaVideoCodec codec;
+  int profile;
+  int level;
+  int bit_depth;
+  SbMediaPrimaryId primary_id;
+  SbMediaTransferId transfer_id;
+  SbMediaMatrixId matrix_id;
+
+  if (starboard::ParseVideoCodec(codecs.c_str(),
+                                 &codec,
+                                 &profile,
+                                 &level,
+                                 &bit_depth,
+                                 &primary_id,
+                                 &transfer_id,
+                                 &matrix_id)) {
+    return bit_depth;
+  }
+
+  // Assume SDR when there isn't enough information to determine the bit depth.
+  return 8;
+}
 
 }  // namespace
 
@@ -58,7 +81,7 @@
       return kSbMediaAudioCodecOpus;
     default:
       // Cobalt only supports a subset of audio codecs defined by Chromium.
-      DLOG(ERROR) << "Unsupported audio codec " << ::media::GetCodecName(codec);
+      DLOG(ERROR) << "Unsupported audio codec " << GetCodecName(codec);
       return kSbMediaAudioCodecNone;
   }
   NOTREACHED();
@@ -85,7 +108,7 @@
       return kSbMediaVideoCodecAv1;
     default:
       // Cobalt only supports a subset of video codecs defined by Chromium.
-      DLOG(ERROR) << "Unsupported video codec " << ::media::GetCodecName(codec);
+      DLOG(ERROR) << "Unsupported video codec " << GetCodecName(codec);
       return kSbMediaVideoCodecNone;
   }
   NOTREACHED();
@@ -93,7 +116,7 @@
 }
 
 SbMediaAudioSampleInfo MediaAudioConfigToSbMediaAudioSampleInfo(
-    const ::media::AudioDecoderConfig& audio_decoder_config,
+    const AudioDecoderConfig& audio_decoder_config,
     const char* mime_type) {
   DCHECK(audio_decoder_config.IsValidConfig());
 
@@ -126,34 +149,34 @@
   return audio_sample_info;
 }
 
-::media::DemuxerStream::Type SbMediaTypeToDemuxerStreamType(SbMediaType type) {
+DemuxerStream::Type SbMediaTypeToDemuxerStreamType(SbMediaType type) {
   if (type == kSbMediaTypeAudio) {
-    return ::media::DemuxerStream::AUDIO;
+    return DemuxerStream::AUDIO;
   }
   DCHECK_EQ(type, kSbMediaTypeVideo);
-  return ::media::DemuxerStream::VIDEO;
+  return DemuxerStream::VIDEO;
 }
 
-SbMediaType DemuxerStreamTypeToSbMediaType(::media::DemuxerStream::Type type) {
-  if (type == ::media::DemuxerStream::AUDIO) {
+SbMediaType DemuxerStreamTypeToSbMediaType(DemuxerStream::Type type) {
+  if (type == DemuxerStream::AUDIO) {
     return kSbMediaTypeAudio;
   }
-  DCHECK_EQ(type, ::media::DemuxerStream::VIDEO);
+  DCHECK_EQ(type, DemuxerStream::VIDEO);
   return kSbMediaTypeVideo;
 }
 
-void FillDrmSampleInfo(const scoped_refptr<::media::DecoderBuffer>& buffer,
+void FillDrmSampleInfo(const scoped_refptr<DecoderBuffer>& buffer,
                        SbDrmSampleInfo* drm_info,
                        SbDrmSubSampleMapping* subsample_mapping) {
   DCHECK(drm_info);
   DCHECK(subsample_mapping);
 
-  const ::media::DecryptConfig* config = buffer->decrypt_config();
+  const DecryptConfig* config = buffer->decrypt_config();
 
-  if (config->encryption_scheme() == ::media::EncryptionScheme::kCenc) {
+  if (config->encryption_scheme() == EncryptionScheme::kCenc) {
     drm_info->encryption_scheme = kSbDrmEncryptionSchemeAesCtr;
   } else {
-    DCHECK_EQ(config->encryption_scheme(), ::media::EncryptionScheme::kCbcs);
+    DCHECK_EQ(config->encryption_scheme(), EncryptionScheme::kCbcs);
     drm_info->encryption_scheme = kSbDrmEncryptionSchemeAesCbc;
   }
 
@@ -186,7 +209,7 @@
 
   if (drm_info->subsample_count > 0) {
     COMPILE_ASSERT(
-        sizeof(SbDrmSubSampleMapping) == sizeof(::media::SubsampleEntry),
+        sizeof(SbDrmSubSampleMapping) == sizeof(SubsampleEntry),
         SubSampleEntrySizesMatch);
     drm_info->subsample_mapping =
         reinterpret_cast<const SbDrmSubSampleMapping*>(
@@ -207,7 +230,7 @@
 }
 
 // Ensure that the enums in starboard/media.h match enums in
-// ::media::VideoColorSpace and gfx::ColorSpace.
+// VideoColorSpace and gfx::ColorSpace.
 #define ENUM_EQ(a, b) \
   COMPILE_ASSERT(static_cast<int>(a) == static_cast<int>(b), mismatching_enums)
 
@@ -265,13 +288,10 @@
 ENUM_EQ(kSbMediaMatrixIdBt2020ConstantLuminance,
         VideoColorSpace::MatrixID::BT2020_CL);
 ENUM_EQ(kSbMediaMatrixIdYDzDx, VideoColorSpace::MatrixID::YDZDX);
-// TODO(b/230891177): Add `kSbMediaMatrixIdInvalid` to SbMediaMatrixId and set
-// it to 255 and uncomment and refine the section for `kSbMediaMatrixIdInvalid`
-// in MediaToSbMediaColorMetadata().
-// #if SB_API_VERSION >= SB_MEDIA_MATRIX_ID_INVALID_API_VERSION
-//   ENUM_EQ(kSbMediaMatrixIdInvalid,
-//           VideoColorSpace::MatrixID::INVALID);
-// #endif  // SB_API_VERSION >=  SB_MEDIA_MATRIX_ID_INVALID_API_VERSION
+
+#if SB_API_VERSION >= 14
+ENUM_EQ(kSbMediaMatrixIdInvalid, VideoColorSpace::MatrixID::INVALID);
+#endif  // SB_API_VERSION >= 14
 
 // Ensure RangeId enums convert correctly.
 ENUM_EQ(kSbMediaRangeIdUnspecified, gfx::ColorSpace::RangeID::INVALID);
@@ -281,13 +301,15 @@
 
 SbMediaColorMetadata MediaToSbMediaColorMetadata(
     const VideoColorSpace& color_space,
-    const absl::optional<gfx::HDRMetadata>& hdr_metadata) {
+    const absl::optional<gfx::HDRMetadata>& hdr_metadata,
+    const std::string& mime_type) {
   SbMediaColorMetadata sb_media_color_metadata = {};
 
+  sb_media_color_metadata.bits_per_channel = GetBitsPerPixel(mime_type);
+
   // Copy the other color metadata below.
   // TODO(b/230915942): Revisit to ensure that the metadata is valid and
   // consider deprecate them from `SbMediaColorMetadata`.
-  sb_media_color_metadata.bits_per_channel = 0;
   sb_media_color_metadata.chroma_subsampling_horizontal = 0;
   sb_media_color_metadata.chroma_subsampling_vertical = 0;
   sb_media_color_metadata.cb_subsampling_horizontal = 0;
@@ -340,13 +362,11 @@
   sb_media_color_metadata.matrix =
       static_cast<SbMediaMatrixId>(color_space.matrix);
 
-  // #if SB_API_VERSION < SB_MEDIA_MATRIX_ID_INVALID_API_VERSION
-  //   // Use `kSbMediaMatrixIdUnknown` when `kSbMediaMatrixIdInvalid` isn't
-  //   // available.
-  //   if (color_space.matrix == VideoColorSpace::MatrixID::INVALID) {
-  //     sb_media_color_metadata.matrix = kSbMediaMatrixIdUnknown;
-  //   }
-  // #endif  // SB_API_VERSION < SB_MEDIA_MATRIX_ID_INVALID_API_VERSION
+#if SB_API_VERSION < 14
+  if (color_space.matrix == VideoColorSpace::MatrixID::INVALID) {
+    sb_media_color_metadata.matrix = kSbMediaMatrixIdUnknown;
+  }
+#endif  // SB_API_VERSION < 14
 
   sb_media_color_metadata.range =
       static_cast<SbMediaRangeId>(color_space.range);
@@ -361,5 +381,35 @@
   return sb_media_color_metadata;
 }
 
+int GetSbMediaVideoBufferBudget(const VideoDecoderConfig* video_config,
+                                const std::string& mime_type) {
+  if (!video_config) {
+    return SbMediaGetVideoBufferBudget(kSbMediaVideoCodecH264, 1920, 1080, 8);
+  }
+
+  auto width = video_config->visible_rect().size().width();
+  auto height = video_config->visible_rect().size().height();
+  auto bits_per_pixel = GetBitsPerPixel(mime_type);
+  auto codec = MediaVideoCodecToSbMediaVideoCodec(video_config->codec());
+  return SbMediaGetVideoBufferBudget(codec, width, height, bits_per_pixel);
+}
+
+std::string ExtractCodecs(const std::string& mime_type) {
+  static const char kCodecs[] = "codecs=";
+
+  // SplitString will also trim the results.
+  std::vector<std::string> tokens = ::base::SplitString(
+      mime_type, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  for (size_t i = 1; i < tokens.size(); ++i) {
+    if (base::strncasecmp(tokens[i].c_str(), kCodecs, strlen(kCodecs))) {
+      continue;
+    }
+    auto codec = tokens[i].substr(strlen(kCodecs));
+    base::TrimString(codec, " \"", &codec);
+    return codec;
+  }
+  LOG(WARNING) << "Failed to find codecs in mime type \"" << mime_type << '\"';
+  return "";
+}
+
 }  // namespace media
-}  // namespace cobalt
diff --git a/third_party/chromium/media/base/starboard_utils.h b/third_party/chromium/media/base/starboard_utils.h
new file mode 100644
index 0000000..fe9febc
--- /dev/null
+++ b/third_party/chromium/media/base/starboard_utils.h
@@ -0,0 +1,64 @@
+// Copyright 2016 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 MEDIA_BASE_STARBOARD_UTILS_H_
+#define MEDIA_BASE_STARBOARD_UTILS_H_
+
+#include "starboard/drm.h"
+#include "starboard/media.h"
+#include "media/base/audio_codecs.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/demuxer_stream.h"
+#include "media/base/video_codecs.h"
+#include "media/base/video_decoder_config.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/gfx/hdr_metadata.h"
+
+namespace media {
+
+SbMediaAudioCodec MediaAudioCodecToSbMediaAudioCodec(AudioCodec codec);
+SbMediaVideoCodec MediaVideoCodecToSbMediaVideoCodec(VideoCodec codec);
+
+SbMediaAudioSampleInfo MediaAudioConfigToSbMediaAudioSampleInfo(
+    const AudioDecoderConfig& audio_decoder_config,
+    const char* mime_type);
+
+DemuxerStream::Type SbMediaTypeToDemuxerStreamType(SbMediaType type);
+SbMediaType DemuxerStreamTypeToSbMediaType(DemuxerStream::Type type);
+
+void FillDrmSampleInfo(const scoped_refptr<DecoderBuffer>& buffer,
+                       SbDrmSampleInfo* drm_info,
+                       SbDrmSubSampleMapping* subsample_mapping);
+
+SbMediaColorMetadata MediaToSbMediaColorMetadata(
+    const VideoColorSpace& color_space,
+    const absl::optional<gfx::HDRMetadata>& hdr_metadata,
+    const std::string& mime_type);
+
+int GetSbMediaVideoBufferBudget(const VideoDecoderConfig* video_config,
+                                const std::string& mime_type);
+
+// Extract the value of "codecs" parameter from |mime_type|. It will return
+// "avc1.42E01E" for `video/mp4; codecs="avc1.42E01E"`.
+// Note that this function assumes that the input is always valid and does
+// minimum validation.
+// This function is exposed as a public function so it can be tested.
+// TODO(b/232559177): Unify the implementations once `MimeType` is moved to
+//                    "starboard/common".
+std::string ExtractCodecs(const std::string& mime_type);
+
+}  // namespace media
+
+#endif  // MEDIA_BASE_STARBOARD_UTILS_H_
diff --git a/third_party/chromium/media/base/starboard_utils_test.cc b/third_party/chromium/media/base/starboard_utils_test.cc
new file mode 100644
index 0000000..ab5460c
--- /dev/null
+++ b/third_party/chromium/media/base/starboard_utils_test.cc
@@ -0,0 +1,59 @@
+// Copyright 2022 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 "media/base/starboard_utils.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+namespace {
+
+TEST(MediaUtil, ExtractCodecs) {
+  // Valid inputs.
+  // Single codec in codecs.
+  EXPECT_EQ(ExtractCodecs("video/mp4; codecs=\"avc1.42E01E\""), "avc1.42E01E");
+  // Double codecs in codecs.
+  EXPECT_EQ(ExtractCodecs("video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\""),
+            "avc1.42E01E, mp4a.40.2");
+  // "codecs" isn't the first attribute.
+  EXPECT_EQ(ExtractCodecs("video/mp4; width=1080; codecs=\"avc1.42E01E\""),
+            "avc1.42E01E");
+  // "codecs" isn't the last attribute.
+  EXPECT_EQ(ExtractCodecs("video/mp4; codecs=\"avc1.42E01E\"; height=1920"),
+            "avc1.42E01E");
+  // Leading space in "codecs".
+  EXPECT_EQ(ExtractCodecs("video/mp4; codecs=\" avc1.42E01E\""), "avc1.42E01E");
+  // Trailing space in "codecs".
+  EXPECT_EQ(ExtractCodecs("video/mp4; codecs=\"avc1.42E01E \""), "avc1.42E01E");
+  // "codecs" is empty (containing only space).
+  EXPECT_EQ(ExtractCodecs("video/mp4; codecs=\" \""), "");
+
+  // Invalid inputs.  There is no expectation on the return value, but the
+  // function should just not crash.
+  // Empty string.
+  ExtractCodecs("");
+  // "codecs=" before the main type.
+  ExtractCodecs("codecs=\"avc1.42E01E\"; video/mp4");
+  // Two "codecs=".
+  ExtractCodecs("codecs=\"avc1.42E01E\"; video/mp4; codecs=\"avc1.42E01E\"");
+  // No ';' after content type.
+  ExtractCodecs("video/mp4 codecs=\"avc1.42E01E\"");
+  // No initial '\"'.
+  ExtractCodecs("video/mp4; codecs=avc1.42E01E\"");
+  // No trailing '\"'.
+  ExtractCodecs("video/mp4; codecs=\"avc1.42E01E");
+}
+
+}  // namespace
+}  // namespace media
diff --git a/third_party/chromium/media/filters/source_buffer_stream.cc b/third_party/chromium/media/filters/source_buffer_stream.cc
index cdd8f4d..b6d4bbc 100644
--- a/third_party/chromium/media/filters/source_buffer_stream.cc
+++ b/third_party/chromium/media/filters/source_buffer_stream.cc
@@ -201,7 +201,8 @@
           base::Milliseconds(kMinimumInterbufferDistanceInMs)),
       memory_limit_(
           GetDemuxerStreamVideoMemoryLimit(Demuxer::DemuxerTypes::kChunkDemuxer,
-                                           &video_config)) {
+                                           &video_config,
+                                           mime_type_)) {
   DCHECK(video_config.IsValidConfig());
   video_configs_.push_back(video_config);
   DVLOG(2) << __func__ << ": video_buffer_size= " << memory_limit_;
@@ -1902,7 +1903,7 @@
   memory_limit_ = std::max(
       memory_limit_,
       GetDemuxerStreamVideoMemoryLimit(Demuxer::DemuxerTypes::kChunkDemuxer,
-                                       &config));
+                                       &config, mime_type_));
 #endif  // defined(STARBOARD)
   return true;
 }
diff --git a/third_party/chromium/media/test/run_all_unittests.cc b/third_party/chromium/media/test/run_all_unittests.cc
index f788643..55dbb7d 100644
--- a/third_party/chromium/media/test/run_all_unittests.cc
+++ b/third_party/chromium/media/test/run_all_unittests.cc
@@ -2,6 +2,24 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#if defined(STARBOARD)
+
+#include "starboard/client_porting/wrap_main/wrap_main.h"
+#include "starboard/event.h"
+#include "starboard/system.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+int InitAndRunAllTests(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
+}  // namespace
+
+STARBOARD_WRAP_SIMPLE_MAIN(InitAndRunAllTests);
+
+#else  // defined(STARBOARD)
+
 #include "base/bind.h"
 #include "base/test/launcher/unit_test_launcher.h"
 #include "base/test/test_discardable_memory_allocator.h"
@@ -56,3 +74,5 @@
       argc, argv,
       base::BindOnce(&TestSuiteNoAtExit::Run, base::Unretained(&test_suite)));
 }
+
+#endif  // defined(STARBOARD)
diff --git a/third_party/crashpad/METADATA b/third_party/crashpad/METADATA
index ab73133..77003f3 100644
--- a/third_party/crashpad/METADATA
+++ b/third_party/crashpad/METADATA
@@ -1,5 +1,3 @@
-# Format: google3/devtools/metadata/metadata.proto (go/google3metadata)
-
 name: "crashpad"
 description:
   "Crashpad is a crash reporter tool created by Chromium. This will allow "
@@ -8,10 +6,6 @@
 
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/third_party/crashpad_mirror"
-  }
-  url {
     type: GIT
     value: "https://chromium.googlesource.com/crashpad/crashpad"
   }
diff --git a/third_party/de265_includes/METADATA b/third_party/de265_includes/METADATA
index 30b29c4..0176fdd 100644
--- a/third_party/de265_includes/METADATA
+++ b/third_party/de265_includes/METADATA
@@ -3,6 +3,7 @@
   "libde265 is an h.265 video codec library; it adds HEVC support to Cobalt. "
   "This directory contains the necessary header files; the include files come "
   "from the installed package."
+
 third_party {
   url {
     type: GIT
diff --git a/third_party/devtools/METADATA b/third_party/devtools/METADATA
index 5274aa8..f1535f7 100644
--- a/third_party/devtools/METADATA
+++ b/third_party/devtools/METADATA
@@ -4,10 +4,6 @@
 
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/third_party/chromium/devtools_mirror"
-  }
-  url {
     type: GIT
     value: "https://chromium.googlesource.com/devtools/devtools-frontend"
   }
diff --git a/third_party/ffmpeg_includes/METADATA b/third_party/ffmpeg_includes/METADATA
index 368b0f4..a9cd962 100644
--- a/third_party/ffmpeg_includes/METADATA
+++ b/third_party/ffmpeg_includes/METADATA
@@ -10,6 +10,7 @@
   "which causes issues when building a binary on one system but running on "
   "another. ffmpeg_includes contains the header files of the different "
   "versions required, so that there are no unresolved dependencies."
+
 third_party {
   # url for ffmpeg
   url {
@@ -25,5 +26,4 @@
   # sub-directory is unknown. The version specified in the sub-directory name
   # corresponds to the version of libavcodec, not the version of ffmpeg/libav.
   license_type: RESTRICTED
-
 }
diff --git a/third_party/flac/METADATA b/third_party/flac/METADATA
index 734dcd0..2451ee6 100644
--- a/third_party/flac/METADATA
+++ b/third_party/flac/METADATA
@@ -4,10 +4,6 @@
 
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/third_party/flac_mirror"
-  }
-  url {
     type: GIT
     value: "https://chromium.googlesource.com/chromium/deps/flac"
   }
diff --git a/third_party/freetype2/METADATA b/third_party/freetype2/METADATA
index 64e86e4..7610988 100644
--- a/third_party/freetype2/METADATA
+++ b/third_party/freetype2/METADATA
@@ -1,14 +1,11 @@
 name: "freetype2"
 description:
   "Font engine, used for decoding and loading fonts. Required by Skia font parsing."
+
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/third_party/freetype2_mirror"
-  }
-  url {
     type: GIT
-    value: "git://git.savannah.gnu.org/freetype/freetype2.git"
+    value: "https://chromium.googlesource.com/chromium/src/third_party/freetype2"
   }
   # Note: version is 2.10.2 release.
   version: "132f19b779828b194b3fede187cee719785db4d8"
diff --git a/third_party/google_benchmark/METADATA b/third_party/google_benchmark/METADATA
index 2f26782..c6ccc74 100644
--- a/third_party/google_benchmark/METADATA
+++ b/third_party/google_benchmark/METADATA
@@ -3,6 +3,7 @@
   "Benchmark is a Google library that benchmarks code snippets, similar to "
   "unit tests. Cobalt currently enables Benchmark on Linux and Android to "
   "benchmark memory and thread usage."
+
 third_party {
   url {
     type: LOCAL_SOURCE
diff --git a/third_party/googletest/METADATA b/third_party/googletest/METADATA
index f4fb390..76cb08b 100644
--- a/third_party/googletest/METADATA
+++ b/third_party/googletest/METADATA
@@ -3,12 +3,13 @@
   "Google Test is Google's C++ test framework. The code in this directory is "
   "currently only a header file used to redirect Chromium gtest_prod consumer "
   "code to where Cobalt puts it, and is only used by V8."
+
 third_party {
   # Note: Because this directory only includes a header file that simply
   # redirects code, there is no "version" or "last_upgrade_date" available.
   url {
     type: GIT
-    value: "https://github.com/google/googletest"
+    value: "https://chromium.googlesource.com/chromium/src/third_party/googletest"
   }
   license_type: NOTICE
 }
diff --git a/third_party/harfbuzz-ng/METADATA b/third_party/harfbuzz-ng/METADATA
index b5f2ab3..2d0df07 100644
--- a/third_party/harfbuzz-ng/METADATA
+++ b/third_party/harfbuzz-ng/METADATA
@@ -2,9 +2,8 @@
 description:
   "HarfBuzz (also formerly known as harfbuzz-ng) is a text shaping library and "
   "a dependency of Cobalt's layout engine."
+
 third_party {
-  # Note: This library was added to Cobalt from Chromium, but the information
-  # below reflects that of the original source code.
   url {
     type: GIT
     value: "https://github.com/harfbuzz/harfbuzz"
diff --git a/third_party/icu/METADATA b/third_party/icu/METADATA
index 010858a..17a9669 100644
--- a/third_party/icu/METADATA
+++ b/third_party/icu/METADATA
@@ -1,14 +1,10 @@
 name: "icu"
 description:
-  "Subtree at third_party/icu."
   "ICU is a library that provides cross-platform support for Unicode and "
   "software internationalization."
+
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/third_party/icu_mirror"
-  }
-  url {
     type: GIT
     value: "https://chromium.googlesource.com/chromium/deps/icu"
   }
diff --git a/third_party/inspector_protocol/METADATA b/third_party/inspector_protocol/METADATA
index e82edec..e06bd94 100644
--- a/third_party/inspector_protocol/METADATA
+++ b/third_party/inspector_protocol/METADATA
@@ -4,10 +4,6 @@
 
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/third_party/chromium/inspector_protocol_mirror"
-  }
-  url {
     type: GIT
     value: "https://chromium.googlesource.com/deps/inspector_protocol"
   }
diff --git a/third_party/jinja2/METADATA b/third_party/jinja2/METADATA
index d7004b1..49daf66 100644
--- a/third_party/jinja2/METADATA
+++ b/third_party/jinja2/METADATA
@@ -2,12 +2,11 @@
 description:
   "jinja is a templating language for Python. It is a dependency for Cobalt's "
   "IDL parser."
+
 third_party {
-  # Note: This library was added to Cobalt from Chromium, but the information
-  # below reflects that of the original source code.
   url {
-    type: ARCHIVE
-    value: "https://pypi.python.org/packages/source/J/Jinja2/Jinja2-2.7.1.tar.gz"
+    type: GIT
+    value: "https://chromium.googlesource.com/chromium/src/third_party/jinja2"
   }
   last_upgrade_date {
     year: 2013
diff --git a/third_party/libdav1d/METADATA b/third_party/libdav1d/METADATA
index 0c9b304..c6ed72c 100644
--- a/third_party/libdav1d/METADATA
+++ b/third_party/libdav1d/METADATA
@@ -1,7 +1,7 @@
 name: "libdav1d"
 description:
-  "Subtree at third_party/libdav1d."
   "dav1d is an AV1 cross-platform decoder focused on speed and correctness."
+
 third_party {
   url {
     type: LOCAL_SOURCE
diff --git a/third_party/libevent/METADATA b/third_party/libevent/METADATA
index b9adb7c..9e0ca2c 100644
--- a/third_party/libevent/METADATA
+++ b/third_party/libevent/METADATA
@@ -2,9 +2,8 @@
 description:
   "libevent is a library that is used for asynchronous event notification "
   "in Cobalt."
+
 third_party {
-  # Note: This library was added to Cobalt from Chromium, but the information
-  # below reflects that of the original source code.
   url {
     type: GIT
     value: "https://github.com/libevent/libevent.git"
diff --git a/third_party/libjpeg-turbo/METADATA b/third_party/libjpeg-turbo/METADATA
index 81d7ca0..093fff0 100644
--- a/third_party/libjpeg-turbo/METADATA
+++ b/third_party/libjpeg-turbo/METADATA
@@ -2,6 +2,7 @@
 description:
   "libjpeg-turbo is a SIMD-accelerated libjpeg-compatible JPEG codec library. "
   "It was added to Cobalt to eventually enable screencasting for DevTools."
+
 third_party {
   url {
     type: GIT
diff --git a/third_party/libpng/METADATA b/third_party/libpng/METADATA
index feddcf2..6466c6a 100644
--- a/third_party/libpng/METADATA
+++ b/third_party/libpng/METADATA
@@ -2,6 +2,7 @@
 description:
   "libpng is the official PNG reference library. Cobalt uses libpng for "
   "encoding and decoding PNG images."
+
 third_party {
   url {
     type: GIT
diff --git a/third_party/libvpx/METADATA b/third_party/libvpx/METADATA
index 8173d88..5bc0959 100644
--- a/third_party/libvpx/METADATA
+++ b/third_party/libvpx/METADATA
@@ -1,13 +1,9 @@
 name: "libvpx"
 description:
-  "Subtree at third_party/libvpx."
   "libvpx is a WebM VP8/VP9 codec SDK."
+
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/third_party/libvpx_mirror"
-  }
-  url {
     type: GIT
     value: "https://chromium.googlesource.com/webm/libvpx"
   }
diff --git a/third_party/libxml/METADATA b/third_party/libxml/METADATA
index 7600572..230f364 100644
--- a/third_party/libxml/METADATA
+++ b/third_party/libxml/METADATA
@@ -5,7 +5,7 @@
 third_party {
   url {
     type: GIT
-    value: "https://gitlab.gnome.org/GNOME/libxml2"
+    value: "https://chromium.googlesource.com/chromium/src/third_party/libxml"
   }
   # NOTE: This is @v2.9.10 release tag, which does not match the repo
   # version exactly, because of various intermediate Chromium changes
diff --git a/third_party/linux-syscall-support/METADATA b/third_party/linux-syscall-support/METADATA
index 375e633..8a51058 100644
--- a/third_party/linux-syscall-support/METADATA
+++ b/third_party/linux-syscall-support/METADATA
@@ -5,10 +5,6 @@
 
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/third_party/linux-syscall-support_mirror"
-  }
-  url {
     type: GIT
     value: "https://chromium.googlesource.com/linux-syscall-support"
   }
diff --git a/third_party/lz4_lib/METADATA b/third_party/lz4_lib/METADATA
index f9cd0eb..ab2350b 100644
--- a/third_party/lz4_lib/METADATA
+++ b/third_party/lz4_lib/METADATA
@@ -1,8 +1,7 @@
 name: "lz4"
 description:
-  "Subtree at third_party/lz4_lib. Only the lib/ subdirectory is maintained to"
-  "avoid keeping GPLv2 licensed code in our repository. This repository does"
-  "not have any local changes."
+  "Only the lib/ subdirectory is maintained to avoid keeping GPLv2 licensed"
+  "code in our repository. This repository does not have any local changes."
 
 third_party {
   url {
diff --git a/third_party/markupsafe/METADATA b/third_party/markupsafe/METADATA
index 47bc43d..eb33a4a 100644
--- a/third_party/markupsafe/METADATA
+++ b/third_party/markupsafe/METADATA
@@ -2,12 +2,13 @@
 description:
   "MarkupSafe escapes characters so text is safe to use in HTML and XML. It is "
   "a dependency for Cobalt's IDL parser."
+
 third_party {
   # Note: This library was added to Cobalt from Chromium, but the information
   # below reflects that of the original source code.
   url {
     type: GIT
-    value: "https://github.com/mitsuhiko/markupsafe"
+    value: "https://chromium.googlesource.com/chromium/src/third_party/markupsafe"
   }
   version: "cbac3a73c628aed66800e993e3931fcb43f76dd0"
   last_upgrade_date {
diff --git a/third_party/mini_chromium/METADATA b/third_party/mini_chromium/METADATA
index 37eb033..dd2e940 100644
--- a/third_party/mini_chromium/METADATA
+++ b/third_party/mini_chromium/METADATA
@@ -6,10 +6,6 @@
 
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/third_party/mini_chromium_mirror"
-  }
-  url {
     type: GIT
     value: "https://chromium.googlesource.com/chromium/mini_chromium"
   }
diff --git a/third_party/modp_b64/METADATA b/third_party/modp_b64/METADATA
index 34722b8..528af96 100644
--- a/third_party/modp_b64/METADATA
+++ b/third_party/modp_b64/METADATA
@@ -3,12 +3,11 @@
   "modp base64 decoder is part of the stringencoders library and is a high "
   "performance base64 encoder/decoder. Cobalt uses base64 decoding to enable "
   "client apps to perform device authentication."
+
 third_party {
-  # Note: This library was initially added to Cobalt from Chromium. "version"
-  # and "last_upgrade_date" are unknown.
   url {
     type: GIT
-    value: "https://github.com/client9/stringencoders"
+    value: "https://chromium.googlesource.com/chromium/src/third_party/modp_b64"
   }
   license_type: NOTICE
 }
diff --git a/third_party/ply/METADATA b/third_party/ply/METADATA
index 47368bd..a7fa5fc 100644
--- a/third_party/ply/METADATA
+++ b/third_party/ply/METADATA
@@ -2,12 +2,11 @@
 description:
   "PLY is an implementation of lex and yacc parsing tools for Python. It is "
   "a dependency for Cobalt's IDL parser."
+
 third_party {
-  # Note: This library was added to Cobalt from Chromium, but the information
-  # below reflects that of the original source code.
   url {
-    type: ARCHIVE
-    value: "http://www.dabeaz.com/ply/ply-3.4.tar.gz"
+    type: GIT
+    value: "https://chromium.googlesource.com/chromium/src/third_party/ply"
   }
   last_upgrade_date {
     year: 2011
diff --git a/third_party/protobuf/METADATA b/third_party/protobuf/METADATA
index f218dc8..64896ad 100644
--- a/third_party/protobuf/METADATA
+++ b/third_party/protobuf/METADATA
@@ -3,12 +3,13 @@
   "Protocol Buffers are Google's language-neutral, platform-neutral, "
   "extensible mechanism for serializing structured data. Protocol Buffers are "
   "required for Widevine, which is required for EME implementation in Cobalt."
+
 third_party {
   # Note: This library was added to Cobalt from Chromium, but the information
   # below reflects that of the original source code.
   url {
     type: GIT
-    value: "https://github.com/google/protobuf"
+    value: "https://chromium.googlesource.com/chromium/src/third_party/protobuf"
   }
   version: "3470b6895aa659b7559ed678e029a5338e535f14"
   last_upgrade_date {
diff --git a/third_party/pulseaudio_includes/METADATA b/third_party/pulseaudio_includes/METADATA
index bd68f71..b1f95cb 100644
--- a/third_party/pulseaudio_includes/METADATA
+++ b/third_party/pulseaudio_includes/METADATA
@@ -3,6 +3,7 @@
   "PulseAudio is a sound system for POSIX OSes, i.e. a proxy for sound "
   "applications. pulseaudio_includes contains the header files necessary "
   "for the library to be dynamically linked into crosstool builds."
+
 third_party {
   url {
     type: GIT
diff --git a/third_party/skia/METADATA b/third_party/skia/METADATA
index e7ad8a5..a2e052b 100644
--- a/third_party/skia/METADATA
+++ b/third_party/skia/METADATA
@@ -1,6 +1,5 @@
 name: "skia"
 description:
-  "Subtree at third_party/skia."
   "Skia is an open-source graphics library for drawing, geometries, and "
   "images, with both hardware and software support. Cobalt most recently "
   "updated its version of Skia from m61 to m79 because many changes took place "
@@ -9,12 +8,8 @@
 
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/third_party/skia_mirror"
-  }
-  url {
     type: GIT
-    value: "https://skia.googlesource.com/skia"
+    value: "https://chromium.googlesource.com/skia"
   }
   version: "2542bdfcd686c7bfa9f687d80627f8ef468b2838"
   last_upgrade_date {
diff --git a/third_party/skia_next/METADATA b/third_party/skia_next/METADATA
index a146d93..1da1521 100644
--- a/third_party/skia_next/METADATA
+++ b/third_party/skia_next/METADATA
@@ -1,16 +1,11 @@
 name: "skia"
 description:
-  "Subtree at third_party/skia_next/third_party/skia."
   "Skia is an open-source graphics library for drawing, geometries, and "
   "images, with both hardware and software support. Cobalt most recently "
   "updated its version of Skia from m79 to m97."
 
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/third_party/skia_mirror"
-  }
-  url {
     type: GIT
     value: "https://skia.googlesource.com/skia"
   }
diff --git a/third_party/super_fast_hash/METADATA b/third_party/super_fast_hash/METADATA
index d996f91..0f9822a 100644
--- a/third_party/super_fast_hash/METADATA
+++ b/third_party/super_fast_hash/METADATA
@@ -2,9 +2,8 @@
 description:
   "SuperFastHash is a non-cryptographic hashing algorithm that Cobalt uses for "
   "memory tracking."
+
 third_party {
-  # Note: This library was initially added to Cobalt from Chromium. "version"
-  # and "last_upgrade_date" are unknown.
   url {
     type: HOMEPAGE
     value: "http://www.azillionmonkeys.com/qed/hash.html"
diff --git a/third_party/v8/METADATA b/third_party/v8/METADATA
index 607ff60..a886829 100644
--- a/third_party/v8/METADATA
+++ b/third_party/v8/METADATA
@@ -1,16 +1,12 @@
 name: "v8"
 description:
-  "Subtree at v8."
   "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.8"
+
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/v8_mirror"
-  }
-  url {
     type: GIT
     value: "https://chromium.googlesource.com/v8/v8"
   }
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setInterval.html b/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setInterval.html
index 1d7d178..7c0c23c 100644
--- a/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setInterval.html
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setInterval.html
@@ -1,34 +1,18 @@
-<!--
-var interval1 = setInterval(function() {
-  clearInterval(interval1);
-  postMessage(1);
-  throw new Error();
-}, 10);
-close();
-var interval2 = setInterval(function() {
-  clearInterval(interval2);
-  postMessage(1);
-  throw new Error();
-}, 10);
-/*
--->
+<!-- Pulled from chromium at commit: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/external/wpt/workers/interfaces/WorkerGlobalScope/close/setInterval.html;drc=5fe256ab5e5eb3d73d23ab52c69ba113145d921b -->
 <!doctype html>
 <title>close() and setInterval</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id="log"></div>
 <script>
-var worker = new Worker('#');
-worker.onmessage = function(e) {
-  assert_unreached("Got message");
-};
-worker.onerror = function(e) {
-  assert_unreached("Got error");
-};
-setTimeout(done, 2000);
+  setup({ single_test: true });
+
+  var worker = new Worker('setInterval.js');
+  worker.onmessage = function (e) {
+    assert_unreached("Got message");
+  };
+  worker.onerror = function (e) {
+    assert_unreached("Got error");
+  };
+  setTimeout(done, 2000);
 </script>
-<!--
-*/
-//-->
-
-
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setInterval.js b/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setInterval.js
new file mode 100644
index 0000000..5b9186d
--- /dev/null
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setInterval.js
@@ -0,0 +1,11 @@
+var interval1 = setInterval(function () {
+  clearInterval(interval1);
+  postMessage('1');
+  throw new Error();
+}, 10);
+close();
+var interval2 = setInterval(function () {
+  clearInterval(interval2);
+  postMessage('1');
+  throw new Error();
+}, 10);
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setTimeout.html b/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setTimeout.html
index c2fa10d..9fb3bca 100644
--- a/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setTimeout.html
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setTimeout.html
@@ -1,28 +1,18 @@
-<!--
-function x() {
-  postMessage(1);
-  throw new Error();
-}
-setTimeout(x, 0);
-close();
-setTimeout(x, 0);
-/*
--->
+<!-- Pulled from chromium at commit: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/external/wpt/workers/interfaces/WorkerGlobalScope/close/setTimeout.html;drc=5fe256ab5e5eb3d73d23ab52c69ba113145d921b -->
 <!doctype html>
 <title>close() and setTimeout</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id="log"></div>
 <script>
-var worker = new Worker('#');
-worker.onmessage = function(e) {
-  assert_unreached("Got message");
-};
-worker.onerror = function(e) {
-  assert_unreached("Got error");
-};
-setTimeout(done, 2000);
+  setup({ single_test: true });
+
+  var worker = new Worker('setTimeout.js');
+  worker.onmessage = function (e) {
+    assert_unreached("Got message");
+  };
+  worker.onerror = function (e) {
+    assert_unreached("Got error");
+  };
+  setTimeout(done, 2000);
 </script>
-<!--
-*/
-//-->
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setTimeout.js b/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setTimeout.js
new file mode 100644
index 0000000..d409a5d
--- /dev/null
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerGlobalScope/close/setTimeout.js
@@ -0,0 +1,7 @@
+function x() {
+  postMessage('1');
+  throw new Error();
+}
+setTimeout(x, 0);
+close();
+setTimeout(x, 0);
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/001.html b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/001.html
index a808975..6bb6a54 100644
--- a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/001.html
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/001.html
@@ -1,23 +1,15 @@
-<!--
-setTimeout(function() { postMessage(1) }, 10);
-/*
--->
+<!-- Pulled from chromium at commit: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/external/wpt/workers/interfaces/WorkerUtils/WindowTimers/001.html;drc=e5b8fefe2aae5674dc00817d444a50f67bdb6ea0 -->
 <!doctype html>
 <title>setTimeout</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id=log></div>
 <script>
-async_test(function() {
-  var worker = new Worker('#');
-  worker.onmessage = this.step_func(function(e) {
-    assert_equals(e.data, 1);
-    this.done();
+  async_test(function () {
+    var worker = new Worker('001.js');
+    worker.onmessage = this.step_func(function (e) {
+      assert_equals(e.data, '1');
+      this.done();
+    });
   });
-});
 </script>
-<!--
-*/
-//-->
-
-
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/001.js b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/001.js
new file mode 100644
index 0000000..7b57771
--- /dev/null
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/001.js
@@ -0,0 +1 @@
+setTimeout(function () { postMessage('1') }, 10);
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/002.html b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/002.html
index 06685a9..81880a6 100644
--- a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/002.html
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/002.html
@@ -1,21 +1,14 @@
-<!--
-var t = setTimeout(function() { postMessage(1); }, 10);
-clearTimeout(t);
-/*
--->
+<!-- Pulled from chromium at commit: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/external/wpt/workers/interfaces/WorkerUtils/WindowTimers/002.html;drc=e5b8fefe2aae5674dc00817d444a50f67bdb6ea0 -->
 <!doctype html>
 <title>clearTimeout</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id=log></div>
 <script>
-async_test(function() {
-  var worker = new Worker('#');
-  var gotMessage = false;
-  worker.onmessage = function() { gotMessage = true; };
-  setTimeout(this.step_func(function() { assert_false(gotMessage); this.done(); }), 100);
-});
+  async_test(function () {
+    var worker = new Worker('002.js');
+    var gotMessage = false;
+    worker.onmessage = function () { gotMessage = true; };
+    setTimeout(this.step_func(function () { assert_false(gotMessage); this.done(); }), 100);
+  });
 </script>
-<!--
-*/
-//-->
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/002.js b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/002.js
new file mode 100644
index 0000000..fa4afe5
--- /dev/null
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/002.js
@@ -0,0 +1,2 @@
+var t = setTimeout(function () { postMessage('1'); }, 10);
+clearTimeout(t);
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/003.html b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/003.html
index 942f139..e588d85 100644
--- a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/003.html
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/003.html
@@ -1,22 +1,15 @@
-<!--
-setInterval(function() { postMessage(1); }, 10);
-/*
--->
+<!-- Pulled from chromium at commit: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/external/wpt/workers/interfaces/WorkerUtils/WindowTimers/003.html;drc=e5b8fefe2aae5674dc00817d444a50f67bdb6ea0 -->
 <!doctype html>
 <title>setInterval</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id=log></div>
 <script>
-async_test(function() {
-  var worker = new Worker('#');
-  worker.onmessage = this.step_func(function(e) {
-    assert_equals(e.data, 1);
-    this.done();
+  async_test(function () {
+    var worker = new Worker('003.js');
+    worker.onmessage = this.step_func(function (e) {
+      assert_equals(e.data, '1');
+      this.done();
+    });
   });
-});
 </script>
-<!--
-*/
-//-->
-
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/003.js b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/003.js
new file mode 100644
index 0000000..cd4e60e
--- /dev/null
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/003.js
@@ -0,0 +1,3 @@
+var interval = setInterval(function () {
+  postMessage('1');
+}, 10);
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/004.html b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/004.html
index 5548eec..a91763b 100644
--- a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/004.html
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/004.html
@@ -1,23 +1,14 @@
-<!--
-var t = setInterval(function() {
-  postMessage(1);
-}, 10);
-clearInterval(t);
-/*
--->
+<!-- Pulled from chromium at commit: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/external/wpt/workers/interfaces/WorkerUtils/WindowTimers/004.html;drc=e5b8fefe2aae5674dc00817d444a50f67bdb6ea0 -->
 <!doctype html>
 <title>clearInterval</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id=log></div>
 <script>
-async_test(function() {
-  var worker = new Worker('#');
-  var i = 0;
-  worker.onmessage = function() { i++; }
-  setTimeout(this.step_func(function() { assert_equals(i, 0); this.done(); }), 100);
-});
+  async_test(function () {
+    var worker = new Worker('004.js');
+    var i = 0;
+    worker.onmessage = function () { i++; }
+    setTimeout(this.step_func(function () { assert_equals(i, 0); this.done(); }), 100);
+  });
 </script>
-<!--
-*/
-//-->
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/004.js b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/004.js
new file mode 100644
index 0000000..ac113a3
--- /dev/null
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/004.js
@@ -0,0 +1,4 @@
+var t = setInterval(function () {
+  postMessage('1');
+}, 10);
+clearInterval(t);
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/005.html b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/005.html
new file mode 100644
index 0000000..a1e5044
--- /dev/null
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/005.html
@@ -0,0 +1,15 @@
+<!-- Pulled from chromium at commit: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/external/wpt/workers/interfaces/WorkerUtils/WindowTimers/005.html;drc=e5b8fefe2aae5674dc00817d444a50f67bdb6ea0 -->
+<!doctype html>
+<title>setInterval when closing</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log style="color: white;"></div>
+<script>
+  async_test(function () {
+    var worker = new Worker('005.js');
+    worker.onmessage = this.step_func(function (e) {
+      assert_equals(e.data, '1');
+      this.done();
+    });
+  });
+</script>
diff --git a/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/005.js b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/005.js
new file mode 100644
index 0000000..215d311
--- /dev/null
+++ b/third_party/web_platform_tests/workers/interfaces/WorkerUtils/WindowTimers/005.js
@@ -0,0 +1,3 @@
+self.close();
+var t = setInterval(function () { }, 10);
+postMessage(t);
diff --git a/third_party/zlib/METADATA b/third_party/zlib/METADATA
index 3993673..6a895f0 100644
--- a/third_party/zlib/METADATA
+++ b/third_party/zlib/METADATA
@@ -1,16 +1,11 @@
 name: "zlib"
 description:
-  "Subtree at third_party/zlib. Zlib compression, required by QUIC, libpng, "
-  "WebSockets and Freetype2."
+  "Zlib compression, required by QUIC, libpng, WebSockets and Freetype2."
 
 third_party {
   url {
-    type: LOCAL_SOURCE
-    value: "/third_party/zlib_mirror"
-  }
-  url {
     type: GIT
-    value: "https://github.com/madler/zlib"
+    value: "https://chromium.googlesource.com/chromium/src/third_party/zlib"
   }
   version: "d6d7cb80b8f35d0ff7478468fc75d87e0e2eda28"
   last_upgrade_date {
diff --git a/tools/idl_parser/idl_lexer_test.py b/tools/idl_parser/idl_lexer_test.py
deleted file mode 100755
index f8d8bb9..0000000
--- a/tools/idl_parser/idl_lexer_test.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2013 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import os
-import unittest
-
-from idl_lexer import IDLLexer
-from idl_ppapi_lexer import IDLPPAPILexer
-
-
-#
-# FileToTokens
-#
-# From a source file generate a list of tokens.
-#
-def FileToTokens(lexer, filename):
-  with open(filename, 'rb') as srcfile:
-    lexer.Tokenize(srcfile.read(), filename)
-    return lexer.GetTokens()
-
-
-#
-# TextToTokens
-#
-# From a source file generate a list of tokens.
-#
-def TextToTokens(lexer, text):
-  lexer.Tokenize(text)
-  return lexer.GetTokens()
-
-
-class WebIDLLexer(unittest.TestCase):
-  def setUp(self):
-    self.lexer = IDLLexer()
-    cur_dir = os.path.dirname(os.path.realpath(__file__))
-    self.filenames = [
-        os.path.join(cur_dir, 'test_lexer/values.in'),
-        os.path.join(cur_dir, 'test_lexer/keywords.in')
-    ]
-
-  #
-  # testRebuildText
-  #
-  # From a set of tokens, generate a new source text by joining with a
-  # single space.  The new source is then tokenized and compared against the
-  # old set.
-  #
-  def testRebuildText(self):
-    for filename in self.filenames:
-      tokens1 = FileToTokens(self.lexer, filename)
-      to_text = '\n'.join(['%s' % t.value for t in tokens1])
-      tokens2 = TextToTokens(self.lexer, to_text)
-
-      count1 = len(tokens1)
-      count2 = len(tokens2)
-      self.assertEqual(count1, count2)
-
-      for i in range(count1):
-        msg = 'Value %s does not match original %s on line %d of %s.' % (
-              tokens2[i].value, tokens1[i].value, tokens1[i].lineno, filename)
-        self.assertEqual(tokens1[i].value, tokens2[i].value, msg)
-
-  #
-  # testExpectedType
-  #
-  # From a set of tokens pairs, verify the type field of the second matches
-  # the value of the first, so that:
-  # integer 123 float 1.1 ...
-  # will generate a passing test, when the first token has both the type and
-  # value of the keyword integer and the second has the type of integer and
-  # value of 123 and so on.
-  #
-  def testExpectedType(self):
-    for filename in self.filenames:
-      tokens = FileToTokens(self.lexer, filename)
-      count = len(tokens)
-      self.assertTrue(count > 0)
-      self.assertFalse(count & 1)
-
-      index = 0
-      while index < count:
-        expect_type = tokens[index].value
-        actual_type = tokens[index + 1].type
-        msg = 'Type %s does not match expected %s on line %d of %s.' % (
-              actual_type, expect_type, tokens[index].lineno, filename)
-        index += 2
-        self.assertEqual(expect_type, actual_type, msg)
-
-
-class PepperIDLLexer(WebIDLLexer):
-  def setUp(self):
-    self.lexer = IDLPPAPILexer()
-    cur_dir = os.path.dirname(os.path.realpath(__file__))
-    self.filenames = [
-        os.path.join(cur_dir, 'test_lexer/values_ppapi.in'),
-        os.path.join(cur_dir, 'test_lexer/keywords_ppapi.in')
-    ]
-
-
-if __name__ == '__main__':
-  unittest.main()
diff --git a/tools/idl_parser/idl_parser_test.py b/tools/idl_parser/idl_parser_test.py
deleted file mode 100755
index 135a394..0000000
--- a/tools/idl_parser/idl_parser_test.py
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2013 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import glob
-import unittest
-
-from idl_lexer import IDLLexer
-from idl_parser import IDLParser, ParseFile
-from idl_ppapi_lexer import IDLPPAPILexer
-from idl_ppapi_parser import IDLPPAPIParser
-
-def ParseCommentTest(comment):
-  comment = comment.strip()
-  comments = comment.split(None, 1)
-  return comments[0], comments[1]
-
-
-class WebIDLParser(unittest.TestCase):
-  def setUp(self):
-    self.parser = IDLParser(IDLLexer(), mute_error=True)
-    self.filenames = glob.glob('test_parser/*_web.idl')
-
-  def _TestNode(self, node):
-    comments = node.GetListOf('Comment')
-    for comment in comments:
-      check, value = ParseCommentTest(comment.GetName())
-      if check == 'BUILD':
-        msg = 'Expecting %s, but found %s.\n' % (value, str(node))
-        self.assertEqual(value, str(node), msg)
-
-      if check == 'ERROR':
-        msg = node.GetLogLine('Expecting\n\t%s\nbut found \n\t%s\n' % (
-                              value, str(node)))
-        self.assertEqual(value, node.GetName(), msg)
-
-      if check == 'PROP':
-        key, expect = value.split('=')
-        actual = str(node.GetProperty(key))
-        msg = 'Mismatched property %s: %s vs %s.\n' % (key, expect, actual)
-        self.assertEqual(expect, actual, msg)
-
-      if check == 'TREE':
-        quick = '\n'.join(node.Tree())
-        lineno = node.GetProperty('LINENO')
-        msg = 'Mismatched tree at line %d:\n%sVS\n%s' % (lineno, value, quick)
-        self.assertEqual(value, quick, msg)
-
-  @unittest.skip("failing ")
-  def testExpectedNodes(self):
-    for filename in self.filenames:
-      filenode = ParseFile(self.parser, filename)
-      children = filenode.GetChildren()
-      self.assertTrue(len(children) > 2, 'Expecting children in %s.' %
-                      filename)
-
-      for node in filenode.GetChildren()[2:]:
-        self._TestNode(node)
-
-@unittest.skip("Not supported in Cobalt")
-class PepperIDLParser(unittest.TestCase):
-  def setUp(self):
-    self.parser = IDLPPAPIParser(IDLPPAPILexer(), mute_error=True)
-    self.filenames = glob.glob('test_parser/*_ppapi.idl')
-
-  def _TestNode(self, filename, node):
-    comments = node.GetListOf('Comment')
-    for comment in comments:
-      check, value = ParseCommentTest(comment.GetName())
-      if check == 'BUILD':
-        msg = '%s - Expecting %s, but found %s.\n' % (
-            filename, value, str(node))
-        self.assertEqual(value, str(node), msg)
-
-      if check == 'ERROR':
-        msg = node.GetLogLine('%s - Expecting\n\t%s\nbut found \n\t%s\n' % (
-                              filename, value, str(node)))
-        self.assertEqual(value, node.GetName(), msg)
-
-      if check == 'PROP':
-        key, expect = value.split('=')
-        actual = str(node.GetProperty(key))
-        msg = '%s - Mismatched property %s: %s vs %s.\n' % (
-                              filename, key, expect, actual)
-        self.assertEqual(expect, actual, msg)
-
-      if check == 'TREE':
-        quick = '\n'.join(node.Tree())
-        lineno = node.GetProperty('LINENO')
-        msg = '%s - Mismatched tree at line %d:\n%sVS\n%s' % (
-                              filename, lineno, value, quick)
-        self.assertEqual(value, quick, msg)
-
-  def testExpectedNodes(self):
-    for filename in self.filenames:
-      filenode = ParseFile(self.parser, filename)
-      children = filenode.GetChildren()
-      self.assertTrue(len(children) > 2, 'Expecting children in %s.' %
-                      filename)
-
-      for node in filenode.GetChildren()[2:]:
-        self._TestNode(filename, node)
-
-if __name__ == '__main__':
-  unittest.main(verbosity=2)
-
diff --git a/tools/idl_parser/idl_ppapi_lexer.py b/tools/idl_parser/idl_ppapi_lexer.py
deleted file mode 100755
index a13c4e4..0000000
--- a/tools/idl_parser/idl_ppapi_lexer.py
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2013 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-""" Lexer for PPAPI IDL
-
-The lexer uses the PLY library to build a tokenizer which understands both
-WebIDL and Pepper tokens.
-
-WebIDL, and WebIDL regular expressions can be found at:
-   http://heycam.github.io/webidl/
-PLY can be found at:
-   http://www.dabeaz.com/ply/
-"""
-
-from idl_lexer import IDLLexer
-
-
-#
-# IDL PPAPI Lexer
-#
-class IDLPPAPILexer(IDLLexer):
-  # Token definitions
-  #
-  # These need to be methods for lexer construction, despite not using self.
-  # pylint: disable=R0201
-
-  # Special multi-character operators
-  def t_LSHIFT(self, t):
-    r'<<'
-    return t
-
-  def t_RSHIFT(self, t):
-    r'>>'
-    return t
-
-  def t_INLINE(self, t):
-    r'\#inline (.|\n)*?\#endinl.*'
-    self.AddLines(t.value.count('\n'))
-    return t
-
-  # Return a "preprocessor" inline block
-  def __init__(self):
-    IDLLexer.__init__(self)
-    self._AddTokens(['INLINE', 'LSHIFT', 'RSHIFT'])
-    self._AddKeywords(['label', 'struct'])
-
-    # Add number types
-    self._AddKeywords(['char', 'int8_t', 'int16_t', 'int32_t', 'int64_t'])
-    self._AddKeywords(['uint8_t', 'uint16_t', 'uint32_t', 'uint64_t'])
-    self._AddKeywords(['double_t', 'float_t'])
-
-    # Add handle types
-    self._AddKeywords(['handle_t', 'PP_FileHandle'])
-
-    # Add pointer types (void*, char*, const char*, const void*)
-    self._AddKeywords(['mem_t', 'str_t', 'cstr_t', 'interface_t'])
-
-    # Remove JS types
-    self._DelKeywords(['boolean', 'byte', 'ByteString', 'Date', 'DOMString',
-                       'double', 'float', 'long', 'object', 'octet', 'Promise',
-                       'record', 'RegExp', 'short', 'unsigned', 'USVString'])
-
-
-# If run by itself, attempt to build the lexer
-if __name__ == '__main__':
-  lexer = IDLPPAPILexer()
-  lexer.Tokenize(open('test_parser/inline_ppapi.idl').read())
-  for tok in lexer.GetTokens():
-    print '\n' + str(tok)
diff --git a/tools/idl_parser/idl_ppapi_parser.py b/tools/idl_parser/idl_ppapi_parser.py
deleted file mode 100755
index 2556ffb..0000000
--- a/tools/idl_parser/idl_ppapi_parser.py
+++ /dev/null
@@ -1,329 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2013 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-""" Parser for PPAPI IDL """
-
-#
-# IDL Parser
-#
-# The parser is uses the PLY yacc library to build a set of parsing rules based
-# on WebIDL.
-#
-# WebIDL, and WebIDL grammar can be found at:
-#   http://heycam.github.io/webidl/
-# PLY can be found at:
-#   http://www.dabeaz.com/ply/
-#
-# The parser generates a tree by recursively matching sets of items against
-# defined patterns.  When a match is made, that set of items is reduced
-# to a new item.   The new item can provide a match for parent patterns.
-# In this way an AST is built (reduced) depth first.
-#
-
-#
-# Disable check for line length and Member as Function due to how grammar rules
-# are defined with PLY
-#
-# pylint: disable=R0201
-# pylint: disable=C0301
-
-import sys
-
-from idl_ppapi_lexer import IDLPPAPILexer
-from idl_parser import IDLParser, ListFromConcat, ParseFile
-from idl_node import IDLNode
-
-class IDLPPAPIParser(IDLParser):
-#
-# We force all input files to start with two comments.  The first comment is a
-# Copyright notice followed by a file comment and finally by file level
-# productions.
-#
-  # [0] Insert a TOP definition for Copyright and Comments
-  def p_Top(self, p):
-    """Top : COMMENT COMMENT Definitions"""
-    Copyright = self.BuildComment('Copyright', p, 1)
-    Filedoc = self.BuildComment('Comment', p, 2)
-    p[0] = ListFromConcat(Copyright, Filedoc, p[3])
-
-#
-#The parser is based on the WebIDL standard.  See:
-# http://heycam.github.io/webidl/#idl-grammar
-#
-  # [1]
-  def p_Definitions(self, p):
-    """Definitions : ExtendedAttributeList Definition Definitions
-           | """
-    if len(p) > 1:
-      p[2].AddChildren(p[1])
-      p[0] = ListFromConcat(p[2], p[3])
-
-      # [2] Add INLINE definition
-  def p_Definition(self, p):
-    """Definition : CallbackOrInterface
-                  | Struct
-                  | Partial
-                  | Dictionary
-                  | Exception
-                  | Enum
-                  | Typedef
-                  | ImplementsStatement
-                  | Label
-                  | Inline"""
-    p[0] = p[1]
-
-  def p_Inline(self, p):
-    """Inline : INLINE"""
-    words = p[1].split()
-    name = self.BuildAttribute('NAME', words[1])
-    lines = p[1].split('\n')
-    value = self.BuildAttribute('VALUE', '\n'.join(lines[1:-1]) + '\n')
-    children = ListFromConcat(name, value)
-    p[0] = self.BuildProduction('Inline', p, 1, children)
-
-#
-# Label
-#
-# A label is a special kind of enumeration which allows us to go from a
-# set of version numbrs to releases
-#
-  def p_Label(self, p):
-    """Label : LABEL identifier '{' LabelList '}' ';'"""
-    p[0] = self.BuildNamed('Label', p, 2, p[4])
-
-  def p_LabelList(self, p):
-    """LabelList : identifier '=' float LabelCont"""
-    val  = self.BuildAttribute('VALUE', p[3])
-    label = self.BuildNamed('LabelItem', p, 1, val)
-    p[0] = ListFromConcat(label, p[4])
-
-  def p_LabelCont(self, p):
-    """LabelCont : ',' LabelList
-                 |"""
-    if len(p) > 1:
-      p[0] = p[2]
-
-  def p_LabelContError(self, p):
-    """LabelCont : error LabelCont"""
-    p[0] = p[2]
-
-  # [5.1] Add "struct" style interface
-  def p_Struct(self, p):
-    """Struct : STRUCT identifier Inheritance '{' StructMembers '}' ';'"""
-    p[0] = self.BuildNamed('Struct', p, 2, ListFromConcat(p[3], p[5]))
-
-  def p_StructMembers(self, p):
-    """StructMembers : StructMember StructMembers
-                     |"""
-    if len(p) > 1:
-      p[0] = ListFromConcat(p[1], p[2])
-
-  def p_StructMember(self, p):
-    """StructMember : ExtendedAttributeList Type identifier ';'"""
-    p[0] = self.BuildNamed('Member', p, 3, ListFromConcat(p[1], p[2]))
-
-  def p_Typedef(self, p):
-    """Typedef : TYPEDEF ExtendedAttributeListNoComments Type identifier ';'"""
-    p[0] = self.BuildNamed('Typedef', p, 4, ListFromConcat(p[2], p[3]))
-
-  def p_TypedefFunc(self, p):
-    """Typedef : TYPEDEF ExtendedAttributeListNoComments ReturnType identifier '(' ArgumentList ')' ';'"""
-    args = self.BuildProduction('Arguments', p, 5, p[6])
-    p[0] = self.BuildNamed('Callback', p, 4, ListFromConcat(p[2], p[3], args))
-
-  def p_ConstValue(self, p):
-    """ConstValue : integer
-                  | integer LSHIFT integer
-                  | integer RSHIFT integer"""
-    val = str(p[1])
-    if len(p) > 2:
-      val = "%s %s %s" % (p[1], p[2], p[3])
-    p[0] = ListFromConcat(self.BuildAttribute('TYPE', 'integer'),
-                          self.BuildAttribute('VALUE', val))
-
-  def p_ConstValueStr(self, p):
-    """ConstValue : string"""
-    p[0] = ListFromConcat(self.BuildAttribute('TYPE', 'string'),
-                          self.BuildAttribute('VALUE', p[1]))
-
-  # Boolean & Float Literals area already BuildAttributes
-  def p_ConstValueLiteral(self, p):
-    """ConstValue : FloatLiteral
-                  | BooleanLiteral """
-    p[0] = p[1]
-
-  def p_EnumValueList(self, p):
-    """EnumValueList : EnumValue EnumValues"""
-    p[0] = ListFromConcat(p[1], p[2])
-
-  def p_EnumValues(self, p):
-    """EnumValues : ',' EnumValue EnumValues
-                  |"""
-    if len(p) > 1:
-      p[0] = ListFromConcat(p[2], p[3])
-
-  def p_EnumValue(self, p):
-    """EnumValue : ExtendedAttributeList identifier
-                 | ExtendedAttributeList identifier '=' ConstValue"""
-    p[0] = self.BuildNamed('EnumItem', p, 2, p[1])
-    if len(p) > 3:
-      p[0].AddChildren(p[4])
-
-  # Omit PromiseType, as it is a JS type.
-  def p_NonAnyType(self, p):
-    """NonAnyType : PrimitiveType TypeSuffix
-                  | identifier TypeSuffix
-                  | SEQUENCE '<' Type '>' Null"""
-    IDLParser.p_NonAnyType(self, p)
-
-  def p_PrimitiveType(self, p):
-    """PrimitiveType : IntegerType
-                     | UnsignedIntegerType
-                     | FloatType
-                     | HandleType
-                     | PointerType"""
-    if type(p[1]) == str:
-      p[0] = self.BuildNamed('PrimitiveType', p, 1)
-    else:
-      p[0] = p[1]
-
-  def p_PointerType(self, p):
-    """PointerType : STR_T
-                   | MEM_T
-                   | CSTR_T
-                   | INTERFACE_T
-                   | NULL"""
-    p[0] = p[1]
-
-  def p_HandleType(self, p):
-    """HandleType : HANDLE_T
-                  | PP_FILEHANDLE"""
-    p[0] = p[1]
-
-  def p_FloatType(self, p):
-    """FloatType : FLOAT_T
-                 | DOUBLE_T"""
-    p[0] = p[1]
-
-  def p_UnsignedIntegerType(self, p):
-    """UnsignedIntegerType : UINT8_T
-                           | UINT16_T
-                           | UINT32_T
-                           | UINT64_T"""
-    p[0] = p[1]
-
-
-  def p_IntegerType(self, p):
-    """IntegerType : CHAR
-                   | INT8_T
-                   | INT16_T
-                   | INT32_T
-                   | INT64_T"""
-    p[0] = p[1]
-
-  # These targets are no longer used
-  def p_OptionalLong(self, p):
-    """ """
-    pass
-
-  def p_UnrestrictedFloatType(self, p):
-    """ """
-    pass
-
-  def p_null(self, p):
-    """ """
-    pass
-
-  def p_PromiseType(self, p):
-    """ """
-    pass
-
-  def p_EnumValueListComma(self, p):
-    """ """
-    pass
-
-  def p_EnumValueListString(self, p):
-    """ """
-    pass
-
-  def p_StringType(self, p):
-    """ """
-    pass
-
-  def p_RecordType(self, p):
-    """ """
-    pass
-
-  def p_RecordTypeError(self, p):
-    """ """
-    pass
-
-  # We only support:
-  #    [ identifier ]
-  #    [ identifier ( ArgumentList )]
-  #    [ identifier ( ValueList )]
-  #    [ identifier = identifier ]
-  #    [ identifier = ( IdentifierList )]
-  #    [ identifier = ConstValue ]
-  #    [ identifier = identifier ( ArgumentList )]
-  # [51] map directly to 74-77
-  # [52-54, 56] are unsupported
-  def p_ExtendedAttribute(self, p):
-    """ExtendedAttribute : ExtendedAttributeNoArgs
-                         | ExtendedAttributeArgList
-                         | ExtendedAttributeValList
-                         | ExtendedAttributeIdent
-                         | ExtendedAttributeIdentList
-                         | ExtendedAttributeIdentConst
-                         | ExtendedAttributeNamedArgList"""
-    p[0] = p[1]
-
-  def p_ExtendedAttributeValList(self, p):
-    """ExtendedAttributeValList : identifier '(' ValueList ')'"""
-    arguments = self.BuildProduction('Values', p, 2, p[3])
-    p[0] = self.BuildNamed('ExtAttribute', p, 1, arguments)
-
-  def p_ValueList(self, p):
-    """ValueList : ConstValue ValueListCont"""
-    p[0] = ListFromConcat(p[1], p[2])
-
-  def p_ValueListCont(self, p):
-    """ValueListCont : ValueList
-                     |"""
-    if len(p) > 1:
-      p[0] = p[1]
-
-  def p_ExtendedAttributeIdentConst(self, p):
-    """ExtendedAttributeIdentConst : identifier '=' ConstValue"""
-    p[0] = self.BuildNamed('ExtAttribute', p, 1, p[3])
-
-
-  def __init__(self, lexer, verbose=False, debug=False, mute_error=False):
-    IDLParser.__init__(self, lexer, verbose, debug, mute_error)
-
-
-def main(argv):
-  nodes = []
-  parser = IDLPPAPIParser(IDLPPAPILexer())
-  errors = 0
-
-  for filename in argv:
-    filenode = ParseFile(parser, filename)
-    if filenode:
-      errors += filenode.GetProperty('ERRORS')
-      nodes.append(filenode)
-
-  ast = IDLNode('AST', '__AST__', 0, 0, nodes)
-
-  print '\n'.join(ast.Tree(accept_props=['PROD', 'TYPE', 'VALUE']))
-  if errors:
-    print '\nFound %d errors.\n' % errors
-
-
-  return errors
-
-
-if __name__ == '__main__':
-  sys.exit(main(sys.argv[1:]))
diff --git a/tools/idl_parser/run_tests.py b/tools/idl_parser/run_tests.py
deleted file mode 100755
index 878f17e..0000000
--- a/tools/idl_parser/run_tests.py
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2013 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import glob
-import os
-import sys
-import unittest
-
-if __name__ == '__main__':
-  suite = unittest.TestSuite()
-  cur_dir = os.path.dirname(os.path.realpath(__file__))
-  for testname in glob.glob(os.path.join(cur_dir, '*_test.py')):
-    print 'Adding Test: ' + testname
-    module = __import__(os.path.basename(testname)[:-3])
-    suite.addTests(unittest.defaultTestLoader.loadTestsFromModule(module))
-  result = unittest.TextTestRunner(verbosity=2).run(suite)
-  if result.wasSuccessful():
-    sys.exit(0)
-  else:
-    sys.exit(1)
diff --git a/tools/idl_parser/test_lexer/keywords.in b/tools/idl_parser/test_lexer/keywords.in
deleted file mode 100644
index abca990..0000000
--- a/tools/idl_parser/test_lexer/keywords.in
+++ /dev/null
@@ -1,52 +0,0 @@
-ANY any
-ATTRIBUTE attribute
-BOOLEAN boolean
-BYTESTRING ByteString
-BYTE byte
-CALLBACK callback
-CONST const
-CREATOR creator
-DATE Date
-DELETER deleter
-DICTIONARY dictionary
-DOMSTRING DOMString
-DOUBLE double
-ENUM enum
-EXCEPTION exception
-FALSE false
-FLOAT float
-GETTER getter
-IMPLEMENTS implements
-INFINITY Infinity
-INHERIT inherit
-INTERFACE interface
-ITERABLE iterable
-LEGACYCALLER legacycaller
-LEGACYITERABLE legacyiterable
-LONG long
-MAPLIKE maplike
-NAN Nan
-NULL null
-OBJECT object
-OCTET octet
-OPTIONAL optional
-OR or
-PARTIAL partial
-PROMISE Promise
-READONLY readonly
-RECORD record
-REGEXP RegExp
-REQUIRED required
-SEQUENCE sequence
-SERIALIZER serializer
-SETLIKE setlike
-SETTER setter
-SHORT short
-STATIC static
-STRINGIFIER stringifier
-TYPEDEF typedef
-TRUE true
-UNSIGNED unsigned
-UNRESTRICTED unrestricted
-USVSTRING USVString
-VOID void
diff --git a/tools/idl_parser/test_lexer/keywords_ppapi.in b/tools/idl_parser/test_lexer/keywords_ppapi.in
deleted file mode 100644
index 62567e4..0000000
--- a/tools/idl_parser/test_lexer/keywords_ppapi.in
+++ /dev/null
@@ -1,44 +0,0 @@
-ANY any
-ATTRIBUTE attribute
-CALLBACK callback
-CONST const
-CREATOR creator
-DELETER deleter
-DICTIONARY dictionary
-FALSE false
-EXCEPTION exception
-GETTER getter
-IMPLEMENTS implements
-INFINITY Infinity
-INTERFACE interface
-LABEL label
-LEGACYCALLER legacycaller
-NAN Nan
-NULL null
-OPTIONAL optional
-OR or
-PARTIAL partial
-READONLY readonly
-SETTER setter
-STATIC static
-STRINGIFIER stringifier
-TYPEDEF typedef
-TRUE true
-VOID void
-CHAR char
-INT8_T int8_t
-INT16_T int16_t
-INT32_T int32_t
-INT64_T int64_t
-UINT8_T uint8_t
-UINT16_T uint16_t
-UINT32_T uint32_t
-UINT64_T uint64_t
-DOUBLE_T double_t
-FLOAT_T float_t
-MEM_T mem_t
-STR_T str_t
-CSTR_T cstr_t
-INTERFACE_T interface_t
-HANDLE_T handle_t
-PP_FILEHANDLE PP_FileHandle
\ No newline at end of file
diff --git a/tools/idl_parser/test_lexer/values.in b/tools/idl_parser/test_lexer/values.in
deleted file mode 100644
index be714d0..0000000
--- a/tools/idl_parser/test_lexer/values.in
+++ /dev/null
@@ -1,55 +0,0 @@
-integer 1 integer 123 integer 12345
-identifier A123 identifier A_A
-
-COMMENT /*XXXX*/
-COMMENT //XXXX
-
-COMMENT /*MULTI LINE*/
-
-[ [
-] ]
-* *
-. .
-( (
-) )
-{ {
-} }
-[ [
-] ]
-, ,
-; ;
-: :
-= =
-+ +
-- -
-/ /
-~ ~
-| |
-& &
-^ ^
-> >
-< <
-
-ELLIPSIS ...
-
-float 1.1
-float 1e1
-float -1.1
-float -1e1
-float 1e-1
-float -1e-1
-float 1.0e1
-float -1.0e-1
-
-integer 00
-integer 01
-integer 0123
-integer 01234567
-integer 123
-integer 1234567890
-integer 0x123
-integer 0X123
-integer 0x1234567890AbCdEf
-integer 0X1234567890aBcDeF
-
-identifier blah
diff --git a/tools/idl_parser/test_lexer/values_ppapi.in b/tools/idl_parser/test_lexer/values_ppapi.in
deleted file mode 100644
index 33fa577..0000000
--- a/tools/idl_parser/test_lexer/values_ppapi.in
+++ /dev/null
@@ -1,50 +0,0 @@
-integer 1 integer 123 integer 12345
-identifier A123 identifier A_A
-
-COMMENT /*XXXX*/
-COMMENT //XXXX
-
-COMMENT /*MULTI LINE*/
-
-[ [
-] ]
-* *
-. .
-( (
-) )
-{ {
-} }
-[ [
-] ]
-, ,
-; ;
-: :
-= =
-+ +
-- -
-/ /
-~ ~
-| |
-& &
-^ ^
-> >
-< <
-
-LSHIFT <<
-RSHIFT >>
-ELLIPSIS ...
-
-float 1.1
-float 1e1
-float -1.1
-float -1e1
-float 1e-1
-float -1e-1
-float 1.0e1
-float -1.0e-1
-
-integer 00
-integer 01
-integer 0123
-
-identifier blah
diff --git a/tools/idl_parser/test_parser/callback_web.idl b/tools/idl_parser/test_parser/callback_web.idl
deleted file mode 100644
index b16b6b5..0000000
--- a/tools/idl_parser/test_parser/callback_web.idl
+++ /dev/null
@@ -1,116 +0,0 @@
-/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
-   Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file. */
-
-/* Test Callback productions
-
-Run with --test to generate an AST and verify that all comments accurately
-reflect the state of the Nodes.
-
-BUILD Type(Name)
-This comment signals that a node of type <Type> is created with the
-name <Name>.
-
-ERROR Error String
-This comment signals that a error of <Error String> is generated.  The error
-is not assigned to a node, but are expected in order.
-
-PROP Key=Value
-This comment signals that a property has been set on the Node such that
-<Key> = <Value>.
-
-TREE
-Type(Name)
-  Type(Name)
-  Type(Name)
-    Type(Name)
-    ...
-This comment signals that a tree of nodes matching the BUILD comment
-symatics should exist.  This is an exact match.
-*/
-
-
-/* TREE
- *Callback(VoidFunc)
- *  Type()
- *    PrimitiveType(void)
- *  Arguments()
- */
-callback VoidFunc = void();
-
-/* TREE
- *Callback(VoidFuncLongErr)
- *  Type()
- *    PrimitiveType(void)
- *  Arguments()
- *    Error(Unexpected ).)
- */
-callback VoidFuncLongErr = void ( long );
-
-/* TREE
- *Callback(VoidFuncLong)
- *  Type()
- *    PrimitiveType(void)
- *  Arguments()
- *    Argument(L1)
- *      Type()
- *        PrimitiveType(long)
- */
-callback VoidFuncLong = void ( long L1 );
-
-/* TREE
- *Callback(VoidFuncLongArray)
- *  Type()
- *    PrimitiveType(void)
- *  Arguments()
- *    Argument(L1)
- *      Type()
- *        PrimitiveType(long)
- *        Array()
- */
-callback VoidFuncLongArray = void ( long[] L1 );
-
-/* TREE
- *Callback(VoidFuncLongArray5)
- *  Type()
- *    PrimitiveType(void)
- *  Arguments()
- *    Argument(L1)
- *      Type()
- *        PrimitiveType(long)
- *        Array(5)
- */
-callback VoidFuncLongArray5 = void ( long[5] L1 );
-
-
-/* TREE
- *Callback(VoidFuncLongArray54)
- *  Type()
- *    PrimitiveType(void)
- *  Arguments()
- *    Argument(L1)
- *      Type()
- *        PrimitiveType(long)
- *        Array(5)
- *    Argument(L2)
- *      Type()
- *        PrimitiveType(long long)
- *        Array(4)
- */
-callback VoidFuncLongArray54 = void ( long[5] L1, long long [4] L2 );
-
-
-/* TREE
- *Callback(VoidFuncLongIdent)
- *  Type()
- *    PrimitiveType(void)
- *  Arguments()
- *    Argument(L1)
- *      Type()
- *        PrimitiveType(long)
- *        Array(5)
- *    Argument(L2)
- *      Type()
- *        Typeref(VoidFuncLongArray)
- */
-callback VoidFuncLongIdent = void ( long[5] L1, VoidFuncLongArray L2 );
diff --git a/tools/idl_parser/test_parser/dictionary_web.idl b/tools/idl_parser/test_parser/dictionary_web.idl
deleted file mode 100644
index 8351246..0000000
--- a/tools/idl_parser/test_parser/dictionary_web.idl
+++ /dev/null
@@ -1,118 +0,0 @@
-/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
-   Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file. */
-
-/* Test Dictionary productions
-
-Run with --test to generate an AST and verify that all comments accurately
-reflect the state of the Nodes.
-
-BUILD Type(Name)
-This comment signals that a node of type <Type> is created with the
-name <Name>.
-
-ERROR Error String
-This comment signals that a error of <Error String> is generated.  The error
-is not assigned to a node, but are expected in order.
-
-PROP Key=Value
-This comment signals that a property has been set on the Node such that
-<Key> = <Value>.
-
-TREE
-Type(Name)
-  Type(Name)
-  Type(Name)
-    Type(Name)
-    ...
-This comment signals that a tree of nodes matching the BUILD comment
-symatics should exist.  This is an exact match.
-*/
-
-
-/* TREE
- *Dictionary(MyDict)
- */
-dictionary MyDict { };
-
-/* TREE
- *Dictionary(MyDictInherit)
- *  Inherit(Foo)
- */
-dictionary MyDictInherit : Foo {};
-
-/* TREE
- *Dictionary(MyDictPartial)
- */
-partial dictionary MyDictPartial { };
-
-/* ERROR Unexpected ":" after identifier "MyDictInherit". */
-partial dictionary MyDictInherit : Foo {};
-
-/* TREE
- *Dictionary(MyDictBig)
- *  Key(setString)
- *    Type()
- *      StringType(DOMString)
- *    Default(Foo)
- *  Key(setLong)
- *    Type()
- *      PrimitiveType(unsigned long long)
- *    Default(123)
- *  Key(unsetLong)
- *    Type()
- *      PrimitiveType(long)
- */
-dictionary MyDictBig {
-  DOMString setString = "Foo";
-  unsigned long long setLong = 123;
-  long unsetLong;
-};
-
-/* TREE
- *Dictionary(MyDictRequired)
- *  Key(setLong)
- *    Type()
- *      PrimitiveType(long)
- */
-dictionary MyDictRequired {
-  required long setLong;
-};
-
-/* ERROR Unexpected "{" after keyword "dictionary". */
-dictionary {
-  DOMString? setString = null;
-};
-
-/* TREE
- *Dictionary(MyDictionaryInvalidOptional)
- *  Key(mandatory)
- *    Type()
- *      StringType(DOMString)
- *  Error(Unexpected keyword "optional" after ">".)
- */
-dictionary MyDictionaryInvalidOptional {
-  DOMString mandatory;
-  sequence<DOMString> optional;
-};
-
-/* ERROR Unexpected identifier "NoColon" after identifier "ForParent". */
-dictionary ForParent NoColon {
-  DOMString? setString = null;
-};
-
-/* TREE
- *Dictionary(MyDictNull)
- *  Key(setString)
- *    Type()
- *      StringType(DOMString)
- *    Default(NULL)
- */
-dictionary MyDictNull {
-  DOMString? setString = null;
-};
-
-/* ERROR Unexpected keyword "attribute" after "{". */
-dictionary MyDictUnexpectedAttribute {
-  attribute DOMString foo = "";
-};
diff --git a/tools/idl_parser/test_parser/enum_ppapi.idl b/tools/idl_parser/test_parser/enum_ppapi.idl
deleted file mode 100644
index 1b088b8..0000000
--- a/tools/idl_parser/test_parser/enum_ppapi.idl
+++ /dev/null
@@ -1,126 +0,0 @@
-/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
-   Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file. */
-
-/* Test Enum productions
-
-Run with --test to generate an AST and verify that all comments accurately
-reflect the state of the Nodes.
-
-BUILD Type(Name)
-This comment signals that a node of type <Type> is created with the
-name <Name>.
-
-ERROR Error String
-This comment signals that a error of <Error String> is generated.  The error
-is not assigned to a node, but are expected in order.
-
-PROP Key=Value
-This comment signals that a property has been set on the Node such that
-<Key> = <Value>.
-
-TREE
-Type(Name)
-  Type(Name)
-  Type(Name)
-    Type(Name)
-    ...
-This comment signals that a tree of nodes matching the BUILD comment
-symatics should exist.  This is an exact match.
-*/
-
-/* TREE
- *Enum(MealType1)
- *  EnumItem(rice)
- *  EnumItem(noodles)
- *  EnumItem(other)
-*/
-enum MealType1 {
-  /* BUILD EnumItem (rice) */
-  rice,
-  /* BUILD EnumItem (noodles) */
-  noodles,
-  /* BUILD EnumItem(other) */
-  other
-};
-
-/* BUILD Error(Enum missing name.) */
-/* ERROR Enum missing name. */
-enum {
-  rice,
-  noodles,
-  other,
-};
-
-/* TREE
- *Enum(MealType2)
- *  EnumItem(rice)
- *  EnumItem(noodles)
- *  EnumItem(other)
-*/
-enum MealType2 {
-  /* BUILD EnumItem(rice) */
-  rice,
-  /* BUILD EnumItem(noodles) */
-  noodles = 1,
-  /* BUILD EnumItem(other) */
-  other
-};
-
-/* BUILD Error(Unexpected identifier "noodles" after identifier "rice".) */
-/* ERROR Unexpected identifier "noodles" after identifier "rice". */
-enum MissingComma {
-  rice
-  noodles,
-  other
-};
-
-/* BUILD Error(Trailing comma in block.) */
-/* ERROR Trailing comma in block. */
-enum TrailingComma {
-  rice,
-  noodles,
-  other,
-};
-
-/* BUILD Error(Unexpected "," after ",".) */
-/* ERROR Unexpected "," after ",". */
-enum ExtraComma {
-  rice,
-  noodles,
-  ,other,
-};
-
-/* BUILD Error(Unexpected keyword "interface" after "{".) */
-/* ERROR Unexpected keyword "interface" after "{". */
-enum ExtraComma {
-  interface,
-  noodles,
-  ,other,
-};
-
-/* BUILD Error(Unexpected string "somename" after "{".) */
-/* ERROR Unexpected string "somename" after "{". */
-enum ExtraComma {
-  "somename",
-  noodles,
-  other,
-};
-
-/* BUILD Enum(MealType3) */
-enum MealType3 {
-  /* BUILD EnumItem(rice) */
-  rice = 1 << 1,
-  /* BUILD EnumItem(noodles) */
-  noodles = 0x1 << 0x2,
-  /* BUILD EnumItem(other) */
-  other = 012 << 777
-};
-
-/* BUILD Enum(MealType4) */
-enum MealType4 {
-  /* BUILD EnumItem(rice) */
-  rice = true,
-  /* BUILD EnumItem(noodles) */
-  noodles = false
-};
diff --git a/tools/idl_parser/test_parser/enum_web.idl b/tools/idl_parser/test_parser/enum_web.idl
deleted file mode 100644
index e3107c0..0000000
--- a/tools/idl_parser/test_parser/enum_web.idl
+++ /dev/null
@@ -1,123 +0,0 @@
-/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
-   Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file. */
-
-/* Test Enum productions
-
-Run with --test to generate an AST and verify that all comments accurately
-reflect the state of the Nodes.
-
-BUILD Type(Name)
-This comment signals that a node of type <Type> is created with the
-name <Name>.
-
-ERROR Error String
-This comment signals that a error of <Error String> is generated.  The error
-is not assigned to a node, but are expected in order.
-
-PROP Key=Value
-This comment signals that a property has been set on the Node such that
-<Key> = <Value>.
-
-TREE
-Type(Name)
-  Type(Name)
-  Type(Name)
-    Type(Name)
-    ...
-This comment signals that a tree of nodes matching the BUILD comment
-symatics should exist.  This is an exact match.
-*/
-
-/* TREE
- *Enum(MealType1)
- *  EnumItem(rice)
- *  EnumItem(noodles)
- *  EnumItem(other)
-*/
-enum MealType1 {
-  /* BUILD EnumItem (rice) */
-  "rice",
-  /* BUILD EnumItem (noodles) */
-  "noodles",
-  /* BUILD EnumItem(other) */
-  "other"
-};
-
-/* BUILD Error(Enum missing name.) */
-/* ERROR Enum missing name. */
-enum {
-  "rice",
-  "noodles",
-  "other"
-};
-
-/* TREE
- *Enum(MealType2)
- *  EnumItem(rice)
- *  EnumItem(noodles)
- *  EnumItem(other)
-*/
-enum MealType2 {
-  /* BUILD EnumItem(rice) */
-  "rice",
-  /* BUILD EnumItem(noodles) */
-  "noodles",
-  /* BUILD EnumItem(other) */
-  "other"
-};
-
-/* TREE
- *Enum(TrailingComma)
- *  EnumItem(rice)
- *  EnumItem(noodles)
- *  EnumItem(other)
-*/
-enum TrailingComma {
-  "rice",
-  "noodles",
-  "other",
-};
-
-/* BUILD Error(Unexpected string "noodles" after string "rice".) */
-/* ERROR Unexpected string "noodles" after string "rice". */
-enum MissingComma {
-  "rice"
-  "noodles",
-  "other"
-};
-
-/* BUILD Error(Unexpected "," after ",".) */
-/* ERROR Unexpected "," after ",". */
-enum ExtraComma {
-  "rice",
-  "noodles",
-  ,"other",
-};
-
-/* BUILD Error(Unexpected keyword "interface" after "{".) */
-/* ERROR Unexpected keyword "interface" after "{". */
-enum ExtraComma {
-  interface,
-  "noodles",
-  ,"other",
-};
-
-/* BUILD Error(Unexpected identifier "somename" after "{".) */
-/* ERROR Unexpected identifier "somename" after "{". */
-enum ExtraComma {
-  somename,
-  "noodles",
-  ,"other",
-};
-
-/* BUILD Enum(MealType3) */
-enum MealType3 {
-  /* BUILD EnumItem(rice) */
-  "rice",
-  /* BUILD EnumItem(noodles) */
-  "noodles",
-  /* BUILD EnumItem(other) */
-  "other"
-};
-
diff --git a/tools/idl_parser/test_parser/exception_web.idl b/tools/idl_parser/test_parser/exception_web.idl
deleted file mode 100644
index 2e28107..0000000
--- a/tools/idl_parser/test_parser/exception_web.idl
+++ /dev/null
@@ -1,87 +0,0 @@
-/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
-   Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file. */
-
-/* Test Exception productions
-
-Run with --test to generate an AST and verify that all comments accurately
-reflect the state of the Nodes.
-
-BUILD Type(Name)
-This comment signals that a node of type <Type> is created with the
-name <Name>.
-
-ERROR Error String
-This comment signals that a error of <Error String> is generated.  The error
-is not assigned to a node, but are expected in order.
-
-PROP Key=Value
-This comment signals that a property has been set on the Node such that
-<Key> = <Value>.
-
-TREE
-Type(Name)
-  Type(Name)
-  Type(Name)
-    Type(Name)
-    ...
-This comment signals that a tree of nodes matching the BUILD comment
-symatics should exist.  This is an exact match.
-*/
-
-
-/* TREE
- *Exception(MyExc)
- */
-exception MyExc { };
-
-/* TREE
- *Exception(MyExcInherit)
- *  Inherit(Foo)
- */
-exception MyExcInherit : Foo {};
-
-/* ERROR Unexpected keyword "exception" after keyword "partial". */
-partial exception MyExcPartial { };
-
-/* TREE
- *Exception(MyExcBig)
- *  ExceptionField(MyString)
- *    Type()
- *      StringType(DOMString)
- *  Error(Unexpected "=" after identifier "ErrorSetLong".)
- *  ExceptionField(MyLong)
- *    Type()
- *      PrimitiveType(long)
- */
-exception MyExcBig {
-  DOMString MyString;
-  unsigned long long ErrorSetLong = 123;
-  long MyLong;
-};
-
-
-/* ERROR Unexpected "{" after keyword "exception". */
-exception {
-  DOMString? setString = null;
-};
-
-
-/* ERROR Unexpected identifier "NoColon" after identifier "ForParent". */
-exception ForParent NoColon {
-  DOMString? setString = null;
-};
-
-/* TREE
- *Exception(MyExcConst)
- *  Const(setString)
- *    StringType(DOMString)
- *    Value(NULL)
- */
-exception MyExcConst {
-  const DOMString? setString = null;
-};
-
-
-
-
diff --git a/tools/idl_parser/test_parser/extattr_ppapi.idl b/tools/idl_parser/test_parser/extattr_ppapi.idl
deleted file mode 100644
index 07afbc0..0000000
--- a/tools/idl_parser/test_parser/extattr_ppapi.idl
+++ /dev/null
@@ -1,99 +0,0 @@
-/* Copyright 2013 The Chromium Authors. All rights reserved.
-   Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file. */
-
-/* Test ExtendedAttribute productions
-
-Run with --test to generate an AST and verify that all comments accurately
-reflect the state of the Nodes.
-
-BUILD Type(Name)
-This comment signals that a node of type <Type> is created with the
-name <Name>.
-
-ERROR Error String
-This comment signals that a error of <Error String> is generated.  The error
-is not assigned to a node, but are expected in order.
-
-PROP Key=Value
-This comment signals that a property has been set on the Node such that
-<Key> = <Value>.
-
-TREE
-Type(Name)
-  Type(Name)
-  Type(Name)
-    Type(Name)
-    ...
-This comment signals that a tree of nodes matching the BUILD comment
-symatics should exist.  This is an exact match.
-*/
-
-/* TREE
- *Interface(Foo)
- *  ExtAttributes()
- *    ExtAttribute(foo)
- *      Arguments()
- */
-
-[foo()] interface Foo {};
-
-/* TREE
- *Interface(Foo)
- *  ExtAttributes()
- *    ExtAttribute(foo)
- *      Values()
- */
-
-[foo(1)] interface Foo {};
-
-/* TREE
- *Interface(Foo)
- *  ExtAttributes()
- *    ExtAttribute(foo)
- *      Values()
- */
-
-[foo(1 true 1.2e-3)] interface Foo {};
-
-/* TREE
- *Interface(Foo)
- *  ExtAttributes()
- *    ExtAttribute(foo)
- *      Arguments()
- *        Error(Unexpected ).)
- */
-
-[foo(null)] interface Foo {};
-
-/* TREE
- *Interface(Foo)
- *  ExtAttributes()
- *    ExtAttribute(foo)
- */
-
-[foo=1] interface Foo {};
-
-/* TREE
- *Interface(Foo)
- *  ExtAttributes()
- *    ExtAttribute(foo)
- */
-
-[foo=true] interface Foo {};
-
-/* TREE
- *Interface(Foo)
- *  ExtAttributes()
- *    ExtAttribute(foo)
- */
-
-[foo=1.2e-3] interface Foo {};
-
-/* TREE
- *Interface(Foo)
- *  ExtAttributes()
- *    ExtAttribute(foo)
- */
-
-[foo=(bar, baz)] interface Foo {};
diff --git a/tools/idl_parser/test_parser/implements_web.idl b/tools/idl_parser/test_parser/implements_web.idl
deleted file mode 100644
index 252dd4b..0000000
--- a/tools/idl_parser/test_parser/implements_web.idl
+++ /dev/null
@@ -1,52 +0,0 @@
-/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
-   Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file. */
-
-/* Test Implements productions
-
-Run with --test to generate an AST and verify that all comments accurately
-reflect the state of the Nodes.
-
-BUILD Type(Name)
-This comment signals that a node of type <Type> is created with the
-name <Name>.
-
-ERROR Error String
-This comment signals that a error of <Error String> is generated.  The error
-is not assigned to a node, but are expected in order.
-
-PROP Key=Value
-This comment signals that a property has been set on the Node such that
-<Key> = <Value>.
-
-TREE
-Type(Name)
-  Type(Name)
-  Type(Name)
-    Type(Name)
-    ...
-This comment signals that a tree of nodes matching the BUILD comment
-symatics should exist.  This is an exact match.
-*/
-
-/* BUILD Implements(A) */
-/* PROP REFERENCE=B */
-A implements B;
-
-/* ERROR Unexpected ";" after keyword "implements". */
-A implements;
-
-/* BUILD Implements(B) */
-/* PROP REFERENCE=C */
-B implements C;
-
-/* ERROR Unexpected keyword "implements" after "]". */
-[foo] implements B;
-
-/* BUILD Implements(D) */
-/* PROP REFERENCE=E */
-D implements E;
-
-/* ERROR Unexpected keyword "implements" after comment. */
-implements C;
-
diff --git a/tools/idl_parser/test_parser/inline_ppapi.idl b/tools/idl_parser/test_parser/inline_ppapi.idl
deleted file mode 100644
index 134f60d..0000000
--- a/tools/idl_parser/test_parser/inline_ppapi.idl
+++ /dev/null
@@ -1,46 +0,0 @@
-/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
-   Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file. */
-
-/* Test Typedef productions
-
-Run with --test to generate an AST and verify that all comments accurately
-reflect the state of the Nodes.
-
-BUILD Type(Name)
-This comment signals that a node of type <Type> is created with the
-name <Name>.
-
-ERROR Error String
-This comment signals that a error of <Error String> is generated.  The error
-is not assigned to a node, but are expected in order.
-
-PROP Key=Value
-This comment signals that a property has been set on the Node such that
-<Key> = <Value>.
-
-TREE
-Type(Name)
-  Type(Name)
-  Type(Name)
-    Type(Name)
-    ...
-This comment signals that a tree of nodes matching the BUILD comment
-symatics should exist.  This is an exact match.
-*/
-
-/* TREE
- *Inline(C)
- */
-
-#inline C
-This is my block of C code
-#endinl
-
-/* TREE
- *Inline(CC)
- */
-#inline CC
-This is my block of CC code
-#endinl
-
diff --git a/tools/idl_parser/test_parser/interface_web.idl b/tools/idl_parser/test_parser/interface_web.idl
deleted file mode 100644
index c8ec6d2..0000000
--- a/tools/idl_parser/test_parser/interface_web.idl
+++ /dev/null
@@ -1,442 +0,0 @@
-/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
-   Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file. */
-
-/* Test Interface productions
-
-Run with --test to generate an AST and verify that all comments accurately
-reflect the state of the Nodes.
-
-BUILD Type(Name)
-This comment signals that a node of type <Type> is created with the
-name <Name>.
-
-ERROR Error String
-This comment signals that a error of <Error String> is generated.  The error
-is not assigned to a node, but are expected in order.
-
-PROP Key=Value
-This comment signals that a property has been set on the Node such that
-<Key> = <Value>.
-
-TREE
-Type(Name)
-  Type(Name)
-  Type(Name)
-    Type(Name)
-    ...
-This comment signals that a tree of nodes matching the BUILD comment
-symatics should exist.  This is an exact match.
-*/
-
-
-/* TREE
- *Interface(MyIFace)
- */
-interface MyIFace { };
-
-/* TREE
- *Interface(MyIFaceInherit)
- *  Inherit(Foo)
- */
-interface MyIFaceInherit : Foo {};
-
-/* TREE
- *Interface(MyIFacePartial)
- */
-partial interface MyIFacePartial { };
-
-/* ERROR Unexpected ":" after identifier "MyIFaceInherit". */
-partial interface MyIFaceInherit : Foo {};
-
-/* TREE
- *Interface(MyIFaceMissingArgument)
- *  Operation(foo)
- *    Arguments()
- *      Argument(arg)
- *        Type()
- *          StringType(DOMString)
- *      Error(Missing argument.)
- *    Type()
- *      PrimitiveType(void)
- */
-interface MyIFaceMissingArgument {
-  void foo(DOMString arg, );
-};
-
-/* TREE
- *Error(Unexpected keyword "double" after keyword "readonly".)
- */
-interface MyIFaceMissingAttribute {
-  readonly double foo;
-};
-
-/* TREE
- *Interface(MyIFaceContainsUnresolvedConflictDiff)
- *  Operation(foo)
- *    Arguments()
- *    Type()
- *      StringType(DOMString)
- *  Error(Unexpected "<" after ";".)
- */
-interface MyIFaceContainsUnresolvedConflictDiff {
-    DOMString foo();
-<<<<<< ours
-    DOMString bar();
-    iterable<long>;
-======
->>>>>> theirs
-};
-
-/* TREE
- *Interface(MyIFaceWrongRecordKeyType)
- *  Operation(foo)
- *    Arguments()
- *      Argument(arg)
- *        Type()
- *          Error(Unexpected identifier "int" after "<".)
- *    Type()
- *      PrimitiveType(void)
- */
-interface MyIFaceWrongRecordKeyType {
-  void foo(record<int, ByteString> arg);
-};
-
-/* TREE
- *Interface(MyIFaceBig)
- *  Const(setString)
- *    StringType(DOMString)
- *    Value(NULL)
- */
-interface MyIFaceBig {
-  const DOMString? setString = null;
-};
-
-/* TREE
- *Interface(MyIfaceEmptySequenceDefalutValue)
- *  Operation(foo)
- *    Arguments()
- *      Argument(arg)
- *        Type()
- *          Sequence()
- *            Type()
- *              StringType(DOMString)
- *        Default()
- *    Type()
- *      PrimitiveType(void)
- */
-interface MyIfaceEmptySequenceDefalutValue {
-  void foo(optional sequence<DOMString> arg = []);
-};
-
-/* TREE
- *Interface(MyIfaceWithRecords)
- *  Operation(foo)
- *    Arguments()
- *      Argument(arg)
- *        Type()
- *          Record()
- *            StringType(DOMString)
- *            Type()
- *              PrimitiveType(long)
- *    Type()
- *      PrimitiveType(void)
- *  Operation(bar)
- *    Arguments()
- *      Argument(arg1)
- *        Type()
- *          Typeref(int)
- *      Argument(arg2)
- *        Type()
- *          Record()
- *            StringType(ByteString)
- *            Type()
- *              PrimitiveType(float)
- *    Type()
- *      PrimitiveType(double)
- */
-interface MyIfaceWithRecords {
-  void foo(record<DOMString, long> arg);
-  double bar(int arg1, record<ByteString, float> arg2);
-};
-
-/* TREE
- *Interface(MyIFaceBig2)
- *  Const(nullValue)
- *    StringType(DOMString)
- *    Value(NULL)
- *  Const(longValue)
- *    PrimitiveType(long)
- *    Value(123)
- *  Const(longValue2)
- *    PrimitiveType(long long)
- *    Value(123)
- *  Attribute(myString)
- *    Type()
- *      StringType(DOMString)
- *  Attribute(readOnlyString)
- *    Type()
- *      StringType(DOMString)
- *  Attribute(staticString)
- *    Type()
- *      StringType(DOMString)
- *  Operation(myFunction)
- *    Arguments()
- *      Argument(myLong)
- *        Type()
- *          PrimitiveType(long long)
- *    Type()
- *      PrimitiveType(void)
- *  Operation(staticFunction)
- *    Arguments()
- *      Argument(myLong)
- *        Type()
- *          PrimitiveType(long long)
- *    Type()
- *      PrimitiveType(void)
- */
-interface MyIFaceBig2 {
-  const DOMString? nullValue = null;
-  const long longValue = 123;
-  const long long longValue2 = 123;
-  attribute DOMString myString;
-  readonly attribute DOMString readOnlyString;
-  static attribute DOMString staticString;
-  void myFunction(long long myLong);
-  static void staticFunction(long long myLong);
-};
-
-
-/* TREE
- *Interface(MyIFaceSpecials)
- *  Operation(set)
- *    Arguments()
- *      Argument(property)
- *        Type()
- *          StringType(DOMString)
- *    Type()
- *      PrimitiveType(void)
- *  Operation(_unnamed_)
- *    Arguments()
- *      Argument(property)
- *        Type()
- *          StringType(DOMString)
- *    Type()
- *      PrimitiveType(double)
- *  Operation(GetFiveSix)
- *    Arguments()
- *      Argument(arg)
- *        Type()
- *          Typeref(SomeType)
- *    Type()
- *      PrimitiveType(long long)
- *      Array(5)
- *        Array(6)
- */
-interface MyIFaceSpecials {
-  setter creator void set(DOMString property);
-  getter double (DOMString property);
-  long long [5][6] GetFiveSix(SomeType arg);
-};
-
-/* TREE
- *Interface(MyIFaceStringifiers)
- *  Stringifier()
- *  Stringifier()
- *    Operation(_unnamed_)
- *      Arguments()
- *      Type()
- *        StringType(DOMString)
- *  Stringifier()
- *    Operation(namedStringifier)
- *      Arguments()
- *      Type()
- *        StringType(DOMString)
- *  Stringifier()
- *    Attribute(stringValue)
- *      Type()
- *        StringType(DOMString)
- */
-interface MyIFaceStringifiers {
-  stringifier;
-  stringifier DOMString ();
-  stringifier DOMString namedStringifier();
-  stringifier attribute DOMString stringValue;
-};
-
-/* TREE
- *Interface(MyExtendedAttributeInterface)
- *  Operation(method)
- *    Arguments()
- *    Type()
- *      PrimitiveType(void)
- *    ExtAttributes()
- *      ExtAttribute(Attr)
- *      ExtAttribute(MethodIdentList)
- *  ExtAttributes()
- *    ExtAttribute(MyExtendedAttribute)
- *    ExtAttribute(MyExtendedIdentListAttribute)
- */
-[MyExtendedAttribute,
- MyExtendedIdentListAttribute=(Foo, Bar, Baz)]
-interface MyExtendedAttributeInterface {
-  [Attr, MethodIdentList=(Foo, Bar)] void method();
-};
-
-/* TREE
- *Interface(MyIfacePromise)
- *  Operation(method1)
- *    Arguments()
- *    Type()
- *      Promise(Promise)
- *        Type()
- *          PrimitiveType(void)
- *  Operation(method2)
- *    Arguments()
- *    Type()
- *      Promise(Promise)
- *        Type()
- *          PrimitiveType(long)
- *  Operation(method3)
- *    Arguments()
- *    Type()
- *      Promise(Promise)
- *        Type()
- *          Any()
- *  Operation(method4)
- *    Arguments()
- *    Type()
- *      Promise(Promise)
- *        Type()
- *          Any()
- */
-interface MyIfacePromise {
-  Promise<void> method1();
-  Promise<long> method2();
-  Promise<any> method3();
-  Promise method4();
-};
-
-/* TREE
- *Interface(MyIfaceIterable)
- *  Iterable()
- *    Type()
- *      PrimitiveType(long)
- *  Iterable()
- *    Type()
- *      PrimitiveType(double)
- *    Type()
- *      StringType(DOMString)
- *  LegacyIterable()
- *    Type()
- *      PrimitiveType(boolean)
- */
-interface MyIfaceIterable {
-  iterable<long>;
-  iterable<double, DOMString>;
-  legacyiterable<boolean>;
-};
-
-/* TREE
- *Interface(MyIfaceMaplike)
- *  Maplike()
- *    Type()
- *      PrimitiveType(long)
- *    Type()
- *      StringType(DOMString)
- *  Maplike()
- *    Type()
- *      PrimitiveType(double)
- *    Type()
- *      PrimitiveType(boolean)
- */
-interface MyIfaceMaplike {
-  readonly maplike<long, DOMString>;
-  maplike<double, boolean>;
-};
-
-/* TREE
- *Interface(MyIfaceSetlike)
- *  Setlike()
- *    Type()
- *      PrimitiveType(long)
- *  Setlike()
- *    Type()
- *      PrimitiveType(double)
- */
-interface MyIfaceSetlike {
-  readonly setlike<long>;
-  setlike<double>;
-};
-
-/* TREE
- *Interface(MyIfaceSerializer)
- *  Serializer()
- *  Serializer()
- *    Operation(toJSON)
- *      Arguments()
- *      Type()
- *        Any()
- *  Serializer()
- *  Serializer()
- *    Map()
- *  Serializer()
- *    Map()
- *  Serializer()
- *    Map()
- *  Serializer()
- *    Map()
- *  Serializer()
- *    Map()
- *  Serializer()
- *    Map()
- *  Serializer()
- *    Map()
- *  Serializer()
- *    List()
- *  Serializer()
- *    List()
- *  Serializer()
- *    List()
- */
-interface MyIfaceSerializer {
-  serializer;
-  serializer any toJSON();
-  serializer = name;
-  serializer = {};
-  serializer = { getter };
-  serializer = { attribute };
-  serializer = { inherit, attribute };
-  serializer = { inherit };
-  serializer = { inherit, name1, name2 };
-  serializer = { name1, name2 };
-  serializer = [];
-  serializer = [getter];
-  serializer = [name1, name2];
-};
-
-/* TREE
- *Interface(MyIfaceFrozenArray)
- *  Attribute(foo)
- *    Type()
- *      FrozenArray()
- *        Type()
- *          StringType(DOMString)
- */
-interface MyIfaceFrozenArray {
-  readonly attribute FrozenArray<DOMString> foo;
-};
-
-/* TREE
- *Interface(MyIfaceUnion)
- *  Attribute(foo)
- *    Type()
- *      UnionType()
- *        Type()
- *          StringType(DOMString)
- *        Type()
- *          PrimitiveType(long)
- */
-interface MyIfaceUnion {
-  attribute (DOMString or long) foo;
-};
diff --git a/tools/idl_parser/test_parser/label_ppapi.idl b/tools/idl_parser/test_parser/label_ppapi.idl
deleted file mode 100644
index 264699d..0000000
--- a/tools/idl_parser/test_parser/label_ppapi.idl
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
-   Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file. */
-
-/* Test Typedef productions
-
-Run with --test to generate an AST and verify that all comments accurately
-reflect the state of the Nodes.
-
-BUILD Type(Name)
-This comment signals that a node of type <Type> is created with the
-name <Name>.
-
-ERROR Error String
-This comment signals that a error of <Error String> is generated.  The error
-is not assigned to a node, but are expected in order.
-
-PROP Key=Value
-This comment signals that a property has been set on the Node such that
-<Key> = <Value>.
-
-TREE
-Type(Name)
-  Type(Name)
-  Type(Name)
-    Type(Name)
-    ...
-This comment signals that a tree of nodes matching the BUILD comment
-symatics should exist.  This is an exact match.
-*/
-
-/* TREE
- *Label(Chrome1)
- *  LabelItem(M13)
- */
-label Chrome1 {
-  M13 = 0.0
-};
-
-/* TREE
- *Label(Chrome2)
- *  LabelItem(M12)
- *  LabelItem(M13)
- */
-label Chrome2 {
-  M12 = 1.0,
-  M13 = 2.0,
-};
\ No newline at end of file
diff --git a/tools/idl_parser/test_parser/struct_ppapi.idl b/tools/idl_parser/test_parser/struct_ppapi.idl
deleted file mode 100644
index 59bc7eb..0000000
--- a/tools/idl_parser/test_parser/struct_ppapi.idl
+++ /dev/null
@@ -1,56 +0,0 @@
-/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
-   Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file. */
-
-/* Test Struct productions
-
-Run with --test to generate an AST and verify that all comments accurately
-reflect the state of the Nodes.
-
-BUILD Type(Name)
-This comment signals that a node of type <Type> is created with the
-name <Name>.
-
-ERROR Error String
-This comment signals that a error of <Error String> is generated.  The error
-is not assigned to a node, but are expected in order.
-
-PROP Key=Value
-This comment signals that a property has been set on the Node such that
-<Key> = <Value>.
-
-TREE
-Type(Name)
-  Type(Name)
-  Type(Name)
-    Type(Name)
-    ...
-This comment signals that a tree of nodes matching the BUILD comment
-symatics should exist.  This is an exact match.
-*/
-
-/* TREE
- *Struct(MyStruct)
- *  Member(x)
- *    Type()
- *      PrimitiveType(uint32_t)
- *  Member(y)
- *    Type()
- *      PrimitiveType(uint64_t)
- *  Member(string)
- *    ExtAttributes()
- *      ExtAttribute(fake_attribute)
- *    Type()
- *      PrimitiveType(str_t)
- *  Member(z)
- *    Type()
- *      Typeref(Promise)
- *  ExtAttributes()
- *    ExtAttribute(union)
- */
-[union] struct MyStruct {
-  uint32_t x;
-  uint64_t y;
-  [fake_attribute] str_t string;
-  Promise z;
-};
diff --git a/tools/idl_parser/test_parser/typedef_ppapi.idl b/tools/idl_parser/test_parser/typedef_ppapi.idl
deleted file mode 100644
index 1a80415..0000000
--- a/tools/idl_parser/test_parser/typedef_ppapi.idl
+++ /dev/null
@@ -1,92 +0,0 @@
-/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
-   Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file. */
-
-/* Test Typedef productions
-
-Run with --test to generate an AST and verify that all comments accurately
-reflect the state of the Nodes.
-
-BUILD Type(Name)
-This comment signals that a node of type <Type> is created with the
-name <Name>.
-
-ERROR Error String
-This comment signals that a error of <Error String> is generated.  The error
-is not assigned to a node, but are expected in order.
-
-PROP Key=Value
-This comment signals that a property has been set on the Node such that
-<Key> = <Value>.
-
-TREE
-Type(Name)
-  Type(Name)
-  Type(Name)
-    Type(Name)
-    ...
-This comment signals that a tree of nodes matching the BUILD comment
-symatics should exist.  This is an exact match.
-*/
-
-/* TREE
- *Callback(foo)
- *  Type()
- *    PrimitiveType(void)
- *  Arguments()
- *    Argument(x)
- *      Type()
- *        PrimitiveType(int32_t)
- */
-callback foo = void (int32_t x);
-
-/* TREE
- *Callback(foo)
- *  Type()
- *    PrimitiveType(void)
- *  Arguments()
- *    Argument(x)
- *      Type()
- *        PrimitiveType(int32_t)
- */
-typedef void foo(int32_t x);
-
-/* TREE
- *Typedef(MyLong)
- *  Type()
- *    PrimitiveType(int32_t)
- */
-typedef int32_t MyLong;
-
-/* TREE
- *Typedef(MyLongArray)
- *  Type()
- *    PrimitiveType(str_t)
- *    Array()
- */
-typedef str_t[] MyLongArray;
-
-/* TREE
- *Typedef(MyLongArray5)
- *  Type()
- *    PrimitiveType(mem_t)
- *    Array(5)
- */
-typedef mem_t[5] MyLongArray5;
-
-/* TREE
- *Typedef(MyLongArrayN5)
- *  Type()
- *    PrimitiveType(handle_t)
- *    Array()
- *      Array(5)
- */
-typedef handle_t[][5] MyLongArrayN5;
-
-
-/* TREE
- *Typedef(bar)
- *  Type()
- *    Typeref(foo)
- */
-typedef foo bar;
\ No newline at end of file
diff --git a/tools/idl_parser/test_parser/typedef_web.idl b/tools/idl_parser/test_parser/typedef_web.idl
deleted file mode 100644
index 6e651c1..0000000
--- a/tools/idl_parser/test_parser/typedef_web.idl
+++ /dev/null
@@ -1,206 +0,0 @@
-/* Copyright (c) 2013 The Chromium Authors. All rights reserved.
-   Use of this source code is governed by a BSD-style license that can be
-  found in the LICENSE file. */
-
-/* Test Typedef productions
-
-Run with --test to generate an AST and verify that all comments accurately
-reflect the state of the Nodes.
-
-BUILD Type(Name)
-This comment signals that a node of type <Type> is created with the
-name <Name>.
-
-ERROR Error String
-This comment signals that a error of <Error String> is generated.  The error
-is not assigned to a node, but are expected in order.
-
-PROP Key=Value
-This comment signals that a property has been set on the Node such that
-<Key> = <Value>.
-
-TREE
-Type(Name)
-  Type(Name)
-  Type(Name)
-    Type(Name)
-    ...
-This comment signals that a tree of nodes matching the BUILD comment
-symatics should exist.  This is an exact match.
-*/
-
-
-/* TREE
- *Typedef(MyLong)
- *  Type()
- *    PrimitiveType(long)
- */
-typedef long MyLong;
-
-/* TREE
- *Typedef(MyLong)
- *  ExtAttributes()
- *    ExtAttribute(foo)
- *  Type()
- *    PrimitiveType(long)
- */
-typedef [foo] long MyLong;
-
-/* TREE
- *Typedef(MyLongArray)
- *  Type()
- *    PrimitiveType(long)
- *    Array()
- */
-typedef long[] MyLongArray;
-
-/* TREE
- *Typedef(MyLongSizedArray)
- *  Type()
- *    PrimitiveType(long)
- *    Array(4)
- */
-typedef long[4] MyLongSizedArray;
-
-/* TREE
- *Typedef(MyLongSizedArrayArray)
- *  Type()
- *    PrimitiveType(long)
- *    Array(4)
- *      Array(5)
- */
-typedef long[4][5] MyLongSizedArrayArray;
-
-/* TREE
- *Typedef(MyLongArraySizedArray)
- *  Type()
- *    PrimitiveType(long)
- *    Array()
- *      Array(5)
- */
-typedef long[][5] MyLongArraySizedArray;
-
-/* TREE
- *Typedef(MyTypeFive)
- *  Type()
- *    Typeref(MyType)
- *    Array(5)
- */
-typedef MyType[5] MyTypeFive;
-
-/* TREE
- *Typedef(MyTypeUnsizedFive)
- *  Type()
- *    Typeref(MyType)
- *    Array()
- *      Array(5)
- */
-typedef MyType[][5] MyTypeUnsizedFive;
-
-/* TREE
- *Typedef(MyLongLong)
- *  Type()
- *    PrimitiveType(long long)
- */
-typedef long long MyLongLong;
-
-/* TREE
- *Typedef(MyULong)
- *  Type()
- *    PrimitiveType(unsigned long)
- */
-typedef unsigned long MyULong;
-
-/* TREE
- *Typedef(MyULongLong)
- *  Type()
- *    PrimitiveType(unsigned long long)
- */
-typedef unsigned long long MyULongLong;
-
-/* TREE
- *Typedef(MyString)
- *  Type()
- *    StringType(DOMString)
- */
-typedef DOMString MyString;
-
-/* TREE
- *Typedef(MyObject)
- *  Type()
- *    PrimitiveType(object)
- */
-typedef object MyObject;
-
-/* TREE
- *Typedef(MyDate)
- *  Type()
- *    PrimitiveType(Date)
- */
-typedef Date MyDate;
-
-/* TREE
- *Typedef(MyFloat)
- *  Type()
- *    PrimitiveType(float)
- */
-typedef float MyFloat;
-
-/* TREE
- *Typedef(MyUFloat)
- *  Type()
- *    PrimitiveType(float)
- */
-typedef unrestricted float MyUFloat;
-
-/* TREE
- *Typedef(MyDouble)
- *  Type()
- *    PrimitiveType(double)
- */
-typedef double MyDouble;
-
-/* TREE
- *Typedef(MyUDouble)
- *  Type()
- *    PrimitiveType(double)
- */
-typedef unrestricted double MyUDouble;
-
-/* TREE
- *Typedef(MyBool)
- *  Type()
- *    PrimitiveType(boolean)
- */
-typedef boolean MyBool;
-
-/* TREE
- *Typedef(MyByte)
- *  Type()
- *    PrimitiveType(byte)
- */
-typedef byte MyByte;
-
-/* TREE
- *Typedef(MyOctet)
- *  Type()
- *    PrimitiveType(octet)
- */
-typedef octet MyOctet;
-
-/* TREE
- *Typedef(MyRecord)
- *  Type()
- *    Record()
- *      StringType(ByteString)
- *      Type()
- *        Typeref(int)
- */
-typedef record<ByteString, int> MyRecord;
-
-/* TREE
- *Typedef(MyInvalidRecord)
- *  Type()
- *    Error(Unexpected keyword "double" after "<".)
- */
-typedef record<double, ByteString> MyInvalidRecord;