Import Cobalt 20.lts.4.266393
diff --git a/src/cobalt/black_box_tests/black_box_tests.py b/src/cobalt/black_box_tests/black_box_tests.py
index 0154522..400202f 100644
--- a/src/cobalt/black_box_tests/black_box_tests.py
+++ b/src/cobalt/black_box_tests/black_box_tests.py
@@ -64,14 +64,8 @@
 _device_params = None
 # Binding address used to create the test server.
 _binding_address = None
-
-
-def GetDeviceParams():
-
-  global _device_params
-  _device_params = cobalt_runner.GetDeviceParamsFromCommandLine()
-  # Keep other modules from seeing these args
-  sys.argv = sys.argv[:1]
+# Port used to create the web platform test http server.
+_wpt_http_port = None
 
 
 class BlackBoxTestCase(unittest.TestCase):
@@ -104,9 +98,11 @@
   def GetBindingAddress(self):
     return _binding_address
 
+  def GetWptHttpPort(self):
+    return _wpt_http_port
+
 
 def LoadTests(platform, config, device_id, out_directory):
-
   launcher = abstract_launcher.LauncherFactory(
       platform,
       'cobalt',
@@ -134,19 +130,44 @@
 class BlackBoxTests(object):
   """Helper class to run all black box tests and return results."""
 
-  def __init__(self, test_name=None, proxy_port_number=None):
+  def __init__(self,
+               server_binding_address,
+               proxy_address=None,
+               proxy_port=None,
+               test_name=None,
+               wpt_http_port=None,
+               device_ips=None):
     logging.basicConfig(level=logging.DEBUG)
-    GetDeviceParams()
 
-    # Port number used to create the proxy server. If the --proxy target param
-    # is not set, a random free port is used.
-    if proxy_port_number is None:
-      proxy_port_number = str(self.GetUnusedPort(_binding_address))
-      _device_params.target_params.append('--proxy=%s:%s' % (_binding_address,
-                                                             proxy_port_number))
+    # Setup global variables used by test cases
+    global _device_params
+    _device_params = cobalt_runner.GetDeviceParamsFromCommandLine()
+    # Keep other modules from seeing these args
+    sys.argv = sys.argv[:1]
+    global _binding_address
+    _binding_address = server_binding_address
+    # Port used to create the web platform test http server. If not specified,
+    # a random free port is used.
+    if wpt_http_port is None:
+      wpt_http_port = str(self.GetUnusedPort([server_binding_address]))
+    global _wpt_http_port
+    _wpt_http_port = wpt_http_port
+    _device_params.target_params.append(
+        '--web-platform-test-server=http://web-platform.test:%s' %
+        wpt_http_port)
 
+    # Port used to create the proxy server. If not specified, a random free
+    # port is used.
+    if proxy_port is None:
+      proxy_port = str(self.GetUnusedPort([server_binding_address]))
+    if proxy_address is None:
+      proxy_address = server_binding_address
+    _device_params.target_params.append('--proxy=%s:%s' %
+                                        (proxy_address, proxy_port))
+
+    self.proxy_port = proxy_port
     self.test_name = test_name
-    self.proxy_port_number = proxy_port_number
+    self.device_ips = device_ips
 
     # Test domains used in web platform tests to be resolved to the server
     # binding address.
@@ -158,15 +179,16 @@
         'xn--n8j6ds53lwwkrqhv28a.web-platform.test',
         'xn--lve-6lad.web-platform.test'
     ]
-    self.host_resolve_map = dict([(host, _binding_address) for host in hosts])
+    self.host_resolve_map = dict([(host, server_binding_address) for host in hosts])
 
   def Run(self):
-    if self.proxy_port_number == '-1':
+    if self.proxy_port == '-1':
       return 1
-    logging.info('Using proxy port number: %s', self.proxy_port_number)
+    logging.info('Using proxy port: %s', self.proxy_port)
 
-    with ProxyServer(port=self.proxy_port_number,
-                     host_resolve_map=self.host_resolve_map):
+    with ProxyServer(
+        port=self.proxy_port, host_resolve_map=self.host_resolve_map,
+        client_ips=self.device_ips):
       if self.test_name:
         suite = unittest.TestLoader().loadTestsFromModule(
             importlib.import_module(_TEST_DIR_PATH + self.test_name))
@@ -178,42 +200,77 @@
           verbosity=0, stream=sys.stdout).run(suite).wasSuccessful()
       return return_code
 
-  def GetUnusedPort(self, machine_address):
-    """Find a free port on the machine address by pinging with socket."""
-    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+  def GetUnusedPort(self, addresses):
+    """Find a free port on the list of addresses by pinging with sockets."""
+    SOCKET_SUCCESS = 0
+
+    if not addresses:
+      logging.error('Can not find unused port on invalid addresses.')
+      return -1
+
+    socks = []
+    for address in addresses:
+      socks.append((address, socket.socket(socket.AF_INET, socket.SOCK_STREAM)))
     try:
-      for i in range(1, _PORT_SELECTION_RETRY_LIMIT):
-        port_number = random.randint(_PORT_SELECTION_RANGE[0],
-                                     _PORT_SELECTION_RANGE[1])
-        result = sock.connect_ex((machine_address, port_number))
-        if result != 0:
-          return port_number
-        if i == _PORT_SELECTION_RETRY_LIMIT - 1:
-          logging.error(
-              'Can not find unused port on target machine within %s attempts.',
-              _PORT_SELECTION_RETRY_LIMIT)
-          return -1
+      for _ in range(_PORT_SELECTION_RETRY_LIMIT):
+        port = random.randint(_PORT_SELECTION_RANGE[0], _PORT_SELECTION_RANGE[1])
+        unused = True
+        for sock in socks:
+          result = sock[1].connect_ex((sock[0], port))
+          if result == SOCKET_SUCCESS:
+            unused = False
+            break
+        if unused:
+          return port
+      logging.error(
+          'Can not find unused port on addresses within %s attempts.' %
+          _PORT_SELECTION_RETRY_LIMIT)
+      return -1
     finally:
-      sock.close()
+      for sock in socks:
+        sock[1].close()
 
 
 def main():
   parser = argparse.ArgumentParser()
-  parser.add_argument('--test_name',
-                      help=('Name of test to be run. If not specified, all '
-                            'tests are run.'))
-  parser.add_argument('--server_binding_address',
-                      default='127.0.0.1',
-                      help='Binding address used to create the test server.')
-  parser.add_argument('--proxy_port_number',
-                      help=('Port number used to create the proxy http server'
-                            'that all black box tests are run through. If not'
-                            'specified, a random free port is used.'))
+  parser.add_argument(
+      '--server_binding_address',
+      default='127.0.0.1',
+      help='Binding address used to create the test server.')
+  parser.add_argument(
+      '--proxy_address',
+      default=None,
+      help=('Address to the proxy server that all black box'
+            'tests are run through. If not specified, the'
+            'server binding address is used.'))
+  parser.add_argument(
+      '--proxy_port',
+      default=None,
+      help=('Port used to create the proxy server that all'
+            'black box tests are run through. If not'
+            'specified, a random free port is used.'))
+  parser.add_argument(
+      '--test_name',
+      default=None,
+      help=('Name of test to be run. If not specified, all '
+            'tests are run.'))
+  parser.add_argument(
+      '--wpt_http_port',
+      default=None,
+      help=('Port used to create the web platform test http'
+            'server. If not specified, a random free port is'
+            'used.'))
+  parser.add_argument(
+      '--device_ips',
+      default=None,
+      nargs='*',
+      help=('IPs of test devices that will be allowed to connect. If not'
+            'specified, all IPs will be allowed to connect.'))
   args, _ = parser.parse_known_args()
 
-  global _binding_address
-  _binding_address = args.server_binding_address
-  test_object = BlackBoxTests(args.test_name, args.proxy_port_number)
+  test_object = BlackBoxTests(args.server_binding_address, args.proxy_address,
+                              args.proxy_port, args.test_name,
+                              args.wpt_http_port, args.device_ips)
   sys.exit(test_object.Run())
 
 
diff --git a/src/cobalt/black_box_tests/proxy_server.py b/src/cobalt/black_box_tests/proxy_server.py
index ee29a39..5b51830 100644
--- a/src/cobalt/black_box_tests/proxy_server.py
+++ b/src/cobalt/black_box_tests/proxy_server.py
@@ -24,15 +24,16 @@
 import os
 import signal
 import subprocess
-import sys
-SRC_DIR = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
 import tempfile
 import time
 
+SRC_DIR = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
+
 
 class ProxyServer(object):
 
-  def __init__(self, hostname='0.0.0.0', port='8000', host_resolve_map=None):
+  def __init__(self, hostname='0.0.0.0', port='8000', host_resolve_map=None,
+               client_ips=None):
     self.command = [
         'python',
         os.path.join(SRC_DIR, 'third_party', 'proxy_py', 'proxy.py'),
@@ -42,10 +43,14 @@
     self.host_resolver_path = None
 
     if host_resolve_map:
-        with tempfile.NamedTemporaryFile(delete=False) as hosts:
-            json.dump(host_resolve_map, hosts)
-        self.host_resolver_path = hosts.name
-        self.command += ['--host_resolver', self.host_resolver_path]
+      with tempfile.NamedTemporaryFile(delete=False) as hosts:
+        json.dump(host_resolve_map, hosts)
+      self.host_resolver_path = hosts.name
+      self.command += ['--host_resolver', self.host_resolver_path]
+
+    if client_ips:
+      self.command += ['--client_ips']
+      self.command += client_ips
 
   def __enter__(self):
     self.proc = subprocess.Popen(self.command)
@@ -55,4 +60,4 @@
   def __exit__(self, exc_type, exc_value, traceback):
     self.proc.send_signal(signal.SIGINT)
     if self.host_resolver_path:
-        os.unlink(self.host_resolver_path)
+      os.unlink(self.host_resolver_path)
diff --git a/src/cobalt/black_box_tests/tests/web_platform_tests.py b/src/cobalt/black_box_tests/tests/web_platform_tests.py
index 12adcd0..2355b59 100644
--- a/src/cobalt/black_box_tests/tests/web_platform_tests.py
+++ b/src/cobalt/black_box_tests/tests/web_platform_tests.py
@@ -33,7 +33,8 @@
       self.skipTest('Can only run web platform tests on debug or devel config.')
 
   def test_simple(self):
-    with WebPlatformTestServer(binding_address=self.GetBindingAddress()):
+    with WebPlatformTestServer(binding_address=self.GetBindingAddress(),
+                               wpt_http_port=self.GetWptHttpPort()):
       target_params = []
 
       filters = self.cobalt_config.GetWebPlatformTestFilters()
diff --git a/src/cobalt/black_box_tests/web_platform_test_server.py b/src/cobalt/black_box_tests/web_platform_test_server.py
index 5199151..d3a9d96 100644
--- a/src/cobalt/black_box_tests/web_platform_test_server.py
+++ b/src/cobalt/black_box_tests/web_platform_test_server.py
@@ -31,12 +31,16 @@
 class WebPlatformTestServer(object):
   """Runs a WPT StashServer on its own thread in a Python context manager."""
 
-  def __init__(self, binding_address=None):
+  def __init__(self, binding_address=None, wpt_http_port=None):
     # IP config['host'] should map to either through a dns or the hosts file.
     if binding_address:
       self._binding_address = binding_address
     else:
       self._binding_address = '127.0.0.1'
+    if wpt_http_port:
+      self._wpt_http_port = wpt_http_port
+    else:
+      self._wpt_http_port = '8000'
 
   def main(self):
     kwargs = vars(serve.get_parser().parse_args())
@@ -45,6 +49,7 @@
     config = serve.load_config(os.path.join(WPT_DIR, 'config.default.json'),
                                os.path.join(WPT_DIR, 'config.json'),
                                **kwargs)
+    config['ports']['http'][0] = int(self._wpt_http_port)
 
     serve.setup_logger(config['log_level'])
 
diff --git a/src/cobalt/browser/trace_manager.h b/src/cobalt/browser/trace_manager.h
deleted file mode 100644
index b774f11..0000000
--- a/src/cobalt/browser/trace_manager.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2016 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef COBALT_BROWSER_TRACE_MANAGER_H_
-#define COBALT_BROWSER_TRACE_MANAGER_H_
-
-#include <map>
-#include <string>
-
-#include "base/threading/thread_checker.h"
-#include "cobalt/debug/console/command_manager.h"
-#include "cobalt/trace_event/event_parser.h"
-#include "cobalt/trace_event/scoped_trace_to_file.h"
-
-namespace cobalt {
-namespace browser {
-
-// Wrapper class which wraps all trace related stuff.
-class TraceManager {
- public:
-  // Returns whether there's a trace that is active.
-  static bool IsTracing();
-
-  TraceManager();
-
-  // Called by browser module when an input event is produced.
-  void OnInputEventProduced();
-
-  // Message handler callback for trace message from debug console.
-  void OnTraceMessage(const std::string& message);
-
-  // Message handler callback for input trace message from debug console.
-  void OnInputTraceMessage(const std::string& message);
-
-  // Called when receiving and finishing receiving parsed trace events.
-  void OnReceiveTraceEvent(
-      const scoped_refptr<trace_event::EventParser::ScopedEvent>& event);
-  void OnFinishReceiveTraceEvent();
-
- private:
-  typedef std::multimap<int64,
-                        scoped_refptr<trace_event::EventParser::ScopedEvent> >
-      StartTimeToEventMap;
-
-  // The message loop on which we'll do all our work.
-  MessageLoop* const self_message_loop_;
-
-  // Command handler object for trace command from the debug console.
-  debug::console::ConsoleCommandManager::CommandHandler trace_command_handler_;
-  // Command handler object for input trace command from the debug console.
-  debug::console::ConsoleCommandManager::CommandHandler
-      input_trace_command_handler_;
-  // Whether input tracing is enabled.
-  bool input_tracing_enabled_;
-
-  THREAD_CHECKER(thread_checker_);
-  // This object can be set to start a trace if a hotkey (like F3) is pressed.
-  // While initialized, it means that a trace is on-going.
-  scoped_ptr<trace_event::ScopedTraceToFile> trace_to_file_;
-  // Record of a list of events we're interested in, ordered by starting time.
-  StartTimeToEventMap start_time_to_event_map_;
-};
-
-}  // namespace browser
-}  // namespace cobalt
-
-#endif  // COBALT_BROWSER_TRACE_MANAGER_H_
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index e912fc3..164d0dd 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-244012
\ No newline at end of file
+266393
\ No newline at end of file
diff --git a/src/cobalt/content/ssl/certs/5c44d531.0 b/src/cobalt/content/ssl/certs/5c44d531.0
deleted file mode 100644
index cedf596..0000000
--- a/src/cobalt/content/ssl/certs/5c44d531.0
+++ /dev/null
@@ -1,33 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
-TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
-dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX
-DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
-ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
-b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291
-qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp
-uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU
-Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE
-pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp
-5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M
-UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN
-GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy
-5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv
-6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK
-eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6
-B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/
-BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov
-L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
-HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG
-SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS
-CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen
-5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897
-IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK
-gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL
-+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL
-vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm
-bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk
-N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC
-Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z
-ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ==
------END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/5e98733a.0 b/src/cobalt/content/ssl/certs/5e98733a.0
new file mode 100644
index 0000000..7ac10cc
--- /dev/null
+++ b/src/cobalt/content/ssl/certs/5e98733a.0
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw
+gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL
+Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg
+MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw
+BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0
+MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1
+c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ
+bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg
+Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ
+2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E
+T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j
+5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM
+C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T
+DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX
+wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A
+2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm
+nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8
+dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl
+N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj
+c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS
+5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS
+Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr
+hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/
+B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI
+AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw
+H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+
+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk
+2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol
+IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk
+5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY
+n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw==
+-----END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/812e17de.0 b/src/cobalt/content/ssl/certs/812e17de.0
deleted file mode 100644
index 05879ff..0000000
--- a/src/cobalt/content/ssl/certs/812e17de.0
+++ /dev/null
@@ -1,22 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc
-MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj
-IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB
-IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE
-RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl
-U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290
-IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU
-ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC
-QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr
-rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S
-NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc
-QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH
-txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP
-BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
-AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp
-tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa
-IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl
-6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+
-xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
-Cm26OWMohpLzGITY+9HPBVZkVw==
------END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/9f0f5fd6.0 b/src/cobalt/content/ssl/certs/9f0f5fd6.0
deleted file mode 100644
index 7f60447..0000000
--- a/src/cobalt/content/ssl/certs/9f0f5fd6.0
+++ /dev/null
@@ -1,32 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET
-MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb
-BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz
-MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx
-FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g
-Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2
-fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl
-LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV
-WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF
-TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb
-5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc
-CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri
-wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ
-wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG
-m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4
-F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng
-WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB
-BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0
-2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF
-AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/
-0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw
-F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS
-g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj
-qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN
-h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/
-ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V
-btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj
-Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ
-8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW
-gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE=
------END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/f060240e.0 b/src/cobalt/content/ssl/certs/f060240e.0
deleted file mode 100644
index 6d0133d..0000000
--- a/src/cobalt/content/ssl/certs/f060240e.0
+++ /dev/null
@@ -1,22 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw
-PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz
-cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9
-MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz
-IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ
-ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR
-VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL
-kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd
-EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas
-H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0
-HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud
-DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4
-QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu
-Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/
-AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8
-yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR
-FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA
-ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB
-kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
-l7+ijrRU
------END CERTIFICATE-----
diff --git a/src/cobalt/cssom/combinator.h b/src/cobalt/cssom/combinator.h
index 1e54192..d97fe80 100644
--- a/src/cobalt/cssom/combinator.h
+++ b/src/cobalt/cssom/combinator.h
@@ -34,9 +34,12 @@
   kDescendantCombinator,
   kNextSiblingCombinator,
   kFollowingSiblingCombinator,
-  kCombinatorCount,
+  kCombinatorTypeCount,
 };
 
+// Avoids the clang error: arithmetic between different enumeration types.
+constexpr size_t kCombinatorCount = static_cast<size_t>(kCombinatorTypeCount);
+
 // A combinator is punctuation that represents a particular kind of relationship
 // between the selectors on either side.
 //   https://www.w3.org/TR/selectors4/#combinator
