Import Cobalt.21.master.0.282262

Includes the following patches:
  https://cobalt-review.googlesource.com/c/cobalt/+/5810
    by rongo.li@mstar.corp-partner.google.com
diff --git a/src/.pre-commit-config.yaml b/src/.pre-commit-config.yaml
new file mode 100644
index 0000000..440ebc1
--- /dev/null
+++ b/src/.pre-commit-config.yaml
@@ -0,0 +1,54 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+default_stages: [commit]
+repos:
+-   repo: local
+    hooks:
+    -   id: cpplint
+        name: cpplint
+        entry: cpplint
+        language: system
+        types: [c++]
+        args: [--verbose=4, --quiet]
+        exclude: '.*tests?.(cc|h)$'
+        stages: [push]
+    -   id: cpplint_test
+        name: cpplint_test
+        entry: cpplint
+        language: system
+        types: [c++]
+        args: [--verbose=5, --quiet]
+        files: '.*tests?.(cc|h)$'
+        stages: [push]
+    -   id: yapf
+        name: yapf
+        entry: yapf
+        language: system
+        types: [python]
+        args: [--style=yapf, -i]
+    -   id: pylint
+        name: pylint
+        entry: pylint
+        language: system
+        types: [python]
+        args: [-d W0201]
+        stages: [push]
+    -   id: clang-format
+        name: clang-format
+        entry: third_party/precommit-hooks/clang-format_wrapper.py
+        language: python
+        types: [c++]
+        args: [-i, -style=file]
+    -   id: google-java-format
+        name: google-java-format
+        entry: third_party/precommit-hooks/google-java-format_wrapper.py
+        language: python
+        types: [java]
+        args: [-i]
+    -   id: gcheckstyle
+        name: gcheckstyle
+        entry: third_party/precommit-hooks/gcheckstyle_wrapper.py
+        language: python
+        types: [java]
+        verbose: true
+        stages: [push]
diff --git a/src/.pylintrc b/src/.pylintrc
new file mode 100644
index 0000000..77b0063
--- /dev/null
+++ b/src/.pylintrc
@@ -0,0 +1,305 @@
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Profiled execution.
+profile=no
+
+# Add files or directories to the blacklist. They should be base names, not
+# paths.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+
+[MESSAGES CONTROL]
+
+# Enable the message, report, category or checker with the given id(s). You can
+# either give multiple identifier separated by comma (,) or put this option
+# multiple time.
+#enable=
+
+# Disable the message, report, category or checker with the given id(s). You
+# can either give multiple identifier separated by comma (,) or put this option
+# multiple time (only on the command line, not in the configuration file where
+# it should appear only once).
+# CHANGED:
+# C0103: Invalid name ""
+# C0111: Missing docstring
+# C0302: Too many lines in module (N)
+# I0010: Unable to consider inline option ''
+# I0011: Locally disabling WNNNN
+#
+# R0801: Similar lines in N files
+# R0901: Too many ancestors (8/7)
+# R0902: Too many instance attributes (N/7)
+# R0903: Too few public methods (N/2)
+# R0904: Too many public methods (N/20)
+# R0911: Too many return statements (N/6)
+# R0912: Too many branches (N/12)
+# R0913: Too many arguments (N/5)
+# R0914: Too many local variables (N/15)
+# R0915: Too many statements (N/50)
+# R0921: Abstract class not referenced
+# R0922: Abstract class is only referenced 1 times
+# W0122: Use of the exec statement
+# W0141: Used builtin function ''
+# W0142: Used * or ** magic
+# W0402: Uses of a deprecated module 'string'
+# W0404: 41: Reimport 'XX' (imported line NN)
+# W0511: TODO
+# W0603: Using the global statement
+# W0703: Catch "Exception"
+# W1201: Specify string format arguments as logging function parameters
+#
+# These should get enabled, but the codebase has too many violations currently.
+# bad-continuation
+# anomalous-backslash-in-string
+# bad-context-manager
+# bad-indentation
+# bad-str-strip-call
+# bad-whitespace
+# cell-var-from-loop
+# deprecated-lambda
+# eval-used
+# function-redefined
+# import-error
+# locally-enabled
+# missing-final-newline
+# no-init
+# no-name-in-module
+# no-self-use
+# not-callable
+# old-style-class
+# protected-access
+# superfluous-parens
+# super-on-old-class
+# too-many-function-args
+# trailing-whitespace
+# unnecessary-semicolon
+# unpacking-non-sequence
+# unused-import
+# useless-else-on-loop
+disable=C0103,C0111,C0302,I0010,I0011,R0801,R0901,R0902,R0903,R0904,R0911,R0912,R0913,R0914,R0915,R0921,R0922,W0122,W0141,W0142,W0402,W0404,W0511,W0603,W0703,W1201,bad-continuation,anomalous-backslash-in-string,bad-context-manager,bad-indentation,bad-str-strip-call,bad-whitespace,cell-var-from-loop,deprecated-lambda,eval-used,function-redefined,import-error,locally-enabled,missing-final-newline,no-init,no-name-in-module,no-self-use,not-callable,old-style-class,protected-access,superfluous-parens,super-on-old-class,too-many-function-args,trailing-whitespace,unnecessary-semicolon,unpacking-non-sequence,unused-import,useless-else-on-loop
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html
+output-format=text
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+# CHANGED:
+reports=no
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (RP0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (RP0004).
+comment=no
+
+
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching the beginning of the name of dummy variables
+# (i.e. not used).
+dummy-variables-rgx=_|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=SQLObject,twisted.internet.reactor,hashlib,google.appengine.api.memcache
+
+# When zope mode is activated, add a predefined set of Zope acquired attributes
+# to generated-members.
+zope=no
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed. Python regular
+# expressions are accepted.
+generated-members=REQUEST,acl_users,aq_parent,multiprocessing.managers.SyncManager
+
+
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=4
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+
+[FORMAT]
+
+# Maximum number of characters on a single line.
+max-line-length=80
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+# CHANGED:
+indent-string='  '
+
+
+[BASIC]
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter,apply,input
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# Regular expression which should only match functions or classes name which do
+# not require a docstring
+no-docstring-rgx=__.*__
+
+
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=5
+
+# Argument names that match this expression will be ignored. Default to name
+# with leading underscore
+ignored-argument-names=_.*
+
+# Maximum number of locals for function / method body
+max-locals=15
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branchs=12
+
+# Maximum number of statements in function / method body
+max-statements=50
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=2
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=20
+
+
+[CLASSES]
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+# List of valid names for the first argument in a class method.
+valid-classmethod-first-arg=cls
+
+
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report RP0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report RP0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report RP0402 must
+# not be disabled)
+int-import-graph=
+
+
+[EXCEPTIONS]
+
+# Exceptions that will emit a warning when being caught. Defaults to
+# "Exception"
+overgeneral-exceptions=Exception
diff --git a/src/base/files/file_util.cc b/src/base/files/file_util.cc
index cb9a0d5..d8e445a 100644
--- a/src/base/files/file_util.cc
+++ b/src/base/files/file_util.cc
@@ -21,8 +21,12 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/threading/scoped_blocking_call.h"
 #include "build/build_config.h"
+
+#if defined(STARBOARD)
+#include "starboard/common/file.h"
 #include "starboard/memory.h"
 #include "starboard/types.h"
+#endif
 
 namespace base {
 
diff --git a/src/base/memory/ref_counted.h b/src/base/memory/ref_counted.h
index f52e803..c19072a 100644
--- a/src/base/memory/ref_counted.h
+++ b/src/base/memory/ref_counted.h
@@ -26,7 +26,6 @@
  public:
   bool HasOneRef() const { return ref_count_ == 1; }
   bool HasAtLeastOneRef() const { return ref_count_ >= 1; }
-  int RefCounts() const { return ref_count_; }
 
  protected:
   explicit RefCountedBase(StartRefCountFromZeroTag) {
diff --git a/src/base/metrics/persistent_histogram_storage.cc b/src/base/metrics/persistent_histogram_storage.cc
index 8abb646..3801baf 100644
--- a/src/base/metrics/persistent_histogram_storage.cc
+++ b/src/base/metrics/persistent_histogram_storage.cc
@@ -15,7 +15,7 @@
 #include "build/build_config.h"
 
 #if defined(STARBOARD)
-#include "starboard/file.h"
+#include "starboard/common/file.h"
 #endif
 
 namespace {
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md
index 04eb0a6..f2872b4 100644
--- a/src/cobalt/CHANGELOG.md
+++ b/src/cobalt/CHANGELOG.md
@@ -102,6 +102,23 @@
    (https://lottiefiles.com/web-player). In order to support Lottie, Cobalt
    updated its Skia port from m61 to m79.
 
+ - **Added support for MediaKeySystemMediaCapability.encryptionScheme.**
+
+   Cobalt now supports `MediaKeySystemMediaCapability.encryptionScheme` for
+   `Navigator.requestMediaKeySystemAccess()`. `encryptionScheme` can be 'cenc',
+   'cbcs', or 'cbcs-1-9'.
+   The default implementation assumes that:
+   1. When the Widevine DRM system is used, all the above encryption schemes
+      should be supported across all containers and codecs supported by the
+      platform.
+   2. When the PlayReady DRM system is used, only 'cenc' is supported across all
+      containers and codecs supported by the platform.
+
+   It is possible to customize this behavior via an extension to
+  `SbMediaCanPlayMimeAndKeySystem()`.  Please see the Starboard change log and
+   the comment of `SbMediaCanPlayMimeAndKeySystem()` in `media.h` for more
+   details.
+
 ## Version 20
 
  - **Support for QUIC and SPDY is now enabled.**
diff --git a/src/cobalt/audio/async_audio_decoder.cc b/src/cobalt/audio/async_audio_decoder.cc
index 174cc57..c7885a1 100644
--- a/src/cobalt/audio/async_audio_decoder.cc
+++ b/src/cobalt/audio/async_audio_decoder.cc
@@ -38,7 +38,7 @@
     decode_finish_callback.Run(reader->sample_rate(),
                                reader->ResetAndReturnAudioBus());
   } else {
-    decode_finish_callback.Run(0.f, std::unique_ptr<ShellAudioBus>());
+    decode_finish_callback.Run(0.f, std::unique_ptr<AudioBus>());
   }
 }
 
diff --git a/src/cobalt/audio/async_audio_decoder.h b/src/cobalt/audio/async_audio_decoder.h
index ced6fed..db8a15a 100644
--- a/src/cobalt/audio/async_audio_decoder.h
+++ b/src/cobalt/audio/async_audio_decoder.h
@@ -28,7 +28,7 @@
 class AsyncAudioDecoder {
  public:
   typedef base::Callback<void(float sample_rate,
-                              std::unique_ptr<ShellAudioBus> audio_bus)>
+                              std::unique_ptr<AudioBus> audio_bus)>
       DecodeFinishCallback;
 
   AsyncAudioDecoder();
diff --git a/src/cobalt/audio/audio_buffer.cc b/src/cobalt/audio/audio_buffer.cc
index 1b6ff38..0a4c203 100644
--- a/src/cobalt/audio/audio_buffer.cc
+++ b/src/cobalt/audio/audio_buffer.cc
@@ -25,8 +25,7 @@
 namespace cobalt {
 namespace audio {
 
-AudioBuffer::AudioBuffer(float sample_rate,
-                         std::unique_ptr<ShellAudioBus> audio_bus)
+AudioBuffer::AudioBuffer(float sample_rate, std::unique_ptr<AudioBus> audio_bus)
     : sample_rate_(sample_rate), audio_bus_(std::move(audio_bus)) {
   DCHECK_GT(sample_rate_, 0);
   DCHECK_GT(length(), 0);
diff --git a/src/cobalt/audio/audio_buffer.h b/src/cobalt/audio/audio_buffer.h
index 9028a2b..d20beed 100644
--- a/src/cobalt/audio/audio_buffer.h
+++ b/src/cobalt/audio/audio_buffer.h
@@ -39,7 +39,7 @@
 //   https://www.w3.org/TR/webaudio/#AudioBuffer
 class AudioBuffer : public script::Wrappable {
  public:
-  AudioBuffer(float sample_rate, std::unique_ptr<ShellAudioBus> audio_bus);
+  AudioBuffer(float sample_rate, std::unique_ptr<AudioBus> audio_bus);
 
   // Web API: AudioBuffer
   //
@@ -60,14 +60,14 @@
 
   // Custom, not in any spec
   //
-  ShellAudioBus* audio_bus() { return audio_bus_.get(); }
+  AudioBus* audio_bus() { return audio_bus_.get(); }
 
   DEFINE_WRAPPABLE_TYPE(AudioBuffer);
 
  private:
   const float sample_rate_;
 
-  std::unique_ptr<ShellAudioBus> audio_bus_;
+  std::unique_ptr<AudioBus> audio_bus_;
 
   DISALLOW_COPY_AND_ASSIGN(AudioBuffer);
 };
diff --git a/src/cobalt/audio/audio_buffer_source_node.cc b/src/cobalt/audio/audio_buffer_source_node.cc
index 654e6a8..df8ca6b 100644
--- a/src/cobalt/audio/audio_buffer_source_node.cc
+++ b/src/cobalt/audio/audio_buffer_source_node.cc
@@ -27,7 +27,7 @@
 namespace audio {
 
 typedef media::InterleavedSincResampler InterleavedSincResampler;
-typedef media::ShellAudioBus ShellAudioBus;
+typedef media::AudioBus AudioBus;
 
 // numberOfInputs  : 0
 // numberOfOutputs : 1
@@ -115,7 +115,7 @@
   state_ = kStopped;
 }
 
-std::unique_ptr<ShellAudioBus> AudioBufferSourceNode::PassAudioBusFromSource(
+std::unique_ptr<AudioBus> AudioBufferSourceNode::PassAudioBusFromSource(
     int32 number_of_frames, SampleType sample_type, bool* finished) {
   DCHECK_GT(number_of_frames, 0);
   DCHECK(finished);
@@ -126,7 +126,7 @@
   *finished = false;
 
   if (state_ == kNone || !buffer_) {
-    return std::unique_ptr<ShellAudioBus>();
+    return std::unique_ptr<AudioBus>();
   }
 
   if (state_ == kStopped ||
@@ -140,7 +140,7 @@
                                 base::Unretained(this)));
       buffer_source_added_ = false;
     }
-    return std::unique_ptr<ShellAudioBus>();
+    return std::unique_ptr<AudioBus>();
   }
 
   DCHECK_EQ(state_, kStarted);
@@ -151,22 +151,22 @@
   int32 frames_to_end = buffer_->length() - read_index_;
   int32 channel_count = static_cast<int32>(audio_bus->channels());
 
-  std::unique_ptr<ShellAudioBus> result;
+  std::unique_ptr<AudioBus> result;
 
   if (!interleaved_resampler_) {
     int32 audio_bus_frames = std::min(number_of_frames, frames_to_end);
     if (sample_type == kSampleTypeInt16) {
-      result.reset(new ShellAudioBus(
-          channel_count, audio_bus_frames,
-          reinterpret_cast<int16*>(audio_bus->interleaved_data()) +
-              read_index_ * channel_count));
+      result.reset(
+          new AudioBus(channel_count, audio_bus_frames,
+                       reinterpret_cast<int16*>(audio_bus->interleaved_data()) +
+                           read_index_ * channel_count));
     } else {
       DCHECK_EQ(sample_type, kSampleTypeFloat32);
 
-      result.reset(new ShellAudioBus(
-          channel_count, audio_bus_frames,
-          reinterpret_cast<float*>(audio_bus->interleaved_data()) +
-              read_index_ * channel_count));
+      result.reset(
+          new AudioBus(channel_count, audio_bus_frames,
+                       reinterpret_cast<float*>(audio_bus->interleaved_data()) +
+                           read_index_ * channel_count));
     }
     read_index_ += audio_bus_frames;
     return result;
@@ -219,8 +219,8 @@
     interleaved_resampler_->Resample(interleaved_output.get(),
                                      number_of_frames);
 
-    result.reset(new ShellAudioBus(channel_count, number_of_frames,
-                                   kSampleTypeInt16, kStorageTypeInterleaved));
+    result.reset(new AudioBus(channel_count, number_of_frames, kSampleTypeInt16,
+                              kStorageTypeInterleaved));
     for (int32 i = 0; i < channel_count * number_of_frames; ++i) {
       uint8* dest_ptr = result->interleaved_data() + sizeof(int16) * i;
       *reinterpret_cast<int16*>(dest_ptr) =
@@ -229,9 +229,8 @@
   } else {
     DCHECK_EQ(sample_type, kSampleTypeFloat32);
 
-    result.reset(new ShellAudioBus(channel_count, number_of_frames,
-                                   kSampleTypeFloat32,
-                                   kStorageTypeInterleaved));
+    result.reset(new AudioBus(channel_count, number_of_frames,
+                              kSampleTypeFloat32, kStorageTypeInterleaved));
     interleaved_resampler_->Resample(
         reinterpret_cast<float*>(result->interleaved_data()), number_of_frames);
   }
diff --git a/src/cobalt/audio/audio_buffer_source_node.h b/src/cobalt/audio/audio_buffer_source_node.h
index 1731aef..b01f54c 100644
--- a/src/cobalt/audio/audio_buffer_source_node.h
+++ b/src/cobalt/audio/audio_buffer_source_node.h
@@ -22,8 +22,8 @@
 #include "cobalt/audio/audio_buffer.h"
 #include "cobalt/audio/audio_node.h"
 #include "cobalt/base/tokens.h"
+#include "cobalt/media/base/audio_bus.h"
 #include "cobalt/media/base/interleaved_sinc_resampler.h"
-#include "cobalt/media/base/shell_audio_bus.h"
 #include "cobalt/script/environment_settings.h"
 
 namespace cobalt {
@@ -35,7 +35,7 @@
 //   https://www.w3.org/TR/webaudio/#AudioBufferSourceNode
 class AudioBufferSourceNode : public AudioNode {
   typedef media::InterleavedSincResampler InterleavedSincResampler;
-  typedef media::ShellAudioBus ShellAudioBus;
+  typedef media::AudioBus AudioBus;
 
  public:
   AudioBufferSourceNode(script::EnvironmentSettings* settings,
@@ -75,8 +75,9 @@
     SetAttributeEventListener(base::Tokens::ended(), event_listener);
   }
 
-  std::unique_ptr<ShellAudioBus> PassAudioBusFromSource(
-      int32 number_of_frames, SampleType sample_type, bool* finished) override;
+  std::unique_ptr<AudioBus> PassAudioBusFromSource(int32 number_of_frames,
+                                                   SampleType sample_type,
+                                                   bool* finished) override;
 
   DEFINE_WRAPPABLE_TYPE(AudioBufferSourceNode);
   void TraceMembers(script::Tracer* tracer) override;
diff --git a/src/cobalt/audio/audio_context.cc b/src/cobalt/audio/audio_context.cc
index e909b72..6c4808e 100644
--- a/src/cobalt/audio/audio_context.cc
+++ b/src/cobalt/audio/audio_context.cc
@@ -64,7 +64,7 @@
   DCHECK(main_message_loop_->BelongsToCurrentThread());
 
   return scoped_refptr<AudioBuffer>(new AudioBuffer(
-      sample_rate, std::unique_ptr<ShellAudioBus>(new ShellAudioBus(
+      sample_rate, std::unique_ptr<AudioBus>(new AudioBus(
                        num_of_channels, length, GetPreferredOutputSampleType(),
                        kStorageTypeInterleaved))));
 }
@@ -151,7 +151,7 @@
 // Success callback and error callback should be scheduled to run on the main
 // thread's event loop.
 void AudioContext::DecodeFinish(int callback_id, float sample_rate,
-                                std::unique_ptr<ShellAudioBus> audio_bus) {
+                                std::unique_ptr<AudioBus> audio_bus) {
   if (!main_message_loop_->BelongsToCurrentThread()) {
     main_message_loop_->PostTask(
         FROM_HERE,
diff --git a/src/cobalt/audio/audio_context.h b/src/cobalt/audio/audio_context.h
index a4667bf..536b2bd 100644
--- a/src/cobalt/audio/audio_context.h
+++ b/src/cobalt/audio/audio_context.h
@@ -190,7 +190,7 @@
 
   void DecodeAudioDataInternal(std::unique_ptr<DecodeCallbackInfo> info);
   void DecodeFinish(int callback_id, float sample_rate,
-                    std::unique_ptr<ShellAudioBus> audio_bus);
+                    std::unique_ptr<AudioBus> audio_bus);
 
   script::GlobalEnvironment* global_environment_;
 
diff --git a/src/cobalt/audio/audio_destination_node.cc b/src/cobalt/audio/audio_destination_node.cc
index 4107070..e35f1f9 100644
--- a/src/cobalt/audio/audio_destination_node.cc
+++ b/src/cobalt/audio/audio_destination_node.cc
@@ -64,8 +64,7 @@
   audio_device_to_delete_ = NULL;
 }
 
-void AudioDestinationNode::FillAudioBus(bool all_consumed,
-                                        ShellAudioBus* audio_bus,
+void AudioDestinationNode::FillAudioBus(bool all_consumed, AudioBus* audio_bus,
                                         bool* silence) {
   // This is called on Audio thread.
   AudioLock::AutoLock lock(audio_lock());
diff --git a/src/cobalt/audio/audio_destination_node.h b/src/cobalt/audio/audio_destination_node.h
index 8744034..b758d92 100644
--- a/src/cobalt/audio/audio_destination_node.h
+++ b/src/cobalt/audio/audio_destination_node.h
@@ -22,7 +22,7 @@
 #include "cobalt/audio/audio_device.h"
 #include "cobalt/audio/audio_helpers.h"
 #include "cobalt/audio/audio_node.h"
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 #include "cobalt/script/environment_settings.h"
 
 namespace cobalt {
@@ -37,7 +37,7 @@
 //   https://www.w3.org/TR/webaudio/#AudioDestinationNode
 class AudioDestinationNode : public AudioNode,
                              public AudioDevice::RenderCallback {
-  typedef media::ShellAudioBus ShellAudioBus;
+  typedef media::AudioBus AudioBus;
 
  public:
   AudioDestinationNode(script::EnvironmentSettings* settings,
@@ -51,14 +51,15 @@
 
   // From AudioNode.
   void OnInputNodeConnected() override;
-  std::unique_ptr<ShellAudioBus> PassAudioBusFromSource(
-      int32 number_of_frames, SampleType sample_type, bool* finished) override {
+  std::unique_ptr<AudioBus> PassAudioBusFromSource(int32 number_of_frames,
+                                                   SampleType sample_type,
+                                                   bool* finished) override {
     NOTREACHED();
-    return std::unique_ptr<ShellAudioBus>();
+    return std::unique_ptr<AudioBus>();
   }
 
   // From AudioDevice::RenderCallback.
-  void FillAudioBus(bool all_consumed, ShellAudioBus* audio_bus,
+  void FillAudioBus(bool all_consumed, AudioBus* audio_bus,
                     bool* silence) override;
 
   DEFINE_WRAPPABLE_TYPE(AudioDestinationNode);
diff --git a/src/cobalt/audio/audio_device.cc b/src/cobalt/audio/audio_device.cc
index d253f51..b177d07 100644
--- a/src/cobalt/audio/audio_device.cc
+++ b/src/cobalt/audio/audio_device.cc
@@ -27,7 +27,7 @@
 namespace cobalt {
 namespace audio {
 
-typedef media::ShellAudioBus ShellAudioBus;
+typedef media::AudioBus AudioBus;
 
 namespace {
 const int kRenderBufferSizeFrames = 1024;
@@ -69,7 +69,7 @@
   // The |render_callback_| returns audio data in planar form.  So we read it
   // into |input_audio_bus_| and convert it into interleaved form and store in
   // |output_frame_buffer_|.
-  ShellAudioBus input_audio_bus_;
+  AudioBus input_audio_bus_;
 
   std::unique_ptr<uint8[]> output_frame_buffer_;
 
@@ -102,7 +102,7 @@
 #endif  // SB_API_VERSION >= 11
       input_audio_bus_(static_cast<size_t>(number_of_channels),
                        static_cast<size_t>(kRenderBufferSizeFrames),
-                       GetPreferredOutputSampleType(), ShellAudioBus::kPlanar),
+                       GetPreferredOutputSampleType(), AudioBus::kPlanar),
       output_frame_buffer_(
           new uint8[frames_per_channel_ * number_of_channels_ *
                     GetStarboardSampleTypeSize(output_sample_type_)]) {
@@ -226,8 +226,8 @@
     for (size_t channel = 0; channel < input_audio_bus_.channels(); ++channel) {
       *output_buffer = ConvertSample<InputType, OutputType>(
           input_audio_bus_
-              .GetSampleForType<InputType, media::ShellAudioBus::kPlanar>(
-                  channel, frame));
+              .GetSampleForType<InputType, media::AudioBus::kPlanar>(channel,
+                                                                     frame));
       ++output_buffer;
     }
   }
@@ -237,7 +237,7 @@
   TRACE_EVENT0("cobalt::audio", "AudioDevice::Impl::FillOutputAudioBus()");
 
   const bool is_input_int16 =
-      input_audio_bus_.sample_type() == media::ShellAudioBus::kInt16;
+      input_audio_bus_.sample_type() == media::AudioBus::kInt16;
   const bool is_output_int16 =
       output_sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated;
 
diff --git a/src/cobalt/audio/audio_device.h b/src/cobalt/audio/audio_device.h
index b7974a7..1ccc7f9 100644
--- a/src/cobalt/audio/audio_device.h
+++ b/src/cobalt/audio/audio_device.h
@@ -19,7 +19,7 @@
 #include <vector>
 
 #include "base/basictypes.h"
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 
 namespace cobalt {
 namespace audio {
@@ -28,7 +28,7 @@
  public:
   class RenderCallback {
    public:
-    typedef media::ShellAudioBus ShellAudioBus;
+    typedef media::AudioBus AudioBus;
 
     // |all_consumed| will be set to true if all audio frames has been consumed.
     // This gives the AudioDestinationNode a chance to decide if the AudioDevice
@@ -38,7 +38,7 @@
     // |silence| will be set to true before calling if |audio_buffer| contains
     // only silence samples, it will be set to |false| otherwise.  It will be
     // set to false on return if |audio_buffer| has been modified.
-    virtual void FillAudioBus(bool all_consumed, ShellAudioBus* audio_buffer,
+    virtual void FillAudioBus(bool all_consumed, AudioBus* audio_buffer,
                               bool* silence) = 0;
 
    protected:
diff --git a/src/cobalt/audio/audio_file_reader.h b/src/cobalt/audio/audio_file_reader.h
index f53eae4..6a20209 100644
--- a/src/cobalt/audio/audio_file_reader.h
+++ b/src/cobalt/audio/audio_file_reader.h
@@ -35,7 +35,7 @@
   virtual int32 number_of_channels() const = 0;
   virtual SampleType sample_type() const = 0;
 
-  virtual std::unique_ptr<ShellAudioBus> ResetAndReturnAudioBus() = 0;
+  virtual std::unique_ptr<AudioBus> ResetAndReturnAudioBus() = 0;
 };
 
 }  // namespace audio
diff --git a/src/cobalt/audio/audio_file_reader_wav.cc b/src/cobalt/audio/audio_file_reader_wav.cc
index 94c0bf9..f92b8bd 100644
--- a/src/cobalt/audio/audio_file_reader_wav.cc
+++ b/src/cobalt/audio/audio_file_reader_wav.cc
@@ -192,9 +192,8 @@
       static_cast<int32>(size / (bytes_per_src_sample * number_of_channels_));
 
   // We store audio samples in the current platform's preferred format.
-  audio_bus_.reset(new ShellAudioBus(number_of_channels_, number_of_frames_,
-                                     sample_type_,
-                                     ShellAudioBus::kInterleaved));
+  audio_bus_.reset(new AudioBus(number_of_channels_, number_of_frames_,
+                                sample_type_, AudioBus::kInterleaved));
 
 // Both the source data and the destination data are stored in interleaved.
 #if SB_IS(LITTLE_ENDIAN)
diff --git a/src/cobalt/audio/audio_file_reader_wav.h b/src/cobalt/audio/audio_file_reader_wav.h
index 460f268..bf3b95a 100644
--- a/src/cobalt/audio/audio_file_reader_wav.h
+++ b/src/cobalt/audio/audio_file_reader_wav.h
@@ -36,7 +36,7 @@
   int32 number_of_channels() const override { return number_of_channels_; }
   SampleType sample_type() const override { return sample_type_; }
 
-  std::unique_ptr<ShellAudioBus> ResetAndReturnAudioBus() override {
+  std::unique_ptr<AudioBus> ResetAndReturnAudioBus() override {
     return std::move(audio_bus_);
   }
 
@@ -52,7 +52,7 @@
 
   bool is_valid() { return audio_bus_ != NULL; }
 
-  std::unique_ptr<ShellAudioBus> audio_bus_;
+  std::unique_ptr<AudioBus> audio_bus_;
   float sample_rate_;
   int32 number_of_frames_;
   int32 number_of_channels_;
diff --git a/src/cobalt/audio/audio_helpers.h b/src/cobalt/audio/audio_helpers.h
index b4dd6f9..dc98086 100644
--- a/src/cobalt/audio/audio_helpers.h
+++ b/src/cobalt/audio/audio_helpers.h
@@ -15,7 +15,7 @@
 #ifndef COBALT_AUDIO_AUDIO_HELPERS_H_
 #define COBALT_AUDIO_AUDIO_HELPERS_H_
 
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 
 #if defined(OS_STARBOARD)
 #include "starboard/audio_sink.h"
@@ -25,13 +25,13 @@
 namespace cobalt {
 namespace audio {
 
-typedef media::ShellAudioBus ShellAudioBus;
-typedef media::ShellAudioBus::SampleType SampleType;
-typedef media::ShellAudioBus::StorageType StorageType;
-const SampleType kSampleTypeInt16 = media::ShellAudioBus::kInt16;
-const SampleType kSampleTypeFloat32 = media::ShellAudioBus::kFloat32;
-const StorageType kStorageTypeInterleaved = media::ShellAudioBus::kInterleaved;
-const StorageType kStorageTypePlanar = media::ShellAudioBus::kPlanar;
+typedef media::AudioBus AudioBus;
+typedef media::AudioBus::SampleType SampleType;
+typedef media::AudioBus::StorageType StorageType;
+const SampleType kSampleTypeInt16 = media::AudioBus::kInt16;
+const SampleType kSampleTypeFloat32 = media::AudioBus::kFloat32;
+const StorageType kStorageTypeInterleaved = media::AudioBus::kInterleaved;
+const StorageType kStorageTypePlanar = media::AudioBus::kPlanar;
 
 const float kMaxInt16AsFloat32 = 32767.0f;
 
@@ -52,7 +52,7 @@
 #endif
 
 // Get the size in bytes of an internal sample type, which is an alias for
-// media::ShellAudioBus::SampleType.
+// media::AudioBus::SampleType.
 inline size_t GetSampleTypeSize(SampleType sample_type) {
   switch (sample_type) {
     case kSampleTypeInt16:
diff --git a/src/cobalt/audio/audio_node.h b/src/cobalt/audio/audio_node.h
index 1e9c1dd..925356c 100644
--- a/src/cobalt/audio/audio_node.h
+++ b/src/cobalt/audio/audio_node.h
@@ -27,7 +27,7 @@
 #include "cobalt/audio/audio_node_output.h"
 #include "cobalt/dom/dom_exception.h"
 #include "cobalt/dom/event_target.h"
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 #include "cobalt/script/environment_settings.h"
 
 namespace cobalt {
@@ -49,7 +49,7 @@
 // (if it has any).
 //   https://www.w3.org/TR/webaudio/#AudioNode-section
 class AudioNode : public dom::EventTarget {
-  typedef media::ShellAudioBus ShellAudioBus;
+  typedef media::AudioBus AudioBus;
 
  public:
   AudioNode(script::EnvironmentSettings* settings, AudioContext* context);
@@ -108,7 +108,7 @@
   // Called when a new input node has been connected.
   virtual void OnInputNodeConnected() {}
 
-  virtual std::unique_ptr<ShellAudioBus> PassAudioBusFromSource(
+  virtual std::unique_ptr<AudioBus> PassAudioBusFromSource(
       int32 number_of_frames, SampleType sample_type, bool* finished) = 0;
 
   AudioLock* audio_lock() const { return audio_lock_.get(); }
diff --git a/src/cobalt/audio/audio_node_input.cc b/src/cobalt/audio/audio_node_input.cc
index f8f5503..977d67e 100644
--- a/src/cobalt/audio/audio_node_input.cc
+++ b/src/cobalt/audio/audio_node_input.cc
@@ -26,12 +26,12 @@
 
 namespace {
 
-typedef media::ShellAudioBus ShellAudioBus;
+typedef media::AudioBus AudioBus;
 
 void MixAudioBufferBasedOnInterpretation(
     const float* speaker, const float* discrete,
-    const AudioNodeChannelInterpretation& interpretation, ShellAudioBus* source,
-    ShellAudioBus* output_audio_data) {
+    const AudioNodeChannelInterpretation& interpretation, AudioBus* source,
+    AudioBus* output_audio_data) {
   const float* kMatrix =
       interpretation == kAudioNodeChannelInterpretationSpeakers ? speaker
                                                                 : discrete;
@@ -49,7 +49,7 @@
 // Up down mix equations for mono, stereo, quad, 5.1:
 //   https://www.w3.org/TR/webaudio/#ChannelLayouts
 void MixAudioBuffer(const AudioNodeChannelInterpretation& interpretation,
-                    ShellAudioBus* source, ShellAudioBus* output_audio_data) {
+                    AudioBus* source, AudioBus* output_audio_data) {
   DCHECK_GT(source->channels(), 0u);
   DCHECK_GT(output_audio_data->channels(), 0u);
   DCHECK(interpretation == kAudioNodeChannelInterpretationSpeakers ||
@@ -224,8 +224,8 @@
   }
 }
 
-void AudioNodeInput::FillAudioBus(ShellAudioBus* output_audio_bus,
-                                  bool* silence, bool* all_finished) {
+void AudioNodeInput::FillAudioBus(AudioBus* output_audio_bus, bool* silence,
+                                  bool* all_finished) {
   DCHECK(silence);
   DCHECK(all_finished);
 
@@ -247,7 +247,7 @@
   for (std::set<AudioNodeOutput*>::iterator iter = outputs_.begin();
        iter != outputs_.end(); ++iter) {
     bool finished = false;
-    std::unique_ptr<ShellAudioBus> audio_bus = (*iter)->PassAudioBusFromSource(
+    std::unique_ptr<AudioBus> audio_bus = (*iter)->PassAudioBusFromSource(
         static_cast<int32>(output_audio_bus->frames()),
         output_audio_bus->sample_type(), &finished);
     *all_finished &= finished;
diff --git a/src/cobalt/audio/audio_node_input.h b/src/cobalt/audio/audio_node_input.h
index 74aca8b..1c5bee1 100644
--- a/src/cobalt/audio/audio_node_input.h
+++ b/src/cobalt/audio/audio_node_input.h
@@ -20,7 +20,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "cobalt/audio/audio_buffer.h"
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 
 namespace cobalt {
 namespace audio {
@@ -36,7 +36,7 @@
 // number can change depending on the connection(s) made to the input. If the
 // input has no connections, then it has one channel which is silent.
 class AudioNodeInput : public base::RefCountedThreadSafe<AudioNodeInput> {
-  typedef media::ShellAudioBus ShellAudioBus;
+  typedef media::AudioBus AudioBus;
 
  public:
   explicit AudioNodeInput(AudioNode* owner_node) : owner_node_(owner_node) {}
@@ -50,8 +50,7 @@
   // For each input, an AudioNode performs a mixing of all connections to that
   // input. FillAudioBus() performs that action. In the case of multiple
   // connections, it sums the result into |audio_bus|.
-  void FillAudioBus(ShellAudioBus* audio_bus, bool* silence,
-                    bool* all_finished);
+  void FillAudioBus(AudioBus* audio_bus, bool* silence, bool* all_finished);
 
  private:
   AudioNode* const owner_node_;
diff --git a/src/cobalt/audio/audio_node_input_output_test.cc b/src/cobalt/audio/audio_node_input_output_test.cc
index 970254a..41a2ca2 100644
--- a/src/cobalt/audio/audio_node_input_output_test.cc
+++ b/src/cobalt/audio/audio_node_input_output_test.cc
@@ -27,18 +27,18 @@
 #include "cobalt/script/typed_arrays.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-// TODO: Consolidate ShellAudioBus creation code
+// TODO: Consolidate AudioBus creation code
 
 namespace cobalt {
 namespace audio {
 
-typedef media::ShellAudioBus ShellAudioBus;
+typedef media::AudioBus AudioBus;
 
 constexpr int kRenderBufferSizeFrames = 32;
 
 class AudioDestinationNodeMock : public AudioNode,
                                  public AudioDevice::RenderCallback {
-  typedef media::ShellAudioBus ShellAudioBus;
+  typedef media::AudioBus AudioBus;
 
  public:
   AudioDestinationNodeMock(script::EnvironmentSettings* settings,
@@ -50,14 +50,15 @@
   }
 
   // From AudioNode.
-  std::unique_ptr<ShellAudioBus> PassAudioBusFromSource(
-      int32 number_of_frames, SampleType sample_type, bool* finished) override {
+  std::unique_ptr<AudioBus> PassAudioBusFromSource(int32 number_of_frames,
+                                                   SampleType sample_type,
+                                                   bool* finished) override {
     NOTREACHED();
-    return std::unique_ptr<ShellAudioBus>();
+    return std::unique_ptr<AudioBus>();
   }
 
   // From AudioDevice::RenderCallback.
-  void FillAudioBus(bool all_consumed, ShellAudioBus* audio_bus,
+  void FillAudioBus(bool all_consumed, AudioBus* audio_bus,
                     bool* silence) override {
     AudioLock::AutoLock lock(audio_lock());
 
@@ -68,9 +69,9 @@
 };
 
 void FillAudioBusFromOneSource(
-    std::unique_ptr<ShellAudioBus> src_data,
-    const AudioNodeChannelInterpretation& interpretation,
-    ShellAudioBus* audio_bus, bool* silence) {
+    std::unique_ptr<AudioBus> src_data,
+    const AudioNodeChannelInterpretation& interpretation, AudioBus* audio_bus,
+    bool* silence) {
   dom::testing::StubEnvironmentSettings environment_settings;
 
   scoped_refptr<AudioContext> audio_context(
@@ -139,11 +140,11 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -180,12 +181,12 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -219,12 +220,12 @@
     src_data_in_float[i] = 50.0f;
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -254,12 +255,12 @@
     src_data_in_float[i] = 50.0f;
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -292,12 +293,12 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -334,12 +335,12 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -376,12 +377,12 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -418,12 +419,12 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -460,12 +461,12 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -498,12 +499,12 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -536,12 +537,12 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -574,12 +575,12 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -612,12 +613,12 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -650,12 +651,12 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data(
-      new ShellAudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
+  std::unique_ptr<AudioBus> src_data(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames, src_data_in_float));
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   FillAudioBusFromOneSource(std::move(src_data), kInterpretation,
@@ -691,8 +692,8 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data_1(new ShellAudioBus(
-      kNumOfSrcChannels, kNumOfFrames_1, src_data_in_float_1));
+  std::unique_ptr<AudioBus> src_data_1(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames_1, src_data_in_float_1));
   scoped_refptr<AudioBufferSourceNode> source_1(
       audio_context->CreateBufferSource(environment_settings()));
   scoped_refptr<AudioBuffer> buffer_1(
@@ -709,8 +710,8 @@
     }
   }
 
-  std::unique_ptr<ShellAudioBus> src_data_2(new ShellAudioBus(
-      kNumOfSrcChannels, kNumOfFrames_2, src_data_in_float_2));
+  std::unique_ptr<AudioBus> src_data_2(
+      new AudioBus(kNumOfSrcChannels, kNumOfFrames_2, src_data_in_float_2));
   scoped_refptr<AudioBufferSourceNode> source_2(
       audio_context->CreateBufferSource(environment_settings()));
   scoped_refptr<AudioBuffer> buffer_2(
@@ -726,9 +727,9 @@
   source_1->Start(0, 0, NULL);
   source_2->Start(0, 0, NULL);
 
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfDestChannels, kRenderBufferSizeFrames,
+                   AudioBus::kFloat32, AudioBus::kPlanar));
   audio_bus->ZeroAllFrames();
   bool silence = true;
   destination->FillAudioBus(true, audio_bus.get(), &silence);
@@ -788,9 +789,9 @@
 
   scoped_refptr<AudioContext> audio_context(
       new AudioContext(environment_settings()));
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfChannels, kRenderBufferSizeFrames, AudioBus::kFloat32,
+                   AudioBus::kPlanar));
   audio_bus->ZeroAllFrames();
   scoped_refptr<AudioBuffer> buffer(
       new AudioBuffer(audio_context->sample_rate(), std::move(audio_bus)));
@@ -828,9 +829,9 @@
 
   scoped_refptr<AudioContext> audio_context(
       new AudioContext(environment_settings()));
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfChannels, kRenderBufferSizeFrames, AudioBus::kFloat32,
+                   AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
   scoped_refptr<AudioBuffer> buffer(
       new AudioBuffer(audio_context->sample_rate(), std::move(audio_bus)));
@@ -868,9 +869,9 @@
 
   scoped_refptr<AudioContext> audio_context(
       new AudioContext(environment_settings()));
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kInt16, ShellAudioBus::kPlanar));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfChannels, kRenderBufferSizeFrames, AudioBus::kInt16,
+                   AudioBus::kPlanar));
   audio_bus->ZeroAllFrames();
 
   scoped_refptr<AudioBuffer> buffer(
@@ -911,9 +912,9 @@
 
   scoped_refptr<AudioContext> audio_context(
       new AudioContext(environment_settings()));
-  std::unique_ptr<ShellAudioBus> audio_bus(
-      new ShellAudioBus(kNumOfChannels, kRenderBufferSizeFrames,
-                        ShellAudioBus::kInt16, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> audio_bus(
+      new AudioBus(kNumOfChannels, kRenderBufferSizeFrames, AudioBus::kInt16,
+                   AudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
 
   scoped_refptr<AudioBuffer> buffer(
@@ -966,9 +967,9 @@
 
   for (size_t buffer_sample_rate : kBufferSampleRateArr) {
     for (SampleType sample_type : kSampleTypeArr) {
-      std::unique_ptr<ShellAudioBus> src_data(
-          new ShellAudioBus(kNumOfSrcChannels, kNumOfSrcFrames, sample_type,
-                            ShellAudioBus::kInterleaved));
+      std::unique_ptr<AudioBus> src_data(
+          new AudioBus(kNumOfSrcChannels, kNumOfSrcFrames, sample_type,
+                       AudioBus::kInterleaved));
       src_data->ZeroAllFrames();
       scoped_refptr<AudioBuffer> buffer(
           new AudioBuffer(buffer_sample_rate, std::move(src_data)));
@@ -988,9 +989,9 @@
       source->Connect(destination, 0, 0, NULL);
       source->Start(0, 0, NULL);
 
-      std::unique_ptr<ShellAudioBus> audio_bus(
-          new ShellAudioBus(kNumOfDestChannels, kNumOfDestFrames, sample_type,
-                            ShellAudioBus::kInterleaved));
+      std::unique_ptr<AudioBus> audio_bus(
+          new AudioBus(kNumOfDestChannels, kNumOfDestFrames, sample_type,
+                       AudioBus::kInterleaved));
       audio_bus->ZeroAllFrames();
       bool silence = true;
       destination->FillAudioBus(true, audio_bus.get(), &silence);
diff --git a/src/cobalt/audio/audio_node_output.cc b/src/cobalt/audio/audio_node_output.cc
index 8ab0f43..ed6c507 100644
--- a/src/cobalt/audio/audio_node_output.cc
+++ b/src/cobalt/audio/audio_node_output.cc
@@ -24,7 +24,7 @@
 namespace cobalt {
 namespace audio {
 
-typedef media::ShellAudioBus ShellAudioBus;
+typedef media::AudioBus AudioBus;
 
 AudioNodeOutput::~AudioNodeOutput() {
   owner_node_->audio_lock()->AssertLocked();
@@ -57,7 +57,7 @@
   }
 }
 
-std::unique_ptr<ShellAudioBus> AudioNodeOutput::PassAudioBusFromSource(
+std::unique_ptr<AudioBus> AudioNodeOutput::PassAudioBusFromSource(
     int32 number_of_frames, SampleType sample_type, bool* finished) {
   // This is called by Audio thread.
   owner_node_->audio_lock()->AssertLocked();
diff --git a/src/cobalt/audio/audio_node_output.h b/src/cobalt/audio/audio_node_output.h
index 4122390..1774ce4 100644
--- a/src/cobalt/audio/audio_node_output.h
+++ b/src/cobalt/audio/audio_node_output.h
@@ -22,7 +22,7 @@
 #include "base/memory/ref_counted.h"
 #include "cobalt/audio/audio_buffer.h"
 #include "cobalt/audio/audio_helpers.h"
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 
 namespace cobalt {
 namespace audio {
@@ -33,7 +33,7 @@
 // This represents the output coming out of the AudioNode.
 // It may be connected to one or more AudioNodeInputs.
 class AudioNodeOutput : public base::RefCountedThreadSafe<AudioNodeOutput> {
-  typedef media::ShellAudioBus ShellAudioBus;
+  typedef media::AudioBus AudioBus;
 
  public:
   explicit AudioNodeOutput(AudioNode* owner_node) : owner_node_(owner_node) {}
@@ -44,9 +44,9 @@
 
   void DisconnectAll();
 
-  std::unique_ptr<ShellAudioBus> PassAudioBusFromSource(int32 number_of_frames,
-                                                        SampleType sample_type,
-                                                        bool* finished);
+  std::unique_ptr<AudioBus> PassAudioBusFromSource(int32 number_of_frames,
+                                                   SampleType sample_type,
+                                                   bool* finished);
 
  private:
   AudioNode* const owner_node_;
diff --git a/src/cobalt/base/localized_strings.cc b/src/cobalt/base/localized_strings.cc
index 1c590a4..3388ca7 100644
--- a/src/cobalt/base/localized_strings.cc
+++ b/src/cobalt/base/localized_strings.cc
@@ -17,8 +17,8 @@
 #include <algorithm>
 
 #include "base/logging.h"
+#include "starboard/common/file.h"
 #include "starboard/common/log.h"
-#include "starboard/file.h"
 #include "starboard/system.h"
 #include "starboard/types.h"
 
diff --git a/src/cobalt/base/path_provider.cc b/src/cobalt/base/path_provider.cc
index ae07949..6dfbbe7 100644
--- a/src/cobalt/base/path_provider.cc
+++ b/src/cobalt/base/path_provider.cc
@@ -31,6 +31,9 @@
     base::FilePath directory(path.get());
     if (base::PathExists(directory) || base::CreateDirectory(directory)) {
       return directory;
+    } else {
+      DLOG(ERROR) << "Attempt to open or create this path failed: " +
+                         directory.value();
     }
   }
   return base::FilePath();
diff --git a/src/cobalt/black_box_tests/black_box_tests.py b/src/cobalt/black_box_tests/black_box_tests.py
index 220c342..60f5596 100644
--- a/src/cobalt/black_box_tests/black_box_tests.py
+++ b/src/cobalt/black_box_tests/black_box_tests.py
@@ -30,6 +30,7 @@
 from starboard.tools import abstract_launcher
 from starboard.tools import build
 from starboard.tools import command_line
+from starboard.tools import log_level
 
 _DISABLED_BLACKBOXTEST_CONFIGS = [
     'android-arm/devel',
@@ -294,13 +295,7 @@
             'specified, all IPs will be allowed to connect.'))
   args, _ = parser.parse_known_args()
 
-  # This format matches Cobalt's console log format.
-  logging_format = ('[%(process)d:%(asctime)s.%(msecs)03d...:'
-                    '%(levelname)s:%(filename)s(%(lineno)s)] %(message)s')
-  logging.basicConfig(
-      level=logging.INFO, format=logging_format, datefmt='%m%d/%H%M%S')
-  if args.verbose:
-    logging.getLogger().setLevel(logging.DEBUG)
+  log_level.InitializeLogging(args)
 
   test_object = BlackBoxTests(args.server_binding_address, args.proxy_address,
                               args.proxy_port, args.test_name,
diff --git a/src/cobalt/black_box_tests/testdata/pointer_test.html b/src/cobalt/black_box_tests/testdata/pointer_test.html
index 1f1b275..340e5da 100644
--- a/src/cobalt/black_box_tests/testdata/pointer_test.html
+++ b/src/cobalt/black_box_tests/testdata/pointer_test.html
@@ -98,14 +98,10 @@
         ['pointerover', 'top', 'bubbling'],
         ['pointerover', 'outer', 'bubbling'],
         ['pointerenter', 'top_two', 'at target'],
-        ['pointerenter', 'top', 'at target'],
-        ['pointerenter', 'outer', 'at target'],
         ['mouseover', 'top_two', 'at target'],
         ['mouseover', 'top', 'bubbling'],
         ['mouseover', 'outer', 'bubbling'],
         ['mouseenter', 'top_two', 'at target'],
-        ['mouseenter', 'top', 'at target'],
-        ['mouseenter', 'outer', 'at target'],
         // actions.move_to_element_with_offset(top_two, 10, 10).pause(_SLEEP_AFTER_MOVE_TIME)
         ['pointermove', 'top_two', 'at target'],
         ['pointermove', 'top', 'bubbling'],
@@ -139,14 +135,10 @@
         ['pointerover', 'top', 'bubbling'],
         ['pointerover', 'outer', 'bubbling'],
         ['pointerenter', 'top_one', 'at target'],
-        ['pointerenter', 'top', 'at target'],
-        ['pointerenter', 'outer', 'at target'],
         ['mouseover', 'top_one', 'at target'],
         ['mouseover', 'top', 'bubbling'],
         ['mouseover', 'outer', 'bubbling'],
         ['mouseenter', 'top_one', 'at target'],
-        ['mouseenter', 'top', 'at target'],
-        ['mouseenter', 'outer', 'at target'],
         // actions.click(top_three)
         ['pointerout', 'top_one', 'at target'],
         ['pointerout', 'top', 'bubbling'],
@@ -166,14 +158,10 @@
         ['pointerover', 'top', 'bubbling'],
         ['pointerover', 'outer', 'bubbling'],
         ['pointerenter', 'top_three', 'at target'],
-        ['pointerenter', 'top', 'at target'],
-        ['pointerenter', 'outer', 'at target'],
         ['mouseover', 'top_three', 'at target'],
         ['mouseover', 'top', 'bubbling'],
         ['mouseover', 'outer', 'bubbling'],
         ['mouseenter', 'top_three', 'at target'],
-        ['mouseenter', 'top', 'at target'],
-        ['mouseenter', 'outer', 'at target'],
         ['pointerdown', 'top_three', 'at target'],
         ['pointerdown', 'top', 'bubbling'],
         ['pointerdown', 'outer', 'bubbling'],
@@ -208,14 +196,10 @@
         ['pointerover', 'top', 'bubbling'],
         ['pointerover', 'outer', 'bubbling'],
         ['pointerenter', 'top_four', 'at target'],
-        ['pointerenter', 'top', 'at target'],
-        ['pointerenter', 'outer', 'at target'],
         ['mouseover', 'top_four', 'at target'],
         ['mouseover', 'top', 'bubbling'],
         ['mouseover', 'outer', 'bubbling'],
         ['mouseenter', 'top_four', 'at target'],
-        ['mouseenter', 'top', 'at target'],
-        ['mouseenter', 'outer', 'at target'],
         ['pointerdown', 'top_four', 'at target'],
         ['pointerdown', 'top', 'bubbling'],
         ['pointerdown', 'outer', 'bubbling'],
@@ -257,14 +241,10 @@
         ['pointerover', 'top', 'bubbling'],
         ['pointerover', 'outer', 'bubbling'],
         ['pointerenter', 'top_six', 'at target'],
-        ['pointerenter', 'top', 'at target'],
-        ['pointerenter', 'outer', 'at target'],
         ['mouseover', 'top_six', 'at target'],
         ['mouseover', 'top', 'bubbling'],
         ['mouseover', 'outer', 'bubbling'],
         ['mouseenter', 'top_six', 'at target'],
-        ['mouseenter', 'top', 'at target'],
-        ['mouseenter', 'outer', 'at target'],
         // actions.move_to_element(bottom_six).pause(_SLEEP_AFTER_MOVE_TIME)
         ['pointerout', 'top_six', 'at target'],
         ['pointerout', 'top', 'bubbling'],
@@ -287,13 +267,11 @@
         ['pointerover', 'outer', 'bubbling'],
         ['pointerenter', 'bottom_six', 'at target'],
         ['pointerenter', 'bottom', 'at target'],
-        ['pointerenter', 'outer', 'at target'],
         ['mouseover', 'bottom_six', 'at target'],
         ['mouseover', 'bottom', 'bubbling'],
         ['mouseover', 'outer', 'bubbling'],
         ['mouseenter', 'bottom_six', 'at target'],
         ['mouseenter', 'bottom', 'at target'],
-        ['mouseenter', 'outer', 'at target'],
         // actions.click(bottom_five)
         ['pointerout', 'bottom_six', 'at target'],
         ['pointerout', 'bottom', 'bubbling'],
@@ -313,14 +291,10 @@
         ['pointerover', 'bottom', 'bubbling'],
         ['pointerover', 'outer', 'bubbling'],
         ['pointerenter', 'bottom_five', 'at target'],
-        ['pointerenter', 'bottom', 'at target'],
-        ['pointerenter', 'outer', 'at target'],
         ['mouseover', 'bottom_five', 'at target'],
         ['mouseover', 'bottom', 'bubbling'],
         ['mouseover', 'outer', 'bubbling'],
         ['mouseenter', 'bottom_five', 'at target'],
-        ['mouseenter', 'bottom', 'at target'],
-        ['mouseenter', 'outer', 'at target'],
         ['pointerdown', 'bottom_five', 'at target'],
         ['pointerdown', 'bottom', 'bubbling'],
         ['pointerdown', 'outer', 'bubbling'],
@@ -349,14 +323,10 @@
         ['pointerover', 'bottom', 'bubbling'],
         ['pointerover', 'outer', 'bubbling'],
         ['pointerenter', 'bottom_four', 'at target'],
-        ['pointerenter', 'bottom', 'at target'],
-        ['pointerenter', 'outer', 'at target'],
         ['mouseover', 'bottom_four', 'at target'],
         ['mouseover', 'bottom', 'bubbling'],
         ['mouseover', 'outer', 'bubbling'],
         ['mouseenter', 'bottom_four', 'at target'],
-        ['mouseenter', 'bottom', 'at target'],
-        ['mouseenter', 'outer', 'at target'],
         ['pointerdown', 'bottom_four', 'at target'],
         ['pointerdown', 'bottom', 'bubbling'],
         ['pointerdown', 'outer', 'bubbling'],
@@ -383,12 +353,8 @@
         ['mousemove', 'bottom_two', 'at target'],
         ['pointerover', 'bottom_two', 'at target'],
         ['pointerenter', 'bottom_two', 'at target'],
-        ['pointerenter', 'bottom', 'at target'],
-        ['pointerenter', 'outer', 'at target'],
         ['mouseover', 'bottom_two', 'at target'],
         ['mouseenter', 'bottom_two', 'at target'],
-        ['mouseenter', 'bottom', 'at target'],
-        ['mouseenter', 'outer', 'at target'],
         // actions.move_to_element(bottom_one).pause(_SLEEP_AFTER_MOVE_TIME)
         ['pointerout', 'bottom_two', 'at target'],
         ['pointerleave', 'bottom_two', 'at target'],
@@ -398,12 +364,8 @@
         ['mousemove', 'bottom_one', 'at target'],
         ['pointerover', 'bottom_one', 'at target'],
         ['pointerenter', 'bottom_one', 'at target'],
-        ['pointerenter', 'bottom', 'at target'],
-        ['pointerenter', 'outer', 'at target'],
         ['mouseover', 'bottom_one', 'at target'],
         ['mouseenter', 'bottom_one', 'at target'],
-        ['mouseenter', 'bottom', 'at target'],
-        ['mouseenter', 'outer', 'at target'],
         // find_element_by_id(runner, 'end').click()
         ['pointerout', 'bottom_one', 'at target'],
         ['pointerleave', 'bottom_one', 'at target'],
@@ -418,11 +380,9 @@
         ['pointerover', 'end', 'at target'],
         ['pointerover', 'outer', 'bubbling'],
         ['pointerenter', 'end', 'at target'],
-        ['pointerenter', 'outer', 'at target'],
         ['mouseover', 'end', 'at target'],
         ['mouseover', 'outer', 'bubbling'],
         ['mouseenter', 'end', 'at target'],
-        ['mouseenter', 'outer', 'at target'],
         ['pointerdown', 'end', 'at target'],
         ['pointerdown', 'outer', 'bubbling'],
         ['mousedown', 'end', 'at target'],
@@ -489,33 +449,72 @@
         e.target.setPointerCapture(e.pointerId);
       }
 
-      function SetHandlers(event, classname, callback) {
-        var elements = document.getElementsByClassName(classname);
+      // If the event type has value 'type', then report an error if the
+      // 'name' property on the event target already has 'value'. Otherwise,
+      // set it to the 'value'. This is used to detect erroneous boundary
+      // events (enter/leave, over/out), and up/down event sequences on an
+      // element.
+      function TrackAndVerifyTargetState(event, type, name, value) {
+        if (event.type == type) {
+          if (event.target[name] == value) {
+            console.log('ERROR: ' + type + 'event received while ' +
+                        name + ' == ' + event.target[name]);
+            assertTrue(event.target[name] != value);
+          }
+          event.target[name] = value;
+        }
+      }
+
+      function CheckState(e) {
+        // Check the target element state when the event is 'at target'.
+        if (e.eventPhase == 2)  {
+          // Verify that there is not a duplicated or missing event for enter,
+          // leave, over, out, up, or down.
+          TrackAndVerifyTargetState(e, 'mouseenter', 'mouseenter', true);
+          TrackAndVerifyTargetState(e, 'mouseleave', 'mouseenter', false);
+          TrackAndVerifyTargetState(e, 'mouseover', 'mouseover', true);
+          TrackAndVerifyTargetState(e, 'mouseout', 'mouseover', false);
+          TrackAndVerifyTargetState(e, 'mousedown', 'mousedown', true);
+          TrackAndVerifyTargetState(e, 'mouseup', 'mousedown', false);
+
+          TrackAndVerifyTargetState(e, 'pointerenter', 'pointerenter', true);
+          TrackAndVerifyTargetState(e, 'pointerleave', 'pointerenter', false);
+          TrackAndVerifyTargetState(e, 'pointerover', 'pointerover', true);
+          TrackAndVerifyTargetState(e, 'pointerout', 'pointerover', false);
+          TrackAndVerifyTargetState(e, 'pointerdown', 'pointerdown', true);
+          TrackAndVerifyTargetState(e, 'pointerup', 'pointerdown', false);
+        }
+      }
+
+      function SetHandlers(event, selector, callback) {
+        var elements = document.querySelectorAll(selector);
         for (var i = 0; i < elements.length; ++i) {
           elements[i].addEventListener(event, callback);
         }
       }
 
-      function SetAllHandlers(prefix, classname, callback) {
-        SetHandlers(prefix + 'enter', classname, callback);
-        SetHandlers(prefix + 'leave', classname, callback);
-        SetHandlers(prefix + 'over', classname, callback);
-        SetHandlers(prefix + 'out', classname, callback);
-        SetHandlers(prefix + 'down', classname, callback);
-        SetHandlers(prefix + 'up', classname, callback);
-        SetHandlers(prefix + 'move', classname, callback);
+      function SetAllHandlers(prefix, selector, callback) {
+        SetHandlers(prefix + 'enter', selector, callback);
+        SetHandlers(prefix + 'leave', selector, callback);
+        SetHandlers(prefix + 'over', selector, callback);
+        SetHandlers(prefix + 'out', selector, callback);
+        SetHandlers(prefix + 'down', selector, callback);
+        SetHandlers(prefix + 'up', selector, callback);
+        SetHandlers(prefix + 'move', selector, callback);
       }
 
       window.onload = function() {
-        SetAllHandlers('mouse', 'track', LogEvent);
-        SetAllHandlers('pointer', 'track', LogEvent);
-        SetHandlers('click', 'track', LogEvent);
-        SetAllHandlers('mouse', 'cancel', Cancel);
-        SetAllHandlers('pointer', 'cancel', Cancel);
-        SetAllHandlers('mouse', 'stop', Stop);
-        SetAllHandlers('pointer', 'stop', Stop);
-        SetHandlers('pointerdown', 'capture', Capture);
-        SetHandlers('click', 'end', EndTest);
+        SetAllHandlers('mouse', '.track', LogEvent);
+        SetAllHandlers('pointer', '.track', LogEvent);
+        SetHandlers('click', '.track', LogEvent);
+        SetAllHandlers('mouse', '.cancel', Cancel);
+        SetAllHandlers('pointer', '.cancel', Cancel);
+        SetAllHandlers('mouse', '.stop', Stop);
+        SetAllHandlers('pointer', '.stop', Stop);
+        SetHandlers('pointerdown', '.capture', Capture);
+        SetHandlers('click', '.end', EndTest);
+        SetAllHandlers('mouse', '*', CheckState);
+        SetAllHandlers('pointer', '*', CheckState);
         console.log("Setup finished");
         setupFinished();
       }
diff --git a/src/cobalt/black_box_tests/tests/pointer_test.py b/src/cobalt/black_box_tests/tests/pointer_test.py
index 706bbc5..02e9a00 100644
--- a/src/cobalt/black_box_tests/tests/pointer_test.py
+++ b/src/cobalt/black_box_tests/tests/pointer_test.py
@@ -44,8 +44,7 @@
 class PointerTest(black_box_tests.BlackBoxTestCase):
   """Tests pointer and mouse event."""
 
-  def test_simple(self):
-
+  def test_pointer_events(self):
     try:
       with ThreadedWebServer(
           binding_address=self.GetBindingAddress()) as server:
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index f0a72e0..27edd11 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -1381,3 +1381,9 @@
 
 }  // namespace browser
 }  // namespace cobalt
+
+const char* GetCobaltUserAgentString() {
+  static std::string ua = cobalt::browser::CreateUserAgentString(
+      cobalt::browser::GetUserAgentPlatformInfoFromSystem());
+  return ua.c_str();
+}
diff --git a/src/cobalt/browser/application.h b/src/cobalt/browser/application.h
index 2b93e86..6f8ed3c 100644
--- a/src/cobalt/browser/application.h
+++ b/src/cobalt/browser/application.h
@@ -231,4 +231,9 @@
 }  // namespace browser
 }  // namespace cobalt
 
+
+extern "C" {
+SB_IMPORT const char* GetCobaltUserAgentString();
+}
+
 #endif  // COBALT_BROWSER_APPLICATION_H_
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index 66b62b6..6d14fbe 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -433,7 +433,8 @@
                  base::Unretained(this)),
       network_module_, GetViewportSize(), GetResourceProvider(),
       kLayoutMaxRefreshFrequencyInHz,
-      base::Bind(&BrowserModule::CreateDebugClient, base::Unretained(this))));
+      base::Bind(&BrowserModule::CreateDebugClient, base::Unretained(this)),
+      base::Bind(&BrowserModule::OnMaybeFreeze, base::Unretained(this))));
   lifecycle_observers_.AddObserver(debug_console_.get());
 #endif  // defined(ENABLE_DEBUGGER)
 
@@ -563,17 +564,18 @@
   DestroySplashScreen(base::TimeDelta());
   if (options_.enable_splash_screen_on_reloads ||
       main_web_module_generation_ == 1) {
-    base::Optional<std::string> key = SplashScreenCache::GetKeyForStartUrl(url);
+    splash_screen_cache_->SetUrl(url);
     if (fallback_splash_screen_url_ ||
-        (key && splash_screen_cache_->IsSplashScreenCached(*key))) {
+        splash_screen_cache_->IsSplashScreenCached()) {
       splash_screen_.reset(new SplashScreen(
           application_state_,
           base::Bind(&BrowserModule::QueueOnSplashScreenRenderTreeProduced,
                      base::Unretained(this)),
           network_module_, viewport_size, GetResourceProvider(),
-          kLayoutMaxRefreshFrequencyInHz, fallback_splash_screen_url_, url,
+          kLayoutMaxRefreshFrequencyInHz, fallback_splash_screen_url_,
           splash_screen_cache_.get(),
-          base::Bind(&BrowserModule::DestroySplashScreen, weak_this_)));
+          base::Bind(&BrowserModule::DestroySplashScreen, weak_this_),
+          base::Bind(&BrowserModule::OnMaybeFreeze, base::Unretained(this))));
       lifecycle_observers_.AddObserver(splash_screen_.get());
     }
   }
@@ -629,6 +631,10 @@
   options.debugger_state = debugger_state.get();
 #endif  // ENABLE_DEBUGGER
 
+  // Pass down this callback from to Web module.
+  options_.web_module_options.maybe_freeze_callback =
+      base::Bind(&BrowserModule::OnMaybeFreeze, base::Unretained(this));
+
   web_module_.reset(new WebModule(
       url, application_state_,
       base::Bind(&BrowserModule::QueueOnRenderTreeProduced,
@@ -1466,6 +1472,7 @@
   DCHECK(application_state_ == base::kApplicationStateBlurred);
   application_state_ = base::kApplicationStateConcealed;
   ConcealInternal();
+  OnMaybeFreeze();
 }
 
 void BrowserModule::Focus() {
@@ -1795,6 +1802,35 @@
                     Unfreeze(GetResourceProvider()));
 }
 
+void BrowserModule::OnMaybeFreeze() {
+  TRACE_EVENT0("cobalt::browser", "BrowserModule::MaybeFreeze()");
+  if (base::MessageLoop::current() != self_message_loop_) {
+    self_message_loop_->task_runner()->PostTask(
+        FROM_HERE,
+        base::Bind(&BrowserModule::OnMaybeFreeze, base::Unretained(this)));
+    return;
+  }
+
+  bool splash_screen_ready_to_freeze = splash_screen_ ?
+      splash_screen_->IsReadyToFreeze() : true;
+#if defined(ENABLE_DEBUGGER)
+  bool debug_console_ready_to_freeze = debug_console_ ?
+      debug_console_->IsReadyToFreeze() : true;
+#endif  // defined(ENABLE_DEBUGGER)
+  bool web_module_ready_to_freeze = web_module_->IsReadyToFreeze();
+  if (splash_screen_ready_to_freeze &&
+#if defined(ENABLE_DEBUGGER)
+      debug_console_ready_to_freeze &&
+#endif  // defined(ENABLE_DEBUGGER)
+      web_module_ready_to_freeze) {
+#if SB_API_VERSION >= SB_ADD_CONCEALED_STATE_SUPPORT_VERSION || \
+    SB_HAS(CONCEALED_STATE)
+    SbSystemRequestFreeze();
+#endif  // SB_API_VERSION >= SB_ADD_CONCEALED_STATE_SUPPORT_VERSION ||
+        // SB_HAS(CONCEALED_STATE)
+  }
+}
+
 ViewportSize BrowserModule::GetViewportSize() {
   // We trust the renderer module for width and height the most, if it exists.
   if (renderer_module_) {
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index 194810a..2d9b43f 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -426,6 +426,11 @@
   // the app state update.
   void UnfreezeInternal();
 
+  // Check debug console, splash screen and web module if they are
+  // ready to freeze at Concealed state. If so, call SystemRequestFreeze
+  // to freeze Cobalt.
+  void OnMaybeFreeze();
+
   // Gets a viewport size to use for now. This may change depending on the
   // current application state. While concealed, this returns the requested
   // viewport size. If there was no requested viewport size, it returns a
diff --git a/src/cobalt/browser/debug_console.cc b/src/cobalt/browser/debug_console.cc
index 0503509..5716112 100644
--- a/src/cobalt/browser/debug_console.cc
+++ b/src/cobalt/browser/debug_console.cc
@@ -110,7 +110,8 @@
     network::NetworkModule* network_module,
     const cssom::ViewportSize& window_dimensions,
     render_tree::ResourceProvider* resource_provider, float layout_refresh_rate,
-    const debug::CreateDebugClientCallback& create_debug_client_callback) {
+    const debug::CreateDebugClientCallback& create_debug_client_callback,
+    const base::Closure& maybe_freeze_callback) {
   mode_ = GetInitialMode();
 
   WebModule::Options web_module_options;
@@ -134,6 +135,10 @@
       base::Bind(&CreateDebugHub,
                  base::Bind(&DebugConsole::GetMode, base::Unretained(this)),
                  create_debug_client_callback);
+
+  // Pass down this callback from Browser module to Web module eventually.
+  web_module_options.maybe_freeze_callback = maybe_freeze_callback;
+
   web_module_.reset(new WebModule(
       GURL(kInitialDebugConsoleUrl), initial_application_state,
       render_tree_produced_callback,
diff --git a/src/cobalt/browser/debug_console.h b/src/cobalt/browser/debug_console.h
index 8d573e2..7983443 100644
--- a/src/cobalt/browser/debug_console.h
+++ b/src/cobalt/browser/debug_console.h
@@ -51,7 +51,8 @@
       const cssom::ViewportSize& window_dimensions,
       render_tree::ResourceProvider* resource_provider,
       float layout_refresh_rate,
-      const debug::CreateDebugClientCallback& create_debug_client_callback);
+      const debug::CreateDebugClientCallback& create_debug_client_callback,
+      const base::Closure& maybe_freeze_callback);
   ~DebugConsole();
 
   // Filters a key event.
@@ -109,6 +110,10 @@
 
   void ReduceMemory() { web_module_->ReduceMemory(); }
 
+  bool IsReadyToFreeze() {
+    return web_module_->IsReadyToFreeze();
+  }
+
  private:
   void OnError(const GURL& url, const std::string& error) {
     LOG(ERROR) << error;
diff --git a/src/cobalt/browser/splash_screen.cc b/src/cobalt/browser/splash_screen.cc
index 962142f..7d18340 100644
--- a/src/cobalt/browser/splash_screen.cc
+++ b/src/cobalt/browser/splash_screen.cc
@@ -57,10 +57,10 @@
     const cssom::ViewportSize& window_dimensions,
     render_tree::ResourceProvider* resource_provider, float layout_refresh_rate,
     const base::Optional<GURL>& fallback_splash_screen_url,
-    const GURL& initial_main_web_module_url,
     SplashScreenCache* splash_screen_cache,
     const base::Callback<void(base::TimeDelta)>&
-        on_splash_screen_shutdown_complete)
+        on_splash_screen_shutdown_complete,
+    const base::Closure& maybe_freeze_callback)
     : render_tree_produced_callback_(render_tree_produced_callback),
       self_message_loop_(base::MessageLoop::current()),
       on_splash_screen_shutdown_complete_(on_splash_screen_shutdown_complete),
@@ -76,15 +76,10 @@
       base::ThreadPriority::HIGHEST;
 
   base::Optional<GURL> url_to_pass = fallback_splash_screen_url;
-  // Use the cached URL rather than the passed in URL if it exists.
-  base::Optional<std::string> key =
-      SplashScreenCache::GetKeyForStartUrl(initial_main_web_module_url);
   DCHECK(fallback_splash_screen_url ||
-         (key && splash_screen_cache &&
-          splash_screen_cache->IsSplashScreenCached(*key)));
-  if (key && splash_screen_cache &&
-      splash_screen_cache->IsSplashScreenCached(*key)) {
-    url_to_pass = GURL(loader::kCacheScheme + ("://" + *key));
+         (splash_screen_cache && splash_screen_cache->IsSplashScreenCached()));
+  if (splash_screen_cache && splash_screen_cache->IsSplashScreenCached()) {
+    url_to_pass = splash_screen_cache->GetCachedSplashScreenUrl();
     web_module_options.can_fetch_cache = true;
     web_module_options.splash_screen_cache = splash_screen_cache;
   }
@@ -99,6 +94,9 @@
   // module contents, make sure blending is enabled for its background.
   web_module_options.clear_window_with_background_color = false;
 
+  // Pass down this callback from Browser module to Web module eventually.
+  web_module_options.maybe_freeze_callback = maybe_freeze_callback;
+
   DCHECK(url_to_pass);
   web_module_.reset(new WebModule(
       *url_to_pass, initial_application_state, render_tree_produced_callback_,
diff --git a/src/cobalt/browser/splash_screen.h b/src/cobalt/browser/splash_screen.h
index d6e08a4..381280f 100644
--- a/src/cobalt/browser/splash_screen.h
+++ b/src/cobalt/browser/splash_screen.h
@@ -42,10 +42,10 @@
                render_tree::ResourceProvider* resource_provider,
                float layout_refresh_rate,
                const base::Optional<GURL>& fallback_splash_screen_url,
-               const GURL& initial_main_web_module_url,
                cobalt::browser::SplashScreenCache* splash_screen_cache,
                const base::Callback<void(base::TimeDelta)>&
-                   on_splash_screen_shutdown_complete);
+                   on_splash_screen_shutdown_complete,
+               const base::Closure& maybe_freeze_callback);
   ~SplashScreen();
 
   void SetSize(const cssom::ViewportSize& viewport_size) {
@@ -80,6 +80,8 @@
 
   WebModule& web_module() { return *web_module_; }
 
+  bool IsReadyToFreeze() { return web_module_->IsReadyToFreeze(); }
+
  private:
   // Run when window.close() is called by the WebModule.
   void OnWindowClosed();
diff --git a/src/cobalt/browser/splash_screen_cache.cc b/src/cobalt/browser/splash_screen_cache.cc
index 43cb6f7..5cf8299 100644
--- a/src/cobalt/browser/splash_screen_cache.cc
+++ b/src/cobalt/browser/splash_screen_cache.cc
@@ -23,10 +23,10 @@
 #include "base/strings/string_util.h"
 #include "base/synchronization/lock.h"
 #include "cobalt/base/get_application_key.h"
+#include "starboard/common/file.h"
 #include "starboard/common/string.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/directory.h"
-#include "starboard/file.h"
 
 namespace cobalt {
 namespace browser {
@@ -59,10 +59,10 @@
   base::AutoLock lock(lock_);
 }
 
-bool SplashScreenCache::CacheSplashScreen(const std::string& key,
-                                          const std::string& content) const {
+bool SplashScreenCache::CacheSplashScreen(const std::string& content) const {
   base::AutoLock lock(lock_);
-  if (key.empty()) {
+  base::Optional<std::string> key = GetKeyForStartUrl(url_);
+  if (!key) {
     return false;
   }
 
@@ -76,10 +76,11 @@
                        kSbFileMaxPath)) {
     return false;
   }
-  if (!CreateDirsForKey(key)) {
+  if (!CreateDirsForKey(key.value())) {
     return false;
   }
-  std::string full_path = std::string(path.data()) + kSbFileSepString + key;
+  std::string full_path =
+      std::string(path.data()) + kSbFileSepString + key.value();
   starboard::ScopedFile cache_file(
       full_path.c_str(), kSbFileCreateAlways | kSbFileWrite, NULL, NULL);
 
@@ -87,15 +88,18 @@
                              static_cast<int>(content.size())) > 0;
 }
 
-bool SplashScreenCache::IsSplashScreenCached(const std::string& key) const {
+bool SplashScreenCache::IsSplashScreenCached() const {
   base::AutoLock lock(lock_);
   std::vector<char> path(kSbFileMaxPath, 0);
   if (!SbSystemGetPath(kSbSystemPathCacheDirectory, path.data(),
                        kSbFileMaxPath)) {
     return false;
   }
-  std::string full_path = std::string(path.data()) + kSbFileSepString + key;
-  return !key.empty() && SbFileExists(full_path.c_str());
+  base::Optional<std::string> key = GetKeyForStartUrl(url_);
+  if (!key) return false;
+  std::string full_path =
+      std::string(path.data()) + kSbFileSepString + key.value();
+  return SbFileExists(full_path.c_str());
 }
 
 int SplashScreenCache::ReadCachedSplashScreen(
diff --git a/src/cobalt/browser/splash_screen_cache.h b/src/cobalt/browser/splash_screen_cache.h
index deacfc8..336d124 100644
--- a/src/cobalt/browser/splash_screen_cache.h
+++ b/src/cobalt/browser/splash_screen_cache.h
@@ -20,6 +20,7 @@
 
 #include "base/optional.h"
 #include "base/synchronization/lock.h"
+#include "cobalt/loader/cache_fetcher.h"
 #include "url/gurl.h"
 
 namespace cobalt {
@@ -36,25 +37,35 @@
   SplashScreenCache();
 
   // Cache the splash screen.
-  bool CacheSplashScreen(const std::string& key,
-                         const std::string& content) const;
+  bool CacheSplashScreen(const std::string& content) const;
 
   // Read the cached the splash screen.
   int ReadCachedSplashScreen(const std::string& key,
                              std::unique_ptr<char[]>* result) const;
 
-  // Determine if a splash screen is cached corresponding to the key.
-  bool IsSplashScreenCached(const std::string& key) const;
+  // Determine if a splash screen is cached corresponding to the current url.
+  bool IsSplashScreenCached() const;
 
+  // Set the URL of the currently requested splash screen.
+  void SetUrl(const GURL& url) { url_ = url; }
+
+  // Get the cache location of the currently requested splash screen.
+  GURL GetCachedSplashScreenUrl() {
+    base::Optional<std::string> key = GetKeyForStartUrl(url_);
+    return GURL(loader::kCacheScheme + ("://" + *key));
+  }
+
+ private:
   // Get the key that corresponds to a starting URL. Optionally create
   // subdirectories along the path.
   static base::Optional<std::string> GetKeyForStartUrl(const GURL& url);
 
- private:
   // Lock to protect access to the cache file.
   mutable base::Lock lock_;
   // Hash of the last read page contents.
   mutable uint32_t last_page_hash_;
+  // Latest url that was navigated to.
+  GURL url_;
 };
 
 }  // namespace browser
diff --git a/src/cobalt/browser/user_agent_string.cc b/src/cobalt/browser/user_agent_string.cc
index 2e7f57d..8e973b5 100644
--- a/src/cobalt/browser/user_agent_string.cc
+++ b/src/cobalt/browser/user_agent_string.cc
@@ -224,10 +224,15 @@
   std::string os_name_and_version = platform_info.os_name_and_version;
 
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
-  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
-  if (command_line->HasSwitch(switches::kUserAgentOsNameVersion)) {
-    os_name_and_version =
-        command_line->GetSwitchValueASCII(switches::kUserAgentOsNameVersion);
+  // Because we add Cobalt's user agent string to Crashpad before we actually
+  // start Cobalt, the command line won't be initialized when we first try to
+  // get the user agent string.
+  if (base::CommandLine::InitializedForCurrentProcess()) {
+    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+    if (command_line->HasSwitch(switches::kUserAgentOsNameVersion)) {
+      os_name_and_version =
+          command_line->GetSwitchValueASCII(switches::kUserAgentOsNameVersion);
+    }
   }
 #endif  // ENABLE_DEBUG_COMMAND_LINE_SWITCHES
 
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index 3f8186d..d852db5 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -86,23 +86,18 @@
 // deeper than this could be discarded, and will not be rendered.
 const int kDOMMaxElementDepth = 32;
 
-bool CacheUrlContent(SplashScreenCache* splash_screen_cache, const GURL& url,
+void CacheUrlContent(SplashScreenCache* splash_screen_cache,
                      const std::string& content) {
-  base::Optional<std::string> key = SplashScreenCache::GetKeyForStartUrl(url);
-  if (key) {
-    return splash_screen_cache->SplashScreenCache::CacheSplashScreen(*key,
-                                                                     content);
-  }
-  return false;
+  splash_screen_cache->SplashScreenCache::CacheSplashScreen(content);
 }
 
-base::Callback<bool(const GURL&, const std::string&)> CacheUrlContentCallback(
+base::Callback<void(const std::string&)> CacheUrlContentCallback(
     SplashScreenCache* splash_screen_cache) {
   // This callback takes in first the url, then the content string.
   if (splash_screen_cache) {
     return base::Bind(CacheUrlContent, base::Unretained(splash_screen_cache));
   } else {
-    return base::Callback<bool(const GURL&, const std::string&)>();
+    return base::Callback<void(const std::string&)>();
   }
 }
 
@@ -242,6 +237,10 @@
 
   void CancelSynchronousLoads();
 
+  void IsReadyToFreeze(volatile bool* is_ready_to_freeze) {
+    *is_ready_to_freeze = !media_session_client_->is_active();
+  }
+
  private:
   class DocumentLoadedObserver;
 
@@ -593,6 +592,8 @@
 
   media_session_client_ = media_session::MediaSessionClient::Create();
   media_session_client_->SetMediaPlayerFactory(data.web_media_player_factory);
+  media_session_client_->SetMaybeFreezeCallback(
+      data.options.maybe_freeze_callback);
 
   system_caption_settings_ = new cobalt::dom::captions::SystemCaptionSettings(
       environment_settings_.get());
@@ -1692,5 +1693,16 @@
                             base::Unretained(impl_.get()), callback));
 }
 
+bool WebModule::IsReadyToFreeze() {
+  DCHECK_NE(base::MessageLoop::current(), message_loop());
+
+  volatile bool is_ready_to_freeze = false;
+  message_loop()->task_runner()->PostBlockingTask(
+      FROM_HERE, base::Bind(&WebModule::Impl::IsReadyToFreeze,
+                            base::Unretained(impl_.get()),
+                            &is_ready_to_freeze));
+  return is_ready_to_freeze;
+}
+
 }  // namespace browser
 }  // namespace cobalt
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h
index cc09c00..afbb6a7 100644
--- a/src/cobalt/browser/web_module.h
+++ b/src/cobalt/browser/web_module.h
@@ -270,6 +270,11 @@
     // there is no state to restore.
     debug::backend::DebuggerState* debugger_state = nullptr;
 #endif  // defined(ENABLE_DEBUGGER)
+
+    // This callback is for checking the mediasession actions transitions. When
+    // there is no playback during Concealed state, we should provide a chance
+    // for Cobalt to freeze.
+    base::Closure maybe_freeze_callback;
   };
 
   typedef layout::LayoutManager::LayoutResults LayoutResults;
@@ -397,6 +402,9 @@
   void RequestJavaScriptHeapStatistics(
       const JavaScriptHeapStatisticsCallback& callback);
 
+  // Indicate the web module is ready to freeze.
+  bool IsReadyToFreeze();
+
  private:
   // Data required to construct a WebModule, initialized in the constructor and
   // passed to |Initialize|.
diff --git a/src/cobalt/build/all.gyp b/src/cobalt/build/all.gyp
index f798b39..79ece6f 100644
--- a/src/cobalt/build/all.gyp
+++ b/src/cobalt/build/all.gyp
@@ -66,6 +66,7 @@
         '<(DEPTH)/cobalt/network/network.gyp:*',
         '<(DEPTH)/cobalt/overlay_info/overlay_info.gyp:*',
         '<(DEPTH)/cobalt/render_tree/render_tree.gyp:*',
+        '<(DEPTH)/cobalt/renderer/backend/backend.gyp:graphics_system_test_deploy',
         '<(DEPTH)/cobalt/renderer/renderer.gyp:*',
         '<(DEPTH)/cobalt/renderer/sandbox/sandbox.gyp:*',
         '<(DEPTH)/cobalt/samples/simple_example/simple_example.gyp:*',
@@ -89,7 +90,7 @@
         '<(DEPTH)/net/net.gyp:net_unittests_deploy',
         '<(DEPTH)/sql/sql.gyp:sql_unittests_deploy',
         '<(DEPTH)/starboard/elf_loader/elf_loader.gyp:elf_loader_test_deploy',
-        '<(DEPTH)/starboard/loader_app/loader_app.gyp:loader_app',
+        '<(DEPTH)/starboard/loader_app/loader_app.gyp:loader_app_tests_deploy',
         '<(DEPTH)/starboard/nplb/nplb_evergreen_compat_tests/nplb_evergreen_compat_tests.gyp:nplb_evergreen_compat_tests_deploy',
       ],
       'conditions': [
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 28196cb..a485246 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-278102
\ No newline at end of file
+282262
\ No newline at end of file
diff --git a/src/cobalt/configuration/configuration.cc b/src/cobalt/configuration/configuration.cc
index 1502037..9c5087c 100644
--- a/src/cobalt/configuration/configuration.cc
+++ b/src/cobalt/configuration/configuration.cc
@@ -25,7 +25,8 @@
 Configuration* Configuration::configuration_ = nullptr;
 
 Configuration* Configuration::GetInstance() {
-  return base::Singleton<Configuration>::get();
+  return base::Singleton<Configuration,
+                         base::LeakySingletonTraits<Configuration>>::get();
 }
 
 Configuration::Configuration() {
diff --git a/src/cobalt/content/fonts/font_files/NotoColorEmoji.woff2 b/src/cobalt/content/fonts/font_files/NotoColorEmoji.woff2
index 9cac5c0..72a6830 100644
--- a/src/cobalt/content/fonts/font_files/NotoColorEmoji.woff2
+++ b/src/cobalt/content/fonts/font_files/NotoColorEmoji.woff2
Binary files differ
diff --git a/src/cobalt/demos/content/background-mode-demo/background-mode-demo.html b/src/cobalt/demos/content/background-mode-demo/background-mode-demo.html
new file mode 100644
index 0000000..058300b
--- /dev/null
+++ b/src/cobalt/demos/content/background-mode-demo/background-mode-demo.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Loop Playback</title>
+  <style>
+    body {
+      background-color: rgb(255, 255, 255);
+      color: #0047ab;
+      font-size: 50px;
+    }
+    video {
+      transform: translateX(100px) rotate(3deg);
+    }
+  </style>
+</head>
+<body>
+  Loop Playback
+  <script type="text/javascript" src="background-mode-demo.js"></script>
+  <div>Playback Actions:
+     <span id='info' style='white-space: pre; background-color:#00FF00'></span>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/demos/content/background-mode-demo/background-mode-demo.js b/src/cobalt/demos/content/background-mode-demo/background-mode-demo.js
new file mode 100644
index 0000000..6de9271
--- /dev/null
+++ b/src/cobalt/demos/content/background-mode-demo/background-mode-demo.js
@@ -0,0 +1,188 @@
+// Copyright 2018 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.
+
+// The page simply plays an audio or a video stream in a loop, it can be used
+// in the following forms:
+//   background-mode-demo.html&type=video
+//   background-mode-demo.html&type=audio
+// If the stream is adaptive, it has to be fit in memory as this demo will
+// download the whole stream at once.
+
+var video = null;
+var type = null;
+
+let playlist = getPlaylist();
+let index = 0;
+
+var kAdaptiveAudioChunkSize = 720 * 1024;
+
+function downloadAndAppend(url, begin, end, source_buffer, callback) {
+  var xhr = new XMLHttpRequest;
+  xhr.open('GET', url, true);
+  xhr.responseType = 'arraybuffer';
+  xhr.addEventListener('load', function(e) {
+    var onupdateend = function() {
+      source_buffer.removeEventListener('updateend', onupdateend);
+      callback();
+    };
+    source_buffer.addEventListener('updateend', onupdateend);
+    source_buffer.appendBuffer(new Uint8Array(e.target.response));
+  });
+
+  xhr.setRequestHeader('Range', ('bytes=' + begin +'-' + end));
+  xhr.send();
+}
+
+function createVideoElement() {
+  var video = document.createElement('video');
+  video.autoplay = true;
+  video.style.width = '1280px';
+  video.style.height = '720px';
+  document.body.appendChild(video);
+
+  return video;
+}
+
+function onVideoEnded() {
+  startNextVideo();
+}
+
+function startAdaptiveVideo() {
+  video.src = '';
+  video.load();
+  var mediasource = new MediaSource;
+  mediasource.addEventListener('sourceopen', function () {
+    if (type == 'audio') {
+      var audio_source_buffer = mediasource.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"');
+	downloadAndAppend('../media-element-demo/dash-audio.mp4', 0, kAdaptiveAudioChunkSize * 10, audio_source_buffer, function () {
+        mediasource.endOfStream();
+      });
+    }
+
+    if (type == 'video') {
+      var video_source_buffer = mediasource.addSourceBuffer('video/mp4; codecs="avc1.640028"');
+      var audio_source_buffer = mediasource.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"');
+      downloadAndAppend('../media-element-demo/dash-video-1080p.mp4', 0, 15 * 1024 * 1024, video_source_buffer, function () {
+        video_source_buffer.abort();
+           // Append the first two segments of the 240p video so we can see the transition.
+        downloadAndAppend('../media-element-demo/dash-audio.mp4', 0, kAdaptiveAudioChunkSize * 10, audio_source_buffer, function () {
+          mediasource.endOfStream();
+        });
+      });
+    }
+  })
+
+  video.src = window.URL.createObjectURL(mediasource);
+  video.addEventListener('ended', onVideoEnded);
+}
+
+
+function startNextVideo() {
+  startAdaptiveVideo();
+}
+
+function main() {
+  var get_parameters = window.location.search.substr(1).split('&');
+  for (var param of get_parameters) {
+    splitted = param.split('=');
+    if (splitted[0] == 'type') {
+      type = splitted[1];
+    }
+  }
+
+  if (type != 'audio' && type != 'video') {
+    throw "invalid type " + type;
+  }
+
+  video = createVideoElement();
+  startNextVideo();
+  updateMetadata();
+}
+
+main();
+
+// MeidaSession
+function updateMetadata() {
+  let track = playlist[index];
+
+  navigator.mediaSession.metadata = new MediaMetadata({
+    title: track.title,
+    artist: track.artist,
+    //artwork: track.artwork
+  });
+  navigator.mediaSession.playbackState = "playing";
+}
+
+let defaultSkipTime = 10;
+
+navigator.mediaSession.setActionHandler('seekbackward', function(event) {
+  const skipTime = event.seekOffset || defaultSkipTime;
+  video.currentTime = Math.max(video.currentTime - skipTime, 0);
+  updatePositionState();
+});
+
+navigator.mediaSession.setActionHandler('seekforward', function(event) {
+  const skipTime = event.seekOffset || defaultSkipTime;
+  video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
+  updatePositionState();
+});
+
+navigator.mediaSession.setActionHandler('play', function() {
+  log_info('TimeStamp: ' + getTime() + ' seconds' + ' play');
+  video.play();
+  navigator.mediaSession.playbackState = "playing";
+});
+
+navigator.mediaSession.setActionHandler('pause', function() {
+  log_info('TimeStamp: ' + getTime() + ' seconds' + ' pause');
+  video.pause();
+  navigator.mediaSession.playbackState = "paused";
+});
+
+
+try {
+  navigator.mediaSession.setActionHandler('stop', function() {
+    log_info('TimeStamp: ' + getTime() + ' seconds' + ' stop');
+  });
+} catch(error) {
+}
+
+try {
+  navigator.mediaSession.setActionHandler('seekto', function(event) {
+    if (event.fastSeek && ('fastSeek' in video)) {
+      video.fastSeek(event.seekTime);
+      return;
+    }
+    video.currentTime = event.seekTime;
+    updatePositionState();
+  });
+} catch(error) {
+}
+
+function getPlaylist() {
+  return [{
+      title: 'Background mode demo',
+      artist: 'Cobalt',
+    }];
+}
+
+function log_info(message) {
+  console.warn(message);
+  document.getElementById('info').innerHTML += message + '.\n';
+}
+
+function getTime() {
+  return Math.floor(Date.now() / 1000 | 0);
+}
+
diff --git a/src/cobalt/demos/content/media-element-demo/key-systems.html b/src/cobalt/demos/content/media-element-demo/key-systems.html
new file mode 100644
index 0000000..7358ca5
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/key-systems.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Media Element Demo</title>
+  <style>
+    .title {
+      background-color: #FFF;
+      color: #0047ab;
+      font-size: 20px;
+    }
+    .query {
+    }
+    .result-success {
+      background-color: rgb(0, 128, 0);
+    }
+    .result-failure {
+      background-color: rgb(128, 0, 0);
+    }
+    body {
+      background-color: #FFF;
+      font-size: 20px;
+    }
+  </style>
+</head>
+<body>
+  <span class="title">Queries on Key Systems</span>
+  <script type="text/javascript" src="key-systems.js"></script>
+</body>
+</html>
diff --git a/src/cobalt/demos/content/media-element-demo/key-systems.js b/src/cobalt/demos/content/media-element-demo/key-systems.js
new file mode 100644
index 0000000..ec8f149
--- /dev/null
+++ b/src/cobalt/demos/content/media-element-demo/key-systems.js
@@ -0,0 +1,111 @@
+// 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.
+
+function getRepresentation(keySystem, audioMime, videoMime, encryptionScheme) {
+  var representation = keySystem;
+  if (typeof audioMime !== 'undefined') {
+    representation += ', ' + audioMime;
+  }
+  if (typeof videoMime !== 'undefined') {
+    representation += ', ' + videoMime;
+  }
+  if (typeof encryptionScheme !== 'undefined') {
+    representation += ', encryptionscheme="' + encryptionScheme + '"';
+  }
+  return representation;
+}
+
+function checkForSupport(keySystem, audioMime, videoMime, encryptionScheme,
+                         expectedResult) {
+  var configs = [{
+    initDataTypes: ['cenc', 'sinf', 'webm'],
+    audioCapabilities: [],
+    videoCapabilities: [],
+  }];
+  if (typeof audioMime !== 'undefined') {
+    configs[0].audioCapabilities.push(
+        {contentType: audioMime, encryptionScheme: encryptionScheme});
+  }
+  if (typeof videoMime !== 'undefined') {
+    configs[0].videoCapabilities.push(
+        {contentType: videoMime, encryptionScheme: encryptionScheme});
+  }
+  var representation = getRepresentation(keySystem, audioMime, videoMime,
+                                         encryptionScheme);
+  navigator.requestMediaKeySystemAccess(keySystem, configs)
+      .then(onKeySystemAccess.bind(this, representation, expectedResult),
+            onFailure.bind(this, representation, expectedResult));
+}
+
+function addQueryResult(query, result, expectedResult) {
+  var row = document.createElement('div');
+
+  var cell = document.createElement('span');
+  cell.className = 'query';
+  cell.textContent = query + ' => ';
+  row.appendChild(cell);
+
+  cell = document.createElement('span');
+  cell.className = result == expectedResult ? 'result-success' :
+                                              'result-failure';
+  cell.textContent = result;
+  row.appendChild(cell);
+
+  document.body.appendChild(row);
+}
+
+function onKeySystemAccess(representation, expectedResult, keySystemAccess) {
+  addQueryResult(representation, 'supported', expectedResult);
+}
+
+function onFailure(representation, expectedResult) {
+  addQueryResult(representation, 'not supported', expectedResult);
+}
+
+checkForSupport('com.widevine.alpha.invalid', 'audio/mp4; codecs="mp4a.40.2"',
+                undefined, undefined, 'not supported');
+checkForSupport('com.widevine.alpha.invalid', undefined,
+                'video/webm; codecs="vp9"', undefined, 'not supported');
+
+// 'invalid-scheme' is not a valid scheme.
+checkForSupport('com.widevine.alpha', 'audio/mp4; codecs="mp4a.40.2"',
+                undefined, 'invalid-scheme', 'not supported');
+checkForSupport('com.widevine.alpha', undefined, 'video/webm; codecs="vp9"',
+                'invalid-scheme', 'not supported');
+
+checkForSupport('com.widevine.alpha', 'audio/mp4; codecs="mp4a.40.2"',
+                undefined, undefined, 'supported');
+checkForSupport('com.widevine.alpha', undefined, 'video/webm; codecs="vp9"',
+                undefined, 'supported');
+
+// Empty string is not a valid scheme.
+checkForSupport('com.widevine.alpha', 'audio/mp4; codecs="mp4a.40.2"',
+                undefined, '', 'not supported');
+checkForSupport('com.widevine.alpha', undefined, 'video/webm; codecs="vp9"',
+                '', 'not supported');
+
+checkForSupport('com.widevine.alpha', 'audio/mp4; codecs="mp4a.40.2"',
+                undefined, 'cenc', 'supported');
+checkForSupport('com.widevine.alpha', undefined, 'video/webm; codecs="vp9"',
+                'cenc', 'supported');
+
+checkForSupport('com.widevine.alpha', 'audio/mp4; codecs="mp4a.40.2"',
+                undefined, 'cbcs', 'supported');
+checkForSupport('com.widevine.alpha', undefined, 'video/webm; codecs="vp9"',
+                'cbcs', 'supported');
+
+checkForSupport('com.widevine.alpha', 'audio/mp4; codecs="mp4a.40.2"',
+                 undefined, 'cbcs-1-9', 'supported');
+checkForSupport('com.widevine.alpha', undefined, 'video/webm; codecs="vp9"',
+                'cbcs-1-9', 'supported');
diff --git a/src/cobalt/doc/resources/devtools-overlay-console-modes.png b/src/cobalt/doc/resources/devtools-overlay-console-modes.png
new file mode 100644
index 0000000..5103358
--- /dev/null
+++ b/src/cobalt/doc/resources/devtools-overlay-console-modes.png
Binary files differ
diff --git a/src/cobalt/doc/web_debugging.md b/src/cobalt/doc/web_debugging.md
index c4711db..5090e4f 100644
--- a/src/cobalt/doc/web_debugging.md
+++ b/src/cobalt/doc/web_debugging.md
@@ -102,28 +102,146 @@
 
 ### Console
 
-Cobalt has two consoles:
-* Overlay console in Cobalt itself (shown with ctrl-O or F1).
-* Remote console shown in a connected DevTools session.
+Cobalt has two types of consoles:
+
+*   Overlay Console: shown at runtime of Cobalt. It has multiple mode that it
+    can cycle between as well:
+    *   HUD
+    *   HUD & Debug Console
+    *   Media Console
+*   Remote Console: shown in a connected devtools session.
 
 Both console UIs show messages logged from JavaScript (with `console.log()`,
 etc.), and have a command line to evaluate arbitrary JavaScript in the context
 of the page being debugged.
 
+#### Overlay Console
+
 The overlay console also shows non-JavaScript logging from Cobalt itself, which
 is mostly interesting to Cobalt developers rather than web app developers.
 
+The various modes of the overlay console are accessed by repeatedly pressing
+"`F1`" or "`Ctrl+O`". They cycle in order between: none, HUD, HUD & Debug, and
+Media. Alternatively, initial console state can be set with the
+`--debug_console=off|hud|debug|media` command-line switch (`--debug_console=on`
+is accepted as a legacy option and maps to "debug" setting).
+
+![Overlay Console mode switching](resources/devtools-overlay-console-flow.png)
+
+##### HUD overlay
+
+This brings up an overlay panel which does not block sending input to the
+underlying Cobalt app. It serves to display real-time statistics (e.g. memory
+usage) and configuration values (e.g. disabled codecs) of the Cobalt app in a
+compact string.
+
+##### Debug Console overlay
+
+This overlay is interactive and it shows messages from Cobalt, along with logs
+from Javacript `console.log()`. While it is active, you cannot interact directly
+with the underlying page.
+
+Additionally, it can act as a JS interpreter that will evaluate arbitrary
+expressions on the page being debugged. The output from these JS commands will
+also be printed to the Debug console.
+
+Finally, it has some special debug commands which can be listed by calling
+`d.help()`. They are provided by a debug helper object and the list of functions
+are invoked by prepending either "`debug`" or "`d`". For example, you can
+disable the vp9 codec manually for all future played videos in this session of
+Cobalt by sending `debug.disable_media_codecs("vp9")` to the console.
+
+Note: you can clear the disabled media codecs by sending
+`debug.disable_media_codecs("")`. The command takes a semicolon separated list
+of codecs as the input list of codecs to disable.
+
+##### Media Console overlay
+
+The media console is a specialized console of the debug overlay system, for
+playback and media related tasks. The current list of implemented features are:
+
+*   Reading the play/pause state of the primary video
+*   Reading the current time and duration of the primary video
+*   Reading the playback rate of the primary video
+*   Reading the currently disabled codecs for the player
+*   Toggling between playing and pausing the primary video
+*   Setting the current playback rate between various presets for the primary
+    video
+*   Toggling the enabled/disabled state of the available codecs
+
+While the media console is shown, it is not possible to interact with the page
+below it directly.
+
+Additionally, the console does not show any meaningful information or
+interactions when no video is currently playing (all the readouts are blank or
+undefined). A status message of “No primary video.” indicates there is no valid
+player element on the current page.
+
+In the case of multiple videos playing (such as picture in picture), only the
+primary (fullscreen) video’s information is shown and the controls are only
+enabled for the primary video.
+
+The list of hotkeys and commands are dynamically generated as they are found to
+be available on app startup.
+
+Basic always-enabled commands are (case-sensitive):
+
+*   "`p`" Toggle the play/pause state
+*   "`]`" Increase the playback rate
+*   "`[`" Decrease the playback rate
+
+The above commands will take effect instantly for the currently playing video.
+They have no effect if there is no video playing.
+
+The following commands are dynamically loaded based on the capability of the
+system:
+
+*   "`CTRL+NUM`" Enable/disable specific video codec
+*   "`ALT+NUM`" Enable/disable specific audio codec
+
+**Important:** Media Console cannot be used to directly select a specific codec for
+playback. See the section below for rough outline of steps to work around this.
+
+The list of available codecs for any video is chosen based on the decoders on
+the platform, and what formats YouTube itself serves. As a result, the only way
+to get a particular codec to play is to disable all the options until the
+desired codec is the one that is picked. Simply do the following procedure:
+
+*   Pick the video you want to play.
+*   Enable “stats for nerds” (See [help page for
+    instructions](https://support.google.com/youtube/answer/7519898)).
+*   Write down the codecs that are chosen when playing the video, without any
+    codecs disabled (one for video, and one for audio).
+*   Disable the default codecs.
+*   Replay the same video from the browse screen.
+*   Repeat until you identify all codecs that are available for the video, until
+    the video is unable to be played.
+*   Use the above knowledge to disable the codecs to force the player into
+    choosing a particular codec, by process of elimination.
+
+**Important:** Disabled codecs only take effect when a video starts playing.
+When you play a video, the current list of disabled codecs is used to select an
+arbitrary enabled format. When you seek in the video, the disabled codecs list
+does not take effect. Only when you exit the player and re-enter by playing a
+video will any toggled codecs be affected.
+
+**Important:** Disabled codecs list is persistent for the app-run. If you
+disable “av01”, then until you re-enable it, “av01” formats will never be
+chosen.
+
+**Important:** If you disable all the available codecs, no video codec can be
+selected and an error dialog will be shown. This means that YouTube does not
+have the video in any other formats, outside of the codecs that are disabled.
+The player reports that it cannot play the video in any of the available formats
+so playback will fail here, which is intended.
+
+#### Remote Console
+
 The console in DevTools is a richer UI that can show evaluated objects with an
 expander so you can dig in to their properties. Logging from JavaScript with
 `console.log()` can show objects and exceptions as well, in contrast to the
 text-only messages shown in the console overlay.
 
-> There may be some things (e.g.  timers) that still need to be hooked up to the
-> V8 backend, so please file a bug if something isn't working as expected.
-
-> When built with MozJs instead of V8, the functionality of the console is
-> limited to showing only text log messages.
-
 Chrome docs:
 
 *   https://developers.google.com/web/tools/chrome-devtools/console/
diff --git a/src/cobalt/dom/eme/media_key_system_media_capability.idl b/src/cobalt/dom/eme/media_key_system_media_capability.idl
index 56e86d5..7f1da60 100644
--- a/src/cobalt/dom/eme/media_key_system_media_capability.idl
+++ b/src/cobalt/dom/eme/media_key_system_media_capability.idl
@@ -16,7 +16,11 @@
 
 dictionary MediaKeySystemMediaCapability {
   DOMString contentType = "";
+
   // TODO: Implement robustness as per
   //       https://www.w3.org/TR/encrypted-media/#dom-mediakeysystemmediacapability-robustness.
   // DOMString robustness = "";
+
+  // https://wicg.github.io/encrypted-media-encryption-scheme/
+  DOMString? encryptionScheme = null;
 };
diff --git a/src/cobalt/dom/html_element.cc b/src/cobalt/dom/html_element.cc
index 80f7a7a..ea5606b 100644
--- a/src/cobalt/dom/html_element.cc
+++ b/src/cobalt/dom/html_element.cc
@@ -79,6 +79,11 @@
 // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex
 const int32 kUiNavFocusTabIndexThreshold = -2;
 
+// Track which HTMLElement is currently focused by UI navigation so that
+// redundant blur / focus events are not fired. Do not de-reference this
+// variable -- it is only meant to identify objects.
+const HTMLElement* g_ui_nav_focus_ = nullptr;
+
 struct NonTrivialStaticFields {
   NonTrivialStaticFields() {
     cssom::PropertyKeyVector computed_style_invalidation_properties;
@@ -1027,10 +1032,7 @@
   // themselves still need to have their computed style updated, in case the
   // value of display is changed.
   if (computed_style()->display() == cssom::KeywordValue::GetNone()) {
-    if (ui_nav_item_) {
-      ui_nav_item_->SetEnabled(false);
-      ui_nav_item_ = nullptr;
-    }
+    ReleaseUiNavigationItem();
     return;
   }
 
@@ -1167,15 +1169,19 @@
   directionality_ = base::nullopt;
 }
 
-void HTMLElement::OnUiNavBlur() { Blur(); }
+void HTMLElement::OnUiNavBlur() {
+  if (g_ui_nav_focus_ == this) {
+    g_ui_nav_focus_ = nullptr;
+    Blur();
+  }
+}
 
 void HTMLElement::OnUiNavFocus() {
-  // Ensure the focusing steps do not trigger the UI navigation item to
-  // force focus again.
-  if (!ui_nav_focusing_) {
-    ui_nav_focusing_ = true;
+  // Suppress the focus event if this is already focused -- i.e. the HTMLElement
+  // initiated the focus change that resulted in this call to OnUiNavFocus.
+  if (g_ui_nav_focus_ != this) {
+    g_ui_nav_focus_ = this;
     Focus();
-    ui_nav_focusing_ = false;
   }
 }
 
@@ -1211,12 +1217,7 @@
 }
 
 HTMLElement::~HTMLElement() {
-  // Disable any associated navigation item to prevent callbacks during
-  // destruction.
-  if (ui_nav_item_) {
-    ui_nav_item_->SetEnabled(false);
-    ui_nav_item_ = nullptr;
-  }
+  ReleaseUiNavigationItem();
 
   if (IsInDocument()) {
     dom_stat_tracker_->OnHtmlElementRemovedFromDocument();
@@ -1255,11 +1256,7 @@
   // Node::OnRemovedFromDocument().
   ClearRuleMatchingStateInternal(false /*invalidate_descendants*/);
 
-  // Release the associated navigation item as the object is no longer visible.
-  if (ui_nav_item_) {
-    ui_nav_item_->SetEnabled(false);
-    ui_nav_item_ = nullptr;
-  }
+  ReleaseUiNavigationItem();
 }
 
 void HTMLElement::OnMutation() { InvalidateMatchingRulesRecursively(); }
@@ -1340,6 +1337,45 @@
     old_active_element->AsHTMLElement()->RunUnFocusingSteps();
   }
 
+  // Custom, not in any spec.
+  // Set the focus item for the UI navigation system. Search up the DOM tree to
+  // find the nearest ancestor that is a UI navigation item if needed. Do this
+  // step before dispatching events as the event handlers may make UI navigation
+  // changes.
+  for (Node* node = this; node; node = node->parent_node()) {
+    Element* element = node->AsElement();
+    if (!element) {
+      continue;
+    }
+    HTMLElement* html_element = element->AsHTMLElement();
+    if (!html_element) {
+      continue;
+    }
+    if (!html_element->ui_nav_item_ ||
+        html_element->ui_nav_item_->IsContainer()) {
+      continue;
+    }
+    if (g_ui_nav_focus_ == html_element) {
+      // UI navigation is already focused on the correct element.
+      break;
+    }
+    // Updating the g_ui_nav_focus_ has the additional effect of suppressing
+    // the Blur call for the previously focused HTMLElement and the Focus call
+    // for this HTMLElement as a result of OnUiNavBlur / OnUiNavFocus callbacks
+    // that result from initiating the UI navigation focus change.
+    g_ui_nav_focus_ = html_element;
+    // Only navigation items attached to the root container are interactable.
+    // If the item is not registered with a container, then force a layout to
+    // connect items to their containers and eventually to the root container.
+    scoped_refptr<ui_navigation::NavItem> nav_item = html_element->ui_nav_item_;
+    if (!nav_item->GetContainerItem()) {
+      // UI navigation items are updated as part of generating the render tree.
+      node_document()->DoSynchronousLayoutAndGetRenderTree();
+    }
+    nav_item->Focus();
+    break;
+  }
+
   // focusin: A user agent MUST dispatch this event when an event target is
   // about to receive focus. This event type MUST be dispatched before the
   // element is given focus. The event target MUST be the element which is about
@@ -1368,18 +1404,6 @@
 
   // Custom, not in any spec.
   ClearRuleMatchingState();
-
-  // Set the focus item for the UI navigation system.
-  if (ui_nav_item_ && !ui_nav_item_->IsContainer() && !ui_nav_focusing_) {
-    // Only navigation items attached to the root container are interactable.
-    // If the item is not registered with a container, then force a layout to
-    // connect items to their containers and eventually to the root container.
-    if (!ui_nav_item_->GetContainerItem()) {
-      // UI navigation items are updated as part of generating the render tree.
-      node_document()->DoSynchronousLayoutAndGetRenderTree();
-    }
-    ui_nav_item_->Focus();
-  }
 }
 
 // Algorithm for RunUnFocusingSteps:
@@ -2011,6 +2035,11 @@
     }
   }
 
+  // Update the UI navigation item and invalidate layout boxes if needed.
+  if (!UpdateUiNavigationAndReturnIfLayoutBoxesAreValid()) {
+    invalidation_flags.invalidate_layout_boxes = true;
+  }
+
   if (invalidation_flags.mark_descendants_as_display_none) {
     MarkNotDisplayedOnDescendants();
   }
@@ -2033,9 +2062,6 @@
     }
   }
 
-  // Update the UI navigation item.
-  UpdateUiNavigationType();
-
   computed_style_valid_ = true;
   pseudo_elements_computed_styles_valid_ = true;
 }
@@ -2096,7 +2122,7 @@
          computed_style()->visibility() == cssom::KeywordValue::GetVisible();
 }
 
-void HTMLElement::UpdateUiNavigationType() {
+bool HTMLElement::UpdateUiNavigationAndReturnIfLayoutBoxesAreValid() {
   base::Optional<ui_navigation::NativeItemType> ui_nav_item_type;
   if (computed_style()->overflow() == cssom::KeywordValue::GetAuto() ||
       computed_style()->overflow() == cssom::KeywordValue::GetScroll()) {
@@ -2115,14 +2141,19 @@
       if (ui_nav_item_->GetType() == *ui_nav_item_type) {
         // Keep using the existing navigation item.
         ui_nav_item_->SetDir(ui_nav_item_dir);
-        return;
+        return true;
       }
       // The current navigation item isn't of the correct type. Disable it so
       // that callbacks won't be invoked for it. The object will be destroyed
       // when all references to it are released.
+      if (g_ui_nav_focus_ == this) {
+        g_ui_nav_focus_ = nullptr;
+        ui_nav_item_->UnfocusAll();
+      }
       ui_nav_item_->SetEnabled(false);
       ui_nav_item_ = nullptr;
     }
+
     ui_nav_item_ = new ui_navigation::NavItem(
         *ui_nav_item_type,
         base::Bind(
@@ -2141,8 +2172,36 @@
             FROM_HERE,
             base::Bind(&HTMLElement::OnUiNavScroll, base::AsWeakPtr(this))));
     ui_nav_item_->SetDir(ui_nav_item_dir);
+    return false;
   } else if (ui_nav_item_) {
     // This navigation item is no longer relevant.
+    if (g_ui_nav_focus_ == this) {
+      g_ui_nav_focus_ = nullptr;
+      ui_nav_item_->UnfocusAll();
+    }
+    ui_nav_item_->SetEnabled(false);
+    ui_nav_item_ = nullptr;
+    return false;
+  }
+
+  return true;
+}
+
+void HTMLElement::ReleaseUiNavigationItem() {
+  if (ui_nav_item_) {
+    // Make sure layout updates this element.
+    InvalidateLayoutBoxesOfNodeAndAncestors();
+    if (ui_nav_item_->IsContainer()) {
+      // Make sure layout updates any focus items that may be in this container.
+      InvalidateLayoutBoxesOfDescendants();
+    }
+
+    // Disable the UI navigation item so it won't receive anymore callbacks
+    // while being released.
+    if (g_ui_nav_focus_ == this) {
+      g_ui_nav_focus_ = nullptr;
+      ui_nav_item_->UnfocusAll();
+    }
     ui_nav_item_->SetEnabled(false);
     ui_nav_item_ = nullptr;
   }
diff --git a/src/cobalt/dom/html_element.h b/src/cobalt/dom/html_element.h
index 09211ad..3994f6f 100644
--- a/src/cobalt/dom/html_element.h
+++ b/src/cobalt/dom/html_element.h
@@ -419,7 +419,8 @@
   void ClearRuleMatchingStateInternal(bool invalidate_descendants);
 
   // Update the UI navigation item type for this element.
-  void UpdateUiNavigationType();
+  bool UpdateUiNavigationAndReturnIfLayoutBoxesAreValid();
+  void ReleaseUiNavigationItem();
 
   // Clear the list of active background images, and notify the animated image
   // tracker to stop the animations.
@@ -515,12 +516,6 @@
   // boxes without requiring a new layout.
   scoped_refptr<ui_navigation::NavItem> ui_nav_item_;
 
-  // This temporary flag is used to avoid a cycle on focus changes. When the
-  // HTML element receives focus, it must inform the UI navigation item. When
-  // the UI navigation item receives focus (either by calling SetFocus or by an
-  // update from the UI engine), it will tell the HTML element it was focused.
-  bool ui_nav_focusing_ = false;
-
   // HTMLElement is a friend of Animatable so that animatable can insert and
   // remove animations into HTMLElement's set of animations.
   friend class DOMAnimatable;
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc
index 12061ca..beb04c7 100644
--- a/src/cobalt/dom/html_media_element.cc
+++ b/src/cobalt/dom/html_media_element.cc
@@ -251,10 +251,9 @@
   DLOG_IF(ERROR, !key_system.empty())
       << "CanPlayType() only accepts one parameter but (" << key_system
       << ") is passed as a second parameter.";
-  const bool kIsProgressive = true;
   auto support_type =
-      html_element_context()->can_play_type_handler()->CanPlayType(
-          mime_type, key_system, kIsProgressive);
+      html_element_context()->can_play_type_handler()->CanPlayProgressive(
+          mime_type);
   std::string result = "";
   switch (support_type) {
     case kSbMediaSupportTypeNotSupported:
diff --git a/src/cobalt/dom/media_source.cc b/src/cobalt/dom/media_source.cc
index e94f4c4..57817f4 100644
--- a/src/cobalt/dom/media_source.cc
+++ b/src/cobalt/dom/media_source.cc
@@ -269,10 +269,8 @@
   DOMSettings* dom_settings =
       base::polymorphic_downcast<DOMSettings*>(settings);
   DCHECK(dom_settings->can_play_type_handler());
-  const bool kIsProgressive = false;
   SbMediaSupportType support_type =
-      dom_settings->can_play_type_handler()->CanPlayType(type.c_str(), "",
-                                                         kIsProgressive);
+      dom_settings->can_play_type_handler()->CanPlayAdaptive(type.c_str(), "");
   if (support_type == kSbMediaSupportTypeNotSupported) {
     LOG(INFO) << "MediaSource::IsTypeSupported(" << type
               << ") -> not supported/false";
diff --git a/src/cobalt/dom/navigator.cc b/src/cobalt/dom/navigator.cc
index c57b886..a6d3ce3 100644
--- a/src/cobalt/dom/navigator.cc
+++ b/src/cobalt/dom/navigator.cc
@@ -31,12 +31,117 @@
 
 using cobalt::media_session::MediaSession;
 
-namespace {
-const char kLicensesRelativePath[] = "/licenses/licenses_cobalt.txt";
-}  // namespace
-
 namespace cobalt {
 namespace dom {
+namespace {
+
+const char kLicensesRelativePath[] = "/licenses/licenses_cobalt.txt";
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+
+std::string ToString(const std::string& str, int indent_level);
+std::string ToString(const eme::MediaKeySystemMediaCapability& capability,
+                     int indent_level);
+std::string ToString(const eme::MediaKeySystemConfiguration& configuration,
+                     int indent_level);
+
+std::string GetIndent(int indent_level) {
+  std::string indent;
+  while (indent_level > 0) {
+    indent += "  ";
+    --indent_level;
+  }
+  return indent;
+}
+
+template <typename T>
+std::string ToString(const script::Sequence<T>& sequence, int indent_level) {
+  std::stringstream ss;
+
+  ss << "{\n";
+
+  for (auto iter = sequence.begin(); iter != sequence.end(); ++iter) {
+    ss << ToString(*iter, indent_level + 1) << ",\n";
+  }
+
+  ss << GetIndent(indent_level) << "}";
+
+  return ss.str();
+}
+
+std::string ToString(const std::string& str, int indent_level) {
+  return GetIndent(indent_level) + str;
+}
+
+std::string ToString(const eme::MediaKeySystemMediaCapability& capability,
+                     int indent_level) {
+  return GetIndent(indent_level) + '\'' + capability.content_type() + "'/'" +
+         capability.encryption_scheme().value_or("(null)") + '\'';
+}
+
+std::string ToString(const eme::MediaKeySystemConfiguration& configuration,
+                     int indent_level) {
+  DCHECK(configuration.has_label());
+
+  std::stringstream ss;
+
+  ss << GetIndent(indent_level) << "label:'" << configuration.label()
+     << "': {\n";
+  if (configuration.has_init_data_types()) {
+    ss << GetIndent(indent_level + 1) << "init_data_types: "
+       << ToString(configuration.init_data_types(), indent_level + 1) << ",\n";
+  }
+  if (configuration.has_audio_capabilities()) {
+    ss << GetIndent(indent_level + 1) << "audio_capabilities: "
+       << ToString(configuration.audio_capabilities(), indent_level + 1)
+       << ",\n";
+  }
+  if (configuration.has_video_capabilities()) {
+    ss << GetIndent(indent_level + 1) << "video_capabilities: "
+       << ToString(configuration.video_capabilities(), indent_level + 1)
+       << ",\n";
+  }
+  ss << GetIndent(indent_level) << "}";
+
+  return ss.str();
+}
+
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+
+// This function is used when the underlying SbMediaCanPlayMimeAndKeySystem()
+// implementation doesn't support extra attributes on |key_system|, it makes
+// decision based on the key system itself.
+bool IsEncryptionSchemeSupportedByDefault(
+    const std::string& key_system, const std::string& encryption_scheme) {
+  // 1. Playready only supports "cenc".
+  if (key_system.find("playready") != key_system.npos) {
+    return encryption_scheme == "cenc";
+  }
+  // 2. Fairplay only supports "cbcs" and "cbcs-1-9".
+  if (key_system.find("fairplay") != key_system.npos) {
+    return encryption_scheme == "cbcs" || encryption_scheme == "cbcs-1-9";
+  }
+  // 3. Widevine only supports "cenc", "cbcs" and "cbcs-1-9".
+  if (key_system.find("widevine") != key_system.npos) {
+    return encryption_scheme == "cenc" || encryption_scheme == "cbcs" ||
+           encryption_scheme == "cbcs-1-9";
+  }
+
+  // The key system is unknown, assume only "cenc" is supported.
+  return encryption_scheme == "cenc";
+}
+
+bool CanPlay(const media::CanPlayTypeHandler& can_play_type_handler,
+             const std::string& content_type, const std::string& key_system) {
+  auto can_play_result = can_play_type_handler.CanPlayAdaptive(
+      content_type.c_str(), key_system.c_str());
+  LOG_IF(INFO, can_play_result == kSbMediaSupportTypeMaybe)
+      << "CanPlayAdaptive() returns \"maybe\".";
+  return can_play_result == kSbMediaSupportTypeProbably ||
+         can_play_result == kSbMediaSupportTypeMaybe;
+}
+
+}  // namespace
 
 Navigator::Navigator(
     script::EnvironmentSettings* settings, const std::string& user_agent,
@@ -123,17 +228,16 @@
   return media_session_;
 }
 
-namespace {
-
+// TODO: Move the following two functions to the bottom of the file, in sync
+//       with the order of declaration.
 // See
 // https://www.w3.org/TR/encrypted-media/#get-supported-capabilities-for-audio-video-type.
 base::Optional<script::Sequence<MediaKeySystemMediaCapability>>
-TryGetSupportedCapabilities(
+Navigator::TryGetSupportedCapabilities(
+    const media::CanPlayTypeHandler& can_play_type_handler,
     const std::string& key_system,
     const script::Sequence<MediaKeySystemMediaCapability>&
-        requested_media_capabilities,
-    const media::CanPlayTypeHandler* can_play_type_handler) {
-  DCHECK(can_play_type_handler);
+        requested_media_capabilities) {
   // 2. Let supported media capabilities be an empty sequence of
   //    MediaKeySystemMediaCapability dictionaries.
   script::Sequence<MediaKeySystemMediaCapability> supported_media_capabilities;
@@ -152,17 +256,10 @@
     // 3.13. If the user agent and [CDM] implementation definitely support
     //       playback of encrypted media data for the combination of container,
     //       media types [...]:
-    const bool kIsProgressive = false;
-    if (can_play_type_handler->CanPlayType(
-            content_type.c_str(), key_system.c_str(), kIsProgressive) ==
-        kSbMediaSupportTypeProbably) {
-      LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type
-                << ", " << key_system << ") -> supported";
+    if (CanPlayWithCapability(can_play_type_handler, key_system,
+                              requested_media_capability)) {
       // 3.13.1. Add requested media capability to supported media capabilities.
       supported_media_capabilities.push_back(requested_media_capability);
-    } else {
-      LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type
-                << ", " << key_system << ") -> not supported";
     }
   }
   // 4. If supported media capabilities is empty, return null.
@@ -179,10 +276,11 @@
 // is always given and go straight to "3.1.1.2 Get Supported Configuration and
 // Consent". See
 // https://www.w3.org/TR/encrypted-media/#get-supported-configuration-and-consent.
-base::Optional<eme::MediaKeySystemConfiguration> TryGetSupportedConfiguration(
+base::Optional<eme::MediaKeySystemConfiguration>
+Navigator::TryGetSupportedConfiguration(
+    const media::CanPlayTypeHandler& can_play_type_handler,
     const std::string& key_system,
-    const eme::MediaKeySystemConfiguration& candidate_configuration,
-    const media::CanPlayTypeHandler* can_play_type_handler) {
+    const eme::MediaKeySystemConfiguration& candidate_configuration) {
   // 1. Let accumulated configuration be a new MediaKeySystemConfiguration
   //    dictionary.
   eme::MediaKeySystemConfiguration accumulated_configuration;
@@ -225,8 +323,8 @@
     //       Supported Capabilities for Audio/Video Type" algorithm.
     base::Optional<script::Sequence<MediaKeySystemMediaCapability>>
         maybe_video_capabilities = TryGetSupportedCapabilities(
-            key_system, candidate_configuration.video_capabilities(),
-            can_play_type_handler);
+            can_play_type_handler, key_system,
+            candidate_configuration.video_capabilities());
     // 16.2. If video capabilities is null, return NotSupported.
     if (!maybe_video_capabilities) {
       return base::nullopt;
@@ -249,8 +347,8 @@
     //       Supported Capabilities for Audio/Video Type" algorithm.
     base::Optional<script::Sequence<MediaKeySystemMediaCapability>>
         maybe_audio_capabilities = TryGetSupportedCapabilities(
-            key_system, candidate_configuration.audio_capabilities(),
-            can_play_type_handler);
+            can_play_type_handler, key_system,
+            candidate_configuration.audio_capabilities());
     // 17.2. If audio capabilities is null, return NotSupported.
     if (!maybe_audio_capabilities) {
       return base::nullopt;
@@ -269,8 +367,6 @@
   return accumulated_configuration;
 }
 
-}  // namespace
-
 // See
 // https://www.w3.org/TR/encrypted-media/#dom-navigator-requestmediakeysystemaccess.
 script::Handle<Navigator::InterfacePromise>
@@ -286,6 +382,12 @@
       script_value_factory_
           ->CreateInterfacePromise<scoped_refptr<eme::MediaKeySystemAccess>>();
 
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+  LOG(INFO) << "Navigator.RequestMediaKeySystemAccess() called with '"
+            << key_system << "', and\n"
+            << ToString(supported_configurations, 0);
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+
   // 1. If |keySystem| is the empty string, return a promise rejected
   //    with a newly created TypeError.
   // 2. If |supportedConfigurations| is empty, return a promise rejected
@@ -302,14 +404,19 @@
     // 6.3.3. If supported configuration is not NotSupported:
     base::Optional<eme::MediaKeySystemConfiguration>
         maybe_supported_configuration = TryGetSupportedConfiguration(
-            key_system, supported_configurations.at(configuration_index),
-            dom_settings->can_play_type_handler());
+            *dom_settings->can_play_type_handler(), key_system,
+            supported_configurations.at(configuration_index));
     if (maybe_supported_configuration) {
       // 6.3.3.1. Let access be a new MediaKeySystemAccess object.
       scoped_refptr<eme::MediaKeySystemAccess> media_key_system_access(
           new eme::MediaKeySystemAccess(key_system,
                                         *maybe_supported_configuration,
                                         script_value_factory_));
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+      LOG(INFO) << "Navigator.RequestMediaKeySystemAccess() resolved with '"
+                << media_key_system_access->key_system() << "', and\n"
+                << ToString(media_key_system_access->GetConfiguration(), 0);
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
       // 6.3.3.2. Resolve promise.
       promise->Resolve(media_key_system_access);
       return promise;
@@ -334,5 +441,115 @@
   tracer->Trace(system_caption_settings_);
 }
 
+bool Navigator::CanPlayWithCapability(
+    const media::CanPlayTypeHandler& can_play_type_handler,
+    const std::string& key_system,
+    const MediaKeySystemMediaCapability& media_capability) {
+  const std::string& content_type = media_capability.content_type();
+
+  // There is no encryption scheme specified, check directly.
+  if (!media_capability.encryption_scheme().has_value()) {
+    if (CanPlay(can_play_type_handler, content_type, key_system)) {
+      LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type
+                << ", " << key_system << ") -> supported";
+      return true;
+    }
+    LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type
+              << ", " << key_system << ") -> not supported";
+    return false;
+  }
+
+  if (!key_system_with_attributes_supported_.has_value()) {
+    if (!CanPlay(can_play_type_handler, content_type, key_system)) {
+      // If the check on the basic key system fails, we don't even bother to
+      // check if it supports key system with attributes.
+      LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type
+                << ", " << key_system << ") -> not supported";
+      return false;
+    }
+    auto key_system_with_invalid_attribute =
+        std::string(key_system) + "; invalid_attributes=\"value\"";
+    // If an implementation supports attributes, it should ignore unknown
+    // attributes and return true, as the key system has been verified to be
+    // supported above.
+    key_system_with_attributes_supported_ = CanPlay(
+        can_play_type_handler, content_type, key_system_with_invalid_attribute);
+    if (key_system_with_attributes_supported_.value()) {
+      LOG(INFO) << "Navigator::RequestMediaKeySystemAccess() will use key"
+                << " system with attributes.";
+    } else {
+      LOG(INFO) << "Navigator::RequestMediaKeySystemAccess() won't use key"
+                << " system with attributes.";
+    }
+  }
+
+  DCHECK(key_system_with_attributes_supported_.has_value());
+
+  // As a key system with attributes support is optional, and the logic to
+  // determine whether the encryptionScheme is supported can be quite different
+  // depending on whether this is supported, we encapsulate the logic into two
+  // different functions.
+  if (key_system_with_attributes_supported_.value()) {
+    return CanPlayWithAttributes(can_play_type_handler, content_type,
+                                 key_system,
+                                 media_capability.encryption_scheme().value());
+  }
+
+  return CanPlayWithoutAttributes(can_play_type_handler, content_type,
+                                  key_system,
+                                  media_capability.encryption_scheme().value());
+}
+
+bool Navigator::CanPlayWithoutAttributes(
+    const media::CanPlayTypeHandler& can_play_type_handler,
+    const std::string& content_type, const std::string& key_system,
+    const std::string& encryption_scheme) {
+  DCHECK(key_system_with_attributes_supported_.has_value());
+  DCHECK(!key_system_with_attributes_supported_.value());
+
+  if (!IsEncryptionSchemeSupportedByDefault(key_system, encryption_scheme)) {
+    LOG(INFO) << "Navigator::RequestMediaKeySystemAccess() rejects "
+              << key_system << " because encryptionScheme \""
+              << encryption_scheme << "\" is not supported.";
+    return false;
+  }
+
+  if (CanPlay(can_play_type_handler, content_type, key_system)) {
+    LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type
+              << ", " << key_system << ") with encryptionScheme \""
+              << encryption_scheme << "\" -> supported";
+    return true;
+  }
+
+  LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type << ", "
+            << key_system << ") with encryptionScheme \"" << encryption_scheme
+            << "\" -> not supported";
+  return false;
+}
+
+bool Navigator::CanPlayWithAttributes(
+    const media::CanPlayTypeHandler& can_play_type_handler,
+    const std::string& content_type, const std::string& key_system,
+    const std::string& encryption_scheme) {
+  DCHECK(key_system_with_attributes_supported_.has_value());
+  DCHECK(key_system_with_attributes_supported_.value());
+
+  auto key_system_with_attributes =
+      key_system + "; encryptionscheme=\"" + encryption_scheme + '"';
+
+  if (CanPlay(can_play_type_handler, content_type,
+              key_system_with_attributes)) {
+    LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type
+              << ", " << key_system << ") with encryptionScheme \""
+              << encryption_scheme << "\" -> supported";
+    return true;
+  }
+
+  LOG(INFO) << "Navigator::RequestMediaKeySystemAccess(" << content_type << ", "
+            << key_system << ") with encryptionScheme \"" << encryption_scheme
+            << "\" -> not supported";
+  return false;
+}
+
 }  // namespace dom
 }  // namespace cobalt
diff --git a/src/cobalt/dom/navigator.h b/src/cobalt/dom/navigator.h
index 10a417d..1489f78 100644
--- a/src/cobalt/dom/navigator.h
+++ b/src/cobalt/dom/navigator.h
@@ -18,6 +18,7 @@
 #include <string>
 
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "cobalt/dom/captions/system_caption_settings.h"
 #include "cobalt/dom/eme/media_key_system_configuration.h"
 #include "cobalt/dom/mime_type_array.h"
@@ -26,6 +27,7 @@
 #include "cobalt/media_session/media_session.h"
 #include "cobalt/script/promise.h"
 #include "cobalt/script/script_value_factory.h"
+#include "cobalt/script/sequence.h"
 #include "cobalt/script/wrappable.h"
 
 namespace cobalt {
@@ -84,6 +86,33 @@
  private:
   ~Navigator() override {}
 
+  base::Optional<script::Sequence<MediaKeySystemMediaCapability>>
+  TryGetSupportedCapabilities(
+      const media::CanPlayTypeHandler& can_play_type_handler,
+      const std::string& key_system,
+      const script::Sequence<MediaKeySystemMediaCapability>&
+          requested_media_capabilities);
+
+  base::Optional<eme::MediaKeySystemConfiguration> TryGetSupportedConfiguration(
+      const media::CanPlayTypeHandler& can_play_type_handler,
+      const std::string& key_system,
+      const eme::MediaKeySystemConfiguration& candidate_configuration);
+
+  bool CanPlayWithCapability(
+      const media::CanPlayTypeHandler& can_play_type_handler,
+      const std::string& key_system,
+      const MediaKeySystemMediaCapability& media_capability);
+
+  bool CanPlayWithoutAttributes(
+      const media::CanPlayTypeHandler& can_play_type_handler,
+      const std::string& content_type, const std::string& key_system,
+      const std::string& encryption_scheme);
+
+  bool CanPlayWithAttributes(
+      const media::CanPlayTypeHandler& can_play_type_handler,
+      const std::string& content_type, const std::string& key_system,
+      const std::string& encryption_scheme);
+
   std::string user_agent_;
   std::string language_;
   scoped_refptr<MimeTypeArray> mime_types_;
@@ -93,6 +122,7 @@
   scoped_refptr<cobalt::dom::captions::SystemCaptionSettings>
       system_caption_settings_;
   script::ScriptValueFactory* script_value_factory_;
+  base::Optional<bool> key_system_with_attributes_supported_;
 
   DISALLOW_COPY_AND_ASSIGN(Navigator);
 };
diff --git a/src/cobalt/dom/window.cc b/src/cobalt/dom/window.cc
index d4ecd45..a667e9e 100644
--- a/src/cobalt/dom/window.cc
+++ b/src/cobalt/dom/window.cc
@@ -710,7 +710,7 @@
     return;
   }
   DLOG(INFO) << "Caching splash screen for URL " << location()->url();
-  splash_screen_cache_callback_.Run(location()->url(), content);
+  splash_screen_cache_callback_.Run(content);
 }
 
 const scoped_refptr<OnScreenKeyboard>& Window::on_screen_keyboard() const {
diff --git a/src/cobalt/dom/window.h b/src/cobalt/dom/window.h
index a590389..3b3976f 100644
--- a/src/cobalt/dom/window.h
+++ b/src/cobalt/dom/window.h
@@ -121,7 +121,7 @@
   // close() was called.
   typedef base::Callback<void(base::TimeDelta)> CloseCallback;
   typedef UrlRegistry<MediaSource> MediaSourceRegistry;
-  typedef base::Callback<bool(const GURL&, const std::string&)> CacheCallback;
+  typedef base::Callback<void(const std::string&)> CacheCallback;
 
   enum ClockType {
     kClockTypeTestRunner,
diff --git a/src/cobalt/extension/media_session.h b/src/cobalt/extension/media_session.h
index efb9f17..dccbd42 100644
--- a/src/cobalt/extension/media_session.h
+++ b/src/cobalt/extension/media_session.h
@@ -25,13 +25,13 @@
 
 #define kCobaltExtensionMediaSessionName "dev.cobalt.extension.MediaSession"
 
-enum CobaltExtensionPlaybackState {
-  kCobaltExtensionPlaying = 0,
-  kCobaltExtensionPaused = 1,
-  kCobaltExtensionNone = 2
-};
+typedef enum CobaltExtensionMediaSessionPlaybackState {
+  kCobaltExtensionMediaSessionNone = 0,
+  kCobaltExtensionMediaSessionPaused = 1,
+  kCobaltExtensionMediaSessionPlaying = 2
+} CobaltExtensionMediaSessionPlaybackState;
 
-enum CobaltExtensionMediaSessionAction {
+typedef enum CobaltExtensionMediaSessionAction {
   kCobaltExtensionMediaSessionActionPlay,
   kCobaltExtensionMediaSessionActionPause,
   kCobaltExtensionMediaSessionActionSeekbackward,
@@ -44,7 +44,7 @@
 
   // Not part of spec, but used in Cobalt implementation.
   kCobaltExtensionMediaSessionActionNumActions,
-};
+} CobaltExtensionMediaSessionAction;
 
 typedef struct CobaltExtensionMediaImage {
   // These fields are null-terminated strings copied over from IDL.
@@ -63,6 +63,20 @@
   size_t artwork_count;
 } CobaltExtensionMediaMetadata;
 
+typedef struct CobaltExtensionMediaSessionActionDetails {
+  CobaltExtensionMediaSessionAction action;
+
+  // Seek time/offset are non-negative. Negative value signifies "unset".
+  double seek_offset;
+  double seek_time;
+
+  bool fast_seek;
+} CobaltExtensionMediaSessionActionDetails;
+
+typedef void (*CobaltExtensionMediaSessionUpdatePlatformPlaybackStateCallback)(
+    CobaltExtensionMediaSessionPlaybackState state, void* callback_context);
+typedef void (*CobaltExtensionMediaSessionInvokeActionCallback)(
+    CobaltExtensionMediaSessionActionDetails details, void* callback_context);
 
 // This struct and all its members should only be used for piping data to each
 // platform's implementation of OnMediaSessionStateChanged and they are only
@@ -70,11 +84,21 @@
 // will be referenced later.
 typedef struct CobaltExtensionMediaSessionState {
   SbTimeMonotonic duration;
-  enum CobaltExtensionPlaybackState actual_playback_state;
+  CobaltExtensionMediaSessionPlaybackState actual_playback_state;
   bool available_actions[kCobaltExtensionMediaSessionActionNumActions];
-  CobaltExtensionMediaMetadata metadata;
+  CobaltExtensionMediaMetadata* metadata;
   double actual_playback_rate;
   SbTimeMonotonic current_playback_position;
+
+  // Callback to MediaSessionClient::UpdatePlatformPlaybackState for when the
+  // platform updates state.
+  CobaltExtensionMediaSessionUpdatePlatformPlaybackStateCallback
+      update_platform_playback_state_callback;
+
+  // Callback to MediaSessionClient::InvokeAction for when the platform handles
+  // a new media action.
+  void* callback_context;
+  CobaltExtensionMediaSessionInvokeActionCallback invoke_action_callback;
 } CobaltExtensionMediaSessionState;
 
 typedef struct CobaltExtensionMediaSessionApi {
@@ -92,8 +116,17 @@
 
 } CobaltExtensionMediaSessionApi;
 
+inline void CobaltExtensionMediaSessionActionDetailsInit(
+    CobaltExtensionMediaSessionActionDetails* details,
+    CobaltExtensionMediaSessionAction action) {
+  details->action = action;
+  details->seek_offset = -1.0;
+  details->seek_time = -1.0;
+  details->fast_seek = false;
+}
+
 #ifdef __cplusplus
 }  // extern "C"
 #endif
 
-#endif  // COBALT_EXTENSION_MEDIA_SESSION_H_
\ No newline at end of file
+#endif  // COBALT_EXTENSION_MEDIA_SESSION_H_
diff --git a/src/cobalt/fetch/embedded_scripts/fetch.js b/src/cobalt/fetch/embedded_scripts/fetch.js
index ac5a7a3..ec89191 100644
--- a/src/cobalt/fetch/embedded_scripts/fetch.js
+++ b/src/cobalt/fetch/embedded_scripts/fetch.js
@@ -1,22 +1,22 @@
-'use strict';(function(c){function E(a){"string"!==typeof a&&(a=String(a));if(/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(a))throw new g("Invalid character in header field name");return a.toLowerCase()}function Q(a){"string"!==typeof a&&(a=String(a));var b;var d=0;for(b=a.length;d<b;d++){var e=a.charCodeAt(d);if(9!==e&&10!==e&&13!==e&&32!==e)break}for(b=a.length-1;b>d&&(e=a.charCodeAt(b),9===e||10===e||13===e||32===e);b--);a=a.substring(d,b+1);d=0;for(b=a.length;d<b;d++)if(e=a.charCodeAt(d),256<=e||0===e||
-10===e||13===e)throw new g("Invalid character in header field value");return a}function W(a,b){throw new g("Immutable header cannot be modified");}function X(a,b){return!1}function Y(a,b){a=a.toLowerCase();return-1<Z.indexOf(a)||a.startsWith("proxy-")||a.startsWith("sec-")?!0:!1}function aa(a,b){a=a.toLowerCase();return-1<ba.indexOf(a)||"content-type"===a&&(b=b.split(";")[0].toLowerCase(),-1<ca.indexOf(b))?!1:!0}function L(a,b){return-1<da.indexOf(a.toLowerCase())?!0:!1}function h(a){this[l]=new I;
-void 0===this[v]&&(this[v]=X);if(void 0!==a){if(null===a||"object"!==typeof a)throw new g("Constructing Headers with invalid parameters");a instanceof h?a.forEach(function(a,d){this.append(d,a)},this):F.isArray(a)?a.forEach(function(a){if(2!==a.length)throw new g("Constructing Headers with invalid parameters");this.append(a[0],a[1])},this):Object.getOwnPropertyNames(a).forEach(function(b){this.append(b,a[b])},this)}}function G(a,b){var d=ea(h.prototype);d[v]=b;h.call(d,a);return d}function M(a){if(a.bodyUsed)return t.reject(new g("Body was already read"));
-if(null===a.body)return t.resolve(new u(0));if(fa(a.body))return t.reject(new g("ReadableStream was already locked"));var b=a.body.getReader(),d=[],e=0;return b.read().then(function ha(a){if(a.done){if(0===d.length)a=new u(0);else if(1===d.length)a=new u(d[0].buffer);else{a=new u(e);for(var k=0,c=d.length,f=0;k<c;k++)a.set(d[k],f),f+=d[k].length}return a}return a.value instanceof u?(e+=a.value.length,d.push(a.value),b.read().then(ha)):t.reject(new g("Invalid stream data type"))})}function R(){this._initBody=
-function(a){this[N]=!1;this[m]=null===a||void 0===a?null:a instanceof O?a:new O({start:function(b){if(a)if("string"===typeof a)b.enqueue(FetchInternal.encodeToUTF8(a));else if(S.prototype.isPrototypeOf(a))b.enqueue(new u(a.slice(0)));else if(ia(a))b.enqueue(new u(a.buffer.slice(0)));else if(a instanceof Blob)b.enqueue(new u(FetchInternal.blobToArrayBuffer(a)));else throw new g("Unsupported BodyInit type");b.close()}});this[n].get("content-type")||("string"===typeof a?this[n].set("content-type","text/plain;charset=UTF-8"):
-a instanceof Blob&&""!==a.type&&this[n].set("content-type",a.type))};P(this,{body:{get:function(){return this[m]}},bodyUsed:{get:function(){return this[N]?!0:this[m]?!!ja(this[m]):!1}}});this.arrayBuffer=function(){return this[z]?t.reject(new DOMException("Aborted","AbortError")):M(this).then(function(a){return a.buffer})};this.text=function(){return this[z]?t.reject(new DOMException("Aborted","AbortError")):M(this).then(function(a){return FetchInternal.decodeFromUTF8(a)})};this.json=function(){return this[z]?
-t.reject(new DOMException("Aborted","AbortError")):this.text().then(JSON.parse)};return this}function w(a,b){var d=void 0!==b&&null!==b&&void 0===b.cloneBody;b=b||{};var e=b.body||b.cloneBody,c=b.headers,f=new AbortController;this[A]=f.signal;f=null;if(a instanceof w)this[x]=a.url,this[B]=a.cache,this[C]=a.credentials,void 0===c&&(c=a.headers),this[D]=a.integrity,this[y]=a.method,this[p]=a.mode,d&&"navigate"===this[p]&&(this[p]="same-origin"),this[H]=a.redirect,e||null===a.body||(e=a.body,a[N]=!0),
-f=a[A];else{this[x]=String(a);if(!FetchInternal.isUrlValid(this[x],!1))throw new g("Invalid request URL");this[p]="cors";this[C]="same-origin"}if(void 0!==b.window&&null!==b.window)throw new g("Invalid request window");this[B]=b.cache||this[B]||"default";if(-1===ka.indexOf(this[B]))throw new g("Invalid request cache mode");this[C]=b.credentials||this[C]||"same-origin";if(-1===la.indexOf(this[C]))throw new g("Invalid request credentials");void 0!==b.integrity?this[D]=b.integrity:void 0===this[D]&&
-(this[D]="");a=(b.method||this[y]||"GET").toUpperCase();if(-1===ma.indexOf(a))throw new g("Invalid request method");this[y]=a;if(b.mode&&-1===na.indexOf(b.mode))throw new g("Invalid request mode");this[p]=b.mode||this[p]||"no-cors";if("no-cors"===this[p]){if(-1===oa.indexOf(this[y]))throw new g("Invalid request method for no-cors");if(""!==this[D])throw new g("Request integrity data is not allowed with no-cors");}if("same-origin"!==this[p]&&"only-if-cached"===this[B])throw new g("Request mode must be same-origin for only-if-cached");
-this[H]=b.redirect||this[H]||"follow";if(-1===pa.indexOf(this[H]))throw new g("Invalid request redirect mode");this[n]="no-cors"===this[p]?G(c,aa):G(c,Y);if(("GET"===this[y]||"HEAD"===this[y])&&e)throw new g("Request body is not allowed for GET or HEAD");"signal"in b&&(f=b.signal);f&&this[A].follow(f);this._initBody(e)}function qa(a,b){var d=G(void 0,b);a.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach(function(a){var b=a.split(":");if(a=b.shift().trim())b=b.join(":").trim(),d.append(a,b)});return d}
-function r(a,b){b||(b={});this[J]="default";this[q]="status"in b?b.status:200;if(200>this[q]||599<this[q])throw new T("Invalid response status");this[U]=200<=this[q]&&300>this[q];if("statusText"in b){var d=b.statusText;for(var e=0,f=d.length,c;e<f;e++)if(c=d.charCodeAt(e),9!==c&&(32>c||255<c||127===c))throw g("Invalid response status text");}else d="OK";this[K]=d;this[n]=G(b.headers,L);this[x]=b.url||"";if(a&&-1<ra.indexOf(this[q]))throw new g("Response body is not allowed with a null body status");
-this[z]=b.is_aborted||!1;this._initBody(a)}if(!c.fetch){var F=c.Array,S=c.ArrayBuffer,ea=c.Object.create,P=c.Object.defineProperties,f=c.Symbol,sa=f.iterator,I=c.Map,T=c.RangeError,g=c.TypeError,u=c.Uint8Array,t=c.Promise,O=c.ReadableStream,V=c.ReadableStreamTee,ja=c.IsReadableStreamDisturbed,fa=c.IsReadableStreamLocked,m=f("body"),N=f("bodyUsed"),B=f("cache"),C=f("credentials"),v=f("guardCallback"),n=f("headers"),D=f("integrity"),l=f("map"),y=f("method"),p=f("mode"),U=f("ok"),H=f("redirect"),q=f("status"),
-K=f("statusText"),J=f("type"),x=f("url"),z=f("is_aborted"),A=f("signal"),Z="accept-charset accept-encoding access-control-request-headers access-control-request-method connection content-length cookie cookie2 date dnt expect host keep-alive origin referer te trailer transfer-encoding upgrade via".split(" "),da=["set-cookie","set-cookie2"],ba=["accept","accept-language","content-language"],ca=["application/x-www-form-urlencoded","multipart/form-data","text/plain"],ka="default no-store reload no-cache force-cache only-if-cached".split(" "),
-la=["omit","same-origin","include"],ma="DELETE GET HEAD OPTIONS POST PUT".split(" "),oa=["GET","HEAD","POST"],na=["same-origin","no-cors","cors"],pa=["follow","error","manual"],ra=[101,204,205,304],ta=[301,302,303,307,308],ua="[object Int8Array];[object Uint8Array];[object Uint8ClampedArray];[object Int16Array];[object Uint16Array];[object Int32Array];[object Uint32Array];[object Float32Array];[object Float64Array]".split(";"),ia=S.isView||function(a){return a&&-1<ua.indexOf(Object.prototype.toString.call(a))};
-h.prototype.append=function(a,b){if(2!==arguments.length)throw g("Invalid parameters to append");a=E(a);b=Q(b);this[v](a,b)||(this[l].has(a)?this[l].set(a,this[l].get(a)+", "+b):this[l].set(a,b))};h.prototype["delete"]=function(a){if(1!==arguments.length)throw g("Invalid parameters to delete");this[v](a,"invalid")||this[l].delete(E(a))};h.prototype.get=function(a){if(1!==arguments.length)throw g("Invalid parameters to get");a=E(a);var b=this[l].get(a);return void 0!==b?b:null};h.prototype.has=function(a){if(1!==
-arguments.length)throw g("Invalid parameters to has");return this[l].has(E(a))};h.prototype.set=function(a,b){if(2!==arguments.length)throw g("Invalid parameters to set");a=E(a);b=Q(b);this[v](a,b)||this[l].set(a,b)};h.prototype.forEach=function(a,b){var d=this;F.from(this[l].entries()).sort().forEach(function(e){a.call(b,e[1],e[0],d)})};h.prototype.keys=function(){return(new I(F.from(this[l].entries()).sort())).keys()};h.prototype.values=function(){return(new I(F.from(this[l].entries()).sort())).values()};
-h.prototype.entries=function(){return(new I(F.from(this[l].entries()).sort())).entries()};h.prototype[sa]=h.prototype.entries;w.prototype.clone=function(){var a=null;null!==this[m]&&(a=V(this[m],!0),this[m]=a[0],a=a[1]);return new w(this,{cloneBody:a,signal:this[A]})};P(w.prototype,{cache:{get:function(){return this[B]}},credentials:{get:function(){return this[C]}},headers:{get:function(){return this[n]}},integrity:{get:function(){return this[D]}},method:{get:function(){return this[y]}},mode:{get:function(){return this[p]}},
-redirect:{get:function(){return this[H]}},url:{get:function(){return this[x]}},signal:{get:function(){return this[A]}}});R.call(w.prototype);R.call(r.prototype);r.prototype.clone=function(){var a=null;null!==this[m]&&(a=V(this[m],!0),this[m]=a[0],a=a[1]);return new r(a,{status:this[q],statusText:this[K],headers:G(this[n],L),url:this[x],is_aborted:this[z]})};P(r.prototype,{headers:{get:function(){return this[n]}},ok:{get:function(){return this[U]}},status:{get:function(){return this[q]}},statusText:{get:function(){return this[K]}},
-type:{get:function(){return this[J]}},url:{get:function(){return this[x]}}});r.error=function(){var a=new r(null);a[n][v]=W;a[J]="error";a[q]=0;a[K]="";return a};r.redirect=function(a,b){if(!FetchInternal.isUrlValid(a,!0))throw new g("Invalid URL for response redirect");void 0===b&&(b=302);if(-1===ta.indexOf(b))throw new T("Invalid status code for response redirect");return new r(null,{status:b,headers:{location:a}})};c.Headers=h;c.Request=w;c.Response=r;c.fetch=function(a,b){return new t(function(d,
-e){var c=!1,f=!1,h=new w(a,b),k=new XMLHttpRequest,l=null;if(h.signal.aborted)return e(new DOMException("Aborted","AbortError"));var m=new O({start:function(a){l=a},cancel:function(a){c=!0;k.abort()}}),p=function(){if(!c){c=!0;m.cancel();if(l)try{ReadableStreamDefaultControllerError(l,new DOMException("Aborted","AbortError"))}catch(va){}setTimeout(function(){try{k.abort()}catch(va){}},0)}};k.onload=function(){l.close()};k.onreadystatechange=function(){if(k.readyState===k.HEADERS_RECEIVED){var a={status:k.status,
-statusText:k.statusText,headers:qa(k.getAllResponseHeaders()||"",L)};a.url="responseURL"in k?k.responseURL:a.headers.get("X-Request-URL");try{var b=new r(m,a);h[A].addEventListener("abort",function(){b[z]=!0;p();e(new DOMException("Aborted","AbortError"))});b[J]=f?"cors":"basic";d(b)}catch(wa){e(wa)}}};k.onerror=function(){l.error(new g("Network request failed"));e(new g("Network request failed"))};k.ontimeout=function(){l.error(new g("Network request failed"));e(new g("Network request failed"))};
-k.open(h.method,h.url,!0);"include"===h.credentials&&(k.withCredentials=!0);h.headers.forEach(function(a,b){k.setRequestHeader(b,a)});var n=function(a){c||l.enqueue(a)},q=function(a){f=a};null===h.body?k.fetch(n,q,null):M(h).then(function(a){k.fetch(n,q,a)})})};c.fetch.polyfill=!0}})(this);
\ No newline at end of file
+'use strict';(function(h){function J(a){"string"!==typeof a&&(a=String(a));if(/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(a))throw new e("Invalid character in header field name");return a.toLowerCase()}function X(a){"string"!==typeof a&&(a=String(a));var b;var c=0;for(b=a.length;c<b;c++){var d=a.charCodeAt(c);if(9!==d&&10!==d&&13!==d&&32!==d)break}for(b=a.length-1;b>c&&(d=a.charCodeAt(b),9===d||10===d||13===d||32===d);b--);a=a.substring(c,b+1);c=0;for(b=a.length;c<b;c++)if(d=a.charCodeAt(c),256<=d||0===d||
+10===d||13===d)throw new e("Invalid character in header field value");return a}function fa(a,b){throw new e("Immutable header cannot be modified");}function ha(a,b){return!1}function ia(a,b){a=a.toLowerCase();return-1<ja.indexOf(a)||a.startsWith("proxy-")||a.startsWith("sec-")?!0:!1}function ka(a,b){a=a.toLowerCase();return-1<la.indexOf(a)||"content-type"===a&&(b=b.split(";")[0].toLowerCase(),-1<ma.indexOf(b))?!1:!0}function S(a,b){return-1<na.indexOf(a.toLowerCase())?!0:!1}function n(a){this[p]=
+new P;void 0===this[A]&&(this[A]=ha);if(void 0!==a){if(null===a||"object"!==typeof a)throw new e("Constructing Headers with invalid parameters");a instanceof n?a.forEach(function(b,c){this.append(c,b)},this):K.isArray(a)?a.forEach(function(b){if(2!==b.length)throw new e("Constructing Headers with invalid parameters");this.append(b[0],b[1])},this):Object.getOwnPropertyNames(a).forEach(function(b){this.append(b,a[b])},this)}}function L(a,b){var c=oa(n.prototype);c[A]=b;n.call(c,a);return c}function T(a){if(a.bodyUsed)return z.reject(new e("Body was already read"));
+if(null===a.body)return z.resolve(new v(0));if(pa(a.body))return z.reject(new e("ReadableStream was already locked"));var b=a.body.getReader(),c=[],d=0;return b.read().then(function q(f){if(f.done){if(0===c.length)f=new v(0);else if(1===c.length)f=new v(c[0].buffer);else{f=new v(d);for(var g=0,w=c.length,M=0;g<w;g++)f.set(c[g],M),M+=c[g].length}return f}return f.value instanceof v?(d+=f.value.length,c.push(f.value),b.read().then(q)):z.reject(new e("Invalid stream data type"))})}function Y(){this._initBody=
+function(a){this[U]=!1;this[r]=null===a||void 0===a?null:a instanceof V?a:new V({start:function(b){if(a)if("string"===typeof a)b.enqueue(FetchInternal.encodeToUTF8(a));else if(Z.prototype.isPrototypeOf(a))b.enqueue(new v(a.slice(0)));else if(qa(a)){var c=new v(a.buffer);c=v.from(c.slice(a.byteOffset,a.byteLength+1));b.enqueue(c)}else if(a instanceof Blob)b.enqueue(new v(FetchInternal.blobToArrayBuffer(a)));else throw new e("Unsupported BodyInit type");b.close()}});this[x].get("content-type")||("string"===
+typeof a?this[x].set("content-type","text/plain;charset=UTF-8"):a instanceof Blob&&""!==a.type&&this[x].set("content-type",a.type))};W(this,{body:{get:function(){return this[r]}},bodyUsed:{get:function(){return this[U]?!0:this[r]?!!ra(this[r]):!1}}});this.arrayBuffer=function(){return this[E]?z.reject(new DOMException("Aborted","AbortError")):T(this).then(function(a){return a.buffer})};this.text=function(){return this[E]?z.reject(new DOMException("Aborted","AbortError")):T(this).then(function(a){return FetchInternal.decodeFromUTF8(a)})};
+this.json=function(){return this[E]?z.reject(new DOMException("Aborted","AbortError")):this.text().then(JSON.parse)};return this}function B(a,b){var c=void 0!==b&&null!==b&&void 0===b.cloneBody;b=b||{};var d=b.body||b.cloneBody;""===b.body&&(d="");var l=b.headers,f=new AbortController;this[F]=f.signal;f=null;if(a instanceof B)this[C]=a.url,this[G]=a.cache,this[H]=a.credentials,void 0===l&&(l=a.headers),this[I]=a.integrity,this[D]=a.method,this[t]=a.mode,c&&"navigate"===this[t]&&(this[t]="same-origin"),
+this[N]=a.redirect,d||null===a.body||(d=a.body,a[U]=!0),f=a[F];else{this[C]=String(a);if(!FetchInternal.isUrlValid(this[C],!1))throw new e("Invalid request URL");this[t]="cors";this[H]="same-origin"}if(void 0!==b.window&&null!==b.window)throw new e("Invalid request window");this[G]=b.cache||this[G]||"default";if(-1===sa.indexOf(this[G]))throw new e("Invalid request cache mode");this[H]=b.credentials||this[H]||"same-origin";if(-1===ta.indexOf(this[H]))throw new e("Invalid request credentials");void 0!==
+b.integrity?this[I]=b.integrity:void 0===this[I]&&(this[I]="");a=(b.method||this[D]||"GET").toUpperCase();if(-1===ua.indexOf(a))throw new e("Invalid request method");this[D]=a;if(b.mode&&-1===va.indexOf(b.mode))throw new e("Invalid request mode");this[t]=b.mode||this[t]||"no-cors";if("no-cors"===this[t]){if(-1===wa.indexOf(this[D]))throw new e("Invalid request method for no-cors");if(""!==this[I])throw new e("Request integrity data is not allowed with no-cors");}if("same-origin"!==this[t]&&"only-if-cached"===
+this[G])throw new e("Request mode must be same-origin for only-if-cached");this[N]=b.redirect||this[N]||"follow";if(-1===xa.indexOf(this[N]))throw new e("Invalid request redirect mode");this[x]="no-cors"===this[t]?L(l,ka):L(l,ia);if(("GET"===this[D]||"HEAD"===this[D])&&d)throw new e("Request body is not allowed for GET or HEAD");"signal"in b&&(f=b.signal);f&&this[F].follow(f);this._initBody(d)}function ya(a,b){var c=L(void 0,b);a.replace(/\r?\n[\t ]+/g," ").split(/\r?\n/).forEach(function(d){var l=
+d.split(":");if(d=l.shift().trim())l=l.join(":").trim(),c.append(d,l)});return c}function u(a,b){b||(b={});this[Q]="default";this[y]="status"in b?b.status:200;if(200>this[y]||599<this[y])throw new aa("Invalid response status");this[ba]=200<=this[y]&&300>this[y];if("statusText"in b){var c=b.statusText;for(var d=0,l=c.length,f;d<l;d++)if(f=c.charCodeAt(d),9!==f&&(32>f||255<f||127===f))throw e("Invalid response status text");}else c="OK";this[R]=c;this[x]=L(b.headers,S);this[C]=b.url||"";if(a&&-1<za.indexOf(this[y]))throw new e("Response body is not allowed with a null body status");
+this[E]=b.is_aborted||!1;this._initBody(a)}if(!h.fetch){var K=h.Array,Z=h.ArrayBuffer,oa=h.Object.create,W=h.Object.defineProperties,k=h.Symbol,Aa=k.iterator,P=h.Map,aa=h.RangeError,e=h.TypeError,v=h.Uint8Array,z=h.Promise,V=h.ReadableStream,ca=h.ReadableStreamTee,ra=h.IsReadableStreamDisturbed,pa=h.IsReadableStreamLocked,r=k("body"),U=k("bodyUsed"),G=k("cache"),H=k("credentials"),A=k("guardCallback"),x=k("headers"),I=k("integrity"),p=k("map"),D=k("method"),t=k("mode"),ba=k("ok"),N=k("redirect"),
+y=k("status"),R=k("statusText"),Q=k("type"),C=k("url"),E=k("is_aborted"),F=k("signal"),ja="accept-charset accept-encoding access-control-request-headers access-control-request-method connection content-length cookie cookie2 date dnt expect host keep-alive origin referer te trailer transfer-encoding upgrade via".split(" "),na=["set-cookie","set-cookie2"],la=["accept","accept-language","content-language"],ma=["application/x-www-form-urlencoded","multipart/form-data","text/plain"],sa="default no-store reload no-cache force-cache only-if-cached".split(" "),
+ta=["omit","same-origin","include"],ua="DELETE GET HEAD OPTIONS POST PUT".split(" "),wa=["GET","HEAD","POST"],va=["same-origin","no-cors","cors"],xa=["follow","error","manual"],za=[101,204,205,304],Ba=[301,302,303,307,308],Ca="[object Int8Array];[object Uint8Array];[object Uint8ClampedArray];[object Int16Array];[object Uint16Array];[object Int32Array];[object Uint32Array];[object Float32Array];[object Float64Array]".split(";"),qa=Z.isView||function(a){return a&&-1<Ca.indexOf(Object.prototype.toString.call(a))};
+n.prototype.append=function(a,b){if(2!==arguments.length)throw e("Invalid parameters to append");a=J(a);b=X(b);this[A](a,b)||(this[p].has(a)?this[p].set(a,this[p].get(a)+", "+b):this[p].set(a,b))};n.prototype["delete"]=function(a){if(1!==arguments.length)throw e("Invalid parameters to delete");this[A](a,"invalid")||this[p].delete(J(a))};n.prototype.get=function(a){if(1!==arguments.length)throw e("Invalid parameters to get");a=J(a);var b=this[p].get(a);return void 0!==b?b:null};n.prototype.has=function(a){if(1!==
+arguments.length)throw e("Invalid parameters to has");return this[p].has(J(a))};n.prototype.set=function(a,b){if(2!==arguments.length)throw e("Invalid parameters to set");a=J(a);b=X(b);this[A](a,b)||this[p].set(a,b)};n.prototype.forEach=function(a,b){var c=this;K.from(this[p].entries()).sort().forEach(function(d){a.call(b,d[1],d[0],c)})};n.prototype.keys=function(){return(new P(K.from(this[p].entries()).sort())).keys()};n.prototype.values=function(){return(new P(K.from(this[p].entries()).sort())).values()};
+n.prototype.entries=function(){return(new P(K.from(this[p].entries()).sort())).entries()};n.prototype[Aa]=n.prototype.entries;B.prototype.clone=function(){var a=null;null!==this[r]&&(a=ca(this[r],!0),this[r]=a[0],a=a[1]);return new B(this,{cloneBody:a,signal:this[F]})};W(B.prototype,{cache:{get:function(){return this[G]}},credentials:{get:function(){return this[H]}},headers:{get:function(){return this[x]}},integrity:{get:function(){return this[I]}},method:{get:function(){return this[D]}},mode:{get:function(){return this[t]}},
+redirect:{get:function(){return this[N]}},url:{get:function(){return this[C]}},signal:{get:function(){return this[F]}}});Y.call(B.prototype);Y.call(u.prototype);u.prototype.clone=function(){var a=null;null!==this[r]&&(a=ca(this[r],!0),this[r]=a[0],a=a[1]);return new u(a,{status:this[y],statusText:this[R],headers:L(this[x],S),url:this[C],is_aborted:this[E]})};W(u.prototype,{headers:{get:function(){return this[x]}},ok:{get:function(){return this[ba]}},status:{get:function(){return this[y]}},statusText:{get:function(){return this[R]}},
+type:{get:function(){return this[Q]}},url:{get:function(){return this[C]}}});u.error=function(){var a=new u(null);a[x][A]=fa;a[Q]="error";a[y]=0;a[R]="";return a};u.redirect=function(a,b){if(!FetchInternal.isUrlValid(a,!0))throw new e("Invalid URL for response redirect");void 0===b&&(b=302);if(-1===Ba.indexOf(b))throw new aa("Invalid status code for response redirect");return new u(null,{status:b,headers:{location:a}})};h.Headers=n;h.Request=B;h.Response=u;h.fetch=function(a,b){return new z(function(c,
+d){var l=!1,f=!1,q=new B(a,b),g=new XMLHttpRequest,w=null;if(q.signal.aborted)return d(new DOMException("Aborted","AbortError"));var M=new V({start:function(m){w=m},cancel:function(m){l=!0;g.abort()}}),Da=function(){if(!l){l=!0;M.cancel();if(w)try{ReadableStreamDefaultControllerError(w,new DOMException("Aborted","AbortError"))}catch(m){}setTimeout(function(){try{g.abort()}catch(m){}},0)}};g.onload=function(){w.close()};g.onreadystatechange=function(){if(g.readyState===g.HEADERS_RECEIVED){var m={status:g.status,
+statusText:g.statusText,headers:ya(g.getAllResponseHeaders()||"",S)};m.url="responseURL"in g?g.responseURL:m.headers.get("X-Request-URL");try{var O=new u(M,m);q[F].addEventListener("abort",function(){O[E]=!0;Da();d(new DOMException("Aborted","AbortError"))});O[Q]=f?"cors":"basic";c(O)}catch(Ea){d(Ea)}}};g.onerror=function(){w.error(new e("Network request failed"));d(new e("Network request failed"))};g.ontimeout=function(){w.error(new e("Network request failed"));d(new e("Network request failed"))};
+g.open(q.method,q.url,!0);"include"===q.credentials&&(g.withCredentials=!0);q.headers.forEach(function(m,O){g.setRequestHeader(O,m)});var da=function(m){l||w.enqueue(m)},ea=function(m){f=m};null===q.body?g.fetch(da,ea,null):T(q).then(function(m){g.fetch(da,ea,m)})})};h.fetch.polyfill=!0}})(this);
\ No newline at end of file
diff --git a/src/cobalt/fetch/fetch.js b/src/cobalt/fetch/fetch.js
index b5de929..8c8162b 100644
--- a/src/cobalt/fetch/fetch.js
+++ b/src/cobalt/fetch/fetch.js
@@ -434,7 +434,11 @@
     } else if (ArrayBuffer.prototype.isPrototypeOf(data)) {
       controller.enqueue(new Uint8Array(data.slice(0)))
     } else if (isArrayBufferView(data)) {
-      controller.enqueue(new Uint8Array(data.buffer.slice(0)))
+      // View as bytes
+      const asBytes = new Uint8Array(data.buffer);
+      // slice and copy
+      var byteSlice = Uint8Array.from(asBytes.slice(data.byteOffset, data.byteLength + 1));
+      controller.enqueue(byteSlice);
     } else if (data instanceof Blob) {
       controller.enqueue(new Uint8Array(FetchInternal.blobToArrayBuffer(data)))
     } else {
@@ -529,6 +533,7 @@
                   init.cloneBody === undefined
     init = init || {}
     var body = init.body || init.cloneBody
+    if(init.body === '') body = '';
     var headersInit = init.headers
 
     // AbortSignal cannot be constructed directly, so create a temporary
diff --git a/src/cobalt/h5vcc/h5vcc_updater.cc b/src/cobalt/h5vcc/h5vcc_updater.cc
index 4b7a171..bebd3b0 100644
--- a/src/cobalt/h5vcc/h5vcc_updater.cc
+++ b/src/cobalt/h5vcc/h5vcc_updater.cc
@@ -33,7 +33,7 @@
   if (updater_module_->GetUpdaterChannel().compare(channel) != 0 &&
       updater_module_->IsChannelValid(channel)) {
     updater_module_->SetUpdaterChannel(channel);
-    updater_module_->MarkChannelChanged();
+    updater_module_->CompareAndSwapChannelChanged(0, 1);
     updater_module_->RunUpdateCheck();
   }
 }
diff --git a/src/cobalt/layout/paragraph.cc b/src/cobalt/layout/paragraph.cc
index 55ccd1e..a18aecb 100644
--- a/src/cobalt/layout/paragraph.cc
+++ b/src/cobalt/layout/paragraph.cc
@@ -125,17 +125,22 @@
   return start_position;
 }
 
-bool Paragraph::FindBreakPosition(const scoped_refptr<dom::FontList>& used_font,
-                                  int32 start_position, int32 end_position,
-                                  LayoutUnit available_width,
-                                  bool should_collapse_trailing_white_space,
-                                  bool allow_overflow,
-                                  Paragraph::BreakPolicy break_policy,
-                                  int32* break_position,
-                                  LayoutUnit* break_width) {
+bool Paragraph::FindBreakPosition(
+    BaseDirection direction, bool should_attempt_to_wrap,
+    const scoped_refptr<dom::FontList>& used_font, int32 start_position,
+    int32 end_position, LayoutUnit available_width,
+    bool should_collapse_trailing_white_space, bool allow_overflow,
+    Paragraph::BreakPolicy break_policy, int32* break_position,
+    LayoutUnit* break_width) {
   DCHECK(is_closed_);
 
-  *break_position = start_position;
+  DCHECK(direction == base_direction_);
+  if (AreInlineAndScriptDirectionsTheSame(direction, start_position) ||
+      should_attempt_to_wrap) {
+    *break_position = start_position;
+  } else {
+    *break_position = end_position;
+  }
   *break_width = LayoutUnit();
 
   // If overflow isn't allowed and there is no available width, then there is
@@ -165,9 +170,10 @@
     // |break_width| will be updated with the position of the last available
     // break position.
     FindIteratorBreakPosition(
-        used_font, line_break_iterator_, start_position, end_position,
-        available_width, should_collapse_trailing_white_space,
-        allow_normal_overflow, break_position, break_width);
+        direction, should_attempt_to_wrap, used_font, line_break_iterator_,
+        start_position, end_position, available_width,
+        should_collapse_trailing_white_space, allow_normal_overflow,
+        break_position, break_width);
   }
 
   // If break word is the break policy, attempt to break unbreakable "words" at
@@ -177,20 +183,39 @@
   if (break_policy == kBreakPolicyBreakWord) {
     // Only continue allowing overflow if the break position has not moved from
     // start, meaning that no normal break positions were found.
-    allow_overflow = allow_overflow && (*break_position == start_position);
+    if (AreInlineAndScriptDirectionsTheSame(direction, start_position) ||
+        should_attempt_to_wrap) {
+      allow_overflow = allow_overflow && (*break_position == start_position);
+    } else {
+      allow_overflow = allow_overflow && (*break_position == end_position);
+    }
 
     // Find the last available break-word break position. |break_position| and
     // |break_width| will be updated with the position of the last available
     // break position. The search begins at the location of the last normal
     // break position that fit within the available width.
-    FindIteratorBreakPosition(
-        used_font, character_break_iterator_, *break_position, end_position,
-        available_width, false, allow_overflow, break_position, break_width);
+    if (AreInlineAndScriptDirectionsTheSame(direction, start_position) ||
+        should_attempt_to_wrap) {
+      FindIteratorBreakPosition(direction, should_attempt_to_wrap, used_font,
+                                character_break_iterator_, *break_position,
+                                end_position, available_width, false,
+                                allow_overflow, break_position, break_width);
+    } else {
+      FindIteratorBreakPosition(direction, should_attempt_to_wrap, used_font,
+                                character_break_iterator_, start_position,
+                                *break_position, available_width, false,
+                                allow_overflow, break_position, break_width);
+    }
   }
 
   // No usable break position was found if the break position has not moved
   // from the start position.
-  return *break_position > start_position;
+  if (AreInlineAndScriptDirectionsTheSame(direction, start_position) ||
+      should_attempt_to_wrap) {
+    return *break_position > start_position;
+  } else {
+    return *break_position < end_position;
+  }
 }
 
 int32 Paragraph::GetNextBreakPosition(int32 position,
@@ -268,6 +293,12 @@
   return (GetBidiLevel(position) % 2) == 1;
 }
 
+bool Paragraph::AreInlineAndScriptDirectionsTheSame(BaseDirection direction,
+                                                    int32 position) const {
+  return ((direction == kLeftToRightBaseDirection && !IsRTL(position)) ||
+          (direction == kRightToLeftBaseDirection && IsRTL(position)));
+}
+
 bool Paragraph::IsCollapsibleWhiteSpace(int32 position) const {
   // Only check for the space character. Other collapsible white space
   // characters will have already been converted into the space characters and
@@ -338,6 +369,7 @@
 }
 
 void Paragraph::FindIteratorBreakPosition(
+    BaseDirection direction, bool should_attempt_to_wrap,
     const scoped_refptr<dom::FontList>& used_font,
     icu::BreakIterator* const break_iterator, int32 start_position,
     int32 end_position, LayoutUnit available_width,
@@ -347,19 +379,37 @@
   // position. Continue until TryIncludeSegmentWithinAvailableWidth() returns
   // false, indicating that no more segments can be included.
   break_iterator->setText(unicode_text_);
-  for (int32 segment_end = break_iterator->following(start_position);
-       segment_end != icu::BreakIterator::DONE && segment_end < end_position;
-       segment_end = break_iterator->next()) {
-    if (!TryIncludeSegmentWithinAvailableWidth(
-            used_font, *break_position, segment_end, available_width,
-            should_collapse_trailing_white_space, &allow_overflow,
-            break_position, break_width)) {
-      break;
+  if (AreInlineAndScriptDirectionsTheSame(direction, start_position) ||
+      should_attempt_to_wrap) {
+    for (int32 segment_end = break_iterator->following(start_position);
+         segment_end != icu::BreakIterator::DONE && segment_end < end_position;
+         segment_end = break_iterator->next()) {
+      if (!TryIncludeSegmentWithinAvailableWidth(
+              direction, should_attempt_to_wrap, used_font, *break_position,
+              segment_end, available_width,
+              should_collapse_trailing_white_space, &allow_overflow,
+              break_position, break_width)) {
+        break;
+      }
+    }
+  } else {
+    for (int32 segment_begin = break_iterator->preceding(end_position);
+         segment_begin != icu::BreakIterator::DONE &&
+         segment_begin > start_position;
+         segment_begin = break_iterator->previous()) {
+      if (!TryIncludeSegmentWithinAvailableWidth(
+              direction, should_attempt_to_wrap, used_font, segment_begin,
+              *break_position, available_width,
+              should_collapse_trailing_white_space, &allow_overflow,
+              break_position, break_width)) {
+        break;
+      }
     }
   }
 }
 
 bool Paragraph::TryIncludeSegmentWithinAvailableWidth(
+    BaseDirection direction, bool should_attempt_to_wrap,
     const scoped_refptr<dom::FontList>& used_font, int32 segment_start,
     int32 segment_end, LayoutUnit available_width,
     bool should_collapse_trailing_white_space, bool* allow_overflow,
@@ -388,7 +438,12 @@
     return false;
   }
 
-  *break_position = segment_end;
+  if (AreInlineAndScriptDirectionsTheSame(direction, segment_start) ||
+      should_attempt_to_wrap) {
+    *break_position = segment_end;
+  } else {
+    *break_position = segment_start;
+  }
   *break_width += segment_width;
 
   if (*allow_overflow) {
diff --git a/src/cobalt/layout/paragraph.h b/src/cobalt/layout/paragraph.h
index 3feb5d8..d64233c 100644
--- a/src/cobalt/layout/paragraph.h
+++ b/src/cobalt/layout/paragraph.h
@@ -111,7 +111,8 @@
   // substring coming before |break_position|.
   //
   // Returns false if no usable break position was found.
-  bool FindBreakPosition(const scoped_refptr<dom::FontList>& used_font,
+  bool FindBreakPosition(BaseDirection direction, bool should_attempt_to_wrap,
+                         const scoped_refptr<dom::FontList>& used_font,
                          int32 start_position, int32 end_position,
                          LayoutUnit available_width,
                          bool should_collapse_trailing_white_space,
@@ -136,6 +137,8 @@
 
   int GetBidiLevel(int32 position) const;
   bool IsRTL(int32 position) const;
+  bool AreInlineAndScriptDirectionsTheSame(BaseDirection direction,
+                                           int32 position) const;
   bool IsCollapsibleWhiteSpace(int32 position) const;
   bool GetNextRunPosition(int32 position, int32* next_run_position) const;
   int32 GetTextEndPosition() const;
@@ -174,7 +177,9 @@
   // that first overflowing segment will be included. The parameter
   // |break_width| indicates the width of the portion of the substring coming
   // before |break_position|.
-  void FindIteratorBreakPosition(const scoped_refptr<dom::FontList>& used_font,
+  void FindIteratorBreakPosition(BaseDirection direction,
+                                 bool should_attempt_to_wrap,
+                                 const scoped_refptr<dom::FontList>& used_font,
                                  icu::BreakIterator* const break_iterator,
                                  int32 start_position, int32 end_position,
                                  LayoutUnit available_width,
@@ -197,6 +202,7 @@
   // of false does not guarantee that the segment was not included, but simply
   // that no additional segments can be included.
   bool TryIncludeSegmentWithinAvailableWidth(
+      BaseDirection direction, bool should_attempt_to_wrap,
       const scoped_refptr<dom::FontList>& used_font, int32 start_position,
       int32 end_position, LayoutUnit available_width,
       bool should_collapse_trailing_white_space, bool* allow_overflow,
diff --git a/src/cobalt/layout/text_box.cc b/src/cobalt/layout/text_box.cc
index 4507080b..f1bd926 100644
--- a/src/cobalt/layout/text_box.cc
+++ b/src/cobalt/layout/text_box.cc
@@ -42,7 +42,9 @@
       paragraph_(paragraph),
       text_start_position_(text_start_position),
       text_end_position_(text_end_position),
+      truncated_text_start_position_(text_start_position),
       truncated_text_end_position_(text_end_position),
+      previous_truncated_text_start_position_(text_start_position),
       previous_truncated_text_end_position_(text_end_position),
       truncated_text_offset_from_left_(0),
       used_font_(used_style_provider->GetUsedFontList(
@@ -237,12 +239,16 @@
 }
 
 void TextBox::DoPreEllipsisPlacementProcessing() {
+  previous_truncated_text_start_position_ = truncated_text_start_position_;
   previous_truncated_text_end_position_ = truncated_text_end_position_;
+  truncated_text_start_position_ = text_start_position_;
   truncated_text_end_position_ = text_end_position_;
 }
 
 void TextBox::DoPostEllipsisPlacementProcessing() {
-  if (previous_truncated_text_end_position_ != truncated_text_end_position_) {
+  if (previous_truncated_text_start_position_ !=
+          truncated_text_start_position_ ||
+      previous_truncated_text_end_position_ != truncated_text_end_position_) {
     InvalidateRenderTreeNodesOfBoxAndAncestors();
   }
 }
@@ -397,7 +403,7 @@
     // color is animated, in which case it could become non-transparent.
     if (used_color.a() > 0.0f || is_color_animated ||
         text_shadow != cssom::KeywordValue::GetNone()) {
-      int32 text_start_position = GetNonCollapsedTextStartPosition();
+      int32 text_start_position = GetVisibleTextStartPosition();
       int32 text_length = GetVisibleTextLength();
 
       scoped_refptr<render_tree::GlyphBuffer> glyph_buffer =
@@ -481,7 +487,12 @@
   // If the ellipsis has already been placed, then the text is fully truncated
   // by the ellipsis.
   if (*is_placed) {
-    truncated_text_end_position_ = text_start_position_;
+    if (paragraph_->AreInlineAndScriptDirectionsTheSame(base_direction,
+                                                        text_start_position_)) {
+      truncated_text_end_position_ = text_start_position_;
+    } else {
+      truncated_text_start_position_ = text_end_position_;
+    }
     return;
   }
 
@@ -510,8 +521,8 @@
   // text box. Otherwise, it can only appear after the first character
   // (https://www.w3.org/TR/css3-ui/#propdef-text-overflow).
   if (paragraph_->FindBreakPosition(
-          used_font_, start_position, end_position, desired_content_offset,
-          false, !(*is_placement_requirement_met),
+          base_direction, false, used_font_, start_position, end_position,
+          desired_content_offset, false, !(*is_placement_requirement_met),
           Paragraph::kBreakPolicyBreakWord, &found_position, &found_offset)) {
     // A usable break position was found. Calculate the placed offset using the
     // the break position's distance from the content box's start edge. In the
@@ -525,7 +536,12 @@
     } else {
       *placed_offset = content_box_start_offset + found_offset;
     }
-    truncated_text_end_position_ = found_position;
+    if (paragraph_->AreInlineAndScriptDirectionsTheSame(base_direction,
+                                                        start_position)) {
+      truncated_text_end_position_ = found_position;
+    } else {
+      truncated_text_start_position_ = found_position;
+    }
     // An acceptable break position was not found. If the placement requirement
     // was already met prior to this box, then the ellipsis doesn't require a
     // character from this box to appear prior to its position, so simply place
@@ -533,7 +549,12 @@
   } else if (is_placement_requirement_met) {
     *placed_offset =
         GetMarginBoxStartEdgeOffsetFromContainingBlock(base_direction);
-    truncated_text_end_position_ = text_start_position_;
+    if (paragraph_->AreInlineAndScriptDirectionsTheSame(base_direction,
+                                                        start_position)) {
+      truncated_text_end_position_ = text_start_position_;
+    } else {
+      truncated_text_start_position_ = text_end_position_;
+    }
     // The placement requirement has not already been met. Given that an
     // acceptable break position was not found within the text, the ellipsis can
     // only be placed at the end edge of the box.
@@ -603,7 +624,8 @@
       // fits within the available width. Overflow is never allowed.
       LayoutUnit wrap_width;
       if (!paragraph_->FindBreakPosition(
-              used_font_, start_position, text_end_position_, available_width,
+              paragraph_->base_direction(), true, used_font_, start_position,
+              text_end_position_, available_width,
               should_collapse_trailing_white_space, false, break_policy,
               &wrap_position, &wrap_width)) {
         // If no break position is found, but the line existence is already
@@ -720,19 +742,24 @@
                                            Paragraph::kVisualTextOrder);
 }
 
+int32 TextBox::GetVisibleTextStartPosition() const {
+  return std::max(GetNonCollapsedTextStartPosition(),
+                  truncated_text_start_position_);
+}
+
 int32 TextBox::GetVisibleTextEndPosition() const {
   return std::min(GetNonCollapsedTextEndPosition(),
                   truncated_text_end_position_);
 }
 
 int32 TextBox::GetVisibleTextLength() const {
-  return GetVisibleTextEndPosition() - GetNonCollapsedTextStartPosition();
+  return GetVisibleTextEndPosition() - GetVisibleTextStartPosition();
 }
 
 bool TextBox::HasVisibleText() const { return GetVisibleTextLength() > 0; }
 
 std::string TextBox::GetVisibleText() const {
-  return paragraph_->RetrieveUtf8SubString(GetNonCollapsedTextStartPosition(),
+  return paragraph_->RetrieveUtf8SubString(GetVisibleTextStartPosition(),
                                            GetVisibleTextEndPosition(),
                                            Paragraph::kVisualTextOrder);
 }
diff --git a/src/cobalt/layout/text_box.h b/src/cobalt/layout/text_box.h
index c2edcc6..855a0a2 100644
--- a/src/cobalt/layout/text_box.h
+++ b/src/cobalt/layout/text_box.h
@@ -133,6 +133,7 @@
   int32 GetNonCollapsibleTextLength() const;
   std::string GetNonCollapsibleText() const;
 
+  int32 GetVisibleTextStartPosition() const;
   int32 GetVisibleTextEndPosition() const;
   int32 GetVisibleTextLength() const;
   bool HasVisibleText() const;
@@ -145,7 +146,7 @@
   const scoped_refptr<Paragraph> paragraph_;
   // The position within the paragraph where the text contained in this box
   // begins.
-  const int32 text_start_position_;
+  int32 text_start_position_;
   // The position within the paragraph where the text contained in this box
   // ends.
   int32 text_end_position_;
@@ -154,11 +155,13 @@
   // "Implementations must hide characters and atomic inline-level elements at
   // the applicable edge(s) of the line as necessary to fit the ellipsis."
   //   https://www.w3.org/TR/css3-ui/#propdef-text-overflow
+  int32 truncated_text_start_position_;
   int32 truncated_text_end_position_;
-  // Tracking of the previous value of |truncated_text_end_position_|, which
+  // Tracking of the previous value of the truncated text position, which
   // allows for determination of whether or not the value changed during
   // ellipsis placement. When this occurs, the cached render tree nodes of this
   // box and its ancestors are invalidated.
+  int32 previous_truncated_text_start_position_;
   int32 previous_truncated_text_end_position_;
   // The horizontal offset to apply to rendered text as a result of an ellipsis
   // truncating the text. This value can be non-zero when the text box is in a
diff --git a/src/cobalt/layout/topmost_event_target.cc b/src/cobalt/layout/topmost_event_target.cc
index 6316a6b..df2cd6a 100644
--- a/src/cobalt/layout/topmost_event_target.cc
+++ b/src/cobalt/layout/topmost_event_target.cc
@@ -130,18 +130,45 @@
 }
 
 namespace {
+// Return the nearest common ancestor of previous_element and target_element
+scoped_refptr<dom::Element> GetNearestCommonAncestor(
+    scoped_refptr<dom::HTMLElement> previous_element,
+    scoped_refptr<dom::HTMLElement> target_element) {
+  scoped_refptr<dom::Element> nearest_common_ancestor;
+  if (previous_element == target_element) {
+    nearest_common_ancestor = target_element;
+  } else {
+    if (previous_element && target_element) {
+      // Find the nearest common ancestor, if there is any.
+      dom::Document* previous_document = previous_element->node_document();
+      // The elements only have a common ancestor if they are both in the same
+      // document.
+      if (previous_document &&
+          previous_document == target_element->node_document()) {
+        // The nearest ancestor of the target element that is already
+        // designated is the nearest common ancestor of it and the previous
+        // element.
+        nearest_common_ancestor = target_element;
+        while (nearest_common_ancestor &&
+               nearest_common_ancestor->AsHTMLElement() &&
+               !nearest_common_ancestor->AsHTMLElement()->IsDesignated()) {
+          nearest_common_ancestor = nearest_common_ancestor->parent_element();
+        }
+      }
+    }
+  }
+  return nearest_common_ancestor;
+}
+
 void SendStateChangeLeaveEvents(
     bool is_pointer_event, scoped_refptr<dom::HTMLElement> previous_element,
     scoped_refptr<dom::HTMLElement> target_element,
+    scoped_refptr<dom::Element> nearest_common_ancestor,
     dom::PointerEventInit* event_init) {
   // Send enter/leave/over/out (status change) events when needed.
   if (previous_element != target_element) {
     const scoped_refptr<dom::Window>& view = event_init->view();
 
-    // The enter/leave status change events apply to all ancestors up to the
-    // nearest common ancestor between the previous and current element.
-    scoped_refptr<dom::Element> nearest_common_ancestor;
-
     // Send out and leave events.
     if (previous_element) {
       // LottiePlayer elements may change playback state.
@@ -149,24 +176,9 @@
         previous_element->AsLottiePlayer()->OnUnHover();
       }
 
-      event_init->set_related_target(target_element);
-      // Find the nearest common ancestor, if there is any.
       dom::Document* previous_document = previous_element->node_document();
-      if (previous_document) {
-        if (target_element &&
-            previous_document == target_element->node_document()) {
-          // The nearest ancestor of the current element that is already
-          // designated is the nearest common ancestor of it and the previous
-          // element.
-          nearest_common_ancestor = target_element;
-          while (nearest_common_ancestor &&
-                 nearest_common_ancestor->AsHTMLElement() &&
-                 !nearest_common_ancestor->AsHTMLElement()->IsDesignated()) {
-            nearest_common_ancestor = nearest_common_ancestor->parent_element();
-          }
-        }
-      }
 
+      event_init->set_related_target(target_element);
       if (is_pointer_event) {
         previous_element->DispatchEvent(new dom::PointerEvent(
             base::Tokens::pointerout(), view, *event_init));
@@ -209,15 +221,12 @@
 void SendStateChangeEnterEvents(
     bool is_pointer_event, scoped_refptr<dom::HTMLElement> previous_element,
     scoped_refptr<dom::HTMLElement> target_element,
+    scoped_refptr<dom::Element> nearest_common_ancestor,
     dom::PointerEventInit* event_init) {
   // Send enter/leave/over/out (status change) events when needed.
   if (previous_element != target_element) {
     const scoped_refptr<dom::Window>& view = event_init->view();
 
-    // The enter/leave status change events apply to all ancestors up to the
-    // nearest common ancestor between the previous and current element.
-    scoped_refptr<dom::Element> nearest_common_ancestor;
-
     // Send over and enter events.
     if (target_element) {
       // LottiePlayer elements may change playback state.
@@ -399,8 +408,14 @@
   scoped_refptr<dom::HTMLElement> previous_html_element(
       previous_html_element_weak_);
 
+  // The enter/leave status change events apply to all ancestors up to the
+  // nearest common ancestor between the previous and current element.
+  scoped_refptr<dom::Element> nearest_common_ancestor(
+      GetNearestCommonAncestor(previous_html_element, target_element));
+
   SendStateChangeLeaveEvents(pointer_event, previous_html_element,
-                             target_element, &event_init);
+                             target_element, nearest_common_ancestor,
+                             &event_init);
 
   if (target_element) {
     target_element->DispatchEvent(event);
@@ -424,26 +439,41 @@
     }
   }
 
-  if (target_element && !is_touchpad_event) {
-    // Send the click event if needed, which is not prevented by canceling the
-    // pointerdown event.
-    //   https://www.w3.org/TR/uievents/#event-type-click
-    //   https://www.w3.org/TR/pointerevents/#compatibility-mapping-with-mouse-events
-    if (event_init.button() == 0 &&
-        ((mouse_event->type() == base::Tokens::pointerup()) ||
-         (mouse_event->type() == base::Tokens::mouseup()))) {
+  if (event_init.button() == 0 &&
+      ((mouse_event->type() == base::Tokens::pointerup()) ||
+       (mouse_event->type() == base::Tokens::mouseup()))) {
+    // This is an 'up' event for the last pressed button indicating that no
+    // more buttons are pressed.
+    if (target_element && !is_touchpad_event) {
+      // Send the click event if needed, which is not prevented by canceling
+      // the pointerdown event.
+      //   https://www.w3.org/TR/uievents/#event-type-click
+      //   https://www.w3.org/TR/pointerevents/#compatibility-mapping-with-mouse-events
       target_element->DispatchEvent(
           new dom::MouseEvent(base::Tokens::click(), view, event_init));
     }
+    if (target_element && (pointer_event->pointer_type() != "mouse")) {
+      // If it's not a mouse event, then releasing the last button means
+      // that there is no longer an indicated element.
+      dom::Document* document = target_element->node_document();
+      if (document) {
+        document->SetIndicatedElement(NULL);
+        target_element = NULL;
+      }
+    }
   }
 
   SendStateChangeEnterEvents(pointer_event, previous_html_element,
-                             target_element, &event_init);
+                             target_element, nearest_common_ancestor,
+                             &event_init);
 
   if (target_element) {
-    dom::Document* document = target_element->node_document();
-    if (document) {
-      document->SetIndicatedElement(target_element);
+    // Touchpad input never indicates document elements.
+    if (!is_touchpad_event) {
+      dom::Document* document = target_element->node_document();
+      if (document) {
+        document->SetIndicatedElement(target_element);
+      }
     }
     previous_html_element_weak_ = base::AsWeakPtr(target_element.get());
   } else {
diff --git a/src/cobalt/layout_tests/layout_tests.cc b/src/cobalt/layout_tests/layout_tests.cc
index 4cd46c1..17804c8 100644
--- a/src/cobalt/layout_tests/layout_tests.cc
+++ b/src/cobalt/layout_tests/layout_tests.cc
@@ -207,7 +207,7 @@
   renderer::RenderTreePixelTester::Options pixel_tester_options;
   if (renderer::RenderTreePixelTester::IsReferencePlatform()) {
     // Use stricter tolerances on reference platforms.
-    pixel_tester_options.gaussian_blur_sigma = 3.0f;
+    pixel_tester_options.gaussian_blur_sigma = 3.5f;
   }
   RunTest(GetParam(), graphics_context_, pixel_tester_options);
 }
diff --git a/src/cobalt/layout_tests/testdata/bidi/bidi-paragraphs-should-maintain-proper-ordering-when-split-across-multiple-lines-expected.png b/src/cobalt/layout_tests/testdata/bidi/bidi-paragraphs-should-maintain-proper-ordering-when-split-across-multiple-lines-expected.png
index d0f9074..7e86a49 100644
--- a/src/cobalt/layout_tests/testdata/bidi/bidi-paragraphs-should-maintain-proper-ordering-when-split-across-multiple-lines-expected.png
+++ b/src/cobalt/layout_tests/testdata/bidi/bidi-paragraphs-should-maintain-proper-ordering-when-split-across-multiple-lines-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/bidi/bidi-paragraphs-should-maintain-proper-ordering-when-split-across-multiple-lines.html b/src/cobalt/layout_tests/testdata/bidi/bidi-paragraphs-should-maintain-proper-ordering-when-split-across-multiple-lines.html
index 71a614f..463fedd 100644
--- a/src/cobalt/layout_tests/testdata/bidi/bidi-paragraphs-should-maintain-proper-ordering-when-split-across-multiple-lines.html
+++ b/src/cobalt/layout_tests/testdata/bidi/bidi-paragraphs-should-maintain-proper-ordering-when-split-across-multiple-lines.html
@@ -30,8 +30,12 @@
           لتد2ل3تت4دل5ت6د ط1لتدلنش 123456789 745 ط1لت
           987 لتد 654</span>
   </div>
+  <div class="containing-block" dir="rtl">
+    <span>12لتدل3ت dدلت4دلتد5ل6تد يabطا32ل Single Word
+          1234 ط1لتدلنش ab12دلتد34cd ط1aلتد2ل 3تت489
+          12 ل5ت6745 ط1لتدلنش ط1لaتدلتدتد ط11246 ط1abcd
+          لتد2ل3تت4دل5ت6د ط1لتدلنش 123456789 745 ط1لت
+          987 لتد 654</span>
+  </div>
 </body>
 </html>
-
-</body>
-</html>
\ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/5-2-use-system-fallback-if-no-matching-family-is-found-expected.png b/src/cobalt/layout_tests/testdata/css3-fonts/5-2-use-system-fallback-if-no-matching-family-is-found-expected.png
index 291576a..bf339a6 100644
--- a/src/cobalt/layout_tests/testdata/css3-fonts/5-2-use-system-fallback-if-no-matching-family-is-found-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-fonts/5-2-use-system-fallback-if-no-matching-family-is-found-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/color-emojis-should-render-properly-expected.png b/src/cobalt/layout_tests/testdata/css3-fonts/color-emojis-should-render-properly-expected.png
index 4b3d234..e509aaa 100644
--- a/src/cobalt/layout_tests/testdata/css3-fonts/color-emojis-should-render-properly-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-fonts/color-emojis-should-render-properly-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/color-emojis-should-render-properly.html b/src/cobalt/layout_tests/testdata/css3-fonts/color-emojis-should-render-properly.html
index 22e2cab..e1e8f5b 100644
--- a/src/cobalt/layout_tests/testdata/css3-fonts/color-emojis-should-render-properly.html
+++ b/src/cobalt/layout_tests/testdata/css3-fonts/color-emojis-should-render-properly.html
@@ -29,7 +29,7 @@
     <span>&#x1f648;&#x1f649;&#x1f64a;&#x1f468;&#x1f469;&#x1f3c3;&#x1f40c;</span>
     <span>&#x1f41c;&#x1f490;&#x1f332;&#x1f347;&#x1f95d;&#x1f344;&#x1f354;</span>
     <span>&#x1f35f;&#x1f35e;&#x1f95e;&#x1f32e;&#x1f363;&#x1f361;&#x1f368;</span>
-    <span class="bold-span">&#x1f30e;&#x1f31a;</span>
+    <span class="bold-span">&#x1f30e;&#x1f31a;&#x1f970;</span>
   </div>
 </body>
 </html>
diff --git a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-clip-when-it-overflows-expected.png b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-clip-when-it-overflows-expected.png
index 27b72da..0bbe59c 100644
--- a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-clip-when-it-overflows-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-clip-when-it-overflows-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-ellipsize-each-line-that-overflows-expected.png b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-ellipsize-each-line-that-overflows-expected.png
index 164d112..2e89634 100644
--- a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-ellipsize-each-line-that-overflows-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-ellipsize-each-line-that-overflows-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-ellipsize-overflowing-first-word-expected.png b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-ellipsize-overflowing-first-word-expected.png
index 59aa7b9..4db6e7b 100644
--- a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-ellipsize-overflowing-first-word-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-ellipsize-overflowing-first-word-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-handle-transform-functions-via-matrix-expected.png b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-handle-transform-functions-via-matrix-expected.png
index b311cd1..21d31da 100644
--- a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-handle-transform-functions-via-matrix-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-handle-transform-functions-via-matrix-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-handle-transform-functions-via-rotation-expected.png b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-handle-transform-functions-via-rotation-expected.png
index 4cfa868..85a1acb 100644
--- a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-handle-transform-functions-via-rotation-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-handle-transform-functions-via-rotation-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-not-ellipsize-first-character-on-line-expected.png b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-not-ellipsize-first-character-on-line-expected.png
index 27b72da..0bbe59c 100644
--- a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-not-ellipsize-first-character-on-line-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-not-ellipsize-first-character-on-line-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-properly-position-ellipsis-in-spans-with-margins-expected.png b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-properly-position-ellipsis-in-spans-with-margins-expected.png
index a88d55a..f71519f 100644
--- a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-properly-position-ellipsis-in-spans-with-margins-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-properly-position-ellipsis-in-spans-with-margins-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-retain-background-color-of-ellipsized-span-expected.png b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-retain-background-color-of-ellipsized-span-expected.png
index ae53279..ac84ae0 100644
--- a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-retain-background-color-of-ellipsized-span-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-retain-background-color-of-ellipsized-span-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-use-style-of-containing-block-expected.png b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-use-style-of-containing-block-expected.png
index 5d56a8b..e059ac1 100644
--- a/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-use-style-of-containing-block-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-ui/5-2-ellipsis-should-use-style-of-containing-block-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-ui/5-2-shaped-text-should-accurately-calculate-width-before-ellipsis-expected.png b/src/cobalt/layout_tests/testdata/css3-ui/5-2-shaped-text-should-accurately-calculate-width-before-ellipsis-expected.png
index 7b2d801..8b20b2a 100644
--- a/src/cobalt/layout_tests/testdata/css3-ui/5-2-shaped-text-should-accurately-calculate-width-before-ellipsis-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-ui/5-2-shaped-text-should-accurately-calculate-width-before-ellipsis-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-ui/5-2-text-overflow-ellipsis-should-display-ellipsis-on-overflow-when-overflow-is-hidden-expected.png b/src/cobalt/layout_tests/testdata/css3-ui/5-2-text-overflow-ellipsis-should-display-ellipsis-on-overflow-when-overflow-is-hidden-expected.png
index bf2da54..7fecf85 100644
--- a/src/cobalt/layout_tests/testdata/css3-ui/5-2-text-overflow-ellipsis-should-display-ellipsis-on-overflow-when-overflow-is-hidden-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-ui/5-2-text-overflow-ellipsis-should-display-ellipsis-on-overflow-when-overflow-is-hidden-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-ui/5-2-text-overflow-ellipsis-should-not-be-inherited-expected.png b/src/cobalt/layout_tests/testdata/css3-ui/5-2-text-overflow-ellipsis-should-not-be-inherited-expected.png
index bb3987f..a264063 100644
--- a/src/cobalt/layout_tests/testdata/css3-ui/5-2-text-overflow-ellipsis-should-not-be-inherited-expected.png
+++ b/src/cobalt/layout_tests/testdata/css3-ui/5-2-text-overflow-ellipsis-should-not-be-inherited-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/web-platform-tests/fetch/web_platform_tests.txt b/src/cobalt/layout_tests/testdata/web-platform-tests/fetch/web_platform_tests.txt
index 5c11ed1..72ab7d8 100644
--- a/src/cobalt/layout_tests/testdata/web-platform-tests/fetch/web_platform_tests.txt
+++ b/src/cobalt/layout_tests/testdata/web-platform-tests/fetch/web_platform_tests.txt
@@ -97,12 +97,8 @@
 api/request/request-cache-only-if-cached.html,DISABLE
 api/request/request-cache-reload.html,DISABLE
 api/request/request-clone.sub.html,PASS
-# Fails because Body only supports text, json, and arrayBuffer. See the
-# corresponding tests for Response which have been customized to test only
-# those types.
-api/request/request-consume.html,DISABLE
-# Fails because blob and formData are not supported.
-api/request/request-consume-empty.html,DISABLE
+api/request/request-consume.html,PASS
+api/request/request-consume-empty.html,PASS
 # Disabled due to failing test fixture
 api/request/request-disturbed.html,DISABLE
 api/request/request-error.html,PASS
diff --git a/src/cobalt/layout_tests/web_platform_test_parser.cc b/src/cobalt/layout_tests/web_platform_test_parser.cc
index 864148b..0e1b721 100644
--- a/src/cobalt/layout_tests/web_platform_test_parser.cc
+++ b/src/cobalt/layout_tests/web_platform_test_parser.cc
@@ -126,7 +126,6 @@
                     << "\"" << top_level << "\"";
       return std::vector<WebPlatformTestInfo>();
     }
-    DLOG(INFO) << "[Temporary debugging] javascript engine for parser exiting";
   }
 
   base::FilePath test_dir(GetTestInputRootDirectory()
diff --git a/src/cobalt/media/base/shell_audio_bus.cc b/src/cobalt/media/base/audio_bus.cc
similarity index 87%
rename from src/cobalt/media/base/shell_audio_bus.cc
rename to src/cobalt/media/base/audio_bus.cc
index 5140d01..ea63e7a 100644
--- a/src/cobalt/media/base/shell_audio_bus.cc
+++ b/src/cobalt/media/base/audio_bus.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 
 #include <algorithm>
 #include <limits>
@@ -24,20 +24,17 @@
 
 namespace {
 
-typedef ShellAudioBus::StorageType StorageType;
-typedef ShellAudioBus::SampleType SampleType;
+typedef AudioBus::StorageType StorageType;
+typedef AudioBus::SampleType SampleType;
 
 const float kFloat32ToInt16Factor = 32768.f;
 
-inline void ConvertSample(ShellAudioBus::SampleType src_type,
-                          const uint8* src_ptr,
-                          ShellAudioBus::SampleType dest_type,
-                          uint8* dest_ptr) {
+inline void ConvertSample(AudioBus::SampleType src_type, const uint8* src_ptr,
+                          AudioBus::SampleType dest_type, uint8* dest_ptr) {
   if (src_type == dest_type) {
-    SbMemoryCopy(
-        dest_ptr, src_ptr,
-        src_type == ShellAudioBus::kInt16 ? sizeof(int16) : sizeof(float));
-  } else if (src_type == ShellAudioBus::kFloat32) {
+    SbMemoryCopy(dest_ptr, src_ptr,
+                 src_type == AudioBus::kInt16 ? sizeof(int16) : sizeof(float));
+  } else if (src_type == AudioBus::kFloat32) {
     float sample_in_float = *reinterpret_cast<const float*>(src_ptr);
     int32 sample_in_int32 =
         static_cast<int32>(sample_in_float * kFloat32ToInt16Factor);
@@ -78,8 +75,8 @@
 
 }  // namespace
 
-ShellAudioBus::ShellAudioBus(size_t channels, size_t frames,
-                             SampleType sample_type, StorageType storage_type)
+AudioBus::AudioBus(size_t channels, size_t frames, SampleType sample_type,
+                   StorageType storage_type)
     : channels_(channels),
       frames_(frames),
       sample_type_(sample_type),
@@ -106,7 +103,7 @@
   }
 }
 
-ShellAudioBus::ShellAudioBus(size_t frames, const std::vector<float*>& samples)
+AudioBus::AudioBus(size_t frames, const std::vector<float*>& samples)
     : channels_(samples.size()),
       frames_(frames),
       sample_type_(kFloat32),
@@ -119,7 +116,7 @@
   }
 }
 
-ShellAudioBus::ShellAudioBus(size_t channels, size_t frames, float* samples)
+AudioBus::AudioBus(size_t channels, size_t frames, float* samples)
     : channels_(channels),
       frames_(frames),
       sample_type_(kFloat32),
@@ -129,7 +126,7 @@
   channel_data_.push_back(reinterpret_cast<uint8*>(samples));
 }
 
-ShellAudioBus::ShellAudioBus(size_t frames, const std::vector<int16*>& samples)
+AudioBus::AudioBus(size_t frames, const std::vector<int16*>& samples)
     : channels_(samples.size()),
       frames_(frames),
       sample_type_(kInt16),
@@ -142,7 +139,7 @@
   }
 }
 
-ShellAudioBus::ShellAudioBus(size_t channels, size_t frames, int16* samples)
+AudioBus::AudioBus(size_t channels, size_t frames, int16* samples)
     : channels_(channels),
       frames_(frames),
       sample_type_(kInt16),
@@ -152,7 +149,7 @@
   channel_data_.push_back(reinterpret_cast<uint8*>(samples));
 }
 
-size_t ShellAudioBus::GetSampleSizeInBytes() const {
+size_t AudioBus::GetSampleSizeInBytes() const {
   if (sample_type_ == kInt16) {
     return sizeof(int16);
   }
@@ -160,29 +157,29 @@
   return sizeof(float);
 }
 
-const uint8* ShellAudioBus::interleaved_data() const {
+const uint8* AudioBus::interleaved_data() const {
   DCHECK_EQ(storage_type_, kInterleaved);
   return channel_data_[0];
 }
 
-const uint8* ShellAudioBus::planar_data(size_t channel) const {
+const uint8* AudioBus::planar_data(size_t channel) const {
   DCHECK_LT(channel, channels_);
   DCHECK_EQ(storage_type_, kPlanar);
   return channel_data_[channel];
 }
 
-uint8* ShellAudioBus::interleaved_data() {
+uint8* AudioBus::interleaved_data() {
   DCHECK_EQ(storage_type_, kInterleaved);
   return channel_data_[0];
 }
 
-uint8* ShellAudioBus::planar_data(size_t channel) {
+uint8* AudioBus::planar_data(size_t channel) {
   DCHECK_LT(channel, channels_);
   DCHECK_EQ(storage_type_, kPlanar);
   return channel_data_[channel];
 }
 
-void ShellAudioBus::ZeroFrames(size_t start_frame, size_t end_frame) {
+void AudioBus::ZeroFrames(size_t start_frame, size_t end_frame) {
   DCHECK_LE(start_frame, end_frame);
   DCHECK_LE(end_frame, frames_);
   end_frame = std::min(end_frame, frames_);
@@ -201,7 +198,7 @@
   }
 }
 
-void ShellAudioBus::Assign(const ShellAudioBus& source) {
+void AudioBus::Assign(const AudioBus& source) {
   DCHECK_EQ(channels_, source.channels_);
   if (channels_ != source.channels_) {
     ZeroAllFrames();
@@ -232,8 +229,8 @@
   }
 }
 
-void ShellAudioBus::Assign(const ShellAudioBus& source,
-                           const std::vector<float>& matrix) {
+void AudioBus::Assign(const AudioBus& source,
+                      const std::vector<float>& matrix) {
   DCHECK_EQ(channels() * source.channels(), matrix.size());
   DCHECK_EQ(sample_type_, kFloat32);
   DCHECK_EQ(source.sample_type_, kFloat32);
@@ -258,7 +255,7 @@
 }
 
 template <StorageType SourceStorageType, StorageType DestStorageType>
-void ShellAudioBus::MixFloatSamples(const ShellAudioBus& source) {
+void AudioBus::MixFloatSamples(const AudioBus& source) {
   const size_t frames = std::min(frames_, source.frames_);
 
   if (SourceStorageType == DestStorageType) {
@@ -284,7 +281,7 @@
 }
 
 template <StorageType SourceStorageType, StorageType DestStorageType>
-void ShellAudioBus::MixInt16Samples(const ShellAudioBus& source) {
+void AudioBus::MixInt16Samples(const AudioBus& source) {
   const size_t frames = std::min(frames_, source.frames_);
 
   if (SourceStorageType == DestStorageType) {
@@ -318,7 +315,7 @@
   }
 }
 
-void ShellAudioBus::Mix(const ShellAudioBus& source) {
+void AudioBus::Mix(const AudioBus& source) {
   DCHECK_EQ(channels_, source.channels_);
   DCHECK_EQ(sample_type_, source.sample_type_);
 
@@ -359,8 +356,7 @@
   }
 }
 
-void ShellAudioBus::Mix(const ShellAudioBus& source,
-                        const std::vector<float>& matrix) {
+void AudioBus::Mix(const AudioBus& source, const std::vector<float>& matrix) {
   DCHECK_EQ(channels() * source.channels(), matrix.size());
   DCHECK_EQ(sample_type_, source.sample_type_);
 
@@ -411,7 +407,7 @@
   }
 }
 
-uint8* ShellAudioBus::GetSamplePtr(size_t channel, size_t frame) {
+uint8* AudioBus::GetSamplePtr(size_t channel, size_t frame) {
   DCHECK_LT(channel, channels_);
   DCHECK_LT(frame, frames_);
 
@@ -423,7 +419,7 @@
   }
 }
 
-const uint8* ShellAudioBus::GetSamplePtr(size_t channel, size_t frame) const {
+const uint8* AudioBus::GetSamplePtr(size_t channel, size_t frame) const {
   DCHECK_LT(channel, channels_);
   DCHECK_LT(frame, frames_);
 
diff --git a/src/cobalt/media/base/shell_audio_bus.h b/src/cobalt/media/base/audio_bus.h
similarity index 85%
rename from src/cobalt/media/base/shell_audio_bus.h
rename to src/cobalt/media/base/audio_bus.h
index cdd4668..2b4c369 100644
--- a/src/cobalt/media/base/shell_audio_bus.h
+++ b/src/cobalt/media/base/audio_bus.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_MEDIA_BASE_SHELL_AUDIO_BUS_H_
-#define COBALT_MEDIA_BASE_SHELL_AUDIO_BUS_H_
+#ifndef COBALT_MEDIA_BASE_AUDIO_BUS_H_
+#define COBALT_MEDIA_BASE_AUDIO_BUS_H_
 
 #include <memory>
 #include <vector>
@@ -37,11 +37,11 @@
 // second of such audio contains 48000 frames (96000 samples).
 // Note: This class doesn't do endianness conversions.  It assumes that all data
 // is in the correct endianness.
-class COBALT_EXPORT ShellAudioBus {
+class COBALT_EXPORT AudioBus {
  public:
   // Guaranteed alignment of each channel's data; use 64-byte alignment so it
   // satisfies all our current platforms.  Note that this is only used for
-  // buffers that are allocated and owned by the ShellAudioBus.  We don't
+  // buffers that are allocated and owned by the AudioBus.  We don't
   // enforce alignment for the buffers passed in and extra caution should be
   // taken if they are used as hardware buffer.
   static const size_t kChannelAlignmentInBytes = 64;
@@ -50,12 +50,12 @@
 
   enum StorageType { kInterleaved, kPlanar };
 
-  ShellAudioBus(size_t channels, size_t frames, SampleType sample_type,
-                StorageType storage_type);
-  ShellAudioBus(size_t frames, const std::vector<float*>& samples);
-  ShellAudioBus(size_t channels, size_t frames, float* samples);
-  ShellAudioBus(size_t frames, const std::vector<int16*>& samples);
-  ShellAudioBus(size_t channels, size_t frames, int16* samples);
+  AudioBus(size_t channels, size_t frames, SampleType sample_type,
+           StorageType storage_type);
+  AudioBus(size_t frames, const std::vector<float*>& samples);
+  AudioBus(size_t channels, size_t frames, float* samples);
+  AudioBus(size_t frames, const std::vector<int16*>& samples);
+  AudioBus(size_t channels, size_t frames, int16* samples);
 
   size_t channels() const { return channels_; }
   size_t frames() const { return frames_; }
@@ -84,7 +84,7 @@
   // conversion between different sample types and storage types.  When source
   // has less frames than the destination object, it will only copy these frames
   // and will not fill the rest frames in our buffer with 0.
-  void Assign(const ShellAudioBus& source);
+  void Assign(const AudioBus& source);
 
   // The same as the above function except that this function also does mixing.
   // |matrix| is a |dest.channels()| row * |source.channels()| column matrix in
@@ -96,14 +96,14 @@
   //   + source.sample[source.channels() - 1][frame] *
   //         matrix[channels() * source.channels() + source.channels() - 1];
   // Note: Both objects must have storage type of kFloat32.
-  void Assign(const ShellAudioBus& source, const std::vector<float>& matrix);
+  void Assign(const AudioBus& source, const std::vector<float>& matrix);
 
   // The following functions are the same as the Assign() functions except that
   // they add the calculated samples to the target samples instead of replacing
   // the target samples with the calculated samples.
   // Note: Both objects must have storage type of kFloat32.
-  void Mix(const ShellAudioBus& source);
-  void Mix(const ShellAudioBus& source, const std::vector<float>& matrix);
+  void Mix(const AudioBus& source);
+  void Mix(const AudioBus& source, const std::vector<float>& matrix);
 
  public:
   // The .*ForTypes? functions below are optimized versions that assume what
@@ -134,10 +134,10 @@
   }
 
   template <StorageType SourceStorageType, StorageType DestStorageType>
-  void MixFloatSamples(const ShellAudioBus& source);
+  void MixFloatSamples(const AudioBus& source);
 
   template <StorageType SourceStorageType, StorageType DestStorageType>
-  void MixInt16Samples(const ShellAudioBus& source);
+  void MixInt16Samples(const AudioBus& source);
 
  private:
   void SetFloat32Sample(size_t channel, size_t frame, float sample) {
@@ -160,10 +160,10 @@
   SampleType sample_type_;
   StorageType storage_type_;
 
-  DISALLOW_COPY_AND_ASSIGN(ShellAudioBus);
+  DISALLOW_COPY_AND_ASSIGN(AudioBus);
 };
 
 }  // namespace media
 }  // namespace cobalt
 
-#endif  // COBALT_MEDIA_BASE_SHELL_AUDIO_BUS_H_
+#endif  // COBALT_MEDIA_BASE_AUDIO_BUS_H_
diff --git a/src/cobalt/media/base/pipeline.h b/src/cobalt/media/base/pipeline.h
index f0d37fc..c2bbcfc 100644
--- a/src/cobalt/media/base/pipeline.h
+++ b/src/cobalt/media/base/pipeline.h
@@ -92,7 +92,9 @@
   virtual ~Pipeline() {}
 
   virtual void Suspend() {}
-  virtual void Resume() {}
+  // TODO: This is temporary for supporting background media playback.
+  //       Need to be removed with media refactor.
+  virtual void Resume(PipelineWindow window) {}
 
   // Build a pipeline to using the given filter collection to construct a filter
   // chain, executing |seek_cb| when the initial seek/preroll has completed.
diff --git a/src/cobalt/media/base/sbplayer_pipeline.cc b/src/cobalt/media/base/sbplayer_pipeline.cc
index 4f4f9d1..68abc49 100644
--- a/src/cobalt/media/base/sbplayer_pipeline.cc
+++ b/src/cobalt/media/base/sbplayer_pipeline.cc
@@ -90,7 +90,10 @@
   ~SbPlayerPipeline() override;
 
   void Suspend() override;
-  void Resume() override;
+  // TODO: This is temporary for supporting background media playback.
+  //       Need to be removed with media refactor.
+  void Resume(PipelineWindow window) override;
+
   void Start(Demuxer* demuxer,
              const SetDrmSystemReadyCB& set_drm_system_ready_cb,
              const PipelineStatusCB& ended_cb, const ErrorCB& error_cb,
@@ -175,7 +178,7 @@
   void CallSeekCB(PipelineStatus status);
 
   void SuspendTask(base::WaitableEvent* done_event);
-  void ResumeTask(base::WaitableEvent* done_event);
+  void ResumeTask(PipelineWindow window, base::WaitableEvent* done_event);
 
   // Store the media time retrieved by GetMediaTime so we can cache it as an
   // estimate and avoid calling SbPlayerGetInfo too frequently.
@@ -331,7 +334,7 @@
   waitable_event.Wait();
 }
 
-void SbPlayerPipeline::Resume() {
+void SbPlayerPipeline::Resume(PipelineWindow window) {
   DCHECK(!task_runner_->BelongsToCurrentThread());
 
   base::WaitableEvent waitable_event(
@@ -339,7 +342,8 @@
       base::WaitableEvent::InitialState::NOT_SIGNALED);
   task_runner_->PostTask(FROM_HERE,
                          base::Bind(&SbPlayerPipeline::ResumeTask,
-                                    base::Unretained(this), &waitable_event));
+                                    base::Unretained(this),
+                                    window, &waitable_event));
   waitable_event.Wait();
 }
 
@@ -1314,7 +1318,8 @@
   done_event->Signal();
 }
 
-void SbPlayerPipeline::ResumeTask(base::WaitableEvent* done_event) {
+void SbPlayerPipeline::ResumeTask(PipelineWindow window,
+                                  base::WaitableEvent* done_event) {
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK(done_event);
   DCHECK(suspended_);
@@ -1324,8 +1329,10 @@
     return;
   }
 
+  window_ = window;
+
   if (player_) {
-    player_->Resume();
+    player_->Resume(window);
   }
 
   suspended_ = false;
diff --git a/src/cobalt/media/base/starboard_player.cc b/src/cobalt/media/base/starboard_player.cc
index d23cdab..d7eeb2e 100644
--- a/src/cobalt/media/base/starboard_player.cc
+++ b/src/cobalt/media/base/starboard_player.cc
@@ -140,7 +140,7 @@
 #if SB_HAS(PLAYER_WITH_URL)
       ,
       is_url_based_(false)
-#endif  // SB_HAS(PLAYER_WITH_URL)
+#endif  // SB_HAS(PLAYER_WITH_URL
 {
   DCHECK(!get_decode_target_graphics_context_provider_func_.is_null());
   DCHECK(audio_config.IsValidConfig() || video_config.IsValidConfig());
@@ -450,9 +450,11 @@
   player_ = kSbPlayerInvalid;
 }
 
-void StarboardPlayer::Resume() {
+void StarboardPlayer::Resume(SbWindow window) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
+  window_ = window;
+
   // Check if the player is already resumed.
   if (state_ != kSuspended) {
     DCHECK(SbPlayerIsValid(player_));
@@ -540,16 +542,22 @@
   TRACE_EVENT0("cobalt::media", "StarboardPlayer::CreatePlayer");
   DCHECK(task_runner_->BelongsToCurrentThread());
 
+bool is_visible = SbWindowIsValid(window_);
 #if SB_API_VERSION >= 11
   SbMediaAudioCodec audio_codec = audio_sample_info_.codec;
-  SbMediaVideoCodec video_codec = video_sample_info_.codec;
+  SbMediaVideoCodec video_codec = kSbMediaVideoCodecNone;
+  // TODO: This is temporary for supporting background media playback.
+  //       Need to be removed with media refactor.
+  if (is_visible) {
+    video_codec = video_sample_info_.codec;
+  }
 #else   // SB_API_VERSION >= 11
   SbMediaAudioCodec audio_codec = kSbMediaAudioCodecNone;
   if (audio_config_.IsValidConfig()) {
     audio_codec = MediaAudioCodecToSbMediaAudioCodec(audio_config_.codec());
   }
   SbMediaVideoCodec video_codec = kSbMediaVideoCodecNone;
-  if (video_config_.IsValidConfig()) {
+  if (video_config_.IsValidConfig() && is_visible) {
     video_codec = MediaVideoCodecToSbMediaVideoCodec(video_config_.codec());
   }
 #endif  // SB_API_VERSION >= 11
@@ -562,6 +570,11 @@
   creation_param.drm_system = drm_system_;
   creation_param.audio_sample_info = audio_sample_info_;
   creation_param.video_sample_info = video_sample_info_;
+  // TODO: This is temporary for supporting background media playback.
+  //       Need to be removed with media refactor.
+  if (!is_visible) {
+    creation_param.video_sample_info.codec = kSbMediaVideoCodecNone;
+  }
   creation_param.output_mode = output_mode_;
   DCHECK_EQ(SbPlayerGetPreferredOutputMode(&creation_param), output_mode_);
   player_ = SbPlayerCreate(
@@ -573,6 +586,11 @@
 #else  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
 
   DCHECK(SbPlayerOutputModeSupported(output_mode_, video_codec, drm_system_));
+  // TODO: This is temporary for supporting background media playback.
+  //       Need to be removed with media refactor.
+  if (!is_visible) {
+    DCHECK(audio_codec != kSbMediaAudioCodecNone);
+  }
   player_ = SbPlayerCreate(
       window_, video_codec, audio_codec,
       drm_system_, has_audio ? &audio_sample_info_ : NULL,
diff --git a/src/cobalt/media/base/starboard_player.h b/src/cobalt/media/base/starboard_player.h
index 2b00770..b4c1581 100644
--- a/src/cobalt/media/base/starboard_player.h
+++ b/src/cobalt/media/base/starboard_player.h
@@ -114,7 +114,9 @@
 #endif  // SB_HAS(PLAYER_WITH_URL)
 
   void Suspend();
-  void Resume();
+  // TODO: This is temporary for supporting background media playback.
+  //       Need to be removed with media refactor.
+  void Resume(SbWindow window);
 
   SbDecodeTarget GetCurrentSbDecodeTarget();
   SbPlayerOutputMode GetSbPlayerOutputMode();
@@ -213,7 +215,7 @@
   const GetDecodeTargetGraphicsContextProviderFunc
       get_decode_target_graphics_context_provider_func_;
   scoped_refptr<CallbackHelper> callback_helper_;
-  const SbWindow window_;
+  SbWindow window_;
   SbDrmSystem drm_system_ = kSbDrmSystemInvalid;
   Host* const host_;
   // Consider merge |SbPlayerSetBoundsHelper| into CallbackHelper.
diff --git a/src/cobalt/media/can_play_type_handler.h b/src/cobalt/media/can_play_type_handler.h
index 40d1ea6..00b94f1 100644
--- a/src/cobalt/media/can_play_type_handler.h
+++ b/src/cobalt/media/can_play_type_handler.h
@@ -25,9 +25,10 @@
 class CanPlayTypeHandler {
  public:
   virtual ~CanPlayTypeHandler() {}
-  virtual SbMediaSupportType CanPlayType(const std::string& mime_type,
-                                         const std::string& key_system,
-                                         bool is_progressive) const = 0;
+  virtual SbMediaSupportType CanPlayProgressive(
+      const std::string& mime_type) const = 0;
+  virtual SbMediaSupportType CanPlayAdaptive(
+      const std::string& mime_type, const std::string& key_system) const = 0;
   virtual void SetDisabledMediaCodecs(const std::string& codecs) = 0;
 
  protected:
diff --git a/src/cobalt/media/fetcher_buffered_data_source.cc b/src/cobalt/media/fetcher_buffered_data_source.cc
index 2409eb8..9eb25be 100644
--- a/src/cobalt/media/fetcher_buffered_data_source.cc
+++ b/src/cobalt/media/fetcher_buffered_data_source.cc
@@ -407,7 +407,7 @@
     read_cb.Run(static_cast<int>(bytes_peeked));
     // If we have a large buffer size, it could be ideal if we can keep sending
     // small requests when the read offset is far from the beginning of the
-    // buffer.  However as the ShellDemuxer will cache many frames and the
+    // buffer.  However as the ProgressiveDemuxer will cache many frames and the
     // buffer we are using is usually small, we will just avoid sending requests
     // here to make code simple.
     return;
diff --git a/src/cobalt/media/filters/source_buffer_stream.cc b/src/cobalt/media/filters/source_buffer_stream.cc
index c4208ba..0d30352 100644
--- a/src/cobalt/media/filters/source_buffer_stream.cc
+++ b/src/cobalt/media/filters/source_buffer_stream.cc
@@ -736,7 +736,7 @@
 
   size_t bytes_to_free = 0;
 
-  int garbage_collection_duration_threshold_in_seconds =
+  int64_t garbage_collection_duration_threshold_in_seconds =
       SbMediaGetBufferGarbageCollectionDurationThreshold() / kSbTimeSecond;
   // Check if we're under or at the memory/duration limit.
   const auto kGcDurationThresholdInMilliseconds =
diff --git a/src/cobalt/media/media.gyp b/src/cobalt/media/media.gyp
index bc1e422..4b31fc0 100644
--- a/src/cobalt/media/media.gyp
+++ b/src/cobalt/media/media.gyp
@@ -29,6 +29,8 @@
         'media_module.cc',
         'media_module.h',
 
+        'base/audio_bus.cc',
+        'base/audio_bus.h',
         'base/audio_codecs.cc',
         'base/audio_codecs.h',
         'base/audio_decoder_config.cc',
@@ -90,10 +92,6 @@
         'base/sbplayer_pipeline.cc',
         'base/sbplayer_set_bounds_helper.cc',
         'base/sbplayer_set_bounds_helper.h',
-        'base/shell_audio_bus.cc',
-        'base/shell_audio_bus.h',
-        'base/shell_data_source_reader.cc',
-        'base/shell_data_source_reader.h',
         'base/starboard_player.cc',
         'base/starboard_player.h',
         'base/starboard_utils.cc',
@@ -125,20 +123,6 @@
         'filters/h264_to_annex_b_bitstream_converter.h',
         'filters/h265_parser.cc',
         'filters/h265_parser.h',
-        'filters/shell_au.cc',
-        'filters/shell_au.h',
-        'filters/shell_avc_parser.cc',
-        'filters/shell_avc_parser.h',
-        'filters/shell_demuxer.cc',
-        'filters/shell_demuxer.h',
-        'filters/shell_mp4_map.cc',
-        'filters/shell_mp4_map.h',
-        'filters/shell_mp4_parser.cc',
-        'filters/shell_mp4_parser.h',
-        'filters/shell_parser.cc',
-        'filters/shell_parser.h',
-        'filters/shell_rbsp_stream.cc',
-        'filters/shell_rbsp_stream.h',
         'filters/source_buffer_range.cc',
         'filters/source_buffer_range.h',
         'filters/source_buffer_state.cc',
@@ -216,6 +200,22 @@
         'player/web_media_player_impl.h',
         'player/web_media_player_proxy.cc',
         'player/web_media_player_proxy.h',
+        'progressive/avc_access_unit.cc',
+        'progressive/avc_access_unit.h',
+        'progressive/avc_parser.cc',
+        'progressive/avc_parser.h',
+        'progressive/data_source_reader.cc',
+        'progressive/data_source_reader.h',
+        'progressive/mp4_map.cc',
+        'progressive/mp4_map.h',
+        'progressive/mp4_parser.cc',
+        'progressive/mp4_parser.h',
+        'progressive/progressive_demuxer.cc',
+        'progressive/progressive_demuxer.h',
+        'progressive/progressive_parser.cc',
+        'progressive/progressive_parser.h',
+        'progressive/rbsp_stream.cc',
+        'progressive/rbsp_stream.h',
       ],
       'direct_dependent_settings': {
         'include_dirs': [
@@ -240,9 +240,9 @@
         '<(DEPTH)/testing/gtest.gyp:gtest',
       ],
       'sources': [
-        'base/mock_shell_data_source_reader.h',
-        'filters/shell_mp4_map_unittest.cc',
-        'filters/shell_rbsp_stream_unittest.cc',
+        'progressive/mock_data_source_reader.h',
+        'progressive/mp4_map_unittest.cc',
+        'progressive/rbsp_stream_unittest.cc',
       ],
       'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
     },
diff --git a/src/cobalt/media/media_module.cc b/src/cobalt/media/media_module.cc
index e476a6f..9938d41 100644
--- a/src/cobalt/media/media_module.cc
+++ b/src/cobalt/media/media_module.cc
@@ -48,37 +48,27 @@
               << "\" from console/command line.";
   }
 
-  SbMediaSupportType CanPlayType(const std::string& mime_type,
-                                 const std::string& key_system,
-                                 bool is_progressive) const override {
-    if (is_progressive) {
-      // |mime_type| is something like:
-      //   video/mp4
-      //   video/webm
-      //   video/mp4; codecs="avc1.4d401e"
-      //   video/webm; codecs="vp9"
-      // We do a rough pre-filter to ensure that only video/mp4 is supported as
-      // progressive.
-      if (SbStringFindString(mime_type.c_str(), "video/mp4") == 0 &&
-          SbStringFindString(mime_type.c_str(), "application/x-mpegURL") == 0) {
-        return kSbMediaSupportTypeNotSupported;
-      }
+  SbMediaSupportType CanPlayProgressive(
+      const std::string& mime_type) const override {
+    // |mime_type| is something like:
+    //   video/mp4
+    //   video/webm
+    //   video/mp4; codecs="avc1.4d401e"
+    //   video/webm; codecs="vp9"
+    // We do a rough pre-filter to ensure that only video/mp4 is supported as
+    // progressive.
+    if (SbStringFindString(mime_type.c_str(), "video/mp4") == 0 &&
+        SbStringFindString(mime_type.c_str(), "application/x-mpegURL") == 0) {
+      return kSbMediaSupportTypeNotSupported;
     }
-    if (!disabled_media_codecs_.empty()) {
-      auto mime_codecs = ExtractCodecs(mime_type);
-      for (auto& disabled_codec : disabled_media_codecs_) {
-        for (auto& mime_codec : mime_codecs) {
-          if (mime_codec.find(disabled_codec) != std::string::npos) {
-            LOG(INFO) << "Codec (" << mime_codec
-                      << ") is disabled via console/command line.";
-            return kSbMediaSupportTypeNotSupported;
-          }
-        }
-      }
-    }
-    SbMediaSupportType type =
-        SbMediaCanPlayMimeAndKeySystem(mime_type.c_str(), key_system.c_str());
-    return type;
+
+    return CanPlayType(mime_type, "");
+  }
+
+  SbMediaSupportType CanPlayAdaptive(
+      const std::string& mime_type,
+      const std::string& key_system) const override {
+    return CanPlayType(mime_type, key_system);
   }
 
  private:
@@ -107,6 +97,25 @@
     return codecs;
   }
 
+  SbMediaSupportType CanPlayType(const std::string& mime_type,
+                                 const std::string& key_system) const {
+    if (!disabled_media_codecs_.empty()) {
+      auto mime_codecs = ExtractCodecs(mime_type);
+      for (auto& disabled_codec : disabled_media_codecs_) {
+        for (auto& mime_codec : mime_codecs) {
+          if (mime_codec.find(disabled_codec) != std::string::npos) {
+            LOG(INFO) << "Codec (" << mime_codec
+                      << ") is disabled via console/command line.";
+            return kSbMediaSupportTypeNotSupported;
+          }
+        }
+      }
+    }
+    SbMediaSupportType type =
+        SbMediaCanPlayMimeAndKeySystem(mime_type.c_str(), key_system.c_str());
+    return type;
+  }
+
   // List of disabled media codecs that will be treated as unsupported.
   std::vector<std::string> disabled_media_codecs_;
 };
@@ -120,6 +129,7 @@
   if (system_window_) {
     window = system_window_->GetSbWindow();
   }
+
   return std::unique_ptr<WebMediaPlayer>(new media::WebMediaPlayerImpl(
       window,
       base::Bind(&MediaModule::GetSbDecodeTargetGraphicsContextProvider,
@@ -149,11 +159,16 @@
 
   resource_provider_ = resource_provider;
 
+  SbWindow window = kSbWindowInvalid;
+  if (system_window_) {
+    window = system_window_->GetSbWindow();
+  }
+
   for (Players::iterator iter = players_.begin(); iter != players_.end();
        ++iter) {
     DCHECK(!iter->second);
     if (!iter->second) {
-      iter->first->Resume();
+      iter->first->Resume(window);
     }
   }
 
diff --git a/src/cobalt/media/player/web_media_player.h b/src/cobalt/media/player/web_media_player.h
index 86c4827..ea66d09 100644
--- a/src/cobalt/media/player/web_media_player.h
+++ b/src/cobalt/media/player/web_media_player.h
@@ -115,7 +115,9 @@
 
   // Suspend/Resume
   virtual void Suspend() = 0;
-  virtual void Resume() = 0;
+  // TODO: This is temporary for supporting background media playback.
+  //       Need to be removed with media refactor.
+  virtual void Resume(SbWindow window) = 0;
 
   // True if the loaded media has a playable video/audio track.
   virtual bool HasVideo() const = 0;
diff --git a/src/cobalt/media/player/web_media_player_impl.cc b/src/cobalt/media/player/web_media_player_impl.cc
index 6f18dc7..7638848 100644
--- a/src/cobalt/media/player/web_media_player_impl.cc
+++ b/src/cobalt/media/player/web_media_player_impl.cc
@@ -24,8 +24,8 @@
 #include "cobalt/media/base/limits.h"
 #include "cobalt/media/base/media_log.h"
 #include "cobalt/media/filters/chunk_demuxer.h"
-#include "cobalt/media/filters/shell_demuxer.h"
 #include "cobalt/media/player/web_media_player_proxy.h"
+#include "cobalt/media/progressive/progressive_demuxer.h"
 #include "starboard/double.h"
 #include "starboard/types.h"
 
@@ -292,8 +292,8 @@
   is_local_source_ = !url.SchemeIs("http") && !url.SchemeIs("https");
 
   progressive_demuxer_.reset(
-      new ShellDemuxer(pipeline_thread_.task_runner(), buffer_allocator_,
-                       proxy_->data_source(), media_log_));
+      new ProgressiveDemuxer(pipeline_thread_.task_runner(), buffer_allocator_,
+                             proxy_->data_source(), media_log_));
 
   state_.is_progressive = true;
   StartPipeline(progressive_demuxer_.get());
@@ -532,7 +532,9 @@
 
 void WebMediaPlayerImpl::Suspend() { pipeline_->Suspend(); }
 
-void WebMediaPlayerImpl::Resume() { pipeline_->Resume(); }
+void WebMediaPlayerImpl::Resume(PipelineWindow window) {
+  pipeline_->Resume(window);
+}
 
 bool WebMediaPlayerImpl::DidLoadingProgress() const {
   DCHECK_EQ(main_loop_, base::MessageLoop::current());
diff --git a/src/cobalt/media/player/web_media_player_impl.h b/src/cobalt/media/player/web_media_player_impl.h
index a1a51be..c77d8b3 100644
--- a/src/cobalt/media/player/web_media_player_impl.h
+++ b/src/cobalt/media/player/web_media_player_impl.h
@@ -140,7 +140,9 @@
 
   // Suspend/Resume
   void Suspend() override;
-  void Resume() override;
+  // TODO: This is temporary for supporting background media playback.
+  //       Need to be removed with media refactor.
+  void Resume(PipelineWindow window) override;
 
   // True if the loaded media has a playable video/audio track.
   bool HasVideo() const override;
diff --git a/src/cobalt/media/filters/shell_au.cc b/src/cobalt/media/progressive/avc_access_unit.cc
similarity index 74%
rename from src/cobalt/media/filters/shell_au.cc
rename to src/cobalt/media/progressive/avc_access_unit.cc
index fadfadb..25ad24e 100644
--- a/src/cobalt/media/filters/shell_au.cc
+++ b/src/cobalt/media/progressive/avc_access_unit.cc
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/media/filters/shell_au.h"
+#include "cobalt/media/progressive/avc_access_unit.h"
 
 #include <algorithm>
 
 #include "cobalt/media/base/decoder_buffer.h"
 #include "cobalt/media/base/endian_util.h"
 #include "cobalt/media/base/timestamp_constants.h"
-#include "cobalt/media/filters/shell_parser.h"
+#include "cobalt/media/progressive/progressive_parser.h"
 
 namespace cobalt {
 namespace media {
@@ -27,7 +27,7 @@
 namespace {
 
 bool ReadBytes(uint64 offset, size_t size, uint8* buffer,
-               ShellDataSourceReader* reader) {
+               DataSourceReader* reader) {
   if (reader->BlockingRead(offset, size, buffer) != size) {
     DLOG(ERROR) << "unable to download AU";
     return false;
@@ -36,7 +36,7 @@
 }
 
 bool ReadBytes(uint64 offset, size_t size, DecoderBuffer* decoder_buffer,
-               uint64 decoder_buffer_offset, ShellDataSourceReader* reader) {
+               uint64 decoder_buffer_offset, DataSourceReader* reader) {
   size_t buffer_index = 0;
   auto& allocations = decoder_buffer->allocations();
   while (size > 0) {
@@ -67,17 +67,17 @@
   return true;
 }
 
-// ==== ShellEndOfStreamAU ==================================================
+// ==== EndOfStreamAU ==================================================
 
-class ShellEndOfStreamAU : public ShellAU {
+class EndOfStreamAU : public AvcAccessUnit {
  public:
-  ShellEndOfStreamAU(Type type, TimeDelta timestamp)
+  EndOfStreamAU(Type type, TimeDelta timestamp)
       : type_(type), timestamp_(timestamp), duration_(kInfiniteDuration) {}
 
  private:
   bool IsEndOfStream() const override { return true; }
   bool IsValid() const override { return true; }
-  bool Read(ShellDataSourceReader* reader, DecoderBuffer* buffer) override {
+  bool Read(DataSourceReader* reader, DecoderBuffer* buffer) override {
     NOTREACHED();
     return false;
   }
@@ -102,20 +102,19 @@
   TimeDelta duration_;
 };
 
-// ==== ShellAudioAU =======================================================
+// ==== AudioAU =======================================================
 
-class ShellAudioAU : public ShellAU {
+class AudioAU : public AvcAccessUnit {
  public:
-  ShellAudioAU(uint64 offset, size_t size, size_t prepend_size,
-               bool is_keyframe, TimeDelta timestamp, TimeDelta duration,
-               ShellParser* parser);
+  AudioAU(uint64 offset, size_t size, size_t prepend_size, bool is_keyframe,
+          TimeDelta timestamp, TimeDelta duration, ProgressiveParser* parser);
 
  private:
   bool IsEndOfStream() const override { return false; }
   bool IsValid() const override {
     return offset_ != 0 && size_ != 0 && timestamp_ != kNoTimestamp;
   }
-  bool Read(ShellDataSourceReader* reader, DecoderBuffer* buffer) override;
+  bool Read(DataSourceReader* reader, DecoderBuffer* buffer) override;
   Type GetType() const override { return DemuxerStream::AUDIO; }
   bool IsKeyframe() const override { return is_keyframe_; }
   bool AddPrepend() const override { return true; }
@@ -132,12 +131,12 @@
   bool is_keyframe_;
   TimeDelta timestamp_;
   TimeDelta duration_;
-  ShellParser* parser_;
+  ProgressiveParser* parser_;
 };
 
-ShellAudioAU::ShellAudioAU(uint64 offset, size_t size, size_t prepend_size,
-                           bool is_keyframe, TimeDelta timestamp,
-                           TimeDelta duration, ShellParser* parser)
+AudioAU::AudioAU(uint64 offset, size_t size, size_t prepend_size,
+                 bool is_keyframe, TimeDelta timestamp, TimeDelta duration,
+                 ProgressiveParser* parser)
     : offset_(offset),
       size_(size),
       prepend_size_(prepend_size),
@@ -146,7 +145,7 @@
       duration_(duration),
       parser_(parser) {}
 
-bool ShellAudioAU::Read(ShellDataSourceReader* reader, DecoderBuffer* buffer) {
+bool AudioAU::Read(DataSourceReader* reader, DecoderBuffer* buffer) {
   DCHECK_LE(size_ + prepend_size_, buffer->data_size());
   if (!ReadBytes(offset_, size_, buffer, prepend_size_, reader)) return false;
 
@@ -158,20 +157,20 @@
   return true;
 }
 
-// ==== ShellVideoAU =======================================================
+// ==== VideoAU =======================================================
 
-class ShellVideoAU : public ShellAU {
+class VideoAU : public AvcAccessUnit {
  public:
-  ShellVideoAU(uint64 offset, size_t size, size_t prepend_size,
-               uint8 length_of_nalu_size, bool is_keyframe, TimeDelta timestamp,
-               TimeDelta duration, ShellParser* parser);
+  VideoAU(uint64 offset, size_t size, size_t prepend_size,
+          uint8 length_of_nalu_size, bool is_keyframe, TimeDelta timestamp,
+          TimeDelta duration, ProgressiveParser* parser);
 
  private:
   bool IsEndOfStream() const override { return false; }
   bool IsValid() const override {
     return offset_ != 0 && size_ != 0 && timestamp_ != kNoTimestamp;
   }
-  bool Read(ShellDataSourceReader* reader, DecoderBuffer* buffer) override;
+  bool Read(DataSourceReader* reader, DecoderBuffer* buffer) override;
   Type GetType() const override { return DemuxerStream::VIDEO; }
   bool IsKeyframe() const override { return is_keyframe_; }
   bool AddPrepend() const override { return is_keyframe_; }
@@ -193,13 +192,13 @@
   bool is_keyframe_;
   TimeDelta timestamp_;
   TimeDelta duration_;
-  ShellParser* parser_;
+  ProgressiveParser* parser_;
 };
 
-ShellVideoAU::ShellVideoAU(uint64 offset, size_t size, size_t prepend_size,
-                           uint8 length_of_nalu_size, bool is_keyframe,
-                           TimeDelta timestamp, TimeDelta duration,
-                           ShellParser* parser)
+VideoAU::VideoAU(uint64 offset, size_t size, size_t prepend_size,
+                 uint8 length_of_nalu_size, bool is_keyframe,
+                 TimeDelta timestamp, TimeDelta duration,
+                 ProgressiveParser* parser)
     : offset_(offset),
       size_(size),
       prepend_size_(prepend_size),
@@ -212,7 +211,7 @@
   CHECK_NE(length_of_nalu_size_, 3);
 }
 
-bool ShellVideoAU::Read(ShellDataSourceReader* reader, DecoderBuffer* buffer) {
+bool VideoAU::Read(DataSourceReader* reader, DecoderBuffer* buffer) {
   size_t au_left = size_;                 // bytes left in the AU
   uint64 au_offset = offset_;             // offset to read in the reader
   size_t buf_left = buffer->data_size();  // bytes left in the buffer
@@ -280,33 +279,34 @@
 
 }  // namespace
 
-// ==== ShellAU ================================================================
+// ==== AvcAccessUnit
+// ================================================================
 
-ShellAU::ShellAU() {}
+AvcAccessUnit::AvcAccessUnit() {}
 
-ShellAU::~ShellAU() {}
+AvcAccessUnit::~AvcAccessUnit() {}
 
 // static
-scoped_refptr<ShellAU> ShellAU::CreateEndOfStreamAU(DemuxerStream::Type type,
-                                                    TimeDelta timestamp) {
-  return new ShellEndOfStreamAU(type, timestamp);
+scoped_refptr<AvcAccessUnit> AvcAccessUnit::CreateEndOfStreamAU(
+    DemuxerStream::Type type, TimeDelta timestamp) {
+  return new EndOfStreamAU(type, timestamp);
 }
 
 // static
-scoped_refptr<ShellAU> ShellAU::CreateAudioAU(
+scoped_refptr<AvcAccessUnit> AvcAccessUnit::CreateAudioAU(
     uint64 offset, size_t size, size_t prepend_size, bool is_keyframe,
-    TimeDelta timestamp, TimeDelta duration, ShellParser* parser) {
-  return new ShellAudioAU(offset, size, prepend_size, is_keyframe, timestamp,
-                          duration, parser);
+    TimeDelta timestamp, TimeDelta duration, ProgressiveParser* parser) {
+  return new AudioAU(offset, size, prepend_size, is_keyframe, timestamp,
+                     duration, parser);
 }
 
 // static
-scoped_refptr<ShellAU> ShellAU::CreateVideoAU(
+scoped_refptr<AvcAccessUnit> AvcAccessUnit::CreateVideoAU(
     uint64 offset, size_t size, size_t prepend_size, uint8 length_of_nalu_size,
     bool is_keyframe, TimeDelta timestamp, TimeDelta duration,
-    ShellParser* parser) {
-  return new ShellVideoAU(offset, size, prepend_size, length_of_nalu_size,
-                          is_keyframe, timestamp, duration, parser);
+    ProgressiveParser* parser) {
+  return new VideoAU(offset, size, prepend_size, length_of_nalu_size,
+                     is_keyframe, timestamp, duration, parser);
 }
 
 }  // namespace media
diff --git a/src/cobalt/media/filters/shell_au.h b/src/cobalt/media/progressive/avc_access_unit.h
similarity index 63%
rename from src/cobalt/media/filters/shell_au.h
rename to src/cobalt/media/progressive/avc_access_unit.h
index d6254e4..7c283e9 100644
--- a/src/cobalt/media/filters/shell_au.h
+++ b/src/cobalt/media/progressive/avc_access_unit.h
@@ -12,44 +12,44 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_MEDIA_FILTERS_SHELL_AU_H_
-#define COBALT_MEDIA_FILTERS_SHELL_AU_H_
+#ifndef COBALT_MEDIA_PROGRESSIVE_ACCESS_UNIT_H_
+#define COBALT_MEDIA_PROGRESSIVE_ACCESS_UNIT_H_
 
 #include "base/memory/ref_counted.h"
 #include "cobalt/media/base/demuxer_stream.h"
-#include "cobalt/media/base/shell_data_source_reader.h"
+#include "cobalt/media/progressive/data_source_reader.h"
 
 namespace cobalt {
 namespace media {
 
-class ShellParser;
+class ProgressiveParser;
 
 static const int kAnnexBStartCodeSize = 4;
 static const uint8_t kAnnexBStartCode[] = {0, 0, 0, 1};
 
-// The basic unit of currency between ShellDemuxer and ShellParser, the ShellAU
-// defines all needed information for a given AccessUnit (Frame) of encoded
-// media data.
-class ShellAU : public base::RefCountedThreadSafe<ShellAU> {
+// The basic unit of currency between ProgressiveDemuxer and ProgressiveParser,
+// the AvcAccessUnit defines all needed information for a given AvcAccessUnit
+// (Frame) of encoded media data.
+class AvcAccessUnit : public base::RefCountedThreadSafe<AvcAccessUnit> {
  public:
   typedef base::TimeDelta TimeDelta;
   typedef DemuxerStream::Type Type;
 
-  static scoped_refptr<ShellAU> CreateEndOfStreamAU(Type type,
-                                                    TimeDelta timestamp);
-  static scoped_refptr<ShellAU> CreateAudioAU(
+  static scoped_refptr<AvcAccessUnit> CreateEndOfStreamAU(Type type,
+                                                          TimeDelta timestamp);
+  static scoped_refptr<AvcAccessUnit> CreateAudioAU(
       uint64 offset, size_t size, size_t prepend_size, bool is_keyframe,
-      TimeDelta timestamp, TimeDelta duration, ShellParser* parser);
-  static scoped_refptr<ShellAU> CreateVideoAU(
+      TimeDelta timestamp, TimeDelta duration, ProgressiveParser* parser);
+  static scoped_refptr<AvcAccessUnit> CreateVideoAU(
       uint64 offset, size_t size, size_t prepend_size,
       uint8 length_of_nalu_size, bool is_keyframe, TimeDelta timestamp,
-      TimeDelta duration, ShellParser* parser);
+      TimeDelta duration, ProgressiveParser* parser);
 
   virtual bool IsEndOfStream() const = 0;
   virtual bool IsValid() const = 0;
   // Read an AU from reader to buffer and also do all the necessary operations
   // like prepending head to make it ready to decode.
-  virtual bool Read(ShellDataSourceReader* reader, DecoderBuffer* buffer) = 0;
+  virtual bool Read(DataSourceReader* reader, DecoderBuffer* buffer) = 0;
   virtual Type GetType() const = 0;
   virtual bool IsKeyframe() const = 0;
   virtual bool AddPrepend() const = 0;
@@ -63,13 +63,13 @@
   virtual void SetTimestamp(TimeDelta timestamp) = 0;
 
  protected:
-  friend class base::RefCountedThreadSafe<ShellAU>;
+  friend class base::RefCountedThreadSafe<AvcAccessUnit>;
 
-  ShellAU();
-  virtual ~ShellAU();
+  AvcAccessUnit();
+  virtual ~AvcAccessUnit();
 };
 
 }  // namespace media
 }  // namespace cobalt
 
-#endif  // COBALT_MEDIA_FILTERS_SHELL_AU_H_
+#endif  // COBALT_MEDIA_PROGRESSIVE_ACCESS_UNIT_H_
diff --git a/src/cobalt/media/filters/shell_avc_parser.cc b/src/cobalt/media/progressive/avc_parser.cc
similarity index 92%
rename from src/cobalt/media/filters/shell_avc_parser.cc
rename to src/cobalt/media/progressive/avc_parser.cc
index baca931..e08141a 100644
--- a/src/cobalt/media/filters/shell_avc_parser.cc
+++ b/src/cobalt/media/progressive/avc_parser.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/media/filters/shell_avc_parser.h"
+#include "cobalt/media/progressive/avc_parser.h"
 
 #include <limits>
 #include <vector>
@@ -23,9 +23,9 @@
 #include "cobalt/media/base/endian_util.h"
 #include "cobalt/media/base/media_util.h"
 #include "cobalt/media/base/video_types.h"
-#include "cobalt/media/filters/shell_au.h"
-#include "cobalt/media/filters/shell_rbsp_stream.h"
 #include "cobalt/media/formats/mp4/aac.h"
+#include "cobalt/media/progressive/avc_access_unit.h"
+#include "cobalt/media/progressive/rbsp_stream.h"
 #include "starboard/memory.h"
 
 namespace cobalt {
@@ -36,19 +36,19 @@
 // lower five bits of first byte in SPS should be 7
 static const uint8 kSPSNALType = 7;
 
-ShellAVCParser::ShellAVCParser(scoped_refptr<ShellDataSourceReader> reader,
-                               const scoped_refptr<MediaLog>& media_log)
-    : ShellParser(reader),
+AVCParser::AVCParser(scoped_refptr<DataSourceReader> reader,
+                     const scoped_refptr<MediaLog>& media_log)
+    : ProgressiveParser(reader),
       media_log_(media_log),
       nal_header_size_(0),
       video_prepend_size_(0) {
   DCHECK(media_log);
 }
 
-ShellAVCParser::~ShellAVCParser() {}
+AVCParser::~AVCParser() {}
 
-bool ShellAVCParser::Prepend(scoped_refptr<ShellAU> au,
-                             scoped_refptr<DecoderBuffer> buffer) {
+bool AVCParser::Prepend(scoped_refptr<AvcAccessUnit> au,
+                        scoped_refptr<DecoderBuffer> buffer) {
   // sanity-check inputs
   if (!au || !buffer) {
     NOTREACHED() << "bad input to Prepend()";
@@ -93,8 +93,7 @@
   return true;
 }
 
-bool ShellAVCParser::DownloadAndParseAVCConfigRecord(uint64 offset,
-                                                     uint32 size) {
+bool AVCParser::DownloadAndParseAVCConfigRecord(uint64 offset, uint32 size) {
   if (size == 0) {
     return false;
   }
@@ -109,8 +108,8 @@
 }
 
 // static
-bool ShellAVCParser::ParseSPS(const uint8* sps, size_t sps_size,
-                              ShellSPSRecord* record_out) {
+bool AVCParser::ParseSPS(const uint8* sps, size_t sps_size,
+                         SPSRecord* record_out) {
   DCHECK(sps) << "no sps provided";
   DCHECK(record_out) << "no output structure provided";
   // first byte is NAL type id, check that it is SPS
@@ -119,7 +118,7 @@
     return false;
   }
   // convert SPS NALU to RBSP stream
-  ShellRBSPStream sps_rbsp(sps + 1, sps_size - 1);
+  RBSPStream sps_rbsp(sps + 1, sps_size - 1);
   uint8 profile_idc = 0;
   if (!sps_rbsp.ReadByte(&profile_idc)) {
     DLOG(ERROR) << "failure reading profile_idc from sps RBSP";
@@ -294,7 +293,7 @@
   return true;
 }
 
-bool ShellAVCParser::ParseAVCConfigRecord(uint8* buffer, uint32 size) {
+bool AVCParser::ParseAVCConfigRecord(uint8* buffer, uint32 size) {
   if (size < kAVCConfigMinSize) {
     DLOG(ERROR) << base::StringPrintf("AVC config record bad size: %d", size);
     return false;
@@ -392,7 +391,7 @@
     }
   }
   // now we parse the valid SPS we extracted from byte stream earlier.
-  ShellSPSRecord sps_record;
+  SPSRecord sps_record;
   if (!ParseSPS(buffer + usable_sps_offset, usable_sps_size, &sps_record)) {
     DLOG(WARNING) << "error parsing SPS";
     return false;
@@ -409,8 +408,8 @@
                             buffer + usable_pps_offset, usable_pps_size);
 }
 
-bool ShellAVCParser::BuildAnnexBPrepend(uint8* sps, uint32 sps_size, uint8* pps,
-                                        uint32 pps_size) {
+bool AVCParser::BuildAnnexBPrepend(uint8* sps, uint32 sps_size, uint8* pps,
+                                   uint32 pps_size) {
   // We will need to attach the sps and pps (if provided) to each keyframe
   // video packet, with the AnnexB start code in front of each. Start with
   // sps size and start code
@@ -445,7 +444,7 @@
   return true;
 }
 
-void ShellAVCParser::ParseAudioSpecificConfig(uint8 b0, uint8 b1) {
+void AVCParser::ParseAudioSpecificConfig(uint8 b0, uint8 b1) {
   media::mp4::AAC aac;
   std::vector<uint8> aac_config(2);
 
@@ -472,8 +471,8 @@
       Unencrypted(), base::TimeDelta(), 0);
 }
 
-size_t ShellAVCParser::CalculatePrependSize(DemuxerStream::Type type,
-                                            bool is_keyframe) {
+size_t AVCParser::CalculatePrependSize(DemuxerStream::Type type,
+                                       bool is_keyframe) {
   size_t prepend_size = 0;
   if (type == DemuxerStream::VIDEO) {
     bool needs_prepend = is_keyframe;
diff --git a/src/cobalt/media/filters/shell_avc_parser.h b/src/cobalt/media/progressive/avc_parser.h
similarity index 77%
rename from src/cobalt/media/filters/shell_avc_parser.h
rename to src/cobalt/media/progressive/avc_parser.h
index 758a7a2..e383bdf 100644
--- a/src/cobalt/media/filters/shell_avc_parser.h
+++ b/src/cobalt/media/progressive/avc_parser.h
@@ -12,13 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_MEDIA_FILTERS_SHELL_AVC_PARSER_H_
-#define COBALT_MEDIA_FILTERS_SHELL_AVC_PARSER_H_
+#ifndef COBALT_MEDIA_PROGRESSIVE_AVC_PARSER_H_
+#define COBALT_MEDIA_PROGRESSIVE_AVC_PARSER_H_
 
 #include <vector>
 
 #include "cobalt/media/base/media_log.h"
-#include "cobalt/media/filters/shell_parser.h"
+#include "cobalt/media/progressive/progressive_parser.h"
 
 namespace cobalt {
 namespace media {
@@ -29,27 +29,27 @@
 static const int kAnnexBPrependMaxSize = 1024;
 
 // while not an actual parser, provides shared functionality to both the
-// mp4 and flv parsers which derive from it. Implements part of ShellParser
-// while leaving the rest for its children.
-class ShellAVCParser : public ShellParser {
+// mp4 and flv parsers which derive from it. Implements part of
+// ProgressiveParser while leaving the rest for its children.
+class AVCParser : public ProgressiveParser {
  public:
-  explicit ShellAVCParser(scoped_refptr<ShellDataSourceReader> reader,
-                          const scoped_refptr<MediaLog>& media_log);
-  virtual ~ShellAVCParser();
+  explicit AVCParser(scoped_refptr<DataSourceReader> reader,
+                     const scoped_refptr<MediaLog>& media_log);
+  virtual ~AVCParser();
 
-  struct ShellSPSRecord {
+  struct SPSRecord {
     math::Size coded_size;
     math::Rect visible_rect;
     math::Size natural_size;
     uint32 num_ref_frames;
   };
   static bool ParseSPS(const uint8* sps, size_t sps_size,
-                       ShellSPSRecord* record_out);
+                       SPSRecord* record_out);
 
   // GetNextAU we must pass on to FLV or MP4 children.
-  virtual scoped_refptr<ShellAU> GetNextAU(DemuxerStream::Type type) = 0;
+  virtual scoped_refptr<AvcAccessUnit> GetNextAU(DemuxerStream::Type type) = 0;
   // Prepends are common to all AVC/AAC containers so we can do this one here.
-  bool Prepend(scoped_refptr<ShellAU> au,
+  bool Prepend(scoped_refptr<AvcAccessUnit> au,
                scoped_refptr<DecoderBuffer> buffer) override;
 
  protected:
@@ -77,4 +77,4 @@
 }  // namespace media
 }  // namespace cobalt
 
-#endif  // COBALT_MEDIA_FILTERS_SHELL_AVC_PARSER_H_
+#endif  // COBALT_MEDIA_PROGRESSIVE_AVC_PARSER_H_
diff --git a/src/cobalt/media/base/shell_data_source_reader.cc b/src/cobalt/media/progressive/data_source_reader.cc
similarity index 81%
rename from src/cobalt/media/base/shell_data_source_reader.cc
rename to src/cobalt/media/progressive/data_source_reader.cc
index 2c7c38f..fd4ff71 100644
--- a/src/cobalt/media/base/shell_data_source_reader.cc
+++ b/src/cobalt/media/progressive/data_source_reader.cc
@@ -12,16 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/media/base/shell_data_source_reader.h"
+#include "cobalt/media/progressive/data_source_reader.h"
 
 #include "starboard/types.h"
 
 namespace cobalt {
 namespace media {
 
-const int ShellDataSourceReader::kReadError = DataSource::kReadError;
+const int DataSourceReader::kReadError = DataSource::kReadError;
 
-ShellDataSourceReader::ShellDataSourceReader()
+DataSourceReader::DataSourceReader()
     : data_source_(NULL),
       blocking_read_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
                            base::WaitableEvent::InitialState::NOT_SIGNALED),
@@ -29,15 +29,15 @@
       read_has_failed_(false),
       last_bytes_read_(0) {}
 
-ShellDataSourceReader::~ShellDataSourceReader() {}
+DataSourceReader::~DataSourceReader() {}
 
-void ShellDataSourceReader::SetDataSource(DataSource* data_source) {
+void DataSourceReader::SetDataSource(DataSource* data_source) {
   DCHECK(data_source);
   data_source_ = data_source;
 }
 
 // currently only single-threaded reads supported
-int ShellDataSourceReader::BlockingRead(int64 position, int size, uint8* data) {
+int DataSourceReader::BlockingRead(int64 position, int size, uint8* data) {
   // read failures are unrecoverable, all subsequent reads will also fail
   if (read_has_failed_) {
     return kReadError;
@@ -57,7 +57,7 @@
       }
       data_source_->Read(
           position, size, data,
-          base::Bind(&ShellDataSourceReader::BlockingReadCompleted, this));
+          base::Bind(&DataSourceReader::BlockingReadCompleted, this));
     }
 
     // wait for callback on read completion
@@ -91,7 +91,7 @@
   return total_bytes_read;
 }
 
-void ShellDataSourceReader::Stop() {
+void DataSourceReader::Stop() {
   if (data_source_) {
     data_source_->Stop();
 
@@ -100,13 +100,13 @@
   }
 }
 
-void ShellDataSourceReader::BlockingReadCompleted(int bytes_read) {
+void DataSourceReader::BlockingReadCompleted(int bytes_read) {
   last_bytes_read_ = bytes_read;
   // wake up blocked thread
   blocking_read_event_.Signal();
 }
 
-int64 ShellDataSourceReader::FileSize() {
+int64 DataSourceReader::FileSize() {
   if (file_size_ == -1) {
     base::AutoLock auto_lock(lock_);
     if (data_source_ && !data_source_->GetSize(&file_size_)) {
diff --git a/src/cobalt/media/base/shell_data_source_reader.h b/src/cobalt/media/progressive/data_source_reader.h
similarity index 85%
rename from src/cobalt/media/base/shell_data_source_reader.h
rename to src/cobalt/media/progressive/data_source_reader.h
index 2049a9d..c773083 100644
--- a/src/cobalt/media/base/shell_data_source_reader.h
+++ b/src/cobalt/media/progressive/data_source_reader.h
@@ -12,9 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_MEDIA_BASE_SHELL_DATA_SOURCE_READER_H_
-#define COBALT_MEDIA_BASE_SHELL_DATA_SOURCE_READER_H_
-
+#ifndef COBALT_MEDIA_PROGRESSIVE_DATA_SOURCE_READER_H_
+#define COBALT_MEDIA_PROGRESSIVE_DATA_SOURCE_READER_H_
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
@@ -32,12 +31,11 @@
 // object is also the sole owner of a pointer to DataSource.  If we want to add
 // asynchronous reading to this object it will need its own thread and a
 // callback queue.
-class ShellDataSourceReader
-    : public base::RefCountedThreadSafe<ShellDataSourceReader> {
+class DataSourceReader : public base::RefCountedThreadSafe<DataSourceReader> {
  public:
   static const int kReadError;
 
-  ShellDataSourceReader();
+  DataSourceReader();
   virtual void SetDataSource(DataSource* data_source);
 
   // Block the calling thread's message loop until read is complete.
@@ -53,8 +51,8 @@
   virtual void Stop();
 
  protected:
-  friend class base::RefCountedThreadSafe<ShellDataSourceReader>;
-  virtual ~ShellDataSourceReader();
+  friend class base::RefCountedThreadSafe<DataSourceReader>;
+  virtual ~DataSourceReader();
   // blocking read callback
   virtual void BlockingReadCompleted(int bytes_read);
 
@@ -69,4 +67,4 @@
 }  // namespace media
 }  // namespace cobalt
 
-#endif  // COBALT_MEDIA_BASE_SHELL_DATA_SOURCE_READER_H_
+#endif  // COBALT_MEDIA_PROGRESSIVE_DATA_SOURCE_READER_H_
diff --git a/src/cobalt/media/sandbox/shell_demuxer_fuzzer.cc b/src/cobalt/media/progressive/demuxer_fuzzer.cc
similarity index 78%
rename from src/cobalt/media/sandbox/shell_demuxer_fuzzer.cc
rename to src/cobalt/media/progressive/demuxer_fuzzer.cc
index 74d1e7e..ebd2766 100644
--- a/src/cobalt/media/sandbox/shell_demuxer_fuzzer.cc
+++ b/src/cobalt/media/progressive/demuxer_fuzzer.cc
@@ -25,7 +25,7 @@
 #include "cobalt/media/sandbox/media_sandbox.h"
 #include "media/base/bind_to_loop.h"
 #include "media/base/pipeline_status.h"
-#include "media/filters/shell_demuxer.h"
+#include "media/progressive/progressive_demuxer.h"
 
 namespace cobalt {
 namespace media {
@@ -39,19 +39,20 @@
 using ::media::DemuxerHost;
 using ::media::DemuxerStream;
 using ::media::PipelineStatus;
-using ::media::ShellDemuxer;
+using ::media::ProgressiveDemuxer;
 
-class ShellDemuxerFuzzer : DemuxerHost {
+class DemuxerFuzzer : DemuxerHost {
  public:
-  explicit ShellDemuxerFuzzer(const std::vector<uint8>& content)
+  explicit DemuxerFuzzer(const std::vector<uint8>& content)
       : error_occurred_(false), eos_count_(0), stopped_(false) {
-    demuxer_ = new ShellDemuxer(base::MessageLoop::current()->task_runner(),
-                                new InMemoryDataSource(content));
+    demuxer_ =
+        new ProgressiveDemuxer(base::MessageLoop::current()->task_runner(),
+                               new InMemoryDataSource(content));
   }
 
   void Fuzz() {
     demuxer_->Initialize(
-        this, Bind(&ShellDemuxerFuzzer::InitializeCB, base::Unretained(this)));
+        this, Bind(&DemuxerFuzzer::InitializeCB, base::Unretained(this)));
 
     // Check if there is any error or if both of the audio and video streams
     // have reached eos.
@@ -59,7 +60,7 @@
       base::RunLoop().RunUntilIdle();
     }
 
-    demuxer_->Stop(Bind(&ShellDemuxerFuzzer::StopCB, base::Unretained(this)));
+    demuxer_->Stop(Bind(&DemuxerFuzzer::StopCB, base::Unretained(this)));
 
     while (!stopped_) {
       base::RunLoop().RunUntilIdle();
@@ -101,10 +102,10 @@
       error_occurred_ = true;
       return;
     }
-    audio_stream->Read(BindToCurrentLoop(Bind(
-        &ShellDemuxerFuzzer::ReadCB, base::Unretained(this), audio_stream)));
-    video_stream->Read(BindToCurrentLoop(Bind(
-        &ShellDemuxerFuzzer::ReadCB, base::Unretained(this), video_stream)));
+    audio_stream->Read(BindToCurrentLoop(
+        Bind(&DemuxerFuzzer::ReadCB, base::Unretained(this), audio_stream)));
+    video_stream->Read(BindToCurrentLoop(
+        Bind(&DemuxerFuzzer::ReadCB, base::Unretained(this), video_stream)));
   }
 
   void StopCB() { stopped_ = true; }
@@ -122,18 +123,18 @@
     }
     DCHECK(!error_occurred_);
     stream->Read(BindToCurrentLoop(
-        Bind(&ShellDemuxerFuzzer::ReadCB, base::Unretained(this), stream)));
+        Bind(&DemuxerFuzzer::ReadCB, base::Unretained(this), stream)));
   }
 
   bool error_occurred_;
   int eos_count_;
   bool stopped_;
-  scoped_refptr<ShellDemuxer> demuxer_;
+  scoped_refptr<ProgressiveDemuxer> demuxer_;
 };
 
-class ShellDemuxerFuzzerApp : public FuzzerApp {
+class DemuxerFuzzerApp : public FuzzerApp {
  public:
-  explicit ShellDemuxerFuzzerApp(MediaSandbox* media_sandbox)
+  explicit DemuxerFuzzerApp(MediaSandbox* media_sandbox)
       : media_sandbox_(media_sandbox) {}
 
   std::vector<uint8> ParseFileContent(
@@ -149,7 +150,7 @@
 
   void Fuzz(const std::string& file_name,
             const std::vector<uint8>& fuzzing_content) override {
-    ShellDemuxerFuzzer demuxer_fuzzer(fuzzing_content);
+    DemuxerFuzzer demuxer_fuzzer(fuzzing_content);
     demuxer_fuzzer.Fuzz();
   }
 
@@ -159,9 +160,8 @@
 
 int SandboxMain(int argc, char** argv) {
   MediaSandbox media_sandbox(
-      argc, argv,
-      base::FilePath(FILE_PATH_LITERAL("shell_demuxer_fuzzer.json")));
-  ShellDemuxerFuzzerApp fuzzer_app(&media_sandbox);
+      argc, argv, base::FilePath(FILE_PATH_LITERAL("demuxer_fuzzer.json")));
+  DemuxerFuzzerApp fuzzer_app(&media_sandbox);
 
   if (fuzzer_app.Init(argc, argv)) {
     fuzzer_app.RunFuzzingLoop();
diff --git a/src/cobalt/media/base/mock_shell_data_source_reader.h b/src/cobalt/media/progressive/mock_data_source_reader.h
similarity index 71%
rename from src/cobalt/media/base/mock_shell_data_source_reader.h
rename to src/cobalt/media/progressive/mock_data_source_reader.h
index 08935b2..cf3ddab 100644
--- a/src/cobalt/media/base/mock_shell_data_source_reader.h
+++ b/src/cobalt/media/progressive/mock_data_source_reader.h
@@ -14,20 +14,20 @@
  * limitations under the License.
  */
 
-#ifndef COBALT_MEDIA_BASE_MOCK_SHELL_DATA_SOURCE_READER_H_
-#define COBALT_MEDIA_BASE_MOCK_SHELL_DATA_SOURCE_READER_H_
+#ifndef COBALT_MEDIA_PROGRESSIVE_MOCK_DATA_SOURCE_READER_H_
+#define COBALT_MEDIA_PROGRESSIVE_MOCK_DATA_SOURCE_READER_H_
 
-#include "media/base/shell_data_source_reader.h"
+#include "media/progressive/data_source_reader.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace cobalt {
 namespace media {
 
-class MockShellDataSourceReader : public ShellDataSourceReader {
+class MockDataSourceReader : public DataSourceReader {
  public:
-  MockShellDataSourceReader() {}
+  MockDataSourceReader() {}
 
-  // ShellDataSourceReader implementation
+  // DataSourceReader implementation
   MOCK_METHOD1(SetDataSource, void(DataSource*));
   MOCK_METHOD3(BlockingRead, int(int64, int, uint8*));
   MOCK_METHOD0(FileSize, int64());
@@ -37,4 +37,4 @@
 }  // namespace media
 }  // namespace cobalt
 
-#endif  // COBALT_MEDIA_BASE_MOCK_SHELL_DATA_SOURCE_READER_H_
+#endif  // COBALT_MEDIA_PROGRESSIVE_MOCK_DATA_SOURCE_READER_H_
diff --git a/src/cobalt/media/filters/shell_mp4_map.cc b/src/cobalt/media/progressive/mp4_map.cc
similarity index 92%
rename from src/cobalt/media/filters/shell_mp4_map.cc
rename to src/cobalt/media/progressive/mp4_map.cc
index 3a5189e..3a65467 100644
--- a/src/cobalt/media/filters/shell_mp4_map.cc
+++ b/src/cobalt/media/progressive/mp4_map.cc
@@ -12,23 +12,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/media/filters/shell_mp4_map.h"
+#include "cobalt/media/progressive/mp4_map.h"
 
 #include <algorithm>
 
 #include "base/strings/stringprintf.h"
 #include "cobalt/media/base/endian_util.h"
-#include "cobalt/media/filters/shell_mp4_parser.h"
+#include "cobalt/media/progressive/mp4_parser.h"
 
 namespace cobalt {
 namespace media {
 
 // ==== TableCache =============================================================
 
-ShellMP4Map::TableCache::TableCache(uint64 table_offset, uint32 entry_count,
-                                    uint32 entry_size,
-                                    uint32 cache_size_entries,
-                                    scoped_refptr<ShellDataSourceReader> reader)
+MP4Map::TableCache::TableCache(uint64 table_offset, uint32 entry_count,
+                               uint32 entry_size, uint32 cache_size_entries,
+                               scoped_refptr<DataSourceReader> reader)
     : entry_size_(entry_size),
       entry_count_(entry_count),
       cache_size_entries_(cache_size_entries),
@@ -37,7 +36,7 @@
       cache_first_entry_number_(-1),
       cache_entry_count_(0) {}
 
-uint8* ShellMP4Map::TableCache::GetBytesAtEntry(uint32 entry_number) {
+uint8* MP4Map::TableCache::GetBytesAtEntry(uint32 entry_number) {
   // don't fetch the unfetchable
   if (entry_number >= entry_count_) {
     return NULL;
@@ -77,7 +76,7 @@
   return &cache_[0] + (cache_offset * entry_size_);
 }
 
-bool ShellMP4Map::TableCache::ReadU32Entry(uint32 entry_number, uint32* entry) {
+bool MP4Map::TableCache::ReadU32Entry(uint32 entry_number, uint32* entry) {
   if (uint8* data = GetBytesAtEntry(entry_number)) {
     *entry = endian_util::load_uint32_big_endian(data);
     return true;
@@ -86,8 +85,8 @@
   return false;
 }
 
-bool ShellMP4Map::TableCache::ReadU32PairEntry(uint32 entry_number,
-                                               uint32* first, uint32* second) {
+bool MP4Map::TableCache::ReadU32PairEntry(uint32 entry_number, uint32* first,
+                                          uint32* second) {
   if (uint8* data = GetBytesAtEntry(entry_number)) {
     if (first) *first = endian_util::load_uint32_big_endian(data);
     if (second) *second = endian_util::load_uint32_big_endian(data + 4);
@@ -97,8 +96,8 @@
   return false;
 }
 
-bool ShellMP4Map::TableCache::ReadU32EntryIntoU64(uint32 entry_number,
-                                                  uint64* entry) {
+bool MP4Map::TableCache::ReadU32EntryIntoU64(uint32 entry_number,
+                                             uint64* entry) {
   if (uint8* data = GetBytesAtEntry(entry_number)) {
     *entry = endian_util::load_uint32_big_endian(data);
     return true;
@@ -107,7 +106,7 @@
   return false;
 }
 
-bool ShellMP4Map::TableCache::ReadU64Entry(uint32 entry_number, uint64* entry) {
+bool MP4Map::TableCache::ReadU64Entry(uint32 entry_number, uint64* entry) {
   if (uint8* data = GetBytesAtEntry(entry_number)) {
     *entry = endian_util::load_uint64_big_endian(data);
     return true;
@@ -116,7 +115,7 @@
   return false;
 }
 
-// ==== ShellMP4Map ============================================================
+// ==== MP4Map ============================================================
 
 // atom | name                  | size | description, (*) means optional table
 // -----+-----------------------+------+----------------------------------------
@@ -128,7 +127,7 @@
 // stts | time-to-sample        | 8    | run-length sample number to duration
 // stsz | sample size           | 4    | per-sample list of sample sizes
 
-ShellMP4Map::ShellMP4Map(scoped_refptr<ShellDataSourceReader> reader)
+MP4Map::MP4Map(scoped_refptr<DataSourceReader> reader)
     : reader_(reader),
       current_chunk_sample_(0),
       next_chunk_sample_(0),
@@ -155,14 +154,14 @@
       stts_table_index_(0),
       stsz_default_size_(0) {}
 
-bool ShellMP4Map::IsComplete() {
+bool MP4Map::IsComplete() {
   // all required table pointers must be valid for map to function
   return (co64_ || stco_) && stsc_ && stts_ && (stsz_ || stsz_default_size_);
 }
 
 // The sample size is a lookup in the stsz table, which is indexed per sample
 // number.
-bool ShellMP4Map::GetSize(uint32 sample_number, uint32* size_out) {
+bool MP4Map::GetSize(uint32 sample_number, uint32* size_out) {
   DCHECK(size_out);
   DCHECK(stsz_ || stsz_default_size_);
 
@@ -184,7 +183,7 @@
 // then use the stsz to sum samples to the byte offset with that chunk. The sum
 // of the chunk offset and the byte offset within the chunk is the offset of
 // the sample.
-bool ShellMP4Map::GetOffset(uint32 sample_number, uint64* offset_out) {
+bool MP4Map::GetOffset(uint32 sample_number, uint64* offset_out) {
   DCHECK(offset_out);
   DCHECK(stsc_);
   DCHECK(stco_ || co64_);
@@ -255,7 +254,7 @@
 // durations to find the dts of that sample number. We then integrate sample
 // numbers through the ctts to find the composition time offset, which we add to
 // the dts to return the pts.
-bool ShellMP4Map::GetTimestamp(uint32 sample_number, uint64* timestamp_out) {
+bool MP4Map::GetTimestamp(uint32 sample_number, uint64* timestamp_out) {
   DCHECK(timestamp_out);
   if (sample_number > highest_valid_sample_number_) {
     return false;
@@ -280,7 +279,7 @@
 }
 
 // Sum through the stts to find the duration of the given sample_number.
-bool ShellMP4Map::GetDuration(uint32 sample_number, uint32* duration_out) {
+bool MP4Map::GetDuration(uint32 sample_number, uint32* duration_out) {
   DCHECK(duration_out);
   if (sample_number > highest_valid_sample_number_) {
     return false;
@@ -295,7 +294,7 @@
   return true;
 }
 
-bool ShellMP4Map::GetIsKeyframe(uint32 sample_number, bool* is_keyframe_out) {
+bool MP4Map::GetIsKeyframe(uint32 sample_number, bool* is_keyframe_out) {
   DCHECK(is_keyframe_out);
   if (sample_number > highest_valid_sample_number_) {
     return false;
@@ -336,14 +335,14 @@
   return true;
 }
 
-bool ShellMP4Map::IsEOS(uint32 sample_number) {
+bool MP4Map::IsEOS(uint32 sample_number) {
   return (sample_number > highest_valid_sample_number_);
 }
 
 // First look up the sample number for the provided timestamp by integrating
 // timestamps through the stts. Then do a binary search on the stss to find the
 // keyframe nearest that sample number.
-bool ShellMP4Map::GetKeyframe(uint64 timestamp, uint32* sample_out) {
+bool MP4Map::GetKeyframe(uint64 timestamp, uint32* sample_out) {
   DCHECK(sample_out);
   // Advance stts to the provided timestamp range
   if (!stts_AdvanceToTime(timestamp)) {
@@ -375,8 +374,8 @@
 
 // Set up map state and load first part of table, or entire table if it is small
 // enough, for each of the supporated atoms.
-bool ShellMP4Map::SetAtom(uint32 four_cc, uint64 offset, uint64 size,
-                          uint32 cache_size_entries, const uint8* atom) {
+bool MP4Map::SetAtom(uint32 four_cc, uint64 offset, uint64 size,
+                     uint32 cache_size_entries, const uint8* atom) {
   // All map atoms are variable-length tables starting with 4 bytes of
   // version/flag info followed by a uint32 indicating the number of items in
   // table. The stsz atom bucks tradition by putting an optional default value
@@ -463,7 +462,7 @@
   return atom_init;
 }
 
-bool ShellMP4Map::co64_Init() {
+bool MP4Map::co64_Init() {
   DCHECK(co64_);
   // load offset of first chunk into current_chunk_offset_
   if (co64_->GetEntryCount() > 0) {
@@ -482,7 +481,7 @@
 // uint32 sample count
 // uint32 composition offset in ticks
 //
-bool ShellMP4Map::ctts_Init() {
+bool MP4Map::ctts_Init() {
   DCHECK(ctts_);
   // get cache segment vector to reserve table entries in advance
   int cache_segments =
@@ -506,7 +505,7 @@
 // To find the composition offset of a given sample number we must integrate
 // through the ctts to find the range of samples containing sample_number. Note
 // that the ctts is an optional table.
-bool ShellMP4Map::ctts_AdvanceToSample(uint32 sample_number) {
+bool MP4Map::ctts_AdvanceToSample(uint32 sample_number) {
   // ctts table is optional, so treat not having one as non-fatal
   if (!ctts_) {
     return true;
@@ -573,8 +572,8 @@
   return true;
 }
 
-bool ShellMP4Map::ctts_SlipCacheToSample(uint32 sample_number,
-                                         int starting_cache_index) {
+bool MP4Map::ctts_SlipCacheToSample(uint32 sample_number,
+                                    int starting_cache_index) {
   DCHECK_LT(starting_cache_index, ctts_samples_.size());
   int cache_index = starting_cache_index;
   for (; cache_index + 1 < ctts_samples_.size(); cache_index++) {
@@ -593,7 +592,7 @@
   return true;
 }
 
-bool ShellMP4Map::stco_Init() {
+bool MP4Map::stco_Init() {
   DCHECK(stco_);
   // load offset of first chunk into current_chunk_offset_
   if (stco_->GetEntryCount() > 0) {
@@ -610,7 +609,7 @@
 // uint32 first chunk number with this sample count
 // uint32 samples-per-chunk
 // uint32 sample description id (unused)
-bool ShellMP4Map::stsc_Init() {
+bool MP4Map::stsc_Init() {
   DCHECK(stsc_);
   // set up vector to correct final size
   int cache_segments =
@@ -660,7 +659,7 @@
 // to be consumed incrementally and with minimal memory consumption we calculate
 // this integration step only when needed, and save results for each cached
 // piece of the table, to avoid having to recalculate needed data.
-bool ShellMP4Map::stsc_AdvanceToSample(uint32 sample_number) {
+bool MP4Map::stsc_AdvanceToSample(uint32 sample_number) {
   DCHECK(stsc_);
   // sample_number could be before first chunk, meaning that we are seeking
   // backwards and have left the current chunk. Find the closest part of the
@@ -736,8 +735,8 @@
   return true;
 }
 
-bool ShellMP4Map::stsc_SlipCacheToSample(uint32 sample_number,
-                                         int starting_cache_index) {
+bool MP4Map::stsc_SlipCacheToSample(uint32 sample_number,
+                                    int starting_cache_index) {
   DCHECK_LT(starting_cache_index, stsc_sample_sums_.size());
   // look through old sample sums for the first entry that exceeds sample
   // sample_number, we want the entry right before that
@@ -776,7 +775,7 @@
 }
 
 // stss is a list of sample numbers that are keyframes.
-bool ShellMP4Map::stss_Init() {
+bool MP4Map::stss_Init() {
   int cache_segments =
       (stss_->GetEntryCount() / stss_->GetCacheSizeEntries()) + 1;
   stss_keyframes_.reserve(cache_segments);
@@ -800,7 +799,7 @@
 }
 
 // advance by one table entry through stss, updating cache if necessary
-bool ShellMP4Map::stss_AdvanceStep() {
+bool MP4Map::stss_AdvanceStep() {
   DCHECK(stss_);
   stss_last_keyframe_ = stss_next_keyframe_;
   stss_table_index_++;
@@ -823,7 +822,7 @@
   return true;
 }
 
-bool ShellMP4Map::stss_FindNearestKeyframe(uint32 sample_number) {
+bool MP4Map::stss_FindNearestKeyframe(uint32 sample_number) {
   DCHECK(stss_);
   // it is assumed that there's at least one cache entry created by
   // stss_Init();
@@ -955,7 +954,7 @@
 // The stts table has the following per-entry layout:
 // uint32 sample count - number of sequential samples with this duration
 // uint32 sample duration - duration in ticks of this sample range
-bool ShellMP4Map::stts_Init() {
+bool MP4Map::stts_Init() {
   int cache_segments =
       (stts_->GetEntryCount() / stts_->GetCacheSizeEntries()) + 1;
   stts_samples_.reserve(cache_segments);
@@ -982,7 +981,7 @@
   return true;
 }
 
-bool ShellMP4Map::stts_AdvanceToSample(uint32 sample_number) {
+bool MP4Map::stts_AdvanceToSample(uint32 sample_number) {
   DCHECK(stts_);
   // sample_number could be before our current sample range, in which case
   // we skip to the nearest table entry before sample_number and integrate
@@ -1015,8 +1014,8 @@
 // Move our integration steps to a previously saved entry in the cache tables.
 // Searches linearly through the vector of old cached values, so can accept a
 // starting index to do the search from.
-bool ShellMP4Map::stts_SlipCacheToSample(uint32 sample_number,
-                                         int starting_cache_index) {
+bool MP4Map::stts_SlipCacheToSample(uint32 sample_number,
+                                    int starting_cache_index) {
   DCHECK_LT(starting_cache_index, stts_samples_.size());
   int cache_index = starting_cache_index;
   for (; cache_index + 1 < stts_samples_.size(); cache_index++) {
@@ -1039,7 +1038,7 @@
   return true;
 }
 
-bool ShellMP4Map::stts_AdvanceToTime(uint64 timestamp) {
+bool MP4Map::stts_AdvanceToTime(uint64 timestamp) {
   DCHECK(stts_);
 
   if (timestamp < stts_first_sample_time_) {
@@ -1067,7 +1066,7 @@
   return true;
 }
 
-bool ShellMP4Map::stts_IntegrateStep() {
+bool MP4Map::stts_IntegrateStep() {
   // advance time to next sample range
   uint32 range_size = stts_next_first_sample_ - stts_first_sample_;
   stts_first_sample_time_ += (range_size * stts_sample_duration_);
@@ -1111,8 +1110,7 @@
   return true;
 }
 
-bool ShellMP4Map::stts_SlipCacheToTime(uint64 timestamp,
-                                       int starting_cache_index) {
+bool MP4Map::stts_SlipCacheToTime(uint64 timestamp, int starting_cache_index) {
   DCHECK_LT(starting_cache_index, stts_timestamps_.size());
   int cache_index = starting_cache_index;
   for (; cache_index + 1 < stts_timestamps_.size(); cache_index++) {
@@ -1135,7 +1133,7 @@
   return true;
 }
 
-bool ShellMP4Map::stsz_Init() { return stsz_->GetBytesAtEntry(0) != NULL; }
+bool MP4Map::stsz_Init() { return stsz_->GetBytesAtEntry(0) != NULL; }
 
 }  // namespace media
 }  // namespace cobalt
diff --git a/src/cobalt/media/filters/shell_mp4_map.h b/src/cobalt/media/progressive/mp4_map.h
similarity index 93%
rename from src/cobalt/media/filters/shell_mp4_map.h
rename to src/cobalt/media/progressive/mp4_map.h
index 88e1319..93bc08f 100644
--- a/src/cobalt/media/filters/shell_mp4_map.h
+++ b/src/cobalt/media/progressive/mp4_map.h
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_MEDIA_FILTERS_SHELL_MP4_MAP_H_
-#define COBALT_MEDIA_FILTERS_SHELL_MP4_MAP_H_
+#ifndef COBALT_MEDIA_PROGRESSIVE_MP4_MAP_H_
+#define COBALT_MEDIA_PROGRESSIVE_MP4_MAP_H_
 
 #include <vector>
 
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
-#include "cobalt/media/base/shell_data_source_reader.h"
+#include "cobalt/media/progressive/data_source_reader.h"
 
 namespace cobalt {
 namespace media {
@@ -37,9 +37,9 @@
 // them to provide byte offsets, sizes, and timestamps of a mp4 atom. The
 // caching design benefits from, but does not require, sequential access
 // in sample numbers.
-class ShellMP4Map : public base::RefCountedThreadSafe<ShellMP4Map> {
+class MP4Map : public base::RefCountedThreadSafe<MP4Map> {
  public:
-  explicit ShellMP4Map(scoped_refptr<ShellDataSourceReader> reader);
+  explicit MP4Map(scoped_refptr<DataSourceReader> reader);
 
   bool IsComplete();
 
@@ -117,7 +117,7 @@
                uint32 entry_count,   // number of entries in table
                uint32 entry_size,    // size in bytes of each entry in table
                uint32 cache_size_entries,  // number of entries to cache in mem
-               scoped_refptr<ShellDataSourceReader> reader);  // reader to use
+               scoped_refptr<DataSourceReader> reader);  // reader to use
 
     // The following Read* functions all read values in big endian.
     bool ReadU32Entry(uint32 entry_number, uint32* entry);
@@ -138,7 +138,7 @@
     uint32 entry_count_;         // size of table in entries
     uint32 cache_size_entries_;  // max number of entries to fit in memory
     uint64 table_offset_;        // offset of table in stream
-    scoped_refptr<ShellDataSourceReader> reader_;  // means to read more table
+    scoped_refptr<DataSourceReader> reader_;  // means to read more table
 
     // current cache state
     std::vector<uint8> cache_;         // the cached part of the table
@@ -146,7 +146,7 @@
     uint32 cache_entry_count_;         // number of valid entries in cache
   };
 
-  scoped_refptr<ShellDataSourceReader> reader_;
+  scoped_refptr<DataSourceReader> reader_;
 
   // current integration state for GetOffset(), we save the sum of sample sizes
   // within the current chunk.
@@ -211,4 +211,4 @@
 }  // namespace media
 }  // namespace cobalt
 
-#endif  // COBALT_MEDIA_FILTERS_SHELL_MP4_MAP_H_
+#endif  // COBALT_MEDIA_PROGRESSIVE_MP4_MAP_H_
diff --git a/src/cobalt/media/filters/shell_mp4_map_unittest.cc b/src/cobalt/media/progressive/mp4_map_unittest.cc
similarity index 95%
rename from src/cobalt/media/filters/shell_mp4_map_unittest.cc
rename to src/cobalt/media/progressive/mp4_map_unittest.cc
index 1fa0a73..347b7bb 100644
--- a/src/cobalt/media/filters/shell_mp4_map_unittest.cc
+++ b/src/cobalt/media/progressive/mp4_map_unittest.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/media/filters/shell_mp4_map.h"
+#include "cobalt/media/progressive/mp4_map.h"
 
 #include <stdlib.h>  // for rand and srand
 
@@ -23,8 +23,8 @@
 #include <vector>
 
 #include "cobalt/media/base/endian_util.h"
-#include "cobalt/media/base/mock_shell_data_source_reader.h"
-#include "cobalt/media/filters/shell_mp4_parser.h"
+#include "cobalt/media/progressive/mock_data_source_reader.h"
+#include "cobalt/media/progressive/mp4_parser.h"
 #include "starboard/memory.h"
 #include "starboard/types.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -48,8 +48,8 @@
 using cobalt::media::kEntrySize_stss;
 using cobalt::media::kEntrySize_stsz;
 using cobalt::media::kEntrySize_stts;
-using cobalt::media::MockShellDataSourceReader;
-using cobalt::media::ShellMP4Map;
+using cobalt::media::MockDataSourceReader;
+using cobalt::media::MP4Map;
 
 using ::testing::_;
 using ::testing::AllOf;
@@ -403,16 +403,16 @@
   }
 };
 
-class ShellMP4MapTest : public testing::Test {
+class MP4MapTest : public testing::Test {
  protected:
-  ShellMP4MapTest() {
+  MP4MapTest() {
     // make a new mock reader
-    reader_ = new ::testing::NiceMock<MockShellDataSourceReader>();
+    reader_ = new ::testing::NiceMock<MockDataSourceReader>();
     // make a new map with a mock reader.
-    map_ = new ShellMP4Map(reader_);
+    map_ = new MP4Map(reader_);
   }
 
-  virtual ~ShellMP4MapTest() {
+  virtual ~MP4MapTest() {
     DCHECK(map_->HasOneRef());
     map_ = NULL;
 
@@ -421,7 +421,7 @@
     reader_ = NULL;
   }
 
-  void ResetMap() { map_ = new ShellMP4Map(reader_); }
+  void ResetMap() { map_ = new MP4Map(reader_); }
 
   void CreateTestSampleTable(unsigned int seed, int num_of_samples,
                              int min_sample_size, int max_sample_size,
@@ -454,21 +454,21 @@
   }
 
   // ==== Test Fixture Members
-  scoped_refptr<ShellMP4Map> map_;
-  scoped_refptr<MockShellDataSourceReader> reader_;
+  scoped_refptr<MP4Map> map_;
+  scoped_refptr<MockDataSourceReader> reader_;
   std::unique_ptr<SampleTable> sample_table_;
 };
 
 // ==== SetAtom() Tests ========================================================
 /*
-TEST_F(ShellMP4MapTest, SetAtomWithZeroDefaultSize) {
+TEST_F(MP4MapTest, SetAtomWithZeroDefaultSize) {
   // SetAtom() should fail with a zero default size on an stsc.
   NOTIMPLEMENTED();
 }
 */
 // ==== GetSize() Tests ========================================================
 
-TEST_F(ShellMP4MapTest, GetSizeWithDefaultSize) {
+TEST_F(MP4MapTest, GetSizeWithDefaultSize) {
   CreateTestSampleTable(100, 1000, 0xb0df00d, 0xb0df00d, 5, 10, 5, 10, 10, 20,
                         10, 20);
   sample_table_->ClearReadStatistics();
@@ -490,7 +490,7 @@
   ASSERT_EQ(sample_table_->read_count(), 0);
 }
 
-TEST_F(ShellMP4MapTest, GetSizeIterationWithHugeCache) {
+TEST_F(MP4MapTest, GetSizeIterationWithHugeCache) {
   for (int max_sample_size = 10; max_sample_size < 20; ++max_sample_size) {
     CreateTestSampleTable(200 + max_sample_size, 1000, 10, max_sample_size, 5,
                           10, 5, 10, 10, 20, 10, 20);
@@ -517,7 +517,7 @@
   }
 }
 
-TEST_F(ShellMP4MapTest, GetSizeIterationTinyCache) {
+TEST_F(MP4MapTest, GetSizeIterationTinyCache) {
   for (int max_sample_size = 10; max_sample_size < 20; ++max_sample_size) {
     CreateTestSampleTable(300 + max_sample_size, 1000, 10, max_sample_size, 5,
                           10, 5, 10, 10, 20, 10, 20);
@@ -543,7 +543,7 @@
   }
 }
 
-TEST_F(ShellMP4MapTest, GetSizeRandomAccess) {
+TEST_F(MP4MapTest, GetSizeRandomAccess) {
   CreateTestSampleTable(101, 2000, 20, 24, 5, 10, 5, 10, 10, 20, 10, 20);
   for (int i = 24; i < 27; ++i) {
     ResetMap();
@@ -593,7 +593,7 @@
 
 // ==== GetOffset() Tests ======================================================
 
-TEST_F(ShellMP4MapTest, GetOffsetIterationHugeCache) {
+TEST_F(MP4MapTest, GetOffsetIterationHugeCache) {
   for (int coindex = 0; coindex < 2; ++coindex) {
     CreateTestSampleTable(102 + coindex, 1000, 20, 25, 5, 10, 5, 10, 10, 20, 10,
                           20);
@@ -617,7 +617,7 @@
   }
 }
 
-TEST_F(ShellMP4MapTest, GetOffsetIterationTinyCache) {
+TEST_F(MP4MapTest, GetOffsetIterationTinyCache) {
   for (int coindex = 0; coindex < 2; ++coindex) {
     CreateTestSampleTable(103, 30, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
     for (int i = 1; i < 12; ++i) {
@@ -644,7 +644,7 @@
 
 // Random access within cache should just result in correct re-integration
 // through the stsc.
-TEST_F(ShellMP4MapTest, GetOffsetRandomAccessHugeCache) {
+TEST_F(MP4MapTest, GetOffsetRandomAccessHugeCache) {
   for (int coindex = 0; coindex < 2; ++coindex) {
     CreateTestSampleTable(104, 300, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
     ResetMap();
@@ -664,7 +664,7 @@
 
 // Random access across cache boundaries should not break computation of
 // offsets.
-TEST_F(ShellMP4MapTest, GetOffsetRandomAccessTinyCache) {
+TEST_F(MP4MapTest, GetOffsetRandomAccessTinyCache) {
   for (int coindex = 0; coindex < 2; ++coindex) {
     CreateTestSampleTable(105, 300, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
     ResetMap();
@@ -718,7 +718,7 @@
   }
 }
 
-TEST_F(ShellMP4MapTest, GetOffsetRandomAccessWithDefaultSize) {
+TEST_F(MP4MapTest, GetOffsetRandomAccessWithDefaultSize) {
   for (int coindex = 0; coindex < 2; ++coindex) {
     CreateTestSampleTable(106, 300, 20, 20, 5, 10, 5, 10, 10, 20, 10, 20);
     ResetMap();
@@ -755,7 +755,7 @@
 
 // ==== GetDuration() Tests ====================================================
 
-TEST_F(ShellMP4MapTest, GetDurationIteration) {
+TEST_F(MP4MapTest, GetDurationIteration) {
   CreateTestSampleTable(107, 60, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
   ResetMap();
   SetTestTable(kAtomType_stts, 2);
@@ -773,7 +773,7 @@
       map_->GetDuration(sample_table_->sample_count(), &failed_duration));
 }
 
-TEST_F(ShellMP4MapTest, GetDurationRandomAccess) {
+TEST_F(MP4MapTest, GetDurationRandomAccess) {
   CreateTestSampleTable(108, 60, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
   ResetMap();
   SetTestTable(kAtomType_stts, 3);
@@ -810,7 +810,7 @@
 
 // ==== GetTimestamp() Tests ===================================================
 
-TEST_F(ShellMP4MapTest, GetTimestampIterationNoCompositionTime) {
+TEST_F(MP4MapTest, GetTimestampIterationNoCompositionTime) {
   CreateTestSampleTable(109, 60, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
   ResetMap();
   SetTestTable(kAtomType_stts, 7);
@@ -828,7 +828,7 @@
       map_->GetTimestamp(sample_table_->sample_count(), &failed_timestamp));
 }
 
-TEST_F(ShellMP4MapTest, GetTimestampRandomAccessNoCompositionTime) {
+TEST_F(MP4MapTest, GetTimestampRandomAccessNoCompositionTime) {
   CreateTestSampleTable(110, 60, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
   ResetMap();
   SetTestTable(kAtomType_stts, 10);
@@ -858,7 +858,7 @@
   }
 }
 
-TEST_F(ShellMP4MapTest, GetTimestampIteration) {
+TEST_F(MP4MapTest, GetTimestampIteration) {
   CreateTestSampleTable(111, 300, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
   for (int i = 1; i < 20; ++i) {
     ResetMap();
@@ -879,7 +879,7 @@
   }
 }
 
-TEST_F(ShellMP4MapTest, GetTimestampRandomAccess) {
+TEST_F(MP4MapTest, GetTimestampRandomAccess) {
   CreateTestSampleTable(112, 300, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
   for (int i = 1; i < 20; ++i) {
     ResetMap();
@@ -904,7 +904,7 @@
 // ==== GetIsKeyframe() Tests ==================================================
 
 // the map should consider every valid sample number a keyframe without an stss
-TEST_F(ShellMP4MapTest, GetIsKeyframeNoKeyframeTable) {
+TEST_F(MP4MapTest, GetIsKeyframeNoKeyframeTable) {
   ResetMap();
   bool is_keyframe_out = false;
   ASSERT_TRUE(map_->GetIsKeyframe(100, &is_keyframe_out));
@@ -921,7 +921,7 @@
   }
 }
 
-TEST_F(ShellMP4MapTest, GetIsKeyframeIteration) {
+TEST_F(MP4MapTest, GetIsKeyframeIteration) {
   CreateTestSampleTable(113, 1000, 0xb0df00d, 0xb0df00d, 5, 10, 5, 10, 10, 20,
                         10, 20);
   ResetMap();
@@ -936,7 +936,7 @@
   }
 }
 
-TEST_F(ShellMP4MapTest, GetIsKeyframeRandomAccess) {
+TEST_F(MP4MapTest, GetIsKeyframeRandomAccess) {
   CreateTestSampleTable(114, 1000, 0xb0df00d, 0xb0df00d, 5, 10, 5, 10, 10, 20,
                         10, 20);
   ResetMap();
@@ -1005,7 +1005,7 @@
 
 // every frame should be returned as a keyframe. This tests if our computation
 // of timestamps => sample numbers is equivalent to sample numbers => timestamps
-TEST_F(ShellMP4MapTest, GetKeyframeNoKeyframeTableIteration) {
+TEST_F(MP4MapTest, GetKeyframeNoKeyframeTableIteration) {
   CreateTestSampleTable(115, 30, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
   ResetMap();
   SetTestTable(kAtomType_stts, 7);
@@ -1022,7 +1022,7 @@
   }
 }
 
-TEST_F(ShellMP4MapTest, GetKeyframeNoKeyframeTableRandomAccess) {
+TEST_F(MP4MapTest, GetKeyframeNoKeyframeTableRandomAccess) {
   CreateTestSampleTable(116, 30, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
   ResetMap();
   SetTestTable(kAtomType_stts, 5);
@@ -1056,7 +1056,7 @@
 }
 
 // GetKeyframe is not normally called iteratively, so we test random access
-TEST_F(ShellMP4MapTest, GetKeyframe) {
+TEST_F(MP4MapTest, GetKeyframe) {
   CreateTestSampleTable(117, 60, 20, 25, 5, 10, 5, 10, 10, 20, 10, 20);
   ResetMap();
   SetTestTable(kAtomType_stss, 3);
diff --git a/src/cobalt/media/filters/shell_mp4_parser.cc b/src/cobalt/media/progressive/mp4_parser.cc
similarity index 91%
rename from src/cobalt/media/filters/shell_mp4_parser.cc
rename to src/cobalt/media/progressive/mp4_parser.cc
index 7d9efb1..957395d 100644
--- a/src/cobalt/media/filters/shell_mp4_parser.cc
+++ b/src/cobalt/media/progressive/mp4_parser.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/media/filters/shell_mp4_parser.h"
+#include "cobalt/media/progressive/mp4_parser.h"
 
 #include <inttypes.h>
 #include <limits>
@@ -67,10 +67,10 @@
 static const int kMapTableAtomCacheEntries_ctts = 51543 / kEntrySize_ctts;
 
 // static
-PipelineStatus ShellMP4Parser::Construct(
-    scoped_refptr<ShellDataSourceReader> reader,
-    const uint8* construction_header, scoped_refptr<ShellParser>* parser,
-    const scoped_refptr<MediaLog>& media_log) {
+PipelineStatus MP4Parser::Construct(scoped_refptr<DataSourceReader> reader,
+                                    const uint8* construction_header,
+                                    scoped_refptr<ProgressiveParser>* parser,
+                                    const scoped_refptr<MediaLog>& media_log) {
   DCHECK(parser);
   DCHECK(media_log);
   *parser = NULL;
@@ -90,28 +90,28 @@
   }
 
   // construct new mp4 parser
-  *parser = new ShellMP4Parser(reader, ftyp_atom_size, media_log);
+  *parser = new MP4Parser(reader, ftyp_atom_size, media_log);
   return PIPELINE_OK;
 }
 
-ShellMP4Parser::ShellMP4Parser(scoped_refptr<ShellDataSourceReader> reader,
-                               uint32 ftyp_atom_size,
-                               const scoped_refptr<MediaLog>& media_log)
-    : ShellAVCParser(reader, media_log),
+MP4Parser::MP4Parser(scoped_refptr<DataSourceReader> reader,
+                     uint32 ftyp_atom_size,
+                     const scoped_refptr<MediaLog>& media_log)
+    : AVCParser(reader, media_log),
       atom_offset_(ftyp_atom_size),  // start at next atom, skipping over ftyp
       current_trak_is_video_(false),
       current_trak_is_audio_(false),
       current_trak_time_scale_(0),
       video_time_scale_hz_(0),
       audio_time_scale_hz_(0),
-      audio_map_(new ShellMP4Map(reader)),
-      video_map_(new ShellMP4Map(reader)),
+      audio_map_(new MP4Map(reader)),
+      video_map_(new MP4Map(reader)),
       audio_sample_(0),
       video_sample_(0),
       first_audio_hole_ticks_(0),
       first_audio_hole_(base::TimeDelta::FromSeconds(0)) {}
 
-ShellMP4Parser::~ShellMP4Parser() {}
+MP4Parser::~MP4Parser() {}
 
 // For MP4 we traverse the file's atom structure attempting to find the audio
 // and video configuration information and the locations in the file of the
@@ -119,7 +119,7 @@
 // NALUs in the file. As some of the stbl subatoms can be quite large we cache
 // a fixed maximum quantity of each stbl subatom and update the cache only on
 // miss.
-bool ShellMP4Parser::ParseConfig() {
+bool MP4Parser::ParseConfig() {
   while (!IsConfigComplete() || !audio_map_->IsComplete() ||
          !video_map_->IsComplete()) {
     if (!ParseNextAtom()) {
@@ -129,7 +129,7 @@
   return true;
 }
 
-scoped_refptr<ShellAU> ShellMP4Parser::GetNextAU(DemuxerStream::Type type) {
+scoped_refptr<AvcAccessUnit> MP4Parser::GetNextAU(DemuxerStream::Type type) {
   uint32 size = 0;
   uint32 duration_ticks = 0;
   uint64 timestamp_ticks = 0;
@@ -148,8 +148,8 @@
         !audio_map_->GetTimestamp(audio_sample_, &timestamp_ticks)) {
       // determine if EOS or error
       if (audio_map_->IsEOS(audio_sample_)) {
-        return ShellAU::CreateEndOfStreamAU(DemuxerStream::AUDIO,
-                                            audio_track_duration_);
+        return AvcAccessUnit::CreateEndOfStreamAU(DemuxerStream::AUDIO,
+                                                  audio_track_duration_);
       } else {
         DLOG(ERROR) << "parsed bad audio AU";
         return NULL;
@@ -195,8 +195,8 @@
         !video_map_->GetTimestamp(video_sample_, &timestamp_ticks) ||
         !video_map_->GetIsKeyframe(video_sample_, &is_keyframe)) {
       if (video_map_->IsEOS(video_sample_)) {
-        return ShellAU::CreateEndOfStreamAU(DemuxerStream::VIDEO,
-                                            video_track_duration_);
+        return AvcAccessUnit::CreateEndOfStreamAU(DemuxerStream::VIDEO,
+                                                  video_track_duration_);
       } else {
         DLOG(ERROR) << "parsed bad video AU";
         return NULL;
@@ -219,13 +219,14 @@
   size_t prepend_size = CalculatePrependSize(type, is_keyframe);
 
   if (type == DemuxerStream::AUDIO)
-    return ShellAU::CreateAudioAU(offset, size, prepend_size, is_keyframe,
-                                  timestamp, duration, this);
-  return ShellAU::CreateVideoAU(offset, size, prepend_size, nal_header_size_,
-                                is_keyframe, timestamp, duration, this);
+    return AvcAccessUnit::CreateAudioAU(offset, size, prepend_size, is_keyframe,
+                                        timestamp, duration, this);
+  return AvcAccessUnit::CreateVideoAU(offset, size, prepend_size,
+                                      nal_header_size_, is_keyframe, timestamp,
+                                      duration, this);
 }
 
-bool ShellMP4Parser::SeekTo(base::TimeDelta timestamp) {
+bool MP4Parser::SeekTo(base::TimeDelta timestamp) {
   if (audio_time_scale_hz_ == 0 || video_time_scale_hz_ == 0) {
     DLOG_IF(ERROR, audio_time_scale_hz_ == 0)
         << "|audio_time_scale_hz_| cannot be 0.";
@@ -272,7 +273,7 @@
 // fourCC code       | ASCII  | four-byte ASCII code we treat as uint32
 // extended size     | uint64 | optional size field, only here if atom size is 1
 // <--- rest of atom body starts here
-bool ShellMP4Parser::ParseNextAtom() {
+bool MP4Parser::ParseNextAtom() {
   uint8 atom[kAtomDownload];
   int bytes_read = reader_->BlockingRead(atom_offset_, kAtomDownload, atom);
   if (bytes_read < kAtomDownload) {
@@ -461,7 +462,7 @@
   return atom_parse_success;
 }
 
-bool ShellMP4Parser::ParseMP4_esds(uint64 atom_data_size) {
+bool MP4Parser::ParseMP4_esds(uint64 atom_data_size) {
   if (atom_data_size < kFullBoxHeaderAndFlagSize) {
     DLOG(WARNING) << base::StringPrintf(
         "esds box should at least be %d bytes but now it is %" PRId64 " bytes",
@@ -501,7 +502,7 @@
   return false;
 }
 
-bool ShellMP4Parser::ParseMP4_hdlr(uint64 atom_data_size, uint8* hdlr) {
+bool MP4Parser::ParseMP4_hdlr(uint64 atom_data_size, uint8* hdlr) {
   // ensure we're downloading enough of the hdlr to parse
   DCHECK_LE(kDesiredBytes_hdlr + 16, kAtomDownload);
   // sanity-check for minimum size
@@ -534,7 +535,7 @@
   return true;
 }
 
-bool ShellMP4Parser::ParseMP4_mdhd(uint64 atom_data_size, uint8* mdhd) {
+bool MP4Parser::ParseMP4_mdhd(uint64 atom_data_size, uint8* mdhd) {
   DCHECK_LE(kDesiredBytes_mdhd + 16, kAtomDownload);
   if (atom_data_size < kDesiredBytes_mdhd) {
     DLOG(WARNING) << base::StringPrintf("bad size %" PRId64 " on mdhd",
@@ -577,7 +578,7 @@
   return true;
 }
 
-bool ShellMP4Parser::ParseMP4_mp4a(uint64 atom_data_size, uint8* mp4a) {
+bool MP4Parser::ParseMP4_mp4a(uint64 atom_data_size, uint8* mp4a) {
   DCHECK_LE(kDesiredBytes_mp4a + 16, kAtomDownload);
   // we only need the first two bytes of the header, which details the version
   // number of this atom, which tells us the size of the rest of the header,
@@ -619,7 +620,7 @@
 // 12     | time scale        | 4
 // 16     | duration:         | 4
 //
-bool ShellMP4Parser::ParseMP4_mvhd(uint64 atom_data_size, uint8* mvhd) {
+bool MP4Parser::ParseMP4_mvhd(uint64 atom_data_size, uint8* mvhd) {
   DCHECK_LE(kDesiredBytes_mvhd + 16, kAtomDownload);
   // it should be at least long enough for us to extract the parts we want
   if (atom_data_size < kDesiredBytes_mvhd) {
@@ -641,8 +642,7 @@
   return true;
 }
 
-base::TimeDelta ShellMP4Parser::TicksToTime(uint64 ticks,
-                                            uint32 time_scale_hz) {
+base::TimeDelta MP4Parser::TicksToTime(uint64 ticks, uint32 time_scale_hz) {
   DCHECK_NE(time_scale_hz, 0);
 
   if (time_scale_hz == 0) {
@@ -652,7 +652,7 @@
                                            time_scale_hz);
 }
 
-uint64 ShellMP4Parser::TimeToTicks(base::TimeDelta time, uint32 time_scale_hz) {
+uint64 MP4Parser::TimeToTicks(base::TimeDelta time, uint32 time_scale_hz) {
   DCHECK_NE(time_scale_hz, 0);
 
   if (time_scale_hz == 0) {
diff --git a/src/cobalt/media/filters/shell_mp4_parser.h b/src/cobalt/media/progressive/mp4_parser.h
similarity index 82%
rename from src/cobalt/media/filters/shell_mp4_parser.h
rename to src/cobalt/media/progressive/mp4_parser.h
index 6a16b9f..6d0d617 100644
--- a/src/cobalt/media/filters/shell_mp4_parser.h
+++ b/src/cobalt/media/progressive/mp4_parser.h
@@ -12,12 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_MEDIA_FILTERS_SHELL_MP4_PARSER_H_
-#define COBALT_MEDIA_FILTERS_SHELL_MP4_PARSER_H_
+#ifndef COBALT_MEDIA_PROGRESSIVE_MP4_PARSER_H_
+#define COBALT_MEDIA_PROGRESSIVE_MP4_PARSER_H_
 
 #include "cobalt/media/base/media_log.h"
-#include "cobalt/media/filters/shell_avc_parser.h"
-#include "cobalt/media/filters/shell_mp4_map.h"
+#include "cobalt/media/progressive/avc_parser.h"
+#include "cobalt/media/progressive/mp4_map.h"
 
 namespace cobalt {
 namespace media {
@@ -27,7 +27,7 @@
 // second download (typically), but no larger. This is currently set at 16
 // bytes for the 8 byte header + optional 8 byte size extension plus 20 bytes
 // for the needed values within an mvhd header. We leave this is the header so
-// that ShellMP4Map can re-use,
+// that MP4Map can re-use,
 static const int kAtomDownload = 36;
 
 // mp4 atom fourCC codes as big-endian unsigned ints
@@ -59,24 +59,23 @@
 static const uint32 kAtomType_vmhd = 0x766d6864;  // skip whole atom
 // TODO: mp4v!!
 
-class ShellMP4Parser : public ShellAVCParser {
+class MP4Parser : public AVCParser {
  public:
   // Attempts to make sense of the provided bytes of the top of a file as an
   // flv, and if it does make sense returns PIPELINE_OK and |*parser| contains a
-  // ShellMP4Parser initialized with some basic state.  If it doesn't make sense
+  // MP4Parser initialized with some basic state.  If it doesn't make sense
   // this returns an error status and |*parser| contains NULL.
-  static PipelineStatus Construct(scoped_refptr<ShellDataSourceReader> reader,
+  static PipelineStatus Construct(scoped_refptr<DataSourceReader> reader,
                                   const uint8* construction_header,
-                                  scoped_refptr<ShellParser>* parser,
+                                  scoped_refptr<ProgressiveParser>* parser,
                                   const scoped_refptr<MediaLog>& media_log);
-  ShellMP4Parser(scoped_refptr<ShellDataSourceReader> reader,
-                 uint32 ftyp_atom_size,
-                 const scoped_refptr<MediaLog>& media_log);
-  ~ShellMP4Parser() override;
+  MP4Parser(scoped_refptr<DataSourceReader> reader, uint32 ftyp_atom_size,
+            const scoped_refptr<MediaLog>& media_log);
+  ~MP4Parser() override;
 
-  // === ShellParser implementation
+  // === ProgressiveParser implementation
   bool ParseConfig() override;
-  scoped_refptr<ShellAU> GetNextAU(DemuxerStream::Type type) override;
+  scoped_refptr<AvcAccessUnit> GetNextAU(DemuxerStream::Type type) override;
   bool SeekTo(base::TimeDelta timestamp) override;
 
  private:
@@ -99,8 +98,8 @@
   uint32 audio_time_scale_hz_;
   base::TimeDelta audio_track_duration_;
   base::TimeDelta video_track_duration_;
-  scoped_refptr<ShellMP4Map> audio_map_;
-  scoped_refptr<ShellMP4Map> video_map_;
+  scoped_refptr<MP4Map> audio_map_;
+  scoped_refptr<MP4Map> video_map_;
   uint32 audio_sample_;
   uint32 video_sample_;
   // for keeping buffers continuous across time scales
@@ -111,4 +110,4 @@
 }  // namespace media
 }  // namespace cobalt
 
-#endif  // COBALT_MEDIA_FILTERS_SHELL_MP4_PARSER_H_
+#endif  // COBALT_MEDIA_PROGRESSIVE_MP4_PARSER_H_
diff --git a/src/cobalt/media/filters/shell_demuxer.cc b/src/cobalt/media/progressive/progressive_demuxer.cc
similarity index 77%
rename from src/cobalt/media/filters/shell_demuxer.cc
rename to src/cobalt/media/progressive/progressive_demuxer.cc
index 3d0a8cf..a396825 100644
--- a/src/cobalt/media/filters/shell_demuxer.cc
+++ b/src/cobalt/media/progressive/progressive_demuxer.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/media/filters/shell_demuxer.h"
+#include "cobalt/media/progressive/progressive_demuxer.h"
 
 #include <inttypes.h>
 
@@ -33,14 +33,16 @@
 namespace cobalt {
 namespace media {
 
-ShellDemuxerStream::ShellDemuxerStream(ShellDemuxer* demuxer, Type type)
+ProgressiveDemuxerStream::ProgressiveDemuxerStream(ProgressiveDemuxer* demuxer,
+                                                   Type type)
     : demuxer_(demuxer), type_(type) {
-  TRACE_EVENT0("media_stack", "ShellDemuxerStream::ShellDemuxerStream()");
+  TRACE_EVENT0("media_stack",
+               "ProgressiveDemuxerStream::ProgressiveDemuxerStream()");
   DCHECK(demuxer_);
 }
 
-void ShellDemuxerStream::Read(const ReadCB& read_cb) {
-  TRACE_EVENT0("media_stack", "ShellDemuxerStream::Read()");
+void ProgressiveDemuxerStream::Read(const ReadCB& read_cb) {
+  TRACE_EVENT0("media_stack", "ProgressiveDemuxerStream::Read()");
   DCHECK(!read_cb.is_null());
 
   base::AutoLock auto_lock(lock_);
@@ -48,7 +50,7 @@
   // Don't accept any additional reads if we've been told to stop.
   // The demuxer_ may have been destroyed in the pipleine thread.
   if (stopped_) {
-    TRACE_EVENT0("media_stack", "ShellDemuxerStream::Read() EOS sent.");
+    TRACE_EVENT0("media_stack", "ProgressiveDemuxerStream::Read() EOS sent.");
     read_cb.Run(DemuxerStream::kOk,
                 scoped_refptr<DecoderBuffer>(DecoderBuffer::CreateEOSBuffer()));
     return;
@@ -61,7 +63,7 @@
     // Send the oldest buffer back.
     scoped_refptr<DecoderBuffer> buffer = buffer_queue_.front();
     if (buffer->end_of_stream()) {
-      TRACE_EVENT0("media_stack", "ShellDemuxerStream::Read() EOS sent.");
+      TRACE_EVENT0("media_stack", "ProgressiveDemuxerStream::Read() EOS sent.");
     } else {
       // Do not pop EOS buffers, so that subsequent read requests also get EOS
       total_buffer_size_ -= buffer->data_size();
@@ -70,31 +72,33 @@
     }
     read_cb.Run(DemuxerStream::kOk, buffer);
   } else {
-    TRACE_EVENT0("media_stack", "ShellDemuxerStream::Read() request queued.");
+    TRACE_EVENT0("media_stack",
+                 "ProgressiveDemuxerStream::Read() request queued.");
     read_queue_.push_back(read_cb);
   }
 }
 
-AudioDecoderConfig ShellDemuxerStream::audio_decoder_config() {
+AudioDecoderConfig ProgressiveDemuxerStream::audio_decoder_config() {
   return demuxer_->AudioConfig();
 }
 
-VideoDecoderConfig ShellDemuxerStream::video_decoder_config() {
+VideoDecoderConfig ProgressiveDemuxerStream::video_decoder_config() {
   return demuxer_->VideoConfig();
 }
 
-Ranges<base::TimeDelta> ShellDemuxerStream::GetBufferedRanges() {
+Ranges<base::TimeDelta> ProgressiveDemuxerStream::GetBufferedRanges() {
   base::AutoLock auto_lock(lock_);
   return buffered_ranges_;
 }
 
-DemuxerStream::Type ShellDemuxerStream::type() const { return type_; }
+DemuxerStream::Type ProgressiveDemuxerStream::type() const { return type_; }
 
-void ShellDemuxerStream::EnableBitstreamConverter() { NOTIMPLEMENTED(); }
+void ProgressiveDemuxerStream::EnableBitstreamConverter() { NOTIMPLEMENTED(); }
 
-void ShellDemuxerStream::EnqueueBuffer(scoped_refptr<DecoderBuffer> buffer) {
+void ProgressiveDemuxerStream::EnqueueBuffer(
+    scoped_refptr<DecoderBuffer> buffer) {
   TRACE_EVENT1(
-      "media_stack", "ShellDemuxerStream::EnqueueBuffer()", "timestamp",
+      "media_stack", "ProgressiveDemuxerStream::EnqueueBuffer()", "timestamp",
       buffer->end_of_stream() ? -1 : buffer->timestamp().InMicroseconds());
   base::AutoLock auto_lock(lock_);
   if (stopped_) {
@@ -107,7 +111,7 @@
 
   if (buffer->end_of_stream()) {
     TRACE_EVENT0("media_stack",
-                 "ShellDemuxerStream::EnqueueBuffer() EOS received.");
+                 "ProgressiveDemuxerStream::EnqueueBuffer() EOS received.");
   } else if (buffer->timestamp() != kNoTimestamp) {
     if (last_buffer_timestamp_ != kNoTimestamp &&
         last_buffer_timestamp_ < buffer->timestamp()) {
@@ -135,23 +139,23 @@
   }
 }
 
-base::TimeDelta ShellDemuxerStream::GetLastBufferTimestamp() const {
+base::TimeDelta ProgressiveDemuxerStream::GetLastBufferTimestamp() const {
   base::AutoLock auto_lock(lock_);
   return last_buffer_timestamp_;
 }
 
-size_t ShellDemuxerStream::GetTotalBufferSize() const {
+size_t ProgressiveDemuxerStream::GetTotalBufferSize() const {
   base::AutoLock auto_lock(lock_);
   return total_buffer_size_;
 }
 
-size_t ShellDemuxerStream::GetTotalBufferCount() const {
+size_t ProgressiveDemuxerStream::GetTotalBufferCount() const {
   base::AutoLock auto_lock(lock_);
   return total_buffer_count_;
 }
 
-void ShellDemuxerStream::FlushBuffers() {
-  TRACE_EVENT0("media_stack", "ShellDemuxerStream::FlushBuffers()");
+void ProgressiveDemuxerStream::FlushBuffers() {
+  TRACE_EVENT0("media_stack", "ProgressiveDemuxerStream::FlushBuffers()");
   base::AutoLock auto_lock(lock_);
   // TODO: Investigate if the following warning is valid.
   DLOG_IF(WARNING, !read_queue_.empty()) << "Read requests should be empty";
@@ -161,8 +165,8 @@
   last_buffer_timestamp_ = kNoTimestamp;
 }
 
-void ShellDemuxerStream::Stop() {
-  TRACE_EVENT0("media_stack", "ShellDemuxerStream::Stop()");
+void ProgressiveDemuxerStream::Stop() {
+  TRACE_EVENT0("media_stack", "ProgressiveDemuxerStream::Stop()");
   DCHECK(demuxer_->MessageLoopBelongsToCurrentThread());
   base::AutoLock auto_lock(lock_);
   buffer_queue_.clear();
@@ -172,7 +176,7 @@
   // fulfill any pending callbacks with EOS buffers set to end timestamp
   for (ReadQueue::iterator it = read_queue_.begin(); it != read_queue_.end();
        ++it) {
-    TRACE_EVENT0("media_stack", "ShellDemuxerStream::Stop() EOS sent.");
+    TRACE_EVENT0("media_stack", "ProgressiveDemuxerStream::Stop() EOS sent.");
     it->Run(DemuxerStream::kOk,
             scoped_refptr<DecoderBuffer>(DecoderBuffer::CreateEOSBuffer()));
   }
@@ -181,16 +185,16 @@
 }
 
 //
-// ShellDemuxer
+// ProgressiveDemuxer
 //
-ShellDemuxer::ShellDemuxer(
+ProgressiveDemuxer::ProgressiveDemuxer(
     const scoped_refptr<base::SingleThreadTaskRunner>& message_loop,
     DecoderBuffer::Allocator* buffer_allocator, DataSource* data_source,
     const scoped_refptr<MediaLog>& media_log)
     : message_loop_(message_loop),
       buffer_allocator_(buffer_allocator),
       host_(NULL),
-      blocking_thread_("ShellDemuxerBlk"),
+      blocking_thread_("ProgDemuxerBlk"),
       data_source_(data_source),
       media_log_(media_log),
       stopped_(false),
@@ -201,20 +205,20 @@
   DCHECK(buffer_allocator_);
   DCHECK(data_source_);
   DCHECK(media_log_);
-  reader_ = new ShellDataSourceReader();
+  reader_ = new DataSourceReader();
   reader_->SetDataSource(data_source_);
 }
 
-ShellDemuxer::~ShellDemuxer() {
+ProgressiveDemuxer::~ProgressiveDemuxer() {
   // Explicitly stop |blocking_thread_| to ensure that it stops before the
   // destructiing of any other members.
   blocking_thread_.Stop();
 }
 
-void ShellDemuxer::Initialize(DemuxerHost* host,
-                              const PipelineStatusCB& status_cb,
-                              bool enable_text_tracks) {
-  TRACE_EVENT0("media_stack", "ShellDemuxer::Initialize()");
+void ProgressiveDemuxer::Initialize(DemuxerHost* host,
+                                    const PipelineStatusCB& status_cb,
+                                    bool enable_text_tracks) {
+  TRACE_EVENT0("media_stack", "ProgressiveDemuxer::Initialize()");
   DCHECK(!enable_text_tracks);
   DCHECK(MessageLoopBelongsToCurrentThread());
   DCHECK(reader_);
@@ -226,9 +230,9 @@
 
   // create audio and video demuxer stream objects
   audio_demuxer_stream_.reset(
-      new ShellDemuxerStream(this, DemuxerStream::AUDIO));
+      new ProgressiveDemuxerStream(this, DemuxerStream::AUDIO));
   video_demuxer_stream_.reset(
-      new ShellDemuxerStream(this, DemuxerStream::VIDEO));
+      new ProgressiveDemuxerStream(this, DemuxerStream::VIDEO));
 
   // start the blocking thread and have it download and parse the media config
   if (!blocking_thread_.Start()) {
@@ -237,16 +241,18 @@
   }
 
   blocking_thread_.task_runner()->PostTask(
-      FROM_HERE, base::Bind(&ShellDemuxer::ParseConfigBlocking,
+      FROM_HERE, base::Bind(&ProgressiveDemuxer::ParseConfigBlocking,
                             base::Unretained(this), status_cb));
 }
 
-void ShellDemuxer::ParseConfigBlocking(const PipelineStatusCB& status_cb) {
+void ProgressiveDemuxer::ParseConfigBlocking(
+    const PipelineStatusCB& status_cb) {
   DCHECK(blocking_thread_.task_runner()->BelongsToCurrentThread());
   DCHECK(!parser_);
 
   // construct stream parser with error callback
-  PipelineStatus status = ShellParser::Construct(reader_, &parser_, media_log_);
+  PipelineStatus status =
+      ProgressiveParser::Construct(reader_, &parser_, media_log_);
   // if we can't construct a parser for this stream it's a fatal error, return
   // false so ParseConfigDone will notify the caller to Initialize() via
   // status_cb.
@@ -286,8 +292,8 @@
   ParseConfigDone(status_cb, PIPELINE_OK);
 }
 
-void ShellDemuxer::ParseConfigDone(const PipelineStatusCB& status_cb,
-                                   PipelineStatus status) {
+void ProgressiveDemuxer::ParseConfigDone(const PipelineStatusCB& status_cb,
+                                         PipelineStatus status) {
   DCHECK(blocking_thread_.task_runner()->BelongsToCurrentThread());
 
   if (HasStopCalled()) {
@@ -306,18 +312,18 @@
   status_cb.Run(PIPELINE_OK);
 }
 
-void ShellDemuxer::Request(DemuxerStream::Type type) {
+void ProgressiveDemuxer::Request(DemuxerStream::Type type) {
   if (!blocking_thread_.task_runner()->BelongsToCurrentThread()) {
     blocking_thread_.task_runner()->PostTask(
         FROM_HERE,
-        base::Bind(&ShellDemuxer::Request, base::Unretained(this), type));
+        base::Bind(&ProgressiveDemuxer::Request, base::Unretained(this), type));
     return;
   }
 
   DCHECK(!requested_au_) << "overlapping requests not supported!";
   flushing_ = false;
   // Ask parser for next AU
-  scoped_refptr<ShellAU> au = parser_->GetNextAU(type);
+  scoped_refptr<AvcAccessUnit> au = parser_->GetNextAU(type);
   // fatal parsing error returns NULL or malformed AU
   if (!au || !au->IsValid()) {
     if (!HasStopCalled()) {
@@ -332,12 +338,12 @@
 
   const char* ALLOW_UNUSED_TYPE event_type =
       type == DemuxerStream::AUDIO ? "audio" : "video";
-  TRACE_EVENT2("media_stack", "ShellDemuxer::RequestTask()", "type", event_type,
-               "timestamp", au->GetTimestamp().InMicroseconds());
+  TRACE_EVENT2("media_stack", "ProgressiveDemuxer::RequestTask()", "type",
+               event_type, "timestamp", au->GetTimestamp().InMicroseconds());
 
   // don't issue allocation requests for EOS AUs
   if (au->IsEndOfStream()) {
-    TRACE_EVENT0("media_stack", "ShellDemuxer::RequestTask() EOS sent");
+    TRACE_EVENT0("media_stack", "ProgressiveDemuxer::RequestTask() EOS sent");
     // enqueue EOS buffer with correct stream
     scoped_refptr<DecoderBuffer> eos_buffer = DecoderBuffer::CreateEOSBuffer();
     if (type == DemuxerStream::AUDIO) {
@@ -357,7 +363,7 @@
   AllocateBuffer();
 }
 
-void ShellDemuxer::AllocateBuffer() {
+void ProgressiveDemuxer::AllocateBuffer() {
   DCHECK(requested_au_);
 
   if (HasStopCalled()) {
@@ -385,7 +391,8 @@
       const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(100);
       blocking_thread_.message_loop()->task_runner()->PostDelayedTask(
           FROM_HERE,
-          base::Bind(&ShellDemuxer::AllocateBuffer, base::Unretained(this)),
+          base::Bind(&ProgressiveDemuxer::AllocateBuffer,
+                     base::Unretained(this)),
           kDelay);
       return;
     }
@@ -404,13 +411,14 @@
       const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(100);
       blocking_thread_.message_loop()->task_runner()->PostDelayedTask(
           FROM_HERE,
-          base::Bind(&ShellDemuxer::AllocateBuffer, base::Unretained(this)),
+          base::Bind(&ProgressiveDemuxer::AllocateBuffer,
+                     base::Unretained(this)),
           kDelay);
     }
   }
 }
 
-void ShellDemuxer::Download(scoped_refptr<DecoderBuffer> buffer) {
+void ProgressiveDemuxer::Download(scoped_refptr<DecoderBuffer> buffer) {
   DCHECK(blocking_thread_.task_runner()->BelongsToCurrentThread());
   // We need a requested_au_ or to have canceled this request and
   // are buffering to a new location for this to make sense
@@ -418,8 +426,9 @@
 
   const char* ALLOW_UNUSED_TYPE event_type =
       requested_au_->GetType() == DemuxerStream::AUDIO ? "audio" : "video";
-  TRACE_EVENT2("media_stack", "ShellDemuxer::Download()", "type", event_type,
-               "timestamp", requested_au_->GetTimestamp().InMicroseconds());
+  TRACE_EVENT2("media_stack", "ProgressiveDemuxer::Download()", "type",
+               event_type, "timestamp",
+               requested_au_->GetTimestamp().InMicroseconds());
   // do nothing if stopped
   if (HasStopCalled()) {
     DLOG(INFO) << "aborting download task, stopped";
@@ -466,11 +475,11 @@
   host_->OnBufferedTimeRangesChanged(buffered);
 
   blocking_thread_.task_runner()->PostTask(
-      FROM_HERE,
-      base::Bind(&ShellDemuxer::IssueNextRequest, base::Unretained(this)));
+      FROM_HERE, base::Bind(&ProgressiveDemuxer::IssueNextRequest,
+                            base::Unretained(this)));
 }
 
-void ShellDemuxer::IssueNextRequest() {
+void ProgressiveDemuxer::IssueNextRequest() {
   DCHECK(!requested_au_);
   // if we're stopped don't download anymore
   if (HasStopCalled()) {
@@ -519,10 +528,10 @@
   // running in a tight loop and seek or stop request has no chance to kick in.
   blocking_thread_.task_runner()->PostTask(
       FROM_HERE,
-      base::Bind(&ShellDemuxer::Request, base::Unretained(this), type));
+      base::Bind(&ProgressiveDemuxer::Request, base::Unretained(this), type));
 }
 
-void ShellDemuxer::Stop() {
+void ProgressiveDemuxer::Stop() {
   DCHECK(MessageLoopBelongsToCurrentThread());
   // set our internal stop flag, to not treat read failures as
   // errors anymore but as a natural part of stopping
@@ -535,8 +544,8 @@
   reader_->Stop();
 }
 
-void ShellDemuxer::DataSourceStopped(const base::Closure& callback) {
-  TRACE_EVENT0("media_stack", "ShellDemuxer::DataSourceStopped()");
+void ProgressiveDemuxer::DataSourceStopped(const base::Closure& callback) {
+  TRACE_EVENT0("media_stack", "ProgressiveDemuxer::DataSourceStopped()");
   DCHECK(MessageLoopBelongsToCurrentThread());
   // stop the download thread
   blocking_thread_.Stop();
@@ -548,20 +557,23 @@
   callback.Run();
 }
 
-bool ShellDemuxer::HasStopCalled() {
+bool ProgressiveDemuxer::HasStopCalled() {
   base::AutoLock auto_lock(lock_for_stopped_);
   return stopped_;
 }
 
-void ShellDemuxer::Seek(base::TimeDelta time, const PipelineStatusCB& cb) {
+void ProgressiveDemuxer::Seek(base::TimeDelta time,
+                              const PipelineStatusCB& cb) {
   blocking_thread_.message_loop()->task_runner()->PostTask(
-      FROM_HERE, base::Bind(&ShellDemuxer::SeekTask, base::Unretained(this),
-                            time, BindToCurrentLoop(cb)));
+      FROM_HERE,
+      base::Bind(&ProgressiveDemuxer::SeekTask, base::Unretained(this), time,
+                 BindToCurrentLoop(cb)));
 }
 
 // runs on blocking thread
-void ShellDemuxer::SeekTask(base::TimeDelta time, const PipelineStatusCB& cb) {
-  TRACE_EVENT1("media_stack", "ShellDemuxer::SeekTask()", "timestamp",
+void ProgressiveDemuxer::SeekTask(base::TimeDelta time,
+                                  const PipelineStatusCB& cb) {
+  TRACE_EVENT1("media_stack", "ProgressiveDemuxer::SeekTask()", "timestamp",
                time.InMicroseconds());
   DLOG(INFO) << base::StringPrintf("seek to: %" PRId64 " ms",
                                    time.InMilliseconds());
@@ -586,7 +598,7 @@
   }
 }
 
-DemuxerStream* ShellDemuxer::GetStream(media::DemuxerStream::Type type) {
+DemuxerStream* ProgressiveDemuxer::GetStream(media::DemuxerStream::Type type) {
   if (type == DemuxerStream::AUDIO) {
     return audio_demuxer_stream_.get();
   } else if (type == DemuxerStream::VIDEO) {
@@ -597,20 +609,20 @@
   return NULL;
 }
 
-base::TimeDelta ShellDemuxer::GetStartTime() const {
+base::TimeDelta ProgressiveDemuxer::GetStartTime() const {
   // we always assume a start time of 0
   return base::TimeDelta();
 }
 
-const AudioDecoderConfig& ShellDemuxer::AudioConfig() {
+const AudioDecoderConfig& ProgressiveDemuxer::AudioConfig() {
   return parser_->AudioConfig();
 }
 
-const VideoDecoderConfig& ShellDemuxer::VideoConfig() {
+const VideoDecoderConfig& ProgressiveDemuxer::VideoConfig() {
   return parser_->VideoConfig();
 }
 
-bool ShellDemuxer::MessageLoopBelongsToCurrentThread() const {
+bool ProgressiveDemuxer::MessageLoopBelongsToCurrentThread() const {
   return message_loop_->BelongsToCurrentThread();
 }
 
diff --git a/src/cobalt/media/filters/shell_demuxer.h b/src/cobalt/media/progressive/progressive_demuxer.h
similarity index 81%
rename from src/cobalt/media/filters/shell_demuxer.h
rename to src/cobalt/media/progressive/progressive_demuxer.h
index 38f3dde..6ecb323 100644
--- a/src/cobalt/media/filters/shell_demuxer.h
+++ b/src/cobalt/media/progressive/progressive_demuxer.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_MEDIA_FILTERS_SHELL_DEMUXER_H_
-#define COBALT_MEDIA_FILTERS_SHELL_DEMUXER_H_
+#ifndef COBALT_MEDIA_PROGRESSIVE_PROGRESSIVE_DEMUXER_H_
+#define COBALT_MEDIA_PROGRESSIVE_PROGRESSIVE_DEMUXER_H_
 
 #include <deque>
 #include <memory>
@@ -28,17 +28,17 @@
 #include "cobalt/media/base/demuxer_stream.h"
 #include "cobalt/media/base/media_log.h"
 #include "cobalt/media/base/ranges.h"
-#include "cobalt/media/filters/shell_parser.h"
+#include "cobalt/media/progressive/progressive_parser.h"
 
 namespace cobalt {
 namespace media {
 
 class DecoderBuffer;
-class ShellDemuxer;
+class ProgressiveDemuxer;
 
-class ShellDemuxerStream : public DemuxerStream {
+class ProgressiveDemuxerStream : public DemuxerStream {
  public:
-  ShellDemuxerStream(ShellDemuxer* demuxer, Type type);
+  ProgressiveDemuxerStream(ProgressiveDemuxer* demuxer, Type type);
 
   // DemuxerStream implementation
   void Read(const ReadCB& read_cb) override;
@@ -56,7 +56,7 @@
     NOTREACHED();
   }
 
-  // Functions used by ShellDemuxer
+  // Functions used by ProgressiveDemuxer
   Ranges<base::TimeDelta> GetBufferedRanges();
   void EnqueueBuffer(scoped_refptr<DecoderBuffer> buffer);
   void FlushBuffers();
@@ -72,7 +72,7 @@
   void RebuildEnqueuedRanges_Locked();
 
   // non-owning pointer to avoid circular reference
-  ShellDemuxer* demuxer_;
+  ProgressiveDemuxer* demuxer_;
   Type type_;
 
   // Used to protect everything below.
@@ -97,19 +97,19 @@
   size_t total_buffer_size_ = 0;
   size_t total_buffer_count_ = 0;
 
-  DISALLOW_COPY_AND_ASSIGN(ShellDemuxerStream);
+  DISALLOW_COPY_AND_ASSIGN(ProgressiveDemuxerStream);
 };
 
-class MEDIA_EXPORT ShellDemuxer : public Demuxer {
+class MEDIA_EXPORT ProgressiveDemuxer : public Demuxer {
  public:
-  ShellDemuxer(const scoped_refptr<base::SingleThreadTaskRunner>& message_loop,
-               DecoderBuffer::Allocator* buffer_allocator,
-               DataSource* data_source,
-               const scoped_refptr<MediaLog>& media_log);
-  ~ShellDemuxer() override;
+  ProgressiveDemuxer(
+      const scoped_refptr<base::SingleThreadTaskRunner>& message_loop,
+      DecoderBuffer::Allocator* buffer_allocator, DataSource* data_source,
+      const scoped_refptr<MediaLog>& media_log);
+  ~ProgressiveDemuxer() override;
 
   // Demuxer implementation.
-  std::string GetDisplayName() const override { return "ShellDemuxer"; }
+  std::string GetDisplayName() const override { return "ProgressiveDemuxer"; }
   void Initialize(DemuxerHost* host, const PipelineStatusCB& status_cb,
                   bool enable_text_tracks) override;
   void AbortPendingReads() override {}
@@ -140,12 +140,12 @@
   // in to it, and enqueue the data in the appropriate demuxer stream.
   void Request(DemuxerStream::Type type);
 
-  // The DemuxerStream objects ask their parent ShellDemuxer stream class
+  // The DemuxerStream objects ask their parent ProgressiveDemuxer stream class
   // for these configuration data rather than duplicating in the child classes
   const AudioDecoderConfig& AudioConfig();
   const VideoDecoderConfig& VideoConfig();
 
-  // Provide access to ShellDemuxerStream.
+  // Provide access to ProgressiveDemuxerStream.
   bool MessageLoopBelongsToCurrentThread() const;
 
  private:
@@ -170,17 +170,17 @@
   base::Thread blocking_thread_;
   DataSource* data_source_;
   scoped_refptr<MediaLog> media_log_;
-  scoped_refptr<ShellDataSourceReader> reader_;
+  scoped_refptr<DataSourceReader> reader_;
 
   base::Lock lock_for_stopped_;
   bool stopped_;
   bool flushing_;
 
-  std::unique_ptr<ShellDemuxerStream> audio_demuxer_stream_;
-  std::unique_ptr<ShellDemuxerStream> video_demuxer_stream_;
-  scoped_refptr<ShellParser> parser_;
+  std::unique_ptr<ProgressiveDemuxerStream> audio_demuxer_stream_;
+  std::unique_ptr<ProgressiveDemuxerStream> video_demuxer_stream_;
+  scoped_refptr<ProgressiveParser> parser_;
 
-  scoped_refptr<ShellAU> requested_au_;
+  scoped_refptr<AvcAccessUnit> requested_au_;
   bool audio_reached_eos_;
   bool video_reached_eos_;
 };
@@ -188,4 +188,4 @@
 }  // namespace media
 }  // namespace cobalt
 
-#endif  // COBALT_MEDIA_FILTERS_SHELL_DEMUXER_H_
+#endif  // COBALT_MEDIA_PROGRESSIVE_PROGRESSIVE_DEMUXER_H_
diff --git a/src/cobalt/media/filters/shell_parser.cc b/src/cobalt/media/progressive/progressive_parser.cc
similarity index 71%
rename from src/cobalt/media/filters/shell_parser.cc
rename to src/cobalt/media/progressive/progressive_parser.cc
index 3ff978e..f9ffb84 100644
--- a/src/cobalt/media/filters/shell_parser.cc
+++ b/src/cobalt/media/progressive/progressive_parser.cc
@@ -12,24 +12,25 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/media/filters/shell_parser.h"
+#include "cobalt/media/progressive/progressive_parser.h"
 
 #include "base/logging.h"
 #include "cobalt/media/base/timestamp_constants.h"
-#include "cobalt/media/filters/shell_mp4_parser.h"
+#include "cobalt/media/progressive/mp4_parser.h"
 
 namespace cobalt {
 namespace media {
 
-// ==== ShellParser ============================================================
+// ==== ProgressiveParser
+// ============================================================
 
 // how many bytes to download of the file to determine type?
-const int ShellParser::kInitialHeaderSize = 9;
+const int ProgressiveParser::kInitialHeaderSize = 9;
 
 // static
-PipelineStatus ShellParser::Construct(
-    scoped_refptr<ShellDataSourceReader> reader,
-    scoped_refptr<ShellParser>* parser,
+PipelineStatus ProgressiveParser::Construct(
+    scoped_refptr<DataSourceReader> reader,
+    scoped_refptr<ProgressiveParser>* parser,
     const scoped_refptr<MediaLog>& media_log) {
   DCHECK(parser);
   DCHECK(media_log);
@@ -44,15 +45,15 @@
   }
 
   // attempt to construct mp4 parser from this header
-  return ShellMP4Parser::Construct(reader, header, parser, media_log);
+  return MP4Parser::Construct(reader, header, parser, media_log);
 }
 
-ShellParser::ShellParser(scoped_refptr<ShellDataSourceReader> reader)
+ProgressiveParser::ProgressiveParser(scoped_refptr<DataSourceReader> reader)
     : reader_(reader), duration_(kInfiniteDuration), bits_per_second_(0) {}
 
-ShellParser::~ShellParser() {}
+ProgressiveParser::~ProgressiveParser() {}
 
-bool ShellParser::IsConfigComplete() {
+bool ProgressiveParser::IsConfigComplete() {
   return video_config_.IsValidConfig() && audio_config_.IsValidConfig() &&
          duration_ != kInfiniteDuration;
 }
diff --git a/src/cobalt/media/filters/shell_parser.h b/src/cobalt/media/progressive/progressive_parser.h
similarity index 77%
rename from src/cobalt/media/filters/shell_parser.h
rename to src/cobalt/media/progressive/progressive_parser.h
index 119a20c..a0e6f41 100644
--- a/src/cobalt/media/filters/shell_parser.h
+++ b/src/cobalt/media/progressive/progressive_parser.h
@@ -12,31 +12,31 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_MEDIA_FILTERS_SHELL_PARSER_H_
-#define COBALT_MEDIA_FILTERS_SHELL_PARSER_H_
-
+#ifndef COBALT_MEDIA_PROGRESSIVE_PARSER_H_
+#define COBALT_MEDIA_PROGRESSIVE_PARSER_H_
 #include "base/memory/ref_counted.h"
 #include "cobalt/media/base/audio_decoder_config.h"
 #include "cobalt/media/base/demuxer_stream.h"
 #include "cobalt/media/base/media_log.h"
 #include "cobalt/media/base/pipeline.h"
-#include "cobalt/media/base/shell_data_source_reader.h"
 #include "cobalt/media/base/video_decoder_config.h"
-#include "cobalt/media/filters/shell_au.h"
+#include "cobalt/media/progressive/avc_access_unit.h"
+#include "cobalt/media/progressive/data_source_reader.h"
 
 namespace cobalt {
 namespace media {
 
-// abstract base class to define a stream parser interface used by ShellDemuxer.
-class ShellParser : public base::RefCountedThreadSafe<ShellParser> {
+// abstract base class to define a stream parser interface used by
+// ProgressiveDemuxer.
+class ProgressiveParser : public base::RefCountedThreadSafe<ProgressiveParser> {
  public:
   static const int kInitialHeaderSize;
   // Determine stream type, construct appropriate parser object, and returns
   // PIPELINE_OK on success or error code.
-  static PipelineStatus Construct(scoped_refptr<ShellDataSourceReader> reader,
-                                  scoped_refptr<ShellParser>* parser,
+  static PipelineStatus Construct(scoped_refptr<DataSourceReader> reader,
+                                  scoped_refptr<ProgressiveParser>* parser,
                                   const scoped_refptr<MediaLog>& media_log);
-  explicit ShellParser(scoped_refptr<ShellDataSourceReader> reader);
+  explicit ProgressiveParser(scoped_refptr<DataSourceReader> reader);
 
   // Seek through the file looking for audio and video configuration info,
   // saving as much config state as is possible. Should try to be fast but this
@@ -47,10 +47,10 @@
   // downloding and decoding the next access unit in the stream, or NULL on
   // fatal error. On success this advances the respective audio or video cursor
   // to the next AU.
-  virtual scoped_refptr<ShellAU> GetNextAU(DemuxerStream::Type type) = 0;
+  virtual scoped_refptr<AvcAccessUnit> GetNextAU(DemuxerStream::Type type) = 0;
   // Write the appropriate prepend header for the supplied au into the supplied
   // buffer. Return false on error.
-  virtual bool Prepend(scoped_refptr<ShellAU> au,
+  virtual bool Prepend(scoped_refptr<AvcAccessUnit> au,
                        scoped_refptr<DecoderBuffer> buffer) = 0;
   // Advance internal state to provided timestamp. Return false on error.
   virtual bool SeekTo(base::TimeDelta timestamp) = 0;
@@ -68,9 +68,9 @@
 
  protected:
   // only allow RefCountedThreadSafe to delete us
-  friend class base::RefCountedThreadSafe<ShellParser>;
-  virtual ~ShellParser();
-  scoped_refptr<ShellDataSourceReader> reader_;
+  friend class base::RefCountedThreadSafe<ProgressiveParser>;
+  virtual ~ProgressiveParser();
+  scoped_refptr<DataSourceReader> reader_;
   AudioDecoderConfig audio_config_;
   VideoDecoderConfig video_config_;
   base::TimeDelta duration_;
@@ -80,4 +80,4 @@
 }  // namespace media
 }  // namespace cobalt
 
-#endif  // COBALT_MEDIA_FILTERS_SHELL_PARSER_H_
+#endif  // COBALT_MEDIA_PROGRESSIVE_PARSER_H_
diff --git a/src/cobalt/media/filters/shell_rbsp_stream.cc b/src/cobalt/media/progressive/rbsp_stream.cc
similarity index 90%
rename from src/cobalt/media/filters/shell_rbsp_stream.cc
rename to src/cobalt/media/progressive/rbsp_stream.cc
index 6a3fb50..ea90f7a 100644
--- a/src/cobalt/media/filters/shell_rbsp_stream.cc
+++ b/src/cobalt/media/progressive/rbsp_stream.cc
@@ -12,15 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/media/filters/shell_rbsp_stream.h"
+#include "cobalt/media/progressive/rbsp_stream.h"
 
 #include "base/logging.h"
 
 namespace cobalt {
 namespace media {
 
-ShellRBSPStream::ShellRBSPStream(const uint8* nalu_buffer,
-                                 size_t nalu_buffer_size)
+RBSPStream::RBSPStream(const uint8* nalu_buffer, size_t nalu_buffer_size)
     : nalu_buffer_(nalu_buffer),
       nalu_buffer_size_(nalu_buffer_size),
       nalu_buffer_byte_offset_(0),
@@ -29,7 +28,7 @@
       rbsp_bit_offset_(0) {}
 
 // read unsigned Exp-Golomb coded integer, ISO 14496-10 Section 9.1
-bool ShellRBSPStream::ReadUEV(uint32* uev_out) {
+bool RBSPStream::ReadUEV(uint32* uev_out) {
   DCHECK(uev_out);
   int leading_zero_bits = -1;
   for (uint8 b = 0; b == 0; leading_zero_bits++) {
@@ -52,7 +51,7 @@
 }
 
 // read signed Exp-Golomb coded integer, ISO 14496-10 Section 9.1
-bool ShellRBSPStream::ReadSEV(int32* sev_out) {
+bool RBSPStream::ReadSEV(int32* sev_out) {
   DCHECK(sev_out);
   // we start off by reading an unsigned Exp-Golomb coded number
   uint32 uev = 0;
@@ -71,7 +70,7 @@
 
 // read and return up to 32 bits, filling from the right, meaning that
 // ReadBits(17) on a stream of all 1s would return 0x01ffff
-bool ShellRBSPStream::ReadBits(size_t bits, uint32* bits_out) {
+bool RBSPStream::ReadBits(size_t bits, uint32* bits_out) {
   DCHECK(bits_out);
   if (bits > 32) {
     return false;
@@ -105,7 +104,7 @@
 }
 
 // jump over bytes in the RBSP stream
-bool ShellRBSPStream::SkipBytes(size_t bytes) {
+bool RBSPStream::SkipBytes(size_t bytes) {
   for (int i = 0; i < bytes; ++i) {
     if (!ConsumeNALUByte()) {
       return false;
@@ -115,7 +114,7 @@
 }
 
 // jump over bits in the RBSP stream
-bool ShellRBSPStream::SkipBits(size_t bits) {
+bool RBSPStream::SkipBits(size_t bits) {
   // skip bytes first
   size_t bytes = bits >> 3;
   if (bytes > 0) {
@@ -152,7 +151,7 @@
 
 // advance by one byte through the NALU buffer, respecting the encoding of
 // 00 00 03 => 00 00. Updates the state of current_nalu_byte_ to the new value.
-bool ShellRBSPStream::ConsumeNALUByte() {
+bool RBSPStream::ConsumeNALUByte() {
   if (nalu_buffer_byte_offset_ >= nalu_buffer_size_) {
     return false;
   }
@@ -174,7 +173,7 @@
 
 // return single bit in the LSb from the RBSP stream. Bits are read from MSb
 // to LSb in the stream.
-bool ShellRBSPStream::ReadRBSPBit(uint8* bit_out) {
+bool RBSPStream::ReadRBSPBit(uint8* bit_out) {
   DCHECK(bit_out);
   // check to see if we need to consume a fresh byte
   if (rbsp_bit_offset_ == 0) {
@@ -190,7 +189,7 @@
   return true;
 }
 
-bool ShellRBSPStream::ReadRBSPByte(uint8* byte_out) {
+bool RBSPStream::ReadRBSPByte(uint8* byte_out) {
   DCHECK(byte_out);
   // fast path for byte-aligned access
   if (rbsp_bit_offset_ == 0) {
diff --git a/src/cobalt/media/filters/shell_rbsp_stream.h b/src/cobalt/media/progressive/rbsp_stream.h
similarity index 91%
rename from src/cobalt/media/filters/shell_rbsp_stream.h
rename to src/cobalt/media/progressive/rbsp_stream.h
index 5a46499..0c8d8bc 100644
--- a/src/cobalt/media/filters/shell_rbsp_stream.h
+++ b/src/cobalt/media/progressive/rbsp_stream.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef COBALT_MEDIA_FILTERS_SHELL_RBSP_STREAM_H_
-#define COBALT_MEDIA_FILTERS_SHELL_RBSP_STREAM_H_
+#ifndef COBALT_MEDIA_PROGRESSIVE_RBSP_STREAM_H_
+#define COBALT_MEDIA_PROGRESSIVE_RBSP_STREAM_H_
 
 #include "base/basictypes.h"
 
@@ -25,11 +25,11 @@
 // that some other atoms are defined. This class takes a non-owning reference
 // to a buffer and extract various types from the stream while silently
 // consuming the extra encoding bytes and advancing a bit stream pointer.
-class ShellRBSPStream {
+class RBSPStream {
  public:
   // NON-OWNING pointer to buffer. It is assumed the client will dispose of
   // this buffer.
-  ShellRBSPStream(const uint8* nalu_buffer, size_t nalu_buffer_size);
+  RBSPStream(const uint8* nalu_buffer, size_t nalu_buffer_size);
   // all Read/Skip methods return the value by reference and return true
   // on success, false on read error/EOB. Once the object has returned
   // false the consistency of the data is not guaranteed.
@@ -70,4 +70,4 @@
 }  // namespace media
 }  // namespace cobalt
 
-#endif  // COBALT_MEDIA_FILTERS_SHELL_RBSP_STREAM_H_
+#endif  // COBALT_MEDIA_PROGRESSIVE_RBSP_STREAM_H_
diff --git a/src/cobalt/media/filters/shell_rbsp_stream_unittest.cc b/src/cobalt/media/progressive/rbsp_stream_unittest.cc
similarity index 89%
rename from src/cobalt/media/filters/shell_rbsp_stream_unittest.cc
rename to src/cobalt/media/progressive/rbsp_stream_unittest.cc
index 472639b..33669ed 100644
--- a/src/cobalt/media/filters/shell_rbsp_stream_unittest.cc
+++ b/src/cobalt/media/progressive/rbsp_stream_unittest.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "cobalt/media/filters/shell_rbsp_stream.h"
+#include "cobalt/media/progressive/rbsp_stream.h"
 
 #include <list>
 #include <memory>
@@ -24,11 +24,11 @@
 namespace cobalt {
 namespace media {
 
-class ShellRBSPStreamTest : public testing::Test {
+class RBSPStreamTest : public testing::Test {
  protected:
-  ShellRBSPStreamTest() {}
+  RBSPStreamTest() {}
 
-  virtual ~ShellRBSPStreamTest() {}
+  virtual ~RBSPStreamTest() {}
 
   // Given num encode the value in signed exp-golomb syntax and push
   // the value on the provided bitlist
@@ -76,7 +76,7 @@
   }
 
   // after building a bitlist in various fun ways call this method to
-  // create a buffer on the heap that can be passed to ShellRBSPStream
+  // create a buffer on the heap that can be passed to RBSPStream
   // for deserialization.
   std::unique_ptr<uint8[]> SerializeToBuffer(const std::list<bool>& bitlist,
                                              bool add_sequence_bytes,
@@ -150,7 +150,7 @@
   }
 };
 
-TEST_F(ShellRBSPStreamTest, ReadUEV) {
+TEST_F(RBSPStreamTest, ReadUEV) {
   std::list<bool> fibbits;
   // encode first 47 Fibonacci numbers
   uint32 f_n_minus_2 = 0;
@@ -171,9 +171,9 @@
   size_t fib_buffer_no_sequence_size;
   std::unique_ptr<uint8[]> fib_buffer_no_sequence =
       SerializeToBuffer(fibbits, false, fib_buffer_no_sequence_size);
-  ShellRBSPStream fib_stream(fib_buffer.get(), fib_buffer_size);
-  ShellRBSPStream fib_stream_no_sequence(fib_buffer_no_sequence.get(),
-                                         fib_buffer_no_sequence_size);
+  RBSPStream fib_stream(fib_buffer.get(), fib_buffer_size);
+  RBSPStream fib_stream_no_sequence(fib_buffer_no_sequence.get(),
+                                    fib_buffer_no_sequence_size);
   // deserialize the same sequence from both buffers
   uint32 uev = 0;
   uint32 uev_n = 0;
@@ -203,7 +203,7 @@
   ASSERT_FALSE(fib_stream_no_sequence.ReadUEV(&uev_n));
 }
 
-TEST_F(ShellRBSPStreamTest, ReadSEV) {
+TEST_F(RBSPStreamTest, ReadSEV) {
   std::list<bool> lucasbits;
   // encode first 44 Lucas numbers with alternating sign
   int32 l_n_minus_2 = 1;
@@ -227,10 +227,9 @@
   size_t lucas_deseq_buffer_size = 0;
   std::unique_ptr<uint8[]> lucas_deseq_buffer =
       SerializeToBuffer(lucasbits, false, lucas_deseq_buffer_size);
-  ShellRBSPStream lucas_seq_stream(lucas_seq_buffer.get(),
-                                   lucas_seq_buffer_size);
-  ShellRBSPStream lucas_deseq_stream(lucas_deseq_buffer.get(),
-                                     lucas_deseq_buffer_size);
+  RBSPStream lucas_seq_stream(lucas_seq_buffer.get(), lucas_seq_buffer_size);
+  RBSPStream lucas_deseq_stream(lucas_deseq_buffer.get(),
+                                lucas_deseq_buffer_size);
   l_n_minus_2 = 1;
   l_n_minus_1 = 2;
   int32 sev = 0;
@@ -286,10 +285,10 @@
     // 1111 111+0 (to complete the byte)
     0xfe};
 
-TEST_F(ShellRBSPStreamTest, ReadUEVTooLarge) {
+TEST_F(RBSPStreamTest, ReadUEVTooLarge) {
   // construct a stream from the supplied test data
-  ShellRBSPStream uev_too_big(kTestRBSPExpGolombTooBig,
-                              sizeof(kTestRBSPExpGolombTooBig));
+  RBSPStream uev_too_big(kTestRBSPExpGolombTooBig,
+                         sizeof(kTestRBSPExpGolombTooBig));
   // first call should succeed
   uint32 uev = 0;
   ASSERT_TRUE(uev_too_big.ReadUEV(&uev));
@@ -301,10 +300,10 @@
   ASSERT_FALSE(uev_too_big.ReadUEV(&uev));
 }
 
-TEST_F(ShellRBSPStreamTest, ReadSEVTooLarge) {
+TEST_F(RBSPStreamTest, ReadSEVTooLarge) {
   // construct a stream from the supplied test data
-  ShellRBSPStream sev_too_big(kTestRBSPExpGolombTooBig,
-                              sizeof(kTestRBSPExpGolombTooBig));
+  RBSPStream sev_too_big(kTestRBSPExpGolombTooBig,
+                         sizeof(kTestRBSPExpGolombTooBig));
   // first call should succeed
   int32 sev = 0;
   ASSERT_TRUE(sev_too_big.ReadSEV(&sev));
@@ -316,7 +315,7 @@
   ASSERT_FALSE(sev_too_big.ReadSEV(&sev));
 }
 
-TEST_F(ShellRBSPStreamTest, ReadBit) {
+TEST_F(RBSPStreamTest, ReadBit) {
   std::list<bool> padded_ones;
   // build a bitfield of 1 padded by n zeros, for n in range[0, 1024]
   for (int i = 0; i < 1024; i++) {
@@ -329,12 +328,12 @@
   size_t sequence_buff_size = 0;
   std::unique_ptr<uint8[]> sequence_buff =
       SerializeToBuffer(padded_ones, true, sequence_buff_size);
-  ShellRBSPStream seq_stream(sequence_buff.get(), sequence_buff_size);
+  RBSPStream seq_stream(sequence_buff.get(), sequence_buff_size);
 
   size_t desequence_buff_size = 0;
   std::unique_ptr<uint8[]> desequence_buff =
       SerializeToBuffer(padded_ones, false, desequence_buff_size);
-  ShellRBSPStream deseq_stream(desequence_buff.get(), desequence_buff_size);
+  RBSPStream deseq_stream(desequence_buff.get(), desequence_buff_size);
   for (std::list<bool>::iterator it = padded_ones.begin();
        it != padded_ones.end(); ++it) {
     uint8 bit = 0;
@@ -351,7 +350,7 @@
   ASSERT_FALSE(deseq_stream.ReadByte(&fail_byte));
 }
 
-TEST_F(ShellRBSPStreamTest, ReadByte) {
+TEST_F(RBSPStreamTest, ReadByte) {
   // build a field of 16 x (0xaa byte followed by 0 bit)
   std::list<bool> aa_field;
   for (int i = 0; i < 16; ++i) {
@@ -364,7 +363,7 @@
   size_t aabuff_size = 0;
   std::unique_ptr<uint8[]> aabuff =
       SerializeToBuffer(aa_field, true, aabuff_size);
-  ShellRBSPStream aa_stream(aabuff.get(), aabuff_size);
+  RBSPStream aa_stream(aabuff.get(), aabuff_size);
   for (int i = 0; i < 16; ++i) {
     uint8 aa = 0;
     ASSERT_TRUE(aa_stream.ReadByte(&aa));
@@ -397,11 +396,11 @@
   size_t zseqbuff_size = 0;
   std::unique_ptr<uint8[]> zseqbuff =
       SerializeToBuffer(zero_field, true, zseqbuff_size);
-  ShellRBSPStream zseq_stream(zseqbuff.get(), zseqbuff_size);
+  RBSPStream zseq_stream(zseqbuff.get(), zseqbuff_size);
   size_t zdseqbuff_size = 0;
   std::unique_ptr<uint8[]> zdseqbuff =
       SerializeToBuffer(zero_field, false, zdseqbuff_size);
-  ShellRBSPStream zdseq_stream(zdseqbuff.get(), zdseqbuff_size);
+  RBSPStream zdseq_stream(zdseqbuff.get(), zdseqbuff_size);
   for (int i = 0; i < 24; ++i) {
     // read the leading 1 bit
     uint8 seq_bit = 0;
@@ -454,7 +453,7 @@
   }
 }
 
-TEST_F(ShellRBSPStreamTest, ReadBits) {
+TEST_F(RBSPStreamTest, ReadBits) {
   // test the assertion in the ReadBits comment, as it had a bug :)
   std::list<bool> seventeen_ones;
   for (int i = 0; i < 17; ++i) {
@@ -463,8 +462,8 @@
   size_t seventeen_ones_size = 0;
   std::unique_ptr<uint8[]> seventeen_ones_buff =
       SerializeToBuffer(seventeen_ones, false, seventeen_ones_size);
-  ShellRBSPStream seventeen_ones_stream(seventeen_ones_buff.get(),
-                                        seventeen_ones_size);
+  RBSPStream seventeen_ones_stream(seventeen_ones_buff.get(),
+                                   seventeen_ones_size);
   uint32 seventeen_ones_word = 0;
   ASSERT_TRUE(seventeen_ones_stream.ReadBits(17, &seventeen_ones_word));
   ASSERT_EQ(seventeen_ones_word, 0x0001ffff);
@@ -479,7 +478,7 @@
   }
   size_t pows_size = 0;
   std::unique_ptr<uint8[]> pows_buff = SerializeToBuffer(pows, true, pows_size);
-  ShellRBSPStream pows_stream(pows_buff.get(), pows_size);
+  RBSPStream pows_stream(pows_buff.get(), pows_size);
   // ReadBits(0) should succeed and not modify the value of the ref output or
   // internal bit iterator
   uint32 dont_touch = 0xfeedfeed;
@@ -493,7 +492,7 @@
   }
 }
 
-TEST_F(ShellRBSPStreamTest, SkipBytes) {
+TEST_F(RBSPStreamTest, SkipBytes) {
   // serialize all nine-bit values from zero to 512
   std::list<bool> nines;
   for (int i = 0; i < 512; ++i) {
@@ -507,8 +506,8 @@
   size_t nines_deseq_size = 0;
   std::unique_ptr<uint8[]> nines_deseq_buff =
       SerializeToBuffer(nines, false, nines_deseq_size);
-  ShellRBSPStream nines_stream(nines_buff.get(), nines_size);
-  ShellRBSPStream nines_deseq_stream(nines_deseq_buff.get(), nines_deseq_size);
+  RBSPStream nines_stream(nines_buff.get(), nines_size);
+  RBSPStream nines_deseq_stream(nines_deseq_buff.get(), nines_deseq_size);
   // iterate through streams, skipping in one and reading in the other, always
   // comparing values.
   for (int i = 0; i < 512; ++i) {
@@ -547,9 +546,9 @@
   size_t run_length_deseq_size = 0;
   std::unique_ptr<uint8[]> run_length_deseq_buff =
       SerializeToBuffer(run_length, false, run_length_deseq_size);
-  ShellRBSPStream run_length_stream(run_length_buff.get(), run_length_size);
-  ShellRBSPStream run_length_deseq_stream(run_length_deseq_buff.get(),
-                                          run_length_deseq_size);
+  RBSPStream run_length_stream(run_length_buff.get(), run_length_size);
+  RBSPStream run_length_deseq_stream(run_length_deseq_buff.get(),
+                                     run_length_deseq_size);
   // read first bit, skip first byte from each stream, read next bit
   uint8 bit = 0;
   ASSERT_TRUE(run_length_stream.ReadBit(&bit));
@@ -593,7 +592,7 @@
   ASSERT_FALSE(run_length_deseq_stream.SkipBytes(1));
 }
 
-TEST_F(ShellRBSPStreamTest, SkipBits) {
+TEST_F(RBSPStreamTest, SkipBits) {
   std::list<bool> one_ohs;
   // encode one 1, followed by one zero, followed by 2 1s, followed by 2 zeros,
   // etc
@@ -611,8 +610,8 @@
   size_t skip_ohs_size = 0;
   std::unique_ptr<uint8[]> skip_ohs_buff =
       SerializeToBuffer(one_ohs, false, skip_ohs_size);
-  ShellRBSPStream skip_ones(skip_ones_buff.get(), skip_ones_size);
-  ShellRBSPStream skip_ohs(skip_ohs_buff.get(), skip_ohs_size);
+  RBSPStream skip_ones(skip_ones_buff.get(), skip_ones_size);
+  RBSPStream skip_ohs(skip_ohs_buff.get(), skip_ohs_size);
   for (int i = 1; i < 64; ++i) {
     // skip the ones
     ASSERT_TRUE(skip_ones.SkipBits(i));
diff --git a/src/cobalt/media/sandbox/demuxer_helper.cc b/src/cobalt/media/sandbox/demuxer_helper.cc
index fa343fa..c5b2991 100644
--- a/src/cobalt/media/sandbox/demuxer_helper.cc
+++ b/src/cobalt/media/sandbox/demuxer_helper.cc
@@ -24,7 +24,7 @@
 #include "media/base/bind_to_loop.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/video_decoder_config.h"
-#include "media/filters/shell_demuxer.h"
+#include "media/progressive/progressive_demuxer.h"
 
 namespace cobalt {
 namespace media {
@@ -263,7 +263,7 @@
                                     fetcher_factory->network_module(),
                                     loader::kNoCORSMode, loader::Origin()));
   scoped_refptr<Demuxer> demuxer =
-      new ::media::ShellDemuxer(media_message_loop, data_source);
+      new ::media::ProgressiveDemuxer(media_message_loop, data_source);
   demuxer->Initialize(
       host_, base::Bind(&DemuxerHelper::OnDemuxerReady, base::Unretained(this),
                         demuxer, demuxer_ready_cb, bytes_to_cache));
diff --git a/src/cobalt/media/sandbox/format_guesstimator.cc b/src/cobalt/media/sandbox/format_guesstimator.cc
index 9d6c3f2..a616d53 100644
--- a/src/cobalt/media/sandbox/format_guesstimator.cc
+++ b/src/cobalt/media/sandbox/format_guesstimator.cc
@@ -34,8 +34,8 @@
 #include "cobalt/render_tree/image.h"
 #include "net/base/filename_util.h"
 #include "net/base/url_util.h"
+#include "starboard/common/file.h"
 #include "starboard/common/string.h"
-#include "starboard/file.h"
 #include "starboard/memory.h"
 #include "starboard/types.h"
 
diff --git a/src/cobalt/media/sandbox/web_media_player_sandbox.cc b/src/cobalt/media/sandbox/web_media_player_sandbox.cc
index c947020..f97a2b6 100644
--- a/src/cobalt/media/sandbox/web_media_player_sandbox.cc
+++ b/src/cobalt/media/sandbox/web_media_player_sandbox.cc
@@ -30,8 +30,8 @@
 #include "cobalt/media/sandbox/media_sandbox.h"
 #include "cobalt/media/sandbox/web_media_player_helper.h"
 #include "cobalt/render_tree/image.h"
+#include "starboard/common/file.h"
 #include "starboard/event.h"
-#include "starboard/file.h"
 #include "starboard/log.h"
 #include "starboard/system.h"
 
diff --git a/src/cobalt/media_capture/encoders/audio_encoder.h b/src/cobalt/media_capture/encoders/audio_encoder.h
index 63134e0..0efe09b 100644
--- a/src/cobalt/media_capture/encoders/audio_encoder.h
+++ b/src/cobalt/media_capture/encoders/audio_encoder.h
@@ -20,10 +20,10 @@
 #include "base/basictypes.h"
 #include "base/strings/string_piece.h"
 #include "base/time/time.h"
+#include "cobalt/media/base/audio_bus.h"
 #include "cobalt/media_stream/audio_parameters.h"
 #include "starboard/common/mutex.h"
 
-#include "cobalt/media/base/shell_audio_bus.h"
 
 namespace cobalt {
 namespace media_capture {
@@ -31,7 +31,7 @@
 
 class AudioEncoder {
  public:
-  typedef media::ShellAudioBus ShellAudioBus;
+  typedef media::AudioBus AudioBus;
 
   class Listener {
    public:
@@ -44,7 +44,7 @@
   virtual ~AudioEncoder() = default;
 
   // Encode raw audio data.
-  virtual void Encode(const ShellAudioBus& audio_bus,
+  virtual void Encode(const AudioBus& audio_bus,
                       base::TimeTicks reference_time) = 0;
   // Finish encoding.
   virtual void Finish(base::TimeTicks timecode) = 0;
diff --git a/src/cobalt/media_capture/encoders/flac_audio_encoder.cc b/src/cobalt/media_capture/encoders/flac_audio_encoder.cc
index 0e45933..7c57c69 100644
--- a/src/cobalt/media_capture/encoders/flac_audio_encoder.cc
+++ b/src/cobalt/media_capture/encoders/flac_audio_encoder.cc
@@ -59,11 +59,11 @@
       base::Bind(&FlacAudioEncoder::DestroyEncoder, base::Unretained(this)));
 }
 
-void FlacAudioEncoder::Encode(const ShellAudioBus& audio_bus,
+void FlacAudioEncoder::Encode(const AudioBus& audio_bus,
                               base::TimeTicks reference_time) {
-  std::unique_ptr<ShellAudioBus> audio_bus_copy(
-      new ShellAudioBus(audio_bus.channels(), audio_bus.frames(),
-                        audio_bus.sample_type(), audio_bus.storage_type()));
+  std::unique_ptr<AudioBus> audio_bus_copy(
+      new AudioBus(audio_bus.channels(), audio_bus.frames(),
+                   audio_bus.sample_type(), audio_bus.storage_type()));
   audio_bus_copy->Assign(audio_bus);
 
   // base::Unretained usage is safe here, since we're posting to a thread that
@@ -107,7 +107,7 @@
 
 void FlacAudioEncoder::DestroyEncoder() { flac_encoder_.reset(); }
 
-void FlacAudioEncoder::DoEncode(std::unique_ptr<ShellAudioBus> audio_bus,
+void FlacAudioEncoder::DoEncode(std::unique_ptr<AudioBus> audio_bus,
                                 base::TimeTicks reference_time) {
   DCHECK(flac_encoder_);
   flac_encoder_->Encode(audio_bus.get());
diff --git a/src/cobalt/media_capture/encoders/flac_audio_encoder.h b/src/cobalt/media_capture/encoders/flac_audio_encoder.h
index 90d4bd4..a982eaf 100644
--- a/src/cobalt/media_capture/encoders/flac_audio_encoder.h
+++ b/src/cobalt/media_capture/encoders/flac_audio_encoder.h
@@ -36,7 +36,7 @@
 
   // Called from the thread the object is constructed on (usually
   // a dedicated audio thread).
-  void Encode(const ShellAudioBus& audio_bus,
+  void Encode(const AudioBus& audio_bus,
               base::TimeTicks reference_time) override;
 
   // This can be called from any thread
@@ -54,7 +54,7 @@
   // These functions are called on the encoder thread.
   void CreateEncoder(const media_stream::AudioParameters& params);
   void DestroyEncoder();
-  void DoEncode(std::unique_ptr<ShellAudioBus> audio_bus,
+  void DoEncode(std::unique_ptr<AudioBus> audio_bus,
                 base::TimeTicks reference_time);
   void DoFinish(base::TimeTicks reference_time);
 
diff --git a/src/cobalt/media_capture/encoders/linear16_audio_encoder.cc b/src/cobalt/media_capture/encoders/linear16_audio_encoder.cc
index f18f035..c5148ac 100644
--- a/src/cobalt/media_capture/encoders/linear16_audio_encoder.cc
+++ b/src/cobalt/media_capture/encoders/linear16_audio_encoder.cc
@@ -43,9 +43,9 @@
   return match_iterator == mime_type_container.begin();
 }
 
-void Linear16AudioEncoder::Encode(const ShellAudioBus& audio_bus,
+void Linear16AudioEncoder::Encode(const AudioBus& audio_bus,
                                   base::TimeTicks reference_time) {
-  DCHECK_EQ(audio_bus.sample_type(), ShellAudioBus::kInt16);
+  DCHECK_EQ(audio_bus.sample_type(), AudioBus::kInt16);
   DCHECK_EQ(audio_bus.channels(), size_t(1));
   auto data = audio_bus.interleaved_data();
   size_t data_size = audio_bus.GetSampleSizeInBytes() * audio_bus.frames();
diff --git a/src/cobalt/media_capture/encoders/linear16_audio_encoder.h b/src/cobalt/media_capture/encoders/linear16_audio_encoder.h
index bb5f5d7..75040f4 100644
--- a/src/cobalt/media_capture/encoders/linear16_audio_encoder.h
+++ b/src/cobalt/media_capture/encoders/linear16_audio_encoder.h
@@ -29,7 +29,7 @@
   static bool IsLinear16MIMEType(const base::StringPiece& mime_type);
 
   Linear16AudioEncoder() = default;
-  void Encode(const ShellAudioBus& audio_bus,
+  void Encode(const AudioBus& audio_bus,
               base::TimeTicks reference_time) override;
   void Finish(base::TimeTicks reference_time) override;
   std::string GetMimeType() const override;
diff --git a/src/cobalt/media_capture/media_recorder.cc b/src/cobalt/media_capture/media_recorder.cc
index 3a015f0..6642bfc 100644
--- a/src/cobalt/media_capture/media_recorder.cc
+++ b/src/cobalt/media_capture/media_recorder.cc
@@ -161,10 +161,10 @@
   StopRecording();
 }
 
-void MediaRecorder::OnData(const ShellAudioBus& audio_bus,
+void MediaRecorder::OnData(const AudioBus& audio_bus,
                            base::TimeTicks reference_time) {
   // The source is always int16 data from the microphone.
-  DCHECK_EQ(audio_bus.sample_type(), ShellAudioBus::kInt16);
+  DCHECK_EQ(audio_bus.sample_type(), AudioBus::kInt16);
   DCHECK_EQ(audio_bus.channels(), size_t(1));
   DCHECK(audio_encoder_);
   audio_encoder_->Encode(audio_bus, reference_time);
diff --git a/src/cobalt/media_capture/media_recorder.h b/src/cobalt/media_capture/media_recorder.h
index a7b1c9d..5883af8 100644
--- a/src/cobalt/media_capture/media_recorder.h
+++ b/src/cobalt/media_capture/media_recorder.h
@@ -118,7 +118,7 @@
   RecordingState state() { return recording_state_; }
 
   // MediaStreamAudioSink overrides.
-  void OnData(const ShellAudioBus& audio_bus,
+  void OnData(const AudioBus& audio_bus,
               base::TimeTicks reference_time) override;
   void OnSetFormat(const media_stream::AudioParameters& params) override;
   void OnReadyStateChanged(
diff --git a/src/cobalt/media_capture/media_recorder_test.cc b/src/cobalt/media_capture/media_recorder_test.cc
index cfc5e3c..46fab4a 100644
--- a/src/cobalt/media_capture/media_recorder_test.cc
+++ b/src/cobalt/media_capture/media_recorder_test.cc
@@ -52,7 +52,7 @@
   frames.push_back(32767);
   frames.push_back(1000);
   frames.push_back(0);
-  cobalt::media_stream::MediaStreamAudioTrack::ShellAudioBus audio_bus(
+  cobalt::media_stream::MediaStreamAudioTrack::AudioBus audio_bus(
       1, frames.size(), frames.data());
   base::TimeTicks current_time = base::TimeTicks::Now();
   media_recorder->OnData(audio_bus, current_time);
@@ -68,9 +68,8 @@
   MOCK_METHOD0(EnsureSourceIsStarted, bool());
   MOCK_METHOD0(EnsureSourceIsStopped, void());
 
-  void DeliverDataToTracks(
-      const MediaStreamAudioTrack::ShellAudioBus& audio_bus,
-      base::TimeTicks reference_time) {
+  void DeliverDataToTracks(const MediaStreamAudioTrack::AudioBus& audio_bus,
+                           base::TimeTicks reference_time) {
     MediaStreamAudioSource::DeliverDataToTracks(audio_bus, reference_time);
   }
 
@@ -191,8 +190,8 @@
   frames.push_back(32767);
   frames.push_back(1000);
   frames.push_back(0);
-  media_stream::MediaStreamAudioTrack::ShellAudioBus audio_bus(1, frames.size(),
-                                                               frames.data());
+  media_stream::MediaStreamAudioTrack::AudioBus audio_bus(1, frames.size(),
+                                                          frames.data());
   base::TimeTicks current_time = base::TimeTicks::Now();
   media_recorder_->OnData(audio_bus, current_time);
   current_time += base::TimeDelta::FromSecondsD(frames.size() / kSampleRate);
diff --git a/src/cobalt/media_session/media_session_client.cc b/src/cobalt/media_session/media_session_client.cc
index 3263591..4b32aa9 100644
--- a/src/cobalt/media_session/media_session_client.cc
+++ b/src/cobalt/media_session/media_session_client.cc
@@ -64,7 +64,6 @@
         session_state->available_actions());
   }
 }
-
 }  // namespace
 
 MediaSessionClient::MediaSessionClient(
@@ -171,38 +170,41 @@
   return result;
 }
 
-void MediaSessionClient::UpdatePlatformPlaybackState(
-    MediaSessionPlaybackState state) {
+void MediaSessionClient::UpdatePlatformCobaltExtensionPlaybackState(
+    CobaltExtensionMediaSessionPlaybackState state) {
   DCHECK(media_session_->task_runner_);
   if (!media_session_->task_runner_->BelongsToCurrentThread()) {
     media_session_->task_runner_->PostTask(
-        FROM_HERE, base::Bind(&MediaSessionClient::UpdatePlatformPlaybackState,
-                              base::Unretained(this), state));
+        FROM_HERE,
+        base::Bind(
+            &MediaSessionClient::UpdatePlatformCobaltExtensionPlaybackState,
+            base::Unretained(this), state));
     return;
   }
 
-  platform_playback_state_ = state;
+  platform_playback_state_ = ConvertPlaybackState(state);
   if (session_state_.actual_playback_state() != ComputeActualPlaybackState()) {
     UpdateMediaSessionState();
   }
+
+  if (!is_active() && !maybe_freeze_callback_.is_null()) {
+    maybe_freeze_callback_.Run();
+  }
 }
 
 void MediaSessionClient::InvokeActionInternal(
-    std::unique_ptr<MediaSessionActionDetails> details) {
-  DCHECK(details->has_action());
+    CobaltExtensionMediaSessionActionDetails* details) {
+  DCHECK(details->action >= 0 &&
+         details->action < kCobaltExtensionMediaSessionActionNumActions);
 
   // Some fields should only be set for applicable actions.
-  DCHECK(!details->has_seek_offset() ||
-         details->action() == kMediaSessionActionSeekforward ||
-         details->action() == kMediaSessionActionSeekbackward);
-  DCHECK(!details->has_seek_time() ||
-         details->action() == kMediaSessionActionSeekto);
-  DCHECK(!details->has_fast_seek() ||
-         details->action() == kMediaSessionActionSeekto);
-
-  // Seek times/offsets are non-negative, even for seeking backwards.
-  DCHECK(!details->has_seek_time() || details->seek_time() >= 0.0);
-  DCHECK(!details->has_seek_offset() || details->seek_offset() >= 0.0);
+  DCHECK(details->seek_offset < 0.0 ||
+         details->action == kCobaltExtensionMediaSessionActionSeekforward ||
+         details->action == kCobaltExtensionMediaSessionActionSeekbackward);
+  DCHECK(details->seek_time < 0.0 ||
+         details->action == kCobaltExtensionMediaSessionActionSeekto);
+  DCHECK(!details->fast_seek ||
+         details->action == kCobaltExtensionMediaSessionActionSeekto);
 
   DCHECK(media_session_->task_runner_);
   if (!media_session_->task_runner_->BelongsToCurrentThread()) {
@@ -212,14 +214,16 @@
     return;
   }
 
-  MediaSession::ActionMap::iterator it =
-      media_session_->action_map_.find(details->action());
+  MediaSession::ActionMap::iterator it = media_session_->action_map_.find(
+      ConvertMediaSessionAction(details->action));
 
   if (it == media_session_->action_map_.end()) {
     return;
   }
 
-  it->second->value().Run(*details);
+  std::unique_ptr<MediaSessionActionDetails> script_details =
+      ConvertActionDetails(details);
+  it->second->value().Run(*script_details);
 
   // Queue a session update to reflect the effects of the action.
   if (!media_session_->media_position_state_) {
@@ -270,13 +274,14 @@
     const MediaSessionState& session_state) {
   if (extension_ && extension_->version >= 1) {
     CobaltExtensionMediaSessionState ext_state;
-    CobaltExtensionMediaMetadata ext_metadata = {0};
     size_t artwork_size = 0;
     if (session_state.has_metadata() &&
         session_state.metadata().value().has_artwork()) {
       artwork_size = session_state.metadata().value().artwork().size();
     }
-    ext_metadata.artwork_count = artwork_size;
+    std::unique_ptr<CobaltExtensionMediaImage[]> ext_artwork =
+        std::unique_ptr<CobaltExtensionMediaImage[]>(
+            new CobaltExtensionMediaImage[artwork_size]);
 
     ext_state.duration = session_state.duration();
     ext_state.actual_playback_rate = session_state.actual_playback_rate();
@@ -286,12 +291,15 @@
         ConvertPlaybackState(session_state.actual_playback_state());
     ConvertMediaSessionActions(session_state.available_actions(),
                                ext_state.available_actions);
+    std::string album = "";
+    std::string artist = "";
+    std::string title = "";
 
     if (session_state.has_metadata()) {
       const MediaMetadataInit& metadata = session_state.metadata().value();
-      ext_metadata.album = metadata.album().c_str();
-      ext_metadata.artist = metadata.artist().c_str();
-      ext_metadata.title = metadata.title().c_str();
+      album = metadata.album();
+      artist = metadata.artist();
+      title = metadata.title();
       if (artwork_size > 0) {
         const MediaImageSequence& artwork(metadata.artwork());
         for (MediaImageSequence::size_type i = 0; i < artwork_size; i++) {
@@ -300,26 +308,63 @@
           ext_image.src = media_image.src().c_str();
           ext_image.size = media_image.sizes().c_str();
           ext_image.type = media_image.type().c_str();
-          ext_metadata.artwork[i] = ext_image;
+          ext_artwork[i] = ext_image;
         }
       }
-      ext_state.metadata = ext_metadata;
     }
+    CobaltExtensionMediaMetadata ext_metadata = {
+        album.c_str(), artist.c_str(), title.c_str(), ext_artwork.get(),
+        artwork_size};
+    ext_state.metadata = &ext_metadata;
+
+    ext_state.update_platform_playback_state_callback =
+        &UpdatePlatformPlaybackStateCallback;
+    ext_state.invoke_action_callback = &InvokeActionCallback;
+    ext_state.callback_context = this;
 
     extension_->OnMediaSessionStateChanged(ext_state);
   }
 }
 
-CobaltExtensionPlaybackState MediaSessionClient::ConvertPlaybackState(
-    MediaSessionPlaybackState in_state) {
-  switch (in_state) {
+// static
+void MediaSessionClient::UpdatePlatformPlaybackStateCallback(
+    CobaltExtensionMediaSessionPlaybackState state, void* callback_context) {
+  MediaSessionClient* client =
+      static_cast<MediaSessionClient*>(callback_context);
+  client->UpdatePlatformCobaltExtensionPlaybackState(state);
+}
+
+// static
+void MediaSessionClient::InvokeActionCallback(
+    CobaltExtensionMediaSessionActionDetails details, void* callback_context) {
+  MediaSessionClient* client =
+      static_cast<MediaSessionClient*>(callback_context);
+  client->InvokeCobaltExtensionAction(details);
+}
+
+CobaltExtensionMediaSessionPlaybackState
+MediaSessionClient::ConvertPlaybackState(MediaSessionPlaybackState state) {
+  switch (state) {
     case kMediaSessionPlaybackStatePlaying:
-      return CobaltExtensionPlaybackState::kCobaltExtensionPlaying;
+      return kCobaltExtensionMediaSessionPlaying;
     case kMediaSessionPlaybackStatePaused:
-      return CobaltExtensionPlaybackState::kCobaltExtensionPaused;
+      return kCobaltExtensionMediaSessionPaused;
     case kMediaSessionPlaybackStateNone:
     default:
-      return CobaltExtensionPlaybackState::kCobaltExtensionNone;
+      return kCobaltExtensionMediaSessionNone;
+  }
+}
+
+MediaSessionPlaybackState MediaSessionClient::ConvertPlaybackState(
+    CobaltExtensionMediaSessionPlaybackState state) {
+  switch (state) {
+    case kCobaltExtensionMediaSessionPlaying:
+      return kMediaSessionPlaybackStatePlaying;
+    case kCobaltExtensionMediaSessionPaused:
+      return kMediaSessionPlaybackStatePaused;
+    case kCobaltExtensionMediaSessionNone:
+    default:
+      return kMediaSessionPlaybackStateNone;
   }
 }
 
@@ -328,32 +373,73 @@
     bool result[kCobaltExtensionMediaSessionActionNumActions]) {
   for (int i = 0; i < kCobaltExtensionMediaSessionActionNumActions; i++) {
     result[i] = false;
-  }
-  if (actions[kMediaSessionActionPause]) {
-    result[kCobaltExtensionMediaSessionActionPause] = true;
-  }
-  if (actions[kMediaSessionActionPlay]) {
-    result[kCobaltExtensionMediaSessionActionPlay] = true;
-  }
-  if (actions[kMediaSessionActionSeekbackward]) {
-    result[kCobaltExtensionMediaSessionActionSeekbackward] = true;
-  }
-  if (actions[kMediaSessionActionPrevioustrack]) {
-    result[kCobaltExtensionMediaSessionActionPrevioustrack] = true;
-  }
-  if (actions[kMediaSessionActionNexttrack]) {
-    result[kCobaltExtensionMediaSessionActionNexttrack] = true;
-  }
-  if (actions[kMediaSessionActionSeekforward]) {
-    result[kCobaltExtensionMediaSessionActionSeekforward] = true;
-  }
-  if (actions[kMediaSessionActionSeekto]) {
-    result[kCobaltExtensionMediaSessionActionSeekto] = true;
-  }
-  if (actions[kMediaSessionActionStop]) {
-    result[kCobaltExtensionMediaSessionActionStop] = true;
+    MediaSessionAction action = static_cast<MediaSessionAction>(i);
+    if (actions[action]) {
+      result[ConvertMediaSessionAction(action)] = true;
+    }
   }
 }
 
+std::unique_ptr<MediaSessionActionDetails>
+MediaSessionClient::ConvertActionDetails(
+    CobaltExtensionMediaSessionActionDetails* ext_details) {
+  std::unique_ptr<MediaSessionActionDetails> details(
+      new MediaSessionActionDetails());
+  details->set_action(ConvertMediaSessionAction(ext_details->action));
+  if (ext_details->seek_offset >= 0.0) {
+    details->set_seek_offset(ext_details->seek_offset);
+  }
+  if (ext_details->seek_time >= 0.0) {
+    details->set_seek_time(ext_details->seek_time);
+  }
+  details->set_fast_seek(ext_details->fast_seek);
+  return details;
+}
+
+CobaltExtensionMediaSessionAction MediaSessionClient::ConvertMediaSessionAction(
+    MediaSessionAction action) {
+  switch (action) {
+    case kMediaSessionActionPause:
+      return kCobaltExtensionMediaSessionActionPause;
+    case kMediaSessionActionSeekbackward:
+      return kCobaltExtensionMediaSessionActionSeekbackward;
+    case kMediaSessionActionPrevioustrack:
+      return kCobaltExtensionMediaSessionActionPrevioustrack;
+    case kMediaSessionActionNexttrack:
+      return kCobaltExtensionMediaSessionActionNexttrack;
+    case kMediaSessionActionSeekforward:
+      return kCobaltExtensionMediaSessionActionSeekforward;
+    case kMediaSessionActionSeekto:
+      return kCobaltExtensionMediaSessionActionSeekto;
+    case kMediaSessionActionStop:
+      return kCobaltExtensionMediaSessionActionStop;
+    case kMediaSessionActionPlay:
+    default:
+      return kCobaltExtensionMediaSessionActionPlay;
+  }
+}
+
+MediaSessionAction MediaSessionClient::ConvertMediaSessionAction(
+    CobaltExtensionMediaSessionAction action) {
+  switch (action) {
+    case kCobaltExtensionMediaSessionActionPause:
+      return kMediaSessionActionPause;
+    case kCobaltExtensionMediaSessionActionSeekbackward:
+      return kMediaSessionActionSeekbackward;
+    case kCobaltExtensionMediaSessionActionPrevioustrack:
+      return kMediaSessionActionPrevioustrack;
+    case kCobaltExtensionMediaSessionActionNexttrack:
+      return kMediaSessionActionNexttrack;
+    case kCobaltExtensionMediaSessionActionSeekforward:
+      return kMediaSessionActionSeekforward;
+    case kCobaltExtensionMediaSessionActionSeekto:
+      return kMediaSessionActionSeekto;
+    case kCobaltExtensionMediaSessionActionStop:
+      return kMediaSessionActionStop;
+    case kCobaltExtensionMediaSessionActionPlay:
+    default:
+      return kMediaSessionActionPlay;
+  }
+}
 }  // namespace media_session
 }  // namespace cobalt
diff --git a/src/cobalt/media_session/media_session_client.h b/src/cobalt/media_session/media_session_client.h
index 9b5d6fa..d2d5221 100644
--- a/src/cobalt/media_session/media_session_client.h
+++ b/src/cobalt/media_session/media_session_client.h
@@ -58,28 +58,66 @@
   // the "guessed playback state"
   // https://wicg.github.io/mediasession/#guessed-playback-state
   // Can be invoked from any thread.
-  void UpdatePlatformPlaybackState(MediaSessionPlaybackState state);
+  void UpdatePlatformCobaltExtensionPlaybackState(
+      CobaltExtensionMediaSessionPlaybackState state);
+
+  // Deprecated - use the alternative
+  // UpdatePlatformCobaltExtensionPlaybackState.
+  // TODO: Delete once platform migrations to CobaltExtensionMediaSessionApi are
+  // complete.
+  void UpdatePlatformPlaybackState(MediaSessionPlaybackState state) {
+    UpdatePlatformCobaltExtensionPlaybackState(ConvertPlaybackState(state));
+  }
 
   // Invokes a given media session action
   // https://wicg.github.io/mediasession/#actions-model
   // Can be invoked from any thread.
-  void InvokeAction(MediaSessionAction action) {
-    std::unique_ptr<MediaSessionActionDetails> details(
-        new MediaSessionActionDetails());
-    details->set_action(action);
-    InvokeActionInternal(std::move(details));
+  void InvokeAction(CobaltExtensionMediaSessionAction action) {
+    CobaltExtensionMediaSessionActionDetails details = {};
+    CobaltExtensionMediaSessionActionDetailsInit(&details, action);
+    InvokeActionInternal(std::move(&details));
   }
 
   // Invokes a given media session action that takes additional details.
-  void InvokeAction(std::unique_ptr<MediaSessionActionDetails> details) {
-    InvokeActionInternal(std::move(details));
+  void InvokeCobaltExtensionAction(
+      CobaltExtensionMediaSessionActionDetails details) {
+    InvokeActionInternal(&details);
   }
 
+  // Deprecated - use the alternative InvokeCobaltExtensionAction.
+  // TODO: Delete once platform migrations to CobaltExtensionMediaSessionApi are
+  // complete.
+  void InvokeAction(std::unique_ptr<MediaSessionActionDetails> details) {
+    CobaltExtensionMediaSessionActionDetails* ext_details = {};
+    CobaltExtensionMediaSessionActionDetailsInit(
+        ext_details, ConvertMediaSessionAction(details->action()));
+    if (details->has_seek_offset()) {
+      ext_details->seek_offset = details->seek_offset().value();
+    }
+    if (details->has_seek_time()) {
+      ext_details->seek_time = details->seek_time().value();
+    }
+    if (details->has_fast_seek()) {
+      ext_details->fast_seek = details->fast_seek().value();
+    }
+    InvokeActionInternal(std::move(ext_details));
+  }
   // Invoked on the browser thread when any metadata, position state, playback
   // state, or supported session actions change.
   virtual void OnMediaSessionStateChanged(
       const MediaSessionState& session_state);
 
+  // Indicate the media session client is active or not depending on the
+  // media session playback state.
+  bool is_active() {
+    return platform_playback_state_ != kMediaSessionPlaybackStateNone;
+  }
+
+  // Set maybe freeze callback.
+  void SetMaybeFreezeCallback(const base::Closure& maybe_freeze_callback) {
+    maybe_freeze_callback_ = maybe_freeze_callback;
+  }
+
  private:
   THREAD_CHECKER(thread_checker_);
   scoped_refptr<MediaSession> media_session_;
@@ -91,13 +129,31 @@
   void UpdateMediaSessionState();
   MediaSessionPlaybackState ComputeActualPlaybackState() const;
   MediaSessionState::AvailableActionsSet ComputeAvailableActions() const;
-  CobaltExtensionPlaybackState ConvertPlaybackState(
-      MediaSessionPlaybackState in_state);
+  void InvokeActionInternal(CobaltExtensionMediaSessionActionDetails* details);
+
   void ConvertMediaSessionActions(
       const MediaSessionState::AvailableActionsSet& actions,
       bool result[kCobaltExtensionMediaSessionActionNumActions]);
+  std::unique_ptr<MediaSessionActionDetails> ConvertActionDetails(
+      CobaltExtensionMediaSessionActionDetails* ext_details);
 
-  void InvokeActionInternal(std::unique_ptr<MediaSessionActionDetails> details);
+  // Static callback wrappers for MediaSessionAPI extension.
+  static void UpdatePlatformPlaybackStateCallback(
+      CobaltExtensionMediaSessionPlaybackState state, void* callback_context);
+  static void InvokeActionCallback(
+      CobaltExtensionMediaSessionActionDetails details, void* callback_context);
+
+  // MediaSessionAPI extension type conversion helpers.
+  CobaltExtensionMediaSessionPlaybackState ConvertPlaybackState(
+      MediaSessionPlaybackState state);
+  MediaSessionPlaybackState ConvertPlaybackState(
+      CobaltExtensionMediaSessionPlaybackState state);
+  CobaltExtensionMediaSessionAction ConvertMediaSessionAction(
+      MediaSessionAction action);
+  MediaSessionAction ConvertMediaSessionAction(
+      CobaltExtensionMediaSessionAction action);
+
+  base::Closure maybe_freeze_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(MediaSessionClient);
 };
diff --git a/src/cobalt/media_session/media_session_test.cc b/src/cobalt/media_session/media_session_test.cc
index 687167a..8b2f22e 100644
--- a/src/cobalt/media_session/media_session_test.cc
+++ b/src/cobalt/media_session/media_session_test.cc
@@ -20,6 +20,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "cobalt/bindings/testing/script_object_owner.h"
+#include "cobalt/extension/media_session.h"
 #include "cobalt/media_session/media_session_client.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/script_value.h"
@@ -68,6 +69,7 @@
       override {
     session_state_ = session_state;
     ++session_change_count_;
+    MediaSessionClient::OnMediaSessionStateChanged(session_state);
   }
   void WaitForSessionStateChange() {
     size_t current_change_count = session_change_count_;
@@ -193,12 +195,12 @@
   session->SetActionHandler(kMediaSessionActionPlay, holder);
   client.WaitForSessionStateChange();
   EXPECT_EQ(1, client.GetMediaSessionState().available_actions().to_ulong());
-  client.InvokeAction(kMediaSessionActionPlay);
+  client.InvokeAction(kCobaltExtensionMediaSessionActionPlay);
 
   session->SetActionHandler(kMediaSessionActionPlay, null_holder);
   client.WaitForSessionStateChange();
   EXPECT_EQ(0, client.GetMediaSessionState().available_actions().to_ulong());
-  client.InvokeAction(kMediaSessionActionPlay);
+  client.InvokeAction(kCobaltExtensionMediaSessionActionPlay);
 
   EXPECT_GE(client.GetMediaSessionChangeCount(), 3);
 }
@@ -315,7 +317,7 @@
 
   MockCallbackFunction cf;
   FakeScriptValue<MediaSession::MediaSessionActionHandler> holder(&cf);
-  std::unique_ptr<MediaSessionActionDetails> details;
+  CobaltExtensionMediaSessionActionDetails details;
 
   session->SetActionHandler(kMediaSessionActionSeekto, holder);
   session->SetActionHandler(kMediaSessionActionSeekforward, holder);
@@ -323,31 +325,31 @@
 
   EXPECT_CALL(cf, Run(SeekNoOffset(kMediaSessionActionSeekforward)))
       .WillOnce(Return(CallbackResult<void>()));
-  client.InvokeAction(kMediaSessionActionSeekforward);
+  client.InvokeAction(kCobaltExtensionMediaSessionActionSeekforward);
 
   EXPECT_CALL(cf, Run(SeekNoOffset(kMediaSessionActionSeekbackward)))
       .WillOnce(Return(CallbackResult<void>()));
-  client.InvokeAction(kMediaSessionActionSeekbackward);
+  client.InvokeAction(kCobaltExtensionMediaSessionActionSeekbackward);
 
   EXPECT_CALL(cf, Run(SeekTime(1.2))).WillOnce(Return(CallbackResult<void>()));
-  details.reset(new MediaSessionActionDetails());
-  details->set_action(kMediaSessionActionSeekto);
-  details->set_seek_time(1.2);
-  client.InvokeAction(std::move(details));
+  CobaltExtensionMediaSessionActionDetailsInit(
+      &details, kCobaltExtensionMediaSessionActionSeekto);
+  details.seek_time = 1.2;
+  client.InvokeCobaltExtensionAction(details);
 
   EXPECT_CALL(cf, Run(SeekOffset(kMediaSessionActionSeekforward, 3.4)))
       .WillOnce(Return(CallbackResult<void>()));
-  details.reset(new MediaSessionActionDetails());
-  details->set_action(kMediaSessionActionSeekforward);
-  details->set_seek_offset(3.4);
-  client.InvokeAction(std::move(details));
+  CobaltExtensionMediaSessionActionDetailsInit(
+      &details, kCobaltExtensionMediaSessionActionSeekforward);
+  details.seek_offset = 3.4;
+  client.InvokeCobaltExtensionAction(details);
 
   EXPECT_CALL(cf, Run(SeekOffset(kMediaSessionActionSeekbackward, 5.6)))
       .WillOnce(Return(CallbackResult<void>()));
-  details.reset(new MediaSessionActionDetails());
-  details->set_action(kMediaSessionActionSeekbackward);
-  details->set_seek_offset(5.6);
-  client.InvokeAction(std::move(details));
+  CobaltExtensionMediaSessionActionDetailsInit(
+      &details, kCobaltExtensionMediaSessionActionSeekbackward);
+  details.seek_offset = 5.6;
+  client.InvokeCobaltExtensionAction(details);
 
   client.WaitForSessionStateChange();
   EXPECT_GE(client.GetMediaSessionChangeCount(), 0);
diff --git a/src/cobalt/media_stream/media_stream_audio_deliverer.h b/src/cobalt/media_stream/media_stream_audio_deliverer.h
index 40628d4..b942bdd 100644
--- a/src/cobalt/media_stream/media_stream_audio_deliverer.h
+++ b/src/cobalt/media_stream/media_stream_audio_deliverer.h
@@ -26,9 +26,10 @@
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
+
+#include "cobalt/media/base/audio_bus.h"
 #include "cobalt/media_stream/audio_parameters.h"
 
-#include "cobalt/media/base/shell_audio_bus.h"
 
 namespace cobalt {
 namespace media_stream {
@@ -119,7 +120,7 @@
 
 // Deliver data to all consumers. This method may be called on any thread.
 
-  void OnData(const media::ShellAudioBus& audio_bus,
+  void OnData(const media::AudioBus& audio_bus,
               base::TimeTicks reference_time) {
     TRACE_EVENT1("media_stream", "MediaStreamAudioDeliverer::OnData",
                  "reference time (ms)",
diff --git a/src/cobalt/media_stream/media_stream_audio_sink.h b/src/cobalt/media_stream/media_stream_audio_sink.h
index 680bbd3..034dbec 100644
--- a/src/cobalt/media_stream/media_stream_audio_sink.h
+++ b/src/cobalt/media_stream/media_stream_audio_sink.h
@@ -15,12 +15,13 @@
 #ifndef COBALT_MEDIA_STREAM_MEDIA_STREAM_AUDIO_SINK_H_
 #define COBALT_MEDIA_STREAM_MEDIA_STREAM_AUDIO_SINK_H_
 
+#include "cobalt/media/base/audio_bus.h"
+
 #include "cobalt/media_stream/audio_parameters.h"
 
 #include "cobalt/media_stream/media_stream_source.h"
 #include "cobalt/media_stream/media_stream_track.h"
 
-#include "cobalt/media/base/shell_audio_bus.h"
 
 namespace cobalt {
 namespace media_stream {
@@ -29,11 +30,11 @@
 // Note: users of this class will call OnSetFormat is before OnData.
 class MediaStreamAudioSink {
  public:
-  typedef media::ShellAudioBus ShellAudioBus;
+  typedef media::AudioBus AudioBus;
   MediaStreamAudioSink() = default;
 
   // These are called on the same thread.
-  virtual void OnData(const ShellAudioBus& audio_bus,
+  virtual void OnData(const AudioBus& audio_bus,
                       base::TimeTicks reference_time) = 0;
   virtual void OnSetFormat(const media_stream::AudioParameters& params) = 0;
   virtual void OnReadyStateChanged(
diff --git a/src/cobalt/media_stream/media_stream_audio_source.cc b/src/cobalt/media_stream/media_stream_audio_source.cc
index 6c20d49..2d15393 100644
--- a/src/cobalt/media_stream/media_stream_audio_source.cc
+++ b/src/cobalt/media_stream/media_stream_audio_source.cc
@@ -60,7 +60,7 @@
 }
 
 void MediaStreamAudioSource::DeliverDataToTracks(
-    const MediaStreamAudioTrack::ShellAudioBus& audio_bus,
+    const MediaStreamAudioTrack::AudioBus& audio_bus,
     base::TimeTicks reference_time) {
   deliverer_.OnData(audio_bus, reference_time);
 }
diff --git a/src/cobalt/media_stream/media_stream_audio_source.h b/src/cobalt/media_stream/media_stream_audio_source.h
index 77c00d7..69d789c 100644
--- a/src/cobalt/media_stream/media_stream_audio_source.h
+++ b/src/cobalt/media_stream/media_stream_audio_source.h
@@ -58,9 +58,8 @@
   // Subclasses should call these methods. |DeliverDataToTracks| can be
   // called from a different thread than where MediaStreamAudioSource
   // was created.
-  void DeliverDataToTracks(
-      const MediaStreamAudioTrack::ShellAudioBus& audio_bus,
-      base::TimeTicks reference_time);
+  void DeliverDataToTracks(const MediaStreamAudioTrack::AudioBus& audio_bus,
+                           base::TimeTicks reference_time);
 
   void SetFormat(const media_stream::AudioParameters& params) {
     DLOG(INFO) << "MediaStreamAudioSource@" << this << "::SetFormat("
diff --git a/src/cobalt/media_stream/media_stream_audio_source_test.cc b/src/cobalt/media_stream/media_stream_audio_source_test.cc
index f526bba..4157d3d 100644
--- a/src/cobalt/media_stream/media_stream_audio_source_test.cc
+++ b/src/cobalt/media_stream/media_stream_audio_source_test.cc
@@ -17,7 +17,7 @@
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 #include "cobalt/media_stream/media_stream_audio_track.h"
 #include "cobalt/media_stream/testing/mock_media_stream_audio_source.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -35,7 +35,7 @@
 namespace cobalt {
 namespace media_stream {
 
-typedef media::ShellAudioBus ShellAudioBus;
+typedef media::AudioBus AudioBus;
 
 class MediaStreamAudioSourceTest : public testing::Test {
  public:
diff --git a/src/cobalt/media_stream/media_stream_audio_track.cc b/src/cobalt/media_stream/media_stream_audio_track.cc
index c6deb82..0823274 100644
--- a/src/cobalt/media_stream/media_stream_audio_track.cc
+++ b/src/cobalt/media_stream/media_stream_audio_track.cc
@@ -69,7 +69,7 @@
   stop_callback_ = stop_callback;
 }
 
-void MediaStreamAudioTrack::OnData(const ShellAudioBus& audio_bus,
+void MediaStreamAudioTrack::OnData(const AudioBus& audio_bus,
                                    base::TimeTicks reference_time) {
   deliverer_.OnData(audio_bus, reference_time);
 }
diff --git a/src/cobalt/media_stream/media_stream_audio_track.h b/src/cobalt/media_stream/media_stream_audio_track.h
index b7d7708..f1b9b7d 100644
--- a/src/cobalt/media_stream/media_stream_audio_track.h
+++ b/src/cobalt/media_stream/media_stream_audio_track.h
@@ -19,7 +19,7 @@
 #include "base/strings/string_piece.h"
 #include "base/threading/thread_checker.h"
 #include "cobalt/dom/event_target.h"
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 #include "cobalt/media_stream/audio_parameters.h"
 #include "cobalt/media_stream/media_stream_audio_deliverer.h"
 #include "cobalt/media_stream/media_stream_audio_sink.h"
@@ -50,7 +50,7 @@
     settings.set_sample_size(parameters.bits_per_sample());
     return settings;
   }
-  typedef media::ShellAudioBus ShellAudioBus;
+  typedef media::AudioBus AudioBus;
   explicit MediaStreamAudioTrack(script::EnvironmentSettings* settings)
       : MediaStreamTrack(settings) {}
 
@@ -81,7 +81,7 @@
   void Start(const base::Closure& stop_callback);
 
   // Called by MediaStreamAudioDeliverer.
-  void OnData(const ShellAudioBus& audio_bus, base::TimeTicks reference_time);
+  void OnData(const AudioBus& audio_bus, base::TimeTicks reference_time);
   void OnSetFormat(const media_stream::AudioParameters& params);
 
   THREAD_CHECKER(thread_checker_);
diff --git a/src/cobalt/media_stream/media_stream_audio_track_test.cc b/src/cobalt/media_stream/media_stream_audio_track_test.cc
index 8fc542a..5512ae2 100644
--- a/src/cobalt/media_stream/media_stream_audio_track_test.cc
+++ b/src/cobalt/media_stream/media_stream_audio_track_test.cc
@@ -16,7 +16,7 @@
 
 #include "base/bind_helpers.h"
 #include "cobalt/dom/testing/stub_environment_settings.h"
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 #include "cobalt/media_stream/media_stream_audio_deliverer.h"
 #include "cobalt/media_stream/testing/mock_media_stream_audio_sink.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -36,7 +36,7 @@
 namespace cobalt {
 namespace media_stream {
 
-typedef media::ShellAudioBus ShellAudioBus;
+typedef media::AudioBus AudioBus;
 
 // This fixture is created, so it can be added as a friend to
 // |MediaStreamAudioTrack|.  This enables calling a private method
@@ -71,8 +71,8 @@
 
   deliverer.OnSetFormat(expected_params);
 
-  ShellAudioBus audio_bus(kChannelCount, kFrameCount, ShellAudioBus::kInt16,
-                          ShellAudioBus::kInterleaved);
+  AudioBus audio_bus(kChannelCount, kFrameCount, AudioBus::kInt16,
+                     AudioBus::kInterleaved);
 
   deliverer.OnData(audio_bus, expected_time);
 }
@@ -104,8 +104,8 @@
 
   deliverer.OnSetFormat(expected_params);
 
-  ShellAudioBus audio_bus(kChannelCount, kFrameCount, ShellAudioBus::kInt16,
-                          ShellAudioBus::kInterleaved);
+  AudioBus audio_bus(kChannelCount, kFrameCount, AudioBus::kInt16,
+                     AudioBus::kInterleaved);
 
   deliverer.OnData(audio_bus, expected_time);
 }
@@ -141,8 +141,8 @@
 
   deliverer.OnSetFormat(expected_params);
 
-  ShellAudioBus audio_bus(kChannelCount, kFrameCount, ShellAudioBus::kInt16,
-                          ShellAudioBus::kInterleaved);
+  AudioBus audio_bus(kChannelCount, kFrameCount, AudioBus::kInt16,
+                     AudioBus::kInterleaved);
 
   deliverer.OnData(audio_bus, expected_time);
 }
@@ -166,8 +166,8 @@
 
   deliverer.OnSetFormat(expected_params);
 
-  ShellAudioBus audio_bus(kChannelCount, kFrameCount, ShellAudioBus::kInt16,
-                          ShellAudioBus::kInterleaved);
+  AudioBus audio_bus(kChannelCount, kFrameCount, AudioBus::kInt16,
+                     AudioBus::kInterleaved);
 
   track->RemoveSink(&mock_sink);
   deliverer.OnData(audio_bus, expected_time);
diff --git a/src/cobalt/media_stream/microphone_audio_source.cc b/src/cobalt/media_stream/microphone_audio_source.cc
index 778223e..74eff37 100644
--- a/src/cobalt/media_stream/microphone_audio_source.cc
+++ b/src/cobalt/media_stream/microphone_audio_source.cc
@@ -100,7 +100,7 @@
                      base::Unretained(this), options))) {}
 
 void MicrophoneAudioSource::OnDataReceived(
-    std::unique_ptr<MediaStreamAudioTrack::ShellAudioBus> audio_bus) {
+    std::unique_ptr<MediaStreamAudioTrack::AudioBus> audio_bus) {
   base::TimeTicks now = base::TimeTicks::Now();
   DeliverDataToTracks(*audio_bus, now);
 }
diff --git a/src/cobalt/media_stream/microphone_audio_source.h b/src/cobalt/media_stream/microphone_audio_source.h
index d3049d7..c930298 100644
--- a/src/cobalt/media_stream/microphone_audio_source.h
+++ b/src/cobalt/media_stream/microphone_audio_source.h
@@ -70,7 +70,7 @@
       int buffer_size_bytes);
 
   void OnDataReceived(
-      std::unique_ptr<MediaStreamAudioTrack::ShellAudioBus> audio_bus);
+      std::unique_ptr<MediaStreamAudioTrack::AudioBus> audio_bus);
 
   void OnDataCompletion();
   void OnMicrophoneOpen();
diff --git a/src/cobalt/media_stream/testing/mock_media_stream_audio_sink.h b/src/cobalt/media_stream/testing/mock_media_stream_audio_sink.h
index bd11714..7fa06b6 100644
--- a/src/cobalt/media_stream/testing/mock_media_stream_audio_sink.h
+++ b/src/cobalt/media_stream/testing/mock_media_stream_audio_sink.h
@@ -24,8 +24,8 @@
 
 class MockMediaStreamAudioSink : public MediaStreamAudioSink {
  public:
-  MOCK_METHOD2(OnData, void(const ShellAudioBus& audio_bus,
-                            base::TimeTicks reference_time));
+  MOCK_METHOD2(OnData,
+               void(const AudioBus& audio_bus, base::TimeTicks reference_time));
   MOCK_METHOD1(OnSetFormat, void(const media_stream::AudioParameters& params));
   MOCK_METHOD1(OnReadyStateChanged,
                void(media_stream::MediaStreamTrack::ReadyState new_state));
diff --git a/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.cc b/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.cc
index 09ce038..9ba721f 100644
--- a/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.cc
+++ b/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.cc
@@ -96,6 +96,16 @@
   SelectAtlasCache(&offscreen_atlases_, &offscreen_cache_);
   SelectAtlasCache(&offscreen_atlases_1d_, &offscreen_cache_1d_);
 
+  // Delete skottie targets that were not used in the previous render frame.
+  for (size_t index = 0; index < skottie_targets_.size();) {
+    if (skottie_targets_[index]->allocations_used == 0) {
+      skottie_targets_.erase(skottie_targets_.begin() + index);
+    } else {
+      skottie_targets_[index]->allocations_used = 0;
+      ++index;
+    }
+  }
+
   // Delete uncached targets that were not used in the previous render frame.
   for (size_t index = 0; index < uncached_targets_.size();) {
     if (uncached_targets_[index]->allocations_used == 0) {
@@ -215,6 +225,33 @@
   return false;
 }
 
+bool OffscreenTargetManager::GetCachedTarget(
+    const skia::SkottieAnimation* skottie_animation, const math::SizeF& size,
+    TargetInfo* out_target_info) {
+  // Each SkottieAnimation uses its own render target cache. The cache is keyed
+  // off the SkottieAnimation's size and pointer value. Using the pointer can
+  // be risky as an object may be destroyed and a new one created with the same
+  // pointer value. However, since each SkottieAnimation is created assuming
+  // the render cache is dirty, it is safe to accidentally use the render cache
+  // of an old SkottieAnimation with a new one.
+  int64_t id = reinterpret_cast<int64_t>(skottie_animation);
+  math::RectF target_size(std::ceil(size.width()), std::ceil(size.height()));
+  for (size_t i = 0; i < skottie_targets_.size(); ++i) {
+    OffscreenAtlas* target = skottie_targets_[i].get();
+    auto iter = target->allocation_map.find(id);
+    if (iter != target->allocation_map.end() &&
+        iter->second.error_data == target_size) {
+      target->allocations_used += 1;
+      out_target_info->framebuffer = target->framebuffer.get();
+      out_target_info->skia_canvas = target->skia_surface->getCanvas();
+      out_target_info->region = iter->second.target_region;
+      return true;
+    }
+  }
+
+  return false;
+}
+
 void OffscreenTargetManager::AllocateCachedTarget(const render_tree::Node* node,
                                                   const math::SizeF& size,
                                                   const ErrorData& error_data,
@@ -325,6 +362,34 @@
   }
 }
 
+void OffscreenTargetManager::AllocateCachedTarget(
+    const skia::SkottieAnimation* skottie_animation, const math::SizeF& size,
+    TargetInfo* out_target_info) {
+  // Each SkottieAnimation has its own offscreen atlas.
+  math::Size target_size(static_cast<int>(std::ceil(size.width())),
+                         static_cast<int>(std::ceil(size.height())));
+  OffscreenAtlas* atlas = CreateOffscreenAtlas(target_size, true).release();
+  if (!atlas) {
+    // If there was an error allocating the offscreen atlas, indicate by
+    // marking framebuffer and skia canvas as null and returning early.
+    out_target_info->framebuffer = nullptr;
+    out_target_info->skia_canvas = nullptr;
+    return;
+  }
+
+  int64_t id = reinterpret_cast<int64_t>(skottie_animation);
+  // |target_rect| must be calculated the same way as in GetCachedTarget().
+  math::RectF target_rect(std::ceil(size.width()), std::ceil(size.height()));
+  atlas->allocation_map.insert(AllocationMap::value_type(
+      id, AllocationMapValue(target_rect, target_rect)));
+  skottie_targets_.emplace_back(atlas);
+
+  atlas->allocations_used = 1;
+  out_target_info->framebuffer = atlas->framebuffer.get();
+  out_target_info->skia_canvas = atlas->skia_surface->getCanvas();
+  out_target_info->region = target_rect;
+}
+
 void OffscreenTargetManager::AllocateUncachedTarget(
     const math::SizeF& size, TargetInfo* out_target_info) {
   // Align up the requested target size to increase the chances that it can
diff --git a/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.h b/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.h
index ec64a19..69e4bde 100644
--- a/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.h
+++ b/src/cobalt/renderer/rasterizer/egl/offscreen_target_manager.h
@@ -23,6 +23,7 @@
 #include "cobalt/render_tree/node.h"
 #include "cobalt/renderer/backend/egl/framebuffer_render_target.h"
 #include "cobalt/renderer/backend/egl/graphics_context.h"
+#include "cobalt/renderer/rasterizer/skia/skottie_animation.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkSurface.h"
 
@@ -85,6 +86,9 @@
   bool GetCachedTarget(const render_tree::Node* node,
                        const CacheErrorFunction1D& error_function,
                        TargetInfo* out_target_info);
+  bool GetCachedTarget(const skia::SkottieAnimation* skottie_animation,
+                       const math::SizeF& size,
+                       TargetInfo* out_target_info);
 
   // Allocate a cached offscreen target of the specified size.
   // The returned values are only valid until the next call to Update().
@@ -95,6 +99,9 @@
   void AllocateCachedTarget(const render_tree::Node* node, float size,
                             const ErrorData1D& error_data,
                             TargetInfo* out_target_info);
+  void AllocateCachedTarget(const skia::SkottieAnimation* skottie_animation,
+                            const math::SizeF& size,
+                            TargetInfo* out_target_info);
 
   // Allocate an uncached render target. The contents of the target cannot be
   // reused in subsequent frames.  If there was an error allocating the
@@ -122,6 +129,7 @@
   std::unique_ptr<OffscreenAtlas> offscreen_cache_1d_;
 
   std::vector<std::unique_ptr<OffscreenAtlas>> uncached_targets_;
+  std::vector<std::unique_ptr<OffscreenAtlas>> skottie_targets_;
 
   // Align offscreen targets to a particular size to more efficiently use the
   // offscreen target atlas. Use a power of 2 for the alignment so that a bit
diff --git a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
index 263afa5..ec33f8f 100644
--- a/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
+++ b/src/cobalt/renderer/rasterizer/egl/render_tree_node_visitor.cc
@@ -44,6 +44,7 @@
 #include "cobalt/renderer/rasterizer/egl/draw_rrect_color_texture.h"
 #include "cobalt/renderer/rasterizer/skia/hardware_image.h"
 #include "cobalt/renderer/rasterizer/skia/image.h"
+#include "cobalt/renderer/rasterizer/skia/skottie_animation.h"
 
 namespace cobalt {
 namespace renderer {
@@ -111,6 +112,17 @@
       0, 1);
 }
 
+math::SizeF GetTransformScale(const math::Matrix3F& transform) {
+  math::PointF mapped_origin = transform * math::PointF(0, 0);
+  math::PointF mapped_x = transform * math::PointF(1, 0);
+  math::PointF mapped_y = transform * math::PointF(0, 1);
+  math::Vector2dF mapped_vecx(mapped_x.x() - mapped_origin.x(),
+                              mapped_x.y() - mapped_origin.y());
+  math::Vector2dF mapped_vecy(mapped_y.x() - mapped_origin.x(),
+                              mapped_y.y() - mapped_origin.y());
+  return math::SizeF(mapped_vecx.Length(), mapped_vecy.Length());
+}
+
 bool ImageNodeSupportedNatively(render_tree::ImageNode* image_node) {
   // The image node may contain nothing. For example, when it represents a video
   // element before any frame is decoded.
@@ -407,17 +419,9 @@
         }
         draw_state_.opacity = 1.0f;
 
-        math::PointF mapped_origin = draw_state_.transform * math::PointF(0, 0);
-        math::PointF mapped_x = draw_state_.transform * math::PointF(1, 0);
-        math::PointF mapped_y = draw_state_.transform * math::PointF(0, 1);
-        math::Vector2dF mapped_vecx(mapped_x.x() - mapped_origin.x(),
-                                    mapped_x.y() - mapped_origin.y());
-        math::Vector2dF mapped_vecy(mapped_y.x() - mapped_origin.x(),
-                                    mapped_y.y() - mapped_origin.y());
-        float scale_x = mapped_vecx.Length();
-        float scale_y = mapped_vecy.Length();
-        draw_state_.transform = math::ScaleMatrix(std::max(1.0f, scale_x),
-                                                  std::max(1.0f, scale_y));
+        math::SizeF scale = GetTransformScale(draw_state_.transform);
+        draw_state_.transform = math::ScaleMatrix(
+            std::max(1.0f, scale.width()), std::max(1.0f, scale.height()));
 
         // Don't clip the source since it is in its own local space.
         bool limit_to_screen_size = false;
@@ -672,12 +676,63 @@
 }
 
 void RenderTreeNodeVisitor::Visit(render_tree::LottieNode* lottie_node) {
-  if (!IsVisible(lottie_node->GetBounds())) {
+  const render_tree::LottieNode::Builder& data = lottie_node->data();
+  math::RectF content_rect = data.destination_rect;
+  if (!IsVisible(content_rect)) {
     return;
   }
 
-  // Use Skottie to render Lottie animations.
-  FallbackRasterize(lottie_node);
+  skia::SkottieAnimation* animation =
+      base::polymorphic_downcast<skia::SkottieAnimation*>(data.animation.get());
+  if (animation->GetSize().GetArea() == 0) {
+    return;
+  }
+  animation->SetAnimationTime(data.animation_time);
+
+  // Get an offscreen target to cache the animation. Make the target big enough
+  // to avoid scaling artifacts.
+  math::SizeF scale = GetTransformScale(draw_state_.transform);
+  math::SizeF mapped_size = content_rect.size();
+  // Use a uniform scale to avoid impacting aspect ratio calculations.
+  mapped_size.Scale(std::max(scale.width(), scale.height()));
+
+  OffscreenTargetManager::TargetInfo target_info;
+  if (!offscreen_target_manager_->GetCachedTarget(
+      animation, mapped_size, &target_info)) {
+    // No pre-existing target was found. Allocate a new target.
+    animation->ResetRenderCache();
+    offscreen_target_manager_->AllocateCachedTarget(
+        animation, mapped_size, &target_info);
+  }
+  if (target_info.framebuffer == nullptr) {
+    // Unable to allocate the render target for the animation cache.
+    return;
+  }
+
+  // Add a draw call to update the cache.
+  std::unique_ptr<DrawObject> update_cache(new DrawCallback(
+      base::Bind(&skia::SkottieAnimation::UpdateRenderCache,
+                 base::Unretained(animation),
+                 base::Unretained(target_info.skia_canvas),
+                 target_info.region.size())));
+  draw_object_manager_->AddBatchedExternalDraw(
+      std::move(update_cache), lottie_node->GetTypeId(),
+      target_info.framebuffer, target_info.region);
+
+  // Add a draw call to render the cached animation to the current target.
+  backend::TextureEGL* texture = target_info.framebuffer->GetColorTexture();
+  math::Matrix3F texcoord_transform = GetTexcoordTransform(target_info);
+  if (IsOpaque(draw_state_.opacity)) {
+    std::unique_ptr<DrawObject> draw(
+        new DrawRectTexture(graphics_state_, draw_state_, content_rect, texture,
+                            texcoord_transform));
+    AddDraw(std::move(draw), content_rect, DrawObjectManager::kBlendSrcAlpha);
+  } else {
+    std::unique_ptr<DrawObject> draw(new DrawRectColorTexture(
+        graphics_state_, draw_state_, content_rect, kOpaqueWhite, texture,
+        texcoord_transform, false /* clamp_texcoords */));
+    AddDraw(std::move(draw), content_rect, DrawObjectManager::kBlendSrcAlpha);
+  }
 }
 
 void RenderTreeNodeVisitor::Visit(
diff --git a/src/cobalt/renderer/rasterizer/pixel_test.cc b/src/cobalt/renderer/rasterizer/pixel_test.cc
index 464ef3d..a751832 100644
--- a/src/cobalt/renderer/rasterizer/pixel_test.cc
+++ b/src/cobalt/renderer/rasterizer/pixel_test.cc
@@ -4538,6 +4538,61 @@
   TestTree(lottie_node);
 }
 
+TEST_F(PixelTest, LottiePreserveAspectRatioTooShortAnimationTest) {
+  std::vector<uint8> animation_data =
+      GetFileData(GetTestFilePath("white_material_wave_loading.json"));
+  scoped_refptr<LottieAnimation> animation =
+      GetResourceProvider()->CreateLottieAnimation(
+          reinterpret_cast<char*>(&animation_data[0]), animation_data.size());
+  LottieAnimation::LottieProperties lottie_properties;
+  lottie_properties.UpdateState(LottieAnimation::LottieState::kPlaying);
+  animation->BeginRenderFrame(lottie_properties);
+
+  LottieNode::Builder node_builder =
+      LottieNode::Builder(animation, RectF(output_surface_size().width(),
+                                           100.0f));
+  node_builder.animation_time = base::TimeDelta::FromSecondsD(0);
+  scoped_refptr<LottieNode> lottie_node = new LottieNode(node_builder);
+  TestTree(lottie_node);
+}
+
+TEST_F(PixelTest, LottiePreserveAspectRatioTooNarrowAnimationTest) {
+  std::vector<uint8> animation_data =
+      GetFileData(GetTestFilePath("white_material_wave_loading.json"));
+  scoped_refptr<LottieAnimation> animation =
+      GetResourceProvider()->CreateLottieAnimation(
+          reinterpret_cast<char*>(&animation_data[0]), animation_data.size());
+  LottieAnimation::LottieProperties lottie_properties;
+  lottie_properties.UpdateState(LottieAnimation::LottieState::kPlaying);
+  animation->BeginRenderFrame(lottie_properties);
+
+  LottieNode::Builder node_builder =
+      LottieNode::Builder(animation, RectF(100.0f,
+                                           output_surface_size().height()));
+  node_builder.animation_time = base::TimeDelta::FromSecondsD(0);
+  scoped_refptr<LottieNode> lottie_node = new LottieNode(node_builder);
+  TestTree(lottie_node);
+}
+
+TEST_F(PixelTest, LottieScaledWideAnimationTest) {
+  std::vector<uint8> animation_data =
+      GetFileData(GetTestFilePath("white_material_wave_loading.json"));
+  scoped_refptr<LottieAnimation> animation =
+      GetResourceProvider()->CreateLottieAnimation(
+          reinterpret_cast<char*>(&animation_data[0]), animation_data.size());
+  LottieAnimation::LottieProperties lottie_properties;
+  lottie_properties.UpdateState(LottieAnimation::LottieState::kPlaying);
+  animation->BeginRenderFrame(lottie_properties);
+
+  LottieNode::Builder node_builder =
+      LottieNode::Builder(animation, RectF(output_surface_size()));
+  node_builder.animation_time = base::TimeDelta::FromSecondsD(0);
+  scoped_refptr<LottieNode> lottie_node = new LottieNode(node_builder);
+  TestTree(new MatrixTransformNode(lottie_node,
+      ScaleMatrix(4.0f, 1.0f) *
+      TranslateMatrix(output_surface_size().width() * -0.5f, 0.0f)));
+}
+
 #endif  // !SB_HAS(BLITTER)
 
 }  // namespace rasterizer
diff --git a/src/cobalt/renderer/rasterizer/skia/skottie_animation.cc b/src/cobalt/renderer/rasterizer/skia/skottie_animation.cc
index 4fae87f..224b4a9 100644
--- a/src/cobalt/renderer/rasterizer/skia/skottie_animation.cc
+++ b/src/cobalt/renderer/rasterizer/skia/skottie_animation.cc
@@ -15,6 +15,7 @@
 #include "cobalt/renderer/rasterizer/skia/skottie_animation.h"
 
 #include "base/bind.h"
+#include "third_party/skia/include/core/SkRect.h"
 
 namespace cobalt {
 namespace renderer {
@@ -23,6 +24,7 @@
 
 SkottieAnimation::SkottieAnimation(const char* data, size_t length)
     : last_updated_animation_time_(base::TimeDelta()) {
+  ResetRenderCache();
   skottie::Animation::Builder builder;
   skottie_animation_ = builder.make(data, length);
   animation_size_ = math::Size(skottie_animation_->size().width(),
@@ -72,6 +74,11 @@
     return;
   }
 
+  // Do not update the animation time if it has already reached the last frame.
+  if (is_complete_) {
+    return;
+  }
+
   base::TimeDelta current_animation_time = last_updated_animation_time_;
   base::TimeDelta time_elapsed =
       animate_function_time - last_updated_animate_function_time_;
@@ -124,6 +131,21 @@
                                               animate_function_time);
 }
 
+void SkottieAnimation::UpdateRenderCache(SkCanvas* render_target,
+    const math::SizeF& size) {
+  DCHECK(render_target);
+  if (cached_animation_time_ == last_updated_animation_time_) {
+    // The render cache is already up-to-date.
+    return;
+  }
+
+  cached_animation_time_ = last_updated_animation_time_;
+  SkRect bounding_rect = SkRect::MakeWH(size.width(), size.height());
+  render_target->clear(SK_ColorTRANSPARENT);
+  skottie_animation_->render(render_target, &bounding_rect);
+  render_target->flush();
+}
+
 void SkottieAnimation::UpdateAnimationFrameAndAnimateFunctionTimes(
     base::TimeDelta current_animation_time,
     base::TimeDelta current_animate_function_time) {
diff --git a/src/cobalt/renderer/rasterizer/skia/skottie_animation.h b/src/cobalt/renderer/rasterizer/skia/skottie_animation.h
index 3d5531c..0acb854 100644
--- a/src/cobalt/renderer/rasterizer/skia/skottie_animation.h
+++ b/src/cobalt/renderer/rasterizer/skia/skottie_animation.h
@@ -15,7 +15,9 @@
 #ifndef COBALT_RENDERER_RASTERIZER_SKIA_SKOTTIE_ANIMATION_H_
 #define COBALT_RENDERER_RASTERIZER_SKIA_SKOTTIE_ANIMATION_H_
 
+#include "cobalt/math/size_f.h"
 #include "cobalt/render_tree/lottie_animation.h"
+#include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/modules/skottie/include/Skottie.h"
 
 namespace cobalt {
@@ -41,6 +43,12 @@
 
   sk_sp<skottie::Animation> GetSkottieAnimation() { return skottie_animation_; }
 
+  // Rendering the lottie animation can be CPU-intensive. To minimize the cost,
+  // the animation can be updated in an offscreen render target only as needed,
+  // then the offscreen target rendered to the screen.
+  void ResetRenderCache() { cached_animation_time_ = base::TimeDelta::Min(); }
+  void UpdateRenderCache(SkCanvas* render_target, const math::SizeF& size);
+
  private:
   void UpdateAnimationFrameAndAnimateFunctionTimes(
       base::TimeDelta current_animation_time,
@@ -79,6 +87,9 @@
 
   // The most recently updated frame time for |skottie_animation_|.
   base::TimeDelta last_updated_animation_time_;
+
+  // This is the animation time used for the last cache update.
+  base::TimeDelta cached_animation_time_;
 };
 
 }  // namespace skia
diff --git a/src/cobalt/renderer/rasterizer/testdata/LottiePreserveAspectRatioTooNarrowAnimationTest-expected.png b/src/cobalt/renderer/rasterizer/testdata/LottiePreserveAspectRatioTooNarrowAnimationTest-expected.png
new file mode 100644
index 0000000..803f356
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/LottiePreserveAspectRatioTooNarrowAnimationTest-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LottiePreserveAspectRatioTooShortAnimationTest-expected.png b/src/cobalt/renderer/rasterizer/testdata/LottiePreserveAspectRatioTooShortAnimationTest-expected.png
new file mode 100644
index 0000000..804797d
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/LottiePreserveAspectRatioTooShortAnimationTest-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/LottieScaledWideAnimationTest-expected.png b/src/cobalt/renderer/rasterizer/testdata/LottieScaledWideAnimationTest-expected.png
new file mode 100644
index 0000000..86784fd
--- /dev/null
+++ b/src/cobalt/renderer/rasterizer/testdata/LottieScaledWideAnimationTest-expected.png
Binary files differ
diff --git a/src/cobalt/renderer/render_tree_pixel_tester.cc b/src/cobalt/renderer/render_tree_pixel_tester.cc
index f617aab..d6e8be3 100644
--- a/src/cobalt/renderer/render_tree_pixel_tester.cc
+++ b/src/cobalt/renderer/render_tree_pixel_tester.cc
@@ -77,6 +77,12 @@
 
 // static
 bool RenderTreePixelTester::IsReferencePlatform() {
+#if SB_API_VERSION < 12 && SB_HAS(BLITTER)
+  // The blitter rasterizer often relies on software rendering which may not
+  // produce the same results as GPU-based rendering.
+  return false;
+#endif
+
   const char* rasterizer_type =
       configuration::Configuration::GetInstance()->CobaltRasterizerType();
   const bool is_opengles_rasterizer =
diff --git a/src/cobalt/script/v8c/v8c_global_environment.cc b/src/cobalt/script/v8c/v8c_global_environment.cc
index 010b61d..4d43ade 100644
--- a/src/cobalt/script/v8c/v8c_global_environment.cc
+++ b/src/cobalt/script/v8c/v8c_global_environment.cc
@@ -105,8 +105,6 @@
                "V8cGlobalEnvironment::~V8cGlobalEnvironment()");
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   InvalidateWeakPtrs();
-  destruction_helper_.global_wrappable_ = global_wrappable_.get();
-  destruction_helper_.wrapper_factory_ = wrapper_factory_.get();
 }
 
 void V8cGlobalEnvironment::CreateGlobalObject() {
@@ -322,13 +320,6 @@
   // Another GC to make sure global object is collected.
   isolate_->LowMemoryNotification();
   isolate_->SetEmbedderHeapTracer(nullptr);
-  if (global_wrappable_ && global_wrappable_->RefCounts() != 1) {
-    // At this point only environment should hold the last reference.
-    DLOG(INFO) << "[Temporary debugging] more than one ref alive, ref_count: "
-               << global_wrappable_->RefCounts();
-    DLOG(INFO) << "[Temporary debugging] window's v8 wrapper alive?"
-               << wrapper_factory_->HasWrapper(global_wrappable_);
-  }
 }
 
 // static
diff --git a/src/cobalt/script/v8c/v8c_global_environment.h b/src/cobalt/script/v8c/v8c_global_environment.h
index be1f6aa..c131a8d 100644
--- a/src/cobalt/script/v8c/v8c_global_environment.h
+++ b/src/cobalt/script/v8c/v8c_global_environment.h
@@ -132,9 +132,6 @@
     explicit DestructionHelper(v8::Isolate* isolate) : isolate_(isolate) {}
     ~DestructionHelper();
 
-    script::Wrappable* global_wrappable_;
-    WrapperFactory* wrapper_factory_;
-
    private:
     v8::Isolate* isolate_;
   };
@@ -165,10 +162,10 @@
   // destruct.  Were we to not do this, finalizers can run in the order (e.g.)
   // document window, which the Cobalt DOM does not support.
   scoped_refptr<Wrappable> global_wrappable_;
-  std::unique_ptr<WrapperFactory> wrapper_factory_;
   DestructionHelper destruction_helper_;
   v8::Global<v8::Context> context_;
 
+  std::unique_ptr<WrapperFactory> wrapper_factory_;
   std::unique_ptr<V8cScriptValueFactory> script_value_factory_;
 
   // Data that is cached on a per-interface basis. Note that we can get to
diff --git a/src/cobalt/script/v8c/wrapper_factory.cc b/src/cobalt/script/v8c/wrapper_factory.cc
index 27c3463..e123d1d 100644
--- a/src/cobalt/script/v8c/wrapper_factory.cc
+++ b/src/cobalt/script/v8c/wrapper_factory.cc
@@ -38,17 +38,6 @@
       << "RegisterWrappableType registered for type more than once.";
 }
 
-bool WrapperFactory::HasWrapper(Wrappable* wrappable) {
-  v8::Local<v8::Object> wrapper;
-  v8::MaybeLocal<v8::Object> maybe_wrapper =
-      V8cWrapperHandle::MaybeGetObject(isolate_, GetCachedWrapper(wrappable));
-  if (!maybe_wrapper.ToLocal(&wrapper)) {
-    return false;
-  } else {
-    return true;
-  }
-}
-
 v8::Local<v8::Object> WrapperFactory::GetWrapper(
     const scoped_refptr<Wrappable>& wrappable) {
   v8::Local<v8::Object> wrapper;
diff --git a/src/cobalt/script/v8c/wrapper_factory.h b/src/cobalt/script/v8c/wrapper_factory.h
index 986e740..a78a9de 100644
--- a/src/cobalt/script/v8c/wrapper_factory.h
+++ b/src/cobalt/script/v8c/wrapper_factory.h
@@ -50,8 +50,6 @@
 
   v8::Local<v8::Object> GetWrapper(const scoped_refptr<Wrappable>& wrappable);
 
-  // Added temporarily for debugging purpose, will be removed soon.
-  bool HasWrapper(Wrappable* wrappable);
   // Attempt to get the |WrapperPrivate| associated with |wrappable|.  Returns
   // |nullptr| if no |WrapperPrivate| was found.
   WrapperPrivate* MaybeGetWrapperPrivate(Wrappable* wrappable);
diff --git a/src/cobalt/site/docs/development/setup-android.md b/src/cobalt/site/docs/development/setup-android.md
index 676c2e1..4a6f2b0 100644
--- a/src/cobalt/site/docs/development/setup-android.md
+++ b/src/cobalt/site/docs/development/setup-android.md
@@ -37,6 +37,19 @@
     python -m pip install requests
     ```
 
+1.  Install ccache to support build acceleration. ccache is automatically used
+    when available, otherwise defaults to unaccelerated building:
+
+    ```
+    $ sudo apt-get install ccache
+    ```
+
+    We recommend adjusting the cache size as needed to increase cache hits:
+
+    ```
+    $ ccache --max-size=20G
+    ```
+
 1.  Download and install [Android Studio](https://developer.android.com/studio/).
 1.  Run `cobalt/build/gyp_cobalt android-x86` to configure the Cobalt build,
     which also installs the SDK and NDK. (This step will have to be repeated
diff --git a/src/cobalt/site/docs/development/setup-linux.md b/src/cobalt/site/docs/development/setup-linux.md
index c0ac49e..3c4d49f 100644
--- a/src/cobalt/site/docs/development/setup-linux.md
+++ b/src/cobalt/site/docs/development/setup-linux.md
@@ -9,6 +9,8 @@
 on the machine that you are using to view the client. For example, you cannot
 SSH into another machine and run the binary on that machine.
 
+## Set up your workstation
+
 1.  Choose where you want to put the `depot_tools` directory, which is used
     by the Cobalt code. An easy option is to put them in `~/depot_tools`.
     Clone the tools by running the following command:
@@ -48,6 +50,19 @@
         && nvm use default
     ```
 
+1.  Install ccache to support build acceleration. ccache is automatically used
+    when available, otherwise defaults to unaccelerated building:
+
+    ```
+    $ sudo apt install -qqy --no-install-recommends ccache
+    ```
+
+    We recommend adjusting the cache size as needed to increase cache hits:
+
+    ```
+    $ ccache --max-size=20G
+    ```
+
 1.  Clone the Cobalt code repository. The following `git` command creates a
     `cobalt` directory that contains the repository:
 
@@ -55,6 +70,8 @@
     $ git clone https://cobalt.googlesource.com/cobalt
     ```
 
+## Build and Run Cobalt
+
 1.  Build the code by navigating to the `src` directory in your new
     `cobalt` directory and running the following command. You must
     specify a platform when running this command. On Ubuntu Linux, the
diff --git a/src/cobalt/site/docs/development/setup-raspi.md b/src/cobalt/site/docs/development/setup-raspi.md
index 13936bb..3e88d8c 100644
--- a/src/cobalt/site/docs/development/setup-raspi.md
+++ b/src/cobalt/site/docs/development/setup-raspi.md
@@ -48,15 +48,27 @@
 $ apt-get remove -y --purge --auto-remove libgl1-mesa-dev \
           libegl1-mesa-dev libgles2-mesa libgles2-mesa-dev
 $ apt-get install -y libpulse-dev libasound2-dev libavformat-dev \
-          libavresample-dev
+          libavresample-dev rsync
 ```
 
 ## Set up your workstation
 
+<aside class="note">
+<b>Note:</b> Before proceeding further, refer to the documentation for ["Set up your environment - Linux"](setup-linux.md). Complete the section **Set up your workstation**, then return and complete the following steps.
+</aside>
+
 The following steps install the cross-compiling toolchain on your workstation.
 The toolchain runs on an x86 workstation and compiles binaries for your ARM
 Raspberry Pi.
 
+1.  Run the following command to install packages needed to build and run
+    Cobalt for Raspberry Pi:
+
+    ```
+    $ sudo apt install -qqy --no-install-recommends g++-multilib \
+          python-requests wget xz-utils
+    ```
+
 1.  Choose a location for the installed toolchain &ndash; e.g. `raspi-tools`
     &ndash; and set `$RASPI_HOME` to that location.
 
@@ -94,10 +106,16 @@
 
 ## Build, install, and run Cobalt for Raspberry Pi
 
-1.  Run the following commands to build Cobalt:
+1.  Build the code by navigating to the src directory in your cobalt directory and run the
+    following command :
 
     ```
-    $ gyp_cobalt raspi-2
+    $ cobalt/build/gyp_cobalt raspi-2
+    ```
+
+1.  Compile the code from the `src/` directory:
+
+    ```
     $ ninja -C out/raspi-2_debug cobalt
     ```
 
@@ -105,7 +123,7 @@
     on the device:
 
     ```
-    rsync -avzh --exclude="obj*" \
+    rsync -avzLPh --exclude="obj*" --exclude="gen/" \
           $COBALT_SRC/out/raspi-2_debug pi@$RASPI_ADDR:~/
     ```
 
@@ -129,3 +147,21 @@
     Note that you can also exit YouTube on Cobalt by hitting the `[Esc]` key
     enough times to bring up the "Do you want to quit YouTube?" dialog and
     selecting "yes".
+
+### Improving Cobalt performance on Raspberry Pi
+
+1.  You will find that there are some processes installed by default that run on the
+    Raspberry Pi and can take away CPU time from Cobalt. You may wish to consider
+    disabling these processes for maximum (and more consistent) performance, as they
+    have been found to occasionally take >10% of the CPU according to `top`.
+    You can do this by typing:
+
+    ```
+    apt-get remove -y --auto-remove [PACKAGE_NAME, ...]
+    ```
+
+    For example:
+
+    ```
+    apt-get remove -y --auto-remove avahi-daemon
+    ```
diff --git a/src/cobalt/site/docs/reference/starboard/modules/10/file.md b/src/cobalt/site/docs/reference/starboard/modules/10/file.md
index b115501..b9c0e36 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/10/file.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/10/file.md
@@ -169,7 +169,7 @@
 is used primarily to clean up after unit tests. On some platforms, this function
 fails if the file in question is being held open.
 
-`path`: The absolute path fo the file, symlink, or directory to be deleted.
+`path`: The absolute path of the file, symlink, or directory to be deleted.
 
 #### Declaration ####
 
diff --git a/src/cobalt/site/docs/reference/starboard/modules/10/media.md b/src/cobalt/site/docs/reference/starboard/modules/10/media.md
index 9b83d06..7271b29 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/10/media.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/10/media.md
@@ -665,6 +665,8 @@
 outputs. If `true`, then non-protection-capable outputs are expected to be
 blanked.
 
+presubmit: allow sb_export mismatch
+
 #### Declaration ####
 
 ```
@@ -700,6 +702,8 @@
 
 `enabled`: Indicates whether output protection is enabled (`true`) or disabled.
 
+presubmit: allow sb_export mismatch
+
 #### Declaration ####
 
 ```
diff --git a/src/cobalt/site/docs/reference/starboard/modules/11/file.md b/src/cobalt/site/docs/reference/starboard/modules/11/file.md
index ad890dc..f910cb0 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/11/file.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/11/file.md
@@ -168,7 +168,7 @@
 is used primarily to clean up after unit tests. On some platforms, this function
 fails if the file in question is being held open.
 
-`path`: The absolute path fo the file, symlink, or directory to be deleted.
+`path`: The absolute path of the file, symlink, or directory to be deleted.
 
 #### Declaration ####
 
diff --git a/src/cobalt/site/docs/reference/starboard/modules/11/media.md b/src/cobalt/site/docs/reference/starboard/modules/11/media.md
index 8d94612..377cb2b 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/11/media.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/11/media.md
@@ -659,6 +659,8 @@
 outputs. If `true`, then non-protection-capable outputs are expected to be
 blanked.
 
+presubmit: allow sb_export mismatch
+
 #### Declaration ####
 
 ```
@@ -712,6 +714,8 @@
 
 `enabled`: Indicates whether output protection is enabled (`true`) or disabled.
 
+presubmit: allow sb_export mismatch
+
 #### Declaration ####
 
 ```
diff --git a/src/cobalt/site/docs/reference/starboard/modules/12/condition_variable.md b/src/cobalt/site/docs/reference/starboard/modules/12/condition_variable.md
index 836c4b8..22c2b79 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/12/condition_variable.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/12/condition_variable.md
@@ -40,7 +40,7 @@
 #### Definition ####
 
 ```
-typedef union SbConditionVariable  SbConditionVariable
+typedef union SbConditionVariable SbConditionVariable
 ```
 
 ## Functions ##
diff --git a/src/cobalt/site/docs/reference/starboard/modules/12/file.md b/src/cobalt/site/docs/reference/starboard/modules/12/file.md
index c3ba551..e22edeb 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/12/file.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/12/file.md
@@ -184,7 +184,7 @@
 is used primarily to clean up after unit tests. On some platforms, this function
 fails if the file in question is being held open.
 
-`path`: The absolute path fo the file, symlink, or directory to be deleted.
+`path`: The absolute path of the file, symlink, or directory to be deleted.
 
 #### Declaration ####
 
diff --git a/src/cobalt/site/docs/reference/starboard/modules/12/mutex.md b/src/cobalt/site/docs/reference/starboard/modules/12/mutex.md
index 58f1907..a9d2a9a 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/12/mutex.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/12/mutex.md
@@ -40,7 +40,7 @@
 #### Definition ####
 
 ```
-typedef union SbMutex  SbMutex
+typedef union SbMutex SbMutex
 ```
 
 ## Functions ##
diff --git a/src/cobalt/site/docs/reference/starboard/modules/12/once.md b/src/cobalt/site/docs/reference/starboard/modules/12/once.md
index ce3c4d6..61aad5d 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/12/once.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/12/once.md
@@ -22,7 +22,7 @@
 #### Definition ####
 
 ```
-typedef union SbOnceControl  SbOnceControl
+typedef union SbOnceControl SbOnceControl
 ```
 
 ### SbOnceInitRoutine ###
diff --git a/src/cobalt/site/docs/reference/starboard/modules/condition_variable.md b/src/cobalt/site/docs/reference/starboard/modules/condition_variable.md
index 836c4b8..22c2b79 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/condition_variable.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/condition_variable.md
@@ -40,7 +40,7 @@
 #### Definition ####
 
 ```
-typedef union SbConditionVariable  SbConditionVariable
+typedef union SbConditionVariable SbConditionVariable
 ```
 
 ## Functions ##
diff --git a/src/cobalt/site/docs/reference/starboard/modules/file.md b/src/cobalt/site/docs/reference/starboard/modules/file.md
index c3ba551..e22edeb 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/file.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/file.md
@@ -184,7 +184,7 @@
 is used primarily to clean up after unit tests. On some platforms, this function
 fails if the file in question is being held open.
 
-`path`: The absolute path fo the file, symlink, or directory to be deleted.
+`path`: The absolute path of the file, symlink, or directory to be deleted.
 
 #### Declaration ####
 
diff --git a/src/cobalt/site/docs/reference/starboard/modules/mutex.md b/src/cobalt/site/docs/reference/starboard/modules/mutex.md
index 58f1907..a9d2a9a 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/mutex.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/mutex.md
@@ -40,7 +40,7 @@
 #### Definition ####
 
 ```
-typedef union SbMutex  SbMutex
+typedef union SbMutex SbMutex
 ```
 
 ## Functions ##
diff --git a/src/cobalt/site/docs/reference/starboard/modules/once.md b/src/cobalt/site/docs/reference/starboard/modules/once.md
index ce3c4d6..61aad5d 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/once.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/once.md
@@ -22,7 +22,7 @@
 #### Definition ####
 
 ```
-typedef union SbOnceControl  SbOnceControl
+typedef union SbOnceControl SbOnceControl
 ```
 
 ### SbOnceInitRoutine ###
diff --git a/src/cobalt/speech/audio_encoder_flac.cc b/src/cobalt/speech/audio_encoder_flac.cc
index c6899de..279b5fe 100644
--- a/src/cobalt/speech/audio_encoder_flac.cc
+++ b/src/cobalt/speech/audio_encoder_flac.cc
@@ -56,18 +56,18 @@
   FLAC__stream_encoder_delete(encoder_);
 }
 
-void AudioEncoderFlac::Encode(const ShellAudioBus* audio_bus) {
+void AudioEncoderFlac::Encode(const AudioBus* audio_bus) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
   DCHECK_EQ(audio_bus->channels(), size_t(1));
   uint32 frames = static_cast<uint32>(audio_bus->frames());
   std::unique_ptr<FLAC__int32[]> flac_samples(new FLAC__int32[frames]);
   for (uint32 i = 0; i < frames; ++i) {
-    if (audio_bus->sample_type() == ShellAudioBus::kFloat32) {
+    if (audio_bus->sample_type() == AudioBus::kFloat32) {
       flac_samples[i] = static_cast<FLAC__int32>(
           audio_bus->GetFloat32Sample(0, i) * kMaxInt16AsFloat32);
     } else {
-      DCHECK_EQ(audio_bus->sample_type(), ShellAudioBus::kInt16);
+      DCHECK_EQ(audio_bus->sample_type(), AudioBus::kInt16);
       flac_samples[i] =
           static_cast<FLAC__int32>(audio_bus->GetInt16Sample(0, i));
     }
diff --git a/src/cobalt/speech/audio_encoder_flac.h b/src/cobalt/speech/audio_encoder_flac.h
index 71b1822..3317af9 100644
--- a/src/cobalt/speech/audio_encoder_flac.h
+++ b/src/cobalt/speech/audio_encoder_flac.h
@@ -20,7 +20,7 @@
 #include "base/basictypes.h"
 #include "base/callback.h"
 #include "base/threading/thread_checker.h"
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 #include "third_party/flac/include/FLAC/stream_encoder.h"
 
 namespace cobalt {
@@ -29,13 +29,13 @@
 // Encode raw audio to using FLAC codec.
 class AudioEncoderFlac {
  public:
-  typedef media::ShellAudioBus ShellAudioBus;
+  typedef media::AudioBus AudioBus;
 
   explicit AudioEncoderFlac(int sample_rate);
   ~AudioEncoderFlac();
 
   // Encode raw audio data.
-  void Encode(const ShellAudioBus* audio_bus);
+  void Encode(const AudioBus* audio_bus);
   // Finish encoding.
   void Finish();
 
diff --git a/src/cobalt/speech/cobalt_speech_recognizer.cc b/src/cobalt/speech/cobalt_speech_recognizer.cc
index eb9fd8d..0d61e70 100644
--- a/src/cobalt/speech/cobalt_speech_recognizer.cc
+++ b/src/cobalt/speech/cobalt_speech_recognizer.cc
@@ -123,7 +123,7 @@
 }
 
 void CobaltSpeechRecognizer::OnDataReceived(
-    std::unique_ptr<ShellAudioBus> audio_bus) {
+    std::unique_ptr<AudioBus> audio_bus) {
   if (endpointer_delegate_.IsFirstTimeSoundStarted(*audio_bus)) {
     RunEventCallback(new dom::Event(base::Tokens::soundstart()));
   }
@@ -135,8 +135,8 @@
   // silence at the end in case encoder had no data already.
   size_t dummy_frames =
       static_cast<size_t>(kSampleRate * kAudioPacketDurationInSeconds);
-  std::unique_ptr<ShellAudioBus> dummy_audio_bus(new ShellAudioBus(
-      1, dummy_frames, ShellAudioBus::kInt16, ShellAudioBus::kInterleaved));
+  std::unique_ptr<AudioBus> dummy_audio_bus(
+      new AudioBus(1, dummy_frames, AudioBus::kInt16, AudioBus::kInterleaved));
   dummy_audio_bus->ZeroAllFrames();
   service_->RecognizeAudio(std::move(dummy_audio_bus), true);
 }
diff --git a/src/cobalt/speech/cobalt_speech_recognizer.h b/src/cobalt/speech/cobalt_speech_recognizer.h
index f9ca9d4..d67ab5e 100644
--- a/src/cobalt/speech/cobalt_speech_recognizer.h
+++ b/src/cobalt/speech/cobalt_speech_recognizer.h
@@ -18,7 +18,7 @@
 #include <memory>
 #include <string>
 
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/speech/endpointer_delegate.h"
 #include "cobalt/speech/google_speech_service.h"
@@ -39,7 +39,7 @@
 // from there.
 class CobaltSpeechRecognizer : public SpeechRecognizer {
  public:
-  typedef media::ShellAudioBus ShellAudioBus;
+  typedef media::AudioBus AudioBus;
 
   CobaltSpeechRecognizer(network::NetworkModule* network_module,
                          const Microphone::Options& microphone_options,
@@ -51,7 +51,7 @@
 
  private:
   // Callbacks from mic.
-  void OnDataReceived(std::unique_ptr<ShellAudioBus> audio_bus);
+  void OnDataReceived(std::unique_ptr<AudioBus> audio_bus);
   void OnDataCompletion();
   void OnMicrophoneError(MicrophoneManager::MicrophoneError error,
                          std::string error_message);
diff --git a/src/cobalt/speech/endpointer_delegate.cc b/src/cobalt/speech/endpointer_delegate.cc
index 6340eea..1fc2658 100644
--- a/src/cobalt/speech/endpointer_delegate.cc
+++ b/src/cobalt/speech/endpointer_delegate.cc
@@ -45,8 +45,7 @@
 
 void EndPointerDelegate::Stop() { endpointer_.EndSession(); }
 
-bool EndPointerDelegate::IsFirstTimeSoundStarted(
-    const ShellAudioBus& audio_bus) {
+bool EndPointerDelegate::IsFirstTimeSoundStarted(const AudioBus& audio_bus) {
   if (is_first_time_sound_started_) {
     return false;
   }
diff --git a/src/cobalt/speech/endpointer_delegate.h b/src/cobalt/speech/endpointer_delegate.h
index fe2f18b..e222f02 100644
--- a/src/cobalt/speech/endpointer_delegate.h
+++ b/src/cobalt/speech/endpointer_delegate.h
@@ -15,8 +15,8 @@
 #ifndef COBALT_SPEECH_ENDPOINTER_DELEGATE_H_
 #define COBALT_SPEECH_ENDPOINTER_DELEGATE_H_
 
+#include "cobalt/media/base/audio_bus.h"
 #include "content/browser/speech/endpointer/endpointer.h"
-#include "cobalt/media/base/shell_audio_bus.h"
 
 namespace cobalt {
 namespace speech {
@@ -25,7 +25,7 @@
 // speech session (from start speaking to end of speaking).
 class EndPointerDelegate {
  public:
-  typedef media::ShellAudioBus ShellAudioBus;
+  typedef media::AudioBus AudioBus;
 
   explicit EndPointerDelegate(int sample_rate);
   ~EndPointerDelegate();
@@ -36,7 +36,7 @@
   void Stop();
 
   // Return true if it is the first time that the sound started.
-  bool IsFirstTimeSoundStarted(const ShellAudioBus& audio_bus);
+  bool IsFirstTimeSoundStarted(const AudioBus& audio_bus);
 
  private:
   // Used for detecting sound start event.
diff --git a/src/cobalt/speech/google_speech_service.cc b/src/cobalt/speech/google_speech_service.cc
index 83ef7ec..0bd11ac 100644
--- a/src/cobalt/speech/google_speech_service.cc
+++ b/src/cobalt/speech/google_speech_service.cc
@@ -220,8 +220,8 @@
       base::Bind(&GoogleSpeechService::StopInternal, base::Unretained(this)));
 }
 
-void GoogleSpeechService::RecognizeAudio(
-    std::unique_ptr<ShellAudioBus> audio_bus, bool is_last_chunk) {
+void GoogleSpeechService::RecognizeAudio(std::unique_ptr<AudioBus> audio_bus,
+                                         bool is_last_chunk) {
   // Called by the speech recognition manager thread.
   thread_.message_loop()->task_runner()->PostTask(
       FROM_HERE, base::Bind(&GoogleSpeechService::UploadAudioDataInternal,
@@ -393,7 +393,7 @@
 }
 
 void GoogleSpeechService::UploadAudioDataInternal(
-    std::unique_ptr<ShellAudioBus> audio_bus, bool is_last_chunk) {
+    std::unique_ptr<AudioBus> audio_bus, bool is_last_chunk) {
   DCHECK_EQ(thread_.message_loop(), base::MessageLoop::current());
   DCHECK(audio_bus);
 
diff --git a/src/cobalt/speech/google_speech_service.h b/src/cobalt/speech/google_speech_service.h
index b0002ae..a1ea1e5 100644
--- a/src/cobalt/speech/google_speech_service.h
+++ b/src/cobalt/speech/google_speech_service.h
@@ -21,7 +21,7 @@
 
 #include "base/threading/thread.h"
 #include "cobalt/loader/url_fetcher_string_writer.h"
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 #include "cobalt/network/network_module.h"
 #include "cobalt/speech/audio_encoder_flac.h"
 #include "cobalt/speech/google_streaming_api.pb.h"
@@ -43,7 +43,7 @@
 // manager.
 class GoogleSpeechService : public net::URLFetcherDelegate {
  public:
-  typedef media::ShellAudioBus ShellAudioBus;
+  typedef media::AudioBus AudioBus;
   typedef base::Callback<void(const scoped_refptr<dom::Event>&)> EventCallback;
   typedef SpeechRecognitionResultList::SpeechRecognitionResults
       SpeechRecognitionResults;
@@ -63,8 +63,7 @@
   // Stop speech recognizer.
   void Stop();
   // An encoded audio data is available and ready to be recognized.
-  void RecognizeAudio(std::unique_ptr<ShellAudioBus> audio_bus,
-                      bool is_last_chunk);
+  void RecognizeAudio(std::unique_ptr<AudioBus> audio_bus, bool is_last_chunk);
 
   // net::URLFetcherDelegate interface
   void OnURLFetchDownloadProgress(const net::URLFetcher* source,
@@ -81,7 +80,7 @@
   void StopInternal();
   // This method handles wrappables and should run on the MainWebModule thread.
   void ClearFinalResults();
-  void UploadAudioDataInternal(std::unique_ptr<ShellAudioBus> audio_bus,
+  void UploadAudioDataInternal(std::unique_ptr<AudioBus> audio_bus,
                                bool is_last_chunk);
   // This method handles wrappables, and so it must run on the MainWebModule.
   void ProcessAndFireSuccessEvent(proto::SpeechRecognitionEvent event);
diff --git a/src/cobalt/speech/microphone_fake.cc b/src/cobalt/speech/microphone_fake.cc
index 15c955a..a85c191 100644
--- a/src/cobalt/speech/microphone_fake.cc
+++ b/src/cobalt/speech/microphone_fake.cc
@@ -24,14 +24,14 @@
 #include "base/path_service.h"
 #include "base/rand_util.h"
 #include "cobalt/audio/audio_file_reader.h"
-#include "starboard/file.h"
+#include "starboard/common/file.h"
 #include "starboard/memory.h"
 #include "starboard/time.h"
 
 namespace cobalt {
 namespace speech {
 
-typedef audio::ShellAudioBus ShellAudioBus;
+typedef audio::AudioBus AudioBus;
 
 namespace {
 
@@ -89,10 +89,10 @@
   } else {
     file_length_ = std::min(options.audio_data_size, kMaxBufferSize);
     DCHECK_GT(file_length_, 0);
-    audio_bus_.reset(new ShellAudioBus(
-        kSupportedMonoChannel,
-        file_length_ / audio::GetSampleTypeSize(ShellAudioBus::kInt16),
-        ShellAudioBus::kInt16, ShellAudioBus::kInterleaved));
+    audio_bus_.reset(
+        new AudioBus(kSupportedMonoChannel,
+                     file_length_ / audio::GetSampleTypeSize(AudioBus::kInt16),
+                     AudioBus::kInt16, AudioBus::kInterleaved));
     SbMemoryCopy(audio_bus_->interleaved_data(), options.external_audio_data,
                  file_length_);
   }
@@ -131,14 +131,14 @@
     const float kSupportedSampleRate = 16000.0f;
     if (!reader) {
       // If it is not a WAV file, read audio data as raw audio.
-      audio_bus_.reset(new ShellAudioBus(
+      audio_bus_.reset(new AudioBus(
           kSupportedMonoChannel,
-          file_buffer_size / audio::GetSampleTypeSize(ShellAudioBus::kInt16),
-          ShellAudioBus::kInt16, ShellAudioBus::kInterleaved));
+          file_buffer_size / audio::GetSampleTypeSize(AudioBus::kInt16),
+          AudioBus::kInt16, AudioBus::kInterleaved));
       SbMemoryCopy(audio_bus_->interleaved_data(), audio_input.get(),
                    file_buffer_size);
       file_length_ = file_buffer_size;
-    } else if (reader->sample_type() != ShellAudioBus::kInt16 ||
+    } else if (reader->sample_type() != AudioBus::kInt16 ||
                reader->sample_rate() != kSupportedSampleRate ||
                reader->number_of_channels() != kSupportedMonoChannel) {
       // If it is a WAV file but it doesn't meet the audio input criteria, treat
diff --git a/src/cobalt/speech/microphone_fake.h b/src/cobalt/speech/microphone_fake.h
index ac4cf8b..3f6cbbc 100644
--- a/src/cobalt/speech/microphone_fake.h
+++ b/src/cobalt/speech/microphone_fake.h
@@ -52,7 +52,7 @@
 
   bool read_data_from_file_;
   std::vector<base::FilePath> file_paths_;
-  std::unique_ptr<audio::ShellAudioBus> audio_bus_;
+  std::unique_ptr<audio::AudioBus> audio_bus_;
   int file_length_;
   int read_index_;
   bool is_valid_;
diff --git a/src/cobalt/speech/microphone_manager.cc b/src/cobalt/speech/microphone_manager.cc
index a957f2d..cddf31e 100644
--- a/src/cobalt/speech/microphone_manager.cc
+++ b/src/cobalt/speech/microphone_manager.cc
@@ -145,9 +145,9 @@
   // If |read_bytes| is zero, nothing should happen.
   if (read_bytes > 0 && read_bytes % sizeof(int16_t) == 0) {
     size_t frames = read_bytes / sizeof(int16_t);
-    std::unique_ptr<ShellAudioBus> output_audio_bus(new ShellAudioBus(
-        1, frames, ShellAudioBus::kInt16, ShellAudioBus::kInterleaved));
-    ShellAudioBus source(1, frames, samples);
+    std::unique_ptr<AudioBus> output_audio_bus(
+        new AudioBus(1, frames, AudioBus::kInt16, AudioBus::kInterleaved));
+    AudioBus source(1, frames, samples);
     output_audio_bus->Assign(source);
     data_received_callback_.Run(std::move(output_audio_bus));
   } else if (read_bytes != 0) {
diff --git a/src/cobalt/speech/microphone_manager.h b/src/cobalt/speech/microphone_manager.h
index 765d2b6..602f6bf 100644
--- a/src/cobalt/speech/microphone_manager.h
+++ b/src/cobalt/speech/microphone_manager.h
@@ -25,7 +25,7 @@
 #include "base/threading/thread.h"
 #include "base/timer/timer.h"
 #include "cobalt/dom/event.h"
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 #include "cobalt/speech/microphone.h"
 
 namespace cobalt {
@@ -39,9 +39,8 @@
     kAudioCapture,
     kAborted,
   };
-  typedef media::ShellAudioBus ShellAudioBus;
-  typedef base::Callback<void(std::unique_ptr<ShellAudioBus>)>
-      DataReceivedCallback;
+  typedef media::AudioBus AudioBus;
+  typedef base::Callback<void(std::unique_ptr<AudioBus>)> DataReceivedCallback;
   typedef base::Closure CompletionCallback;
   typedef base::Closure SuccessfulOpenCallback;
   typedef base::Callback<void(MicrophoneError, std::string)> ErrorCallback;
diff --git a/src/cobalt/ui_navigation/nav_item.cc b/src/cobalt/ui_navigation/nav_item.cc
index 05f49d6..8eaf1f3 100644
--- a/src/cobalt/ui_navigation/nav_item.cc
+++ b/src/cobalt/ui_navigation/nav_item.cc
@@ -31,7 +31,9 @@
       onfocus_callback_(onfocus_callback),
       onscroll_callback_(onscroll_callback),
       nav_item_type_(type),
-      nav_item_(GetInterface().create_item(type, &s_callbacks_, this)) {}
+      nav_item_(GetInterface().create_item(type, &s_callbacks_, this)) {
+  SbAtomicNoBarrier_Store8(&enabled_, 0);
+}
 
 NavItem::~NavItem() {
   GetInterface().set_item_enabled(nav_item_, false);
diff --git a/src/cobalt/ui_navigation/nav_item.h b/src/cobalt/ui_navigation/nav_item.h
index 64d6aac..c43662f 100644
--- a/src/cobalt/ui_navigation/nav_item.h
+++ b/src/cobalt/ui_navigation/nav_item.h
@@ -18,6 +18,7 @@
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "cobalt/ui_navigation/interface.h"
+#include "starboard/atomic.h"
 
 namespace cobalt {
 namespace ui_navigation {
@@ -39,10 +40,19 @@
   }
 
   void Focus() {
-    GetInterface().set_focus(nav_item_);
+    if (SbAtomicNoBarrier_Load8(&enabled_)) {
+      GetInterface().set_focus(nav_item_);
+    }
+  }
+
+  void UnfocusAll() {
+#if SB_API_VERSION >= SB_UI_NAVIGATION2_VERSION
+    GetInterface().set_focus(kNativeItemInvalid);
+#endif
   }
 
   void SetEnabled(bool enabled) {
+    SbAtomicNoBarrier_Store8(&enabled_, enabled ? 1 : 0);
     GetInterface().set_item_enabled(nav_item_, enabled);
   }
 
@@ -104,6 +114,7 @@
 
   NativeItemType nav_item_type_;
   NativeItem nav_item_;
+  SbAtomic8 enabled_;
 
   static NativeCallbacks s_callbacks_;
 };
diff --git a/src/cobalt/updater/configurator.cc b/src/cobalt/updater/configurator.cc
index d626083..210f088 100644
--- a/src/cobalt/updater/configurator.cc
+++ b/src/cobalt/updater/configurator.cc
@@ -31,6 +31,7 @@
 
 #if defined(COBALT_BUILD_TYPE_DEBUG) || defined(COBALT_BUILD_TYPE_DEVEL)
 const std::set<std::string> valid_channels = {"dev"};
+const std::string kDefaultUpdaterChannel = "dev";
 #elif defined(COBALT_BUILD_TYPE_QA)
 // Find more information about these test channels in the Evergreen test plan.
 const std::set<std::string> valid_channels = {
@@ -51,8 +52,10 @@
     // Test an update that's larger than the available storage on the device
     "tistore",
 };
+const std::string kDefaultUpdaterChannel = "qa";
 #elif defined(COBALT_BUILD_TYPE_GOLD)
 const std::set<std::string> valid_channels = {"prod", "dogfood"};
+const std::string kDefaultUpdaterChannel = "prod";
 #endif
 
 std::string GetDeviceProperty(SbSystemPropertyId id) {
@@ -74,10 +77,21 @@
 
 Configurator::Configurator(network::NetworkModule* network_module)
     : pref_service_(CreatePrefService()),
+      persisted_data_(std::make_unique<update_client::PersistedData>(
+          pref_service_.get(), nullptr)),
+      is_channel_changed_(0),
       unzip_factory_(base::MakeRefCounted<UnzipperFactory>()),
       network_fetcher_factory_(
           base::MakeRefCounted<NetworkFetcherFactoryCobalt>(network_module)),
-      patch_factory_(base::MakeRefCounted<PatcherFactory>()) {}
+      patch_factory_(base::MakeRefCounted<PatcherFactory>()) {
+  const std::string persisted_channel =
+      persisted_data_->GetUpdaterChannel(GetAppGuid());
+  if (persisted_channel.empty()) {
+    SetChannel(kDefaultUpdaterChannel);
+  } else {
+    SetChannel(persisted_channel);
+  }
+}
 Configurator::~Configurator() = default;
 
 int Configurator::InitialDelay() const { return 0; }
@@ -134,9 +148,9 @@
   params.insert(std::make_pair("sbversion", std::to_string(SB_API_VERSION)));
   params.insert(std::make_pair(
       "jsengine", script::GetJavaScriptEngineNameAndVersion()));
-  params.insert(std::make_pair("updaterchannelchanged",
-                               IsChannelChanged() ? "True" : "False"));
-
+  params.insert(std::make_pair(
+      "updaterchannelchanged",
+      SbAtomicNoBarrier_Load(&is_channel_changed_) == 1 ? "True" : "False"));
   // Brand name
   params.insert(
       std::make_pair("brand", GetDeviceProperty(kSbSystemPropertyBrandName)));
@@ -200,6 +214,10 @@
   return {};
 }
 
+void Configurator::CompareAndSwapChannelChanged(int old_value, int new_value) {
+  SbAtomicNoBarrier_CompareAndSwap(&is_channel_changed_, old_value, new_value);
+}
+
 // The updater channel is get and set by main web module thread and update
 // client thread. The getter and set use a lock to prevent synchronization
 // issue.
@@ -211,6 +229,7 @@
 void Configurator::SetChannel(const std::string& updater_channel) {
   base::AutoLock auto_lock(updater_channel_lock_);
   updater_channel_ = updater_channel;
+  persisted_data_->SetUpdaterChannel(GetAppGuid(), updater_channel);
 }
 
 bool Configurator::IsChannelValid(const std::string& channel) {
diff --git a/src/cobalt/updater/configurator.h b/src/cobalt/updater/configurator.h
index 8012b2f..e335b4f 100644
--- a/src/cobalt/updater/configurator.h
+++ b/src/cobalt/updater/configurator.h
@@ -17,6 +17,7 @@
 #include "base/synchronization/lock.h"
 #include "cobalt/network/network_module.h"
 #include "components/update_client/configurator.h"
+#include "components/update_client/persisted_data.h"
 
 class GURL;
 class PrefService;
@@ -76,8 +77,8 @@
 
   void SetChannel(const std::string& updater_channel) override;
 
-  void MarkChannelChanged() { is_channel_changed = true; }
-  bool IsChannelChanged() const override { return is_channel_changed; }
+  void CompareAndSwapChannelChanged(int old_value, int new_value) override;
+
   bool IsChannelValid(const std::string& channel);
 
   std::string GetUpdaterStatus() const;
@@ -88,12 +89,13 @@
   ~Configurator() override;
 
   std::unique_ptr<PrefService> pref_service_;
+  std::unique_ptr<update_client::PersistedData> persisted_data_;
   scoped_refptr<update_client::NetworkFetcherFactory> network_fetcher_factory_;
   scoped_refptr<update_client::UnzipperFactory> unzip_factory_;
   scoped_refptr<update_client::PatcherFactory> patch_factory_;
   std::string updater_channel_;
   base::Lock updater_channel_lock_;
-  bool is_channel_changed = false;
+  SbAtomic32 is_channel_changed_;
   std::string updater_status_;
   base::Lock updater_status_lock_;
 
diff --git a/src/cobalt/updater/crash_sandbox.cc b/src/cobalt/updater/crash_sandbox.cc
index 8f69c81..869c589 100644
--- a/src/cobalt/updater/crash_sandbox.cc
+++ b/src/cobalt/updater/crash_sandbox.cc
@@ -18,5 +18,6 @@
 #include "starboard/event.h"
 
 void SbEventHandle(const SbEvent* event) {
-  SB_CHECK(false);
+  volatile int* a = (int*)(NULL);
+  *a = 1;
 }
diff --git a/src/cobalt/updater/updater_module.cc b/src/cobalt/updater/updater_module.cc
index 0a7904e..2f0452b 100644
--- a/src/cobalt/updater/updater_module.cc
+++ b/src/cobalt/updater/updater_module.cc
@@ -246,6 +246,10 @@
       base::TimeDelta::FromHours(kNextUpdateCheckHours));
 }
 
+void UpdaterModule::CompareAndSwapChannelChanged(int old_value, int new_value) {
+  updater_configurator_->CompareAndSwapChannelChanged(old_value, new_value);
+}
+
 // The following three methods all called by the main web module thread.
 std::string UpdaterModule::GetUpdaterChannel() const {
   return updater_configurator_->GetChannel();
diff --git a/src/cobalt/updater/updater_module.h b/src/cobalt/updater/updater_module.h
index c441d1d..d275556 100644
--- a/src/cobalt/updater/updater_module.h
+++ b/src/cobalt/updater/updater_module.h
@@ -65,8 +65,7 @@
   std::string GetUpdaterChannel() const;
   void SetUpdaterChannel(const std::string& updater_channel);
 
-  void MarkChannelChanged() { updater_configurator_->MarkChannelChanged(); }
-  bool IsChannelChanged() { return updater_configurator_->IsChannelChanged(); }
+  void CompareAndSwapChannelChanged(int old_value, int new_value);
   bool IsChannelValid(const std::string& channel) {
     return updater_configurator_->IsChannelValid(channel);
   }
diff --git a/src/cobalt/updater/utils.cc b/src/cobalt/updater/utils.cc
index 17f0822..dab4b92 100644
--- a/src/cobalt/updater/utils.cc
+++ b/src/cobalt/updater/utils.cc
@@ -22,6 +22,12 @@
 
 namespace cobalt {
 namespace updater {
+namespace {
+// The default manifest version to assume when the actual manifest cannot be
+// parsed for any reason. This should not be used for installation manager
+// errors, or any other error unrelated to parsing the manifest.
+const std::string kDefaultManifestVersion = "1.0.0";
+}  // namespace
 
 bool CreateProductDirectory(base::FilePath* path) {
   if (!GetProductDirectoryPath(path)) {
@@ -112,6 +118,12 @@
       std::string(installation_path.begin(), installation_path.end())));
 
   if (!version.IsValid()) {
+    if (!index) {
+      SB_LOG(ERROR) << "Failed to get the Everegreen version. Defaulting to "
+                    << kDefaultManifestVersion << ".";
+      return kDefaultManifestVersion;
+    }
+
     SB_LOG(ERROR) << "Failed to get the Everegreen version.";
     return "";
   }
diff --git a/src/cobalt/xhr/url_fetcher_buffer_writer.cc b/src/cobalt/xhr/url_fetcher_buffer_writer.cc
index a01a9a8..3b43b8a 100644
--- a/src/cobalt/xhr/url_fetcher_buffer_writer.cc
+++ b/src/cobalt/xhr/url_fetcher_buffer_writer.cc
@@ -103,7 +103,8 @@
   return copy_of_data_as_string_;
 }
 
-void URLFetcherResponseWriter::Buffer::GetAndReset(std::string* str) {
+void URLFetcherResponseWriter::Buffer::GetAndResetDataAndDownloadProgress(
+    std::string* str) {
   DCHECK(str);
 
   ReleaseMemory(str);
@@ -119,9 +120,15 @@
   }
 
   data_as_string_.swap(*str);
+
+  // It is important to reset the |download_progress_| and return the data in
+  // one function to avoid potential race condition that may prevent the last
+  // bit of data of Fetcher from being downloaded, because the data download is
+  // guarded by HasProgressSinceLastGetAndReset().
+  download_progress_ = 0;
 }
 
-void URLFetcherResponseWriter::Buffer::GetAndReset(
+void URLFetcherResponseWriter::Buffer::GetAndResetData(
     PreallocatedArrayBufferData* data) {
   DCHECK(data);
 
diff --git a/src/cobalt/xhr/url_fetcher_buffer_writer.h b/src/cobalt/xhr/url_fetcher_buffer_writer.h
index c82ce32..8eb9736 100644
--- a/src/cobalt/xhr/url_fetcher_buffer_writer.h
+++ b/src/cobalt/xhr/url_fetcher_buffer_writer.h
@@ -57,8 +57,8 @@
     // public member function is called on this object.
     const std::string& GetTemporaryReferenceOfString();
 
-    void GetAndReset(std::string* str);
-    void GetAndReset(PreallocatedArrayBufferData* data);
+    void GetAndResetDataAndDownloadProgress(std::string* str);
+    void GetAndResetData(PreallocatedArrayBufferData* data);
 
     void MaybePreallocate(int64_t capacity);
     void Write(const void* buffer, int num_bytes);
diff --git a/src/cobalt/xhr/xml_http_request.cc b/src/cobalt/xhr/xml_http_request.cc
index 27408e2..0ea3345 100644
--- a/src/cobalt/xhr/xml_http_request.cc
+++ b/src/cobalt/xhr/xml_http_request.cc
@@ -717,7 +717,7 @@
 
   if (fetch_callback_) {
     std::string downloaded_data;
-    response_body_->GetAndReset(&downloaded_data);
+    response_body_->GetAndResetDataAndDownloadProgress(&downloaded_data);
     script::Handle<script::Uint8Array> data =
         script::Uint8Array::New(settings_->global_environment(),
                                 downloaded_data.data(), downloaded_data.size());
@@ -729,9 +729,13 @@
   const base::TimeDelta elapsed(now - last_progress_time_);
   if (elapsed > base::TimeDelta::FromMilliseconds(kProgressPeriodMs)) {
     last_progress_time_ = now;
-    // TODO: Investigate if we have to fire progress event with 0 loaded bytes
-    // when used as Fetch API.
-    UpdateProgress(response_body_->GetAndResetDownloadProgress());
+    if (fetch_callback_) {
+      // TODO: Investigate if we have to fire progress event with 0 loaded bytes
+      //       when used as Fetch API.
+      UpdateProgress(0);
+    } else {
+      UpdateProgress(response_body_->GetAndResetDownloadProgress());
+    }
   }
 }
 
@@ -1025,7 +1029,7 @@
     // request is re-opened.
     std::unique_ptr<script::PreallocatedArrayBufferData> downloaded_data(
         new script::PreallocatedArrayBufferData());
-    response_body_->GetAndReset(downloaded_data.get());
+    response_body_->GetAndResetData(downloaded_data.get());
     auto array_buffer = script::ArrayBuffer::New(
         settings_->global_environment(), std::move(downloaded_data));
     response_array_buffer_reference_.reset(
diff --git a/src/components/update_client/component.cc b/src/components/update_client/component.cc
index df190d0..50ec274 100644
--- a/src/components/update_client/component.cc
+++ b/src/components/update_client/component.cc
@@ -195,6 +195,14 @@
 #endif
 
   if (result.error != UnpackerError::kNone) {
+#if defined(OS_STARBOARD)
+    // When there is an error unpacking the downloaded CRX, such as a failure to
+    // verify the package, we should remember to clear out any drain files.
+    if (base::DirectoryExists(crx_path.DirName())) {
+      CobaltSlotManagement cobalt_slot_management;
+      cobalt_slot_management.CleanupAllDrainFiles(crx_path.DirName());
+    }
+#endif
     main_task_runner->PostTask(
         FROM_HERE,
         base::BindOnce(std::move(callback), ErrorCategory::kUnpack,
diff --git a/src/components/update_client/configurator.h b/src/components/update_client/configurator.h
index 5baa3cc..ab7e01e 100644
--- a/src/components/update_client/configurator.h
+++ b/src/components/update_client/configurator.h
@@ -171,7 +171,9 @@
   // parameters.
   virtual void SetChannel(const std::string& channel) = 0;
 
-  virtual bool IsChannelChanged() const = 0;
+  // Compare and swap the is_channel_changed flag.
+  virtual void CompareAndSwapChannelChanged(int old_value, int new_value) = 0;
+
 #endif
 
  protected:
diff --git a/src/components/update_client/test_configurator.h b/src/components/update_client/test_configurator.h
index f6ede80..c3fbda1 100644
--- a/src/components/update_client/test_configurator.h
+++ b/src/components/update_client/test_configurator.h
@@ -128,7 +128,7 @@
 
 #if defined(STARBOARD)
   void SetChannel(const std::string& channel) override {}
-  bool IsChannelChanged() const override { return false; }
+  void CompareAndSwapChannelChanged(int old_value, int new_value) override {}
 #else
   network::TestURLLoaderFactory* test_url_loader_factory() {
     return &test_url_loader_factory_;
diff --git a/src/components/update_client/update_checker.cc b/src/components/update_client/update_checker.cc
index c4045f0..3cfa2d5 100644
--- a/src/components/update_client/update_checker.cc
+++ b/src/components/update_client/update_checker.cc
@@ -44,14 +44,6 @@
 
 namespace {
 
-#if defined(COBALT_BUILD_TYPE_DEBUG) || defined(COBALT_BUILD_TYPE_DEVEL)
-const std::string kDefaultUpdaterChannel = "dev";
-#elif defined(COBALT_BUILD_TYPE_QA)
-const std::string kDefaultUpdaterChannel = "qa";
-#elif defined(COBALT_BUILD_TYPE_GOLD)
-const std::string kDefaultUpdaterChannel = "prod";
-#endif
-
 // Returns a sanitized version of the brand or an empty string otherwise.
 std::string SanitizeBrand(const std::string& brand) {
   return IsValidBrand(brand) ? brand : std::string("");
@@ -249,22 +241,6 @@
         MakeProtocolPing(app_id, metadata_)));
   }
   std::string updater_channel = config_->GetChannel();
-#if defined(OS_STARBOARD)
-  // If the updater channel is not set, read from pref store instead.
-  if (updater_channel.empty()) {
-    // All apps of the update use the same channel.
-    updater_channel = GetPersistedData()->GetUpdaterChannel(ids_checked_[0]);
-    if (updater_channel.empty()) {
-      updater_channel = kDefaultUpdaterChannel;
-    }
-    // Set the updater channel from the persistent store or to default channel,
-    // if it's not set already.
-    config_->SetChannel(updater_channel);
-  } else {
-    // Update the record of updater channel in pref store.
-    GetPersistedData()->SetUpdaterChannel(ids_checked_[0], updater_channel);
-  }
-#endif
 
   const auto request = MakeProtocolRequest(
       session_id, config_->GetProdId(),
@@ -284,6 +260,10 @@
       config_->EnabledCupSigning(),
       base::BindOnce(&UpdateCheckerImpl::OnRequestSenderComplete,
                      base::Unretained(this)));
+#if defined(OS_STARBOARD)
+  // Reset is_channel_changed flag to false if it is true
+  config_->CompareAndSwapChannelChanged(1, 0);
+#endif
 }
 
 void UpdateCheckerImpl::OnRequestSenderComplete(int error,
diff --git a/src/content/browser/speech/endpointer/endpointer.cc b/src/content/browser/speech/endpointer/endpointer.cc
index 5c19c4a..96d8e6c 100644
--- a/src/content/browser/speech/endpointer/endpointer.cc
+++ b/src/content/browser/speech/endpointer/endpointer.cc
@@ -89,7 +89,7 @@
 }
 
 #if defined(STARBOARD)
-EpStatus Endpointer::ProcessAudio(const ShellAudioBus& audio_bus, float* rms_out) {
+EpStatus Endpointer::ProcessAudio(const AudioBus& audio_bus, float* rms_out) {
   // TODO[Cobalt]: replace ShellAudioData with AudioChunk and deprecate
   // ShellAudioData.
   DCHECK_EQ(audio_bus.channels(), 1);
@@ -97,16 +97,16 @@
   const size_t num_samples = audio_bus.frames();
   const int16_t* audio_data = NULL;
 
-  ShellAudioBus int16_audio_bus(1, num_samples, ShellAudioBus::kInt16,
-                                ShellAudioBus::kInterleaved);
+  AudioBus int16_audio_bus(1, num_samples, AudioBus::kInt16,
+                           AudioBus::kInterleaved);
 
-  if (audio_bus.sample_type() == ShellAudioBus::kFloat32) {
+  if (audio_bus.sample_type() == AudioBus::kFloat32) {
     int16_audio_bus.Assign(audio_bus);
-    DCHECK_EQ(int16_audio_bus.sample_type(), ShellAudioBus::kInt16);
+    DCHECK_EQ(int16_audio_bus.sample_type(), AudioBus::kInt16);
     audio_data =
         reinterpret_cast<const int16_t*>(int16_audio_bus.interleaved_data());
   } else {
-    DCHECK_EQ(audio_bus.sample_type(), ShellAudioBus::kInt16);
+    DCHECK_EQ(audio_bus.sample_type(), AudioBus::kInt16);
     audio_data =
         reinterpret_cast<const int16_t*>(audio_bus.interleaved_data());
   }
diff --git a/src/content/browser/speech/endpointer/endpointer.h b/src/content/browser/speech/endpointer/endpointer.h
index 16bfed6..2264842 100644
--- a/src/content/browser/speech/endpointer/endpointer.h
+++ b/src/content/browser/speech/endpointer/endpointer.h
@@ -7,7 +7,7 @@
 
 #include <stdint.h>
 
-#include "cobalt/media/base/shell_audio_bus.h"
+#include "cobalt/media/base/audio_bus.h"
 #include "content/browser/speech/endpointer/energy_endpointer.h"
 #include "content/common/content_export.h"
 
@@ -47,7 +47,7 @@
 // long_speech_input_complete_silence_length.
 class CONTENT_EXPORT Endpointer {
  public:
-  typedef cobalt::media::ShellAudioBus ShellAudioBus;
+  typedef cobalt::media::AudioBus AudioBus;
 
   explicit Endpointer(int sample_rate);
 
@@ -68,7 +68,7 @@
   // Process a segment of audio, which may be more than one frame.
   // The status of the last frame will be returned.
 #if defined(STARBOARD)
-  EpStatus ProcessAudio(const ShellAudioBus& audio_bus, float* rms_out);
+  EpStatus ProcessAudio(const AudioBus& audio_bus, float* rms_out);
 #else
   EpStatus ProcessAudio(const AudioChunk& raw_audio, float* rms_out);
 #endif
diff --git a/src/content/browser/speech/endpointer/endpointer_unittest.cc b/src/content/browser/speech/endpointer/endpointer_unittest.cc
index 632cc37..4b3cbb5 100644
--- a/src/content/browser/speech/endpointer/endpointer_unittest.cc
+++ b/src/content/browser/speech/endpointer/endpointer_unittest.cc
@@ -120,7 +120,7 @@
 class EndpointerFrameProcessor : public FrameProcessor {
  public:
 #if defined(STARBOARD)
-  typedef Endpointer::ShellAudioBus ShellAudioBus;
+  typedef Endpointer::AudioBus AudioBus;
 #endif
   explicit EndpointerFrameProcessor(Endpointer* endpointer)
       : endpointer_(endpointer) {}
@@ -129,7 +129,7 @@
                         int16_t* samples,
                         int frame_size) override {
 #if defined(STARBOARD)
-    auto frame = std::make_unique<ShellAudioBus>(1, kFrameSize, samples);
+    auto frame = std::make_unique<AudioBus>(1, kFrameSize, samples);
     endpointer_->ProcessAudio(*frame.get(), NULL);
 #else
     scoped_refptr<AudioChunk> frame(
diff --git a/src/docker-compose.yml b/src/docker-compose.yml
index 30d600f..749a24a 100644
--- a/src/docker-compose.yml
+++ b/src/docker-compose.yml
@@ -31,6 +31,15 @@
       dockerfile: base/Dockerfile
     image: cobalt-base
 
+  base-xenial:
+    build:
+      args:
+        - BASE_OS=ubuntu
+        - BASE_OS_TAG=xenial
+      context: ./docker/linux
+      dockerfile: base/Dockerfile
+    image: base-xenial
+
   # Define common build container for Linux
   build-base:
     build:
@@ -40,6 +49,16 @@
     depends_on:
       - base
 
+  build-base-xenial:
+    build:
+      context: ./docker/linux
+      dockerfile: base/build/Dockerfile
+      args:
+        - FROM_IMAGE=base-xenial
+    image: build-base-xenial
+    depends_on:
+      - base-xenial
+
   stub:
     <<: *build-common-definitions
     build:
@@ -56,10 +75,36 @@
       context: ./docker/linux
       dockerfile: linux-x64x11/Dockerfile
     image: cobalt-build-linux
+    depends_on: [ build-base ]
     environment:
       - PLATFORM=linux-x64x11
       - CONFIG=${CONFIG:-debug}
 
+  linux-x64x11-xenial:
+    <<: *common-definitions
+    <<: *build-volumes
+    build:
+      context: ./docker/linux
+      dockerfile: linux-x64x11/Dockerfile
+      args:
+        - FROM_IMAGE=build-base-xenial
+    image: linux-x64x11-xenial
+    depends_on:
+      - build-base-xenial
+
+  linux-x64x11-clang-3-6:
+    <<: *common-definitions
+    <<: *build-volumes
+    build:
+      context: ./docker/linux/
+      dockerfile: clang-3-6/Dockerfile
+    image: cobalt-build-linux-clang-3-6
+    environment:
+      - PLATFORM=linux-x64x11-clang-3-6
+      - CONFIG=${CONFIG:-debug}
+    depends_on:
+      - linux-x64x11-xenial
+
   # Define common build container for Android
   build-android:
     <<: *build-common-definitions
@@ -71,7 +116,7 @@
   android-x86:
     <<: *build-common-definitions
     image: cobalt-build-android
-    depends_on: [ build-evergreen ]
+    depends_on: [ build-android ]
     environment:
       - IS_DOCKER=1
       - PLATFORM=android-x86
@@ -80,7 +125,7 @@
   android-arm:
     <<: *build-common-definitions
     image: cobalt-build-android
-    depends_on: [ build-evergreen ]
+    depends_on: [ build-android ]
     environment:
       - IS_DOCKER=1
       - PLATFORM=android-arm
@@ -89,7 +134,7 @@
   android-arm64:
     <<: *build-common-definitions
     image: cobalt-build-android
-    depends_on: [ build-evergreen ]
+    depends_on: [ build-android ]
     environment:
       - IS_DOCKER=1
       - PLATFORM=android-arm64
@@ -160,8 +205,8 @@
     environment:
       - PLATFORM=${PLATFORM:-linux-x64x11}
       - CONFIG=${CONFIG:-debug}
-      - TEST_TARGET=${TEST_TARGET:-eztime_test}
     volumes:
       - ${COBALT_SRC:-.}/out/${PLATFORM:-linux-x64x11}_${CONFIG:-debug}:/out
     # TODO: Get NPLB unittests to run with IPv6 without using the host network.
     network_mode: "host"
+    depends_on: [ base ]
diff --git a/src/docker/linux/android/Dockerfile b/src/docker/linux/android/Dockerfile
index d76e0ce..ad34732 100644
--- a/src/docker/linux/android/Dockerfile
+++ b/src/docker/linux/android/Dockerfile
@@ -16,6 +16,8 @@
     && rm -rf /var/lib/{apt,dpkg,cache,log} \
     && echo "Done"
 
+RUN mkdir -p /root/.android
+
 CMD (test -f /root/.android/debug.keystore \
     && echo "Android debug keystore exists." \
     || (keytool -genkey -v \
diff --git a/src/docker/linux/base/Dockerfile b/src/docker/linux/base/Dockerfile
index 4f21c76..2746def 100644
--- a/src/docker/linux/base/Dockerfile
+++ b/src/docker/linux/base/Dockerfile
@@ -1,5 +1,6 @@
 ARG BASE_OS
-FROM ${BASE_OS:-gcr.io/cloud-marketplace-containers/google/debian9}
+ARG BASE_OS_TAG
+FROM ${BASE_OS:-gcr.io/cloud-marketplace-containers/google/debian9}:${BASE_OS_TAG:-latest}
 
 ENV PYTHONUNBUFFERED 1
 
diff --git a/src/docker/linux/base/build/Dockerfile b/src/docker/linux/base/build/Dockerfile
index 416b5ee..545908d 100644
--- a/src/docker/linux/base/build/Dockerfile
+++ b/src/docker/linux/base/build/Dockerfile
@@ -1,4 +1,5 @@
-FROM cobalt-base
+ARG FROM_IMAGE
+FROM ${FROM_IMAGE:-cobalt-base}
 
 # === Get Nodejs pinned LTS version via NVM
 ENV NVM_DIR /root/.nvm
@@ -18,17 +19,15 @@
 RUN git clone https://cobalt.googlesource.com/depot_tools /depot_tools
 
 # === Configure common env vars
-ENV PATH="${PATH}:/depot_tools:/root/fake_goma" \
+ENV PATH="${PATH}:/depot_tools" \
     OUTDIR=out \
     DEPOT_TOOLS_UPDATE=0 \
     NINJA_STATUS="[%f/%t %c/sec] " \
     CCACHE_DIR=/root/ccache \
     CCACHE_MAXSIZE=30G
 
-# == Set up gclient and fake Goma with ccache
-COPY ./files/fake_goma /root/fake_goma
+# == Set up gclient and ccache
 RUN cd /tmp && gclient verify || true \
-    && chmod +x /root/fake_goma/gomacc /root/fake_goma/goma_ctl.py \
     && mkdir /root/ccache
 
 WORKDIR /code
diff --git a/src/docker/linux/clang-3-6/Dockerfile b/src/docker/linux/clang-3-6/Dockerfile
new file mode 100644
index 0000000..0bf3926
--- /dev/null
+++ b/src/docker/linux/clang-3-6/Dockerfile
@@ -0,0 +1,14 @@
+FROM linux-x64x11-xenial
+
+ARG DEBIAN_FRONTEND=noninteractive
+
+RUN apt update -qqy \
+    && apt install -qqy --no-install-recommends clang-3.6 \
+    && apt-get clean autoclean \
+    && apt-get autoremove -y --purge \
+    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
+    && rm -rf /var/lib/{apt,dpkg,cache,log} \
+    && echo "Done"
+
+CMD /code/cobalt/build/gyp_cobalt -v -C ${CONFIG} ${PLATFORM} && \
+    ninja -C ${OUTDIR}/${PLATFORM}_${CONFIG} ${TARGET:-cobalt_deploy}
diff --git a/src/docker/linux/files/fake_goma/goma_ctl.py b/src/docker/linux/files/fake_goma/goma_ctl.py
deleted file mode 100755
index 2f2ceb9..0000000
--- a/src/docker/linux/files/fake_goma/goma_ctl.py
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env python
-
-print("Faking Goma via ccache")
diff --git a/src/docker/linux/files/fake_goma/gomacc b/src/docker/linux/files/fake_goma/gomacc
deleted file mode 100755
index d64f812..0000000
--- a/src/docker/linux/files/fake_goma/gomacc
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-ccache "$@"
diff --git a/src/docker/linux/linux-x64x11/Dockerfile b/src/docker/linux/linux-x64x11/Dockerfile
index 524f320..29f7d8d 100644
--- a/src/docker/linux/linux-x64x11/Dockerfile
+++ b/src/docker/linux/linux-x64x11/Dockerfile
@@ -1,4 +1,5 @@
-FROM cobalt-build-base
+ARG FROM_IMAGE
+FROM ${FROM_IMAGE:-cobalt-build-base}
 
 RUN apt update -qqy \
     && apt install -qqy --no-install-recommends \
diff --git a/src/docker/linux/unittest/Dockerfile b/src/docker/linux/unittest/Dockerfile
index 32a35a5..e876c8d 100644
--- a/src/docker/linux/unittest/Dockerfile
+++ b/src/docker/linux/unittest/Dockerfile
@@ -7,7 +7,9 @@
         libavformat57 \
         libavresample3 \
         libavutil55 \
+        libegl1-mesa \
         libgl1-mesa-dri \
+        libgles2-mesa \
         libx11-6 \
         libxcomposite1 \
         libxrender1 \
@@ -27,7 +29,7 @@
 
 RUN mkdir -p /app_launcher_out
 
-CMD unzip /out/app_launcher -d /app_launcher_out && \
+CMD unzip -q /out/app_launcher -d /app_launcher_out && \
     xvfb-run --server-args="-screen 0 1920x1080x24 +render +extension GLX -noreset" \
     python /app_launcher_out/starboard/tools/testing/test_runner.py --run \
-           -o /out --platform $PLATFORM --config $CONFIG -t $TEST_TARGET
+           -o /out --platform $PLATFORM --config $CONFIG
diff --git a/src/net/dial/dial_system_config_starboard.cc b/src/net/dial/dial_system_config_starboard.cc
index 316d889..7ec70d0 100644
--- a/src/net/dial/dial_system_config_starboard.cc
+++ b/src/net/dial/dial_system_config_starboard.cc
@@ -14,8 +14,8 @@
 
 #include "net/dial/dial_system_config.h"
 
+#include "starboard/common/file.h"
 #include "starboard/configuration_constants.h"
-#include "starboard/file.h"
 #include "starboard/system.h"
 
 namespace {
diff --git a/src/net/ssl/ssl_key_logger_impl.cc b/src/net/ssl/ssl_key_logger_impl.cc
index 550d372..2bad355 100644
--- a/src/net/ssl/ssl_key_logger_impl.cc
+++ b/src/net/ssl/ssl_key_logger_impl.cc
@@ -16,7 +16,10 @@
 #include "base/sequenced_task_runner.h"
 #include "base/task/post_task.h"
 #include "base/task/task_traits.h"
+#if defined(STARBOARD)
+#include "starboard/common/file.h"
 #include "starboard/types.h"
+#endif
 
 namespace net {
 
diff --git a/src/requirements.txt b/src/requirements.txt
new file mode 100644
index 0000000..e9215ea
--- /dev/null
+++ b/src/requirements.txt
@@ -0,0 +1,4 @@
+cpplint==1.5.4
+pre-commit==2.6.0
+pylint==2.6.0
+yapf==0.30.0
diff --git a/src/starboard/CHANGELOG.md b/src/starboard/CHANGELOG.md
index cdd706c..40aab86 100644
--- a/src/starboard/CHANGELOG.md
+++ b/src/starboard/CHANGELOG.md
@@ -261,6 +261,23 @@
 when those settings change. For older starboard versions, use
 kSbEventTypeAccessiblitySettingsChanged instead.
 
+### Add extension to SbMediaCanPlayMimeAndKeySystem() for encryptionScheme.
+
+Now the Starboard implementation may choose to support |key_system| with extra
+attributes, in order to selectively support encryption schemes on particular
+containers or codecs.
+The Starboard implementation needn't support |key_system| with extra attributes
+if it meets the requirements for the default implementation of
+`Navigator.requestMediaKeySystemAccess()`, which assumes that:
+1. When the Widevine DRM system is used, all the encryption schemes ('cenc',
+   'cbcs', 'cbcs-1-9') should be supported across all containers and codecs
+   supported by the platform.
+2. When the PlayReady DRM system is used, only 'cenc' is supported across all
+   containers and codecs supported by the platform.
+
+Please see the comment of `SbMediaCanPlayMimeAndKeySystem()` in `media.h` for
+more details.
+
 ## Version 11
 
 ### Add arguments to `SbMediaIsVideoSupported`.
diff --git a/src/starboard/android/apk/app/build.gradle b/src/starboard/android/apk/app/build.gradle
index fdf3b29..5ab0feb 100644
--- a/src/starboard/android/apk/app/build.gradle
+++ b/src/starboard/android/apk/app/build.gradle
@@ -66,7 +66,7 @@
     defaultConfig {
         applicationId "dev.cobalt.coat"
         minSdkVersion 21
-        targetSdkVersion 30
+        targetSdkVersion 29
         versionCode 1
         versionName "${buildId}"
         manifestPlaceholders = [applicationName: "CoAT: ${cobaltTarget}"]
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 fe988bb..9cee635 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
@@ -30,7 +30,7 @@
 import android.os.Build;
 import android.util.Size;
 import android.util.SizeF;
-import android.view.Display;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.CaptioningManager;
 import androidx.annotation.RequiresApi;
@@ -155,13 +155,17 @@
   @SuppressWarnings("unused")
   @UsedByNative
   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
-    // can take their time suspending after that.
-    cobaltMediaSession.suspend();
-    for (CobaltService service : cobaltServices.values()) {
-      service.beforeSuspend();
+    try {
+      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
+      // can take their time suspending after that.
+      cobaltMediaSession.suspend();
+      for (CobaltService service : cobaltServices.values()) {
+        service.beforeSuspend();
+      }
+    } catch (Throwable e) {
+      Log.i(TAG, "Caught exception in beforeSuspend: " + e.getMessage());
     }
   }
 
@@ -521,12 +525,18 @@
       return false;
     }
 
-    Display defaultDisplay = DisplayUtil.getDefaultDisplay(activityHolder.get());
-    if (defaultDisplay == null) {
+    Activity activity = activityHolder.get();
+    if (activity == null) {
       return false;
     }
 
-    int[] supportedHdrTypes = defaultDisplay.getHdrCapabilities().getSupportedHdrTypes();
+    WindowManager windowManager = activity.getWindowManager();
+    if (windowManager == null) {
+      return false;
+    }
+
+    int[] supportedHdrTypes =
+        windowManager.getDefaultDisplay().getHdrCapabilities().getSupportedHdrTypes();
     for (int supportedType : supportedHdrTypes) {
       if (supportedType == hdrType) {
         return true;
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 42c4e98..faa8742 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
@@ -69,7 +69,6 @@
   private static final String KEY_CROP_TOP = "crop-top";
 
   private static final int BITRATE_ADJUSTMENT_FPS = 30;
-  private static final int MAXIMUM_INITIAL_FPS = 30;
 
   private long mNativeMediaCodecBridge;
   private MediaCodec mMediaCodec;
@@ -78,6 +77,8 @@
   private long mLastPresentationTimeUs;
   private final String mMime;
   private boolean mAdaptivePlaybackSupported;
+  private double mPlaybackRate = 1.0;
+  private int mFps = 30;
 
   // Functions that require this will be called frequently in a tight loop.
   // Only create one of these and reuse it to avoid excessive allocations,
@@ -104,6 +105,45 @@
     public static final String VIDEO_AV1 = "video/av01";
   }
 
+  private class FrameRateEstimator {
+    private static final int INVALID_FRAME_RATE = -1;
+    private static final long INVALID_FRAME_TIMESTAMP = -1;
+    private static final int MINIMUN_REQUIRED_FRAMES = 4;
+    private long mLastFrameTimestampUs = INVALID_FRAME_TIMESTAMP;
+    private long mNumberOfFrames = 0;
+    private long mTotalDurationUs = 0;
+
+    public int getEstimatedFrameRate() {
+      if (mTotalDurationUs <= 0 || mNumberOfFrames < MINIMUN_REQUIRED_FRAMES) {
+        return INVALID_FRAME_RATE;
+      }
+      return Math.round((mNumberOfFrames - 1) * 1000000.0f / mTotalDurationUs);
+    }
+
+    public void reset() {
+      mLastFrameTimestampUs = INVALID_FRAME_TIMESTAMP;
+      mNumberOfFrames = 0;
+      mTotalDurationUs = 0;
+    }
+
+    public void onNewFrame(long presentationTimeUs) {
+      mNumberOfFrames++;
+
+      if (mLastFrameTimestampUs == INVALID_FRAME_TIMESTAMP) {
+        mLastFrameTimestampUs = presentationTimeUs;
+        return;
+      }
+      if (presentationTimeUs <= mLastFrameTimestampUs) {
+        Log.v(TAG, String.format("Invalid output presentation timestamp."));
+        return;
+      }
+
+      mTotalDurationUs += presentationTimeUs - mLastFrameTimestampUs;
+      mLastFrameTimestampUs = presentationTimeUs;
+    }
+  }
+
+  private FrameRateEstimator mFrameRateEstimator = null;
   private BitrateAdjustmentTypes mBitrateAdjustmentType = BitrateAdjustmentTypes.NO_ADJUSTMENT;
 
   @SuppressWarnings("unused")
@@ -415,6 +455,14 @@
                   info.offset,
                   info.presentationTimeUs,
                   info.size);
+              if (mFrameRateEstimator != null) {
+                mFrameRateEstimator.onNewFrame(info.presentationTimeUs);
+                int fps = mFrameRateEstimator.getEstimatedFrameRate();
+                if (fps != FrameRateEstimator.INVALID_FRAME_RATE && mFps != fps) {
+                  mFps = fps;
+                  updateOperatingRate();
+                }
+              }
             }
           }
 
@@ -599,6 +647,40 @@
 
   @SuppressWarnings("unused")
   @UsedByNative
+  private void setPlaybackRate(double playbackRate) {
+    if (mPlaybackRate == playbackRate) {
+      return;
+    }
+    mPlaybackRate = playbackRate;
+    if (mFrameRateEstimator != null) {
+      updateOperatingRate();
+    }
+  }
+
+  private void updateOperatingRate() {
+    // We needn't set operation rate if playback rate is 0 or less.
+    if (Double.compare(mPlaybackRate, 0.0) <= 0) {
+      return;
+    }
+    if (mFps == FrameRateEstimator.INVALID_FRAME_RATE) {
+      return;
+    }
+    if (mFps <= 0) {
+      Log.e(TAG, "Failed to set operating rate with invalid fps " + mFps);
+      return;
+    }
+    double operatingRate = mPlaybackRate * mFps;
+    Bundle b = new Bundle();
+    b.putFloat(MediaFormat.KEY_OPERATING_RATE, (float) operatingRate);
+    try {
+      mMediaCodec.setParameters(b);
+    } catch (IllegalStateException e) {
+      Log.e(TAG, "Failed to set MediaCodec operating rate", e);
+    }
+  }
+
+  @SuppressWarnings("unused")
+  @UsedByNative
   private int flush() {
     try {
       mFlushed = true;
@@ -830,17 +912,23 @@
       if (mAdaptivePlaybackSupported) {
         // Since we haven't passed the properties of the stream we're playing
         // down to this level, from our perspective, we could potentially
-        // adapt up to 8k at any point.  We thus request 8k buffers up front,
+        // adapt up to 8k at any point. We thus request 8k buffers up front,
         // unless the decoder claims to not be able to do 8k, in which case
         // we're ok, since we would've rejected a 8k stream when canPlayType
         // was called, and then use those decoder values instead.
-        int maxWidth = Math.min(7680, maxSupportedWidth);
-        int maxHeight = Math.min(4320, maxSupportedHeight);
-        format.setInteger(MediaFormat.KEY_MAX_WIDTH, maxWidth);
-        format.setInteger(MediaFormat.KEY_MAX_HEIGHT, maxHeight);
+        if (Build.VERSION.SDK_INT > 22) {
+          format.setInteger(MediaFormat.KEY_MAX_WIDTH, Math.min(7680, maxSupportedWidth));
+          format.setInteger(MediaFormat.KEY_MAX_HEIGHT, Math.min(4320, maxSupportedHeight));
+        } else {
+          // Android 5.0/5.1 seems not support 8K. Fallback to 4K until we get a
+          // better way to get maximum supported resolution.
+          format.setInteger(MediaFormat.KEY_MAX_WIDTH, Math.min(3840, maxSupportedWidth));
+          format.setInteger(MediaFormat.KEY_MAX_HEIGHT, Math.min(2160, maxSupportedHeight));
+        }
       }
       maybeSetMaxInputSize(format);
       mMediaCodec.configure(format, surface, crypto, flags);
+      mFrameRateEstimator = new FrameRateEstimator();
       return true;
     } catch (IllegalArgumentException e) {
       Log.e(TAG, "Cannot configure the video codec with IllegalArgumentException: ", e);
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 1e39d63..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
@@ -35,9 +35,8 @@
 import android.os.Message;
 import android.view.Choreographer;
 import android.view.Choreographer.FrameCallback;
-import android.view.Display;
+import android.view.WindowManager;
 import androidx.annotation.RequiresApi;
-import dev.cobalt.util.DisplayUtil;
 import dev.cobalt.util.UsedByNative;
 
 /** Makes a best effort to adjust frame release timestamps for a smoother visual result. */
@@ -222,9 +221,9 @@
   }
 
   private static double getDefaultDisplayRefreshRate(Context context) {
-    Display defaultDisplay = DisplayUtil.getDefaultDisplay(context);
-    return defaultDisplay != null
-        ?  defaultDisplay.getRefreshRate()
+    WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+    return manager.getDefaultDisplay() != null
+        ? manager.getDefaultDisplay().getRefreshRate()
         : DISPLAY_REFRESH_RATE_UNKNOWN;
   }
 
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java
index c7c2208..4b23394 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java
@@ -15,14 +15,10 @@
 package dev.cobalt.util;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.util.DisplayMetrics;
 import android.util.Size;
 import android.util.SizeF;
-import android.view.Display;
 import android.view.WindowManager;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 
 /** Utility functions for querying display attributes. */
 public class DisplayUtil {
@@ -30,34 +26,6 @@
   private DisplayUtil() {}
 
   /**
-   * Returns the default display associated with a context.
-   */
-  @Nullable
-  public static Display getDefaultDisplay(Context context) {
-    if (context == null) {
-      return null;
-    }
-    if (android.os.Build.VERSION.SDK_INT >= 30) {
-      return getDefaultDisplayV30(context);
-    } else {
-      return getDefaultDisplayDeprecated(context);
-    }
-  }
-
-  @Nullable
-  @SuppressWarnings("deprecation")
-  private static Display getDefaultDisplayDeprecated(Context context) {
-    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-    return (wm == null) ? null : wm.getDefaultDisplay();
-  }
-
-  @Nullable
-  @RequiresApi(30)
-  private static Display getDefaultDisplayV30(Context context) {
-    return context.getDisplay();
-  }
-
-  /**
    * Returns the physical pixels per inch of the screen in the X and Y
    * dimensions.
    */
@@ -114,10 +82,10 @@
   private static DisplayMetrics cachedDisplayMetrics = null;
 
   private static DisplayMetrics getDisplayMetrics(Context context) {
-    Resources.getSystem().getDisplayMetrics();
     if (cachedDisplayMetrics == null) {
       cachedDisplayMetrics = new DisplayMetrics();
-      getDefaultDisplay(context).getRealMetrics(cachedDisplayMetrics);
+      ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
+          .getDefaultDisplay().getRealMetrics(cachedDisplayMetrics);
     }
     return cachedDisplayMetrics;
   }
diff --git a/src/starboard/android/apk/build.id b/src/starboard/android/apk/build.id
new file mode 100644
index 0000000..a485246
--- /dev/null
+++ b/src/starboard/android/apk/build.id
@@ -0,0 +1 @@
+282262
\ No newline at end of file
diff --git a/src/starboard/android/shared/android_main.cc b/src/starboard/android/shared/android_main.cc
index 1f6513d..a44497e 100644
--- a/src/starboard/android/shared/android_main.cc
+++ b/src/starboard/android/shared/android_main.cc
@@ -67,6 +67,7 @@
   if (j_url) {
     start_url = env->GetStringStandardUTFOrAbort(j_url.Get());
   }
+  SB_LOG(INFO) << "GetStartDeepLink: " << start_url;
   return start_url;
 }
 
diff --git a/src/starboard/android/shared/android_media_session_client.cc b/src/starboard/android/shared/android_media_session_client.cc
new file mode 100644
index 0000000..b822ec1
--- /dev/null
+++ b/src/starboard/android/shared/android_media_session_client.cc
@@ -0,0 +1,297 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/android_media_session_client.h"
+
+#include "base/time/time.h"
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/common/log.h"
+#include "starboard/common/mutex.h"
+#include "starboard/once.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+using ::starboard::android::shared::JniEnvExt;
+using ::starboard::android::shared::ScopedLocalJavaRef;
+
+// These constants are from android.media.session.PlaybackState
+const jlong kPlaybackStateActionStop = 1 << 0;
+const jlong kPlaybackStateActionPause = 1 << 1;
+const jlong kPlaybackStateActionPlay = 1 << 2;
+const jlong kPlaybackStateActionRewind = 1 << 3;
+const jlong kPlaybackStateActionSkipToPrevious = 1 << 4;
+const jlong kPlaybackStateActionSkipToNext = 1 << 5;
+const jlong kPlaybackStateActionFastForward = 1 << 6;
+const jlong kPlaybackStateActionSetRating = 1 << 7;  // not supported
+const jlong kPlaybackStateActionSeekTo = 1 << 8;
+
+// Converts a MediaSessionClient::AvailableActions bitset into
+// a android.media.session.PlaybackState jlong bitset.
+jlong MediaSessionActionsToPlaybackStateActions(const bool* actions) {
+  jlong result = 0;
+  if (actions[kCobaltExtensionMediaSessionActionPause]) {
+    result |= kPlaybackStateActionPause;
+  }
+  if (actions[kCobaltExtensionMediaSessionActionPlay]) {
+    result |= kPlaybackStateActionPlay;
+  }
+  if (actions[kCobaltExtensionMediaSessionActionSeekbackward]) {
+    result |= kPlaybackStateActionRewind;
+  }
+  if (actions[kCobaltExtensionMediaSessionActionPrevioustrack]) {
+    result |= kPlaybackStateActionSkipToPrevious;
+  }
+  if (actions[kCobaltExtensionMediaSessionActionNexttrack]) {
+    result |= kPlaybackStateActionSkipToNext;
+  }
+  if (actions[kCobaltExtensionMediaSessionActionSeekforward]) {
+    result |= kPlaybackStateActionFastForward;
+  }
+  if (actions[kCobaltExtensionMediaSessionActionSeekto]) {
+    result |= kPlaybackStateActionSeekTo;
+  }
+  if (actions[kCobaltExtensionMediaSessionActionStop]) {
+    result |= kPlaybackStateActionStop;
+  }
+  return result;
+}
+
+PlaybackState CobaltExtensionPlaybackStateToPlaybackState(
+    CobaltExtensionMediaSessionPlaybackState in_state) {
+  switch (in_state) {
+    case kCobaltExtensionMediaSessionPlaying:
+      return kPlaying;
+    case kCobaltExtensionMediaSessionPaused:
+      return kPaused;
+    case kCobaltExtensionMediaSessionNone:
+      return kNone;
+  }
+}
+
+CobaltExtensionMediaSessionAction PlaybackStateActionToMediaSessionAction(
+    jlong action) {
+  CobaltExtensionMediaSessionAction result;
+  switch (action) {
+    case kPlaybackStateActionPause:
+      result = kCobaltExtensionMediaSessionActionPause;
+      break;
+    case kPlaybackStateActionPlay:
+      result = kCobaltExtensionMediaSessionActionPlay;
+      break;
+    case kPlaybackStateActionRewind:
+      result = kCobaltExtensionMediaSessionActionSeekbackward;
+      break;
+    case kPlaybackStateActionSkipToPrevious:
+      result = kCobaltExtensionMediaSessionActionPrevioustrack;
+      break;
+    case kPlaybackStateActionSkipToNext:
+      result = kCobaltExtensionMediaSessionActionNexttrack;
+      break;
+    case kPlaybackStateActionFastForward:
+      result = kCobaltExtensionMediaSessionActionSeekforward;
+      break;
+    case kPlaybackStateActionSeekTo:
+      result = kCobaltExtensionMediaSessionActionSeekto;
+      break;
+    case kPlaybackStateActionStop:
+      result = kCobaltExtensionMediaSessionActionStop;
+      break;
+    default:
+      SB_NOTREACHED() << "Unsupported MediaSessionAction 0x" << std::hex
+                      << action;
+      result = static_cast<CobaltExtensionMediaSessionAction>(-1);
+  }
+  return result;
+}
+
+CobaltExtensionMediaSessionPlaybackState
+PlaybackStateToCobaltExtensionPlaybackState(PlaybackState state) {
+  CobaltExtensionMediaSessionPlaybackState result;
+  switch (state) {
+    case kPlaying:
+      result = kCobaltExtensionMediaSessionPlaying;
+      break;
+    case kPaused:
+      result = kCobaltExtensionMediaSessionPaused;
+      break;
+    case kNone:
+      result = kCobaltExtensionMediaSessionNone;
+      break;
+    default:
+      SB_NOTREACHED() << "Unsupported PlaybackState " << state;
+      result = static_cast<CobaltExtensionMediaSessionPlaybackState>(-1);
+  }
+  return result;
+}
+
+SbOnceControl once_flag = SB_ONCE_INITIALIZER;
+SbMutex mutex;
+
+// Callbacks to the last MediaSessionClient to become active, or null.
+// Used to route Java callbacks.
+// In practice, only one MediaSessionClient will become active at a time.
+// Protected by "mutex"
+CobaltExtensionMediaSessionUpdatePlatformPlaybackStateCallback
+    update_platform_playback_state_callback;
+CobaltExtensionMediaSessionInvokeActionCallback invoke_action_callback;
+void* callback_context;
+
+void OnceInit() {
+  SbMutexCreate(&mutex);
+}
+
+void NativeInvokeAction(jlong action, jlong seek_ms) {
+  SbOnce(&once_flag, OnceInit);
+  SbMutexAcquire(&mutex);
+
+  if (invoke_action_callback != NULL && callback_context != NULL) {
+    CobaltExtensionMediaSessionActionDetails details = {};
+    CobaltExtensionMediaSessionActionDetailsInit(
+        &details, PlaybackStateActionToMediaSessionAction(action));
+    // CobaltMediaSession.java only sets seek_ms for SeekTo (not ff/rew).
+    if (details.action == kCobaltExtensionMediaSessionActionSeekto) {
+      details.seek_time = seek_ms / 1000.0;
+    }
+    invoke_action_callback(details, callback_context);
+  }
+
+  SbMutexRelease(&mutex);
+}
+
+void UpdateActiveSessionPlatformPlaybackState(PlaybackState state) {
+  SbOnce(&once_flag, OnceInit);
+  SbMutexAcquire(&mutex);
+
+  CobaltExtensionMediaSessionPlaybackState media_session_state =
+      PlaybackStateToCobaltExtensionPlaybackState(state);
+
+  if (update_platform_playback_state_callback != NULL &&
+      callback_context != NULL) {
+    update_platform_playback_state_callback(media_session_state,
+                                            callback_context);
+  }
+
+  SbMutexRelease(&mutex);
+}
+
+void OnMediaSessionStateChanged(
+    const CobaltExtensionMediaSessionState session_state) {
+  JniEnvExt* env = JniEnvExt::Get();
+
+  jint playback_state = CobaltExtensionPlaybackStateToPlaybackState(
+      session_state.actual_playback_state);
+  void* media_session_client = session_state.callback_context;
+
+  SbOnce(&once_flag, OnceInit);
+  SbMutexAcquire(&mutex);
+  if (playback_state != kNone) {
+    callback_context = media_session_client;
+    update_platform_playback_state_callback =
+        session_state.update_platform_playback_state_callback;
+    invoke_action_callback = session_state.invoke_action_callback;
+  } else if (callback_context == media_session_client) {
+    callback_context = NULL;
+  }
+  SbMutexRelease(&mutex);
+
+  jlong playback_state_actions = MediaSessionActionsToPlaybackStateActions(
+      session_state.available_actions);
+
+  ScopedLocalJavaRef<jstring> j_title;
+  ScopedLocalJavaRef<jstring> j_artist;
+  ScopedLocalJavaRef<jstring> j_album;
+  ScopedLocalJavaRef<jobjectArray> j_artwork;
+
+  if (session_state.metadata != NULL) {
+    CobaltExtensionMediaMetadata* media_metadata(session_state.metadata);
+
+    j_title.Reset(env->NewStringStandardUTFOrAbort(media_metadata->title));
+    j_artist.Reset(env->NewStringStandardUTFOrAbort(media_metadata->artist));
+    j_album.Reset(env->NewStringStandardUTFOrAbort(media_metadata->album));
+
+    size_t artwork_count = media_metadata->artwork_count;
+    if (artwork_count > 0) {
+      CobaltExtensionMediaImage* artwork(media_metadata->artwork);
+      ScopedLocalJavaRef<jclass> media_image_class(
+          env->FindClassExtOrAbort("dev/cobalt/media/MediaImage"));
+      jmethodID media_image_constructor = env->GetMethodID(
+          media_image_class.Get(), "<init>",
+          "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+      env->AbortOnException();
+
+      j_artwork.Reset(static_cast<jobjectArray>(
+          env->NewObjectArray(artwork_count, media_image_class.Get(), NULL)));
+      env->AbortOnException();
+
+      ScopedLocalJavaRef<jstring> j_src;
+      ScopedLocalJavaRef<jstring> j_sizes;
+      ScopedLocalJavaRef<jstring> j_type;
+      for (size_t i = 0; i < artwork_count; i++) {
+        const CobaltExtensionMediaImage& media_image(artwork[i]);
+        j_src.Reset(env->NewStringStandardUTFOrAbort(media_image.src));
+        j_sizes.Reset(env->NewStringStandardUTFOrAbort(media_image.size));
+        j_type.Reset(env->NewStringStandardUTFOrAbort(media_image.type));
+
+        ScopedLocalJavaRef<jobject> j_media_image(
+            env->NewObject(media_image_class.Get(), media_image_constructor,
+                           j_src.Get(), j_sizes.Get(), j_type.Get()));
+
+        env->SetObjectArrayElement(j_artwork.Get(), i, j_media_image.Get());
+      }
+    }
+  }
+
+  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(
+      "updateMediaSession",
+      "(IJJFLjava/lang/String;Ljava/lang/String;Ljava/lang/String;"
+      "[Ldev/cobalt/media/MediaImage;J)V",
+      playback_state, playback_state_actions,
+      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(), durationInMilliseconds);
+}
+
+const CobaltExtensionMediaSessionApi kMediaSessionApi = {
+    kCobaltExtensionMediaSessionName, 1, &OnMediaSessionStateChanged};
+
+const void* GetMediaSessionApi() {
+  return &kMediaSessionApi;
+}
+
+}  // namespace shared
+}  // namespace android
+}  // namespace starboard
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_media_CobaltMediaSession_nativeInvokeAction(JNIEnv* env,
+                                                            jclass unused_clazz,
+                                                            jlong action,
+                                                            jlong seek_ms) {
+  starboard::android::shared::NativeInvokeAction(action, seek_ms);
+}
diff --git a/src/starboard/android/shared/cobalt/android_media_session_client.h b/src/starboard/android/shared/android_media_session_client.h
similarity index 86%
rename from src/starboard/android/shared/cobalt/android_media_session_client.h
rename to src/starboard/android/shared/android_media_session_client.h
index 5fc3f41..83fd0cc 100644
--- a/src/starboard/android/shared/cobalt/android_media_session_client.h
+++ b/src/starboard/android/shared/android_media_session_client.h
@@ -15,17 +15,21 @@
 #ifndef STARBOARD_ANDROID_SHARED_COBALT_ANDROID_MEDIA_SESSION_CLIENT_H_
 #define STARBOARD_ANDROID_SHARED_COBALT_ANDROID_MEDIA_SESSION_CLIENT_H_
 
+#include "cobalt/extension/media_session.h"
+
 namespace starboard {
 namespace android {
 namespace shared {
-namespace cobalt {
 
 // Duplicated in CobaltMediaSession.java
 enum PlaybackState { kPlaying = 0, kPaused = 1, kNone = 2 };
 
 void UpdateActiveSessionPlatformPlaybackState(PlaybackState state);
 
-}  // namespace cobalt
+void OnMediaSessionStateChanged(
+    const CobaltExtensionMediaSessionState session_state);
+
+const void* GetMediaSessionApi();
 }  // namespace shared
 }  // namespace android
 }  // namespace starboard
diff --git a/src/starboard/android/shared/application_android.cc b/src/starboard/android/shared/application_android.cc
index e8fab42..1352a26 100644
--- a/src/starboard/android/shared/application_android.cc
+++ b/src/starboard/android/shared/application_android.cc
@@ -69,6 +69,8 @@
         return "WindowFocusGained";
       case ApplicationAndroid::AndroidCommand::kWindowFocusLost:
         return "WindowFocusLost";
+      case ApplicationAndroid::AndroidCommand::kDeepLink:
+        return "DeepLink";
       default:
         return "unknown";
     }
@@ -298,6 +300,21 @@
     case AndroidCommand::kStop:
       sync_state = activity_state_ = cmd.type;
       break;
+    case AndroidCommand::kDeepLink:
+      char* deep_link = static_cast<char*>(cmd.data);
+      SB_LOG(INFO) << "AndroidCommand::kDeepLink: deep_link=" << deep_link
+                   << " state=" << state();
+      if (deep_link != NULL) {
+        if (state() == kStateUnstarted) {
+          SetStartLink(deep_link);
+          SB_LOG(INFO) << "ApplicationAndroid SetStartLink";
+          SbMemoryDeallocate(static_cast<void*>(deep_link));
+        } else {
+          SB_LOG(INFO) << "ApplicationAndroid Inject: kSbEventTypeLink";
+          Inject(new Event(kSbEventTypeLink, deep_link, SbMemoryDeallocate));
+        }
+      }
+      break;
   }
 
   // If there's a window, sync the app state to the Activity lifecycle, letting
@@ -337,7 +354,7 @@
     // Android main thread. This lets the MediaSession get released now without
     // having to wait to bounce between threads.
     JniEnvExt* env = JniEnvExt::Get();
-    env->CallStarboardVoidMethodOrAbort("beforeSuspend", "()V");
+    env->CallStarboardVoidMethod("beforeSuspend", "()V");
   }
   AndroidCommand cmd {type, data};
   ScopedLock lock(android_command_mutex_);
@@ -538,12 +555,14 @@
 }
 
 void ApplicationAndroid::HandleDeepLink(const char* link_url) {
+  SB_LOG(INFO) << "ApplicationAndroid::HandleDeepLink link_url=" << link_url;
   if (link_url == NULL || link_url[0] == '\0') {
     return;
   }
   char* deep_link = SbStringDuplicate(link_url);
   SB_DCHECK(deep_link);
-  Inject(new Event(kSbEventTypeLink, deep_link, SbMemoryDeallocate));
+
+  SendAndroidCommand(AndroidCommand::kDeepLink, deep_link);
 }
 
 extern "C" SB_EXPORT_PLATFORM
diff --git a/src/starboard/android/shared/application_android.h b/src/starboard/android/shared/application_android.h
index 3ad4820..6dad099 100644
--- a/src/starboard/android/shared/application_android.h
+++ b/src/starboard/android/shared/application_android.h
@@ -51,6 +51,7 @@
       kNativeWindowDestroyed,
       kWindowFocusGained,
       kWindowFocusLost,
+      kDeepLink,
     } CommandType;
 
     CommandType type;
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.h b/src/starboard/android/shared/audio_track_audio_sink_type.h
index 1a8cb37..43b0c86 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.h
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.h
@@ -74,10 +74,10 @@
                                        SbMediaAudioSampleType sample_type,
                                        int sampling_frequency_hz);
 
-  MinRequiredFramesTester min_required_frames_tester_;
   Mutex min_required_frames_map_mutex_;
   // The minimum frames required to avoid underruns of different frequencies.
   std::map<int, int> min_required_frames_map_;
+  MinRequiredFramesTester min_required_frames_tester_;
 };
 
 class AudioTrackAudioSink : public SbAudioSinkPrivate {
diff --git a/src/starboard/android/shared/cobalt/android_media_session_client.cc b/src/starboard/android/shared/cobalt/android_media_session_client.cc
deleted file mode 100644
index 30ec630..0000000
--- a/src/starboard/android/shared/cobalt/android_media_session_client.cc
+++ /dev/null
@@ -1,353 +0,0 @@
-// Copyright 2017 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "starboard/android/shared/cobalt/android_media_session_client.h"
-
-#include "base/time/time.h"
-#include "cobalt/media_session/media_session_action_details.h"
-#include "cobalt/media_session/media_session_client.h"
-#include "cobalt/script/sequence.h"
-#include "starboard/android/shared/jni_env_ext.h"
-#include "starboard/android/shared/jni_utils.h"
-#include "starboard/common/log.h"
-#include "starboard/common/mutex.h"
-#include "starboard/once.h"
-
-namespace starboard {
-namespace android {
-namespace shared {
-namespace cobalt {
-
-using ::cobalt::media_session::MediaImage;
-using ::cobalt::media_session::MediaMetadataInit;
-using ::cobalt::media_session::MediaSession;
-using ::cobalt::media_session::MediaSessionAction;
-using ::cobalt::media_session::MediaSessionActionDetails;
-using ::cobalt::media_session::MediaSessionClient;
-using ::cobalt::media_session::MediaSessionPlaybackState;
-using ::cobalt::media_session::MediaSessionState;
-using ::cobalt::media_session::kMediaSessionActionPause;
-using ::cobalt::media_session::kMediaSessionActionPlay;
-using ::cobalt::media_session::kMediaSessionActionSeekto;
-using ::cobalt::media_session::kMediaSessionActionSeekbackward;
-using ::cobalt::media_session::kMediaSessionActionSeekforward;
-using ::cobalt::media_session::kMediaSessionActionStop;
-using ::cobalt::media_session::kMediaSessionActionPrevioustrack;
-using ::cobalt::media_session::kMediaSessionActionNexttrack;
-using ::cobalt::media_session::kMediaSessionPlaybackStateNone;
-using ::cobalt::media_session::kMediaSessionPlaybackStatePaused;
-using ::cobalt::media_session::kMediaSessionPlaybackStatePlaying;
-
-using MediaImageSequence = ::cobalt::script::Sequence<MediaImage>;
-
-using ::starboard::android::shared::JniEnvExt;
-using ::starboard::android::shared::ScopedLocalJavaRef;
-
-namespace {
-
-// These constants are from android.media.session.PlaybackState
-const jlong kPlaybackStateActionStop = 1 << 0;
-const jlong kPlaybackStateActionPause = 1 << 1;
-const jlong kPlaybackStateActionPlay = 1 << 2;
-const jlong kPlaybackStateActionRewind = 1 << 3;
-const jlong kPlaybackStateActionSkipToPrevious = 1 << 4;
-const jlong kPlaybackStateActionSkipToNext = 1 << 5;
-const jlong kPlaybackStateActionFastForward = 1 << 6;
-const jlong kPlaybackStateActionSetRating = 1 << 7;  // not supported
-const jlong kPlaybackStateActionSeekTo = 1 << 8;
-
-// Converts a MediaSessionClient::AvailableActions bitset into
-// a android.media.session.PlaybackState jlong bitset.
-jlong MediaSessionActionsToPlaybackStateActions(
-    const MediaSessionState::AvailableActionsSet& actions) {
-  jlong result = 0;
-  if (actions[kMediaSessionActionPause]) {
-    result |= kPlaybackStateActionPause;
-  }
-  if (actions[kMediaSessionActionPlay]) {
-    result |= kPlaybackStateActionPlay;
-  }
-  if (actions[kMediaSessionActionSeekbackward]) {
-    result |= kPlaybackStateActionRewind;
-  }
-  if (actions[kMediaSessionActionPrevioustrack]) {
-    result |= kPlaybackStateActionSkipToPrevious;
-  }
-  if (actions[kMediaSessionActionNexttrack]) {
-    result |= kPlaybackStateActionSkipToNext;
-  }
-  if (actions[kMediaSessionActionSeekforward]) {
-    result |= kPlaybackStateActionFastForward;
-  }
-  if (actions[kMediaSessionActionSeekto]) {
-    result |= kPlaybackStateActionSeekTo;
-  }
-  if (actions[kMediaSessionActionStop]) {
-    result |= kPlaybackStateActionStop;
-  }
-  return result;
-}
-
-PlaybackState MediaSessionPlaybackStateToPlaybackState(
-    MediaSessionPlaybackState in_state) {
-  switch (in_state) {
-    case kMediaSessionPlaybackStatePlaying:
-      return kPlaying;
-    case kMediaSessionPlaybackStatePaused:
-      return kPaused;
-    case kMediaSessionPlaybackStateNone:
-      return kNone;
-  }
-}
-
-MediaSessionAction PlaybackStateActionToMediaSessionAction(jlong action) {
-  MediaSessionAction result;
-  switch (action) {
-    case kPlaybackStateActionPause:
-      result = kMediaSessionActionPause;
-      break;
-    case kPlaybackStateActionPlay:
-      result = kMediaSessionActionPlay;
-      break;
-    case kPlaybackStateActionRewind:
-      result = kMediaSessionActionSeekbackward;
-      break;
-    case kPlaybackStateActionSkipToPrevious:
-      result = kMediaSessionActionPrevioustrack;
-      break;
-    case kPlaybackStateActionSkipToNext:
-      result = kMediaSessionActionNexttrack;
-      break;
-    case kPlaybackStateActionFastForward:
-      result = kMediaSessionActionSeekforward;
-      break;
-    case kPlaybackStateActionSeekTo:
-      result = kMediaSessionActionSeekto;
-      break;
-    case kPlaybackStateActionStop:
-      result = kMediaSessionActionStop;
-      break;
-    default:
-      SB_NOTREACHED() << "Unsupported MediaSessionAction 0x"
-                      << std::hex << action;
-      result = static_cast<MediaSessionAction>(-1);
-  }
-  return result;
-}
-
-MediaSessionPlaybackState PlaybackStateToMediaSessionPlaybackState(
-    PlaybackState state) {
-  MediaSessionPlaybackState result;
-  switch (state) {
-    case kPlaying:
-      result = kMediaSessionPlaybackStatePlaying;
-      break;
-    case kPaused:
-      result = kMediaSessionPlaybackStatePaused;
-      break;
-    case kNone:
-      result = kMediaSessionPlaybackStateNone;
-      break;
-    default:
-      SB_NOTREACHED() << "Unsupported PlaybackState " << state;
-      result = static_cast<MediaSessionPlaybackState>(-1);
-  }
-  return result;
-}
-
-}  // namespace
-
-class AndroidMediaSessionClient : public MediaSessionClient {
-  static SbOnceControl once_flag;
-  static SbMutex mutex;
-  // The last MediaSessionClient to become active, or null.
-  // Used to route Java callbacks.
-  // In practice, only one MediaSessionClient will become active at a time.
-  // Protected by "mutex"
-  static AndroidMediaSessionClient* active_client;
-
-  static void OnceInit() { SbMutexCreate(&mutex); }
-
- public:
-  static void NativeInvokeAction(jlong action, jlong seek_ms) {
-    SbOnce(&once_flag, OnceInit);
-    SbMutexAcquire(&mutex);
-
-    if (active_client != NULL) {
-      std::unique_ptr<MediaSessionActionDetails> details(
-          new MediaSessionActionDetails());
-      details->set_action(PlaybackStateActionToMediaSessionAction(action));
-      // CobaltMediaSession.java only sets seek_ms for SeekTo (not ff/rew).
-      if (details->action() == kMediaSessionActionSeekto) {
-        details->set_seek_time(seek_ms / 1000.0);
-      }
-      active_client->InvokeAction(std::move(details));
-    }
-
-    SbMutexRelease(&mutex);
-  }
-
-  static void UpdateActiveSessionPlatformPlaybackState(
-      MediaSessionPlaybackState state) {
-    SbOnce(&once_flag, OnceInit);
-    SbMutexAcquire(&mutex);
-
-    if (active_client != NULL) {
-      active_client->UpdatePlatformPlaybackState(state);
-    }
-
-    SbMutexRelease(&mutex);
-  }
-
-  AndroidMediaSessionClient() {}
-
-  virtual ~AndroidMediaSessionClient() {
-    SbOnce(&once_flag, OnceInit);
-    SbMutexAcquire(&mutex);
-    if (active_client == this) {
-      active_client = NULL;
-    }
-    SbMutexRelease(&mutex);
-  }
-
-  void OnMediaSessionStateChanged(
-      const MediaSessionState& session_state) override {
-    JniEnvExt* env = JniEnvExt::Get();
-
-    jint playback_state = MediaSessionPlaybackStateToPlaybackState(
-        session_state.actual_playback_state());
-
-    SbOnce(&once_flag, OnceInit);
-    SbMutexAcquire(&mutex);
-    if (playback_state != kNone) {
-      active_client = this;
-    } else if (active_client == this) {
-      active_client = NULL;
-    }
-    SbMutexRelease(&mutex);
-
-    jlong playback_state_actions = MediaSessionActionsToPlaybackStateActions(
-        session_state.available_actions());
-
-    ScopedLocalJavaRef<jstring> j_title;
-    ScopedLocalJavaRef<jstring> j_artist;
-    ScopedLocalJavaRef<jstring> j_album;
-    ScopedLocalJavaRef<jobjectArray> j_artwork;
-
-    if (session_state.has_metadata()) {
-      const MediaMetadataInit& media_metadata(session_state.metadata().value());
-
-      j_title.Reset(
-          env->NewStringStandardUTFOrAbort(media_metadata.title().c_str()));
-      j_artist.Reset(
-          env->NewStringStandardUTFOrAbort(media_metadata.artist().c_str()));
-      j_album.Reset(
-          env->NewStringStandardUTFOrAbort(media_metadata.album().c_str()));
-
-      if (media_metadata.has_artwork()) {
-        const MediaImageSequence& artwork(media_metadata.artwork());
-        ScopedLocalJavaRef<jclass> media_image_class(
-            env->FindClassExtOrAbort("dev/cobalt/media/MediaImage"));
-        jmethodID media_image_constructor = env->GetMethodID(
-            media_image_class.Get(), "<init>",
-            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
-        env->AbortOnException();
-
-        j_artwork.Reset(static_cast<jobjectArray>(env->NewObjectArray(
-            artwork.size(), media_image_class.Get(), NULL)));
-        env->AbortOnException();
-
-        ScopedLocalJavaRef<jstring> j_src;
-        ScopedLocalJavaRef<jstring> j_sizes;
-        ScopedLocalJavaRef<jstring> j_type;
-        for (MediaImageSequence::size_type i = 0; i < artwork.size(); i++) {
-          const MediaImage& media_image(artwork.at(i));
-          j_src.Reset(
-              env->NewStringStandardUTFOrAbort(media_image.src().c_str()));
-          j_sizes.Reset(
-              env->NewStringStandardUTFOrAbort(media_image.sizes().c_str()));
-          j_type.Reset(
-              env->NewStringStandardUTFOrAbort(media_image.type().c_str()));
-
-          ScopedLocalJavaRef<jobject> j_media_image(
-              env->NewObject(media_image_class.Get(), media_image_constructor,
-                             j_src.Get(), j_sizes.Get(), j_type.Get()));
-
-          env->SetObjectArrayElement(j_artwork.Get(), i, j_media_image.Get());
-        }
-      }
-    }
-
-    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(
-        "updateMediaSession",
-        "(IJJFLjava/lang/String;Ljava/lang/String;Ljava/lang/String;"
-        "[Ldev/cobalt/media/MediaImage;J)V",
-        playback_state, playback_state_actions,
-        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(),
-        durationInMilliseconds);
-  }
-};
-
-SbOnceControl AndroidMediaSessionClient::once_flag = SB_ONCE_INITIALIZER;
-SbMutex AndroidMediaSessionClient::mutex;
-AndroidMediaSessionClient* AndroidMediaSessionClient::active_client = NULL;
-
-void UpdateActiveSessionPlatformPlaybackState(PlaybackState state) {
-  MediaSessionPlaybackState media_session_state =
-      PlaybackStateToMediaSessionPlaybackState(state);
-
-  AndroidMediaSessionClient::UpdateActiveSessionPlatformPlaybackState(
-      media_session_state);
-}
-
-}  // namespace cobalt
-}  // namespace shared
-}  // namespace android
-}  // namespace starboard
-
-using starboard::android::shared::cobalt::AndroidMediaSessionClient;
-
-extern "C" SB_EXPORT_PLATFORM
-void Java_dev_cobalt_media_CobaltMediaSession_nativeInvokeAction(
-    JNIEnv* env,
-    jclass unused_clazz,
-    jlong action,
-    jlong seek_ms) {
-  AndroidMediaSessionClient::NativeInvokeAction(action, seek_ms);
-}
-
-namespace cobalt {
-namespace media_session {
-
-// static
-std::unique_ptr<MediaSessionClient> MediaSessionClient::Create() {
-  return std::unique_ptr<MediaSessionClient>(new AndroidMediaSessionClient());
-}
-
-}  // namespace media_session
-}  // namespace cobalt
diff --git a/src/starboard/android/shared/cobalt/cobalt_platform.gyp b/src/starboard/android/shared/cobalt/cobalt_platform.gyp
index 6f6379a..2d4ba5b 100644
--- a/src/starboard/android/shared/cobalt/cobalt_platform.gyp
+++ b/src/starboard/android/shared/cobalt/cobalt_platform.gyp
@@ -20,11 +20,10 @@
       'sources': [
         'android_user_authorizer.h',
         'android_user_authorizer.cc',
-        'android_media_session_client.cc',
       ],
       'dependencies': [
-        '<(DEPTH)/cobalt/media_session/media_session.gyp:media_session'
+        '<(DEPTH)/cobalt/base/base.gyp:base',
       ],
-    },
+    }
   ],
 }
diff --git a/src/starboard/android/shared/cobalt/configuration.gypi b/src/starboard/android/shared/cobalt/configuration.gypi
index c374599..c9cdba0 100644
--- a/src/starboard/android/shared/cobalt/configuration.gypi
+++ b/src/starboard/android/shared/cobalt/configuration.gypi
@@ -18,7 +18,6 @@
   'variables': {
     'in_app_dial': 0,
 
-    'custom_media_session_client': 1,
     'enable_account_manager': 1,
 
     # The 'android_system' font package installs only minimal fonts, with a
diff --git a/src/starboard/android/shared/cobalt/configuration.py b/src/starboard/android/shared/cobalt/configuration.py
index 96c11fe..f9cf7d7 100644
--- a/src/starboard/android/shared/cobalt/configuration.py
+++ b/src/starboard/android/shared/cobalt/configuration.py
@@ -66,6 +66,11 @@
 
   # A map of failing or crashing tests per target.
   __FILTERED_TESTS = {
+      'layout_tests': [
+          # Android relies of system fonts and some older Android builds do not
+          # have the update (Emoji 11.0) NotoColorEmoji.ttf installed.
+          'CSS3FontsLayoutTests/Layout.Test/color_emojis_should_render_properly'
+      ],
       'renderer_test': [
           # Instead of returning an error when allocating too much texture
           # memory, Android instead just terminates the process.  Since this
diff --git a/src/starboard/android/shared/gyp_configuration.py b/src/starboard/android/shared/gyp_configuration.py
index f0eba6e..04d5372 100644
--- a/src/starboard/android/shared/gyp_configuration.py
+++ b/src/starboard/android/shared/gyp_configuration.py
@@ -327,6 +327,18 @@
           'SbDirectoryGetNextTest.SunnyDayStaticContent',
           'SbDirectoryOpenTest.SunnyDayStaticContent',
           'SbFileGetPathInfoTest.WorksOnStaticContentDirectories',
+          # Android doesn't currently support specifying a bitrate under 8000
+          'SbMediaCanPlayMimeAndKeySystem.MinimumSupport',
+          # There are issues with playback of heeac format files where the input
+          # |frames_per_channel| is 6912, instead of 12544 (as with other
+          # formats).
+          'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.NoInput/6',
+          # These tests are disabled due to not receiving the kEndOfStream
+          # player state update within the specified timeout.
+          'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.NoInput/7',
+          'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.NoInput/8',
+          'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.NoInput/9',
+          'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.NoInput/10',
       ],
   }
 
diff --git a/src/starboard/android/shared/media_codec_bridge.cc b/src/starboard/android/shared/media_codec_bridge.cc
index b39a61d..f78c347 100644
--- a/src/starboard/android/shared/media_codec_bridge.cc
+++ b/src/starboard/android/shared/media_codec_bridge.cc
@@ -344,6 +344,11 @@
                                           render_timestamp_ns);
 }
 
+void MediaCodecBridge::SetPlaybackRate(double playback_rate) {
+  JniEnvExt::Get()->CallVoidMethodOrAbort(
+      j_media_codec_bridge_, "setPlaybackRate", "(D)V", playback_rate);
+}
+
 jint MediaCodecBridge::Flush() {
   return JniEnvExt::Get()->CallIntMethodOrAbort(j_media_codec_bridge_, "flush",
                                                 "()I");
diff --git a/src/starboard/android/shared/media_codec_bridge.h b/src/starboard/android/shared/media_codec_bridge.h
index 1fc6618..77452c1 100644
--- a/src/starboard/android/shared/media_codec_bridge.h
+++ b/src/starboard/android/shared/media_codec_bridge.h
@@ -131,6 +131,7 @@
   void ReleaseOutputBuffer(jint index, jboolean render);
   void ReleaseOutputBufferAtTimestamp(jint index, jlong render_timestamp_ns);
 
+  void SetPlaybackRate(double playback_rate);
   jint Flush();
   SurfaceDimensions GetOutputDimensions();
   AudioOutputFormatResult GetAudioOutputFormat();
diff --git a/src/starboard/android/shared/media_decoder.cc b/src/starboard/android/shared/media_decoder.cc
index d5668d0..fc5fa0d 100644
--- a/src/starboard/android/shared/media_decoder.cc
+++ b/src/starboard/android/shared/media_decoder.cc
@@ -177,6 +177,12 @@
   }
 }
 
+void MediaDecoder::SetPlaybackRate(double playback_rate) {
+  SB_DCHECK(media_type_ == kSbMediaTypeVideo);
+  SB_DCHECK(media_codec_bridge_);
+  media_codec_bridge_->SetPlaybackRate(playback_rate);
+}
+
 // static
 void* MediaDecoder::DecoderThreadEntryPoint(void* context) {
   SB_DCHECK(context);
diff --git a/src/starboard/android/shared/media_decoder.h b/src/starboard/android/shared/media_decoder.h
index 41a7a11..80d0b7e 100644
--- a/src/starboard/android/shared/media_decoder.h
+++ b/src/starboard/android/shared/media_decoder.h
@@ -86,6 +86,8 @@
   void WriteInputBuffer(const scoped_refptr<InputBuffer>& input_buffer);
   void WriteEndOfStream();
 
+  void SetPlaybackRate(double playback_rate);
+
   size_t GetNumberOfPendingTasks() const {
     return number_of_pending_tasks_.load();
   }
diff --git a/src/starboard/android/shared/media_is_supported.cc b/src/starboard/android/shared/media_is_supported.cc
index 26d1e19..42aa227 100644
--- a/src/starboard/android/shared/media_is_supported.cc
+++ b/src/starboard/android/shared/media_is_supported.cc
@@ -16,12 +16,19 @@
 
 #include "starboard/android/shared/jni_env_ext.h"
 #include "starboard/android/shared/media_common.h"
+#include "starboard/string.h"
 
 bool SbMediaIsSupported(SbMediaVideoCodec video_codec,
                         SbMediaAudioCodec audio_codec,
                         const char* key_system) {
   using starboard::android::shared::IsWidevineL1;
   using starboard::android::shared::JniEnvExt;
+
+  if (SbStringFindCharacter(key_system, ';')) {
+    // TODO: Remove this check and enable key system with attributes support.
+    return false;
+  }
+
   // Filter anything other then aac as we only support paid content on aac.
   // TODO: Add support of Opus if we are going to support software based drm
   // systems.
diff --git a/src/starboard/android/shared/player_components_factory.cc b/src/starboard/android/shared/player_components_factory.cc
index 948b85a..6ac984a 100644
--- a/src/starboard/android/shared/player_components_factory.cc
+++ b/src/starboard/android/shared/player_components_factory.cc
@@ -38,6 +38,15 @@
 
 namespace {
 
+const int kAudioSinkFramesAlignment = 256;
+const int kDefaultAudioSinkMinFramesPerAppend = 1024;
+const int kDefaultAudioSinkMaxCachedFrames =
+    8 * kDefaultAudioSinkMinFramesPerAppend;
+
+int AlignUp(int value, int alignment) {
+  return (value + alignment - 1) / alignment * alignment;
+}
+
 class PlayerComponentsFactory : public PlayerComponents::Factory {
   bool CreateSubComponents(
       const CreationParameters& creation_parameters,
@@ -94,6 +103,8 @@
               creation_parameters.decode_target_graphics_context_provider(),
               creation_parameters.max_video_capabilities(), error_message));
       if (video_decoder_impl->is_valid()) {
+        video_render_algorithm->reset(new android::shared::VideoRenderAlgorithm(
+            video_decoder_impl.get()));
         *video_renderer_sink = video_decoder_impl->GetSink();
         video_decoder->reset(video_decoder_impl.release());
       } else {
@@ -103,12 +114,35 @@
             "Failed to create video decoder with error: " + *error_message;
         return false;
       }
-
-      video_render_algorithm->reset(new android::shared::VideoRenderAlgorithm);
     }
 
     return true;
   }
+
+  void GetAudioRendererParams(const CreationParameters& creation_parameters,
+                              int* max_cached_frames,
+                              int* min_frames_per_append) const override {
+    SB_DCHECK(max_cached_frames);
+    SB_DCHECK(min_frames_per_append);
+    SB_DCHECK(kDefaultAudioSinkMinFramesPerAppend % kAudioSinkFramesAlignment ==
+              0);
+    *min_frames_per_append = kDefaultAudioSinkMinFramesPerAppend;
+
+    // AudioRenderer prefers to use kSbMediaAudioSampleTypeFloat32 and only uses
+    // kSbMediaAudioSampleTypeInt16Deprecated when float32 is not supported.
+    int min_frames_required = SbAudioSinkGetMinBufferSizeInFrames(
+        creation_parameters.audio_sample_info().number_of_channels,
+        SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32)
+            ? kSbMediaAudioSampleTypeFloat32
+            : kSbMediaAudioSampleTypeInt16Deprecated,
+        creation_parameters.audio_sample_info().samples_per_second);
+    // On Android 5.0, the size of audio renderer sink buffer need to be two
+    // times larger than AudioTrack minBufferSize. Otherwise, AudioTrack may
+    // stop working after pause.
+    *max_cached_frames =
+        min_frames_required * 2 + kDefaultAudioSinkMinFramesPerAppend;
+    *max_cached_frames = AlignUp(*max_cached_frames, kAudioSinkFramesAlignment);
+  }
 };
 
 }  // namespace
diff --git a/src/starboard/android/shared/player_create.cc b/src/starboard/android/shared/player_create.cc
index e2cfb48..a3a9a2a 100644
--- a/src/starboard/android/shared/player_create.cc
+++ b/src/starboard/android/shared/player_create.cc
@@ -14,7 +14,7 @@
 
 #include "starboard/player.h"
 
-#include "starboard/android/shared/cobalt/android_media_session_client.h"
+#include "starboard/android/shared/android_media_session_client.h"
 #include "starboard/android/shared/video_decoder.h"
 #include "starboard/android/shared/video_window.h"
 #include "starboard/common/log.h"
@@ -28,9 +28,8 @@
 using starboard::shared::starboard::player::filter::
     FilterBasedPlayerWorkerHandler;
 using starboard::shared::starboard::player::PlayerWorker;
-using starboard::android::shared::cobalt::kPlaying;
-using starboard::android::shared::cobalt::
-    UpdateActiveSessionPlatformPlaybackState;
+using starboard::android::shared::kPlaying;
+using starboard::android::shared::UpdateActiveSessionPlatformPlaybackState;
 using starboard::android::shared::VideoDecoder;
 
 SbPlayer SbPlayerCreate(SbWindow window,
@@ -130,7 +129,10 @@
     UpdateActiveSessionPlatformPlaybackState(kPlaying);
   }
 
-  if (creation_param->output_mode != kSbPlayerOutputModeDecodeToTexture) {
+  if (creation_param->output_mode != kSbPlayerOutputModeDecodeToTexture &&
+      // TODO: This is temporary for supporting background media playback.
+      //       Need to be removed with media refactor.
+      video_codec != kSbMediaVideoCodecNone) {
     // Check the availability of the video window. As we only support one main
     // player, and sub players are in decode to texture mode on Android, a
     // single video window should be enough.
diff --git a/src/starboard/android/shared/player_destroy.cc b/src/starboard/android/shared/player_destroy.cc
index b790729..bdfbba3 100644
--- a/src/starboard/android/shared/player_destroy.cc
+++ b/src/starboard/android/shared/player_destroy.cc
@@ -14,12 +14,11 @@
 
 #include "starboard/player.h"
 
-#include "starboard/android/shared/cobalt/android_media_session_client.h"
+#include "starboard/android/shared/android_media_session_client.h"
 #include "starboard/shared/starboard/player/player_internal.h"
 
-using starboard::android::shared::cobalt::kNone;
-using starboard::android::shared::cobalt::
-    UpdateActiveSessionPlatformPlaybackState;
+using starboard::android::shared::kNone;
+using starboard::android::shared::UpdateActiveSessionPlatformPlaybackState;
 
 void SbPlayerDestroy(SbPlayer player) {
   if (!SbPlayerIsValid(player)) {
diff --git a/src/starboard/android/shared/player_set_playback_rate.cc b/src/starboard/android/shared/player_set_playback_rate.cc
index 2a01fcd..233cd92 100644
--- a/src/starboard/android/shared/player_set_playback_rate.cc
+++ b/src/starboard/android/shared/player_set_playback_rate.cc
@@ -14,14 +14,13 @@
 
 #include "starboard/player.h"
 
-#include "starboard/android/shared/cobalt/android_media_session_client.h"
+#include "starboard/android/shared/android_media_session_client.h"
 #include "starboard/common/log.h"
 #include "starboard/shared/starboard/player/player_internal.h"
 
-using starboard::android::shared::cobalt::kPaused;
-using starboard::android::shared::cobalt::kPlaying;
-using starboard::android::shared::cobalt::
-    UpdateActiveSessionPlatformPlaybackState;
+using starboard::android::shared::kPaused;
+using starboard::android::shared::kPlaying;
+using starboard::android::shared::UpdateActiveSessionPlatformPlaybackState;
 
 bool SbPlayerSetPlaybackRate(SbPlayer player, double playback_rate) {
   if (!SbPlayerIsValid(player)) {
diff --git a/src/starboard/android/shared/starboard_platform.gypi b/src/starboard/android/shared/starboard_platform.gypi
index 2621ee4..65c5044 100644
--- a/src/starboard/android/shared/starboard_platform.gypi
+++ b/src/starboard/android/shared/starboard_platform.gypi
@@ -55,6 +55,7 @@
         'accessibility_get_text_to_speech_settings.cc',
         'accessibility_set_captions_enabled.cc',
         'android_main.cc',
+        'android_media_session_client.cc',
         'application_android.cc',
         'application_android.h',
         'atomic_public.h',
diff --git a/src/starboard/android/shared/system_get_extensions.cc b/src/starboard/android/shared/system_get_extensions.cc
index 869446b..1ffca16 100644
--- a/src/starboard/android/shared/system_get_extensions.cc
+++ b/src/starboard/android/shared/system_get_extensions.cc
@@ -15,7 +15,9 @@
 #include "starboard/system.h"
 
 #include "cobalt/extension/configuration.h"
+#include "cobalt/extension/media_session.h"
 #include "cobalt/extension/platform_service.h"
+#include "starboard/android/shared/android_media_session_client.h"
 #include "starboard/android/shared/configuration.h"
 #include "starboard/android/shared/platform_service.h"
 #include "starboard/common/log.h"
@@ -28,5 +30,8 @@
   if (SbStringCompareAll(name, kCobaltExtensionConfigurationName) == 0) {
     return starboard::android::shared::GetConfigurationApi();
   }
+  if (SbStringCompareAll(name, kCobaltExtensionMediaSessionName) == 0) {
+    return starboard::android::shared::GetMediaSessionApi();
+  }
   return NULL;
 }
diff --git a/src/starboard/android/shared/video_decoder.cc b/src/starboard/android/shared/video_decoder.cc
index 5a5030e..96a490a 100644
--- a/src/starboard/android/shared/video_decoder.cc
+++ b/src/starboard/android/shared/video_decoder.cc
@@ -377,6 +377,7 @@
       media_decoder_->Initialize(
           std::bind(&VideoDecoder::ReportError, this, _1, _2));
     }
+    media_decoder_->SetPlaybackRate(playback_rate_);
     return true;
   }
   media_decoder_.reset();
@@ -577,6 +578,13 @@
   return kSbDecodeTargetInvalid;
 }
 
+void VideoDecoder::SetPlaybackRate(double playback_rate) {
+  playback_rate_ = playback_rate;
+  if (media_decoder_) {
+    media_decoder_->SetPlaybackRate(playback_rate);
+  }
+}
+
 void VideoDecoder::OnNewTextureAvailable() {
   has_new_texture_available_.store(true);
 }
diff --git a/src/starboard/android/shared/video_decoder.h b/src/starboard/android/shared/video_decoder.h
index 8202f57..508817f 100644
--- a/src/starboard/android/shared/video_decoder.h
+++ b/src/starboard/android/shared/video_decoder.h
@@ -83,6 +83,8 @@
   void Reset() override;
   SbDecodeTarget GetCurrentDecodeTarget() override;
 
+  void SetPlaybackRate(double playback_rate);
+
   bool is_valid() const { return media_decoder_ != NULL; }
 
   void OnNewTextureAvailable();
@@ -133,6 +135,8 @@
   int32_t frame_width_ = 0;
   int32_t frame_height_ = 0;
 
+  double playback_rate_ = 1.0;
+
   // The last enqueued |SbMediaColorMetadata|.
   optional<SbMediaColorMetadata> color_metadata_;
 
diff --git a/src/starboard/android/shared/video_render_algorithm.cc b/src/starboard/android/shared/video_render_algorithm.cc
index 1b7e9aa..b234937 100644
--- a/src/starboard/android/shared/video_render_algorithm.cc
+++ b/src/starboard/android/shared/video_render_algorithm.cc
@@ -18,6 +18,7 @@
 
 #include "starboard/android/shared/jni_utils.h"
 #include "starboard/android/shared/media_common.h"
+#include "starboard/common/log.h"
 
 namespace starboard {
 namespace android {
@@ -36,6 +37,12 @@
 
 }  // namespace
 
+VideoRenderAlgorithm::VideoRenderAlgorithm(VideoDecoder* video_decoder)
+    : video_decoder_(video_decoder) {
+  SB_DCHECK(video_decoder_);
+  video_decoder_->SetPlaybackRate(playback_rate_);
+}
+
 void VideoRenderAlgorithm::Render(
     MediaTimeProvider* media_time_provider,
     std::list<scoped_refptr<VideoFrame>>* frames,
@@ -61,6 +68,10 @@
     if (!is_audio_playing) {
       break;
     }
+    if (playback_rate != playback_rate_) {
+      playback_rate_ = playback_rate;
+      video_decoder_->SetPlaybackRate(playback_rate);
+    }
 
     jlong early_us = frames->front()->timestamp() - playback_time;
 
diff --git a/src/starboard/android/shared/video_render_algorithm.h b/src/starboard/android/shared/video_render_algorithm.h
index 187a1ca..132e871 100644
--- a/src/starboard/android/shared/video_render_algorithm.h
+++ b/src/starboard/android/shared/video_render_algorithm.h
@@ -18,6 +18,7 @@
 #include <list>
 
 #include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/video_decoder.h"
 #include "starboard/shared/starboard/player/filter/video_render_algorithm.h"
 
 namespace starboard {
@@ -27,6 +28,8 @@
 class VideoRenderAlgorithm : public ::starboard::shared::starboard::player::
                                  filter::VideoRenderAlgorithm {
  public:
+  explicit VideoRenderAlgorithm(VideoDecoder* video_decoder);
+
   void Render(MediaTimeProvider* media_time_provider,
               std::list<scoped_refptr<VideoFrame>>* frames,
               VideoRendererSink::DrawFrameCB draw_frame_cb) override;
@@ -45,6 +48,8 @@
     jobject j_video_frame_release_time_helper_ = nullptr;
   };
 
+  VideoDecoder* video_decoder_ = nullptr;
+  double playback_rate_ = 1.0;
   VideoFrameReleaseTimeHelper video_frame_release_time_helper_;
   int dropped_frames_ = 0;
 };
diff --git a/src/starboard/android/shared/video_window.cc b/src/starboard/android/shared/video_window.cc
index 8329a85..88b7037 100644
--- a/src/starboard/android/shared/video_window.cc
+++ b/src/starboard/android/shared/video_window.cc
@@ -123,6 +123,11 @@
   // during painting.
   ScopedLock lock(*GetViewSurfaceMutex());
 
+  if (!g_native_video_window) {
+    SB_LOG(INFO) << "Tried to clear video window when it was null.";
+    return;
+  }
+
   if (g_reset_surface_on_clear_window) {
     int width = ANativeWindow_getWidth(g_native_video_window);
     int height = ANativeWindow_getHeight(g_native_video_window);
@@ -133,11 +138,6 @@
     }
   }
 
-  if (!g_native_video_window) {
-    SB_LOG(INFO) << "Tried to clear video window when it was null.";
-    return;
-  }
-
   EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
   eglInitialize(display, NULL, NULL);
   if (display == EGL_NO_DISPLAY) {
diff --git a/src/starboard/build/platform_configuration.py b/src/starboard/build/platform_configuration.py
index 77d8e2e..ebb054e 100644
--- a/src/starboard/build/platform_configuration.py
+++ b/src/starboard/build/platform_configuration.py
@@ -24,7 +24,6 @@
 from starboard.sabi import sabi
 from starboard.tools import ccache
 from starboard.tools import environment
-from starboard.tools import goma
 from starboard.tools import paths
 from starboard.tools import platform
 from starboard.tools.config import Config
@@ -72,12 +71,8 @@
     self._application_configuration = None
     self._application_configuration_search_path = [self._directory]
 
-    # Specifies the build accelerator to be used. Default is ccache. Goma can
-    # be used but will be deprecated.
-    if 'FORCE_GOMA' in os.environ and os.environ['FORCE_GOMA'] == 1:
-      build_accelerator = goma.Goma()
-    else:
-      build_accelerator = ccache.Ccache()
+    # Specifies the build accelerator to be used. Default is ccache.
+    build_accelerator = ccache.Ccache()
     if build_accelerator.Use():
       self.build_accelerator = build_accelerator.GetName()
       logging.info('Using %sbuild accelerator.', self.build_accelerator)
diff --git a/src/starboard/build/toolchain/gcc_toolchain.gni b/src/starboard/build/toolchain/gcc_toolchain.gni
index ef3d025..6fab1ae 100644
--- a/src/starboard/build/toolchain/gcc_toolchain.gni
+++ b/src/starboard/build/toolchain/gcc_toolchain.gni
@@ -17,7 +17,6 @@
 # limitations under the License.
 
 import("//starboard/build/toolchain/clang.gni")
-import("//starboard/build/toolchain/goma.gni")
 
 # This template defines a toolchain for something that works like gcc
 # (including clang).
@@ -111,29 +110,8 @@
       forward_variables_from(invoker_toolchain_args, "*")
     }
 
-    # When the invoker has explicitly overridden use_goma or cc_wrapper in the
-    # toolchain args, use those values, otherwise default to the global one.
-    # This works because the only reasonable override that toolchains might
-    # supply for these values are to force-disable them.
-    if (defined(toolchain_args.use_goma)) {
-      toolchain_uses_goma = toolchain_args.use_goma
-    } else {
-      toolchain_uses_goma = use_goma
-    }
-
-    # When the invoker has explicitly overridden use_goma in the
-    # toolchain args, use those values, otherwise default to the global one.
-    # This works because the only reasonable override that toolchains might
-    # supply for these values are to force-disable them.
-    if (toolchain_uses_goma) {
-      goma_path = "$goma_dir/gomacc"
-      compiler_prefix = "${goma_path} "
-    } else {
-      compiler_prefix = ""
-    }
-
-    cc = compiler_prefix + invoker.cc
-    cxx = compiler_prefix + invoker.cxx
+    cc = invoker.cc
+    cxx = invoker.cxx
     ar = invoker.ar
     ld = invoker.ld
     if (!defined(asm)) {
diff --git a/src/starboard/build/toolchain/goma.gni b/src/starboard/build/toolchain/goma.gni
deleted file mode 100644
index 84914b2..0000000
--- a/src/starboard/build/toolchain/goma.gni
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright (c) 2013 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# Modifications Copyright 2017 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Defines the configuration of Goma.
-
-# Allow ports to set an alternate default for use_goma
-import("//$starboard_path/configuration.gni")
-
-declare_args() {
-  if (!defined(use_goma)) {
-    # Set to true to enable distributed compilation using Goma. By default we
-    # use Goma for stub and linux.
-    use_goma = false
-  }
-
-  # Set the default value based on the platform.
-  if (host_os == "win" || host_os == "winrt_81" ||
-      host_os == "winrt_81_phone" || host_os == "winrt_10") {
-    # Absolute directory containing the gomacc.exe binary.
-    goma_dir = "C:\goma\goma-win64"
-  } else {
-    # Absolute directory containing the gomacc binary.
-    goma_dir = getenv("HOME") + "/goma"
-  }
-}
diff --git a/src/starboard/common/file.h b/src/starboard/common/file.h
index a91c2fa..c24cdfe 100644
--- a/src/starboard/common/file.h
+++ b/src/starboard/common/file.h
@@ -36,6 +36,74 @@
 // |preserve_root|: Whether or not the root directory should be preserved.
 bool SbFileDeleteRecursive(const char* path, bool preserve_root);
 
+// A class that opens an SbFile in its constructor and closes it in its
+// destructor, so the file is open for the lifetime of the object. Member
+// functions call the corresponding SbFile function.
+class ScopedFile {
+ public:
+  ScopedFile(const char* path,
+             int flags,
+             bool* out_created,
+             SbFileError* out_error)
+      : file_(kSbFileInvalid) {
+    file_ = SbFileOpen(path, flags, out_created, out_error);
+  }
+
+  ScopedFile(const char* path, int flags, bool* out_created)
+      : file_(kSbFileInvalid) {
+    file_ = SbFileOpen(path, flags, out_created, NULL);
+  }
+
+  ScopedFile(const char* path, int flags) : file_(kSbFileInvalid) {
+    file_ = SbFileOpen(path, flags, NULL, NULL);
+  }
+
+  ~ScopedFile() { SbFileClose(file_); }
+
+  SbFile file() const { return file_; }
+
+  bool IsValid() const { return SbFileIsValid(file_); }
+
+  int64_t Seek(SbFileWhence whence, int64_t offset) const {
+    return SbFileSeek(file_, whence, offset);
+  }
+
+  int Read(char* data, int size) const { return SbFileRead(file_, data, size); }
+
+  int ReadAll(char* data, int size) const {
+    return SbFileReadAll(file_, data, size);
+  }
+
+  int Write(const char* data, int size) const {
+    return SbFileWrite(file_, data, size);
+  }
+
+  int WriteAll(const char* data, int size) const {
+    return SbFileWriteAll(file_, data, size);
+  }
+
+  bool Truncate(int64_t length) const { return SbFileTruncate(file_, length); }
+
+  bool Flush() const { return SbFileFlush(file_); }
+
+  bool GetInfo(SbFileInfo* out_info) const {
+    return SbFileGetInfo(file_, out_info);
+  }
+
+  int64_t GetSize() const {
+    SbFileInfo file_info;
+    bool success = GetInfo(&file_info);
+    return (success ? file_info.size : -1);
+  }
+
+  // disallow copy and move operations
+  ScopedFile(const ScopedFile&) = delete;
+  ScopedFile& operator=(const ScopedFile&) = delete;
+
+ private:
+  SbFile file_;
+};
+
 }  // namespace starboard
 
 #endif  // STARBOARD_COMMON_FILE_H_
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h
index 98a80c9..e6ba5e4 100644
--- a/src/starboard/configuration.h
+++ b/src/starboard/configuration.h
@@ -71,6 +71,9 @@
 // Add Concealed state support.
 #define SB_ADD_CONCEALED_STATE_SUPPORT_VERSION 14
 
+// Iteration on UI navigation API.
+#define SB_UI_NAVIGATION2_VERSION SB_EXPERIMENTAL_API_VERSION
+
 // --- Release Candidate Feature Defines -------------------------------------
 
 // --- Common Detected Features ----------------------------------------------
diff --git a/src/starboard/doc/evergreen/symbolizing_minidumps.md b/src/starboard/doc/evergreen/symbolizing_minidumps.md
new file mode 100644
index 0000000..4931300
--- /dev/null
+++ b/src/starboard/doc/evergreen/symbolizing_minidumps.md
@@ -0,0 +1,129 @@
+# How to Symbolize Dumps
+
+Evergreen will store the minidumps (`.dmp` files) from the 2 most recent
+crashes on the disk. They are stored under `kSbSystemPathCacheDirectory` in the
+subdirectory `crashpad_database/`. These files can be used along with
+Breakpad's tools to get a full stacktrace of the past crashes. This can help in
+debugging, as these minidumps have the information for the dynamic
+`libcobalt.so` module correctly mapped, which a out-of-the-box dumper could not
+manage.
+
+## Obtaining the Tools to Symbolize Minidumps
+
+Tools for symbolizing these dumps are available through
+[Breakpad](https://chromium.googlesource.com/breakpad/breakpad/). Breakpad is
+an open source crash reporting library that we use to obtain symbol files
+(`.sym`) from unstripped binaries, and to process the symbol files with the
+minidumps to produce human-readable stacktraces.
+
+
+### Building Breakpad
+
+[Breakpad](https://chromium.googlesource.com/breakpad/breakpad/) provides
+instructions for building these tools yourself. The
+[Getting Started with Breakpad](https://chromium.googlesource.com/breakpad/breakpad/+/master/docs/getting_started_with_breakpad.md)
+guide is a good place to start if you want to go through the docs yourself, but
+below is a brief overview of how to get and build the tools.
+
+Download depot_tools:
+```
+$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
+$ export PATH=/path/to/depot_tools:$PATH
+```
+
+Get breakpad:
+```
+$ mkdir breakpad && cd breakpad
+$ fetch breakpad
+$ cd src
+```
+
+Build breakpad:
+```
+$ ./configure && make
+```
+
+This will build the processor (`src/processor/minidump_stackwalk`), and when
+building on Linux it will also build the `dump_syms` tool
+(`src/tools/linux/dump_syms/dump_syms`).
+
+**IMPORTANT:** Once you have fetched Breakpad, you should remove the path to
+depot_tools from your `$PATH` environment variable, as it can conflict with
+Cobalt's depot_tools.
+
+## Symbolizing Minidumps
+
+Now that you have all the tools you need, we can symbolize the dumps. To be
+able to symbolize Cobalt using Evergreen, you need to be get the unstripped
+`libcobalt.so` binary. These will be available as assets in GitHub releases
+[on Cobalt's public GitHub repo](https://github.com/youtube/cobalt/releases).
+
+libcobalt releases will be labeled by the Evergreen version, the architecture,
+the config, and the ELF build id, for example
+"libcobalt_1.0.10_unstripped_armeabi_softfp_qa_ac3132014007df0e.tgz". Here, we
+have:
+* Evergreen Version: 1.0.10
+* Architecture: armeabi_softfp
+* Config: qa
+* ELF Build Id: ac3132014007df0e
+
+Knowing the architecture and config you want, you'll just have to know which
+version of Evergreen you're on or obtain the build id of the library. If you
+need to obtain the ELF build id, you can do so easily by running
+`readelf -n /path/to/libcobalt.so` and look at the hash displayed after "Build
+ID:".
+
+Now you can get the debug symbols from the library using the tools we
+downloaded previously. Unpack libcobalt and dump its symbols into a file:
+
+```
+$ tar xzf /path/to/libcobalt.tgz
+$ /path/to/dump_syms /path/to/unzipped/libcobalt > libcobalt.so.sym
+$ head -n1 libcobalt.so.sym
+MODULE Linux x86_64 6462A5D44C0843D100000000000000000 libcobalt.so
+```
+
+We run `head` on the symbol file to get the debug identifier, the hash
+displayed above (in this case, it's `6462A5D44C0843D100000000000000000`). Now
+we can create the file structure that `minidump_stackwalker` expects and run
+the stackwalker against the minidump:
+
+```
+$ mkdir -p symbols/libcobalt.so/<debug identifier>/
+$ mv libcobalt.so.sym symbols/libcobalt.so/<debug identifier>/
+$ /path/to/minidump_stackwalk /path/to/your/minidump.dmp symbols/
+```
+
+`minidump_stackwalk` produces verbose output on stderr, and the stacktrace on
+stdout, so you may want to redirect stderr.
+
+### Addendum: Adding Other Symbols
+
+We can use the process above to add symbols for any library or executable you
+use, not just `libcobalt.so`. To do this, all you have to do is run the
+`dump_syms` tools on the binary you want symbolized and put that in the
+"symbols/" folder.
+
+```
+$ /path/to/dump_syms /path/to/<your-binary> > <your-binary>.sym
+$ head -n1 <your-binary.sym>
+MODULE Linux x86_64 <debug-identifier> <your-binary>
+$ mkdir -p symbols/<your-binary>/<debug-identifier>
+$ mv <your-binary>.sym symbols/<your-binary>/<debug-identifier>/
+```
+
+Now, `minidump_stackwalk` should symbolize sections within `<your-binary>`. For
+example, if you decided to symbolize the `loader_app`, it would transform the
+stacktrace output from `minidump_stackwalk` from:
+
+```
+9  loader_app + 0x3a31130
+```
+
+to:
+
+```
+9  loader_app!SbEventHandle [sandbox.cc : 44 + 0x8]
+```
+
+Note that the addresses will vary.
diff --git a/src/starboard/elf_loader/elf_loader_impl.cc b/src/starboard/elf_loader/elf_loader_impl.cc
index 3bfc632..cd7a03e 100644
--- a/src/starboard/elf_loader/elf_loader_impl.cc
+++ b/src/starboard/elf_loader/elf_loader_impl.cc
@@ -40,7 +40,7 @@
 
   elf_header_loader_.reset(new ElfHeader());
   if (!elf_header_loader_->LoadElfHeader(elf_file_.get())) {
-    SB_LOG(ERROR) << "Failed to loaded ELF header";
+    SB_LOG(ERROR) << "Failed to load ELF header";
     return false;
   }
 
diff --git a/src/starboard/elf_loader/evergreen_info.h b/src/starboard/elf_loader/evergreen_info.h
index fecd079..2adfe29 100644
--- a/src/starboard/elf_loader/evergreen_info.h
+++ b/src/starboard/elf_loader/evergreen_info.h
@@ -26,6 +26,7 @@
 // the starboard implementation.
 #define EVERGREEN_FILE_PATH_MAX_SIZE 4096
 #define EVERGREEN_BUILD_ID_MAX_SIZE 128
+#define EVERGREEN_USER_AGENT_MAX_SIZE 2048
 
 #define IS_EVERGREEN_ADDRESS(address, evergreen_info)                    \
   (evergreen_info.base_address != 0 &&                                   \
@@ -62,6 +63,12 @@
   size_t build_id_length;
 } EvergreenInfo;
 
+// Annotations that Evergreen will add to Crashpad for more detailed crash
+// reports.
+typedef struct EvergreenAnnotations {
+  char user_agent_string[EVERGREEN_USER_AGENT_MAX_SIZE];
+} EvergreenAnnotations;
+
 // Set the Evergreen information. Should be called only from the
 // elf_loader module. Passing NULL clears the currently stored
 // information.
diff --git a/src/starboard/elf_loader/sandbox.cc b/src/starboard/elf_loader/sandbox.cc
index 857ab89..9dd2f73 100644
--- a/src/starboard/elf_loader/sandbox.cc
+++ b/src/starboard/elf_loader/sandbox.cc
@@ -21,6 +21,7 @@
 #include "starboard/event.h"
 #include "starboard/mutex.h"
 #include "starboard/shared/starboard/command_line.h"
+#include "starboard/string.h"
 #include "starboard/thread_types.h"
 #include "third_party/crashpad/wrapper/wrapper.h"
 
@@ -63,6 +64,20 @@
   g_sb_event_func = reinterpret_cast<void (*)(const SbEvent*)>(
       g_elf_loader.LookupSymbol("SbEventHandle"));
 
+  auto get_user_agent_func = reinterpret_cast<const char* (*)()>(
+      g_elf_loader.LookupSymbol("GetCobaltUserAgentString"));
+  if (!get_user_agent_func) {
+    SB_LOG(ERROR) << "Failed to get user agent string";
+  } else {
+    EvergreenAnnotations cobalt_version_info;
+    SbMemorySet(&cobalt_version_info, sizeof(EvergreenAnnotations), 0);
+    SbStringCopy(cobalt_version_info.user_agent_string, get_user_agent_func(),
+                 EVERGREEN_USER_AGENT_MAX_SIZE);
+    third_party::crashpad::wrapper::AddAnnotationsToCrashpad(
+        cobalt_version_info);
+    SB_DLOG(INFO) << "Added user agent string to Crashpad.";
+  }
+
   if (!g_sb_event_func) {
     SB_LOG(ERROR) << "Failed to find SbEventHandle.";
     return;
diff --git a/src/starboard/evergreen/arm/shared/gyp_configuration.gypi b/src/starboard/evergreen/arm/shared/gyp_configuration.gypi
index 404d3a3..4192b6c 100644
--- a/src/starboard/evergreen/arm/shared/gyp_configuration.gypi
+++ b/src/starboard/evergreen/arm/shared/gyp_configuration.gypi
@@ -24,6 +24,8 @@
 
       # Force char to be signed.
       '-fsigned-char',
+      '-ffunction-sections',
+      '-fdata-sections',
     ],
 
     'linker_flags': [
diff --git a/src/starboard/evergreen/arm64/gyp_configuration.gypi b/src/starboard/evergreen/arm64/gyp_configuration.gypi
index b2a0525..f8843b2 100644
--- a/src/starboard/evergreen/arm64/gyp_configuration.gypi
+++ b/src/starboard/evergreen/arm64/gyp_configuration.gypi
@@ -22,6 +22,8 @@
 
     'compiler_flags': [
       '-isystem<(cobalt_repo_root)/third_party/musl/arch/aarch64',
+      '-ffunction-sections',
+      '-fdata-sections',
     ],
   },
 
diff --git a/src/starboard/evergreen/shared/launcher.py b/src/starboard/evergreen/shared/launcher.py
index 5cfcd55..c8a2787 100644
--- a/src/starboard/evergreen/shared/launcher.py
+++ b/src/starboard/evergreen/shared/launcher.py
@@ -24,7 +24,7 @@
 
 _BASE_STAGING_DIRECTORY = 'evergreen_staging'
 _CRASHPAD_TARGET = 'crashpad_handler'
-_LOADER_TARGET = 'elf_loader_sandbox'
+_DEFAULT_LOADER_TARGET = 'elf_loader_sandbox'
 
 
 class Launcher(abstract_launcher.AbstractLauncher):
@@ -66,6 +66,10 @@
     if not self.loader_config:
       raise ValueError('|loader_config| cannot be |None|.')
 
+    self.loader_target = kwargs.get('loader_target')
+    if not self.loader_target:
+      self.loader_target = _DEFAULT_LOADER_TARGET
+
     self.loader_out_directory = kwargs.get('loader_out_directory')
     if not self.loader_out_directory:
       self.loader_out_directory = paths.BuildOutputDirectory(
@@ -99,7 +103,7 @@
 
     self.launcher = abstract_launcher.LauncherFactory(
         self.loader_platform,
-        _LOADER_TARGET,
+        self.loader_target,
         self.loader_config,
         device_id,
         target_params=target_command_line_params,
@@ -150,7 +154,7 @@
 
     # out/evergreen_staging/linux-x64x11_devel__evergreen-x64_devel/deploy/elf_loader_sandbox
     staging_directory_loader = os.path.join(self.staging_directory, 'deploy',
-                                            _LOADER_TARGET)
+                                            self.loader_target)
 
     # out/evergreen_staging/linux-x64x11_devel__evergreen-x64_devel/deploy/elf_loader_sandbox/content/app/nplb/
     staging_directory_evergreen = os.path.join(staging_directory_loader,
@@ -161,7 +165,7 @@
     # specified by |staging_directory_loader|. A symbolic link here would cause
     # future symbolic links to fall through to the original out-directories.
     shutil.copytree(
-        os.path.join(self.loader_out_directory, 'deploy', _LOADER_TARGET),
+        os.path.join(self.loader_out_directory, 'deploy', self.loader_target),
         staging_directory_loader)
     shutil.copy(
         os.path.join(self.loader_out_directory, 'deploy', _CRASHPAD_TARGET,
@@ -174,8 +178,8 @@
     # TODO: Make the Linux launcher run from the deploy directory, no longer
     #       create these symlinks, and remove the NOTE from the docstring.
     port_symlink.MakeSymLink(
-        os.path.join(staging_directory_loader, _LOADER_TARGET),
-        os.path.join(self.staging_directory, _LOADER_TARGET))
+        os.path.join(staging_directory_loader, self.loader_target),
+        os.path.join(self.staging_directory, self.loader_target))
     port_symlink.MakeSymLink(
         os.path.join(staging_directory_loader, _CRASHPAD_TARGET),
         os.path.join(self.staging_directory, _CRASHPAD_TARGET))
diff --git a/src/starboard/evergreen/testing/linux/clean_up.sh b/src/starboard/evergreen/testing/linux/clean_up.sh
new file mode 100755
index 0000000..d454e2f
--- /dev/null
+++ b/src/starboard/evergreen/testing/linux/clean_up.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# 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.
+
+function clean_up() {
+  clear_storage
+}
+
diff --git a/src/starboard/evergreen/testing/linux/clear_storage.sh b/src/starboard/evergreen/testing/linux/clear_storage.sh
new file mode 100755
index 0000000..6f14bde
--- /dev/null
+++ b/src/starboard/evergreen/testing/linux/clear_storage.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# 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.
+
+function clear_storage() {
+  echo " Clearing Cobalt storage"
+  eval "find ${STORAGE_DIR}/ -mindepth 1 -maxdepth 1 ! -name 'icu' -exec rm -rf {} +" 1> /dev/null
+}
+
diff --git a/src/starboard/evergreen/testing/linux/create_file.sh b/src/starboard/evergreen/testing/linux/create_file.sh
new file mode 100755
index 0000000..0841fb1
--- /dev/null
+++ b/src/starboard/evergreen/testing/linux/create_file.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# 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.
+
+function create_file() {
+  if [[ $# -ne 1 ]]; then
+    error " create_file only accepts a single argument"
+    return 1
+  fi
+
+  echo " Creating file '${1}'"
+
+  eval "mkdir -p \"\"$(dirname "${1}")\"\"" 1> /dev/null
+  eval "touch \"\"${1}\"\"" 1> /dev/null
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/linux/delete_file.sh b/src/starboard/evergreen/testing/linux/delete_file.sh
new file mode 100755
index 0000000..8e8b376
--- /dev/null
+++ b/src/starboard/evergreen/testing/linux/delete_file.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+#
+# 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.
+
+function delete_file() {
+  if [[ $# -ne 1 ]]; then
+    error " delete_file only accepts a single argument"
+    return 1
+  fi
+
+  echo " Deleting file '${1}'"
+
+  eval "rm -f \"\"${1}\"\"" 1> /dev/null
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/linux/deploy_cobalt.sh b/src/starboard/evergreen/testing/linux/deploy_cobalt.sh
new file mode 100755
index 0000000..7f5bb9c
--- /dev/null
+++ b/src/starboard/evergreen/testing/linux/deploy_cobalt.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+#
+# 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.
+
+function deploy_cobalt() {
+  if [[ -z "${OUT}" ]]; then
+    info "Please set the environment variable 'OUT'"
+    exit 1
+  fi
+
+  echo " Checking '${OUT}'"
+
+  PATHS=("${OUT}/deploy/loader_app/loader_app"                          \
+         "${OUT}/deploy/loader_app/content/app/cobalt/lib/libcobalt.so" \
+         "${OUT}/deploy/loader_app/content/app/cobalt/content/")
+
+  for file in "${PATHS[@]}"; do
+    if [[ ! -e "${file}" ]]; then
+      echo " Failed to find '${file}'"
+      exit 1
+    fi
+  done
+
+  echo " Required files were found within '${OUT}'"
+
+  echo " Deploying Cobalt on local device"
+
+  echo " Generating HTML test directory"
+  eval "mkdir -p ${OUT}/deploy/loader_app/content/app/cobalt/content/web/tests/" 1> /dev/null
+
+  echo " Copying HTML test files to HTML test directory"
+  eval "cp ${1}/../tests/*.html ${OUT}/deploy/loader_app/content/app/cobalt/content/web/tests/" 1> /dev/null
+
+  clear_storage
+
+  echo " Successfully deployed!"
+}
+
diff --git a/src/starboard/evergreen/testing/linux/run_command.sh b/src/starboard/evergreen/testing/linux/run_command.sh
new file mode 100755
index 0000000..c299282
--- /dev/null
+++ b/src/starboard/evergreen/testing/linux/run_command.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# 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.
+
+function run_command() {
+  if [[ $# -ne 1 ]]; then
+    error " run_command only accepts a single argument"
+    return 1
+  fi
+
+  # We want to capture the standard output from the command.
+  echo "$(eval "${1}")"
+}
+
diff --git a/src/starboard/evergreen/testing/linux/setup.sh b/src/starboard/evergreen/testing/linux/setup.sh
new file mode 100755
index 0000000..6701ea2
--- /dev/null
+++ b/src/starboard/evergreen/testing/linux/setup.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+#
+# 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.
+
+CONTENT="${OUT}/content/app/cobalt/content"
+STORAGE_DIR="${HOME}/.cobalt_storage"
+STORAGE_DIR_TMPFS="${STORAGE_DIR}.tmpfs"
+
+ID="id"
+TAIL="tail"
+
+# Mounting a temporary filesystem cannot be done on buildbot since it requires
+# sudo. When run locally, check for the temporary filesystem and create and
+# mount it if it does not exist.
+if [[ -z "${IS_BUILDBOT}" ]] || [[ "${IS_BUILDBOT}" -eq 0 ]]; then
+  if ! grep -qs "${STORAGE_DIR_TMPFS}" "/proc/mounts"; then
+    echo " Missing tmpfs mount at ${STORAGE_DIR_TMPFS}"
+
+    if [[ ! -e "${STORAGE_DIR_TMPFS}" ]]; then
+      mkdir -p "${STORAGE_DIR_TMPFS}" 1> /dev/null
+    fi
+
+    if [[ "$(${ID} -u)" -ne 0 ]]; then
+      echo " Not root, trying to mount tmpfs with sudo at ${STORAGE_DIR_TMPFS}"
+      sudo mount -F -t tmpfs -o size=10m "cobalt_storage.tmpfs" "${STORAGE_DIR_TMPFS}" 1> /dev/null
+    else
+      echo " Mounting tmpfs as root at ${STORAGE_DIR_TMPFS}"
+      mount -F -t tmpfs -o size=10m "cobalt_storage.tmpfs" "${STORAGE_DIR_TMPFS}" 1> /dev/null
+    fi
+  fi
+fi
+
diff --git a/src/starboard/evergreen/testing/linux/start_cobalt.sh b/src/starboard/evergreen/testing/linux/start_cobalt.sh
new file mode 100755
index 0000000..99e6e1c
--- /dev/null
+++ b/src/starboard/evergreen/testing/linux/start_cobalt.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+#
+# 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.
+
+TIMEOUT=150
+
+# Runs Cobalt on the desired platform.
+#
+# Globals:
+#   CONTENT
+#   LOG_PATH
+#   TIMEOUT
+#
+# Args:
+#   URL, path for logging, pattern to search logs for, extra arguments.
+#
+# Returns:
+#   0 if the provided pattern was found in the logs, otherwise 1.
+function start_cobalt() {
+  if [[ $# -lt 3 ]]; then
+    error " start_cobalt missing args"
+    return 1
+  fi
+
+  URL="${1}"
+  LOG="${2}"
+  PAT="${3}"
+  ARGS="${4}"
+
+  stop_cobalt
+
+  echo " Starting Cobalt with:"
+  echo "  --url=${URL}"
+  echo "  --content=${CONTENT}"
+
+  for arg in $ARGS; do
+    echo "  ${arg}"
+  done
+
+  echo " Logs will be output to '${LOG_PATH}/${LOG}'"
+
+  eval "${OUT}/loader_app --url=\"\"${URL}\"\" --content=${CONTENT} ${ARGS} 2>&1 | tee \"${LOG_PATH}/${LOG}\"" &
+
+  LOADER=$!
+
+  echo " Cobalt process ID is ${LOADER}"
+
+  echo " Waiting up to ${TIMEOUT} seconds for \"${PAT}\" to be logged"
+
+  wait_and_watch "${PAT}" "${LOG_PATH}/${LOG}"
+
+  FOUND=$?
+
+  echo " Finished after ${WAITED} seconds"
+
+  echo " Stopping with 'kill -9 ${LOADER}'"
+
+  kill -9 "${LOADER}" 1> /dev/null
+
+  sleep 1
+
+  if [[ "${FOUND}" -eq 1 ]]; then
+    return 0
+  fi
+
+  return 1
+}
+
diff --git a/src/starboard/evergreen/testing/linux/stop_cobalt.sh b/src/starboard/evergreen/testing/linux/stop_cobalt.sh
new file mode 100755
index 0000000..0028883
--- /dev/null
+++ b/src/starboard/evergreen/testing/linux/stop_cobalt.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# 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.
+
+function stop_cobalt() {
+  echo " Stopping Cobalt"
+  eval "kill -9 $(pidof "${OUT}/loader_app")" 1> /dev/null
+  sleep 1
+}
+
diff --git a/src/starboard/evergreen/testing/pprint.sh b/src/starboard/evergreen/testing/pprint.sh
new file mode 100755
index 0000000..c44ce31
--- /dev/null
+++ b/src/starboard/evergreen/testing/pprint.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# 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.
+#
+# A set of helper functions to output colored text using ANSI escape codes.
+
+function pprint() {
+  local arg="-e"
+  if [[ "${OSTYPE}" = "*darwin*" ]]; then
+    arg=""
+  fi
+  # This command uses ANSI escape codes to attempt to output colored text. For
+  # more information see https://en.wikipedia.org/wiki/ANSI_escape_code.
+  echo "$arg" "\033[0;${1}m${2}\033[0m" >&2
+}
+
+function info() {
+  pprint 32 "${1}"
+}
+
+function warn() {
+  pprint 33 "${1}"
+}
+
+function error() {
+  pprint 31 "${1}"
+}
+
diff --git a/src/starboard/evergreen/testing/raspi/clean_up.sh b/src/starboard/evergreen/testing/raspi/clean_up.sh
new file mode 100755
index 0000000..50d3792
--- /dev/null
+++ b/src/starboard/evergreen/testing/raspi/clean_up.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# 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.
+
+source $1/../linux/clean_up.sh
+
diff --git a/src/starboard/evergreen/testing/raspi/clear_storage.sh b/src/starboard/evergreen/testing/raspi/clear_storage.sh
new file mode 100755
index 0000000..4ddaa4f
--- /dev/null
+++ b/src/starboard/evergreen/testing/raspi/clear_storage.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# 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.
+
+function clear_storage() {
+  echo " Clearing Cobalt storage"
+  eval "${SSH}\"find ${STORAGE_DIR}/ -mindepth 1 -maxdepth 1 ! -name 'icu' -exec rm -rf {} +\"" 1> /dev/null
+}
+
diff --git a/src/starboard/evergreen/testing/raspi/create_file.sh b/src/starboard/evergreen/testing/raspi/create_file.sh
new file mode 100755
index 0000000..ec054a7
--- /dev/null
+++ b/src/starboard/evergreen/testing/raspi/create_file.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# 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.
+
+function create_file() {
+  if [[ $# -ne 1 ]]; then
+    error " create_file only accepts a single argument"
+    return 1
+  fi
+
+  echo " Creating file '${1}'"
+
+  eval "${SSH}\"mkdir -p \"$(dirname "${1}")\"\"" 1> /dev/null
+  eval "${SSH}\"touch \"${1}\"\"" 1> /dev/null
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/raspi/delete_file.sh b/src/starboard/evergreen/testing/raspi/delete_file.sh
new file mode 100755
index 0000000..017d5b0
--- /dev/null
+++ b/src/starboard/evergreen/testing/raspi/delete_file.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+#
+# 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.
+
+function delete_file() {
+  if [[ $# -ne 1 ]]; then
+    error " delete_file only accepts a single argument"
+    return 1
+  fi
+
+  echo " Deleting file '${1}'"
+
+  eval "${SSH}\"rm -f \"${1}\"\"" 1> /dev/null
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/raspi/deploy_cobalt.sh b/src/starboard/evergreen/testing/raspi/deploy_cobalt.sh
new file mode 100755
index 0000000..36da43e
--- /dev/null
+++ b/src/starboard/evergreen/testing/raspi/deploy_cobalt.sh
@@ -0,0 +1,64 @@
+#!/bin/bash
+#
+# 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.
+
+function deploy_cobalt() {
+  if [[ -z "${OUT}" ]]; then
+    info "Please set the environment variable 'OUT'"
+    exit 1
+  fi
+
+  echo " Checking '${OUT}'"
+
+  PATHS=("${OUT}/deploy/loader_app/loader_app"                          \
+         "${OUT}/deploy/loader_app/content/app/cobalt/lib/libcobalt.so" \
+         "${OUT}/deploy/loader_app/content/app/cobalt/content/")
+
+  for file in "${PATHS[@]}"; do
+    if [[ ! -e "${file}" ]]; then
+      echo " Failed to find '${file}'"
+      exit 1
+    fi
+  done
+
+  echo " Required files were found within '${OUT}'"
+
+  echo " Deploying to the Raspberry Pi 2 at ${RASPI_ADDR}"
+
+  echo " Regenerating Cobalt-on-Evergreen directory"
+  eval "${SSH} rm -rf /home/pi/coeg/" 1> /dev/null
+  eval "${SSH} mkdir /home/pi/coeg/" 1> /dev/null
+
+  echo " Copying loader_app to Cobalt-on-Evergreen directory"
+  eval "${SCP} ${OUT}/deploy/loader_app/loader_app pi@${RASPI_ADDR}:/home/pi/coeg/" 1> /dev/null
+
+  echo " Regenerating system image directory"
+  eval "${SSH} mkdir -p /home/pi/coeg/content/app/cobalt/lib" 1> /dev/null
+
+  echo " Copying cobalt to system image directory"
+  eval "${SCP} ${OUT}/deploy/loader_app/content/app/cobalt/lib/libcobalt.so pi@${RASPI_ADDR}:/home/pi/coeg/content/app/cobalt/lib/" 1> /dev/null
+
+  echo " Copying content to system image directory"
+  eval "${SCP} -r ${OUT}/deploy/loader_app/content/app/cobalt/content/ pi@${RASPI_ADDR}:/home/pi/coeg/content/app/cobalt/" 1> /dev/null
+
+  echo " Generating HTML test directory"
+  eval "${SSH} mkdir -p /home/pi/coeg/content/app/cobalt/content/web/tests/" 1> /dev/null
+
+  echo " Copying HTML test files to HTML test directory"
+  eval "${SCP} ${1}/../tests/*.html pi@${RASPI_ADDR}:/home/pi/coeg/content/app/cobalt/content/web/tests/" 1> /dev/null
+
+  echo " Successfully deployed!"
+}
+
diff --git a/src/starboard/evergreen/testing/raspi/run_command.sh b/src/starboard/evergreen/testing/raspi/run_command.sh
new file mode 100755
index 0000000..7a92b7e
--- /dev/null
+++ b/src/starboard/evergreen/testing/raspi/run_command.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# 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.
+
+function run_command() {
+  if [[ $# -ne 1 ]]; then
+    error " run_command only accepts a single argument"
+    return 1
+  fi
+
+  # We want to capture the standard output from the command.
+  echo "$(eval "${SSH}\"${1}\"")"
+}
+
diff --git a/src/starboard/evergreen/testing/raspi/setup.sh b/src/starboard/evergreen/testing/raspi/setup.sh
new file mode 100755
index 0000000..ab815ef
--- /dev/null
+++ b/src/starboard/evergreen/testing/raspi/setup.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+#
+# 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.
+
+source $1/../pprint.sh
+source $1/run_command.sh
+
+CONTENT="/home/pi/coeg/content/app/cobalt/content"
+STORAGE_DIR="/home/pi/.cobalt_storage"
+STORAGE_DIR_TMPFS="${STORAGE_DIR}.tmpfs"
+
+ID="id"
+TAIL="tail"
+
+if [[ -z "${RASPI_ADDR}" ]]; then
+  info " Please set the environment variable 'RASPI_ADDR'"
+  exit 1
+fi
+
+KEYPATH="${HOME}/.ssh/raspi"
+
+if [[ ! -f "${KEYPATH}" ]]; then
+  ssh-keygen -t rsa -q -f "${KEYPATH}" -N "" 1> /dev/null
+  ssh-copy-id -i "${KEYPATH}.pub" pi@"${RASPI_ADDR}" 1> /dev/null
+  echo " Generated SSH key-pair for Raspberry Pi 2 at ${KEYPATH}"
+else
+  echo " Re-using existing SSH key-pair for Raspberry Pi 2 at ${KEYPATH}"
+fi
+
+SSH="ssh -i ${KEYPATH} pi@${RASPI_ADDR} "
+SCP="scp -i ${KEYPATH} "
+
+echo " Targeting the Raspberry Pi 2 at ${RASPI_ADDR}"
+
+# Mounting a temporary filesystem cannot be done on buildbot since it requires
+# sudo. When run locally, check for the temporary filesystem and create and
+# mount it if it does not exist.
+if [[ -z "${IS_BUILDBOT}" ]] || [[ "${IS_BUILDBOT}" -eq 0 ]]; then
+  eval "${SSH}\"grep -qs \"${STORAGE_DIR_TMPFS}\" \"/proc/mounts\"\"" 1> /dev/null
+
+  if [[ $? -ne 0 ]]; then
+    if [[ ! -e "${STORAGE_DIR_TMPFS}" ]]; then
+      run_command "mkdir -p \"${STORAGE_DIR_TMPFS}\"" 1> /dev/null
+    fi
+
+    if [[ "$(${SSH} "${ID} -u")" -ne 0 ]]; then
+      echo " Not root, trying to mount tmpfs with sudo at ${STORAGE_DIR_TMPFS}"
+      run_command "sudo mount -F -t tmpfs -o size=10m \"cobalt_storage.tmpfs\" \"${STORAGE_DIR_TMPFS}\"" 1> /dev/null
+    else
+      echo " Mounting tmpfs as root at ${STORAGE_DIR_TMPFS}"
+      run_command "mount -F -t tmpfs -o size=10m \"cobalt_storage.tmpfs\" \"${STORAGE_DIR_TMPFS}\"" 1> /dev/null
+    fi
+  fi
+fi
+
diff --git a/src/starboard/evergreen/testing/raspi/start_cobalt.sh b/src/starboard/evergreen/testing/raspi/start_cobalt.sh
new file mode 100755
index 0000000..d1b879c
--- /dev/null
+++ b/src/starboard/evergreen/testing/raspi/start_cobalt.sh
@@ -0,0 +1,78 @@
+#!/bin/bash
+#
+# 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.
+
+TIMEOUT=150
+
+# Runs Cobalt on the desired platform.
+#
+# Globals:
+#   CONTENT
+#   LOG_PATH
+#   TIMEOUT
+#
+# Args:
+#   URL, path for logging, pattern to search logs for, extra arguments.
+#
+# Returns:
+#   0 if the provided pattern was found in the logs, otherwise 1.
+function start_cobalt() {
+  if [[ $# -lt 3 ]]; then
+    error " start_cobalt missing args"
+    return 1
+  fi
+
+  URL="${1}"
+  LOG="${2}"
+  PAT="${3}"
+  ARGS="${4}"
+
+  stop_cobalt
+
+  echo " Starting Cobalt with:"
+  echo "  --url=${URL}"
+  echo "  --content=${CONTENT}"
+
+  for arg in $ARGS; do
+    echo "  ${arg}"
+  done
+
+  echo " Logs will be output to '${LOG_PATH}/${LOG}'"
+
+  eval "${SSH}\"/home/pi/coeg/loader_app --url=\"\"${URL}\"\" --content=${CONTENT} ${ARGS} \" 2>&1 | tee \"${LOG_PATH}/${LOG}\"" &
+
+  LOADER=$!
+
+  echo " Cobalt process ID is ${LOADER}"
+  echo " Waiting up to ${TIMEOUT} seconds for \"${PAT}\" to be logged"
+
+  wait_and_watch "${PAT}" "${LOG_PATH}/${LOG}"
+
+  FOUND=$?
+
+  echo " Finished after ${WAITED} seconds"
+  echo " Stopping with 'kill -9 ${LOADER}'"
+
+  kill -9 "${LOADER}" &> /dev/null
+
+  sleep 1
+
+  if [[ "${FOUND}" -eq 1 ]]; then
+    return 0
+  fi
+
+  return 1
+}
+
diff --git a/src/starboard/evergreen/testing/raspi/stop_cobalt.sh b/src/starboard/evergreen/testing/raspi/stop_cobalt.sh
new file mode 100755
index 0000000..ffd5bac
--- /dev/null
+++ b/src/starboard/evergreen/testing/raspi/stop_cobalt.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# 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.
+
+function stop_cobalt() {
+  echo " Stopping Cobalt"
+  eval "${SSH}\"pidof /home/pi/coeg/loader_app | xargs kill -9\"" 1> /dev/null
+  sleep 1
+}
+
diff --git a/src/starboard/evergreen/testing/run_all_tests.sh b/src/starboard/evergreen/testing/run_all_tests.sh
new file mode 100755
index 0000000..d0602fc
--- /dev/null
+++ b/src/starboard/evergreen/testing/run_all_tests.sh
@@ -0,0 +1,112 @@
+#!/bin/bash
+#
+# 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.
+#
+# Driver script that sets up the testing environment, collects all of the tests
+# to run, runs them, and outputs the results.
+
+DIR="$(dirname "${0}")"
+
+if [[ ! -f "${DIR}/setup.sh" ]]; then
+  echo "The script 'setup.sh' is required"
+  exit 1
+fi
+
+source $DIR/setup.sh
+
+# Find all of the test files within the 'test' subdirectory.
+TESTS=($(eval "find ${DIR}/tests -maxdepth 1 -name '*_test.sh'"))
+
+COUNT=0
+FAILED=()
+PASSED=()
+SKIPPED=()
+
+info " [==========] Deploying Cobalt."
+
+deploy_cobalt "${DIR}/${1}"
+
+info " [==========] Running ${#TESTS[@]} tests."
+
+# Loop over all of the tests found in the current directory and run them.
+for test in "${TESTS[@]}"; do
+  source $test "${DIR}/${1}"
+
+  info " [ RUN      ] ${TEST_NAME}"
+
+  run_test
+
+  RESULT=$?
+
+  if [[ "${RESULT}" -eq 0 ]]; then
+    info  " [   PASSED ] ${TEST_NAME}"
+    PASSED+=("${TEST_NAME}")
+  elif [[ "${RESULT}" -eq 1 ]]; then
+    error " [   FAILED ] ${TEST_NAME}"
+    FAILED+=("$TEST_NAME")
+  elif [[ "${RESULT}" -eq 2 ]]; then
+    warn " [  SKIPPED ] ${TEST_NAME}"
+    SKIPPED+=("$TEST_NAME")
+  fi
+
+  stop_cobalt &> /dev/null
+
+  COUNT=$((COUNT + 1))
+done
+
+info " [==========] ${COUNT} tests ran."
+
+# Output the number of passed tests.
+if [[ "${#PASSED[@]}" -eq 1 ]]; then
+  info " [  PASSED  ] 1 test."
+elif [[ "${#PASSED[@]}" -gt 1 ]]; then
+  info " [  PASSED  ] ${#PASSED[@]} tests."
+fi
+
+# Output the number of skipped tests.
+if [[ "${#SKIPPED[@]}" -eq 1 ]]; then
+  warn " [  SKIPPED ] 1 test, listed below:"
+elif [[ "${#SKIPPED[@]}" -gt 1 ]]; then
+  warn " [  SKIPPED ] ${#SKIPPED[@]} tests, listed below:"
+fi
+
+# Output each of the skipped tests.
+for test in "${SKIPPED[@]}"; do
+  warn " [  SKIPPED ] ${test}"
+done
+
+# Output the number of failed tests.
+if [[ "${#FAILED[@]}" -eq 1 ]]; then
+  error " [  FAILED  ] 1 test, listed below:"
+elif [[ "${#FAILED[@]}" -gt 1 ]]; then
+  error " [  FAILED  ] ${#FAILED[@]} tests, listed below:"
+fi
+
+# Output each of the failed tests.
+for test in "${FAILED[@]}"; do
+  error " [  FAILED  ] ${test}"
+done
+
+info " [==========] Cleaning up."
+
+clean_up
+
+info " [==========] Finished."
+
+if [[ "${#FAILED[@]}" -eq 0 ]]; then
+  exit 0
+fi
+
+exit 1
diff --git a/src/starboard/evergreen/testing/setup.sh b/src/starboard/evergreen/testing/setup.sh
new file mode 100755
index 0000000..1546b28
--- /dev/null
+++ b/src/starboard/evergreen/testing/setup.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+#
+# 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.
+
+if [[ ! -f "${DIR}/pprint.sh" ]]; then
+  echo "The script 'pprint.sh' is required"
+  exit 1
+fi
+
+source $DIR/pprint.sh
+
+info " [==========] Preparing Cobalt."
+
+if [[ $# -ne 1 ]]; then
+  error "A platform must be provided"
+  exit 1
+fi
+
+PLATFORMS=("linux" "raspi")
+
+if [[ ! "${PLATFORMS[@]}" =~ "${1}" ]] && [[ ! -d "${DIR}/${1}" ]]; then
+  error "The platform provided must be one of the following: " "${PLATFORMS[@]}"
+  exit 1
+fi
+
+# List of all required scripts.
+SCRIPTS=("${DIR}/shared/app_key.sh"           \
+         "${DIR}/shared/drain_file.sh"        \
+         "${DIR}/shared/installation_slot.sh" \
+         "${DIR}/shared/wait_and_watch.sh"    \
+
+         # Each of the following scripts must be provided for the targeted
+         # platform. The script 'setup.sh' must be source'd first.
+         "${DIR}/${1}/setup.sh"               \
+
+         "${DIR}/${1}/clean_up.sh"            \
+         "${DIR}/${1}/clear_storage.sh"       \
+         "${DIR}/${1}/create_file.sh"         \
+         "${DIR}/${1}/delete_file.sh"         \
+         "${DIR}/${1}/deploy_cobalt.sh"       \
+         "${DIR}/${1}/run_command.sh"         \
+         "${DIR}/${1}/start_cobalt.sh"        \
+         "${DIR}/${1}/stop_cobalt.sh")
+
+for script in "${SCRIPTS[@]}"; do
+  if [[ ! -f "${script}" ]]; then
+    error "The script '${script}' is required"
+    exit 1
+  fi
+
+  source $script "${DIR}/${1}"
+done
+
+if [[ ! -d "/tmp/" ]]; then
+  error "The '/tmp/' directory is missing"
+  exit 1
+fi
+
+# A path in the temporary directory to write test log files to.
+LOG_PATH="/tmp/youtube_test_logs/$(date +%s%3N)"
+
+mkdir -p "${LOG_PATH}" &> /dev/null
+
+if [[ ! -d "${LOG_PATH}" ]]; then
+  error "Failed to create directory at '${LOG_PATH}'"
+  exit 1
+fi
+
diff --git a/src/starboard/evergreen/testing/shared/app_key.sh b/src/starboard/evergreen/testing/shared/app_key.sh
new file mode 100755
index 0000000..506fe5d
--- /dev/null
+++ b/src/starboard/evergreen/testing/shared/app_key.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# 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.
+
+function get_bad_app_key_file_path() {
+  if [[ $# -ne 1 ]]; then
+    error " get_bad_app_key_file_path only accepts a single argument"
+    return 1
+  fi
+
+  # Warning: do not wrap '$TAIL' with double quotes or else it will not actually
+  # resolve to the correct command.
+  echo "$( \
+    sed -n "s/.*bad_app_key_file_path: \(.*app_key_[A-Za-z0-9+/=]\{32,\}\.bad\).*/\1/p" \
+      "${LOG_PATH}/${1}" | ${TAIL} -1)"
+}
+
diff --git a/src/starboard/evergreen/testing/shared/drain_file.sh b/src/starboard/evergreen/testing/shared/drain_file.sh
new file mode 100755
index 0000000..2e66aa1
--- /dev/null
+++ b/src/starboard/evergreen/testing/shared/drain_file.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# 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.
+
+function get_temporary_drain_file_path() {
+  if [[ $# -ne 1 ]]; then
+    error " get_temporary_drain_file_path only accepts a single argument"
+    return 1
+  fi
+
+  # Warning: do not wrap '$TAIL' with double quotes or else it will not actually
+  # resolve to the correct command.
+  echo "$( \
+    sed -n "s/.*Created drain file at '\(.*d_\)[A-Za-z0-9+/=]\{32,\}\(_[0-9]\{8,\}\).*/\1TEST\2/p" \
+      "${LOG_PATH}/${1}" | ${TAIL} -1)"
+}
+
diff --git a/src/starboard/evergreen/testing/shared/installation_slot.sh b/src/starboard/evergreen/testing/shared/installation_slot.sh
new file mode 100755
index 0000000..0258be3
--- /dev/null
+++ b/src/starboard/evergreen/testing/shared/installation_slot.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+#
+# 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.
+
+function get_current_installation_slot() {
+  if [[ $# -ne 1 ]]; then
+    error " get_current_installation_slot only accepts a single argument"
+    return 1
+  fi
+
+  # Warning: do not wrap '$TAIL' with double quotes or else it will not actually
+  # resolve to the correct command.
+  echo "$( \
+    sed -n "s/.*current_installation=\([0-9]\+\).*/\1/p" \
+      "${LOG_PATH}/${1}" | ${TAIL} -1)"
+}
+
diff --git a/src/starboard/evergreen/testing/shared/wait_and_watch.sh b/src/starboard/evergreen/testing/shared/wait_and_watch.sh
new file mode 100644
index 0000000..f7c39da
--- /dev/null
+++ b/src/starboard/evergreen/testing/shared/wait_and_watch.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+#
+# 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.
+
+function wait_and_watch() {
+  if [[ $# -ne 2 ]]; then
+    error " wait_and_watch requires a pattern and a path"
+    return 1
+  fi
+
+  WAITED=0
+
+  while [[ "${WAITED}" -lt "${TIMEOUT}" ]]; do
+    if grep -Eq "${1}" "${2}"; then
+      return 1
+    fi
+
+    WAITED=$((WAITED + 1))
+    sleep 1
+  done
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/abort_update_if_already_updating_test.sh b/src/starboard/evergreen/testing/tests/abort_update_if_already_updating_test.sh
new file mode 100755
index 0000000..90f8c1f
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/abort_update_if_already_updating_test.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="AbortUpdateIfAlreadyUpdating"
+TEST_FILE="test.html"
+
+function run_test() {
+  clear_storage
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "Created drain file"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to create a drain file for the test package"
+    return 1
+  fi
+
+  FILENAME="$(get_temporary_drain_file_path "${TEST_NAME}.0.log")"
+
+  clear_storage
+
+  create_file "${FILENAME}"
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.1.log" "bailing out"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to find 'bailing out' in logs"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/alternative_content_test.sh b/src/starboard/evergreen/testing/tests/alternative_content_test.sh
new file mode 100755
index 0000000..00d6eca
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/alternative_content_test.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="AlternativeContent"
+TEST_FILE="test.html"
+
+function run_test() {
+  clear_storage
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "update from test channel installed"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to download and install the test package"
+    return 1
+  fi
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.1.log" "App is up to date" "--content=${CONTENT}"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to find 'App is up to date' indicating failure"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/continuous_updates_test.sh b/src/starboard/evergreen/testing/tests/continuous_updates_test.sh
new file mode 100755
index 0000000..f82d212
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/continuous_updates_test.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="ContinuousUpdates"
+TEST_FILE="tseries.html"
+
+function run_test() {
+  clear_storage
+
+  OLD_TIMEOUT="${TIMEOUT}"
+  TIMEOUT=300
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "continuous updates without restart working"
+
+  TIMEOUT="${OLD_TIMEOUT}"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to validate alternating channels"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/crashing_binary_test.sh b/src/starboard/evergreen/testing/tests/crashing_binary_test.sh
new file mode 100755
index 0000000..040538d
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/crashing_binary_test.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="CrashingBinary"
+TEST_FILE="tcrash.html"
+
+function run_test() {
+  clear_storage
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "update from tcrash channel installed"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to download and install the tcrash package"
+    return 1
+  fi
+
+  for i in {1..3}; do
+    start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.${i}.log" "Caught signal: SIG[A-Z]{4}"
+
+    if [[ $? -ne 0 ]]; then
+      error "Binary did not crash on attempt #${i}"
+      return 1
+    fi
+  done
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.4.log" "App is up to date"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to revert to working installation after crashing 3 times"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/disabled_updater_test.sh b/src/starboard/evergreen/testing/tests/disabled_updater_test.sh
new file mode 100755
index 0000000..985f110
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/disabled_updater_test.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="DisabledUpdater"
+TEST_FILE="test.html"
+
+function run_test() {
+  clear_storage
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "update from test channel installed"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to download and install the test package"
+    return 1
+  fi
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.1.log" "App is up to date"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to run downloaded installation"
+    return 1
+  fi
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.2.log" "disable_updates=1" "--disable_updates"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to run system installation"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/empty.html b/src/starboard/evergreen/testing/tests/empty.html
new file mode 100644
index 0000000..b8dd5a8
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/empty.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<html>
+<meta charset="utf-8">
+<title>empty</title>
+</html>
diff --git a/src/starboard/evergreen/testing/tests/load_slot_being_updated_test.sh b/src/starboard/evergreen/testing/tests/load_slot_being_updated_test.sh
new file mode 100755
index 0000000..f61736e
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/load_slot_being_updated_test.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="LoadSlotBeingUpdated"
+TEST_FILE="test.html"
+
+function run_test() {
+  clear_storage
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "update from test channel installed"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to download and install the test package"
+    return 1
+  fi
+
+  FILENAME=$(get_temporary_drain_file_path "${TEST_NAME}.0.log")
+
+  create_file "${FILENAME}"
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.1.log" "Active slot draining"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to abort loading a slot being drained"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/mismatched_architecture_test.sh b/src/starboard/evergreen/testing/tests/mismatched_architecture_test.sh
new file mode 100755
index 0000000..0f8a427
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/mismatched_architecture_test.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="MismatchedArchitecture"
+TEST_FILE="tmsabi.html"
+
+function run_test() {
+  clear_storage
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "update from tmsabi channel installed"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to download and install the tmsabi package"
+    return 1
+  fi
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.1.log" "Failed to load ELF header"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to recognize architecture mismatch"
+    return 1
+  fi
+
+  if ! grep -Eq "RevertBack current_installation=[0-9]+" "${LOG_PATH}/${TEST_NAME}.1.log"; then
+    error "Failed to revert after mismatched download"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/noop_binary_test.sh b/src/starboard/evergreen/testing/tests/noop_binary_test.sh
new file mode 100755
index 0000000..30835a6
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/noop_binary_test.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="NoOpBinary"
+TEST_FILE="tnoop.html"
+
+function run_test() {
+  clear_storage
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "update from tnoop channel installed"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to download and install the tnoop package"
+    return 1
+  fi
+
+  for i in {1..3}; do
+    start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.${i}.log" "Load start=0x[a-f0-9]{8,} base_memory_address=0x[a-f0-9]{8,}"
+
+    if [[ $? -ne 0 ]]; then
+      error "Failed to load binary"
+      return 1
+    fi
+
+    if grep -Eq "Starting application." "${LOG_PATH}/${TEST_NAME}.${i}.log"; then
+      error "Failed to run no-op binary"
+      return 1
+    fi
+  done
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.4.log" "App is up to date"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to revert to working installation after no-oping 3 times"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/out_of_storage_test.sh b/src/starboard/evergreen/testing/tests/out_of_storage_test.sh
new file mode 100755
index 0000000..a970be8
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/out_of_storage_test.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="OutOfStorage"
+TEST_FILE="test.html"
+
+function run_test() {
+  if [[ ! -z "${IS_BUILDBOT}" ]] && [[ "${IS_BUILDBOT}" -eq 1 ]]; then
+    echo " Cannot mount temporary filesystems on builbot, skipping"
+    return 2
+  fi
+
+  clear_storage
+
+  run_command "ln -s \"${STORAGE_DIR_TMPFS}\" \"${STORAGE_DIR}\"" 1> /dev/null
+
+  # We need to explicitly clear the "storage", i.e. the temporary filesystem,
+  # since previous runs might have artifacts leftover.
+  run_command "rm -rf ${STORAGE_DIR}/*" 1> /dev/null
+
+  OLD_TIMEOUT="${TIMEOUT}"
+  TIMEOUT=300
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "Failed to update, error code is 12"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to run out of storage"
+    return 1
+  fi
+
+  TIMEOUT="${OLD_TIMEOUT}"
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/quick_roll_forward_test.sh b/src/starboard/evergreen/testing/tests/quick_roll_forward_test.sh
new file mode 100755
index 0000000..4e15e72
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/quick_roll_forward_test.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="QuickRollForward"
+TEST_FILE="test.html"
+
+function run_test() {
+  clear_storage
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "update from test channel installed"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to download and install the test package"
+    return 1
+  fi
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.1.log" "App is up to date"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to run downloaded installation"
+    return 1
+  fi
+
+  start_cobalt "file:///tests/empty.html" "${TEST_NAME}.2.log" "quick update succeeded"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to adopt downloaded installation on different app"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/racing_updaters_test.sh b/src/starboard/evergreen/testing/tests/racing_updaters_test.sh
new file mode 100755
index 0000000..087b5cd
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/racing_updaters_test.sh
@@ -0,0 +1,69 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="RacingUpdaters"
+TEST_FILE="test.html"
+
+function wait_and_force_race_condition() {
+  if [[ $# -ne 3 ]]; then
+    error " wait_and_force_race_condition requires a pattern, a path and a filename"
+    return 1
+  fi
+
+  # The previous logs are available when this is invoked. Wait before beginning
+  # to check the logs.
+  sleep 15
+
+  wait_and_watch "${1}" "${2}"
+
+  FOUND=$?
+
+  if [[ "${FOUND}" -eq 1 ]]; then
+    create_file "${3}"
+  fi
+}
+
+function run_test() {
+  clear_storage
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "Created drain file"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to create a drain file for the test package"
+    return 1
+  fi
+
+  FILENAME="$(get_temporary_drain_file_path "${TEST_NAME}.0.log")"
+
+  clear_storage
+
+  wait_and_force_race_condition "Created drain file" "${LOG_PATH}/${TEST_NAME}.1.log" "${FILENAME}" &
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.1.log" "failed to lock slot"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to recognize another update is updating slot"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/tcrash.html b/src/starboard/evergreen/testing/tests/tcrash.html
new file mode 100644
index 0000000..e05af63
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/tcrash.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<html>
+<meta charset="utf-8">
+<title>tcrash</title>
+<script>
+  var changeChannel = setInterval(tryChangeChannel, 500);
+
+  function tryChangeChannel() {
+    const channel = window.h5vcc.updater.getUpdaterChannel();
+    const status = window.h5vcc.updater.getUpdateStatus();
+
+    if (channel == "") {
+      return;
+    }
+
+    if (channel == "qa") {
+      if ((status == "App is up to date") || (status == "Update installed, pending restart")) {
+        window.h5vcc.updater.setUpdaterChannel("tcrash");
+        console.log("channel was changed to tcrash");
+        return;
+      }
+    }
+
+    if ((channel == "tcrash") && (status == "Update installed, pending restart")) {
+      console.log("update from tcrash channel installed");
+      clearInterval(changeChannel);
+    }
+  }
+</script>
+</html>
diff --git a/src/starboard/evergreen/testing/tests/test.html b/src/starboard/evergreen/testing/tests/test.html
new file mode 100644
index 0000000..f404546
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/test.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<html>
+<meta charset="utf-8">
+<title>test</title>
+<script>
+  var changeChannel = setInterval(tryChangeChannel, 500);
+
+  function tryChangeChannel() {
+    const channel = window.h5vcc.updater.getUpdaterChannel();
+    const status = window.h5vcc.updater.getUpdateStatus();
+
+    if (channel == "") {
+      return;
+    }
+
+    if (channel == "qa") {
+      if ((status == "App is up to date") || (status == "Update installed, pending restart")) {
+        window.h5vcc.updater.setUpdaterChannel("test");
+        console.log("channel was changed to test");
+        return;
+      }
+    }
+
+    if ((channel == "test") && (status == "Update installed, pending restart")) {
+      console.log("update from test channel installed");
+      clearInterval(changeChannel);
+    }
+  }
+</script>
+</html>
diff --git a/src/starboard/evergreen/testing/tests/tfailv.html b/src/starboard/evergreen/testing/tests/tfailv.html
new file mode 100644
index 0000000..c039830
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/tfailv.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<html>
+<meta charset="utf-8">
+<title>tfailv</title>
+<script>
+  var changeChannel = setInterval(tryChangeChannel, 500);
+
+  function tryChangeChannel() {
+    const channel = window.h5vcc.updater.getUpdaterChannel();
+    const status = window.h5vcc.updater.getUpdateStatus();
+
+    if (channel == "") {
+      return;
+    }
+
+    if (channel == "qa") {
+      if ((status == "App is up to date") || (status == "Update installed, pending restart")) {
+        window.h5vcc.updater.setUpdaterChannel("tfailv");
+        console.log("channel was changed to tfailv");
+        return;
+      }
+    }
+
+    if ((channel == "tfailv") && (status == "Update installed, pending restart")) {
+      console.log("update from tfailv channel installed");
+      clearInterval(changeChannel);
+    }
+  }
+</script>
+</html>
diff --git a/src/starboard/evergreen/testing/tests/tmsabi.html b/src/starboard/evergreen/testing/tests/tmsabi.html
new file mode 100644
index 0000000..ae70b57
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/tmsabi.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<html>
+<meta charset="utf-8">
+<title>tmsabi</title>
+<script>
+  var changeChannel = setInterval(tryChangeChannel, 500);
+
+  function tryChangeChannel() {
+    const channel = window.h5vcc.updater.getUpdaterChannel();
+    const status = window.h5vcc.updater.getUpdateStatus();
+
+    if (channel == "") {
+      return;
+    }
+
+    if (channel == "qa") {
+      if ((status == "App is up to date") || (status == "Update installed, pending restart")) {
+        window.h5vcc.updater.setUpdaterChannel("tmsabi");
+        console.log("channel was changed to tmsabi");
+        return;
+      }
+    }
+
+    if ((channel == "tmsabi") && (status == "Update installed, pending restart")) {
+      console.log("update from tmsabi channel installed");
+      clearInterval(changeChannel);
+    }
+  }
+</script>
+</html>
diff --git a/src/starboard/evergreen/testing/tests/tnoop.html b/src/starboard/evergreen/testing/tests/tnoop.html
new file mode 100644
index 0000000..ecfa328
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/tnoop.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<html>
+<meta charset="utf-8">
+<title>tnoop</title>
+<script>
+  var changeChannel = setInterval(tryChangeChannel, 500);
+
+  function tryChangeChannel() {
+    const channel = window.h5vcc.updater.getUpdaterChannel();
+    const status = window.h5vcc.updater.getUpdateStatus();
+
+    if (channel == "") {
+      return;
+    }
+
+    if (channel == "qa") {
+      if ((status == "App is up to date") || (status == "Update installed, pending restart")) {
+        window.h5vcc.updater.setUpdaterChannel("tnoop");
+        console.log("channel was changed to tnoop");
+        return;
+      }
+    }
+
+    if ((channel == "tnoop") && (status == "Update installed, pending restart")) {
+      console.log("update from tnoop channel installed");
+      clearInterval(changeChannel);
+    }
+  }
+</script>
+</html>
diff --git a/src/starboard/evergreen/testing/tests/tseries.html b/src/starboard/evergreen/testing/tests/tseries.html
new file mode 100644
index 0000000..0d3aeff
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/tseries.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+<html>
+<meta charset="utf-8">
+<title>ttseries</title>
+<script>
+  var alternatedCount = 0;
+  var alternatingChannels = setInterval(alternateChannels, 500);
+
+  function alternateChannels() {
+    const channel = window.h5vcc.updater.getUpdaterChannel();
+    const status = window.h5vcc.updater.getUpdateStatus();
+
+    if (channel == "qa") {
+      console.log("channel changed from qa to tseries1");
+      window.h5vcc.updater.setUpdaterChannel("tseries1");
+      return;
+    }
+
+    if (alternatedCount < 5) {
+      if (status != "Update installed, pending restart") {
+        return;
+      }
+
+      if (channel == "tseries1") {
+        console.log("channel changed from tseries1 to tseries2");
+        window.h5vcc.updater.setUpdaterChannel("tseries2");
+      } else {
+        console.log("channel changed from " + channel + " to tseries1");
+        window.h5vcc.updater.setUpdaterChannel("tseries1");
+      }
+
+      alternatedCount++;
+      return;
+    }
+
+    console.log("continuous updates without restart working");
+
+    clearInterval(alternatingChannels);
+  }
+</script>
+</html>
diff --git a/src/starboard/evergreen/testing/tests/update_fails_verification_test.sh b/src/starboard/evergreen/testing/tests/update_fails_verification_test.sh
new file mode 100755
index 0000000..bfcbbc9
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/update_fails_verification_test.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="UpdateFailsVerification"
+TEST_FILE="tfailv.html"
+
+function run_test() {
+  clear_storage
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "Verification failed. Verifier error = [0-9]+"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to fail verifying the downloaded installation"
+    return 1
+  fi
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.1.log" "App is up to date"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to revert back to the system image"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/update_works_for_only_one_app_test.sh b/src/starboard/evergreen/testing/tests/update_works_for_only_one_app_test.sh
new file mode 100755
index 0000000..2574654
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/update_works_for_only_one_app_test.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="UpdateOnlyWorksForOneApp"
+TEST_FILE="test.html"
+
+function run_test() {
+  clear_storage
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "update from test channel installed"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to download and install the test package"
+    return 1
+  fi
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.1.log" "App is up to date"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to run the downloaded installation"
+    return 1
+  fi
+
+  start_cobalt "file:///tests/empty.html" "${TEST_NAME}.2.log" "quick update succeeded"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to perform a quick update on different app"
+    return 1
+  fi
+
+  FILENAME="$(get_bad_app_key_file_path "${TEST_NAME}.2.log")"
+
+  create_file "${FILENAME}"
+
+  start_cobalt "file:///tests/empty.html" "${TEST_NAME}.3.log" "RevertBack current_installation="
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to revert when the app's bad file exists"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/valid_slot_overwritten_test.sh b/src/starboard/evergreen/testing/tests/valid_slot_overwritten_test.sh
new file mode 100755
index 0000000..6fc33b0
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/valid_slot_overwritten_test.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="ValidSlotOverwritten"
+TEST_FILE="test.html"
+
+function run_test() {
+  clear_storage
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "update from test channel installed"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to download and install the test package"
+    return 1
+  fi
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.1.log" "App is up to date"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to run the downloaded installation"
+    return 1
+  fi
+
+  SLOT="$(get_current_installation_slot "${TEST_NAME}.1.log")"
+
+  # Warning: do not wrap '$TAIL' with double quotes or else it will not actually
+  # resolve to the correct command.
+  delete_file "$(run_command "find ${STORAGE_DIR}/installation_${SLOT} -name app_key_*.good | ${TAIL} -1")"
+
+  create_file "${STORAGE_DIR}/installation_${SLOT}/app_key_TEST.good"
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.2.log" "AdoptInstallation: current_installation=${SLOT}"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to adopt installation"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/testing/tests/verify_qa_channel_update_test.sh b/src/starboard/evergreen/testing/tests/verify_qa_channel_update_test.sh
new file mode 100755
index 0000000..66c4632
--- /dev/null
+++ b/src/starboard/evergreen/testing/tests/verify_qa_channel_update_test.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+#
+# 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.
+
+# Unset the previous test's name and runner function.
+unset TEST_NAME
+unset TEST_FILE
+unset -f run_test
+
+TEST_NAME="VerifyQaChannelUpdate"
+TEST_FILE="test.html"
+
+function run_test() {
+  clear_storage
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.0.log" "update from test channel installed"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to download and install the test package"
+    return 1
+  fi
+
+  start_cobalt "file:///tests/${TEST_FILE}" "${TEST_NAME}.1.log" "App is up to date"
+
+  if [[ $? -ne 0 ]]; then
+    error "Failed to run the downloaded installation"
+    return 1
+  fi
+
+  return 0
+}
+
diff --git a/src/starboard/evergreen/x64/gyp_configuration.gypi b/src/starboard/evergreen/x64/gyp_configuration.gypi
index dcb689e..a9f3eaa 100644
--- a/src/starboard/evergreen/x64/gyp_configuration.gypi
+++ b/src/starboard/evergreen/x64/gyp_configuration.gypi
@@ -22,6 +22,8 @@
 
     'compiler_flags': [
       '-isystem<(cobalt_repo_root)/third_party/musl/arch/x86_64',
+      '-ffunction-sections',
+      '-fdata-sections',
     ],
   },
 
diff --git a/src/starboard/evergreen/x86/gyp_configuration.gypi b/src/starboard/evergreen/x86/gyp_configuration.gypi
index f8a868f..58ebaca 100644
--- a/src/starboard/evergreen/x86/gyp_configuration.gypi
+++ b/src/starboard/evergreen/x86/gyp_configuration.gypi
@@ -22,6 +22,8 @@
 
     'compiler_flags': [
       '-isystem<(cobalt_repo_root)/third_party/musl/arch/i386',
+      '-ffunction-sections',
+      '-fdata-sections',
     ],
   },
 
diff --git a/src/starboard/file.h b/src/starboard/file.h
index 5f90802..1d69e49 100644
--- a/src/starboard/file.h
+++ b/src/starboard/file.h
@@ -330,78 +330,10 @@
 }  // extern "C"
 #endif
 
-#ifdef __cplusplus
-namespace starboard {
-
-// A class that opens an SbFile in its constructor and closes it in its
-// destructor, so the file is open for the lifetime of the object. Member
-// functions call the corresponding SbFile function.
-class ScopedFile {
- public:
-  ScopedFile(const char* path,
-             int flags,
-             bool* out_created,
-             SbFileError* out_error)
-      : file_(kSbFileInvalid) {
-    file_ = SbFileOpen(path, flags, out_created, out_error);
-  }
-
-  ScopedFile(const char* path, int flags, bool* out_created)
-      : file_(kSbFileInvalid) {
-    file_ = SbFileOpen(path, flags, out_created, NULL);
-  }
-
-  ScopedFile(const char* path, int flags) : file_(kSbFileInvalid) {
-    file_ = SbFileOpen(path, flags, NULL, NULL);
-  }
-
-  ~ScopedFile() { SbFileClose(file_); }
-
-  SbFile file() const { return file_; }
-
-  bool IsValid() const { return SbFileIsValid(file_); }
-
-  int64_t Seek(SbFileWhence whence, int64_t offset) const {
-    return SbFileSeek(file_, whence, offset);
-  }
-
-  int Read(char* data, int size) const { return SbFileRead(file_, data, size); }
-
-  int ReadAll(char* data, int size) const {
-    return SbFileReadAll(file_, data, size);
-  }
-
-  int Write(const char* data, int size) const {
-    return SbFileWrite(file_, data, size);
-  }
-
-  int WriteAll(const char* data, int size) const {
-    return SbFileWriteAll(file_, data, size);
-  }
-
-  bool Truncate(int64_t length) const { return SbFileTruncate(file_, length); }
-
-  bool Flush() const { return SbFileFlush(file_); }
-
-  bool GetInfo(SbFileInfo* out_info) const {
-    return SbFileGetInfo(file_, out_info);
-  }
-
-  int64_t GetSize() const {
-    SbFileInfo file_info;
-    bool success = GetInfo(&file_info);
-    return (success ? file_info.size : -1);
-  }
-
-  // disallow copy and move operations
-  ScopedFile(const ScopedFile&) = delete;
-  ScopedFile& operator=(const ScopedFile&) = delete;
-
- private:
-  SbFile file_;
-};
-
-}  // namespace starboard
-#endif  // ifdef __cplusplus
+#if SB_API_VERSION < 13 && defined(__cplusplus)
+extern "C++" {
+#include "starboard/common/file.h"
+}  // extern "C++"
+#endif  // SB_API_VERSION < 13 && defined(__cplusplus)
 
 #endif  // STARBOARD_FILE_H_
diff --git a/src/starboard/linux/shared/compiler_flags.gypi b/src/starboard/linux/shared/compiler_flags.gypi
index 53b3229..af40c10 100644
--- a/src/starboard/linux/shared/compiler_flags.gypi
+++ b/src/starboard/linux/shared/compiler_flags.gypi
@@ -36,9 +36,15 @@
     ],
     'compiler_flags_qa_size': [
       '-Os',
+      # Compile symbols in separate sections
+      '-ffunction-sections',
+      '-fdata-sections',
     ],
     'compiler_flags_qa_speed': [
       '-O2',
+      # Compile symbols in separate sections
+      '-ffunction-sections',
+      '-fdata-sections',
     ],
     'compiler_flags_gold': [
       '-fno-rtti',
@@ -46,9 +52,15 @@
     ],
     'compiler_flags_gold_size': [
       '-Os',
+      # Compile symbols in separate sections
+      '-ffunction-sections',
+      '-fdata-sections',
     ],
     'compiler_flags_gold_speed': [
       '-O2',
+      # Compile symbols in separate sections
+      '-ffunction-sections',
+      '-fdata-sections',
     ],
     'conditions': [
       ['clang==1', {
@@ -135,6 +147,8 @@
     ],
     'ldflags': [
       '-Wl,-rpath=$ORIGIN/lib',
+      # Cleanup unused sections
+      '-Wl,-gc-sections',
     ],
     'target_conditions': [
       ['sb_pedantic_warnings==1', {
diff --git a/src/starboard/linux/shared/configuration.gni b/src/starboard/linux/shared/configuration.gni
index c4562c3..49d1b21 100644
--- a/src/starboard/linux/shared/configuration.gni
+++ b/src/starboard/linux/shared/configuration.gni
@@ -39,9 +39,3 @@
 
 # Use ASAN by default when building with Clang.
 use_asan_by_default = true
-
-declare_args() {
-  # Set to true to enable distributed compilation using Goma. By default we
-  # use Goma for stub and linux.
-  use_goma = true
-}
diff --git a/src/starboard/linux/shared/gyp_configuration.py b/src/starboard/linux/shared/gyp_configuration.py
index f96c364..0cb799e 100644
--- a/src/starboard/linux/shared/gyp_configuration.py
+++ b/src/starboard/linux/shared/gyp_configuration.py
@@ -18,6 +18,7 @@
 from starboard.build import clang
 from starboard.build import platform_configuration
 from starboard.tools import build
+from starboard.tools import paths
 from starboard.tools.testing import test_filter
 
 
@@ -88,13 +89,25 @@
 
   def GetTestFilters(self):
     filters = super(LinuxConfiguration, self).GetTestFilters()
-    for target, tests in self.__FILTERED_TESTS.iteritems():
+
+    has_cdm = os.path.isfile(
+        os.path.join(paths.REPOSITORY_ROOT, 'third_party', 'ce_cdm', 'cdm',
+                     'include', 'cdm.h'))
+
+    if has_cdm:
+      return filters
+
+    # Filter the drm related tests, as ce_cdm is not present.
+    for target, tests in self.__DRM_RELATED_TESTS.iteritems():
       filters.extend(test_filter.TestFilter(target, test) for test in tests)
     return filters
 
   def GetPathToSabiJsonFile(self):
     return self.sabi_json_path
 
-  __FILTERED_TESTS = {  # pylint: disable=invalid-name
-      'nplb': ['SbDrmTest.AnySupportedKeySystems',],
+  __DRM_RELATED_TESTS = {  # pylint: disable=invalid-name
+      'nplb': [
+          'SbDrmTest.AnySupportedKeySystems',
+          'SbMediaCanPlayMimeAndKeySystem.AnySupportedKeySystems',
+      ],
   }
diff --git a/src/starboard/linux/x64x11/clang/3.6/compiler_flags.gypi b/src/starboard/linux/x64x11/clang/3.6/compiler_flags.gypi
index b5368e1..845e1df 100644
--- a/src/starboard/linux/x64x11/clang/3.6/compiler_flags.gypi
+++ b/src/starboard/linux/x64x11/clang/3.6/compiler_flags.gypi
@@ -50,8 +50,6 @@
         'common_clang_flags': [
           '-Werror',
           '-fcolor-diagnostics',
-          # Point to a gcc toolchain that works with this compiler.
-          '--gcc-toolchain=<(GCC_TOOLCHAIN_FOLDER)',
           # Default visibility to hidden, to enable dead stripping.
           '-fvisibility=hidden',
           # Warn for implicit type conversions that may change a value.
@@ -73,18 +71,15 @@
           '-Wno-invalid-offsetof',
           # Suppress 'implicit conversion changes signedness'
           '-Wno-sign-conversion',
-
           # shifting a negative signed value is undefined
           '-Wno-shift-sign-overflow',
-          # Suppress "'&&' within '||'"
-          '-Wno-logical-op-parentheses',
           # Suppress "comparison may be assumed to always evaluate to false"
           '-Wno-tautological-undefined-compare',
           # Suppress "comparison of unsigned enum expression < 0 is always false"
           '-Wno-tautological-compare',
           # Suppress "[type1] has C-linkage specified, but returns user-defined type [type2] which is incompatible with C"
           '-Wno-return-type-c-linkage',
-
+          '-Wno-array-bounds',
           # Suppress "template argument uses unnamed type"
           '-Wno-unnamed-type-template-args',
           # 'this' pointer cannot be NULL...pointer may be assumed
@@ -94,19 +89,19 @@
           '-Wno-inconsistent-missing-override',
           # Triggered by the COMPILE_ASSERT macro.
           '-Wno-unused-local-typedef',
-          # shifting a negative signed value is undefined
-          '-Wno-shift-sign-overflow',
           # Suppress "'&&' within '||'"
           '-Wno-logical-op-parentheses',
-          # Suppress "comparison may be assumed to always evaluate to false"
-          '-Wno-tautological-undefined-compare',
-          # Suppress "comparison of unsigned enum expression < 0 is always false"
-          '-Wno-tautological-compare',
-          # Suppress "[type1] has C-linkage specified, but returns user-defined type [type2] which is incompatible with C"
-          '-Wno-return-type-c-linkage',
           '-Wno-unused-parameter',
           # Suppress warnings about unknown pragmas.
           '-Wno-unknown-pragmas',
+          # Suppress warnings about equality checks within double parentheses.
+          '-Wno-parentheses-equality',
+          # Suppress warnings about unreachable code due to constexpr conditions
+          '-Wno-unreachable-code',
+          # Suppress warnings about values being written but not read before the next write.
+          '-Wno-unused-value',
+          # Suppress warnings related to unused compilation flags in clang.
+          '-Wno-unused-command-line-argument',
         ],
       }],
       ['cobalt_fastbuild==0', {
diff --git a/src/starboard/linux/x64x11/clang/3.6/gyp_configuration.py b/src/starboard/linux/x64x11/clang/3.6/gyp_configuration.py
index 1506d71..8bb0020 100644
--- a/src/starboard/linux/x64x11/clang/3.6/gyp_configuration.py
+++ b/src/starboard/linux/x64x11/clang/3.6/gyp_configuration.py
@@ -18,7 +18,6 @@
 import subprocess
 
 from starboard.linux.shared import gyp_configuration as shared_configuration
-from starboard.tools import build
 from starboard.tools.toolchain import ar
 from starboard.tools.toolchain import bash
 from starboard.tools.toolchain import clang
@@ -34,21 +33,15 @@
                platform='linux-x64x11-clang-3-6',
                asan_enabled_by_default=False,
                sabi_json_path='starboard/sabi/default/sabi.json'):
-    super(LinuxX64X11Clang36Configuration, self).__init__(
-        platform,
-        asan_enabled_by_default,
-        sabi_json_path)
+    super(LinuxX64X11Clang36Configuration,
+          self).__init__(platform, asan_enabled_by_default, sabi_json_path)
 
-    self.toolchain_top_dir = os.path.join(build.GetToolchainsDir(),
-                                          'x86_64-linux-gnu-clang-3.6')
-    self.toolchain_dir = os.path.join(self.toolchain_top_dir, 'llvm',
-                                      'Release+Asserts')
+    self.toolchain_dir = '/usr/lib/llvm-3.6'
 
   def SetupPlatformTools(self, build_number):
-    script_path = os.path.dirname(os.path.realpath(__file__))
-    # Run the script that ensures clang 3.6 is installed.
-    subprocess.call(
-        os.path.join(script_path, 'download_clang.sh'), cwd=script_path)
+    ret = subprocess.call('/usr/bin/clang-3.6 --version', shell=True)
+    if ret != 0:
+      raise Exception('clang-3.6 is not installed.')
 
   def GetEnvironmentVariables(self):
     toolchain_bin_dir = os.path.join(self.toolchain_dir, 'bin')
@@ -56,22 +49,18 @@
     env_variables = super(LinuxX64X11Clang36Configuration,
                           self).GetEnvironmentVariables()
     env_variables.update({
-        'CC': self.build_accelerator + ' ' + os.path.join(toolchain_bin_dir,
-                                                          'clang'),
-        'CXX': self.build_accelerator + ' ' + os.path.join(toolchain_bin_dir,
-                                                           'clang++'),
+        'CC':
+            self.build_accelerator + ' ' +
+            os.path.join(toolchain_bin_dir, 'clang'),
+        'CXX':
+            self.build_accelerator + ' ' +
+            os.path.join(toolchain_bin_dir, 'clang++'),
     })
     return env_variables
 
   def GetVariables(self, config_name):
-    # A significant amount of code in V8 fails to compile on clang 3.6 using
-    # the debug config, due to an internal error in clang.
     variables = super(LinuxX64X11Clang36Configuration,
                       self).GetVariables(config_name)
-    variables.update({
-        'GCC_TOOLCHAIN_FOLDER':
-            '\"%s\"' % os.path.join(self.toolchain_top_dir, 'libstdc++-7'),
-    })
     return variables
 
   def GetTargetToolchain(self, **kwargs):
diff --git a/src/starboard/linux/x64x11/gyp_configuration.py b/src/starboard/linux/x64x11/gyp_configuration.py
index 5bf1c11..5f8069a 100644
--- a/src/starboard/linux/x64x11/gyp_configuration.py
+++ b/src/starboard/linux/x64x11/gyp_configuration.py
@@ -13,8 +13,6 @@
 # limitations under the License.
 """Starboard Linux X64 X11 platform configuration."""
 
-import os.path
-
 from starboard.linux.shared import gyp_configuration as shared_configuration
 from starboard.tools.toolchain import ar
 from starboard.tools.toolchain import bash
@@ -22,7 +20,6 @@
 from starboard.tools.toolchain import clangxx
 from starboard.tools.toolchain import cp
 from starboard.tools.toolchain import touch
-from starboard.tools import paths
 
 
 class LinuxX64X11Configuration(shared_configuration.LinuxConfiguration):
@@ -32,9 +29,8 @@
                platform='linux-x64x11',
                asan_enabled_by_default=True,
                sabi_json_path='starboard/sabi/default/sabi.json'):
-    super(LinuxX64X11Configuration, self).__init__(platform,
-                                                   asan_enabled_by_default,
-                                                   sabi_json_path)
+    super(LinuxX64X11Configuration,
+          self).__init__(platform, asan_enabled_by_default, sabi_json_path)
 
   def GetTargetToolchain(self, **kwargs):
     return self.GetHostToolchain(**kwargs)
@@ -57,25 +53,6 @@
         bash.Shell(),
     ]
 
-  def GetTestFilters(self):
-    filters = super(LinuxX64X11Configuration, self).GetTestFilters()
-    # Remove the exclusion filter on SbDrmTest.AnySupportedKeySystems.
-    # Generally, children of linux/shared do not support widevine, but children
-    # of linux/x64x11 do, if the content decryption module is present.
-
-    has_cdm = os.path.isfile(
-        os.path.join(paths.REPOSITORY_ROOT, 'third_party', 'cdm', 'cdm',
-                     'include', 'content_decryption_module.h'))
-
-    if not has_cdm:
-      return filters
-
-    for test_filter in filters:
-      if (test_filter.target_name == 'nplb' and
-          test_filter.test_name == 'SbDrmTest.AnySupportedKeySystems'):
-        filters.remove(test_filter)
-    return filters
-
 
 def CreatePlatformConfig():
   return LinuxX64X11Configuration(
diff --git a/src/starboard/loader_app/app_key_files.cc b/src/starboard/loader_app/app_key_files.cc
index e387987..68631e5 100644
--- a/src/starboard/loader_app/app_key_files.cc
+++ b/src/starboard/loader_app/app_key_files.cc
@@ -16,10 +16,10 @@
 
 #include <vector>
 
+#include "starboard/common/file.h"
 #include "starboard/common/log.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/directory.h"
-#include "starboard/file.h"
 #include "starboard/string.h"
 
 namespace starboard {
diff --git a/src/starboard/loader_app/drain_file_helper.cc b/src/starboard/loader_app/drain_file_helper.cc
index 8c3e0b1..0a61a97 100644
--- a/src/starboard/loader_app/drain_file_helper.cc
+++ b/src/starboard/loader_app/drain_file_helper.cc
@@ -14,7 +14,7 @@
 
 #include "starboard/loader_app/drain_file_helper.h"
 
-#include "starboard/file.h"
+#include "starboard/common/file.h"
 #include "starboard/loader_app/drain_file.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/src/starboard/loader_app/drain_file_test.cc b/src/starboard/loader_app/drain_file_test.cc
index afbde87..35ae7cd 100644
--- a/src/starboard/loader_app/drain_file_test.cc
+++ b/src/starboard/loader_app/drain_file_test.cc
@@ -17,10 +17,10 @@
 #include <string>
 #include <vector>
 
+#include "starboard/common/file.h"
 #include "starboard/common/log.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/directory.h"
-#include "starboard/file.h"
 #include "starboard/loader_app/drain_file_helper.h"
 #include "starboard/system.h"
 #include "starboard/types.h"
diff --git a/src/starboard/loader_app/installation_manager_test.cc b/src/starboard/loader_app/installation_manager_test.cc
index 168241c..da7e8b0 100644
--- a/src/starboard/loader_app/installation_manager_test.cc
+++ b/src/starboard/loader_app/installation_manager_test.cc
@@ -17,6 +17,7 @@
 #include <string>
 #include <vector>
 
+#include "starboard/common/file.h"
 #include "starboard/configuration_constants.h"
 #include "starboard/loader_app/installation_store.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/src/starboard/loader_app/loader_app.cc b/src/starboard/loader_app/loader_app.cc
index c74437f..1c661af 100644
--- a/src/starboard/loader_app/loader_app.cc
+++ b/src/starboard/loader_app/loader_app.cc
@@ -25,6 +25,7 @@
 #include "starboard/loader_app/loader_app_switches.h"
 #include "starboard/loader_app/slot_management.h"
 #include "starboard/loader_app/system_get_extension_shim.h"
+#include "starboard/memory.h"
 #include "starboard/mutex.h"
 #include "starboard/shared/starboard/command_line.h"
 #include "starboard/string.h"
@@ -109,6 +110,20 @@
     SB_LOG(INFO) << "Loaded Cobalt library information into Crashpad.";
   }
 
+  auto get_user_agent_func = reinterpret_cast<const char* (*)()>(
+      g_elf_loader.LookupSymbol("GetCobaltUserAgentString"));
+  if (!get_user_agent_func) {
+    SB_LOG(ERROR) << "Failed to get user agent string";
+  } else {
+    EvergreenAnnotations cobalt_version_info;
+    SbMemorySet(&cobalt_version_info, sizeof(EvergreenAnnotations), 0);
+    SbStringCopy(cobalt_version_info.user_agent_string, get_user_agent_func(),
+                 EVERGREEN_USER_AGENT_MAX_SIZE);
+    third_party::crashpad::wrapper::AddAnnotationsToCrashpad(
+        cobalt_version_info);
+    SB_DLOG(INFO) << "Added user agent string to Crashpad.";
+  }
+
   g_sb_event_func = reinterpret_cast<void (*)(const SbEvent*)>(
       g_elf_loader.LookupSymbol("SbEventHandle"));
 
diff --git a/src/starboard/loader_app/loader_app.gyp b/src/starboard/loader_app/loader_app.gyp
index 154cb61..e8f730e 100644
--- a/src/starboard/loader_app/loader_app.gyp
+++ b/src/starboard/loader_app/loader_app.gyp
@@ -90,5 +90,17 @@
       },
       'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
     },
+    {
+      'target_name': 'loader_app_tests_deploy',
+      'type': 'none',
+      'dependencies': [
+        'loader_app',
+        '<(DEPTH)/starboard/loader_app/app_key.gyp:app_key_test_deploy',
+        '<(DEPTH)/starboard/loader_app/app_key_files.gyp:app_key_files_test_deploy',
+        '<(DEPTH)/starboard/loader_app/drain_file.gyp:drain_file_test_deploy',
+        '<(DEPTH)/starboard/loader_app/installation_manager.gyp:installation_manager_test_deploy',
+        '<(DEPTH)/starboard/loader_app/slot_management.gyp:slot_management_test_deploy',
+      ],
+    },
   ],
 }
diff --git a/src/starboard/loader_app/slot_management.cc b/src/starboard/loader_app/slot_management.cc
index 9e1eadd..5dc1212 100644
--- a/src/starboard/loader_app/slot_management.cc
+++ b/src/starboard/loader_app/slot_management.cc
@@ -23,6 +23,7 @@
 #include "starboard/loader_app/app_key_files.h"
 #include "starboard/loader_app/drain_file.h"
 #include "starboard/loader_app/installation_manager.h"
+#include "starboard/memory.h"
 #include "starboard/string.h"
 #include "third_party/crashpad/wrapper/wrapper.h"
 
@@ -243,6 +244,20 @@
       SB_LOG(INFO) << "Loaded Cobalt library information into Crashpad.";
     }
 
+    auto get_user_agent_func = reinterpret_cast<const char* (*)()>(
+        library_loader->Resolve("GetCobaltUserAgentString"));
+    if (!get_user_agent_func) {
+      SB_LOG(ERROR) << "Failed to get user agent string";
+    } else {
+      EvergreenAnnotations cobalt_version_info;
+      SbMemorySet(&cobalt_version_info, sizeof(EvergreenAnnotations), 0);
+      SbStringCopy(cobalt_version_info.user_agent_string, get_user_agent_func(),
+                   EVERGREEN_USER_AGENT_MAX_SIZE);
+      third_party::crashpad::wrapper::AddAnnotationsToCrashpad(
+          cobalt_version_info);
+      SB_DLOG(INFO) << "Added user agent string to Crashpad.";
+    }
+
     SB_DLOG(INFO) << "Successfully loaded Cobalt!\n";
     void* p = library_loader->Resolve("SbEventHandle");
     if (p != NULL) {
diff --git a/src/starboard/loader_app/slot_management_test.cc b/src/starboard/loader_app/slot_management_test.cc
index 0eeb83c..5ff9e20 100644
--- a/src/starboard/loader_app/slot_management_test.cc
+++ b/src/starboard/loader_app/slot_management_test.cc
@@ -37,6 +37,10 @@
 
 void SbEventFake(const SbEvent*) {}
 
+const char* GetCobaltUserAgentStringFake() {
+  return "";
+}
+
 class MockLibraryLoader : public LibraryLoader {
  public:
   MOCK_METHOD2(Load,
@@ -132,6 +136,10 @@
                 Load(testing::EndsWith(lib), testing::EndsWith(content)))
         .Times(1)
         .WillOnce(testing::Return(true));
+    EXPECT_CALL(library_loader, Resolve("GetCobaltUserAgentString"))
+        .Times(1)
+        .WillOnce(testing::Return(
+            reinterpret_cast<void*>(&GetCobaltUserAgentStringFake)));
     EXPECT_CALL(library_loader, Resolve("SbEventHandle"))
         .Times(1)
         .WillOnce(testing::Return(reinterpret_cast<void*>(&SbEventFake)));
@@ -261,6 +269,10 @@
                                    testing::EndsWith("/foo")))
       .Times(1)
       .WillOnce(testing::Return(true));
+  EXPECT_CALL(library_loader, Resolve("GetCobaltUserAgentString"))
+      .Times(1)
+      .WillOnce(testing::Return(
+          reinterpret_cast<void*>(&GetCobaltUserAgentStringFake)));
   EXPECT_CALL(library_loader, Resolve("SbEventHandle"))
       .Times(1)
       .WillOnce(testing::Return(reinterpret_cast<void*>(&SbEventFake)));
diff --git a/src/starboard/media.h b/src/starboard/media.h
index bdafbe4..05aaf0d 100644
--- a/src/starboard/media.h
+++ b/src/starboard/media.h
@@ -544,11 +544,44 @@
 //   or |video/mp4; codecs="avc1.42001E"|. It may include arbitrary parameters
 //   like "codecs", "channels", etc.  Note that the "codecs" parameter may
 //   contain more than one codec, delimited by comma.
-// |key_system|: A lowercase value in fhe form of "com.example.somesystem"
+// |key_system|: A lowercase value in the form of "com.example.somesystem"
 //   as suggested by https://w3c.github.io/encrypted-media/#key-system
 //   that can be matched exactly with known DRM key systems of the platform.
 //   When |key_system| is an empty string, the return value is an indication for
 //   non-encrypted media.
+//
+//   An implementation may choose to support |key_system| with extra attributes,
+//   separated by ';', like
+//   |com.example.somesystem; attribute_name1="value1"; attribute_name2=value1|.
+//   If |key_system| with attributes is not supported by an implementation, it
+//   should treat |key_system| as if it contains only the key system, and reject
+//   any input containing extra attributes, i.e. it can keep using its existing
+//   implementation.
+//   When an implementation supports |key_system| with attributes, it has to
+//   support all attributes defined by the Starboard version the implementation
+//   uses.
+//   An implementation should ignore any unknown attributes, and make a decision
+//   solely based on the key system and the known attributes.  For example, if
+//   an implementation supports "com.widevine.alpha", it should also return
+//   `kSbMediaSupportTypeProbably` when |key_system| is
+//   |com.widevine.alpha; invalid_attribute="invalid_value"|.
+//   Currently the only attribute has to be supported is |encryptionscheme|.  It
+//   reflects the value passed to `encryptionScheme` of
+//   MediaKeySystemMediaCapability, as defined in
+//   https://wicg.github.io/encrypted-media-encryption-scheme/, which can take
+//   value "cenc", "cbcs", or "cbcs-1-9".
+//   Empty string is not a valid value for |encryptionscheme| and the
+//   implementation should return `kSbMediaSupportTypeNotSupported` when
+//   |encryptionscheme| is set to "".
+//   The implementation should return `kSbMediaSupportTypeNotSupported` for
+//   unknown values of known attributes.  For example, if an implementation
+//   supports "encryptionscheme" with value "cenc", "cbcs", or "cbcs-1-9", then
+//   it should return `kSbMediaSupportTypeProbably` when |key_system| is
+//   |com.widevine.alpha; encryptionscheme="cenc"|, and return
+//   `kSbMediaSupportTypeNotSupported` when |key_system| is
+//   |com.widevine.alpha; encryptionscheme="invalid"|.
+//   If an implementation supports key system with attributes on one key system,
+//   it has to support key system with attributes on all key systems supported.
 SB_EXPORT SbMediaSupportType
 SbMediaCanPlayMimeAndKeySystem(const char* mime, const char* key_system);
 
diff --git a/src/starboard/nplb/drm_helpers.h b/src/starboard/nplb/drm_helpers.h
index c24b696..e6b0972 100644
--- a/src/starboard/nplb/drm_helpers.h
+++ b/src/starboard/nplb/drm_helpers.h
@@ -68,6 +68,10 @@
     "com.youtube.fairplay",
 };
 
+static const char* kEncryptionSchemes[] = {
+    "cenc", "cbcs", "cbcs-1-9",
+};
+
 }  // namespace nplb
 }  // namespace starboard
 
diff --git a/src/starboard/nplb/file_delete_test.cc b/src/starboard/nplb/file_delete_test.cc
index ada20d8..cea7b61 100644
--- a/src/starboard/nplb/file_delete_test.cc
+++ b/src/starboard/nplb/file_delete_test.cc
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// SbFileDelete is otherwise tested in all the other unit tests.
-
 #include <string>
 
 #include "starboard/file.h"
@@ -24,12 +22,29 @@
 namespace nplb {
 namespace {
 
-TEST(SbFileDeleteTest, NonExistentFileErrors) {
-  std::string filename = starboard::nplb::GetRandomFilename();
-  EXPECT_FALSE(SbFileExists(filename.c_str()));
+TEST(SbFileDeleteTest, SunnyDayDeleteExistingFile) {
+  ScopedRandomFile file;
 
-  bool result = SbFileDelete(filename.c_str());
-  EXPECT_FALSE(result);
+  EXPECT_TRUE(SbFileExists(file.filename().c_str()));
+  EXPECT_TRUE(SbFileDelete(file.filename().c_str()));
+}
+
+TEST(SbFileDeleteTest, SunnyDayDeleteExistingDirectory) {
+  ScopedRandomFile file(ScopedRandomFile::kDontCreate);
+
+  const std::string& path = file.filename();
+
+  EXPECT_FALSE(SbFileExists(path.c_str()));
+  EXPECT_TRUE(SbDirectoryCreate(path.c_str()));
+  EXPECT_TRUE(SbDirectoryCanOpen(path.c_str()));
+  EXPECT_TRUE(SbFileDelete(path.c_str()));
+}
+
+TEST(SbFileDeleteTest, RainyDayNonExistentFileErrors) {
+  ScopedRandomFile file(ScopedRandomFile::kDontCreate);
+
+  EXPECT_FALSE(SbFileExists(file.filename().c_str()));
+  EXPECT_TRUE(SbFileDelete(file.filename().c_str()));
 }
 
 }  // namespace
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
index 2e38c86..3003a56 100644
--- 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
@@ -14,14 +14,38 @@
 
 #include "starboard/media.h"
 
+#include "starboard/common/string.h"
+#include "starboard/nplb/drm_helpers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace starboard {
-namespace shared {
-namespace starboard {
-namespace media {
+namespace nplb {
 namespace {
 
+bool IsKeySystemWithAttributesSupported() {
+  for (auto key_system : kKeySystems) {
+    if (!SbMediaCanPlayMimeAndKeySystem("video/mp4; codecs=\"avc1.4d4015\"",
+                                        key_system)) {
+      continue;
+    }
+
+    // The key system is supported, let's check if the implementation supports
+    // attributes.  By definition, when an implementation supports key system
+    // with attributes, it should make the decision without any unknown
+    // attributes.  So the following |key_system_with_invalid_attribute| should
+    // be supported on such implementation.
+    std::string key_system_with_invalid_attribute = key_system;
+    key_system_with_invalid_attribute += "; invalid_attribute=\"some_value\"";
+    if (SbMediaCanPlayMimeAndKeySystem(
+            "video/mp4; codecs=\"avc1.4d4015\"",
+            key_system_with_invalid_attribute.c_str())) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 TEST(SbMediaCanPlayMimeAndKeySystem, SunnyDay) {
   // Vp9
   SbMediaCanPlayMimeAndKeySystem(
@@ -89,14 +113,121 @@
       "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");
+  // Invalid key system
+  result = SbMediaCanPlayMimeAndKeySystem("video/mp4; codecs=\"avc1.4d4015\"",
+                                          "abc");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+  result = SbMediaCanPlayMimeAndKeySystem("video/mp4; codecs=\"avc1.4d4015\"",
+                                          "widevine");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+  result = SbMediaCanPlayMimeAndKeySystem("video/mp4; codecs=\"avc1.4d4015\"",
+                                          "com.widevine.alpha.invalid");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+  result = SbMediaCanPlayMimeAndKeySystem("video/mp4; codecs=\"avc1.4d4015\"",
+                                          "playready");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+  result = SbMediaCanPlayMimeAndKeySystem("video/mp4; codecs=\"avc1.4d4015\"",
+                                          "com.youtube.playready.invalid");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+  result = SbMediaCanPlayMimeAndKeySystem("video/mp4; codecs=\"avc1.4d4015\"",
+                                          "fairplay");
+  ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
+  result = SbMediaCanPlayMimeAndKeySystem("video/mp4; codecs=\"avc1.4d4015\"",
+                                          "com.youtube.fairplay.invalid");
   ASSERT_EQ(result, kSbMediaSupportTypeNotSupported);
 }
 
+TEST(SbMediaCanPlayMimeAndKeySystem, MinimumSupport) {
+  // H.264 Main Profile Level 4.2
+  SbMediaSupportType result = SbMediaCanPlayMimeAndKeySystem(
+      "video/mp4; codecs=\"avc1.4d402a\"; width=1920; height=1080; "
+      "framerate=30;",
+      "");
+  ASSERT_EQ(result, kSbMediaSupportTypeProbably);
+
+  result = SbMediaCanPlayMimeAndKeySystem(
+      "video/mp4; codecs=\"avc1.4d402a\"; width=1920; height=1080; "
+      "framerate=60;",
+      "");
+  ASSERT_EQ(result, kSbMediaSupportTypeProbably);
+
+  // H.264 Main Profile Level 2.1
+  result = SbMediaCanPlayMimeAndKeySystem(
+      "video/mp4; codecs=\"avc1.4d4015\"; width=432; height=240; "
+      "framerate=15;",
+      "");
+  ASSERT_EQ(result, kSbMediaSupportTypeProbably);
+
+  // AAC-LC
+  result = SbMediaCanPlayMimeAndKeySystem(
+      "audio/mp4; codecs=\"mp4a.40.2\"; channels=2; bitrate=256;", "");
+  ASSERT_EQ(result, kSbMediaSupportTypeProbably);
+
+  // HE-AAC
+  result = SbMediaCanPlayMimeAndKeySystem(
+      "audio/mp4; codecs=\"mp4a.40.5\"; channels=2; bitrate=48;", "");
+  ASSERT_EQ(result, kSbMediaSupportTypeProbably);
+}
+
+TEST(SbMediaCanPlayMimeAndKeySystem, AnySupportedKeySystems) {
+  bool any_supported_key_systems = false;
+  for (auto key_system : kKeySystems) {
+    if (SbMediaCanPlayMimeAndKeySystem("video/mp4; codecs=\"avc1.4d4015\"",
+                                       key_system)) {
+      any_supported_key_systems = true;
+      break;
+    }
+  }
+  ASSERT_TRUE(any_supported_key_systems);
+}
+
+TEST(SbMediaCanPlayMimeAndKeySystem, KeySystemWithAttributes) {
+  if (!IsKeySystemWithAttributesSupported()) {
+    SB_LOG(INFO) << "KeySystemWithAttributes test skipped because key system"
+                 << " with attribute is not supported.";
+    return;
+  }
+
+  for (auto key_system : kKeySystems) {
+    if (!SbMediaCanPlayMimeAndKeySystem("video/mp4; codecs=\"avc1.4d4015\"",
+                                        key_system)) {
+      continue;
+    }
+
+    EXPECT_TRUE(SbMediaCanPlayMimeAndKeySystem(
+        "video/mp4; codecs=\"avc1.4d4015\"",
+        FormatString("%s; %s=\"%s\"", key_system, "invalid_attribute",
+                     "some_value")
+            .c_str()));
+
+    // "" is not a valid value for "encryptionscheme".
+    EXPECT_FALSE(SbMediaCanPlayMimeAndKeySystem(
+        "video/mp4; codecs=\"avc1.4d4015\"",
+        FormatString("%s; %s=\"%s\"", key_system, "encryptionscheme", "")
+            .c_str()));
+
+    bool has_supported_encryption_scheme = false;
+    for (auto encryption_scheme : kEncryptionSchemes) {
+      if (!SbMediaCanPlayMimeAndKeySystem(
+              "video/mp4; codecs=\"avc1.4d4015\"",
+              FormatString("%s; %s=\"%s\"", key_system, "encryptionscheme",
+                           encryption_scheme)
+                  .c_str())) {
+        continue;
+      }
+      has_supported_encryption_scheme = true;
+      EXPECT_TRUE(SbMediaCanPlayMimeAndKeySystem(
+          "video/mp4; codecs=\"avc1.4d4015\"",
+          FormatString("%s; %s=\"%s\"; %s=\"%s\"", key_system,
+                       "encryptionscheme", encryption_scheme,
+                       "invalid_attribute", "some_value")
+              .c_str()));
+    }
+
+    ASSERT_TRUE(has_supported_encryption_scheme);
+  }
+}
+
 }  // namespace
-}  // namespace media
-}  // namespace starboard
-}  // namespace shared
+}  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/mutex_acquire_test.cc b/src/starboard/nplb/mutex_acquire_test.cc
index c37a465..81ad198 100644
--- a/src/starboard/nplb/mutex_acquire_test.cc
+++ b/src/starboard/nplb/mutex_acquire_test.cc
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/common/mutex.h"
 #include "starboard/configuration.h"
+#include "starboard/mutex.h"
 #include "starboard/thread.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/src/starboard/nplb/mutex_acquire_try_test.cc b/src/starboard/nplb/mutex_acquire_try_test.cc
index ccd7e5b..e38e897 100644
--- a/src/starboard/nplb/mutex_acquire_try_test.cc
+++ b/src/starboard/nplb/mutex_acquire_try_test.cc
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/common/mutex.h"
 #include "starboard/configuration.h"
+#include "starboard/mutex.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if SB_API_VERSION >= 12
diff --git a/src/starboard/nplb/mutex_create_test.cc b/src/starboard/nplb/mutex_create_test.cc
index 8c11eaa..f52ba08 100644
--- a/src/starboard/nplb/mutex_create_test.cc
+++ b/src/starboard/nplb/mutex_create_test.cc
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/common/mutex.h"
 #include "starboard/configuration.h"
+#include "starboard/mutex.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if SB_API_VERSION >= 12
diff --git a/src/starboard/nplb/mutex_destroy_test.cc b/src/starboard/nplb/mutex_destroy_test.cc
index e555b70..5df8a3a 100644
--- a/src/starboard/nplb/mutex_destroy_test.cc
+++ b/src/starboard/nplb/mutex_destroy_test.cc
@@ -14,8 +14,8 @@
 
 // Destroy is mostly Sunny Day tested in Create.
 
-#include "starboard/common/mutex.h"
 #include "starboard/configuration.h"
+#include "starboard/mutex.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace starboard {
diff --git a/src/starboard/nplb/nplb.gyp b/src/starboard/nplb/nplb.gyp
index 41f8971..ab22a51 100644
--- a/src/starboard/nplb/nplb.gyp
+++ b/src/starboard/nplb/nplb.gyp
@@ -129,6 +129,7 @@
         'file_can_open_test.cc',
         'file_close_test.cc',
         'file_delete_recursive_test.cc',
+        'file_delete_test.cc',
         'file_get_info_test.cc',
         'file_get_path_info_test.cc',
         'file_helpers.cc',
@@ -191,6 +192,7 @@
         'player_output_mode_supported_test.cc',
         'player_test_util.cc',
         'player_test_util.h',
+        'player_write_sample_test.cc',
         'random_helpers.cc',
         'recursive_mutex_test.cc',
         'rwlock_test.cc',
diff --git a/src/starboard/nplb/nplb_evergreen_compat_tests/storage_test.cc b/src/starboard/nplb/nplb_evergreen_compat_tests/storage_test.cc
index 1214d7c..4cd5584 100644
--- a/src/starboard/nplb/nplb_evergreen_compat_tests/storage_test.cc
+++ b/src/starboard/nplb/nplb_evergreen_compat_tests/storage_test.cc
@@ -15,9 +15,9 @@
 #include <string>
 #include <vector>
 
+#include "starboard/common/file.h"
 #include "starboard/common/log.h"
 #include "starboard/configuration.h"
-#include "starboard/file.h"
 #include "starboard/memory.h"
 #include "starboard/nplb/nplb_evergreen_compat_tests/checks.h"
 #include "starboard/system.h"
diff --git a/src/starboard/nplb/player_test_util.cc b/src/starboard/nplb/player_test_util.cc
index 88f8233..151cbdf 100644
--- a/src/starboard/nplb/player_test_util.cc
+++ b/src/starboard/nplb/player_test_util.cc
@@ -14,12 +14,92 @@
 
 #include "starboard/nplb/player_test_util.h"
 
+#include <functional>
+
+#include "starboard/audio_sink.h"
+#include "starboard/common/string.h"
 #include "starboard/directory.h"
 #include "starboard/nplb/player_creation_param_helpers.h"
+#include "starboard/shared/starboard/player/video_dmp_reader.h"
+#include "starboard/testing/fake_graphics_context_provider.h"
 
 namespace starboard {
 namespace nplb {
 
+namespace {
+
+using shared::starboard::player::video_dmp::VideoDmpReader;
+using std::placeholders::_1;
+using std::placeholders::_2;
+using std::placeholders::_3;
+using std::placeholders::_4;
+using testing::FakeGraphicsContextProvider;
+
+void ErrorFunc(SbPlayer player,
+               void* context,
+               SbPlayerError error,
+               const char* message) {
+  atomic_bool* error_occurred = static_cast<atomic_bool*>(context);
+  error_occurred->exchange(true);
+}
+
+}  // namespace
+
+std::vector<SbPlayerTestConfig> GetSupportedSbPlayerTestConfigs() {
+  const char* kAudioTestFiles[] = {"beneath_the_canopy_aac_stereo.dmp",
+                                   "beneath_the_canopy_aac_5_1.dmp",
+                                   "beneath_the_canopy_aac_mono.dmp",
+                                   "beneath_the_canopy_opus_5_1.dmp",
+                                   "beneath_the_canopy_opus_stereo.dmp",
+                                   "beneath_the_canopy_opus_mono.dmp",
+                                   "sintel_329_ec3.dmp",
+                                   "sintel_381_ac3.dmp",
+                                   "heaac.dmp"};
+
+  const char* kVideoTestFiles[] = {"beneath_the_canopy_137_avc.dmp",
+                                   "beneath_the_canopy_248_vp9.dmp",
+                                   "sintel_399_av1.dmp"};
+
+  const SbPlayerOutputMode kOutputModes[] = {kSbPlayerOutputModeDecodeToTexture,
+                                             kSbPlayerOutputModePunchOut};
+
+  std::vector<SbPlayerTestConfig> test_configs;
+
+  const char* kEmptyName = NULL;
+
+  for (auto audio_filename : kAudioTestFiles) {
+    VideoDmpReader dmp_reader(ResolveTestFileName(audio_filename).c_str());
+    SB_DCHECK(dmp_reader.number_of_audio_buffers() > 0);
+
+    const auto* audio_sample_info = &dmp_reader.audio_sample_info();
+    if (IsMediaConfigSupported(kSbMediaVideoCodecNone, dmp_reader.audio_codec(),
+                               kSbDrmSystemInvalid, audio_sample_info,
+                               "", /* max_video_capabilities */
+                               kSbPlayerOutputModePunchOut)) {
+      test_configs.push_back(std::make_tuple(audio_filename, kEmptyName,
+                                             kSbPlayerOutputModePunchOut));
+    }
+  }
+
+  for (auto video_filename : kVideoTestFiles) {
+    VideoDmpReader dmp_reader(ResolveTestFileName(video_filename).c_str());
+    SB_DCHECK(dmp_reader.number_of_video_buffers() > 0);
+
+    for (auto output_mode : kOutputModes) {
+      if (IsMediaConfigSupported(dmp_reader.video_codec(),
+                                 kSbMediaAudioCodecNone, kSbDrmSystemInvalid,
+                                 NULL, /* audio_sample_info */
+                                 "",   /* max_video_capabilities */
+                                 output_mode)) {
+        test_configs.push_back(
+            std::make_tuple(kEmptyName, video_filename, output_mode));
+      }
+    }
+  }
+
+  return test_configs;
+}
+
 std::string ResolveTestFileName(const char* filename) {
   std::vector<char> content_path(kSbFileMaxPath);
   SB_CHECK(SbSystemGetPath(kSbSystemPathContentDirectory, content_path.data(),
@@ -45,10 +125,10 @@
                             SbPlayerDecoderState state,
                             int ticket) {}
 
-void DummyStatusFunc(SbPlayer player,
-                     void* context,
-                     SbPlayerState state,
-                     int ticket) {}
+void DummyPlayerStatusFunc(SbPlayer player,
+                           void* context,
+                           SbPlayerState state,
+                           int ticket) {}
 
 void DummyErrorFunc(SbPlayer player,
                     void* context,
@@ -116,5 +196,30 @@
 #endif  // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
 }
 
+bool IsMediaConfigSupported(SbMediaVideoCodec video_codec,
+                            SbMediaAudioCodec audio_codec,
+                            SbDrmSystem drm_system,
+                            const SbMediaAudioSampleInfo* audio_sample_info,
+                            const char* max_video_capabilities,
+                            SbPlayerOutputMode output_mode) {
+  if (audio_codec != kSbMediaAudioCodecNone &&
+      audio_sample_info->number_of_channels > SbAudioSinkGetMaxChannels()) {
+    return false;
+  }
+
+  atomic_bool error_occurred;
+  FakeGraphicsContextProvider fake_graphics_context_provider;
+  SbPlayer player = CallSbPlayerCreate(
+      fake_graphics_context_provider.window(), video_codec, audio_codec,
+      drm_system, audio_sample_info, max_video_capabilities,
+      DummyDeallocateSampleFunc, DummyDecoderStatusFunc, DummyPlayerStatusFunc,
+      ErrorFunc, &error_occurred, output_mode,
+      fake_graphics_context_provider.decoder_target_provider());
+  bool is_valid_player = SbPlayerIsValid(player);
+  SbPlayerDestroy(player);
+
+  return is_valid_player && !error_occurred.load();
+}
+
 }  // namespace nplb
 }  // namespace starboard
diff --git a/src/starboard/nplb/player_test_util.h b/src/starboard/nplb/player_test_util.h
index 335e414..ece5faf 100644
--- a/src/starboard/nplb/player_test_util.h
+++ b/src/starboard/nplb/player_test_util.h
@@ -16,6 +16,7 @@
 #define STARBOARD_NPLB_PLAYER_TEST_UTIL_H_
 
 #include <string>
+#include <tuple>
 #include <vector>
 
 #include "starboard/configuration_constants.h"
@@ -24,6 +25,13 @@
 namespace starboard {
 namespace nplb {
 
+typedef std::tuple<const char* /* audio_filename */,
+                   const char* /* video_filename */,
+                   SbPlayerOutputMode /* output_mode */>
+    SbPlayerTestConfig;
+
+std::vector<SbPlayerTestConfig> GetSupportedSbPlayerTestConfigs();
+
 std::string ResolveTestFileName(const char* filename);
 
 void DummyDeallocateSampleFunc(SbPlayer player,
@@ -64,6 +72,13 @@
 bool IsOutputModeSupported(SbPlayerOutputMode output_mode,
                            SbMediaVideoCodec codec);
 
+bool IsMediaConfigSupported(SbMediaVideoCodec video_codec,
+                            SbMediaAudioCodec audio_codec,
+                            SbDrmSystem drm_system,
+                            const SbMediaAudioSampleInfo* audio_sample_info,
+                            const char* max_video_capabilities,
+                            SbPlayerOutputMode output_mode);
+
 }  // namespace nplb
 }  // namespace starboard
 
diff --git a/src/starboard/nplb/player_write_sample_test.cc b/src/starboard/nplb/player_write_sample_test.cc
new file mode 100644
index 0000000..13977ee
--- /dev/null
+++ b/src/starboard/nplb/player_write_sample_test.cc
@@ -0,0 +1,520 @@
+// 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 <deque>
+#include <functional>
+
+#include "starboard/atomic.h"
+#include "starboard/common/optional.h"
+#include "starboard/common/queue.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/common/string.h"
+#include "starboard/nplb/player_test_util.h"
+#include "starboard/shared/starboard/player/video_dmp_reader.h"
+#include "starboard/string.h"
+#include "starboard/testing/fake_graphics_context_provider.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace nplb {
+namespace {
+
+using shared::starboard::player::video_dmp::VideoDmpReader;
+using testing::FakeGraphicsContextProvider;
+using ::testing::ValuesIn;
+
+const SbTimeMonotonic kDefaultWaitForDecoderStateNeedsDataTimeout =
+    5 * kSbTimeSecond;
+const SbTimeMonotonic kDefaultWaitForPlayerStateTimeout = 5 * kSbTimeSecond;
+const SbTimeMonotonic kDefaultWaitForCallbackEventTimeout =
+    15 * kSbTimeMillisecond;
+
+class SbPlayerWriteSampleTest
+    : public ::testing::TestWithParam<SbPlayerTestConfig> {
+ public:
+  SbPlayerWriteSampleTest();
+
+  void SetUp() override;
+  void TearDown() override;
+
+ protected:
+  struct CallbackEvent {
+    CallbackEvent() {}
+
+    CallbackEvent(SbPlayer player, SbPlayerState state, int ticket)
+        : player(player), player_state(state), ticket(ticket) {}
+
+    CallbackEvent(SbPlayer player, SbPlayerDecoderState state, int ticket)
+        : player(player), decoder_state(state), ticket(ticket) {}
+
+    bool HasStateUpdate() const {
+      return player_state.has_engaged() || decoder_state.has_engaged();
+    }
+
+    SbPlayer player = kSbPlayerInvalid;
+    optional<SbPlayerState> player_state;
+    optional<SbPlayerDecoderState> decoder_state;
+    int ticket = SB_PLAYER_INITIAL_TICKET;
+  };
+
+  static void DecoderStatusCallback(SbPlayer player,
+                                    void* context,
+                                    SbMediaType type,
+                                    SbPlayerDecoderState state,
+                                    int ticket);
+
+  static void PlayerStatusCallback(SbPlayer player,
+                                   void* context,
+                                   SbPlayerState state,
+                                   int ticket);
+
+  static void ErrorCallback(SbPlayer player,
+                            void* context,
+                            SbPlayerError error,
+                            const char* message);
+
+  void InitializePlayer();
+
+  void PrepareForSeek();
+
+  void Seek(const SbTime time);
+
+  // When the |output_mode| is decoding to texture, then this method is used to
+  // advance the decoded frames.
+  void GetDecodeTargetWhenSupported();
+
+  // Callback methods from the underlying player.
+  void OnDecoderState(SbPlayer player,
+                      SbMediaType media_type,
+                      SbPlayerDecoderState state,
+                      int ticket);
+  void OnPlayerState(SbPlayer player, SbPlayerState state, int ticket);
+  void OnError(SbPlayer player, SbPlayerError error, const char* message);
+
+  // Waits for |kSbPlayerDecoderStateNeedsData| to be sent.
+  void WaitForDecoderStateNeedsData(
+      const SbTime timeout = kDefaultWaitForDecoderStateNeedsDataTimeout);
+
+  // Waits for desired player state update to be sent.
+  void WaitForPlayerState(
+      const SbPlayerState desired_state,
+      const SbTime timeout = kDefaultWaitForPlayerStateTimeout);
+
+  // Player and Decoder methods for driving input and output.
+  void WriteSingleInput(size_t index);
+  void WriteEndOfStream();
+  void WriteMultipleInputs(size_t start_index, size_t num_inputs_to_write);
+  void DrainOutputs();
+
+  int GetNumBuffers() const;
+
+  SbMediaType GetTestMediaType() const { return test_media_type_; }
+
+  // Determine if the the current event is valid based on previously received
+  // player state updates, or other inputs to the player.
+  void AssertPlayerStateIsValid(SbPlayerState state) const;
+
+  bool HasReceivedPlayerState(SbPlayerState state) const {
+    return player_state_set_.find(state) != player_state_set_.end();
+  }
+
+  // Checks if there are pending callback events and, if so, logs the received
+  // state update in the appropriate tracking container:
+  // * |decoder_state_queue_| for SbPlayerDecoderState updates.
+  // * |player_state_set_| for SbPlayerState updates.
+  // Executes a blocking wait for any new CallbackEvents to be enqueued.
+  void TryProcessCallbackEvents(SbTime timeout);
+
+  // Queue of events from the underlying player.
+  Queue<CallbackEvent> callback_event_queue_;
+
+  // Ordered queue of pending decoder state updates.
+  std::deque<SbPlayerDecoderState> decoder_state_queue_;
+
+  // Set of received player state updates from the underlying player. This is
+  // used to check that the state updates occur in a valid order during normal
+  // playback.
+  std::set<SbPlayerState> player_state_set_;
+
+  // Test instance specific configuration.
+  std::string dmp_filename_;
+  SbMediaType test_media_type_;
+  SbPlayerOutputMode output_mode_;
+  scoped_ptr<VideoDmpReader> dmp_reader_;
+
+  FakeGraphicsContextProvider fake_graphics_context_provider_;
+  SbPlayer player_ = kSbPlayerInvalid;
+
+  bool destroy_player_called_ = false;
+  bool end_of_stream_written_ = false;
+  atomic_bool error_occurred_;
+  int ticket_ = SB_PLAYER_INITIAL_TICKET;
+};
+
+SbPlayerWriteSampleTest::SbPlayerWriteSampleTest() {
+  const char* audio_filename = std::get<0>(GetParam());
+  const char* video_filename = std::get<1>(GetParam());
+  output_mode_ = std::get<2>(GetParam());
+
+  SB_DCHECK(output_mode_ != kSbPlayerOutputModeInvalid);
+
+  if (audio_filename != NULL) {
+    SB_DCHECK(video_filename == NULL);
+
+    dmp_filename_ = audio_filename;
+    test_media_type_ = kSbMediaTypeAudio;
+  } else {
+    SB_DCHECK(video_filename != NULL);
+
+    dmp_filename_ = video_filename;
+    test_media_type_ = kSbMediaTypeVideo;
+  }
+  dmp_reader_.reset(
+      new VideoDmpReader(ResolveTestFileName(dmp_filename_.c_str()).c_str()));
+
+  SB_LOG(INFO) << FormatString(
+      "Initialize SbPlayerWriteSampleTest with dmp file '%s' and with output "
+      "mode '%s'.",
+      dmp_filename_.c_str(),
+      output_mode_ == kSbPlayerOutputModeDecodeToTexture ? "Decode To Texture"
+                                                         : "Punchout");
+}
+
+void SbPlayerWriteSampleTest::SetUp() {
+  SbMediaVideoCodec video_codec = dmp_reader_->video_codec();
+  SbMediaAudioCodec audio_codec = dmp_reader_->audio_codec();
+  const SbMediaAudioSampleInfo* audio_sample_info = NULL;
+
+  if (test_media_type_ == kSbMediaTypeAudio) {
+    SB_DCHECK(audio_codec != kSbMediaAudioCodecNone);
+
+    audio_sample_info = &dmp_reader_->audio_sample_info();
+    video_codec = kSbMediaVideoCodecNone;
+  } else {
+    SB_DCHECK(video_codec != kSbMediaVideoCodecNone);
+
+    audio_codec = kSbMediaAudioCodecNone;
+  }
+
+  player_ = CallSbPlayerCreate(
+      fake_graphics_context_provider_.window(), video_codec, audio_codec,
+      kSbDrmSystemInvalid, audio_sample_info, "", DummyDeallocateSampleFunc,
+      DecoderStatusCallback, PlayerStatusCallback, ErrorCallback, this,
+      output_mode_, fake_graphics_context_provider_.decoder_target_provider());
+
+  ASSERT_TRUE(SbPlayerIsValid(player_));
+
+  InitializePlayer();
+}
+
+void SbPlayerWriteSampleTest::TearDown() {
+  SB_DCHECK(SbPlayerIsValid(player_));
+
+  ASSERT_FALSE(destroy_player_called_);
+  destroy_player_called_ = true;
+  SbPlayerDestroy(player_);
+
+  // We expect the event to be sent already.
+  ASSERT_NO_FATAL_FAILURE(WaitForPlayerState(kSbPlayerStateDestroyed, 0));
+  ASSERT_FALSE(error_occurred_.load());
+}
+
+// static
+void SbPlayerWriteSampleTest::DecoderStatusCallback(SbPlayer player,
+                                                    void* context,
+                                                    SbMediaType type,
+                                                    SbPlayerDecoderState state,
+                                                    int ticket) {
+  SbPlayerWriteSampleTest* sb_player_write_sample_test =
+      static_cast<SbPlayerWriteSampleTest*>(context);
+  sb_player_write_sample_test->OnDecoderState(player, type, state, ticket);
+}
+
+// static
+void SbPlayerWriteSampleTest::PlayerStatusCallback(SbPlayer player,
+                                                   void* context,
+                                                   SbPlayerState state,
+                                                   int ticket) {
+  SbPlayerWriteSampleTest* sb_player_write_sample_test =
+      static_cast<SbPlayerWriteSampleTest*>(context);
+  sb_player_write_sample_test->OnPlayerState(player, state, ticket);
+}
+
+// static
+void SbPlayerWriteSampleTest::ErrorCallback(SbPlayer player,
+                                            void* context,
+                                            SbPlayerError error,
+                                            const char* message) {
+  SbPlayerWriteSampleTest* sb_player_write_sample_test =
+      static_cast<SbPlayerWriteSampleTest*>(context);
+  sb_player_write_sample_test->OnError(player, error, message);
+}
+
+void SbPlayerWriteSampleTest::InitializePlayer() {
+  ASSERT_FALSE(destroy_player_called_);
+  ASSERT_NO_FATAL_FAILURE(WaitForPlayerState(kSbPlayerStateInitialized));
+  Seek(0);
+  SbPlayerSetPlaybackRate(player_, 1.0);
+  SbPlayerSetVolume(player_, 1.0);
+}
+
+void SbPlayerWriteSampleTest::PrepareForSeek() {
+  ASSERT_FALSE(destroy_player_called_);
+  ASSERT_FALSE(HasReceivedPlayerState(kSbPlayerStateDestroyed));
+  ASSERT_TRUE(HasReceivedPlayerState(kSbPlayerStateInitialized));
+  player_state_set_.clear();
+  player_state_set_.insert(kSbPlayerStateInitialized);
+  ticket_++;
+}
+
+void SbPlayerWriteSampleTest::Seek(const SbTime time) {
+  PrepareForSeek();
+  SbPlayerSeek2(player_, time, ticket_);
+  ASSERT_NO_FATAL_FAILURE(WaitForDecoderStateNeedsData());
+  ASSERT_TRUE(decoder_state_queue_.empty());
+}
+
+void SbPlayerWriteSampleTest::GetDecodeTargetWhenSupported() {
+  if (destroy_player_called_) {
+    return;
+  }
+#if SB_HAS(GLES2)
+  fake_graphics_context_provider_.RunOnGlesContextThread([&]() {
+    ASSERT_TRUE(SbPlayerIsValid(player_));
+    if (output_mode_ != kSbPlayerOutputModeDecodeToTexture) {
+#if SB_API_VERSION >= 12
+      ASSERT_EQ(SbPlayerGetCurrentFrame(player_), kSbDecodeTargetInvalid);
+#endif  // SB_API_VERSION >= 12
+      return;
+    }
+    ASSERT_EQ(output_mode_, kSbPlayerOutputModeDecodeToTexture);
+    SbDecodeTarget frame = SbPlayerGetCurrentFrame(player_);
+    if (SbDecodeTargetIsValid(frame)) {
+      SbDecodeTargetRelease(frame);
+    }
+  });
+#endif  // SB_HAS(GLES2)
+}
+
+void SbPlayerWriteSampleTest::OnDecoderState(SbPlayer player,
+                                             SbMediaType type,
+                                             SbPlayerDecoderState state,
+                                             int ticket) {
+  switch (state) {
+    case kSbPlayerDecoderStateNeedsData:
+      callback_event_queue_.Put(CallbackEvent(player, state, ticket));
+      break;
+#if SB_API_VERSION < 12
+    // Note: we do not add these events to the queue since these states are not
+    // used in Cobalt and are being deprecated.
+    case kSbPlayerDecoderStateBufferFull:
+    case kSbPlayerDecoderStateDestroyed:
+      break;
+#endif  // SB_API_VERSION < 12
+  }
+}
+
+void SbPlayerWriteSampleTest::OnPlayerState(SbPlayer player,
+                                            SbPlayerState state,
+                                            int ticket) {
+  callback_event_queue_.Put(CallbackEvent(player, state, ticket));
+}
+
+void SbPlayerWriteSampleTest::OnError(SbPlayer player,
+                                      SbPlayerError error,
+                                      const char* message) {
+  SB_LOG(ERROR) << FormatString("Got SbPlayerError %d with message '%s'", error,
+                                message != NULL ? message : "");
+  error_occurred_.exchange(true);
+}
+
+void SbPlayerWriteSampleTest::WaitForDecoderStateNeedsData(
+    const SbTime timeout) {
+  optional<SbPlayerDecoderState> received_state;
+  SbTimeMonotonic start = SbTimeGetMonotonicNow();
+  do {
+    ASSERT_FALSE(error_occurred_.load());
+    GetDecodeTargetWhenSupported();
+    ASSERT_NO_FATAL_FAILURE(TryProcessCallbackEvents(
+        std::min(timeout, kDefaultWaitForCallbackEventTimeout)));
+    if (decoder_state_queue_.empty()) {
+      continue;
+    }
+    received_state = decoder_state_queue_.front();
+    decoder_state_queue_.pop_front();
+    if (received_state.value() == kSbPlayerDecoderStateNeedsData) {
+      break;
+    }
+  } while (SbTimeGetMonotonicNow() - start < timeout);
+
+  ASSERT_TRUE(received_state.has_engaged()) << "Did not receive any states.";
+  ASSERT_EQ(kSbPlayerDecoderStateNeedsData, received_state.value())
+      << "Did not receive expected state.";
+}
+
+void SbPlayerWriteSampleTest::WaitForPlayerState(
+    const SbPlayerState desired_state,
+    const SbTime timeout) {
+  SbTimeMonotonic start = SbTimeGetMonotonicNow();
+  do {
+    ASSERT_FALSE(error_occurred_.load());
+    GetDecodeTargetWhenSupported();
+    ASSERT_NO_FATAL_FAILURE(TryProcessCallbackEvents(
+        std::min(timeout, kDefaultWaitForCallbackEventTimeout)));
+    if (HasReceivedPlayerState(desired_state)) {
+      break;
+    }
+  } while (SbTimeGetMonotonicNow() - start < timeout);
+  ASSERT_TRUE(HasReceivedPlayerState(desired_state))
+      << "Did not received expected state.";
+}
+
+void SbPlayerWriteSampleTest::WriteSingleInput(size_t index) {
+  ASSERT_FALSE(destroy_player_called_);
+  ASSERT_LT(index, GetNumBuffers());
+  SbPlayerSampleInfo sample_info =
+      dmp_reader_->GetPlayerSampleInfo(test_media_type_, index);
+  SbPlayerWriteSample2(player_, test_media_type_, &sample_info, 1);
+}
+
+void SbPlayerWriteSampleTest::WriteEndOfStream() {
+  ASSERT_FALSE(destroy_player_called_);
+  ASSERT_FALSE(end_of_stream_written_);
+  end_of_stream_written_ = true;
+  SbPlayerWriteEndOfStream(player_, test_media_type_);
+}
+
+void SbPlayerWriteSampleTest::WriteMultipleInputs(size_t start_index,
+                                                  size_t num_inputs_to_write) {
+  SB_DCHECK(num_inputs_to_write > 0);
+  SB_DCHECK(start_index < GetNumBuffers());
+
+  ASSERT_NO_FATAL_FAILURE(WriteSingleInput(start_index));
+  ++start_index;
+  --num_inputs_to_write;
+
+  while (num_inputs_to_write > 0 && start_index < GetNumBuffers()) {
+    ASSERT_NO_FATAL_FAILURE(WaitForDecoderStateNeedsData());
+    ASSERT_NO_FATAL_FAILURE(WriteSingleInput(start_index));
+    ++start_index;
+    --num_inputs_to_write;
+  }
+}
+
+void SbPlayerWriteSampleTest::DrainOutputs() {
+  ASSERT_TRUE(end_of_stream_written_);
+  ASSERT_NO_FATAL_FAILURE(WaitForPlayerState(kSbPlayerStateEndOfStream));
+  // We should not get any new decoder events after end of stream.
+  ASSERT_TRUE(decoder_state_queue_.empty());
+}
+
+int SbPlayerWriteSampleTest::GetNumBuffers() const {
+  return test_media_type_ == kSbMediaTypeAudio
+             ? dmp_reader_->number_of_audio_buffers()
+             : dmp_reader_->number_of_video_buffers();
+}
+
+void SbPlayerWriteSampleTest::AssertPlayerStateIsValid(
+    SbPlayerState state) const {
+  // Note: it is possible to receive the same state that has been previously
+  // received in the case of multiple Seek() calls. Prior to any Seek commands
+  // issued in this test, we should reset the |player_state_set_| member.
+  ASSERT_FALSE(HasReceivedPlayerState(state));
+
+  switch (state) {
+    case kSbPlayerStateInitialized:
+      // No other states have been received before getting Initialized.
+      ASSERT_TRUE(player_state_set_.empty());
+      break;
+    case kSbPlayerStatePrerolling:
+      ASSERT_TRUE(HasReceivedPlayerState(kSbPlayerStateInitialized));
+      ASSERT_FALSE(HasReceivedPlayerState(kSbPlayerStateDestroyed));
+      break;
+    case kSbPlayerStatePresenting:
+      ASSERT_TRUE(HasReceivedPlayerState(kSbPlayerStatePrerolling));
+      ASSERT_FALSE(HasReceivedPlayerState(kSbPlayerStateDestroyed));
+      break;
+    case kSbPlayerStateEndOfStream:
+      ASSERT_TRUE(HasReceivedPlayerState(kSbPlayerStateInitialized));
+      ASSERT_TRUE(HasReceivedPlayerState(kSbPlayerStatePrerolling));
+      ASSERT_FALSE(HasReceivedPlayerState(kSbPlayerStateDestroyed));
+      break;
+    case kSbPlayerStateDestroyed:
+      // Nothing stops the user of the player from destroying the player during
+      // any of the previous states.
+      ASSERT_TRUE(destroy_player_called_);
+      break;
+  }
+}
+
+void SbPlayerWriteSampleTest::TryProcessCallbackEvents(SbTime timeout) {
+  for (;;) {
+    auto event = callback_event_queue_.GetTimed(timeout);
+    if (!event.HasStateUpdate()) {
+      break;
+    }
+    if (event.ticket != ticket_) {
+      continue;
+    }
+    ASSERT_EQ(event.player, player_);
+    SB_DCHECK(event.decoder_state.has_engaged() ^
+              event.player_state.has_engaged());
+    if (event.decoder_state.has_engaged()) {
+      // Callbacks may be in-flight at the time that the player is destroyed by
+      // a call to |SbPlayerDestroy|. In this case, the callbacks are ignored.
+      // However no new callbacks are expected after receiving the player status
+      // |kSbPlayerStateDestroyed|.
+      ASSERT_FALSE(HasReceivedPlayerState(kSbPlayerStateDestroyed));
+      decoder_state_queue_.push_back(event.decoder_state.value());
+      continue;
+    }
+    ASSERT_NO_FATAL_FAILURE(
+        AssertPlayerStateIsValid(event.player_state.value()));
+    player_state_set_.insert(event.player_state.value());
+  }
+}
+
+TEST_P(SbPlayerWriteSampleTest, SeekAndDestroy) {
+  PrepareForSeek();
+  SbPlayerSeek2(player_, 1 * kSbTimeSecond, ticket_);
+}
+
+TEST_P(SbPlayerWriteSampleTest, NoInput) {
+  WriteEndOfStream();
+  DrainOutputs();
+}
+
+TEST_P(SbPlayerWriteSampleTest, SingleInput) {
+  ASSERT_NO_FATAL_FAILURE(WriteSingleInput(0));
+  ASSERT_NO_FATAL_FAILURE(WaitForDecoderStateNeedsData());
+  WriteEndOfStream();
+  DrainOutputs();
+}
+
+TEST_P(SbPlayerWriteSampleTest, MultipleInputs) {
+  ASSERT_NO_FATAL_FAILURE(
+      WriteMultipleInputs(0, std::min<size_t>(10, GetNumBuffers())));
+  ASSERT_NO_FATAL_FAILURE(WaitForDecoderStateNeedsData());
+  WriteEndOfStream();
+  DrainOutputs();
+}
+
+INSTANTIATE_TEST_CASE_P(SbPlayerWriteSampleTests,
+                        SbPlayerWriteSampleTest,
+                        ValuesIn(GetSupportedSbPlayerTestConfigs()));
+
+}  // namespace
+}  // namespace nplb
+}  // namespace starboard
diff --git a/src/starboard/nplb/system_get_path_test.cc b/src/starboard/nplb/system_get_path_test.cc
index 7231d01..55ca991 100644
--- a/src/starboard/nplb/system_get_path_test.cc
+++ b/src/starboard/nplb/system_get_path_test.cc
@@ -16,9 +16,9 @@
 
 #include <algorithm>
 
+#include "starboard/common/file.h"
 #include "starboard/common/string.h"
 #include "starboard/configuration_constants.h"
-#include "starboard/file.h"
 #include "starboard/memory.h"
 #include "starboard/nplb/file_helpers.h"
 #include "starboard/system.h"
diff --git a/src/starboard/raspi/shared/gyp_configuration.gypi b/src/starboard/raspi/shared/gyp_configuration.gypi
index deb191c..d2dd46b 100644
--- a/src/starboard/raspi/shared/gyp_configuration.gypi
+++ b/src/starboard/raspi/shared/gyp_configuration.gypi
@@ -76,7 +76,8 @@
       # libraries.
       '-L<(sysroot)/opt/vc/lib',
       '-Wl,-rpath=<(sysroot)/opt/vc/lib',
-
+      # Cleanup unused sections
+      '-Wl,-gc-sections',
       # We don't wrap these symbols, but this ensures that they aren't
       # linked in.
       '-Wl,--wrap=malloc',
@@ -107,9 +108,15 @@
     ],
     'compiler_flags_qa_size': [
       '-Os',
+      # Compile symbols in separate sections
+      '-ffunction-sections',
+      '-fdata-sections',
     ],
     'compiler_flags_qa_speed': [
       '-O2',
+      # Compile symbols in separate sections
+      '-ffunction-sections',
+      '-fdata-sections',
     ],
     'compiler_flags_cc_qa': [
       '-fno-rtti',
@@ -119,9 +126,15 @@
     ],
     'compiler_flags_gold_size': [
       '-Os',
+      # Compile symbols in separate sections
+      '-ffunction-sections',
+      '-fdata-sections',
     ],
     'compiler_flags_gold_speed': [
       '-O2',
+      # Compile symbols in separate sections
+      '-ffunction-sections',
+      '-fdata-sections',
     ],
     'compiler_flags_cc_gold': [
       '-fno-rtti',
diff --git a/src/starboard/raspi/shared/gyp_configuration.py b/src/starboard/raspi/shared/gyp_configuration.py
index b13b62e..faacb40 100644
--- a/src/starboard/raspi/shared/gyp_configuration.py
+++ b/src/starboard/raspi/shared/gyp_configuration.py
@@ -72,18 +72,22 @@
       self.host_compiler_environment = build.GetHostCompilerEnvironment(
           clang_specification.GetClangSpecification(), self.build_accelerator)
 
-    toolchain = os.path.realpath(os.path.join(
-        self.raspi_home,
-        'tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64'))
+    toolchain = os.path.realpath(
+        os.path.join(
+            self.raspi_home,
+            'tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64'))
     toolchain_bin_dir = os.path.join(toolchain, 'bin')
 
     env_variables = self.host_compiler_environment
     env_variables.update({
-        'CC': self.build_accelerator + ' ' + os.path.join(toolchain_bin_dir,
-            'arm-linux-gnueabihf-gcc'),
-        'CXX': self.build_accelerator + ' ' + os.path.join(toolchain_bin_dir,
-            'arm-linux-gnueabihf-g++'),
-        'STRIP': os.path.join(toolchain_bin_dir, 'arm-linux-gnueabihf-strip'),
+        'CC':
+            self.build_accelerator + ' ' +
+            os.path.join(toolchain_bin_dir, 'arm-linux-gnueabihf-gcc'),
+        'CXX':
+            self.build_accelerator + ' ' +
+            os.path.join(toolchain_bin_dir, 'arm-linux-gnueabihf-g++'),
+        'STRIP':
+            os.path.join(toolchain_bin_dir, 'arm-linux-gnueabihf-strip'),
     })
     return env_variables
 
@@ -152,6 +156,9 @@
   __FILTERED_TESTS = {  # pylint: disable=invalid-name
       'nplb': [
           'SbDrmTest.AnySupportedKeySystems',
+          'SbMediaCanPlayMimeAndKeySystem.AnySupportedKeySystems',
+          'SbMediaCanPlayMimeAndKeySystem.KeySystemWithAttributes',
+          'SbMediaCanPlayMimeAndKeySystem.MinimumSupport',
       ],
       'player_filter_tests': [
           # The implementations for the raspberry pi (0 and 2) are incomplete
diff --git a/src/starboard/raspi/shared/system_get_property.cc b/src/starboard/raspi/shared/system_get_property.cc
index 6fa8f5d..ac31872 100644
--- a/src/starboard/raspi/shared/system_get_property.cc
+++ b/src/starboard/raspi/shared/system_get_property.cc
@@ -154,16 +154,8 @@
       return CopyStringAndTestIfSuccess(out_value, value_length, kFriendlyName);
 
     case kSbSystemPropertyPlatformName: {
-      // Example output: "Raspian Linux armv7l".
-      utsname name;
-
-      if (uname(&name) == -1)
-        return false;
-
-      std::string temp =
-          starboard::FormatString("Raspian %s %s", name.sysname, name.machine);
-
-      return CopyStringAndTestIfSuccess(out_value, value_length, temp.c_str());
+      return CopyStringAndTestIfSuccess(out_value, value_length,
+                                        "X11; Linux armv7l");
     }
 
     default:
diff --git a/src/starboard/shared/linux/system_get_used_cpu_memory.cc b/src/starboard/shared/linux/system_get_used_cpu_memory.cc
index 5205355..3a7ad2e 100644
--- a/src/starboard/shared/linux/system_get_used_cpu_memory.cc
+++ b/src/starboard/shared/linux/system_get_used_cpu_memory.cc
@@ -21,9 +21,9 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include "starboard/common/file.h"
 #include "starboard/common/log.h"
 #include "starboard/common/string.h"
-#include "starboard/file.h"
 
 // We find the current amount of used memory on Linux by opening
 // '/proc/self/status' and scan the file for its "VmRSS" and "VmSwap" entries.
diff --git a/src/starboard/shared/pthread/mutex_acquire.cc b/src/starboard/shared/pthread/mutex_acquire.cc
index 67f26b4..6183f7d 100644
--- a/src/starboard/shared/pthread/mutex_acquire.cc
+++ b/src/starboard/shared/pthread/mutex_acquire.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/common/mutex.h"
+#include "starboard/mutex.h"
 
 #include <pthread.h>
 
diff --git a/src/starboard/shared/pthread/mutex_acquire_try.cc b/src/starboard/shared/pthread/mutex_acquire_try.cc
index 4c216a5..8c5c241 100644
--- a/src/starboard/shared/pthread/mutex_acquire_try.cc
+++ b/src/starboard/shared/pthread/mutex_acquire_try.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/common/mutex.h"
+#include "starboard/mutex.h"
 
 #include <pthread.h>
 
diff --git a/src/starboard/shared/pthread/mutex_create.cc b/src/starboard/shared/pthread/mutex_create.cc
index e72884d..55b6a94 100644
--- a/src/starboard/shared/pthread/mutex_create.cc
+++ b/src/starboard/shared/pthread/mutex_create.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/common/mutex.h"
+#include "starboard/mutex.h"
 
 #include <pthread.h>
 
diff --git a/src/starboard/shared/pthread/mutex_destroy.cc b/src/starboard/shared/pthread/mutex_destroy.cc
index c131595..f2d0b15 100644
--- a/src/starboard/shared/pthread/mutex_destroy.cc
+++ b/src/starboard/shared/pthread/mutex_destroy.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/common/mutex.h"
+#include "starboard/mutex.h"
 
 #include <pthread.h>
 
diff --git a/src/starboard/shared/pthread/mutex_release.cc b/src/starboard/shared/pthread/mutex_release.cc
index fcd7de7..d2ce183 100644
--- a/src/starboard/shared/pthread/mutex_release.cc
+++ b/src/starboard/shared/pthread/mutex_release.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/common/mutex.h"
+#include "starboard/mutex.h"
 
 #include <pthread.h>
 
diff --git a/src/starboard/shared/starboard/link_receiver.cc b/src/starboard/shared/starboard/link_receiver.cc
index bb97f5d..d5ddadd 100644
--- a/src/starboard/shared/starboard/link_receiver.cc
+++ b/src/starboard/shared/starboard/link_receiver.cc
@@ -18,13 +18,13 @@
 #include <unordered_map>
 
 #include "starboard/atomic.h"
+#include "starboard/common/file.h"
 #include "starboard/common/log.h"
 #include "starboard/common/scoped_ptr.h"
 #include "starboard/common/semaphore.h"
 #include "starboard/common/socket.h"
 #include "starboard/common/string.h"
 #include "starboard/configuration_constants.h"
-#include "starboard/file.h"
 #include "starboard/shared/starboard/application.h"
 #include "starboard/socket_waiter.h"
 #include "starboard/system.h"
diff --git a/src/starboard/shared/starboard/localized_strings.cc b/src/starboard/shared/starboard/localized_strings.cc
index 2d7fb02..838c4e4 100644
--- a/src/starboard/shared/starboard/localized_strings.cc
+++ b/src/starboard/shared/starboard/localized_strings.cc
@@ -16,8 +16,8 @@
 
 #include <algorithm>
 
+#include "starboard/common/file.h"
 #include "starboard/common/log.h"
-#include "starboard/file.h"
 #include "starboard/system.h"
 #include "starboard/types.h"
 
diff --git a/src/starboard/shared/starboard/media/codec_util.cc b/src/starboard/shared/starboard/media/codec_util.cc
index 64a649b..7d192fd 100644
--- a/src/starboard/shared/starboard/media/codec_util.cc
+++ b/src/starboard/shared/starboard/media/codec_util.cc
@@ -499,10 +499,10 @@
     return true;
   }
 
-  // 6. Parse chroma subsampling, which we only support 01.
+  // 6. Parse chroma subsampling, which we only support 00 and 01.
   // Note that this value is not returned.
   int chroma;
-  if (!ReadTwoDigitDecimal(codec + 14, &chroma) || chroma != 1) {
+  if (!ReadTwoDigitDecimal(codec + 14, &chroma) || (chroma != 0 && chroma != 1)) {
     return false;
   }
 
diff --git a/src/starboard/shared/starboard/media/media_util.cc b/src/starboard/shared/starboard/media/media_util.cc
index 9b721cb..3269d04 100644
--- a/src/starboard/shared/starboard/media/media_util.cc
+++ b/src/starboard/shared/starboard/media/media_util.cc
@@ -373,8 +373,11 @@
   }
 
   if (codecs.size() == 0) {
-    // This is a progressive query.  We only support "video/mp4" in this case.
-    if (mime_type.type() == "video" && mime_type.subtype() == "mp4") {
+    // This happens when the H5 player is either querying for progressive
+    // playback support, or probing for generic mp4 support without specific
+    // codecs.  We only support "audio/mp4" and "video/mp4" for these cases.
+    if ((mime_type.type() == "audio" || mime_type.type() == "video") &&
+        mime_type.subtype() == "mp4") {
       return kSbMediaSupportTypeMaybe;
     }
     return kSbMediaSupportTypeNotSupported;
diff --git a/src/starboard/shared/starboard/player/file_cache_reader.h b/src/starboard/shared/starboard/player/file_cache_reader.h
index c9e2a08..a32b222 100644
--- a/src/starboard/shared/starboard/player/file_cache_reader.h
+++ b/src/starboard/shared/starboard/player/file_cache_reader.h
@@ -18,8 +18,8 @@
 #include <string>
 #include <vector>
 
+#include "starboard/common/file.h"
 #include "starboard/common/scoped_ptr.h"
-#include "starboard/file.h"
 
 namespace starboard {
 namespace shared {
diff --git a/src/starboard/shared/starboard/player/filter/player_components.h b/src/starboard/shared/starboard/player/filter/player_components.h
index 567c4a1..e4c0eef 100644
--- a/src/starboard/shared/starboard/player/filter/player_components.h
+++ b/src/starboard/shared/starboard/player/filter/player_components.h
@@ -197,9 +197,10 @@
         scoped_refptr<VideoRendererSink>* video_renderer_sink);
 
     // Check AudioRenderer ctor for more details on the parameters.
-    void GetAudioRendererParams(const CreationParameters& creation_parameters,
-                                int* max_cached_frames,
-                                int* min_frames_per_append) const;
+    virtual void GetAudioRendererParams(
+        const CreationParameters& creation_parameters,
+        int* max_cached_frames,
+        int* min_frames_per_append) const;
 
    private:
     SB_DISALLOW_COPY_AND_ASSIGN(Factory);
diff --git a/src/starboard/shared/starboard/player/filter/testing/file_cache_reader_test.cc b/src/starboard/shared/starboard/player/filter/testing/file_cache_reader_test.cc
index 8506b7a..c8ac858 100644
--- a/src/starboard/shared/starboard/player/filter/testing/file_cache_reader_test.cc
+++ b/src/starboard/shared/starboard/player/filter/testing/file_cache_reader_test.cc
@@ -18,8 +18,8 @@
 #include <string>
 #include <vector>
 
+#include "starboard/common/file.h"
 #include "starboard/configuration_constants.h"
-#include "starboard/file.h"
 #include "starboard/memory.h"
 #include "starboard/shared/starboard/player/filter/testing/test_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/src/starboard/shared/starboard/player/filter/testing/video_decoder_test_fixture.h b/src/starboard/shared/starboard/player/filter/testing/video_decoder_test_fixture.h
index 1450b58..49dbd09 100644
--- a/src/starboard/shared/starboard/player/filter/testing/video_decoder_test_fixture.h
+++ b/src/starboard/shared/starboard/player/filter/testing/video_decoder_test_fixture.h
@@ -86,7 +86,11 @@
                           SbPlayerOutputMode output_mode,
                           bool using_stub_decoder);
 
-  ~VideoDecoderTestFixture() { video_decoder_->Reset(); }
+  ~VideoDecoderTestFixture() {
+    if (video_decoder_) {
+      video_decoder_->Reset();
+    }
+  }
 
   void Initialize();
 
diff --git a/src/starboard/shared/starboard/player/video_dmp_reader.h b/src/starboard/shared/starboard/player/video_dmp_reader.h
index 218384d..6770123 100644
--- a/src/starboard/shared/starboard/player/video_dmp_reader.h
+++ b/src/starboard/shared/starboard/player/video_dmp_reader.h
@@ -19,6 +19,7 @@
 #include <string>
 #include <vector>
 
+#include "starboard/common/file.h"
 #include "starboard/common/log.h"
 #include "starboard/common/mutex.h"
 #include "starboard/common/optional.h"
diff --git a/src/starboard/shared/stub/mutex_acquire.cc b/src/starboard/shared/stub/mutex_acquire.cc
index cede4d1..423202e 100644
--- a/src/starboard/shared/stub/mutex_acquire.cc
+++ b/src/starboard/shared/stub/mutex_acquire.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/common/mutex.h"
+#include "starboard/mutex.h"
 
 SbMutexResult SbMutexAcquire(SbMutex* mutex) {
   return kSbMutexDestroyed;
diff --git a/src/starboard/shared/stub/mutex_acquire_try.cc b/src/starboard/shared/stub/mutex_acquire_try.cc
index a9f1ba8..efc87b6 100644
--- a/src/starboard/shared/stub/mutex_acquire_try.cc
+++ b/src/starboard/shared/stub/mutex_acquire_try.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/common/mutex.h"
+#include "starboard/mutex.h"
 
 SbMutexResult SbMutexAcquireTry(SbMutex* mutex) {
   return kSbMutexDestroyed;
diff --git a/src/starboard/shared/stub/mutex_create.cc b/src/starboard/shared/stub/mutex_create.cc
index 11586de..4439ee0 100644
--- a/src/starboard/shared/stub/mutex_create.cc
+++ b/src/starboard/shared/stub/mutex_create.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/common/mutex.h"
+#include "starboard/mutex.h"
 
 bool SbMutexCreate(SbMutex* mutex) {
   return false;
diff --git a/src/starboard/shared/stub/mutex_destroy.cc b/src/starboard/shared/stub/mutex_destroy.cc
index 7922ddd..b88ae22 100644
--- a/src/starboard/shared/stub/mutex_destroy.cc
+++ b/src/starboard/shared/stub/mutex_destroy.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/common/mutex.h"
+#include "starboard/mutex.h"
 
 bool SbMutexDestroy(SbMutex* mutex) {
   return false;
diff --git a/src/starboard/shared/stub/mutex_release.cc b/src/starboard/shared/stub/mutex_release.cc
index 88e6071..207b642 100644
--- a/src/starboard/shared/stub/mutex_release.cc
+++ b/src/starboard/shared/stub/mutex_release.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "starboard/common/mutex.h"
+#include "starboard/mutex.h"
 
 bool SbMutexRelease(SbMutex* mutex) {
   return false;
diff --git a/src/starboard/shared/widevine/drm_system_widevine.cc b/src/starboard/shared/widevine/drm_system_widevine.cc
index c3910d3..4a8eaf1 100644
--- a/src/starboard/shared/widevine/drm_system_widevine.cc
+++ b/src/starboard/shared/widevine/drm_system_widevine.cc
@@ -26,6 +26,7 @@
 #include "starboard/memory.h"
 #include "starboard/once.h"
 #include "starboard/shared/starboard/application.h"
+#include "starboard/shared/starboard/media/mime_type.h"
 #include "starboard/shared/widevine/widevine_storage.h"
 #include "starboard/shared/widevine/widevine_timer.h"
 #include "starboard/time.h"
@@ -40,7 +41,7 @@
 namespace {
 
 const int kInitializationVectorSize = 16;
-const char* kWidevineKeySystem[] = {"com.widevine", "com.widevine.alpha"};
+const char* kWidevineKeySystems[] = {"com.widevine", "com.widevine.alpha"};
 const char kWidevineStorageFileName[] = "wvcdm.dat";
 
 // Key usage may be blocked due to incomplete HDCP authentication which could
@@ -264,8 +265,28 @@
 
 // static
 bool DrmSystemWidevine::IsKeySystemSupported(const char* key_system) {
-  for (auto wv_key_system : kWidevineKeySystem) {
-    if (SbStringCompareAll(key_system, wv_key_system) == 0) {
+  SB_DCHECK(key_system);
+
+  // It is possible that the |key_system| comes with extra attributes, like
+  // `com.widevine.alpha; encryptionscheme="cenc"`.  We prepend "key_system/"
+  // to it, so it can be parsed by MimeType.
+  starboard::media::MimeType mime_type(std::string("key_system/") + key_system);
+
+  if (!mime_type.is_valid()) {
+    return false;
+  }
+  SB_DCHECK(mime_type.type() == "key_system");
+
+  for (auto wv_key_system : kWidevineKeySystems) {
+    if (mime_type.subtype() == wv_key_system) {
+      for (int i = 0; i < mime_type.GetParamCount(); ++i) {
+        if (mime_type.GetParamName(i) == "encryptionscheme") {
+          auto value = mime_type.GetParamStringValue(i);
+          if (value != "cenc" && value != "cbcs" && value != "cbcs-1-9") {
+            return false;
+          }
+        }
+      }
       return true;
     }
   }
diff --git a/src/starboard/shared/widevine/widevine_storage.cc b/src/starboard/shared/widevine/widevine_storage.cc
index 91819e7..d6c8a3d 100644
--- a/src/starboard/shared/widevine/widevine_storage.cc
+++ b/src/starboard/shared/widevine/widevine_storage.cc
@@ -14,8 +14,8 @@
 
 #include "starboard/shared/widevine/widevine_storage.h"
 
+#include "starboard/common/file.h"
 #include "starboard/common/log.h"
-#include "starboard/file.h"
 #include "starboard/shared/widevine/widevine_keybox_hash.h"
 #include "starboard/types.h"
 
diff --git a/src/starboard/shared/x11/application_x11.cc b/src/starboard/shared/x11/application_x11.cc
index 5e78b9b..030a2ed 100644
--- a/src/starboard/shared/x11/application_x11.cc
+++ b/src/starboard/shared/x11/application_x11.cc
@@ -41,6 +41,10 @@
 #include "starboard/shared/x11/window_internal.h"
 #include "starboard/time.h"
 
+namespace {
+const char kTouchscreenPointerSwitch[] = "touchscreen_pointer";
+}
+
 namespace starboard {
 namespace shared {
 namespace x11 {
@@ -720,6 +724,7 @@
     // evdev input will be sent to the first created window only.
     dev_input_.reset(DevInput::Create(window, ConnectionNumber(display_)));
   }
+  touchscreen_pointer_ = GetCommandLine()->HasSwitch(kTouchscreenPointerSwitch);
   return window;
 }
 
@@ -1273,7 +1278,8 @@
       data->key = XButtonEventToSbKey(x_button_event);
       data->type =
           is_press_event ? kSbInputEventTypePress : kSbInputEventTypeUnpress;
-      data->device_type = kSbInputDeviceTypeMouse;
+      data->device_type = touchscreen_pointer_ ? kSbInputDeviceTypeTouchScreen
+                                               : kSbInputDeviceTypeMouse;
       if (is_wheel_event) {
         data->pressure = NAN;
         data->size = {NAN, NAN};
@@ -1299,11 +1305,17 @@
       data->size = {NAN, NAN};
       data->tilt = {NAN, NAN};
       data->type = kSbInputEventTypeMove;
-      data->device_type = kSbInputDeviceTypeMouse;
+      data->device_type = touchscreen_pointer_ ? kSbInputDeviceTypeTouchScreen
+                                               : kSbInputDeviceTypeMouse;
       data->device_id = kMouseDeviceId;
       data->key_modifiers = XEventStateToSbKeyModifiers(x_motion_event->state);
       data->position.x = x_motion_event->x;
       data->position.y = x_motion_event->y;
+      if (touchscreen_pointer_ && !data->key_modifiers) {
+        // For touch screens, only report motion events when a button is
+        // pressed.
+        return NULL;
+      }
       return new Event(kSbEventTypeInput, data.release(),
                        &DeleteDestructor<SbInputData>);
     }
diff --git a/src/starboard/shared/x11/application_x11.h b/src/starboard/shared/x11/application_x11.h
index 75934d3..875dd20 100644
--- a/src/starboard/shared/x11/application_x11.h
+++ b/src/starboard/shared/x11/application_x11.h
@@ -155,6 +155,9 @@
 
   // The /dev/input input handler. Only set when there is an open window.
   scoped_ptr<::starboard::shared::dev_input::DevInput> dev_input_;
+
+  // Indicates whether pointer input is from a touchscreen.
+  bool touchscreen_pointer_;
 };
 
 }  // namespace x11
diff --git a/src/starboard/stub/configuration.gni b/src/starboard/stub/configuration.gni
index 9babbcb..774ab8a 100644
--- a/src/starboard/stub/configuration.gni
+++ b/src/starboard/stub/configuration.gni
@@ -18,9 +18,3 @@
 # Use media source extension implementation that is conformed to the
 # Candidate Recommendation of July 5th 2016.
 cobalt_use_media_source_2016 = true
-
-declare_args() {
-  # Set to true to enable distributed compilation using Goma. By default we
-  # use Goma for stub and linux.
-  use_goma = true
-}
diff --git a/src/starboard/tools/command_line.py b/src/starboard/tools/command_line.py
index cecccb5..ddb4b32 100644
--- a/src/starboard/tools/command_line.py
+++ b/src/starboard/tools/command_line.py
@@ -23,8 +23,18 @@
 import starboard.tools.platform
 
 
+def AddLoggingArguments(arg_parser):
+  arg_parser.add_argument(
+      '--log_level',
+      choices=['debug', 'warning', 'error', 'critical'],
+      default='info',
+      help='The minimum level a log statement must be to be output. This value '
+      "is used to initialize the 'logging' module log level.")
+
+
 def AddPlatformConfigArguments(arg_parser):
   """Adds the platform configuration arguments required for building."""
+  AddLoggingArguments(arg_parser)
   default_config, default_platform = build.GetDefaultConfigAndPlatform()
   arg_parser.add_argument(
       '-p',
diff --git a/src/starboard/tools/goma.py b/src/starboard/tools/goma.py
deleted file mode 100644
index 183ff3f..0000000
--- a/src/starboard/tools/goma.py
+++ /dev/null
@@ -1,130 +0,0 @@
-#
-# Copyright 2019 The Cobalt Authors. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-"""Goma specific definitions and helper functions.
-
-Goma is used for faster builds.  Install goma and add directory to PATH.
-Provides common functions to setup goma across platform builds.
-It checks that goma is enabled and installed, and starts the
-goma proxy.
-
-Override settings using environment variables.
-USE_GOMA  To enable/disable goma. Default is None.
-"""
-
-import logging
-import os
-import subprocess
-import sys
-import util
-
-from starboard.tools import build_accelerator
-
-
-class Goma(build_accelerator.BuildAccelerator):
-  """Goma is a distributed build accelerator."""
-
-  def GetName(self):
-    return 'gomacc'
-
-  def Use(self):
-    return FindAndStartGoma()
-
-
-def _GomaEnabledFromEnv():
-  """Enable goma if USE_GOMA is defined.
-
-  Returns:
-    True to enable goma, defaults to False.
-  """
-  if 'USE_GOMA' in os.environ:
-    return os.environ['USE_GOMA'] == '1'
-  return False
-
-
-def _GetGomaFromPath():
-  """Returns goma directory from PATH, otherwise is None."""
-  gomacc_path = util.Which('gomacc')
-  if gomacc_path is not None:
-    return os.path.dirname(gomacc_path)
-  return None
-
-
-def _GomaInstalled(goma_dir):
-  """Returns True if goma is installed, otherwise is False."""
-  if goma_dir and os.path.isdir(goma_dir):
-    if os.path.isfile(GomaControlFile(goma_dir)):
-      logging.error('Using Goma installed at: %s', goma_dir)
-      return True
-  logging.error('Failed to find goma dir. Check PATH location: %s', goma_dir)
-  return False
-
-
-def GomaControlFile(goma_dir):
-  """Returns path to goma control script."""
-  return os.path.join(goma_dir, 'goma_ctl.py')
-
-
-def _GomaEnsureStart(goma_dir):
-  """Starts the goma proxy.
-
-  Checks the proxy status and tries to start if not running.
-
-  Args:
-    goma_dir: goma install directory
-
-  Returns:
-    True if goma started, otherwise False.
-  """
-  if not _GomaInstalled(goma_dir):
-    return False
-
-  goma_ctl = GomaControlFile(goma_dir)
-  command = [goma_ctl, 'ensure_start']
-  logging.error('starting goma proxy...')
-
-  try:
-    subprocess.check_call(command)
-    return True
-  except subprocess.CalledProcessError as e:
-    logging.error('Goma proxy failed to start.\nCommand: %s\n%s',
-                  ' '.join(e.cmd), e.output)
-    return False
-
-
-def FindAndStartGoma(enable_in_path=True, exit_on_failed_start=False):
-  """Uses goma if installed and proxy is running.
-
-  Args:
-    enable_in_path: If True, enable goma if found in PATH. Otherwise,
-                    it enables it when USE_GOMA=1 is set.
-    exit_on_failed_start: Boolean to exit if goma is enabled, but wasn't
-                            started.
-  Returns:
-    True if goma is enabled and running, otherwise False.
-  """
-  if enable_in_path or _GomaEnabledFromEnv():
-    goma_dir = _GetGomaFromPath()
-    if _GomaEnsureStart(goma_dir):
-      return True
-    else:
-      logging.critical('goma was enabled, but failed to start.')
-      if exit_on_failed_start:
-        sys.exit(1)
-      return False
-  else:
-    logging.info('Goma is disabled. To enable, check for gomacc in PATH '
-                 'and/or set USE_GOMA=1.')
-    return False
diff --git a/src/starboard/tools/log_level.py b/src/starboard/tools/log_level.py
new file mode 100644
index 0000000..6bb05e0
--- /dev/null
+++ b/src/starboard/tools/log_level.py
@@ -0,0 +1,49 @@
+#!/usr/bin/python
+#
+# 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.
+"""Provides a consistent mechanism for the initialize of logging."""
+
+import logging
+import _env  # pylint: disable=unused-import
+
+_NAME_TO_LEVEL = {
+    'info': logging.INFO,
+    'debug': logging.DEBUG,
+    'warning': logging.WARNING,
+    'error': logging.ERROR,
+    'critical': logging.CRITICAL,
+}
+
+
+def InitializeLogging(args):
+  """Parses the provided argparse.Namespace to determine a logging level."""
+  log_level = logging.INFO
+
+  if args:
+    # Allow a 'verbose' flag to force |logging.DEBUG|.
+    if 'verbose' in args and args.verbose:
+      log_level = logging.DEBUG
+    elif 'log_level' in args:
+      log_level = _NAME_TO_LEVEL[args.log_level]
+
+  InitializeLoggingWithLevel(log_level)
+
+
+def InitializeLoggingWithLevel(log_level):
+  logging.basicConfig(
+      level=log_level,
+      format=('[%(process)d:%(asctime)s.%(msecs)03d...:'
+              '%(levelname)s:%(filename)s(%(lineno)s)] %(message)s'),
+      datefmt='%m-%d %H:%M')
diff --git a/src/starboard/tools/log_level_test.py b/src/starboard/tools/log_level_test.py
new file mode 100755
index 0000000..8167c14
--- /dev/null
+++ b/src/starboard/tools/log_level_test.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+"""Tests the log_level module."""
+
+import argparse
+import logging
+import mock
+import unittest
+import _env  # pylint: disable=unused-import
+from starboard.tools import log_level
+
+_INITIALIZE_LOGGING_MOCK = 'starboard.tools.log_level.InitializeLoggingWithLevel'
+
+
+class LogLevelTest(unittest.TestCase):
+
+  @mock.patch(_INITIALIZE_LOGGING_MOCK)
+  def testRainyDayNoArgs(self, initialize_logging_mock):
+    log_level.InitializeLogging(None)
+    initialize_logging_mock.assert_called_with(logging.INFO)
+
+  @mock.patch(_INITIALIZE_LOGGING_MOCK)
+  def testRainyDayEmptyArgs(self, initialize_logging_mock):
+    log_level.InitializeLogging(argparse.Namespace())
+    initialize_logging_mock.assert_called_with(logging.INFO)
+
+  @mock.patch(_INITIALIZE_LOGGING_MOCK)
+  def testSunnyDayCorrectLevels(self, initialize_logging_mock):
+    for name, level in log_level._NAME_TO_LEVEL.items():
+      args = argparse.Namespace()
+      args.log_level = name
+      log_level.InitializeLogging(args)
+      initialize_logging_mock.assert_called_with(level)
+
+  @mock.patch(_INITIALIZE_LOGGING_MOCK)
+  def testSunnyDayVerboseOverride(self, initialize_logging_mock):
+    args = argparse.Namespace()
+    args.verbose = True
+    log_level.InitializeLogging(args)
+    initialize_logging_mock.assert_called_with(logging.DEBUG)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/src/starboard/tools/toolchain/evergreen_linker.py b/src/starboard/tools/toolchain/evergreen_linker.py
index a7bf4d7..04fdc19 100644
--- a/src/starboard/tools/toolchain/evergreen_linker.py
+++ b/src/starboard/tools/toolchain/evergreen_linker.py
@@ -28,6 +28,7 @@
 
     return shell.And('{0} '
                      '--build-id '
+                     '-gc-sections '
                      '-X '
                      '-v '
                      '--eh-frame-hdr '
diff --git a/src/starboard/ui_navigation.h b/src/starboard/ui_navigation.h
index adb1193..6ff4137 100644
--- a/src/starboard/ui_navigation.h
+++ b/src/starboard/ui_navigation.h
@@ -143,6 +143,11 @@
   // kSbUiNavItemTypeFocus. Any previously focused navigation item should
   // receive the blur event. If the item is not transitively a content of the
   // root item, then this does nothing.
+#if SB_API_VERSION >= SB_UI_NAVIGATION2_VERSION
+  //
+  // Specifying kSbUiNavItemInvalid should remove focus from the UI navigation
+  // system.
+#endif
   void (*set_focus)(SbUiNavItem item);
 
   // This is used to enable or disable user interaction with the specified
@@ -193,6 +198,11 @@
   // the specified window. Navigation items are only interactable if they are
   // transitively attached to a window.
   //
+#if SB_API_VERSION >= SB_UI_NAVIGATION2_VERSION
+  // The native UI engine should never change this navigation item's content
+  // offset. It is assumed to be used as a proxy for the system window.
+  //
+#endif
   // A navigation item may only have a SbUiNavItem or SbWindow as its direct
   // container. The navigation item hierarchy is established using
   // set_item_container_item() with the root container attached to a SbWindow
@@ -242,6 +252,13 @@
 
   // Retrieve the current content offset for the navigation item. If |item| is
   // not a container, then the content offset is (0,0).
+#if SB_API_VERSION >= SB_UI_NAVIGATION2_VERSION
+  //
+  // The native UI engine should not change the content offset of a container
+  // unless one of its contents (possibly recursively) is focused. This is to
+  // allow seemlessly disabling then re-enabling focus items without having
+  // their containers change offsets.
+#endif
   void (*get_item_content_offset)(SbUiNavItem item,
                                   float* out_content_offset_x,
                                   float* out_content_offset_y);
diff --git a/src/testing/gtest/src/gtest.cc b/src/testing/gtest/src/gtest.cc
index 7f0193b..50717b7 100644
--- a/src/testing/gtest/src/gtest.cc
+++ b/src/testing/gtest/src/gtest.cc
@@ -37,6 +37,7 @@
 
 #if GTEST_OS_STARBOARD
 #include "starboard/client_porting/eztime/eztime.h"
+#include "starboard/common/file.h"
 #include "starboard/system.h"
 #else
 #include <ctype.h>
diff --git a/src/third_party/aom_includes/METATDATA b/src/third_party/aom_includes/METADATA
similarity index 100%
rename from src/third_party/aom_includes/METATDATA
rename to src/third_party/aom_includes/METADATA
diff --git a/src/third_party/crashpad/client/crash_report_database.h b/src/third_party/crashpad/client/crash_report_database.h
index cc4b8df..0739a9c 100644
--- a/src/third_party/crashpad/client/crash_report_database.h
+++ b/src/third_party/crashpad/client/crash_report_database.h
@@ -404,6 +404,15 @@
   //! \return The number of reports cleaned.
   virtual int CleanDatabase(time_t lockfile_ttl) { return 0; }
 
+  //! \brief Deletes the oldest crash reports and their associated metadata,
+  //!     leaving only num_reports_to_keep left in the database.
+  //!
+  //! \param[in] num_reports_to_keep To number of most recent reports to leave
+  //!     in the database.
+  //!
+  //! \return The operation status code.
+  virtual OperationStatus RemoveOldReports(int num_reports_to_keep) = 0;
+
  protected:
   CrashReportDatabase() {}
 
diff --git a/src/third_party/crashpad/client/crash_report_database_generic.cc b/src/third_party/crashpad/client/crash_report_database_generic.cc
index e90bfa6..e807f17 100644
--- a/src/third_party/crashpad/client/crash_report_database_generic.cc
+++ b/src/third_party/crashpad/client/crash_report_database_generic.cc
@@ -18,7 +18,9 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
+#include <algorithm>
 #include <utility>
+#include <vector>
 
 #include "base/logging.h"
 #include "build/build_config.h"
@@ -191,6 +193,11 @@
   }
 }
 
+bool WasCreatedSooner(CrashReportDatabase::Report a,
+                      CrashReportDatabase::Report b) {
+  return a.creation_time < b.creation_time;
+}
+
 }  // namespace
 
 class CrashReportDatabaseGeneric : public CrashReportDatabase {
@@ -218,6 +225,7 @@
   OperationStatus DeleteReport(const UUID& uuid) override;
   OperationStatus RequestUpload(const UUID& uuid) override;
   int CleanDatabase(time_t lockfile_ttl) override;
+  OperationStatus RemoveOldReports(int num_reports_to_keep) override;
 
   // Build a filepath for the directory for the report to hold attachments.
   base::FilePath AttachmentsPath(const UUID& uuid);
@@ -640,6 +648,32 @@
   return removed;
 }
 
+OperationStatus CrashReportDatabaseGeneric::RemoveOldReports(
+    int num_reports_to_keep) {
+  std::vector<CrashReportDatabase::Report> pending_reports;
+  std::vector<CrashReportDatabase::Report> completed_reports;
+  std::vector<CrashReportDatabase::Report> all_reports;
+
+  GetPendingReports(&pending_reports);
+  GetCompletedReports(&completed_reports);
+
+  all_reports.insert(
+      all_reports.end(), pending_reports.begin(), pending_reports.end());
+  all_reports.insert(
+      all_reports.end(), completed_reports.begin(), completed_reports.end());
+  std::sort(all_reports.begin(), all_reports.end(), WasCreatedSooner);
+
+  while (all_reports.size() > num_reports_to_keep) {
+    OperationStatus os = DeleteReport((*all_reports.begin()).uuid);
+    if (os != kNoError) {
+      return os;
+    }
+    all_reports.erase(all_reports.begin());
+  }
+
+  return kNoError;
+}
+
 OperationStatus CrashReportDatabaseGeneric::RecordUploadAttempt(
     UploadReport* report,
     bool successful,
diff --git a/src/third_party/crashpad/client/crashpad_client.h b/src/third_party/crashpad/client/crashpad_client.h
index e381b41..147b974 100644
--- a/src/third_party/crashpad/client/crashpad_client.h
+++ b/src/third_party/crashpad/client/crashpad_client.h
@@ -388,6 +388,15 @@
   //!
   //! \return `true` on success, `false` on failure with a message logged.
   static bool SendEvergreenInfoToHandler(EvergreenInfo evergreen_info);
+
+  //! \brief Sends mapping info to the handler
+  //!
+  //! A handler must have already been installed before calling this method.
+  //! \param[in] annotations A EvergreenAnnotations struct, whose information
+  //!     was created on Evergreen startup.
+  //!
+  //! \return `true` on success, `false` on failure with a message logged.
+  static bool SendAnnotationsToHandler(EvergreenAnnotations annotations);
 #endif
 
   //! \brief Requests that the handler capture a dump even though there hasn't
diff --git a/src/third_party/crashpad/client/crashpad_client_linux.cc b/src/third_party/crashpad/client/crashpad_client_linux.cc
index 02a4e57..2435153 100644
--- a/src/third_party/crashpad/client/crashpad_client_linux.cc
+++ b/src/third_party/crashpad/client/crashpad_client_linux.cc
@@ -141,6 +141,11 @@
     evergreen_info_ = evergreen_info;
     return SendEvergreenInfoImpl();
   }
+
+  bool SendAnnotations(EvergreenAnnotations annotations) {
+    annotations_ = annotations;
+    return SendAnnotationsImpl();
+  }
 #endif
 
   // The base implementation for all signal handlers, suitable for calling
@@ -181,6 +186,7 @@
 
 #if defined(STARBOARD)
   const EvergreenInfo& GetEvergreenInfo() { return evergreen_info_; }
+  const EvergreenAnnotations& GetAnnotations() { return annotations_; }
 #endif
 
   const ExceptionInformation& GetExceptionInfo() {
@@ -189,6 +195,7 @@
 
 #if defined(STARBOARD)
   virtual bool SendEvergreenInfoImpl() = 0;
+  virtual bool SendAnnotationsImpl() = 0;
 #endif
 
   virtual void HandleCrashImpl() = 0;
@@ -211,6 +218,7 @@
 
 #if defined(STARBOARD)
   EvergreenInfo evergreen_info_;
+  EvergreenAnnotations annotations_;
 #endif
 
   static SignalHandler* handler_;
@@ -250,6 +258,7 @@
 
 #if defined(STARBOARD)
   bool SendEvergreenInfoImpl() override { return false; }
+  bool SendAnnotationsImpl() override { return false; }
 #endif
 
   void HandleCrashImpl() override {
@@ -353,6 +362,14 @@
     client.SendEvergreenInfo(info);
     return true;
   }
+
+  bool SendAnnotationsImpl() override {
+    ExceptionHandlerClient client(sock_to_handler_.get(), true);
+    ExceptionHandlerProtocol::ClientInformation info = {};
+    info.annotations_address = FromPointerCast<VMAddress>(&GetAnnotations());
+    client.SendAnnotations(info);
+    return true;
+  }
 #endif
 
   void HandleCrashImpl() override {
@@ -576,6 +593,16 @@
 
   return SignalHandler::Get()->SendEvergreenInfo(evergreen_info);
 }
+
+bool CrashpadClient::SendAnnotationsToHandler(
+    EvergreenAnnotations annotations) {
+  if (!SignalHandler::Get()) {
+    DLOG(ERROR) << "Crashpad isn't enabled";
+    return false;
+  }
+
+  return SignalHandler::Get()->SendAnnotations(annotations);
+}
 #endif
 
 // static
diff --git a/src/third_party/crashpad/handler/crash_report_upload_thread.cc b/src/third_party/crashpad/handler/crash_report_upload_thread.cc
index ac0f7c1..db960b1 100644
--- a/src/third_party/crashpad/handler/crash_report_upload_thread.cc
+++ b/src/third_party/crashpad/handler/crash_report_upload_thread.cc
@@ -242,7 +242,7 @@
       break;
   }
 #if defined(STARBOARD)
-  database_->DeleteReport(report.uuid);
+  database_->RemoveOldReports(/*num_reports_to_save=*/2);
 #endif
 }
 
diff --git a/src/third_party/crashpad/handler/linux/capture_snapshot.cc b/src/third_party/crashpad/handler/linux/capture_snapshot.cc
index d792945..488bbba 100644
--- a/src/third_party/crashpad/handler/linux/capture_snapshot.cc
+++ b/src/third_party/crashpad/handler/linux/capture_snapshot.cc
@@ -34,14 +34,15 @@
     std::unique_ptr<ProcessSnapshotSanitized>* sanitized_snapshot
 #if defined(STARBOARD)
     ,
-    VMAddress evergreen_information_address
+    VMAddress evergreen_information_address,
+    VMAddress annotations_address
 #endif
     ) {
   std::unique_ptr<ProcessSnapshotLinux> process_snapshot(
       new ProcessSnapshotLinux());
 #if defined(STARBOARD)
-  if (!process_snapshot->Initialize(connection,
-                                    evergreen_information_address)) {
+  if (!process_snapshot->Initialize(
+          connection, evergreen_information_address, annotations_address)) {
 #else
   if (!process_snapshot->Initialize(connection)) {
 #endif
diff --git a/src/third_party/crashpad/handler/linux/capture_snapshot.h b/src/third_party/crashpad/handler/linux/capture_snapshot.h
index d191b8b..51eca35 100644
--- a/src/third_party/crashpad/handler/linux/capture_snapshot.h
+++ b/src/third_party/crashpad/handler/linux/capture_snapshot.h
@@ -67,7 +67,8 @@
     std::unique_ptr<ProcessSnapshotSanitized>* sanitized_snapshot
 #if defined(STARBOARD)
     ,
-    VMAddress evergreen_information_address
+    VMAddress evergreen_information_address,
+    VMAddress annotations_address
 #endif
     );
 
diff --git a/src/third_party/crashpad/handler/linux/crash_report_exception_handler.cc b/src/third_party/crashpad/handler/linux/crash_report_exception_handler.cc
index 3a59dc7..b0f68ce 100644
--- a/src/third_party/crashpad/handler/linux/crash_report_exception_handler.cc
+++ b/src/third_party/crashpad/handler/linux/crash_report_exception_handler.cc
@@ -86,6 +86,12 @@
   evergreen_info_ = info.evergreen_information_address;
   return true;
 }
+
+bool CrashReportExceptionHandler::AddAnnotations(
+    const ExceptionHandlerProtocol::ClientInformation& info) {
+  annotations_address_ = info.annotations_address;
+  return true;
+}
 #endif
 
 bool CrashReportExceptionHandler::HandleException(
@@ -150,7 +156,8 @@
                        &sanitized_snapshot
 #if defined(STARBOARD)
                        ,
-                       evergreen_info_
+                       evergreen_info_,
+                       annotations_address_
 #endif
                        )) {
     return false;
diff --git a/src/third_party/crashpad/handler/linux/crash_report_exception_handler.h b/src/third_party/crashpad/handler/linux/crash_report_exception_handler.h
index b5ea3f7..5cca2ad 100644
--- a/src/third_party/crashpad/handler/linux/crash_report_exception_handler.h
+++ b/src/third_party/crashpad/handler/linux/crash_report_exception_handler.h
@@ -87,6 +87,8 @@
 #if defined(STARBOARD)
   bool AddEvergreenInfo(
       const ExceptionHandlerProtocol::ClientInformation& info) override;
+  bool AddAnnotations(
+      const ExceptionHandlerProtocol::ClientInformation& info) override;
 #endif
 
   bool HandleExceptionWithBroker(
@@ -120,6 +122,7 @@
   const UserStreamDataSources* user_stream_data_sources_;  // weak
 #if defined(STARBOARD)
   VMAddress evergreen_info_;
+  VMAddress annotations_address_;
 #endif
 
   DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler);
diff --git a/src/third_party/crashpad/handler/linux/exception_handler_server.cc b/src/third_party/crashpad/handler/linux/exception_handler_server.cc
index d49cc1c..63215d5 100644
--- a/src/third_party/crashpad/handler/linux/exception_handler_server.cc
+++ b/src/third_party/crashpad/handler/linux/exception_handler_server.cc
@@ -442,6 +442,8 @@
 #if defined(STARBOARD)
     case ExceptionHandlerProtocol::ClientToServerMessage::kTypeAddEvergreenInfo:
       return HandleAddEvergreenInfoRequest(creds, message.client_info);
+    case ExceptionHandlerProtocol::ClientToServerMessage::kTypeAddAnnotations:
+      return HandleAddAnnotationsRequest(creds, message.client_info);
 #endif
   }
 
@@ -456,6 +458,12 @@
     const ExceptionHandlerProtocol::ClientInformation& client_info) {
   return delegate_->AddEvergreenInfo(client_info);
 }
+
+bool ExceptionHandlerServer::HandleAddAnnotationsRequest(
+    const ucred& creds,
+    const ExceptionHandlerProtocol::ClientInformation& client_info) {
+  return delegate_->AddAnnotations(client_info);
+}
 #endif
 
 bool ExceptionHandlerServer::HandleCrashDumpRequest(
diff --git a/src/third_party/crashpad/handler/linux/exception_handler_server.h b/src/third_party/crashpad/handler/linux/exception_handler_server.h
index 0f0e73c..1e87157 100644
--- a/src/third_party/crashpad/handler/linux/exception_handler_server.h
+++ b/src/third_party/crashpad/handler/linux/exception_handler_server.h
@@ -102,6 +102,13 @@
     //! \return `true` on success. `false` on failure with a message logged.
     virtual bool AddEvergreenInfo(
         const ExceptionHandlerProtocol::ClientInformation& info) = 0;
+
+    //! \brief Called on receipt of a request to add Evergreen Annotations.
+    //!
+    //! \param[in] info Information on the client.
+    //! \return `true` on success. `false` on failure with a message logged.
+    virtual bool AddAnnotations(
+        const ExceptionHandlerProtocol::ClientInformation& info) = 0;
 #endif
 
     //! \brief Called on the receipt of a crash dump request from a client for a
@@ -193,6 +200,9 @@
   bool HandleAddEvergreenInfoRequest(
       const ucred& creds,
       const ExceptionHandlerProtocol::ClientInformation& client_info);
+  bool HandleAddAnnotationsRequest(
+      const ucred& creds,
+      const ExceptionHandlerProtocol::ClientInformation& client_info);
 #endif
 
   std::unordered_map<int, std::unique_ptr<Event>> clients_;
diff --git a/src/third_party/crashpad/snapshot/linux/process_snapshot_linux.cc b/src/third_party/crashpad/snapshot/linux/process_snapshot_linux.cc
index 32ee101..a731187 100644
--- a/src/third_party/crashpad/snapshot/linux/process_snapshot_linux.cc
+++ b/src/third_party/crashpad/snapshot/linux/process_snapshot_linux.cc
@@ -55,7 +55,8 @@
 
 #if defined(STARBOARD)
 bool ProcessSnapshotLinux::Initialize(PtraceConnection* connection,
-                                      VMAddress evergreen_information_address) {
+                                      VMAddress evergreen_information_address,
+                                      VMAddress annotations_address) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
 
   if (gettimeofday(&snapshot_time_, nullptr) != 0) {
@@ -69,6 +70,15 @@
     return false;
   }
 
+  EvergreenAnnotations annotations;
+  if (!memory_range_.Read(
+          annotations_address, sizeof(EvergreenAnnotations), &annotations)) {
+    LOG(ERROR) << "Could not read annotations";
+  } else {
+    AddAnnotation("user_agent_string",
+                  std::string(annotations.user_agent_string));
+  }
+
   system_.Initialize(&process_reader_, &snapshot_time_);
 
   InitializeThreads();
diff --git a/src/third_party/crashpad/snapshot/linux/process_snapshot_linux.h b/src/third_party/crashpad/snapshot/linux/process_snapshot_linux.h
index 6c5d7a8..c78a709 100644
--- a/src/third_party/crashpad/snapshot/linux/process_snapshot_linux.h
+++ b/src/third_party/crashpad/snapshot/linux/process_snapshot_linux.h
@@ -70,11 +70,14 @@
   //! \param[in] connection A connection to the process to snapshot.
   //! \param[in] evergreen_information_address An address sent to the handler
   //!     server that points to a populated EvergreenInfo struct.
+  //! \param[in] annotations_address An address sent to the handler server that
+  //!     that points to a populated EvergreenAnnotations struct.
   //!
   //! \return `true` if the snapshot could be created, `false` otherwise with
   //!     an appropriate message logged.
   bool Initialize(PtraceConnection* connnection,
-                  VMAddress evergreen_information_address);
+                  VMAddress evergreen_information_address,
+                  VMAddress annotations_address);
 #endif
 
   //! \brief Finds the thread whose stack contains \a stack_address.
diff --git a/src/third_party/crashpad/util/linux/exception_handler_client.cc b/src/third_party/crashpad/util/linux/exception_handler_client.cc
index b1df219..de27275 100644
--- a/src/third_party/crashpad/util/linux/exception_handler_client.cc
+++ b/src/third_party/crashpad/util/linux/exception_handler_client.cc
@@ -89,6 +89,11 @@
     const ExceptionHandlerProtocol::ClientInformation& info) {
   return SendEvergreenInfoRequest(info);
 }
+
+bool ExceptionHandlerClient::SendAnnotations(
+    const ExceptionHandlerProtocol::ClientInformation& info) {
+  return SendAddAnnotationsRequest(info);
+}
 #endif
 
 int ExceptionHandlerClient::RequestCrashDump(
@@ -161,6 +166,17 @@
   UnixCredentialSocket::SendMsg(server_sock_, &message, sizeof(message));
   return true;
 }
+
+bool ExceptionHandlerClient::SendAddAnnotationsRequest(
+    const ExceptionHandlerProtocol::ClientInformation& info) {
+  ExceptionHandlerProtocol::ClientToServerMessage message;
+  message.type =
+      ExceptionHandlerProtocol::ClientToServerMessage::kTypeAddAnnotations;
+  message.client_info = info;
+
+  UnixCredentialSocket::SendMsg(server_sock_, &message, sizeof(message));
+  return true;
+}
 #endif
 
 int ExceptionHandlerClient::SendCrashDumpRequest(
diff --git a/src/third_party/crashpad/util/linux/exception_handler_client.h b/src/third_party/crashpad/util/linux/exception_handler_client.h
index 3a27739..e2f6398 100644
--- a/src/third_party/crashpad/util/linux/exception_handler_client.h
+++ b/src/third_party/crashpad/util/linux/exception_handler_client.h
@@ -57,6 +57,12 @@
   //! \return `true` on success or `false` on failure.
   bool SendEvergreenInfo(
       const ExceptionHandlerProtocol::ClientInformation& info);
+
+  //! \brief Sends EvergreenAnnotations to the ExceptionHandlerServer.
+  //!
+  //! \param[in] info Information to about this client.
+  //! \return `true` on success or `false` on failure.
+  bool SendAnnotations(const ExceptionHandlerProtocol::ClientInformation& info);
 #endif
 
   //! \brief Request a crash dump from the ExceptionHandlerServer.
@@ -83,6 +89,9 @@
 #if defined(STARBOARD)
   bool SendEvergreenInfoRequest(
       const ExceptionHandlerProtocol::ClientInformation& info);
+
+  bool SendAddAnnotationsRequest(
+      const ExceptionHandlerProtocol::ClientInformation& info);
 #endif
   int SendCrashDumpRequest(
       const ExceptionHandlerProtocol::ClientInformation& info,
diff --git a/src/third_party/crashpad/util/linux/exception_handler_protocol.cc b/src/third_party/crashpad/util/linux/exception_handler_protocol.cc
index 2220ecf..b139017 100644
--- a/src/third_party/crashpad/util/linux/exception_handler_protocol.cc
+++ b/src/third_party/crashpad/util/linux/exception_handler_protocol.cc
@@ -25,7 +25,8 @@
 #endif  // OS_LINUX
 #if defined(STARBOARD)
       ,
-      evergreen_information_address(0)
+      evergreen_information_address(0),
+      annotations_address(0)
 #endif
 {
 }
diff --git a/src/third_party/crashpad/util/linux/exception_handler_protocol.h b/src/third_party/crashpad/util/linux/exception_handler_protocol.h
index 9edd82a..dbfaeca 100644
--- a/src/third_party/crashpad/util/linux/exception_handler_protocol.h
+++ b/src/third_party/crashpad/util/linux/exception_handler_protocol.h
@@ -56,6 +56,10 @@
     //! \brief The address in the client's address space of an EvergreenInfo
     //!     struct, or 0 if there is no such struct.
     VMAddress evergreen_information_address;
+
+    //! \brief The address in the client's address space of an
+    //!     EvergreenAnnotations struct, or 0 if there is no such struct.
+    VMAddress annotations_address;
 #endif
 
 #if defined(OS_LINUX)
@@ -93,7 +97,8 @@
 #if defined(STARBOARD)
       //! \brief Used to store Evergreen mapping info in the handler for use at
       //!     time of crash.
-      kTypeAddEvergreenInfo
+      kTypeAddEvergreenInfo,
+      kTypeAddAnnotations,
 #endif
     };
 
diff --git a/src/third_party/crashpad/wrapper/wrapper.cc b/src/third_party/crashpad/wrapper/wrapper.cc
index 8b8a18d..0ab09e5 100644
--- a/src/third_party/crashpad/wrapper/wrapper.cc
+++ b/src/third_party/crashpad/wrapper/wrapper.cc
@@ -206,6 +206,11 @@
   return client->SendEvergreenInfoToHandler(evergreen_info);
 }
 
+bool AddAnnotationsToCrashpad(EvergreenAnnotations annotations) {
+  ::crashpad::CrashpadClient* client = GetCrashpadClient();
+  return client->SendAnnotationsToHandler(annotations);
+}
+
 }  // namespace wrapper
 }  // namespace crashpad
 }  // namespace third_party
diff --git a/src/third_party/crashpad/wrapper/wrapper.h b/src/third_party/crashpad/wrapper/wrapper.h
index c2b366f..57ab399 100644
--- a/src/third_party/crashpad/wrapper/wrapper.h
+++ b/src/third_party/crashpad/wrapper/wrapper.h
@@ -25,6 +25,8 @@
 
 bool AddEvergreenInfoToCrashpad(EvergreenInfo evergreen_info);
 
+bool AddAnnotationsToCrashpad(EvergreenAnnotations annotations);
+
 }  // namespace wrapper
 }  // namespace crashpad
 }  // namespace third_party
diff --git a/src/third_party/crashpad/wrapper/wrapper_stub.cc b/src/third_party/crashpad/wrapper/wrapper_stub.cc
index 6158445..f2aeb8a 100644
--- a/src/third_party/crashpad/wrapper/wrapper_stub.cc
+++ b/src/third_party/crashpad/wrapper/wrapper_stub.cc
@@ -24,6 +24,10 @@
   return false;
 }
 
+bool AddAnnotationsToCrashpad(EvergreenAnnotations annotations) {
+  return false;
+}
+
 }  // namespace wrapper
 }  // namespace crashpad
 }  // namespace third_party
\ No newline at end of file
diff --git a/src/third_party/libdav1d/METATDATA b/src/third_party/libdav1d/METADATA
similarity index 100%
rename from src/third_party/libdav1d/METATDATA
rename to src/third_party/libdav1d/METADATA
diff --git a/src/third_party/precommit-hooks/clang-format_wrapper.py b/src/third_party/precommit-hooks/clang-format_wrapper.py
new file mode 100755
index 0000000..b11325c
--- /dev/null
+++ b/src/third_party/precommit-hooks/clang-format_wrapper.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+
+import os
+import platform
+import subprocess
+import sys
+
+if __name__ == '__main__':
+  clang_format_args = sys.argv[1:]
+  path_to_clang_format = [os.getcwd(), 'buildtools']
+
+  system = platform.system()
+  if system == 'Linux':
+    path_to_clang_format += ['linux64', 'clang-format']
+  elif system == 'Darwin':
+    path_to_clang_format += ['win', 'clang-format.exe']
+  elif system == 'Windows':
+    path_to_clang_format += ['mac', 'clang-format']
+  else:
+    sys.exit(1)
+
+  clang_format_executable = os.path.join(*path_to_clang_format)
+  sys.exit(subprocess.call([clang_format_executable] + clang_format_args))
diff --git a/src/third_party/precommit-hooks/gcheckstyle_wrapper.py b/src/third_party/precommit-hooks/gcheckstyle_wrapper.py
new file mode 100755
index 0000000..0275960
--- /dev/null
+++ b/src/third_party/precommit-hooks/gcheckstyle_wrapper.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+
+import platform
+import subprocess
+import sys
+
+try:
+  from internal_tools_paths import checkstyle_path
+except ImportError:
+  checkstyle_path = None
+
+if __name__ == '__main__':
+  gcheckstyle_args = sys.argv[1:]
+  try:
+    sys.exit(subprocess.call([checkstyle_path] + gcheckstyle_args))
+  except OSError:
+    print('Checkstyle not found, skipping.')
+    sys.exit(0)
diff --git a/src/third_party/precommit-hooks/google-java-format_wrapper.py b/src/third_party/precommit-hooks/google-java-format_wrapper.py
new file mode 100755
index 0000000..f244fa3
--- /dev/null
+++ b/src/third_party/precommit-hooks/google-java-format_wrapper.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python3
+
+import platform
+import subprocess
+import sys
+
+if __name__ == '__main__':
+  if platform.system() != 'Linux':
+    sys.exit(0)
+
+  google_java_format_args = sys.argv[1:]
+  sys.exit(subprocess.call(['google-java-format'] + google_java_format_args))
diff --git a/src/third_party/web_platform_tests/fetch/api/request/request-consume-empty.html b/src/third_party/web_platform_tests/fetch/api/request/request-consume-empty.html
index c3ca838..a5bc141 100644
--- a/src/third_party/web_platform_tests/fetch/api/request/request-consume-empty.html
+++ b/src/third_party/web_platform_tests/fetch/api/request/request-consume-empty.html
@@ -69,12 +69,18 @@
       }, "Consume request's body as " + bodyType);
     }
 
+/* Cobalt doesn't support FormData request body
     var formData = new FormData();
+*/
     checkRequestWithNoBody("text", checkBodyText);
+/* Cobalt doesn't support Blob request body
     checkRequestWithNoBody("blob", checkBodyBlob);
+*/
     checkRequestWithNoBody("arrayBuffer", checkBodyArrayBuffer);
     checkRequestWithNoBody("json", checkBodyJSON);
+/* Cobalt doesn't support FormData request body
     checkRequestWithNoBody("formData", checkBodyFormData);
+*/
 
     function checkRequestWithEmptyBody(bodyType, body, asText) {
       promise_test(function(test) {
@@ -98,9 +104,11 @@
     checkRequestWithEmptyBody("text", "", false);
     checkRequestWithEmptyBody("blob", new Blob([], { "type" : "text/plain" }), true);
     checkRequestWithEmptyBody("text", "", true);
+/* Cobalt doesn't support URLSearchParams and FormData request body
     checkRequestWithEmptyBody("URLSearchParams", new URLSearchParams(""), true);
     // FIXME: This test assumes that the empty string be returned but it is not clear whether that is right. See https://github.com/w3c/web-platform-tests/pull/3950.
     checkRequestWithEmptyBody("FormData", new FormData(), true);
+*/
     checkRequestWithEmptyBody("ArrayBuffer", new ArrayBuffer(), true);
     </script>
   </body>
diff --git a/src/third_party/web_platform_tests/fetch/api/request/request-consume.html b/src/third_party/web_platform_tests/fetch/api/request/request-consume.html
index 9ac7041..880f471 100644
--- a/src/third_party/web_platform_tests/fetch/api/request/request-consume.html
+++ b/src/third_party/web_platform_tests/fetch/api/request/request-consume.html
@@ -69,11 +69,13 @@
         assert_false(request.bodyUsed, "bodyUsed is false at init");
         return checkBodyText(request, expected);
       }, "Consume " + bodyType  + " request's body as text");
+/* Cobalt doesn't support Blob request body
       promise_test(function(test) {
         var request = new Request("", {"method": "POST", "body": body });
         assert_false(request.bodyUsed, "bodyUsed is false at init");
         return checkBodyBlob(request, expected);
       }, "Consume " + bodyType  + " request's body as blob");
+*/
       promise_test(function(test) {
         var request = new Request("", {"method": "POST", "body": body });
         assert_false(request.bodyUsed, "bodyUsed is false at init");
@@ -114,6 +116,7 @@
     checkRequestBody(new Float32Array(getArrayBuffer()), string, "Float32Array");
     checkRequestBody(new DataView(getArrayBufferWithZeros(), 1, 8), string, "DataView");
 
+/* Cobalt doesn't support FormData request body
     promise_test(function(test) {
       var formData = new FormData();
       formData.append("name", "value")
@@ -121,6 +124,7 @@
       assert_false(request.bodyUsed, "bodyUsed is false at init");
       return checkBodyFormData(request, formData);
     }, "Consume FormData request's body as FormData");
+*/
 
     function checkBlobResponseBody(blobBody, blobData, bodyType, checkFunction) {
       promise_test(function(test) {
@@ -130,11 +134,15 @@
       }, "Consume blob response's body as " + bodyType);
     }
 
+/* Cobalt doesn't support Blob request body
     checkBlobResponseBody(blob, textData, "blob", checkBodyBlob);
+*/
     checkBlobResponseBody(blob, textData, "text", checkBodyText);
     checkBlobResponseBody(blob, textData, "json", checkBodyJSON);
     checkBlobResponseBody(blob, textData, "arrayBuffer", checkBodyArrayBuffer);
+/* Cobalt doesn't support Blob request body
     checkBlobResponseBody(new Blob([""]), "", "blob (empty blob as input)", checkBodyBlob);
+*/
 
     var goodJSONValues = ["null", "1", "true", "\"string\""];
     goodJSONValues.forEach(function(value) {
diff --git a/src/third_party/zlib/contrib/optimizations/chunkcopy.h b/src/third_party/zlib/contrib/optimizations/chunkcopy.h
index 38ba0ed..c856b6a 100644
--- a/src/third_party/zlib/contrib/optimizations/chunkcopy.h
+++ b/src/third_party/zlib/contrib/optimizations/chunkcopy.h
@@ -406,6 +406,26 @@
   return chunkcopy_lapped_relaxed(out, dist, len);
 }
 
+/* TODO(cavalcanti): see crbug.com/1110083. */
+static inline unsigned char FAR* chunkcopy_safe_ugly(unsigned char FAR* out,
+                                                     unsigned dist,
+                                                     unsigned len,
+                                                     unsigned char FAR* limit) {
+#if defined(__GNUC__) && !defined(__clang__)
+  /* Speed is the same as using chunkcopy_safe
+     w/ GCC on ARM (tested gcc 6.3 and 7.5) and avoids
+     undefined behavior.
+  */
+  return chunkcopy_core_safe(out, out - dist, len, limit);
+#elif defined(__clang__) && defined(ARMV8_OS_ANDROID) && !defined(__aarch64__)
+  /* Seems to perform better on 32bit (i.e. Android). */
+  return chunkcopy_core_safe(out, out - dist, len, limit);
+#else
+  /* Seems to perform better on 64bit. */
+  return chunkcopy_lapped_safe(out, dist, len, limit);
+#endif
+}
+
 /*
  * The chunk-copy code above deals with writing the decoded DEFLATE data to
  * the output with SIMD methods to increase decode speed. Reading the input
diff --git a/src/third_party/zlib/contrib/optimizations/inffast_chunk.c b/src/third_party/zlib/contrib/optimizations/inffast_chunk.c
index 4099edf..4bacbc4 100644
--- a/src/third_party/zlib/contrib/optimizations/inffast_chunk.c
+++ b/src/third_party/zlib/contrib/optimizations/inffast_chunk.c
@@ -276,7 +276,7 @@
                            the main copy is near the end.
                           */
                         out = chunkunroll_relaxed(out, &dist, &len);
-                        out = chunkcopy_safe(out, out - dist, len, limit);
+                        out = chunkcopy_safe_ugly(out, dist, len, limit);
                     } else {
                         /* from points to window, so there is no risk of
                            overlapping pointers requiring memset-like behaviour
diff --git a/src/v8/METADATA b/src/v8/METADATA
new file mode 100644
index 0000000..8522c17
--- /dev/null
+++ b/src/v8/METADATA
@@ -0,0 +1,22 @@
+name: "v8"
+description:
+  "Subtree at v8."
+  "v8 is Google's JavaScript engine developed for The Chromium Project. Cobalt "
+  "uses v8 as its primary JavaScript engine. v8 works closely with Cobalt's "
+  "script interface and templated bindings code."
+third_party {
+  url {
+    type: LOCAL_SOURCE
+    value: "/v8_mirror"
+  }
+  url {
+    type: GIT
+    value: "https://chromium.googlesource.com/v8/v8"
+  }
+  version: "1e6ebba9def991e536159fa658bf5564c054733f"
+  last_upgrade_date {
+    year: 2019
+    month: 8
+    day: 26
+  }
+}
diff --git a/src/v8/include/v8config.h b/src/v8/include/v8config.h
index dd065a0..766ca5d 100644
--- a/src/v8/include/v8config.h
+++ b/src/v8/include/v8config.h
@@ -56,7 +56,6 @@
 // -----------------------------------------------------------------------------
 // Operating system detection
 //
-//  V8_OS_STARBOARD     - Starboard (platform abstraction layer for the Cobalt project)
 //  V8_OS_ANDROID       - Android
 //  V8_OS_BSD           - BSDish (Mac OS X, Net/Free/Open/DragonFlyBSD)
 //  V8_OS_CYGWIN        - Cygwin
@@ -71,6 +70,7 @@
 //  V8_OS_POSIX         - POSIX compatible (mostly everything except Windows)
 //  V8_OS_QNX           - QNX Neutrino
 //  V8_OS_SOLARIS       - Sun Solaris and OpenSolaris
+//  V8_OS_STARBOARD     - Starboard (platform abstraction layer for the Cobalt project)
 //  V8_OS_AIX           - AIX
 //  V8_OS_WIN           - Microsoft Windows
 
diff --git a/src/v8/src/base/atomicops.h b/src/v8/src/base/atomicops.h
index 18c99a2..09acdc2 100644
--- a/src/v8/src/base/atomicops.h
+++ b/src/v8/src/base/atomicops.h
@@ -72,9 +72,9 @@
 // Use AtomicWord for a machine-sized pointer.  It will use the Atomic32 or
 // Atomic64 routines below, depending on your architecture.
 #if defined(V8_OS_STARBOARD)
-typedef SbAtomicPtr AtomicWord;
+using AtomicWord = SbAtomicPtr;
 #else
-typedef intptr_t AtomicWord;
+using AtomicWord = intptr_t;
 #endif
 
 // Atomically execute:
diff --git a/src/v8/src/base/cpu.cc b/src/v8/src/base/cpu.cc
index a13c13e..46daeed 100644
--- a/src/v8/src/base/cpu.cc
+++ b/src/v8/src/base/cpu.cc
@@ -5,15 +5,9 @@
 #include "src/base/cpu.h"
 
 #if defined(STARBOARD)
-#include "starboard/client_porting/poem/stdlib_poem.h"
 #include "starboard/cpu_features.h"
 #endif
 
-#if V8_HOST_ARCH_MIPS || V8_HOST_ARCH_MIPS64
-// Assume that if we're on a MIPS platform, we're on Linux.
-#define V8_OS_LINUX 1
-#endif
-
 #if V8_LIBC_MSVCRT
 #include <intrin.h>  // __cpuid()
 #endif
@@ -43,13 +37,11 @@
 #endif
 
 #include <ctype.h>
-#if !V8_OS_STARBOARD
 #include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <algorithm>
-#endif
 
 #include "src/base/logging.h"
 #if V8_OS_WIN
diff --git a/src/v8/src/base/once.cc b/src/v8/src/base/once.cc
index 7378b54..0222687 100644
--- a/src/v8/src/base/once.cc
+++ b/src/v8/src/base/once.cc
@@ -6,6 +6,8 @@
 
 #ifdef _WIN32
 #include <windows.h>
+#elif defined(V8_OS_STARBOARD)
+#include "starboard/thread.h"
 #else
 #include <sched.h>
 #endif
@@ -49,6 +51,8 @@
            ONCE_STATE_EXECUTING_FUNCTION) {
 #ifdef _WIN32
       ::Sleep(0);
+#elif defined(V8_OS_STARBOARD)
+      SbThreadYield();
 #else
       sched_yield();
 #endif
diff --git a/src/v8/src/base/platform/condition-variable.cc b/src/v8/src/base/platform/condition-variable.cc
index b319793..04ea291 100644
--- a/src/v8/src/base/platform/condition-variable.cc
+++ b/src/v8/src/base/platform/condition-variable.cc
@@ -165,7 +165,6 @@
   SbConditionVariableCreate(&native_handle_, nullptr);
 }
 
-
 ConditionVariable::~ConditionVariable() {
   SbConditionVariableDestroy(&native_handle_);
 }
@@ -178,12 +177,10 @@
   SbConditionVariableBroadcast(&native_handle_);
 }
 
-
 void ConditionVariable::Wait(Mutex* mutex) {
   SbConditionVariableWait(&native_handle_, &mutex->native_handle());
 }
 
-
 bool ConditionVariable::WaitFor(Mutex* mutex, const TimeDelta& rel_time) {
   SbTime microseconds = static_cast<SbTime>(rel_time.InMicroseconds());
   SbConditionVariableResult result = SbConditionVariableWaitTimed(
@@ -192,7 +189,7 @@
   return result == kSbConditionVariableSignaled;
 }
 
-#endif  // V8_OS_POSIX
+#endif  // V8_OS_STARBOARD
 
 }  // namespace base
-}  // namespace v8
\ No newline at end of file
+}  // namespace v8
diff --git a/src/v8/src/base/platform/mutex.cc b/src/v8/src/base/platform/mutex.cc
index 68f5132..88d9ba8 100644
--- a/src/v8/src/base/platform/mutex.cc
+++ b/src/v8/src/base/platform/mutex.cc
@@ -4,9 +4,7 @@
 
 #include "src/base/platform/mutex.h"
 
-#if !V8_OS_STARBOARD
 #include <errno.h>
-#endif
 
 namespace v8 {
 namespace base {
diff --git a/src/v8/src/base/platform/platform-starboard.cc b/src/v8/src/base/platform/platform-starboard.cc
index 3cd69ec..a9a0118 100644
--- a/src/v8/src/base/platform/platform-starboard.cc
+++ b/src/v8/src/base/platform/platform-starboard.cc
@@ -1,25 +1,17 @@
-// Copyright 2018 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+// Copyright 2020 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Platform-specific code for Starboard goes here. Starboard is the platform
+// abstraction layer for Cobalt, an HTML5 container used mainly by YouTube
+// LivingRoom products.
 
 #include "src/base/lazy-instance.h"
 #include "src/base/macros.h"
 #include "src/base/platform/platform.h"
 #include "src/base/platform/time.h"
-#include "src/base/utils/random-number-generator.h"
-
 #include "src/base/timezone-cache.h"
-
+#include "src/base/utils/random-number-generator.h"
 #include "starboard/common/condition_variable.h"
 #include "starboard/common/log.h"
 #include "starboard/common/string.h"
@@ -102,9 +94,7 @@
 #endif
 }
 
-double OS::TimeCurrentMillis() {
-  return Time::Now().ToJsTime();
-}
+double OS::TimeCurrentMillis() { return Time::Now().ToJsTime(); }
 
 int OS::ActivationFrameAlignment() {
 #if V8_TARGET_ARCH_ARM
@@ -148,7 +138,8 @@
       break;
     default:
       SB_LOG(ERROR) << "The requested memory allocation access is not"
-      " implemented for Starboard: " << static_cast<int>(access);
+                       " implemented for Starboard: "
+                    << static_cast<int>(access);
       return nullptr;
   }
   void* result = SbMemoryMap(size, sb_flags, "v8::Base::Allocate");
@@ -247,10 +238,10 @@
       break;
     case OS::MemoryPermission::kReadExecute:
 #if SB_CAN(MAP_EXECUTABLE_MEMORY)
-      new_protection = SbMemoryMapFlags(kSbMemoryMapProtectRead |
-                                        kSbMemoryMapProtectExec);
+      new_protection =
+          SbMemoryMapFlags(kSbMemoryMapProtectRead | kSbMemoryMapProtectExec);
 #else
-      CHECK(false);
+      UNREACHABLE();
 #endif
       break;
     default:
@@ -417,9 +408,7 @@
   set_name(options.name());
 }
 
-Thread::~Thread() {
-  delete data_;
-}
+Thread::~Thread() { delete data_; }
 
 static void SetThreadName(const char* name) { SbThreadSetName(name); }
 
diff --git a/src/v8/src/base/platform/semaphore.cc b/src/v8/src/base/platform/semaphore.cc
index f37801a..3545bab 100644
--- a/src/v8/src/base/platform/semaphore.cc
+++ b/src/v8/src/base/platform/semaphore.cc
@@ -190,21 +190,13 @@
 
 #elif V8_OS_STARBOARD
 
-Semaphore::Semaphore(int count) : native_handle_(count) {
-  DCHECK_GE(count, 0);
-}
+Semaphore::Semaphore(int count) : native_handle_(count) { DCHECK_GE(count, 0); }
 
-Semaphore::~Semaphore() {
-}
+Semaphore::~Semaphore() {}
 
-void Semaphore::Signal() {
-  native_handle_.Put();
-}
+void Semaphore::Signal() { native_handle_.Put(); }
 
-
-void Semaphore::Wait() {
-  native_handle_.Take();
-}
+void Semaphore::Wait() { native_handle_.Take(); }
 
 bool Semaphore::WaitFor(const TimeDelta& rel_time) {
   SbTime microseconds = rel_time.InMicroseconds();
diff --git a/src/v8/src/base/platform/time.cc b/src/v8/src/base/platform/time.cc
index 5aa6dcd..ff1fcd8 100644
--- a/src/v8/src/base/platform/time.cc
+++ b/src/v8/src/base/platform/time.cc
@@ -458,15 +458,11 @@
 
 #elif V8_OS_STARBOARD
 
-Time Time::Now() {
-  return Time(SbTimeToPosix(SbTimeGetNow()));
-}
+Time Time::Now() { return Time(SbTimeToPosix(SbTimeGetNow())); }
 
-Time Time::NowFromSystemTime() {
-  return Now();
-}
+Time Time::NowFromSystemTime() { return Now(); }
 
-#endif  // V8_OS_WIN
+#endif  // V8_OS_STARBOARD
 
 // static
 TimeTicks TimeTicks::HighResolutionNow() {
diff --git a/src/venv/.gitkeep b/src/venv/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/venv/.gitkeep