Import Cobalt 23.master.0.309337
diff --git a/cobalt/bindings/templates/dictionary.h.template b/cobalt/bindings/templates/dictionary.h.template
index b31fb2c..d178bd6 100644
--- a/cobalt/bindings/templates/dictionary.h.template
+++ b/cobalt/bindings/templates/dictionary.h.template
@@ -60,7 +60,7 @@
 {% if used_class.conditional %}
 #if defined({{used_class.conditional}})
 {% endif %}
-using {{used_class.fully_qualified_name}};
+using ::{{used_class.fully_qualified_name}};
 {% if used_class.conditional %}
 #endif  // defined({{used_class.conditional}})
 {% endif %}
diff --git a/cobalt/black_box_tests/black_box_tests.py b/cobalt/black_box_tests/black_box_tests.py
index 7ac37d0..34b2ffe 100644
--- a/cobalt/black_box_tests/black_box_tests.py
+++ b/cobalt/black_box_tests/black_box_tests.py
@@ -63,8 +63,6 @@
     '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',
@@ -72,6 +70,8 @@
     'web_debugger',
     'web_platform_tests',
     'web_worker_test',
+    'worker_csp_test',
+    'service_worker_message_test',
     'service_worker_test',
 ]
 # These tests can only be run on platforms whose app launcher can send deep
diff --git a/cobalt/black_box_tests/testdata/http_cache.html b/cobalt/black_box_tests/testdata/http_cache.html
index cad147f..e1f2e48 100644
--- a/cobalt/black_box_tests/testdata/http_cache.html
+++ b/cobalt/black_box_tests/testdata/http_cache.html
@@ -1,10 +1,34 @@
 <!DOCTYPE html>
 
+<!--
+  To add a new check to this test:
+    1. Create the test file to be loaded and place it in
+       /testdata/http_cache_test_resources/. The server delays responses when
+       serving files from that directory, to make a clear distinction of what's
+       being loaded from the cache.
+    2. Add the file name to either CACHED_ELEMENT_LOCATIONS if you expect that
+       file's mime type to be cached or UNCACHED_ELEMENT_LOCATIONS if you
+       expect the file to be excluded from the cache.
+    3. Add a call to the window.onload function to fetch your file, making a
+       call to measureLoadTime after the file has been loaded.
+-->
 <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 TESTDATA_LOCATION = window.location.protocol + '//'
+                              + window.location.host
+                              + '/testdata/http_cache_test_resources/';
+    var CACHED_ELEMENT_LOCATIONS = {
+      'js': TESTDATA_LOCATION + 'http_cache.js',
+      'img': TESTDATA_LOCATION + 'cobalt_logo.png',
+      'css': TESTDATA_LOCATION + 'http_cache.css',
+    };
+    var UNCACHED_ELEMENT_LOCATIONS = {
+      'txt': TESTDATA_LOCATION + 'http_cache.txt',
+    };
+    var EXPECTED_NUMBER_OF_ELEMENTS = Object.keys(CACHED_ELEMENT_LOCATIONS).length
+                                      + Object.keys(UNCACHED_ELEMENT_LOCATIONS).length;
     var loadTimes = new Map();
     var cacheSize = 0;
 
@@ -20,50 +44,90 @@
     var initialLoadTime = loadTimes.has('initialLoadTime') ?
       loadTimes.get('initialLoadTime') : new Date().getTime();
     var reloadUrl = 'http_cache.html?initialLoadTime=' + initialLoadTime;
+    // 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.
+    var timestampString = '?t=' + initialLoadTime;
 
+    // Validate the tranferSize attribute of each performance entry specified
+    // in CACHED_ELEMENT_LOCATIONS and UNCACHED_ELEMENT_LOCATIONS.
+    function checkTransferSizes() {
+      let elementsChecked = 0;
+      // https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-transfersize
+      // Verify that the transferSize attribute for each resource is less than
+      // 300. If greater than 300, it wasn't fetched from the cache.
+      for (let location in CACHED_ELEMENT_LOCATIONS) {
+        elementsChecked++;
+        let tranferSize = performance.getEntriesByName(CACHED_ELEMENT_LOCATIONS[location] + timestampString)[0].transferSize;
+        if (tranferSize > 300) {
+          console.log('Element at ' + CACHED_ELEMENT_LOCATIONS[location]
+                        + ' was expected to be cached, but was loaded from the server instead.');
+          notReached();
+        }
+      }
+
+      for (let location in UNCACHED_ELEMENT_LOCATIONS) {
+        elementsChecked++;
+        let tranferSize = performance.getEntriesByName(UNCACHED_ELEMENT_LOCATIONS[location] + timestampString)[0].transferSize;
+        if (tranferSize <= 300) {
+          console.log('Element at ' + UNCACHED_ELEMENT_LOCATIONS[location]
+                        + ' was not expected to be cached, but was loaded from the cache.');
+          notReached();
+        }
+      }
+
+      if (elementsChecked == (EXPECTED_NUMBER_OF_ELEMENTS)) {
+        onEndTest();
+      } else {
+        console.log(elementsChecked + ' elements were checked, but '
+                      + EXPECTED_NUMBER_OF_ELEMENTS
+                      + ' elements were expected. Verify that EXPECTED_NUMBER_OF_ELEMENTS is correctly set.');
+        notReached();
+      }
+    }
+
+    // On first page load, measure the load time of the specified element and
+    // update the reloadUrl to record the load time. Once the expected number of
+    // elements is reached, reload the page.
+    // On second load, measure the load time of the specified element and
+    // compare it to the first load time to validate if the cache was used,
+    // failing the test if the second load took longer.
     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();