diff --git a/src/cobalt/debug/remote/devtools/front_end/accessibility/AXBreadcrumbsPane.js b/src/cobalt/debug/remote/devtools/front_end/accessibility/AXBreadcrumbsPane.js
index 34708e0..9075c5e 100644
--- a/src/cobalt/debug/remote/devtools/front_end/accessibility/AXBreadcrumbsPane.js
+++ b/src/cobalt/debug/remote/devtools/front_end/accessibility/AXBreadcrumbsPane.js
@@ -112,7 +112,7 @@
   _onKeyDown(event) {
     if (!this._preselectedBreadcrumb)
       return;
-    if (!event.path.some(element => element === this._preselectedBreadcrumb.element()))
+    if (!event.composedPath().some(element => element === this._preselectedBreadcrumb.element()))
       return;
     if (event.shiftKey || event.metaKey || event.ctrlKey)
       return;
diff --git a/src/cobalt/debug/remote/devtools/front_end/accessibility/AccessibilityNodeView.js b/src/cobalt/debug/remote/devtools/front_end/accessibility/AccessibilityNodeView.js
index f0c0a2e..9bb5329 100644
--- a/src/cobalt/debug/remote/devtools/front_end/accessibility/AccessibilityNodeView.js
+++ b/src/cobalt/debug/remote/devtools/front_end/accessibility/AccessibilityNodeView.js
@@ -151,7 +151,7 @@
    * @return {!Element}
    */
   static createExclamationMark(tooltip) {
-    const exclamationElement = createElement('label', 'dt-icon-label');
+    const exclamationElement = createElement('span', 'dt-icon-label');
     exclamationElement.type = 'smallicon-warning';
     exclamationElement.title = tooltip;
     return exclamationElement;
diff --git a/src/cobalt/debug/remote/devtools/front_end/accessibility/accessibilityNode.css b/src/cobalt/debug/remote/devtools/front_end/accessibility/accessibilityNode.css
index a6e59ca..7b624a9 100644
--- a/src/cobalt/debug/remote/devtools/front_end/accessibility/accessibilityNode.css
+++ b/src/cobalt/debug/remote/devtools/front_end/accessibility/accessibilityNode.css
@@ -47,7 +47,7 @@
     padding-left: 4px;
 }
 
-.tree-outline label[is=dt-icon-label] {
+.tree-outline span[is=dt-icon-label] {
     position: relative;
     left: -11px;
 }
@@ -74,7 +74,7 @@
     left: -2px;
 }
 
-.tree-outline label[is=dt-icon-label] + .ax-name {
+.tree-outline span[is=dt-icon-label] + .ax-name {
     margin-left: -11px;
 }
 
diff --git a/src/cobalt/debug/remote/devtools/front_end/changes/changesView.css b/src/cobalt/debug/remote/devtools/front_end/changes/changesView.css
index 8d3ad90..552e9ec 100644
--- a/src/cobalt/debug/remote/devtools/front_end/changes/changesView.css
+++ b/src/cobalt/debug/remote/devtools/front_end/changes/changesView.css
@@ -3,14 +3,15 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-.insertion-point-main{
+[slot=insertion-point-main]{
     flex-direction: column;
     display: flex;
 }
 
-.insertion-point-sidebar {
+[slot=insertion-point-sidebar] {
     overflow: auto;
 }
+
 .editor-container{
     flex: 1;
 }
diff --git a/src/cobalt/debug/remote/devtools/front_end/console/ConsoleViewMessage.js b/src/cobalt/debug/remote/devtools/front_end/console/ConsoleViewMessage.js
index fdc9790..ee0d012 100644
--- a/src/cobalt/debug/remote/devtools/front_end/console/ConsoleViewMessage.js
+++ b/src/cobalt/debug/remote/devtools/front_end/console/ConsoleViewMessage.js
@@ -1179,7 +1179,7 @@
       return;
 
     if (!this._repeatCountElement) {
-      this._repeatCountElement = createElementWithClass('label', 'console-message-repeat-count', 'dt-small-bubble');
+      this._repeatCountElement = createElementWithClass('span', 'console-message-repeat-count', 'dt-small-bubble');
       switch (this._message.level) {
         case SDK.ConsoleMessage.MessageLevel.Warning:
           this._repeatCountElement.type = 'warning';
diff --git a/src/cobalt/debug/remote/devtools/front_end/console_counters/WarningErrorCounter.js b/src/cobalt/debug/remote/devtools/front_end/console_counters/WarningErrorCounter.js
index b51e2ef..b17e8ae 100644
--- a/src/cobalt/debug/remote/devtools/front_end/console_counters/WarningErrorCounter.js
+++ b/src/cobalt/debug/remote/devtools/front_end/console_counters/WarningErrorCounter.js
@@ -39,7 +39,7 @@
    */
   _createItem(shadowRoot, iconType) {
     const item = createElementWithClass('span', 'counter-item');
-    const icon = item.createChild('label', '', 'dt-icon-label');
+    const icon = item.createChild('span', '', 'dt-icon-label');
     icon.type = iconType;
     const text = icon.createChild('span');
     shadowRoot.appendChild(item);
diff --git a/src/cobalt/debug/remote/devtools/front_end/console_counters/errorWarningCounter.css b/src/cobalt/debug/remote/devtools/front_end/console_counters/errorWarningCounter.css
index d6ad447..ee6de7a 100644
--- a/src/cobalt/debug/remote/devtools/front_end/console_counters/errorWarningCounter.css
+++ b/src/cobalt/debug/remote/devtools/front_end/console_counters/errorWarningCounter.css
@@ -18,10 +18,6 @@
     margin-left: 6px;
 }
 
-.counter-item label {
-    cursor: inherit;
-}
-
 .counter-item.counter-item-first {
     margin-left: 0;
 }
diff --git a/src/cobalt/debug/remote/devtools/front_end/dom_extension/DOMExtension.js b/src/cobalt/debug/remote/devtools/front_end/dom_extension/DOMExtension.js
index baa2879..ae9eb89 100644
--- a/src/cobalt/debug/remote/devtools/front_end/dom_extension/DOMExtension.js
+++ b/src/cobalt/debug/remote/devtools/front_end/dom_extension/DOMExtension.js
@@ -270,10 +270,12 @@
  */
 Node.prototype.hasSelection = function() {
   // TODO(luoe): use contains(node, {includeShadow: true}) when it is fixed for shadow dom.
-  const contents = this.querySelectorAll('content');
-  for (const content of contents) {
-    if (Array.prototype.some.call(content.getDistributedNodes(), node => node.hasSelection()))
-      return true;
+  if (this instanceof Element) {
+    const slots = this.querySelectorAll('slot');
+    for (const slot of slots) {
+      if (Array.prototype.some.call(slot.assignedNodes(), node => node.hasSelection()))
+        return true;
+    }
   }
 
   const selection = this.getComponentSelection();
@@ -299,10 +301,11 @@
  * @param {string} tagName
  * @param {string=} customElementType
  * @return {!Element}
+ * @suppress {checkTypes}
  * @suppressGlobalPropertiesCheck
  */
 function createElement(tagName, customElementType) {
-  return document.createElement(tagName, customElementType || '');
+  return document.createElement(tagName, {is: customElementType});
 }
 
 /**
@@ -318,10 +321,11 @@
  * @param {string} elementName
  * @param {string=} className
  * @param {string=} customElementType
+ * @suppress {checkTypes}
  * @return {!Element}
  */
 Document.prototype.createElementWithClass = function(elementName, className, customElementType) {
-  const element = this.createElement(elementName, customElementType || '');
+  const element = this.createElement(elementName, {is: customElementType});
   if (className)
     element.className = className;
   return element;
@@ -651,7 +655,7 @@
   if (this.shadowRoot)
     return this.shadowRoot;
 
-  const distributedNodes = this.getDistributedNodes ? this.getDistributedNodes() : [];
+  const distributedNodes = this instanceof HTMLSlotElement ? this.assignedNodes() : [];
 
   if (distributedNodes.length)
     return distributedNodes[0];
@@ -668,7 +672,7 @@
     if (sibling)
       return sibling;
 
-    node = insertionPoint(node) || node.parentNodeOrShadowHost();
+    node = node.assignedSlot || node.parentNodeOrShadowHost();
   }
 
   /**
@@ -676,10 +680,9 @@
    * @return {?Node}
    */
   function nextSibling(node) {
-    const parent = insertionPoint(node);
-    if (!parent)
+    if (!node.assignedSlot)
       return node.nextSibling;
-    const distributedNodes = parent.getDistributedNodes ? parent.getDistributedNodes() : [];
+    const distributedNodes = node.assignedSlot.assignedNodes();
 
     const position = Array.prototype.indexOf.call(distributedNodes, node);
     if (position + 1 < distributedNodes.length)
@@ -687,15 +690,6 @@
     return null;
   }
 
-  /**
-   * @param {!Node} node
-   * @return {?Node}
-   */
-  function insertionPoint(node) {
-    const insertionPoints = node.getDestinationInsertionPoints ? node.getDestinationInsertionPoints() : [];
-    return insertionPoints.length > 0 ? insertionPoints[insertionPoints.length - 1] : null;
-  }
-
   return null;
 };
 
diff --git a/src/cobalt/debug/remote/devtools/front_end/elements/ElementsSidebarPane.js b/src/cobalt/debug/remote/devtools/front_end/elements/ElementsSidebarPane.js
index 5676b09..1f76f3d 100644
--- a/src/cobalt/debug/remote/devtools/front_end/elements/ElementsSidebarPane.js
+++ b/src/cobalt/debug/remote/devtools/front_end/elements/ElementsSidebarPane.js
@@ -5,8 +5,11 @@
  * @unrestricted
  */
 Elements.ElementsSidebarPane = class extends UI.VBox {
-  constructor() {
-    super(true);
+  /**
+   * @param {boolean=} delegatesFocus
+   */
+  constructor(delegatesFocus) {
+    super(true, delegatesFocus);
     this.element.classList.add('flex-none');
     this._computedStyleModel = new Elements.ComputedStyleModel();
     this._computedStyleModel.addEventListener(
diff --git a/src/cobalt/debug/remote/devtools/front_end/elements/StylesSidebarPane.js b/src/cobalt/debug/remote/devtools/front_end/elements/StylesSidebarPane.js
index 2845ba4..7a10535 100644
--- a/src/cobalt/debug/remote/devtools/front_end/elements/StylesSidebarPane.js
+++ b/src/cobalt/debug/remote/devtools/front_end/elements/StylesSidebarPane.js
@@ -29,10 +29,9 @@
 
 Elements.StylesSidebarPane = class extends Elements.ElementsSidebarPane {
   constructor() {
-    super();
+    super(true /* delegatesFocus */);
     this.setMinimumSize(96, 26);
     this.registerRequiredCSS('elements/stylesSidebarPane.css');
-    this.element.tabIndex = -1;
 
     Common.moduleSetting('colorFormat').addChangeListener(this.update.bind(this));
     Common.moduleSetting('textEditorIndent').addChangeListener(this.update.bind(this));
@@ -94,7 +93,7 @@
    * @return {!Element}
    */
   static createExclamationMark(property) {
-    const exclamationElement = createElement('label', 'dt-icon-label');
+    const exclamationElement = createElement('span', 'dt-icon-label');
     exclamationElement.className = 'exclamation-mark';
     if (!Elements.StylesSidebarPane.ignoreErrorsForProperty(property))
       exclamationElement.type = 'smallicon-warning';
diff --git a/src/cobalt/debug/remote/devtools/front_end/elements/classesPaneWidget.css b/src/cobalt/debug/remote/devtools/front_end/elements/classesPaneWidget.css
index ee810fe..01aec13 100644
--- a/src/cobalt/debug/remote/devtools/front_end/elements/classesPaneWidget.css
+++ b/src/cobalt/debug/remote/devtools/front_end/elements/classesPaneWidget.css
@@ -16,7 +16,7 @@
     justify-content: flex-start;
 }
 
-.styles-element-classes-pane label {
+.styles-element-classes-pane [is=dt-checkbox] {
     margin-right: 15px;
 }
 
diff --git a/src/cobalt/debug/remote/devtools/front_end/emulation/DeviceModeView.js b/src/cobalt/debug/remote/devtools/front_end/emulation/DeviceModeView.js
index e77cf26..144a61b 100644
--- a/src/cobalt/debug/remote/devtools/front_end/emulation/DeviceModeView.js
+++ b/src/cobalt/debug/remote/devtools/front_end/emulation/DeviceModeView.js
@@ -74,7 +74,7 @@
     this._bottomResizerElement.title = Common.UIString('Double-click for full height');
 
     this._pageArea = this._screenArea.createChild('div', 'device-mode-page-area');
-    this._pageArea.createChild('content');
+    this._pageArea.createChild('slot');
   }
 
   _populatePresetsContainer() {
diff --git a/src/cobalt/debug/remote/devtools/front_end/emulation/sensors.css b/src/cobalt/debug/remote/devtools/front_end/emulation/sensors.css
index 03c1311..6960e86 100644
--- a/src/cobalt/debug/remote/devtools/front_end/emulation/sensors.css
+++ b/src/cobalt/debug/remote/devtools/front_end/emulation/sensors.css
@@ -9,10 +9,6 @@
     display: block;
 }
 
-.sensors-view label {
-    margin-bottom: 10px;
-}
-
 .sensors-view input {
     width: 100%;
     max-width: 100px;
diff --git a/src/cobalt/debug/remote/devtools/front_end/inline_editor/CSSShadowEditor.js b/src/cobalt/debug/remote/devtools/front_end/inline_editor/CSSShadowEditor.js
index ddaa9f8..83b8b1a 100644
--- a/src/cobalt/debug/remote/devtools/front_end/inline_editor/CSSShadowEditor.js
+++ b/src/cobalt/debug/remote/devtools/front_end/inline_editor/CSSShadowEditor.js
@@ -68,7 +68,7 @@
    * @return {!Element}
    */
   _createSlider(field) {
-    const slider = UI.createSliderLabel(0, InlineEditor.CSSShadowEditor.maxRange, -1);
+    const slider = UI.createSlider(0, InlineEditor.CSSShadowEditor.maxRange, -1);
     slider.addEventListener('input', this._onSliderInput.bind(this), false);
     field.appendChild(slider);
     return slider;
diff --git a/src/cobalt/debug/remote/devtools/front_end/inline_editor/ColorSwatch.js b/src/cobalt/debug/remote/devtools/front_end/inline_editor/ColorSwatch.js
index 5f5eb0b..2dfc68a 100644
--- a/src/cobalt/debug/remote/devtools/front_end/inline_editor/ColorSwatch.js
+++ b/src/cobalt/debug/remote/devtools/front_end/inline_editor/ColorSwatch.js
@@ -7,6 +7,17 @@
 InlineEditor.ColorSwatch = class extends HTMLSpanElement {
   constructor() {
     super();
+    const root = UI.createShadowRootWithCoreStyles(this, 'inline_editor/colorSwatch.css');
+
+    this._iconElement = root.createChild('span', 'color-swatch');
+    this._iconElement.title = Common.UIString('Shift-click to change color format');
+    this._swatchInner = this._iconElement.createChild('span', 'color-swatch-inner');
+    this._swatchInner.addEventListener('dblclick', e => e.consume(), false);
+    this._swatchInner.addEventListener('mousedown', e => e.consume(), false);
+    this._swatchInner.addEventListener('click', this._handleClick.bind(this), true);
+
+    root.createChild('slot');
+    this._colorValueElement = this.createChild('span');
   }
 
   /**
@@ -15,11 +26,11 @@
   static create() {
     if (!InlineEditor.ColorSwatch._constructor) {
       InlineEditor.ColorSwatch._constructor =
-          UI.registerCustomElement('span', 'color-swatch', InlineEditor.ColorSwatch.prototype);
+          UI.registerCustomElement('span', 'color-swatch', InlineEditor.ColorSwatch);
     }
 
 
-    return /** @type {!InlineEditor.ColorSwatch} */ (new InlineEditor.ColorSwatch._constructor());
+    return /** @type {!InlineEditor.ColorSwatch} */ (InlineEditor.ColorSwatch._constructor());
   }
 
   /**
@@ -134,23 +145,6 @@
   }
 
   /**
-   * @override
-   */
-  createdCallback() {
-    const root = UI.createShadowRootWithCoreStyles(this, 'inline_editor/colorSwatch.css');
-
-    this._iconElement = root.createChild('span', 'color-swatch');
-    this._iconElement.title = Common.UIString('Shift-click to change color format');
-    this._swatchInner = this._iconElement.createChild('span', 'color-swatch-inner');
-    this._swatchInner.addEventListener('dblclick', e => e.consume(), false);
-    this._swatchInner.addEventListener('mousedown', e => e.consume(), false);
-    this._swatchInner.addEventListener('click', this._handleClick.bind(this), true);
-
-    root.createChild('content');
-    this._colorValueElement = this.createChild('span');
-  }
-
-  /**
    * @param {!Event} event
    */
   _handleClick(event) {
@@ -168,6 +162,11 @@
 InlineEditor.BezierSwatch = class extends HTMLSpanElement {
   constructor() {
     super();
+    const root = UI.createShadowRootWithCoreStyles(this, 'inline_editor/bezierSwatch.css');
+    this._iconElement = UI.Icon.create('smallicon-bezier', 'bezier-swatch-icon');
+    root.appendChild(this._iconElement);
+    this._textElement = this.createChild('span');
+    root.createChild('slot');
   }
 
   /**
@@ -176,11 +175,11 @@
   static create() {
     if (!InlineEditor.BezierSwatch._constructor) {
       InlineEditor.BezierSwatch._constructor =
-          UI.registerCustomElement('span', 'bezier-swatch', InlineEditor.BezierSwatch.prototype);
+          UI.registerCustomElement('span', 'bezier-swatch', InlineEditor.BezierSwatch);
     }
 
 
-    return /** @type {!InlineEditor.BezierSwatch} */ (new InlineEditor.BezierSwatch._constructor());
+    return /** @type {!InlineEditor.BezierSwatch} */ (InlineEditor.BezierSwatch._constructor());
   }
 
   /**
@@ -210,17 +209,6 @@
   iconElement() {
     return this._iconElement;
   }
-
-  /**
-   * @override
-   */
-  createdCallback() {
-    const root = UI.createShadowRootWithCoreStyles(this, 'inline_editor/bezierSwatch.css');
-    this._iconElement = UI.Icon.create('smallicon-bezier', 'bezier-swatch-icon');
-    root.appendChild(this._iconElement);
-    this._textElement = this.createChild('span');
-    root.createChild('content');
-  }
 };
 
 /**
@@ -229,6 +217,11 @@
 InlineEditor.CSSShadowSwatch = class extends HTMLSpanElement {
   constructor() {
     super();
+    const root = UI.createShadowRootWithCoreStyles(this, 'inline_editor/cssShadowSwatch.css');
+    this._iconElement = UI.Icon.create('smallicon-shadow', 'shadow-swatch-icon');
+    root.appendChild(this._iconElement);
+    root.createChild('slot');
+    this._contentElement = this.createChild('span');
   }
 
   /**
@@ -237,10 +230,10 @@
   static create() {
     if (!InlineEditor.CSSShadowSwatch._constructor) {
       InlineEditor.CSSShadowSwatch._constructor =
-          UI.registerCustomElement('span', 'css-shadow-swatch', InlineEditor.CSSShadowSwatch.prototype);
+          UI.registerCustomElement('span', 'css-shadow-swatch', InlineEditor.CSSShadowSwatch);
     }
 
-    return /** @type {!InlineEditor.CSSShadowSwatch} */ (new InlineEditor.CSSShadowSwatch._constructor());
+    return /** @type {!InlineEditor.CSSShadowSwatch} */ (InlineEditor.CSSShadowSwatch._constructor());
   }
 
   /**
@@ -290,15 +283,4 @@
   colorSwatch() {
     return this._colorSwatch;
   }
-
-  /**
-   * @override
-   */
-  createdCallback() {
-    const root = UI.createShadowRootWithCoreStyles(this, 'inline_editor/cssShadowSwatch.css');
-    this._iconElement = UI.Icon.create('smallicon-shadow', 'shadow-swatch-icon');
-    root.appendChild(this._iconElement);
-    root.createChild('content');
-    this._contentElement = this.createChild('span');
-  }
 };
diff --git a/src/cobalt/debug/remote/devtools/front_end/inspector_main/renderingOptions.css b/src/cobalt/debug/remote/devtools/front_end/inspector_main/renderingOptions.css
index 77eeb51..b478b44 100644
--- a/src/cobalt/debug/remote/devtools/front_end/inspector_main/renderingOptions.css
+++ b/src/cobalt/debug/remote/devtools/front_end/inspector_main/renderingOptions.css
@@ -8,7 +8,7 @@
     padding: 12px;
  }
 
-label {
+[is=dt-checkbox] {
     margin: 0px 0px 10px 0px;
     flex: none;
 }
diff --git a/src/cobalt/debug/remote/devtools/front_end/layers_test_runner/LayersTestRunner.js b/src/cobalt/debug/remote/devtools/front_end/layers_test_runner/LayersTestRunner.js
index d5c69d7..20e51c3 100644
--- a/src/cobalt/debug/remote/devtools/front_end/layers_test_runner/LayersTestRunner.js
+++ b/src/cobalt/debug/remote/devtools/front_end/layers_test_runner/LayersTestRunner.js
@@ -106,7 +106,8 @@
     screenY: totalOffset.top - element.scrollTop + offsetY,
     clientX: totalOffset.left + offsetX,
     clientY: totalOffset.top + offsetY,
-    button: button
+    button: button,
+    composed: true
   };
 
   if (eventType === 'mouseout') {
diff --git a/src/cobalt/debug/remote/devtools/front_end/network/RequestHeadersView.js b/src/cobalt/debug/remote/devtools/front_end/network/RequestHeadersView.js
index 5c5b4de..2d9ee3e 100644
--- a/src/cobalt/debug/remote/devtools/front_end/network/RequestHeadersView.js
+++ b/src/cobalt/debug/remote/devtools/front_end/network/RequestHeadersView.js
@@ -379,7 +379,7 @@
       statusCodeFragment.createChild('div', 'header-name').textContent = Common.UIString('Status Code') + ': ';
       statusCodeFragment.createChild('span', 'header-separator');
 
-      const statusCodeImage = statusCodeFragment.createChild('label', 'resource-status-image', 'dt-icon-label');
+      const statusCodeImage = statusCodeFragment.createChild('span', 'resource-status-image', 'dt-icon-label');
       statusCodeImage.title = this._request.statusCode + ' ' + this._request.statusText;
 
       if (this._request.statusCode < 300 || this._request.statusCode === 304)
@@ -440,7 +440,7 @@
     if (provisionalHeaders) {
       const cautionText = Common.UIString('Provisional headers are shown');
       const cautionFragment = createDocumentFragment();
-      cautionFragment.createChild('label', '', 'dt-icon-label').type = 'smallicon-warning';
+      cautionFragment.createChild('span', '', 'dt-icon-label').type = 'smallicon-warning';
       cautionFragment.createChild('div', 'caution').textContent = cautionText;
       const cautionTreeElement = new UI.TreeElement(cautionFragment);
       cautionTreeElement.selectable = false;
diff --git a/src/cobalt/debug/remote/devtools/front_end/network/networkConfigView.css b/src/cobalt/debug/remote/devtools/front_end/network/networkConfigView.css
index 5c9f81f..6377126 100644
--- a/src/cobalt/debug/remote/devtools/front_end/network/networkConfigView.css
+++ b/src/cobalt/debug/remote/devtools/front_end/network/networkConfigView.css
@@ -57,11 +57,11 @@
     line-height: 20px;
 }
 
-.network-config-ua label[is="dt-radio"].checked > * {
+.network-config-ua span[is="dt-radio"].checked > * {
     display: none
 }
 
-.network-config-ua input:not(.dt-radio-button) {
+.network-config-ua input {
     display: block;
     width: calc(100% - 20px);
     max-width: 250px;
diff --git a/src/cobalt/debug/remote/devtools/front_end/network/networkLogView.css b/src/cobalt/debug/remote/devtools/front_end/network/networkLogView.css
index b7240de..bf39802 100644
--- a/src/cobalt/debug/remote/devtools/front_end/network/networkLogView.css
+++ b/src/cobalt/debug/remote/devtools/front_end/network/networkLogView.css
@@ -44,7 +44,7 @@
     overflow: hidden;
 }
 
-.network-summary-bar label[is=dt-icon-label] {
+.network-summary-bar span[is=dt-icon-label] {
     margin-right: 6px;
 }
 
diff --git a/src/cobalt/debug/remote/devtools/front_end/object_ui/ObjectPropertiesSection.js b/src/cobalt/debug/remote/devtools/front_end/object_ui/ObjectPropertiesSection.js
index 88a7f85..5cdc53f 100644
--- a/src/cobalt/debug/remote/devtools/front_end/object_ui/ObjectPropertiesSection.js
+++ b/src/cobalt/debug/remote/devtools/front_end/object_ui/ObjectPropertiesSection.js
@@ -426,7 +426,7 @@
    * @param {!Array.<!SDK.RemoteObjectProperty>=} extraProperties
    */
   constructor(object, linkifier, emptyPlaceholder, ignoreHasOwnProperty, extraProperties) {
-    const contentElement = createElement('content');
+    const contentElement = createElement('slot');
     super(contentElement);
 
     this._object = object;
diff --git a/src/cobalt/debug/remote/devtools/front_end/profiler/profilesPanel.css b/src/cobalt/debug/remote/devtools/front_end/profiler/profilesPanel.css
index eab0f1d..4a5384a 100644
--- a/src/cobalt/debug/remote/devtools/front_end/profiler/profilesPanel.css
+++ b/src/cobalt/debug/remote/devtools/front_end/profiler/profilesPanel.css
@@ -128,7 +128,7 @@
     margin: 0 0 5px 0;
 }
 
-.profile-launcher-view-content label {
+.profile-launcher-view-content [is=dt-radio] {
     font-size: 13px;
 }
 
@@ -165,7 +165,7 @@
     margin-left: 22px;
 }
 
-.profile-launcher-view-content p label {
+.profile-launcher-view-content p [is=dt-checkbox] {
     display: flex;
 }
 
@@ -190,7 +190,7 @@
     to { background-color: rgba(255, 255, 120, 0); }
 }
 
-.profile-canvas-decoration label[is=dt-icon-label] {
+.profile-canvas-decoration span[is=dt-icon-label] {
     margin-right: 4px;
 }
 
diff --git a/src/cobalt/debug/remote/devtools/front_end/resources/ApplicationCacheItemsView.js b/src/cobalt/debug/remote/devtools/front_end/resources/ApplicationCacheItemsView.js
index 6c2373b..4f9dcfd 100644
--- a/src/cobalt/debug/remote/devtools/front_end/resources/ApplicationCacheItemsView.js
+++ b/src/cobalt/debug/remote/devtools/front_end/resources/ApplicationCacheItemsView.js
@@ -38,9 +38,9 @@
     this._deleteButton.setVisible(false);
     this._deleteButton.addEventListener(UI.ToolbarButton.Events.Click, this._deleteButtonClicked, this);
 
-    this._connectivityIcon = createElement('label', 'dt-icon-label');
+    this._connectivityIcon = createElement('span', 'dt-icon-label');
     this._connectivityIcon.style.margin = '0 2px 0 5px';
-    this._statusIcon = createElement('label', 'dt-icon-label');
+    this._statusIcon = createElement('span', 'dt-icon-label');
     this._statusIcon.style.margin = '0 2px 0 5px';
 
     this._frameId = frameId;
diff --git a/src/cobalt/debug/remote/devtools/front_end/security/SecurityPanel.js b/src/cobalt/debug/remote/devtools/front_end/security/SecurityPanel.js
index 44f890f..693249d 100644
--- a/src/cobalt/debug/remote/devtools/front_end/security/SecurityPanel.js
+++ b/src/cobalt/debug/remote/devtools/front_end/security/SecurityPanel.js
@@ -78,7 +78,7 @@
       return text;
     }
 
-    const highlightedUrl = createElement('span', 'url-text');
+    const highlightedUrl = createElement('span');
 
     const scheme = url.substr(0, index);
     const content = url.substr(index + schemeSeparator.length);
diff --git a/src/cobalt/debug/remote/devtools/front_end/settings/settingsScreen.css b/src/cobalt/debug/remote/devtools/front_end/settings/settingsScreen.css
index 47c80a7..9989041 100644
--- a/src/cobalt/debug/remote/devtools/front_end/settings/settingsScreen.css
+++ b/src/cobalt/debug/remote/devtools/front_end/settings/settingsScreen.css
@@ -211,15 +211,11 @@
     margin-left: 10px;
 }
 
-.settings-indent-labels label {
-    padding-left: 10px;
-}
-
 .settings-experiment-hidden {
     display: none;
 }
 
-.settings-experiment-hidden label {
+.settings-experiment-hidden [is=dt-checkbox] {
     background-color: #ddd;
 }
 
diff --git a/src/cobalt/debug/remote/devtools/front_end/sources/UISourceCodeFrame.js b/src/cobalt/debug/remote/devtools/front_end/sources/UISourceCodeFrame.js
index 47de620..27ee88e 100644
--- a/src/cobalt/debug/remote/devtools/front_end/sources/UISourceCodeFrame.js
+++ b/src/cobalt/debug/remote/devtools/front_end/sources/UISourceCodeFrame.js
@@ -582,7 +582,7 @@
     this._icon = this.element.createChild('label', '', 'dt-icon-label');
     this._icon.type = Sources.UISourceCodeFrame._iconClassPerLevel[message.level()];
     this._repeatCountElement =
-        this.element.createChild('label', 'text-editor-row-message-repeat-count hidden', 'dt-small-bubble');
+        this.element.createChild('span', 'text-editor-row-message-repeat-count hidden', 'dt-small-bubble');
     this._repeatCountElement.type = Sources.UISourceCodeFrame._bubbleTypePerLevel[message.level()];
     const linesContainer = this.element.createChild('div');
     const lines = this._message.text().split('\n');
@@ -634,7 +634,7 @@
     this._decoration = createElementWithClass('div', 'text-editor-line-decoration');
     this._decoration._messageBucket = this;
     this._wave = this._decoration.createChild('div', 'text-editor-line-decoration-wave');
-    this._icon = this._wave.createChild('label', 'text-editor-line-decoration-icon', 'dt-icon-label');
+    this._icon = this._wave.createChild('span', 'text-editor-line-decoration-icon', 'dt-icon-label');
     /** @type {?number} */
     this._decorationStartColumn = null;
 
diff --git a/src/cobalt/debug/remote/devtools/front_end/sources/sourcesPanel.css b/src/cobalt/debug/remote/devtools/front_end/sources/sourcesPanel.css
index 4b49787..17d4d4a 100644
--- a/src/cobalt/debug/remote/devtools/front_end/sources/sourcesPanel.css
+++ b/src/cobalt/debug/remote/devtools/front_end/sources/sourcesPanel.css
@@ -50,7 +50,7 @@
     margin-top: 0;
 }
 
-.scripts-debug-toolbar-drawer > label {
+.scripts-debug-toolbar-drawer > [is=dt-checkbox] {
     display: flex;
     padding-left: 3px;
     height: 28px;
diff --git a/src/cobalt/debug/remote/devtools/front_end/timeline/TimelineHistoryManager.js b/src/cobalt/debug/remote/devtools/front_end/timeline/TimelineHistoryManager.js
index 7bd3fc0..ecd5308 100644
--- a/src/cobalt/debug/remote/devtools/front_end/timeline/TimelineHistoryManager.js
+++ b/src/cobalt/debug/remote/devtools/front_end/timeline/TimelineHistoryManager.js
@@ -426,12 +426,11 @@
    * @param {!UI.Action} action
    */
   constructor(action) {
-    super(createElementWithClass('button', 'dropdown-button'));
-    const shadowRoot = UI.createShadowRootWithCoreStyles(this.element, 'timeline/historyToolbarButton.css');
-
-    this._contentElement = shadowRoot.createChild('span', 'content');
+    super(createElementWithClass('button', 'history-dropdown-button'));
+    UI.appendStyle(this.element, 'timeline/historyToolbarButton.css');
+    this._contentElement = this.element.createChild('span', 'content');
     const dropdownArrowIcon = UI.Icon.create('smallicon-triangle-down');
-    shadowRoot.appendChild(dropdownArrowIcon);
+    this.element.appendChild(dropdownArrowIcon);
     this.element.addEventListener('click', () => void action.execute(), false);
     this.setEnabled(action.enabled());
     action.addEventListener(UI.Action.Events.Enabled, event => this.setEnabled(/** @type {boolean} */ (event.data)));
diff --git a/src/cobalt/debug/remote/devtools/front_end/timeline/historyToolbarButton.css b/src/cobalt/debug/remote/devtools/front_end/timeline/historyToolbarButton.css
index f66188b..0259abf 100644
--- a/src/cobalt/debug/remote/devtools/front_end/timeline/historyToolbarButton.css
+++ b/src/cobalt/debug/remote/devtools/front_end/timeline/historyToolbarButton.css
@@ -4,18 +4,18 @@
  * found in the LICENSE file.
  */
 
-:host {
+.history-dropdown-button {
   width: 160px;
   height: 26px;
   text-align: left;
   display: flex;
 }
 
-:host([disabled]) {
+.history-dropdown-button[disabled] {
   opacity: .5;
 }
 
-.content {
+.history-dropdown-button > .content {
   padding-right: 5px;
   overflow: hidden;
   text-overflow: ellipsis;
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/HistoryInput.js b/src/cobalt/debug/remote/devtools/front_end/ui/HistoryInput.js
index b5cefa7..81784fc 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/HistoryInput.js
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/HistoryInput.js
@@ -5,24 +5,21 @@
  * @unrestricted
  */
 UI.HistoryInput = class extends HTMLInputElement {
+  constructor() {
+    super();
+    this._history = [''];
+    this._historyPosition = 0;
+    this.addEventListener('keydown', this._onKeyDown.bind(this), false);
+    this.addEventListener('input', this._onInput.bind(this), false);
+  }
   /**
    * @return {!UI.HistoryInput}
    */
   static create() {
     if (!UI.HistoryInput._constructor)
-      UI.HistoryInput._constructor = UI.registerCustomElement('input', 'history-input', UI.HistoryInput.prototype);
+      UI.HistoryInput._constructor = UI.registerCustomElement('input', 'history-input', UI.HistoryInput);
 
-    return /** @type {!UI.HistoryInput} */ (new UI.HistoryInput._constructor());
-  }
-
-  /**
-   * @override
-   */
-  createdCallback() {
-    this._history = [''];
-    this._historyPosition = 0;
-    this.addEventListener('keydown', this._onKeyDown.bind(this), false);
-    this.addEventListener('input', this._onInput.bind(this), false);
+    return /** @type {!UI.HistoryInput} */ (UI.HistoryInput._constructor());
   }
 
   /**
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/Icon.js b/src/cobalt/debug/remote/devtools/front_end/ui/Icon.js
index 9ba57c8..2df6469 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/Icon.js
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/Icon.js
@@ -2,14 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/**
- * @constructor
- * @extends {HTMLSpanElement}
- */
 UI.Icon = class extends HTMLSpanElement {
   constructor() {
     super();
-    throw new Error('icon must be created via factory method.');
+    /** @type {?UI.Icon.Descriptor} */
+    this._descriptor = null;
+    /** @type {?UI.Icon.SpriteSheet} */
+    this._spriteSheet = null;
+    /** @type {string} */
+    this._iconType = '';
   }
 
   /**
@@ -19,9 +20,9 @@
    */
   static create(iconType, className) {
     if (!UI.Icon._constructor)
-      UI.Icon._constructor = UI.registerCustomElement('span', 'ui-icon', UI.Icon.prototype);
+      UI.Icon._constructor = UI.registerCustomElement('span', 'ui-icon', UI.Icon);
 
-    const icon = /** @type {!UI.Icon} */ (new UI.Icon._constructor());
+    const icon = /** @type {!UI.Icon} */ (UI.Icon._constructor());
     if (className)
       icon.className = className;
     if (iconType)
@@ -30,18 +31,6 @@
   }
 
   /**
-   * @override
-   */
-  createdCallback() {
-    /** @type {?UI.Icon.Descriptor} */
-    this._descriptor = null;
-    /** @type {?UI.Icon.SpriteSheet} */
-    this._spriteSheet = null;
-    /** @type {string} */
-    this._iconType = '';
-  }
-
-  /**
    * @param {string} iconType
    */
   setIconType(iconType) {
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/ListWidget.js b/src/cobalt/debug/remote/devtools/front_end/ui/ListWidget.js
index c11ae3a..0ba4777 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/ListWidget.js
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/ListWidget.js
@@ -9,12 +9,11 @@
    * @param {!UI.ListWidget.Delegate<T>} delegate
    */
   constructor(delegate) {
-    super(true);
+    super(true, true /* delegatesFocus */);
     this.registerRequiredCSS('ui/listWidget.css');
     this._delegate = delegate;
 
     this._list = this.contentElement.createChild('div', 'list');
-    this.element.tabIndex = -1;
 
     this._lastSeparator = false;
     /** @type {?UI.ElementFocusRestorer} */
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/SearchableView.js b/src/cobalt/debug/remote/devtools/front_end/ui/SearchableView.js
index a0fa78f..f03af0f 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/SearchableView.js
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/SearchableView.js
@@ -46,7 +46,7 @@
     this._setting = settingName ? Common.settings.createSetting(settingName, {}) : null;
     this._replaceable = false;
 
-    this.contentElement.createChild('content');
+    this.contentElement.createChild('slot');
     this._footerElementContainer = this.contentElement.createChild('div', 'search-bar hidden');
     this._footerElementContainer.style.order = 100;
     this._footerElement = this._footerElementContainer.createChild('div', 'toolbar-search');
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/SoftDropDown.js b/src/cobalt/debug/remote/devtools/front_end/ui/SoftDropDown.js
index 88e8e97..6d21c34 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/SoftDropDown.js
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/SoftDropDown.js
@@ -16,10 +16,10 @@
     this._model = model;
 
     this.element = createElementWithClass('button', 'soft-dropdown');
-    const shadowRoot = UI.createShadowRootWithCoreStyles(this.element, 'ui/softDropDownButton.css');
-    this._titleElement = shadowRoot.createChild('span', 'title');
+    UI.appendStyle(this.element, 'ui/softDropDownButton.css');
+    this._titleElement = this.element.createChild('span', 'title');
     const dropdownArrowIcon = UI.Icon.create('smallicon-triangle-down');
-    shadowRoot.appendChild(dropdownArrowIcon);
+    this.element.appendChild(dropdownArrowIcon);
 
     this._glassPane = new UI.GlassPane();
     this._glassPane.setMarginBehavior(UI.GlassPane.MarginBehavior.NoMargin);
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/SplitWidget.js b/src/cobalt/debug/remote/devtools/front_end/ui/SplitWidget.js
index f0b55ed..950b230 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/SplitWidget.js
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/SplitWidget.js
@@ -46,10 +46,10 @@
     this.contentElement.classList.add('shadow-split-widget');
     this._mainElement =
         this.contentElement.createChild('div', 'shadow-split-widget-contents shadow-split-widget-main vbox');
-    this._mainElement.createChild('content').select = '.insertion-point-main';
+    this._mainElement.createChild('slot').name = 'insertion-point-main';
     this._sidebarElement =
         this.contentElement.createChild('div', 'shadow-split-widget-contents shadow-split-widget-sidebar vbox');
-    this._sidebarElement.createChild('content').select = '.insertion-point-sidebar';
+    this._sidebarElement.createChild('slot').name = 'insertion-point-sidebar';
     this._resizerElement = this.contentElement.createChild('div', 'shadow-split-widget-resizer');
     this._resizerElementSize = null;
 
@@ -165,8 +165,7 @@
       this._mainWidget.detach();
     this._mainWidget = widget;
     if (widget) {
-      widget.element.classList.add('insertion-point-main');
-      widget.element.classList.remove('insertion-point-sidebar');
+      widget.element.slot = 'insertion-point-main';
       if (this._showMode === UI.SplitWidget.ShowMode.OnlyMain || this._showMode === UI.SplitWidget.ShowMode.Both)
         widget.show(this.element);
     }
@@ -184,8 +183,7 @@
       this._sidebarWidget.detach();
     this._sidebarWidget = widget;
     if (widget) {
-      widget.element.classList.add('insertion-point-sidebar');
-      widget.element.classList.remove('insertion-point-main');
+      widget.element.slot = 'insertion-point-sidebar';
       if (this._showMode === UI.SplitWidget.ShowMode.OnlySidebar || this._showMode === UI.SplitWidget.ShowMode.Both)
         widget.show(this.element);
     }
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/TabbedPane.js b/src/cobalt/debug/remote/devtools/front_end/ui/TabbedPane.js
index 44db82d..dfad749 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/TabbedPane.js
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/TabbedPane.js
@@ -46,7 +46,7 @@
     this._tabsElement.addEventListener('keydown', this._keyDown.bind(this), false);
     this._contentElement = this.contentElement.createChild('div', 'tabbed-pane-content');
     this._contentElement.setAttribute('role', 'tabpanel');
-    this._contentElement.createChild('content');
+    this._contentElement.createChild('slot');
     /** @type {!Array.<!UI.TabbedPaneTab>} */
     this._tabs = [];
     /** @type {!Array.<!UI.TabbedPaneTab>} */
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/Toolbar.js b/src/cobalt/debug/remote/devtools/front_end/ui/Toolbar.js
index 790a753..4f8d0bf 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/Toolbar.js
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/Toolbar.js
@@ -45,7 +45,7 @@
     this._enabled = true;
     this._shadowRoot = UI.createShadowRootWithCoreStyles(this.element, 'ui/toolbar.css');
     this._contentElement = this._shadowRoot.createChild('div', 'toolbar-shadow');
-    this._insertionPoint = this._contentElement.createChild('content');
+    this._insertionPoint = this._contentElement.createChild('slot');
   }
 
   /**
@@ -265,7 +265,7 @@
       delete item._toolbar;
     this._items = [];
     this._contentElement.removeChildren();
-    this._insertionPoint = this._contentElement.createChild('content');
+    this._insertionPoint = this._contentElement.createChild('slot');
   }
 
   /**
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/Tooltip.js b/src/cobalt/debug/remote/devtools/front_end/ui/Tooltip.js
index a8034a0..253f22f 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/Tooltip.js
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/Tooltip.js
@@ -54,7 +54,7 @@
    */
   _mouseMove(event) {
     const mouseEvent = /** @type {!MouseEvent} */ (event);
-    const path = mouseEvent.path;
+    const path = mouseEvent.composedPath();
     if (!path || mouseEvent.buttons !== 0 || (mouseEvent.movementX === 0 && mouseEvent.movementY === 0))
       return;
 
@@ -63,7 +63,8 @@
 
     for (const element of path) {
       // The offsetParent is null when the element or an ancestor has 'display: none'.
-      if (element === this._anchorElement || (element.nodeName !== 'CONTENT' && element.offsetParent === null)) {
+      if (!(element instanceof Element) || element === this._anchorElement ||
+          (element.nodeName !== 'SLOT' && element.offsetParent === null)) {
         return;
       } else if (element[UI.Tooltip._symbol]) {
         this._show(element, mouseEvent);
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/UIUtils.js b/src/cobalt/debug/remote/devtools/front_end/ui/UIUtils.js
index 9c2761c..21e6412 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/UIUtils.js
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/UIUtils.js
@@ -674,9 +674,7 @@
  * @param {!Element} element
  */
 UI.installComponentRootStyles = function(element) {
-  UI.appendStyle(element, 'ui/inspectorCommon.css');
-  UI.themeSupport.injectHighlightStyleSheets(element);
-  UI.themeSupport.injectCustomStyleSheets(element);
+  UI._injectCoreStyles(element);
   element.classList.add('platform-' + Host.platform());
 
   // Detect overlay scrollbar enable by checking for nonzero scrollbar width.
@@ -704,13 +702,12 @@
 /**
  * @param {!Element} element
  * @param {string=} cssFile
+ * @param {boolean=} delegatesFocus
  * @return {!DocumentFragment}
  */
-UI.createShadowRootWithCoreStyles = function(element, cssFile) {
-  const shadowRoot = element.createShadowRoot();
-  UI.appendStyle(shadowRoot, 'ui/inspectorCommon.css');
-  UI.themeSupport.injectHighlightStyleSheets(shadowRoot);
-  UI.themeSupport.injectCustomStyleSheets(shadowRoot);
+UI.createShadowRootWithCoreStyles = function(element, cssFile, delegatesFocus) {
+  const shadowRoot = element.attachShadow({mode: 'open', delegatesFocus});
+  UI._injectCoreStyles(shadowRoot);
   if (cssFile)
     UI.appendStyle(shadowRoot, cssFile);
   shadowRoot.addEventListener('focus', UI._focusChanged.bind(UI), true);
@@ -718,6 +715,16 @@
 };
 
 /**
+ * @param {!Element|!ShadowRoot} root
+ */
+UI._injectCoreStyles = function(root) {
+  UI.appendStyle(root, 'ui/inspectorCommon.css');
+  UI.appendStyle(root, 'ui/textButton.css');
+  UI.themeSupport.injectHighlightStyleSheets(root);
+  UI.themeSupport.injectCustomStyleSheets(root);
+};
+
+/**
  * @param {!Document} document
  * @param {!Event} event
  */
@@ -1166,13 +1173,19 @@
 /**
  * @param {string} localName
  * @param {string} typeExtension
- * @param {!Object} prototype
+ * @param {function(new:HTMLElement, *)} definition
  * @return {function()}
  * @suppressGlobalPropertiesCheck
- * @template T
  */
-UI.registerCustomElement = function(localName, typeExtension, prototype) {
-  return document.registerElement(typeExtension, {prototype: Object.create(prototype), extends: localName});
+UI.registerCustomElement = function(localName, typeExtension, definition) {
+  self.customElements.define(typeExtension, class extends definition {
+    constructor() {
+      super();
+      // TODO(einbinder) convert to classes and custom element tags
+      this.setAttribute('is', typeExtension);
+    }
+  }, {extends: localName});
+  return () => createElement(localName, typeExtension);
 };
 
 /**
@@ -1183,12 +1196,14 @@
  * @return {!Element}
  */
 UI.createTextButton = function(text, clickHandler, className, primary) {
-  const element = createElementWithClass('button', className || '', 'text-button');
+  const element = createElementWithClass('button', className || '');
   element.textContent = text;
+  element.classList.add('text-button');
   if (primary)
     element.classList.add('primary-button');
   if (clickHandler)
     element.addEventListener('click', clickHandler, false);
+  element.type = 'button';
   return element;
 };
 
@@ -1213,10 +1228,10 @@
  * @return {!Element}
  */
 UI.createRadioLabel = function(name, title, checked) {
-  const element = createElement('label', 'dt-radio');
+  const element = createElement('span', 'dt-radio');
   element.radioElement.name = name;
   element.radioElement.checked = !!checked;
-  element.createTextChild(title);
+  element.labelElement.createTextChild(title);
   return element;
 };
 
@@ -1226,7 +1241,7 @@
  * @return {!Element}
  */
 UI.createLabel = function(title, iconClass) {
-  const element = createElement('label', 'dt-icon-label');
+  const element = createElement('span', 'dt-icon-label');
   element.createChild('span').textContent = title;
   element.type = iconClass;
   return element;
@@ -1238,8 +1253,8 @@
  * @param {number} max
  * @param {number} tabIndex
  */
-UI.createSliderLabel = function(min, max, tabIndex) {
-  const element = createElement('label', 'dt-slider');
+UI.createSlider = function(min, max, tabIndex) {
+  const element = createElement('span', 'dt-slider');
   element.sliderElement.min = min;
   element.sliderElement.max = max;
   element.sliderElement.step = 1;
@@ -1270,10 +1285,7 @@
   }
 };
 
-/**
- * @extends {HTMLLabelElement}
- */
-UI.CheckboxLabel = class extends HTMLLabelElement {
+UI.CheckboxLabel = class extends HTMLSpanElement {
   constructor() {
     super();
     /** @type {!DocumentFragment} */
@@ -1282,13 +1294,6 @@
     this.checkboxElement;
     /** @type {!Element} */
     this.textElement;
-    throw new Error('Checkbox must be created via factory method.');
-  }
-
-  /**
-   * @override
-   */
-  createdCallback() {
     UI.CheckboxLabel._lastId = (UI.CheckboxLabel._lastId || 0) + 1;
     const id = 'ui-checkbox-label' + UI.CheckboxLabel._lastId;
     this._shadowRoot = UI.createShadowRootWithCoreStyles(this, 'ui/checkboxTextLabel.css');
@@ -1297,7 +1302,7 @@
     this.checkboxElement.setAttribute('id', id);
     this.textElement = this._shadowRoot.createChild('label', 'dt-checkbox-text');
     this.textElement.setAttribute('for', id);
-    this._shadowRoot.createChild('content');
+    this._shadowRoot.createChild('slot');
   }
 
   /**
@@ -1308,8 +1313,8 @@
    */
   static create(title, checked, subtitle) {
     if (!UI.CheckboxLabel._constructor)
-      UI.CheckboxLabel._constructor = UI.registerCustomElement('label', 'dt-checkbox', UI.CheckboxLabel.prototype);
-    const element = /** @type {!UI.CheckboxLabel} */ (new UI.CheckboxLabel._constructor());
+      UI.CheckboxLabel._constructor = UI.registerCustomElement('span', 'dt-checkbox', UI.CheckboxLabel);
+    const element = /** @type {!UI.CheckboxLabel} */ (UI.CheckboxLabel._constructor());
     element.checkboxElement.checked = !!checked;
     if (title !== undefined) {
       element.textElement.textContent = title;
@@ -1350,152 +1355,124 @@
 };
 
 (function() {
-  UI.registerCustomElement('button', 'text-button', {
-    /**
-     * @this {Element}
-     */
-    createdCallback: function() {
-      this.type = 'button';
-      const root = UI.createShadowRootWithCoreStyles(this, 'ui/textButton.css');
-      root.createChild('content');
-    },
+let labelId = 0;
+UI.registerCustomElement('span', 'dt-radio', class extends HTMLSpanElement {
+  constructor() {
+    super();
+    this.radioElement = this.createChild('input', 'dt-radio-button');
+    this.labelElement = this.createChild('label');
 
-    __proto__: HTMLButtonElement.prototype
-  });
+    const id = 'dt-radio-button-id' + (++labelId);
+    this.radioElement.id = id;
+    this.radioElement.type = 'radio';
+    this.labelElement.htmlFor = id;
+    const root = UI.createShadowRootWithCoreStyles(this, 'ui/radioButton.css');
+    root.createChild('slot');
+    this.addEventListener('click', radioClickHandler, false);
+  }
+});
 
-  UI.registerCustomElement('label', 'dt-radio', {
-    /**
-     * @this {Element}
-     */
-    createdCallback: function() {
-      this.radioElement = this.createChild('input', 'dt-radio-button');
-      this.radioElement.type = 'radio';
-      const root = UI.createShadowRootWithCoreStyles(this, 'ui/radioButton.css');
-      root.createChild('content').select = '.dt-radio-button';
-      root.createChild('content');
-      this.addEventListener('click', radioClickHandler, false);
-    },
-
-    __proto__: HTMLLabelElement.prototype
-  });
-
-  /**
+/**
    * @param {!Event} event
    * @suppressReceiverCheck
    * @this {Element}
    */
-  function radioClickHandler(event) {
-    if (this.radioElement.checked || this.radioElement.disabled)
-      return;
-    this.radioElement.checked = true;
-    this.radioElement.dispatchEvent(new Event('change'));
+function radioClickHandler(event) {
+  if (this.radioElement.checked || this.radioElement.disabled)
+    return;
+  this.radioElement.checked = true;
+  this.radioElement.dispatchEvent(new Event('change'));
+}
+
+UI.registerCustomElement('span', 'dt-icon-label', class extends HTMLSpanElement {
+  constructor() {
+    super();
+    const root = UI.createShadowRootWithCoreStyles(this);
+    this._iconElement = UI.Icon.create();
+    this._iconElement.style.setProperty('margin-right', '4px');
+    root.appendChild(this._iconElement);
+    root.createChild('slot');
   }
 
-  UI.registerCustomElement('label', 'dt-icon-label', {
-    /**
-     * @this {Element}
-     */
-    createdCallback: function() {
-      const root = UI.createShadowRootWithCoreStyles(this);
-      this._iconElement = UI.Icon.create();
-      this._iconElement.style.setProperty('margin-right', '4px');
-      root.appendChild(this._iconElement);
-      root.createChild('content');
-    },
-
-    /**
+  /**
      * @param {string} type
      * @this {Element}
      */
-    set type(type) {
-      this._iconElement.setIconType(type);
-    },
+  set type(type) {
+    this._iconElement.setIconType(type);
+  }
+});
 
-    __proto__: HTMLLabelElement.prototype
-  });
+UI.registerCustomElement('span', 'dt-slider', class extends HTMLSpanElement {
+  constructor() {
+    super();
+    const root = UI.createShadowRootWithCoreStyles(this, 'ui/slider.css');
+    this.sliderElement = createElementWithClass('input', 'dt-range-input');
+    this.sliderElement.type = 'range';
+    root.appendChild(this.sliderElement);
+  }
 
-  UI.registerCustomElement('label', 'dt-slider', {
-    /**
-     * @this {Element}
-     */
-    createdCallback: function() {
-      const root = UI.createShadowRootWithCoreStyles(this, 'ui/slider.css');
-      this.sliderElement = createElementWithClass('input', 'dt-range-input');
-      this.sliderElement.type = 'range';
-      root.appendChild(this.sliderElement);
-    },
-
-    /**
+  /**
      * @param {number} amount
      * @this {Element}
      */
-    set value(amount) {
-      this.sliderElement.value = amount;
-    },
+  set value(amount) {
+    this.sliderElement.value = amount;
+  }
 
-    /**
+  /**
      * @this {Element}
      */
-    get value() {
-      return this.sliderElement.value;
-    },
+  get value() {
+    return this.sliderElement.value;
+  }
+});
 
-    __proto__: HTMLLabelElement.prototype
-  });
+UI.registerCustomElement('span', 'dt-small-bubble', class extends HTMLSpanElement {
+  constructor() {
+    super();
+    const root = UI.createShadowRootWithCoreStyles(this, 'ui/smallBubble.css');
+    this._textElement = root.createChild('div');
+    this._textElement.className = 'info';
+    this._textElement.createChild('slot');
+  }
 
-  UI.registerCustomElement('label', 'dt-small-bubble', {
-    /**
-     * @this {Element}
-     */
-    createdCallback: function() {
-      const root = UI.createShadowRootWithCoreStyles(this, 'ui/smallBubble.css');
-      this._textElement = root.createChild('div');
-      this._textElement.className = 'info';
-      this._textElement.createChild('content');
-    },
-
-    /**
+  /**
      * @param {string} type
      * @this {Element}
      */
-    set type(type) {
-      this._textElement.className = type;
-    },
+  set type(type) {
+    this._textElement.className = type;
+  }
+});
 
-    __proto__: HTMLLabelElement.prototype
-  });
+UI.registerCustomElement('div', 'dt-close-button', class extends HTMLDivElement {
+  constructor() {
+    super();
+    const root = UI.createShadowRootWithCoreStyles(this, 'ui/closeButton.css');
+    this._buttonElement = root.createChild('div', 'close-button');
+    const regularIcon = UI.Icon.create('smallicon-cross', 'default-icon');
+    this._hoverIcon = UI.Icon.create('mediumicon-red-cross-hover', 'hover-icon');
+    this._activeIcon = UI.Icon.create('mediumicon-red-cross-active', 'active-icon');
+    this._buttonElement.appendChild(regularIcon);
+    this._buttonElement.appendChild(this._hoverIcon);
+    this._buttonElement.appendChild(this._activeIcon);
+  }
 
-  UI.registerCustomElement('div', 'dt-close-button', {
-    /**
-     * @this {Element}
-     */
-    createdCallback: function() {
-      const root = UI.createShadowRootWithCoreStyles(this, 'ui/closeButton.css');
-      this._buttonElement = root.createChild('div', 'close-button');
-      const regularIcon = UI.Icon.create('smallicon-cross', 'default-icon');
-      this._hoverIcon = UI.Icon.create('mediumicon-red-cross-hover', 'hover-icon');
-      this._activeIcon = UI.Icon.create('mediumicon-red-cross-active', 'active-icon');
-      this._buttonElement.appendChild(regularIcon);
-      this._buttonElement.appendChild(this._hoverIcon);
-      this._buttonElement.appendChild(this._activeIcon);
-    },
-
-    /**
+  /**
      * @param {boolean} gray
      * @this {Element}
      */
-    set gray(gray) {
-      if (gray) {
-        this._hoverIcon.setIconType('mediumicon-gray-cross-hover');
-        this._activeIcon.setIconType('mediumicon-gray-cross-active');
-      } else {
-        this._hoverIcon.setIconType('mediumicon-red-cross-hover');
-        this._activeIcon.setIconType('mediumicon-red-cross-active');
-      }
-    },
-
-    __proto__: HTMLDivElement.prototype
-  });
+  set gray(gray) {
+    if (gray) {
+      this._hoverIcon.setIconType('mediumicon-gray-cross-hover');
+      this._activeIcon.setIconType('mediumicon-gray-cross-active');
+    } else {
+      this._hoverIcon.setIconType('mediumicon-red-cross-hover');
+      this._activeIcon.setIconType('mediumicon-red-cross-active');
+    }
+  }
+});
 })();
 
 /**
@@ -1681,7 +1658,7 @@
   }
 
   /**
-   * @param {!Element} element
+   * @param {!Element|!ShadowRoot} element
    */
   injectHighlightStyleSheets(element) {
     this._injectingStyleSheet = true;
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/View.js b/src/cobalt/debug/remote/devtools/front_end/ui/View.js
index bc3d3be..96a784c 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/View.js
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/View.js
@@ -506,7 +506,7 @@
     this._titleElement.addEventListener('keydown', this._onTitleKeyDown.bind(this), false);
     this.contentElement.insertBefore(this._titleElement, this.contentElement.firstChild);
 
-    this.contentElement.createChild('content');
+    this.contentElement.createChild('slot');
     this._view = view;
     view[UI.ViewManager._ExpandableContainerWidget._symbol] = this;
   }
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/Widget.js b/src/cobalt/debug/remote/devtools/front_end/ui/Widget.js
index 861f157..611890d 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/Widget.js
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/Widget.js
@@ -30,13 +30,14 @@
 UI.Widget = class extends Common.Object {
   /**
    * @param {boolean=} isWebComponent
+   * @param {boolean=} delegatesFocus
    */
-  constructor(isWebComponent) {
+  constructor(isWebComponent, delegatesFocus) {
     super();
     this.contentElement = createElementWithClass('div', 'widget');
     if (isWebComponent) {
       this.element = createElementWithClass('div', 'vbox flex-auto');
-      this._shadowRoot = UI.createShadowRootWithCoreStyles(this.element);
+      this._shadowRoot = UI.createShadowRootWithCoreStyles(this.element, undefined, delegatesFocus);
       this._shadowRoot.appendChild(this.contentElement);
     } else {
       this.element = this.contentElement;
@@ -599,9 +600,10 @@
 UI.VBox = class extends UI.Widget {
   /**
    * @param {boolean=} isWebComponent
+   * @param {boolean=} delegatesFocus
    */
-  constructor(isWebComponent) {
-    super(isWebComponent);
+  constructor(isWebComponent, delegatesFocus) {
+    super(isWebComponent, delegatesFocus);
     this.contentElement.classList.add('vbox');
   }
 
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/filter.css b/src/cobalt/debug/remote/devtools/front_end/ui/filter.css
index 025789e..b09df81 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/filter.css
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/filter.css
@@ -46,10 +46,6 @@
     align-items: center;
 }
 
-.filter-text-filter label {
-    margin: auto 0;
-}
-
 .filter-bitset-filter {
     padding: 2px;
     display: inline-flex;
@@ -116,7 +112,7 @@
     position: relative;
 }
 
-.filter-checkbox-filter > label {
+.filter-checkbox-filter > [is=dt-checkbox] {
     display: flex;
     margin: auto 0;
 }
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/inspectorCommon.css b/src/cobalt/debug/remote/devtools/front_end/ui/inspectorCommon.css
index 2e7fa6e..a65be85 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/inspectorCommon.css
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/inspectorCommon.css
@@ -306,7 +306,7 @@
     white-space: nowrap;
 }
 
-label[is=dt-icon-label] {
+span[is=dt-icon-label] {
     flex: none;
 }
 
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/radioButton.css b/src/cobalt/debug/remote/devtools/front_end/ui/radioButton.css
index 47f4775..cadf3a7 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/radioButton.css
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/radioButton.css
@@ -4,7 +4,7 @@
  * found in the LICENSE file.
  */
 
-::content .dt-radio-button {
+::slotted(input.dt-radio-button) {
     height: 17px;
     width: 17px;
     min-width: 17px;
@@ -16,16 +16,16 @@
     margin: 0 5px 5px 0;
 }
 
-::content .dt-radio-button:active:not(:disabled) {
+::slotted(input.dt-radio-button:active:not(:disabled)) {
     background-image: linear-gradient(to bottom, rgb(194, 194, 194), rgb(239, 239, 239));
 }
 
-::content .dt-radio-button:checked {
+::slotted(input.dt-radio-button:checked) {
     background: url(Images/radioDot.png) center no-repeat,
                 linear-gradient(to bottom, rgb(252, 252, 252), rgb(223, 223, 223));
 }
 
-::content .dt-radio-button:checked:active {
+::slotted(input.dt-radio-button:checked:active) {
     background: url(Images/radioDot.png) center no-repeat,
                 linear-gradient(to bottom, rgb(194, 194, 194), rgb(239, 239, 239));
 }
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/softDropDownButton.css b/src/cobalt/debug/remote/devtools/front_end/ui/softDropDownButton.css
index 3812ef1..2270d27 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/softDropDownButton.css
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/softDropDownButton.css
@@ -3,7 +3,7 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-:host {
+button.soft-dropdown {
     height: 26px;
     text-align: left;
     position: relative;
@@ -11,18 +11,18 @@
     background: none;
 }
 
-:host([disabled]) {
+button.soft-dropdown[disabled] {
     opacity: .5;
 }
 
-:host .title {
+button.soft-dropdown > .title {
     padding-right: 5px;
     width: 120px;
     overflow: hidden;
     text-overflow: ellipsis;
 }
 
-:host([data-keyboard-focus="true"]:focus)::before {
+button.soft-dropdown[data-keyboard-focus="true"]:focus::before {
     content: "";
     position: absolute;
     top: 2px;
diff --git a/src/cobalt/debug/remote/devtools/front_end/ui/textButton.css b/src/cobalt/debug/remote/devtools/front_end/ui/textButton.css
index 001189a..cce75fa 100644
--- a/src/cobalt/debug/remote/devtools/front_end/ui/textButton.css
+++ b/src/cobalt/debug/remote/devtools/front_end/ui/textButton.css
@@ -4,7 +4,7 @@
  * found in the LICENSE file.
  */
 
-:host {
+ .text-button {
     margin: 2px;
     height: 24px;
     font-size: 12px;
@@ -18,52 +18,49 @@
     white-space: nowrap;
 }
 
-:host(:not(:disabled):focus),
-:host(:not(:disabled):hover),
-:host(:not(:disabled):active) {
+.text-button:not(:disabled):focus,
+.text-button:not(:disabled):hover,
+.text-button:not(:disabled):active {
     background-color: var(--toolbar-bg-color);
     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
     cursor: pointer;
 }
 
-:host(:not(:disabled):active) {
+.text-button:not(:disabled):active {
     background-color: #f2f2f2;
 }
 
-:host(:not(:disabled):focus) {
+.text-button:not(:disabled):focus {
     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(66, 133, 244, 0.4);
 }
 
-:host(:disabled) {
+.text-button:disabled {
     opacity: 0.38;
 }
 
-:host(.primary-button), -theme-preserve {
+.text-button.primary-button, -theme-preserve {
     background-color: #4285F4;
     border: none;
     color: #fff;
 }
 
-:host(.primary-button:not(:disabled):focus),
-:host(.primary-button:not(:disabled):hover), -theme-preserve {
-    background-color: #3B78E7;
+.text-button.primary-button:not(:disabled):focus,
+.text-button.primary-button:not(:disabled):hover,
+.text-button.primary-button:not(:disabled):active, -theme-preserve {
+    background-color: var(--accent-color-hover);
 }
 
-:host(.primary-button:not(:disabled):active), -theme-preserve {
-    background-color: #3367D6;
-}
-
-:host-context(.-theme-with-dark-background):host(:not(.primary-button):not(:disabled):focus),
-:host-context(.-theme-with-dark-background):host(:not(.primary-button):not(:disabled):hover),
-:host-context(.-theme-with-dark-background):host(:not(.primary-button):not(:disabled):active) {
+.-theme-with-dark-background .text-button:not(.primary-button):not(:disabled):focus,
+.-theme-with-dark-background .text-button:not(.primary-button):not(:disabled):hover,
+.-theme-with-dark-background .text-button:not(.primary-button):not(:disabled):active {
     background-color: #313131;
     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
 }
 
-:host-context(.-theme-with-dark-background):host(:not(.primary-button):not(:disabled):focus) {
+.-theme-with-dark-background .text-button:not(.primary-button):not(:disabled):focus {
     box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(94, 151, 246, 0.6);
 }
 
-:host-context(.-theme-with-dark-background):host(:not(.primary-button):not(:disabled):active) {
+.-theme-with-dark-background .text-button:not(.primary-button):not(:disabled):active {
     background-color: #3e3e3e;
 }
diff --git a/src/cobalt/doc/performance_tuning.md b/src/cobalt/doc/performance_tuning.md
index c9fc55d..e8336f3 100644
--- a/src/cobalt/doc/performance_tuning.md
+++ b/src/cobalt/doc/performance_tuning.md
@@ -421,11 +421,12 @@
 1. The command line option, "--timed_trace=XX" will instruct Cobalt to trace
    upon startup, for XX seconds (e.g. "--timed_trace=25").  When completed,
    the output will be written to the file `timed_trace.json`.
-2. Using the debug console (hit CTRL+O on a keyboard once or twice), type in the
-   command "d.trace()" and hit enter.  Cobalt will begin a trace.  After
-   some time has passed (and presumably you have performed some actions), you
-   can open the debug console again and type "d.trace()" again to end the trace.
-   The trace output will be written to the file `triggered_trace.json`.
+2. Using the debug console (hit CTRL+O on a keyboard once or twice), type in
+   the command "h5vcc.traceEvent.start()" and hit enter.  Cobalt will begin a
+   trace.  After some time has passed (and presumably you have performed some
+   actions), you can open the debug console again and type
+   "h5vcc.traceEvent.stop()" again to end the trace.
+   The trace output will be written to the file `h5vcc_trace_event.json`.
 
 The directory the output files will be placed within is the directory that the
 Starboard function `SbSystemGetPath()` returns with a `path_id` of
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc
index d803d68..e8fcc53 100644
--- a/src/cobalt/dom/html_media_element.cc
+++ b/src/cobalt/dom/html_media_element.cc
@@ -91,7 +91,7 @@
   if (resource_url.SchemeIs("data")) {
     return true;
   }
-  // Check if resource_url is an hls url. Hls url must contain "hls_variant"
+  // Check if resource_url is an hls url. Hls url must contain "hls_variant".
   return resource_url.spec().find("hls_variant") != std::string::npos;
 }
 #endif  // SB_HAS(PLAYER_WITH_URL)
diff --git a/src/cobalt/dom/media_source.cc b/src/cobalt/dom/media_source.cc
index 57bc8c0..3b01e26 100644
--- a/src/cobalt/dom/media_source.cc
+++ b/src/cobalt/dom/media_source.cc
@@ -51,8 +51,6 @@
 #include "base/compiler_specific.h"
 #include "base/guid.h"
 #include "base/logging.h"
-#include "base/strings/string_split.h"
-#include "base/strings/string_util.h"
 #include "cobalt/base/tokens.h"
 #include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/dom_settings.h"
@@ -69,39 +67,6 @@
 using media::CHUNK_DEMUXER_ERROR_EOS_STATUS_DECODE_ERROR;
 using media::PIPELINE_OK;
 
-namespace {
-
-// Parse mime and codecs from content type. It will return "video/mp4" and
-// "avc1.42E01E, mp4a.40.2" for "video/mp4; codecs="avc1.42E01E, mp4a.40.2".
-// Note that this function does minimum validation as the media stack will check
-// the mime type and codecs strictly.
-bool ParseContentType(const std::string& content_type, std::string* mime,
-                      std::string* codecs) {
-  DCHECK(mime);
-  DCHECK(codecs);
-  static const char kCodecs[] = "codecs=";
-
-  // SplitString will also trim the results.
-  std::vector<std::string> tokens = ::base::SplitString(
-      content_type, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
-  // The first one has to be mime type with delimiter '/' like 'video/mp4'.
-  if (tokens.size() < 2 || tokens[0].find('/') == tokens[0].npos) {
-    return false;
-  }
-  *mime = tokens[0];
-  for (size_t i = 1; i < tokens.size(); ++i) {
-    if (base::strncasecmp(tokens[i].c_str(), kCodecs, strlen(kCodecs))) {
-      continue;
-    }
-    *codecs = tokens[i].substr(strlen("codecs="));
-    base::TrimString(*codecs, " \"", codecs);
-    break;
-  }
-  return !codecs->empty();
-}
-
-}  // namespace
-
 MediaSource::MediaSource()
     : chunk_demuxer_(NULL),
       ready_state_(kMediaSourceReadyStateClosed),
@@ -203,18 +168,9 @@
     return NULL;
   }
 
-  std::string mime;
-  std::string codecs;
-
-  if (!ParseContentType(type, &mime, &codecs)) {
-    DOMException::Raise(DOMException::kNotSupportedErr, exception_state);
-    // Return value should be ignored.
-    return NULL;
-  }
-
   std::string guid = base::GenerateGUID();
   scoped_refptr<SourceBuffer> source_buffer;
-  ChunkDemuxer::Status status = chunk_demuxer_->AddId(guid, mime, codecs);
+  ChunkDemuxer::Status status = chunk_demuxer_->AddId(guid, type);
   switch (status) {
     case ChunkDemuxer::kOk:
       source_buffer =
diff --git a/src/cobalt/media/base/audio_decoder_config.h b/src/cobalt/media/base/audio_decoder_config.h
index 06c4b5a..5c15879 100644
--- a/src/cobalt/media/base/audio_decoder_config.h
+++ b/src/cobalt/media/base/audio_decoder_config.h
@@ -80,6 +80,9 @@
     return encryption_scheme_;
   }
 
+  void set_mime(const std::string& mime) { mime_ = mime; }
+  const std::string& mime() const { return mime_; }
+
  private:
   AudioCodec codec_;
   SampleFormat sample_format_;
@@ -99,6 +102,13 @@
   // as padding added during encoding.
   int codec_delay_;
 
+  // |mime_| contains the mime type string specified when creating the stream.
+  // For example, when the stream is created via MediaSource, it is the mime
+  // string passed to addSourceBuffer().  It can be an empty string when the
+  // appropriate mime string is unknown.  It is provided as an extra information
+  // and can be inconsistent with the other member variables.
+  std::string mime_;
+
   // Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler
   // generated copy constructor and assignment operator. Since the extra data is
   // typically small, the performance impact is minimal.
diff --git a/src/cobalt/media/base/starboard_player.cc b/src/cobalt/media/base/starboard_player.cc
index d0c7e92..0880efb 100644
--- a/src/cobalt/media/base/starboard_player.cc
+++ b/src/cobalt/media/base/starboard_player.cc
@@ -150,6 +150,11 @@
   DCHECK(set_bounds_helper_);
   DCHECK(video_frame_provider_);
 
+#if SB_API_VERSION >= 11
+  audio_sample_info_.codec = kSbMediaAudioCodecNone;
+  video_sample_info_.codec = kSbMediaVideoCodecNone;
+#endif  // SB_API_VERSION >= 11
+
   if (audio_config.IsValidConfig()) {
     UpdateAudioConfig(audio_config);
   }
@@ -157,9 +162,7 @@
     UpdateVideoConfig(video_config);
   }
 
-  output_mode_ = ComputeSbPlayerOutputMode(
-      MediaVideoCodecToSbMediaVideoCodec(video_config.codec()), drm_system,
-      prefer_decode_to_texture);
+  output_mode_ = ComputeSbPlayerOutputMode(prefer_decode_to_texture);
 
   CreatePlayer();
 
@@ -554,8 +557,30 @@
   }
 #endif  // SB_API_VERSION >= 11
 
-  DCHECK(SbPlayerOutputModeSupported(output_mode_, video_codec, drm_system_));
   bool has_audio = audio_codec != kSbMediaAudioCodecNone;
+
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+  SbPlayerCreationParam creation_param = {};
+  creation_param.audio_mime =
+      audio_config_.IsValidConfig() ? audio_config_.mime().c_str() : "";
+  creation_param.video_mime =
+      video_config_.IsValidConfig() ? video_config_.mime().c_str() : "";
+  creation_param.drm_system = drm_system_;
+  creation_param.audio_sample_info = audio_sample_info_;
+  creation_param.video_sample_info = video_sample_info_;
+  creation_param.output_mode = output_mode_;
+  creation_param.max_video_capabilities = max_video_capabilities_.c_str();
+  DCHECK_EQ(SbPlayerGetPreferredOutputMode(&creation_param), output_mode_);
+  player_ = SbPlayerCreate(
+      window_, &creation_param, &StarboardPlayer::DeallocateSampleCB,
+      &StarboardPlayer::DecoderStatusCB, &StarboardPlayer::PlayerStatusCB,
+      &StarboardPlayer::PlayerErrorCB, this,
+      get_decode_target_graphics_context_provider_func_.Run());
+
+#else  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+  DCHECK(SbPlayerOutputModeSupported(output_mode_, video_codec, drm_system_));
   player_ = SbPlayerCreate(
       window_, video_codec, audio_codec,
 #if SB_API_VERSION < 10
@@ -573,6 +598,9 @@
 #endif  // SB_HAS(PLAYER_ERROR_MESSAGE)
       this, output_mode_,
       get_decode_target_graphics_context_provider_func_.Run());
+
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
   DCHECK(SbPlayerIsValid(player_));
 
   if (output_mode_ == kSbPlayerOutputModeDecodeToTexture) {
@@ -935,24 +963,54 @@
 
 // static
 SbPlayerOutputMode StarboardPlayer::ComputeSbPlayerOutputMode(
-    SbMediaVideoCodec codec, SbDrmSystem drm_system,
-    bool prefer_decode_to_texture) {
-  // Try to choose the output mode according to the passed in value of
-  // |prefer_decode_to_texture|.  If the preferred output mode is unavailable
-  // though, fallback to an output mode that is available.
-  SbPlayerOutputMode output_mode = kSbPlayerOutputModeInvalid;
-  if (SbPlayerOutputModeSupported(kSbPlayerOutputModePunchOut, codec,
-                                  drm_system)) {
-    output_mode = kSbPlayerOutputModePunchOut;
-  }
-  if ((prefer_decode_to_texture || output_mode == kSbPlayerOutputModeInvalid) &&
-      SbPlayerOutputModeSupported(kSbPlayerOutputModeDecodeToTexture, codec,
-                                  drm_system)) {
-    output_mode = kSbPlayerOutputModeDecodeToTexture;
-  }
-  CHECK_NE(kSbPlayerOutputModeInvalid, output_mode);
+    bool prefer_decode_to_texture) const {
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+  SbPlayerCreationParam creation_param = {};
+  creation_param.audio_mime =
+      audio_config_.IsValidConfig() ? audio_config_.mime().c_str() : "";
+  creation_param.video_mime =
+      video_config_.IsValidConfig() ? video_config_.mime().c_str() : "";
+  creation_param.drm_system = drm_system_;
+  creation_param.audio_sample_info = audio_sample_info_;
+  creation_param.video_sample_info = video_sample_info_;
+  creation_param.max_video_capabilities = max_video_capabilities_.c_str();
 
+  // Try to choose |kSbPlayerOutputModeDecodeToTexture| when
+  // |prefer_decode_to_texture| is true.
+  if (prefer_decode_to_texture) {
+    creation_param.output_mode = kSbPlayerOutputModeDecodeToTexture;
+  } else {
+    creation_param.output_mode = kSbPlayerOutputModePunchOut;
+  }
+  auto output_mode = SbPlayerGetPreferredOutputMode(&creation_param);
+  CHECK_NE(kSbPlayerOutputModeInvalid, output_mode);
   return output_mode;
+#else  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+  SbMediaVideoCodec video_codec = kSbMediaVideoCodecNone;
+
+#if SB_API_VERSION >= 11
+  video_codec = video_sample_info_.codec;
+#else   // SB_API_VERSION >= 11
+  video_codec = MediaVideoCodecToSbMediaVideoCodec(video_config_.codec());
+#endif  // SB_API_VERSION >= 11
+
+  // Try to choose |kSbPlayerOutputModeDecodeToTexture| when
+  // |prefer_decode_to_texture| is true.
+  if (prefer_decode_to_texture) {
+    if (SbPlayerOutputModeSupported(kSbPlayerOutputModeDecodeToTexture,
+                                    video_codec, drm_system_)) {
+      return kSbPlayerOutputModeDecodeToTexture;
+    }
+  }
+
+  if (SbPlayerOutputModeSupported(kSbPlayerOutputModePunchOut, video_codec,
+                                  drm_system_)) {
+    return kSbPlayerOutputModePunchOut;
+  }
+  CHECK(SbPlayerOutputModeSupported(kSbPlayerOutputModeDecodeToTexture,
+                                    video_codec, drm_system_));
+  return kSbPlayerOutputModeDecodeToTexture;
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
 }
 
 }  // namespace media
diff --git a/src/cobalt/media/base/starboard_player.h b/src/cobalt/media/base/starboard_player.h
index 13ef299..2074cde 100644
--- a/src/cobalt/media/base/starboard_player.h
+++ b/src/cobalt/media/base/starboard_player.h
@@ -210,9 +210,8 @@
 #endif  // SB_HAS(PLAYER_WITH_URL)
   // Returns the output mode that should be used for a video with the given
   // specifications.
-  static SbPlayerOutputMode ComputeSbPlayerOutputMode(
-      SbMediaVideoCodec codec, SbDrmSystem drm_system,
-      bool prefer_decode_to_texture);
+  SbPlayerOutputMode ComputeSbPlayerOutputMode(
+      bool prefer_decode_to_texture) const;
 
   // The following variables are initialized in the ctor and never changed.
 #if SB_HAS(PLAYER_WITH_URL)
diff --git a/src/cobalt/media/base/video_decoder_config.h b/src/cobalt/media/base/video_decoder_config.h
index ffcdb8a..ae8cf34 100644
--- a/src/cobalt/media/base/video_decoder_config.h
+++ b/src/cobalt/media/base/video_decoder_config.h
@@ -112,6 +112,9 @@
     return webm_color_metadata_;
   }
 
+  void set_mime(const std::string& mime) { mime_ = mime; }
+  const std::string& mime() const { return mime_; }
+
  private:
   VideoCodec codec_;
   VideoCodecProfile profile_;
@@ -131,6 +134,13 @@
 
   WebMColorMetadata webm_color_metadata_;
 
+  // |mime_| contains the mime type string specified when creating the stream.
+  // For example, when the stream is created via MediaSource, it is the mime
+  // string passed to addSourceBuffer().  It can be an empty string when the
+  // appropriate mime string is unknown.  It is provided as an extra information
+  // and can be inconsistent with the other member variables.
+  std::string mime_;
+
   // Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler
   // generated copy constructor and assignment operator. Since the extra data is
   // typically small, the performance impact is minimal.
diff --git a/src/cobalt/media/filters/chunk_demuxer.cc b/src/cobalt/media/filters/chunk_demuxer.cc
index bdc048d..e88c81d 100644
--- a/src/cobalt/media/filters/chunk_demuxer.cc
+++ b/src/cobalt/media/filters/chunk_demuxer.cc
@@ -16,6 +16,8 @@
 #include "base/metrics/histogram.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
 #include "base/trace_event/trace_event.h"
 #include "cobalt/media/base/audio_decoder_config.h"
 #include "cobalt/media/base/bind_to_current_loop.h"
@@ -34,9 +36,44 @@
 namespace cobalt {
 namespace media {
 
-ChunkDemuxerStream::ChunkDemuxerStream(Type type, bool splice_frames_enabled,
+namespace {
+
+// Parse type and codecs from mime type. It will return "video/mp4" and
+// "avc1.42E01E, mp4a.40.2" for "video/mp4; codecs="avc1.42E01E, mp4a.40.2".
+// Note that this function does minimum validation as the media stack will check
+// the type and codecs strictly.
+bool ParseMimeType(const std::string& mime_type, std::string* type,
+                   std::string* codecs) {
+  DCHECK(type);
+  DCHECK(codecs);
+  static const char kCodecs[] = "codecs=";
+
+  // SplitString will also trim the results.
+  std::vector<std::string> tokens = ::base::SplitString(
+      mime_type, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  // The first one has to be mime type with delimiter '/' like 'video/mp4'.
+  if (tokens.size() < 2 || tokens[0].find('/') == tokens[0].npos) {
+    return false;
+  }
+  *type = tokens[0];
+  for (size_t i = 1; i < tokens.size(); ++i) {
+    if (base::strncasecmp(tokens[i].c_str(), kCodecs, strlen(kCodecs))) {
+      continue;
+    }
+    *codecs = tokens[i].substr(strlen("codecs="));
+    base::TrimString(*codecs, " \"", codecs);
+    break;
+  }
+  return !codecs->empty();
+}
+
+}  // namespace
+
+ChunkDemuxerStream::ChunkDemuxerStream(Type type, const std::string& mime,
+                                       bool splice_frames_enabled,
                                        MediaTrack::Id media_track_id)
     : type_(type),
+      mime_(mime),
       liveness_(DemuxerStream::LIVENESS_UNKNOWN),
       media_track_id_(media_track_id),
       state_(UNINITIALIZED),
@@ -272,13 +309,17 @@
 AudioDecoderConfig ChunkDemuxerStream::audio_decoder_config() {
   CHECK_EQ(type_, AUDIO);
   base::AutoLock auto_lock(lock_);
-  return stream_->GetCurrentAudioDecoderConfig();
+  auto config = stream_->GetCurrentAudioDecoderConfig();
+  config.set_mime(mime_);
+  return config;
 }
 
 VideoDecoderConfig ChunkDemuxerStream::video_decoder_config() {
   CHECK_EQ(type_, VIDEO);
   base::AutoLock auto_lock(lock_);
-  return stream_->GetCurrentVideoDecoderConfig();
+  auto config = stream_->GetCurrentVideoDecoderConfig();
+  config.set_mime(mime_);
+  return config;
 }
 
 bool ChunkDemuxerStream::SupportsConfigChanges() { return true; }
@@ -607,10 +648,16 @@
 }
 
 ChunkDemuxer::Status ChunkDemuxer::AddId(const std::string& id,
-                                         const std::string& type,
-                                         const std::string& codecs) {
-  DVLOG(1) << __func__ << " id=" << id << " mime_type=" << type
-           << " codecs=" << codecs;
+                                         const std::string& mime) {
+  DVLOG(1) << __func__ << " id=" << id << " mime_type=" << mime;
+
+  std::string type;
+  std::string codecs;
+
+  if (!ParseMimeType(mime, &type, &codecs)) {
+    return kNotSupported;
+  }
+
   base::AutoLock auto_lock(lock_);
 
   if ((state_ != WAITING_FOR_INIT && state_ != INITIALIZING) || IsValidId(id))
@@ -632,8 +679,8 @@
 
   std::unique_ptr<SourceBufferState> source_state(new SourceBufferState(
       std::move(stream_parser), std::move(frame_processor),
-      base::Bind(&ChunkDemuxer::CreateDemuxerStream, base::Unretained(this),
-                 id),
+      base::Bind(&ChunkDemuxer::CreateDemuxerStream, base::Unretained(this), id,
+                 mime),
       media_log_, buffer_allocator_));
 
   SourceBufferState::NewTextTrackCB new_text_track_cb;
@@ -1195,7 +1242,8 @@
 }
 
 ChunkDemuxerStream* ChunkDemuxer::CreateDemuxerStream(
-    const std::string& source_id, DemuxerStream::Type type) {
+    const std::string& source_id, const std::string& mime,
+    DemuxerStream::Type type) {
   // New ChunkDemuxerStreams can be created only during initialization segment
   // processing, which happens when a new chunk of data is appended and the
   // lock_ must be held by ChunkDemuxer::AppendData.
@@ -1223,8 +1271,8 @@
       return NULL;
   }
 
-  std::unique_ptr<ChunkDemuxerStream> stream(
-      new ChunkDemuxerStream(type, splice_frames_enabled_, media_track_id));
+  std::unique_ptr<ChunkDemuxerStream> stream(new ChunkDemuxerStream(
+      type, mime, splice_frames_enabled_, media_track_id));
   DCHECK(track_id_to_demux_stream_map_.find(media_track_id) ==
          track_id_to_demux_stream_map_.end());
   track_id_to_demux_stream_map_[media_track_id] = stream.get();
diff --git a/src/cobalt/media/filters/chunk_demuxer.h b/src/cobalt/media/filters/chunk_demuxer.h
index 574e82e..f0d2428 100644
--- a/src/cobalt/media/filters/chunk_demuxer.h
+++ b/src/cobalt/media/filters/chunk_demuxer.h
@@ -35,8 +35,8 @@
  public:
   typedef std::deque<scoped_refptr<StreamParserBuffer>> BufferQueue;
 
-  ChunkDemuxerStream(Type type, bool splice_frames_enabled,
-                     MediaTrack::Id media_track_id);
+  ChunkDemuxerStream(Type type, const std::string& mime,
+                     bool splice_frames_enabled, MediaTrack::Id media_track_id);
   ~ChunkDemuxerStream() override;
 
   // ChunkDemuxerStream control methods.
@@ -148,6 +148,8 @@
   // Specifies the type of the stream.
   Type type_;
 
+  const std::string mime_;
+
   Liveness liveness_;
 
   std::unique_ptr<SourceBufferStream> stream_;
@@ -212,15 +214,14 @@
   void StartWaitingForSeek(base::TimeDelta seek_time) override;
   void CancelPendingSeek(base::TimeDelta seek_time) override;
 
-  // Registers a new |id| to use for AppendData() calls. |type| indicates
+  // Registers a new |id| to use for AppendData() calls. |mime| indicates
   // the MIME type for the data that we intend to append for this ID.
   // kOk is returned if the demuxer has enough resources to support another ID
   //    and supports the format indicated by |type|.
   // kNotSupported is returned if |type| is not a supported format.
   // kReachedIdLimit is returned if the demuxer cannot handle another ID right
   //    now.
-  Status AddId(const std::string& id, const std::string& type,
-               const std::string& codecs);
+  Status AddId(const std::string& id, const std::string& mime);
 
   // Notifies a caller via |tracks_updated_cb| that the set of media tracks
   // for a given |id| has changed.
@@ -351,6 +352,7 @@
   // Returns a pointer to a new ChunkDemuxerStream instance, which is owned by
   // ChunkDemuxer.
   ChunkDemuxerStream* CreateDemuxerStream(const std::string& source_id,
+                                          const std::string& mime,
                                           DemuxerStream::Type type);
 
   void OnNewTextTrack(ChunkDemuxerStream* text_stream,
diff --git a/src/cobalt/media/filters/shell_demuxer.cc b/src/cobalt/media/filters/shell_demuxer.cc
index a68a82a..6f7f226 100644
--- a/src/cobalt/media/filters/shell_demuxer.cc
+++ b/src/cobalt/media/filters/shell_demuxer.cc
@@ -402,6 +402,7 @@
                               requested_au_->GetMaxSize()));
     if (decoder_buffer) {
       decoder_buffer->set_is_key_frame(requested_au_->IsKeyframe());
+      buffer_allocator_->UpdateVideoConfig(VideoConfig());
       Download(decoder_buffer);
     } else {
       // As the buffer is full of media data, it is safe to delay 100
diff --git a/src/cobalt/media/formats/mp4/box_definitions.cc b/src/cobalt/media/formats/mp4/box_definitions.cc
index 01cb776..aee8f2c 100644
--- a/src/cobalt/media/formats/mp4/box_definitions.cc
+++ b/src/cobalt/media/formats/mp4/box_definitions.cc
@@ -23,6 +23,31 @@
 namespace media {
 namespace mp4 {
 
+namespace {
+
+// Read color coordinate value as defined in the MasteringDisplayColorVolume
+// ('mdcv') box.  Each coordinate is a float encoded in uint16_t, with upper
+// bound set to 50000.
+bool ReadMdcvColorCoordinate(BoxReader* reader,
+                             float* normalized_value_in_float) {
+  const float kColorCoordinateUpperBound = 50000.;
+
+  uint16_t value_in_uint16;
+  RCHECK(reader->Read2(&value_in_uint16));
+
+  float value_in_float = static_cast<float>(value_in_uint16);
+
+  if (value_in_float >= kColorCoordinateUpperBound) {
+    *normalized_value_in_float = 1.f;
+    return true;
+  }
+
+  *normalized_value_in_float = value_in_float / kColorCoordinateUpperBound;
+  return true;
+}
+
+}  // namespace
+
 FileType::FileType() {}
 FileType::~FileType() {}
 FourCC FileType::BoxType() const { return FOURCC_FTYP; }
@@ -620,6 +645,60 @@
   return true;
 }
 
+ColorParameterInformation::ColorParameterInformation() {}
+ColorParameterInformation::~ColorParameterInformation() {}
+FourCC ColorParameterInformation::BoxType() const { return FOURCC_COLR; }
+
+bool ColorParameterInformation::Parse(BoxReader* reader) {
+  FourCC type;
+  RCHECK(reader->ReadFourCC(&type));
+  // TODO: Support 'nclc', 'rICC', and 'prof'.
+  RCHECK(type == FOURCC_NCLX);
+
+  uint8_t full_range_byte;
+  RCHECK(reader->Read2(&colour_primaries) &&
+         reader->Read2(&transfer_characteristics) &&
+         reader->Read2(&matrix_coefficients) &&
+         reader->Read1(&full_range_byte));
+  full_range = full_range_byte & 0x80;
+
+  return true;
+}
+
+MasteringDisplayColorVolume::MasteringDisplayColorVolume() {}
+MasteringDisplayColorVolume::~MasteringDisplayColorVolume() {}
+FourCC MasteringDisplayColorVolume::BoxType() const { return FOURCC_MDCV; }
+
+bool MasteringDisplayColorVolume::Parse(BoxReader* reader) {
+  // Technically the color coordinates may be in any order.  The spec recommends
+  // GBR and it is assumed that the color coordinates are in such order.
+  RCHECK(ReadMdcvColorCoordinate(reader, &display_primaries_gx) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_gy) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_bx) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_by) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_rx) &&
+         ReadMdcvColorCoordinate(reader, &display_primaries_ry) &&
+         ReadMdcvColorCoordinate(reader, &white_point_x) &&
+         ReadMdcvColorCoordinate(reader, &white_point_y) &&
+         reader->Read4(&max_display_mastering_luminance) &&
+         reader->Read4(&min_display_mastering_luminance));
+
+  const uint32_t kUnitOfMasteringLuminance = 10000;
+  max_display_mastering_luminance /= kUnitOfMasteringLuminance;
+  min_display_mastering_luminance /= kUnitOfMasteringLuminance;
+
+  return true;
+}
+
+ContentLightLevelInformation::ContentLightLevelInformation() {}
+ContentLightLevelInformation::~ContentLightLevelInformation() {}
+FourCC ContentLightLevelInformation::BoxType() const { return FOURCC_CLLI; }
+
+bool ContentLightLevelInformation::Parse(BoxReader* reader) {
+  return reader->Read2(&max_content_light_level) &&
+         reader->Read2(&max_pic_average_light_level);
+}
+
 VideoSampleEntry::VideoSampleEntry()
     : format(FOURCC_NULL),
       data_reference_index(0),
@@ -688,6 +767,24 @@
       frame_bitstream_converter = nullptr;
       video_codec = kCodecAV1;
       video_codec_profile = av1_config.profile;
+
+      ColorParameterInformation color_parameter_information;
+      if (reader->HasChild(&color_parameter_information)) {
+        RCHECK(reader->ReadChild(&color_parameter_information));
+        this->color_parameter_information = color_parameter_information;
+      }
+
+      MasteringDisplayColorVolume mastering_display_color_volume;
+      if (reader->HasChild(&mastering_display_color_volume)) {
+        RCHECK(reader->ReadChild(&mastering_display_color_volume));
+        this->mastering_display_color_volume = mastering_display_color_volume;
+      }
+
+      ContentLightLevelInformation content_light_level_information;
+      if (reader->HasChild(&content_light_level_information)) {
+        RCHECK(reader->ReadChild(&content_light_level_information));
+        this->content_light_level_information = content_light_level_information;
+      }
       break;
     }
     default:
diff --git a/src/cobalt/media/formats/mp4/box_definitions.h b/src/cobalt/media/formats/mp4/box_definitions.h
index 90415d9..db69017 100644
--- a/src/cobalt/media/formats/mp4/box_definitions.h
+++ b/src/cobalt/media/formats/mp4/box_definitions.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/compiler_specific.h"
+#include "base/optional.h"
 #include "cobalt/media/base/decrypt_config.h"
 #include "cobalt/media/base/media_export.h"
 #include "cobalt/media/base/media_log.h"
@@ -239,6 +240,37 @@
   uint32_t v_spacing;
 };
 
+struct MEDIA_EXPORT ColorParameterInformation : Box {
+  DECLARE_BOX_METHODS(ColorParameterInformation);
+
+  uint16 colour_primaries;
+  uint16 transfer_characteristics;
+  uint16 matrix_coefficients;
+  bool full_range;
+};
+
+struct MEDIA_EXPORT MasteringDisplayColorVolume : Box {
+  DECLARE_BOX_METHODS(MasteringDisplayColorVolume);
+
+  float display_primaries_gx;
+  float display_primaries_gy;
+  float display_primaries_bx;
+  float display_primaries_by;
+  float display_primaries_rx;
+  float display_primaries_ry;
+  float white_point_x;
+  float white_point_y;
+  uint32 max_display_mastering_luminance;
+  uint32 min_display_mastering_luminance;
+};
+
+struct MEDIA_EXPORT ContentLightLevelInformation : Box {
+  DECLARE_BOX_METHODS(ContentLightLevelInformation);
+
+  uint16 max_content_light_level;
+  uint16 max_pic_average_light_level;
+};
+
 struct MEDIA_EXPORT VideoSampleEntry : Box {
   DECLARE_BOX_METHODS(VideoSampleEntry);
 
@@ -253,6 +285,10 @@
   VideoCodec video_codec;
   VideoCodecProfile video_codec_profile;
 
+  base::Optional<ColorParameterInformation> color_parameter_information;
+  base::Optional<MasteringDisplayColorVolume> mastering_display_color_volume;
+  base::Optional<ContentLightLevelInformation> content_light_level_information;
+
   bool IsFormatValid() const;
 
   scoped_refptr<BitstreamConverter> frame_bitstream_converter;
diff --git a/src/cobalt/media/formats/mp4/fourccs.h b/src/cobalt/media/formats/mp4/fourccs.h
index e79b003..497ac68 100644
--- a/src/cobalt/media/formats/mp4/fourccs.h
+++ b/src/cobalt/media/formats/mp4/fourccs.h
@@ -22,7 +22,9 @@
   FOURCC_AVCC = 0x61766343,
   FOURCC_BLOC = 0x626C6F63,
   FOURCC_CENC = 0x63656e63,
+  FOURCC_CLLI = 0x636c6c69,
   FOURCC_CO64 = 0x636f3634,
+  FOURCC_COLR = 0x636f6c72,
   FOURCC_CTTS = 0x63747473,
   FOURCC_DINF = 0x64696e66,
   FOURCC_EDTS = 0x65647473,
@@ -41,6 +43,7 @@
   FOURCC_HVCC = 0x68766343,
   FOURCC_IODS = 0x696f6473,
   FOURCC_MDAT = 0x6d646174,
+  FOURCC_MDCV = 0x6d646376,
   FOURCC_MDHD = 0x6d646864,
   FOURCC_MDIA = 0x6d646961,
   FOURCC_MECO = 0x6d65636f,
@@ -55,6 +58,7 @@
   FOURCC_MP4V = 0x6d703476,
   FOURCC_MVEX = 0x6d766578,
   FOURCC_MVHD = 0x6d766864,
+  FOURCC_NCLX = 0x6e636c78,
   FOURCC_PASP = 0x70617370,
   FOURCC_PDIN = 0x7064696e,
   FOURCC_PRFT = 0x70726674,
diff --git a/src/cobalt/media/formats/mp4/mp4_stream_parser.cc b/src/cobalt/media/formats/mp4/mp4_stream_parser.cc
index 391318b..1ab9371 100644
--- a/src/cobalt/media/formats/mp4/mp4_stream_parser.cc
+++ b/src/cobalt/media/formats/mp4/mp4_stream_parser.cc
@@ -16,6 +16,8 @@
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "cobalt/media/base/audio_decoder_config.h"
+#include "cobalt/media/base/color_space.h"
+#include "cobalt/media/base/hdr_metadata.h"
 #include "cobalt/media/base/media_tracks.h"
 #include "cobalt/media/base/media_util.h"
 #include "cobalt/media/base/stream_parser_buffer.h"
@@ -28,6 +30,7 @@
 #include "cobalt/media/formats/mp4/es_descriptor.h"
 #include "cobalt/media/formats/mp4/rcheck.h"
 #include "cobalt/media/formats/mpeg/adts_constants.h"
+#include "cobalt/media/formats/webm/webm_colour_parser.h"
 #include "starboard/memory.h"
 #include "starboard/types.h"
 
@@ -36,7 +39,45 @@
 namespace mp4 {
 
 namespace {
+
+using gfx::ColorSpace;
+
 const int kMaxEmptySampleLogs = 20;
+
+gfx::ColorSpace ConvertColorParameterInformationToColorSpace(
+    const ColorParameterInformation& info) {
+  auto primary_id = static_cast<ColorSpace::PrimaryID>(info.colour_primaries);
+  auto transfer_id =
+      static_cast<ColorSpace::TransferID>(info.transfer_characteristics);
+  auto matrix_id = static_cast<ColorSpace::MatrixID>(info.matrix_coefficients);
+
+  // Note that we don't check whether the embedded ids are valid.  We rely on
+  // the underlying video decoder to reject any ids that it doesn't support.
+  return gfx::ColorSpace(
+      primary_id, transfer_id, matrix_id,
+      info.full_range ? ColorSpace::kRangeIdFull : ColorSpace::kRangeIdLimited);
+}
+
+MasteringMetadata ConvertMdcvToMasteringMetadata(
+    const MasteringDisplayColorVolume& mdcv) {
+  MasteringMetadata mastering_metadata;
+
+  mastering_metadata.primary_r_chromaticity_x = mdcv.display_primaries_rx;
+  mastering_metadata.primary_r_chromaticity_y = mdcv.display_primaries_ry;
+  mastering_metadata.primary_g_chromaticity_x = mdcv.display_primaries_gx;
+  mastering_metadata.primary_g_chromaticity_y = mdcv.display_primaries_gy;
+  mastering_metadata.primary_b_chromaticity_x = mdcv.display_primaries_bx;
+  mastering_metadata.primary_b_chromaticity_y = mdcv.display_primaries_by;
+  mastering_metadata.white_point_chromaticity_x = mdcv.white_point_x;
+  mastering_metadata.white_point_chromaticity_y = mdcv.white_point_y;
+  mastering_metadata.luminance_max =
+      static_cast<float>(mdcv.max_display_mastering_luminance);
+  mastering_metadata.luminance_min =
+      static_cast<float>(mdcv.min_display_mastering_luminance);
+
+  return mastering_metadata;
+}
+
 }  // namespace
 
 MP4StreamParser::MP4StreamParser(DecoderBuffer::Allocator* buffer_allocator,
@@ -371,6 +412,30 @@
           // SPS/PPS are embedded in the video stream
           EmptyExtraData(),
           is_track_encrypted ? AesCtrEncryptionScheme() : Unencrypted());
+      if (entry.color_parameter_information) {
+        WebMColorMetadata color_metadata = {};
+
+        color_metadata.color_space =
+            ConvertColorParameterInformationToColorSpace(
+                *entry.color_parameter_information);
+
+        if (entry.mastering_display_color_volume) {
+          color_metadata.hdr_metadata.mastering_metadata =
+              ConvertMdcvToMasteringMetadata(
+                  *entry.mastering_display_color_volume);
+        }
+
+        if (entry.content_light_level_information) {
+          color_metadata.hdr_metadata.max_cll =
+              entry.content_light_level_information->max_content_light_level;
+          color_metadata.hdr_metadata.max_fall =
+              entry.content_light_level_information
+                  ->max_pic_average_light_level;
+        }
+
+        video_config.set_webm_color_metadata(color_metadata);
+      }
+
       DVLOG(1) << "video_track_id=" << video_track_id
                << " config=" << video_config.AsHumanReadableString();
       if (!video_config.IsValidConfig()) {
diff --git a/src/cobalt/media/sandbox/format_guesstimator.cc b/src/cobalt/media/sandbox/format_guesstimator.cc
index ad5c97f..adc856b 100644
--- a/src/cobalt/media/sandbox/format_guesstimator.cc
+++ b/src/cobalt/media/sandbox/format_guesstimator.cc
@@ -19,6 +19,8 @@
 
 #include "base/bind.h"
 #include "base/path_service.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
 #include "base/time/time.h"
 #include "cobalt/media/base/audio_codecs.h"
 #include "cobalt/media/base/audio_decoder_config.h"
@@ -42,20 +44,18 @@
 
 namespace {
 
-// Container to organize the pairs of supported mime types and their codecs.
-struct SupportedTypeCodecInfo {
-  std::string mime;
-  std::string codecs;
-};
+// The possible mime type configurations that are supported by cobalt.
+const std::vector<std::string> kSupportedMimeTypes = {
+    "audio/mp4; codecs=\"ac-3\"",
+    "audio/mp4; codecs=\"ec-3\"",
+    "audio/mp4; codecs=\"mp4a.40.2\"",
+    "audio/webm; codecs=\"opus\"",
 
-// The possible mime and codec configurations that are supported by cobalt.
-const std::vector<SupportedTypeCodecInfo> kSupportedTypesAndCodecs = {
-    {"audio/mp4", "ac-3"},          {"audio/mp4", "ec-3"},
-    {"audio/mp4", "mp4a.40.2"},     {"audio/webm", "opus"},
-
-    {"video/mp4", "av01.0.05M.08"}, {"video/mp4", "avc1.640028, mp4a.40.2"},
-    {"video/mp4", "avc1.640028"},   {"video/mp4", "hvc1.1.6.H150.90"},
-    {"video/webm", "vp9"},
+    "video/mp4; codecs=\"av01.0.05M.08\"",
+    "video/mp4; codecs=\"avc1.640028, mp4a.40.2\"",
+    "video/mp4; codecs=\"avc1.640028\"",
+    "video/mp4; codecs=\"hvc1.1.6.H150.90\"",
+    "video/webm; codecs=\"vp9\"",
 };
 
 // Can be called as:
@@ -103,6 +103,28 @@
   SB_UNREFERENCED_PARAMETER(tracks);
 }
 
+// Extract the value of "codecs" parameter from content type. It will return
+// "avc1.42E01E" for "video/mp4; codecs="avc1.42E01E".
+// Note that this function assumes that the input is always valid and does
+// minimum validation..
+std::string ExtractCodec(const std::string& content_type) {
+  static const char kCodecs[] = "codecs=";
+
+  // SplitString will also trim the results.
+  std::vector<std::string> tokens = ::base::SplitString(
+      content_type, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  for (size_t i = 1; i < tokens.size(); ++i) {
+    if (base::strncasecmp(tokens[i].c_str(), kCodecs, strlen(kCodecs))) {
+      continue;
+    }
+    auto codec = tokens[i].substr(strlen("codecs="));
+    base::TrimString(codec, " \"", &codec);
+    return codec;
+  }
+  NOTREACHED();
+  return "";
+}
+
 }  // namespace
 
 FormatGuesstimator::FormatGuesstimator(const std::string& path_or_url,
@@ -126,17 +148,15 @@
     return;
   }
   progressive_url_ = url;
-  mime_ = "video/mp4";
-  codecs_ = "avc1.640028, mp4a.40.2";
+  mime_type_ = "video/mp4; codecs=\"avc1.640028, mp4a.40.2\"";
 }
 
 void FormatGuesstimator::InitializeAsAdaptive(const base::FilePath& path,
                                               MediaModule* media_module) {
   std::vector<uint8_t> header = ReadHeader(path);
 
-  for (const auto& expected_supported_info : kSupportedTypesAndCodecs) {
-    DCHECK(mime_.empty());
-    DCHECK(codecs_.empty());
+  for (const auto& expected_supported_mime_type : kSupportedMimeTypes) {
+    DCHECK(mime_type_.empty());
 
     ChunkDemuxer* chunk_demuxer = NULL;
     WebMediaPlayerHelper::ChunkDemuxerOpenCB open_cb = base::Bind(
@@ -157,8 +177,7 @@
     }
 
     const std::string id = "stream";
-    if (chunk_demuxer->AddId(id, expected_supported_info.mime,
-                             expected_supported_info.codecs) !=
+    if (chunk_demuxer->AddId(id, expected_supported_mime_type) !=
         ChunkDemuxer::kOk) {
       continue;
     }
@@ -182,11 +201,10 @@
             chunk_demuxer->GetStream(DemuxerStream::Type::AUDIO)) {
       const AudioDecoderConfig& decoder_config =
           demuxer_stream->audio_decoder_config();
-      if (StringToAudioCodec(expected_supported_info.codecs) ==
+      if (StringToAudioCodec(ExtractCodec(expected_supported_mime_type)) ==
           decoder_config.codec()) {
         adaptive_path_ = path;
-        mime_ = expected_supported_info.mime;
-        codecs_ = expected_supported_info.codecs;
+        mime_type_ = expected_supported_mime_type;
         break;
       }
       continue;
@@ -195,11 +213,10 @@
     DCHECK(demuxer_stream);
     const VideoDecoderConfig& decoder_config =
         demuxer_stream->video_decoder_config();
-    if (StringToVideoCodec(expected_supported_info.codecs) ==
+    if (StringToVideoCodec(ExtractCodec(expected_supported_mime_type)) ==
         decoder_config.codec()) {
       adaptive_path_ = path;
-      mime_ = expected_supported_info.mime;
-      codecs_ = expected_supported_info.codecs;
+      mime_type_ = expected_supported_mime_type;
       break;
     }
   }
diff --git a/src/cobalt/media/sandbox/format_guesstimator.h b/src/cobalt/media/sandbox/format_guesstimator.h
index dd95e9c..f9b25fb 100644
--- a/src/cobalt/media/sandbox/format_guesstimator.h
+++ b/src/cobalt/media/sandbox/format_guesstimator.h
@@ -38,7 +38,7 @@
   bool is_adaptive() const { return !adaptive_path_.empty(); }
   bool is_audio() const {
     DCHECK(is_adaptive());
-    return mime_.find("audio/") == 0;
+    return mime_type_.find("audio/") == 0;
   }
 
   const GURL& progressive_url() const {
@@ -53,13 +53,9 @@
     return adaptive_path_.value();
   }
 
-  const std::string& mime() const {
+  const std::string& mime_type() const {
     DCHECK(is_valid());
-    return mime_;
-  }
-  const std::string& codecs() const {
-    DCHECK(is_valid());
-    return codecs_;
+    return mime_type_;
   }
 
  private:
@@ -69,8 +65,7 @@
 
   GURL progressive_url_;
   base::FilePath adaptive_path_;
-  std::string mime_;
-  std::string codecs_;
+  std::string mime_type_;
 };
 
 }  // namespace sandbox
diff --git a/src/cobalt/media/sandbox/media2_sandbox.cc b/src/cobalt/media/sandbox/media2_sandbox.cc
index fdbb7df..0228744 100644
--- a/src/cobalt/media/sandbox/media2_sandbox.cc
+++ b/src/cobalt/media/sandbox/media2_sandbox.cc
@@ -118,15 +118,15 @@
   demuxer->Initialize(&demuxer_host, base::Bind(OnDemuxerStatus), false);
 
   ChunkDemuxer::Status status =
-      demuxer->AddId("audio", "audio/mp4", "mp4a.40.2");
+      demuxer->AddId("audio", "audio/mp4; codecs=\"mp4a.40.2\"");
   DCHECK_EQ(status, ChunkDemuxer::kOk);
 
   int video_url_length = SbStringGetLength(argv[2]);
   if (video_url_length > 5 &&
       SbStringCompare(argv[2] + video_url_length - 5, ".webm", 5) == 0) {
-    status = demuxer->AddId("video", "video/webm", "vp9");
+    status = demuxer->AddId("video", "video/webm; codecs=\"vp9\"");
   } else {
-    status = demuxer->AddId("video", "video/mp4", "avc1.640028");
+    status = demuxer->AddId("video", "video/mp4; codecs=\"avc1.640028\"");
   }
   DCHECK_EQ(status, ChunkDemuxer::kOk);
 
diff --git a/src/cobalt/media/sandbox/web_media_player_sandbox.cc b/src/cobalt/media/sandbox/web_media_player_sandbox.cc
index 7d28698..45f5a3f 100644
--- a/src/cobalt/media/sandbox/web_media_player_sandbox.cc
+++ b/src/cobalt/media/sandbox/web_media_player_sandbox.cc
@@ -90,8 +90,6 @@
   SbLogRaw(ss.str().c_str());
 }
 
-std::string MakeCodecParameter(const std::string& string) { return string; }
-
 void OnInitSegmentReceived(std::unique_ptr<MediaTracks> tracks) {
   SB_UNREFERENCED_PARAMETER(tracks);
 }
@@ -182,8 +180,7 @@
     LOG(INFO) << "Playing " << guesstimator.adaptive_path();
 
     std::string id = guesstimator.is_audio() ? kAudioId : kVideoId;
-    auto codecs = MakeCodecParameter(guesstimator.codecs());
-    auto status = chunk_demuxer_->AddId(id, guesstimator.mime(), codecs);
+    auto status = chunk_demuxer_->AddId(id, guesstimator.mime_type());
     CHECK_EQ(status, ChunkDemuxer::kOk);
 
     chunk_demuxer_->SetTracksWatcher(id, base::Bind(OnInitSegmentReceived));
@@ -234,13 +231,11 @@
     LOG(INFO) << "Playing " << audio_guesstimator.adaptive_path() << " and "
               << video_guesstimator.adaptive_path();
 
-    auto codecs = MakeCodecParameter(audio_guesstimator.codecs());
     auto status =
-        chunk_demuxer_->AddId(kAudioId, audio_guesstimator.mime(), codecs);
+        chunk_demuxer_->AddId(kAudioId, audio_guesstimator.mime_type());
     CHECK_EQ(status, ChunkDemuxer::kOk);
 
-    codecs = MakeCodecParameter(video_guesstimator.codecs());
-    status = chunk_demuxer_->AddId(kVideoId, video_guesstimator.mime(), codecs);
+    status = chunk_demuxer_->AddId(kVideoId, video_guesstimator.mime_type());
     CHECK_EQ(status, ChunkDemuxer::kOk);
 
     chunk_demuxer_->SetTracksWatcher(kAudioId,
diff --git a/src/cobalt/network/url_request_context.cc b/src/cobalt/network/url_request_context.cc
index 7b532ae..671de56 100644
--- a/src/cobalt/network/url_request_context.cc
+++ b/src/cobalt/network/url_request_context.cc
@@ -37,6 +37,7 @@
 #include "net/http/http_transaction_factory.h"
 #include "net/proxy_resolution/proxy_config.h"
 #include "net/proxy_resolution/proxy_resolution_service.h"
+#include "net/third_party/quic/platform/api/quic_flags.h"
 #include "net/ssl/ssl_config_service.h"
 #include "net/ssl/ssl_config_service_defaults.h"
 #include "net/url_request/data_protocol_handler.h"
@@ -93,6 +94,10 @@
               new ProxyConfigService(proxy_config)),
           net_log));
 
+  // ack decimation significantly increases download bandwidth on low-end
+  // android devices.
+  SetQuicFlag(&FLAGS_quic_reloadable_flag_quic_enable_ack_decimation, true);
+
   net::HostResolver::Options options;
   options.max_concurrent_resolves = net::HostResolver::kDefaultParallelism;
   options.max_retry_attempts = net::HostResolver::kDefaultRetryAttempts;
diff --git a/src/cobalt/renderer/backend/backend.gyp b/src/cobalt/renderer/backend/backend.gyp
index 3ff2de4..3064909 100644
--- a/src/cobalt/renderer/backend/backend.gyp
+++ b/src/cobalt/renderer/backend/backend.gyp
@@ -30,5 +30,31 @@
         '<(DEPTH)/cobalt/renderer/backend/starboard/platform_backend.gyp:renderer_platform_backend',
       ],
     },
+    {
+      'target_name': 'graphics_system_test',
+      'type': '<(gtest_target_type)',
+      'sources': [
+        'graphics_system_test.cc',
+      ],
+      'dependencies': [
+        '<(DEPTH)/cobalt/base/base.gyp:base',
+        '<(DEPTH)/testing/gmock.gyp:gmock',
+        '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+        '<(DEPTH)/cobalt/renderer/backend/backend.gyp:renderer_backend',
+        '<(DEPTH)/cobalt/system_window/system_window.gyp:system_window',
+      ],
+    },
+    {
+      'target_name': 'graphics_system_test_deploy',
+      'type': 'none',
+      'dependencies': [
+        'graphics_system_test',
+      ],
+      'variables': {
+        'executable_name': 'graphics_system_test',
+      },
+      'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
+    },
   ],
 }
diff --git a/src/cobalt/renderer/backend/graphics_system_test.cc b/src/cobalt/renderer/backend/graphics_system_test.cc
new file mode 100644
index 0000000..f6ad30c
--- /dev/null
+++ b/src/cobalt/renderer/backend/graphics_system_test.cc
@@ -0,0 +1,123 @@
+// Copyright 2020 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <algorithm>
+#include <memory>
+
+#include "base/optional.h"
+#include "cobalt/renderer/backend/default_graphics_system.h"
+#include "cobalt/renderer/backend/graphics_context.h"
+#include "cobalt/renderer/backend/graphics_system.h"
+#include "starboard/log.h"
+#include "starboard/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace renderer {
+namespace backend {
+
+namespace {
+
+// Number of initializations to use for measuring the reference creation time.
+const int kReferenceCount = 5;
+}  // namespace
+
+TEST(GraphicsSystemTest, GraphicsSystemCanBeInitializedOften) {
+  // Test whether the graphics system can be initialized often without slowing
+  // down.
+  std::unique_ptr<GraphicsSystem> graphics_system;
+
+  // Treat the first initialization as a 'warm up'.
+  graphics_system = CreateDefaultGraphicsSystem();
+  graphics_system.reset();
+
+  SbTimeMonotonic start = SbTimeGetMonotonicNow();
+  for (int i = 0; i < kReferenceCount; ++i) {
+    graphics_system = CreateDefaultGraphicsSystem();
+    graphics_system.reset();
+  }
+  SbTimeMonotonic time_per_initialization =
+      (SbTimeGetMonotonicNow() - start) / kReferenceCount;
+  SB_LOG(INFO) << "Measured duration "
+               << time_per_initialization / kSbTimeMillisecond
+               << "ms per initialization.";
+
+  // Graphics system initializations should not take more than the maximum of
+  // 200ms or three times as long as the time we just measured.
+  SbTimeMonotonic maximum_time_per_initialization =
+      std::max(3 * time_per_initialization, 200 * kSbTimeMillisecond);
+
+  SbTimeMonotonic last = SbTimeGetMonotonicNow();
+  for (int i = 0; i < 20; ++i) {
+    graphics_system = CreateDefaultGraphicsSystem();
+    graphics_system.reset();
+    SbTimeMonotonic now = SbTimeGetMonotonicNow();
+    SB_LOG(INFO) << "Test duration " << (now - last) / kSbTimeMillisecond
+                 << "ms.";
+    ASSERT_LT(now - last, maximum_time_per_initialization);
+    last = now;
+  }
+}
+
+TEST(GraphicsSystemTest, GraphicsContextCanBeInitializedOften) {
+  // Test whether the graphics system and graphics context can be initialized
+  // often without slowing down.
+  std::unique_ptr<GraphicsSystem> graphics_system;
+  std::unique_ptr<GraphicsContext> graphics_context;
+
+  // Treat the first initialization as a 'warm up'.
+  graphics_system = CreateDefaultGraphicsSystem();
+  graphics_context = graphics_system->CreateGraphicsContext();
+
+  graphics_context.reset();
+  graphics_system.reset();
+
+  SbTimeMonotonic start = SbTimeGetMonotonicNow();
+  for (int i = 0; i < kReferenceCount; ++i) {
+    graphics_system = CreateDefaultGraphicsSystem();
+    graphics_context = graphics_system->CreateGraphicsContext();
+
+    graphics_context.reset();
+    graphics_system.reset();
+  }
+  SbTimeMonotonic time_per_initialization = kSbTimeMillisecond +
+      (SbTimeGetMonotonicNow() - start) / kReferenceCount;
+  SB_LOG(INFO) << "Measured duration "
+               << time_per_initialization / kSbTimeMillisecond
+               << "ms per initialization.";
+
+  // Graphics system and context initializations should not take more than the
+  // maximum of 200ms or three times as long as the time we just measured.
+  SbTimeMonotonic maximum_time_per_initialization =
+      std::max(3 * time_per_initialization, 200 * kSbTimeMillisecond);
+
+  SbTimeMonotonic last = SbTimeGetMonotonicNow();
+  for (int i = 0; i < 20; ++i) {
+    graphics_system = CreateDefaultGraphicsSystem();
+    graphics_context = graphics_system->CreateGraphicsContext();
+
+    graphics_context.reset();
+    graphics_system.reset();
+
+    SbTimeMonotonic now = SbTimeGetMonotonicNow();
+    SB_LOG(INFO) << "Test duration " << (now - last) / kSbTimeMillisecond
+                 << "ms.";
+    ASSERT_LT(now - last, maximum_time_per_initialization);
+    last = now;
+  }
+}
+
+}  // namespace backend
+}  // namespace renderer
+}  // namespace cobalt
diff --git a/src/cobalt/script/global_environment.h b/src/cobalt/script/global_environment.h
index 93b10a6..946211b 100644
--- a/src/cobalt/script/global_environment.h
+++ b/src/cobalt/script/global_environment.h
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "cobalt/script/error_report.h"
 #include "cobalt/script/script_value.h"
@@ -35,7 +36,8 @@
 class SourceCode;
 
 // Manages a handle to a JavaScript engine's global object.
-class GlobalEnvironment : public base::RefCounted<GlobalEnvironment> {
+class GlobalEnvironment : public base::RefCounted<GlobalEnvironment>,
+                          public base::SupportsWeakPtr<GlobalEnvironment> {
  public:
   typedef base::Callback<bool(const ErrorReport& error_report)>
       ReportErrorCallback;
@@ -126,17 +128,19 @@
    public:
     ScopedPreventGarbageCollection(GlobalEnvironment* global_environment,
                                    Wrappable* wrappable)
-        : global_environment(global_environment), wrappable(wrappable) {
+        : global_environment(global_environment->AsWeakPtr()), wrappable(wrappable) {
       global_environment->PreventGarbageCollection(
           base::WrapRefCounted(wrappable));
     }
 
     ~ScopedPreventGarbageCollection() {
-      global_environment->AllowGarbageCollection(wrappable);
+      if (global_environment) {
+        global_environment->AllowGarbageCollection(wrappable);
+      }
     }
 
    private:
-    GlobalEnvironment* global_environment;
+    base::WeakPtr<GlobalEnvironment> global_environment;
     Wrappable* wrappable;
   };
 
diff --git a/src/cobalt/version.h b/src/cobalt/version.h
index 98d2e93..b7fa7b2 100644
--- a/src/cobalt/version.h
+++ b/src/cobalt/version.h
@@ -35,6 +35,6 @@
 //                  release is cut.
 //.
 
-#define COBALT_VERSION "20.lts.3"
+#define COBALT_VERSION "20.lts.4"
 
 #endif  // COBALT_VERSION_H_
diff --git a/src/starboard/android/apk/app/build.gradle b/src/starboard/android/apk/app/build.gradle
index ad09589..231fbb9 100644
--- a/src/starboard/android/apk/app/build.gradle
+++ b/src/starboard/android/apk/app/build.gradle
@@ -33,8 +33,8 @@
 println "TARGET: ${cobaltTarget}"
 
 android {
-    compileSdkVersion 28
-    buildToolsVersion "28.0.3"
+    compileSdkVersion 29
+    buildToolsVersion "29.0.2"
 
     signingConfigs {
         // A signing config that matches what is implicitly used for the "debug" build type.
@@ -48,7 +48,7 @@
     defaultConfig {
         applicationId "dev.cobalt.coat"
         minSdkVersion 21
-        targetSdkVersion 28
+        targetSdkVersion 29
         versionCode 1
         versionName "${buildId}"
         manifestPlaceholders = [applicationName: "CoAT: ${cobaltTarget}"]
@@ -130,10 +130,10 @@
     externalNativeBuild {
         cmake {
             path 'CMakeLists.txt'
-            if (project.hasProperty('cobaltGradleDir')) {
-                // Resolve relative to the current dir at config time by getting a canonical File.
-                buildStagingDirectory new File(cobaltGradleDir, 'externalNativeBuild').canonicalFile
-            }
+            // Move the staging directory to be a sibling of the build directory, which we moved
+            // in the top-level build.gradle to be in a common top-level 'build' directory. Get
+            // the canonical file at config time so that it's still right at build time.
+            buildStagingDirectory new File("${buildDir}.cxx").canonicalFile
         }
     }
 }
@@ -154,9 +154,9 @@
 
 dependencies {
     implementation fileTree(include: ['*.jar'], dir: 'libs')
-    implementation 'com.android.support:support-annotations:28.0.0'
-    implementation 'com.android.support:leanback-v17:28.0.0'
-    implementation 'com.android.support:support-v4:28.0.0'
-    implementation 'com.google.android.gms:play-services-auth:16.0.1'
+    implementation 'androidx.annotation:annotation:1.1.0'
+    implementation 'androidx.leanback:leanback:1.0.0'
+    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+    implementation 'com.google.android.gms:play-services-auth:17.0.0'
     implementation 'com.google.protobuf:protobuf-lite:3.0.1'
 }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/account/UserAuthorizerImpl.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/account/UserAuthorizerImpl.java
index 136da8b..1902635 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/account/UserAuthorizerImpl.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/account/UserAuthorizerImpl.java
@@ -27,10 +27,10 @@
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.Looper;
-import android.support.v4.app.ActivityCompat;
-import android.support.v4.content.ContextCompat;
 import android.text.TextUtils;
 import android.widget.Toast;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
 import com.google.android.gms.auth.GoogleAuthException;
 import com.google.android.gms.auth.GoogleAuthUtil;
 import com.google.android.gms.auth.UserRecoverableAuthException;
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/AudioPermissionRequester.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/AudioPermissionRequester.java
index f8640d8..59ae6ad 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/AudioPermissionRequester.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/AudioPermissionRequester.java
@@ -18,8 +18,8 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.support.v4.app.ActivityCompat;
-import android.support.v4.content.ContextCompat;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
 import dev.cobalt.util.Holder;
 import dev.cobalt.util.UsedByNative;
 
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltA11yHelper.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltA11yHelper.java
index 8c07518..b0fcb50 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltA11yHelper.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltA11yHelper.java
@@ -19,11 +19,11 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v4.widget.ExploreByTouchHelper;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.customview.widget.ExploreByTouchHelper;
 import dev.cobalt.util.Log;
 import java.util.BitSet;
 import java.util.List;
@@ -45,11 +45,19 @@
   private static final int FAKE_VIEW_HEIGHT = 10;
   private static final int FAKE_VIEW_WIDTH = 10;
 
-  private int previousFocusedViewId = 1;
+  private static final int CENTER_VIEW_ID = 5;
+  private static final int UP_VIEW_ID = 2;
+  private static final int DOWN_VIEW_ID = 8;
+  private static final int LEFT_VIEW_ID = 4;
+  private static final int RIGHT_VIEW_ID = 6;
+
+  private static final int INPUT_FOCUS_CHANGE_DELAY = 1500; // milliseconds
+
   // This set tracks whether onPopulateNodeForVirtualView has been
   // called for each virtual view id.
   private final BitSet nodePopulatedSet = new BitSet(9);
   private final Handler handler = new Handler();
+  private boolean unhandledInput;
   private boolean hasInitialFocusBeenSet;
 
   public CobaltA11yHelper(View view) {
@@ -77,70 +85,53 @@
     }
   }
 
-  /**
-   * Returns the "patch number" for a given view id, given a focused view id.
-   *
-   * <p>A "patch number" is a 1-9 number that describes where the requestedViewId is now located on
-   * an X-Y grid, given the focusedViewId.
-   *
-   * <p>Patch number grid:
-   * (0,0)----->X
-   *   |+-+-+-+
-   *   ||1|2|3|
-   *   |+-+-+-|
-   *   ||4|5|6|
-   *   |+-+-+-|
-   *   ||7|8|9|
-   *   |+-+-+-+
-   *  \./ Y
-   *
-   * <p>As focus changes, the locations of the views are moved so the focused view is always in the
-   * middle (patch number 5) and all of the other views always in the same relative position with
-   * respect to each other (with those on the edges adjacent to those on the opposite edges --
-   * wrapping around).
-   *
-   * <p>5 is returned whenever focusedViewId = requestedViewId
-   */
-  private static int getPatchNumber(int focusedViewId, int requestedViewId) {
-    // The (x,y) the focused view has in the 9 patch where 5 is in the middle.
-    int focusedX = (focusedViewId - 1) % 3;
-    int focusedY = (focusedViewId - 1) / 3;
+  private void focusOnCenter() {
+    // Setting Accessibility focus to CENTER_VIEW_ID will make TalkBack focus
+    // on CENTER_VIEW_ID immediately, but the actual mouse focus is either
+    // unchanged or return INVALID_ID.
+    handler.post(
+        new Runnable() {
+          @Override
+          public void run() {
+            sendEventForVirtualView(
+                CENTER_VIEW_ID, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+          }
+        });
 
-    // x and y offsets of focused view where middle is (0, 0)
-    int focusedRelativeToCenterX = focusedX - 1;
-    int focusedRelativeToCenterY = focusedY - 1;
-
-    // The (x,y) the requested view has in the 9 patch where 5 is in the middle.
-    int requestedX = (requestedViewId - 1) % 3;
-    int requestedY = (requestedViewId - 1) / 3;
-
-    // x and y offsets of requested view where middle is (0, 0)
-    int requestedRelativeToCenterX = requestedX - 1;
-    int requestedRelativeToCenterY = requestedY - 1;
-
-    // The (x,y) that the requested view has in the 9 patch when focusedViewId
-    // is in the middle.
-    int translatedRequestedX = (1 + 3 + requestedRelativeToCenterX - focusedRelativeToCenterX) % 3;
-    int translatedRequestedY = (1 + 3 + requestedRelativeToCenterY - focusedRelativeToCenterY) % 3;
-
-    return (translatedRequestedY * 3) + translatedRequestedX + 1;
+    // There is a knwon Android bug about setting focus too early
+    // taking no effect. The impact for Cobalt is that sometimes after
+    // we click on a video, TalkBack sees nothing in focus in the watch
+    // page if no user input happens. To avoid this bug we have to
+    // delay the focus long enough for all the TalkBack movements to settle
+    // down. More details here: https://stackoverflow.com/questions/28472985.
+    handler.postDelayed(
+        new Runnable() {
+          @Override
+          public void run() {
+            sendEventForVirtualView(
+                CENTER_VIEW_ID, AccessibilityEvent.TYPE_VIEW_FOCUSED);
+          }
+        }, INPUT_FOCUS_CHANGE_DELAY);
   }
 
   private void maybeInjectEvent(int currentFocusedViewId) {
-    switch (getPatchNumber(previousFocusedViewId, currentFocusedViewId)) {
-      case 5:
+    if (!unhandledInput) { 
+      return;
+    }
+    switch (currentFocusedViewId) {
+      case CENTER_VIEW_ID:
         // no move;
         break;
-      case 2:
+      case UP_VIEW_ID:
         nativeInjectKeyEvent(SB_KEY_GAMEPAD_DPAD_UP);
         break;
-      case 4:
+      case LEFT_VIEW_ID:
         nativeInjectKeyEvent(SB_KEY_GAMEPAD_DPAD_LEFT);
         break;
-      case 6:
+      case RIGHT_VIEW_ID:
         nativeInjectKeyEvent(SB_KEY_GAMEPAD_DPAD_RIGHT);
         break;
-      case 8:
+      case DOWN_VIEW_ID:
         nativeInjectKeyEvent(SB_KEY_GAMEPAD_DPAD_DOWN);
         break;
       default:
@@ -148,9 +139,26 @@
         // not possible to reach this.
         break;
     }
-    previousFocusedViewId = currentFocusedViewId;
+    unhandledInput = false;
+    focusOnCenter();
   }
 
+  /**
+   * <p>Fake number grid:
+   *   |+-+-+-+
+   *   ||1|2|3|
+   *   |+-+-+-|
+   *   ||4|5|6|
+   *   |+-+-+-|
+   *   ||7|8|9|
+   *   |+-+-+-+
+   *
+   * <p>The focus always starts from the middle number 5. When user changes
+   * focus, the focus is then moved to either 2, 4, 6 or 8 and we can capture
+   * the movement this way. The focus is then quickly switched back to the
+   * center 5 to be ready for the next movement.
+   *
+   */
   @Override
   protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfoCompat node) {
     int focusedViewId = getAccessibilityFocusedVirtualViewId();
@@ -158,18 +166,20 @@
     if (focusedViewId < 1 || focusedViewId > 9) {
       // If this is not one of our nine-patch views, it's probably HOST_ID
       // In any case, assume there is no focus change.
-      focusedViewId = previousFocusedViewId;
+      focusedViewId = CENTER_VIEW_ID;
     }
 
     // onPopulateNodeForVirtualView() gets called at least once every
     // time the focused view changes. So see if it's changed since the
     // last time we've been called and inject an event if so.
-    maybeInjectEvent(focusedViewId);
+    if (focusedViewId != CENTER_VIEW_ID) {
+      maybeInjectEvent(focusedViewId);
+    } else {
+      unhandledInput = true;
+    }
 
-    int patchNumber = getPatchNumber(focusedViewId, virtualViewId);
-
-    int x = (patchNumber - 1) % 3;
-    int y = (patchNumber - 1) / 3;
+    int x = (virtualViewId - 1) % 3;
+    int y = (virtualViewId - 1) / 3;
 
     // Note that the specific bounds here are arbitrary. The importance
     // is the relative bounds to each other.
@@ -188,14 +198,7 @@
       // but not before, ask that the accessibility focus be moved from
       // it's initial position on HOST_ID to the one we want to start with.
       hasInitialFocusBeenSet = true;
-      handler.post(
-          new Runnable() {
-            @Override
-            public void run() {
-              sendEventForVirtualView(
-                  previousFocusedViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
-            }
-          });
+      focusOnCenter();
     }
   }
 
@@ -203,59 +206,4 @@
   protected boolean onPerformActionForVirtualView(int virtualViewId, int action, Bundle arguments) {
     return false;
   }
-
-  /** A simple equivilent to Assert.assertEquals so we don't depend on junit */
-  private static void assertEquals(int expected, int actual) {
-    if (expected != actual) {
-      throw new RuntimeException("Expected " + expected + " actual " + actual);
-    }
-  }
-
-  /**
-   * Unit test for getPatchNumber().
-   *
-   * <p>As of this writing, the Java portion of the Cobalt build has no unit test mechanism.
-   *
-   * <p>To run this test, simply call it from application start and start the application.
-   *
-   * <p>TODO: Move this to a real unit test location when one exists.
-   */
-  private static void testGetPatchNumber() {
-    Log.i(TAG, "+testGetPatchNumber");
-
-    assertEquals(1, getPatchNumber(5, 1));
-    assertEquals(2, getPatchNumber(5, 2));
-    assertEquals(3, getPatchNumber(5, 3));
-    assertEquals(4, getPatchNumber(5, 4));
-    assertEquals(5, getPatchNumber(5, 5));
-    assertEquals(6, getPatchNumber(5, 6));
-    assertEquals(7, getPatchNumber(5, 7));
-    assertEquals(8, getPatchNumber(5, 8));
-    assertEquals(9, getPatchNumber(5, 9));
-
-    for (int i = 1; i <= 9; i++) {
-      assertEquals(5, getPatchNumber(i, i));
-    }
-
-    assertEquals(5, getPatchNumber(1, 1));
-    assertEquals(6, getPatchNumber(1, 2));
-    assertEquals(4, getPatchNumber(1, 3));
-    assertEquals(8, getPatchNumber(1, 4));
-    assertEquals(9, getPatchNumber(1, 5));
-    assertEquals(7, getPatchNumber(1, 6));
-    assertEquals(2, getPatchNumber(1, 7));
-    assertEquals(3, getPatchNumber(1, 8));
-    assertEquals(1, getPatchNumber(1, 9));
-
-    assertEquals(9, getPatchNumber(9, 1));
-    assertEquals(7, getPatchNumber(9, 2));
-    assertEquals(8, getPatchNumber(9, 3));
-    assertEquals(3, getPatchNumber(9, 4));
-    assertEquals(1, getPatchNumber(9, 5));
-    assertEquals(2, getPatchNumber(9, 6));
-    assertEquals(6, getPatchNumber(9, 7));
-    assertEquals(4, getPatchNumber(9, 8));
-    assertEquals(5, getPatchNumber(9, 9));
-    Log.i(TAG, "-testGetPatchNumber");
-  }
 }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/PlatformError.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/PlatformError.java
index db0cae7..999b349 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/PlatformError.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/PlatformError.java
@@ -23,7 +23,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings;
-import android.support.annotation.IntDef;
+import androidx.annotation.IntDef;
 import dev.cobalt.util.Holder;
 import dev.cobalt.util.Log;
 import java.lang.annotation.Retention;
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
index 85c7c56..ee77d2d 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java
@@ -18,7 +18,6 @@
 import static android.media.AudioManager.GET_DEVICES_INPUTS;
 import static dev.cobalt.util.Log.TAG;
 
-import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
@@ -34,6 +33,7 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.CaptioningManager;
+import androidx.annotation.RequiresApi;
 import dev.cobalt.account.UserAuthorizer;
 import dev.cobalt.feedback.FeedbackService;
 import dev.cobalt.media.AudioOutputManager;
@@ -146,7 +146,7 @@
 
   @SuppressWarnings("unused")
   @UsedByNative
-  void beforeStartOrResume() {
+  protected void beforeStartOrResume() {
     Log.i(TAG, "Prepare to resume");
     // Bring our platform services to life before resuming so that they're ready to deal with
     // whatever the web app wants to do with them as part of its start/resume logic.
@@ -159,7 +159,7 @@
 
   @SuppressWarnings("unused")
   @UsedByNative
-  void beforeSuspend() {
+  protected void beforeSuspend() {
     Log.i(TAG, "Prepare to suspend");
     // We want the MediaSession to be deactivated immediately before suspending so that by the time
     // the launcher is visible our "Now Playing" card is already gone. Then Cobalt and the web app
@@ -173,7 +173,7 @@
 
   @SuppressWarnings("unused")
   @UsedByNative
-  void afterStopped() {
+  protected void afterStopped() {
     starboardStopped = true;
     ttsHelper.shutdown();
     userAuthorizer.shutdown();
@@ -326,7 +326,7 @@
     }
   }
 
-  @TargetApi(23)
+  @RequiresApi(23)
   private boolean isMicrophoneConnectedV23() {
     // A check specifically for microphones is not available before API 28, so it is assumed that a
     // connected input audio device is a microphone.
@@ -430,7 +430,7 @@
   /** Returns string for kSbSystemPropertyUserAgentAuxField */
   @SuppressWarnings("unused")
   @UsedByNative
-  String getUserAgentAuxField() {
+  protected String getUserAgentAuxField() {
     StringBuilder sb = new StringBuilder();
 
     String packageName = appContext.getApplicationInfo().packageName;
@@ -497,7 +497,7 @@
    * https://developer.android.com/reference/android/view/Display.HdrCapabilities.html for valid
    * values.
    */
-  @TargetApi(24)
+  @RequiresApi(24)
   @SuppressWarnings("unused")
   @UsedByNative
   public boolean isHdrTypeSupported(int hdrType) {
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java
index d76266f..8fb4945 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/ArtworkLoader.java
@@ -19,9 +19,9 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.AsyncTask;
-import android.support.annotation.NonNull;
 import android.util.Pair;
 import android.util.Size;
+import androidx.annotation.NonNull;
 import dev.cobalt.util.Log;
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
index 2105e5b..0508e27 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
@@ -16,11 +16,11 @@
 
 import static dev.cobalt.media.Log.TAG;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.os.Build;
+import androidx.annotation.RequiresApi;
 import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
 import java.util.ArrayList;
@@ -85,7 +85,7 @@
   }
 
   /** Returns the maximum number of HDMI channels for API 23 and above. */
-  @TargetApi(23)
+  @RequiresApi(23)
   private int getMaxChannelsV23() {
     AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
     AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
index ec1fd88..419f5d8 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -16,13 +16,13 @@
 
 import static dev.cobalt.media.Log.TAG;
 
-import android.annotation.TargetApi;
 import android.media.AudioAttributes;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioTimestamp;
 import android.media.AudioTrack;
 import android.os.Build;
+import androidx.annotation.RequiresApi;
 import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
 import java.nio.ByteBuffer;
@@ -213,7 +213,7 @@
     return 0;
   }
 
-  @TargetApi(24)
+  @RequiresApi(24)
   private int getUnderrunCountV24() {
     if (audioTrack == null) {
       Log.e(TAG, "Unable to call getUnderrunCount() with NULL audio track.");
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
index ae511c9..2bfce23 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/CobaltMediaSession.java
@@ -16,7 +16,6 @@
 
 import static dev.cobalt.media.Log.TAG;
 
-import android.annotation.TargetApi;
 import android.app.Activity;
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -31,6 +30,7 @@
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.view.WindowManager;
+import androidx.annotation.RequiresApi;
 import dev.cobalt.util.DisplayUtil;
 import dev.cobalt.util.Holder;
 import dev.cobalt.util.Log;
@@ -262,7 +262,7 @@
         .requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
   }
 
-  @TargetApi(26)
+  @RequiresApi(26)
   private int requestAudioFocusV26() {
     if (audioFocusRequest == null) {
       AudioAttributes audioAtrributes =
@@ -281,7 +281,7 @@
     getAudioManager().abandonAudioFocus(this);
   }
 
-  @TargetApi(26)
+  @RequiresApi(26)
   private void abandonAudioFocusV26() {
     if (audioFocusRequest != null) {
       getAudioManager().abandonAudioFocusRequest(audioFocusRequest);
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
index 1c597b2..064fc65 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -327,6 +327,7 @@
       hdrStaticInfo.putShort((short) (minMasteringLuminance + 0.5f));
       hdrStaticInfo.putShort((short) DEFAULT_MAX_CLL);
       hdrStaticInfo.putShort((short) DEFAULT_MAX_FALL);
+      hdrStaticInfo.rewind();
       this.hdrStaticInfo = hdrStaticInfo;
     }
   }
@@ -495,7 +496,7 @@
     boolean shouldConfigureHdr =
         android.os.Build.VERSION.SDK_INT >= 24
             && colorInfo != null
-            && MediaCodecUtil.isHdrCapableVp9Decoder(findVideoDecoderResult);
+            && MediaCodecUtil.isHdrCapableVideoDecoder(mime, findVideoDecoderResult);
     if (shouldConfigureHdr) {
       Log.d(TAG, "Setting HDR info.");
       mediaFormat.setInteger(MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer);
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
index b7fdad8..8991611 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecUtil.java
@@ -44,6 +44,7 @@
   private static boolean isVp9WhiteListed;
   private static final String SECURE_DECODER_SUFFIX = ".secure";
   private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
+  private static final String AV1_MIME_TYPE = "video/av01";
 
   /**
    * A simple "struct" to bundle up the results from findVideoDecoder, as its clients may require
@@ -392,7 +393,7 @@
     FindVideoDecoderResult findVideoDecoderResult =
         findVideoDecoder(mimeType, secure, frameWidth, frameHeight, bitrate, fps, mustSupportHdr);
     return !findVideoDecoderResult.name.equals("")
-        && (!mustSupportHdr || isHdrCapableVp9Decoder(findVideoDecoderResult));
+        && (!mustSupportHdr || isHdrCapableVideoDecoder(mimeType, findVideoDecoderResult));
   }
 
   /**
@@ -405,23 +406,32 @@
     return !findAudioDecoder(mimeType, bitrate).equals("");
   }
 
-  /** Determine whether the system has a decoder capable of playing HDR VP9. */
+  /**
+   * Determine whether the system has a decoder capable of playing HDR. Currently VP9 and AV1 are
+   * HDR supported codecs
+   */
   @SuppressWarnings("unused")
   @UsedByNative
-  public static boolean hasHdrCapableVp9Decoder() {
+  public static boolean hasHdrCapableVideoDecoder(String mimeType) {
     // VP9Profile* values were not added until API level 24.  See
     // https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel.html.
     if (Build.VERSION.SDK_INT < 24) {
       return false;
     }
+    // AV1ProfileMain10HDR10 value was not added until API level 29.  See
+    // https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel.html.
+    if (mimeType.equals(AV1_MIME_TYPE) && Build.VERSION.SDK_INT < 29) {
+      return false;
+    }
 
     FindVideoDecoderResult findVideoDecoderResult =
-        findVideoDecoder(VP9_MIME_TYPE, false, 0, 0, 0, 0, true);
-    return isHdrCapableVp9Decoder(findVideoDecoderResult);
+        findVideoDecoder(mimeType, false, 0, 0, 0, 0, true);
+    return isHdrCapableVideoDecoder(mimeType, findVideoDecoderResult);
   }
 
-  /** Determine whether findVideoDecoderResult is capable of playing HDR VP9 */
-  public static boolean isHdrCapableVp9Decoder(FindVideoDecoderResult findVideoDecoderResult) {
+  /** Determine whether findVideoDecoderResult is capable of playing HDR */
+  public static boolean isHdrCapableVideoDecoder(
+      String mimeType, FindVideoDecoderResult findVideoDecoderResult) {
     CodecCapabilities codecCapabilities = findVideoDecoderResult.codecCapabilities;
     if (codecCapabilities == null) {
       return false;
@@ -431,9 +441,15 @@
       return false;
     }
     for (CodecProfileLevel codecProfileLevel : codecProfileLevels) {
-      if (codecProfileLevel.profile == CodecProfileLevel.VP9Profile2HDR
-          || codecProfileLevel.profile == CodecProfileLevel.VP9Profile3HDR) {
-        return true;
+      if (mimeType.equals(VP9_MIME_TYPE)) {
+        if (codecProfileLevel.profile == CodecProfileLevel.VP9Profile2HDR
+            || codecProfileLevel.profile == CodecProfileLevel.VP9Profile3HDR) {
+          return true;
+        }
+      } else if (mimeType.equals(AV1_MIME_TYPE)) {
+        if (codecProfileLevel.profile == CodecProfileLevel.AV1ProfileMain10HDR10) {
+          return true;
+        }
       }
     }
 
@@ -569,7 +585,8 @@
                 : name;
         FindVideoDecoderResult findVideoDecoderResult =
             new FindVideoDecoderResult(resultName, videoCapabilities, codecCapabilities);
-        if (hdr && !isHdrCapableVp9Decoder(findVideoDecoderResult)) {
+        if (hdr && !isHdrCapableVideoDecoder(mimeType, findVideoDecoderResult)) {
+          Log.v(TAG, String.format("Rejecting %s, reason: codec does not support HDR", name));
           continue;
         }
         Log.v(TAG, String.format("Found suitable decoder, %s", name));
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
index 24fe96f..3af5ad5 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
@@ -20,7 +20,6 @@
 
 import static dev.cobalt.media.Log.TAG;
 
-import android.annotation.TargetApi;
 import android.media.DeniedByServerException;
 import android.media.MediaCrypto;
 import android.media.MediaCryptoException;
@@ -30,6 +29,7 @@
 import android.media.NotProvisionedException;
 import android.media.UnsupportedSchemeException;
 import android.os.Build;
+import androidx.annotation.RequiresApi;
 import dev.cobalt.coat.CobaltHttpHelper;
 import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
@@ -429,7 +429,7 @@
     mMediaDrm.setPropertyString("sessionSharing", "enable");
   }
 
-  @TargetApi(23)
+  @RequiresApi(23)
   private void setOnKeyStatusChangeListenerV23() {
     mMediaDrm.setOnKeyStatusChangeListener(
         new MediaDrm.OnKeyStatusChangeListener() {
@@ -546,7 +546,12 @@
         Log.e(TAG, "Failed to provision device during MediaCrypto creation.");
         return false;
       }
-      return true;
+      try {
+        mMediaCryptoSession = openSession();
+      } catch (NotProvisionedException e2) {
+        Log.e(TAG, "Device still not provisioned after supposedly successful provisioning", e2);
+        return false;
+      }
     }
 
     if (mMediaCryptoSession == null) {
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/VideoFrameReleaseTimeHelper.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/VideoFrameReleaseTimeHelper.java
index 4287c8c..0274811 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/VideoFrameReleaseTimeHelper.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/VideoFrameReleaseTimeHelper.java
@@ -29,7 +29,6 @@
 
 package dev.cobalt.media;
 
-import android.annotation.TargetApi;
 import android.content.Context;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -37,10 +36,11 @@
 import android.view.Choreographer;
 import android.view.Choreographer.FrameCallback;
 import android.view.WindowManager;
+import androidx.annotation.RequiresApi;
 import dev.cobalt.util.UsedByNative;
 
 /** Makes a best effort to adjust frame release timestamps for a smoother visual result. */
-@TargetApi(16)
+@RequiresApi(16)
 @SuppressWarnings("unused")
 @UsedByNative
 public final class VideoFrameReleaseTimeHelper {
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/storage/CobaltStorageLoader.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/storage/CobaltStorageLoader.java
index c471b0c..680445f 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/storage/CobaltStorageLoader.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/storage/CobaltStorageLoader.java
@@ -17,7 +17,7 @@
 import static dev.cobalt.util.Log.TAG;
 
 import android.os.FileObserver;
-import android.support.annotation.Nullable;
+import androidx.annotation.Nullable;
 import com.google.protobuf.InvalidProtocolBufferException;
 import dev.cobalt.storage.StorageProto.Storage;
 import dev.cobalt.util.Log;
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/Holder.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/Holder.java
index 7fa3407..ad48bbd 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/Holder.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/Holder.java
@@ -14,7 +14,7 @@
 
 package dev.cobalt.util;
 
-import android.support.annotation.Nullable;
+import androidx.annotation.Nullable;
 
 /** Holds a mutable reference to an object, or null. */
 public class Holder<T> {
diff --git a/src/starboard/android/apk/build.gradle b/src/starboard/android/apk/build.gradle
index b50ae22..525c56b 100644
--- a/src/starboard/android/apk/build.gradle
+++ b/src/starboard/android/apk/build.gradle
@@ -20,7 +20,7 @@
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.3.0'
+        classpath 'com.android.tools.build:gradle:3.5.3'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
@@ -53,3 +53,10 @@
 // '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 }
+
+// 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.
+if (hasProperty('android.injected.build.abi')) {
+    def firstAbi = properties.get('android.injected.build.abi').split(',').first()
+    allprojects { setProperty('android.injected.build.abi', firstAbi) }
+}
diff --git a/src/starboard/android/apk/build.id b/src/starboard/android/apk/build.id
new file mode 100644
index 0000000..2a2074e
--- /dev/null
+++ b/src/starboard/android/apk/build.id
@@ -0,0 +1 @@
+263411
\ No newline at end of file
diff --git a/src/starboard/android/apk/cobalt-gradle.sh b/src/starboard/android/apk/cobalt-gradle.sh
index d135b4b..4a37c4f 100755
--- a/src/starboard/android/apk/cobalt-gradle.sh
+++ b/src/starboard/android/apk/cobalt-gradle.sh
@@ -22,7 +22,6 @@
 while [ "$1" ]; do
   case "$1" in
     --sdk) shift; ANDROID_HOME="$1" ;;
-    --ndk) shift; ANDROID_NDK_HOME="$1" ;;
     --cache) shift; mkdir -p "$1";
              GRADLE_ARGS+=("--project-cache-dir" $(cd "$1"; pwd)) ;;
     --reset) RESET_GRADLE=1 ;;
@@ -50,9 +49,7 @@
 fi
 
 export ANDROID_HOME
-export ANDROID_NDK_HOME
 echo "ANDROID_HOME=${ANDROID_HOME}"
-echo "ANDROID_NDK_HOME=${ANDROID_NDK_HOME}"
 echo "TASK: ${GRADLE_ARGS[-1]}"
 
 # Allow parallel gradle builds, as defined by a COBALT_GRADLE_BUILD_COUNT envvar
diff --git a/src/starboard/android/apk/gradle.properties b/src/starboard/android/apk/gradle.properties
index 2d5bfe3..d9f30ec 100644
--- a/src/starboard/android/apk/gradle.properties
+++ b/src/starboard/android/apk/gradle.properties
@@ -23,6 +23,8 @@
 
 # Specifies the JVM arguments used for the daemon process.
 # The setting is particularly useful for tweaking memory settings.
+android.enableJetifier=true
+android.useAndroidX=true
 org.gradle.jvmargs=-Xmx4g
 
 # When configured, Gradle will run in incubating parallel mode.
diff --git a/src/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties b/src/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties
index b17c1fc..cb820ac 100644
--- a/src/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties
+++ b/src/starboard/android/apk/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Jan 29 11:03:00 PST 2019
+#Thu Jan 02 11:15:25 PST 2020
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/src/starboard/android/shared/application_android.cc b/src/starboard/android/shared/application_android.cc
index cae801e..24b5517 100644
--- a/src/starboard/android/shared/application_android.cc
+++ b/src/starboard/android/shared/application_android.cc
@@ -362,7 +362,6 @@
 }
 
 void ApplicationAndroid::ProcessAndroidInput() {
-  SB_DCHECK(input_events_generator_);
   AInputEvent* android_event = NULL;
   while (AInputQueue_getEvent(input_queue_, &android_event) >= 0) {
     SB_LOG(INFO) << "Android input: type="
@@ -370,6 +369,11 @@
     if (AInputQueue_preDispatchEvent(input_queue_, android_event)) {
         continue;
     }
+    if (!input_events_generator_) {
+      SB_DLOG(WARNING) << "Android input event ignored without an SbWindow.";
+      AInputQueue_finishEvent(input_queue_, android_event, false);
+      continue;
+    }
     InputEventsGenerator::Events app_events;
     bool handled = input_events_generator_->CreateInputEventsFromAndroidEvent(
         android_event, &app_events);
@@ -385,7 +389,10 @@
   int err = read(keyboard_inject_readfd_, &key, sizeof(key));
   SB_DCHECK(err >= 0) << "Keyboard inject read failed: errno=" << errno;
   SB_LOG(INFO) << "Keyboard inject: " << key;
-
+  if (!input_events_generator_) {
+    SB_DLOG(WARNING) << "Injected input event ignored without an SbWindow.";
+    return;
+  }
   InputEventsGenerator::Events app_events;
   input_events_generator_->CreateInputEventsFromSbKey(key, &app_events);
   for (int i = 0; i < app_events.size(); ++i) {
diff --git a/src/starboard/android/shared/cobalt/android_media_session_client.cc b/src/starboard/android/shared/cobalt/android_media_session_client.cc
index 1861af9..30ec630 100644
--- a/src/starboard/android/shared/cobalt/android_media_session_client.cc
+++ b/src/starboard/android/shared/cobalt/android_media_session_client.cc
@@ -289,12 +289,16 @@
       }
     }
 
-    jlong duration = session_state.duration();
-    // Set duration to negative if duration is unknown or infinite, as with live
-    // playback.
-    // https://developer.android.com/reference/android/support/v4/media/MediaMetadataCompat#METADATA_KEY_DURATION
-    if (duration == kSbTimeMax) {
-      duration = -1;
+    jlong durationInMilliseconds;
+    if (session_state.duration() == kSbTimeMax) {
+      // Set duration to negative if duration is unknown or infinite, as with live
+      // playback.
+      // https://developer.android.com/reference/android/support/v4/media/MediaMetadataCompat#METADATA_KEY_DURATION
+      durationInMilliseconds = -1;
+    } else {
+      // SbTime is measured in microseconds while Android MediaSession expects
+      // duration in milliseconds.
+      durationInMilliseconds = session_state.duration() / kSbTimeMillisecond;
     }
 
     env->CallStarboardVoidMethodOrAbort(
@@ -305,7 +309,7 @@
         session_state.current_playback_position() / kSbTimeMillisecond,
         static_cast<jfloat>(session_state.actual_playback_rate()),
         j_title.Get(), j_artist.Get(), j_album.Get(), j_artwork.Get(),
-        duration);
+        durationInMilliseconds);
   }
 };
 
diff --git a/src/starboard/android/shared/cobalt/configuration.py b/src/starboard/android/shared/cobalt/configuration.py
index 3633045..8fac1a4 100644
--- a/src/starboard/android/shared/cobalt/configuration.py
+++ b/src/starboard/android/shared/cobalt/configuration.py
@@ -60,6 +60,7 @@
         # Disabled because of: Fail (Tests do not account for player with url?).
         ('csp/WebPlatformTest.Run/'
          'content_security_policy_media_src_media_src_allowed_html'),
+        ('websockets/WebPlatformTest.Run/websockets_*'),
     ]
     return filters
 
diff --git a/src/starboard/android/shared/configuration_public.h b/src/starboard/android/shared/configuration_public.h
index 8236b6e..33e0714 100644
--- a/src/starboard/android/shared/configuration_public.h
+++ b/src/starboard/android/shared/configuration_public.h
@@ -330,6 +330,11 @@
 // stack size for media stack threads.
 #define SB_MEDIA_THREAD_STACK_SIZE 0U
 
+// Specifies whether this platform support the improved player creation and
+// output mode query.  Please see comments of SbPlayerCreate() and
+// SbPlayerGetPreferredOutputMode() in player.h for more details.
+#define SB_HAS_PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT 1
+
 // --- Decoder-only Params ---
 
 // Specifies how media buffers must be aligned on this platform as some
diff --git a/src/starboard/android/shared/log.cc b/src/starboard/android/shared/log.cc
index 35ed73d..6cbc17f 100644
--- a/src/starboard/android/shared/log.cc
+++ b/src/starboard/android/shared/log.cc
@@ -25,11 +25,8 @@
 #include "starboard/common/log.h"
 #include "starboard/common/string.h"
 #include "starboard/configuration.h"
-#include "starboard/thread.h"
-
-#if SB_API_VERSION >= 11
 #include "starboard/shared/starboard/log_mutex.h"
-#endif  // SB_API_VERSION >= 11
+#include "starboard/thread.h"
 
 using starboard::android::shared::JniEnvExt;
 using starboard::android::shared::ScopedLocalJavaRef;
diff --git a/src/starboard/android/shared/media_decoder.cc b/src/starboard/android/shared/media_decoder.cc
index 482b6a8..f1a3dfb 100644
--- a/src/starboard/android/shared/media_decoder.cc
+++ b/src/starboard/android/shared/media_decoder.cc
@@ -18,11 +18,9 @@
 #include "starboard/android/shared/jni_utils.h"
 #include "starboard/android/shared/media_common.h"
 #include "starboard/audio_sink.h"
-#if SB_API_VERSION >= 11
-#include "starboard/format_string.h"
-#endif  // SB_API_VERSION >= 11
 #include "starboard/common/log.h"
 #include "starboard/common/string.h"
+#include "starboard/format_string.h"
 #include "starboard/shared/pthread/thread_create_priority.h"
 
 namespace starboard {
diff --git a/src/starboard/android/shared/media_is_video_supported.cc b/src/starboard/android/shared/media_is_video_supported.cc
index 239130a..d5260c9 100644
--- a/src/starboard/android/shared/media_is_video_supported.cc
+++ b/src/starboard/android/shared/media_is_video_supported.cc
@@ -33,15 +33,21 @@
 const jint HDR_TYPE_HDR10 = 2;
 const jint HDR_TYPE_HLG = 3;
 
-bool IsHDRTransferCharacteristicsSupported(SbMediaTransferId transfer_id) {
-  SB_DCHECK(transfer_id != kSbMediaTransferIdBt709 &&
-            transfer_id != kSbMediaTransferIdUnspecified);
-  // An HDR capable VP9 decoder is needed to handle HDR at all.
-  bool has_hdr_capable_vp9_decoder =
+bool IsHDRTransferCharacteristicsSupported(SbMediaVideoCodec video_codec,
+                                           SbMediaTransferId transfer_id) {
+  const char* mime = SupportedVideoCodecToMimeType(video_codec);
+  if (!mime) {
+    return false;
+  }
+  JniEnvExt* env = JniEnvExt::Get();
+
+  // An HDR capable VP9 or AV1 decoder is needed to handle HDR at all.
+  bool has_hdr_capable_decoder =
       JniEnvExt::Get()->CallStaticBooleanMethodOrAbort(
-          "dev/cobalt/media/MediaCodecUtil", "hasHdrCapableVp9Decoder",
-          "()Z") == JNI_TRUE;
-  if (!has_hdr_capable_vp9_decoder) {
+          "dev/cobalt/media/MediaCodecUtil", "hasHdrCapableVideoDecoder",
+          "(Ljava/lang/String;)Z",
+          env->NewStringStandardUTFOrAbort(mime)) == JNI_TRUE;
+  if (!has_hdr_capable_decoder) {
     return false;
   }
 
@@ -75,7 +81,7 @@
                                        int fps,
                                        bool decode_to_texture_required) {
   if (!IsSDRVideo(bit_depth, primary_id, transfer_id, matrix_id)) {
-    if (!IsHDRTransferCharacteristicsSupported(transfer_id)) {
+    if (!IsHDRTransferCharacteristicsSupported(video_codec, transfer_id)) {
       return false;
     }
   }
diff --git a/src/starboard/android/shared/platform_deploy.gypi b/src/starboard/android/shared/platform_deploy.gypi
index 3d6e60f..ecc7699 100644
--- a/src/starboard/android/shared/platform_deploy.gypi
+++ b/src/starboard/android/shared/platform_deploy.gypi
@@ -40,14 +40,13 @@
       'action': [
         '<(DEPTH)/starboard/android/apk/cobalt-gradle.sh',
         '--sdk', '<(ANDROID_HOME)',
-        '--ndk', '<(NDK_HOME)',
-        '--cache', '<(GRADLE_FILES_DIR)/cache',
-        '--project-dir', '<(DEPTH)/starboard/android/apk/',
+        '--cache', '$${PWD}/<(GRADLE_FILES_DIR)/cache',
+        '--project-dir', '$${PWD}/<(DEPTH)/starboard/android/apk/',
         '-P', 'cobaltBuildAbi=<(ANDROID_ABI)',
-        '-P', 'cobaltDeployApk=<(apk)',
-        '-P', 'cobaltContentDir=<(content_deploy_dir)',
-        '-P', 'cobaltGradleDir=<(GRADLE_FILES_DIR)',
-        '-P', 'cobaltProductDir=<(PRODUCT_DIR)',
+        '-P', 'cobaltDeployApk=$${PWD}/<(apk)',
+        '-P', 'cobaltContentDir=$${PWD}/<(content_deploy_dir)',
+        '-P', 'cobaltGradleDir=$${PWD}/<(GRADLE_FILES_DIR)',
+        '-P', 'cobaltProductDir=$${PWD}/<(PRODUCT_DIR)',
         '-P', 'cobaltTarget=<(executable_name)',
         'assembleCobalt_<(GRADLE_BUILD_TYPE)',
       ],
diff --git a/src/starboard/android/shared/player_create.cc b/src/starboard/android/shared/player_create.cc
index 7edd8d6..142eaab 100644
--- a/src/starboard/android/shared/player_create.cc
+++ b/src/starboard/android/shared/player_create.cc
@@ -30,27 +30,38 @@
     UpdateActiveSessionPlatformPlaybackState;
 
 SbPlayer SbPlayerCreate(SbWindow window,
-                        SbMediaVideoCodec video_codec,
-                        SbMediaAudioCodec audio_codec,
-#if SB_API_VERSION < 10
-                        SbMediaTime duration_pts,
-#endif  // SB_API_VERSION < 10
-                        SbDrmSystem drm_system,
-                        const SbMediaAudioSampleInfo* audio_sample_info,
-                        const char* max_video_capabilities,
+                        const SbPlayerCreationParam* creation_param,
                         SbPlayerDeallocateSampleFunc sample_deallocate_func,
                         SbPlayerDecoderStatusFunc decoder_status_func,
                         SbPlayerStatusFunc player_status_func,
                         SbPlayerErrorFunc player_error_func,
                         void* context,
-                        SbPlayerOutputMode output_mode,
                         SbDecodeTargetGraphicsContextProvider* provider) {
   SB_UNREFERENCED_PARAMETER(window);
-  SB_UNREFERENCED_PARAMETER(max_video_capabilities);
-  SB_UNREFERENCED_PARAMETER(provider);
-#if SB_API_VERSION < 10
-  SB_UNREFERENCED_PARAMETER(duration_pts);
-#endif  // SB_API_VERSION < 10
+
+  if (!creation_param) {
+    SB_LOG(ERROR) << "CreationParam cannot be null.";
+    return kSbPlayerInvalid;
+  }
+
+  if (!creation_param->audio_mime) {
+    SB_LOG(ERROR) << "creation_param->audio_mime cannot be null.";
+    return kSbPlayerInvalid;
+  }
+  if (!creation_param->video_mime) {
+    SB_LOG(ERROR) << "creation_param->video_mime cannot be null.";
+    return kSbPlayerInvalid;
+  }
+  if (!creation_param->max_video_capabilities) {
+    SB_LOG(ERROR) << "creation_param->max_video_capabilities cannot be null.";
+    return kSbPlayerInvalid;
+  }
+
+  SB_LOG(INFO) << "SbPlayerCreate() called with audio mime \""
+               << creation_param->audio_mime << "\", video mime \""
+               << creation_param->video_mime
+               << "\", and max video capabilities \""
+               << creation_param->max_video_capabilities << "\".";
 
   if (!sample_deallocate_func || !decoder_status_func || !player_status_func
 #if SB_HAS(PLAYER_ERROR_MESSAGE)
@@ -60,6 +71,9 @@
     return kSbPlayerInvalid;
   }
 
+  auto audio_codec = creation_param->audio_sample_info.codec;
+  auto video_codec = creation_param->video_sample_info.codec;
+
   if (audio_codec != kSbMediaAudioCodecNone &&
       audio_codec != kSbMediaAudioCodecAac &&
       audio_codec != kSbMediaAudioCodecOpus) {
@@ -67,13 +81,6 @@
     return kSbPlayerInvalid;
   }
 
-  if (audio_codec == kSbMediaAudioCodecAac && !audio_sample_info) {
-    SB_LOG(ERROR)
-        << "SbPlayerCreate() requires a non-NULL SbMediaAudioSampleInfo "
-        << "when |audio_codec| is not kSbMediaAudioCodecNone";
-    return kSbPlayerInvalid;
-  }
-
   if (video_codec != kSbMediaVideoCodecNone &&
       video_codec != kSbMediaVideoCodecH264 &&
       video_codec != kSbMediaVideoCodecVp9 &&
@@ -89,7 +96,8 @@
     return kSbPlayerInvalid;
   }
 
-  if (!SbPlayerOutputModeSupported(output_mode, video_codec, drm_system)) {
+  auto output_mode = creation_param->output_mode;
+  if (SbPlayerGetPreferredOutputMode(creation_param) != output_mode) {
     SB_LOG(ERROR) << "Unsupported player output mode " << output_mode;
     return kSbPlayerInvalid;
   }
@@ -103,13 +111,13 @@
   UpdateActiveSessionPlatformPlaybackState(kPlaying);
 
   starboard::scoped_ptr<PlayerWorker::Handler> handler(
-      new FilterBasedPlayerWorkerHandler(video_codec, audio_codec, drm_system,
-                                         audio_sample_info, output_mode,
-                                         provider));
+      new FilterBasedPlayerWorkerHandler(
+          video_codec, audio_codec, creation_param->drm_system,
+          &creation_param->audio_sample_info, output_mode, provider));
   SbPlayer player = SbPlayerPrivate::CreateInstance(
-      audio_codec, video_codec, audio_sample_info, sample_deallocate_func,
-      decoder_status_func, player_status_func, player_error_func, context,
-      handler.Pass());
+      audio_codec, video_codec, &creation_param->audio_sample_info,
+      sample_deallocate_func, decoder_status_func, player_status_func,
+      player_error_func, context, handler.Pass());
 
   // TODO: accomplish this through more direct means.
   // Set the bounds to initialize the VideoSurfaceView. The initial values don't
diff --git a/src/starboard/android/shared/sdk_utils.py b/src/starboard/android/shared/sdk_utils.py
index bc0c11c..2618b8b 100644
--- a/src/starboard/android/shared/sdk_utils.py
+++ b/src/starboard/android/shared/sdk_utils.py
@@ -13,36 +13,36 @@
 # limitations under the License.
 """Utilities to use the toolchain from the Android NDK."""
 
-import ConfigParser
+import errno
 import fcntl
-import hashlib
 import logging
 import os
 import re
 import shutil
-import StringIO
 import subprocess
 import sys
 import time
-import urllib
 import zipfile
+import requests
 
 from starboard.tools import build
 
+# Which version of the Android NDK to install and build with.
+_NDK_VERSION = '20.1.5948944'
+
 # Packages to install in the Android SDK.
 # We download ndk-bundle separately, so it's not in this list.
 # Get available packages from "sdkmanager --list --verbose"
 _ANDROID_SDK_PACKAGES = [
-    'build-tools;28.0.3',
-    'cmake;3.6.4111459',
+    'build-tools;29.0.2',
+    'cmake;3.10.2.4988404',
     'emulator',
     'extras;android;m2repository',
     'extras;google;m2repository',
-    'lldb;3.1',
+    'ndk;' + _NDK_VERSION,
     'patcher;v4',
-    'platforms;android-28',
+    'platforms;android-29',
     'platform-tools',
-    'tools',
 ]
 
 # Seconds to sleep before writing "y" for android sdk update license prompt.
@@ -50,11 +50,11 @@
 
 # Location from which to download the SDK command-line tools
 # see https://developer.android.com/studio/index.html#command-tools
-_SDK_URL = 'https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip'
+_SDK_URL = 'https://dl.google.com/android/repository/commandlinetools-linux-6200805_latest.zip'
 
 # Location from which to download the Android NDK.
 # see https://developer.android.com/ndk/downloads (perhaps in "NDK archives")
-_NDK_ZIP_REVISION = 'android-ndk-r19c'
+_NDK_ZIP_REVISION = 'android-ndk-r20b'
 _NDK_ZIP_FILE = _NDK_ZIP_REVISION + '-linux-x86_64.zip'
 _NDK_URL = 'https://dl.google.com/android/repository/' + _NDK_ZIP_FILE
 
@@ -70,44 +70,18 @@
 else:
   _SDK_PATH = _STARBOARD_TOOLCHAINS_SDK_DIR
 
-_ANDROID_NDK_HOME = os.environ.get('ANDROID_NDK_HOME')
-if _ANDROID_NDK_HOME:
-  _NDK_PATH = _ANDROID_NDK_HOME
-else:
-  _NDK_PATH = os.path.join(_SDK_PATH, 'ndk-bundle')
+_NDK_PATH = os.path.join(_SDK_PATH, 'ndk', _NDK_VERSION)
 
-_SDKMANAGER_TOOL = os.path.join(_SDK_PATH, 'tools', 'bin', 'sdkmanager')
-
-# Maps the Android ABI to the architecture name of the toolchain.
-_TOOLS_ABI_ARCH_MAP = {
-    'x86': 'x86',
-    'armeabi': 'arm',
-    'armeabi-v7a': 'arm',
-    'arm64-v8a': 'arm64',
-}
-
-_SCRIPT_HASH_PROPERTY = 'SdkUtils.Hash'
-
-with open(__file__, 'rb') as script:
-  _SCRIPT_HASH = hashlib.md5(script.read()).hexdigest()
+_SDKMANAGER_TOOL = os.path.join(_SDK_PATH, 'cmdline-tools', '1.0', 'bin',
+                                'sdkmanager')
 
 
-def _CheckStamp(dir_path):
-  """Checks that the specified directory is up-to-date with the NDK."""
-  stamp_path = os.path.join(dir_path, 'ndk.stamp')
-  return (os.path.exists(stamp_path) and
-          _ReadNdkRevision(stamp_path) == _GetInstalledNdkRevision() and
-          _ReadProperty(stamp_path, _SCRIPT_HASH_PROPERTY) == _SCRIPT_HASH)
-
-
-def _UpdateStamp(dir_path):
-  """Updates the stamp file in the specified directory to the NDK revision."""
-  path = GetNdkPath()
-  properties_path = os.path.join(path, 'source.properties')
-  stamp_path = os.path.join(dir_path, 'ndk.stamp')
-  shutil.copyfile(properties_path, stamp_path)
-  with open(stamp_path, 'a') as stamp:
-    stamp.write('{} = {}\n'.format(_SCRIPT_HASH_PROPERTY, _SCRIPT_HASH))
+def _MakeDirs(destination_path):
+  try:
+    os.makedirs(destination_path)
+  except OSError as e:
+    if e.errno != errno.EEXIST:
+      raise
 
 
 def GetNdkPath():
@@ -118,34 +92,12 @@
   return _SDK_PATH
 
 
-def _ReadNdkRevision(properties_path):
-  return _ReadProperty(properties_path, 'pkg.revision')
-
-
-def _ReadProperty(properties_path, property_key):
-  with open(properties_path, 'r') as f:
-    ini_str = '[properties]\n' + f.read()
-  config = ConfigParser.RawConfigParser()
-  config.readfp(StringIO.StringIO(ini_str))
-  try:
-    return config.get('properties', property_key)
-  except ConfigParser.NoOptionError:
-    return None
-
-
-def _GetInstalledNdkRevision():
-  """Returns the installed NDK's revision."""
-  path = GetNdkPath()
-  properties_path = os.path.join(path, 'source.properties')
-  try:
-    return _ReadNdkRevision(properties_path)
-  except IOError:
-    logging.error("Error: Can't read NDK properties in %s", properties_path)
-    sys.exit(1)
-
-
 def _DownloadAndUnzipFile(url, destination_path):
-  dl_file, dummy_headers = urllib.urlretrieve(url)
+  shutil.rmtree(destination_path, ignore_errors=True)
+  _MakeDirs(destination_path)
+  dl_file = os.path.join(destination_path, 'tmp.zip')
+  request = requests.get(url, allow_redirects=True)
+  open(dl_file, 'wb').write(request.content)
   _UnzipFile(dl_file, destination_path)
 
 
@@ -165,49 +117,34 @@
     toolchains_dir_fd = os.open(_STARBOARD_TOOLCHAINS_DIR, os.O_RDONLY)
     fcntl.flock(toolchains_dir_fd, fcntl.LOCK_EX)
 
+    if os.environ.get('ANDROID_NDK_HOME'):
+      logging.warning('Warning: ANDROID_NDK_HOME is deprecated and ignored.')
+
     if _ANDROID_HOME:
       if not os.access(_SDKMANAGER_TOOL, os.X_OK):
-        logging.error('Error: ANDROID_HOME is set but SDK is not present!')
+        logging.error('Error: ANDROID_HOME is set but SDK is not present.')
         sys.exit(1)
       logging.warning('Warning: Using Android SDK in ANDROID_HOME,'
                       ' which is not automatically updated.\n'
                       '         The following package versions are installed:')
       installed_packages = _GetInstalledSdkPackages()
       for package in _ANDROID_SDK_PACKAGES:
-        version = installed_packages.get(package, '< MISSING! >')
+        version = installed_packages.get(package, '< NOT INSTALLED >')
         msg = '  {:30} : {}'.format(package, version)
         logging.warning(msg)
-    else:
-      logging.warning('Checking Android SDK.')
-      _DownloadInstallOrUpdateSdk()
 
-    ndk_path = GetNdkPath()
-    if _ANDROID_NDK_HOME:
-      logging.warning('Warning: ANDROID_NDK_HOME references NDK %s in %s,'
-                      ' which is not automatically updated.',
-                      _GetInstalledNdkRevision(), ndk_path)
+      if not os.path.exists(GetNdkPath()):
+        logging.error('Error: ANDROID_HOME is is missing NDK %s.',
+                      _NDK_VERSION)
+        sys.exit(1)
 
-    if _ANDROID_HOME or _ANDROID_NDK_HOME:
       reply = raw_input(
           'Do you want to continue using your custom Android tools? [y/N]')
       if reply.upper() != 'Y':
         sys.exit(1)
-    elif not _CheckStamp(ndk_path):
-      logging.warning('Downloading NDK from %s to %s', _NDK_URL, ndk_path)
-      if os.path.exists(ndk_path):
-        shutil.rmtree(ndk_path)
-      # Download the NDK into _STARBOARD_TOOLCHAINS_DIR and move the top
-      # _NDK_ZIP_REVISION directory that is in the zip to 'ndk-bundle'.
-      ndk_unzip_path = os.path.join(_STARBOARD_TOOLCHAINS_DIR,
-                                    _NDK_ZIP_REVISION)
-      if os.path.exists(ndk_unzip_path):
-        shutil.rmtree(ndk_unzip_path)
-      _DownloadAndUnzipFile(_NDK_URL, _STARBOARD_TOOLCHAINS_DIR)
-      # Move NDK into its proper final place.
-      os.rename(ndk_unzip_path, ndk_path)
-      _UpdateStamp(ndk_path)
-
-    logging.warning('Using Android NDK version %s', _GetInstalledNdkRevision())
+    else:
+      logging.warning('Checking Android SDK.')
+      _DownloadInstallOrUpdateSdk()
   finally:
     fcntl.flock(toolchains_dir_fd, fcntl.LOCK_UN)
     os.close(toolchains_dir_fd)
@@ -235,14 +172,15 @@
   section_re = re.compile(r'^[A-Z][^:]*:$')
   version_re = re.compile(r'^\s+Version:\s+(\S+)')
 
-  p = subprocess.Popen(
-      [_SDKMANAGER_TOOL, '--list', '--verbose'], stdout=subprocess.PIPE)
+  p = subprocess.Popen([_SDKMANAGER_TOOL, '--list', '--verbose'],
+                       stdout=subprocess.PIPE)
 
   installed_package_versions = {}
   new_style = False
   old_style = False
   for line in iter(p.stdout.readline, ''):
-
+    # Throw away loading progress indicators up to the last CR.
+    line = line.split('\r')[-1]
     if section_re.match(line):
       if new_style or old_style:
         # We left the new/old style installed packages section
@@ -292,6 +230,14 @@
     if os.path.exists(_STARBOARD_TOOLCHAINS_SDK_DIR):
       shutil.rmtree(_STARBOARD_TOOLCHAINS_SDK_DIR)
     _DownloadAndUnzipFile(_SDK_URL, _STARBOARD_TOOLCHAINS_SDK_DIR)
+    # TODO: Remove this workaround for sdkmanager incorrectly picking up the
+    # "tools" directory from the ZIP as the name of its component.
+    if not os.access(_SDKMANAGER_TOOL, os.X_OK):
+      old_tools_dir = os.path.join(_STARBOARD_TOOLCHAINS_SDK_DIR, 'tools')
+      new_tools_dir = os.path.join(_STARBOARD_TOOLCHAINS_SDK_DIR,
+                                   'cmdline-tools', '1.0')
+      os.mkdir(os.path.dirname(new_tools_dir))
+      os.rename(old_tools_dir, new_tools_dir)
     if not os.access(_SDKMANAGER_TOOL, os.X_OK):
       logging.error('SDK download failed.')
       sys.exit(1)
diff --git a/src/starboard/android/shared/starboard_platform.gypi b/src/starboard/android/shared/starboard_platform.gypi
index 2401618..365ced9 100644
--- a/src/starboard/android/shared/starboard_platform.gypi
+++ b/src/starboard/android/shared/starboard_platform.gypi
@@ -404,9 +404,9 @@
         '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_get_info2.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_get_maximum_number_of_samples_per_write.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_get_preferred_output_mode_prefer_punchout.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_internal.h',
-        '<(DEPTH)/starboard/shared/starboard/player/player_output_mode_supported.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_seek.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_seek2.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc',
diff --git a/src/starboard/android/shared/window_on_screen_keyboard_suggestions_supported.cc b/src/starboard/android/shared/window_on_screen_keyboard_suggestions_supported.cc
index 992e15e..ffd2d59 100644
--- a/src/starboard/android/shared/window_on_screen_keyboard_suggestions_supported.cc
+++ b/src/starboard/android/shared/window_on_screen_keyboard_suggestions_supported.cc
@@ -15,9 +15,7 @@
 #include "starboard/window.h"
 
 #if SB_HAS(ON_SCREEN_KEYBOARD)
-#if SB_API_VERSION >= 11
 bool SbWindowOnScreenKeyboardSuggestionsSupported(SbWindow window) {
   return true;
 }
-#endif  // SB_API_VERSION >= 11
 #endif  // SB_HAS(ON_SCREEN_KEYBOARD)
diff --git a/src/starboard/android/shared/window_update_on_screen_keyboard_suggestions.cc b/src/starboard/android/shared/window_update_on_screen_keyboard_suggestions.cc
index 24f0925..f3e75c2 100644
--- a/src/starboard/android/shared/window_update_on_screen_keyboard_suggestions.cc
+++ b/src/starboard/android/shared/window_update_on_screen_keyboard_suggestions.cc
@@ -17,7 +17,6 @@
 #include "starboard/android/shared/application_android.h"
 
 #if SB_HAS(ON_SCREEN_KEYBOARD)
-#if SB_API_VERSION >= 11
 void SbWindowUpdateOnScreenKeyboardSuggestions(SbWindow window,
                                                const char* suggestions[],
                                                int num_suggestions,
@@ -31,5 +30,4 @@
                                                   ticket);
   return;
 }
-#endif  // SB_API_VERSION >= 11
 #endif  // SB_HAS(ON_SCREEN_KEYBOARD)
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h
index e06b52e..a49c22b 100644
--- a/src/starboard/configuration.h
+++ b/src/starboard/configuration.h
@@ -644,6 +644,15 @@
 #define SB_HAS_AC3_AUDIO 1
 #endif  // defined(SB_HAS_AC3_AUDIO)
 #endif  // SB_API_VERSION >= 11
+
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+#if SB_API_VERSION < 11
+#error \
+    "SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT) requires " \
+    "SB_API_VERSION 11 or later."
+#endif  // SB_API_VERSION < 11
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
 // --- Derived Configuration -------------------------------------------------
 
 // Whether the current platform is little endian.
diff --git a/src/starboard/linux/shared/BUILD.gn b/src/starboard/linux/shared/BUILD.gn
index 8fa8add..bd55f1e 100644
--- a/src/starboard/linux/shared/BUILD.gn
+++ b/src/starboard/linux/shared/BUILD.gn
@@ -533,9 +533,9 @@
     "//starboard/shared/starboard/player/player_destroy.cc",
     "//starboard/shared/starboard/player/player_get_current_frame.cc",
     "//starboard/shared/starboard/player/player_get_info.cc",
+    "//starboard/shared/starboard/player/player_get_preferred_output_mode_prefer_punchout.cc",
     "//starboard/shared/starboard/player/player_internal.cc",
     "//starboard/shared/starboard/player/player_internal.h",
-    "//starboard/shared/starboard/player/player_output_mode_supported.cc",
     "//starboard/shared/starboard/player/player_seek.cc",
     "//starboard/shared/starboard/player/player_set_bounds.cc",
     "//starboard/shared/starboard/player/player_set_playback_rate.cc",
diff --git a/src/starboard/linux/shared/starboard_platform.gypi b/src/starboard/linux/shared/starboard_platform.gypi
index 1297986..ea9844d 100644
--- a/src/starboard/linux/shared/starboard_platform.gypi
+++ b/src/starboard/linux/shared/starboard_platform.gypi
@@ -196,6 +196,7 @@
       '<(DEPTH)/starboard/shared/pthread/mutex_release.cc',
       '<(DEPTH)/starboard/shared/pthread/once.cc',
       '<(DEPTH)/starboard/shared/pthread/thread_context_get_pointer.cc',
+      '<(DEPTH)/starboard/shared/pthread/thread_context_internal.cc',
       '<(DEPTH)/starboard/shared/pthread/thread_context_internal.h',
       '<(DEPTH)/starboard/shared/pthread/thread_create.cc',
       '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc',
@@ -290,6 +291,7 @@
       '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc',
       '<(DEPTH)/starboard/shared/starboard/player/player_get_info2.cc',
       '<(DEPTH)/starboard/shared/starboard/player/player_get_maximum_number_of_samples_per_write.cc',
+      '<(DEPTH)/starboard/shared/starboard/player/player_get_preferred_output_mode_prefer_punchout.cc',
       '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc',
       '<(DEPTH)/starboard/shared/starboard/player/player_internal.h',
       '<(DEPTH)/starboard/shared/starboard/player/player_output_mode_supported.cc',
diff --git a/src/starboard/nplb/drm_create_system_test.cc b/src/starboard/nplb/drm_create_system_test.cc
index 0b68275..9cd4b7a 100644
--- a/src/starboard/nplb/drm_create_system_test.cc
+++ b/src/starboard/nplb/drm_create_system_test.cc
@@ -44,7 +44,6 @@
 TEST(SbDrmTest, NullCallbacks) {
   for (int i = 0; i < SB_ARRAY_SIZE_INT(kKeySystems); ++i) {
     const char* key_system = kKeySystems[i];
-#if SB_API_VERSION >= 10
     {
       SbDrmSystem drm_system = SbDrmCreateSystem(
           key_system, NULL /* context */,
@@ -86,69 +85,9 @@
       EXPECT_FALSE(SbDrmSystemIsValid(drm_system));
       SbDrmDestroySystem(drm_system);
     }
-#elif SB_HAS(DRM_SESSION_CLOSED)
-    {
-      SbDrmSystem drm_system = SbDrmCreateSystem(
-          key_system, NULL /* context */,
-          NULL /* session_update_request_func */, DummySessionUpdatedFunc,
-          DummySessionKeyStatusesChangedFunc, DummySessionClosedFunc);
-      EXPECT_FALSE(SbDrmSystemIsValid(drm_system));
-      SbDrmDestroySystem(drm_system);
-    }
-    {
-      SbDrmSystem drm_system = SbDrmCreateSystem(
-          key_system, NULL /* context */, DummySessionUpdateRequestFunc,
-          NULL /*session_updated_func */, DummySessionKeyStatusesChangedFunc,
-          DummySessionClosedFunc);
-      EXPECT_FALSE(SbDrmSystemIsValid(drm_system));
-      SbDrmDestroySystem(drm_system);
-    }
-    {
-      SbDrmSystem drm_system = SbDrmCreateSystem(
-          key_system, NULL /* context */, DummySessionUpdateRequestFunc,
-          DummySessionUpdatedFunc, NULL /* session_key_statuses_changed_func */,
-          DummySessionClosedFunc);
-      EXPECT_FALSE(SbDrmSystemIsValid(drm_system));
-      SbDrmDestroySystem(drm_system);
-    }
-    {
-      SbDrmSystem drm_system = SbDrmCreateSystem(
-          key_system, NULL /* context */, DummySessionUpdateRequestFunc,
-          DummySessionUpdatedFunc, DummySessionKeyStatusesChangedFunc,
-          NULL /* session_closed_func */);
-      EXPECT_FALSE(SbDrmSystemIsValid(drm_system));
-      SbDrmDestroySystem(drm_system);
-    }
-#else   // SB_HAS(DRM_SESSION_CLOSED)
-    {
-      SbDrmSystem drm_system = SbDrmCreateSystem(
-          key_system, NULL /* context */,
-          NULL /* session_update_request_func */, DummySessionUpdatedFunc,
-          DummySessionKeyStatusesChangedFunc);
-      EXPECT_FALSE(SbDrmSystemIsValid(drm_system));
-      SbDrmDestroySystem(drm_system);
-    }
-    {
-      SbDrmSystem drm_system = SbDrmCreateSystem(
-          key_system, NULL /* context */, DummySessionUpdateRequestFunc,
-          NULL /* session_updated_func */, DummySessionKeyStatusesChangedFunc);
-      EXPECT_FALSE(SbDrmSystemIsValid(drm_system));
-      SbDrmDestroySystem(drm_system);
-    }
-    {
-      SbDrmSystem drm_system = SbDrmCreateSystem(
-          key_system, NULL /* context */, DummySessionUpdateRequestFunc,
-          DummySessionUpdatedFunc,
-          NULL /* session_key_statuses_changed_func */);
-      EXPECT_FALSE(SbDrmSystemIsValid(drm_system));
-      SbDrmDestroySystem(drm_system);
-    }
-#endif  // SB_HAS(DRM_SESSION_CLOSED)
   }
 }
-#endif  // SB_API_VERSION >= 10
 
-#if SB_API_VERSION >= 10
 TEST(SbDrmTest, MultiDrm) {
   const int kMaxPlayersPerKeySystem = 16;
   std::vector<SbDrmSystem> created_drm_systems;
diff --git a/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc b/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc
new file mode 100644
index 0000000..2e38c86
--- /dev/null
+++ b/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc
@@ -0,0 +1,102 @@
+// Copyright 2020 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/media.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace media {
+namespace {
+
+TEST(SbMediaCanPlayMimeAndKeySystem, SunnyDay) {
+  // Vp9
+  SbMediaCanPlayMimeAndKeySystem(
+      "video/webm; codecs=\"vp9\"; width=3840; height=2160; framerate=30; "
+      "bitrate=21823784; eotf=bt709",
+      "");
+  // Avc
+  SbMediaSupportType result = SbMediaCanPlayMimeAndKeySystem(
+      "video/mp4; codecs=\"avc1.4d4015\"; width=640; "
+      "height=360; framerate=30;",
+      "");
+  ASSERT_EQ(result, kSbMediaSupportTypeProbably);
+  // Hdr bt709
+  SbMediaCanPlayMimeAndKeySystem(
+      "video/webm; codecs=\"vp09.02.10.10\";eotf=bt709;width=640;height=360",
+      "");
+  // Aac
+  result = SbMediaCanPlayMimeAndKeySystem(
+      "audio/mp4; codecs=\"mp4a.40.2\"; channels=2", "");
+  ASSERT_EQ(result, kSbMediaSupportTypeProbably);
+  // Opus
+  SbMediaCanPlayMimeAndKeySystem("audio/webm; codecs=\"opus\"; channels=2", "");
+}
+
+TEST(SbMediaCanPlayMimeAndKeySystem, Invalid) {
+  // Invalid codec
+  SbMediaSupportType result =
+      SbMediaCanPlayMimeAndKeySystem("video/webm; codecs=\"abc\";", "");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+
+  result = SbMediaCanPlayMimeAndKeySystem(
+      "video/webm; codecs=\"vp09.00.01.00.22\";", "");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+
+  // Invalid container
+  result =
+      SbMediaCanPlayMimeAndKeySystem("video/abc; codecs=\"avc1.4d4015\";", "");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+
+  // Invalid size
+  result = SbMediaCanPlayMimeAndKeySystem(
+      "video/mp4; codecs=\"avc1.4d4015\"; width=99999; height=1080;", "");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+
+  result = SbMediaCanPlayMimeAndKeySystem(
+      "video/mp4; codecs=\"avc1.4d4015\"; width=1920; height=99999;", "");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+
+  // Invalid bitrate
+  result = SbMediaCanPlayMimeAndKeySystem(
+      "video/mp4; codecs=\"avc1.4d4015\"; width=1920; height=1080; "
+      "bitrate=999999999;",
+      "");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+
+  // Invalid eotf
+  result = SbMediaCanPlayMimeAndKeySystem(
+      "video/webm; codecs=\"vp09.02.10.10\"; width=1920; height=1080; "
+      "eotf=abc",
+      "");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+
+  // Invalid channels
+  result = SbMediaCanPlayMimeAndKeySystem(
+      "audio/mp4; codecs=\"mp4a.40.2\"; channels=99", "");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+
+  // Invalid keysystem
+  result = SbMediaCanPlayMimeAndKeySystem(
+      "video/mp4; codecs=\"avc1.4d4015\"; width=1920; height=1080;", "abc");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+}
+
+}  // namespace
+}  // namespace media
+}  // namespace starboard
+}  // namespace shared
+}  // namespace starboard
diff --git a/src/starboard/nplb/media_set_audio_write_duration_test.cc b/src/starboard/nplb/media_set_audio_write_duration_test.cc
index 0ca9a37..39fecb0 100644
--- a/src/starboard/nplb/media_set_audio_write_duration_test.cc
+++ b/src/starboard/nplb/media_set_audio_write_duration_test.cc
@@ -16,6 +16,7 @@
 
 #include "starboard/common/optional.h"
 #include "starboard/common/spin_lock.h"
+#include "starboard/nplb/player_creation_param_helpers.h"
 #include "starboard/player.h"
 #include "starboard/shared/starboard/media/media_support_internal.h"
 #include "starboard/shared/starboard/player/video_dmp_reader.h"
@@ -98,10 +99,8 @@
     sample_info.buffer_size = player_sample_info.buffer_size;
     sample_info.timestamp = player_sample_info.timestamp;
     sample_info.drm_info = NULL;
-#if SB_API_VERSION >= 11
     sample_info.type = kSbMediaTypeAudio;
     sample_info.audio_sample_info = dmp_reader_.audio_sample_info();
-#endif  // SB_API_VERSION >= 11
 
     SbPlayer player = pending_decoder_status_->player;
     SbMediaType type = pending_decoder_status_->type;
@@ -136,25 +135,40 @@
     SbMediaAudioCodec kAudioCodec = dmp_reader_.audio_codec();
     SbDrmSystem kDrmSystem = kSbDrmSystemInvalid;
 
+    last_input_timestamp_ =
+        dmp_reader_.GetPlayerSampleInfo(kSbMediaTypeAudio, 0).timestamp;
+    first_input_timestamp_ = last_input_timestamp_;
+
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+    SbPlayerCreationParam creation_param = CreatePlayerCreationParam(
+        audio_sample_info.codec, kSbMediaVideoCodecNone);
+    creation_param.audio_sample_info = audio_sample_info;
+    creation_param.output_mode =
+        SbPlayerGetPreferredOutputMode(&creation_param);
+    EXPECT_NE(creation_param.output_mode, kSbPlayerOutputModeInvalid);
+
+    SbPlayer player = SbPlayerCreate(
+        fake_graphics_context_provider_.window(), &creation_param,
+        DummyDeallocateSampleFunc, DecoderStatusFunc, PlayerStatusFunc,
+        DummyErrorFunc, this /* context */,
+        fake_graphics_context_provider_.decoder_target_provider());
+#else   // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
     SbPlayerOutputMode output_mode = kSbPlayerOutputModeDecodeToTexture;
+
     if (!SbPlayerOutputModeSupported(output_mode, kSbMediaVideoCodecNone,
                                      kSbDrmSystemInvalid)) {
       output_mode = kSbPlayerOutputModePunchOut;
     }
 
-    last_input_timestamp_ =
-        dmp_reader_.GetPlayerSampleInfo(kSbMediaTypeAudio, 0).timestamp;
-    first_input_timestamp_ = last_input_timestamp_;
-
     SbPlayer player = SbPlayerCreate(
         fake_graphics_context_provider_.window(), kSbMediaVideoCodecNone,
         kAudioCodec, kSbDrmSystemInvalid, &audio_sample_info,
-#if SB_API_VERSION >= 11
         NULL /* max_video_capabilities */,
-#endif  // SB_API_VERSION >= 11
         DummyDeallocateSampleFunc, DecoderStatusFunc, PlayerStatusFunc,
         DummyErrorFunc, this /* context */, output_mode,
         fake_graphics_context_provider_.decoder_target_provider());
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
     EXPECT_TRUE(SbPlayerIsValid(player));
     return player;
   }
diff --git a/src/starboard/nplb/nplb.gyp b/src/starboard/nplb/nplb.gyp
index 9043651..7dae228 100644
--- a/src/starboard/nplb/nplb.gyp
+++ b/src/starboard/nplb/nplb.gyp
@@ -151,7 +151,7 @@
         # TODO: Separate functions tested by media buffer test into multiple
         # files.
         'media_buffer_test.cc',
-        'media_set_audio_write_duration_test.cc',
+        'media_can_play_mime_and_key_system_test.cc',
         'memory_align_to_page_size_test.cc',
         'memory_allocate_aligned_test.cc',
         'memory_allocate_test.cc',
@@ -181,6 +181,9 @@
         'once_test.cc',
         'optional_test.cc',
         'player_create_test.cc',
+        'player_creation_param_helpers.cc',
+        'player_creation_param_helpers.h',
+        'player_get_preferred_output_mode_test.cc',
         'player_output_mode_supported_test.cc',
         'random_helpers.cc',
         'recursive_mutex_test.cc',
diff --git a/src/starboard/nplb/player_create_test.cc b/src/starboard/nplb/player_create_test.cc
index 0aa6b0e..a3f4efc 100644
--- a/src/starboard/nplb/player_create_test.cc
+++ b/src/starboard/nplb/player_create_test.cc
@@ -16,6 +16,7 @@
 
 #include "starboard/blitter.h"
 #include "starboard/decode_target.h"
+#include "starboard/nplb/player_creation_param_helpers.h"
 #include "starboard/player.h"
 #include "starboard/testing/fake_graphics_context_provider.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -53,56 +54,92 @@
                     const char* message) {}
 #endif  // SB_HAS(PLAYER_ERROR_MESSAGE)
 
-SbMediaAudioSampleInfo GetDefaultAudioSampleInfo() {
-  SbMediaAudioSampleInfo audio_sample_info;
+SbPlayer CallSbPlayerCreate(
+    SbWindow window,
+    SbMediaVideoCodec video_codec,
+    SbMediaAudioCodec audio_codec,
+    SbDrmSystem drm_system,
+    const SbMediaAudioSampleInfo* audio_sample_info,
+    const char* max_video_capabilities,
+    SbPlayerDeallocateSampleFunc sample_deallocate_func,
+    SbPlayerDecoderStatusFunc decoder_status_func,
+    SbPlayerStatusFunc player_status_func,
+    void* context,
+    SbPlayerOutputMode output_mode,
+    SbDecodeTargetGraphicsContextProvider* context_provider) {
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
 
+  if (audio_sample_info) {
+    SB_CHECK(audio_sample_info->codec == audio_codec);
+  } else {
+    SB_CHECK(audio_codec == kSbMediaAudioCodecNone);
+  }
+
+  SbPlayerCreationParam creation_param =
+      CreatePlayerCreationParam(audio_codec, video_codec);
+  if (audio_sample_info) {
+    creation_param.audio_sample_info = *audio_sample_info;
+  }
+  creation_param.drm_system = drm_system;
+  creation_param.output_mode = output_mode;
+  creation_param.max_video_capabilities = max_video_capabilities;
+
+  return SbPlayerCreate(window, &creation_param, sample_deallocate_func,
+                        decoder_status_func, player_status_func, DummyErrorFunc,
+                        context, context_provider);
+
+#else  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+  return SbPlayerCreate(window, video_codec, audio_codec,
+#if SB_API_VERSION < 10
+                        SB_PLAYER_NO_DURATION,
+#endif  // SB_API_VERSION < 10
+                        kSbDrmSystemInvalid, audio_sample_info,
 #if SB_API_VERSION >= 11
-  audio_sample_info.codec = kSbMediaAudioCodecAac;
+                        max_video_capabilities,
 #endif  // SB_API_VERSION >= 11
-  audio_sample_info.format_tag = 0xff;
-  audio_sample_info.number_of_channels = 2;
-  audio_sample_info.samples_per_second = 22050;
-  audio_sample_info.block_alignment = 4;
-  audio_sample_info.bits_per_sample = 32;
-  audio_sample_info.audio_specific_config_size = 0;
-  audio_sample_info.average_bytes_per_second =
-      audio_sample_info.samples_per_second *
-      audio_sample_info.number_of_channels * audio_sample_info.bits_per_sample /
-      8;
+                        sample_deallocate_func, decoder_status_func,
+                        player_status_func,
+#if SB_HAS(PLAYER_ERROR_MESSAGE)
+                        DummyErrorFunc,
+#endif  // SB_HAS(PLAYER_ERROR_MESSAGE)
+                        context, output_mode, context_provider);
 
-  return audio_sample_info;
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+}
+
+bool IsOutputModeSupported(SbPlayerOutputMode output_mode,
+                           SbMediaVideoCodec codec) {
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+  SbPlayerCreationParam creation_param =
+      CreatePlayerCreationParam(kSbMediaAudioCodecNone, codec);
+  creation_param.output_mode = output_mode;
+  return SbPlayerGetPreferredOutputMode(&creation_param) == output_mode;
+#else   // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+  return SbPlayerOutputModeSupported(output_mode, codec, kSbDrmSystemInvalid);
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
 }
 
 TEST_F(SbPlayerTest, SunnyDay) {
-  SbMediaAudioSampleInfo audio_sample_info = GetDefaultAudioSampleInfo();
+  SbMediaAudioSampleInfo audio_sample_info =
+      CreateAudioSampleInfo(kSbMediaAudioCodecAac);
   SbMediaVideoCodec kVideoCodec = kSbMediaVideoCodecH264;
-  SbDrmSystem kDrmSystem = kSbDrmSystemInvalid;
 
   SbPlayerOutputMode output_modes[] = {kSbPlayerOutputModeDecodeToTexture,
                                        kSbPlayerOutputModePunchOut};
 
   for (int i = 0; i < SB_ARRAY_SIZE_INT(output_modes); ++i) {
     SbPlayerOutputMode output_mode = output_modes[i];
-    if (!SbPlayerOutputModeSupported(output_mode, kVideoCodec, kDrmSystem)) {
+
+    if (!IsOutputModeSupported(output_mode, kVideoCodec)) {
       continue;
     }
-
-    SbPlayer player = SbPlayerCreate(
+    SbPlayer player = CallSbPlayerCreate(
         fake_graphics_context_provider_.window(), kSbMediaVideoCodecH264,
-        kSbMediaAudioCodecAac,
-#if SB_API_VERSION < 10
-        SB_PLAYER_NO_DURATION,
-#endif  // SB_API_VERSION < 10
-        kSbDrmSystemInvalid, &audio_sample_info,
-#if SB_API_VERSION >= 11
-        NULL /* max_video_capabilities */,
-#endif  // SB_API_VERSION >= 11
-        DummyDeallocateSampleFunc, DummyDecoderStatusFunc, DummyStatusFunc,
-#if SB_HAS(PLAYER_ERROR_MESSAGE)
-        DummyErrorFunc,
-#endif  // SB_HAS(PLAYER_ERROR_MESSAGE)
-        NULL /* context */, output_mode,
-        fake_graphics_context_provider_.decoder_target_provider());
+        kSbMediaAudioCodecAac, kSbDrmSystemInvalid, &audio_sample_info,
+        "" /* max_video_capabilities */, DummyDeallocateSampleFunc,
+        DummyDecoderStatusFunc, DummyStatusFunc, NULL /* context */,
+        output_mode, fake_graphics_context_provider_.decoder_target_provider());
     EXPECT_TRUE(SbPlayerIsValid(player));
 
     if (output_mode == kSbPlayerOutputModeDecodeToTexture) {
@@ -115,36 +152,26 @@
 
 #if SB_API_VERSION >= 10
 TEST_F(SbPlayerTest, NullCallbacks) {
-  SbMediaAudioSampleInfo audio_sample_info = GetDefaultAudioSampleInfo();
+  SbMediaAudioSampleInfo audio_sample_info =
+      CreateAudioSampleInfo(kSbMediaAudioCodecAac);
   SbMediaVideoCodec kVideoCodec = kSbMediaVideoCodecH264;
-  SbDrmSystem kDrmSystem = kSbDrmSystemInvalid;
 
   SbPlayerOutputMode output_modes[] = {kSbPlayerOutputModeDecodeToTexture,
                                        kSbPlayerOutputModePunchOut};
 
   for (int i = 0; i < SB_ARRAY_SIZE_INT(output_modes); ++i) {
     SbPlayerOutputMode output_mode = output_modes[i];
-    if (!SbPlayerOutputModeSupported(output_mode, kVideoCodec, kDrmSystem)) {
+    if (!IsOutputModeSupported(output_mode, kVideoCodec)) {
       continue;
     }
 
     {
-      SbPlayer player = SbPlayerCreate(
+      SbPlayer player = CallSbPlayerCreate(
           fake_graphics_context_provider_.window(), kSbMediaVideoCodecH264,
-          kSbMediaAudioCodecAac,
-#if SB_API_VERSION < 10
-          SB_PLAYER_NO_DURATION,
-#endif  // SB_API_VERSION < 10
-          kSbDrmSystemInvalid, &audio_sample_info,
-#if SB_API_VERSION >= 11
-          NULL /* max_video_capabilities */,
-#endif  // SB_API_VERSION >= 11
-          NULL /* deallocate_sample_func */, DummyDecoderStatusFunc,
-          DummyStatusFunc,
-#if SB_HAS(PLAYER_ERROR_MESSAGE)
-          DummyErrorFunc,
-#endif  // SB_HAS(PLAYER_ERROR_MESSAGE)
-          NULL /* context */, output_mode,
+          kSbMediaAudioCodecAac, kSbDrmSystemInvalid, &audio_sample_info,
+          "" /* max_video_capabilities */, NULL /* deallocate_sample_func */,
+          DummyDecoderStatusFunc, DummyStatusFunc, NULL /* context */,
+          output_mode,
           fake_graphics_context_provider_.decoder_target_provider());
       EXPECT_FALSE(SbPlayerIsValid(player));
 
@@ -152,22 +179,12 @@
     }
 
     {
-      SbPlayer player = SbPlayerCreate(
+      SbPlayer player = CallSbPlayerCreate(
           fake_graphics_context_provider_.window(), kSbMediaVideoCodecH264,
-          kSbMediaAudioCodecAac,
-#if SB_API_VERSION < 10
-          SB_PLAYER_NO_DURATION,
-#endif  // SB_API_VERSION < 10
-          kSbDrmSystemInvalid, &audio_sample_info,
-#if SB_API_VERSION >= 11
-          NULL /* max_video_capabilities */,
-#endif  // SB_API_VERSION >= 11
-          DummyDeallocateSampleFunc, NULL /* decoder_status_func */,
-          DummyStatusFunc,
-#if SB_HAS(PLAYER_ERROR_MESSAGE)
-          DummyErrorFunc,
-#endif  // SB_HAS(PLAYER_ERROR_MESSAGE)
-          NULL /* context */, output_mode,
+          kSbMediaAudioCodecAac, kSbDrmSystemInvalid, &audio_sample_info,
+          "" /* max_video_capabilities */, DummyDeallocateSampleFunc,
+          NULL /* decoder_status_func */, DummyStatusFunc, NULL /* context */,
+          output_mode,
           fake_graphics_context_provider_.decoder_target_provider());
       EXPECT_FALSE(SbPlayerIsValid(player));
 
@@ -175,22 +192,12 @@
     }
 
     {
-      SbPlayer player = SbPlayerCreate(
+      SbPlayer player = CallSbPlayerCreate(
           fake_graphics_context_provider_.window(), kSbMediaVideoCodecH264,
-          kSbMediaAudioCodecAac,
-#if SB_API_VERSION < 10
-          SB_PLAYER_NO_DURATION,
-#endif  // SB_API_VERSION < 10
-          kSbDrmSystemInvalid, &audio_sample_info,
-#if SB_API_VERSION >= 11
-          NULL /* max_video_capabilities */,
-#endif  // SB_API_VERSION >= 11
-          DummyDeallocateSampleFunc, DummyDecoderStatusFunc,
-          NULL /*status_func */,
-#if SB_HAS(PLAYER_ERROR_MESSAGE)
-          DummyErrorFunc,
-#endif  // SB_HAS(PLAYER_ERROR_MESSAGE)
-          NULL /* context */, output_mode,
+          kSbMediaAudioCodecAac, kSbDrmSystemInvalid, &audio_sample_info,
+          "" /* max_video_capabilities */, DummyDeallocateSampleFunc,
+          DummyDecoderStatusFunc, NULL /*status_func */, NULL /* context */,
+          output_mode,
           fake_graphics_context_provider_.decoder_target_provider());
       EXPECT_FALSE(SbPlayerIsValid(player));
 
@@ -198,13 +205,29 @@
     }
 
 #if SB_HAS(PLAYER_ERROR_MESSAGE)
+
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+    {
+      SbPlayerCreationParam creation_param = CreatePlayerCreationParam(
+          kSbMediaAudioCodecAac, kSbMediaVideoCodecH264);
+
+      SbPlayer player = SbPlayerCreate(
+          fake_graphics_context_provider_.window(), &creation_param,
+          DummyDeallocateSampleFunc, DummyDecoderStatusFunc, DummyStatusFunc,
+          NULL /* error_func */, NULL /* context */,
+          fake_graphics_context_provider_.decoder_target_provider());
+      EXPECT_FALSE(SbPlayerIsValid(player));
+
+      SbPlayerDestroy(player);
+    }
+
+#else  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
     {
       SbPlayer player = SbPlayerCreate(
           fake_graphics_context_provider_.window(), kSbMediaVideoCodecH264,
           kSbMediaAudioCodecAac,
-#if SB_API_VERSION < 10
-          SB_PLAYER_NO_DURATION,
-#endif  // SB_API_VERSION < 10
           kSbDrmSystemInvalid, &audio_sample_info,
 #if SB_API_VERSION >= 11
           NULL /* max_video_capabilities */,
@@ -216,6 +239,9 @@
 
       SbPlayerDestroy(player);
     }
+
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
 #endif  // SB_HAS(PLAYER_ERROR_MESSAGE)
   }
 }
@@ -224,31 +250,21 @@
 #if SB_HAS(AUDIOLESS_VIDEO)
 TEST_F(SbPlayerTest, Audioless) {
   SbMediaVideoCodec kVideoCodec = kSbMediaVideoCodecH264;
-  SbDrmSystem kDrmSystem = kSbDrmSystemInvalid;
 
   SbPlayerOutputMode output_modes[] = {kSbPlayerOutputModeDecodeToTexture,
                                        kSbPlayerOutputModePunchOut};
 
   for (int i = 0; i < SB_ARRAY_SIZE_INT(output_modes); ++i) {
     SbPlayerOutputMode output_mode = output_modes[i];
-    if (!SbPlayerOutputModeSupported(output_mode, kVideoCodec, kDrmSystem)) {
+    if (!IsOutputModeSupported(output_mode, kVideoCodec)) {
       continue;
     }
 
-    SbPlayer player = SbPlayerCreate(
+    SbPlayer player = CallSbPlayerCreate(
         fake_graphics_context_provider_.window(), kVideoCodec,
-        kSbMediaAudioCodecNone,
-#if SB_API_VERSION < 10
-        SB_PLAYER_NO_DURATION,
-#endif  // SB_API_VERSION < 10
-        kSbDrmSystemInvalid, NULL /* audio_sample_info */,
-#if SB_API_VERSION >= 11
-        NULL /* max_video_capabilities */,
-#endif  // SB_API_VERSION >= 11
+        kSbMediaAudioCodecNone, kSbDrmSystemInvalid,
+        NULL /* audio_sample_info */, "" /* max_video_capabilities */,
         DummyDeallocateSampleFunc, DummyDecoderStatusFunc, DummyStatusFunc,
-#if SB_HAS(PLAYER_ERROR_MESSAGE)
-        DummyErrorFunc,
-#endif  // SB_HAS(PLAYER_ERROR_MESSAGE)
         NULL /* context */, output_mode,
         fake_graphics_context_provider_.decoder_target_provider());
     EXPECT_TRUE(SbPlayerIsValid(player));
@@ -264,36 +280,26 @@
 
 #if SB_API_VERSION >= 10
 TEST_F(SbPlayerTest, AudioOnly) {
-  SbMediaAudioSampleInfo audio_sample_info = GetDefaultAudioSampleInfo();
+  SbMediaAudioSampleInfo audio_sample_info =
+      CreateAudioSampleInfo(kSbMediaAudioCodecAac);
   SbMediaAudioCodec kAudioCodec = kSbMediaAudioCodecAac;
   SbMediaVideoCodec kVideoCodec = kSbMediaVideoCodecH264;
-  SbDrmSystem kDrmSystem = kSbDrmSystemInvalid;
 
   SbPlayerOutputMode output_modes[] = {kSbPlayerOutputModeDecodeToTexture,
                                        kSbPlayerOutputModePunchOut};
 
   for (int i = 0; i < SB_ARRAY_SIZE_INT(output_modes); ++i) {
     SbPlayerOutputMode output_mode = output_modes[i];
-    if (!SbPlayerOutputModeSupported(output_mode, kVideoCodec, kDrmSystem)) {
+    if (!IsOutputModeSupported(output_mode, kVideoCodec)) {
       continue;
     }
 
-    SbPlayer player = SbPlayerCreate(
+    SbPlayer player = CallSbPlayerCreate(
         fake_graphics_context_provider_.window(), kSbMediaVideoCodecNone,
-        kAudioCodec,
-#if SB_API_VERSION < 10
-        SB_PLAYER_NO_DURATION,
-#endif  // SB_API_VERSION < 10
-        kSbDrmSystemInvalid, &audio_sample_info,
-#if SB_API_VERSION >= 11
-        NULL /* max_video_capabilities */,
-#endif  // SB_API_VERSION >= 11
-        DummyDeallocateSampleFunc, DummyDecoderStatusFunc, DummyStatusFunc,
-#if SB_HAS(PLAYER_ERROR_MESSAGE)
-        DummyErrorFunc,
-#endif  // SB_HAS(PLAYER_ERROR_MESSAGE)
-        NULL /* context */, output_mode,
-        fake_graphics_context_provider_.decoder_target_provider());
+        kAudioCodec, kSbDrmSystemInvalid, &audio_sample_info,
+        "" /* max_video_capabilities */, DummyDeallocateSampleFunc,
+        DummyDecoderStatusFunc, DummyStatusFunc, NULL /* context */,
+        output_mode, fake_graphics_context_provider_.decoder_target_provider());
     EXPECT_TRUE(SbPlayerIsValid(player));
 
     if (output_mode == kSbPlayerOutputModeDecodeToTexture) {
@@ -303,11 +309,10 @@
     SbPlayerDestroy(player);
   }
 }
-#endif  // SB_API_VERSION >= 10
 
-#if SB_API_VERSION >= 10
 TEST_F(SbPlayerTest, MultiPlayer) {
-  SbMediaAudioSampleInfo audio_sample_info = GetDefaultAudioSampleInfo();
+  SbMediaAudioSampleInfo audio_sample_info =
+      CreateAudioSampleInfo(kSbMediaAudioCodecAac);
   SbDrmSystem kDrmSystem = kSbDrmSystemInvalid;
 
   constexpr SbPlayerOutputMode kOutputModes[] = {
@@ -387,14 +392,11 @@
 #if SB_API_VERSION >= 11
           audio_sample_info.codec = kAudioCodecs[k];
 #endif  // SB_API_VERSION >= 11
-          created_players.push_back(SbPlayerCreate(
+          created_players.push_back(CallSbPlayerCreate(
               fake_graphics_context_provider_.window(), kVideoCodecs[l],
               kAudioCodecs[k], kSbDrmSystemInvalid, &audio_sample_info,
-#if SB_API_VERSION >= 11
-              NULL /* max_video_capabilities */,
-#endif  // SB_API_VERSION >= 11
-              DummyDeallocateSampleFunc, DummyDecoderStatusFunc,
-              DummyStatusFunc, DummyErrorFunc, NULL /* context */,
+              "" /* max_video_capabilities */, DummyDeallocateSampleFunc,
+              DummyDecoderStatusFunc, DummyStatusFunc, NULL /* context */,
               kOutputModes[j],
               fake_graphics_context_provider_.decoder_target_provider()));
           if (!SbPlayerIsValid(created_players.back())) {
diff --git a/src/starboard/nplb/player_creation_param_helpers.cc b/src/starboard/nplb/player_creation_param_helpers.cc
new file mode 100644
index 0000000..62009ff
--- /dev/null
+++ b/src/starboard/nplb/player_creation_param_helpers.cc
@@ -0,0 +1,149 @@
+// Copyright 2020 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/nplb/player_creation_param_helpers.h"
+
+#include "starboard/common/log.h"
+
+namespace starboard {
+namespace nplb {
+
+SbMediaAudioSampleInfo CreateAudioSampleInfo(SbMediaAudioCodec codec) {
+  SbMediaAudioSampleInfo audio_sample_info = {};
+
+#if SB_API_VERSION >= 11
+  audio_sample_info.codec = codec;
+#endif  // SB_API_VERSION >= 11
+
+  switch (codec) {
+    case kSbMediaAudioCodecNone:
+      break;
+    case kSbMediaAudioCodecAac: {
+      static const uint8_t kAacAudioSpecificConfig[16] = {18, 16};
+
+      audio_sample_info.format_tag = 0xff;
+      audio_sample_info.number_of_channels = 2;
+      audio_sample_info.samples_per_second = 44100;
+      audio_sample_info.block_alignment = 4;
+      audio_sample_info.bits_per_sample = 16;
+      audio_sample_info.audio_specific_config = kAacAudioSpecificConfig;
+      audio_sample_info.audio_specific_config_size =
+          sizeof(kAacAudioSpecificConfig);
+      audio_sample_info.average_bytes_per_second =
+          audio_sample_info.samples_per_second *
+          audio_sample_info.number_of_channels *
+          audio_sample_info.bits_per_sample / 8;
+      break;
+    }
+    case kSbMediaAudioCodecAc3:
+    case kSbMediaAudioCodecEac3: {
+      audio_sample_info.format_tag = 0xff;
+      audio_sample_info.number_of_channels = 6;
+      audio_sample_info.samples_per_second = 48000;
+      audio_sample_info.block_alignment = 4;
+      audio_sample_info.bits_per_sample = 16;
+      audio_sample_info.audio_specific_config = nullptr;
+      audio_sample_info.audio_specific_config_size = 0;
+      audio_sample_info.average_bytes_per_second =
+          audio_sample_info.samples_per_second *
+          audio_sample_info.number_of_channels *
+          audio_sample_info.bits_per_sample / 8;
+      break;
+    }
+    case kSbMediaAudioCodecOpus: {
+      static const uint8_t kOpusAudioSpecificConfig[19] = {
+          79, 112, 117, 115, 72, 101, 97, 100, 1, 2, 56, 1, 128, 187};
+
+      audio_sample_info.format_tag = 0xff;
+      audio_sample_info.number_of_channels = 2;
+      audio_sample_info.samples_per_second = 48000;
+      audio_sample_info.block_alignment = 4;
+      audio_sample_info.bits_per_sample = 32;
+      audio_sample_info.audio_specific_config = kOpusAudioSpecificConfig;
+      audio_sample_info.audio_specific_config_size =
+          sizeof(kOpusAudioSpecificConfig);
+      audio_sample_info.average_bytes_per_second =
+          audio_sample_info.samples_per_second *
+          audio_sample_info.number_of_channels *
+          audio_sample_info.bits_per_sample / 8;
+      break;
+    }
+    case kSbMediaAudioCodecVorbis: {
+      // Note that unlike the configuration of the other formats, the following
+      // configuration is made up, instead of taking from a real input.
+      audio_sample_info.format_tag = 0xff;
+      audio_sample_info.number_of_channels = 2;
+      audio_sample_info.samples_per_second = 48000;
+      audio_sample_info.block_alignment = 4;
+      audio_sample_info.bits_per_sample = 16;
+      audio_sample_info.audio_specific_config = nullptr;
+      audio_sample_info.audio_specific_config_size = 0;
+      audio_sample_info.average_bytes_per_second =
+          audio_sample_info.samples_per_second *
+          audio_sample_info.number_of_channels *
+          audio_sample_info.bits_per_sample / 8;
+      break;
+    }
+  }
+  return audio_sample_info;
+}
+
+SbMediaVideoSampleInfo CreateVideoSampleInfo(SbMediaVideoCodec codec) {
+  SbMediaVideoSampleInfo video_sample_info = {};
+
+#if SB_API_VERSION >= 11
+  video_sample_info.codec = codec;
+#endif  // SB_API_VERSION >= 11
+
+#if SB_API_VERSION >= 11
+  video_sample_info.color_metadata.primaries = kSbMediaPrimaryIdBt709;
+  video_sample_info.color_metadata.transfer = kSbMediaTransferIdBt709;
+  video_sample_info.color_metadata.matrix = kSbMediaMatrixIdBt709;
+  video_sample_info.color_metadata.range = kSbMediaRangeIdLimited;
+#else   // SB_API_VERSION >= 11
+  static SbMediaColorMetadata color_metadata;
+  color_metadata.primaries = kSbMediaPrimaryIdBt709;
+  color_metadata.transfer = kSbMediaTransferIdBt709;
+  color_metadata.matrix = kSbMediaMatrixIdBt709;
+  color_metadata.range = kSbMediaRangeIdLimited;
+  video_sample_info.color_metadata = &color_metadata;
+#endif  // SB_API_VERSION >= 11
+
+  video_sample_info.frame_width = 1920;
+  video_sample_info.frame_height = 1080;
+
+  return video_sample_info;
+}
+
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+SbPlayerCreationParam CreatePlayerCreationParam(SbMediaAudioCodec audio_codec,
+                                                SbMediaVideoCodec video_codec) {
+  SbPlayerCreationParam creation_param = {};
+
+  creation_param.audio_mime = "";
+  creation_param.video_mime = "";
+  creation_param.drm_system = kSbDrmSystemInvalid;
+  creation_param.audio_sample_info = CreateAudioSampleInfo(audio_codec);
+  creation_param.video_sample_info = CreateVideoSampleInfo(video_codec);
+  creation_param.output_mode = kSbPlayerOutputModeInvalid;
+  creation_param.max_video_capabilities = "";
+
+  return creation_param;
+}
+
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/player_creation_param_helpers.h b/src/starboard/nplb/player_creation_param_helpers.h
new file mode 100644
index 0000000..8cb327d
--- /dev/null
+++ b/src/starboard/nplb/player_creation_param_helpers.h
@@ -0,0 +1,37 @@
+// Copyright 2020 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_NPLB_PLAYER_CREATION_PARAM_HELPERS_H_
+#define STARBOARD_NPLB_PLAYER_CREATION_PARAM_HELPERS_H_
+
+#include "starboard/configuration.h"
+
+#include "starboard/media.h"
+#include "starboard/player.h"
+
+namespace starboard {
+namespace nplb {
+
+SbMediaAudioSampleInfo CreateAudioSampleInfo(SbMediaAudioCodec codec);
+SbMediaVideoSampleInfo CreateVideoSampleInfo(SbMediaVideoCodec codec);
+
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+SbPlayerCreationParam CreatePlayerCreationParam(SbMediaAudioCodec audio_codec,
+                                                SbMediaVideoCodec video_codec);
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+}  // namespace nplb
+}  // namespace starboard
+
+#endif  // STARBOARD_NPLB_PLAYER_CREATION_PARAM_HELPERS_H_
diff --git a/src/starboard/nplb/player_get_preferred_output_mode_test.cc b/src/starboard/nplb/player_get_preferred_output_mode_test.cc
new file mode 100644
index 0000000..4167dd9
--- /dev/null
+++ b/src/starboard/nplb/player_get_preferred_output_mode_test.cc
@@ -0,0 +1,52 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/player.h"
+
+#include "starboard/configuration.h"
+#include "starboard/nplb/player_creation_param_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+TEST(SbPlayerGetPreferredOutputModeTest, SunnyDay) {
+  auto creation_param =
+      CreatePlayerCreationParam(kSbMediaAudioCodecAac, kSbMediaVideoCodecH264);
+
+  creation_param.output_mode = kSbPlayerOutputModeInvalid;
+  auto output_mode = SbPlayerGetPreferredOutputMode(&creation_param);
+  ASSERT_NE(output_mode, kSbPlayerOutputModeInvalid);
+
+  creation_param.output_mode = output_mode;
+  output_mode = SbPlayerGetPreferredOutputMode(&creation_param);
+  ASSERT_EQ(output_mode, creation_param.output_mode);
+
+  creation_param.output_mode = kSbPlayerOutputModeDecodeToTexture;
+  output_mode = SbPlayerGetPreferredOutputMode(&creation_param);
+  ASSERT_NE(output_mode, kSbPlayerOutputModeInvalid);
+
+  creation_param.output_mode = kSbPlayerOutputModePunchOut;
+  output_mode = SbPlayerGetPreferredOutputMode(&creation_param);
+  ASSERT_NE(output_mode, kSbPlayerOutputModeInvalid);
+}
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
+
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
diff --git a/src/starboard/nplb/player_output_mode_supported_test.cc b/src/starboard/nplb/player_output_mode_supported_test.cc
index 65b35b3..d4e77f2 100644
--- a/src/starboard/nplb/player_output_mode_supported_test.cc
+++ b/src/starboard/nplb/player_output_mode_supported_test.cc
@@ -21,6 +21,8 @@
 namespace nplb {
 namespace {
 
+#if !SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
 TEST(SbPlayerOutputModeSupportedTest, SunnyDay) {
   SbMediaVideoCodec kVideoCodec = kSbMediaVideoCodecH264;
   SbDrmSystem kDrmSystem = kSbDrmSystemInvalid;
@@ -46,6 +48,8 @@
   EXPECT_FALSE(result);
 }
 
+#endif  // !SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
 }  // namespace
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/socket_get_interface_address_test.cc b/src/starboard/nplb/socket_get_interface_address_test.cc
index 36ed9c3..3c1a317 100644
--- a/src/starboard/nplb/socket_get_interface_address_test.cc
+++ b/src/starboard/nplb/socket_get_interface_address_test.cc
@@ -64,7 +64,7 @@
 
   EXPECT_TRUE(SbSocketGetInterfaceAddress(NULL, &source, &netmask));
   // A netmask that starts with 0 is likely incorrect.
-  EXPECT_TRUE(netmask.address[0] & 0x8);
+  EXPECT_TRUE(netmask.address[0] & 0x80);
   EXPECT_TRUE(source.type == kSbSocketAddressTypeIpv4 ||
               source.type == kSbSocketAddressTypeIpv6);
   EXPECT_TRUE(netmask.type == kSbSocketAddressTypeIpv4 ||
@@ -89,7 +89,7 @@
   EXPECT_FALSE(IsLocalhost(&source));
 
   // A netmask that starts with 0 is likely incorrect.
-  EXPECT_TRUE(netmask.address[0] & 0x8);
+  EXPECT_TRUE(netmask.address[0] & 0x80);
   EXPECT_EQ(GetAddressType(), source.type);
   EXPECT_EQ(GetAddressType(), netmask.type);
   EXPECT_EQ(0, source.port);
@@ -128,7 +128,7 @@
   EXPECT_EQ(GetAddressType(), source.type);
   EXPECT_NE(0, source.port);
   // A netmask that starts with 0 is likely incorrect.
-  EXPECT_TRUE(netmask.address[0] & 0x8);
+  EXPECT_TRUE(netmask.address[0] & 0x80);
   EXPECT_EQ(GetAddressType(), netmask.type);
   EXPECT_NE(0, SbMemoryCompare(source.address, invalid_address.address,
                                SB_ARRAY_SIZE(source.address)));
diff --git a/src/starboard/player.h b/src/starboard/player.h
index 08e16d1..b579672 100644
--- a/src/starboard/player.h
+++ b/src/starboard/player.h
@@ -117,6 +117,51 @@
   kSbPlayerOutputModeInvalid,
 } SbPlayerOutputMode;
 
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+// The playback related parameters to pass into SbPlayerCreate() and
+// SbPlayerGetPreferredOutputMode().
+typedef struct SbPlayerCreationParam {
+  // The audio mime of the stream if available.  Otherwise it will point to an
+  // empty string.  It will never be NULL.
+  const char* audio_mime;
+  // The video mime of the stream if available.  Otherwise it will point to an
+  // empty string.  It will never be NULL.
+  const char* video_mime;
+  // Provides an appropriate DRM system if the media stream has encrypted
+  // portions.  It will be |kSbDrmSystemInvalid| if the stream does not have
+  // encrypted portions.
+  SbDrmSystem drm_system;
+  // Contains a populated SbMediaAudioSampleInfo if |audio_sample_info.codec|
+  // isn't |kSbMediaAudioCodecNone|.  When |audio_sample_info.codec| is
+  // |kSbMediaAudioCodecNone|, the video doesn't have an audio track.
+  SbMediaAudioSampleInfo audio_sample_info;
+  // Contains a populated SbMediaVideoSampleInfo if |video_sample_info.codec|
+  // isn't |kSbMediaVideoCodecNone|.  When |video_sample_info.codec| is
+  // |kSbMediaVideoCodecNone|, the video is audio only.
+  SbMediaVideoSampleInfo video_sample_info;
+  // Selects how the decoded video frames will be output.  For example,
+  // |kSbPlayerOutputModePunchOut| indicates that the decoded video frames will
+  // be output to a background video layer by the platform, and
+  // |kSbPlayerOutputDecodeToTexture| indicates that the decoded video frames
+  // should be made available for the application to pull via calls to
+  // SbPlayerGetCurrentFrame().
+  SbPlayerOutputMode output_mode;
+  // Indicates the max video capabilities required. The web app will not provide
+  // a video stream exceeding the maximums described by this parameter. Allows
+  // the platform to optimize playback pipeline for low quality video streams if
+  // it knows that it will never adapt to higher quality streams. The string
+  // uses the same format as the string passed in to
+  // SbMediaCanPlayMimeAndKeySystem(), for example, when it is set to
+  // "width=1920; height=1080; framerate=15;", the video will never adapt to
+  // resolution higher than 1920x1080 or frame per second higher than 15 fps.
+  // When the maximums are unknown, this will be set to an empty string.  It
+  // will never be set to NULL.
+  const char* max_video_capabilities;
+} SbPlayerCreationParam;
+
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
 #if SB_API_VERSION >= 11
 
 // Identify the type of side data accompanied with |SbPlayerSampleInfo|, as side
@@ -411,15 +456,16 @@
 //   is no longer valid after this function returns.  The implementation has to
 //   make a copy of the content if it is needed after the function returns.
 #if SB_API_VERSION >= 11
-// |max_video_capabilities|: This string communicates the video maximums to the
-//   platform. The web app will not provide a video stream exceeding the
-//   maximums described by this parameter. Allows the platform to optimize
-//   playback pipeline for low quality video streams if it knows that it will
-//   never adapt to higher quality streams. The string uses the same format as
-//   the string passed in to SbMediaCanPlayMimeAndKeySystem(), for example, when
-//   it is set to "width=1920; height=1080; framerate=15;", the video will never
-//   adapt to resolution higher than 1920x1080 or frame per second higher than
-//   15 fps. When the maximums are unknown, this will be set to NULL.
+// |max_video_capabilities|: This string communicates the max video capabilities
+//   required to the platform. The web app will not provide a video stream
+//   exceeding the maximums described by this parameter. Allows the platform to
+//   optimize playback pipeline for low quality video streams if it knows that
+//   it will never adapt to higher quality streams. The string uses the same
+//   format as the string passed in to SbMediaCanPlayMimeAndKeySystem(), for
+//   example, when it is set to "width=1920; height=1080; framerate=15;", the
+//   video will never adapt to resolution higher than 1920x1080 or frame per
+//   second higher than 15 fps. When the maximums are unknown, this will be set
+//   to NULL.
 #endif  // SB_API_VERSION >= 11
 #if SB_HAS(AUDIOLESS_VIDEO)
 //   When |audio_codec| is |kSbMediaAudioCodecNone|, this must be set to NULL.
@@ -467,6 +513,21 @@
 // |decoder_status_func|, |player_status_func|, or |player_error_func| if it
 // applies), then |kSbPlayerInvalid| must be returned.
 #endif  // SB_API_VERSION >= 10
+
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+SB_EXPORT SbPlayer
+SbPlayerCreate(SbWindow window,
+               const SbPlayerCreationParam* creation_param,
+               SbPlayerDeallocateSampleFunc sample_deallocate_func,
+               SbPlayerDecoderStatusFunc decoder_status_func,
+               SbPlayerStatusFunc player_status_func,
+               SbPlayerErrorFunc player_error_func,
+               void* context,
+               SbDecodeTargetGraphicsContextProvider* context_provider);
+
+#else  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
 SB_EXPORT SbPlayer
 SbPlayerCreate(SbWindow window,
                SbMediaVideoCodec video_codec,
@@ -492,12 +553,38 @@
                void* context,
                SbPlayerOutputMode output_mode,
                SbDecodeTargetGraphicsContextProvider* context_provider);
+
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+// Returns the preferred output mode of the implementation when a video
+// described by |creation_param| is played.  It is assumed that it is okay to
+// call SbPlayerCreate() with the same video described by |creation_param|,
+// with its |output_mode| member replaced by the returned output mode.
+// When the caller has no preference on the output mode, it will set
+// |creation_param->output_mode| to |kSbPlayerOutputModeInvalid|, and the
+// implementation can return its preferred output mode based on the information
+// contained in |creation_param|.  The caller can also set
+// |creation_param->output_mode| to its preferred output mode, and the
+// implementation should return the same output mode if it is supported,
+// otherwise the implementation should return an output mode that it is
+// supported, as if |creation_param->output_mode| is set to
+// |kSbPlayerOutputModeInvalid| prior to the call.
+// Note that it is not the responsibility of this function to verify whether the
+// video described by |creation_param| can be played on the platform, and the
+// implementation should try its best effort to return a valid output mode.
+// |creation_param| will never be NULL.
+SB_EXPORT SbPlayerOutputMode
+SbPlayerGetPreferredOutputMode(const SbPlayerCreationParam* creation_param);
+
+#else   // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
 // Returns true if the given player output mode is supported by the platform.
 // If this function returns true, it is okay to call SbPlayerCreate() with
 // the given |output_mode|.
 SB_EXPORT bool SbPlayerOutputModeSupported(SbPlayerOutputMode output_mode,
                                            SbMediaVideoCodec codec,
                                            SbDrmSystem drm_system);
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
 
 // Destroys |player|, freeing all associated resources. Each callback must
 // receive one more callback to say that the player was destroyed. Callbacks
diff --git a/src/starboard/raspi/shared/launcher.py b/src/starboard/raspi/shared/launcher.py
index 75e57c6..2ea99b7 100644
--- a/src/starboard/raspi/shared/launcher.py
+++ b/src/starboard/raspi/shared/launcher.py
@@ -143,7 +143,7 @@
     retry_count = 0
     expected_prompts = [
         r'.*Are\syou\ssure.*',  # Fingerprint verification
-        r'\S+ password:',  # Password prompt
+        r'.* password:',  # Password prompt
         '.*[a-zA-Z]+.*',  # Any other text input
     ]
     while True:
diff --git a/src/starboard/raspi/shared/starboard_platform.gypi b/src/starboard/raspi/shared/starboard_platform.gypi
index 3c0ebb4..f71d0c8 100644
--- a/src/starboard/raspi/shared/starboard_platform.gypi
+++ b/src/starboard/raspi/shared/starboard_platform.gypi
@@ -234,6 +234,7 @@
         '<(DEPTH)/starboard/shared/pthread/mutex_release.cc',
         '<(DEPTH)/starboard/shared/pthread/once.cc',
         '<(DEPTH)/starboard/shared/pthread/thread_context_get_pointer.cc',
+        '<(DEPTH)/starboard/shared/pthread/thread_context_internal.cc',
         '<(DEPTH)/starboard/shared/pthread/thread_context_internal.h',
         '<(DEPTH)/starboard/shared/pthread/thread_create.cc',
         '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc',
@@ -326,6 +327,7 @@
         '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_get_info2.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_get_maximum_number_of_samples_per_write.cc',
+        '<(DEPTH)/starboard/shared/starboard/player/player_get_preferred_output_mode_prefer_punchout.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc',
         '<(DEPTH)/starboard/shared/starboard/player/player_internal.h',
         '<(DEPTH)/starboard/shared/starboard/player/player_output_mode_supported.cc',
diff --git a/src/starboard/shared/pthread/thread_context_get_pointer.cc b/src/starboard/shared/pthread/thread_context_get_pointer.cc
index d7e2e7d..4625ebf 100644
--- a/src/starboard/shared/pthread/thread_context_get_pointer.cc
+++ b/src/starboard/shared/pthread/thread_context_get_pointer.cc
@@ -21,88 +21,24 @@
 #include "starboard/common/log.h"
 #include "starboard/shared/pthread/thread_context_internal.h"
 
-// TODO: Implement for e.g. Mac OSX, BSD flavors, QNX - maybe in another file.
-#if !defined(__gnu_linux__)  // Note: __linux__ is undef'd for Starboard builds.
-#error "SbThreadContext is only implemented for Linux"
-#endif
-
-// UCLIBC pretends to be GLIBC.
-#if (defined(__GLIBC__) || defined(__GNU_LIBRARY__)) && !defined(__UCLIBC__)
-#define SB_IS_GLIBC 1
-#else
-#define SB_IS_GLIBC 0
-#endif
-
-// clang-format off
-#if SB_IS_ARCH_X86
-# if SB_IS_32_BIT
-#  error("TODO: Validate UCONTEXT macros on 32-bit X86")
-#  define UCONTEXT_IP(ucontext) ((ucontext)->uc_mcontext.gregs[REG_EIP])
-#  define UCONTEXT_SP(ucontext) ((ucontext)->uc_mcontext.gregs[REG_ESP])
-#  define UCONTEXT_FP(ucontext) ((ucontext)->uc_mcontext.gregs[REG_EBP])
-# elif SB_IS_64_BIT
-#  define UCONTEXT_IP(ucontext) ((ucontext)->uc_mcontext.gregs[REG_RIP])
-#  define UCONTEXT_SP(ucontext) ((ucontext)->uc_mcontext.gregs[REG_RSP])
-#  define UCONTEXT_FP(ucontext) ((ucontext)->uc_mcontext.gregs[REG_RBP])
-# endif
-#elif SB_IS_ARCH_ARM
-# if SB_IS_32_BIT
-#  if SB_IS_GLIBC && ((__GLIBC__ * 100 + __GLIBC_MINOR__) < 204)
-#   error("TODO: Validate UCONTEXT macros on 32-bit ARM w/ old GLIBC")
-#   define UCONTEXT_IP(ucontext) ((ucontext)->uc_mcontext.gregs[R15])
-#   define UCONTEXT_SP(ucontext) ((ucontext)->uc_mcontext.gregs[R13])
-#   define UCONTEXT_FP(ucontext) ((ucontext)->uc_mcontext.gregs[R11])
-#  else  // SB_IS_GLIBC < 2.04
-#   define UCONTEXT_IP(ucontext) ((ucontext)->uc_mcontext.arm_pc)
-#   define UCONTEXT_SP(ucontext) ((ucontext)->uc_mcontext.arm_sp)
-#   define UCONTEXT_FP(ucontext) ((ucontext)->uc_mcontext.arm_fp)
-#  endif  // SB_IS_GLIBC < 2.04
-# elif SB_IS_64_BIT
-#  error("TODO: Validate UCONTEXT macros on 64-bit ARM")
-#  define UCONTEXT_IP(ucontext) ((ucontext)->uc_mcontext.pc)
-#  define UCONTEXT_SP(ucontext) ((ucontext)->uc_mcontext.sp)
-#  define UCONTEXT_FP(ucontext) ((ucontext)->uc_mcontext.regs[29])
-# endif
-#elif SB_IS_ARCH_MIPS
-#  error("TODO: Validate UCONTEXT macros on MIPS")
-#  define UCONTEXT_IP(ucontext) ((ucontext)->uc_mcontext.pc)
-#  define UCONTEXT_SP(ucontext) ((ucontext)->uc_mcontext.gregs[29])
-#  define UCONTEXT_FP(ucontext) ((ucontext)->uc_mcontext.gregs[30])
-#elif SB_IS_ARCH_PPC
-# if SB_IS_GLIBC
-#  error("TODO: Validate UCONTEXT macros on PPC w/ GLIBC")
-#  define UCONTEXT_IP(ucontext) ((ucontext)->uc_mcontext.regs->nip)
-#  define UCONTEXT_SP(ucontext) ((ucontext)->uc_mcontext.regs->gpr[PT_R1])
-#  define UCONTEXT_FP(ucontext) ((ucontext)->uc_mcontext.regs->gpr[PT_R31])
-# else  // SB_IS_GLIBC
-#  error("TODO: Validate UCONTEXT macros on PPC")
-#  define UCONTEXT_IP(ucontext) ((ucontext)->uc_mcontext.gp_regs[32])
-#  define UCONTEXT_SP(ucontext) ((ucontext)->uc_mcontext.gp_regs[1])
-#  define UCONTEXT_FP(ucontext) ((ucontext)->uc_mcontext.gp_regs[31])
-# endif  // SB_IS_GLIBC
-#else  // SB_IS_ARCH_XXX
-# error "SbThreadContext isn't implemented for this CPU architecture"
-#endif  // SB_IS_ARCH_XXX
-// clang-format on
-
 bool SbThreadContextGetPointer(SbThreadContext context,
                                SbThreadContextProperty property,
                                void** out_value) {
-  if (context == kSbThreadContextInvalid || context->ucontext == nullptr) {
+  if (context == kSbThreadContextInvalid || context->ip_ == nullptr) {
     return false;
   }
 
   switch (property) {
     case kSbThreadContextInstructionPointer:
-      *out_value = reinterpret_cast<void*>(UCONTEXT_IP(context->ucontext));
+      *out_value = context->ip_;
       return true;
 
     case kSbThreadContextStackPointer:
-      *out_value = reinterpret_cast<void*>(UCONTEXT_SP(context->ucontext));
+      *out_value = context->sp_;
       return true;
 
     case kSbThreadContextFramePointer:
-      *out_value = reinterpret_cast<void*>(UCONTEXT_FP(context->ucontext));
+      *out_value = context->fp_;
       return true;
 
     default:
diff --git a/src/starboard/shared/pthread/thread_context_internal.cc b/src/starboard/shared/pthread/thread_context_internal.cc
new file mode 100644
index 0000000..f5e7353
--- /dev/null
+++ b/src/starboard/shared/pthread/thread_context_internal.cc
@@ -0,0 +1,73 @@
+// Copyright 2020 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/pthread/thread_context_internal.h"
+
+#if SB_API_VERSION >= 11
+
+#if !defined(__gnu_linux__)  // Note: __linux__ is undef'd for Starboard builds.
+#error "SbThreadContext is only implemented for Linux"
+#endif
+
+#if !(SB_IS_32_BIT || SB_IS_64_BIT)
+#error "CPU architecture must be either 32-bit or 64-bit"
+#endif
+
+// UCLIBC pretends to be GLIBC.
+#if (defined(__GLIBC__) || defined(__GNU_LIBRARY__)) && !defined(__UCLIBC__)
+#define SB_IS_GLIBC 1
+#else
+#define SB_IS_GLIBC 0
+#endif
+
+SbThreadContextPrivate::SbThreadContextPrivate(ucontext_t* ucontext) {
+  mcontext_t& mcontext = ucontext->uc_mcontext;
+
+// TODO: Remove redundant #if checks when
+//       SB_MINIMUM_API_VERSION >= SB_SABI_FILE_VERSION.
+#if SB_IS_ARCH_X86 || SB_IS_ARCH_X64
+#if SB_IS_64_BIT
+  // 64-bit X86 (aka X64)
+  ip_ = reinterpret_cast<void*>(mcontext.gregs[REG_RIP]);
+  sp_ = reinterpret_cast<void*>(mcontext.gregs[REG_RSP]);
+  fp_ = reinterpret_cast<void*>(mcontext.gregs[REG_RBP]);
+#else
+  // 32-bit X86
+  ip_ = reinterpret_cast<void*>(mcontext.gregs[REG_EIP]);
+  sp_ = reinterpret_cast<void*>(mcontext.gregs[REG_ESP]);
+  fp_ = reinterpret_cast<void*>(mcontext.gregs[REG_EBP]);
+#endif
+#elif SB_IS_ARCH_ARM || SB_IS_ARCH_ARM64
+#if SB_IS_64_BIT
+  // 64-bit ARM
+  ip_ = reinterpret_cast<void*>(mcontext.pc);
+  sp_ = reinterpret_cast<void*>(mcontext.sp);
+  fp_ = reinterpret_cast<void*>(mcontext.regs[29]);
+#elif SB_IS_GLIBC && ((__GLIBC__ * 100 + __GLIBC_MINOR__) < 204)
+  // 32-bit ARM w/ old GLIBC that used gregs[]
+  ip_ = reinterpret_cast<void*>(mcontext.gregs[R15]);
+  sp_ = reinterpret_cast<void*>(mcontext.gregs[R13]);
+  fp_ = reinterpret_cast<void*>(mcontext.gregs[R11]);
+#else
+  // 32-bit ARM
+  ip_ = reinterpret_cast<void*>(mcontext.arm_pc);
+  sp_ = reinterpret_cast<void*>(mcontext.arm_sp);
+  fp_ = reinterpret_cast<void*>(mcontext.arm_fp);
+#endif
+#else  // SB_IS_ARCH_XXX
+#error "SbThreadContext isn't implemented for this CPU architecture"
+#endif  // SB_IS_ARCH_XXX
+}
+
+#endif  // SB_API_VERSION >= 11
diff --git a/src/starboard/shared/pthread/thread_context_internal.h b/src/starboard/shared/pthread/thread_context_internal.h
index 3effcd6..14ef86d 100644
--- a/src/starboard/shared/pthread/thread_context_internal.h
+++ b/src/starboard/shared/pthread/thread_context_internal.h
@@ -15,11 +15,23 @@
 #ifndef STARBOARD_SHARED_PTHREAD_THREAD_CONTEXT_INTERNAL_H_
 #define STARBOARD_SHARED_PTHREAD_THREAD_CONTEXT_INTERNAL_H_
 
+#include <signal.h>
+
 #include "starboard/thread.h"
 
 #if SB_API_VERSION >= 11
 struct SbThreadContextPrivate {
-  ucontext_t* ucontext = nullptr;
+  explicit SbThreadContextPrivate(ucontext_t* ucontext);
+
+  SbThreadContextPrivate() = default;
+  SbThreadContextPrivate(const SbThreadContextPrivate&) = default;
+  SbThreadContextPrivate(SbThreadContextPrivate&&) = default;
+  SbThreadContextPrivate& operator=(const SbThreadContextPrivate&) = default;
+  SbThreadContextPrivate& operator=(SbThreadContextPrivate&&) = default;
+
+  void* ip_ = nullptr;
+  void* sp_ = nullptr;
+  void* fp_ = nullptr;
 };
 #endif  // SB_API_VERSION >= 11
 
diff --git a/src/starboard/shared/pthread/thread_sampler_internal.cc b/src/starboard/shared/pthread/thread_sampler_internal.cc
index bca4da4..a56f8ec 100644
--- a/src/starboard/shared/pthread/thread_sampler_internal.cc
+++ b/src/starboard/shared/pthread/thread_sampler_internal.cc
@@ -109,7 +109,7 @@
   starboard::ScopedLock lock(GetMutex());
   SB_DCHECK(frozen_sampler_ == sampler) << "SbThreadSampler didn't freeze.";
   if (frozen_sampler_ != sampler) return false;
-  sb_context_.ucontext = nullptr;
+  sb_context_ = SbThreadContextPrivate();
   sem_post(&thaw_semaphore_);
   frozen_sampler_ = kSbThreadSamplerInvalid;
   return true;
@@ -119,7 +119,7 @@
                                          void* context) {
   SB_UNREFERENCED_PARAMETER(info);
   if (signal != SIGPROF) return;
-  sb_context_.ucontext = reinterpret_cast<ucontext_t*>(context);
+  sb_context_ = SbThreadContextPrivate(reinterpret_cast<ucontext_t*>(context));
   // |Freeze| can return the context now.
   sem_post(&freeze_semaphore_);
   // Keep this thread frozen until |Thaw| is called.
diff --git a/src/starboard/shared/starboard/player/filter/video_frame_cadence_pattern_generator.cc b/src/starboard/shared/starboard/player/filter/video_frame_cadence_pattern_generator.cc
index 83ccb11..e291f6f 100644
--- a/src/starboard/shared/starboard/player/filter/video_frame_cadence_pattern_generator.cc
+++ b/src/starboard/shared/starboard/player/filter/video_frame_cadence_pattern_generator.cc
@@ -75,9 +75,9 @@
 }
 
 void VideoFrameCadencePatternGenerator::AdvanceToNextFrame() {
-  SB_DCHECK(refresh_rate_ != kInvalidRefreshRate);
-  SB_DCHECK(frame_rate_ != kInvalidFrameRate);
-
+  // It is possible that AdvanceToNextFrame() is called before refresh rate and
+  // frame rate are set.  This can happen when the platform release the frame on
+  // rendering.
   ++frame_index_;
 }
 
diff --git a/src/starboard/shared/starboard/player/player_create.cc b/src/starboard/shared/starboard/player/player_create.cc
index fd6f379..c8dfbdd 100644
--- a/src/starboard/shared/starboard/player/player_create.cc
+++ b/src/starboard/shared/starboard/player/player_create.cc
@@ -33,6 +33,49 @@
     FilterBasedPlayerWorkerHandler;
 using starboard::shared::starboard::player::PlayerWorker;
 
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+SbPlayer SbPlayerCreate(SbWindow window,
+                        const SbPlayerCreationParam* creation_param,
+                        SbPlayerDeallocateSampleFunc sample_deallocate_func,
+                        SbPlayerDecoderStatusFunc decoder_status_func,
+                        SbPlayerStatusFunc player_status_func,
+                        SbPlayerErrorFunc player_error_func,
+                        void* context,
+                        SbDecodeTargetGraphicsContextProvider* provider) {
+  if (!creation_param) {
+    SB_LOG(ERROR) << "CreationParam cannot be null.";
+    return kSbPlayerInvalid;
+  }
+  if (!creation_param->audio_mime) {
+    SB_LOG(ERROR) << "creation_param->audio_mime cannot be null.";
+    return kSbPlayerInvalid;
+  }
+  if (!creation_param->video_mime) {
+    SB_LOG(ERROR) << "creation_param->video_mime cannot be null.";
+    return kSbPlayerInvalid;
+  }
+  if (!creation_param->max_video_capabilities) {
+    SB_LOG(ERROR) << "creation_param->max_video_capabilities cannot be null.";
+    return kSbPlayerInvalid;
+  }
+
+  SB_LOG(INFO) << "SbPlayerCreate() called with audio mime \""
+               << creation_param->audio_mime << "\", video mime \""
+               << creation_param->video_mime
+               << "\", and max video capabilities \""
+               << creation_param->max_video_capabilities << "\".";
+
+  SbMediaAudioCodec audio_codec = creation_param->audio_sample_info.codec;
+  SbMediaVideoCodec video_codec = creation_param->video_sample_info.codec;
+  SbDrmSystem drm_system = creation_param->drm_system;
+  const SbMediaAudioSampleInfo* audio_sample_info =
+      &creation_param->audio_sample_info;
+  const char* max_video_capabilities = creation_param->max_video_capabilities;
+  const auto output_mode = creation_param->output_mode;
+
+#else  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
 SbPlayer SbPlayerCreate(SbWindow window,
                         SbMediaVideoCodec video_codec,
                         SbMediaAudioCodec audio_codec,
@@ -53,6 +96,8 @@
                         void* context,
                         SbPlayerOutputMode output_mode,
                         SbDecodeTargetGraphicsContextProvider* provider) {
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
   SB_UNREFERENCED_PARAMETER(window);
 #if SB_API_VERSION >= 11
   SB_UNREFERENCED_PARAMETER(max_video_capabilities);
@@ -120,10 +165,17 @@
     return kSbPlayerInvalid;
   }
 
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+  if (SbPlayerGetPreferredOutputMode(creation_param) != output_mode) {
+    SB_LOG(ERROR) << "Unsupported player output mode " << output_mode;
+    return kSbPlayerInvalid;
+  }
+#else   // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
   if (!SbPlayerOutputModeSupported(output_mode, video_codec, drm_system)) {
     SB_LOG(ERROR) << "Unsupported player output mode " << output_mode;
     return kSbPlayerInvalid;
   }
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
 
   UpdateActiveSessionPlatformPlaybackState(kPlaying);
 
diff --git a/src/starboard/shared/starboard/player/player_get_preferred_output_mode_prefer_punchout.cc b/src/starboard/shared/starboard/player/player_get_preferred_output_mode_prefer_punchout.cc
new file mode 100644
index 0000000..f69b6d7
--- /dev/null
+++ b/src/starboard/shared/starboard/player/player_get_preferred_output_mode_prefer_punchout.cc
@@ -0,0 +1,74 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/player.h"
+
+#include <algorithm>
+
+#include "starboard/configuration.h"
+#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
+
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+SbPlayerOutputMode SbPlayerGetPreferredOutputMode(
+    const SbPlayerCreationParam* creation_param) {
+  using starboard::shared::starboard::player::filter::VideoDecoder;
+
+  if (!creation_param) {
+    SB_LOG(ERROR) << "creation_param cannot be NULL";
+    return kSbPlayerOutputModeInvalid;
+  }
+
+  if (!creation_param->audio_mime) {
+    SB_LOG(ERROR) << "creation_param->audio_mime cannot be NULL";
+    return kSbPlayerOutputModeInvalid;
+  }
+
+  if (!creation_param->video_mime) {
+    SB_LOG(ERROR) << "creation_param->video_mime cannot be NULL";
+    return kSbPlayerOutputModeInvalid;
+  }
+
+  if (!creation_param->max_video_capabilities) {
+    SB_LOG(ERROR) << "creation_param->max_video_capabilities cannot be NULL";
+    return kSbPlayerOutputModeInvalid;
+  }
+
+  auto codec = creation_param->video_sample_info.codec;
+  auto drm_system = creation_param->drm_system;
+
+  SbPlayerOutputMode output_modes_to_check[] = {
+      kSbPlayerOutputModePunchOut, kSbPlayerOutputModeDecodeToTexture,
+  };
+
+  // Check |kSbPlayerOutputModeDecodeToTexture| first if the caller prefers it.
+  if (creation_param->output_mode == kSbPlayerOutputModeDecodeToTexture) {
+    std::swap(output_modes_to_check[0], output_modes_to_check[1]);
+  }
+
+  if (VideoDecoder::OutputModeSupported(output_modes_to_check[0], codec,
+                                        drm_system)) {
+    return output_modes_to_check[0];
+  }
+
+  if (VideoDecoder::OutputModeSupported(output_modes_to_check[1], codec,
+                                        drm_system)) {
+    return output_modes_to_check[1];
+  }
+
+  SB_NOTREACHED();
+  return kSbPlayerOutputModeInvalid;
+}
+
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
diff --git a/src/starboard/shared/starboard/player/player_output_mode_supported.cc b/src/starboard/shared/starboard/player/player_output_mode_supported.cc
index e3af869..beadfb7 100644
--- a/src/starboard/shared/starboard/player/player_output_mode_supported.cc
+++ b/src/starboard/shared/starboard/player/player_output_mode_supported.cc
@@ -18,6 +18,8 @@
 #include "starboard/configuration.h"
 #include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
 
+#if !SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
 bool SbPlayerOutputModeSupported(SbPlayerOutputMode output_mode,
                                  SbMediaVideoCodec codec,
                                  SbDrmSystem drm_system) {
@@ -25,3 +27,4 @@
       OutputModeSupported(output_mode, codec, drm_system);
 }
 
+#endif  // !SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
diff --git a/src/starboard/shared/stub/player_create.cc b/src/starboard/shared/stub/player_create.cc
index d7d2243..ff11851 100644
--- a/src/starboard/shared/stub/player_create.cc
+++ b/src/starboard/shared/stub/player_create.cc
@@ -14,6 +14,24 @@
 
 #include "starboard/player.h"
 
+#include "starboard/configuration.h"
+
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+SbPlayer SbPlayerCreate(
+    SbWindow /*window*/,
+    const SbPlayerCreationParam* /*creation_param*/,
+    SbPlayerDeallocateSampleFunc /*sample_deallocate_func*/,
+    SbPlayerDecoderStatusFunc /*decoder_status_func*/,
+    SbPlayerStatusFunc /*player_status_func*/,
+    SbPlayerErrorFunc /*player_error_func*/,
+    void* /*context*/,
+    SbDecodeTargetGraphicsContextProvider* /*context_provider*/) {
+  return kSbPlayerInvalid;
+}
+
+#else  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
 SbPlayer SbPlayerCreate(SbWindow /*window*/,
                         SbMediaVideoCodec /*video_codec*/,
                         SbMediaAudioCodec /*audio_codec*/,
@@ -34,3 +52,5 @@
                         SbDecodeTargetGraphicsContextProvider* /*provider*/) {
   return kSbPlayerInvalid;
 }
+
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
diff --git a/src/starboard/shared/stub/player_get_preferred_output_mode.cc b/src/starboard/shared/stub/player_get_preferred_output_mode.cc
new file mode 100644
index 0000000..f52fcc3
--- /dev/null
+++ b/src/starboard/shared/stub/player_get_preferred_output_mode.cc
@@ -0,0 +1,26 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/player.h"
+
+#include "starboard/configuration.h"
+
+#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
+SbPlayerOutputMode SbPlayerGetPreferredOutputMode(
+    const SbPlayerCreationParam* /*creation_param*/) {
+  return kSbPlayerOutputModeInvalid;
+}
+
+#endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
diff --git a/src/starboard/shared/stub/player_output_mode_supported.cc b/src/starboard/shared/stub/player_output_mode_supported.cc
index 3b3f7dc..2b470fa 100644
--- a/src/starboard/shared/stub/player_output_mode_supported.cc
+++ b/src/starboard/shared/stub/player_output_mode_supported.cc
@@ -14,8 +14,12 @@
 
 #include "starboard/player.h"
 
+#if !SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
+
 bool SbPlayerOutputModeSupported(SbPlayerOutputMode /*output_mode*/,
                                  SbMediaVideoCodec /*codec*/,
                                  SbDrmSystem /*drm_system*/) {
   return false;
 }
+
+#endif  // !SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
diff --git a/src/starboard/stub/BUILD.gn b/src/starboard/stub/BUILD.gn
index cfeb1ba..be648ee 100644
--- a/src/starboard/stub/BUILD.gn
+++ b/src/starboard/stub/BUILD.gn
@@ -279,6 +279,7 @@
     "//starboard/shared/stub/player_destroy.cc",
     "//starboard/shared/stub/player_get_current_frame.cc",
     "//starboard/shared/stub/player_get_info.cc",
+    "//starboard/shared/stub/player_get_preferred_output_mode.cc",
     "//starboard/shared/stub/player_output_mode_supported.cc",
     "//starboard/shared/stub/player_seek.cc",
     "//starboard/shared/stub/player_set_bounds.cc",
diff --git a/src/starboard/stub/stub_sources.gypi b/src/starboard/stub/stub_sources.gypi
index 572bc18..ac44bed 100644
--- a/src/starboard/stub/stub_sources.gypi
+++ b/src/starboard/stub/stub_sources.gypi
@@ -150,6 +150,7 @@
         '<(DEPTH)/starboard/shared/stub/player_get_info.cc',
         '<(DEPTH)/starboard/shared/stub/player_get_info2.cc',
         '<(DEPTH)/starboard/shared/stub/player_get_maximum_number_of_samples_per_write.cc',
+        '<(DEPTH)/starboard/shared/stub/player_get_preferred_output_mode.cc',
         '<(DEPTH)/starboard/shared/stub/player_output_mode_supported.cc',
         '<(DEPTH)/starboard/shared/stub/player_seek.cc',
         '<(DEPTH)/starboard/shared/stub/player_seek2.cc',
diff --git a/src/starboard/tools/app_launcher_packager.py b/src/starboard/tools/app_launcher_packager.py
index 9be2843..5114a5b 100644
--- a/src/starboard/tools/app_launcher_packager.py
+++ b/src/starboard/tools/app_launcher_packager.py
@@ -142,7 +142,9 @@
   for p in starboard.tools.platform.GetAll():
     platform_path = os.path.relpath(
         starboard.tools.platform.Get(p).path, repo_root)
-    platforms_map[p] = platform_path
+    # Store posix paths even on Windows so MH Linux hosts can use them.
+    # The template has code to re-normalize them when used on Windows hosts.
+    platforms_map[p] = platform_path.replace('\\', '/')
   template = jinja2.Template(
       open(os.path.join(current_dir, 'platform.py.template')).read())
   with open(os.path.join(dest_dir, 'platform.py'), 'w+') as f:
diff --git a/src/starboard/tools/platform.py.template b/src/starboard/tools/platform.py.template
index 7a211bc..17d55b5 100644
--- a/src/starboard/tools/platform.py.template
+++ b/src/starboard/tools/platform.py.template
@@ -21,6 +21,7 @@
 
 # The name->platform path mapping.
 _PATH_MAP = {{platforms_map}}
+_PATH_MAP = {k:os.path.normpath(v) for k,v in _PATH_MAP.iteritems()}
 
 
 # Cache of the name->PlatformInfo mapping.
diff --git a/src/third_party/proxy_py/proxy.py b/src/third_party/proxy_py/proxy.py
index 7b4a8ec..6b614a6 100755
--- a/src/third_party/proxy_py/proxy.py
+++ b/src/third_party/proxy_py/proxy.py
@@ -630,10 +630,11 @@
     Subclass MUST implement `handle` method. It accepts an instance of accepted `Client` connection.
     """
 
-    def __init__(self, hostname='127.0.0.1', port=8899, backlog=100):
+    def __init__(self, hostname='127.0.0.1', port=8899, backlog=100, client_ips=None):
         self.hostname = hostname
         self.port = port
         self.backlog = backlog
+        self.client_ips = client_ips
         self.socket = None
 
     def handle(self, client):
@@ -648,6 +649,12 @@
             self.socket.listen(self.backlog)
             while True:
                 conn, addr = self.socket.accept()
+                if self.client_ips and addr[0] not in self.client_ips:
+                    logger.warning('Closing socket on rejected client IP %s' % addr[0])
+                    conn.shutdown(socket.SHUT_RDWR)
+                    conn.close()
+                    continue
+                logger.info('Handling socket on accepted client IP %s' % addr[0])
                 client = Client(conn, addr)
                 self.handle(client)
         except Exception as e:
@@ -665,8 +672,8 @@
 
     def __init__(self, hostname='127.0.0.1', port=8899, backlog=100,
                  auth_code=None, server_recvbuf_size=8192, client_recvbuf_size=8192,
-                 host_resolver=None):
-        super(HTTP, self).__init__(hostname, port, backlog)
+                 host_resolver=None, client_ips=None):
+        super(HTTP, self).__init__(hostname, port, backlog, client_ips)
         self.auth_code = auth_code
         self.client_recvbuf_size = client_recvbuf_size
         self.server_recvbuf_size = server_recvbuf_size
@@ -719,7 +726,9 @@
                                                                   'that proxy.py can open concurrently.')
     parser.add_argument('--log-level', default='INFO', help='DEBUG, INFO (default), WARNING, ERROR, CRITICAL')
     parser.add_argument('--host_resolver', default=None, help='Default: No host resolution. '
-                                                      'JSON hosts file used for hostname IP resolution.')
+                                                              'JSON hosts file used for hostname IP resolution.')
+    parser.add_argument('--client_ips', default=None, nargs='*', help='Default: No client IP restriction. '
+                                                                      'The only client IPs that the proxy will accept.')
     args = parser.parse_args()
 
     logging.basicConfig(level=getattr(logging, args.log_level),
@@ -743,7 +752,8 @@
                      auth_code=auth_code,
                      server_recvbuf_size=int(args.server_recvbuf_size),
                      client_recvbuf_size=int(args.client_recvbuf_size),
-                     host_resolver=host_resolver)
+                     host_resolver=host_resolver,
+                     client_ips=args.client_ips)
         proxy.run()
     except KeyboardInterrupt:
         pass