+        if (cacheSize == EXPECTED_NUMBER_OF_ELEMENTS) {
+          setTimeout(() => { checkTransferSizes(); }, 500);
         }
       } else {
         reloadUrl += '&' + element + '=' + onLoadTime;
-        if (cacheSize == NUMBER_OF_CACHE_ELEMENTS) {
+        if (cacheSize == EXPECTED_NUMBER_OF_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;
-
+      // text/javascript
       let initialJsTime = new Date().getTime();
       let script = document.createElement('script');
-      script.onload = () => {
+      script.onload = (event) => {
         measureLoadTime('javascript', initialJsTime);
       };
       script.onerror = () => {
         notReached();
       };
-      script.src = 'http_cache_test_resources/http_cache.js' + timestampString;
+      script.src = CACHED_ELEMENT_LOCATIONS['js'] + timestampString;
       document.getElementById('js_div').appendChild(script);
 
+      // image/png
       let initialImgTime = new Date().getTime();
       let image = document.createElement('img');
       image.onload = () => {
@@ -72,10 +136,11 @@
       image.onerror = (e) => {
         notReached();
       };
-      image.src = 'http_cache_test_resources/cobalt_logo.png' + timestampString;
+      image.src = CACHED_ELEMENT_LOCATIONS['img'] + timestampString;
       image.alt = 'falied to load image';
       document.getElementById('image_div').appendChild(image);
 
+      // text/css
       let initialCssTime = new Date().getTime();
       let css = document.createElement('link');
       css.onload = () => {
@@ -85,8 +150,19 @@
         notReached();
       };
       css.rel = 'stylesheet';
-      css.href = 'http_cache_test_resources/http_cache.css' + timestampString;
+      css.href = CACHED_ELEMENT_LOCATIONS['css'] + timestampString;
       document.getElementById('css_div').appendChild(css);
+
+      // text/plain
+      let initialTxtTime = new Date().getTime();
+      let txt = document.createElement('p');
+      fetch(UNCACHED_ELEMENT_LOCATIONS['txt'] + timestampString)
+        .then(response => response.text())
+        .then(text => {
+          txt.innerHTML = text;
+          document.getElementById('txt_div').appendChild(txt);
+          measureLoadTime('txt', initialTxtTime);
+      });
     }
 
   </script>
@@ -103,4 +179,7 @@
   <div id="css_div">
     <h2>Loading CSS file</h2>
   </div>
+  <div id="txt_div">
+    <h2>Loading txt file</h2>
+  </div>
 </body>
diff --git a/cobalt/black_box_tests/testdata/http_cache_test_resources/http_cache.txt b/cobalt/black_box_tests/testdata/http_cache_test_resources/http_cache.txt
new file mode 100644
index 0000000..ad4f0dc
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/http_cache_test_resources/http_cache.txt
@@ -0,0 +1 @@
+Cobalt does not cache text/plain files. This is NOT expected to be cached.
diff --git a/cobalt/black_box_tests/testdata/service_worker_message_test.html b/cobalt/black_box_tests/testdata/service_worker_message_test.html
new file mode 100644
index 0000000..ac8f3f4
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/service_worker_message_test.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<!--
+  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.
+-->
+<!--
+  This is a basic test of the message event for service workers.
+-->
+
+<html>
+
+<head>
+    <title>Cobalt Service Worker Message Test</title>
+    <script src='black_box_js_test_utils.js'></script>
+</head>
+
+<body>
+    <script>
+        var expected_event_count = 0;
+
+        function count_event(expected_value) {
+            expected_event_count += 1;
+            console.log('Expected events counted:', expected_event_count)
+            if (expected_value) {
+                assertEqual(expected_value, expected_event_count)
+            }
+            return expected_event_count;
+        }
+
+        navigator.serviceWorker.onmessage = function (event) {
+            console.log('Got onmessage event ', JSON.stringify(event.data));;
+            assertTrue(event instanceof MessageEvent);
+            assertEqual('[object MessageEvent]', `${event}`);
+            assertEqual('[object ServiceWorkerContainer]', `${event.target}`);
+            assertEqual('[object Object]', `${event.data}`);
+            switch (count_event()) {
+                case 3:
+                    assertEqual('Foo', event.data.text);
+                    assertEqual('BarString', event.data.bar);
+                    break;
+                case 4:
+                    assertEqual('Service Worker received a message',
+                        event.data.text);
+                    assertEqual('{"message":"Registration ready resolved."}',
+                        JSON.stringify(event.data.data));
+                    assertEqual('[object ExtendableMessageEvent]',
+                        event.data.event_type);
+                    break;
+                case 5:
+                    assertEqual('Foo', event.data.text);
+                    assertEqual('BarString', event.data.bar);
+                    break;
+                case 6:
+                    assertEqual('Service Worker received a message',
+                        event.data.text);
+                    assertEqual('"Controllerchange event received."',
+                        JSON.stringify(event.data.data));
+                    assertEqual('[object ExtendableMessageEvent]',
+                        event.data.event_type);
+                    break;
+                default:
+                    notReached();
+                    break;
+            }
+
+        }
+
+        navigator.serviceWorker.oncontrollerchange = function (event) {
+            console.log('Got oncontrollerchange event', event.target);
+            count_event(2);
+            navigator.serviceWorker.controller.postMessage(
+                'Controllerchange event received.');
+        }
+
+        navigator.serviceWorker.ready.then(function (
+            registration) {
+            assertNotEqual(null, registration);
+            assertNotEqual(null, registration.active);
+            registration.active.postMessage({ message:
+                'Registration ready resolved.'});
+            count_event(1);
+            window.setTimeout(
+                () => {
+                    registration.unregister()
+                        .then(function (success) {
+                            console.log('(Expected) unregister success :',
+                                success);
+                            count_event(7);
+                        }, function (error) {
+                            console.log('(Unexpected) unregister ' +
+                                `${error}`, error);
+                            assertIncludes('SecurityError: ', `${error}`);
+                            notReached();
+                        });
+                }, 1000);
+        });
+
+        navigator.serviceWorker.register('service_worker_message_test.js', {
+            scope: './',
+        }).catch(function (error) {
+            console.log('(Unexpected) :', error);
+            notReached();
+        });
+
+        setupFinished();
+        // This delay has to be long enough to guarantee that the test has
+        // finished.
+        window.setTimeout(
+            () => {
+                console.log('Events:', expected_event_count)
+                assertEqual(7, expected_event_count);
+                onEndTest();
+            }, 2000);
+
+    </script>
+</body>
+
+</html>
diff --git a/cobalt/black_box_tests/testdata/service_worker_message_test.js b/cobalt/black_box_tests/testdata/service_worker_message_test.js
new file mode 100644
index 0000000..20a3334
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/service_worker_message_test.js
@@ -0,0 +1,49 @@
+// 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.
+
+console.log('Service Worker Script Started');
+self.onmessage = function (event) {
+  console.log('Got onmessage event', event, JSON.stringify(event.data), event.source, event.target);
+  if (event instanceof ExtendableMessageEvent) {
+    var options = {
+      includeUncontrolled: false, type: 'window'
+    };
+    event.waitUntil(self.clients.matchAll(options).then(function (clients) {
+      for (var i = 0; i < clients.length; i++) {
+        message = { text: 'Foo', bar: 'BarString' };
+        console.log('Posting to client:', JSON.stringify(message));
+        clients[i].postMessage(message);
+      }
+    }));
+    event.waitUntil(self.clients.matchAll(options).then(function (clients) {
+      for (var i = 0; i < clients.length; i++) {
+        message = {
+          text: 'Service Worker received a message',
+          data: event.data,
+          event_type: `${event}`
+        }
+        console.log('Posting to client:', JSON.stringify(message));
+        clients[i].postMessage(message);
+      }
+    }));
+  }
+}
+
+self.onactivate = function (e) {
+  e.waitUntil(self.clients.claim().then(function (result) {
+    console.log('(Expected) self.clients.claim():', result);
+  }, function (error) {
+    console.log(`(Unexpected) self.clients.claim(): ${error}`, error);
+  }));
+}
diff --git a/cobalt/black_box_tests/testdata/web_worker_test.html b/cobalt/black_box_tests/testdata/web_worker_test.html
index 2976cdf..9be8f5b 100644
--- a/cobalt/black_box_tests/testdata/web_worker_test.html
+++ b/cobalt/black_box_tests/testdata/web_worker_test.html
@@ -37,9 +37,10 @@
     console.log('running');
 
     // This is expected trigger a an error event on window.
-    var worker_with_error = new Worker('web_worker_test_with_syntax_error.js');
+    var worker_with_error = new Worker('web_worker_test_with_syntax_error.js',
+        { name : 'worker_with_error'});
 
-    var worker = new Worker('web_worker_test.js');
+    var worker = new Worker('web_worker_test.js', { name : 'test_worker'});
     console.log(worker);
     worker.onmessage = function (event) {
         message_event_count += 1;
diff --git a/cobalt/black_box_tests/testdata/worker_csp_test.html b/cobalt/black_box_tests/testdata/worker_csp_test.html
new file mode 100644
index 0000000..fdb1cea
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/worker_csp_test.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<!--
+  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.
+-->
+
+<head>
+  <title>Verify that XHR on workers correctly follows CSP</title>
+  <script src='black_box_js_test_utils.js'></script>
+</head>
+
+<body style="background-color: white;">
+  <h1>
+    <span>ID element</span>
+  </h1>
+  <script>
+  var worker = new Worker('worker_csp_test.js');
+  worker.onmessage = function (event) {
+    notReached();
+  };
+
+  window.setTimeout(
+    () => {
+      worker.terminate();
+      onEndTest();
+    }, 250);
+  </script>
+</body>
diff --git a/cobalt/black_box_tests/testdata/worker_csp_test.js b/cobalt/black_box_tests/testdata/worker_csp_test.js
new file mode 100644
index 0000000..58874aa
--- /dev/null
+++ b/cobalt/black_box_tests/testdata/worker_csp_test.js
@@ -0,0 +1,6 @@
+const xhr = new XMLHttpRequest();
+// Script should fail and stop execution at this step due to SecurityViolation.
+xhr.open("GET", "http://www.google.com");
+xhr.send();
+
+self.postMessage("Should not be reached!");
diff --git a/cobalt/black_box_tests/tests/http_cache.py b/cobalt/black_box_tests/tests/http_cache.py
index 3b24fc5..63719ff 100644
--- a/cobalt/black_box_tests/tests/http_cache.py
+++ b/cobalt/black_box_tests/tests/http_cache.py
@@ -35,7 +35,7 @@
     parsed_path = urlparse(self.path)
 
     if parsed_path.path.startswith('/testdata/http_cache_test_resources/'):
-      time.sleep(10)
+      time.sleep(3)
 
     return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
 
diff --git a/cobalt/black_box_tests/tests/service_worker_message_test.py b/cobalt/black_box_tests/tests/service_worker_message_test.py
new file mode 100644
index 0000000..9e92654
--- /dev/null
+++ b/cobalt/black_box_tests/tests/service_worker_message_test.py
@@ -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.
+"""Tests Service Worker Messaging functionality."""
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer
+
+
+class ServiceWorkerMessageTest(black_box_tests.BlackBoxTestCase):
+  """Test basic Service Worker functionality."""
+
+  def test_simple(self):
+
+    with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server:
+      url = server.GetURL(file_name='testdata/service_worker_message_test.html')
+
+      with self.CreateCobaltRunner(url=url) as runner:
+        runner.WaitForJSTestsSetup()
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/black_box_tests/tests/worker_csp_test.py b/cobalt/black_box_tests/tests/worker_csp_test.py
new file mode 100644
index 0000000..1af8735
--- /dev/null
+++ b/cobalt/black_box_tests/tests/worker_csp_test.py
@@ -0,0 +1,44 @@
+# 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 workers handling CSP Violations."""
+
+import os
+
+from cobalt.black_box_tests import black_box_tests
+from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer, MakeCustomHeaderRequestHandlerClass
+
+paths_to_headers = {
+    'worker_csp_test.html': {
+        'Content-Security-Policy':
+            "script-src 'unsafe-inline' 'self'; connect-src 'self';"
+    },
+    'worker_csp_test.js': {
+        'Content-Security-Policy': "connect-src 'self';"
+    }
+}
+
+
+class WorkerCspTest(black_box_tests.BlackBoxTestCase):
+  """Verify correct correct CSP behavior."""
+
+  def test_simple(self):
+    path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
+    with ThreadedWebServer(
+        binding_address=self.GetBindingAddress(),
+        handler=MakeCustomHeaderRequestHandlerClass(
+            path, paths_to_headers)) as server:
+      url = server.GetURL(file_name='testdata/worker_csp_test.html')
+
+      with self.CreateCobaltRunner(url=url) as runner:
+        self.assertTrue(runner.JSTestsSucceeded())
diff --git a/cobalt/black_box_tests/threaded_web_server.py b/cobalt/black_box_tests/threaded_web_server.py
index 1064355..1e173e3 100644
--- a/cobalt/black_box_tests/threaded_web_server.py
+++ b/cobalt/black_box_tests/threaded_web_server.py
@@ -60,6 +60,87 @@
   return TestDataHTTPRequestHandler
 
 
+def MakeCustomHeaderRequestHandlerClass(base_path, paths_to_headers):
+  """RequestHandler that serves files that reside relative to base_path.
+
+  Args:
+    base_path: A path considered to be the root directory.
+    paths_to_headers: A dictionary with keys partial paths and values of header
+    dicts. Key is expected to be a substring of a file being served.
+
+    E.g. if you have a test with files foo.html and foo.js, you can serve them
+    separate headers with the following:
+    paths_to_headers = {
+      'foo.html': {
+        'Content-Security-Policy', "script-src 'unsafe-inline';"
+      },
+      'foo.js': {'Content-Security-Policy', "default-src 'self';"}
+    }
+
+  Returns:
+    A RequestHandler class.
+  """
+
+  class TestDataHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+    """Handles HTTP requests and serves responses with specified headers.
+
+    For testing purposes only.
+    """
+
+    _current_working_directory = os.getcwd()
+    _base_path = base_path
+    _paths_to_headers = paths_to_headers
+
+    def do_GET(self):  # pylint: disable=invalid-name
+      content = self.get_content()
+      self.wfile.write(content)
+
+    def do_HEAD(self):  # pylint: disable=invalid-name
+      # Run get_content to send the headers, but ignore the returned results
+      self.get_content()
+
+    def translate_path(self, path):
+      """Translate the request path to the file in the testdata directory."""
+
+      potential_path = SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(
+          self, path)
+      potential_path = potential_path.replace(self._current_working_directory,
+                                              self._base_path)
+      return potential_path
+
+    def get_content(self):
+      """Returns the contents of the file at path with added headers."""
+      path = self.translate_path(self.path)
+      modified_date_time = None
+      raw_content = None
+      try:
+        with open(path, 'rb') as f:
+          filestream = os.fstat(f.fileno())
+          modified_date_time = self.date_time_string(filestream.st_mtime)
+          raw_content = f.read()
+      except IOError:
+        self.send_error(404, 'File not found')
+        return None
+
+      content_type = self.guess_type(path)
+      self.send_response(200)
+      self.send_header('Content-type', content_type)
+      for partial_path, headers in self._paths_to_headers.items():
+        if partial_path in path:
+          for header_key, header_value in headers.items():
+            self.send_header(header_key, header_value)
+
+      content_length = len(raw_content)
+
+      self.send_header('Content-Length', content_length)
+      self.send_header('Last-Modified', modified_date_time)
+      self.end_headers()
+
+      return raw_content
+
+  return TestDataHTTPRequestHandler
+
+
 class ThreadedWebServer(object):
   """A HTTP WebServer that serves requests in a separate thread."""
 
diff --git a/cobalt/browser/application.cc b/cobalt/browser/application.cc
index b6a691f..855b484 100644
--- a/cobalt/browser/application.cc
+++ b/cobalt/browser/application.cc
@@ -100,6 +100,15 @@
 
 const char kPersistentSettingsJson[] = "settings.json";
 
+// The watchdog client name used to represent Stats.
+const char kWatchdogName[] = "stats";
+// The watchdog time interval in microseconds allowed between pings before
+// triggering violations.
+const int64_t kWatchdogTimeInterval = 10000000;
+// The watchdog time wait in microseconds to initially wait before triggering
+// violations.
+const int64_t kWatchdogTimeWait = 2000000;
+
 bool IsStringNone(const std::string& str) {
   return !base::strcasecmp(str.c_str(), "none");
 }
@@ -512,12 +521,13 @@
     "available trackers.";
 #endif  // defined(ENABLE_DEBUGGER) && defined(STARBOARD_ALLOWS_MEMORY_TRACKING)
 
-bool AddCrashHandlerAnnotations() {
+void AddCrashHandlerAnnotations() {
   auto crash_handler_extension =
-      SbSystemGetExtension(kCobaltExtensionCrashHandlerName);
+      static_cast<const CobaltExtensionCrashHandlerApi*>(
+          SbSystemGetExtension(kCobaltExtensionCrashHandlerName));
   if (!crash_handler_extension) {
     LOG(INFO) << "No crash handler extension, not sending annotations.";
-    return false;
+    return;
   }
 
   auto platform_info = cobalt::browser::GetUserAgentPlatformInfoFromSystem();
@@ -541,23 +551,41 @@
   product.push_back('\0');
   version.push_back('\0');
 
-  CrashpadAnnotations crashpad_annotations;
-  memset(&crashpad_annotations, 0, sizeof(CrashpadAnnotations));
-  strncpy(crashpad_annotations.user_agent_string, user_agent.c_str(),
-          USER_AGENT_STRING_MAX_SIZE);
-  strncpy(crashpad_annotations.product, product.c_str(),
-          CRASHPAD_ANNOTATION_DEFAULT_LENGTH);
-  strncpy(crashpad_annotations.version, version.c_str(),
-          CRASHPAD_ANNOTATION_DEFAULT_LENGTH);
-  bool result = static_cast<const CobaltExtensionCrashHandlerApi*>(
-                    crash_handler_extension)
-                    ->OverrideCrashpadAnnotations(&crashpad_annotations);
+  bool result = true;
+  if (crash_handler_extension->version == 1) {
+    CrashpadAnnotations crashpad_annotations;
+    memset(&crashpad_annotations, 0, sizeof(CrashpadAnnotations));
+    strncpy(crashpad_annotations.user_agent_string, user_agent.c_str(),
+            USER_AGENT_STRING_MAX_SIZE);
+    strncpy(crashpad_annotations.product, product.c_str(),
+            CRASHPAD_ANNOTATION_DEFAULT_LENGTH);
+    strncpy(crashpad_annotations.version, version.c_str(),
+            CRASHPAD_ANNOTATION_DEFAULT_LENGTH);
+    result = crash_handler_extension->OverrideCrashpadAnnotations(
+        &crashpad_annotations);
+  } else if (crash_handler_extension->version > 1) {
+    // These particular annotation key names (e.g., ver) are expected by
+    // Crashpad.
+    // TODO(b/201538792): figure out a clean way to define these constants once
+    // and use them everywhere.
+    if (!crash_handler_extension->SetString("user_agent_string",
+                                            user_agent.c_str())) {
+      result = false;
+    }
+    if (!crash_handler_extension->SetString("prod", product.c_str())) {
+      result = false;
+    }
+    if (!crash_handler_extension->SetString("ver", version.c_str())) {
+      result = false;
+    }
+  } else {
+    result = false;
+  }  // Invalid extension version
   if (result) {
     LOG(INFO) << "Sent annotations to crash handler";
   } else {
-    LOG(ERROR) << "Could not send annotations to crash handler.";
+    LOG(ERROR) << "Could not send some annotation(s) to crash handler.";
   }
-  return result;
 }
 
 }  // namespace
@@ -650,6 +678,12 @@
       watchdog::Watchdog::CreateInstance(persistent_settings_.get());
   DCHECK(watchdog);
 
+  // Registers Stats as a watchdog client.
+  if (watchdog)
+    watchdog->Register(kWatchdogName, kWatchdogName,
+                       base::kApplicationStateStarted, kWatchdogTimeInterval,
+                       kWatchdogTimeWait, watchdog::NONE);
+
   cobalt::cache::Cache::GetInstance()->set_persistent_settings(
       persistent_settings_.get());
 
@@ -660,11 +694,10 @@
   unconsumed_deep_link_ = GetInitialDeepLink();
   DLOG(INFO) << "Initial deep link: " << unconsumed_deep_link_;
 
-  WebModule::Options web_options("MainWebModule");
   storage::StorageManager::Options storage_manager_options;
   network::NetworkModule::Options network_module_options;
   // Create the main components of our browser.
-  BrowserModule::Options options(web_options);
+  BrowserModule::Options options;
   network_module_options.preferred_language = language;
   network_module_options.persistent_settings = persistent_settings_.get();
   options.persistent_settings = persistent_settings_.get();
@@ -749,7 +782,7 @@
   // Set callback to be notified when a navigation occurs that destroys the
   // underlying WebModule.
   options.web_module_created_callback =
-      base::Bind(&Application::WebModuleCreated, base::Unretained(this));
+      base::Bind(&Application::MainWebModuleCreated, base::Unretained(this));
 
   // The main web module's stat tracker tracks event stats.
   options.web_module_options.track_event_stats = true;
@@ -1370,8 +1403,9 @@
 }
 #endif
 
-void Application::WebModuleCreated(WebModule* web_module) {
-  TRACE_EVENT0("cobalt::browser", "Application::WebModuleCreated()");
+void Application::MainWebModuleCreated(WebModule* web_module) {
+  TRACE_EVENT0("cobalt::browser", "Application::MainWebModuleCreated()");
+  // Note: This is a callback function that runs in the MainWebModule thread.
   DCHECK(web_module);
   if (preload_timestamp_.has_value()) {
     web_module->SetApplicationStartOrPreloadTimestamp(
@@ -1431,6 +1465,16 @@
   TRACE_EVENT0("cobalt::browser", "Application::UpdatePeriodicStats()");
   c_val_stats_.app_lifetime = base::StartupTimer::TimeElapsed();
 
+  // Pings watchdog.
+  watchdog::Watchdog* watchdog = watchdog::Watchdog::GetInstance();
+  if (watchdog) {
+#if defined(_DEBUG)
+    // Injects delay for watchdog debugging.
+    watchdog->MaybeInjectDebugDelay(kWatchdogName);
+#endif  // defined(_DEBUG)
+    watchdog->Ping(kWatchdogName);
+  }
+
   int64_t used_cpu_memory = SbSystemGetUsedCPUMemory();
   base::Optional<int64_t> used_gpu_memory;
   if (SbSystemHasCapability(kSbSystemCapabilityCanQueryGPUMemoryStats)) {
@@ -1499,10 +1543,9 @@
     // again after the next WebModule is created.
     unconsumed_deep_link_ = link;
     deep_link = unconsumed_deep_link_;
+    deep_link_timestamp_ = timestamp;
   }
 
-  deep_link_timestamp_ = timestamp;
-
   LOG(INFO) << "Dispatching deep link: " << deep_link;
   DispatchEventInternal(new base::DeepLinkEvent(
       deep_link, base::Bind(&Application::OnDeepLinkConsumedCallback,
@@ -1516,11 +1559,17 @@
 
 void Application::DispatchDeepLinkIfNotConsumed() {
   std::string deep_link;
+#if SB_API_VERSION >= 13
+  SbTimeMonotonic timestamp;
+#endif  // SB_API_VERSION >= 13
   // This block exists to ensure that the lock is held while accessing
   // unconsumed_deep_link_.
   {
     base::AutoLock auto_lock(unconsumed_deep_link_lock_);
     deep_link = unconsumed_deep_link_;
+#if SB_API_VERSION >= 13
+    timestamp = deep_link_timestamp_;
+#endif  // SB_API_VERSION >= 13
   }
 
   if (!deep_link.empty()) {
@@ -1531,7 +1580,7 @@
   }
 #if SB_API_VERSION >= 13
   if (browser_module_) {
-    browser_module_->SetDeepLinkTimestamp(deep_link_timestamp_);
+    browser_module_->SetDeepLinkTimestamp(timestamp);
   }
 #endif  // SB_API_VERSION >= 13
 }
diff --git a/cobalt/browser/application.h b/cobalt/browser/application.h
index d2ad7ca..96480b7 100644
--- a/cobalt/browser/application.h
+++ b/cobalt/browser/application.h
@@ -90,7 +90,7 @@
 #endif
 
   // Called when a navigation occurs in the BrowserModule.
-  void WebModuleCreated(WebModule* web_module);
+  void MainWebModuleCreated(WebModule* web_module);
 
   void CollectUnloadEventTimingInfo(base::TimeTicks start_time,
                                     base::TimeTicks end_time);
diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc
index b80da96..780f86d 100644
--- a/cobalt/browser/browser_module.cc
+++ b/cobalt/browser/browser_module.cc
@@ -341,11 +341,12 @@
 
   // Setup our main web module to have the H5VCC API injected into it.
   DCHECK(!ContainsKey(
+      options_.web_module_options.injected_global_object_attributes, "h5vcc"));
+  DCHECK(!ContainsKey(
       options_.web_module_options.web_options.injected_global_object_attributes,
       "h5vcc"));
-  options_.web_module_options.web_options
-      .injected_global_object_attributes["h5vcc"] =
-      base::Bind(&BrowserModule::CreateH5vcc, base::Unretained(this));
+  options_.web_module_options.injected_global_object_attributes["h5vcc"] =
+      base::Bind(&BrowserModule::CreateH5vccCallback, base::Unretained(this));
 
   if (command_line->HasSwitch(switches::kDisableTimerResolutionLimit)) {
     options_.web_module_options.limit_performance_timer_resolution = false;
@@ -515,7 +516,7 @@
   if (web_module_) {
     lifecycle_observers_.RemoveObserver(web_module_.get());
   }
-  web_module_.reset(NULL);
+  web_module_.reset();
 
   // Increment the navigation generation so that we can attach it to event
   // callbacks as a way of identifying the new web module from the old ones.
@@ -608,7 +609,8 @@
   options.web_options.service_worker_jobs =
       service_worker_registry_.service_worker_jobs();
   options.web_options.platform_info = platform_info_.get();
-  web_module_.reset(new WebModule(
+  web_module_.reset(new WebModule("MainWebModule"));
+  web_module_->Run(
       url, application_state_,
       base::Bind(&BrowserModule::QueueOnRenderTreeProduced,
                  base::Unretained(this), main_web_module_generation_),
@@ -616,7 +618,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, options));
+      GetResourceProvider(), kLayoutMaxRefreshFrequencyInHz, options);
   lifecycle_observers_.AddObserver(web_module_.get());
 
   if (system_window_) {
@@ -686,6 +688,7 @@
     const base::Optional<math::Rect>& clip_rect,
     const base::Closure& done_callback) {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::RequestScreenshotToFile()");
+  DCHECK_EQ(base::MessageLoop::current(), self_message_loop_);
   DCHECK(screen_shot_writer_);
 
   scoped_refptr<render_tree::Node> render_tree;
@@ -704,6 +707,7 @@
     const base::Optional<math::Rect>& clip_rect,
     const ScreenShotWriter::ImageEncodeCompleteCallback& screenshot_ready) {
   TRACE_EVENT0("cobalt::browser", "BrowserModule::RequestScreenshotToMemory()");
+  DCHECK_EQ(base::MessageLoop::current(), self_message_loop_);
   DCHECK(screen_shot_writer_);
 
   scoped_refptr<render_tree::Node> render_tree;
@@ -898,6 +902,7 @@
 }
 
 void BrowserModule::OnWindowSizeChanged(const ViewportSize& viewport_size) {
+  DCHECK_EQ(base::MessageLoop::current(), self_message_loop_);
   if (web_module_) {
     web_module_->SetSize(viewport_size);
   }
@@ -964,6 +969,7 @@
 
 void BrowserModule::OnCaptionSettingsChanged(
     const base::AccessibilityCaptionSettingsChangedEvent* event) {
+  DCHECK_EQ(base::MessageLoop::current(), self_message_loop_);
   if (web_module_) {
     web_module_->InjectCaptionSettingsChangedEvent();
   }
@@ -972,6 +978,7 @@
 #if SB_API_VERSION >= 13
 void BrowserModule::OnDateTimeConfigurationChanged(
     const base::DateTimeConfigurationChangedEvent* event) {
+  DCHECK_EQ(base::MessageLoop::current(), self_message_loop_);
   icu::TimeZone::adoptDefault(icu::TimeZone::detectHostTimeZone());
   if (web_module_) {
     web_module_->UpdateDateTimeConfiguration();
@@ -1122,12 +1129,14 @@
 }
 
 void BrowserModule::OnWindowOnOnlineEvent(const base::Event* event) {
+  DCHECK_EQ(base::MessageLoop::current(), self_message_loop_);
   if (web_module_) {
     web_module_->InjectWindowOnOnlineEvent(event);
   }
 }
 
 void BrowserModule::OnWindowOnOfflineEvent(const base::Event* event) {
+  DCHECK_EQ(base::MessageLoop::current(), self_message_loop_);
   if (web_module_) {
     web_module_->InjectWindowOnOfflineEvent(event);
   }
@@ -2021,13 +2030,15 @@
   }
 }
 
-scoped_refptr<script::Wrappable> BrowserModule::CreateH5vcc(
-    script::EnvironmentSettings* settings) {
-  DCHECK(web_module_);
+scoped_refptr<script::Wrappable> BrowserModule::CreateH5vccCallback(
+    WebModule* web_module, web::EnvironmentSettings* settings) {
+  DCHECK_NE(base::MessageLoop::current(), self_message_loop_);
+  // Note: This is a callback function that runs in the MainWebModule thread.
+  // The web_module_ member can not be safely used in this function.
 
   h5vcc::H5vcc::Settings h5vcc_settings;
   h5vcc_settings.set_media_source_setting_func = base::Bind(
-      &WebModule::SetMediaSourceSetting, base::Unretained(web_module_.get()));
+      &WebModule::SetMediaSourceSetting, base::Unretained(web_module));
   h5vcc_settings.network_module = network_module_;
 #if SB_IS(EVERGREEN)
   h5vcc_settings.updater_module = updater_module_;
@@ -2035,22 +2046,27 @@
   h5vcc_settings.account_manager = account_manager_;
   h5vcc_settings.event_dispatcher = event_dispatcher_;
 
-  auto* dom_settings = base::polymorphic_downcast<dom::DOMSettings*>(settings);
-
-  h5vcc_settings.user_agent_data =
-      dom_settings->window()->navigator()->user_agent_data();
-  h5vcc_settings.global_environment =
-      dom_settings->context()->global_environment();
+  h5vcc_settings.user_agent_data = settings->context()
+                                       ->GetWindowOrWorkerGlobalScope()
+                                       ->navigator_base()
+                                       ->user_agent_data();
+  h5vcc_settings.global_environment = settings->context()->global_environment();
   h5vcc_settings.persistent_settings = options_.persistent_settings;
 
   auto* h5vcc_object = new h5vcc::H5vcc(h5vcc_settings);
   if (!web_module_created_callback_.is_null()) {
-    web_module_created_callback_.Run(web_module_.get());
+    web_module_created_callback_.Run(web_module);
   }
   return scoped_refptr<script::Wrappable>(h5vcc_object);
 }
 
 void BrowserModule::SetDeepLinkTimestamp(SbTimeMonotonic timestamp) {
+  if (base::MessageLoop::current() != self_message_loop_) {
+    self_message_loop_->task_runner()->PostTask(
+        FROM_HERE, base::Bind(&BrowserModule::SetDeepLinkTimestamp,
+                              base::Unretained(this), timestamp));
+    return;
+  }
   DCHECK(web_module_);
   web_module_->SetDeepLinkTimestamp(timestamp);
 }
diff --git a/cobalt/browser/browser_module.h b/cobalt/browser/browser_module.h
index 6730fa2..646b750 100644
--- a/cobalt/browser/browser_module.h
+++ b/cobalt/browser/browser_module.h
@@ -99,9 +99,8 @@
   // All browser subcomponent options should have default constructors that
   // setup reasonable default options.
   struct Options {
-    explicit Options(const WebModule::Options& web_options)
-        : web_module_options(web_options),
-          command_line_auto_mem_settings(
+    Options()
+        : command_line_auto_mem_settings(
               memory_settings::AutoMemSettings::kTypeCommandLine),
           build_auto_mem_settings(memory_settings::AutoMemSettings::kTypeBuild),
           enable_splash_screen_on_reloads(true) {}
@@ -465,9 +464,10 @@
   // Returns the topic used, or an empty Optional if a topic isn't found.
   base::Optional<std::string> SetSplashScreenTopicFallback(const GURL& url);
 
-  // Function that creates the H5vcc object that will be injected into WebModule
-  scoped_refptr<script::Wrappable> CreateH5vcc(
-      script::EnvironmentSettings* settings);
+  // Callback function that creates the H5vcc object that will be injected into
+  // the MainWebModule.
+  scoped_refptr<script::Wrappable> CreateH5vccCallback(
+      WebModule* web_module, web::EnvironmentSettings* settings);
 
   // Validates the PersistentSettings for cache backend, if in use.
   void ValidateCacheBackendSettings();
diff --git a/cobalt/browser/debug_console.cc b/cobalt/browser/debug_console.cc
index 72069e3..1f0bba5 100644
--- a/cobalt/browser/debug_console.cc
+++ b/cobalt/browser/debug_console.cc
@@ -97,7 +97,7 @@
 scoped_refptr<script::Wrappable> CreateDebugHub(
     const debug::console::DebugHub::GetHudModeCallback& get_hud_mode_function,
     const debug::CreateDebugClientCallback& create_debug_client_callback,
-    script::EnvironmentSettings* settings) {
+    web::EnvironmentSettings* settings) {
   return new debug::console::DebugHub(get_hud_mode_function,
                                       create_debug_client_callback);
 }
@@ -116,7 +116,7 @@
     const base::Closure& maybe_freeze_callback) {
   mode_ = GetInitialMode();
 
-  WebModule::Options web_module_options("DebugConsoleWebModule");
+  WebModule::Options web_module_options;
   // The debug console does not load any image assets.
   web_module_options.image_cache_capacity = 0;
   // Disable CSP for the Debugger's WebModule. This will also allow eval() in
@@ -143,15 +143,15 @@
   web_module_options.web_options.network_module = network_module;
   web_module_options.web_options.platform_info = platform_info;
 
-  web_module_.reset(
-      new WebModule(GURL(kInitialDebugConsoleUrl), initial_application_state,
-                    render_tree_produced_callback,
-                    base::Bind(&DebugConsole::OnError, base::Unretained(this)),
-                    WebModule::CloseCallback(), /* window_close_callback */
-                    base::Closure(),            /* window_minimize_callback */
-                    NULL /* can_play_type_handler */, NULL /* media_module */,
-                    window_dimensions, resource_provider, layout_refresh_rate,
-                    web_module_options));
+  web_module_.reset(new WebModule("DebugConsoleWebModule"));
+  web_module_->Run(GURL(kInitialDebugConsoleUrl), initial_application_state,
+                   render_tree_produced_callback,
+                   base::Bind(&DebugConsole::OnError, base::Unretained(this)),
+                   WebModule::CloseCallback(), /* window_close_callback */
+                   base::Closure(),            /* window_minimize_callback */
+                   NULL /* can_play_type_handler */, NULL /* media_module */,
+                   window_dimensions, resource_provider, layout_refresh_rate,
+                   web_module_options);
 }
 
 DebugConsole::~DebugConsole() {}
diff --git a/cobalt/browser/on_screen_keyboard_starboard_bridge.cc b/cobalt/browser/on_screen_keyboard_starboard_bridge.cc
index 969f822..ccd818e 100644
--- a/cobalt/browser/on_screen_keyboard_starboard_bridge.cc
+++ b/cobalt/browser/on_screen_keyboard_starboard_bridge.cc
@@ -97,8 +97,8 @@
                                               keep_focus);
 }
 
-void OnScreenKeyboardStarboardBridge::SetBackgroundColor(
-    const char* background_color) {
+void OnScreenKeyboardStarboardBridge::SetBackgroundColor(uint8 r, uint8 g,
+                                                         uint8 b) {
   const CobaltExtensionOnScreenKeyboardApi* on_screen_keyboard_extension =
       static_cast<const CobaltExtensionOnScreenKeyboardApi*>(
           SbSystemGetExtension(kCobaltExtensionOnScreenKeyboardName));
@@ -108,11 +108,11 @@
              kCobaltExtensionOnScreenKeyboardName) == 0 &&
       on_screen_keyboard_extension->version >= 1) {
     on_screen_keyboard_extension->SetBackgroundColor(sb_window_provider_.Run(),
-                                                     background_color);
+                                                     r, g, b);
   }
 }
 
-void OnScreenKeyboardStarboardBridge::SetDarkTheme(bool dark_theme) {
+void OnScreenKeyboardStarboardBridge::SetLightTheme(bool light_theme) {
   const CobaltExtensionOnScreenKeyboardApi* on_screen_keyboard_extension =
       static_cast<const CobaltExtensionOnScreenKeyboardApi*>(
           SbSystemGetExtension(kCobaltExtensionOnScreenKeyboardName));
@@ -121,8 +121,8 @@
       strcmp(on_screen_keyboard_extension->name,
              kCobaltExtensionOnScreenKeyboardName) == 0 &&
       on_screen_keyboard_extension->version >= 1) {
-    on_screen_keyboard_extension->SetDarkTheme(sb_window_provider_.Run(),
-                                               dark_theme);
+    on_screen_keyboard_extension->SetLightTheme(sb_window_provider_.Run(),
+                                                light_theme);
   }
 }
 }  // namespace browser
diff --git a/cobalt/browser/on_screen_keyboard_starboard_bridge.h b/cobalt/browser/on_screen_keyboard_starboard_bridge.h
index 6ee9a70..682f2f4 100644
--- a/cobalt/browser/on_screen_keyboard_starboard_bridge.h
+++ b/cobalt/browser/on_screen_keyboard_starboard_bridge.h
@@ -59,9 +59,9 @@
 
   void SetKeepFocus(bool keep_focus) override;
 
-  void SetBackgroundColor(const char* background_color) override;
+  void SetBackgroundColor(uint8 r, uint8 g, uint8 b) override;
 
-  void SetDarkTheme(bool dark_theme) override;
+  void SetLightTheme(bool light_theme) override;
 
  private:
   base::Callback<SbWindow()> sb_window_provider_;
diff --git a/cobalt/browser/splash_screen.cc b/cobalt/browser/splash_screen.cc
index 2739ec0..952f790 100644
--- a/cobalt/browser/splash_screen.cc
+++ b/cobalt/browser/splash_screen.cc
@@ -67,7 +67,7 @@
       self_message_loop_(base::MessageLoop::current()),
       on_splash_screen_shutdown_complete_(on_splash_screen_shutdown_complete),
       shutdown_signaled_(false) {
-  WebModule::Options web_module_options("SplashScreenWebModule");
+  WebModule::Options web_module_options;
 
   // We want the splash screen to load and appear as quickly as possible, so
   // we set it and its image decoding thread to be high priority.
@@ -106,13 +106,14 @@
   web_module_options.web_options.platform_info = platform_info;
 
   DCHECK(url_to_pass);
-  web_module_.reset(new WebModule(
-      *url_to_pass, initial_application_state, render_tree_produced_callback_,
-      base::Bind(&OnError), on_window_close,
-      base::Closure(),  // window_minimize_callback
-      NULL /* can_play_type_handler */, NULL /* media_module */,
-      window_dimensions, resource_provider, layout_refresh_rate,
-      web_module_options));
+  web_module_.reset(new WebModule("SplashScreenWebModule"));
+  web_module_->Run(*url_to_pass, initial_application_state,
+                   render_tree_produced_callback_, base::Bind(&OnError),
+                   on_window_close,
+                   base::Closure(),  // window_minimize_callback
+                   NULL /* can_play_type_handler */, NULL /* media_module */,
+                   window_dimensions, resource_provider, layout_refresh_rate,
+                   web_module_options);
 }
 
 SplashScreen::~SplashScreen() {
diff --git a/cobalt/browser/web_module.cc b/cobalt/browser/web_module.cc
index ecc0f56..a7cfd00 100644
--- a/cobalt/browser/web_module.cc
+++ b/cobalt/browser/web_module.cc
@@ -1320,15 +1320,32 @@
   } while (event && !layout_manager_->IsRenderTreePending());
 }
 
-WebModule::Options::Options(const std::string& name)
-    : web_options(name),
-      layout_trigger(layout::LayoutManager::kOnDocumentMutation),
+WebModule::Options::Options()
+    : layout_trigger(layout::LayoutManager::kOnDocumentMutation),
       mesh_cache_capacity(configuration::Configuration::GetInstance()
                               ->CobaltMeshCacheSizeInBytes()) {
   web_options.stack_size = cobalt::browser::kWebModuleStackSize;
 }
 
-WebModule::WebModule(
+WebModule::WebModule(const std::string& name)
+    : ui_nav_root_(new ui_navigation::NavItem(
+          ui_navigation::kNativeItemTypeContainer,
+          // Currently, events do not need to be processed for the root item.
+          base::Closure(), base::Closure(), base::Closure())) {
+  web_agent_.reset(new web::Agent(name));
+}
+
+WebModule::~WebModule() {
+  DCHECK(message_loop());
+  DCHECK(web_agent_);
+  // This will cancel the timers for tasks, which help the thread exit
+  ClearAllIntervalsAndTimeouts();
+  web_agent_->WaitUntilDone();
+  web_agent_->Stop();
+  web_agent_.reset();
+}
+
+void WebModule::Run(
     const GURL& initial_url, base::ApplicationState initial_application_state,
     const OnRenderTreeProducedCallback& render_tree_produced_callback,
     OnErrorCallback error_callback, const CloseCallback& window_close_callback,
@@ -1336,11 +1353,7 @@
     media::CanPlayTypeHandler* can_play_type_handler,
     media::MediaModule* media_module, const ViewportSize& window_dimensions,
     render_tree::ResourceProvider* resource_provider, float layout_refresh_rate,
-    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.
-          base::Closure(), base::Closure(), base::Closure())) {
+    const Options& options) {
   ConstructionData construction_data(
       initial_url, initial_application_state, render_tree_produced_callback,
       error_callback, window_close_callback, window_minimize_callback,
@@ -1351,24 +1364,27 @@
 #endif  // defined(ENABLE_DEBUGGER)
       &synchronous_loader_interrupt_, options);
 
-  web_agent_.reset(
-      new web::Agent(options.web_options,
-                     base::Bind(&WebModule::Initialize, base::Unretained(this),
-                                construction_data),
-                     this));
+  web::Agent::Options web_options(options.web_options);
+  if (!options.injected_global_object_attributes.empty()) {
+    for (Options::InjectedGlobalObjectAttributes::const_iterator iter =
+             options.injected_global_object_attributes.begin();
+         iter != options.injected_global_object_attributes.end(); ++iter) {
+      DCHECK(!ContainsKey(web_options.injected_global_object_attributes,
+                          iter->first));
+      // Trampoline to the given callback, adding a pointer to this WebModule.
+      web_options.injected_global_object_attributes[iter->first] =
+          base::Bind(iter->second, base::Unretained(this));
+    }
+  }
+
+  web_agent_->Run(web_options,
+                  base::Bind(&WebModule::InitializeTaskInThread,
+                             base::Unretained(this), construction_data),
+                  this);
 }
 
-WebModule::~WebModule() {
-  DCHECK(message_loop());
-  DCHECK(web_agent_);
-  // This will cancel the timers for tasks, which help the thread exit
-  ClearAllIntervalsAndTimeouts();
-  web_agent_->WaitUntilDone();
-  web_agent_.reset();
-}
-
-void WebModule::Initialize(const ConstructionData& data,
-                           web::Context* context) {
+void WebModule::InitializeTaskInThread(const ConstructionData& data,
+                                       web::Context* context) {
   DCHECK_EQ(base::MessageLoop::current(), message_loop());
   impl_.reset(new Impl(context, data));
 }
diff --git a/cobalt/browser/web_module.h b/cobalt/browser/web_module.h
index 3e442c8..a29c1bd 100644
--- a/cobalt/browser/web_module.h
+++ b/cobalt/browser/web_module.h
@@ -55,6 +55,7 @@
 #include "cobalt/web/blob.h"
 #include "cobalt/web/context.h"
 #include "cobalt/web/csp_delegate.h"
+#include "cobalt/web/environment_settings.h"
 #include "cobalt/web/user_agent_platform_info.h"
 #include "cobalt/webdriver/session_driver.h"
 #include "starboard/atomic.h"
@@ -90,9 +91,15 @@
                   public LifecycleObserver {
  public:
   struct Options {
+    typedef base::Callback<scoped_refptr<script::Wrappable>(
+        WebModule*, web::EnvironmentSettings*)>
+        CreateObjectFunction;
+    typedef base::hash_map<std::string, CreateObjectFunction>
+        InjectedGlobalObjectAttributes;
+
     // All optional parameters defined in this structure should have their
     // values initialized in the default constructor to useful defaults.
-    explicit Options(const std::string& name);
+    Options();
 
     web::Agent::Options web_options;
 
@@ -240,6 +247,11 @@
     // time.
     base::Callback<void(base::TimeTicks, base::TimeTicks)>
         collect_unload_event_time_callback;
+
+    // injected_global_attributes contains a map of attributes to be injected
+    // into the Web Agent's window object upon construction.  This provides
+    // a mechanism to inject custom APIs into the Web Agent object.
+    InjectedGlobalObjectAttributes injected_global_object_attributes;
   };
 
   typedef layout::LayoutManager::LayoutResults LayoutResults;
@@ -248,18 +260,19 @@
   typedef base::Callback<void(const GURL&, const std::string&)> OnErrorCallback;
   typedef dom::Window::CloseCallback CloseCallback;
 
-  WebModule(const GURL& initial_url,
-            base::ApplicationState initial_application_state,
-            const OnRenderTreeProducedCallback& render_tree_produced_callback,
-            OnErrorCallback error_callback,
-            const CloseCallback& window_close_callback,
-            const base::Closure& window_minimize_callback,
-            media::CanPlayTypeHandler* can_play_type_handler,
-            media::MediaModule* media_module,
-            const cssom::ViewportSize& window_dimensions,
-            render_tree::ResourceProvider* resource_provider,
-            float layout_refresh_rate, const Options& options);
+  explicit WebModule(const std::string& name);
   ~WebModule();
+  void Run(const GURL& initial_url,
+           base::ApplicationState initial_application_state,
+           const OnRenderTreeProducedCallback& render_tree_produced_callback,
+           OnErrorCallback error_callback,
+           const CloseCallback& window_close_callback,
+           const base::Closure& window_minimize_callback,
+           media::CanPlayTypeHandler* can_play_type_handler,
+           media::MediaModule* media_module,
+           const cssom::ViewportSize& window_dimensions,
+           render_tree::ResourceProvider* resource_provider,
+           float layout_refresh_rate, const Options& options);
 
   // Injects an on screen keyboard input event into the web module. The value
   // for type represents beforeinput or input.
@@ -391,7 +404,7 @@
 
  private:
   // Data required to construct a WebModule, initialized in the constructor and
-  // passed to |Initialize|.
+  // passed to |InitializeTaskInThread|.
   struct ConstructionData {
     ConstructionData(const GURL& initial_url,
                      base::ApplicationState initial_application_state,
@@ -453,9 +466,10 @@
   // Forward declaration of the private implementation class.
   class Impl;
 
-  // Called by the constructor to create the private implementation object and
+  // Called by |Run| to create the private implementation object and
   // perform any other initialization required on the dedicated thread.
-  void Initialize(const ConstructionData& data, web::Context* context);
+  void InitializeTaskInThread(const ConstructionData& data,
+                              web::Context* context);
 
   void ClearAllIntervalsAndTimeouts();
 
@@ -467,7 +481,7 @@
   // The message loop this object is running on.
   base::MessageLoop* message_loop() const {
     DCHECK(web_agent_);
-    return web_agent_->message_loop();
+    return web_agent_ ? web_agent_->message_loop() : nullptr;
   }
 
   // Private implementation object.
diff --git a/cobalt/demos/content/watchdog-demo/index.html b/cobalt/demos/content/watchdog-demo/index.html
index c5f17f8..d23e7d4 100644
--- a/cobalt/demos/content/watchdog-demo/index.html
+++ b/cobalt/demos/content/watchdog-demo/index.html
@@ -63,7 +63,7 @@
           } else if (watchdogFunction == 'unregister') {
             ret = h5vcc.crashLog.unregister('test-name');
           } else if (watchdogFunction == 'ping') {
-            ret = h5vcc.crashLog.ping('test-name', `test-ping`);
+            ret = h5vcc.crashLog.ping('test-name', 'test-ping');
           } else if (watchdogFunction == 'getWatchdogViolations') {
             ret = h5vcc.crashLog.getWatchdogViolations();
           } else if (watchdogFunction == 'getPersistentSettingWatchdogEnable') {
diff --git a/cobalt/dom/event_queue.cc b/cobalt/dom/event_queue.cc
index ca851e8..1bd5282 100644
--- a/cobalt/dom/event_queue.cc
+++ b/cobalt/dom/event_queue.cc
@@ -44,23 +44,6 @@
   events_.push_back(event);
 }
 
-void EventQueue::EnqueueAndMaybeDispatchImmediately(
-    const scoped_refptr<web::Event>& event) {
-  DCHECK(message_loop_->BelongsToCurrentThread());
-
-  bool was_empty = events_.empty();
-
-  Enqueue(event);
-
-  if (was_empty) {
-    // We can only dispatch the event immediately if there aren't any existing
-    // events in the queue, because dom activities (including events) have to
-    // happen in order, and any existing events in the queue may mean that these
-    // events have to be dispatched after other activities not tracked here.
-    DispatchEvents();
-  }
-}
-
 void EventQueue::CancelAllEvents() {
   DCHECK(message_loop_->BelongsToCurrentThread());
 
diff --git a/cobalt/dom/event_queue.h b/cobalt/dom/event_queue.h
index 6aad863..a81e482 100644
--- a/cobalt/dom/event_queue.h
+++ b/cobalt/dom/event_queue.h
@@ -44,11 +44,6 @@
   explicit EventQueue(web::EventTarget* event_target);
 
   void Enqueue(const scoped_refptr<web::Event>& event);
-  // Try to dispatch the event immediately if there are no existing events in
-  // the queue, otherwise it has the same behavior as Enqueue(), which enqueues
-  // the event and the event will be dispatched after the existing events.
-  void EnqueueAndMaybeDispatchImmediately(
-      const scoped_refptr<web::Event>& event);
   void CancelAllEvents();
 
   void TraceMembers(script::Tracer* tracer) override;
diff --git a/cobalt/dom/event_queue_test.cc b/cobalt/dom/event_queue_test.cc
index be4a970..e8fc35f 100644
--- a/cobalt/dom/event_queue_test.cc
+++ b/cobalt/dom/event_queue_test.cc
@@ -90,42 +90,6 @@
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(EventQueueTest, EnqueueAndMaybeDispatchImmediatelyTest) {
-  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<web::EventListener>(event_listener.get()),
-      false);
-
-  {
-    ::testing::InSequence s;
-    ExpectHandleEventCallWithEventAndTarget(event_listener.get(), event,
-                                            event_target);
-    // The event should be dispatched immediately as the queue is empty, so the
-    // expectation should be set before being enqueued.
-    event_queue.EnqueueAndMaybeDispatchImmediately(event);
-  }
-
-  {
-    ::testing::InSequence s;
-    event_queue.Enqueue(event);
-    // The event won't be dispatched immediately as the queue isn't empty, so
-    // the expectations can be set after being enqueued.
-    event_queue.EnqueueAndMaybeDispatchImmediately(event);
-    ExpectHandleEventCallWithEventAndTarget(event_listener.get(), event,
-                                            event_target);
-    ExpectHandleEventCallWithEventAndTarget(event_listener.get(), event,
-                                            event_target);
-    base::RunLoop().RunUntilIdle();
-  }
-}
-
 TEST_F(EventQueueTest, CancelAllEventsTest) {
   scoped_refptr<web::EventTarget> event_target =
       new web::EventTarget(&environment_settings_);
diff --git a/cobalt/dom/html_script_element.cc b/cobalt/dom/html_script_element.cc
index 42f7997..236cd3e 100644
--- a/cobalt/dom/html_script_element.cc
+++ b/cobalt/dom/html_script_element.cc
@@ -351,6 +351,7 @@
           base::Bind(&loader::TextDecoder::Create,
                      base::Bind(&HTMLScriptElement::OnSyncContentProduced,
                                 base::Unretained(this)),
+                     loader::TextDecoder::ResponseStartedCallback(),
                      loader::Decoder::OnCompleteFunction()),
           base::Bind(&HTMLScriptElement::OnSyncLoadingComplete,
                      base::Unretained(this)));
diff --git a/cobalt/dom/media_source.cc b/cobalt/dom/media_source.cc
index 1dcc7d3..22f6bc5 100644
--- a/cobalt/dom/media_source.cc
+++ b/cobalt/dom/media_source.cc
@@ -243,9 +243,8 @@
   ChunkDemuxer::Status status = chunk_demuxer_->AddId(guid, type);
   switch (status) {
     case ChunkDemuxer::kOk:
-      source_buffer = new SourceBuffer(settings, guid, this,
-                                       asynchronous_reduction_enabled_,
-                                       chunk_demuxer_, &event_queue_);
+      source_buffer =
+          new SourceBuffer(settings, guid, this, chunk_demuxer_, &event_queue_);
       break;
     case ChunkDemuxer::kNotSupported:
       web::DOMException::Raise(web::DOMException::kNotSupportedErr,
diff --git a/cobalt/dom/media_source_settings.cc b/cobalt/dom/media_source_settings.cc
index a330f83..84ac0f9 100644
--- a/cobalt/dom/media_source_settings.cc
+++ b/cobalt/dom/media_source_settings.cc
@@ -52,6 +52,12 @@
       LOG(INFO) << name << ": set to " << value;
       return true;
     }
+  } else if (name == "MediaSource.MaxSourceBufferAppendSizeInBytes") {
+    if (value > 0) {
+      max_source_buffer_append_size_in_bytes_ = value;
+      LOG(INFO) << name << ": set to " << value;
+      return true;
+    }
   } else {
     LOG(WARNING) << "Ignore unknown setting with name \"" << name << "\"";
     return false;
diff --git a/cobalt/dom/media_source_settings.h b/cobalt/dom/media_source_settings.h
index 8d1b0be..61641b1 100644
--- a/cobalt/dom/media_source_settings.h
+++ b/cobalt/dom/media_source_settings.h
@@ -36,6 +36,7 @@
       const = 0;
   virtual base::Optional<bool> IsAsynchronousReductionEnabled() const = 0;
   virtual base::Optional<int> GetMinSizeForImmediateJob() const = 0;
+  virtual base::Optional<int> GetMaxSourceBufferAppendSizeInBytes() const = 0;
 
  protected:
   MediaSourceSettings() = default;
@@ -67,6 +68,10 @@
     base::AutoLock auto_lock(lock_);
     return min_size_for_immediate_job_;
   }
+  base::Optional<int> GetMaxSourceBufferAppendSizeInBytes() const override {
+    base::AutoLock auto_lock(lock_);
+    return max_source_buffer_append_size_in_bytes_;
+  }
 
   // Returns true when the setting associated with `name` is set to `value`.
   // Returns false when `name` is not associated with any settings, or if
@@ -79,6 +84,7 @@
   base::Optional<int> minimum_processor_count_to_offload_algorithm_;
   base::Optional<bool> is_asynchronous_reduction_enabled_;
   base::Optional<int> min_size_for_immediate_job_;
+  base::Optional<int> max_source_buffer_append_size_in_bytes_;
 };
 
 }  // namespace dom
diff --git a/cobalt/dom/media_source_settings_test.cc b/cobalt/dom/media_source_settings_test.cc
index 27d9e9e..25d7878 100644
--- a/cobalt/dom/media_source_settings_test.cc
+++ b/cobalt/dom/media_source_settings_test.cc
@@ -27,6 +27,7 @@
   EXPECT_FALSE(impl.GetMinimumProcessorCountToOffloadAlgorithm());
   EXPECT_FALSE(impl.IsAsynchronousReductionEnabled());
   EXPECT_FALSE(impl.GetMinSizeForImmediateJob());
+  EXPECT_FALSE(impl.GetMaxSourceBufferAppendSizeInBytes());
 }
 
 TEST(MediaSourceSettingsImplTest, SunnyDay) {
@@ -37,11 +38,13 @@
       impl.Set("MediaSource.MinimumProcessorCountToOffloadAlgorithm", 101));
   ASSERT_TRUE(impl.Set("MediaSource.EnableAsynchronousReduction", 1));
   ASSERT_TRUE(impl.Set("MediaSource.MinSizeForImmediateJob", 103));
+  ASSERT_TRUE(impl.Set("MediaSource.MaxSourceBufferAppendSizeInBytes", 100000));
 
   EXPECT_EQ(impl.GetSourceBufferEvictExtraInBytes().value(), 100);
   EXPECT_EQ(impl.GetMinimumProcessorCountToOffloadAlgorithm().value(), 101);
   EXPECT_TRUE(impl.IsAsynchronousReductionEnabled().value());
   EXPECT_EQ(impl.GetMinSizeForImmediateJob().value(), 103);
+  EXPECT_EQ(impl.GetMaxSourceBufferAppendSizeInBytes().value(), 100000);
 }
 
 TEST(MediaSourceSettingsImplTest, RainyDay) {
@@ -52,11 +55,13 @@
       impl.Set("MediaSource.MinimumProcessorCountToOffloadAlgorithm", -101));
   ASSERT_FALSE(impl.Set("MediaSource.EnableAsynchronousReduction", 2));
   ASSERT_FALSE(impl.Set("MediaSource.MinSizeForImmediateJob", -103));
+  ASSERT_FALSE(impl.Set("MediaSource.MaxSourceBufferAppendSizeInBytes", 0));
 
   EXPECT_FALSE(impl.GetSourceBufferEvictExtraInBytes());
   EXPECT_FALSE(impl.GetMinimumProcessorCountToOffloadAlgorithm());
   EXPECT_FALSE(impl.IsAsynchronousReductionEnabled());
   EXPECT_FALSE(impl.GetMinSizeForImmediateJob());
+  EXPECT_FALSE(impl.GetMaxSourceBufferAppendSizeInBytes());
 }
 
 TEST(MediaSourceSettingsImplTest, ZeroValuesWork) {
@@ -67,6 +72,7 @@
       impl.Set("MediaSource.MinimumProcessorCountToOffloadAlgorithm", 0));
   ASSERT_TRUE(impl.Set("MediaSource.EnableAsynchronousReduction", 0));
   ASSERT_TRUE(impl.Set("MediaSource.MinSizeForImmediateJob", 0));
+  // O is an invalid value for "MediaSource.MaxSourceBufferAppendSizeInBytes".
 
   EXPECT_EQ(impl.GetSourceBufferEvictExtraInBytes().value(), 0);
   EXPECT_EQ(impl.GetMinimumProcessorCountToOffloadAlgorithm().value(), 0);
@@ -82,17 +88,20 @@
       impl.Set("MediaSource.MinimumProcessorCountToOffloadAlgorithm", 0));
   ASSERT_TRUE(impl.Set("MediaSource.EnableAsynchronousReduction", 0));
   ASSERT_TRUE(impl.Set("MediaSource.MinSizeForImmediateJob", 0));
+  ASSERT_TRUE(impl.Set("MediaSource.MaxSourceBufferAppendSizeInBytes", 1));
 
   ASSERT_TRUE(impl.Set("MediaSource.SourceBufferEvictExtraInBytes", 1));
   ASSERT_TRUE(
       impl.Set("MediaSource.MinimumProcessorCountToOffloadAlgorithm", 1));
   ASSERT_TRUE(impl.Set("MediaSource.EnableAsynchronousReduction", 1));
   ASSERT_TRUE(impl.Set("MediaSource.MinSizeForImmediateJob", 1));
+  ASSERT_TRUE(impl.Set("MediaSource.MaxSourceBufferAppendSizeInBytes", 2));
 
   EXPECT_EQ(impl.GetSourceBufferEvictExtraInBytes().value(), 1);
   EXPECT_EQ(impl.GetMinimumProcessorCountToOffloadAlgorithm().value(), 1);
   EXPECT_TRUE(impl.IsAsynchronousReductionEnabled().value());
   EXPECT_EQ(impl.GetMinSizeForImmediateJob().value(), 1);
+  EXPECT_EQ(impl.GetMaxSourceBufferAppendSizeInBytes().value(), 2);
 }
 
 TEST(MediaSourceSettingsImplTest, InvalidSettingNames) {
diff --git a/cobalt/dom/on_screen_keyboard.cc b/cobalt/dom/on_screen_keyboard.cc
index 188aa1e..6f8da1b 100644
--- a/cobalt/dom/on_screen_keyboard.cc
+++ b/cobalt/dom/on_screen_keyboard.cc
@@ -23,6 +23,63 @@
 
 namespace cobalt {
 namespace dom {
+namespace {
+bool IsValidRGB(int r, int g, int b) {
+  if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+bool ParseColor(const char* color_str, int& r, int& g, int& b) {
+  size_t len = strlen(color_str);
+  if (len == 0) {
+    return false;
+  }
+
+  // Handle hexadecimal color notation #RRGGBB
+  int r_tmp, g_tmp, b_tmp;
+  bool is_hex =
+      SbStringScanF(color_str, "#%02x%02x%02x", &r_tmp, &g_tmp, &b_tmp) == 3;
+  if (is_hex && IsValidRGB(r_tmp, g_tmp, b_tmp)) {
+    r = r_tmp;
+    g = g_tmp;
+    b = b_tmp;
+    return true;
+  }
+
+  // Handle rgb color notation rgb(R, G, B)
+  if (!is_hex && len >= 10 &&
+      SbStringCompareNoCaseN("rgb(", color_str, 4) == 0) {
+    int rgb_tmp[3] = {-1, -1, -1};
+    const char* ptr = color_str + 4;
+    int i = 0;
+    while (*ptr) {
+      if (isdigit(*ptr)) {
+        char* end;
+        rgb_tmp[i++] = static_cast<int>(strtol(ptr, &end, 10));
+        if (i == 3) {
+          break;
+        }
+        ptr = (const char*)end;
+      } else if (isspace(*ptr) || *ptr == ',') {
+        ptr++;
+      } else {
+        return false;
+      }
+    }
+
+    if (IsValidRGB(rgb_tmp[0], rgb_tmp[1], rgb_tmp[2])) {
+      r = rgb_tmp[0];
+      g = rgb_tmp[1];
+      b = rgb_tmp[2];
+      return true;
+    }
+  }
+  return false;
+}
+}  // namespace
 
 OnScreenKeyboard::OnScreenKeyboard(
     script::EnvironmentSettings* settings, OnScreenKeyboardBridge* bridge,
@@ -48,10 +105,17 @@
   bridge_->Show(data_.c_str(), ticket);
 
   if (background_color_.has_value()) {
-    bridge_->SetBackgroundColor(background_color_.value().c_str());
+    int r, g, b;
+    if (ParseColor(background_color_.value().c_str(), r, g, b)) {
+      bridge_->SetBackgroundColor(static_cast<uint8>(r), static_cast<uint8>(g),
+                                  static_cast<uint8>(b));
+    } else {
+      LOG(WARNING) << "Invalid on-screen keyboard background color: "
+                   << background_color_.value();
+    }
   }
-  if (dark_theme_.has_value()) {
-    bridge_->SetDarkTheme(dark_theme_.value());
+  if (light_theme_.has_value()) {
+    bridge_->SetLightTheme(light_theme_.value());
   }
 
   return promise;
diff --git a/cobalt/dom/on_screen_keyboard.h b/cobalt/dom/on_screen_keyboard.h
index ce7d0fa..ecad6bf 100644
--- a/cobalt/dom/on_screen_keyboard.h
+++ b/cobalt/dom/on_screen_keyboard.h
@@ -106,10 +106,10 @@
     return background_color_;
   }
 
-  void set_dark_theme(base::Optional<bool> dark_theme) {
-    dark_theme_ = dark_theme;
+  void set_light_theme(base::Optional<bool> light_theme) {
+    light_theme_ = light_theme;
   }
-  base::Optional<bool> dark_theme() const { return dark_theme_; }
+  base::Optional<bool> light_theme() const { return light_theme_; }
 
   // Called by the WebModule to dispatch DOM show, hide, focus, blur and
   // suggestions updated events.
@@ -149,7 +149,7 @@
 
   base::Optional<std::string> background_color_;
 
-  base::Optional<bool> dark_theme_;
+  base::Optional<bool> light_theme_;
 
   DISALLOW_COPY_AND_ASSIGN(OnScreenKeyboard);
 };
diff --git a/cobalt/dom/on_screen_keyboard.idl b/cobalt/dom/on_screen_keyboard.idl
index fe13887..9487eec 100644
--- a/cobalt/dom/on_screen_keyboard.idl
+++ b/cobalt/dom/on_screen_keyboard.idl
@@ -51,7 +51,7 @@
   // 2. rgb color notation rgb(R, G, B), e.g. "rgb(0, 0, 255)".
   attribute DOMString? backgroundColor;
 
-  // This attribute overrides the dark theme of on-screen keyboard.
+  // This attribute overrides the light theme of on-screen keyboard.
   // The attribute is only fully supported on Apple TV at the moment.
-  attribute boolean? darkTheme;
+  attribute boolean? lightTheme;
 };
diff --git a/cobalt/dom/on_screen_keyboard_bridge.h b/cobalt/dom/on_screen_keyboard_bridge.h
index c5623af..ac6ad1c 100644
--- a/cobalt/dom/on_screen_keyboard_bridge.h
+++ b/cobalt/dom/on_screen_keyboard_bridge.h
@@ -17,6 +17,7 @@
 
 #include <string>
 
+#include "base/basictypes.h"
 #include "cobalt/dom/dom_rect.h"
 
 namespace cobalt {
@@ -39,8 +40,8 @@
   virtual scoped_refptr<DOMRect> BoundingRect() const = 0;
   virtual void SetKeepFocus(bool keep_focus) = 0;
   virtual bool IsValidTicket(int ticket) const = 0;
-  virtual void SetBackgroundColor(const char* background_color) = 0;
-  virtual void SetDarkTheme(bool dark_theme) = 0;
+  virtual void SetBackgroundColor(uint8 r, uint8 g, uint8 b) = 0;
+  virtual void SetLightTheme(bool light_theme) = 0;
 };
 
 }  // namespace dom
diff --git a/cobalt/dom/on_screen_keyboard_test.cc b/cobalt/dom/on_screen_keyboard_test.cc
index fd616a3..b567585 100644
--- a/cobalt/dom/on_screen_keyboard_test.cc
+++ b/cobalt/dom/on_screen_keyboard_test.cc
@@ -112,9 +112,9 @@
 
   void SetKeepFocus(bool keep_focus) override { SetKeepFocusMock(keep_focus); }
 
-  void SetBackgroundColor(const char* background_color) override {}
+  void SetBackgroundColor(uint8 r, uint8 g, uint8 b) override {}
 
-  void SetDarkTheme(bool dark_theme) override {}
+  void SetLightTheme(bool light_theme) override {}
 
   MOCK_METHOD1(ShowMock, void(std::string));
   MOCK_METHOD0(HideMock, void());
@@ -386,7 +386,7 @@
     let promise;
     let logString;
   )";
-  EXPECT_TRUE(EvaluateScript(let_script, NULL));
+  EXPECT_TRUE(EvaluateScript(let_script));
   const char event_script[] = R"(
     logString = '(empty)';
     window.onScreenKeyboard.onshow = function() {
@@ -436,7 +436,7 @@
     let promise;
     let logString;
   )";
-  EXPECT_TRUE(EvaluateScript(let_script, NULL));
+  EXPECT_TRUE(EvaluateScript(let_script));
   const char event_script[] = R"(
     logString = '(empty)';
     window.onScreenKeyboard.onhide = function() {
@@ -485,7 +485,7 @@
     let promise;
     let logString;
   )";
-  EXPECT_TRUE(EvaluateScript(let_script, NULL));
+  EXPECT_TRUE(EvaluateScript(let_script));
   const char event_script[] = R"(
     logString = '(empty)';
     window.onScreenKeyboard.onfocus = function() {
@@ -534,7 +534,7 @@
     let promise;
     let logString;
   )";
-  EXPECT_TRUE(EvaluateScript(let_script, NULL));
+  EXPECT_TRUE(EvaluateScript(let_script));
   const char event_script[] = R"(
     logString = '(empty)';
     window.onScreenKeyboard.onblur = function() {
@@ -605,7 +605,7 @@
     window.onScreenKeyboard.keepFocus = false;
     window.onScreenKeyboard.keepFocus = true;
   )";
-  EXPECT_TRUE(EvaluateScript(script, NULL));
+  EXPECT_TRUE(EvaluateScript(script));
 }
 
 TEST_F(OnScreenKeyboardTest, SetBackgroundColor) {
@@ -635,21 +635,21 @@
   EXPECT_EQ(color_str, result);
 }
 
-TEST_F(OnScreenKeyboardTest, SetDarkTheme) {
+TEST_F(OnScreenKeyboardTest, SetLightTheme) {
   if (SkipLocale()) return;
 
   std::string result;
-  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.darkTheme;", &result));
+  EXPECT_TRUE(EvaluateScript("window.onScreenKeyboard.lightTheme;", &result));
   EXPECT_EQ("null", result);
 
-  std::string dark_theme_str = "false";
+  std::string light_theme_str = "true";
   EXPECT_TRUE(
-      EvaluateScript("window.onScreenKeyboard.darkTheme = " + dark_theme_str +
+      EvaluateScript("window.onScreenKeyboard.lightTheme = " + light_theme_str +
                          ";"
-                         "window.onScreenKeyboard.darkTheme",
+                         "window.onScreenKeyboard.lightTheme",
                      &result));
 
-  EXPECT_EQ(dark_theme_str, result);
+  EXPECT_EQ(light_theme_str, result);
 }
 
 }  // namespace dom
diff --git a/cobalt/dom/source_buffer.cc b/cobalt/dom/source_buffer.cc
index 1f6278b..449eaa1 100644
--- a/cobalt/dom/source_buffer.cc
+++ b/cobalt/dom/source_buffer.cc
@@ -103,6 +103,19 @@
   return std::max<int>(bytes, 0);
 }
 
+size_t GetMaxAppendSizeInBytes(script::EnvironmentSettings* settings,
+                               size_t default_value) {
+  DOMSettings* dom_settings =
+      base::polymorphic_downcast<DOMSettings*>(settings);
+  DCHECK(dom_settings);
+  DCHECK(dom_settings->media_source_settings());
+  int bytes = dom_settings->media_source_settings()
+                  ->GetMaxSourceBufferAppendSizeInBytes()
+                  .value_or(default_value);
+  DCHECK_GT(bytes, 0);
+  return bytes;
+}
+
 }  // namespace
 
 SourceBuffer::OnInitSegmentReceivedHelper::OnInitSegmentReceivedHelper(
@@ -133,13 +146,13 @@
 
 SourceBuffer::SourceBuffer(script::EnvironmentSettings* settings,
                            const std::string& id, MediaSource* media_source,
-                           bool asynchronous_reduction_enabled,
                            ChunkDemuxer* chunk_demuxer, EventQueue* event_queue)
     : web::EventTarget(settings),
       on_init_segment_received_helper_(new OnInitSegmentReceivedHelper(this)),
       id_(id),
-      asynchronous_reduction_enabled_(asynchronous_reduction_enabled),
       evict_extra_in_bytes_(GetEvictExtraInBytes(settings)),
+      max_append_buffer_size_(
+          GetMaxAppendSizeInBytes(settings, kDefaultMaxAppendBufferSize)),
       media_source_(media_source),
       chunk_demuxer_(chunk_demuxer),
       event_queue_(event_queue),
@@ -154,6 +167,7 @@
   DCHECK(event_queue);
 
   LOG(INFO) << "Evict extra in bytes is set to " << evict_extra_in_bytes_;
+  LOG(INFO) << "Max append size in bytes is set to " << max_append_buffer_size_;
 
   chunk_demuxer_->SetTracksWatcher(
       id_,
@@ -381,10 +395,7 @@
   std::unique_ptr<SourceBufferAlgorithm> algorithm(
       new SourceBufferRemoveAlgorithm(
           chunk_demuxer_, id_, DoubleToTimeDelta(start), DoubleToTimeDelta(end),
-          base::Bind(asynchronous_reduction_enabled_
-                         ? &SourceBuffer::ScheduleAndMaybeDispatchImmediately
-                         : &SourceBuffer::ScheduleEvent,
-                     base::Unretained(this)),
+          base::Bind(&SourceBuffer::ScheduleEvent, base::Unretained(this)),
           base::Bind(&SourceBuffer::OnAlgorithmFinalized,
                      base::Unretained(this))));
   auto algorithm_runner =
@@ -481,16 +492,6 @@
   event_queue_->Enqueue(event);
 }
 
-void SourceBuffer::ScheduleAndMaybeDispatchImmediately(base::Token event_name) {
-  ScheduleEvent(event_name);
-  // TODO(b/244773734): Re-enable direct event dispatching
-  /*
-  scoped_refptr<web::Event> event = new web::Event(event_name);
-  event->set_target(this);
-  event_queue_->EnqueueAndMaybeDispatchImmediately(event);
-  */
-}
-
 bool SourceBuffer::PrepareAppend(size_t new_data_size,
                                  script::ExceptionState* exception_state) {
   TRACE_EVENT1("cobalt::dom", "SourceBuffer::PrepareAppend()", "new_data_size",
@@ -555,15 +556,12 @@
   std::unique_ptr<SourceBufferAlgorithm> algorithm(
       new SourceBufferAppendAlgorithm(
           media_source_, chunk_demuxer_, id_, pending_append_data_.get(), size,
-          DoubleToTimeDelta(append_window_start_),
+          max_append_buffer_size_, DoubleToTimeDelta(append_window_start_),
           DoubleToTimeDelta(append_window_end_),
           DoubleToTimeDelta(timestamp_offset_),
           base::Bind(&SourceBuffer::UpdateTimestampOffset,
                      base::Unretained(this)),
-          base::Bind(asynchronous_reduction_enabled_
-                         ? &SourceBuffer::ScheduleAndMaybeDispatchImmediately
-                         : &SourceBuffer::ScheduleEvent,
-                     base::Unretained(this)),
+          base::Bind(&SourceBuffer::ScheduleEvent, base::Unretained(this)),
           base::Bind(&SourceBuffer::OnAlgorithmFinalized,
                      base::Unretained(this)),
           &metrics_));
diff --git a/cobalt/dom/source_buffer.h b/cobalt/dom/source_buffer.h
index c7336e6..25b15ac 100644
--- a/cobalt/dom/source_buffer.h
+++ b/cobalt/dom/source_buffer.h
@@ -90,8 +90,8 @@
   // Custom, not in any spec.
   //
   SourceBuffer(script::EnvironmentSettings* settings, const std::string& id,
-               MediaSource* media_source, bool asynchronous_reduction_enabled,
-               ChunkDemuxer* chunk_demuxer, EventQueue* event_queue);
+               MediaSource* media_source, ChunkDemuxer* chunk_demuxer,
+               EventQueue* event_queue);
 
   // Web API: SourceBuffer
   //
@@ -167,7 +167,6 @@
 
   void OnInitSegmentReceived(std::unique_ptr<MediaTracks> tracks);
   void ScheduleEvent(base::Token event_name);
-  void ScheduleAndMaybeDispatchImmediately(base::Token event_name);
   bool PrepareAppend(size_t new_data_size,
                      script::ExceptionState* exception_state);
   void AppendBufferInternal(const unsigned char* data, size_t size,
@@ -185,10 +184,12 @@
       const std::string& track_type,
       const std::string& byte_stream_track_id) const;
 
+  static const size_t kDefaultMaxAppendBufferSize = 128 * 1024;
+
   scoped_refptr<OnInitSegmentReceivedHelper> on_init_segment_received_helper_;
   const std::string id_;
-  const bool asynchronous_reduction_enabled_;
   const size_t evict_extra_in_bytes_;
+  const size_t max_append_buffer_size_;
 
   MediaSource* media_source_;
   ChunkDemuxer* chunk_demuxer_;
diff --git a/cobalt/dom/source_buffer_algorithm.cc b/cobalt/dom/source_buffer_algorithm.cc
index 50eec01..a24e51c 100644
--- a/cobalt/dom/source_buffer_algorithm.cc
+++ b/cobalt/dom/source_buffer_algorithm.cc
@@ -27,8 +27,8 @@
 SourceBufferAppendAlgorithm::SourceBufferAppendAlgorithm(
     MediaSource* media_source, ::media::ChunkDemuxer* chunk_demuxer,
     const std::string& id, const uint8_t* buffer, size_t size_in_bytes,
-    base::TimeDelta append_window_start, base::TimeDelta append_window_end,
-    base::TimeDelta timestamp_offset,
+    size_t max_append_size_in_bytes, base::TimeDelta append_window_start,
+    base::TimeDelta append_window_end, base::TimeDelta timestamp_offset,
     UpdateTimestampOffsetCB&& update_timestamp_offset_cb,
     ScheduleEventCB&& schedule_event_cb, base::OnceClosure&& finalized_cb,
     SourceBufferMetrics* metrics)
@@ -36,6 +36,7 @@
       chunk_demuxer_(chunk_demuxer),
       id_(id),
       buffer_(buffer),
+      max_append_size_(max_append_size_in_bytes),
       bytes_remaining_(size_in_bytes),
       append_window_start_(append_window_start),
       append_window_end_(append_window_end),
@@ -46,6 +47,7 @@
       metrics_(metrics) {
   DCHECK(media_source_);
   DCHECK(chunk_demuxer_);
+  DCHECK_GT(static_cast<int>(max_append_size_), 0);
   if (bytes_remaining_ > 0) {
     DCHECK(buffer);
   }
@@ -60,9 +62,7 @@
   DCHECK(finished);
   DCHECK(!*finished);
 
-  const size_t kMaxAppendSize = 128 * 1024;
-
-  size_t append_size = std::min(bytes_remaining_, kMaxAppendSize);
+  size_t append_size = std::min(bytes_remaining_, max_append_size_);
 
   TRACE_EVENT1("cobalt::dom", "SourceBufferAppendAlgorithm::Process()",
                "append_size", append_size);
diff --git a/cobalt/dom/source_buffer_algorithm.h b/cobalt/dom/source_buffer_algorithm.h
index d9e0f16..3505794 100644
--- a/cobalt/dom/source_buffer_algorithm.h
+++ b/cobalt/dom/source_buffer_algorithm.h
@@ -58,8 +58,8 @@
   SourceBufferAppendAlgorithm(
       MediaSource* media_source, ::media::ChunkDemuxer* chunk_demuxer,
       const std::string& id, const uint8_t* buffer, size_t size_in_bytes,
-      base::TimeDelta append_window_start, base::TimeDelta append_window_end,
-      base::TimeDelta timestamp_offset,
+      size_t max_append_size_in_bytes, base::TimeDelta append_window_start,
+      base::TimeDelta append_window_end, base::TimeDelta timestamp_offset,
       UpdateTimestampOffsetCB&& update_timestamp_offset_cb,
       ScheduleEventCB&& schedule_event_cb, base::OnceClosure&& finalized_cb,
       SourceBufferMetrics* metrics);
@@ -74,6 +74,7 @@
   ::media::ChunkDemuxer* const chunk_demuxer_;
   const std::string id_;
   const uint8_t* buffer_;
+  const size_t max_append_size_;
   size_t bytes_remaining_;
   const base::TimeDelta append_window_start_;
   const base::TimeDelta append_window_end_;
diff --git a/cobalt/dom/testing/stub_window.h b/cobalt/dom/testing/stub_window.h
index 6651633..9a67b66 100644
--- a/cobalt/dom/testing/stub_window.h
+++ b/cobalt/dom/testing/stub_window.h
@@ -91,18 +91,6 @@
     on_screen_keyboard_bridge_ = on_screen_keyboard_bridge;
   }
 
- private:
-  static void StubLoadCompleteCallback(
-      const base::Optional<std::string>& error) {}
-
-  void InitializeWebContext() {
-    web_context_.reset(new web::testing::StubWebContext());
-    web_context()->setup_environment_settings(
-        new dom::testing::StubEnvironmentSettings(options_));
-    web_context()->environment_settings()->set_creation_url(
-        GURL("about:blank"));
-  }
-
   void InitializeWindow() {
     loader_factory_.reset(new loader::LoaderFactory(
         "Test", web_context()->fetcher_factory(), NULL,
@@ -143,6 +131,19 @@
         window_, web_context()->environment_settings());
   }
 
+ private:
+  static void StubLoadCompleteCallback(
+      const base::Optional<std::string>& error) {}
+
+  void InitializeWebContext() {
+    web_context_.reset(new web::testing::StubWebContext());
+    web_context()->setup_environment_settings(
+        new dom::testing::StubEnvironmentSettings(options_));
+    web_context()->environment_settings()->set_creation_url(
+        GURL("about:blank"));
+  }
+
+
   OnScreenKeyboardBridge* on_screen_keyboard_bridge_ = nullptr;
   DOMSettings::Options options_;
   std::unique_ptr<css_parser::Parser> css_parser_;
diff --git a/cobalt/dom/testing/test_with_javascript.h b/cobalt/dom/testing/test_with_javascript.h
index 30204af..e51747f 100644
--- a/cobalt/dom/testing/test_with_javascript.h
+++ b/cobalt/dom/testing/test_with_javascript.h
@@ -42,24 +42,38 @@
     EXPECT_TRUE(GlobalStats::GetInstance()->CheckNoLeaks());
   }
 
-  StubWindow* stub_window() { return stub_window_.get(); }
-  web::testing::StubWebContext* stub_web_context() {
+  StubWindow* stub_window() const { return stub_window_.get(); }
+  web::testing::StubWebContext* stub_web_context() const {
+    DCHECK(stub_window_);
     return stub_window_->web_context();
   }
 
-  Window* window() { return stub_window_->window().get(); }
+  Window* window() const {
+    DCHECK(stub_window_);
+    return stub_window_->window().get();
+  }
 
-  bool EvaluateScript(const std::string& js_code, std::string* result) {
+  web::Context* web_context() const { return stub_web_context(); }
+
+  scoped_refptr<script::GlobalEnvironment> global_environment() const {
+    DCHECK(web_context());
+    return web_context()->global_environment();
+  }
+
+  bool EvaluateScript(const std::string& js_code,
+                      std::string* result = nullptr) {
+    DCHECK(global_environment());
     return global_environment()->EvaluateScript(
         CreateSourceCodeAndPrepareEval(js_code), result);
   }
 
   bool EvaluateScript(
       const std::string& js_code,
-      const scoped_refptr<script::Wrappable>& owning_object,
-      base::Optional<script::ValueHandleHolder::Reference>* result = NULL) {
+      base::Optional<script::ValueHandleHolder::Reference>* result) {
+    DCHECK(global_environment());
     return global_environment()->EvaluateScript(
-        CreateSourceCodeAndPrepareEval(js_code), owning_object, result);
+        CreateSourceCodeAndPrepareEval(js_code),
+        web_context()->GetWindowOrWorkerGlobalScope(), result);
   }
 
   ::testing::StrictMock<script::testing::MockExceptionState>*
diff --git a/cobalt/extension/crash_handler.h b/cobalt/extension/crash_handler.h
index 8cf4732..912caba 100644
--- a/cobalt/extension/crash_handler.h
+++ b/cobalt/extension/crash_handler.h
@@ -36,8 +36,15 @@
 
   // The fields below this point were added in version 1 or later.
 
+  // Deprecated in version 2 and later.
   bool (*OverrideCrashpadAnnotations)(
       CrashpadAnnotations* crashpad_annotations);
+
+  // The fields below this point were added in version 2 or later.
+
+  // Sets a (key, value) pair for the handler to include when annotating a
+  // crash. Returns true on success and false on failure.
+  bool (*SetString)(const char* key, const char* value);
 } CobaltExtensionCrashHandlerApi;
 
 #ifdef __cplusplus
diff --git a/cobalt/extension/extension_test.cc b/cobalt/extension/extension_test.cc
index cd7158d..58f051e 100644
--- a/cobalt/extension/extension_test.cc
+++ b/cobalt/extension/extension_test.cc
@@ -221,9 +221,14 @@
   }
 
   EXPECT_STREQ(extension_api->name, kExtensionName);
-  EXPECT_EQ(extension_api->version, 1u);
+  EXPECT_GE(extension_api->version, 1u);
+  EXPECT_LE(extension_api->version, 2u);
   EXPECT_NE(extension_api->OverrideCrashpadAnnotations, nullptr);
 
+  if (extension_api->version >= 2) {
+    EXPECT_NE(extension_api->SetString, nullptr);
+  }
+
   const ExtensionApi* second_extension_api =
       static_cast<const ExtensionApi*>(SbSystemGetExtension(kExtensionName));
   EXPECT_EQ(second_extension_api, extension_api)
diff --git a/cobalt/extension/on_screen_keyboard.h b/cobalt/extension/on_screen_keyboard.h
index a1d922d..315e2b7 100644
--- a/cobalt/extension/on_screen_keyboard.h
+++ b/cobalt/extension/on_screen_keyboard.h
@@ -15,8 +15,6 @@
 #ifndef COBALT_EXTENSION_ON_SCREEN_KEYBOARD_H_
 #define COBALT_EXTENSION_ON_SCREEN_KEYBOARD_H_
 
-#include <stdint.h>
-
 #include "starboard/system.h"
 #include "starboard/window.h"
 
@@ -38,16 +36,12 @@
 
   // The fields below this point were added in version 1 or later.
 
-  // This function overrides the background color of on-screen keyboard.
-  // The function is only enabled for Apple TV at the moment.
-  // background_color should be a string of below formats:
-  // 1. hex color notation #RRGGBB, e.g. "#0000FF".
-  // 2. rgb color notation rgb(R, G, B), e.g. "rgb(0, 0, 255)".
-  void (*SetBackgroundColor)(SbWindow window, const char* background_color);
+  // This function overrides the background color of on-screen keyboard in RGB
+  // color space, where r, g, b are between 0 and 255.
+  void (*SetBackgroundColor)(SbWindow window, uint8_t r, uint8_t g, uint8_t b);
 
-  // This function overrides the dark theme of on-screen keyboard.
-  // The function is only enabled for Apple TV at the moment.
-  void (*SetDarkTheme)(SbWindow window, bool dark_theme);
+  // This function overrides the light theme of on-screen keyboard.
+  void (*SetLightTheme)(SbWindow window, bool light_theme);
 } CobaltExtensionOnScreenKeyboardApi;
 
 #ifdef __cplusplus
diff --git a/cobalt/h5vcc/h5vcc_crash_log.cc b/cobalt/h5vcc/h5vcc_crash_log.cc
index 737d64c..a240d92 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.cc
+++ b/cobalt/h5vcc/h5vcc_crash_log.cc
@@ -20,6 +20,7 @@
 #include "base/atomicops.h"
 #include "base/memory/singleton.h"
 #include "base/synchronization/lock.h"
+#include "cobalt/extension/crash_handler.h"
 
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
 #include STARBOARD_CORE_DUMP_HANDLER_INCLUDE
@@ -97,6 +98,15 @@
 
 bool H5vccCrashLog::SetString(const std::string& key,
                               const std::string& value) {
+  auto crash_handler_extension =
+      static_cast<const CobaltExtensionCrashHandlerApi*>(
+          SbSystemGetExtension(kCobaltExtensionCrashHandlerName));
+  if (crash_handler_extension && crash_handler_extension->version >= 2) {
+    return crash_handler_extension->SetString(key.c_str(), value.c_str());
+  }
+  // The platform has not implemented a version of the CrashHandler Cobalt
+  // Extension appropriate for this use case.
+
   // Forward the call to a global singleton so that we keep a consistent crash
   // log globally.
   CrashLogDictionary::GetInstance()->SetString(key, value);
diff --git a/cobalt/h5vcc/h5vcc_crash_log.idl b/cobalt/h5vcc/h5vcc_crash_log.idl
index 7892219..e93caac 100644
--- a/cobalt/h5vcc/h5vcc_crash_log.idl
+++ b/cobalt/h5vcc/h5vcc_crash_log.idl
@@ -56,9 +56,8 @@
   boolean ping(DOMString name, DOMString ping_info);
 
   // Returns a json string containing the Watchdog violations since the last
-  // call, up to the 200 most recent. Clears internal cache of Watchdog
-  // violations to prevent duplicates. Timestamps are stored as strings due to
-  // int size constraints.
+  // call, up to 200. Clears internal cache of Watchdog violations to prevent
+  // duplicates. Timestamps are stored as strings due to int size constraints.
   // Example json:
   // {
   //   "test-name":{
diff --git a/cobalt/layout_tests/layout_snapshot.cc b/cobalt/layout_tests/layout_snapshot.cc
index 57e3769..88d07f3 100644
--- a/cobalt/layout_tests/layout_snapshot.cc
+++ b/cobalt/layout_tests/layout_snapshot.cc
@@ -83,7 +83,7 @@
 
   // 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("SnapshotURL");
+  browser::WebModule::Options web_module_options;
   web_module_options.layout_trigger = layout::LayoutManager::kTestRunnerMode;
   web_module_options.image_cache_capacity = kImageCacheCapacity;
   web_module_options.provide_screenshot_function = screenshot_provider;
@@ -97,7 +97,8 @@
   base::Optional<browser::WebModule::LayoutResults> results;
 
   // Create the WebModule and wait for a layout to occur.
-  browser::WebModule web_module(
+  browser::WebModule web_module("SnapshotURL");
+  web_module.Run(
       url, base::kApplicationStateStarted,
       base::Bind(&WebModuleOnRenderTreeProducedCallback, &results, &run_loop,
                  base::MessageLoop::current()),
diff --git a/cobalt/layout_tests/web_platform_tests.cc b/cobalt/layout_tests/web_platform_tests.cc
index 520ed72..c5406bf 100644
--- a/cobalt/layout_tests/web_platform_tests.cc
+++ b/cobalt/layout_tests/web_platform_tests.cc
@@ -209,7 +209,7 @@
       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");
+  browser::WebModule::Options web_module_options;
   web_module_options.layout_trigger = layout::LayoutManager::kTestRunnerMode;
   // We assume that we won't suspend/resume while running the tests, and so
   // we take advantage of the convenience of inline script tags.
@@ -222,7 +222,8 @@
   base::RunLoop run_loop;
 
   // Create the WebModule and wait for a layout to occur.
-  browser::WebModule web_module(
+  browser::WebModule web_module("RunWebPlatformTest");
+  web_module.Run(
       url, base::kApplicationStateStarted,
       base::Bind(&WebModuleOnRenderTreeProducedCallback, &results),
       base::Bind(&WebModuleErrorCallback, &run_loop,
diff --git a/cobalt/loader/loader_factory.cc b/cobalt/loader/loader_factory.cc
index ba571ba..0cb575e 100644
--- a/cobalt/loader/loader_factory.cc
+++ b/cobalt/loader/loader_factory.cc
@@ -71,7 +71,8 @@
 
   std::unique_ptr<Loader> loader(new Loader(
       fetcher_creator,
-      base::Bind(&loader::TextDecoder::Create, link_available_callback),
+      base::Bind(&loader::TextDecoder::Create, link_available_callback,
+                 loader::TextDecoder::ResponseStartedCallback()),
       load_complete_callback,
       base::Bind(&LoaderFactory::OnLoaderDestroyed, base::Unretained(this)),
       is_suspended_));
diff --git a/cobalt/loader/loader_test.cc b/cobalt/loader/loader_test.cc
index fc0b3b6..424f402 100644
--- a/cobalt/loader/loader_test.cc
+++ b/cobalt/loader/loader_test.cc
@@ -250,13 +250,13 @@
   FileFetcher::Options fetcher_options;
   TextDecoderCallback text_decoder_callback(&run_loop);
   LoaderCallback loader_callback(&run_loop);
-  Loader loader(
-      base::Bind(&FileFetcher::Create, file_path, fetcher_options),
-      base::Bind(&loader::TextDecoder::Create,
-                 base::Bind(&TextDecoderCallback::OnDone,
-                            base::Unretained(&text_decoder_callback))),
-      base::Bind(&LoaderCallback::OnLoadComplete,
-                 base::Unretained(&loader_callback)));
+  Loader loader(base::Bind(&FileFetcher::Create, file_path, fetcher_options),
+                base::Bind(&loader::TextDecoder::Create,
+                           base::Bind(&TextDecoderCallback::OnDone,
+                                      base::Unretained(&text_decoder_callback)),
+                           loader::TextDecoder::ResponseStartedCallback()),
+                base::Bind(&LoaderCallback::OnLoadComplete,
+                           base::Unretained(&loader_callback)));
 
   // When the message loop runs, the loader will start loading. It'll quit when
   // loading is finished.
diff --git a/cobalt/loader/script_loader_factory.cc b/cobalt/loader/script_loader_factory.cc
index 8fb5cac..0e64cf0 100644
--- a/cobalt/loader/script_loader_factory.cc
+++ b/cobalt/loader/script_loader_factory.cc
@@ -47,19 +47,31 @@
     const csp::SecurityCallback& url_security_callback,
     const TextDecoder::TextAvailableCallback& script_available_callback,
     const Loader::OnCompleteFunction& load_complete_callback) {
+  return CreateScriptLoader(
+      url, origin, url_security_callback, script_available_callback,
+      TextDecoder::ResponseStartedCallback(), load_complete_callback);
+}
+
+std::unique_ptr<Loader> ScriptLoaderFactory::CreateScriptLoader(
+    const GURL& url, const Origin& origin,
+    const csp::SecurityCallback& url_security_callback,
+    const TextDecoder::TextAvailableCallback& script_available_callback,
+    const TextDecoder::ResponseStartedCallback& response_started_callback,
+    const Loader::OnCompleteFunction& load_complete_callback) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   Loader::FetcherCreator fetcher_creator =
       MakeFetcherCreator(url, url_security_callback, kNoCORSMode, origin,
                          disk_cache::kUncompiledScript);
 
-  std::unique_ptr<Loader> loader(new Loader(
-      fetcher_creator,
-      base::Bind(&loader::TextDecoder::Create, script_available_callback),
-      load_complete_callback,
-      base::Bind(&ScriptLoaderFactory::OnLoaderDestroyed,
-                 base::Unretained(this)),
-      is_suspended_));
+  std::unique_ptr<Loader> loader(
+      new Loader(fetcher_creator,
+                 base::Bind(&TextDecoder::Create, script_available_callback,
+                            response_started_callback),
+                 load_complete_callback,
+                 base::Bind(&ScriptLoaderFactory::OnLoaderDestroyed,
+                            base::Unretained(this)),
+                 is_suspended_));
 
   OnLoaderCreated(loader.get());
   return loader;
diff --git a/cobalt/loader/script_loader_factory.h b/cobalt/loader/script_loader_factory.h
index 5fcc8a4..2ae2a40 100644
--- a/cobalt/loader/script_loader_factory.h
+++ b/cobalt/loader/script_loader_factory.h
@@ -48,6 +48,13 @@
       const TextDecoder::TextAvailableCallback& script_available_callback,
       const Loader::OnCompleteFunction& load_complete_callback);
 
+  std::unique_ptr<Loader> CreateScriptLoader(
+      const GURL& url, const Origin& origin,
+      const csp::SecurityCallback& url_security_callback,
+      const TextDecoder::TextAvailableCallback& script_available_callback,
+      const TextDecoder::ResponseStartedCallback& response_started_callback,
+      const Loader::OnCompleteFunction& load_complete_callback);
+
  protected:
   void OnLoaderCreated(Loader* loader);
   void OnLoaderDestroyed(Loader* loader);
diff --git a/cobalt/loader/text_decoder.h b/cobalt/loader/text_decoder.h
index 07a2492..16ad4ce 100644
--- a/cobalt/loader/text_decoder.h
+++ b/cobalt/loader/text_decoder.h
@@ -34,6 +34,9 @@
 // results.
 class TextDecoder : public Decoder {
  public:
+  typedef base::Callback<bool(
+      Fetcher* fetcher, const scoped_refptr<net::HttpResponseHeaders>& headers)>
+      ResponseStartedCallback;
   typedef base::Callback<void(const loader::Origin&,
                               std::unique_ptr<std::string>)>
       TextAvailableCallback;
@@ -43,10 +46,24 @@
   // This function is used for binding callback for creating TextDecoder.
   static std::unique_ptr<Decoder> Create(
       const TextAvailableCallback& text_available_callback,
+      const ResponseStartedCallback& response_started_callback =
+          ResponseStartedCallback(),
       const loader::Decoder::OnCompleteFunction& load_complete_callback =
           loader::Decoder::OnCompleteFunction()) {
-    return std::unique_ptr<Decoder>(
-        new TextDecoder(text_available_callback, load_complete_callback));
+    return std::unique_ptr<Decoder>(new TextDecoder(text_available_callback,
+                                                    response_started_callback,
+                                                    load_complete_callback));
+  }
+
+  LoadResponseType OnResponseStarted(
+      Fetcher* fetcher, const scoped_refptr<net::HttpResponseHeaders>& headers)
+      override WARN_UNUSED_RESULT {
+    if (fetcher && headers && !response_started_callback_.is_null()) {
+      if (!response_started_callback_.Run(fetcher, headers)) {
+        return kLoadResponseAbort;
+      }
+    }
+    return kLoadResponseContinue;
   }
 
   // From Decoder.
@@ -105,13 +122,16 @@
  private:
   explicit TextDecoder(
       const TextAvailableCallback& text_available_callback,
+      const ResponseStartedCallback& response_started_callback,
       const loader::Decoder::OnCompleteFunction& load_complete_callback)
       : text_available_callback_(text_available_callback),
+        response_started_callback_(response_started_callback),
         load_complete_callback_(load_complete_callback),
         suspended_(false) {}
 
   THREAD_CHECKER(thread_checker_);
   TextAvailableCallback text_available_callback_;
+  ResponseStartedCallback response_started_callback_;
   loader::Decoder::OnCompleteFunction load_complete_callback_;
   loader::Origin last_url_origin_;
   std::unique_ptr<std::string> text_;
diff --git a/cobalt/media/base/pipeline.h b/cobalt/media/base/pipeline.h
index e729c41..9f9995e 100644
--- a/cobalt/media/base/pipeline.h
+++ b/cobalt/media/base/pipeline.h
@@ -57,6 +57,7 @@
   typedef ::media::PipelineStatistics PipelineStatistics;
   typedef ::media::PipelineStatus PipelineStatus;
   typedef ::media::PipelineStatusCallback PipelineStatusCallback;
+  typedef ::media::PipelineStatusCB PipelineStatusCB;
 
   typedef base::Callback<void(PipelineStatus status, bool is_initial_preroll,
                               const std::string& error_message)>
@@ -126,7 +127,7 @@
   // It is an error to call this method after the pipeline has already started.
   virtual void Start(Demuxer* demuxer,
                      const SetDrmSystemReadyCB& set_drm_system_ready_cb,
-                     PipelineStatusCallback ended_cb, const ErrorCB& error_cb,
+                     const PipelineStatusCB& ended_cb, const ErrorCB& error_cb,
                      const SeekCB& seek_cb,
                      const BufferingStateCB& buffering_state_cb,
                      const base::Closure& duration_change_cb,
@@ -140,7 +141,7 @@
                      const OnEncryptedMediaInitDataEncounteredCB&
                          encrypted_media_init_data_encountered_cb,
                      const std::string& source_url,
-                     PipelineStatusCallback ended_cb, const ErrorCB& error_cb,
+                     const PipelineStatusCB& ended_cb, const ErrorCB& error_cb,
                      const SeekCB& seek_cb,
                      const BufferingStateCB& buffering_state_cb,
                      const base::Closure& duration_change_cb,
diff --git a/cobalt/media/base/sbplayer_pipeline.cc b/cobalt/media/base/sbplayer_pipeline.cc
index c48b29c..9b9d538 100644
--- a/cobalt/media/base/sbplayer_pipeline.cc
+++ b/cobalt/media/base/sbplayer_pipeline.cc
@@ -76,7 +76,7 @@
 struct StartTaskParameters {
   Demuxer* demuxer;
   SetDrmSystemReadyCB set_drm_system_ready_cb;
-  PipelineStatusCallback ended_cb;
+  ::media::PipelineStatusCB ended_cb;
   ErrorCB error_cb;
   Pipeline::SeekCB seek_cb;
   Pipeline::BufferingStateCB buffering_state_cb;
@@ -113,7 +113,7 @@
 
   void Start(Demuxer* demuxer,
              const SetDrmSystemReadyCB& set_drm_system_ready_cb,
-             PipelineStatusCallback ended_cb, const ErrorCB& error_cb,
+             const PipelineStatusCB& ended_cb, const ErrorCB& error_cb,
              const SeekCB& seek_cb, const BufferingStateCB& buffering_state_cb,
              const base::Closure& duration_change_cb,
              const base::Closure& output_mode_change_cb,
@@ -123,7 +123,7 @@
   void Start(const SetDrmSystemReadyCB& set_drm_system_ready_cb,
              const OnEncryptedMediaInitDataEncounteredCB&
                  encrypted_media_init_data_encountered_cb,
-             const std::string& source_url, PipelineStatusCallback ended_cb,
+             const std::string& source_url, const PipelineStatusCB& ended_cb,
              const ErrorCB& error_cb, const SeekCB& seek_cb,
              const BufferingStateCB& buffering_state_cb,
              const base::Closure& duration_change_cb,
@@ -265,7 +265,7 @@
 
   // Permanent callbacks passed in via Start().
   SetDrmSystemReadyCB set_drm_system_ready_cb_;
-  PipelineStatusCallback ended_cb_;
+  PipelineStatusCB ended_cb_;
   ErrorCB error_cb_;
   BufferingStateCB buffering_state_cb_;
   base::Closure duration_change_cb_;
@@ -441,7 +441,7 @@
 
 void SbPlayerPipeline::Start(Demuxer* demuxer,
                              const SetDrmSystemReadyCB& set_drm_system_ready_cb,
-                             PipelineStatusCallback ended_cb,
+                             const PipelineStatusCB& ended_cb,
                              const ErrorCB& error_cb, const SeekCB& seek_cb,
                              const BufferingStateCB& buffering_state_cb,
                              const base::Closure& duration_change_cb,
@@ -462,7 +462,7 @@
   StartTaskParameters parameters;
   parameters.demuxer = demuxer;
   parameters.set_drm_system_ready_cb = set_drm_system_ready_cb;
-  parameters.ended_cb = std::move(ended_cb);
+  parameters.ended_cb = ended_cb;
   parameters.error_cb = error_cb;
   parameters.seek_cb = seek_cb;
   parameters.buffering_state_cb = buffering_state_cb;
@@ -484,7 +484,7 @@
                              const OnEncryptedMediaInitDataEncounteredCB&
                                  on_encrypted_media_init_data_encountered_cb,
                              const std::string& source_url,
-                             PipelineStatusCallback ended_cb,
+                             const PipelineStatusCB& ended_cb,
                              const ErrorCB& error_cb, const SeekCB& seek_cb,
                              const BufferingStateCB& buffering_state_cb,
                              const base::Closure& duration_change_cb,
@@ -504,7 +504,7 @@
   StartTaskParameters parameters;
   parameters.demuxer = NULL;
   parameters.set_drm_system_ready_cb = set_drm_system_ready_cb;
-  parameters.ended_cb = std::move(ended_cb);
+  parameters.ended_cb = ended_cb;
   parameters.error_cb = error_cb;
   parameters.seek_cb = seek_cb;
   parameters.buffering_state_cb = buffering_state_cb;
@@ -810,7 +810,7 @@
 
   demuxer_ = parameters.demuxer;
   set_drm_system_ready_cb_ = parameters.set_drm_system_ready_cb;
-  ended_cb_ = std::move(parameters.ended_cb);
+  ended_cb_ = parameters.ended_cb;
   error_cb_ = parameters.error_cb;
   {
     base::AutoLock auto_lock(lock_);
@@ -1327,7 +1327,7 @@
       break;
     }
     case kSbPlayerStateEndOfStream:
-      std::move(ended_cb_).Run(::media::PIPELINE_OK);
+      ended_cb_.Run(::media::PIPELINE_OK);
       ended_ = true;
       break;
     case kSbPlayerStateDestroyed:
diff --git a/cobalt/media/base/starboard_player.cc b/cobalt/media/base/starboard_player.cc
index 7e7da28..3c9d8cf 100644
--- a/cobalt/media/base/starboard_player.cc
+++ b/cobalt/media/base/starboard_player.cc
@@ -567,12 +567,6 @@
 
   bool is_visible = SbWindowIsValid(window_);
   SbMediaAudioCodec audio_codec = audio_sample_info_.codec;
-  SbMediaVideoCodec video_codec = kSbMediaVideoCodecNone;
-  // TODO: This is temporary for supporting background media playback.
-  //       Need to be removed with media refactor.
-  if (is_visible) {
-    video_codec = video_sample_info_.codec;
-  }
 
   bool has_audio = audio_codec != kSbMediaAudioCodecNone;
 
@@ -1023,8 +1017,8 @@
       "  kSbPlayerStatePrerolling received       %" PRId64 " us\n"
       "  First media sample(s) written [a/v]     %" PRId64 "/%" PRId64 " us\n"
       "  kSbPlayerStatePresenting received       %" PRId64 " us\n"
-      "  Startup latency statistics (us):\n"
-      "    min: %" PRId64 ", median: %" PRId64 ", average: %" PRId64
+      "  Startup latency statistics (us):"
+      "        min: %" PRId64 ", median: %" PRId64 ", average: %" PRId64
       ", max: %" PRId64,
       startup_latency,
       first_events_str.c_str(), player_initialization_time_delta,
diff --git a/cobalt/media/progressive/avc_parser.cc b/cobalt/media/progressive/avc_parser.cc
index f9dcc0b..4c447db 100644
--- a/cobalt/media/progressive/avc_parser.cc
+++ b/cobalt/media/progressive/avc_parser.cc
@@ -477,6 +477,7 @@
       aac.GetChannelLayout(kSbrInMimetype),
       aac.GetOutputSamplesPerSecond(kSbrInMimetype), aac.codec_specific_data(),
       ::media::EncryptionScheme::kUnencrypted, base::TimeDelta(), 0);
+  audio_config_.set_aac_extra_data(aac.codec_specific_data());
 }
 
 size_t AVCParser::CalculatePrependSize(DemuxerStream::Type type,
diff --git a/cobalt/media_integration_tests/functionality/general_playback.py b/cobalt/media_integration_tests/functionality/general_playback.py
index 8b3ea3a..8dcd83d 100644
--- a/cobalt/media_integration_tests/functionality/general_playback.py
+++ b/cobalt/media_integration_tests/functionality/general_playback.py
@@ -40,8 +40,7 @@
 
 TEST_PARAMETERS = [
     ('H264', PlaybackUrls.H264_ONLY, None),
-    # TODO(b/223856877) -- find out why Progressive still broken
-    #('PROGRESSIVE', PlaybackUrls.PROGRESSIVE, None),
+    ('PROGRESSIVE', PlaybackUrls.PROGRESSIVE, None),
     ('ENCRYPTED', PlaybackUrls.ENCRYPTED, None),
     ('VR', PlaybackUrls.VR, None),
     ('VP9', PlaybackUrls.VP9, MimeStrings.VP9),
diff --git a/cobalt/script/script_value.h b/cobalt/script/script_value.h
index e1345e2..25567d7 100644
--- a/cobalt/script/script_value.h
+++ b/cobalt/script/script_value.h
@@ -17,6 +17,7 @@
 
 #include <algorithm>
 #include <memory>
+#include <utility>
 
 #include "base/basictypes.h"
 #include "base/logging.h"
@@ -171,8 +172,10 @@
       : Handle(reference.referenced_value().MakeWeakCopy().release()) {}
 
   Handle(const Handle& other) : script_value_(other.script_value_) {
-    script_value_->PreventGarbageCollection();
-    script_value_->reference_count_++;
+    if (script_value_) {
+      script_value_->PreventGarbageCollection();
+      script_value_->reference_count_++;
+    }
   }
   // We need the default constructor for nullable ScriptValue.
   Handle() = default;
diff --git a/cobalt/script/v8c/conversion_helpers.h b/cobalt/script/v8c/conversion_helpers.h
index 81bf303..d1779af 100644
--- a/cobalt/script/v8c/conversion_helpers.h
+++ b/cobalt/script/v8c/conversion_helpers.h
@@ -85,7 +85,8 @@
 inline void ToJSValue(v8::Isolate* isolate, const std::string& in_string,
                       v8::Local<v8::Value>* out_value) {
   v8::MaybeLocal<v8::String> maybe_string = v8::String::NewFromUtf8(
-      isolate, in_string.data(), v8::NewStringType::kNormal, in_string.size());
+      isolate, in_string.data(), v8::NewStringType::kNormal,
+      static_cast<int>(in_string.size()));
   v8::Local<v8::Value> string;
   if (!maybe_string.ToLocal(&string)) {
     *out_value = v8::Null(isolate);
@@ -107,7 +108,8 @@
 inline void ToJSValue(v8::Isolate* isolate, const std::vector<uint8_t>& in_data,
                       v8::Local<v8::Value>* out_value) {
   v8::MaybeLocal<v8::String> maybe_string = v8::String::NewFromOneByte(
-      isolate, in_data.data(), v8::NewStringType::kNormal, in_data.size());
+      isolate, in_data.data(), v8::NewStringType::kNormal,
+      static_cast<int>(in_data.size()));
   v8::Local<v8::Value> string;
   if (!maybe_string.ToLocal(&string)) {
     *out_value = v8::Null(isolate);
@@ -165,12 +167,12 @@
 }
 
 template <typename T>
-inline const double UpperBound() {
+inline double UpperBound() {
   return std::numeric_limits<T>::max();
 }
 
 template <typename T>
-inline const double LowerBound() {
+inline double LowerBound() {
   return std::numeric_limits<T>::min();
 }
 
@@ -179,19 +181,19 @@
 // step 1 of ConvertToInt, see:
 // https://heycam.github.io/webidl/#abstract-opdef-converttoint
 template <>
-inline const double UpperBound<int64_t>() {
+inline double UpperBound<int64_t>() {
   const double kInt64UpperBound = static_cast<double>((1ll << 53) - 1);
   return kInt64UpperBound;
 }
 
 template <>
-inline const double LowerBound<int64_t>() {
+inline double LowerBound<int64_t>() {
   const double kInt64LowerBound = static_cast<double>(-(1ll << 53) + 1);
   return kInt64LowerBound;
 }
 
 template <>
-inline const double UpperBound<uint64_t>() {
+inline double UpperBound<uint64_t>() {
   const double kUInt64UpperBound = static_cast<double>((1ll << 53) - 1);
   return kUInt64UpperBound;
 }
@@ -602,8 +604,7 @@
 
     // 4.3. Let P be the result of calling ToString(i).
     // 4.4. Call CreateDataProperty(A, P, E).
-    v8::Maybe<bool> set_result =
-        array->Set(isolate->GetCurrentContext(), index, element);
+    array->Set(isolate->GetCurrentContext(), index, element).Check();
 
     // 4.5. Set i to i + 1.
   }
diff --git a/cobalt/script/v8c/helpers.h b/cobalt/script/v8c/helpers.h
index c2bfbef..2529039 100644
--- a/cobalt/script/v8c/helpers.h
+++ b/cobalt/script/v8c/helpers.h
@@ -15,6 +15,8 @@
 #ifndef COBALT_SCRIPT_V8C_HELPERS_H_
 #define COBALT_SCRIPT_V8C_HELPERS_H_
 
+#include <string>
+
 #include "starboard/common/string.h"
 #include "v8/include/v8.h"
 
@@ -35,7 +37,7 @@
   }
   return v8::String::NewFromOneByte(
              isolate, reinterpret_cast<const uint8_t*>(string),
-             v8::NewStringType::kInternalized, strlen(string))
+             v8::NewStringType::kInternalized, static_cast<int>(strlen(string)))
       .ToLocalChecked();
 }
 
diff --git a/cobalt/script/v8c/v8c_array_buffer.h b/cobalt/script/v8c/v8c_array_buffer.h
index 17d468c..e1d1212 100644
--- a/cobalt/script/v8c/v8c_array_buffer.h
+++ b/cobalt/script/v8c/v8c_array_buffer.h
@@ -36,7 +36,7 @@
   V8cArrayBuffer() = default;
 
   V8cArrayBuffer(v8::Isolate* isolate, v8::Local<v8::Value> value)
-      : isolate_(isolate), ScopedPersistent(isolate, value) {
+      : ScopedPersistent(isolate, value), isolate_(isolate) {
     DCHECK(value->IsArrayBuffer());
   }
 
@@ -95,8 +95,7 @@
     return;
   }
 
-  *out_array_buffer =
-      std::move(V8cUserObjectHolder<V8cArrayBuffer>(isolate, value));
+  *out_array_buffer = V8cUserObjectHolder<V8cArrayBuffer>(isolate, value);
 }
 
 }  // namespace v8c
diff --git a/cobalt/script/v8c/v8c_array_buffer_view.h b/cobalt/script/v8c/v8c_array_buffer_view.h
index caaf6a2..0c00986 100644
--- a/cobalt/script/v8c/v8c_array_buffer_view.h
+++ b/cobalt/script/v8c/v8c_array_buffer_view.h
@@ -36,7 +36,7 @@
   // Default constructor should only be used by bindings code.
   V8cArrayBufferView() = default;
   V8cArrayBufferView(v8::Isolate* isolate, v8::Local<v8::Value> value)
-      : isolate_(isolate), ScopedPersistent(isolate, value) {
+      : ScopedPersistent(isolate, value), isolate_(isolate) {
     DCHECK(value->IsArrayBufferView());
   }
 
@@ -111,7 +111,7 @@
   }
 
   *out_array_buffer_view =
-      std::move(V8cUserObjectHolder<V8cArrayBufferView>(isolate, value));
+      V8cUserObjectHolder<V8cArrayBufferView>(isolate, value);
 }
 
 }  // namespace v8c
diff --git a/cobalt/script/v8c/v8c_data_view.h b/cobalt/script/v8c/v8c_data_view.h
index 0a0ae84..d8f56ee 100644
--- a/cobalt/script/v8c/v8c_data_view.h
+++ b/cobalt/script/v8c/v8c_data_view.h
@@ -35,7 +35,7 @@
   // Default constructor should only be used by bindings code.
   V8cDataView() = default;
   V8cDataView(v8::Isolate* isolate, v8::Local<v8::Value> value)
-      : isolate_(isolate), ScopedPersistent(isolate, value) {
+      : ScopedPersistent(isolate, value), isolate_(isolate) {
     DCHECK(value->IsDataView());
   }
 
@@ -110,8 +110,7 @@
     return;
   }
 
-  *out_array_buffer_view =
-      std::move(V8cUserObjectHolder<V8cDataView>(isolate, value));
+  *out_array_buffer_view = V8cUserObjectHolder<V8cDataView>(isolate, value);
 }
 
 }  // namespace v8c
diff --git a/cobalt/script/v8c/v8c_typed_arrays.h b/cobalt/script/v8c/v8c_typed_arrays.h
index e8c9474..82e7509 100644
--- a/cobalt/script/v8c/v8c_typed_arrays.h
+++ b/cobalt/script/v8c/v8c_typed_arrays.h
@@ -37,7 +37,7 @@
   // Default constructor should only be used by bindings code.
   V8cTypedArray() = default;
   V8cTypedArray(v8::Isolate* isolate, v8::Local<v8::Value> value)
-      : isolate_(isolate), ScopedPersistent(isolate, value) {
+      : ScopedPersistent(isolate, value), isolate_(isolate) {
     DCHECK(value->IsTypedArray());
   }
 
@@ -115,8 +115,8 @@
     return;
   }
 
-  *out_typed_array = std::move(
-      V8cUserObjectHolder<V8cTypedArray>(isolate, value.As<v8::TypedArray>()));
+  *out_typed_array =
+      V8cUserObjectHolder<V8cTypedArray>(isolate, value.As<v8::TypedArray>());
 }
 
 template <typename CType, typename BaseTypeName, typename V8Type,
@@ -129,7 +129,7 @@
   // Default constructor should only be used by bindings code.
   V8cTypedArrayImpl() = default;
   V8cTypedArrayImpl(v8::Isolate* isolate, v8::Local<v8::Value> value)
-      : isolate_(isolate), ScopedPersistent(isolate, value) {
+      : ScopedPersistent(isolate, value), isolate_(isolate) {
     DCHECK(((**value).*(V8ValueIsTypeFunction))());
   }
 
@@ -178,42 +178,42 @@
 COBALT_SCRIPT_TYPED_ARRAY_LIST(COBALT_SCRIPT_USING_V8C_ARRAY)
 #undef COBALT_SCRIPT_USING_V8C_ARRAY
 
-#define COBALT_SCRIPT_CONVERSION_BOILERPLATE(array, ctype)                   \
-  template <>                                                                \
-  struct TypeTraits<array> {                                                 \
-    using ConversionType = V8cUserObjectHolder<V8c##array>;                  \
-    using ReturnType = const ScriptValue<array>*;                            \
-  };                                                                         \
-                                                                             \
-  inline void ToJSValue(v8::Isolate* isolate,                                \
-                        const ScriptValue<array>* array_value,               \
-                        v8::Local<v8::Value>* out_value) {                   \
-    if (!array_value) {                                                      \
-      *out_value = v8::Null(isolate);                                        \
-      return;                                                                \
-    }                                                                        \
-    const auto* v8c_array_value =                                            \
-        base::polymorphic_downcast<const V8cUserObjectHolder<V8c##array>*>(  \
-            array_value);                                                    \
-    *out_value = v8c_array_value->v8_value();                                \
-  }                                                                          \
-                                                                             \
-  inline void FromJSValue(v8::Isolate* isolate, v8::Local<v8::Value> value,  \
-                          int conversion_flags,                              \
-                          ExceptionState* exception_state,                   \
-                          V8cUserObjectHolder<V8c##array>* out_array) {      \
-    DCHECK_EQ(0, conversion_flags);                                          \
-    DCHECK(out_array);                                                       \
-    if (!value->IsObject()) {                                                \
-      exception_state->SetSimpleException(kNotObjectType);                   \
-      return;                                                                \
-    }                                                                        \
-    if (!value->Is##array()) {                                               \
-      exception_state->SetSimpleException(kTypeError,                        \
-                                          "Expected object of type array");  \
-      return;                                                                \
-    }                                                                        \
-    *out_array = std::move(V8cUserObjectHolder<V8c##array>(isolate, value)); \
+#define COBALT_SCRIPT_CONVERSION_BOILERPLATE(array, ctype)                  \
+  template <>                                                               \
+  struct TypeTraits<array> {                                                \
+    using ConversionType = V8cUserObjectHolder<V8c##array>;                 \
+    using ReturnType = const ScriptValue<array>*;                           \
+  };                                                                        \
+                                                                            \
+  inline void ToJSValue(v8::Isolate* isolate,                               \
+                        const ScriptValue<array>* array_value,              \
+                        v8::Local<v8::Value>* out_value) {                  \
+    if (!array_value) {                                                     \
+      *out_value = v8::Null(isolate);                                       \
+      return;                                                               \
+    }                                                                       \
+    const auto* v8c_array_value =                                           \
+        base::polymorphic_downcast<const V8cUserObjectHolder<V8c##array>*>( \
+            array_value);                                                   \
+    *out_value = v8c_array_value->v8_value();                               \
+  }                                                                         \
+                                                                            \
+  inline void FromJSValue(v8::Isolate* isolate, v8::Local<v8::Value> value, \
+                          int conversion_flags,                             \
+                          ExceptionState* exception_state,                  \
+                          V8cUserObjectHolder<V8c##array>* out_array) {     \
+    DCHECK_EQ(0, conversion_flags);                                         \
+    DCHECK(out_array);                                                      \
+    if (!value->IsObject()) {                                               \
+      exception_state->SetSimpleException(kNotObjectType);                  \
+      return;                                                               \
+    }                                                                       \
+    if (!value->Is##array()) {                                              \
+      exception_state->SetSimpleException(kTypeError,                       \
+                                          "Expected object of type array"); \
+      return;                                                               \
+    }                                                                       \
+    *out_array = V8cUserObjectHolder<V8c##array>(isolate, value);           \
   }
 COBALT_SCRIPT_TYPED_ARRAY_LIST(COBALT_SCRIPT_CONVERSION_BOILERPLATE)
 #undef COBALT_SCRIPT_CONVERSION_BOILERPLATE
diff --git a/cobalt/site/docs/development/setup-android.md b/cobalt/site/docs/development/setup-android.md
index 2c8bdb5..0382b42 100644
--- a/cobalt/site/docs/development/setup-android.md
+++ b/cobalt/site/docs/development/setup-android.md
@@ -17,6 +17,15 @@
 
 1.  Download and install [Android Studio](https://developer.android.com/studio/).
 
+1.  To enable parallel gradle builds, add the following to your `~/.bashrc`:
+
+    ```
+    export COBALT_GRADLE_BUILD_COUNT=4
+    ```
+
+    Where 4 is the number of parallel threads. You can adjust the number of
+    parallel threads according to how your workstation performs.
+
 1.  Run `cobalt/build/gn.py -p android-x86` to configure the Cobalt build,
     which also installs the SDK and NDK. (This step will have to be repeated
     with 'android-arm' or 'android-arm64' to target those architectures.) The
@@ -214,6 +223,15 @@
 instead of 'cobalt'. Then you can set breakpoints, etc. in the test the same as
 when debugging Cobalt.
 
+## Debugging (Terminal)
+
+Use `adb logcat` while Cobalt is running, or use `adb bugreport` shortly after
+exiting to view Android logs. You will need to filter or search for
+Cobalt-related output.
+
+As with the Linux build, use the `debug`, `devel`, or `qa` configs to trace
+Cobalt's callstacks.
+
 ## Removing the Cobalt Android Environment
 
 1.  Unset ANDROID_HOME and or ANDROID_NDK_HOME in your shell and in .bashrc
diff --git a/cobalt/site/docs/development/setup-linux.md b/cobalt/site/docs/development/setup-linux.md
index 8f570d6..190cac0 100644
--- a/cobalt/site/docs/development/setup-linux.md
+++ b/cobalt/site/docs/development/setup-linux.md
@@ -186,6 +186,14 @@
       </tr>
     </table>
 
+## Debugging Cobalt
+
+`debug`, `devel`, and `qa` configs of Cobalt expose a feature enabling
+developers to trace Cobalt's callstacks per-thread. This is not only a great way
+to debug application performance, but also a great way to debug issues and
+better understand Cobalt's execution flow in general.
+
+Simply build and run one of these configs and observe the terminal output.
 <!--
 <aside class="note">
 <b>Note:</b> If you plan to upload reviews to the Cobalt repository, you
diff --git a/cobalt/updater/BUILD.gn b/cobalt/updater/BUILD.gn
index da5cd31..94e7ae6 100644
--- a/cobalt/updater/BUILD.gn
+++ b/cobalt/updater/BUILD.gn
@@ -100,9 +100,11 @@
 
 target(gtest_target_type, "updater_test") {
   testonly = true
+  has_pedantic_warnings = true
 
   sources = [
     "//starboard/common/test_main.cc",
+    "configurator_test.cc",
     "utils_test.cc",
   ]
 
diff --git a/cobalt/updater/configurator.cc b/cobalt/updater/configurator.cc
index 033152a..3383817 100644
--- a/cobalt/updater/configurator.cc
+++ b/cobalt/updater/configurator.cc
@@ -32,6 +32,8 @@
 const int kDelayOneMinute = 60;
 const int kDelayOneHour = kDelayOneMinute * 60;
 const char kDefaultUpdaterChannel[] = "prod";
+const char kOmahaCobaltTrunkAppID[] = "{A9557415-DDCD-4948-8113-C643EFCF710C}";
+const char kOmahaCobaltAppID[] = "{6D4E53F3-CC64-4CB8-B6BD-AB0B8F300E1C}";
 
 std::string GetDeviceProperty(SbSystemPropertyId id) {
   const size_t kSystemPropertyMaxLength = 1024;
@@ -221,9 +223,17 @@
 
 std::vector<uint8_t> Configurator::GetRunActionKeyHash() const { return {}; }
 
+std::string Configurator::GetAppGuidHelper(const std::string& version) {
+  if (version.find(".lts.") != std::string::npos &&
+      version.find(".master.") == std::string::npos) {
+    return kOmahaCobaltAppID;
+  }
+  return kOmahaCobaltTrunkAppID;
+}
+
 std::string Configurator::GetAppGuid() const {
-  // Omaha console app id for trunk
-  return "{A9557415-DDCD-4948-8113-C643EFCF710C}";
+  const std::string version(COBALT_VERSION);
+  return GetAppGuidHelper(version);
 }
 
 std::unique_ptr<update_client::ProtocolHandlerFactory>
diff --git a/cobalt/updater/configurator.h b/cobalt/updater/configurator.h
index 630861a..992f176 100644
--- a/cobalt/updater/configurator.h
+++ b/cobalt/updater/configurator.h
@@ -101,6 +101,8 @@
 
   bool GetUseCompressedUpdates() const override;
   void SetUseCompressedUpdates(bool use_compressed_updates) override;
+  // Added for testing purposes.
+  static std::string GetAppGuidHelper(const std::string&);
 
  private:
   friend class base::RefCountedThreadSafe<Configurator>;
diff --git a/cobalt/updater/configurator_test.cc b/cobalt/updater/configurator_test.cc
new file mode 100644
index 0000000..cba7ccf
--- /dev/null
+++ b/cobalt/updater/configurator_test.cc
@@ -0,0 +1,58 @@
+// 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/updater/configurator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+const char kOmahaCobaltTrunkAppID[] = "{A9557415-DDCD-4948-8113-C643EFCF710C}";
+const char kOmahaCobaltAppID[] = "{6D4E53F3-CC64-4CB8-B6BD-AB0B8F300E1C}";
+}  // namespace
+
+namespace cobalt {
+namespace updater {
+
+class ConfiguratorTest : public testing::Test {
+ protected:
+  ConfiguratorTest() {}
+  ~ConfiguratorTest() {}
+};
+
+TEST_F(ConfiguratorTest, GetAppGuidReturnsTrunkIdWithVersionMaster) {
+  CHECK_EQ(cobalt::updater::Configurator::GetAppGuidHelper("23.master.0"),
+           kOmahaCobaltTrunkAppID);
+}
+
+TEST_F(ConfiguratorTest, GetAppGuidReturnsTrunkIdWithVersionMain) {
+  CHECK_EQ(cobalt::updater::Configurator::GetAppGuidHelper("23.main.0"),
+           kOmahaCobaltTrunkAppID);
+}
+
+TEST_F(ConfiguratorTest, GetAppGuidReturnsProdIdWithVersionLts) {
+  CHECK_EQ(cobalt::updater::Configurator::GetAppGuidHelper("23.lts.0"),
+           kOmahaCobaltAppID);
+}
+
+TEST_F(ConfiguratorTest, GetAppGuidReturnsTrunkIdWithVersionEmpty) {
+  CHECK_EQ(cobalt::updater::Configurator::GetAppGuidHelper(""),
+           kOmahaCobaltTrunkAppID);
+}
+
+TEST_F(ConfiguratorTest, GetAppGuidReturnsTrunkIdWithVersionMasterLts) {
+  CHECK_EQ(cobalt::updater::Configurator::GetAppGuidHelper("23.master.lts.0"),
+           kOmahaCobaltTrunkAppID);
+}
+
+}  // namespace updater
+}  // namespace cobalt
diff --git a/cobalt/web/agent.cc b/cobalt/web/agent.cc
index f52e113..f44a345 100644
--- a/cobalt/web/agent.cc
+++ b/cobalt/web/agent.cc
@@ -19,6 +19,7 @@
 #include <utility>
 
 #include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/loader/fetcher_factory.h"
 #include "cobalt/loader/script_loader_factory.h"
@@ -49,7 +50,7 @@
 namespace {
 class Impl : public Context {
  public:
-  explicit Impl(const Agent::Options& options);
+  Impl(const std::string& name, const Agent::Options& options);
   virtual ~Impl();
 
   void AddEnvironmentSettingsChangeObserver(
@@ -222,7 +223,8 @@
       environment_settings_change_observers_;
 };
 
-Impl::Impl(const Agent::Options& options) : name_(options.name) {
+Impl::Impl(const std::string& name, const Agent::Options& options)
+    : name_(name) {
   TRACE_EVENT0("cobalt::web", "Agent::Impl::Impl()");
   service_worker_jobs_ = options.service_worker_jobs;
   platform_info_ = options.platform_info;
@@ -235,7 +237,7 @@
   DCHECK(fetcher_factory_);
 
   script_loader_factory_.reset(new loader::ScriptLoaderFactory(
-      options.name.c_str(), fetcher_factory_.get(), options.thread_priority));
+      name.c_str(), fetcher_factory_.get(), options.thread_priority));
   DCHECK(script_loader_factory_);
 
   javascript_engine_ =
@@ -469,9 +471,31 @@
 
 void Agent::WillDestroyCurrentMessageLoop() { context_.reset(); }
 
-Agent::Agent(const Options& options, InitializeCallback initialize_callback,
-             DestructionObserver* destruction_observer)
-    : thread_(options.name) {
+Agent::Agent(const std::string& name) : thread_(name) {}
+
+void Agent::Stop() {
+  DCHECK(message_loop());
+  DCHECK(thread_.IsRunning());
+
+  if (context() && context()->service_worker_jobs()) {
+    context()->service_worker_jobs()->UnregisterWebContext(context());
+  }
+
+  // Ensure that the destruction observer got added before stopping the thread.
+  destruction_observer_added_.Wait();
+  // Stop the thread. This will cause the destruction observer to be notified.
+  thread_.Stop();
+}
+
+Agent::~Agent() {
+  DCHECK(!thread_.IsRunning());
+  if (thread_.IsRunning()) {
+    Stop();
+  }
+}
+
+void Agent::Run(const Options& options, InitializeCallback initialize_callback,
+                DestructionObserver* destruction_observer) {
   // Start the dedicated thread and create the internal implementation
   // object on that thread.
   base::Thread::Options thread_options(base::MessageLoop::TYPE_DEFAULT,
@@ -481,8 +505,9 @@
   DCHECK(message_loop());
 
   message_loop()->task_runner()->PostTask(
-      FROM_HERE, base::Bind(&Agent::Initialize, base::Unretained(this), options,
-                            initialize_callback));
+      FROM_HERE,
+      base::Bind(&Agent::InitializeTaskInThread, base::Unretained(this),
+                 options, initialize_callback));
 
   if (destruction_observer) {
     message_loop()->task_runner()->PostTask(
@@ -507,30 +532,17 @@
                             base::Unretained(&destruction_observer_added_)));
 }
 
-Agent::~Agent() {
-  DCHECK(message_loop());
-  DCHECK(thread_.IsRunning());
-
-  if (context() && context()->service_worker_jobs()) {
-    context()->service_worker_jobs()->UnregisterWebContext(context());
-  }
-
-  // Ensure that the destruction observer got added before stopping the thread.
-  destruction_observer_added_.Wait();
-  // Stop the thread. This will cause the destruction observer to be notified.
-  thread_.Stop();
-}
-
-void Agent::Initialize(const Options& options,
-                       InitializeCallback initialize_callback) {
+void Agent::InitializeTaskInThread(const Options& options,
+                                   InitializeCallback initialize_callback) {
   DCHECK_EQ(base::MessageLoop::current(), message_loop());
-  context_.reset(CreateContext(options, thread_.message_loop()));
+  context_.reset(
+      CreateContext(thread_.thread_name(), options, thread_.message_loop()));
   initialize_callback.Run(context_.get());
 }
 
-Context* Agent::CreateContext(const Options& options,
+Context* Agent::CreateContext(const std::string& name, const Options& options,
                               base::MessageLoop* message_loop) {
-  auto* context = new Impl(options);
+  auto* context = new Impl(name, options);
   context->set_message_loop(message_loop);
   if (options.service_worker_jobs) {
     options.service_worker_jobs->RegisterWebContext(context);
diff --git a/cobalt/web/agent.h b/cobalt/web/agent.h
index 956a6f6..aa48bb1 100644
--- a/cobalt/web/agent.h
+++ b/cobalt/web/agent.h
@@ -40,19 +40,12 @@
 class Agent : public base::MessageLoop::DestructionObserver {
  public:
   struct Options {
-    explicit Options(const std::string name) : name(name) {}
     typedef base::Callback<scoped_refptr<script::Wrappable>(
-        script::EnvironmentSettings*)>
+        web::EnvironmentSettings*)>
         CreateObjectFunction;
     typedef base::hash_map<std::string, CreateObjectFunction>
         InjectedGlobalObjectAttributes;
 
-    // The name of the Web Agent.  This is useful for debugging purposes as
-    // in the case where multiple Web Agent objects exist, it can be used to
-    // differentiate which objects belong to which Web Agent.  It is used
-    // to name some CVals.
-    std::string name;
-
     // Specifies the priority of the web agent's thread.  This is the thread
     // that is responsible for executing JavaScript.
     base::ThreadPriority thread_priority = base::ThreadPriority::NORMAL;
@@ -83,14 +76,17 @@
       JavaScriptHeapStatisticsCallback;
 
   typedef base::Callback<void(Context*)> InitializeCallback;
-  Agent(const Options& options, InitializeCallback initialize_callback,
-        DestructionObserver* destruction_observer = nullptr);
+  explicit Agent(const std::string& name);
   ~Agent();
 
-  static Context* CreateContext(const Options& options,
+  void Run(const Options& options, InitializeCallback initialize_callback,
+           DestructionObserver* destruction_observer = nullptr);
+  void Stop();
+
+  static Context* CreateContext(const std::string& name, const Options& options,
                                 base::MessageLoop* message_loop = nullptr);
   static Context* CreateContext(const std::string& name) {
-    return CreateContext(Options(name));
+    return CreateContext(name, Options());
   }
 
   Context* context() {
@@ -118,8 +114,8 @@
  private:
   // Called by the constructor to create the private implementation object and
   // perform any other initialization required on the dedicated thread.
-  void Initialize(const Options& options,
-                  InitializeCallback initialize_callback);
+  void InitializeTaskInThread(const Options& options,
+                              InitializeCallback initialize_callback);
 
   // The thread created and owned by this Web Agent.
   // All sub-objects of this object are created on this thread, and all public
diff --git a/cobalt/web/blob_test.cc b/cobalt/web/blob_test.cc
index f3cb1b3..6ddc919 100644
--- a/cobalt/web/blob_test.cc
+++ b/cobalt/web/blob_test.cc
@@ -206,7 +206,7 @@
 
 INSTANTIATE_TEST_CASE_P(
     BlobTestsWithJavaScript, BlobTestWithJavaScript,
-    ::testing::ValuesIn(testing::TestWebWithJavaScript::GetWorkerTypes()),
+    ::testing::ValuesIn(testing::TestWebWithJavaScript::GetWebTypes()),
     testing::TestWebWithJavaScript::GetTypeName);
 
 
diff --git a/cobalt/web/csp_violation_reporter.cc b/cobalt/web/csp_violation_reporter.cc
index 2cb1951..63d2cbd 100644
--- a/cobalt/web/csp_violation_reporter.cc
+++ b/cobalt/web/csp_violation_reporter.cc
@@ -134,22 +134,16 @@
   ViolationEvent violation_data;
   GatherSecurityPolicyViolationEventData(global_, violation_info,
                                          &violation_data);
+  EventTarget* target = global_;
   if (global_->IsWindow()) {
-    global_->AsWindow()->document()->DispatchEvent(
-        new SecurityPolicyViolationEvent(
-            violation_data.document_uri, violation_data.referrer,
-            violation_data.blocked_uri, violation_data.violated_directive,
-            violation_data.effective_directive, violation_data.original_policy,
-            violation_data.source_file, violation_data.status_code,
-            violation_data.line_number, violation_data.column_number));
-  } else {
-    global_->DispatchEvent(new SecurityPolicyViolationEvent(
-        violation_data.document_uri, violation_data.referrer,
-        violation_data.blocked_uri, violation_data.violated_directive,
-        violation_data.effective_directive, violation_data.original_policy,
-        violation_data.source_file, violation_data.status_code,
-        violation_data.line_number, violation_data.column_number));
+    target = global_->AsWindow()->document();
   }
+  target->DispatchEvent(new SecurityPolicyViolationEvent(
+      violation_data.document_uri, violation_data.referrer,
+      violation_data.blocked_uri, violation_data.violated_directive,
+      violation_data.effective_directive, violation_data.original_policy,
+      violation_data.source_file, violation_data.status_code,
+      violation_data.line_number, violation_data.column_number));
 
   if (violation_info.endpoints.empty() || post_sender_.is_null()) {
     return;
diff --git a/cobalt/web/custom_event_test.cc b/cobalt/web/custom_event_test.cc
index 9bf87ae..5d1bded 100644
--- a/cobalt/web/custom_event_test.cc
+++ b/cobalt/web/custom_event_test.cc
@@ -109,7 +109,7 @@
 
 INSTANTIATE_TEST_CASE_P(
     CustomEventTestsWithJavaScript, CustomEventTestWithJavaScript,
-    ::testing::ValuesIn(testing::TestWebWithJavaScript::GetWorkerTypes()),
+    ::testing::ValuesIn(testing::TestWebWithJavaScript::GetWebTypes()),
     testing::TestWebWithJavaScript::GetTypeName);
 
 }  // namespace web
diff --git a/cobalt/web/message_event.cc b/cobalt/web/message_event.cc
index a6e8888..b38a1fe 100644
--- a/cobalt/web/message_event.cc
+++ b/cobalt/web/message_event.cc
@@ -29,17 +29,36 @@
 #include "starboard/memory.h"
 #include "v8/include/v8.h"
 
+namespace cobalt {
+namespace web {
+
 namespace {
 const char* const kResponseTypes[] = {"text", "blob", "arraybuffer", "any"};
 
-COMPILE_ASSERT(arraysize(kResponseTypes) ==
-                   cobalt::web::MessageEvent::kResponseTypeMax,
+COMPILE_ASSERT(arraysize(kResponseTypes) == MessageEvent::kResponseTypeMax,
                enum_and_array_size_mismatch);
 
 }  // namespace
 
-namespace cobalt {
-namespace web {
+MessageEvent::MessageEvent(const std::string& type,
+                           const MessageEventInit& init_dict)
+    : Event(type, init_dict), response_type_(kAny) {
+  if (init_dict.has_data() && init_dict.data()) {
+    data_ = script::SerializeScriptValue(*(init_dict.data()));
+  }
+  if (init_dict.has_origin()) {
+    origin_ = init_dict.origin();
+  }
+  if (init_dict.has_last_event_id()) {
+    last_event_id_ = init_dict.last_event_id();
+  }
+  if (init_dict.has_source()) {
+    source_ = init_dict.source();
+  }
+  if (init_dict.has_ports()) {
+    ports_ = init_dict.ports();
+  }
+}
 
 // static
 std::string MessageEvent::GetResponseTypeAsString(
@@ -63,7 +82,7 @@
 MessageEvent::Response MessageEvent::data(
     script::EnvironmentSettings* settings) const {
   if (!data_ && !data_io_buffer_) {
-    return Response("");
+    return Response(script::Handle<script::ValueHandle>());
   }
 
   script::GlobalEnvironment* global_environment = nullptr;
@@ -71,7 +90,7 @@
       response_type_ == kAny) {
     DCHECK(settings);
     global_environment =
-        base::polymorphic_downcast<web::EnvironmentSettings*>(settings)
+        base::polymorphic_downcast<EnvironmentSettings*>(settings)
             ->context()
             ->global_environment();
     DCHECK(global_environment);
@@ -92,8 +111,7 @@
           std::move(buffer_copy);
       if (response_type_ == kBlob) {
         DCHECK(settings);
-        scoped_refptr<web::Blob> blob =
-            new web::Blob(settings, response_buffer);
+        scoped_refptr<Blob> blob = new Blob(settings, response_buffer);
         return Response(blob);
       }
       return Response(response_buffer);
@@ -101,12 +119,14 @@
     case kAny: {
       v8::Isolate* isolate = global_environment->isolate();
       script::v8c::EntryScope entry_scope(isolate);
+      DCHECK(isolate);
+      DCHECK(data_);
       return Response(script::Handle<script::ValueHandle>(
           std::move(script::DeserializeScriptValue(isolate, *data_))));
     }
     default:
       NOTREACHED() << "Invalid response type.";
-      return Response("");
+      return Response(script::Handle<script::ValueHandle>());
   }
 }
 
diff --git a/cobalt/web/message_event.h b/cobalt/web/message_event.h
index b39a198..ba81c1f 100644
--- a/cobalt/web/message_event.h
+++ b/cobalt/web/message_event.h
@@ -20,28 +20,32 @@
 #include <string>
 #include <utility>
 
-#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/strings/string_piece.h"
 #include "cobalt/base/token.h"
 #include "cobalt/script/array_buffer.h"
+#include "cobalt/script/sequence.h"
 #include "cobalt/script/union_type.h"
-#include "cobalt/script/v8c/v8c_value_handle.h"
-#include "cobalt/script/wrappable.h"
+#include "cobalt/script/value_handle.h"
 #include "cobalt/web/blob.h"
 #include "cobalt/web/event.h"
+#include "cobalt/web/event_target.h"
 #include "cobalt/web/message_event_init.h"
+#include "cobalt/web/message_port.h"
 #include "net/base/io_buffer.h"
 #include "v8/include/v8.h"
 
 namespace cobalt {
 namespace web {
 
-class MessageEvent : public web::Event {
+class MessageEvent : public Event {
  public:
-  typedef script::UnionType4<std::string, scoped_refptr<web::Blob>,
+  typedef script::UnionType4<std::string, scoped_refptr<Blob>,
                              script::Handle<script::ArrayBuffer>,
                              script::Handle<script::ValueHandle>>
       Response;
+
+
   // These response types are ordered in the likelihood of being used.
   // Keeping them in expected order will help make code faster.
   enum ResponseType { kText, kBlob, kArrayBuffer, kAny, kResponseTypeMax };
@@ -56,14 +60,26 @@
   MessageEvent(base::Token type, ResponseType response_type,
                const scoped_refptr<net::IOBufferWithSize>& data)
       : Event(type), response_type_(response_type), data_io_buffer_(data) {}
-  MessageEvent(const std::string& type, const web::MessageEventInit& init_dict)
-      : Event(type, init_dict),
-        response_type_(kAny),
-        data_(script::SerializeScriptValue(*(init_dict.data()))) {}
+  MessageEvent(const std::string& type, const MessageEventInit& init_dict);
 
   Response data(script::EnvironmentSettings* settings = nullptr) const;
+  const std::string& origin() const { return origin_; }
+  const std::string& last_event_id() const { return last_event_id_; }
+  const scoped_refptr<EventTarget>& source() const { return source_; }
+  script::Sequence<scoped_refptr<MessagePort>> ports() const { return ports_; }
 
   // These helper functions are custom, and not in any spec.
+  void set_origin(const std::string& origin) { origin_ = origin; }
+  void set_last_event_id(const std::string& last_event_id) {
+    last_event_id_ = last_event_id;
+  }
+  void set_source(const scoped_refptr<EventTarget>& source) {
+    source_ = source;
+  }
+  void set_ports(script::Sequence<scoped_refptr<MessagePort>> ports) {
+    ports_ = ports;
+  }
+
   static std::string GetResponseTypeAsString(const ResponseType response_type);
   static MessageEvent::ResponseType GetResponseType(base::StringPiece to_match);
 
@@ -73,6 +89,10 @@
   ResponseType response_type_ = kText;
   scoped_refptr<net::IOBufferWithSize> data_io_buffer_;
   std::unique_ptr<script::DataBuffer> data_;
+  std::string origin_;
+  std::string last_event_id_;
+  scoped_refptr<EventTarget> source_;
+  script::Sequence<scoped_refptr<MessagePort>> ports_;
 };
 
 }  // namespace web
diff --git a/cobalt/web/message_event.idl b/cobalt/web/message_event.idl
index 0e15ab7..909fb66 100644
--- a/cobalt/web/message_event.idl
+++ b/cobalt/web/message_event.idl
@@ -20,4 +20,9 @@
   Constructor(DOMString type, optional MessageEventInit eventInitDict)
 ] interface MessageEvent : Event {
   [CallWith = EnvironmentSettings] readonly attribute any data;
+  readonly attribute USVString origin;
+  readonly attribute DOMString lastEventId;
+  readonly attribute EventTarget? source;
+  // TODO(b/236750294): Make this be FrozenArray<MessagePort> when available.
+  readonly attribute sequence<MessagePort> ports;
 };
diff --git a/cobalt/web/message_event_init.idl b/cobalt/web/message_event_init.idl
index 9ccf914..edf7e7c 100644
--- a/cobalt/web/message_event_init.idl
+++ b/cobalt/web/message_event_init.idl
@@ -12,8 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// https://www.w3.org/TR/html50/webappapis.html#erroreventinit
+// https://html.spec.whatwg.org/multipage/comms.html#messageeventinit
 
 dictionary MessageEventInit : EventInit {
-  any data = null;
+  any data;
+  USVString origin = "";
+  DOMString lastEventId = "";
+  EventTarget? source;
+  sequence<MessagePort> ports;
 };
diff --git a/cobalt/web/message_event_test.cc b/cobalt/web/message_event_test.cc
index d8f6d6d..93893b2 100644
--- a/cobalt/web/message_event_test.cc
+++ b/cobalt/web/message_event_test.cc
@@ -19,11 +19,14 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
-#include "cobalt/dom/testing/test_with_javascript.h"
+#include "base/memory/scoped_refptr.h"
 #include "cobalt/script/v8c/entry_scope.h"
 #include "cobalt/script/value_handle.h"
+#include "cobalt/web/blob.h"
 #include "cobalt/web/message_event_init.h"
 #include "cobalt/web/testing/gtest_workarounds.h"
+#include "cobalt/web/testing/test_with_javascript.h"
+#include "net/base/io_buffer.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -31,8 +34,7 @@
 namespace web {
 
 namespace {
-class MessageEventTestWithJavaScript : public dom::testing::TestWithJavaScript {
-};
+class MessageEventTestWithJavaScript : public testing::TestWebWithJavaScript {};
 }  // namespace
 
 TEST(MessageEventTest, ConstructorWithEventTypeString) {
@@ -49,19 +51,44 @@
   EXPECT_FALSE(event->propagation_stopped());
   EXPECT_FALSE(event->immediate_propagation_stopped());
   MessageEvent::Response event_data = event->data();
-  EXPECT_TRUE(event_data.IsType<std::string>());
-  EXPECT_EQ("", event_data.AsType<std::string>());
+  EXPECT_TRUE(event_data.IsType<script::Handle<script::ValueHandle>>());
+  EXPECT_TRUE(
+      event_data.AsType<script::Handle<script::ValueHandle>>().IsEmpty());
 }
 
-TEST_F(MessageEventTestWithJavaScript,
-       ConstructorWithEventTypeAndDefaultInitDict) {
-  MessageEventInit init;
-  base::Optional<script::ValueHandleHolder::Reference> reference;
-  EvaluateScript("'data_value'", window(), &reference);
-  init.set_data(&(reference->referenced_value()));
-  scoped_refptr<MessageEvent> event = new MessageEvent("mytestevent", init);
+TEST(MessageEventTest, ConstructorWithText) {
+  std::string message_string("ConstructorWithTextMessageData");
+  scoped_refptr<net::IOBufferWithSize> data =
+      base::MakeRefCounted<net::IOBufferWithSize>(message_string.size());
+  memcpy(data->data(), message_string.c_str(), message_string.size());
+  scoped_refptr<MessageEvent> event =
+      new MessageEvent(base::Tokens::message(), MessageEvent::kText, data);
 
-  EXPECT_EQ("mytestevent", event->type());
+  EXPECT_EQ("message", 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());
+  MessageEvent::Response event_data = event->data();
+  EXPECT_TRUE(event_data.IsType<std::string>());
+  EXPECT_EQ(message_string.size(), event_data.AsType<std::string>().size());
+  EXPECT_EQ("ConstructorWithTextMessageData", event_data.AsType<std::string>());
+}
+
+TEST_P(MessageEventTestWithJavaScript, ConstructorWithBlob) {
+  std::string message_string("ConstructorWithBlobMessageData");
+  scoped_refptr<net::IOBufferWithSize> data =
+      base::MakeRefCounted<net::IOBufferWithSize>(message_string.size());
+  memcpy(data->data(), message_string.c_str(), message_string.size());
+  scoped_refptr<MessageEvent> event =
+      new MessageEvent(base::Tokens::message(), MessageEvent::kBlob, data);
+
+  EXPECT_EQ("message", event->type());
   EXPECT_EQ(NULL, event->target().get());
   EXPECT_EQ(NULL, event->current_target().get());
   EXPECT_EQ(Event::kNone, event->event_phase());
@@ -72,7 +99,139 @@
   EXPECT_FALSE(event->propagation_stopped());
   EXPECT_FALSE(event->immediate_propagation_stopped());
   MessageEvent::Response event_data =
-      event->data(window()->environment_settings());
+      event->data(web_context()->environment_settings());
+  EXPECT_TRUE(event_data.IsType<scoped_refptr<Blob>>());
+  EXPECT_EQ(message_string.size(),
+            event_data.AsType<scoped_refptr<Blob>>()->size());
+  EXPECT_EQ("ConstructorWithBlobMessageData",
+            std::string(reinterpret_cast<const char*>(
+                            event_data.AsType<scoped_refptr<Blob>>()->data()),
+                        static_cast<size_t>(
+                            event_data.AsType<scoped_refptr<Blob>>()->size())));
+  EXPECT_TRUE(event_data.AsType<scoped_refptr<Blob>>()->type().empty());
+}
+
+TEST_P(MessageEventTestWithJavaScript, ConstructorWithArrayBuffer) {
+  std::string message_string("ConstructorWithArrayBufferMessageData");
+  scoped_refptr<net::IOBufferWithSize> data =
+      base::MakeRefCounted<net::IOBufferWithSize>(message_string.size());
+  memcpy(data->data(), message_string.c_str(), message_string.size());
+  scoped_refptr<MessageEvent> event = new MessageEvent(
+      base::Tokens::message(), MessageEvent::kArrayBuffer, data);
+
+  EXPECT_EQ("message", 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());
+  MessageEvent::Response event_data =
+      event->data(web_context()->environment_settings());
+  EXPECT_TRUE(event_data.IsType<script::Handle<script::ArrayBuffer>>());
+  EXPECT_EQ(
+      message_string.size(),
+      event_data.AsType<script::Handle<script::ArrayBuffer>>()->ByteLength());
+  EXPECT_EQ(
+      "ConstructorWithArrayBufferMessageData",
+      std::string(
+          reinterpret_cast<const char*>(
+              event_data.AsType<script::Handle<script::ArrayBuffer>>()->Data()),
+          static_cast<size_t>(
+              event_data.AsType<script::Handle<script::ArrayBuffer>>()
+                  ->ByteLength())));
+}
+
+TEST_P(MessageEventTestWithJavaScript, ConstructorWithAny) {
+  base::Optional<script::ValueHandleHolder::Reference> reference;
+  EvaluateScript("'ConstructorWithAnyMessageData'", &reference);
+  std::unique_ptr<script::DataBuffer> data(
+      script::SerializeScriptValue(reference->referenced_value()));
+  EXPECT_NE(nullptr, data.get());
+  EXPECT_NE(nullptr, data->ptr);
+  EXPECT_GT(data->size, 0U);
+  scoped_refptr<MessageEvent> event =
+      new MessageEvent(base::Tokens::message(), std::move(data));
+
+  EXPECT_EQ("message", 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());
+  MessageEvent::Response event_data =
+      event->data(web_context()->environment_settings());
+  EXPECT_TRUE(event_data.IsType<script::Handle<script::ValueHandle>>());
+  EXPECT_FALSE(
+      event_data.AsType<script::Handle<script::ValueHandle>>().IsEmpty());
+  auto script_value =
+      event_data.AsType<script::Handle<script::ValueHandle>>().GetScriptValue();
+  auto* isolate = script::GetIsolate(*script_value);
+  script::v8c::EntryScope entry_scope(isolate);
+  v8::Local<v8::Value> v8_value = script::GetV8Value(*script_value);
+  std::string actual =
+      *(v8::String::Utf8Value(isolate, v8_value.As<v8::String>()));
+  EXPECT_EQ("ConstructorWithAnyMessageData", actual);
+}
+
+TEST_P(MessageEventTestWithJavaScript,
+       ConstructorWithEventTypeAndDefaultInitDict) {
+  MessageEventInit init;
+  scoped_refptr<MessageEvent> event = new MessageEvent("mytestevent", init);
+
+  EXPECT_EQ("mytestevent", event->type());
+  EXPECT_EQ(nullptr, event->target().get());
+  EXPECT_EQ(nullptr, 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());
+
+  MessageEvent::Response event_data =
+      event->data(web_context()->environment_settings());
+  EXPECT_TRUE(event_data.IsType<script::Handle<script::ValueHandle>>());
+  EXPECT_TRUE(
+      event_data.AsType<script::Handle<script::ValueHandle>>().IsEmpty());
+
+  EXPECT_TRUE(event->origin().empty());
+  EXPECT_TRUE(event->last_event_id().empty());
+  EXPECT_EQ(nullptr, event->source().get());
+  EXPECT_TRUE(event->ports().empty());
+}
+
+TEST_P(MessageEventTestWithJavaScript, ConstructorWithEventTypeAndInitDict) {
+  MessageEventInit init;
+  base::Optional<script::ValueHandleHolder::Reference> reference;
+  EvaluateScript("'data_value'", &reference);
+  init.set_data(&(reference->referenced_value()));
+  init.set_origin("OriginString");
+  init.set_last_event_id("lastEventIdString");
+  init.set_source(web_context()->GetWindowOrWorkerGlobalScope());
+  scoped_refptr<MessageEvent> event = new MessageEvent("mytestevent", init);
+
+  EXPECT_EQ("mytestevent", event->type());
+  EXPECT_EQ(nullptr, event->target().get());
+  EXPECT_EQ(nullptr, 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());
+
+  MessageEvent::Response event_data =
+      event->data(web_context()->environment_settings());
   EXPECT_TRUE(event_data.IsType<script::Handle<script::ValueHandle>>());
   auto script_value =
       event_data.AsType<script::Handle<script::ValueHandle>>().GetScriptValue();
@@ -82,24 +241,44 @@
   std::string actual =
       *(v8::String::Utf8Value(isolate, v8_value.As<v8::String>()));
   EXPECT_EQ("data_value", actual);
+
+  EXPECT_EQ("OriginString", event->origin());
+  EXPECT_EQ("lastEventIdString", event->last_event_id());
+  EXPECT_EQ(web_context()->GetWindowOrWorkerGlobalScope(),
+            event->source().get());
+  EXPECT_TRUE(event->ports().empty());
 }
 
-TEST_F(MessageEventTestWithJavaScript,
-       ConstructorWithEventTypeAndErrorInitDict) {
+TEST_P(MessageEventTestWithJavaScript,
+       ConstructorWithEventTypeAndMessageEventInitDict) {
   std::string result;
   EXPECT_TRUE(
       EvaluateScript("var event = new MessageEvent('dog', "
-                     "    {'cancelable':true, "
-                     "     'data':'data_value'});"
+                     "    {cancelable: true, "
+                     "     origin: 'OriginValue',"
+                     "     lastEventId: 'LastEventIdValue',"
+                     "     source: this,"
+                     "     data: {value: 'data_value'},"
+                     "    }"
+                     ");"
                      "if (event.type == 'dog' &&"
                      "    event.bubbles == false &&"
                      "    event.cancelable == true &&"
-                     "    event.data == 'data_value') "
-                     "    event.data;",
+                     "    event.origin == 'OriginValue' &&"
+                     "    event.lastEventId == 'LastEventIdValue' &&"
+                     "    event.source == this &&"
+                     "    event.ports.length == 0 &&"
+                     "    event.data.value == 'data_value') "
+                     "    event.data.value;",
                      &result))
       << "Failed to evaluate script.";
   EXPECT_EQ("data_value", result);
 }
 
+INSTANTIATE_TEST_CASE_P(
+    MessageEventTestsWithJavaScript, MessageEventTestWithJavaScript,
+    ::testing::ValuesIn(testing::TestWebWithJavaScript::GetWebTypes()),
+    testing::TestWebWithJavaScript::GetTypeName);
+
 }  // namespace web
 }  // namespace cobalt
diff --git a/cobalt/web/message_port.h b/cobalt/web/message_port.h
index 183e35a..9d53f49 100644
--- a/cobalt/web/message_port.h
+++ b/cobalt/web/message_port.h
@@ -29,7 +29,6 @@
 #include "cobalt/web/context.h"
 #include "cobalt/web/event_target.h"
 #include "cobalt/web/event_target_listener_info.h"
-#include "cobalt/web/message_event.h"
 
 namespace cobalt {
 namespace web {
diff --git a/cobalt/web/message_port.idl b/cobalt/web/message_port.idl
index 86b9528..24bab52 100644
--- a/cobalt/web/message_port.idl
+++ b/cobalt/web/message_port.idl
@@ -17,7 +17,6 @@
 interface MessagePort /* : EventTarget */ {
   // [RaisesException]
   void postMessage(any message);
-  // void postMessage(USVString message, sequence<USVString> transfer);
   // TODO: Support sequence<object>: b/218501774
   // void postMessage(any message, sequence<object> transfer);
   // TODO: Support overloads with dictionary parameter: b/218506730
diff --git a/cobalt/web/testing/test_with_javascript.h b/cobalt/web/testing/test_with_javascript.h
index f37276b..011ab7a 100644
--- a/cobalt/web/testing/test_with_javascript.h
+++ b/cobalt/web/testing/test_with_javascript.h
@@ -23,7 +23,6 @@
 #include "cobalt/dom/testing/stub_window.h"
 #include "cobalt/dom/window.h"
 #include "cobalt/script/wrappable.h"
-#include "cobalt/web/window_or_worker_global_scope.h"
 #include "cobalt/worker/testing/test_with_javascript.h"
 
 namespace cobalt {
@@ -42,17 +41,14 @@
       DCHECK(!this->worker_global_scope());
       this->ClearWebContext();
       window_.reset(new dom::testing::StubWindow());
+      window_->InitializeWindow();
     }
   }
 
-  WindowOrWorkerGlobalScope* window_or_worker_global_scope() {
-    return window_ ? window_->window().get() : this->worker_global_scope();
-  }
-
-  scoped_refptr<script::GlobalEnvironment> global_environment() override {
-    if (window_) return window_->global_environment();
+  web::Context* web_context() const override {
+    if (window_) return window_->web_context();
     return worker::testing::TestWithJavaScriptBase<
-        TypeIdProvider>::global_environment();
+        TypeIdProvider>::web_context();
   }
 
  private:
@@ -66,7 +62,7 @@
  public:
   // Return a vector of values for all known worker types, to be used in the
   // INSTANTIATE_TEST_CASE_P() declaration.
-  static std::vector<base::TypeId> GetWorkerTypes() {
+  static std::vector<base::TypeId> GetWebTypes() {
     std::vector<base::TypeId> worker_types =
         worker::testing::TestWorkersWithJavaScript::GetWorkerTypes();
     worker_types.push_back(base::GetTypeId<dom::Window>());
diff --git a/cobalt/web/url_test.cc b/cobalt/web/url_test.cc
index ee6f405..fa0dbcc 100644
--- a/cobalt/web/url_test.cc
+++ b/cobalt/web/url_test.cc
@@ -195,7 +195,7 @@
 
 INSTANTIATE_TEST_CASE_P(
     URLTestsWithJavaScript, URLTestWithJavaScript,
-    ::testing::ValuesIn(testing::TestWebWithJavaScript::GetWorkerTypes()),
+    ::testing::ValuesIn(testing::TestWebWithJavaScript::GetWebTypes()),
     testing::TestWebWithJavaScript::GetTypeName);
 
 
diff --git a/cobalt/web/url_utils.cc b/cobalt/web/url_utils.cc
index 462c6f4..c69bc2d 100644
--- a/cobalt/web/url_utils.cc
+++ b/cobalt/web/url_utils.cc
@@ -126,7 +126,8 @@
 //   https://www.w3.org/TR/2014/WD-url-1-20141209/#pre-update-steps
 void URLUtils::RunPreUpdateSteps(const GURL& new_url,
                                  const std::string& value) {
-  DLOG(INFO) << "Update URL to " << new_url.possibly_invalid_spec();
+  DLOG(INFO) << "Update URL to "
+             << (value.empty() ? new_url.possibly_invalid_spec() : value);
 
   // 1. If value is not given, let value be the result of serializing the
   // associated url.
diff --git a/cobalt/webdriver/session_driver.cc b/cobalt/webdriver/session_driver.cc
index 1c45c01..b04aa70 100644
--- a/cobalt/webdriver/session_driver.cc
+++ b/cobalt/webdriver/session_driver.cc
@@ -14,6 +14,8 @@
 
 #include "cobalt/webdriver/session_driver.h"
 
+#include <utility>
+
 #include "base/logging.h"
 #include "cobalt/base/log_message_handler.h"
 
@@ -56,13 +58,13 @@
       logging_callback_id_(0) {
   logging_callback_id_ = base::LogMessageHandler::GetInstance()->AddCallback(
       base::Bind(&SessionDriver::LogMessageHandler, base::Unretained(this)));
-  window_driver_ = create_window_driver_callback_.Run(GetUniqueWindowId());
+  RefreshWindowDriver();
 }
 
 void SessionDriver::RefreshWindowDriver() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  window_driver_ =
-      create_window_driver_callback_.Run(window_driver_->window_id());
+  window_driver_ = create_window_driver_callback_.Run(
+      window_driver_ ? window_driver_->window_id() : GetUniqueWindowId());
 }
 
 SessionDriver::~SessionDriver() {
diff --git a/cobalt/worker/BUILD.gn b/cobalt/worker/BUILD.gn
index 8c0077a..cf05426 100644
--- a/cobalt/worker/BUILD.gn
+++ b/cobalt/worker/BUILD.gn
@@ -26,7 +26,9 @@
     "dedicated_worker.h",
     "dedicated_worker_global_scope.cc",
     "dedicated_worker_global_scope.h",
+    "extendable_event.cc",
     "extendable_event.h",
+    "extendable_message_event.cc",
     "extendable_message_event.h",
     "navigation_preload_manager.cc",
     "navigation_preload_manager.h",
@@ -80,6 +82,7 @@
 
   sources = [
     "dedicated_worker_global_scope_test.cc",
+    "extendable_message_event_test.cc",
     "service_worker_global_scope_test.cc",
     "worker_global_scope_test.cc",
     "worker_location_test.cc",
diff --git a/cobalt/worker/dedicated_worker.cc b/cobalt/worker/dedicated_worker.cc
index 0dff482..1daf030 100644
--- a/cobalt/worker/dedicated_worker.cc
+++ b/cobalt/worker/dedicated_worker.cc
@@ -58,7 +58,7 @@
   //    allow the page to start dedicated workers).
   // 2. Let outside settings be the current settings object.
   // 3. Parse the scriptURL argument relative to outside settings.
-  Worker::Options options(kDedicatedWorkerName);
+  Worker::Options options;
   const GURL& base_url = environment_settings()->base_url();
   options.url = base_url.Resolve(script_url_);
 
@@ -83,7 +83,7 @@
   options.web_options.service_worker_jobs =
       options.outside_settings->context()->service_worker_jobs();
 
-  worker_.reset(new Worker(options));
+  worker_.reset(new Worker(kDedicatedWorkerName, options));
   // 10. Return worker.
 }
 
diff --git a/cobalt/worker/extendable_event.cc b/cobalt/worker/extendable_event.cc
new file mode 100644
index 0000000..6b49b47
--- /dev/null
+++ b/cobalt/worker/extendable_event.cc
@@ -0,0 +1,83 @@
+// 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/extendable_event.h"
+
+namespace cobalt {
+namespace worker {
+
+void ExtendableEvent::WaitUntil(
+    script::EnvironmentSettings* settings,
+    std::unique_ptr<script::Promise<script::ValueHandle*>>& promise,
+    script::ExceptionState* exception_state) {
+  // Algorithm for waitUntil(), to add lifetime promise to event.
+  //   https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil
+
+  // 1. If event’s isTrusted attribute is false, throw an "InvalidStateError"
+  //    DOMException.
+  // 2. If event is not active, throw an "InvalidStateError" DOMException.
+  if (!IsActive()) {
+    web::DOMException::Raise(web::DOMException::kInvalidStateErr,
+                             exception_state);
+    return;
+  }
+  // 3. Add promise to event’s extend lifetime promises.
+  // 4. Increment event’s pending promises count by one.
+  ++pending_promise_count_;
+  // 5. Upon fulfillment or rejection of promise, queue a microtask to run
+  //    these substeps:
+  std::unique_ptr<base::OnceCallback<void()>> callback(
+      new base::OnceCallback<void()>(std::move(
+          base::BindOnce(&ExtendableEvent::StateChange, base::Unretained(this),
+                         settings, promise.get()))));
+  promise->AddStateChangeCallback(std::move(callback));
+  promise.release();
+}
+
+void ExtendableEvent::StateChange(
+    script::EnvironmentSettings* settings,
+    const script::Promise<script::ValueHandle*>* promise) {
+  // Implement the microtask called upon fulfillment or rejection of a
+  // promise, as part of the algorithm for waitUntil().
+  //   https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil
+  DCHECK(promise);
+  has_rejected_promise_ |= promise->State() == script::PromiseState::kRejected;
+  // 5.1. Decrement event’s pending promises count by one.
+  --pending_promise_count_;
+  // 5.2. If event’s pending promises count is 0, then:
+  if (0 == pending_promise_count_) {
+    if (done_callback_) {
+      std::move(done_callback_).Run(has_rejected_promise_);
+    }
+    web::Context* context =
+        base::polymorphic_downcast<web::EnvironmentSettings*>(settings)
+            ->context();
+    ServiceWorkerJobs* jobs = context->service_worker_jobs();
+    DCHECK(jobs);
+    // 5.2.1. Let registration be the current global object's associated
+    //        service worker's containing service worker registration.
+    jobs->message_loop()->task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &ServiceWorkerJobs::WaitUntilSubSteps, base::Unretained(jobs),
+            base::Unretained(context->GetWindowOrWorkerGlobalScope()
+                                 ->AsServiceWorker()
+                                 ->service_worker_object()
+                                 ->containing_service_worker_registration())));
+  }
+  delete promise;
+}
+
+}  // namespace worker
+}  // namespace cobalt
diff --git a/cobalt/worker/extendable_event.h b/cobalt/worker/extendable_event.h
index e7d7a6a..051e034 100644
--- a/cobalt/worker/extendable_event.h
+++ b/cobalt/worker/extendable_event.h
@@ -44,9 +44,9 @@
 class ExtendableEvent : public web::Event {
  public:
   explicit ExtendableEvent(const std::string& type) : Event(type) {}
-  explicit ExtendableEvent(base::Token type,
-                           base::OnceCallback<void(bool)> done_callback =
-                               base::OnceCallback<void(bool)>())
+  ExtendableEvent(base::Token type,
+                  base::OnceCallback<void(bool)> done_callback =
+                      base::OnceCallback<void(bool)>())
       : Event(type), done_callback_(std::move(done_callback)) {}
   ExtendableEvent(const std::string& type, const ExtendableEventInit& init_dict)
       : Event(type, init_dict) {}
@@ -54,65 +54,10 @@
   void WaitUntil(
       script::EnvironmentSettings* settings,
       std::unique_ptr<script::Promise<script::ValueHandle*>>& promise,
-      script::ExceptionState* exception_state) {
-    // Algorithm for waitUntil(), to add lifetime promise to event.
-    //   https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil
-
-    // 1. If event’s isTrusted attribute is false, throw an "InvalidStateError"
-    //    DOMException.
-    // 2. If event is not active, throw an "InvalidStateError" DOMException.
-    if (!IsActive()) {
-      web::DOMException::Raise(web::DOMException::kInvalidStateErr,
-                               exception_state);
-      return;
-    }
-    // 3. Add promise to event’s extend lifetime promises.
-    // 4. Increment event’s pending promises count by one.
-    ++pending_promise_count_;
-    // 5. Upon fulfillment or rejection of promise, queue a microtask to run
-    //    these substeps:
-    std::unique_ptr<base::OnceCallback<void()>> callback(
-        new base::OnceCallback<void()>(std::move(
-            base::BindOnce(&ExtendableEvent::StateChange,
-                           base::Unretained(this), settings, promise.get()))));
-    promise->AddStateChangeCallback(std::move(callback));
-    promise.release();
-  }
+      script::ExceptionState* exception_state);
 
   void StateChange(script::EnvironmentSettings* settings,
-                   const script::Promise<script::ValueHandle*>* promise) {
-    // Implement the microtask called upon fulfillment or rejection of a
-    // promise, as part of the algorithm for waitUntil().
-    //   https://w3c.github.io/ServiceWorker/#dom-extendableevent-waituntil
-    DCHECK(promise);
-    has_rejected_promise_ |=
-        promise->State() == script::PromiseState::kRejected;
-    // 5.1. Decrement event’s pending promises count by one.
-    --pending_promise_count_;
-    // 5.2. If event’s pending promises count is 0, then:
-    if (0 == pending_promise_count_) {
-      if (done_callback_) {
-        std::move(done_callback_).Run(has_rejected_promise_);
-      }
-      web::Context* context =
-          base::polymorphic_downcast<web::EnvironmentSettings*>(settings)
-              ->context();
-      ServiceWorkerJobs* jobs = context->service_worker_jobs();
-      DCHECK(jobs);
-      // 5.2.1. Let registration be the current global object's associated
-      //        service worker's containing service worker registration.
-      jobs->message_loop()->task_runner()->PostTask(
-          FROM_HERE,
-          base::BindOnce(&ServiceWorkerJobs::WaitUntilSubSteps,
-                         base::Unretained(jobs),
-                         base::Unretained(
-                             context->GetWindowOrWorkerGlobalScope()
-                                 ->AsServiceWorker()
-                                 ->service_worker_object()
-                                 ->containing_service_worker_registration())));
-    }
-    delete promise;
-  }
+                   const script::Promise<script::ValueHandle*>* promise);
 
   bool IsActive() {
     // An ExtendableEvent object is said to be active when its timed out flag
diff --git a/cobalt/worker/extendable_event_init.idl b/cobalt/worker/extendable_event_init.idl
index d040bfb..5a8c5ed 100644
--- a/cobalt/worker/extendable_event_init.idl
+++ b/cobalt/worker/extendable_event_init.idl
@@ -14,6 +14,6 @@
 
 // https://w3c.github.io/ServiceWorker/#dictdef-extendableeventinit
 
-dictionary ExtendableEventInit : EventInit{
+dictionary ExtendableEventInit : EventInit {
   // Defined for the forward compatibility across the derived events
 };
diff --git a/cobalt/worker/extendable_message_event.cc b/cobalt/worker/extendable_message_event.cc
new file mode 100644
index 0000000..200e2a6
--- /dev/null
+++ b/cobalt/worker/extendable_message_event.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/worker/extendable_message_event.h"
+
+#include <string>
+#include <utility>
+
+#include "cobalt/script/environment_settings.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/v8c/entry_scope.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/worker/extendable_message_event_init.h"
+#include "v8/include/v8.h"
+
+namespace cobalt {
+namespace worker {
+
+ExtendableMessageEvent::ExtendableMessageEvent(
+    const std::string& type, const ExtendableMessageEventInit& init_dict)
+    : ExtendableEvent(type, init_dict) {
+  if (init_dict.has_data() && init_dict.data()) {
+    DCHECK(init_dict.data());
+    data_ = script::SerializeScriptValue(*(init_dict.data()));
+  }
+  if (init_dict.has_origin()) {
+    origin_ = init_dict.origin();
+  }
+  if (init_dict.has_last_event_id()) {
+    last_event_id_ = init_dict.last_event_id();
+  }
+  if (init_dict.has_source()) {
+    source_ = init_dict.source();
+  }
+  if (init_dict.has_ports()) {
+    ports_ = init_dict.ports();
+  }
+}
+
+script::Handle<script::ValueHandle> ExtendableMessageEvent::data(
+    script::EnvironmentSettings* settings) const {
+  if (!settings) return script::Handle<script::ValueHandle>();
+  script::GlobalEnvironment* global_environment =
+      base::polymorphic_downcast<web::EnvironmentSettings*>(settings)
+          ->context()
+          ->global_environment();
+  DCHECK(global_environment);
+  v8::Isolate* isolate = global_environment->isolate();
+  script::v8c::EntryScope entry_scope(isolate);
+  DCHECK(isolate);
+  if (!data_) return script::Handle<script::ValueHandle>();
+  return script::Handle<script::ValueHandle>(
+      std::move(script::DeserializeScriptValue(isolate, *data_)));
+}
+
+}  // namespace worker
+}  // namespace cobalt
diff --git a/cobalt/worker/extendable_message_event.h b/cobalt/worker/extendable_message_event.h
index c6364a7..475be64 100644
--- a/cobalt/worker/extendable_message_event.h
+++ b/cobalt/worker/extendable_message_event.h
@@ -17,57 +17,71 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 
+#include "base/memory/scoped_refptr.h"
 #include "base/optional.h"
 #include "cobalt/base/token.h"
+#include "cobalt/script/union_type.h"
 #include "cobalt/script/value_handle.h"
 #include "cobalt/script/wrappable.h"
+#include "cobalt/web/event_target.h"
 #include "cobalt/web/message_port.h"
+#include "cobalt/worker/client.h"
 #include "cobalt/worker/extendable_event.h"
 #include "cobalt/worker/extendable_message_event_init.h"
+#include "cobalt/worker/service_worker.h"
 
 namespace cobalt {
 namespace worker {
 
 class ExtendableMessageEvent : public ExtendableEvent {
  public:
-  typedef scoped_refptr<script::Wrappable> SourceType;
+  using SourceType =
+      cobalt::script::UnionType3<scoped_refptr<Client>,
+                                 scoped_refptr<ServiceWorker>,
+                                 scoped_refptr<web::MessagePort>>;
 
   explicit ExtendableMessageEvent(const std::string& type)
       : ExtendableEvent(type) {}
   explicit ExtendableMessageEvent(base::Token type) : ExtendableEvent(type) {}
+  ExtendableMessageEvent(base::Token type,
+                         std::unique_ptr<script::DataBuffer> data)
+      : ExtendableEvent(type), data_(std::move(data)) {}
   ExtendableMessageEvent(const std::string& type,
-                         const ExtendableMessageEventInit& init_dict)
-      : ExtendableEvent(type, init_dict) {}
+                         const ExtendableMessageEventInit& init_dict);
 
+  script::Handle<script::ValueHandle> data(
+      script::EnvironmentSettings* settings = nullptr) const;
 
-  const script::ValueHandleHolder* data() const {
-    if (!data_) {
-      return NULL;
-    }
-
-    return &(data_->referenced_value());
-  }
-
-  std::string origin() const { return origin_; }
-  std::string last_event_id() const { return last_event_id_; }
-
-  base::Optional<SourceType> source() { return source_; }
-
+  const std::string& origin() const { return origin_; }
+  const std::string& last_event_id() const { return last_event_id_; }
+  const scoped_refptr<web::EventTarget>& source() const { return source_; }
   script::Sequence<scoped_refptr<MessagePort>> ports() const { return ports_; }
 
+  // These helper functions are custom, and not in any spec.
+  void set_origin(const std::string& origin) { origin_ = origin; }
+  void set_last_event_id(const std::string& last_event_id) {
+    last_event_id_ = last_event_id;
+  }
+  void set_source(const scoped_refptr<web::EventTarget>& source) {
+    source_ = source;
+  }
+  void set_ports(script::Sequence<scoped_refptr<MessagePort>> ports) {
+    ports_ = ports;
+  }
+
   DEFINE_WRAPPABLE_TYPE(ExtendableMessageEvent);
 
  protected:
   ~ExtendableMessageEvent() override {}
 
  private:
-  std::unique_ptr<script::ValueHandleHolder::Reference> data_;
-
-  std::string origin_ = "Origin Stub Value";
-  std::string last_event_id_ = "Last Event Id Stub Value";
-  base::Optional<SourceType> source_;
+  std::string origin_;
+  std::string last_event_id_;
+  scoped_refptr<web::EventTarget> source_;
   script::Sequence<scoped_refptr<MessagePort>> ports_;
+  std::unique_ptr<script::DataBuffer> data_;
 };
 
 }  // namespace worker
diff --git a/cobalt/worker/extendable_message_event.idl b/cobalt/worker/extendable_message_event.idl
index 1e2d5da..600fa71 100644
--- a/cobalt/worker/extendable_message_event.idl
+++ b/cobalt/worker/extendable_message_event.idl
@@ -18,10 +18,10 @@
   Exposed = ServiceWorker,
   Constructor(DOMString type, optional ExtendableMessageEventInit eventInitDict)
 ] interface ExtendableMessageEvent : ExtendableEvent {
-  readonly attribute any data;
+  [CallWith = EnvironmentSettings] readonly attribute any data;
   readonly attribute USVString origin;
   readonly attribute DOMString lastEventId;
-  [SameObject] readonly attribute (Client or ServiceWorker or MessagePort)? source;
+  readonly attribute EventTarget                             ? source;
   // TODO(b/236750294): Make this be FrozenArray<MessagePort> when available.
   readonly attribute sequence<MessagePort> ports;
 };
diff --git a/cobalt/worker/extendable_message_event_init.idl b/cobalt/worker/extendable_message_event_init.idl
index 25da4cb..82c4e09 100644
--- a/cobalt/worker/extendable_message_event_init.idl
+++ b/cobalt/worker/extendable_message_event_init.idl
@@ -15,9 +15,10 @@
 // https://w3c.github.io/ServiceWorker/#dictdef-extendablemessageeventinit
 
 dictionary ExtendableMessageEventInit : ExtendableEventInit {
-  any data = null;
+  any data;
   USVString origin = "";
   DOMString lastEventId = "";
-  (Client or ServiceWorker or MessagePort)? source = null;
+  EventTarget? source;
+  //(Client or ServiceWorker or MessagePort) ? source = null;
   sequence<MessagePort> ports;
 };
diff --git a/cobalt/worker/extendable_message_event_test.cc b/cobalt/worker/extendable_message_event_test.cc
new file mode 100644
index 0000000..b5162b7
--- /dev/null
+++ b/cobalt/worker/extendable_message_event_test.cc
@@ -0,0 +1,199 @@
+// 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/extendable_message_event.h"
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "cobalt/script/testing/fake_script_value.h"
+#include "cobalt/script/v8c/entry_scope.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/web/testing/gtest_workarounds.h"
+#include "cobalt/web/testing/mock_event_listener.h"
+#include "cobalt/worker/extendable_message_event_init.h"
+#include "cobalt/worker/testing/test_with_javascript.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace worker {
+
+using script::testing::FakeScriptValue;
+using web::testing::MockEventListener;
+
+namespace {
+class ExtendableMessageEventTestWithJavaScript
+    : public testing::TestServiceWorkerWithJavaScript {
+ protected:
+  ExtendableMessageEventTestWithJavaScript() {
+    fake_event_listener_ = MockEventListener::Create();
+  }
+
+  ~ExtendableMessageEventTestWithJavaScript() override {}
+
+  std::unique_ptr<MockEventListener> fake_event_listener_;
+};
+}  // namespace
+
+TEST_F(ExtendableMessageEventTestWithJavaScript,
+       ConstructorWithEventTypeString) {
+  scoped_refptr<ExtendableMessageEvent> event =
+      new ExtendableMessageEvent("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());
+  script::Handle<script::ValueHandle> event_data =
+      event->data(web_context()->environment_settings());
+  EXPECT_TRUE(event_data.IsEmpty());
+}
+
+TEST_F(ExtendableMessageEventTestWithJavaScript, ConstructorWithAny) {
+  base::Optional<script::ValueHandleHolder::Reference> reference;
+  EvaluateScript("'ConstructorWithAnyMessageData'", &reference);
+  std::unique_ptr<script::DataBuffer> data(
+      script::SerializeScriptValue(reference->referenced_value()));
+  EXPECT_NE(nullptr, data.get());
+  EXPECT_NE(nullptr, data->ptr);
+  EXPECT_GT(data->size, 0U);
+  scoped_refptr<ExtendableMessageEvent> event =
+      new ExtendableMessageEvent(base::Tokens::message(), std::move(data));
+
+  EXPECT_EQ("message", 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());
+  script::Handle<script::ValueHandle> event_data =
+      event->data(web_context()->environment_settings());
+  EXPECT_FALSE(event_data.IsEmpty());
+  auto script_value = event_data.GetScriptValue();
+  auto* isolate = script::GetIsolate(*script_value);
+  script::v8c::EntryScope entry_scope(isolate);
+  v8::Local<v8::Value> v8_value = script::GetV8Value(*script_value);
+  std::string actual =
+      *(v8::String::Utf8Value(isolate, v8_value.As<v8::String>()));
+  EXPECT_EQ("ConstructorWithAnyMessageData", actual);
+}
+
+TEST_F(ExtendableMessageEventTestWithJavaScript,
+       ConstructorWithEventTypeAndDefaultInitDict) {
+  ExtendableMessageEventInit init;
+  scoped_refptr<ExtendableMessageEvent> event =
+      new ExtendableMessageEvent("mytestevent", init);
+
+  EXPECT_EQ("mytestevent", event->type());
+  EXPECT_EQ(nullptr, event->target().get());
+  EXPECT_EQ(nullptr, 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());
+
+  script::Handle<script::ValueHandle> event_data =
+      event->data(web_context()->environment_settings());
+  EXPECT_TRUE(event_data.IsEmpty());
+  EXPECT_TRUE(event->origin().empty());
+  EXPECT_TRUE(event->last_event_id().empty());
+  EXPECT_EQ(nullptr, event->source().get());
+  EXPECT_TRUE(event->ports().empty());
+}
+
+TEST_F(ExtendableMessageEventTestWithJavaScript,
+       ConstructorWithEventTypeAndInitDict) {
+  ExtendableMessageEventInit init;
+  base::Optional<script::ValueHandleHolder::Reference> reference;
+  EvaluateScript("'data_value'", &reference);
+  init.set_data(&(reference->referenced_value()));
+  init.set_origin("OriginString");
+  init.set_last_event_id("lastEventIdString");
+  init.set_source(web_context()->GetWindowOrWorkerGlobalScope());
+  scoped_refptr<ExtendableMessageEvent> event =
+      new ExtendableMessageEvent("mytestevent", init);
+
+  EXPECT_EQ("mytestevent", event->type());
+  EXPECT_EQ(nullptr, event->target().get());
+  EXPECT_EQ(nullptr, 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());
+
+  script::Handle<script::ValueHandle> event_data =
+      event->data(web_context()->environment_settings());
+  auto script_value = event_data.GetScriptValue();
+  auto* isolate = script::GetIsolate(*script_value);
+  script::v8c::EntryScope entry_scope(isolate);
+  v8::Local<v8::Value> v8_value = script::GetV8Value(*script_value);
+  std::string actual =
+      *(v8::String::Utf8Value(isolate, v8_value.As<v8::String>()));
+  EXPECT_EQ("data_value", actual);
+
+  EXPECT_EQ("OriginString", event->origin());
+  EXPECT_EQ("lastEventIdString", event->last_event_id());
+  EXPECT_EQ(web_context()->GetWindowOrWorkerGlobalScope(),
+            event->source().get());
+  EXPECT_TRUE(event->ports().empty());
+}
+
+TEST_F(ExtendableMessageEventTestWithJavaScript,
+       ConstructorWithEventTypeAndExtendableMessageEventInitDict) {
+  std::string result;
+  EXPECT_TRUE(
+      EvaluateScript("var event = new ExtendableMessageEvent('dog', "
+                     "    {cancelable: true, "
+                     "     origin: 'OriginValue',"
+                     "     lastEventId: 'LastEventIdValue',"
+                     "     source: this,"
+                     "     data: {value: 'data_value'},"
+                     "    }"
+                     ");"
+                     "if (event.type == 'dog' &&"
+                     "    event.bubbles == false &&"
+                     "    event.cancelable == true &&"
+                     "    event.origin == 'OriginValue' &&"
+                     "    event.lastEventId == 'LastEventIdValue' &&"
+                     "    event.source == this &&"
+                     "    event.ports.length == 0 &&"
+                     "    event.data.value == 'data_value') "
+                     "    event.data.value;",
+                     &result))
+      << "Failed to evaluate script.";
+  EXPECT_EQ("data_value", result);
+}
+
+}  // namespace worker
+}  // namespace cobalt
diff --git a/cobalt/worker/service_worker.cc b/cobalt/worker/service_worker.cc
index 4d6ac58..0756da2 100644
--- a/cobalt/worker/service_worker.cc
+++ b/cobalt/worker/service_worker.cc
@@ -17,8 +17,12 @@
 #include <memory>
 #include <utility>
 
+#include "base/bind.h"
 #include "cobalt/script/environment_settings.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/web/event_target.h"
 #include "cobalt/web/message_port.h"
+#include "cobalt/worker/extendable_message_event.h"
 #include "cobalt/worker/service_worker_global_scope.h"
 #include "cobalt/worker/service_worker_object.h"
 #include "cobalt/worker/service_worker_state.h"
@@ -30,8 +34,33 @@
                              worker::ServiceWorkerObject* worker)
     : web::EventTarget(settings),
       worker_(worker),
-      message_port_(new web::MessagePort(worker->worker_global_scope())),
       state_(kServiceWorkerStateParsed) {}
 
+
+void ServiceWorker::PostMessage(const script::ValueHandleHolder& message) {
+  // https://w3c.github.io/ServiceWorker/#service-worker-postmessage-options
+  web::EventTarget* event_target = worker_->worker_global_scope();
+  if (!event_target) return;
+
+  base::MessageLoop* message_loop =
+      event_target->environment_settings()->context()->message_loop();
+  if (!message_loop) {
+    return;
+  }
+  std::unique_ptr<script::DataBuffer> data_buffer(
+      script::SerializeScriptValue(message));
+  if (!data_buffer) {
+    return;
+  }
+  message_loop->task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(
+                     [](web::EventTarget* event_target,
+                        std::unique_ptr<script::DataBuffer> data_buffer) {
+                       event_target->DispatchEvent(new ExtendableMessageEvent(
+                           base::Tokens::message(), std::move(data_buffer)));
+                     },
+                     base::Unretained(event_target), std::move(data_buffer)));
+}
+
 }  // namespace worker
 }  // namespace cobalt
diff --git a/cobalt/worker/service_worker.h b/cobalt/worker/service_worker.h
index a281746..193a6a4 100644
--- a/cobalt/worker/service_worker.h
+++ b/cobalt/worker/service_worker.h
@@ -20,6 +20,7 @@
 #include <utility>
 
 #include "cobalt/script/environment_settings.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"
@@ -43,10 +44,7 @@
 
   // Web API: ServiceWorker
   //
-  void PostMessage(const script::ValueHandleHolder& message) {
-    DCHECK(message_port_);
-    if (worker_->worker_global_scope()) message_port_->PostMessage(message);
-  }
+  void PostMessage(const script::ValueHandleHolder& message);
 
   // The scriptURL getter steps are to return the
   // service worker's serialized script url.
@@ -77,13 +75,9 @@
   DEFINE_WRAPPABLE_TYPE(ServiceWorker);
 
  private:
-  ~ServiceWorker() override {
-    message_port_.reset();
-    worker_ = nullptr;
-  }
+  ~ServiceWorker() override { worker_ = nullptr; }
 
   scoped_refptr<ServiceWorkerObject> worker_;
-  scoped_refptr<web::MessagePort> message_port_;
   ServiceWorkerState state_;
 };
 
diff --git a/cobalt/worker/service_worker_global_scope_test.cc b/cobalt/worker/service_worker_global_scope_test.cc
index a2a1c0c..0c56208 100644
--- a/cobalt/worker/service_worker_global_scope_test.cc
+++ b/cobalt/worker/service_worker_global_scope_test.cc
@@ -29,6 +29,8 @@
 using script::testing::FakeScriptValue;
 using web::testing::MockEventListener;
 
+namespace {
+
 class ServiceWorkerGlobalScopeTest
     : public testing::TestServiceWorkerWithJavaScript {
  protected:
@@ -41,6 +43,8 @@
   std::unique_ptr<MockEventListener> fake_event_listener_;
 };
 
+}  // namespace
+
 TEST_F(ServiceWorkerGlobalScopeTest, RegistrationIsObject) {
   std::string result;
   EXPECT_TRUE(EvaluateScript("typeof self.registration", &result));
diff --git a/cobalt/worker/service_worker_object.cc b/cobalt/worker/service_worker_object.cc
index 436f256..e63c421 100644
--- a/cobalt/worker/service_worker_object.cc
+++ b/cobalt/worker/service_worker_object.cc
@@ -58,6 +58,7 @@
     DCHECK(message_loop());
     DCHECK(web_context_);
     web_agent_->WaitUntilDone();
+    web_agent_->Stop();
     web_agent_.reset();
     web_context_ = nullptr;
   }
@@ -123,10 +124,11 @@
 void ServiceWorkerObject::ObtainWebAgentAndWaitUntilDone() {
   TRACE_EVENT0("cobalt::worker",
                "ServiceWorkerObject::ObtainWebAgentAndWaitUntilDone()");
-  web_agent_.reset(new web::Agent(
+  web_agent_.reset(new web::Agent(options_.name));
+  web_agent_->Run(
       options_.web_options,
       base::Bind(&ServiceWorkerObject::Initialize, base::Unretained(this)),
-      this));
+      this);
   web_agent_->WaitUntilDone();
 }
 
diff --git a/cobalt/worker/service_worker_object.h b/cobalt/worker/service_worker_object.h
index 8b4daa6..a3cf5cb 100644
--- a/cobalt/worker/service_worker_object.h
+++ b/cobalt/worker/service_worker_object.h
@@ -56,15 +56,16 @@
  public:
   // Worker Options needed at thread run time.
   struct Options {
-    explicit Options(
+    Options(
         const std::string& name, network::NetworkModule* network_module,
         ServiceWorkerRegistrationObject* containing_service_worker_registration)
-        : web_options(name),
+        : name(name),
           containing_service_worker_registration(
               containing_service_worker_registration) {
       web_options.network_module = network_module;
     }
 
+    std::string name;
     web::Agent::Options web_options;
     ServiceWorkerRegistrationObject* containing_service_worker_registration;
   };
diff --git a/cobalt/worker/testing/test_with_javascript.h b/cobalt/worker/testing/test_with_javascript.h
index f6499d5..72dda4c 100644
--- a/cobalt/worker/testing/test_with_javascript.h
+++ b/cobalt/worker/testing/test_with_javascript.h
@@ -83,23 +83,34 @@
     web_context_.reset();
   }
 
-  WorkerGlobalScope* worker_global_scope() { return worker_global_scope_; }
-
-  virtual scoped_refptr<script::GlobalEnvironment> global_environment() {
-    return web_context_->global_environment();
+  WorkerGlobalScope* worker_global_scope() const {
+    return worker_global_scope_;
   }
 
-  bool EvaluateScript(const std::string& js_code, std::string* result) {
-    DCHECK(this->global_environment());
-    scoped_refptr<script::SourceCode> source_code =
-        script::SourceCode::CreateSourceCode(
-            js_code, base::SourceLocation(__FILE__, __LINE__, 1));
+  virtual web::Context* web_context() const {
+    DCHECK(web_context_);
+    return web_context_.get();
+  }
 
-    this->global_environment()->EnableEval();
-    this->global_environment()->SetReportEvalCallback(base::Closure());
-    bool succeeded =
-        this->global_environment()->EvaluateScript(source_code, result);
-    return succeeded;
+  scoped_refptr<script::GlobalEnvironment> global_environment() const {
+    DCHECK(this->web_context());
+    return this->web_context()->global_environment();
+  }
+
+  bool EvaluateScript(const std::string& js_code,
+                      std::string* result = nullptr) {
+    DCHECK(global_environment());
+    return global_environment()->EvaluateScript(
+        CreateSourceCodeAndPrepareEval(js_code), result);
+  }
+
+  bool EvaluateScript(
+      const std::string& js_code,
+      base::Optional<script::ValueHandleHolder::Reference>* result) {
+    DCHECK(global_environment());
+    return global_environment()->EvaluateScript(
+        CreateSourceCodeAndPrepareEval(js_code),
+        this->web_context()->GetWindowOrWorkerGlobalScope(), result);
   }
 
   ::testing::StrictMock<script::testing::MockExceptionState>*
@@ -108,6 +119,15 @@
   }
 
  private:
+  scoped_refptr<script::SourceCode> CreateSourceCodeAndPrepareEval(
+      const std::string& js_code) {
+    DCHECK(global_environment());
+    global_environment()->EnableEval();
+    global_environment()->SetReportEvalCallback(base::Closure());
+    return script::SourceCode::CreateSourceCode(
+        js_code, base::SourceLocation(__FILE__, __LINE__, 1));
+  }
+
   std::unique_ptr<web::testing::StubWebContext> web_context_;
   WorkerGlobalScope* worker_global_scope_ = nullptr;
   scoped_refptr<DedicatedWorkerGlobalScope> dedicated_worker_global_scope_;
diff --git a/cobalt/worker/worker.cc b/cobalt/worker/worker.cc
index a820c8f..870783d 100644
--- a/cobalt/worker/worker.cc
+++ b/cobalt/worker/worker.cc
@@ -43,7 +43,7 @@
 bool PermitAnyURL(const GURL&, bool) { return true; }
 }  // namespace
 
-Worker::Worker(const Options& options) : options_(options) {
+Worker::Worker(const char* name, const Options& options) : options_(options) {
   // Algorithm for 'run a worker'
   //   https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#run-a-worker
   // 1. Let is shared be true if worker is a SharedWorker object, and false
@@ -57,9 +57,15 @@
   // 6. Let agent be the result of obtaining a dedicated/shared worker agent
   //    given outside settings and is shared. Run the rest of these steps in
   //    that agent.
-  web_agent_.reset(new web::Agent(
-      options.web_options,
-      base::Bind(&Worker::Initialize, base::Unretained(this)), this));
+  std::string agent_name(name);
+  if (!options.options.name().empty()) {
+    agent_name.push_back(':');
+    agent_name.append(options.options.name());
+  }
+  web_agent_.reset(new web::Agent(agent_name));
+  web_agent_->Run(options.web_options,
+                  base::Bind(&Worker::Initialize, base::Unretained(this)),
+                  this);
 }
 
 void Worker::WillDestroyCurrentMessageLoop() {
@@ -126,7 +132,7 @@
 #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);
+  dedicated_worker_global_scope->set_name(options_.options.name());
   // 11. Append owner to worker global scope's owner set.
   // 12. If is shared is true, then:
   //     1. Set worker global scope's constructor origin to outside settings's
@@ -170,6 +176,8 @@
   loader_ = web_context_->script_loader_factory()->CreateScriptLoader(
       url, origin, csp_callback,
       base::Bind(&Worker::OnContentProduced, base::Unretained(this)),
+      base::Bind(&WorkerGlobalScope::InitializePolicyContainerCallback,
+                 worker_global_scope_),
       base::Bind(&Worker::OnLoadingComplete, base::Unretained(this)));
 }
 
@@ -309,6 +317,7 @@
   if (web_agent_) {
     DCHECK(message_loop());
     web_agent_->WaitUntilDone();
+    web_agent_->Stop();
     web_agent_.reset();
     web_context_ = nullptr;
   }
diff --git a/cobalt/worker/worker.h b/cobalt/worker/worker.h
index 2c488ec..feea239 100644
--- a/cobalt/worker/worker.h
+++ b/cobalt/worker/worker.h
@@ -55,8 +55,6 @@
  public:
   // Worker Options needed at thread run time.
   struct Options {
-    explicit Options(const std::string& name) : web_options(name) {}
-
     web::Agent::Options web_options;
 
     // True if worker is a SharedWorker object, and false otherwise.
@@ -70,7 +68,7 @@
     WorkerOptions options;
   };
 
-  explicit Worker(const Options& options);
+  Worker(const char* name, const Options& options);
   ~Worker();
   Worker(const Worker&) = delete;
   Worker& operator=(const Worker&) = delete;
diff --git a/cobalt/worker/worker_global_scope.cc b/cobalt/worker/worker_global_scope.cc
index 662aaa5..4c68bbe 100644
--- a/cobalt/worker/worker_global_scope.cc
+++ b/cobalt/worker/worker_global_scope.cc
@@ -23,6 +23,7 @@
 #include "base/message_loop/message_loop_current.h"
 #include "base/threading/thread.h"
 #include "base/trace_event/trace_event.h"
+#include "cobalt/loader/net_fetcher.h"
 #include "cobalt/loader/origin.h"
 #include "cobalt/script/environment_settings.h"
 #include "cobalt/web/context.h"
@@ -195,6 +196,53 @@
   set_navigator_base(navigator_);
 }
 
+bool WorkerGlobalScope::InitializePolicyContainerCallback(
+    loader::Fetcher* fetcher,
+    const scoped_refptr<net::HttpResponseHeaders>& headers) {
+  DCHECK(headers);
+  // https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#initialize-worker-policy-container
+  // 1. If workerGlobalScope's url is local but its scheme is not "blob":
+  //   1. Assert: workerGlobalScope's owner set's size is 1.
+  //   2. Set workerGlobalScope's policy container to a clone of
+  //      workerGlobalScope's owner set[0]'s relevant settings object's policy
+  //      container.
+  // 2. Otherwise, set workerGlobalScope's policy container to the result of
+  //    creating a policy container from a fetch response given response and
+  //    environment.
+  // Steps from create a policy container from a fetch response:
+  // https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#creating-a-policy-container-from-a-fetch-response
+  // 1. If response's URL's scheme is "blob", then return a clone of response's
+  //    URL's blob URL entry's environment's policy container.
+  // 2. Let result be a new policy container.
+  // 3. Set result's CSP list to the result of parsing a response's Content
+  //    Security Policies given response.
+  // 4. If environment is non-null, then set result's embedder policy to the
+  //    result of obtaining an embedder policy given response and environment.
+  //    Otherwise, set it to "unsafe-none".
+  // 5. Set result's referrer policy to the result of parsing the
+  //    `Referrer-Policy` header given response. [REFERRERPOLICY]
+  // 6. Return result.
+
+  // Steps 3-6. Since csp_delegate doesn't fully mirror PolicyContainer, we
+  // don't create a new one here and return it. Instead we update the existing
+  // one for this worker and return true for success and false for failure.
+  csp::ResponseHeaders csp_headers(headers);
+  if (csp_delegate()->OnReceiveHeaders(csp_headers)) {
+    return true;
+  }
+  // Only NetFetchers are expected to call this, since only they have the
+  // response headers.
+  loader::NetFetcher* net_fetcher =
+      base::polymorphic_downcast<loader::NetFetcher*>(fetcher);
+  net::URLFetcher* url_fetcher = net_fetcher->url_fetcher();
+  LOG(INFO) << "Failure receiving Content Security Policy headers "
+               "for URL: "
+            << url_fetcher->GetURL() << ".";
+  // Return true regardless of CSP headers being received to continue loading
+  // the response.
+  return true;
+}
+
 void WorkerGlobalScope::ImportScripts(const std::vector<std::string>& urls,
                                       script::ExceptionState* exception_state) {
   ImportScriptsInternal(urls, exception_state, URLLookupCallback(),
diff --git a/cobalt/worker/worker_global_scope.h b/cobalt/worker/worker_global_scope.h
index f4e2291..345220b 100644
--- a/cobalt/worker/worker_global_scope.h
+++ b/cobalt/worker/worker_global_scope.h
@@ -63,6 +63,13 @@
 
   virtual void Initialize() {}
 
+  // https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#initialize-worker-policy-container
+  // Intended to be called from a loader::Decoder::OnResponseStarted to
+  // initialize CSP headers once they're fetched.
+  bool InitializePolicyContainerCallback(
+      loader::Fetcher* fetcher,
+      const scoped_refptr<net::HttpResponseHeaders>& headers);
+
   // Web API: WorkerGlobalScope
   //
   scoped_refptr<WorkerGlobalScope> self() { return this; }
diff --git a/cobalt/worker/worker_global_scope_test.cc b/cobalt/worker/worker_global_scope_test.cc
index 67ee3ed..216da15 100644
--- a/cobalt/worker/worker_global_scope_test.cc
+++ b/cobalt/worker/worker_global_scope_test.cc
@@ -20,6 +20,7 @@
 #include "cobalt/bindings/testing/utils.h"
 #include "cobalt/script/testing/fake_script_value.h"
 #include "cobalt/web/error_event.h"
+#include "cobalt/web/message_event.h"
 #include "cobalt/web/testing/mock_event_listener.h"
 #include "cobalt/worker/testing/test_with_javascript.h"
 
diff --git a/glimp/gles/context.cc b/glimp/gles/context.cc
index 5a5eb59..ac0b53b 100644
--- a/glimp/gles/context.cc
+++ b/glimp/gles/context.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Google Inc. All Rights Reserved.
+ * 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.
@@ -37,6 +37,7 @@
 
 namespace {
 
+std::atomic_int s_context_id_counter_(0);
 SbOnceControl s_tls_current_context_key_once_control = SB_ONCE_INITIALIZER;
 SbThreadLocalKey s_tls_current_context_key = kSbThreadLocalKeyInvalid;
 
@@ -54,6 +55,7 @@
 Context::Context(nb::scoped_ptr<ContextImpl> context_impl,
                  Context* share_context)
     : impl_(context_impl.Pass()),
+      context_id_(s_context_id_counter_++),
       current_thread_(kSbThreadInvalid),
       has_been_current_(false),
       active_texture_(GL_TEXTURE0),
@@ -974,7 +976,7 @@
   SB_DCHECK(access & GL_MAP_INVALIDATE_BUFFER_BIT)
       << "glimp requires the GL_MAP_INVALIDATE_BUFFER_BIT flag to be set.";
   SB_DCHECK(access & GL_MAP_UNSYNCHRONIZED_BIT)
-      << "glimp requres the GL_MAP_UNSYNCHRONIZED_BIT flag to be set.";
+      << "glimp requires the GL_MAP_UNSYNCHRONIZED_BIT flag to be set.";
   SB_DCHECK(!(access & GL_MAP_FLUSH_EXPLICIT_BIT))
       << "glimp does not support the GL_MAP_FLUSH_EXPLICIT_BIT flag.";
   SB_DCHECK(length == bound_buffer->size_in_bytes())
diff --git a/glimp/gles/context.h b/glimp/gles/context.h
index 5444618..66e9ed7 100644
--- a/glimp/gles/context.h
+++ b/glimp/gles/context.h
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Google Inc. All Rights Reserved.
+ * 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.
@@ -19,6 +19,7 @@
 
 #include <GLES3/gl3.h>
 
+#include <atomic>
 #include <map>
 #include <set>
 #include <string>
@@ -58,6 +59,9 @@
   // Releases the current thread's current context.
   static void ReleaseTLSCurrentContext();
 
+  // Returns the unique id of the context instance.
+  int context_id() const { return context_id_; }
+
   egl::Surface* draw_surface() {
     return default_draw_framebuffer_->color_attachment_surface();
   }
@@ -302,6 +306,10 @@
   // A reference to the platform-specific implementation aspects of the context.
   nb::scoped_ptr<ContextImpl> impl_;
 
+  // The unique id of context instance. It might be queried from different
+  // threads.
+  std::atomic_int context_id_;
+
   // The thread that currently holds this context as its current context.
   SbThread current_thread_;
 
diff --git a/net/data/url_request_unittest/redirect302-to-echo-cacheable.mock-http-headers b/net/data/url_request_unittest/redirect302-to-echo-cacheable.mock-http-headers
index fce2271..d41294c 100644
--- a/net/data/url_request_unittest/redirect302-to-echo-cacheable.mock-http-headers
+++ b/net/data/url_request_unittest/redirect302-to-echo-cacheable.mock-http-headers
@@ -2,3 +2,4 @@
 Location: /echo
 Content-Length: 1
 Cache-control: max-age=60000
+Content-Type: example/unit_test
diff --git a/net/disk_cache/simple/simple_synchronous_entry.cc b/net/disk_cache/simple/simple_synchronous_entry.cc
index c8ca20a..2a42726 100644
--- a/net/disk_cache/simple/simple_synchronous_entry.cc
+++ b/net/disk_cache/simple/simple_synchronous_entry.cc
@@ -336,6 +336,7 @@
             GetFilenameFromEntryFileKeyAndFileIndex(entry_file_key_, i));
 #if defined(STARBOARD)
         ok = false;
+        // Note: Files can not be renamed on Starboard.
 #else
         ok = base::ReplaceFile(old_name, new_name, &out_error) && ok;
 #endif
@@ -350,6 +351,7 @@
           path_.AppendASCII(GetSparseFilenameFromEntryFileKey(entry_file_key_));
 #if defined(STARBOARD)
       ok = false;
+      // Note: Files can not be renamed on Starboard.
 #else
       ok = base::ReplaceFile(old_name, new_name, &out_error) && ok;
 #endif
diff --git a/net/disk_cache/simple/simple_util.cc b/net/disk_cache/simple/simple_util.cc
index 5c1f5a7..513de96 100644
--- a/net/disk_cache/simple/simple_util.cc
+++ b/net/disk_cache/simple/simple_util.cc
@@ -63,20 +63,30 @@
 std::string GetFilenameFromEntryFileKeyAndFileIndex(
     const SimpleFileTracker::EntryFileKey& key,
     int file_index) {
+#if defined(STARBOARD)
+  return base::StringPrintf("%016" PRIx64 "_%1d", key.entry_hash, file_index);
+#else
+  // Files are not renamed on Starboard.
   if (key.doom_generation == 0)
     return base::StringPrintf("%016" PRIx64 "_%1d", key.entry_hash, file_index);
   else
     return base::StringPrintf("todelete_%016" PRIx64 "_%1d_%" PRIu64,
                               key.entry_hash, file_index, key.doom_generation);
+#endif
 }
 
 std::string GetSparseFilenameFromEntryFileKey(
     const SimpleFileTracker::EntryFileKey& key) {
+#if defined(STARBOARD)
+  return base::StringPrintf("%016" PRIx64 "_s", key.entry_hash);
+#else
+  // Files are not renamed on Starboard.
   if (key.doom_generation == 0)
     return base::StringPrintf("%016" PRIx64 "_s", key.entry_hash);
   else
     return base::StringPrintf("todelete_%016" PRIx64 "_s_%" PRIu64,
                               key.entry_hash, key.doom_generation);
+#endif
 }
 
 std::string GetFilenameFromKeyAndFileIndex(const std::string& key,
diff --git a/net/http/http_cache_lookup_manager_unittest.cc b/net/http/http_cache_lookup_manager_unittest.cc
index ac7dec9..d3776d7 100644
--- a/net/http/http_cache_lookup_manager_unittest.cc
+++ b/net/http/http_cache_lookup_manager_unittest.cc
@@ -37,7 +37,8 @@
 std::unique_ptr<MockTransaction> CreateMockTransaction(const GURL& url) {
   MockTransaction mock_trans = {
       url.spec().c_str(), "GET", base::Time(), "", LOAD_NORMAL,
-      "HTTP/1.1 200 OK",
+      "HTTP/1.1 200 OK\n"
+      "Content-Type: example/unit_test",
       "Date: Wed, 28 Nov 2007 09:40:09 GMT\n"
       "Last-Modified: Wed, 28 Nov 2007 00:40:09 GMT\n",
       base::Time(), "<html><body>Google Blah Blah</body></html>",
diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc
index 9f13b95..5758e00 100644
--- a/net/http/http_cache_transaction.cc
+++ b/net/http/http_cache_transaction.cc
@@ -68,7 +68,8 @@
 static const char* const kMimeTypesCacheAllowlist[] = {
     "text/html", "text/css",      "image/gif",  "image/jpeg",
     "image/png", "image/svg+xml", "image/webp", "font/otf",
-    "font/ttf",  "font/woff",     "font/woff2", "text/javascript"};
+    "font/ttf",  "font/woff",     "font/woff2", "text/javascript",
+    "example/unit_test", "application/javascript"};
 #endif
 
 constexpr TimeDelta kStaleRevalidateTimeout = TimeDelta::FromSeconds(60);
@@ -3085,14 +3086,11 @@
   // Only allow caching for specific mime types.
   std::string mime_type;
   response_.headers->GetMimeType(&mime_type);
-  // TODO(b/243727663): Empty mime types should not get cached either.
-  bool is_allowed_mime_type = mime_type.empty();
-  if (!is_allowed_mime_type) {
-    for (auto allowed_type : kMimeTypesCacheAllowlist) {
-      if (mime_type.compare(allowed_type) == 0) {
-        is_allowed_mime_type = true;
-        break;
-      }
+  bool is_allowed_mime_type = false;
+  for (auto allowed_type : kMimeTypesCacheAllowlist) {
+    if (mime_type.compare(allowed_type) == 0) {
+      is_allowed_mime_type = true;
+      break;
     }
   }
 #else
diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc
index 0740dc8..0d887ad 100644
--- a/net/http/http_cache_unittest.cc
+++ b/net/http/http_cache_unittest.cc
@@ -369,7 +369,8 @@
     base::Time(),
     "",
     LOAD_VALIDATE_CACHE,
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test\n",
     "Cache-Control: max-age=10000\n",
     base::Time(),
     "<html><body>Google Blah Blah</body></html>",
@@ -437,6 +438,10 @@
 // AddHeadersFromString() (_not_ AddHeaderFromString()).
 #define EXTRA_HEADER EXTRA_HEADER_LINE "\r\n"
 
+// Adds the unit_test MIME type to the EXTRA_HEADER for compatibility
+// with http_cache_transaction that now will require a MIME type.
+#define MIME_TYPE_EXTRA_HEADER "Content-Type: example/unit_test\r\n" EXTRA_HEADER
+
 static const char kExtraHeaderKey[] = "Extra";
 
 // Static.
@@ -477,7 +482,10 @@
       ranges.size() != 1) {
     // This is not a byte range request. We return 200.
     response_status->assign("HTTP/1.1 200 OK");
-    response_headers->assign("Date: Wed, 28 Nov 2007 09:40:09 GMT");
+    // Adds the unit_test MIME type to the response_headers for compatibility
+    // with http_cache_transaction that now will require a MIME type.
+    response_headers->assign("Date: Wed, 28 Nov 2007 09:40:09 GMT\n"
+                             "Content-Type: example/unit_test");
     response_data->assign("Not a range");
     return;
   }
@@ -537,7 +545,8 @@
 const MockTransaction kRangeGET_TransactionOK = {
     "http://www.google.com/range", "GET", base::Time(),
     "Range: bytes = 40-49\r\n" EXTRA_HEADER, LOAD_NORMAL,
-    "HTTP/1.1 206 Partial Content",
+    "HTTP/1.1 206 Partial Content\n"
+    "Content-Type: example/unit_test",
     "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
     "ETag: \"foo\"\n"
     "Accept-Ranges: bytes\n"
@@ -4759,7 +4768,8 @@
   MockHttpCache cache;
 
   ScopedMockTransaction transaction(kETagGET_Transaction);
-  transaction.status = "HTTP/1.0 200 OK";
+  transaction.status = "HTTP/1.0 200 OK\n"
+                       "Content-Type: example/unit_test\n";
 
   // Write to the cache.
   RunTransactionTest(cache.http_cache(), transaction);
@@ -4782,7 +4792,8 @@
   MockHttpCache cache;
 
   ScopedMockTransaction transaction(kETagGET_Transaction);
-  transaction.status = "HTTP/1.0 200 OK";
+  transaction.status = "HTTP/1.0 200 OK\n"
+                       "Content-Type: example/unit_test\n";
 
   // Write to the cache.
   RunTransactionTest(cache.http_cache(), transaction);
@@ -4961,7 +4972,8 @@
 TEST_F(HttpCacheTest, ConditionalizedRequestUpdatesCache1) {
   // First network response for |kUrl|.
   static const Response kNetResponse1 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
     "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
     "body1"
@@ -4969,7 +4981,8 @@
 
   // Second network response for |kUrl|.
   static const Response kNetResponse2 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
     "Last-Modified: Fri, 03 Jul 2009 02:14:27 GMT\n",
     "body2"
@@ -4987,7 +5000,8 @@
 TEST_F(HttpCacheTest, ConditionalizedRequestUpdatesCache2) {
   // First network response for |kUrl|.
   static const Response kNetResponse1 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
     "Etag: \"ETAG1\"\n"
     "Expires: Wed, 7 Sep 2033 21:46:42 GMT\n",  // Should never expire.
@@ -4996,7 +5010,8 @@
 
   // Second network response for |kUrl|.
   static const Response kNetResponse2 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
     "Etag: \"ETAG2\"\n"
     "Expires: Wed, 7 Sep 2033 21:46:42 GMT\n",  // Should never expire.
@@ -5015,7 +5030,8 @@
 TEST_F(HttpCacheTest, ConditionalizedRequestUpdatesCache3) {
   // First network response for |kUrl|.
   static const Response kNetResponse1 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
     "Server: server1\n"
     "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
@@ -5024,7 +5040,8 @@
 
   // Second network response for |kUrl|.
   static const Response kNetResponse2 = {
-    "HTTP/1.1 304 Not Modified",
+    "HTTP/1.1 304 Not Modified\n"
+    "Content-Type: example/unit_test",
     "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
     "Server: server2\n"
     "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
@@ -5035,7 +5052,8 @@
     "HTTP/1.1 200 OK",
     "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
     "Server: server2\n"
-    "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
+    "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n"
+    "Content-Type: example/unit_test\n",
     "body1"
   };
 
@@ -5055,7 +5073,8 @@
   const char kUrl[] = "http://foobar.com/main.css";
 
   static const Response kNetResponse = {
-    "HTTP/1.1 304 Not Modified",
+    "HTTP/1.1 304 Not Modified\n"
+    "Content-Type: example/unit_test",
     "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
     "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
     ""
@@ -5099,7 +5118,8 @@
   const char kUrl[] = "http://foobar.com/main.css";
 
   static const Response kNetResponse = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
     "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
     "foobar!!!"
@@ -5140,7 +5160,8 @@
 // (the if-modified-since date is 2 days AFTER the cache's modification date).
 TEST_F(HttpCacheTest, ConditionalizedRequestUpdatesCache6) {
   static const Response kNetResponse1 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
     "Server: server1\n"
     "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
@@ -5149,7 +5170,8 @@
 
   // Second network response for |kUrl|.
   static const Response kNetResponse2 = {
-    "HTTP/1.1 304 Not Modified",
+    "HTTP/1.1 304 Not Modified\n"
+    "Content-Type: example/unit_test",
     "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
     "Server: server2\n"
     "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
@@ -5170,7 +5192,8 @@
 // response (304) to update the cache.
 TEST_F(HttpCacheTest, ConditionalizedRequestUpdatesCache7) {
   static const Response kNetResponse1 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
     "Etag: \"Foo1\"\n"
     "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
@@ -5179,7 +5202,8 @@
 
   // Second network response for |kUrl|.
   static const Response kNetResponse2 = {
-    "HTTP/1.1 304 Not Modified",
+    "HTTP/1.1 304 Not Modified\n"
+    "Content-Type: example/unit_test",
     "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
     "Etag: \"Foo2\"\n"
     "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
@@ -5197,7 +5221,8 @@
 // and if-modified-since updates the cache.
 TEST_F(HttpCacheTest, ConditionalizedRequestUpdatesCache8) {
   static const Response kNetResponse1 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
     "Etag: \"Foo1\"\n"
     "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
@@ -5206,7 +5231,8 @@
 
   // Second network response for |kUrl|.
   static const Response kNetResponse2 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
     "Etag: \"Foo2\"\n"
     "Last-Modified: Fri, 03 Jul 2009 02:14:27 GMT\n",
@@ -5225,7 +5251,8 @@
 // and if-modified-since does not update the cache with only one match.
 TEST_F(HttpCacheTest, ConditionalizedRequestUpdatesCache9) {
   static const Response kNetResponse1 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
     "Etag: \"Foo1\"\n"
     "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
@@ -5234,7 +5261,8 @@
 
   // Second network response for |kUrl|.
   static const Response kNetResponse2 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
     "Etag: \"Foo2\"\n"
     "Last-Modified: Fri, 03 Jul 2009 02:14:27 GMT\n",
@@ -5254,7 +5282,8 @@
 // and if-modified-since does not update the cache with only one match.
 TEST_F(HttpCacheTest, ConditionalizedRequestUpdatesCache10) {
   static const Response kNetResponse1 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
     "Etag: \"Foo1\"\n"
     "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
@@ -5263,7 +5292,8 @@
 
   // Second network response for |kUrl|.
   static const Response kNetResponse2 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
     "Etag: \"Foo2\"\n"
     "Last-Modified: Fri, 03 Jul 2009 02:14:27 GMT\n",
@@ -5654,7 +5684,9 @@
 
   RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
 
-  EXPECT_EQ("HTTP/1.1 200 OK\nContent-Length: 42\n", headers);
+  EXPECT_EQ("HTTP/1.1 200 OK\n"
+            "Content-Type: example/unit_test\n"
+            "Content-Length: 42\n", headers);
   RemoveMockTransaction(&transaction);
 }
 
@@ -7050,7 +7082,8 @@
 TEST_F(HttpCacheTest, RangeGET_301) {
   MockHttpCache cache;
   ScopedMockTransaction transaction(kRangeGET_TransactionOK);
-  transaction.status = "HTTP/1.1 301 Moved Permanently";
+  transaction.status = "HTTP/1.1 301 Moved Permanently\n"
+                       "Content-Type: example/unit_test";
   transaction.response_headers = "Location: http://www.bar.com/\n";
   transaction.data = "";
   transaction.handler = NULL;
@@ -8394,6 +8427,7 @@
   ScopedMockTransaction transaction(kRangeGET_TransactionOK);
 
   std::string raw_headers("HTTP/1.1 200 OK\n"
+                          "Content-Type: example/unit_test\n"
                           "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
                           "ETag: \"foo\"\n"
                           "Accept-Ranges: bytes\n"
@@ -8411,6 +8445,7 @@
       "HTTP/1.1 200 OK\n"
       "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
       "Accept-Ranges: bytes\n"
+      "Content-Type: example/unit_test\n"
       "ETag: \"foo\"\n"
       "Content-Length: 80\n");
 
@@ -8552,7 +8587,8 @@
   // The server will return 200 instead of a byte range.
   std::string expected_headers(
       "HTTP/1.1 200 OK\n"
-      "Date: Wed, 28 Nov 2007 09:40:09 GMT\n");
+      "Date: Wed, 28 Nov 2007 09:40:09 GMT\n"
+      "Content-Type: example/unit_test\n");
 
   EXPECT_EQ(expected_headers, headers);
   EXPECT_EQ(2, cache.network_layer()->transaction_count());
@@ -8607,6 +8643,7 @@
   AddMockTransaction(&kRangeGET_TransactionOK);
 
   std::string raw_headers("HTTP/1.1 200 OK\n"
+                          "Content-Type: example/unit_test\n"
                           "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
                           "ETag: \"foo\"\n"
                           "Accept-Ranges: bytes\n"
@@ -8699,7 +8736,7 @@
 
   // Now make a regular request.
   std::string headers;
-  transaction.request_headers = EXTRA_HEADER;
+  transaction.request_headers = MIME_TYPE_EXTRA_HEADER;
   transaction.data = "Not a range";
   RangeTransactionServer handler;
   handler.set_bad_200(true);
@@ -8720,6 +8757,7 @@
   ScopedMockTransaction transaction(kRangeGET_TransactionOK);
 
   std::string raw_headers("HTTP/1.1 200 OK\n"
+                          "Content-Type: example/unit_test\n"
                           "Last-Modified: Sat, 18 Apr 2009 01:10:43 GMT\n"
                           "ETag: \"foo\"\n"
                           "Accept-Ranges: bytes\n"
@@ -8842,7 +8880,8 @@
   MockHttpCache cache;
 
   ScopedMockTransaction kTestTransaction(kSimpleGET_Transaction);
-  kTestTransaction.status = "HTTP/1.1 301 Moved Permanently";
+  kTestTransaction.status = "HTTP/1.1 301 Moved Permanently\n"
+                            "Content-Type: example/unit_test";
   kTestTransaction.response_headers = "Location: http://www.bar.com/\n";
 
   MockHttpRequest request(kTestTransaction);
@@ -9138,7 +9177,8 @@
   request.data = kData;
 
   static const Response kNetResponse1 = {
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Fri, 12 Jun 2009 21:46:42 GMT\n"
     "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
     kData
@@ -9154,7 +9194,8 @@
   request.load_flags = LOAD_VALIDATE_CACHE;
 
   static const Response kNetResponse2 = {
-    "HTTP/1.1 304 Not Modified",
+    "HTTP/1.1 304 Not Modified\n"
+    "Content-Type: example/unit_test",
     "Date: Wed, 22 Jul 2009 03:15:26 GMT\n",
     ""
   };
@@ -9178,6 +9219,7 @@
 
   EXPECT_EQ("HTTP/1.1 200 OK\n"
             "Date: Wed, 22 Jul 2009 03:15:26 GMT\n"
+            "Content-Type: example/unit_test\n"
             "Last-Modified: Wed, 06 Feb 2008 22:38:21 GMT\n",
             ToSimpleString(response.headers));
 
@@ -9673,6 +9715,7 @@
   AddMockTransaction(&kRangeGET_TransactionOK);
 
   std::string raw_headers("HTTP/1.1 200 OK\n"
+                          "Content-Type: example/unit_test\n"
                           "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
                           "ETag: \"foo\"\n"
                           "Accept-Ranges: bytes\n"
@@ -10091,6 +10134,7 @@
   AddMockTransaction(&kRangeGET_TransactionOK);
 
   std::string raw_headers("HTTP/1.1 200 OK\n"
+                          "Content-Type: example/unit_test\n"
                           "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
                           "ETag: \"foo\"\n"
                           "Accept-Ranges: bytes\n"
diff --git a/net/http/http_transaction_test_util.cc b/net/http/http_transaction_test_util.cc
index 9b9c92c..8914895 100644
--- a/net/http/http_transaction_test_util.cc
+++ b/net/http/http_transaction_test_util.cc
@@ -48,7 +48,8 @@
     base::Time(),
     "",
     LOAD_NORMAL,
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Cache-Control: max-age=10000\n",
     base::Time(),
     "<html><body>Google Blah Blah</body></html>",
@@ -67,7 +68,8 @@
     base::Time(),
     "",
     LOAD_NORMAL,
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "",
     base::Time(),
     "<html><body>Google Blah Blah</body></html>",
@@ -86,7 +88,8 @@
     base::Time(),
     "",
     LOAD_NORMAL,
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Date: Wed, 28 Nov 2007 09:40:09 GMT\n"
     "Last-Modified: Wed, 28 Nov 2007 00:40:09 GMT\n",
     base::Time(),
@@ -106,7 +109,8 @@
     base::Time(),
     "",
     LOAD_NORMAL,
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Cache-Control: max-age=10000\n"
     "Etag: \"foopy\"\n",
     base::Time(),
@@ -126,7 +130,8 @@
     base::Time(),
     "Range: 0-100\r\n",
     LOAD_NORMAL,
-    "HTTP/1.1 200 OK",
+    "HTTP/1.1 200 OK\n"
+    "Content-Type: example/unit_test",
     "Cache-Control: max-age=10000\n",
     base::Time(),
     "<html><body>Google Blah Blah</body></html>",
diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc
index b4a9ab4..e55c504 100644
--- a/net/url_request/url_request.cc
+++ b/net/url_request/url_request.cc
@@ -1156,12 +1156,6 @@
     raw_header_size_ = GetTotalReceivedBytes();
 
     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_);
-    }
-#endif  // defined(STARBOARD)
   }
 }
 
@@ -1171,6 +1165,13 @@
   if (has_notified_completion_)
     return;
 
+  #if defined (STARBOARD)
+    load_timing_info_.encoded_body_size = static_cast<uint64_t>(GetTotalReceivedBytes());
+    if (load_timing_info_callback_) {
+      load_timing_info_callback_.Run(load_timing_info_);
+    }
+  #endif  // defined(STARBOARD)
+
   is_pending_ = false;
   is_redirecting_ = false;
   has_notified_completion_ = true;
diff --git a/net/url_request/url_request_http_job_unittest.cc b/net/url_request/url_request_http_job_unittest.cc
index fc7b92b..e0f4358 100644
--- a/net/url_request/url_request_http_job_unittest.cc
+++ b/net/url_request/url_request_http_job_unittest.cc
@@ -494,6 +494,7 @@
   {
     MockWrite writes[] = {MockWrite(kSimpleGetMockWrite)};
     MockRead reads[] = {MockRead("HTTP/1.1 200 OK\r\n"
+                                 "Content-Type: example/unit_test\r\n"
                                  "Content-Length: 12\r\n\r\n"),
                         MockRead("Test Content")};
 
diff --git a/starboard/android/apk/app/CMakeLists.txt b/starboard/android/apk/app/CMakeLists.txt
index d5d598f..5b1cb17 100644
--- a/starboard/android/apk/app/CMakeLists.txt
+++ b/starboard/android/apk/app/CMakeLists.txt
@@ -38,6 +38,13 @@
   )
 endif()
 
+# Abort gracefully if the config has not been generated. This should only happen
+# when building from Android Studio which builds all supported ABIs by default.
+if(NOT EXISTS "${COBALT_PRODUCT_DIR}")
+  message(WARNING "android-${COBALT_ARCH}_${COBALT_CONFIG} not configured. Please run GN. Skipping config.")
+  return()
+endif()
+
 # If COBALT_CONTENT_DIR isn't set for a particular deploy target use the
 # 'content/data' directory.
 if(NOT COBALT_CONTENT_DIR)
@@ -47,9 +54,9 @@
 endif()
 
 # If COBALT_LIBRARY_DIR isn't set for a particular deploy target use the
-# toplevel lib/ subdirectory.
+# product root.
 if(NOT COBALT_LIBRARY_DIR)
-  set(COBALT_LIBRARY_DIR ${COBALT_PRODUCT_DIR}/lib)
+  set(COBALT_LIBRARY_DIR ${COBALT_PRODUCT_DIR})
 endif()
 
 # For platform deploy builds, use the -n parameter to skip Cobalt ninja and
@@ -76,9 +83,11 @@
 # ("cobalt_content" never gets created, so this runs every time.)
 add_custom_command(OUTPUT cobalt_content
     DEPENDS coat_lib
+    COMMAND ${CMAKE_COMMAND} -E make_directory
+            ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../../../../${COBALT_CONFIG}
     COMMAND ${CMAKE_COMMAND} -E create_symlink
             ${COBALT_CONTENT_DIR}
-            ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../../../../cobalt_content
+            ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/../../../../${COBALT_CONFIG}/cobalt_content
 )
 
 # We need a target (not a file) for the phony native dependency below.
diff --git a/starboard/android/apk/app/build.gradle b/starboard/android/apk/app/build.gradle
index 2712f25..c78527f 100644
--- a/starboard/android/apk/app/build.gradle
+++ b/starboard/android/apk/app/build.gradle
@@ -140,16 +140,16 @@
         }
         // Add the directories symlinked by the CMake "cobalt_content" custom command.
         debug {
-            assets.srcDir "${buildDir}/intermediates/cxx/cobalt_content"
+            assets.srcDir "${buildDir}/intermediates/cxx/debug/cobalt_content"
         }
         devel {
-            assets.srcDir "${buildDir}/intermediates/cxx/cobalt_content"
+            assets.srcDir "${buildDir}/intermediates/cxx/devel/cobalt_content"
         }
         qa {
-            assets.srcDir "${buildDir}/intermediates/cxx/cobalt_content"
+            assets.srcDir "${buildDir}/intermediates/cxx/qa/cobalt_content"
         }
         release {
-            assets.srcDir "${buildDir}/intermediates/cxx/cobalt_content"
+            assets.srcDir "${buildDir}/intermediates/cxx/gold/cobalt_content"
         }
     }
     externalNativeBuild {
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
index eeed7a7..1478b37 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -781,6 +781,10 @@
       Log.e(TAG, "Failed to set operating rate with invalid fps " + mFps);
       return;
     }
+    if (mMediaCodec == null) {
+      Log.e(TAG, "Failed to set operating rate when media codec is null.");
+      return;
+    }
     double operatingRate = mPlaybackRate * mFps;
     Bundle b = new Bundle();
     b.putFloat(MediaFormat.KEY_OPERATING_RATE, (float) operatingRate);
diff --git a/starboard/android/apk/build.gradle b/starboard/android/apk/build.gradle
index 3db6ad6..1247002 100644
--- a/starboard/android/apk/build.gradle
+++ b/starboard/android/apk/build.gradle
@@ -42,17 +42,16 @@
     }
 }
 
-task clean(type: Delete) {
-    delete rootProject.buildDir
-}
-
 // Move the 'buildDir' for all projects into sub-directories of a shared top-level build directory,
 // which is either the root's original 'buildDir' or a custom location when building for platform
 // deploy.  Note that the platform deploy action sets a custom 'cobaltGradleDir' property rather
 // than setting 'buildDir' directly on the command line since Gradle tries to get smart about
 // 'buildDir' which can end up putting it at the wrong depth in the file system.
-def rootBuildDir = hasProperty('cobaltGradleDir') ? new File(cobaltGradleDir, 'build') : buildDir
-allprojects { buildDir = new File(rootBuildDir, project.name).canonicalFile }
+allprojects { buildDir = new File(gradle.ext.rootBuildDir, project.name).canonicalFile }
+
+task clean(type: Delete) {
+    delete gradle.ext.rootBuildDir
+}
 
 // Android Studio Gradle plugin 3.5+ builds all supported ABIs for a connected device. Override the
 // property it sets to build just the first one, which is the preferred ABI for the device.
diff --git a/starboard/android/apk/cobalt-gradle.sh b/starboard/android/apk/cobalt-gradle.sh
deleted file mode 100755
index a5b430f..0000000
--- a/starboard/android/apk/cobalt-gradle.sh
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/bin/bash
-# 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.
-
-# Helper to set ANDROID_HOME and ANDROID_NDK_HOME from command-line args
-# before running gradlew, as specified by leading --sdk and --ndk args.
-#
-# Also resets hung gradle builds when specified by a leading --reset arg.
-
-GRADLE_ARGS=()
-while [ "$1" ]; do
-  case "$1" in
-    --sdk) shift; ANDROID_HOME="$1" ;;
-    --cache) shift; mkdir -p "$1";
-             GRADLE_ARGS+=("--project-cache-dir" $(cd "$1"; pwd)) ;;
-    --reset) RESET_GRADLE=1 ;;
-    *) break ;;
-  esac
-  shift
-done
-GRADLE_ARGS+=("$@")
-
-# Cleanup Gradle from previous builds. Used as part of the GYP step.
-if [[ "${RESET_GRADLE}" ]]; then
-  echo "Cleaning Gradle daemons and locks."
-  # If there are any lock files, kill any hung processes still waiting on them.
-  if compgen -G '/var/lock/cobalt-gradle.lock.*'; then
-    lsof -t /var/lock/cobalt-gradle.lock.* | xargs -rt kill
-  fi
-  # Stop the Gradle daemon (if still running).
-  $(dirname "$0")/gradlew --stop
-  # Remove Gradle caches (including its lock files).
-  rm -rf ${HOME}/.gradle/caches
-  # Show the gradle version, which will cause it to download if needed.
-  $(dirname "$0")/gradlew -v
-  # After resetting, exit without running any gradle tasks.
-  exit
-fi
-
-export ANDROID_HOME
-echo "ANDROID_HOME=${ANDROID_HOME}"
-
-# Allow parallel gradle builds, as defined by a COBALT_GRADLE_BUILD_COUNT envvar
-# or default to 1 if that's not set (so buildbot only runs 1 gradle at a time).
-BUCKETS=${COBALT_GRADLE_BUILD_COUNT:-1}
-if [ "$BUCKETS" -eq 1 ]; then
-  echo "Gradle daemon and parallel gradle disabled for Cobalt build"
-  GRADLE_ARGS+=(
-    "-Dorg.gradle.parallel=false"
-    "-Dorg.gradle.workers.max=1"
-    "-Dorg.gradle.daemon=false"
-  )
-fi
-
-MD5=$(echo "${GRADLE_ARGS[@]}" | md5sum)
-LOCKNUM=$(( ${BUCKETS} * 0x${MD5:0:6} / 0x1000000 ))
-
-echo "TASK: ${GRADLE_ARGS[-1]}"
-flock /var/lock/cobalt-gradle.lock.${LOCKNUM} $(dirname "$0")/gradlew "${GRADLE_ARGS[@]}"
diff --git a/starboard/android/apk/settings.gradle b/starboard/android/apk/settings.gradle
index 4fe2070..3b60efd 100644
--- a/starboard/android/apk/settings.gradle
+++ b/starboard/android/apk/settings.gradle
@@ -15,3 +15,7 @@
 include ':app'
 
 println "GRADLE VERSION: $gradle.gradleVersion"
+
+gradle.ext.rootBuildDir = hasProperty('cobaltGradleDir')
+    ? new File(cobaltGradleDir, 'build')
+    : new File(rootDir, "/../../../out/android_studio/gradle/build").canonicalFile
diff --git a/starboard/android/shared/install_target.gni b/starboard/android/shared/install_target.gni
index 0a635e1..6eae3d3 100644
--- a/starboard/android/shared/install_target.gni
+++ b/starboard/android/shared/install_target.gni
@@ -16,6 +16,16 @@
 import("//starboard/android/shared/toolchain/toolchain.gni")
 import("//starboard/build/config/install.gni")
 
+declare_args() {
+  # Must be set to a positive integer. Needs to be a string due to us not being
+  # able to cast to an integer in GN.
+  num_gradle_workers = getenv("COBALT_GRADLE_BUILD_COUNT")
+}
+
+if (num_gradle_workers == "") {
+  num_gradle_workers = "1"
+}
+
 template("install_target") {
   not_needed(invoker, [ "type" ])
 
@@ -36,10 +46,10 @@
       gradle_build_type = build_type
     }
 
-    bash_script = "//starboard/android/apk/cobalt-gradle.sh"
+    gradle_wrapper = "//starboard/android/apk/gradlew"
 
     sources = [
-      bash_script,
+      gradle_wrapper,
       target_output,
     ]
     sources += apk_sources
@@ -52,14 +62,16 @@
     cobalt_gradle_dir = rebase_path(gradle_files_dir)
     cobalt_product_dir = rebase_path(root_out_dir)
     cobalt_library_dir = rebase_path(root_out_dir)
+    project_cache_dir =
+        rebase_path("$root_build_dir/gradle/$installable_target_name/cache")
+    use_parallel_build = num_gradle_workers != "1"
 
     script = "//starboard/build/run_bash.py"
     args = [
-      rebase_path(bash_script, root_build_dir),
-      "--sdk",
-      android_sdk_path,
-      "--cache",
-      rebase_path("$root_build_dir/gradle/$installable_target_name/cache"),
+      "env",
+      "ANDROID_HOME=${android_sdk_path}",
+      rebase_path(gradle_wrapper, root_build_dir),
+      "--project-cache-dir=$project_cache_dir",
       "--project-dir",
       cobalt_project_dir,
       "-P",
@@ -79,6 +91,9 @@
       "-P",
       "enableVulkan=$enable_vulkan",
       "assembleCobalt_$gradle_build_type",
+      "-Dorg.gradle.workers.max=$num_gradle_workers",
+      "-Dorg.gradle.workers.parallel=$use_parallel_build",
+      "-Dorg.gradle.workers.daemon=$use_parallel_build",
     ]
   }
 
diff --git a/starboard/build/install/no_install.gni b/starboard/build/install/no_install.gni
index f2ca601..8d8c448 100644
--- a/starboard/build/install/no_install.gni
+++ b/starboard/build/install/no_install.gni
@@ -19,6 +19,7 @@
              [
                "testonly",
                "installable_target_name",
+               "deps",
              ])
   forward_variables_from(invoker,
                          [
@@ -27,5 +28,8 @@
                          ])
   group(target_name) {
     deps = [ ":$installable_target_name" ]
+    if (defined(invoker.deps)) {
+      deps += invoker.deps
+    }
   }
 }
diff --git a/starboard/elf_loader/sandbox.cc b/starboard/elf_loader/sandbox.cc
index 019309f..2f2eb05 100644
--- a/starboard/elf_loader/sandbox.cc
+++ b/starboard/elf_loader/sandbox.cc
@@ -79,13 +79,16 @@
   if (!get_user_agent_func) {
     SB_LOG(ERROR) << "Failed to get user agent string";
   } else {
-    CrashpadAnnotations cobalt_version_info;
-    memset(&cobalt_version_info, 0, sizeof(CrashpadAnnotations));
-    starboard::strlcpy(cobalt_version_info.user_agent_string,
-                       get_user_agent_func(), USER_AGENT_STRING_MAX_SIZE);
-    third_party::crashpad::wrapper::AddAnnotationsToCrashpad(
-        cobalt_version_info);
-    SB_DLOG(INFO) << "Added user agent string to Crashpad.";
+    std::vector<char> buffer(USER_AGENT_STRING_MAX_SIZE);
+    starboard::strlcpy(buffer.data(), get_user_agent_func(),
+                       USER_AGENT_STRING_MAX_SIZE);
+    if (third_party::crashpad::wrapper::InsertCrashpadAnnotation(
+            third_party::crashpad::wrapper::kCrashpadUserAgentStringKey,
+            buffer.data())) {
+      SB_DLOG(INFO) << "Added user agent string to Crashpad.";
+    } else {
+      SB_DLOG(INFO) << "Failed to add user agent string to Crashpad.";
+    }
   }
 
   if (!g_sb_event_func) {
diff --git a/starboard/loader_app/loader_app.cc b/starboard/loader_app/loader_app.cc
index 8bae605..fcb2975 100644
--- a/starboard/loader_app/loader_app.cc
+++ b/starboard/loader_app/loader_app.cc
@@ -160,13 +160,16 @@
   if (!get_user_agent_func) {
     SB_LOG(ERROR) << "Failed to get user agent string";
   } else {
-    CrashpadAnnotations cobalt_version_info;
-    memset(&cobalt_version_info, 0, sizeof(CrashpadAnnotations));
-    starboard::strlcpy(cobalt_version_info.user_agent_string,
-                       get_user_agent_func(), USER_AGENT_STRING_MAX_SIZE);
-    third_party::crashpad::wrapper::AddAnnotationsToCrashpad(
-        cobalt_version_info);
-    SB_DLOG(INFO) << "Added user agent string to Crashpad.";
+    std::vector<char> buffer(USER_AGENT_STRING_MAX_SIZE);
+    starboard::strlcpy(buffer.data(), get_user_agent_func(),
+                       USER_AGENT_STRING_MAX_SIZE);
+    if (third_party::crashpad::wrapper::InsertCrashpadAnnotation(
+            third_party::crashpad::wrapper::kCrashpadUserAgentStringKey,
+            buffer.data())) {
+      SB_DLOG(INFO) << "Added user agent string to Crashpad.";
+    } else {
+      SB_DLOG(INFO) << "Failed to add user agent string to Crashpad.";
+    }
   }
 
   g_sb_event_func = reinterpret_cast<void (*)(const SbEvent*)>(
diff --git a/starboard/loader_app/slot_management.cc b/starboard/loader_app/slot_management.cc
index 9f76b11..fc5549a 100644
--- a/starboard/loader_app/slot_management.cc
+++ b/starboard/loader_app/slot_management.cc
@@ -28,6 +28,7 @@
 #include "starboard/loader_app/installation_manager.h"
 #include "starboard/memory.h"
 #include "starboard/string.h"
+#include "third_party/crashpad/wrapper/annotations.h"
 #include "third_party/crashpad/wrapper/wrapper.h"
 
 namespace starboard {
@@ -315,13 +316,16 @@
     if (!get_user_agent_func) {
       SB_LOG(ERROR) << "Failed to get user agent string";
     } else {
-      CrashpadAnnotations cobalt_version_info;
-      memset(&cobalt_version_info, 0, sizeof(CrashpadAnnotations));
-      starboard::strlcpy(cobalt_version_info.user_agent_string,
-                         get_user_agent_func(), USER_AGENT_STRING_MAX_SIZE);
-      third_party::crashpad::wrapper::AddAnnotationsToCrashpad(
-          cobalt_version_info);
-      SB_DLOG(INFO) << "Added user agent string to Crashpad.";
+      std::vector<char> buffer(USER_AGENT_STRING_MAX_SIZE);
+      starboard::strlcpy(buffer.data(), get_user_agent_func(),
+                         USER_AGENT_STRING_MAX_SIZE);
+      if (third_party::crashpad::wrapper::InsertCrashpadAnnotation(
+              third_party::crashpad::wrapper::kCrashpadUserAgentStringKey,
+              buffer.data())) {
+        SB_DLOG(INFO) << "Added user agent string to Crashpad.";
+      } else {
+        SB_DLOG(INFO) << "Failed to add user agent string to Crashpad.";
+      }
     }
 
     SB_DLOG(INFO) << "Successfully loaded Cobalt!\n";
diff --git a/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc
index 0e6cf9a..ef15ed2 100644
--- a/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc
+++ b/starboard/shared/ffmpeg/ffmpeg_demuxer_impl.cc
@@ -247,6 +247,65 @@
   }
 }
 
+// Attempts to parse a codec profile from |extradata|. Upon success,
+// |out_profile| is populated with the profile and true is returned. Upon
+// failure, false is returned and |out_profile| is not modified.
+bool TryParseH264Profile(const uint8_t* extradata,
+                         size_t extradata_size,
+                         CobaltExtensionDemuxerVideoCodecProfile& out_profile) {
+  if (extradata_size < 2) {
+    return false;
+  }
+  const uint8_t version = extradata[0];
+  if (version != 1) {
+    return false;
+  }
+  const int profile = extradata[1];
+  switch (profile) {
+    case FF_PROFILE_H264_BASELINE:
+      out_profile = kCobaltExtensionDemuxerH264ProfileBaseline;
+      return true;
+    case FF_PROFILE_H264_MAIN:
+      out_profile = kCobaltExtensionDemuxerH264ProfileMain;
+      return true;
+    case FF_PROFILE_H264_EXTENDED:
+      out_profile = kCobaltExtensionDemuxerH264ProfileExtended;
+      return true;
+    case FF_PROFILE_H264_HIGH:
+      out_profile = kCobaltExtensionDemuxerH264ProfileHigh;
+      return true;
+    case FF_PROFILE_H264_HIGH_10:
+      out_profile = kCobaltExtensionDemuxerH264ProfileHigh10Profile;
+      return true;
+    case FF_PROFILE_H264_HIGH_422:
+      out_profile = kCobaltExtensionDemuxerH264ProfileHigh422Profile;
+      return true;
+    case FF_PROFILE_H264_HIGH_444_PREDICTIVE:
+      out_profile = kCobaltExtensionDemuxerH264ProfileHigh444PredictiveProfile;
+      return true;
+    default:
+      SB_LOG(ERROR) << "Unknown H264 profile: " << profile;
+      return false;
+  }
+}
+
+// Attempts to parse a codec profile from |extradata|. Upon success,
+// |out_profile| is populated with the profile and true is returned. Upon
+// failure, false is returned and |out_profile| is not modified.
+bool TryParseH265Profile(const uint8_t* extradata,
+                         size_t extradata_size,
+                         int& out_profile) {
+  if (extradata_size < 2) {
+    return false;
+  }
+  const uint8_t version = extradata[0];
+  if (version != 1) {
+    return false;
+  }
+  out_profile = extradata[1] & 0x1F;
+  return true;
+}
+
 int AVIOReadOperation(void* opaque, uint8_t* buf, int buf_size) {
   auto* data_source = static_cast<CobaltExtensionDemuxerDataSource*>(opaque);
   const int bytes_read =
@@ -898,8 +957,15 @@
       config->profile = ProfileIDToVideoCodecProfile(codec_context->profile);
       if (config->profile == kCobaltExtensionDemuxerVideoCodecProfileUnknown &&
           codec_context->extradata && codec_context->extradata_size) {
-        // TODO(b/231631898): handle the extra data here, if necessary.
-        SB_LOG(ERROR) << "Extra data is not currently handled.";
+        CobaltExtensionDemuxerVideoCodecProfile profile =
+            kCobaltExtensionDemuxerVideoCodecProfileUnknown;
+        // Attempt to populate profile based on extradata.
+        if (TryParseH264Profile(codec_context->extradata,
+                                codec_context->extradata_size, profile)) {
+          config->profile = profile;
+        } else {
+          SB_LOG(ERROR) << "Could not parse H264 profile from extradata.";
+        }
       }
       break;
     }
@@ -915,8 +981,11 @@
 #endif  // FF_PROFILE_HEVC_REXT
            ) &&
           codec_context->extradata && codec_context->extradata_size) {
-        // TODO(b/231631898): handle the extra data here, if necessary.
-        SB_LOG(ERROR) << "Extra data is not currently handled.";
+        // Attempt to populate hevc_profile based on extradata.
+        if (!TryParseH265Profile(codec_context->extradata,
+                                 codec_context->extradata_size, hevc_profile)) {
+          SB_LOG(ERROR) << "Could not parse H265 profile from extradata.";
+        }
       } else {
         hevc_profile = codec_context->profile;
       }
diff --git a/starboard/shared/starboard/crash_handler.cc b/starboard/shared/starboard/crash_handler.cc
index 572a022..0d6ec2d 100644
--- a/starboard/shared/starboard/crash_handler.cc
+++ b/starboard/shared/starboard/crash_handler.cc
@@ -25,14 +25,16 @@
 namespace {
 
 bool OverrideCrashpadAnnotations(CrashpadAnnotations* crashpad_annotations) {
-  CrashpadAnnotations annotations;
-  memset(&annotations, 0, sizeof(CrashpadAnnotations));
-  memcpy(&annotations, crashpad_annotations, sizeof(CrashpadAnnotations));
-  return third_party::crashpad::wrapper::AddAnnotationsToCrashpad(annotations);
+  return false;  // Deprecated
+}
+
+bool SetString(const char* key, const char* value) {
+  return third_party::crashpad::wrapper::InsertCrashpadAnnotation(key, value);
 }
 
 const CobaltExtensionCrashHandlerApi kCrashHandlerApi = {
-    kCobaltExtensionCrashHandlerName, 1, &OverrideCrashpadAnnotations,
+    kCobaltExtensionCrashHandlerName, 2, &OverrideCrashpadAnnotations,
+    &SetString,
 };
 
 }  // namespace
diff --git a/starboard/shared/starboard/player/filter/video_renderer_internal_impl.cc b/starboard/shared/starboard/player/filter/video_renderer_internal_impl.cc
index b07a4fe..0e11da1 100644
--- a/starboard/shared/starboard/player/filter/video_renderer_internal_impl.cc
+++ b/starboard/shared/starboard/player/filter/video_renderer_internal_impl.cc
@@ -122,7 +122,6 @@
 #if SB_PLAYER_FILTER_ENABLE_STATE_CHECK
     first_input_written_at_ = SbTimeGetMonotonicNow();
 #endif  // SB_PLAYER_FILTER_ENABLE_STATE_CHECK
-    absolute_time_of_first_input_ = SbTimeGetMonotonicNow();
   }
 
   SB_DCHECK(need_more_input_.load());
diff --git a/starboard/shared/starboard/player/filter/video_renderer_internal_impl.h b/starboard/shared/starboard/player/filter/video_renderer_internal_impl.h
index ee8bad0..a2fd2e4 100644
--- a/starboard/shared/starboard/player/filter/video_renderer_internal_impl.h
+++ b/starboard/shared/starboard/player/filter/video_renderer_internal_impl.h
@@ -92,7 +92,6 @@
   PrerolledCB prerolled_cb_;
   EndedCB ended_cb_;
 
-  SbTimeMonotonic absolute_time_of_first_input_ = 0;
   // Our owner will attempt to seek to time 0 when playback begins.  In
   // general, seeking could require a full reset of the underlying decoder on
   // some platforms, so we make an effort to improve playback startup
diff --git a/starboard/shared/win32/audio_decoder.cc b/starboard/shared/win32/audio_decoder.cc
index d404441..0b54739 100644
--- a/starboard/shared/win32/audio_decoder.cc
+++ b/starboard/shared/win32/audio_decoder.cc
@@ -61,9 +61,19 @@
     : audio_codec_(audio_codec),
       audio_sample_info_(audio_sample_info),
       drm_system_(drm_system),
-      sample_type_(kSbMediaAudioSampleTypeFloat32),
+      sample_type_((audio_codec == kSbMediaAudioCodecAc3 ||
+                    audio_codec == kSbMediaAudioCodecEac3)
+                       ?
+#if SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
+                       kSbMediaAudioSampleTypeInt16
+#else
+                       kSbMediaAudioSampleTypeInt16Deprecated
+#endif  // SB_HAS_QUIRK(SUPPORT_INT16_AUDIO_SAMPLES)
+                       : kSbMediaAudioSampleTypeFloat32),
       stream_ended_(false) {
-  SB_DCHECK(audio_codec == kSbMediaAudioCodecAac);
+  SB_DCHECK(audio_codec == kSbMediaAudioCodecAac ||
+            audio_codec == kSbMediaAudioCodecAc3 ||
+            audio_codec == kSbMediaAudioCodecEac3);
 }
 
 AudioDecoder::~AudioDecoder() {
diff --git a/starboard/shared/win32/audio_transform.cc b/starboard/shared/win32/audio_transform.cc
index 5a03f94..81aebd0 100644
--- a/starboard/shared/win32/audio_transform.cc
+++ b/starboard/shared/win32/audio_transform.cc
@@ -14,9 +14,12 @@
 
 #include "starboard/shared/win32/audio_transform.h"
 
+#include <mfapi.h>
+
 #include <vector>
 
 #include "starboard/memory.h"
+#include "starboard/shared/uwp/wasapi_include.h"
 #include "starboard/shared/win32/error_utils.h"
 #include "starboard/shared/win32/media_common.h"
 #include "starboard/shared/win32/media_foundation_utils.h"
@@ -40,6 +43,12 @@
     case kSbMediaAudioCodecOpus: {
       return MFAudioFormat_Opus;
     }
+    case kSbMediaAudioCodecAc3: {
+      return MFAudioFormat_Dolby_AC3;
+    }
+    case kSbMediaAudioCodecEac3: {
+      return MFAudioFormat_Dolby_DDPlus;
+    }
     default: {
       SB_NOTIMPLEMENTED();
       return MFAudioFormat_PCM;
@@ -47,9 +56,55 @@
   }
 }
 
+GUID ConvertToWin32TransformType(SbMediaAudioCodec codec) {
+  switch (codec) {
+    case kSbMediaAudioCodecAac: {
+      return CLSID_MSAACDecMFT;
+    }
+    case kSbMediaAudioCodecAc3:
+    case kSbMediaAudioCodecEac3: {
+      return CLSID_MSDDPlusDecMFT;
+    }
+    default: {
+      SB_NOTIMPLEMENTED();
+      return MFAudioFormat_Float;
+    }
+  }
+}
+
+GUID ConvertToWin32OutputFormat(SbMediaAudioCodec codec) {
+  switch (codec) {
+    case kSbMediaAudioCodecAac:
+    case kSbMediaAudioCodecOpus:
+    case kSbMediaAudioCodecNone: {
+      return MFAudioFormat_Float;
+    }
+    case kSbMediaAudioCodecAc3: {
+      return MFAudioFormat_Dolby_AC3_SPDIF;
+    }
+    case kSbMediaAudioCodecEac3: {
+      return KSDATAFORMAT_SUBTYPE_IEC61937_DOLBY_DIGITAL_PLUS;
+    }
+    default: {
+      SB_NOTIMPLEMENTED();
+      return MFAudioFormat_Float;
+    }
+  }
+}
+
 class WinAudioFormat {
  public:
   explicit WinAudioFormat(const SbMediaAudioSampleInfo& audio_sample_info) {
+    if (audio_sample_info.codec == kSbMediaAudioCodecAac) {
+      CreateAacAudioFormat(audio_sample_info);
+    } else {
+      SB_DCHECK(audio_sample_info.codec == kSbMediaAudioCodecAc3 ||
+                audio_sample_info.codec == kSbMediaAudioCodecEac3);
+      CreateAc3AudioFormat(audio_sample_info);
+    }
+  }
+
+  void CreateAacAudioFormat(const SbMediaAudioSampleInfo& audio_sample_info) {
     // The HEAACWAVEFORMAT structure has many specializations with varying data
     // appended at the end.
     // The "-1" is used to account for pbAudioSpecificConfig[1] at the end of
@@ -78,11 +133,32 @@
 
     if (audio_sample_info.audio_specific_config_size > 0) {
       memcpy(wave_format->pbAudioSpecificConfig,
-                   audio_sample_info.audio_specific_config,
-                   audio_sample_info.audio_specific_config_size);
+             audio_sample_info.audio_specific_config,
+             audio_sample_info.audio_specific_config_size);
     }
   }
 
+  void CreateAc3AudioFormat(const SbMediaAudioSampleInfo& audio_sample_info) {
+    format_buffer_.resize(sizeof(WAVEFORMATEXTENSIBLE));
+    WAVEFORMATEXTENSIBLE* wave_format =
+        reinterpret_cast<WAVEFORMATEXTENSIBLE*>(format_buffer_.data());
+
+    wave_format->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+    wave_format->Format.nChannels = audio_sample_info.number_of_channels;
+    wave_format->Format.wBitsPerSample = audio_sample_info.bits_per_sample;
+    wave_format->Format.nSamplesPerSec = audio_sample_info.samples_per_second;
+    wave_format->Format.nBlockAlign = audio_sample_info.block_alignment;
+    wave_format->Format.nAvgBytesPerSec = 0;
+    wave_format->Format.cbSize =
+        sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+    wave_format->Samples.wValidBitsPerSample =
+        wave_format->Format.wBitsPerSample;
+    wave_format->dwChannelMask = audio_sample_info.number_of_channels > 2
+                                     ? KSAUDIO_SPEAKER_5POINT1
+                                     : KSAUDIO_SPEAKER_STEREO;
+    wave_format->SubFormat = ConvertToWin32AudioCodec(audio_sample_info.codec);
+  }
+
   WAVEFORMATEX* WaveFormatData() {
     return reinterpret_cast<WAVEFORMATEX*>(format_buffer_.data());
   }
@@ -97,8 +173,12 @@
 scoped_ptr<MediaTransform> CreateAudioTransform(
     const SbMediaAudioSampleInfo& audio,
     SbMediaAudioCodec codec) {
+  SB_DCHECK(codec == kSbMediaAudioCodecAac || codec == kSbMediaAudioCodecAc3 ||
+            codec == kSbMediaAudioCodecEac3);
   ComPtr<IMFTransform> transform;
-  HRESULT hr = CreateDecoderTransform(CLSID_MSAACDecMFT, &transform);
+  HRESULT hr =
+      CreateDecoderTransform(ConvertToWin32TransformType(codec), &transform);
+
   CheckResult(hr);
 
   ComPtr<IMFMediaType> input_type;
@@ -123,7 +203,7 @@
 
   scoped_ptr<MediaTransform> output(new MediaTransform(transform));
   output->SetInputType(selected);
-  output->SetOutputTypeBySubType(MFAudioFormat_Float);
+  output->SetOutputTypeBySubType(ConvertToWin32OutputFormat(codec));
 
   return output.Pass();
 }
diff --git a/starboard/shared/win32/win32_audio_decoder.cc b/starboard/shared/win32/win32_audio_decoder.cc
index df49374..fdfb0c0 100644
--- a/starboard/shared/win32/win32_audio_decoder.cc
+++ b/starboard/shared/win32/win32_audio_decoder.cc
@@ -19,6 +19,7 @@
 
 #include "starboard/atomic.h"
 #include "starboard/shared/starboard/thread_checker.h"
+#include "starboard/shared/uwp/wasapi_include.h"
 #include "starboard/shared/win32/atomic_queue.h"
 #include "starboard/shared/win32/audio_decoder.h"
 #include "starboard/shared/win32/audio_transform.h"
@@ -35,10 +36,23 @@
 using ::starboard::shared::win32::CreateAudioTransform;
 
 const size_t kAacSamplesPerFrame = 1024;
-// We are using float samples on Xb1.
-const size_t kBytesPerSample = sizeof(float);
+// We are using float samples for AAC on Xb1.
+const size_t kAacBytesPerSample = sizeof(float);
 
 namespace {
+size_t GetExpectedBufferSize(SbMediaAudioCodec codec, int num_channels) {
+  switch (codec) {
+    case kSbMediaAudioCodecAac:
+      return num_channels * kAacSamplesPerFrame * kAacBytesPerSample;
+    case kSbMediaAudioCodecAc3:
+      return kAc3BufferSize;
+    case kSbMediaAudioCodecEac3:
+      return kEac3BufferSize;
+    default:
+      SB_NOTREACHED();
+      return size_t(0);
+  }
+}
 
 class AbstractWin32AudioDecoderImpl : public AbstractWin32AudioDecoder {
  public:
@@ -53,12 +67,27 @@
         sample_type_(sample_type),
         number_of_channels_(audio_sample_info.number_of_channels),
         heaac_detected_(false),
-        samples_per_second_(
-            static_cast<int>(audio_sample_info.samples_per_second)) {
+        expected_buffer_size_(
+            GetExpectedBufferSize(codec,
+                                  audio_sample_info.number_of_channels)) {
     scoped_ptr<MediaTransform> audio_decoder =
         CreateAudioTransform(audio_sample_info, codec_);
     impl_.reset(
         new DecryptingDecoder("audio", audio_decoder.Pass(), drm_system));
+    switch (codec) {
+      case kSbMediaAudioCodecAc3:
+        samples_per_second_ = kAc3SamplesPerSecond;
+        number_of_channels_ = kIec60958Channels;
+        break;
+      case kSbMediaAudioCodecEac3:
+        samples_per_second_ = kEac3SamplesPerSecond;
+        number_of_channels_ = kIec60958Channels;
+        break;
+      default:
+        samples_per_second_ =
+            static_cast<int>(audio_sample_info.samples_per_second);
+        number_of_channels_ = audio_sample_info.number_of_channels;
+    }
   }
 
   void Consume(ComPtr<IMFSample> sample) {
@@ -86,13 +115,18 @@
     const uint8_t* data = reinterpret_cast<uint8_t*>(buffer);
     const size_t data_size = static_cast<size_t>(length);
 
-    const size_t expect_size_in_bytes =
-        number_of_channels_ * kAacSamplesPerFrame * kBytesPerSample;
-    if (data_size / expect_size_in_bytes == 2) {
+    if (codec_ == kSbMediaAudioCodecAac &&
+        (data_size / expected_buffer_size_ == 2)) {
       heaac_detected_.store(true);
     }
 
-    const size_t decoded_data_size = std::max(expect_size_in_bytes, data_size);
+    const size_t decoded_data_size = std::max(expected_buffer_size_, data_size);
+
+    if (codec_ == kSbMediaAudioCodecAc3) {
+      SB_DCHECK(decoded_data_size == kAc3BufferSize);
+    } else if (codec_ == kSbMediaAudioCodecEac3) {
+      SB_DCHECK(decoded_data_size == kEac3BufferSize);
+    }
 
     DecodedAudioPtr data_ptr(
         new DecodedAudio(number_of_channels_, sample_type_, audio_frame_fmt_,
@@ -109,12 +143,14 @@
   bool TryWrite(const scoped_refptr<InputBuffer>& buff) override {
     SB_DCHECK(thread_checker_.CalledOnValidThread());
 
-    // The incoming audio is in ADTS format which has a 7 bytes header.  But
-    // the audio decoder is configured to accept raw AAC.  So we have to adjust
-    // the data, size, and subsample mapping to skip the ADTS header.
-    const int kADTSHeaderSize = 7;
-    const bool write_ok = impl_->TryWriteInputBuffer(buff, kADTSHeaderSize);
-    return write_ok;
+    if (codec_ == kSbMediaAudioCodecAac) {
+      // The incoming audio is in ADTS format which has a 7 bytes header.  But
+      // the audio decoder is configured to accept raw AAC.  So we have to
+      // adjust the data, size, and subsample mapping to skip the ADTS header.
+      const int kADTSHeaderSize = 7;
+      return impl_->TryWriteInputBuffer(buff, kADTSHeaderSize);
+    }
+    return impl_->TryWriteInputBuffer(buff, 0);
   }
 
   void WriteEndOfStream() override {
@@ -182,6 +218,7 @@
   uint16_t number_of_channels_;
   atomic_bool heaac_detected_;
   int samples_per_second_;
+  const size_t expected_buffer_size_;
 };
 
 }  // anonymous namespace.
diff --git a/starboard/tools/testing/sharding_configuration.json b/starboard/tools/testing/sharding_configuration.json
index 7dbbc2b..0641b05 100644
--- a/starboard/tools/testing/sharding_configuration.json
+++ b/starboard/tools/testing/sharding_configuration.json
@@ -56,5 +56,94 @@
       "net_unittests": [2, 2],
       "player_filter_tests": [2, 2]
     }
+  ],
+  "raspi-2":[
+    {
+      "base_unittests": [1, 6],
+      "layout_tests": [5, 6],
+      "net_unittests": [2, 6],
+      "player_filter_tests": [1, 6],
+      "renderer_test": [6, 6]
+    },
+    {
+      "base_unittests": [2, 6],
+      "layout_tests": [4, 6],
+      "net_unittests": [1, 6],
+      "nplb": [1, 5],
+      "player_filter_tests": [5, 6],
+      "renderer_test": [5, 6]
+    },
+    {
+      "base_unittests": [3, 6],
+      "layout_tests": [6, 6],
+      "net_unittests": [4, 6],
+      "nplb": [4, 5],
+      "player_filter_tests": [3, 6],
+      "renderer_test": [1, 6]
+    },
+    {
+      "base_unittests": [5, 6],
+      "layout_tests": [3, 6],
+      "net_unittests": [5, 6],
+      "nplb": [2, 5],
+      "player_filter_tests": [2, 6],
+      "renderer_test": [2, 6]
+    },
+    {
+      "base_unittests": [6, 6],
+      "layout_tests": [1, 6],
+      "net_unittests": [3, 6],
+      "nplb": [3, 5],
+      "player_filter_tests": [4, 6],
+      "renderer_test": [4, 6]
+    },
+    {
+      "app_key_files_test": "*",
+      "app_key_test": "*",
+      "audio_test": "*",
+      "base_test": "*",
+      "base_unittests": [4, 6],
+      "bindings_test": "*",
+      "browser_test": "*",
+      "crypto_unittests": "*",
+      "csp_test": "*",
+      "css_parser_test": "*",
+      "cssom_test": "*",
+      "cwrappers_test": "*",
+      "dom_parser_test": "*",
+      "dom_test": "*",
+      "drain_file_test": "*",
+      "elf_loader_test": "*",
+      "extension_test": "*",
+      "graphics_system_test": "*",
+      "installation_manager_test": "*",
+      "layout_test": "*",
+      "layout_tests": [2, 6],
+      "loader_test": "*",
+      "math_test": "*",
+      "media_capture_test": "*",
+      "media_session_test": "*",
+      "media_stream_test": "*",
+      "memory_store_test": "*",
+      "nb_test": "*",
+      "net_unittests": [6, 6],
+      "network_test": "*",
+      "nplb": [5, 5],
+      "player_filter_tests": [6, 6],
+      "poem_unittests": "*",
+      "render_tree_test": "*",
+      "renderer_test": [3, 6],
+      "slot_management_test": "*",
+      "starboard_platform_tests": "*",
+      "storage_test": "*",
+      "text_encoding_test": "*",
+      "web_animations_test": "*",
+      "web_test": "*",
+      "webdriver_test": "*",
+      "websocket_test": "*",
+      "worker_test": "*",
+      "xhr_test": "*",
+      "zip_unittests": "*"
+    }
   ]
 }
diff --git a/starboard/tools/testing/test_runner.py b/starboard/tools/testing/test_runner.py
index fe2fde7..2fc8f24 100755
--- a/starboard/tools/testing/test_runner.py
+++ b/starboard/tools/testing/test_runner.py
@@ -428,7 +428,7 @@
     if gtest_filter_value:
       test_params.append("--gtest_filter=" + gtest_filter_value)
 
-    if shard_index and shard_count:
+    if shard_count is not None:
       test_params.append("--gtest_total_shards={}".format(shard_count))
       test_params.append("--gtest_shard_index={}".format(shard_index))
 
diff --git a/starboard/win/shared/BUILD.gn b/starboard/win/shared/BUILD.gn
index 5f20368..162b1c5 100644
--- a/starboard/win/shared/BUILD.gn
+++ b/starboard/win/shared/BUILD.gn
@@ -180,6 +180,7 @@
     "//starboard/shared/stub/window_set_on_screen_keyboard_keep_focus.cc",
     "//starboard/shared/stub/window_show_on_screen_keyboard.cc",
     "//starboard/shared/stub/window_update_on_screen_keyboard_suggestions.cc",
+    "//starboard/shared/uwp/wasapi_include.h",
     "//starboard/shared/win32/adapter_utils.cc",
     "//starboard/shared/win32/adapter_utils.h",
     "//starboard/shared/win32/atomic_public.h",
diff --git a/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.cpp b/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
index d50e5f3..18d2410 100644
--- a/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
+++ b/third_party/angle/src/libANGLE/renderer/d3d/DynamicHLSL.cpp
@@ -75,7 +75,7 @@
     "  float3 j    = pow(SRGB_OETF(i), gamma);\n"
     "  float3 adj  = (minLNits + (maxLNits - minLNits) * j) / kRefWhiteLevelSRGB;\n"
     "  float3 cri_float = (float3)cri;\n"
-    "  float3 ret = lerp(adj, L, cri_float);\n"
+    "  float3 ret = lerp(L, adj, cri_float);\n"
     "  return ret;\n"
     "}\n"
     "//input: normalized L in units of RefWhite (1.0=100nits), output: normalized E\n"
@@ -97,7 +97,7 @@
     "{\n"
     "    PS_OUTPUT output;\n"
     "   \n"
-    "    float3 input_colors = gl_Color[0].rgb;\n"
+    "    float3 input_colors = max(gl_Color[0].rgb, 0);\n"
     "    float3 lin_osd_graphics = SRGB_EOTF(input_colors);\n"
     "    lin_osd_graphics =  mul(BT709_TO_BT2020, lin_osd_graphics);\n"
     "    lin_osd_graphics *=  kMaxNits/kRefWhiteLevelSRGB;\n"
diff --git a/third_party/crashpad/client/BUILD.gn b/third_party/crashpad/client/BUILD.gn
index 19aa8db..6ecd5c5 100644
--- a/third_party/crashpad/client/BUILD.gn
+++ b/third_party/crashpad/client/BUILD.gn
@@ -90,12 +90,13 @@
     "../util",
   ]
 
+  deps = []
+
   if (crashpad_is_in_starboard) {
     public_deps += [ "//starboard/elf_loader:evergreen_info" ]
+    deps += [ "../wrapper/proto:crashpad_annotations_proto" ]
   }
 
-  deps = []
-
   if (crashpad_is_win) {
     libs = [ "rpcrt4.lib" ]
     cflags = [ "/wd4201" ]  # nonstandard extension used : nameless struct/union
diff --git a/third_party/crashpad/client/crashpad_client.h b/third_party/crashpad/client/crashpad_client.h
index 9dd12dd..4cf5a0e 100644
--- a/third_party/crashpad/client/crashpad_client.h
+++ b/third_party/crashpad/client/crashpad_client.h
@@ -41,7 +41,6 @@
 #if defined(STARBOARD)
 #include "starboard/elf_loader/evergreen_info.h"
 #include "third_party/crashpad/snapshot/sanitized/sanitization_information.h"
-#include "third_party/crashpad/wrapper/annotations.h"
 #endif
 
 namespace crashpad {
@@ -391,14 +390,17 @@
   //! \return `true` on success, `false` on failure with a message logged.
   static bool SendEvergreenInfoToHandler(EvergreenInfo evergreen_info);
 
-  //! \brief Sends mapping info to the handler
+  //! \brief Inserts annotation mapping info for the handler
   //!
-  //! A handler must have already been installed before calling this method.
-  //! \param[in] annotations A CrashpadAnnotations struct, whose information
-  //!     was created on Evergreen startup.
+  //! A signal handler must have already been installed before calling this
+  //! method. Whether or not the annotation is sent to the Crashpad handler,
+  //! or just prepared to be sent, depends on whether the Crashpad handler is
+  //! started at launch or at crash.
+  //! \param[in] key The annotation's key.
+  //! \param[in] value The annotation's value.
   //!
   //! \return `true` on success, `false` on failure with a message logged.
-  static bool SendAnnotationsToHandler(CrashpadAnnotations annotations);
+  static bool InsertAnnotationForHandler(const char* key, const char* value);
 
   //! \brief Sends mapping info to the handler
   //!
diff --git a/third_party/crashpad/client/crashpad_client_linux.cc b/third_party/crashpad/client/crashpad_client_linux.cc
index 5738668..1b579bf 100644
--- a/third_party/crashpad/client/crashpad_client_linux.cc
+++ b/third_party/crashpad/client/crashpad_client_linux.cc
@@ -26,6 +26,7 @@
 #include "base/logging.h"
 #include "base/strings/stringprintf.h"
 #include "client/client_argv_handling.h"
+#include "starboard/common/mutex.h"
 #include "third_party/lss/lss.h"
 #include "util/file/file_io.h"
 #include "util/file/filesystem.h"
@@ -37,6 +38,15 @@
 #include "util/misc/from_pointer_cast.h"
 #include "util/posix/double_fork_and_exec.h"
 #include "util/posix/signals.h"
+// TODO(b/201538792): resolve conflict between mini_chromium and base functions.
+#ifdef LogMessage
+#define LOG_MESSAGE_DEFINED
+#undef LogMessage
+#endif
+#include "third_party/crashpad/wrapper/proto/crashpad_annotations.pb.h"
+#ifdef LOG_MESSAGE_DEFINED
+#define LogMessage MLogMessage
+#endif
 
 namespace crashpad {
 
@@ -44,9 +54,7 @@
 
 #if defined(STARBOARD)
 constexpr char kEvergreenInfoKey[] = "evergreen-information";
-constexpr char kProductKey[] = "annotation=prod";
-constexpr char kVersionKey[] = "annotation=ver";
-constexpr char kUAKey[] = "annotation=user_agent_string";
+constexpr char kAnnotationKey[] = "annotation=%s";
 #endif
 
 std::string FormatArgumentInt(const std::string& name, int value) {
@@ -183,10 +191,10 @@
     return SendEvergreenInfoImpl();
   }
 
-  bool SendAnnotations(CrashpadAnnotations annotations) {
-    annotations_ = annotations;
-    return SendAnnotationsImpl();
+  bool InsertAnnotation(const char* key, const char* value) {
+    return InsertAnnotationImpl(key, value);
   }
+
 #endif
 
   // The base implementation for all signal handlers, suitable for calling
@@ -227,7 +235,6 @@
 
 #if defined(STARBOARD)
   const EvergreenInfo& GetEvergreenInfo() { return evergreen_info_; }
-  const CrashpadAnnotations& GetAnnotations() { return annotations_; }
   const SanitizationInformation& GetSanitizationInformation() {
     return sanitization_information_;
   }
@@ -239,7 +246,7 @@
 
 #if defined(STARBOARD)
   virtual bool SendEvergreenInfoImpl() = 0;
-  virtual bool SendAnnotationsImpl() = 0;
+  virtual bool InsertAnnotationImpl(const char* key, const char* value) = 0;
 #endif
 
   virtual void HandleCrashImpl() = 0;
@@ -262,7 +269,6 @@
 
 #if defined(STARBOARD)
   EvergreenInfo evergreen_info_;
-  CrashpadAnnotations annotations_;
   SanitizationInformation sanitization_information_;
 #endif
 
@@ -297,12 +303,18 @@
     argv_strings_.push_back(FormatArgumentAddress("trace-parent-with-exception",
                                                   &GetExceptionInfo()));
 
+#if defined(STARBOARD)
+    argv_strings_.push_back("--handler-started-at-crash");
+#endif
+
     StringVectorToCStringVector(argv_strings_, &argv_);
     return Install(unhandled_signals);
   }
 
 #if defined(STARBOARD)
   bool SendEvergreenInfoImpl() override {
+    starboard::ScopedLock scoped_lock(argv_lock_);
+
     bool updated = false;
     for (auto& s : argv_strings_) {
       if (s.compare(2, strlen(kEvergreenInfoKey), kEvergreenInfoKey) == 0) {
@@ -324,27 +336,19 @@
     return true;
   }
 
-  bool SendAnnotationsImpl() override {
-    bool updated_product = false;
-    bool updated_version = false;
-    bool updated_user_agent_string = false;
+  bool InsertAnnotationImpl(const char* key, const char* value) override {
+    starboard::ScopedLock scoped_lock(argv_lock_);
+
+    std::string formatted_key = base::StringPrintf(kAnnotationKey, key);
+    bool updated_annotation = false;
     for (auto& s : argv_strings_) {
-      if (UpdateAnnotation(s, kProductKey, GetAnnotations().product)) {
-        updated_product = true;
-      } else if (UpdateAnnotation(s, kVersionKey, GetAnnotations().version)) {
-        updated_version = true;
-      } else if (UpdateAnnotation(s, kUAKey, GetAnnotations().user_agent_string)) {
-        updated_user_agent_string = true;
+      if (UpdateAnnotation(s, formatted_key, value)) {
+        updated_annotation = true;
       }
     }
-    if (!updated_product) {
-      AddAnnotation(argv_strings_, kProductKey, GetAnnotations().product);
-    }
-    if (!updated_version) {
-      AddAnnotation(argv_strings_, kVersionKey, GetAnnotations().version);
-    }
-    if (!updated_user_agent_string) {
-      AddAnnotation(argv_strings_, kUAKey, GetAnnotations().user_agent_string);
+
+    if (!updated_annotation) {
+      AddAnnotation(argv_strings_, formatted_key, value);
     }
 
     StringVectorToCStringVector(argv_strings_, &argv_);
@@ -382,6 +386,10 @@
 
   std::vector<std::string> argv_strings_;
   std::vector<const char*> argv_;
+#if defined(STARBOARD)
+  // Protects access to both argv_strings_ and argv_.
+  starboard::Mutex argv_lock_;
+#endif
   std::vector<std::string> envp_strings_;
   std::vector<const char*> envp_;
   bool set_envp_ = false;
@@ -446,6 +454,10 @@
   }
 
 #if defined(STARBOARD)
+  char* GetSerializedAnnotations() {
+    return serialized_annotations_.data();
+  }
+
   bool SendEvergreenInfoImpl() override {
     ExceptionHandlerClient client(sock_to_handler_.get(), true);
     ExceptionHandlerProtocol::ClientInformation info = {};
@@ -455,13 +467,36 @@
     return true;
   }
 
-  bool SendAnnotationsImpl() override {
+  bool InsertAnnotationImpl(const char* key, const char* value) override {
+    starboard::ScopedLock scoped_lock(annotations_lock_);
+
+    std::string key_str(key);
+    std::string value_str(value);
+
+    if (strcmp(key, "ver") == 0) {
+      annotations_.set_ver(value_str);
+    } else if (strcmp(key, "prod") == 0) {
+      annotations_.set_prod(value_str);
+    } else if (strcmp(key, "user_agent_string") == 0) {
+      annotations_.set_user_agent_string(value_str);
+    } else {
+      (*annotations_.mutable_runtime_annotations())[key_str] = value_str;
+    }
+
+    serialized_annotations_.resize(annotations_.ByteSize());
+    annotations_.SerializeToArray(serialized_annotations_.data(),
+        annotations_.ByteSize());
+
     ExceptionHandlerClient client(sock_to_handler_.get(), true);
     ExceptionHandlerProtocol::ClientInformation info = {};
-    info.annotations_address = FromPointerCast<VMAddress>(&GetAnnotations());
+    info.serialized_annotations_address = FromPointerCast<VMAddress>(
+        GetSerializedAnnotations());
+    info.serialized_annotations_size = serialized_annotations_.size();
     client.SendAnnotations(info);
+
     return true;
   }
+
 #endif
 
   void HandleCrashImpl() override {
@@ -471,9 +506,12 @@
 #if defined(STARBOARD)
     info.sanitization_information_address =
         FromPointerCast<VMAddress>(&GetSanitizationInformation());
-    info.annotations_address = FromPointerCast<VMAddress>(&GetAnnotations());
+    info.serialized_annotations_address = FromPointerCast<VMAddress>(
+        GetSerializedAnnotations());
+    info.serialized_annotations_size = serialized_annotations_.size();
     info.evergreen_information_address =
         FromPointerCast<VMAddress>(&GetEvergreenInfo());
+    info.handler_start_type = ExceptionHandlerProtocol::kStartAtLaunch;
 #endif
 
 #if defined(OS_CHROMEOS)
@@ -497,6 +535,13 @@
 
   ScopedFileHandle sock_to_handler_;
   pid_t handler_pid_ = -1;
+#if defined(STARBOARD)
+  crashpad::wrapper::CrashpadAnnotations annotations_;
+  std::vector<char> serialized_annotations_;
+
+  // Protects access to both annotations_ and serialized_annotations_;
+  starboard::Mutex annotations_lock_;
+#endif
 
 #if defined(OS_CHROMEOS)
   // An optional UNIX timestamp passed to us from Chrome.
@@ -694,14 +739,14 @@
   return SignalHandler::Get()->SendEvergreenInfo(evergreen_info);
 }
 
-bool CrashpadClient::SendAnnotationsToHandler(
-    CrashpadAnnotations annotations) {
+bool CrashpadClient::InsertAnnotationForHandler(
+    const char* key, const char* value) {
   if (!SignalHandler::Get()) {
     DLOG(ERROR) << "Crashpad isn't enabled";
     return false;
   }
 
-  return SignalHandler::Get()->SendAnnotations(annotations);
+  return SignalHandler::Get()->InsertAnnotation(key, value);
 }
 
 bool CrashpadClient::SendSanitizationInformationToHandler(
diff --git a/third_party/crashpad/handler/handler_main.cc b/third_party/crashpad/handler/handler_main.cc
index 51bd64e..cbf9bb5 100644
--- a/third_party/crashpad/handler/handler_main.cc
+++ b/third_party/crashpad/handler/handler_main.cc
@@ -172,6 +172,9 @@
 #if defined(STARBOARD)
 "      --evergreen-information=EVERGREEN_INFORMATION_ADDRESS\n"
 "                              the address of a EvegreenInfo struct.\n"
+"      --handler-started-at-crash\n"
+"                              the handler was started at time of crash, as\n"
+"                              opposed to time of launch\n"
 #endif
 "      --url=URL               send crash reports to this Breakpad server URL,\n"
 "                              only if uploads are enabled for the database\n"
@@ -214,6 +217,7 @@
   bool shared_client_connection;
 #if defined(STARBOARD)
   VMAddress evergreen_information_address;
+  bool handler_started_at_crash = false;
 #endif  // defined(STARBOARD)
 #if defined(OS_ANDROID)
   bool write_minidump_to_log;
@@ -590,6 +594,7 @@
     kOptionTraceParentWithException,
 #if defined(STARBOARD)
     kOptionEvergreenInformaton,
+    kOptionHandlerStartedAtCrash,
 #endif  // defined(STARBOARD)
 #endif
     kOptionURL,
@@ -676,6 +681,10 @@
      required_argument,
      nullptr,
      kOptionEvergreenInformaton},
+    {"handler-started-at-crash",
+     no_argument,
+     nullptr,
+     kOptionHandlerStartedAtCrash},
 #endif
     {"url", required_argument, nullptr, kOptionURL},
 #if defined(OS_CHROMEOS)
@@ -850,6 +859,10 @@
         }
         break;
       }
+      case kOptionHandlerStartedAtCrash: {
+        options.handler_started_at_crash = true;
+        break;
+      }
 #endif   // defined(STARBOARD)
 #endif  // OS_LINUX || OS_ANDROID
       case kOptionURL: {
@@ -1070,6 +1083,9 @@
 #if defined(STARBOARD)
     info.evergreen_information_address =
         options.evergreen_information_address;
+    if (options.handler_started_at_crash) {
+      info.handler_start_type = ExceptionHandlerProtocol::kStartAtCrash;
+    }
 #endif   // defined(STARBOARD)
     return exception_handler->HandleException(getppid(), geteuid(), info)
                ? EXIT_SUCCESS
diff --git a/third_party/crashpad/handler/linux/capture_snapshot.cc b/third_party/crashpad/handler/linux/capture_snapshot.cc
index 8fb0735..59350fd 100644
--- a/third_party/crashpad/handler/linux/capture_snapshot.cc
+++ b/third_party/crashpad/handler/linux/capture_snapshot.cc
@@ -31,19 +31,15 @@
     VMAddress requesting_thread_stack_address,
     pid_t* requesting_thread_id,
     std::unique_ptr<ProcessSnapshotLinux>* snapshot,
-    std::unique_ptr<ProcessSnapshotSanitized>* sanitized_snapshot
-#if defined(STARBOARD)
-    ,
-    VMAddress evergreen_information_address,
-    VMAddress annotations_address
-#endif
-    ) {
+    std::unique_ptr<ProcessSnapshotSanitized>* sanitized_snapshot) {
   std::unique_ptr<ProcessSnapshotLinux> process_snapshot(
       new ProcessSnapshotLinux());
 #if defined(STARBOARD)
   if (!process_snapshot->Initialize(connection,
                                     info.evergreen_information_address,
-                                    info.annotations_address)) {
+                                    info.serialized_annotations_address,
+                                    info.serialized_annotations_size,
+                                    info.handler_start_type)) {
 #else
   if (!process_snapshot->Initialize(connection)) {
 #endif
diff --git a/third_party/crashpad/handler/linux/capture_snapshot.h b/third_party/crashpad/handler/linux/capture_snapshot.h
index 51eca35..b01e316 100644
--- a/third_party/crashpad/handler/linux/capture_snapshot.h
+++ b/third_party/crashpad/handler/linux/capture_snapshot.h
@@ -64,13 +64,7 @@
     VMAddress requesting_thread_stack_address,
     pid_t* requesting_thread_id,
     std::unique_ptr<ProcessSnapshotLinux>* process_snapshot,
-    std::unique_ptr<ProcessSnapshotSanitized>* sanitized_snapshot
-#if defined(STARBOARD)
-    ,
-    VMAddress evergreen_information_address,
-    VMAddress annotations_address
-#endif
-    );
+    std::unique_ptr<ProcessSnapshotSanitized>* sanitized_snapshot);
 
 }  // namespace crashpad
 
diff --git a/third_party/crashpad/handler/linux/crash_report_exception_handler.cc b/third_party/crashpad/handler/linux/crash_report_exception_handler.cc
index b0f68ce..9a86073 100644
--- a/third_party/crashpad/handler/linux/crash_report_exception_handler.cc
+++ b/third_party/crashpad/handler/linux/crash_report_exception_handler.cc
@@ -89,7 +89,8 @@
 
 bool CrashReportExceptionHandler::AddAnnotations(
     const ExceptionHandlerProtocol::ClientInformation& info) {
-  annotations_address_ = info.annotations_address;
+  serialized_annotations_address_ = info.serialized_annotations_address;
+  serialized_annotations_size_ = info.serialized_annotations_size;
   return true;
 }
 #endif
@@ -153,13 +154,7 @@
                        requesting_thread_stack_address,
                        requesting_thread_id,
                        &process_snapshot,
-                       &sanitized_snapshot
-#if defined(STARBOARD)
-                       ,
-                       evergreen_info_,
-                       annotations_address_
-#endif
-                       )) {
+                       &sanitized_snapshot)) {
     return false;
   }
 
diff --git a/third_party/crashpad/handler/linux/crash_report_exception_handler.h b/third_party/crashpad/handler/linux/crash_report_exception_handler.h
index 5cca2ad..7a32ac0 100644
--- a/third_party/crashpad/handler/linux/crash_report_exception_handler.h
+++ b/third_party/crashpad/handler/linux/crash_report_exception_handler.h
@@ -122,7 +122,8 @@
   const UserStreamDataSources* user_stream_data_sources_;  // weak
 #if defined(STARBOARD)
   VMAddress evergreen_info_;
-  VMAddress annotations_address_;
+  VMAddress serialized_annotations_address_;
+  int serialized_annotations_size_;
 #endif
 
   DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler);
diff --git a/third_party/crashpad/snapshot/linux/process_snapshot_linux.cc b/third_party/crashpad/snapshot/linux/process_snapshot_linux.cc
index 5fe900b..48fdb11 100644
--- a/third_party/crashpad/snapshot/linux/process_snapshot_linux.cc
+++ b/third_party/crashpad/snapshot/linux/process_snapshot_linux.cc
@@ -20,7 +20,16 @@
 #include "util/linux/exception_information.h"
 
 #if defined(STARBOARD)
-#include "third_party/crashpad/wrapper/annotations.h"
+#include "third_party/crashpad/util/linux/exception_handler_protocol.h"
+// TODO(b/201538792): resolve conflict between mini_chromium and base functions.
+#ifdef LogMessage
+#define LOG_MESSAGE_DEFINED
+#undef LogMessage
+#endif
+#include "third_party/crashpad/wrapper/proto/crashpad_annotations.pb.h"
+#ifdef LOG_MESSAGE_DEFINED
+#define LogMessage MLogMessage
+#endif
 #endif
 
 namespace crashpad {
@@ -56,7 +65,9 @@
 #if defined(STARBOARD)
 bool ProcessSnapshotLinux::Initialize(PtraceConnection* connection,
                                       VMAddress evergreen_information_address,
-                                      VMAddress annotations_address) {
+                                      VMAddress serialized_annotations_address,
+                                      int serialized_annotations_size,
+                                      ExceptionHandlerProtocol::HandlerStartType handler_start_type) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
 
   if (gettimeofday(&snapshot_time_, nullptr) != 0) {
@@ -70,15 +81,10 @@
     return false;
   }
 
-  CrashpadAnnotations annotations;
-  if (!memory_range_.Read(
-          annotations_address, sizeof(CrashpadAnnotations), &annotations)) {
-    LOG(ERROR) << "Could not read annotations";
+  if (handler_start_type == ExceptionHandlerProtocol::kStartAtCrash) {
+    LOG(INFO) << "Annotations do not need to be read from client process";
   } else {
-    AddAnnotation("user_agent_string",
-                  std::string(annotations.user_agent_string));
-    AddAnnotation("prod", std::string(annotations.product));
-    AddAnnotation("ver", std::string(annotations.version));
+    AddAnnotations(serialized_annotations_address, serialized_annotations_size);
   }
 
   system_.Initialize(&process_reader_, &snapshot_time_);
@@ -90,6 +96,33 @@
   INITIALIZATION_STATE_SET_VALID(initialized_);
   return true;
 }
+
+void ProcessSnapshotLinux::AddAnnotations(
+    VMAddress serialized_annotations_address, int serialized_annotations_size) {
+  std::vector<char> buffer(serialized_annotations_size);
+  if (!memory_range_.Read(serialized_annotations_address,
+                          serialized_annotations_size,
+                          buffer.data())) {
+    LOG(ERROR) << "Could not read annotations";
+    return;
+  }
+
+  crashpad::wrapper::CrashpadAnnotations annotations;
+  if (!annotations.ParseFromArray(buffer.data(), serialized_annotations_size)) {
+      LOG(ERROR) << "Could not parse annotations";
+      return;
+  }
+
+  AddAnnotation("user_agent_string",
+                std::string(annotations.user_agent_string()));
+  AddAnnotation("prod", std::string(annotations.prod()));
+  AddAnnotation("ver", std::string(annotations.ver()));
+
+  for (auto& runtime_annotation : annotations.runtime_annotations()) {
+    AddAnnotation(runtime_annotation.first, runtime_annotation.second);
+  }
+  LOG(INFO) << "Annotations successfully added from client process";
+}
 #endif
 
 pid_t ProcessSnapshotLinux::FindThreadWithStackAddress(
diff --git a/third_party/crashpad/snapshot/linux/process_snapshot_linux.h b/third_party/crashpad/snapshot/linux/process_snapshot_linux.h
index 8cd9ef5..0d20528 100644
--- a/third_party/crashpad/snapshot/linux/process_snapshot_linux.h
+++ b/third_party/crashpad/snapshot/linux/process_snapshot_linux.h
@@ -45,6 +45,7 @@
 #if defined(STARBOARD)
 #include "snapshot/module_snapshot_evergreen.h"
 #include "starboard/elf_loader/evergreen_info.h"
+#include "third_party/crashpad/util/linux/exception_handler_protocol.h"
 #endif
 
 namespace crashpad {
@@ -77,7 +78,9 @@
   //!     an appropriate message logged.
   bool Initialize(PtraceConnection* connnection,
                   VMAddress evergreen_information_address,
-                  VMAddress annotations_address);
+                  VMAddress serialized_annotations_address,
+                  int serialized_annotations_size,
+                  ExceptionHandlerProtocol::HandlerStartType handler_start_type);
 #endif
 
   //! \brief Finds the thread whose stack contains \a stack_address.
@@ -158,6 +161,8 @@
   void InitializeModules();
 #if defined(STARBOARD)
   void InitializeModules(VMAddress evergreen_information_address);
+  void AddAnnotations(VMAddress serialized_annotations_address,
+                      int serialized_annotations_size);
 #endif
   void InitializeAnnotations();
 
diff --git a/third_party/crashpad/util/linux/exception_handler_protocol.cc b/third_party/crashpad/util/linux/exception_handler_protocol.cc
index b139017..5f58721 100644
--- a/third_party/crashpad/util/linux/exception_handler_protocol.cc
+++ b/third_party/crashpad/util/linux/exception_handler_protocol.cc
@@ -26,7 +26,9 @@
 #if defined(STARBOARD)
       ,
       evergreen_information_address(0),
-      annotations_address(0)
+      serialized_annotations_address(0),
+      serialized_annotations_size(0),
+      handler_start_type(kStartAtLaunch)
 #endif
 {
 }
diff --git a/third_party/crashpad/util/linux/exception_handler_protocol.h b/third_party/crashpad/util/linux/exception_handler_protocol.h
index 4cc1662..4f94bd9 100644
--- a/third_party/crashpad/util/linux/exception_handler_protocol.h
+++ b/third_party/crashpad/util/linux/exception_handler_protocol.h
@@ -38,6 +38,12 @@
   //! \brief A boolean status suitable for communication between processes.
   enum Bool : char { kBoolFalse, kBoolTrue };
 
+#if defined(STARBOARD)
+  //! \brief Describes when, in the client process lifecycle, the Crashpad
+  //!     handler was or will be started.
+  enum HandlerStartType : char { kStartAtCrash, kStartAtLaunch };
+#endif
+
   //! \brief Information about a client registered with an
   //!     ExceptionHandlerServer.
   struct ClientInformation {
@@ -57,9 +63,15 @@
     //!     struct, or 0 if there is no such struct.
     VMAddress evergreen_information_address;
 
-    //! \brief The address in the client's address space of an
-    //!     CrashpadAnnotations struct, or 0 if there is no such struct.
-    VMAddress annotations_address;
+    //! \brief The address in the client's address space of a character array
+    //!     containing a serialized CrashpadAnnotations proto, or 0 if there is
+    //!     no such address.
+    VMAddress serialized_annotations_address;
+
+    //! \brief The byte size of the serialized annotations.
+    int serialized_annotations_size;
+
+    HandlerStartType handler_start_type;
 #endif
 
 #if defined(OS_LINUX)
diff --git a/third_party/crashpad/wrapper/proto/crashpad_annotations.proto b/third_party/crashpad/wrapper/proto/crashpad_annotations.proto
index eb739bc..8eb6205 100644
--- a/third_party/crashpad/wrapper/proto/crashpad_annotations.proto
+++ b/third_party/crashpad/wrapper/proto/crashpad_annotations.proto
@@ -22,10 +22,10 @@
 // Next id: 5
 message CrashpadAnnotations {
   // The product name.
-  string product = 1;
+  string prod = 1;
 
   // The product version.
-  string version = 2;
+  string ver = 2;
 
   // The User-Agent string that identifies brand, model, etc.
   string user_agent_string = 3;
diff --git a/third_party/crashpad/wrapper/wrapper.cc b/third_party/crashpad/wrapper/wrapper.cc
index 8f19a70..700f9b7 100644
--- a/third_party/crashpad/wrapper/wrapper.cc
+++ b/third_party/crashpad/wrapper/wrapper.cc
@@ -33,8 +33,11 @@
 namespace crashpad {
 namespace wrapper {
 
-namespace {
+const char kCrashpadVersionKey[]  = "ver";
+const char kCrashpadProductKey[]  = "prod";
+const char kCrashpadUserAgentStringKey[]  = "user_agent_string";
 
+namespace {
 // TODO: Get evergreen information from installation.
 const std::string kCrashpadVersion = "1.0.0.0";
 #if defined(STARBOARD_BUILD_TYPE_GOLD)
@@ -215,9 +218,9 @@
   return client->SendEvergreenInfoToHandler(evergreen_info);
 }
 
-bool AddAnnotationsToCrashpad(CrashpadAnnotations annotations) {
+bool InsertCrashpadAnnotation(const char* key, const char* value) {
   ::crashpad::CrashpadClient* client = GetCrashpadClient();
-  return client->SendAnnotationsToHandler(annotations);
+  return client->InsertAnnotationForHandler(key, value);
 }
 
 }  // namespace wrapper
diff --git a/third_party/crashpad/wrapper/wrapper.h b/third_party/crashpad/wrapper/wrapper.h
index 4def1a8..be60fec 100644
--- a/third_party/crashpad/wrapper/wrapper.h
+++ b/third_party/crashpad/wrapper/wrapper.h
@@ -16,17 +16,28 @@
 #define THIRD_PARTY_CRASHPAD_WRAPPER_WRAPPER_H_
 
 #include "starboard/elf_loader/evergreen_info.h" // nogncheck
-#include "third_party/crashpad/wrapper/annotations.h"
 
 namespace third_party {
 namespace crashpad {
 namespace wrapper {
 
+// The key name used in Crashpad for the version annotation.
+extern const char kCrashpadVersionKey[];
+
+// The key name used in Crashpad for the version annotation.
+extern const char kCrashpadProductKey[];
+
+// The key name used in Crashpad for the user_agent_string annotation.
+extern const char kCrashpadUserAgentStringKey[];
+
 void InstallCrashpadHandler(bool start_at_crash);
 
 bool AddEvergreenInfoToCrashpad(EvergreenInfo evergreen_info);
 
-bool AddAnnotationsToCrashpad(CrashpadAnnotations annotations);
+// Associates the given value with the given key in Crashpad's map of
+// annotations, updating the existing value if the map already contains the
+// key. Returns true on success and false on failure.
+bool InsertCrashpadAnnotation(const char* key, const char* value);
 
 }  // namespace wrapper
 }  // namespace crashpad
diff --git a/third_party/crashpad/wrapper/wrapper_stub.cc b/third_party/crashpad/wrapper/wrapper_stub.cc
index bf3c093..f96dd94 100644
--- a/third_party/crashpad/wrapper/wrapper_stub.cc
+++ b/third_party/crashpad/wrapper/wrapper_stub.cc
@@ -18,13 +18,17 @@
 namespace crashpad {
 namespace wrapper {
 
+const char kCrashpadVersionKey[]  = "";
+const char kCrashpadProductKey[]  = "";
+const char kCrashpadUserAgentStringKey[]  = "";
+
 void InstallCrashpadHandler(bool start_at_crash) {}
 
 bool AddEvergreenInfoToCrashpad(EvergreenInfo evergreen_info) {
   return false;
 }
 
-bool AddAnnotationsToCrashpad(CrashpadAnnotations annotations) {
+bool InsertCrashpadAnnotation(const char* key, const char* value) {
   return false;
 }