Import Cobalt 19.android.1.203995
diff --git a/src/base/string_util_starboard.h b/src/base/string_util_starboard.h
index 533f493..12f978b 100644
--- a/src/base/string_util_starboard.h
+++ b/src/base/string_util_starboard.h
@@ -35,6 +35,10 @@
   return SbStringCompareNoCaseN(string1, string2, count);
 }
 
+#if defined(vsnprintf)
+#undef vsnprintf
+#endif
+
 inline int vsnprintf(char* buffer, size_t size,
                      const char* format, va_list arguments) {
   return SbStringFormat(buffer, size, format, arguments);
diff --git a/src/cobalt/CHANGELOG.md b/src/cobalt/CHANGELOG.md
index 433e565..03c4753 100644
--- a/src/cobalt/CHANGELOG.md
+++ b/src/cobalt/CHANGELOG.md
@@ -7,6 +7,18 @@
   - Fix bug where Cobalt would not refresh the layout when the textContent
     property of a DOM TextNode is modified.
 
+ - **Add support for decoding JPEG images as multi-plane YUV**
+
+   JPEG images are decoded into RGBA in previous versions of Cobalt.  The native
+   format of most JPEG images is YV12, which takes only 3/8 of memory compare to
+   RGBA.  Now JPEG images are decoded into multi-plane YUV images on platforms
+   with "rasterizer_type" set to "direct-gles".  As a result, when decoding to
+   multi-plane image is enabled, image cache size set by AutoMem will be reduced
+   by half due to the more compact nature of the YUV image format versus RGB.
+   This feature can also be enabled/disabled explicitly by passing command line
+   parameter "allow_image_decoding_to_multi_plane" to Cobalt with value "true"
+   or "false".
+
 ## Version 19
  - **Add support for V8 JavaScript Engine**
 
diff --git a/src/cobalt/audio/audio.gyp b/src/cobalt/audio/audio.gyp
index c61b10d..86a9732 100644
--- a/src/cobalt/audio/audio.gyp
+++ b/src/cobalt/audio/audio.gyp
@@ -17,6 +17,22 @@
     'sb_pedantic_warnings': 1,
   },
   'targets': [
+    # This target can choose the correct media dependency.
+    {
+      'target_name': 'media_audio',
+      'type': 'static_library',
+      'conditions': [
+        ['cobalt_media_source_2016==1', {
+          'dependencies': [
+            '<(DEPTH)/cobalt/media/media2.gyp:media2',
+          ],
+        }, {
+          'dependencies': [
+            '<(DEPTH)/cobalt/media/media.gyp:media',
+          ],
+        }],
+      ],
+    },
     {
       'target_name': 'audio',
       'type': 'static_library',
@@ -46,6 +62,7 @@
         'audio_node_output.h',
       ],
       'dependencies': [
+        'media_audio',
         '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/browser/browser_bindings_gen.gyp:generated_types',
       ],
diff --git a/src/cobalt/audio/audio_buffer_source_node.cc b/src/cobalt/audio/audio_buffer_source_node.cc
index b694792..e517a9d 100644
--- a/src/cobalt/audio/audio_buffer_source_node.cc
+++ b/src/cobalt/audio/audio_buffer_source_node.cc
@@ -14,17 +14,22 @@
 
 #include "cobalt/audio/audio_buffer_source_node.h"
 
+#include <math.h>
+
 #include <algorithm>
 
 #include "cobalt/audio/audio_context.h"
+#include "cobalt/audio/audio_helpers.h"
 #include "cobalt/audio/audio_node_output.h"
 
 namespace cobalt {
 namespace audio {
 
 #if defined(COBALT_MEDIA_SOURCE_2016)
+typedef media::InterleavedSincResampler InterleavedSincResampler;
 typedef media::ShellAudioBus ShellAudioBus;
 #else   // defined(COBALT_MEDIA_SOURCE_2016)
+typedef ::media::InterleavedSincResampler InterleavedSincResampler;
 typedef ::media::ShellAudioBus ShellAudioBus;
 #endif  // defined(COBALT_MEDIA_SOURCE_2016)
 
@@ -49,6 +54,13 @@
   AudioLock::AutoLock lock(audio_lock());
 
   buffer_ = buffer;
+
+  if (buffer_->sample_rate() != context()->sample_rate()) {
+    interleaved_resampler_ =
+        std::unique_ptr<InterleavedSincResampler>(new InterleavedSincResampler(
+            buffer_->sample_rate() / context()->sample_rate(),
+            static_cast<int32>(buffer_->audio_bus()->channels())));
+  }
 }
 
 void AudioBufferSourceNode::Start(double when, double offset,
@@ -109,7 +121,9 @@
     return scoped_ptr<ShellAudioBus>();
   }
 
-  if (state_ == kStopped || buffer_->length() == read_index_) {
+  if (state_ == kStopped ||
+      (!interleaved_resampler_ && read_index_ == buffer_->length()) ||
+      (interleaved_resampler_ && interleaved_resampler_->ReachedEOS())) {
     *finished = true;
     return scoped_ptr<ShellAudioBus>();
   }
@@ -120,25 +134,93 @@
   DCHECK_EQ(sample_type, audio_bus->sample_type());
 
   int32 frames_to_end = buffer_->length() - read_index_;
-  number_of_frames = std::min(number_of_frames, frames_to_end);
+  int32 channel_count = static_cast<int32>(audio_bus->channels());
 
   scoped_ptr<ShellAudioBus> 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));
+    } 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));
+    }
+    read_index_ += audio_bus_frames;
+    return result.Pass();
+  }
+
+  // Resample audio if the audio buffer sample rate is not equal to the audio
+  // context sample rate.
+
+  // Queue frames.
+  while (!interleaved_resampler_->HasEnoughData(number_of_frames)) {
+    int32 frames_to_queue = static_cast<int32>(ceil(
+        number_of_frames * buffer_->sample_rate() / context()->sample_rate()));
+
+    frames_to_queue = std::min(frames_to_queue, frames_to_end);
+
+    if (sample_type == kSampleTypeInt16) {
+      int16* samples_in_int16 =
+          reinterpret_cast<int16*>(audio_bus->interleaved_data()) +
+          read_index_ * channel_count;
+      scoped_array<float> samples_in_float(
+          new float[frames_to_queue * channel_count]);
+      for (int32 i = 0; i < frames_to_queue * channel_count; ++i) {
+        samples_in_float[i] = ConvertSample<int16, float>(samples_in_int16[i]);
+      }
+
+      interleaved_resampler_->QueueBuffer(samples_in_float.Pass(),
+                                          frames_to_queue);
+    } else {
+      DCHECK_EQ(sample_type, kSampleTypeFloat32);
+
+      float* samples_in_float =
+          reinterpret_cast<float*>(audio_bus->interleaved_data()) +
+          read_index_ * channel_count;
+      interleaved_resampler_->QueueBuffer(samples_in_float, frames_to_queue);
+    }
+
+    read_index_ += frames_to_queue;
+    frames_to_end = buffer_->length() - read_index_;
+
+    // Last time queueing buffer: signify end of stream.
+    if (read_index_ == buffer_->length()) {
+      interleaved_resampler_->QueueBuffer(NULL, 0);
+    }
+  }
+
+  // Write resampled frames.
   if (sample_type == kSampleTypeInt16) {
-    result.reset(new media::ShellAudioBus(
-        audio_bus->channels(), number_of_frames,
-        reinterpret_cast<int16*>(audio_bus->interleaved_data()) +
-            read_index_ * audio_bus->channels()));
+    scoped_array<float> interleaved_output(
+        new float[number_of_frames * channel_count]);
+    interleaved_resampler_->Resample(interleaved_output.get(),
+                                     number_of_frames);
+
+    result.reset(new ShellAudioBus(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) =
+          ConvertSample<float, int16>(interleaved_output[i]);
+    }
   } else {
     DCHECK_EQ(sample_type, kSampleTypeFloat32);
 
-    result.reset(new media::ShellAudioBus(
-        audio_bus->channels(), number_of_frames,
-        reinterpret_cast<float*>(audio_bus->interleaved_data()) +
-            read_index_ * audio_bus->channels()));
+    result.reset(new ShellAudioBus(channel_count, number_of_frames,
+                                   kSampleTypeFloat32,
+                                   kStorageTypeInterleaved));
+    interleaved_resampler_->Resample(
+        reinterpret_cast<float*>(result->interleaved_data()), number_of_frames);
   }
 
-  read_index_ += number_of_frames;
   return result.Pass();
 }
 
diff --git a/src/cobalt/audio/audio_buffer_source_node.h b/src/cobalt/audio/audio_buffer_source_node.h
index 3d2b485..22634b4 100644
--- a/src/cobalt/audio/audio_buffer_source_node.h
+++ b/src/cobalt/audio/audio_buffer_source_node.h
@@ -21,8 +21,10 @@
 #include "cobalt/audio/audio_node.h"
 #include "cobalt/base/tokens.h"
 #if defined(COBALT_MEDIA_SOURCE_2016)
+#include "cobalt/media/base/interleaved_sinc_resampler.h"
 #include "cobalt/media/base/shell_audio_bus.h"
 #else  // defined(COBALT_MEDIA_SOURCE_2016)
+#include "media/base/interleaved_sinc_resampler.h"
 #include "media/base/shell_audio_bus.h"
 #endif  // defined(COBALT_MEDIA_SOURCE_2016)
 
@@ -35,8 +37,10 @@
 //   https://www.w3.org/TR/webaudio/#AudioBufferSourceNode
 class AudioBufferSourceNode : public AudioNode {
 #if defined(COBALT_MEDIA_SOURCE_2016)
+  typedef media::InterleavedSincResampler InterleavedSincResampler;
   typedef media::ShellAudioBus ShellAudioBus;
 #else   // defined(COBALT_MEDIA_SOURCE_2016)
+  typedef ::media::InterleavedSincResampler InterleavedSincResampler;
   typedef ::media::ShellAudioBus ShellAudioBus;
 #endif  // defined(COBALT_MEDIA_SOURCE_2016)
 
@@ -102,6 +106,10 @@
   // current playback position.
   int32 read_index_;
 
+  // |interleaved_resampler_| is the InterleavedSincResampler object that will
+  // be used if resampling needs to occur.
+  std::unique_ptr<InterleavedSincResampler> interleaved_resampler_;
+
   DISALLOW_COPY_AND_ASSIGN(AudioBufferSourceNode);
 };
 
diff --git a/src/cobalt/audio/audio_context.cc b/src/cobalt/audio/audio_context.cc
index f6e3f9a..54f8fea 100644
--- a/src/cobalt/audio/audio_context.cc
+++ b/src/cobalt/audio/audio_context.cc
@@ -23,7 +23,9 @@
     : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
       ALLOW_THIS_IN_INITIALIZER_LIST(
           weak_this_(weak_ptr_factory_.GetWeakPtr())),
-      sample_rate_(0.0f),
+      sample_rate_(
+          static_cast<float>(SbAudioSinkGetNearestSupportedSampleFrequency(
+              kStandardOutputSampleRate))),
       current_time_(0.0f),
       audio_lock_(new AudioLock()),
       ALLOW_THIS_IN_INITIALIZER_LIST(
diff --git a/src/cobalt/audio/audio_device.cc b/src/cobalt/audio/audio_device.cc
index f5969b0..2b99dc0 100644
--- a/src/cobalt/audio/audio_device.cc
+++ b/src/cobalt/audio/audio_device.cc
@@ -46,7 +46,6 @@
 namespace {
 const int kRenderBufferSizeFrames = 1024;
 const int kFramesPerChannel = kRenderBufferSizeFrames * 8;
-const int kStandardOutputSampleRate = 48000;
 }  // namespace
 
 #if defined(SB_USE_SB_AUDIO_SINK)
diff --git a/src/cobalt/audio/audio_helpers.h b/src/cobalt/audio/audio_helpers.h
index 39f5847..2914367 100644
--- a/src/cobalt/audio/audio_helpers.h
+++ b/src/cobalt/audio/audio_helpers.h
@@ -50,6 +50,8 @@
 
 const float kMaxInt16AsFloat32 = 32767.0f;
 
+const int kStandardOutputSampleRate = 48000;
+
 #if defined(OS_STARBOARD)
 // Get the size in bytes of an SbMediaAudioSampleType.
 inline size_t GetStarboardSampleTypeSize(SbMediaAudioSampleType sample_type) {
diff --git a/src/cobalt/audio/audio_node_input_output_test.cc b/src/cobalt/audio/audio_node_input_output_test.cc
index 7861788c..8f6c36f 100644
--- a/src/cobalt/audio/audio_node_input_output_test.cc
+++ b/src/cobalt/audio/audio_node_input_output_test.cc
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <math.h>
+
 #include "cobalt/audio/audio_buffer_source_node.h"
 #include "cobalt/audio/audio_context.h"
 #include "cobalt/audio/audio_helpers.h"
@@ -31,7 +33,7 @@
 typedef ::media::ShellAudioBus ShellAudioBus;
 #endif  // defined(COBALT_MEDIA_SOURCE_2016)
 
-const int kRenderBufferSizeFrames = 32;
+constexpr int kRenderBufferSizeFrames = 32;
 
 class AudioDestinationNodeMock : public AudioNode,
                                  public AudioDevice::RenderCallback {
@@ -76,7 +78,8 @@
   scoped_refptr<AudioContext> audio_context(new AudioContext());
   scoped_refptr<AudioBufferSourceNode> source(
       audio_context->CreateBufferSource());
-  scoped_refptr<AudioBuffer> buffer(new AudioBuffer(44100, src_data.Pass()));
+  scoped_refptr<AudioBuffer> buffer(
+      new AudioBuffer(audio_context->sample_rate(), src_data.Pass()));
   source->set_buffer(buffer);
 
   scoped_refptr<AudioDestinationNodeMock> destination(
@@ -116,9 +119,9 @@
 };
 
 TEST_F(AudioNodeInputOutputTest, StereoToStereoSpeakersLayoutTest) {
-  const size_t kNumOfSrcChannels = 2;
-  const size_t kNumOfDestChannels = 2;
-  const size_t kNumOfFrames = 25;
+  constexpr size_t kNumOfSrcChannels = 2;
+  constexpr size_t kNumOfDestChannels = 2;
+  constexpr size_t kNumOfFrames = 25;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
@@ -157,9 +160,9 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, StereoToStereoDiscreteLayoutTest) {
-  const size_t kNumOfSrcChannels = 2;
-  const size_t kNumOfDestChannels = 2;
-  const size_t kNumOfFrames = 25;
+  constexpr size_t kNumOfSrcChannels = 2;
+  constexpr size_t kNumOfDestChannels = 2;
+  constexpr size_t kNumOfFrames = 25;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
@@ -199,9 +202,9 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, MonoToStereoSpeakersLayoutTest) {
-  const size_t kNumOfSrcChannels = 1;
-  const size_t kNumOfDestChannels = 2;
-  const size_t kNumOfFrames = 25;
+  constexpr size_t kNumOfSrcChannels = 1;
+  constexpr size_t kNumOfDestChannels = 2;
+  constexpr size_t kNumOfFrames = 25;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
@@ -234,9 +237,9 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, MonoToStereoDiscreteLayoutTest) {
-  const size_t kNumOfSrcChannels = 1;
-  const size_t kNumOfDestChannels = 2;
-  const size_t kNumOfFrames = 25;
+  constexpr size_t kNumOfSrcChannels = 1;
+  constexpr size_t kNumOfDestChannels = 2;
+  constexpr size_t kNumOfFrames = 25;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
@@ -269,9 +272,9 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, QuadToStereoSpeakersLayoutTest) {
-  const size_t kNumOfSrcChannels = 4;
-  const size_t kNumOfDestChannels = 2;
-  const size_t kNumOfFrames = 25;
+  constexpr size_t kNumOfSrcChannels = 4;
+  constexpr size_t kNumOfDestChannels = 2;
+  constexpr size_t kNumOfFrames = 25;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
@@ -311,9 +314,9 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, QuadToStereoDiscreteLayoutTest) {
-  const size_t kNumOfSrcChannels = 4;
-  const size_t kNumOfDestChannels = 2;
-  const size_t kNumOfFrames = 25;
+  constexpr size_t kNumOfSrcChannels = 4;
+  constexpr size_t kNumOfDestChannels = 2;
+  constexpr size_t kNumOfFrames = 25;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
@@ -353,9 +356,9 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, FivePointOneToStereoSpeakersLayoutTest) {
-  const size_t kNumOfSrcChannels = 6;
-  const size_t kNumOfDestChannels = 2;
-  const size_t kNumOfFrames = 10;
+  constexpr size_t kNumOfSrcChannels = 6;
+  constexpr size_t kNumOfDestChannels = 2;
+  constexpr size_t kNumOfFrames = 10;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
@@ -395,9 +398,9 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, FivePointOneToStereoDiscreteLayoutTest) {
-  const size_t kNumOfSrcChannels = 6;
-  const size_t kNumOfDestChannels = 2;
-  const size_t kNumOfFrames = 10;
+  constexpr size_t kNumOfSrcChannels = 6;
+  constexpr size_t kNumOfDestChannels = 2;
+  constexpr size_t kNumOfFrames = 10;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
@@ -437,9 +440,9 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, StereoToMonoSpeakersLayoutTest) {
-  const size_t kNumOfSrcChannels = 2;
-  const size_t kNumOfDestChannels = 1;
-  const size_t kNumOfFrames = 25;
+  constexpr size_t kNumOfSrcChannels = 2;
+  constexpr size_t kNumOfDestChannels = 1;
+  constexpr size_t kNumOfFrames = 25;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
@@ -475,9 +478,9 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, StereoToMonoDiscreteLayoutTest) {
-  const size_t kNumOfSrcChannels = 2;
-  const size_t kNumOfDestChannels = 1;
-  const size_t kNumOfFrames = 25;
+  constexpr size_t kNumOfSrcChannels = 2;
+  constexpr size_t kNumOfDestChannels = 1;
+  constexpr size_t kNumOfFrames = 25;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
@@ -513,9 +516,9 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, QuadToMonoSpeakersLayoutTest) {
-  const size_t kNumOfSrcChannels = 4;
-  const size_t kNumOfDestChannels = 1;
-  const size_t kNumOfFrames = 25;
+  constexpr size_t kNumOfSrcChannels = 4;
+  constexpr size_t kNumOfDestChannels = 1;
+  constexpr size_t kNumOfFrames = 25;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
@@ -551,9 +554,9 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, QuadToMonoDiscreteLayoutTest) {
-  const size_t kNumOfSrcChannels = 4;
-  const size_t kNumOfDestChannels = 1;
-  const size_t kNumOfFrames = 25;
+  constexpr size_t kNumOfSrcChannels = 4;
+  constexpr size_t kNumOfDestChannels = 1;
+  constexpr size_t kNumOfFrames = 25;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
@@ -589,9 +592,9 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, FivePointOneToMonoSpeakersLayoutTest) {
-  const size_t kNumOfSrcChannels = 6;
-  const size_t kNumOfDestChannels = 1;
-  const size_t kNumOfFrames = 10;
+  constexpr size_t kNumOfSrcChannels = 6;
+  constexpr size_t kNumOfDestChannels = 1;
+  constexpr size_t kNumOfFrames = 10;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
@@ -627,9 +630,9 @@
 }
 
 TEST_F(AudioNodeInputOutputTest, FivePointOneToMonoDiscreteLayoutTest) {
-  const size_t kNumOfSrcChannels = 6;
-  const size_t kNumOfDestChannels = 1;
-  const size_t kNumOfFrames = 10;
+  constexpr size_t kNumOfSrcChannels = 6;
+  constexpr size_t kNumOfDestChannels = 1;
+  constexpr size_t kNumOfFrames = 10;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationDiscrete;
 
@@ -667,12 +670,12 @@
 TEST_F(AudioNodeInputOutputTest, MultipleInputNodesLayoutTest) {
   scoped_refptr<AudioContext> audio_context(new AudioContext());
 
-  const size_t kNumOfSrcChannels = 2;
-  const size_t kNumOfDestChannels = 2;
+  constexpr size_t kNumOfSrcChannels = 2;
+  constexpr size_t kNumOfDestChannels = 2;
   const AudioNodeChannelInterpretation kInterpretation =
       kAudioNodeChannelInterpretationSpeakers;
 
-  const size_t kNumOfFrames_1 = 25;
+  constexpr size_t kNumOfFrames_1 = 25;
   float src_data_in_float_1[50];
   for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
     for (size_t frame = 0; frame < kNumOfFrames_1; ++frame) {
@@ -686,10 +689,10 @@
   scoped_refptr<AudioBufferSourceNode> source_1(
       audio_context->CreateBufferSource());
   scoped_refptr<AudioBuffer> buffer_1(
-      new AudioBuffer(44100, src_data_1.Pass()));
+      new AudioBuffer(audio_context->sample_rate(), src_data_1.Pass()));
   source_1->set_buffer(buffer_1);
 
-  const size_t kNumOfFrames_2 = 50;
+  constexpr size_t kNumOfFrames_2 = 50;
   float src_data_in_float_2[100];
   for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
     for (size_t frame = 0; frame < kNumOfFrames_2; ++frame) {
@@ -703,7 +706,7 @@
   scoped_refptr<AudioBufferSourceNode> source_2(
       audio_context->CreateBufferSource());
   scoped_refptr<AudioBuffer> buffer_2(
-      new AudioBuffer(44100, src_data_2.Pass()));
+      new AudioBuffer(audio_context->sample_rate(), src_data_2.Pass()));
   source_2->set_buffer(buffer_2);
 
   scoped_refptr<AudioDestinationNodeMock> destination(
@@ -743,24 +746,23 @@
   }
 }
 
-TEST_F(AudioNodeInputOutputTest, CreateBufferTest) {
-  const size_t kNumOfChannels = 2;
-  const size_t kNumOfFrames = 25;
-  const size_t kSampleRate = 44100;
+TEST_F(AudioNodeInputOutputTest, CreateBufferLayoutTest) {
+  constexpr size_t kNumOfChannels = 2;
+  constexpr size_t kNumOfFrames = 25;
 
   scoped_refptr<AudioContext> audio_context(new AudioContext());
-  scoped_refptr<AudioBuffer> buffer(
-      audio_context->CreateBuffer(kNumOfChannels, kNumOfFrames, kSampleRate));
+  scoped_refptr<AudioBuffer> buffer(audio_context->CreateBuffer(
+      kNumOfChannels, kNumOfFrames, audio_context->sample_rate()));
 
   EXPECT_EQ(buffer->number_of_channels(), kNumOfChannels);
   EXPECT_EQ(buffer->length(), kNumOfFrames);
-  EXPECT_EQ(buffer->sample_rate(), kSampleRate);
+  EXPECT_EQ(buffer->sample_rate(), audio_context->sample_rate());
 }
 
-TEST_F(AudioNodeInputOutputTest, CopyToChannelPlanarFloat32Test) {
-  const size_t kNumOfChannels = 2;
-  const size_t kNumOfFrames = 25;
-  const size_t kOffset = 8;
+TEST_F(AudioNodeInputOutputTest, CopyToChannelPlanarFloat32LayoutTest) {
+  constexpr size_t kNumOfChannels = 2;
+  constexpr size_t kNumOfFrames = 25;
+  constexpr size_t kOffset = 8;
 
   float src_arr[kNumOfChannels][kNumOfFrames];
   for (size_t channel = 0; channel < kNumOfChannels; ++channel) {
@@ -779,7 +781,8 @@
       new ShellAudioBus(kNumOfChannels, kRenderBufferSizeFrames,
                         ShellAudioBus::kFloat32, ShellAudioBus::kPlanar));
   audio_bus->ZeroAllFrames();
-  scoped_refptr<AudioBuffer> buffer(new AudioBuffer(44100, audio_bus.Pass()));
+  scoped_refptr<AudioBuffer> buffer(
+      new AudioBuffer(audio_context->sample_rate(), audio_bus.Pass()));
   buffer->CopyToChannel(channel0_arr, 0, kOffset, NULL);
   buffer->CopyToChannel(channel1_arr, 1, kOffset, NULL);
 
@@ -795,10 +798,10 @@
   }
 }
 
-TEST_F(AudioNodeInputOutputTest, CopyToChannelInterleavedFloat32Test) {
-  const size_t kNumOfChannels = 2;
-  const size_t kNumOfFrames = 25;
-  const size_t kOffset = 8;
+TEST_F(AudioNodeInputOutputTest, CopyToChannelInterleavedFloat32LayoutTest) {
+  constexpr size_t kNumOfChannels = 2;
+  constexpr size_t kNumOfFrames = 25;
+  constexpr size_t kOffset = 8;
 
   float src_arr[kNumOfChannels][kNumOfFrames];
   for (size_t channel = 0; channel < kNumOfChannels; ++channel) {
@@ -817,7 +820,8 @@
       new ShellAudioBus(kNumOfChannels, kRenderBufferSizeFrames,
                         ShellAudioBus::kFloat32, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
-  scoped_refptr<AudioBuffer> buffer(new AudioBuffer(44100, audio_bus.Pass()));
+  scoped_refptr<AudioBuffer> buffer(
+      new AudioBuffer(audio_context->sample_rate(), audio_bus.Pass()));
   buffer->CopyToChannel(channel0_arr, 0, kOffset, NULL);
   buffer->CopyToChannel(channel1_arr, 1, kOffset, NULL);
 
@@ -833,10 +837,10 @@
   }
 }
 
-TEST_F(AudioNodeInputOutputTest, CopyToChannelPlanarInt16Test) {
-  const size_t kNumOfChannels = 2;
-  const size_t kNumOfFrames = 25;
-  const size_t kOffset = 8;
+TEST_F(AudioNodeInputOutputTest, CopyToChannelPlanarInt16LayoutTest) {
+  constexpr size_t kNumOfChannels = 2;
+  constexpr size_t kNumOfFrames = 25;
+  constexpr size_t kOffset = 8;
 
   float src_arr[kNumOfChannels][kNumOfFrames];
   for (size_t channel = 0; channel < kNumOfChannels; ++channel) {
@@ -855,7 +859,8 @@
       new ShellAudioBus(kNumOfChannels, kRenderBufferSizeFrames,
                         ShellAudioBus::kInt16, ShellAudioBus::kPlanar));
   audio_bus->ZeroAllFrames();
-  scoped_refptr<AudioBuffer> buffer(new AudioBuffer(44100, audio_bus.Pass()));
+  scoped_refptr<AudioBuffer> buffer(
+      new AudioBuffer(audio_context->sample_rate(), audio_bus.Pass()));
   buffer->CopyToChannel(channel0_arr, 0, kOffset, NULL);
   buffer->CopyToChannel(channel1_arr, 1, kOffset, NULL);
 
@@ -873,10 +878,10 @@
   }
 }
 
-TEST_F(AudioNodeInputOutputTest, CopyToChannelInterleavedInt16Test) {
-  const size_t kNumOfChannels = 2;
-  const size_t kNumOfFrames = 25;
-  const size_t kOffset = 8;
+TEST_F(AudioNodeInputOutputTest, CopyToChannelInterleavedInt16LayoutTest) {
+  constexpr size_t kNumOfChannels = 2;
+  constexpr size_t kNumOfFrames = 25;
+  constexpr size_t kOffset = 8;
 
   float src_arr[kNumOfChannels][kNumOfFrames];
   for (size_t channel = 0; channel < kNumOfChannels; ++channel) {
@@ -895,7 +900,8 @@
       new ShellAudioBus(kNumOfChannels, kRenderBufferSizeFrames,
                         ShellAudioBus::kInt16, ShellAudioBus::kInterleaved));
   audio_bus->ZeroAllFrames();
-  scoped_refptr<AudioBuffer> buffer(new AudioBuffer(44100, audio_bus.Pass()));
+  scoped_refptr<AudioBuffer> buffer(
+      new AudioBuffer(audio_context->sample_rate(), audio_bus.Pass()));
   buffer->CopyToChannel(channel0_arr, 0, kOffset, NULL);
   buffer->CopyToChannel(channel1_arr, 1, kOffset, NULL);
 
@@ -913,5 +919,98 @@
   }
 }
 
+TEST_F(AudioNodeInputOutputTest, ResampleBufferSampleRateLayoutTest) {
+  constexpr size_t kNumOfSrcChannels = 2;
+  constexpr size_t kNumOfDestChannels = 2;
+  constexpr size_t kNumOfSrcFrames = 500;
+  constexpr size_t kNumOfDestFrames = 640;
+  const AudioNodeChannelInterpretation kInterpretation =
+      kAudioNodeChannelInterpretationSpeakers;
+  const size_t kBufferSampleRateArr[2] = {44100, 52200};
+  const SampleType kSampleTypeArr[2] = {kSampleTypeFloat32, kSampleTypeInt16};
+  constexpr float kMaxAvgError = 0.01f;
+
+  float src_arr[kNumOfSrcChannels][kNumOfSrcFrames];
+  for (size_t channel = 0; channel < kNumOfSrcChannels; ++channel) {
+    for (size_t frame = 0; frame < kNumOfSrcFrames; ++frame) {
+      // Values range from 0 to 1
+      float func_arg = (channel + kNumOfSrcChannels * frame) /
+                       static_cast<float>(kNumOfSrcFrames * kNumOfSrcChannels);
+      // Values range from -1 to 1
+      src_arr[channel][frame] = static_cast<float>(sin(2 * M_PI * func_arg));
+    }
+  }
+
+  script::Handle<script::Float32Array> channel_0_arr =
+      script::Float32Array::New(global_environment(), src_arr[0],
+                                kNumOfSrcFrames);
+  script::Handle<script::Float32Array> channel_1_arr =
+      script::Float32Array::New(global_environment(), src_arr[1],
+                                kNumOfSrcFrames);
+
+  for (size_t buffer_sample_rate : kBufferSampleRateArr) {
+    for (SampleType sample_type : kSampleTypeArr) {
+      scoped_ptr<ShellAudioBus> src_data(
+          new ShellAudioBus(kNumOfSrcChannels, kNumOfSrcFrames, sample_type,
+                            ShellAudioBus::kInterleaved));
+      src_data->ZeroAllFrames();
+      scoped_refptr<AudioBuffer> buffer(
+          new AudioBuffer(buffer_sample_rate, src_data.Pass()));
+      buffer->CopyToChannel(channel_0_arr, 0, 0, NULL);
+      buffer->CopyToChannel(channel_1_arr, 1, 0, NULL);
+
+      scoped_refptr<AudioContext> audio_context(new AudioContext());
+      scoped_refptr<AudioBufferSourceNode> source(
+          audio_context->CreateBufferSource());
+      source->set_buffer(buffer);
+
+      scoped_refptr<AudioDestinationNodeMock> destination(
+          new AudioDestinationNodeMock(audio_context.get()));
+      destination->set_channel_interpretation(kInterpretation);
+      source->Connect(destination, 0, 0, NULL);
+      source->Start(0, 0, NULL);
+
+      scoped_ptr<ShellAudioBus> audio_bus(
+          new ShellAudioBus(kNumOfDestChannels, kNumOfDestFrames, sample_type,
+                            ShellAudioBus::kInterleaved));
+      audio_bus->ZeroAllFrames();
+      bool silence = true;
+      destination->FillAudioBus(true, audio_bus.get(), &silence);
+      EXPECT_FALSE(silence);
+
+      size_t num_output_frames = static_cast<size_t>(
+          kNumOfSrcFrames * audio_context->sample_rate() / buffer_sample_rate);
+      float sum_of_errors = 0;
+      for (size_t channel = 0; channel < kNumOfDestChannels; ++channel) {
+        for (size_t frame = 0; frame < kNumOfDestFrames; ++frame) {
+          float func_arg =
+              (channel + kNumOfSrcChannels * frame) /
+              static_cast<float>((num_output_frames * kNumOfSrcChannels));
+          if (sample_type == kSampleTypeFloat32) {
+            float actual_val = audio_bus->GetFloat32Sample(channel, frame);
+            float expected_val = 0.0f;
+            if (frame < num_output_frames) {
+              expected_val = static_cast<float>(sin(2 * M_PI * func_arg));
+            }
+            sum_of_errors += fabs(actual_val - expected_val);
+          } else {
+            int16 actual_val = audio_bus->GetInt16Sample(channel, frame);
+            int16 expected_val = 0;
+            if (frame < num_output_frames) {
+              expected_val = ConvertSample<float, int16>(
+                  static_cast<float>(sin(2 * M_PI * func_arg)));
+            }
+            sum_of_errors += ConvertSample<int16, float>(
+                static_cast<int16>(abs(actual_val - expected_val)));
+          }
+        }
+      }
+
+      float avg_error = sum_of_errors / kNumOfDestFrames;
+      EXPECT_LE(avg_error, kMaxAvgError);
+    }
+  }
+}
+
 }  // namespace audio
 }  // namespace cobalt
diff --git a/src/cobalt/audio/audio_test.gyp b/src/cobalt/audio/audio_test.gyp
index f3b3020..1e7c749 100644
--- a/src/cobalt/audio/audio_test.gyp
+++ b/src/cobalt/audio/audio_test.gyp
@@ -19,7 +19,7 @@
   'targets': [
     # This target can choose the correct media dependency.
     {
-      'target_name': 'media',
+      'target_name': 'media_audio_test',
       'type': 'static_library',
       'conditions': [
         ['cobalt_media_source_2016==1', {
@@ -40,7 +40,7 @@
         'audio_node_input_output_test.cc',
       ],
       'dependencies': [
-        'media',
+        'media_audio_test',
         '<(DEPTH)/cobalt/dom/dom.gyp:dom',
         '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
         '<(DEPTH)/testing/gmock.gyp:gmock',
diff --git a/src/cobalt/base/base.gyp b/src/cobalt/base/base.gyp
index 4b70f93..cffd01c 100644
--- a/src/cobalt/base/base.gyp
+++ b/src/cobalt/base/base.gyp
@@ -20,6 +20,10 @@
       # generating PDBs.
       'product_name': 'cobalt_base',
       'type': 'static_library',
+      'defines': ["COBALT_ENABLE_VERSION_COMPATIBILITY_VALIDATIONS=1"],
+      'direct_dependent_settings': {
+         'defines': ["COBALT_ENABLE_VERSION_COMPATIBILITY_VALIDATIONS=1"],
+      },
       'sources': [
         'accessibility_changed_event.h',
         'address_sanitizer.h',
diff --git a/src/cobalt/base/token.cc b/src/cobalt/base/token.cc
index ba5b852..78bb9f0 100644
--- a/src/cobalt/base/token.cc
+++ b/src/cobalt/base/token.cc
@@ -68,6 +68,10 @@
 
 }  // namespace
 
+#ifdef ENABLE_TOKEN_ALPHABETICAL_SORTING
+bool Token::sort_alphabetically_ = false;
+#endif  // ENABLE_TOKEN_TEXT_SORTING
+
 Token::Token() : str_(TokenStorage::GetInstance()->GetStorage("")) {}
 
 Token::Token(const char* str)
diff --git a/src/cobalt/base/token.h b/src/cobalt/base/token.h
index 1bb3925..8f1efb7 100644
--- a/src/cobalt/base/token.h
+++ b/src/cobalt/base/token.h
@@ -20,6 +20,8 @@
 
 #include "base/basictypes.h"
 #include "base/hash_tables.h"
+#include "base/logging.h"
+#include "starboard/string.h"
 
 namespace base {
 
@@ -46,14 +48,39 @@
   void Initialize(const char* str);
 
   const char* str_;
+
+#ifdef ENABLE_TOKEN_ALPHABETICAL_SORTING
+
+ public:
+  // For unit tests to sort in a predictable order at the cost of efficiency.
+  // This is not thread safe!
+  class ScopedAlphabeticalSorting {
+   public:
+    ScopedAlphabeticalSorting() {
+      DLOG(WARNING)
+          << "ScopedAlphabeticalSorting should only be used for testing.";
+      DCHECK(!Token::sort_alphabetically_);
+      Token::sort_alphabetically_ = true;
+    }
+    ~ScopedAlphabeticalSorting() {
+      DCHECK(Token::sort_alphabetically_);
+      Token::sort_alphabetically_ = false;
+    }
+  };
+
+  static bool sort_alphabetically() { return sort_alphabetically_; }
+
+ private:
+  static bool sort_alphabetically_;
+#endif  // ENABLE_TOKEN_ALPHABETICAL_SORTING
 };
 
 inline bool operator==(const Token& lhs, const std::string& rhs) {
-  return strcmp(lhs.c_str(), rhs.c_str()) == 0;
+  return SbStringCompareAll(lhs.c_str(), rhs.c_str()) == 0;
 }
 
 inline bool operator==(const std::string& lhs, const Token& rhs) {
-  return strcmp(lhs.c_str(), rhs.c_str()) == 0;
+  return SbStringCompareAll(lhs.c_str(), rhs.c_str()) == 0;
 }
 
 inline bool operator!=(const Token& lhs, const Token& rhs) {
@@ -69,10 +96,20 @@
 }
 
 inline bool operator<(const Token& lhs, const Token& rhs) {
+#ifdef ENABLE_TOKEN_ALPHABETICAL_SORTING
+  if (Token::sort_alphabetically()) {
+    return SbStringCompareAll(lhs.c_str(), rhs.c_str()) < 0;
+  }
+#endif  // ENABLE_TOKEN_ALPHABETICAL_SORTING
   return lhs.c_str() < rhs.c_str();
 }
 
 inline bool operator>(const Token& lhs, const Token& rhs) {
+#ifdef ENABLE_TOKEN_ALPHABETICAL_SORTING
+  if (Token::sort_alphabetically()) {
+    return SbStringCompareAll(lhs.c_str(), rhs.c_str()) > 0;
+  }
+#endif  // ENABLE_TOKEN_ALPHABETICAL_SORTING
   return lhs.c_str() > rhs.c_str();
 }
 
diff --git a/src/cobalt/base/token_test.cc b/src/cobalt/base/token_test.cc
index a04b17f..a319543 100644
--- a/src/cobalt/base/token_test.cc
+++ b/src/cobalt/base/token_test.cc
@@ -20,6 +20,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 #include "base/logging.h"
+#include "starboard/string.h"
 
 namespace base {
 namespace {
@@ -54,6 +55,50 @@
   EXPECT_NE(empty, non_empty);
 }
 
+TEST(TokenTest, CompareSorted) {
+  std::vector<Token> tokens;
+
+  // Tokens constructed in no particular order so the storage isn't in order.
+  tokens.push_back(Token("Cobalt"));
+  tokens.push_back(Token("Chrome"));
+  tokens.push_back(Token("Firefox"));
+  tokens.push_back(Token("Mozilla"));
+  tokens.push_back(Token("Navigator"));
+  tokens.push_back(Token("Safari"));
+  tokens.push_back(Token("Explorer"));
+  tokens.push_back(Token("Opera"));
+
+  // Sort the vector so we iterate it alphabetically.
+  std::sort(tokens.begin(), tokens.end(),
+            [](const Token& l, const Token& r) -> bool {
+              return SbStringCompareAll(l.c_str(), r.c_str()) < 0;
+            });
+
+  // The natural order of the tokens is not alphabetical.
+  Token prev_token;
+  bool first = true;
+  bool in_order = true;
+  for (const auto token : tokens) {
+    if (!first) {
+      in_order &= (prev_token < token);
+    }
+    prev_token = token;
+    first = false;
+  }
+  EXPECT_FALSE(in_order);
+
+  // The order of the tokens should be alphabetical using a sort scope.
+  Token::ScopedAlphabeticalSorting sort_scope;
+  first = true;
+  for (const auto token : tokens) {
+    if (!first) {
+      EXPECT_LT(prev_token, token);
+    }
+    prev_token = token;
+    first = false;
+  }
+}
+
 TEST(TokenTest, Collision) {
   const char kTestString[] = "abcdefghijklmnopqrstuvwxyz";
   const uint32 kExtraTokens = 100;
diff --git a/src/cobalt/base/tokens.h b/src/cobalt/base/tokens.h
index 3d12ee2..26c35db 100644
--- a/src/cobalt/base/tokens.h
+++ b/src/cobalt/base/tokens.h
@@ -109,6 +109,7 @@
     MacroOpWithNameOnly(resize)                                      \
     MacroOpWithNameOnly(result)                                      \
     MacroOpWithNameOnly(resume)                                      \
+    MacroOpWithNameOnly(scroll)                                      \
     MacroOpWithNameOnly(securitypolicyviolation)                     \
     MacroOpWithNameOnly(seeked)                                      \
     MacroOpWithNameOnly(seeking)                                     \
diff --git a/src/cobalt/base/wrap_main_starboard.h b/src/cobalt/base/wrap_main_starboard.h
index 75e2749..32e50ac 100644
--- a/src/cobalt/base/wrap_main_starboard.h
+++ b/src/cobalt/base/wrap_main_starboard.h
@@ -101,8 +101,10 @@
     case kSbEventTypeUser:
     case kSbEventTypeLink:
     case kSbEventTypeVerticalSync:
+#if SB_API_VERSION < SB_DEPRECATE_DISCONNECT_VERSION
     case kSbEventTypeNetworkDisconnect:
     case kSbEventTypeNetworkConnect:
+#endif  // SB_API_VERSION < SB_DEPRECATE_DISCONNECT_VERSION
     case kSbEventTypeScheduled:
     case kSbEventTypeAccessiblitySettingsChanged:
 #if SB_API_VERSION >= 6
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc
index 7938c90..23810fb 100644
--- a/src/cobalt/browser/application.cc
+++ b/src/cobalt/browser/application.cc
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#if defined(STARBOARD)
+#include "starboard/client_porting/poem/stdio_leaks_poem.h"
+#endif
+
 #include "cobalt/browser/application.h"
 
 #include <algorithm>
@@ -57,7 +61,6 @@
 #include "cobalt/browser/switches.h"
 #include "cobalt/loader/image/image_decoder.h"
 #include "cobalt/math/size.h"
-#include "cobalt/network/network_event.h"
 #include "cobalt/script/javascript_engine.h"
 #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES)
 #include "cobalt/storage/savegame_fake.h"
@@ -720,10 +723,6 @@
   app_status_ = (should_preload ? kPreloadingAppStatus : kRunningAppStatus);
 
   // Register event callbacks.
-  network_event_callback_ =
-      base::Bind(&Application::OnNetworkEvent, base::Unretained(this));
-  event_dispatcher_.AddEventCallback(network::NetworkEvent::TypeId(),
-                                     network_event_callback_);
   deep_link_event_callback_ =
       base::Bind(&Application::OnDeepLinkEvent, base::Unretained(this));
   event_dispatcher_.AddEventCallback(base::DeepLinkEvent::TypeId(),
@@ -823,8 +822,6 @@
   memory_tracker_tool_.reset(NULL);
 
   // Unregister event callbacks.
-  event_dispatcher_.RemoveEventCallback(network::NetworkEvent::TypeId(),
-                                        network_event_callback_);
   event_dispatcher_.RemoveEventCallback(base::DeepLinkEvent::TypeId(),
                                         deep_link_event_callback_);
 #if SB_API_VERSION >= 8
@@ -940,14 +937,6 @@
       break;
 #endif  // SB_API_VERSION >= SB_ON_SCREEN_KEYBOARD_SUGGESTIONS_VERSION
 #endif  // SB_HAS(ON_SCREEN_KEYBOARD)
-    case kSbEventTypeNetworkConnect:
-      DispatchEventInternal(
-          new network::NetworkEvent(network::NetworkEvent::kConnection));
-      break;
-    case kSbEventTypeNetworkDisconnect:
-      DispatchEventInternal(
-          new network::NetworkEvent(network::NetworkEvent::kDisconnection));
-      break;
     case kSbEventTypeLink: {
       const char* link = static_cast<const char*>(starboard_event->data);
       DispatchEventInternal(new base::DeepLinkEvent(link));
@@ -968,6 +957,10 @@
 #if SB_API_VERSION >= 6
     case kSbEventTypePreload:
 #endif  // SB_API_VERSION >= 6
+#if SB_API_VERSION < SB_DEPRECATE_DISCONNECT_VERSION
+    case kSbEventTypeNetworkConnect:
+    case kSbEventTypeNetworkDisconnect:
+#endif  // SB_API_VERSION < SB_DEPRECATE_DISCONNECT_VERSION
     case kSbEventTypeScheduled:
     case kSbEventTypeStart:
     case kSbEventTypeStop:
@@ -978,28 +971,6 @@
   }
 }
 
-void Application::OnNetworkEvent(const base::Event* event) {
-  TRACE_EVENT0("cobalt::browser", "Application::OnNetworkEvent()");
-  DCHECK(network_event_thread_checker_.CalledOnValidThread());
-  const network::NetworkEvent* network_event =
-      base::polymorphic_downcast<const network::NetworkEvent*>(event);
-  if (network_event->type() == network::NetworkEvent::kDisconnection) {
-    LOG(INFO) << "Detected a network disconnection.";
-    network_status_ = kDisconnectedNetworkStatus;
-    ++network_disconnect_count_;
-    browser_module_->Navigate(GURL("h5vcc://network-failure"));
-  } else if (network_event->type() == network::NetworkEvent::kConnection) {
-    network_status_ = kConnectedNetworkStatus;
-    ++network_connect_count_;
-    if (network_disconnect_count_ > 0) {
-      DLOG(INFO) << "Got network connection event, reloading browser.";
-      browser_module_->Reload();
-    } else {
-      DLOG(INFO) << "Got network connection event, NOT reloading browser.";
-    }
-  }
-}
-
 void Application::OnApplicationEvent(SbEventType event_type) {
   TRACE_EVENT0("cobalt::browser", "Application::OnApplicationEvent()");
   DCHECK(application_event_thread_checker_.CalledOnValidThread());
@@ -1074,8 +1045,10 @@
     case kSbEventTypeAccessiblitySettingsChanged:
     case kSbEventTypeInput:
     case kSbEventTypeLink:
+#if SB_API_VERSION < SB_DEPRECATE_DISCONNECT_VERSION
     case kSbEventTypeNetworkConnect:
     case kSbEventTypeNetworkDisconnect:
+#endif  // SB_API_VERSION < SB_DEPRECATE_DISCONNECT_VERSION
     case kSbEventTypeScheduled:
     case kSbEventTypeUser:
     case kSbEventTypeVerticalSync:
diff --git a/src/cobalt/browser/browser.gyp b/src/cobalt/browser/browser.gyp
index 9ff6f60..09b607c 100644
--- a/src/cobalt/browser/browser.gyp
+++ b/src/cobalt/browser/browser.gyp
@@ -30,8 +30,6 @@
         'browser_module.h',
         'debug_console.cc',
         'debug_console.h',
-        'h5vcc_url_handler.cc',
-        'h5vcc_url_handler.h',
         'lifecycle_console_commands.cc',
         'lifecycle_console_commands.h',
         'lifecycle_observer.h',
diff --git a/src/cobalt/browser/browser_bindings_gen.gyp b/src/cobalt/browser/browser_bindings_gen.gyp
index 4826693..0281bd6 100644
--- a/src/cobalt/browser/browser_bindings_gen.gyp
+++ b/src/cobalt/browser/browser_bindings_gen.gyp
@@ -167,6 +167,7 @@
         '../h5vcc/h5vcc_audio_config_array.idl',
         '../h5vcc/h5vcc_crash_log.idl',
         '../h5vcc/h5vcc_deep_link_event_target.idl',
+        '../h5vcc/h5vcc_platform_service.idl',
         '../h5vcc/h5vcc_runtime.idl',
         '../h5vcc/h5vcc_runtime_event_target.idl',
         '../h5vcc/h5vcc_settings.idl',
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc
index affccf8..d65529d 100644
--- a/src/cobalt/browser/browser_module.cc
+++ b/src/cobalt/browser/browser_module.cc
@@ -289,8 +289,6 @@
   // Apply platform memory setting adjustments and defaults.
   ApplyAutoMemSettings();
 
-  h5vcc_url_handler_.reset(new H5vccURLHandler(this));
-
 #if SB_HAS(CORE_DUMP_HANDLER_SUPPORT)
   SbCoreDumpRegisterHandler(BrowserModule::CoreDumpHandler, this);
   on_error_triggered_count_ = 0;
@@ -459,13 +457,13 @@
   // simply set the pending navigate url, which will cause the navigation to
   // occur when Cobalt resumes, and return.
   if (application_state_ == base::kApplicationStateSuspended) {
-    pending_navigate_url_ = url.spec();
+    pending_navigate_url_ = url;
     return;
   }
 
   // Now that we know the navigation is occurring, clear out
   // |pending_navigate_url_|.
-  pending_navigate_url_.clear();
+  pending_navigate_url_ = GURL::EmptyGURL();
 
   // Destroy old WebModule first, so we don't get a memory high-watermark after
   // the second WebModule's constructor runs, but before scoped_ptr::reset() is
@@ -1175,7 +1173,7 @@
   // positive response; otherwise, if Cobalt is currently preloaded or
   // suspended, then this is the url that Cobalt will navigate to when it starts
   // or resumes.
-  pending_navigate_url_ = url.spec();
+  pending_navigate_url_ = url;
 
   // Start the OnErrorRetry() timer if it isn't already running.
   // The minimum delay between calls to OnErrorRetry() exponentially grows as
@@ -1203,8 +1201,28 @@
   ++on_error_retry_count_;
   on_error_retry_time_ = base::TimeTicks::Now();
   waiting_for_error_retry_ = true;
-  TryURLHandlers(
-      GURL("h5vcc://network-failure?retry-url=" + pending_navigate_url_));
+
+  SystemPlatformErrorHandler::SystemPlatformErrorOptions options;
+  options.error_type = kSbSystemPlatformErrorTypeConnectionError;
+  options.callback =
+      base::Bind(&BrowserModule::OnNetworkFailureSystemPlatformResponse,
+                 base::Unretained(this));
+  system_platform_error_handler_.RaiseSystemPlatformError(options);
+}
+
+void BrowserModule::OnNetworkFailureSystemPlatformResponse(
+    SbSystemPlatformErrorResponse response) {
+  // A positive response means we should retry, anything else we stop.
+  if (response == kSbSystemPlatformErrorResponsePositive) {
+    // We shouldn't be here if we don't have a pending URL from an error.
+    DCHECK(pending_navigate_url_.is_valid());
+    if (pending_navigate_url_.is_valid()) {
+      Navigate(pending_navigate_url_);
+    }
+  } else {
+    LOG(ERROR) << "Stop after network error";
+    SbSystemRequestStop(0);
+  }
 }
 
 bool BrowserModule::FilterKeyEvent(base::Token type,
@@ -1670,8 +1688,8 @@
                "BrowserModule::StartOrResumeInternalPostStateUpdate");
   // If there's a navigation that's pending, then attempt to navigate to its
   // specified URL now, unless we're still waiting for an error retry.
-  if (!pending_navigate_url_.empty() && !waiting_for_error_retry_) {
-    Navigate(GURL(pending_navigate_url_));
+  if (pending_navigate_url_.is_valid() && !waiting_for_error_retry_) {
+    Navigate(pending_navigate_url_);
   }
 }
 
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h
index ba70296..7d51d79 100644
--- a/src/cobalt/browser/browser_module.h
+++ b/src/cobalt/browser/browser_module.h
@@ -33,7 +33,6 @@
 #include "cobalt/base/on_screen_keyboard_hidden_event.h"
 #include "cobalt/base/on_screen_keyboard_shown_event.h"
 #include "cobalt/base/on_screen_keyboard_suggestions_updated_event.h"
-#include "cobalt/browser/h5vcc_url_handler.h"
 #include "cobalt/browser/lifecycle_console_commands.h"
 #include "cobalt/browser/lifecycle_observer.h"
 #include "cobalt/browser/memory_settings/auto_mem.h"
@@ -291,10 +290,16 @@
   // Error callback for any error that stops the program.
   void OnError(const GURL& url, const std::string& error);
 
-  // OnErrorRetry() runs a retry URL through the URL handlers. It should only be
-  // called by |on_error_retry_timer_|.
+  // OnErrorRetry() shows a platform network error to give the user a chance to
+  // fix broken network settings before retrying. It should only be called by
+  // |on_error_retry_timer_|.
   void OnErrorRetry();
 
+  // Navigates back to the URL that caused an error if the response from the
+  // platform error was positive, otherwise stops the app.
+  void OnNetworkFailureSystemPlatformResponse(
+      SbSystemPlatformErrorResponse response);
+
   // Filters a key event.
   // Returns true if the event should be passed on to other handlers,
   // false if it was consumed within this function.
@@ -551,9 +556,6 @@
   LifecycleConsoleCommands lifecycle_console_commands_;
 #endif  // defined(ENABLE_DEBUG_CONSOLE)
 
-  // Handler object for h5vcc URLs.
-  scoped_ptr<H5vccURLHandler> h5vcc_url_handler_;
-
   // The splash screen. The pointer wrapped here should be non-NULL iff
   // the splash screen is currently displayed.
   scoped_ptr<SplashScreen> splash_screen_;
@@ -577,7 +579,7 @@
   // state. This url is set within OnError() and also when a navigation is
   // deferred as a result of Cobalt being suspended; it is cleared when a
   // navigation occurs.
-  std::string pending_navigate_url_;
+  GURL pending_navigate_url_;
 
   // The number of OnErrorRetry() calls that have occurred since the last
   // OnDone() call. This is used to determine the exponential backoff delay
diff --git a/src/cobalt/browser/cobalt.gyp b/src/cobalt/browser/cobalt.gyp
index 818c991..6790f77 100644
--- a/src/cobalt/browser/cobalt.gyp
+++ b/src/cobalt/browser/cobalt.gyp
@@ -20,33 +20,49 @@
     {
       'target_name': 'cobalt',
       'type': '<(final_executable_type)',
-      'dependencies': [
-        '<(DEPTH)/cobalt/browser/browser.gyp:browser',
-      ],
       'conditions': [
-        ['clang and target_os not in ["tvos", "android", "orbis"] and sb_target_platform not in ["linux-x64x11-clang-3-6", "linux-x86x11"]', {
+        ['sb_evergreen != 1', {
           'dependencies': [
-            '<(DEPTH)/third_party/musl/musl.gyp:c'
+            '<(DEPTH)/cobalt/browser/browser.gyp:browser',
+          ],
+          'conditions': [
+            ['clang and target_os not in ["tvos", "android", "orbis"] and sb_target_platform not in ["linux-x64x11-clang-3-6", "linux-x86x11"]', {
+              'dependencies': [
+                '<(DEPTH)/third_party/musl/musl.gyp:c'
+              ],
+            }],
+            ['cobalt_enable_lib == 1', {
+              'sources': [
+                'lib/cobalt.def',
+                'lib/main.cc',
+              ],
+            }, {
+              'sources': [
+                'main.cc',
+              ],
+            }],
+            ['cobalt_splash_screen_file != ""', {
+              'dependencies': [
+                '<(DEPTH)/cobalt/browser/splash_screen/splash_screen.gyp:copy_splash_screen',
+              ],
+            }],
           ],
         }],
-        ['cobalt_enable_lib == 1', {
-          'sources': [
-            'lib/cobalt.def',
-            'lib/main.cc',
+        ['sb_evergreen == 1', {
+          'ldflags': [
+            '-Wl,--dynamic-list=<(DEPTH)/starboard/starboard.syms',
+            '-Wl,--whole-archive',
+            # TODO: Figure out how to take the gyp output from a variable.
+            'obj/starboard/linux/x64x11/evergreen/libstarboard_platform.a',
+            '-Wl,--no-whole-archive',
           ],
-        }, {
-          'sources': [
-            'main.cc',
-          ],
-        }],
-        ['cobalt_splash_screen_file != ""', {
           'dependencies': [
-            '<(DEPTH)/cobalt/browser/splash_screen/splash_screen.gyp:copy_splash_screen',
+            '<(DEPTH)/starboard/starboard.gyp:starboard_full',
+            '<(DEPTH)/cobalt/browser/cobalt.gyp:cobalt_evergreen',
           ],
         }],
       ],
     },
-
     {
       'target_name': 'cobalt_deploy',
       'type': 'none',
@@ -58,7 +74,6 @@
       },
       'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
     },
-
     {
       # Convenience target to build cobalt and copy the demos into
       # content/data/test/cobalt/demos
@@ -71,6 +86,43 @@
     },
   ],
   'conditions': [
+    ['sb_evergreen == 1', {
+      'targets': [
+        {
+          'target_name': 'cobalt_evergreen',
+          'type': 'shared_library',
+          'libraries/': [
+            ['exclude', '.*'],
+          ],
+          'dependencies': [
+            '<(DEPTH)/cobalt/base/base.gyp:base',
+            '<(DEPTH)/cobalt/browser/browser.gyp:browser',
+          ],
+          'sources': [
+            'main.cc',
+          ],
+          # TODO: Remove once the log.h is refactored to have only C linkage dependencies.
+          'ldflags': [
+            'obj/starboard/shared/starboard/starboard_platform.log_message.cc.o',
+          ],
+          'ldflags/': [
+            ['exclude', '-Wl,--wrap=eglSwapBuffers'],
+          ],
+          'conditions': [
+            ['clang and target_os not in ["tvos", "android", "orbis"] and sb_target_platform not in ["linux-x64x11-clang-3-6", "linux-x86x11"]', {
+              'dependencies': [
+                '<(DEPTH)/third_party/musl/musl.gyp:c'
+              ],
+            }],
+            ['cobalt_splash_screen_file != ""', {
+              'dependencies': [
+                '<(DEPTH)/cobalt/browser/splash_screen/splash_screen.gyp:copy_splash_screen',
+              ],
+            }],
+          ],
+        },
+      ]
+    }],
     ['build_snapshot_app_stats', {
       'targets': [
         {
diff --git a/src/cobalt/browser/h5vcc_url_handler.cc b/src/cobalt/browser/h5vcc_url_handler.cc
deleted file mode 100644
index cf295f4..0000000
--- a/src/cobalt/browser/h5vcc_url_handler.cc
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2015 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "cobalt/browser/h5vcc_url_handler.h"
-
-#include <string>
-
-#include "base/bind.h"
-#include "cobalt/browser/browser_module.h"
-#include "cobalt/browser/system_platform_error_handler.h"
-
-namespace cobalt {
-namespace browser {
-
-namespace {
-// Utility functions to get info from a GURL. Because our scheme is
-// non-standard, the methods in GURL will not parse the components for us.
-std::string GetH5vccUrlType(const GURL& url) {
-  const std::string path = url.path();
-  size_t start = path.find_first_not_of("/");
-  if (start == std::string::npos) {
-    DLOG(WARNING) << "Cannot determine type of URL.";
-    return "";
-  }
-  size_t end = path.find_first_of("/?&", start);
-  if (end != std::string::npos) {
-    end -= start;
-  }
-  return path.substr(start, end);
-}
-
-std::string GetH5vccUrlQuery(const GURL& url) {
-  const std::string path = url.path();
-  size_t start = path.find("?");
-  if (start == std::string::npos) {
-    DLOG(WARNING) << "Cannot find query string in URL.";
-    return "";
-  }
-  return path.substr(start + 1);
-}
-
-std::string GetH5vccUrlQueryParam(const GURL& url, const std::string& name) {
-  const std::string query = GetH5vccUrlQuery(url);
-  size_t name_start = query.find(name);
-  if (name_start == std::string::npos) {
-    DLOG(WARNING) << "Query parameter " << name << " not found in URL.";
-    return "";
-  }
-  size_t name_end = query.find('=', name_start);
-  if (name_end == std::string::npos) {
-    DLOG(WARNING) << "Value of " << name << " not found in URL.";
-    return "";
-  }
-  size_t value_start = name_end - name_start + 1;
-  size_t value_end = query.find_first_of('&', value_start);
-  if (value_end != std::string::npos) {
-    value_end -= value_start;
-  }
-  return query.substr(value_start, value_end);
-}
-
-const char kH5vccScheme[] = "h5vcc";
-const char kNetworkFailure[] = "network-failure";
-
-const char kRetryParam[] = "retry-url";
-}  // namespace
-
-H5vccURLHandler::H5vccURLHandler(BrowserModule* browser_module)
-    : ALLOW_THIS_IN_INITIALIZER_LIST(URLHandler(
-          browser_module,
-          base::Bind(&H5vccURLHandler::HandleURL, base::Unretained(this)))) {}
-
-bool H5vccURLHandler::HandleURL(const GURL& url) {
-  bool was_handled = false;
-  if (url.SchemeIs(kH5vccScheme)) {
-    url_ = url;
-    const std::string type = GetH5vccUrlType(url);
-    if (type == kNetworkFailure) {
-      was_handled = HandleNetworkFailure();
-    } else {
-      LOG(WARNING) << "Unknown h5vcc URL type: " << type;
-    }
-  }
-  return was_handled;
-}
-
-bool H5vccURLHandler::HandleNetworkFailure() {
-  SystemPlatformErrorHandler::SystemPlatformErrorOptions options;
-  options.error_type = kSbSystemPlatformErrorTypeConnectionError;
-  options.callback =
-      base::Bind(&H5vccURLHandler::OnNetworkFailureSystemPlatformResponse,
-                 base::Unretained(this));
-  browser_module()->system_platform_error_handler()->RaiseSystemPlatformError(
-      options);
-  return true;
-}
-
-void H5vccURLHandler::OnNetworkFailureSystemPlatformResponse(
-    SbSystemPlatformErrorResponse response) {
-  const std::string retry_url = GetH5vccUrlQueryParam(url_, kRetryParam);
-  // A positive response means we should retry.
-  if (response == kSbSystemPlatformErrorResponsePositive &&
-      retry_url.length() > 0) {
-    GURL url(retry_url);
-    if (url.is_valid()) {
-      browser_module()->Navigate(GURL(retry_url));
-      return;
-    }
-  }
-  // We were told not to retry, or don't have a retry URL, so leave the app.
-  LOG(ERROR) << "Stop after network error";
-  SbSystemRequestStop(0);
-}
-
-}  // namespace browser
-}  // namespace cobalt
diff --git a/src/cobalt/browser/h5vcc_url_handler.h b/src/cobalt/browser/h5vcc_url_handler.h
deleted file mode 100644
index 803808f..0000000
--- a/src/cobalt/browser/h5vcc_url_handler.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2015 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef COBALT_BROWSER_H5VCC_URL_HANDLER_H_
-#define COBALT_BROWSER_H5VCC_URL_HANDLER_H_
-
-#include "cobalt/account/account_manager.h"
-#include "cobalt/browser/url_handler.h"
-
-namespace cobalt {
-namespace browser {
-
-// Handler for URLs with the scheme h5vcc://. These URLs are not loaded into a
-// web module using the usual loader/fetcher mechanism, but are instead
-// handled separately, e.g. by showing a system dialog.
-class H5vccURLHandler : public URLHandler {
- public:
-  explicit H5vccURLHandler(BrowserModule* browser_module);
-  ~H5vccURLHandler() {}
-
- private:
-  bool HandleURL(const GURL& url);
-  bool HandleNetworkFailure();
-
-  void OnNetworkFailureSystemPlatformResponse(
-      SbSystemPlatformErrorResponse response);
-
-  GURL url_;
-};
-
-}  // namespace browser
-}  // namespace cobalt
-
-#endif  // COBALT_BROWSER_H5VCC_URL_HANDLER_H_
diff --git a/src/cobalt/browser/memory_settings/auto_mem.cc b/src/cobalt/browser/memory_settings/auto_mem.cc
index d1e60bd..5cf9304 100644
--- a/src/cobalt/browser/memory_settings/auto_mem.cc
+++ b/src/cobalt/browser/memory_settings/auto_mem.cc
@@ -35,6 +35,7 @@
 #include "cobalt/browser/memory_settings/pretty_print.h"
 #include "cobalt/browser/memory_settings/scaling_function.h"
 #include "cobalt/browser/switches.h"
+#include "cobalt/loader/image/image_decoder.h"
 #include "cobalt/math/clamp.h"
 
 namespace cobalt {
@@ -448,7 +449,9 @@
       switches::kImageCacheSizeInBytes,
       command_line_settings.cobalt_image_cache_size_in_bytes,
       build_settings.cobalt_image_cache_size_in_bytes,
-      CalculateImageCacheSize(ui_resolution));
+      CalculateImageCacheSize(
+          ui_resolution,
+          loader::image::ImageDecoder::AllowDecodingToMultiPlane()));
   EnsureValuePositive(image_cache_size_in_bytes_.get());
   image_cache_size_in_bytes_->set_memory_type(MemorySetting::kGPU);
   // ImageCache releases memory linearly until a progress value of 75%, then
diff --git a/src/cobalt/browser/memory_settings/auto_mem_test.cc b/src/cobalt/browser/memory_settings/auto_mem_test.cc
index 375d409..b7b7c8e 100644
--- a/src/cobalt/browser/memory_settings/auto_mem_test.cc
+++ b/src/cobalt/browser/memory_settings/auto_mem_test.cc
@@ -24,6 +24,7 @@
 #include "cobalt/browser/memory_settings/calculations.h"
 #include "cobalt/browser/memory_settings/test_common.h"
 #include "cobalt/browser/switches.h"
+#include "cobalt/loader/image/image_decoder.h"
 #include "cobalt/math/size.h"
 #include "starboard/system.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -130,7 +131,7 @@
 }
 
 // Tests the expectation that if the command line specifies that the variable
-// is "autoset" that the builtin setting is overriden.
+// is "autoset" that the builtin setting is overridden.
 TEST(AutoMem, CommandLineSpecifiesAutoset) {
   AutoMemSettings command_line_settings(AutoMemSettings::kTypeCommandLine);
   command_line_settings.cobalt_image_cache_size_in_bytes = -1;
@@ -139,9 +140,12 @@
 
   AutoMem auto_mem(kResolution1080p, command_line_settings, build_settings);
 
-  EXPECT_MEMORY_SETTING(auto_mem.image_cache_size_in_bytes(),
-                        MemorySetting::kAutoSet, MemorySetting::kGPU,
-                        CalculateImageCacheSize(kResolution1080p));
+  EXPECT_MEMORY_SETTING(
+      auto_mem.image_cache_size_in_bytes(), MemorySetting::kAutoSet,
+      MemorySetting::kGPU,
+      CalculateImageCacheSize(
+          kResolution1080p,
+          loader::image::ImageDecoder::AllowDecodingToMultiPlane()));
 }
 
 // Tests that skia atlas texture will be bind to the built in value, iff it has
diff --git a/src/cobalt/browser/memory_settings/calculations.cc b/src/cobalt/browser/memory_settings/calculations.cc
index 538bb87..7b5a35f 100644
--- a/src/cobalt/browser/memory_settings/calculations.cc
+++ b/src/cobalt/browser/memory_settings/calculations.cc
@@ -105,9 +105,17 @@
 
 }  // namespace
 
-int64_t CalculateImageCacheSize(const math::Size& dimensions) {
+int64_t CalculateImageCacheSize(const math::Size& dimensions,
+                                bool allow_image_decoding_to_multi_plane) {
   const double display_scale = DisplayScaleTo1080p(dimensions);
-  static const int64_t kReferenceSize1080p = 32 * 1024 * 1024;
+  const int64_t kReferenceSize1080p =
+      allow_image_decoding_to_multi_plane
+          ? kImageCacheSize1080pWithDecodingToMultiPlane
+          : kImageCacheSize1080pWithoutDecodingToMultiPlane;
+  const int64_t kMinImageCacheSize =
+      allow_image_decoding_to_multi_plane
+          ? kMinImageCacheSizeWithDecodingToMultiPlane
+          : kMinImageCacheSizeWithoutDecodingToMultiPlane;
   double output_bytes = kReferenceSize1080p * display_scale;
 
   return math::Clamp<int64_t>(static_cast<int64_t>(output_bytes),
diff --git a/src/cobalt/browser/memory_settings/calculations.h b/src/cobalt/browser/memory_settings/calculations.h
index 8544386..d034fa1 100644
--- a/src/cobalt/browser/memory_settings/calculations.h
+++ b/src/cobalt/browser/memory_settings/calculations.h
@@ -30,8 +30,12 @@
 // These functions are exposed here for testing purposes and should not be used
 // directly.
 // Calculates the ImageCacheSize in bytes.
-// The return ranges from [kMinImageCacheSize, kMaxImageCacheSize].
-int64_t CalculateImageCacheSize(const math::Size& dimensions);
+// When |allow_image_decoding_to_multi_plane| is set to true, the return ranges
+// from [kMinImageCacheSizeWithDecodingToMultiPlane, kMaxImageCacheSize].
+// When |allow_image_decoding_to_multi_plane| is set to false, the return ranges
+// from [kMinImageCacheSizeWithoutDecodingToMultiPlane, kMaxImageCacheSize].
+int64_t CalculateImageCacheSize(const math::Size& dimensions,
+                                bool allow_image_decoding_to_multi_plane);
 
 // Calculates the SkiaAtlasGlyphTextureSize.
 // When the ui resolution is 1920x1080, then the returned atlas texture size
diff --git a/src/cobalt/browser/memory_settings/calculations_test.cc b/src/cobalt/browser/memory_settings/calculations_test.cc
index 9ed2ea3..fdc4573 100644
--- a/src/cobalt/browser/memory_settings/calculations_test.cc
+++ b/src/cobalt/browser/memory_settings/calculations_test.cc
@@ -88,20 +88,47 @@
 }
 
 // Tests the expectation that CalculateImageCacheSize() is a pure function
-// (side effect free) and will produce the expected results.
-TEST(MemoryCalculations, CalculateImageCacheSize) {
-  EXPECT_EQ(kMinImageCacheSize, CalculateImageCacheSize(GetDimensions(k720p)));
-  EXPECT_EQ(32 * 1024 * 1024,  // 32MB.
-            CalculateImageCacheSize(GetDimensions(k1080p)));
-  EXPECT_EQ(kMaxImageCacheSize, CalculateImageCacheSize(GetDimensions(kUHD4k)));
+// (side effect free) and will produce the expected results when decoding to
+// multi-plane image is enabled.
+TEST(MemoryCalculations, CalculateImageCacheSizeWithDecodingToMultiPlane) {
+  EXPECT_EQ(kMinImageCacheSizeWithDecodingToMultiPlane,
+            CalculateImageCacheSize(GetDimensions(k720p), true));
+  EXPECT_EQ(kImageCacheSize1080pWithDecodingToMultiPlane,
+            CalculateImageCacheSize(GetDimensions(k1080p), true));
+  EXPECT_EQ(kMaxImageCacheSize,
+            CalculateImageCacheSize(GetDimensions(kUHD4k), true));
 
   // Expect that the floor is hit for smaller values.
-  EXPECT_EQ(kMinImageCacheSize, CalculateImageCacheSize(GetDimensions(k480p)));
+  EXPECT_EQ(kMinImageCacheSizeWithDecodingToMultiPlane,
+            CalculateImageCacheSize(GetDimensions(k480p), true));
 
   // Expect that the ceiling is hit for larger values.
   EXPECT_EQ(kMaxImageCacheSize,
-            CalculateImageCacheSize(GetDimensions(kCinema4k)));
-  EXPECT_EQ(kMaxImageCacheSize, CalculateImageCacheSize(GetDimensions(k8k)));
+            CalculateImageCacheSize(GetDimensions(kCinema4k), true));
+  EXPECT_EQ(kMaxImageCacheSize,
+            CalculateImageCacheSize(GetDimensions(k8k), true));
+}
+
+// Tests the expectation that CalculateImageCacheSize() is a pure function
+// (side effect free) and will produce the expected results when decoding to
+// multi-plane image is disabled.
+TEST(MemoryCalculations, CalculateImageCacheSizeWithoutDecodingToMultiPlane) {
+  EXPECT_EQ(kMinImageCacheSizeWithoutDecodingToMultiPlane,
+            CalculateImageCacheSize(GetDimensions(k720p), false));
+  EXPECT_EQ(kImageCacheSize1080pWithoutDecodingToMultiPlane,
+            CalculateImageCacheSize(GetDimensions(k1080p), false));
+  EXPECT_EQ(kMaxImageCacheSize,
+            CalculateImageCacheSize(GetDimensions(kUHD4k), false));
+
+  // Expect that the floor is hit for smaller values.
+  EXPECT_EQ(kMinImageCacheSizeWithoutDecodingToMultiPlane,
+            CalculateImageCacheSize(GetDimensions(k480p), false));
+
+  // Expect that the ceiling is hit for larger values.
+  EXPECT_EQ(kMaxImageCacheSize,
+            CalculateImageCacheSize(GetDimensions(kCinema4k), false));
+  EXPECT_EQ(kMaxImageCacheSize,
+            CalculateImageCacheSize(GetDimensions(k8k), false));
 }
 
 // Tests the expectation that CalculateSkiaGlyphAtlasTextureSize() is a pure
diff --git a/src/cobalt/browser/memory_settings/constants.h b/src/cobalt/browser/memory_settings/constants.h
index 2cee30a..74631b3 100644
--- a/src/cobalt/browser/memory_settings/constants.h
+++ b/src/cobalt/browser/memory_settings/constants.h
@@ -27,16 +27,33 @@
   // This was experimentally selected.
   kMiscCobaltCpuSizeInBytes = 119 * 1024 * 1024,
 
-  kMinImageCacheSize = 20 * 1024 * 1024,  // 20mb.
+  // Decoding image to multi-plane allows the decoded images to use 3/8 of
+  // memory compare to their RGBA counter part due to the more compact nature of
+  // the YUV images versus RGBA.  As most images in the image cache are jpegs,
+  // this effectively reduces the image cache size requirements by 2.  So we
+  // reduce the image cache size at 1080p and the minimum requirement of image
+  // cache by half when decoding to multi-plane is enabled.
+
+  // The image cache size when the output resolution is 1080p.  When calculating
+  // the image cache size for other output resolutions, we scale the image cache
+  // size at 1080p proportionally to the number of pixels of other resolutions.
+  // So when the pixels are doubled, the image cache size is also doubled,
+  // subject to clamping between minimum size and maximum size listed below.
+  kImageCacheSize1080pWithDecodingToMultiPlane = 16 * 1024 * 1024,     // 16mb
+  kImageCacheSize1080pWithoutDecodingToMultiPlane = 32 * 1024 * 1024,  // 32mb
+
+  kMinImageCacheSizeWithDecodingToMultiPlane = 10 * 1024 * 1024,     // 10mb
+  kMinImageCacheSizeWithoutDecodingToMultiPlane = 20 * 1024 * 1024,  // 20mb
+
   kMaxImageCacheSize = 64 * 1024 * 1024,  // 64mb
 
   kMinSkiaGlyphTextureAtlasWidth = 2048,
   kMinSkiaGlyphTextureAtlasHeight = 2048,
   kSkiaGlyphAtlasTextureBytesPerPixel = 2,
-  kDefaultRemoteTypeFaceCacheSize = 4 * 1024 * 1024,  // 4mb.
+  kDefaultRemoteTypeFaceCacheSize = 4 * 1024 * 1024,           // 4mb
   kDefaultJsGarbageCollectionThresholdSize = 8 * 1024 * 1024,  // 8mb
 
-  kMinSkiaCacheSize = 4 * 1024 * 1024,  // 4mb.
+  kMinSkiaCacheSize = 4 * 1024 * 1024,  // 4mb
 };
 
 }  // namespace memory_settings
diff --git a/src/cobalt/browser/on_screen_keyboard_starboard_bridge.cc b/src/cobalt/browser/on_screen_keyboard_starboard_bridge.cc
index 169a3d0..a5de195 100644
--- a/src/cobalt/browser/on_screen_keyboard_starboard_bridge.cc
+++ b/src/cobalt/browser/on_screen_keyboard_starboard_bridge.cc
@@ -52,14 +52,13 @@
     suggestions_data[i] = suggestions.at(i).c_str();
   }
   // Delay providing the SbWindow until as late as possible.
-  // TODO: Uncomment this when there's a Starboard implementation.
-  UNREFERENCED_PARAMETER(ticket);
-  // SbWindowUpdateOnScreenKeyboardSuggestions(
-  //     sb_window_provider_.Run(), suggestions_data.get(),
-  //     static_cast<int>(suggestions.size()), ticket);
+  SbWindowUpdateOnScreenKeyboardSuggestions(
+      sb_window_provider_.Run(), suggestions_data.get(),
+      static_cast<int>(suggestions.size()), ticket);
 #else
-  LOG(WARNING) << "Starboard version " << SB_API_VERSION
-               << " does not support on-screen keyboard suggestions.";
+  LOG(WARNING)
+      << "Starboard version " << SB_API_VERSION
+      << " does not support on-screen keyboard suggestions on this platform.";
 #endif  // SB_API_VERSION >= SB_ON_SCREEN_KEYBOARD_SUGGESTIONS_VERSION
 }
 
@@ -68,6 +67,19 @@
   return SbWindowIsOnScreenKeyboardShown(sb_window_provider_.Run());
 }
 
+bool OnScreenKeyboardStarboardBridge::SuggestionsSupported() const {
+// Delay providing the SbWindow until as late as possible.
+#if SB_API_VERSION >= SB_ON_SCREEN_KEYBOARD_SUGGESTIONS_VERSION
+  return SbWindowOnScreenKeyboardSuggestionsSupported(
+      sb_window_provider_.Run());
+#else
+  LOG(WARNING)
+      << "Starboard version " << SB_API_VERSION
+      << " does not support on-screen keyboard suggestions on this platform.";
+  return false;
+#endif  // SB_API_VERSION >= SB_ON_SCREEN_KEYBOARD_SUGGESTIONS_VERSION
+}
+
 scoped_refptr<dom::DOMRect>
 OnScreenKeyboardStarboardBridge::BoundingRect() const {
   // Delay providing the SbWindow until as late as possible.
diff --git a/src/cobalt/browser/on_screen_keyboard_starboard_bridge.h b/src/cobalt/browser/on_screen_keyboard_starboard_bridge.h
index ac87782..2b6bda0 100644
--- a/src/cobalt/browser/on_screen_keyboard_starboard_bridge.h
+++ b/src/cobalt/browser/on_screen_keyboard_starboard_bridge.h
@@ -49,6 +49,8 @@
 
   bool IsShown() const override;
 
+  bool SuggestionsSupported() const override;
+
   scoped_refptr<dom::DOMRect> BoundingRect() const override;
 
   bool IsValidTicket(int ticket) const override;
diff --git a/src/cobalt/browser/system_platform_error_handler.cc b/src/cobalt/browser/system_platform_error_handler.cc
index 22db204..339df63 100644
--- a/src/cobalt/browser/system_platform_error_handler.cc
+++ b/src/cobalt/browser/system_platform_error_handler.cc
@@ -25,6 +25,7 @@
 
   CallbackData* callback_data = new CallbackData{ &mutex_, options.callback };
 
+#if SB_API_VERSION < SB_DEPRECATE_CLEAR_PLATFORM_ERROR_VERSION
   SbSystemPlatformError handle = SbSystemRaisePlatformError(options.error_type,
       &SystemPlatformErrorHandler::HandleSystemPlatformErrorResponse,
       callback_data);
@@ -33,6 +34,16 @@
     delete callback_data;
     callback_data = nullptr;
   }
+#else   // SB_API_VERSION < SB_DEPRECATE_CLEAR_PLATFORM_ERROR_VERSION
+  if (!SbSystemRaisePlatformError(
+          options.error_type,
+          &SystemPlatformErrorHandler::HandleSystemPlatformErrorResponse,
+          callback_data)) {
+    DLOG(WARNING) << "Did not handle error: " << options.error_type;
+    delete callback_data;
+    callback_data = nullptr;
+  }
+#endif  // SB_API_VERSION < SB_DEPRECATE_CLEAR_PLATFORM_ERROR_VERSION
 
   // In case the response callback is never called, track the callback data
   // for all active errors. When this object is destroyed, all dangling data
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc
index e3eba8d..6380a83 100644
--- a/src/cobalt/browser/web_module.cc
+++ b/src/cobalt/browser/web_module.cc
@@ -606,6 +606,7 @@
   // accessible from |Window|, so we must explicitly add them as roots.
   global_environment_->AddRoot(&mutation_observer_task_manager_);
   global_environment_->AddRoot(media_source_registry_.get());
+  global_environment_->AddRoot(blob_registry_.get());
 
 #if defined(ENABLE_REMOTE_DEBUGGING)
   if (data.options.wait_for_web_debugger) {
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index 7ae88d5..bae6060 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-194710
\ No newline at end of file
+203995
\ No newline at end of file
diff --git a/src/cobalt/build/cobalt_configuration.gypi b/src/cobalt/build/cobalt_configuration.gypi
index e7ffa98..a94bac8 100644
--- a/src/cobalt/build/cobalt_configuration.gypi
+++ b/src/cobalt/build/cobalt_configuration.gypi
@@ -486,6 +486,7 @@
       'ENABLE_PARTIAL_LAYOUT_CONTROL',
       'ENABLE_TEST_DATA',
       'ENABLE_TEST_RUNNER',
+      'ENABLE_TOKEN_ALPHABETICAL_SORTING',
 
       # TODO: Rename to COBALT_LOGGING_ENABLED.
       '__LB_SHELL__FORCE_LOGGING__',
@@ -505,6 +506,7 @@
       'ENABLE_PARTIAL_LAYOUT_CONTROL',
       'ENABLE_TEST_DATA',
       'ENABLE_TEST_RUNNER',
+      'ENABLE_TOKEN_ALPHABETICAL_SORTING',
       '__LB_SHELL__FORCE_LOGGING__',
       'SK_DEVELOPER',
     ],
diff --git a/src/cobalt/css_parser/grammar.y b/src/cobalt/css_parser/grammar.y
index 4ce4b67..0378a14 100644
--- a/src/cobalt/css_parser/grammar.y
+++ b/src/cobalt/css_parser/grammar.y
@@ -252,6 +252,7 @@
 %token kReverseToken                    // reverse
 // %token kRightToken                   // right - also property name token
 %token kSansSerifToken                  // sans-serif
+%token kScrollToken                     // scroll
 %token kSerifToken                      // serif
 %token kSilverToken                     // silver
 %token kSolidToken                      // solid
@@ -1881,6 +1882,9 @@
   | kSansSerifToken {
     $$ = TrivialStringPiece::FromCString(cssom::kSansSerifKeywordName);
   }
+  | kScrollToken {
+    $$ = TrivialStringPiece::FromCString(cssom::kScrollKeywordName);
+  }
   | kSerifToken {
     $$ = TrivialStringPiece::FromCString(cssom::kSerifKeywordName);
   }
@@ -4158,12 +4162,19 @@
   ;
 
 // Specifies whether content of a block container element is clipped when it
-// overflows the element's box.
+// overflows the element's box and whether a scrolling mechanism should be
+// provided.
 //   https://www.w3.org/TR/CSS2/visufx.html#overflow
 overflow_property_value:
-    kHiddenToken maybe_whitespace {
+    kAutoToken maybe_whitespace {
+    $$ = AddRef(cssom::KeywordValue::GetAuto().get());
+  }
+  | kHiddenToken maybe_whitespace {
     $$ = AddRef(cssom::KeywordValue::GetHidden().get());
   }
+  | kScrollToken maybe_whitespace {
+    $$ = AddRef(cssom::KeywordValue::GetScroll().get());
+  }
   | kVisibleToken maybe_whitespace {
     $$ = AddRef(cssom::KeywordValue::GetVisible().get());
   }
diff --git a/src/cobalt/css_parser/parser_test.cc b/src/cobalt/css_parser/parser_test.cc
index f41d520..644f8e8 100644
--- a/src/cobalt/css_parser/parser_test.cc
+++ b/src/cobalt/css_parser/parser_test.cc
@@ -5575,6 +5575,14 @@
             style->GetPropertyValue(cssom::kOverflowWrapProperty));
 }
 
+TEST_F(ParserTest, ParsesAutoOverflow) {
+  scoped_refptr<cssom::CSSDeclaredStyleData> style =
+      parser_.ParseStyleDeclarationList("overflow: auto;", source_location_);
+
+  EXPECT_EQ(cssom::KeywordValue::GetAuto(),
+            style->GetPropertyValue(cssom::kOverflowProperty));
+}
+
 TEST_F(ParserTest, ParsesHiddenOverflow) {
   scoped_refptr<cssom::CSSDeclaredStyleData> style =
       parser_.ParseStyleDeclarationList("overflow: hidden;", source_location_);
@@ -5583,6 +5591,14 @@
             style->GetPropertyValue(cssom::kOverflowProperty));
 }
 
+TEST_F(ParserTest, ParsesScrollOverflow) {
+  scoped_refptr<cssom::CSSDeclaredStyleData> style =
+      parser_.ParseStyleDeclarationList("overflow: scroll;", source_location_);
+
+  EXPECT_EQ(cssom::KeywordValue::GetScroll(),
+            style->GetPropertyValue(cssom::kOverflowProperty));
+}
+
 TEST_F(ParserTest, ParsesVisibleOverflow) {
   scoped_refptr<cssom::CSSDeclaredStyleData> style =
       parser_.ParseStyleDeclarationList("overflow: visible;", source_location_);
diff --git a/src/cobalt/css_parser/scanner.cc b/src/cobalt/css_parser/scanner.cc
index 16d9094..93d609e 100644
--- a/src/cobalt/css_parser/scanner.cc
+++ b/src/cobalt/css_parser/scanner.cc
@@ -671,8 +671,18 @@
 
   // Negative numbers are handled in the grammar.
   bool dot_seen(false);
+  double integer_part(0);
+  double fractional_part(0);
+  int fractional_digits(0);
   while (true) {
-    if (!IsAsciiDigit(input_iterator_[0])) {
+    if (IsAsciiDigit(input_iterator_[0])) {
+      if (dot_seen) {
+        ++fractional_digits;
+        fractional_part = (fractional_part * 10) + (input_iterator_[0] - '0');
+      } else {
+        integer_part = (integer_part * 10) + (input_iterator_[0] - '0');
+      }
+    } else {
       // Only one dot is allowed for a number,
       // and it must be followed by a digit.
       if (input_iterator_[0] != '.' || dot_seen ||
@@ -699,40 +709,35 @@
 
   // Handle numbers in scientific notation.
   bool is_scientific(false);
-  if (IsAsciiAlphaCaselessEqual(*input_iterator_, 'e')) {
-    // Only one exponent symbol is allowed for a number,
-    // and it must be followed by a sign and digits, or just digits.
-    int exponent_prefix = 0;
-    if ((input_iterator_[1] == '-' || input_iterator_[1] == '+') &&
-         IsAsciiDigit(input_iterator_[2])) {
-      exponent_prefix = 3;
+  double exponent_part(0);
+  int exponent_sign(1);
+  if (IsAsciiAlphaCaselessEqual(input_iterator_[0], 'e')) {
+    int exponent_prefix(1);
+    if (input_iterator_[1] == '-') exponent_sign = -1;
+    if (input_iterator_[1] == '-' || input_iterator_[1] == '+') {
+      ++exponent_prefix;
     }
-    if (IsAsciiDigit(input_iterator_[1])) {
-      exponent_prefix = 2;
-    }
-    if (exponent_prefix > 0) {
+    if (IsAsciiDigit(input_iterator_[exponent_prefix])) {
       is_scientific = true;
       input_iterator_ = input_iterator_ + exponent_prefix;
     }
     while (IsAsciiDigit(input_iterator_[0])) {
+      exponent_part = (exponent_part * 10) + (input_iterator_[0] - '0');
       ++input_iterator_;
     }
   }
 
   number.end = input_iterator_;
-  char* number_end(const_cast<char*>(number.end));
-  // We parse into |double| for two reasons:
-  //   - C++03 doesn't have std::strtof() function;
-  //   - |float|'s significand is not large enough to represent |int| precisely.
-  // |number_end| is used by std::strtod() as a pure output parameter - it's
-  // input value is not used. std::strtod() may parse more of the number than
-  // we expect, e.g. in the case of hexadecimal format. In these cases
-  // (number_end != number.end), return an invalid number token.
-  double real_as_double(strtod(number.begin, &number_end));
-  if (number_end != number.end ||
-      real_as_double != real_as_double ||  // n != n if and only if it's NaN.
-      real_as_double == std::numeric_limits<float>::infinity() ||
-      real_as_double > std::numeric_limits<float>::max()) {
+
+  // Compute the number from its parts collected above according to:
+  // https://www.w3.org/TR/css-syntax-3/#convert-a-string-to-a-number
+  // We parse into |double| because |float|'s significand is not large enough
+  // to represent |int| precisely.
+  double real_as_double =
+      integer_part + fractional_part * pow(10, -fractional_digits);
+  if (is_scientific) real_as_double *= pow(10, exponent_sign * exponent_part);
+
+  if (real_as_double > std::numeric_limits<float>::max()) {
     token_value->string.begin = number.begin;
     token_value->string.end = number.end;
     return kInvalidNumberToken;
@@ -2134,6 +2139,10 @@
         *property_value_token = kRepeatToken;
         return true;
       }
+      if (IsEqualToCssIdentifier(name.begin, cssom::kScrollKeywordName)) {
+        *property_value_token = kScrollToken;
+        return true;
+      }
       if (IsEqualToCssIdentifier(name.begin, cssom::kSilverKeywordName)) {
         *property_value_token = kSilverToken;
         return true;
diff --git a/src/cobalt/css_parser/scanner_test.cc b/src/cobalt/css_parser/scanner_test.cc
index 5089c71..7879254 100644
--- a/src/cobalt/css_parser/scanner_test.cc
+++ b/src/cobalt/css_parser/scanner_test.cc
@@ -14,6 +14,8 @@
 
 #include "cobalt/css_parser/scanner.h"
 
+#include <locale.h>
+
 #include "cobalt/css_parser/grammar.h"
 #include "cobalt/css_parser/string_pool.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -24,13 +26,58 @@
 // When we support any of the at rule which already has a DISABLED test listed
 // below, this DISABLED test should be enabled.
 
-class ScannerTest : public ::testing::Test {
+const char* kNumericLocales[] = {
+  "",       // default locale
+  "C",      // dot radix separator
+  "de_DE",  // comma radix separator
+};
+
+class ScannerTest : public ::testing::Test,
+                    public ::testing::WithParamInterface<const char*> {
+ public:
+  virtual void SetUp() {
+    // Don't mess with the locale for non-parameterized tests.
+    if (!testing::UnitTest::GetInstance()->current_test_info()->value_param()) {
+      old_locale_.clear();
+      return;
+    }
+
+    // Save the old locale.
+    char* old_locale_cstr = setlocale(LC_NUMERIC, nullptr);
+    EXPECT_TRUE(old_locale_cstr != nullptr) << "Cant' save original locale";
+    old_locale_ = old_locale_cstr;
+
+    // Keep the default locale when the param is empty, and for other param
+    // values we'll skip the test if we can't change the locale to it.
+    locale_okay_ =
+        (GetParam()[0] == 0 || setlocale(LC_NUMERIC, GetParam()) != nullptr);
+  }
+
+  virtual void TearDown() {
+    if (!old_locale_.empty()) setlocale(LC_NUMERIC, old_locale_.c_str());
+  }
+
+  // TODO: Use GTEST_SKIP in |SetUp| when we have a newer version of gtest.
+  bool SkipLocale() {
+    if (!locale_okay_) {
+      std::cout << "[  SKIPPED ] Can't set locale to " << GetParam()
+                << std::endl;
+    }
+    return !locale_okay_;
+  }
+
  protected:
+  std::string old_locale_;
+  bool locale_okay_;
+
   StringPool string_pool_;
   TokenValue token_value_;
   YYLTYPE token_location_;
 };
 
+INSTANTIATE_TEST_CASE_P(LocaleNumeric, ScannerTest,
+                        ::testing::ValuesIn(kNumericLocales));
+
 TEST_F(ScannerTest, ScansSingleCodePointUnicodeRange) {
   Scanner scanner("u+1f4a9 U+1F4A9", &string_pool_);
 
@@ -317,7 +364,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansPercentage) {
+TEST_P(ScannerTest, ScansPercentage) {
+  if (SkipLocale()) return;
   Scanner scanner("2.71828%", &string_pool_);
 
   ASSERT_EQ(kPercentageToken, yylex(&token_value_, &token_location_, &scanner));
@@ -335,7 +383,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansReal) {
+TEST_P(ScannerTest, ScansReal) {
+  if (SkipLocale()) return;
   Scanner scanner("2.71828", &string_pool_);
 
   ASSERT_EQ(kRealToken, yylex(&token_value_, &token_location_, &scanner));
@@ -344,7 +393,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansNegativeReal) {
+TEST_P(ScannerTest, ScansNegativeReal) {
+  if (SkipLocale()) return;
   Scanner scanner("-3.14159", &string_pool_);
 
   ASSERT_EQ('-', yylex(&token_value_, &token_location_, &scanner));
@@ -390,7 +440,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansScientificNotationNumberWithPositiveExponent) {
+TEST_P(ScannerTest, ScansScientificNotationNumberWithPositiveExponent) {
+  if (SkipLocale()) return;
   Scanner scanner("2.5e+6", &string_pool_);
 
   ASSERT_EQ(kRealToken, yylex(&token_value_, &token_location_, &scanner));
@@ -408,29 +459,28 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
+TEST_F(ScannerTest, ScansScientificNotationNumberWithScientificExponent) {
+  // This should be parsed as the number "30000" and invalid dimension "e5",
+  // not 3*10^400000 nor 30000*10^5.
+  Scanner scanner("3e4e5", &string_pool_);
+
+  ASSERT_EQ(kInvalidDimensionToken,
+            yylex(&token_value_, &token_location_, &scanner));
+  ASSERT_EQ("3e4e5", token_value_.string);
+
+  ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
+}
+
 TEST_F(ScannerTest, ScansHexadecimalNumber) {
   // We don't support scanning of hexadecimal numbers. This test is just to
   // confirm that we recover without crashing.
   Scanner scanner("0x0", &string_pool_);
 
-#if defined(COBALT_WIN) && _MSC_VER < 1900
-  // On Windows, with a MSVS version prior to VS2015, the behavior of
-  // |std::strtod| used to scan a number doesn't conform to the standard, so
-  // we get a different result on that platform.
+  // "x0" is an invalid dimension.
   ASSERT_EQ(kInvalidDimensionToken,
             yylex(&token_value_, &token_location_, &scanner));
   ASSERT_EQ("0x0", token_value_.string);
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
-#else
-  ASSERT_EQ(kInvalidNumberToken,
-            yylex(&token_value_, &token_location_, &scanner));
-  ASSERT_EQ("0", token_value_.string);
-
-  ASSERT_EQ(kIdentifierToken, yylex(&token_value_, &token_location_, &scanner));
-  ASSERT_EQ("x0", token_value_.string);
-
-  ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
-#endif
 }
 
 TEST_F(ScannerTest, ScansUnknownDashFunction) {
@@ -1113,7 +1163,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansCentimeters) {
+TEST_P(ScannerTest, ScansCentimeters) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24cm", &string_pool_);
 
   ASSERT_EQ(kCentimetersToken,
@@ -1123,7 +1174,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansZeroGlyphWidthsAkaCh) {
+TEST_P(ScannerTest, ScansZeroGlyphWidthsAkaCh) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24ch", &string_pool_);
 
   ASSERT_EQ(kZeroGlyphWidthsAkaChToken,
@@ -1133,7 +1185,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansDegrees) {
+TEST_P(ScannerTest, ScansDegrees) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24deg", &string_pool_);
 
   ASSERT_EQ(kDegreesToken, yylex(&token_value_, &token_location_, &scanner));
@@ -1142,7 +1195,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansDotsPerPixel) {
+TEST_P(ScannerTest, ScansDotsPerPixel) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24dppx", &string_pool_);
 
   ASSERT_EQ(kDotsPerPixelToken,
@@ -1152,7 +1206,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansDotsPerCentimeter) {
+TEST_P(ScannerTest, ScansDotsPerCentimeter) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24dpcm", &string_pool_);
 
   ASSERT_EQ(kDotsPerCentimeterToken,
@@ -1162,7 +1217,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansDotsPerInch) {
+TEST_P(ScannerTest, ScansDotsPerInch) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24dpi", &string_pool_);
 
   ASSERT_EQ(kDotsPerInchToken,
@@ -1172,7 +1228,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansFontSizesAkaEm) {
+TEST_P(ScannerTest, ScansFontSizesAkaEm) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24em", &string_pool_);
 
   ASSERT_EQ(kFontSizesAkaEmToken,
@@ -1182,7 +1239,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansXHeightsAkaEx) {
+TEST_P(ScannerTest, ScansXHeightsAkaEx) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24ex", &string_pool_);
 
   ASSERT_EQ(kXHeightsAkaExToken,
@@ -1192,7 +1250,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansFractions) {
+TEST_P(ScannerTest, ScansFractions) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24fr", &string_pool_);
 
   ASSERT_EQ(kFractionsToken, yylex(&token_value_, &token_location_, &scanner));
@@ -1201,7 +1260,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansGradians) {
+TEST_P(ScannerTest, ScansGradians) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24grad", &string_pool_);
 
   ASSERT_EQ(kGradiansToken, yylex(&token_value_, &token_location_, &scanner));
@@ -1210,7 +1270,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansHertz) {
+TEST_P(ScannerTest, ScansHertz) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24hz", &string_pool_);
 
   ASSERT_EQ(kHertzToken, yylex(&token_value_, &token_location_, &scanner));
@@ -1219,7 +1280,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansInches) {
+TEST_P(ScannerTest, ScansInches) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24in", &string_pool_);
 
   ASSERT_EQ(kInchesToken, yylex(&token_value_, &token_location_, &scanner));
@@ -1228,7 +1290,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansKilohertz) {
+TEST_P(ScannerTest, ScansKilohertz) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24khz", &string_pool_);
 
   ASSERT_EQ(kKilohertzToken, yylex(&token_value_, &token_location_, &scanner));
@@ -1237,7 +1300,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansMillimeters) {
+TEST_P(ScannerTest, ScansMillimeters) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24mm", &string_pool_);
 
   ASSERT_EQ(kMillimetersToken,
@@ -1247,7 +1311,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansMilliseconds) {
+TEST_P(ScannerTest, ScansMilliseconds) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24ms", &string_pool_);
 
   ASSERT_EQ(kMillisecondsToken,
@@ -1257,7 +1322,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansPixels) {
+TEST_P(ScannerTest, ScansPixels) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24px", &string_pool_);
 
   ASSERT_EQ(kPixelsToken, yylex(&token_value_, &token_location_, &scanner));
@@ -1266,7 +1332,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansPoints) {
+TEST_P(ScannerTest, ScansPoints) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24pt", &string_pool_);
 
   ASSERT_EQ(kPointsToken, yylex(&token_value_, &token_location_, &scanner));
@@ -1275,7 +1342,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansPicas) {
+TEST_P(ScannerTest, ScansPicas) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24pc", &string_pool_);
 
   ASSERT_EQ(kPicasToken, yylex(&token_value_, &token_location_, &scanner));
@@ -1284,7 +1352,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansRadians) {
+TEST_P(ScannerTest, ScansRadians) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24rad", &string_pool_);
 
   ASSERT_EQ(kRadiansToken, yylex(&token_value_, &token_location_, &scanner));
@@ -1293,7 +1362,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansRootElementFontSizesAkaRem) {
+TEST_P(ScannerTest, ScansRootElementFontSizesAkaRem) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24rem", &string_pool_);
 
   ASSERT_EQ(kRootElementFontSizesAkaRemToken,
@@ -1303,7 +1373,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansSeconds) {
+TEST_P(ScannerTest, ScansSeconds) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24s", &string_pool_);
 
   ASSERT_EQ(kSecondsToken, yylex(&token_value_, &token_location_, &scanner));
@@ -1312,7 +1383,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansTurns) {
+TEST_P(ScannerTest, ScansTurns) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24turn", &string_pool_);
 
   ASSERT_EQ(kTurnsToken, yylex(&token_value_, &token_location_, &scanner));
@@ -1321,7 +1393,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansViewportWidthPercentsAkaVw) {
+TEST_P(ScannerTest, ScansViewportWidthPercentsAkaVw) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24vw", &string_pool_);
 
   ASSERT_EQ(kViewportWidthPercentsAkaVwToken,
@@ -1331,7 +1404,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansViewportHeightPercentsAkaVh) {
+TEST_P(ScannerTest, ScansViewportHeightPercentsAkaVh) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24vh", &string_pool_);
 
   ASSERT_EQ(kViewportHeightPercentsAkaVhToken,
@@ -1341,7 +1415,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScansViewportSmallerSizePercentsAkaVmin) {
+TEST_P(ScannerTest, ScansViewportSmallerSizePercentsAkaVmin) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24vmin", &string_pool_);
 
   ASSERT_EQ(kViewportSmallerSizePercentsAkaVminToken,
@@ -1351,7 +1426,8 @@
   ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner));
 }
 
-TEST_F(ScannerTest, ScanskViewportLargerSizePercentsAkaVmax) {
+TEST_P(ScannerTest, ScanskViewportLargerSizePercentsAkaVmax) {
+  if (SkipLocale()) return;
   Scanner scanner("8.24vmax", &string_pool_);
 
   ASSERT_EQ(kViewportLargerSizePercentsAkaVmaxToken,
diff --git a/src/cobalt/cssom/compound_selector.cc b/src/cobalt/cssom/compound_selector.cc
index fb067fc..eb441de 100644
--- a/src/cobalt/cssom/compound_selector.cc
+++ b/src/cobalt/cssom/compound_selector.cc
@@ -31,12 +31,6 @@
   if (rhs->type() < lhs->type()) {
     return false;
   }
-  if (lhs->prefix() < rhs->prefix()) {
-    return true;
-  }
-  if (rhs->prefix() < lhs->prefix()) {
-    return false;
-  }
   if (lhs->text() < rhs->text()) {
     return true;
   }
@@ -121,14 +115,12 @@
       !simple_selectors_.empty() ||
       simple_selector->AlwaysRequiresRuleMatchingVerificationVisit();
 
-  bool should_sort =
-      !simple_selectors_.empty() &&
-      SimpleSelectorsLessThan(simple_selector.get(), simple_selectors_.back());
-  simple_selectors_.push_back(simple_selector.release());
-  if (should_sort) {
-    std::sort(simple_selectors_.begin(), simple_selectors_.end(),
-              SimpleSelectorsLessThan);
-  }
+  // Insert the new selector in sorted order.
+  SimpleSelector* new_selector = simple_selector.release();
+  auto pos =
+      std::lower_bound(simple_selectors_.begin(), simple_selectors_.end(),
+                       new_selector, SimpleSelectorsLessThan);
+  simple_selectors_.insert(pos, new_selector);
 }
 
 void CompoundSelector::set_right_combinator(scoped_ptr<Combinator> combinator) {
diff --git a/src/cobalt/cssom/computed_style.cc b/src/cobalt/cssom/computed_style.cc
index 18f51e9..7bb30fc 100644
--- a/src/cobalt/cssom/computed_style.cc
+++ b/src/cobalt/cssom/computed_style.cc
@@ -373,6 +373,7 @@
     case KeywordValue::kReverse:
     case KeywordValue::kRight:
     case KeywordValue::kSansSerif:
+    case KeywordValue::kScroll:
     case KeywordValue::kSerif:
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
@@ -472,6 +473,7 @@
     case KeywordValue::kReverse:
     case KeywordValue::kRight:
     case KeywordValue::kSansSerif:
+    case KeywordValue::kScroll:
     case KeywordValue::kSerif:
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
@@ -578,6 +580,7 @@
     case KeywordValue::kReverse:
     case KeywordValue::kRight:
     case KeywordValue::kSansSerif:
+    case KeywordValue::kScroll:
     case KeywordValue::kSerif:
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
@@ -707,6 +710,7 @@
     case KeywordValue::kReverse:
     case KeywordValue::kRight:
     case KeywordValue::kSansSerif:
+    case KeywordValue::kScroll:
     case KeywordValue::kSerif:
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
@@ -831,6 +835,7 @@
     case KeywordValue::kReverse:
     case KeywordValue::kRight:
     case KeywordValue::kSansSerif:
+    case KeywordValue::kScroll:
     case KeywordValue::kSerif:
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
@@ -953,6 +958,7 @@
     case KeywordValue::kReverse:
     case KeywordValue::kRight:
     case KeywordValue::kSansSerif:
+    case KeywordValue::kScroll:
     case KeywordValue::kSerif:
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
@@ -1069,6 +1075,7 @@
     case KeywordValue::kReverse:
     case KeywordValue::kRight:
     case KeywordValue::kSansSerif:
+    case KeywordValue::kScroll:
     case KeywordValue::kSerif:
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
@@ -1179,6 +1186,7 @@
     case KeywordValue::kReverse:
     case KeywordValue::kRight:
     case KeywordValue::kSansSerif:
+    case KeywordValue::kScroll:
     case KeywordValue::kSerif:
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
@@ -1590,6 +1598,7 @@
     case KeywordValue::kReverse:
     case KeywordValue::kRight:
     case KeywordValue::kSansSerif:
+    case KeywordValue::kScroll:
     case KeywordValue::kSerif:
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
@@ -1867,6 +1876,7 @@
     case KeywordValue::kReverse:
     case KeywordValue::kRight:
     case KeywordValue::kSansSerif:
+    case KeywordValue::kScroll:
     case KeywordValue::kSerif:
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
@@ -2116,6 +2126,7 @@
     case KeywordValue::kReverse:
     case KeywordValue::kRight:
     case KeywordValue::kSansSerif:
+    case KeywordValue::kScroll:
     case KeywordValue::kSerif:
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
@@ -2500,6 +2511,7 @@
     case KeywordValue::kReverse:
     case KeywordValue::kRight:
     case KeywordValue::kSansSerif:
+    case KeywordValue::kScroll:
     case KeywordValue::kSerif:
     case KeywordValue::kSolid:
     case KeywordValue::kStart:
diff --git a/src/cobalt/cssom/css_style_rule.cc b/src/cobalt/cssom/css_style_rule.cc
index 9e97253..457e3b1 100644
--- a/src/cobalt/cssom/css_style_rule.cc
+++ b/src/cobalt/cssom/css_style_rule.cc
@@ -17,6 +17,7 @@
 #include "cobalt/cssom/css_rule_style_declaration.h"
 #include "cobalt/cssom/css_rule_visitor.h"
 #include "cobalt/cssom/css_style_sheet.h"
+#include "cobalt/cssom/serializer.h"
 
 namespace cobalt {
 namespace cssom {
@@ -33,6 +34,13 @@
   }
 }
 
+std::string CSSStyleRule::selector_text() const {
+  std::string output;
+  Serializer serializer(&output);
+  serializer.SerializeSelectors(selectors_);
+  return output;
+}
+
 const scoped_refptr<CSSStyleDeclaration> CSSStyleRule::style() const {
   return style_;
 }
diff --git a/src/cobalt/cssom/css_style_rule.h b/src/cobalt/cssom/css_style_rule.h
index d82e4e5..c4b4379 100644
--- a/src/cobalt/cssom/css_style_rule.h
+++ b/src/cobalt/cssom/css_style_rule.h
@@ -45,6 +45,7 @@
                const scoped_refptr<CSSRuleStyleDeclaration>& style);
 
   // Web API: CSSStyleRule
+  std::string selector_text() const;
   const scoped_refptr<CSSStyleDeclaration> style() const;
 
   // Web API: CSSRule
diff --git a/src/cobalt/cssom/css_style_rule.idl b/src/cobalt/cssom/css_style_rule.idl
index 99f75d8..f39a45b 100644
--- a/src/cobalt/cssom/css_style_rule.idl
+++ b/src/cobalt/cssom/css_style_rule.idl
@@ -15,5 +15,6 @@
 // https://www.w3.org/TR/2013/WD-cssom-20131205/#the-cssstylerule-interface
 
 interface CSSStyleRule : CSSRule {
+  readonly attribute DOMString selectorText;  // TODO: remove readonly
   [SameObject] readonly attribute CSSStyleDeclaration style;
 };
diff --git a/src/cobalt/cssom/cssom.gyp b/src/cobalt/cssom/cssom.gyp
index 8e527b3..36f78f3 100644
--- a/src/cobalt/cssom/cssom.gyp
+++ b/src/cobalt/cssom/cssom.gyp
@@ -198,6 +198,8 @@
         'simple_selector.h',
         'specificity.cc',
         'specificity.h',
+        'serializer.cc',
+        'serializer.h',
         'string_value.cc',
         'string_value.h',
         'style_sheet.cc',
diff --git a/src/cobalt/cssom/cssom_test.gyp b/src/cobalt/cssom/cssom_test.gyp
index d06c4d0..d08f3d8 100644
--- a/src/cobalt/cssom/cssom_test.gyp
+++ b/src/cobalt/cssom/cssom_test.gyp
@@ -48,12 +48,14 @@
         'selector_test.cc',
         'selector_tree_test.cc',
         'selector_visitor_test.cc',
+        'serializer_test.cc',
         'specificity_test.cc',
         'style_sheet_list_test.cc',
         'timing_function_test.cc',
         'transform_function_visitor_test.cc',
       ],
       'dependencies': [
+        '<(DEPTH)/cobalt/base/base.gyp:base',
         '<(DEPTH)/cobalt/css_parser/css_parser.gyp:css_parser',
         '<(DEPTH)/cobalt/cssom/cssom.gyp:cssom',
         '<(DEPTH)/cobalt/test/test.gyp:run_all_unittests',
diff --git a/src/cobalt/cssom/keyword_names.cc b/src/cobalt/cssom/keyword_names.cc
index 7a2c199..d22a30f 100644
--- a/src/cobalt/cssom/keyword_names.cc
+++ b/src/cobalt/cssom/keyword_names.cc
@@ -94,6 +94,7 @@
 const char kReverseKeywordName[] = "reverse";
 const char kRightKeywordName[] = "right";
 const char kSansSerifKeywordName[] = "sans-serif";
+const char kScrollKeywordName[] = "scroll";
 const char kSerifKeywordName[] = "serif";
 const char kSilverKeywordName[] = "silver";
 const char kSolidKeywordName[] = "solid";
diff --git a/src/cobalt/cssom/keyword_names.h b/src/cobalt/cssom/keyword_names.h
index fe751d1..390d2c7 100644
--- a/src/cobalt/cssom/keyword_names.h
+++ b/src/cobalt/cssom/keyword_names.h
@@ -96,6 +96,7 @@
 extern const char kReverseKeywordName[];
 extern const char kRightKeywordName[];
 extern const char kSansSerifKeywordName[];
+extern const char kScrollKeywordName[];
 extern const char kSerifKeywordName[];
 extern const char kSilverKeywordName[];
 extern const char kSolidKeywordName[];
diff --git a/src/cobalt/cssom/keyword_value.cc b/src/cobalt/cssom/keyword_value.cc
index 19873db..a2adff9 100644
--- a/src/cobalt/cssom/keyword_value.cc
+++ b/src/cobalt/cssom/keyword_value.cc
@@ -69,6 +69,7 @@
         reverse_value(new KeywordValue(KeywordValue::kReverse)),
         right_value(new KeywordValue(KeywordValue::kRight)),
         sans_serif_value(new KeywordValue(KeywordValue::kSansSerif)),
+        scroll_value(new KeywordValue(KeywordValue::kScroll)),
         serif_value(new KeywordValue(KeywordValue::kSerif)),
         solid_value(new KeywordValue(KeywordValue::kSolid)),
         start_value(new KeywordValue(KeywordValue::kStart)),
@@ -126,6 +127,7 @@
   const scoped_refptr<KeywordValue> reverse_value;
   const scoped_refptr<KeywordValue> right_value;
   const scoped_refptr<KeywordValue> sans_serif_value;
+  const scoped_refptr<KeywordValue> scroll_value;
   const scoped_refptr<KeywordValue> serif_value;
   const scoped_refptr<KeywordValue> solid_value;
   const scoped_refptr<KeywordValue> start_value;
@@ -327,6 +329,10 @@
   return non_trivial_static_fields.Get().sans_serif_value;
 }
 
+const scoped_refptr<KeywordValue>& KeywordValue::GetScroll() {
+  return non_trivial_static_fields.Get().scroll_value;
+}
+
 const scoped_refptr<KeywordValue>& KeywordValue::GetSerif() {
   return non_trivial_static_fields.Get().serif_value;
 }
@@ -457,6 +463,8 @@
       return kReverseKeywordName;
     case kRight:
       return kRightKeywordName;
+    case kScroll:
+      return kScrollKeywordName;
     case kSansSerif:
       return kSansSerifKeywordName;
     case kSerif:
diff --git a/src/cobalt/cssom/keyword_value.h b/src/cobalt/cssom/keyword_value.h
index 62c5e00..8fe7c5b 100644
--- a/src/cobalt/cssom/keyword_value.h
+++ b/src/cobalt/cssom/keyword_value.h
@@ -49,6 +49,10 @@
     // properties.
     //   https://www.w3.org/TR/CSS21/visudet.html#the-width-property
     //   https://www.w3.org/TR/CSS21/visudet.html#the-height-property
+    // "auto" is a value of the "overflow" property whose behavior is dependent
+    // on the user agent; it generally should provide a scrolling mechanism for
+    // overflowing boxes.
+    //   https://www.w3.org/TR/CSS21/visufx.html#overflow
     kAuto,
 
     // "backwards" is a value of "animation-fill-mode" property which causes the
@@ -277,6 +281,11 @@
     //   https://www.w3.org/TR/css3-fonts/#generic-font-families
     kSansSerif,
 
+    // "scroll" is a value of the "overflow" property which indicates that
+    // content is clipped and a scrolling mechanism should be provided.
+    //   https://www.w3.org/TR/CSS21/visufx.html#overflow
+    kScroll,
+
     // "serif" is a value of "font_family" property which indicates a generic
     // font family representing the formal text style for script.
     //   https://www.w3.org/TR/css3-fonts/#generic-font-families
@@ -376,6 +385,7 @@
   static const scoped_refptr<KeywordValue>& GetReverse();
   static const scoped_refptr<KeywordValue>& GetRight();
   static const scoped_refptr<KeywordValue>& GetSansSerif();
+  static const scoped_refptr<KeywordValue>& GetScroll();
   static const scoped_refptr<KeywordValue>& GetSerif();
   static const scoped_refptr<KeywordValue>& GetSolid();
   static const scoped_refptr<KeywordValue>& GetStart();
diff --git a/src/cobalt/cssom/keyword_value_test.cc b/src/cobalt/cssom/keyword_value_test.cc
index 421e992..5566852 100644
--- a/src/cobalt/cssom/keyword_value_test.cc
+++ b/src/cobalt/cssom/keyword_value_test.cc
@@ -50,6 +50,7 @@
   EXPECT_EQ(KeywordValue::kRepeat, KeywordValue::GetRepeat()->value());
   EXPECT_EQ(KeywordValue::kRight, KeywordValue::GetRight()->value());
   EXPECT_EQ(KeywordValue::kSansSerif, KeywordValue::GetSansSerif()->value());
+  EXPECT_EQ(KeywordValue::kScroll, KeywordValue::GetScroll()->value());
   EXPECT_EQ(KeywordValue::kSerif, KeywordValue::GetSerif()->value());
   EXPECT_EQ(KeywordValue::kStart, KeywordValue::GetStart()->value());
   EXPECT_EQ(KeywordValue::kStatic, KeywordValue::GetStatic()->value());
diff --git a/src/cobalt/cssom/not_pseudo_class.h b/src/cobalt/cssom/not_pseudo_class.h
index f3c670b..08cadcf 100644
--- a/src/cobalt/cssom/not_pseudo_class.h
+++ b/src/cobalt/cssom/not_pseudo_class.h
@@ -34,7 +34,7 @@
 class NotPseudoClass : public PseudoClass {
  public:
   NotPseudoClass()
-      : PseudoClass(base::Tokens::pseudo_class_selector_prefix()) {}
+      : PseudoClass(base::Tokens::not_pseudo_class_selector()) {}
   ~NotPseudoClass() override {}
 
   // From Selector.
diff --git a/src/cobalt/cssom/pseudo_class.h b/src/cobalt/cssom/pseudo_class.h
index 8fbd11d..aa5cea0 100644
--- a/src/cobalt/cssom/pseudo_class.h
+++ b/src/cobalt/cssom/pseudo_class.h
@@ -16,6 +16,8 @@
 #define COBALT_CSSOM_PSEUDO_CLASS_H_
 
 #include "base/compiler_specific.h"
+#include "cobalt/base/token.h"
+#include "cobalt/base/tokens.h"
 #include "cobalt/cssom/simple_selector.h"
 #include "cobalt/cssom/specificity.h"
 
diff --git a/src/cobalt/cssom/serializer.cc b/src/cobalt/cssom/serializer.cc
new file mode 100644
index 0000000..97e4255
--- /dev/null
+++ b/src/cobalt/cssom/serializer.cc
@@ -0,0 +1,458 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/cssom/serializer.h"
+#include "cobalt/cssom/active_pseudo_class.h"
+#include "cobalt/cssom/after_pseudo_element.h"
+#include "cobalt/cssom/attribute_selector.h"
+#include "cobalt/cssom/before_pseudo_element.h"
+#include "cobalt/cssom/class_selector.h"
+#include "cobalt/cssom/complex_selector.h"
+#include "cobalt/cssom/compound_selector.h"
+#include "cobalt/cssom/empty_pseudo_class.h"
+#include "cobalt/cssom/focus_pseudo_class.h"
+#include "cobalt/cssom/hover_pseudo_class.h"
+#include "cobalt/cssom/id_selector.h"
+#include "cobalt/cssom/not_pseudo_class.h"
+#include "cobalt/cssom/simple_selector.h"
+#include "cobalt/cssom/type_selector.h"
+
+namespace cobalt {
+namespace cssom {
+
+namespace {
+// Used to replace an unknown, unrecognized or unrepresentable character.
+constexpr int kUnicodeReplacementCharacter = 0xFFFD;
+constexpr char kUnicodeReplacementCharacterUtf8[] = u8"\uFFFD";
+}  // namespace
+
+Serializer::Serializer(std::string* output) : output_(output) {}
+
+void Serializer::SerializeIdentifier(base::Token identifier) {
+  // https://www.w3.org/TR/cssom/#serialize-an-identifier
+  //
+  // To serialize an identifier means to create a string represented by the
+  // concatenation of, for each character of the identifier: For each character
+  // of the identifier:
+  int char_num = 0;
+  uint32_t first_char = 0;
+  const uint8_t* next_p = reinterpret_cast<const uint8_t*>(identifier.c_str());
+  while (*next_p) {
+    uint32_t c;  // code point
+    const uint8_t* curr_p = next_p;
+    next_p += DecodeUTF8(curr_p, &c);
+
+    char_num++;
+    if (char_num == 1) first_char = c;
+
+    // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
+    // (U+FFFD).
+    if (c == 0x00) {
+      output_->append(kUnicodeReplacementCharacterUtf8);
+      continue;
+    }
+
+    // If the character is in the range [\1-\1f] (U+0001 to U+001F) or is
+    // U+007F, then the character escaped as code point.
+    if ((0x01 <= c && c <= 0x1F) || c == 0x7f) {
+      EscapeCodePoint(c);
+      continue;
+    }
+
+    // If the character is the first character and is in the range [0-9] (U+0030
+    // to U+0039), then the character escaped as code point.
+    bool is_numeric = ('0' <= c && c <= '9');
+    if (char_num == 1 && is_numeric) {
+      EscapeCodePoint(c);
+      continue;
+    }
+
+    // If the character is the second character and is in the range [0-9]
+    // (U+0030 to U+0039) and the first character is a "-" (U+002D), then the
+    // character escaped as code point.
+    if (char_num == 2 && is_numeric && first_char == '-') {
+      EscapeCodePoint(c);
+      continue;
+    }
+
+    // If the character is the first character and is a "-" (U+002D), and there
+    // is no second character, then the escaped character.
+    if (char_num == 1 && c == '-' && *next_p == '\0') {
+      EscapeCharacter(c);
+      continue;
+    }
+
+    // If the character is not handled by one of the above rules and is greater
+    // than or equal to U+0080, is "-" (U+002D) or "_" (U+005F), or is in one of
+    // the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to U+005A), or \[a-z]
+    // (U+0061 to U+007A), then the character itself.
+    if (c >= 0x80 || c == '-' || c == '_' || is_numeric ||
+        ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {
+      do {
+        output_->push_back(*curr_p);
+      } while (++curr_p < next_p);
+      continue;
+    }
+
+    // Otherwise, the escaped character.
+    EscapeCharacter(c);
+  }
+}
+
+void Serializer::SerializeString(const std::string& string) {
+  // https://www.w3.org/TR/cssom/#serialize-a-string
+  //
+  // Create a string represented by '"' (U+0022), followed by the result of
+  // applying the rules below to each character of the given string, followed by
+  // '"' (U+0022):
+  output_->push_back('"');
+
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(string.c_str());
+  for (size_t n = 0, length = string.length(); n < length; ++n) {
+    uint8_t c = p[n];
+
+    // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
+    // (U+FFFD) escaped as code point.
+    if (c == 0x00) {
+      EscapeCodePoint(kUnicodeReplacementCharacter);
+      continue;
+    }
+
+    // If the character is in the range [\1-\1f] (U+0001 to U+001F) or is
+    // U+007F, the character escaped as code point.
+    if ((0x01 <= c && c <= 0x1F) || c == 0x7f) {
+      EscapeCodePoint(c);
+      continue;
+    }
+
+    // If the character is '"' (U+0022) or "\" (U+005C), the escaped character.
+    if (c == 0x22 || c == 0x5c) {
+      EscapeCharacter(c);
+      continue;
+    }
+
+    // Otherwise, the character itself.
+    output_->push_back(c);
+  }
+
+  // ...followed by '"' (U+0022)
+  output_->push_back('"');
+}
+
+void Serializer::SerializeSelectors(const Selectors& selectors) {
+  // https://www.w3.org/TR/cssom/#serializing-selectors
+  //
+  // To serialize a group of selectors serialize each selector in the group of
+  // selectors and then serialize the group.
+  for (auto it = selectors.begin(); it != selectors.end(); it++) {
+    // https://www.w3.org/TR/cssom/#serialize-a-comma-separated-list
+    //
+    // To serialize a comma-separated list concatenate all items of the list in
+    // list order while separating them by ", ", i.e., COMMA (U+002C) followed
+    // by a single SPACE (U+0020).
+    if (it != selectors.begin()) {
+      output_->append(", ");
+    }
+    SerializeSelector(**it);
+  }
+}
+
+void Serializer::SerializeSelector(const Selector& selector) {
+  const_cast<Selector*>(&selector)->Accept(this);
+}
+
+int Serializer::DecodeUTF8(const uint8_t* p, uint32_t* out_c) {
+  DCHECK(p && *p);
+  uint32_t c;
+  int len;
+  if (p[0] < 0x80) {
+    *out_c = p[0];
+    return 1;
+  } else if (p[0] < 0xC0) {
+    DLOG(ERROR) << "Bad UTF-8 first byte";
+    *out_c = kUnicodeReplacementCharacter;
+    return 1;
+  } else if (p[0] < 0xE0) {
+    c = p[0] & 0x1F;
+    len = 2;
+  } else if (p[0] < 0xF0) {
+    c = p[0] & 0x0F;
+    len = 3;
+  } else if (p[0] < 0xF8) {
+    c = p[0] & 0x07;
+    len = 4;
+  } else {
+    DLOG(ERROR) << "Bad UTF-8 first byte";
+    *out_c = kUnicodeReplacementCharacter;
+    return 1;
+  }
+  for (int n = 1; n < len; ++n) {
+    if ((p[n] & 0xC0) != 0x80) {
+      DLOG(ERROR) << "Bad UTF-8 byte " << n;
+      *out_c = kUnicodeReplacementCharacter;
+      return n;
+    }
+    c <<= 6;
+    c |= p[n] & 0x3F;
+  }
+  *out_c = c;
+  return len;
+}
+
+void Serializer::EscapeCodePoint(uint32_t c) {
+  // Highest valid code point in Unicode.
+  DCHECK_LE(c, 0x10FFFFu);
+
+  // To escape a character as code point means to create a string of "\"
+  // (U+005C), followed by the Unicode code point as the smallest possible
+  // number of hexadecimal digits in the range 0-9 a-f (U+0030 to U+0039 and
+  // U+0061 to U+0066) to represent the code point in base 16, followed by a
+  // single SPACE (U+0020).
+  output_->push_back('\\');
+
+  constexpr char kHexDigits[] = "0123456789abcdef";
+  char buffer[9];
+  int pos = sizeof(buffer);
+  buffer[--pos] = '\0';
+  while (c != 0) {
+    buffer[--pos] = kHexDigits[c & 0x0F];
+    c >>= 4;
+  }
+  if (pos + 1 == sizeof(buffer)) buffer[--pos] = '0';
+  output_->append(buffer + pos);
+
+  // ...followed by a single SPACE (U+0020).
+  output_->push_back(' ');
+}
+
+void Serializer::EscapeCharacter(uint32_t c) {
+  // To escape a character means to create a string of "\" (U+005C), followed by
+  // the character.
+  DCHECK_GE(c, 0x20u);
+  DCHECK_LE(c, 0x7Fu);
+  output_->push_back('\\');
+  output_->push_back(static_cast<char>(c));
+}
+
+void Serializer::VisitUniversalSelector(UniversalSelector* universal_selector) {
+  UNREFERENCED_PARAMETER(universal_selector);
+  // https://www.w3.org/TR/cssom/#serialize-a-selector
+  // If this is a universal selector append "*" (U+002A) to s.
+  output_->push_back('*');
+}
+
+void Serializer::VisitTypeSelector(TypeSelector* type_selector) {
+  // https://www.w3.org/TR/cssom/#serialize-a-selector
+  // If this is a type selector append the escaped element name to s.
+  SerializeIdentifier(type_selector->element_name());
+}
+
+void Serializer::VisitAttributeSelector(AttributeSelector* attribute_selector) {
+  // https://www.w3.org/TR/cssom/#serialize-a-selector
+
+  // 1. Append "[" (U+005B) to s.
+  output_->push_back('[');
+
+  // 2. If the namespace prefix maps to a namespace that is not the null
+  // namespace (not in a namespace) append the escaped namespace prefix,
+  // followed by a "|" (U+007C) to s.
+  // [Cobalt doesn't support @namespace]
+
+  // 3. Append the escaped attribute name to s.
+  SerializeIdentifier(attribute_selector->attribute_name());
+
+  // 4. If there is an attribute value specified, append "=", "~=", "|=", "^=",
+  // "$=", or "*=" as appropriate (depending on the type of attribute selector),
+  // followed by the string escaped attribute value, to s.
+  const char* match_op = nullptr;
+  switch (attribute_selector->value_match_type()) {
+    case AttributeSelector::kNoMatch:
+      match_op = nullptr;
+      break;
+    case AttributeSelector::kEquals:
+      match_op = "=";
+      break;
+    case AttributeSelector::kIncludes:
+      match_op = "~=";
+      break;
+    case AttributeSelector::kDashMatch:
+      match_op = "|=";
+      break;
+    case AttributeSelector::kBeginsWith:
+      match_op = "^=";
+      break;
+    case AttributeSelector::kEndsWith:
+      match_op = "$=";
+      break;
+    case AttributeSelector::kContains:
+      match_op = "*=";
+      break;
+  }
+  if (match_op != nullptr) {
+    output_->append(match_op);
+    SerializeString(attribute_selector->attribute_value());
+  }
+
+  // 5. If the attribute selector has the case-sensitivity flag present, append
+  // " i" (U+0020 U+0069) to s.
+  // [Cobalt doesn't support the CSS4 case-sensitivity attributes.]
+
+  // 6. Append "]" (U+005D) to s.
+  output_->push_back(']');
+}
+
+void Serializer::VisitClassSelector(ClassSelector* class_selector) {
+  // https://www.w3.org/TR/cssom/#serialize-a-selector
+  // Append a "." (U+002E), followed by the escaped class name to s.
+  output_->push_back('.');
+  SerializeIdentifier(class_selector->class_name());
+}
+
+void Serializer::VisitIdSelector(IdSelector* id_selector) {
+  // https://www.w3.org/TR/cssom/#serialize-a-selector
+  // Append a "#" (U+0023), followed by the escaped ID to s.
+  output_->push_back('#');
+  SerializeIdentifier(id_selector->id());
+}
+
+void Serializer::VisitPseudoClass(PseudoClass* pseudo_class) {
+  // https://www.w3.org/TR/cssom/#serialize-a-selector
+  // If the pseudo-class does not accept arguments append ":" (U+003A),
+  // followed by the name of the pseudo-class, to s.
+  output_->push_back(':');
+  output_->append(pseudo_class->text().c_str());
+}
+
+void Serializer::VisitActivePseudoClass(
+    ActivePseudoClass* active_pseudo_class) {
+  VisitPseudoClass(active_pseudo_class);
+}
+
+void Serializer::VisitEmptyPseudoClass(EmptyPseudoClass* empty_pseudo_class) {
+  VisitPseudoClass(empty_pseudo_class);
+}
+
+void Serializer::VisitFocusPseudoClass(FocusPseudoClass* focus_pseudo_class) {
+  VisitPseudoClass(focus_pseudo_class);
+}
+
+void Serializer::VisitHoverPseudoClass(HoverPseudoClass* hover_pseudo_class) {
+  VisitPseudoClass(hover_pseudo_class);
+}
+
+void Serializer::VisitNotPseudoClass(NotPseudoClass* not_pseudo_class) {
+  // https://www.w3.org/TR/cssom/#serialize-a-simple-selector
+  //
+  // Append ":" (U+003A), followed by the name of the pseudo-class, followed by
+  // "(" (U+0028), followed by the value of the pseudo-class argument(s)
+  // determined as per below, followed by ")" (U+0029), to s.
+  // ...
+  // :not() - The result of serializing the value using the rules for
+  // serializing a group of selectors.
+  VisitPseudoClass(not_pseudo_class);
+  output_->push_back('(');
+  not_pseudo_class->selector()->Accept(this);
+  output_->push_back(')');
+}
+
+void Serializer::VisitPseudoElement(PseudoElement* pseudo_element) {
+  // https://www.w3.org/TR/cssom/#serialize-a-selector
+  // If this is the last part of the chain of the selector and there is a
+  // pseudo-element, append "::" followed by the name of the pseudo-element,
+  // to s.
+  output_->append("::");
+  output_->append(pseudo_element->text().c_str());
+}
+
+void Serializer::VisitAfterPseudoElement(
+    AfterPseudoElement* after_pseudo_element) {
+  VisitPseudoElement(after_pseudo_element);
+}
+
+void Serializer::VisitBeforePseudoElement(
+    BeforePseudoElement* before_pseudo_element) {
+  VisitPseudoElement(before_pseudo_element);
+}
+
+void Serializer::VisitCompoundSelector(CompoundSelector* compound_selector) {
+  // https://www.w3.org/TR/cssom/#serialize-a-selector
+  //
+  // 1. If there is only one simple selector in the compound selectors which is
+  // a universal selector, append the result of serializing the universal
+  // selector to s.
+  if (compound_selector->simple_selectors().size() == 1 &&
+      compound_selector->simple_selectors().front()->AsUniversalSelector()) {
+    compound_selector->simple_selectors().front()->Accept(this);
+    return;
+  }
+
+  // 2. Otherwise, for each simple selector in the compound selectors that is
+  // not a universal selector of which the namespace prefix maps to a namespace
+  // that is not the default namespace serialize the simple selector and append
+  // the result to s.
+  // [Cobalt doesn't support @namespace]
+  for (CompoundSelector::SimpleSelectors::const_iterator iter =
+           compound_selector->simple_selectors().begin();
+       iter != compound_selector->simple_selectors().end(); ++iter) {
+    if ((*iter)->AsUniversalSelector() == NULL) {
+      (*iter)->Accept(this);
+    }
+  }
+}
+
+void Serializer::VisitComplexSelector(ComplexSelector* complex_selector) {
+  // https://www.w3.org/TR/cssom/#serialize-a-selector
+  //
+  // If this is not the last part of the chain of the selector append a single
+  // SPACE (U+0020), followed by the combinator ">", "+", "~", ">>", "||", as
+  // appropriate, followed by another single SPACE (U+0020) if the combinator
+  // was not whitespace, to s.
+  CompoundSelector* selector = complex_selector->first_selector();
+  if (!selector) return;
+  selector->Accept(this);
+  Combinator* combinator = selector->right_combinator();
+  while (combinator) {
+    // The |VisitFooCombinator| methods below add the spaces before & after.
+    combinator->Accept(this);
+    selector = combinator->right_selector();
+    selector->Accept(this);
+    combinator = selector->right_combinator();
+  }
+}
+
+void Serializer::VisitChildCombinator(ChildCombinator* child_combinator) {
+  UNREFERENCED_PARAMETER(child_combinator);
+  output_->append(" > ");
+}
+
+void Serializer::VisitNextSiblingCombinator(
+    NextSiblingCombinator* next_sibling_combinator) {
+  UNREFERENCED_PARAMETER(next_sibling_combinator);
+  output_->append(" + ");
+}
+
+void Serializer::VisitDescendantCombinator(
+    DescendantCombinator* descendant_combinator) {
+  UNREFERENCED_PARAMETER(descendant_combinator);
+  output_->push_back(' ');
+}
+
+void Serializer::VisitFollowingSiblingCombinator(
+    FollowingSiblingCombinator* following_sibling_combinator) {
+  UNREFERENCED_PARAMETER(following_sibling_combinator);
+  output_->append(" ~ ");
+}
+
+}  // namespace cssom
+}  // namespace cobalt
diff --git a/src/cobalt/cssom/serializer.h b/src/cobalt/cssom/serializer.h
new file mode 100644
index 0000000..39a723c
--- /dev/null
+++ b/src/cobalt/cssom/serializer.h
@@ -0,0 +1,102 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_CSSOM_SERIALIZER_H_
+#define COBALT_CSSOM_SERIALIZER_H_
+
+#include <string>
+
+#include "cobalt/base/token.h"
+#include "cobalt/cssom/combinator_visitor.h"
+#include "cobalt/cssom/selector.h"
+#include "cobalt/cssom/selector_visitor.h"
+
+namespace cobalt {
+namespace cssom {
+
+class PseudoClass;
+class PseudoElement;
+
+// Serializes various types in the CSSOM to a string. Currently only implements
+// selector serialization; declarations, rules, and whole sheets aren't yet
+// implemented.
+class Serializer : public SelectorVisitor, public CombinatorVisitor {
+ public:
+  Serializer(const Serializer& other) = delete;
+  Serializer& operator=(const Serializer& other) = delete;
+
+  explicit Serializer(std::string* output);
+
+  void SerializeIdentifier(base::Token identifier);
+  void SerializeString(const std::string& string);
+  void SerializeSelectors(const Selectors& selectors);
+  void SerializeSelector(const Selector& selector);
+
+ private:
+  // Decode a UTF-8 character pointed to by |p| into |out_c|.
+  // Returns the number of bytes consumed.
+  // TODO: Move this to a place it can be shared, or use another shared impl.
+  int DecodeUTF8(const uint8_t* p, uint32_t* out_c);
+
+  // Escape a Unicode code point with its hex value.
+  void EscapeCodePoint(uint32_t c);
+
+  // Escape an ASCII character with a backslash.
+  void EscapeCharacter(uint32_t c);
+
+  // Simple selectors - Overrides from SelectorVisitor.
+  void VisitUniversalSelector(UniversalSelector* universal_selector) override;
+  void VisitTypeSelector(TypeSelector* type_selector) override;
+  void VisitAttributeSelector(AttributeSelector* attribute_selector) override;
+  void VisitClassSelector(ClassSelector* class_selector) override;
+  void VisitIdSelector(IdSelector* id_selector) override;
+
+  // Pseudo classes - Overrides from SelectorVisitor.
+  void VisitPseudoClass(PseudoClass* pseudo_class);
+  void VisitActivePseudoClass(ActivePseudoClass* active_pseudo_class) override;
+  void VisitEmptyPseudoClass(EmptyPseudoClass* empty_pseudo_class) override;
+  void VisitFocusPseudoClass(FocusPseudoClass* focus_pseudo_class) override;
+  void VisitHoverPseudoClass(HoverPseudoClass* hover_pseudo_class) override;
+  void VisitNotPseudoClass(NotPseudoClass* not_pseudo_class) override;
+
+  // Pseudo elements - Overrides from SelectorVisitor.
+  void VisitPseudoElement(PseudoElement* pseudo_element);
+  void VisitAfterPseudoElement(
+      AfterPseudoElement* after_pseudo_element) override;
+  void VisitBeforePseudoElement(
+      BeforePseudoElement* before_pseudo_element) override;
+
+  // Compound selector - Overrides from SelectorVisitor.
+  void VisitCompoundSelector(CompoundSelector* compound_selector) override;
+
+  // Complex selector - Overrides from SelectorVisitor.
+  void VisitComplexSelector(ComplexSelector* complex_selector) override;
+
+  // Combinators - Overrides from CombinatorVisitor.
+  void VisitChildCombinator(ChildCombinator* child_combinator) override;
+  void VisitNextSiblingCombinator(
+      NextSiblingCombinator* next_sibling_combinator) override;
+  void VisitDescendantCombinator(
+      DescendantCombinator* descendant_combinator) override;
+  void VisitFollowingSiblingCombinator(
+      FollowingSiblingCombinator* following_sibling_combinator) override;
+
+  // Pointer to the output string provided by the user.
+  std::string* output_;
+};
+
+}  // namespace cssom
+}  // namespace cobalt
+
+#endif  // COBALT_CSSOM_SERIALIZER_H_
diff --git a/src/cobalt/cssom/serializer_test.cc b/src/cobalt/cssom/serializer_test.cc
new file mode 100644
index 0000000..9d45540
--- /dev/null
+++ b/src/cobalt/cssom/serializer_test.cc
@@ -0,0 +1,244 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/cssom/serializer.h"
+
+#include "cobalt/base/token.h"
+#include "cobalt/css_parser/parser.h"
+#include "cobalt/cssom/css_style_rule.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace cssom {
+
+namespace {
+struct TestPair {
+  // Non-normalized source: the expected serialized output differs.
+  TestPair(const char* serialized, const char* source)
+      : serialized(serialized), source(source) {}
+  // Normalized source: the expected serialized output is identical.
+  TestPair(const char* normalized)  // NOLINT(runtime/explicit)
+      : serialized(normalized), source(normalized) {}
+  const char* serialized;
+  const char* source;
+};
+}  // namespace
+
+TEST(SerializerTest, SerializeSelectorsTest) {
+  // clang-format off
+  const TestPair selectors[] = {
+      // Simple selectors
+      "*",
+      "tag",
+      "[attr]",
+      "[attr=\"value\"]",
+      "[attr~=\"value\"]",
+      "[attr|=\"value\"]",
+      "[attr^=\"value\"]",
+      "[attr$=\"value\"]",
+      "[attr*=\"value\"]",
+      ".class",
+      "#id",
+      ":active",
+      ":empty",
+      ":focus",
+      ":hover",
+      ":not(tag)",
+      "::before",
+      "::after",
+
+      // Compound selectors
+      "tag[attr=\"value\"]",
+      "tag.class",
+      "tag#id",
+      "tag:hover",
+      "tag::after",
+      "[attr].class",
+      "[attr=\"value\"]#id",
+      "[attr~=\"value\"]:active",
+      "[attr|=\"value\"]::after",
+      ".class#id",
+      ".class:empty",
+      ".class::before",
+      "#id:hover",
+      "#id::after",
+      ":focus::before",
+      "tag[attr=\"value\"].class",
+      "tag[attr^=\"value\"].class#id",
+      "tag[attr$=\"value\"].class#id:active",
+      "tag[attr*=\"value\"].class#id:active::before",
+
+      // Universal selector is dropped from a compound selector.
+      {"tag",                          "*tag"},
+      {"[attr=\"value\"]",             "*[attr=\"value\"]"},
+      {".class",                       "*.class"},
+      {"#id",                          "*#id"},
+      {":focus",                       "*:focus"},
+      {"::before",                     "*::before"},
+      {"tag[attr=\"value\"].class#id:empty::before",
+       "*tag[attr=\"value\"].class#id:empty::before"},
+
+      // Old syntax pseudo-element with one ':' is normalized to use '::'.
+      {"div::before",                  "div:before"},
+      {"div::after",                   "div:after"},
+
+      // Compound classes/ids/attributes are sorted, and no whitespace in attrs.
+      {".aa.bb.cc.dd.ee.ff.gg.hh.ii",  ".ff.dd.aa.bb.gg.hh.ee.ii.cc"},
+      {"#aa#bb#cc#dd#ee#ff#gg#hh#ii",  "#ff#dd#aa#bb#gg#hh#ee#ii#cc"},
+      {"[a=\"x\"][b][c=\"z\"][d=\"w\"][e=\"y\"]",
+       "[ d = w ][a =x][e= y][ c=z][ b ]"},
+
+      // Whitespace is normalized around combinators.
+      {"div img",                      "div       img"},
+      {"div > img",                    "div   >   img"},
+      {"div + img",                    "div   +   img"},
+      {"div ~ img",                    "div   ~   img"},
+      {"div > img",                    "div>img"},
+      {"div + img",                    "div+img"},
+      {"div ~ img",                    "div~img"},
+      {"div img",                      "div    img  "},
+      {"div + img > .class",           "div   +img>   .class"},
+      {"[attr^=\"value\"] + div",      "[attr ^= value]    +div "},
+      {"div:empty ~ [attr=\"value\"]", "div:empty~[attr = value]"},
+
+      // Tags/classes/ids with a descendant combinator are not sorted.
+      "tag1 tag2",
+      "tag2 tag1",
+      ".class1 .class2",
+      ".class2 .class1",
+      "#id1 #id2",
+      "#id2 #id1",
+
+      // Type is sorted first, then text of same-typed simple selectors.
+      {"I[C^=\"y\"][G=\"x\"][J].B.E.H#A#D#F", "[J]I.H[G=x]#F.E#D[C^=y].B#A"},
+
+      // Pseudo-classes are sorted, :not() is sorted by its argument, and all
+      // pseudo-classes are sorted before (':' or '::') pseudo-elements.
+      {":active:empty:focus:hover:not(aa):not(bb):not(cc)::after::before",
+       ":not(bb):empty::before:focus:not(aa):after:active:hover:not(cc)"},
+
+      // Different types of selectors can be in a comma-separated group.
+      "tag, :not(.class), outer inner, parent > child, [attr=\"value\"], #id",
+
+      // The the order of the group is not rearranged.
+      ".classA.classB, tag:hover",
+      "tag:hover, .classA.classB",
+
+      // Spacing of a comma-separated group is normalized, but it's not sorted.
+      {"pig, cow, horse, sheep, goat",  " pig,cow ,horse , sheep, goat "},
+
+      // Each compound selector in a group is individually sorted.
+      {".cow.pig, .horse, .goat.sheep", ".pig.cow, .horse, .sheep.goat"},
+
+      // Unnecessarily escaped identifier is normalized.
+      {"X_y-1", "\\X\\_\\y\\-\\31"},
+
+      // Attribute values are quoted.
+      {"[quotes=\"value\"]", "[quotes=value]"},
+
+      // Attributes with un-escaped string values.
+      "[empty=\"\"]",
+      "[alpha=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\"]",
+      "[numeric=\"0123456789\"]",
+      "[ascii=\" !#$%&'()*+,-./:;<=>?@[]^_`{|}~\"]",
+      u8"[unicode=\"2-\u00a3 3-\u1d01 4-\U0002070e\"]",
+
+      // Attributes with escaped string values.
+      "[low=\"\\1 \\2 \\3 \\4 \\5 \\6 \\7 \\8 \\9 \\a \\b \\c \\d \\e \\f \"]",
+      "[low=\"\\10 \\11 \\12 \\13 \\14 \\15 \\16 \\17 \"]",
+      "[low=\"\\18 \\19 \\1a \\1b \\1c \\1d \\1e \\1f \"]",
+      {"[quote_backslash=\"-\\\"-\\\\-\"]",
+       "[quote_backslash=\"-\\22 -\\5c -\"]"},
+  };
+  // clang-format on
+  base::Token::ScopedAlphabeticalSorting sort_scope;
+  scoped_ptr<css_parser::Parser> css_parser = css_parser::Parser::Create();
+  base::SourceLocation loc("[object SelectorTest]", 1, 1);
+  for (const TestPair& selector : selectors) {
+    scoped_refptr<CSSStyleRule> css_style_rule =
+        css_parser->ParseRule(std::string(selector.source) + " {}", loc)
+            ->AsCSSStyleRule();
+    EXPECT_EQ(selector.serialized, css_style_rule->selector_text())
+        << "  Source: \"" << selector.source << '"';
+  }
+}
+
+TEST(SerializerTest, SerializeIdentifierTest) {
+  // clang-format off
+  const TestPair identifiers[] = {
+      // Non-escaped ASCII.
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-012346789",
+
+      // Escaped ASCII.
+      {"low\\1 \\2 \\3 \\4 \\5 \\6 \\7 \\8 \\9 \\a \\b \\c \\d \\e \\f ",
+       "low\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"},
+      {"low\\10 \\11 \\12 \\13 \\14 \\15 \\16 \\17 ",
+       "low\x10\x11\x12\x13\x14\x15\x16\x17"},
+      {"low\\18 \\19 \\1a \\1b \\1c \\1d \\1e \\1f ",
+       "low\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"},
+      {"del\\7f ",
+       "del\x7f"},
+      {"vis\\ \\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\.\\/",
+       "vis !\"#$%&'()*+,./"},
+      {"vis\\:\\;\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\`\\{\\|\\}\\~",
+       "vis:;<=>?@[\\]^`{|}~"},
+
+      // Initial hyphen is escaped if it's the only character.
+      {"\\-", "-"},
+
+      // Initial hyphen is not escaped if there's more after it.
+      "-xyz",
+
+      // Leading numeric is escaped, with or without initial hyphen.
+      {"\\30 0", "00"},
+      {"\\31 0", "10"},
+      {"\\32 0", "20"},
+      {"\\33 0", "30"},
+      {"\\34 0", "40"},
+      {"\\35 0", "50"},
+      {"\\36 0", "60"},
+      {"\\37 0", "70"},
+      {"\\38 0", "80"},
+      {"\\39 0", "90"},
+      {"-\\30 0", "-00"},
+      {"-\\31 0", "-10"},
+      {"-\\32 0", "-20"},
+      {"-\\33 0", "-30"},
+      {"-\\34 0", "-40"},
+      {"-\\35 0", "-50"},
+      {"-\\36 0", "-60"},
+      {"-\\37 0", "-70"},
+      {"-\\38 0", "-80"},
+      {"-\\39 0", "-90"},
+
+      // 2-, 3-, and 4-byte UTF-8 (i.e. >= U+0080) is not escaped.
+      u8"utf8_2byte-\u00a3",
+      u8"utf8_3byte-\u1d01",
+      u8"utf8_4byte-\U0002070e",
+
+      // Embedded NUL character is changed to the replacement character.
+      {u8"XX\uFFFDYY", "XX\xc0\x80YY"}
+  };
+  // clang-format on
+  for (const TestPair& identifier : identifiers) {
+    std::string serialized_identifier;
+    Serializer serializer(&serialized_identifier);
+    serializer.SerializeIdentifier(base::Token(identifier.source));
+    EXPECT_EQ(identifier.serialized, serialized_identifier)
+        << "  Source: \"" << identifier.source << '"';
+  }
+}
+
+}  // namespace cssom
+}  // namespace cobalt
diff --git a/src/cobalt/cssom/simple_selector.h b/src/cobalt/cssom/simple_selector.h
index 768ddf3..a2cd08c 100644
--- a/src/cobalt/cssom/simple_selector.h
+++ b/src/cobalt/cssom/simple_selector.h
@@ -33,6 +33,7 @@
 class PseudoClass;
 class PseudoElement;
 class TypeSelector;
+class UniversalSelector;
 
 // A simple selector is either a type selector, universal selector, attribute
 // selector, class selector, ID selector, or pseudo-class.
@@ -56,6 +57,7 @@
   base::Token text() const { return text_; }
 
   virtual PseudoElement* AsPseudoElement() { return NULL; }
+  virtual UniversalSelector* AsUniversalSelector() { return NULL; }
 
   virtual bool AlwaysRequiresRuleMatchingVerificationVisit() const {
     return false;
diff --git a/src/cobalt/cssom/universal_selector.h b/src/cobalt/cssom/universal_selector.h
index 86eb56f..858b44d 100644
--- a/src/cobalt/cssom/universal_selector.h
+++ b/src/cobalt/cssom/universal_selector.h
@@ -42,6 +42,7 @@
   Specificity GetSpecificity() const override { return Specificity(0, 0, 0); }
 
   // From SimpleSelector.
+  UniversalSelector* AsUniversalSelector() override { return this; }
   void IndexSelectorTreeNode(SelectorTree::Node* parent_node,
                              SelectorTree::Node* child_node,
                              CombinatorType combinator) override;
diff --git a/src/cobalt/debug/backend/command_map.h b/src/cobalt/debug/backend/command_map.h
index f29308f..4e01994 100644
--- a/src/cobalt/debug/backend/command_map.h
+++ b/src/cobalt/debug/backend/command_map.h
@@ -36,12 +36,20 @@
 template <class T>
 class CommandMap : public std::map<std::string, CommandFn<T>> {
  public:
-  explicit CommandMap(T* agent) : agent_(agent) {}
+  // If |domain| is specified, then commands for that domain should be mapped
+  // using just the the simple method name (i.e. "method" not "Domain.method").
+  explicit CommandMap(T* agent, const std::string& domain = std::string())
+      : agent_(agent), domain_(domain) {}
 
   // Calls the mapped method implementation.
   // Returns a true iff the command method is mapped and has been run.
   bool RunCommand(const Command& command) {
-    auto iter = this->find(command.GetMethod());
+    // If the domain matches, trim it and the dot from the method name.
+    const std::string& method =
+        (domain_ == command.GetDomain())
+            ? command.GetMethod().substr(domain_.size() + 1)
+            : command.GetMethod();
+    auto iter = this->find(method);
     if (iter == this->end()) return false;
     auto command_fn = iter->second;
     (agent_->*command_fn)(command);
@@ -55,6 +63,7 @@
 
  private:
   T* agent_;
+  std::string domain_;
 };
 
 }  // namespace backend
diff --git a/src/cobalt/debug/backend/console_agent.cc b/src/cobalt/debug/backend/console_agent.cc
index 31436ab..380d118 100644
--- a/src/cobalt/debug/backend/console_agent.cc
+++ b/src/cobalt/debug/backend/console_agent.cc
@@ -24,21 +24,11 @@
 namespace {
 // Definitions from the set specified here:
 // https://chromedevtools.github.io/devtools-protocol/tot/Console
-//
+constexpr char kInspectorDomain[] = "Console";
+
 // The "Console" protocol domain is deprecated, but we still use it to forward
 // console messages from our console web API implementation (used only with
 // mozjs) to avoid blurring the line to the "Runtime" domain implementation.
-
-// Parameter fields:
-const char kMessageText[] = "message.text";
-const char kMessageLevel[] = "message.level";
-const char kMessageSource[] = "message.source";
-
-// Constant parameter values:
-const char kMessageSourceValue[] = "console-api";
-
-// Events:
-const char kMessageAdded[] = "Console.messageAdded";
 }  // namespace
 
 ConsoleAgent::Listener::Listener(dom::Console* console,
@@ -53,14 +43,16 @@
 ConsoleAgent::ConsoleAgent(DebugDispatcher* dispatcher, dom::Console* console)
     : dispatcher_(dispatcher),
       ALLOW_THIS_IN_INITIALIZER_LIST(console_listener_(console, this)),
-      ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this)) {
+      ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this, kInspectorDomain)) {
   DCHECK(dispatcher_);
-  commands_["Console.disable"] = &ConsoleAgent::Disable;
-  commands_["Console.enable"] = &ConsoleAgent::Enable;
+  commands_["disable"] = &ConsoleAgent::Disable;
+  commands_["enable"] = &ConsoleAgent::Enable;
 
-  dispatcher_->AddDomain("Console", commands_.Bind());
+  dispatcher_->AddDomain(kInspectorDomain, commands_.Bind());
 }
 
+ConsoleAgent::~ConsoleAgent() { dispatcher_->RemoveDomain(kInspectorDomain); }
+
 void ConsoleAgent::Disable(const Command& command) { command.SendResponse(); }
 
 void ConsoleAgent::Enable(const Command& command) { command.SendResponse(); }
@@ -68,10 +60,11 @@
 void ConsoleAgent::OnMessageAdded(const std::string& text,
                                   dom::Console::Level level) {
   JSONObject params(new base::DictionaryValue());
-  params->SetString(kMessageText, text);
-  params->SetString(kMessageLevel, dom::Console::GetLevelAsString(level));
-  params->SetString(kMessageSource, kMessageSourceValue);
-  dispatcher_->SendEvent(kMessageAdded, params);
+  params->SetString("message.text", text);
+  params->SetString("message.level", dom::Console::GetLevelAsString(level));
+  params->SetString("message.source", "console-api");
+  dispatcher_->SendEvent(std::string(kInspectorDomain) + ".messageAdded",
+                         params);
 }
 
 }  // namespace backend
diff --git a/src/cobalt/debug/backend/console_agent.h b/src/cobalt/debug/backend/console_agent.h
index 78ea6d9..32f7a10 100644
--- a/src/cobalt/debug/backend/console_agent.h
+++ b/src/cobalt/debug/backend/console_agent.h
@@ -33,6 +33,7 @@
 class ConsoleAgent {
  public:
   ConsoleAgent(DebugDispatcher* dispatcher, dom::Console* console);
+  ~ConsoleAgent();
 
  private:
   class Listener : public dom::Console::Listener {
diff --git a/src/cobalt/debug/backend/content/css_agent.js b/src/cobalt/debug/backend/content/css_agent.js
new file mode 100644
index 0000000..b121b71
--- /dev/null
+++ b/src/cobalt/debug/backend/content/css_agent.js
@@ -0,0 +1,139 @@
+// 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.
+
+(function(debugScriptRunner) {
+
+// Attach methods to handle commands in the 'CSS' devtools domain.
+// https://chromedevtools.github.io/devtools-protocol/tot/CSS
+var commands = debugScriptRunner.CSS = {};
+
+// https://chromedevtools.github.io/devtools-protocol/tot/CSS#method-getComputedStyleForNode
+commands.getComputedStyleForNode = function(params) {
+  var node = debugScriptRunner.DOM._findNode(params);
+  return '{}';
+}
+
+// https://chromedevtools.github.io/devtools-protocol/tot/CSS#method-getInlineStylesForNode
+commands.getInlineStylesForNode = function(params) {
+  var node = debugScriptRunner.DOM._findNode(params);
+  return '{}';
+}
+
+// https://chromedevtools.github.io/devtools-protocol/tot/CSS#method-getMatchedStylesForNode
+commands.getMatchedStylesForNode = function(params) {
+  var node = debugScriptRunner.DOM._findNode(params);
+  if (!node) {
+    return JSON.stringify({error: {message: 'Invalid Node'}});
+  }
+
+  var result = {};
+  result.matchedCSSRules = _matchedRules(node);
+  return JSON.stringify(result);
+}
+
+var _matchedRules = function(node) {
+  // TODO: Use debugScriptRunner to get the matches cached in C++.
+  return _allRules().reduce(
+      function(accum, cssRule) {
+        // TODO: Report other rule types
+        if (cssRule.type === CSSRule.STYLE_RULE
+            && node.matches(cssRule.selectorText)) {
+          accum.push(new devtools.RuleMatch(cssRule, node));
+        }
+        return accum;
+      }, []);
+}
+
+// Returns an array of all CSSRule objects from all stylesheets in the document.
+var _allRules = function() {
+  // Array.slice() converts document.styleSheets to a proper array, which we can
+  // iterate with reduce().
+  return [].slice.call(document.styleSheets).reduce(
+      function(accum, styleSheet) {
+        try {
+          // Do the slice() trick to get the cssRules from each styleSheet as an
+          // array that we concat() into a single reduced array with all rules.
+          return accum.concat([].slice.call(styleSheet.cssRules));
+        } catch (e) {
+          // CSP blocks reading some rules, but the debugger should be allowed.
+          console.error(styleSheet.href, '\n', e);
+          return accum;
+        }
+      }, []);
+}
+
+// Namespace for constructors of types defined in the Devtools protocol.
+var devtools = {};
+
+// https://chromedevtools.github.io/devtools-protocol/tot/CSS#type-RuleMatch
+devtools.RuleMatch = function(cssRule, node) {
+  this.rule = new devtools.CSSRule(cssRule);
+  this.matchingSelectors = [];
+
+  var selectors = this.rule.selectorList.selectors;
+  for (var i = 0; i < selectors.length; i++) {
+    if (node.matches(selectors[i].text)) {
+      this.matchingSelectors.push(i);
+    }
+  }
+}
+
+// https://chromedevtools.github.io/devtools-protocol/tot/CSS#type-CSSRule
+devtools.CSSRule = function(cssRule) {
+  // TODO: this.styleSheetId
+  this.origin = 'regular'; // TODO
+  this.selectorList = new devtools.SelectorList(cssRule.selectorText);
+  if (cssRule.type === CSSRule.STYLE_RULE) {
+    this.style = new devtools.CSSStyle(cssRule.style);
+  }
+}
+
+// https://chromedevtools.github.io/devtools-protocol/tot/CSS#type-SelectorList
+devtools.SelectorList = function(cssSelectorText) {
+  this.text = cssSelectorText;
+  this.selectors = cssSelectorText.split(',').map(
+      selector => new devtools.Value(selector.trim()));
+}
+
+// https://chromedevtools.github.io/devtools-protocol/tot/CSS#type-Value
+devtools.Value = function(value) {
+  this.text = value;
+  // TODO: this.range
+}
+
+// https://chromedevtools.github.io/devtools-protocol/tot/CSS#type-CSSStyle
+devtools.CSSStyle = function(cssStyleDecl) {
+  this.shorthandEntries = [];  // TODO
+  this.text = cssStyleDecl.cssText;
+  this.cssProperties = [].slice.apply(cssStyleDecl).map(
+      property => new devtools.CSSProperty(cssStyleDecl, property));
+}
+
+// https://chromedevtools.github.io/devtools-protocol/tot/CSS#type-CSSProperty
+devtools.CSSProperty = function(cssStyleDecl, property) {
+  this.name = property;
+  this.value = cssStyleDecl.getPropertyValue(property);
+  // this.important = cssStyleDecl.getPropertyPriority(property) === 'important';
+}
+
+// Polyfill Element.matches()
+// https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
+Element.prototype.matches = Element.prototype.matches || function(s) {
+  var matches = document.querySelectorAll(s), i = matches.length;
+  while (--i >= 0 && matches.item(i) != this) {}
+  return i > -1;
+};
+
+// TODO: Pass debugScriptRunner from C++ instead of getting it from the window.
+})(window.debugScriptRunner);
diff --git a/src/cobalt/debug/backend/content/dom.js b/src/cobalt/debug/backend/content/dom.js
deleted file mode 100644
index e395ee3..0000000
--- a/src/cobalt/debug/backend/content/dom.js
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2016 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// JavaScript functions used by the Chrome debugging protocol DOM domain:
-// https://chromedevtools.github.io/devtools-protocol/1-3/DOM
-
-devtoolsBackend.dom = {};
-
-// Alias to match the uppercase domain of the devtools protocol.
-devtoolsBackend.DOM = devtoolsBackend.dom;
-
-// Creates and returns a new Node object corresponding to the document node,
-// including its children up to a default depth.
-// https://chromedevtools.github.io/devtools-protocol/1-3/DOM#method-getDocument
-devtoolsBackend.dom.getDocument = function(params) {
-  var result = {};
-  result.root = this.getNodeWithChildren(document, 2);
-  result.root.documentURL = document.URL;
-  return JSON.stringify(result);
-}
-
-// Creates an array of Node objects corresponding to the children of the
-// specified node, and returns them via an event. A depth may be specified,
-// where a negative depth means to return all descendants. If no depth is
-// specified, the default is 1, a single level.
-// https://chromedevtools.github.io/devtools-protocol/1-3/DOM#method-requestChildNodes
-devtoolsBackend.dom.requestChildNodes = function(params) {
-  var node = this.findNode(params);
-  var depth = params.depth || 1;
-  var result = {};
-  result.parentId = params.nodeId;
-  result.nodes = this.getChildNodes(node, depth);
-
-  // Send the result via an event, and an empty response.
-  devtoolsBackend.sendEvent('DOM.setChildNodes', JSON.stringify(result));
-  return '{}';
-}
-
-// Finds the node corresponding to a remote objectId. Also sends all nodes on
-// the path from the requested one to the root as a series of setChildNodes
-// events.
-// https://chromedevtools.github.io/devtools-protocol/1-3/DOM#method-requestNode
-devtoolsBackend.dom.requestNode = function(params) {
-  var node = this.findNode(params);
-  var nodeInfo = new this.Node(node);
-  var result = {};
-  result.nodeId = nodeInfo.nodeId;
-
-  var parent = node.parentNode;
-  while (parent) {
-    var parentInfo = new this.Node(parent);
-    var params = {};
-    params.parentId = parentInfo.nodeId;
-    params.nodes = [];
-    params.nodes.push(nodeInfo);
-    devtoolsBackend.sendEvent('DOM.setChildNodes', JSON.stringify(params));
-    node = parent;
-    nodeInfo = parentInfo;
-    parent = parent.parentNode;
-  }
-
-  return JSON.stringify(result);
-}
-
-// Returns a Runtime.RemoteObject corresponding to a node.
-// https://chromedevtools.github.io/devtools-protocol/1-3/DOM#method-resolveNode
-devtoolsBackend.dom.resolveNode = function(params) {
-  var node = this.findNode(params);
-  var returnByValue = true;
-  var result = {};
-  result.object = new devtoolsBackend.runtime.RemoteObject(
-        node, params.objectGroup, returnByValue);
-
-  return JSON.stringify(result);
-}
-
-// Returns the bounding box of a node. Used for node highlighting.
-devtoolsBackend.dom.getBoundingClientRect = function(params) {
-  var node = this.findNode(params);
-  return JSON.stringify(node.getBoundingClientRect());
-}
-
-// Creates and returns a Node object that represents the specified node.
-// Adds the node's children up to the specified depth. A negative depth will
-// cause all descendants to be added.
-devtoolsBackend.dom.getNodeWithChildren = function(node, depth) {
-  var result = new this.Node(node);
-  if (depth != 0) {
-    result.children = this.getChildNodes(node, depth);
-  }
-  return result;
-}
-
-// Creates and returns an array of Node objects corresponding to the children
-// of the specified node, recursing on each on up to the specified depth.
-devtoolsBackend.dom.getChildNodes = function(node, depth) {
-  if (!node.childNodes) {
-    return [];
-  }
-
-  var children = [];
-  for (var i = 0; i < node.childNodes.length; i++) {
-    var child = node.childNodes[i];
-    if (!this.nodeIsIgnorable(child)) {
-      children.push(this.getNodeWithChildren(child, depth - 1));
-    }
-  }
-  return children;
-}
-
-// Finds a node specified by either nodeId or objectId (to get a node
-// from its corresponding remote object).
-devtoolsBackend.dom.findNode = function(params) {
-  if (params.nodeId != null) {
-    return this.nodeStore[params.nodeId];
-  }
-
-  if (params.objectId != null) {
-    return devtoolsBackend.runtime.getObject(params.objectId);
-  }
-
-  // Either nodeId or objectId must be specified.
-  return null;
-}
-
-// Adds a node to the internal node store and returns a unique id that can
-// be used to access it again.
-devtoolsBackend.dom.addNode = function(node) {
-  // If we've already added this node, then use the same nodeId.
-  for (var i = 0; i < this.nodeStore.length; i++) {
-    if (this.nodeStore[i] === node) {
-      return i;
-    }
-  }
-
-  var nodeId = this.nextNodeId++;
-  this.nodeStore[nodeId] = node;
-  return nodeId;
-}
-
-// Whether a node is ignorable. We ignore text nodes with white-space only
-// content, as they just clutter up the DOM tree.
-devtoolsBackend.dom.nodeIsIgnorable = function(node) {
-  return node.nodeType == Node.TEXT_NODE &&
-      !(/[^\t\n\r ]/.test(node.textContent));
-}
-
-// Creates a new Node object, which is the type used to return information
-// about nodes to devtools. All Node objects are added to |nodeStore|,
-// so they can be retrieved later via |nodeId|.
-devtoolsBackend.dom.Node = function(node) {
-  this.nodeId = devtoolsBackend.dom.addNode(node);
-  this.localName = node.nodeName;
-  this.nodeName = node.nodeName;
-  this.nodeType = node.nodeType;
-  this.nodeValue = node.nodeValue || "";
-  this.childNodeCount = node.childNodes.length;
-
-  if (node.attributes) {
-    this.attributes = [];
-    for (var i = 0; i < node.attributes.length; i++) {
-      this.attributes.push(node.attributes[i].name);
-      this.attributes.push(node.attributes[i].value);
-    }
-  }
-}
-
-devtoolsBackend.dom.nodeStore = [];
-devtoolsBackend.dom.nextNodeId = 0;
diff --git a/src/cobalt/debug/backend/content/dom_agent.js b/src/cobalt/debug/backend/content/dom_agent.js
new file mode 100644
index 0000000..ab99b77
--- /dev/null
+++ b/src/cobalt/debug/backend/content/dom_agent.js
@@ -0,0 +1,187 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+(function(debugScriptRunner) {
+
+// Attach methods to handle commands in the 'DOM' devtools domain.
+// https://chromedevtools.github.io/devtools-protocol/1-3/DOM
+var commands = debugScriptRunner.DOM = {};
+
+// Creates and returns a new Node object corresponding to the document node,
+// including its children up to a default depth.
+// https://chromedevtools.github.io/devtools-protocol/1-3/DOM#method-getDocument
+commands.getDocument = function(params) {
+  var result = {};
+  result.root = _getNodeWithChildren(document, 2);
+  result.root.documentURL = document.URL;
+  return JSON.stringify(result);
+}
+
+// Creates an array of Node objects corresponding to the children of the
+// specified node, and returns them via an event. A depth may be specified,
+// where a negative depth means to return all descendants. If no depth is
+// specified, the default is 1, a single level.
+// https://chromedevtools.github.io/devtools-protocol/1-3/DOM#method-requestChildNodes
+commands.requestChildNodes = function(params) {
+  var node = commands._findNode(params);
+  var depth = params.depth || 1;
+  var result = {};
+  result.parentId = params.nodeId;
+  result.nodes = _getChildNodes(node, depth);
+
+  // Send the result via an event, and an empty response.
+  debugScriptRunner.sendEvent('DOM.setChildNodes', JSON.stringify(result));
+  return '{}';
+}
+
+// Finds the node corresponding to a remote objectId. Also sends all nodes on
+// the path from the requested one to the root as a series of setChildNodes
+// events.
+// https://chromedevtools.github.io/devtools-protocol/1-3/DOM#method-requestNode
+commands.requestNode = function(params) {
+  var node = commands._findNode(params);
+  var nodeInfo = new devtools.Node(node);
+  var result = {};
+  result.nodeId = nodeInfo.nodeId;
+
+  var parent = node.parentNode;
+  while (parent) {
+    var parentInfo = new devtools.Node(parent);
+    var params = {};
+    params.parentId = parentInfo.nodeId;
+    params.nodes = [];
+    params.nodes.push(nodeInfo);
+    debugScriptRunner.sendEvent('DOM.setChildNodes', JSON.stringify(params));
+    node = parent;
+    nodeInfo = parentInfo;
+    parent = parent.parentNode;
+  }
+
+  return JSON.stringify(result);
+}
+
+// Returns a Runtime.RemoteObject corresponding to a node.
+// https://chromedevtools.github.io/devtools-protocol/1-3/DOM#method-resolveNode
+commands.resolveNode = function(params) {
+  var node = commands._findNode(params);
+  var result = {};
+  result.object =
+      debugScriptRunner.createRemoteObject(node, params.objectGroup);
+
+  return JSON.stringify(result);
+}
+
+// Returns the bounding box of a node. This pseudo-command in the DOM domain is
+// a helper for the C++ |DOMAgent::HighlightNode|.
+commands._getBoundingClientRect = function(params) {
+  var node = commands._findNode(params);
+  return JSON.stringify(node.getBoundingClientRect());
+}
+
+// Creates and returns a Node object that represents the specified node.
+// Adds the node's children up to the specified depth. A negative depth will
+// cause all descendants to be added.
+var _getNodeWithChildren = function(node, depth) {
+  var result = new devtools.Node(node);
+  if (depth != 0) {
+    result.children = _getChildNodes(node, depth);
+  }
+  return result;
+}
+
+// Creates and returns an array of Node objects corresponding to the children
+// of the specified node, recursing on each on up to the specified depth.
+var _getChildNodes = function(node, depth) {
+  if (!node.childNodes) {
+    return [];
+  }
+
+  var children = [];
+  for (var i = 0; i < node.childNodes.length; i++) {
+    var child = node.childNodes[i];
+    if (!_nodeIsIgnorable(child)) {
+      children.push(_getNodeWithChildren(child, depth - 1));
+    }
+  }
+  return children;
+}
+
+// Finds a node specified by either nodeId or objectId (to get a node from its
+// corresponding remote object). This is "exported" as a pseudo-command in the
+// DOM domain for other agents to use.
+commands._findNode = function(params) {
+  if (params.nodeId != null) {
+    return _nodeStore[params.nodeId];
+  }
+
+  if (params.objectId != null) {
+    // TODO: Add an IDL method to debugScriptRunner.
+    return debugScriptRunner.runtime.getObject(params.objectId);
+  }
+
+  // Either nodeId or objectId must be specified.
+  return null;
+}
+
+// Adds a node to the internal node store and returns a unique id that can
+// be used to access it again.
+var _addNode = function(node) {
+  // If we've already added this node, then use the same nodeId.
+  for (var i = 0; i < _nodeStore.length; i++) {
+    if (_nodeStore[i] === node) {
+      return i;
+    }
+  }
+
+  var nodeId = _nextNodeId++;
+  _nodeStore[nodeId] = node;
+  return nodeId;
+}
+
+// Whether a node is ignorable. We ignore text nodes with white-space only
+// content, as they just clutter up the DOM tree.
+var _nodeIsIgnorable = function(node) {
+  return node.nodeType == Node.TEXT_NODE &&
+      !(/[^\t\n\r ]/.test(node.textContent));
+}
+
+var _nodeStore = [];
+var _nextNodeId = 0;
+
+// Namespace for constructors of types defined in the Devtools protocol.
+var devtools = {};
+
+// Creates a new Node object, which is the type used to return information
+// about nodes to devtools. All Node objects are added to |nodeStore|,
+// so they can be retrieved later via |nodeId|.
+// https://chromedevtools.github.io/devtools-protocol/tot/DOM#type-Node
+devtools.Node = function(node) {
+  this.nodeId = _addNode(node);
+  this.localName = node.nodeName;
+  this.nodeName = node.nodeName;
+  this.nodeType = node.nodeType;
+  this.nodeValue = node.nodeValue || '';
+  this.childNodeCount = node.childNodes.length;
+
+  if (node.attributes) {
+    this.attributes = [];
+    for (var i = 0; i < node.attributes.length; i++) {
+      this.attributes.push(node.attributes[i].name);
+      this.attributes.push(node.attributes[i].value);
+    }
+  }
+}
+
+// TODO: Pass debugScriptRunner from C++ instead of getting it from the window.
+})(window.debugScriptRunner);
diff --git a/src/cobalt/debug/backend/content/runtime.js b/src/cobalt/debug/backend/content/runtime.js
deleted file mode 100644
index 6cf4af3..0000000
--- a/src/cobalt/debug/backend/content/runtime.js
+++ /dev/null
@@ -1,515 +0,0 @@
-// Copyright 2016 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Top level callback function that can be called by any component to create
-// a Runtime.RemoteObject from a JS object passed in from C++.
-devtoolsBackend.createRemoteObjectCallback = function(object, params) {
-  var remoteObject = null;
-  try {
-    remoteObject = new devtoolsBackend.runtime.RemoteObject(
-        object, params.objectGroup, params.returnByValue);
-  } catch(e) {
-    console.log('Exception creating remote object: ' + e);
-  }
-
-  var json = "";
-  try {
-    json = JSON.stringify(remoteObject);
-  } catch(e) {
-    console.log('Exception stringifying remote object: ' + e)
-  }
-  return json;
-}
-
-// JavaScript functions used by the Chrome debugging protocol Runtime domain:
-// https://chromedevtools.github.io/devtools-protocol/1-3/Runtime
-
-devtoolsBackend.runtime = {};
-
-// Alias to match the uppercase domain of the devtools protocol.
-devtoolsBackend.Runtime = devtoolsBackend.runtime;
-
-// Creates an executionContextCreated event.
-// https://chromedevtools.github.io/devtools-protocol/1-3/Runtime#event-executionContextCreated
-devtoolsBackend.runtime.executionContextCreatedEvent = function() {
-  var event = {
-    context: {
-      id: 1,
-      origin: window.location.origin,
-      name: "Cobalt",
-      auxData: {
-        isDefault: true
-      }
-    }
-  };
-  return JSON.stringify(event);
-}
-
-// Calls a function on a previously accessed RemoteObject with an argument list
-// and returns a new RemoteObject. Used extensively by devtools for
-// auto-completion. The new RemoteObject uses the same |objectGroup| as the
-// original object.
-// https://chromedevtools.github.io/devtools-protocol/1-3/Runtime#method-callFunctionOn
-devtoolsBackend.runtime.callFunctionOn = function(params) {
-  var result = {};
-  var value = null;
-  try {
-    eval('var f = ' + params.functionDeclaration);
-    var objectEntry = this._objectStore[params.objectId];
-    var thisArg = objectEntry.object;
-    var objectGroup = objectEntry.objectGroup;
-    value = f.apply(thisArg, params.arguments);
-    result.wasThrown = false;
-  } catch(e) {
-    value = e;
-    result.exceptionDetails = e;
-    result.wasThrown = true;
-  }
-
-  result.result =
-      new this.RemoteObject(value, objectGroup, params.returnByValue);
-  return JSON.stringify(result);
-}
-
-// Evaluates a string and returns a RemoteObject.
-// https://chromedevtools.github.io/devtools-protocol/1-3/Runtime#method-evaluate
-devtoolsBackend.runtime.evaluate = function(params) {
-  var result = {};
-  var value = null;
-  try {
-    if (params.includeCommandLineAPI) {
-      this._addCommandLineAPI();
-    }
-
-    // Use |eval| indirectly, to cause evaluation at global scope.
-    // This is so subsequent calls can access variables declared here, etc.
-    var geval = eval;
-    value = geval(params.expression);
-
-    // Store the last result if we're doing a "real" evaluation, not an
-    // auto-complete. Seems a little special case-y, but this is taken from
-    // Chrome's implementation, and looks like the only way to differentiate.
-    if (params.objectGroup == 'console') {
-      this._lastResult = value;
-    }
-
-    result.wasThrown = false;
-  } catch(e) {
-    value = e;
-    result.exceptionDetails = e;
-    result.wasThrown = true;
-  }
-
-  // Create the RemoteObject corresponding to the result.
-  result.result =
-      new this.RemoteObject(value, params.objectGroup, params.returnByValue);
-
-  // Add the preview, if requested.
-  if (params.generatePreview) {
-    var preview = this.generatePreview(value);
-    if (preview) {
-      result.result.preview = preview;
-    }
-  }
-
-  if (params.includeCommandLineAPI) {
-    this._removeCommandLineAPI();
-  }
-
-  return JSON.stringify(result);
-}
-
-// Returns all let, const and class variables from global scope.
-// https://chromedevtools.github.io/devtools-protocol/1-3/Runtime#method-globalLexicalScopeNames
-devtoolsBackend.runtime.globalLexicalScopeNames = function(params) {
-  var result = [];
-  // TODO: Get the globals.
-  return JSON.stringify(result);
-}
-
-// Returns the properties of a previously accessed object as an array of
-// PropertyDescriptor objects.
-// The parameters specifify several options:
-// * ownProperties - only include immediate properties, not the prototype chain.
-// * accessorPropertiesOnly - only include accessor properties.
-// https://chromedevtools.github.io/devtools-protocol/1-3/Runtime#method-getProperties
-devtoolsBackend.runtime.getProperties = function(params) {
-  var result = {};
-  var properties = [];
-  try {
-    var objectEntry = this._objectStore[params.objectId];
-    var object = objectEntry.object;
-    var objectGroup = objectEntry.objectGroup;
-    this.addProperties(object, objectGroup, !params.ownProperties, properties);
-
-    if (params.accessorPropertiesOnly) {
-      properties = properties.filter(function(element, index, array) {
-          return (element.get || element.set);
-        });
-    }
-    result.wasThrown = false;
-  } catch(e) {
-    value = e;
-    result.exceptionDetails = e;
-    result.wasThrown = true;
-  }
-
-  result.result = properties;
-  return JSON.stringify(result);
-}
-
-// Releases our reference to a previously accessed object.
-// https://chromedevtools.github.io/devtools-protocol/1-3/Runtime#method-releaseObject
-devtoolsBackend.runtime.releaseObject = function(params) {
-  delete this._objectStore[params.objectId];
-}
-
-// Releases our references to a group of previously accessed objects.
-// https://chromedevtools.github.io/devtools-protocol/1-3/Runtime#method-releaseObjectGroup
-devtoolsBackend.runtime.releaseObjectGroup = function(params) {
-  for (var objectId in this._objectStore) {
-    var objectEntry = this._objectStore[objectId];
-    if (objectEntry && objectEntry.objectGroup == params.objectGroup) {
-      delete this._objectStore[objectId];
-    }
-  }
-}
-
-// Adds the properties of |object| to the |result| array as new
-// PropertyDescriptor objects. Some properties may be objects themselves,
-// in which case new RemoteObjects are created using the specified
-// |objectGroup|, which should be that of |object|.
-// If |includePrototype| is set, the function will be called recursively on
-// the prototype chain of the object.
-devtoolsBackend.runtime.addProperties =
-    function(object, objectGroup, includePrototype, result) {
-  var properties = Object.getOwnPropertyNames(object);
-  var foundProto = false;
-  for (var i = 0; i < properties.length; i++) {
-    var key = properties[i];
-    foundProto = foundProto || (key == '__proto__');
-
-    // If we can't find the property, and its name corresponds to a number,
-    // try it as an array index (integer instead of string).
-    if (object[key] == null) {
-      if (!isNaN(key)) {
-        key = parseInt(key);
-      } else {
-        continue;
-      }
-    }
-
-    var propertyDescriptor =
-        new this.PropertyDescriptor(object, objectGroup, key);
-    result.push(propertyDescriptor);
-  }
-
-  var proto = null;
-  try {
-    proto = Object.getPrototypeOf(object);
-  } catch (e) {}
-
-  if (includePrototype) {
-    // Recursively add the properties from the prototype chain.
-    if (proto) {
-      this.addProperties(proto, objectGroup, includePrototype, result);
-    }
-  } else if (proto && !foundProto) {
-    // |getOwnPropertyNames| may not include the object prototype,
-    // so if that's the case, add it now. It's a deprecated name, but devtools
-    // still uses it.
-    var propertyDescriptor =
-        new this.PropertyDescriptor(object, objectGroup, '__proto__');
-    result.push(propertyDescriptor);
-  }
-}
-
-// Gets an object from the internal object store.
-devtoolsBackend.runtime.getObject = function(objectId) {
-  return this._objectStore[objectId].object;
-}
-
-// Adds an object to the internal object store and returns a unique id that can
-// be used to access it again.
-devtoolsBackend.runtime.addObject = function(object, objectGroup) {
-  // If we've already added this object, then use the same objectId.
-  for (var objectId in this._objectStore) {
-    var objectEntry = this._objectStore[objectId];
-    if (objectEntry.object === object &&
-        objectEntry.objectGroup == objectGroup) {
-      return objectId;
-    }
-  }
-
-  var objectId = this._nextObjectId.toString();
-  this._nextObjectId += 1;
-  this._objectStore[objectId] = {};
-  this._objectStore[objectId].object = object;
-  this._objectStore[objectId].objectGroup = objectGroup;
-  return objectId;
-}
-
-// Generates an object preview, which may be requested for the evaluate
-// command.
-devtoolsBackend.runtime.generatePreview = function(object) {
-  if (!object || (typeof object != 'object')) {
-    return null;
-  } else {
-    return new this.ObjectPreview(object);
-  }
-}
-
-// Returns the subtype of an object, or null if the specified value is not an
-// object.
-devtoolsBackend.runtime.getSubtype = function(object) {
-  if (typeof object == 'object') {
-    if (object instanceof Array) {
-      return 'array';
-    } else if (object instanceof Date) {
-      return 'date';
-    } else if (object instanceof Error) {
-      return 'error';
-    } else if (object instanceof Node) {
-      return 'node';
-    } else if (object instanceof RegExp) {
-      return 'regexp';
-    }
-  }
-  return null;
-}
-
-// Tries to get the classname of an object by following the prototype chain
-// and looking for a constructor.
-devtoolsBackend.runtime.getClassName = function(object) {
-  try {
-    for (var obj = object; obj && !this.className;
-         obj = Object.getPrototypeOf(obj)) {
-      if (obj.constructor) {
-        return obj.constructor.name;
-      }
-    }
-  } catch(e) {}
-
-  return null;
-}
-
-// Creates a RemoteObject, which is the type used to return many values to
-// devtools. If |value| is an object, then is it inserted into
-// |devtoolsBackend.runtime._objectStore| and the |objectId| key used to
-// access it is included in the RemoteObject. If |value| is not an object,
-// or |returnByValue| is true, then |value| is directly included in the
-// RemoteObject.
-devtoolsBackend.runtime.RemoteObject = function(value, objectGroup,
-                                                returnByValue) {
-  this.type = typeof value;
-
-  if (value == null) {
-    this.subtype == 'null';
-    this.value = null;
-    return;
-  }
-
-  if (this.type == 'object') {
-    this.objectId = devtoolsBackend.runtime.addObject(value, objectGroup);
-    this.subtype = devtoolsBackend.runtime.getSubtype(value);
-    this.className = devtoolsBackend.runtime.getClassName(value);
-  }
-
-  // Fill in the description field. Devtools will only display arrays
-  // correctly if their description follows a particular format. For other
-  // values, try to use the generic string conversion, and fall back to the
-  // className if that fails.
-  if (this.subtype == 'array') {
-    this.description = 'Array[' + value.length + ']';
-  } else {
-    try {
-      this.description = value.toString();
-    } catch(e) {
-      this.description = this.className;
-    }
-  }
-
-  if (returnByValue || this.type != 'object') {
-    this.value = value;
-  }
-}
-
-// Creates a PropertyDescriptor for |property| of |object|,
-// which is the type used to return object properties to devtools.
-// Some properties may be objects, in which case new RemoteObjects are created
-// and inserted into |devtoolsBackend.runtime._objectStore| using the specified
-// |objectGroup|, which should be that of |object|.
-devtoolsBackend.runtime.PropertyDescriptor = function(object, objectGroup,
-                                                      property) {
-  this.name = property.toString();
-  var descriptor = Object.getOwnPropertyDescriptor(object, property);
-  // Some Cobalt objects don't seem to support |getOwnPropertyDescriptor|,
-  // so we handle that case in the else clause below.
-  if (descriptor) {
-    this.configurable = descriptor.configurable;
-    this.enumerable = descriptor.enumerable;
-    if (descriptor.get) {
-      this.get = new devtoolsBackend.runtime.RemoteObject(descriptor.get,
-                                                          objectGroup,
-                                                          false);
-    }
-    if (descriptor.set) {
-      this.set = new devtoolsBackend.runtime.RemoteObject(descriptor.set,
-                                                          objectGroup,
-                                                          false);
-    }
-    if (descriptor.value != null) {
-      this.value = new devtoolsBackend.runtime.RemoteObject(descriptor.value,
-                                                            objectGroup,
-                                                            false);
-    }
-    this.writable = descriptor.writable;
-  } else if (object[property] != null) {
-    this.configurable = false;
-    this.enumerable = object.propertyIsEnumerable(property);
-    if (object.__lookupGetter__(property)) {
-      this.get = object.__lookupGetter__(property);
-    }
-    if (object.__lookupSetter__(property)) {
-      this.set = object.__lookupSetter__(property);
-    }
-    this.value = new devtoolsBackend.runtime.RemoteObject(object[property],
-                                                          objectGroup, false);
-  }
-}
-
-// Creates an ObjectPreview, the type to represent a preview of an object,
-// which may be requested by devtools in the evaluate command.
-devtoolsBackend.runtime.ObjectPreview = function(value) {
-  this.type = typeof value;
-  this.subtype = devtoolsBackend.runtime.getSubtype(value);
-  this.lossless = true;
-  this.overflow = false;
-  this.properties = [];
-
-  // Use the className as the preview description. This matches Chrome.
-  this.description = devtoolsBackend.runtime.getClassName(value);
-
-  // If we have an array-like object, add the array items, or append the
-  // length to the description if there's too many.
-  if (value.length != null) {
-    var MAX_ARRAY_ITEMS = 99;
-    if (value.length <= MAX_ARRAY_ITEMS) {
-      for (var i = 0; i < value.length; i++) {
-        var property = new devtoolsBackend.runtime.PropertyPreview(i, value[i]);
-        this.properties.push(property);
-        if (typeof value[i] == 'object') {
-          this.lossless = false;
-        }
-      }
-    } else {
-      this.description += '[' + value.length + ']';
-      this.lossless = false;
-      this.overflow = true;
-    }
-    return;
-  }
-
-  // Add object properties, up to a maximum.
-  var MAX_PROPERTIES = 5;
-  var numProperties = 0;
-  for (var name in value) {
-    if (value[name] != null) {
-      if (++numProperties > MAX_PROPERTIES) {
-        this.lossless = false;
-        this.overflow = true;
-        break;
-      }
-      if (typeof property == 'object') {
-        this.lossless = false;
-      }
-      var property = new devtoolsBackend.runtime.PropertyPreview(name,
-                                                                 value[name]);
-      this.properties.push(property);
-    }
-  }
-}
-
-// Creates a PropertyPreview, the type to represent a preview of a single
-// object property.
-devtoolsBackend.runtime.PropertyPreview = function(name, value) {
-  this.name = name.toString();
-  this.type = typeof value;
-
-  try {
-    this.value = value.toString();
-  } catch(e) {}
-
-  if (this.type == 'object') {
-    this.subtype = devtoolsBackend.runtime.getSubtype(value);
-  }
-}
-
-// The object store used to reference objects by internally generated id.
-devtoolsBackend.runtime._objectStore = {};
-devtoolsBackend.runtime._nextObjectId = 0;
-
-// The last evaluated result.
-devtoolsBackend.runtime._lastResult = null;
-
-// Values in the global scope that have been overridden by corresponding
-// members of the Command Line API for the duration of an evaluation. We use
-// this to restore the original values after the evaluation.
-devtoolsBackend.runtime._globalOverrides = {};
-
-// Command Line API implementation.
-// This is a set of convenience variables/functions that are not present in
-// the global scope by default, but can be specified as available to the
-// Runtime.evaluate function by the includeCommandLineAPI parameter.
-// https://developers.google.com/web/tools/chrome-devtools/debug/command-line/command-line-reference
-devtoolsBackend.runtime._commandLineAPI = {};
-
-devtoolsBackend.runtime._commandLineAPI.$_ =
-    devtoolsBackend.runtime._lastResult;
-
-devtoolsBackend.runtime._commandLineAPI.$ =
-    document.querySelector.bind(document);
-
-devtoolsBackend.runtime._commandLineAPI.$$ =
-    document.querySelectorAll.bind(document);
-
-devtoolsBackend.runtime._commandLineAPI.keys = Object.keys;
-
-devtoolsBackend.runtime._commandLineAPI.values = function(object) {
-  var keys = Object.keys(object);
-  var result = [];
-  for (var i = 0; i < keys.length; i++) {
-    result.push(object[keys[i]]);
-  }
-  return result;
-}
-
-devtoolsBackend.runtime._addCommandLineAPI = function() {
-  this._commandLineAPI.$_ = this._lastResult;
-  for (var property in this._commandLineAPI) {
-    if (this._commandLineAPI.hasOwnProperty(property)) {
-      this._globalOverrides[property] = window[property];
-      window[property] = this._commandLineAPI[property];
-    }
-  }
-}
-
-devtoolsBackend.runtime._removeCommandLineAPI = function() {
-  for (var property in this._globalOverrides) {
-    if (this._globalOverrides.hasOwnProperty(property)) {
-      window[property] = this._globalOverrides[property];
-      delete this._globalOverrides[property];
-    }
-  }
-}
diff --git a/src/cobalt/debug/backend/css_agent.cc b/src/cobalt/debug/backend/css_agent.cc
new file mode 100644
index 0000000..5e6f212
--- /dev/null
+++ b/src/cobalt/debug/backend/css_agent.cc
@@ -0,0 +1,57 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/debug/backend/css_agent.h"
+
+namespace cobalt {
+namespace debug {
+namespace backend {
+
+namespace {
+// Definitions from the set specified here:
+// https://chromedevtools.github.io/devtools-protocol/tot/DOM
+constexpr char kInspectorDomain[] = "CSS";
+
+// File to load JavaScript CSS debugging domain implementation from.
+constexpr char kScriptFile[] = "css_agent.js";
+}  // namespace
+
+CSSAgent::CSSAgent(DebugDispatcher* dispatcher)
+    : dispatcher_(dispatcher),
+      ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this, kInspectorDomain)) {
+  DCHECK(dispatcher_);
+
+  commands_["disable"] = &CSSAgent::Disable;
+  commands_["enable"] = &CSSAgent::Enable;
+
+  dispatcher_->AddDomain(kInspectorDomain, commands_.Bind());
+}
+
+CSSAgent::~CSSAgent() { dispatcher_->RemoveDomain(kInspectorDomain); }
+
+void CSSAgent::Enable(const Command& command) {
+  bool initialized = dispatcher_->RunScriptFile(kScriptFile);
+  if (initialized) {
+    command.SendResponse();
+  } else {
+    command.SendErrorResponse(Command::kInternalError,
+                              "Cannot create CSS inspector.");
+  }
+}
+
+void CSSAgent::Disable(const Command& command) { command.SendResponse(); }
+
+}  // namespace backend
+}  // namespace debug
+}  // namespace cobalt
diff --git a/src/cobalt/debug/backend/css_agent.h b/src/cobalt/debug/backend/css_agent.h
new file mode 100644
index 0000000..e34f03b
--- /dev/null
+++ b/src/cobalt/debug/backend/css_agent.h
@@ -0,0 +1,45 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef COBALT_DEBUG_BACKEND_CSS_AGENT_H_
+#define COBALT_DEBUG_BACKEND_CSS_AGENT_H_
+
+#include "cobalt/debug/backend/command_map.h"
+#include "cobalt/debug/backend/debug_dispatcher.h"
+#include "cobalt/debug/command.h"
+
+namespace cobalt {
+namespace debug {
+namespace backend {
+
+class CSSAgent {
+ public:
+  explicit CSSAgent(DebugDispatcher* dispatcher);
+  ~CSSAgent();
+
+ private:
+  void Enable(const Command& command);
+  void Disable(const Command& command);
+
+  // Helper object to connect to the debug dispatcher, etc.
+  DebugDispatcher* dispatcher_;
+
+  // Map of member functions implementing commands.
+  CommandMap<CSSAgent> commands_;
+};
+
+}  // namespace backend
+}  // namespace debug
+}  // namespace cobalt
+
+#endif  // COBALT_DEBUG_BACKEND_CSS_AGENT_H_
diff --git a/src/cobalt/debug/backend/debug_dispatcher.cc b/src/cobalt/debug/backend/debug_dispatcher.cc
index 3098cb7..e168243 100644
--- a/src/cobalt/debug/backend/debug_dispatcher.cc
+++ b/src/cobalt/debug/backend/debug_dispatcher.cc
@@ -30,10 +30,10 @@
                                  const dom::CspDelegate* csp_delegate,
                                  script::ScriptDebugger* script_debugger)
     : script_debugger_(script_debugger),
-      ALLOW_THIS_IN_INITIALIZER_LIST(script_runner_(
-          new DebugScriptRunner(global_environment, csp_delegate,
-                                base::Bind(&DebugDispatcher::SendEventInternal,
-                                           base::Unretained(this))))),
+      ALLOW_THIS_IN_INITIALIZER_LIST(script_runner_(new DebugScriptRunner(
+          global_environment, script_debugger, csp_delegate,
+          base::Bind(&DebugDispatcher::SendEventInternal,
+                     base::Unretained(this))))),
       message_loop_(MessageLoop::current()),
       is_paused_(false),
       // No manual reset, not initially signaled.
@@ -48,6 +48,8 @@
        it != clients_.end(); ++it) {
     (*it)->OnDetach(detach_reason);
   }
+  DCHECK(domain_registry_.empty())
+      << domain_registry_.begin()->first << " domain still registered.";
   for (DomainRegistry::iterator it = domain_registry_.begin();
        it != domain_registry_.end(); ++it) {
     RemoveDomain(it->first);
@@ -62,27 +64,6 @@
   clients_.erase(client);
 }
 
-JSONObject DebugDispatcher::CreateRemoteObject(
-    const script::ValueHandleHolder* object) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-
-  // Use default values for the parameters in the JS createRemoteObjectCallback.
-  static const char* kDefaultParams = "{}";
-
-  // This will execute a JavaScript function to create a Runtime.Remote object
-  // that describes the opaque JavaScript object.
-  base::optional<std::string> json_result =
-      script_runner_->CreateRemoteObject(object, kDefaultParams);
-
-  // Parse the serialized JSON result.
-  if (json_result) {
-    return JSONParse(json_result.value());
-  } else {
-    DLOG(WARNING) << "Could not create Runtime.RemoteObject";
-    return JSONObject(new base::DictionaryValue());
-  }
-}
-
 void DebugDispatcher::SendCommand(const Command& command) {
   // Create a closure that will run the command and the response callback.
   // The task is either posted to the debug target (WebModule) thread if
@@ -168,24 +149,6 @@
   SendEventInternal(method, json_params);
 }
 
-void DebugDispatcher::SendScriptEvent(const std::string& event,
-                                      const std::string& method,
-                                      const std::string& json_params) {
-  script::ScriptDebugger::ScopedPauseOnExceptionsState no_pause(
-      script_debugger_, script::ScriptDebugger::kNone);
-
-  std::string json_result;
-  bool success = script_runner_->RunCommand(method, json_params, &json_result);
-
-  if (!success) {
-    DLOG(ERROR) << "Script event failed (" << method << "): " << json_result;
-  } else if (json_result.empty()) {
-    DLOG(ERROR) << "Script event method not defined: " << method;
-  } else {
-    SendEventInternal(event, json_result);
-  }
-}
-
 void DebugDispatcher::SendEventInternal(
     const std::string& method, const base::optional<std::string>& json_params) {
   for (std::set<DebugClient*>::iterator it = clients_.begin();
@@ -214,17 +177,17 @@
   bool success = script_runner_->RunCommand(method, json_params, &json_result);
 
   JSONObject response(new base::DictionaryValue());
-  if (json_result.empty()) {
-    // An empty result means the method isn't implemented so return no response.
-    response.reset();
-  } else if (!success) {
-    // On error, |json_result| is the error message.
-    response->SetString("error.message", json_result);
-  } else {
+  if (success) {
     JSONObject result = JSONParse(json_result);
     if (result) {
       response->Set("result", result.release());
     }
+  } else if (!json_result.empty()) {
+    // On error, |json_result| is the error message.
+    response->SetString("error.message", json_result);
+  } else {
+    // An empty error means the method isn't implemented so return no response.
+    response.reset();
   }
   return response.Pass();
 }
diff --git a/src/cobalt/debug/backend/debug_dispatcher.h b/src/cobalt/debug/backend/debug_dispatcher.h
index d148ff7..205e559 100644
--- a/src/cobalt/debug/backend/debug_dispatcher.h
+++ b/src/cobalt/debug/backend/debug_dispatcher.h
@@ -103,19 +103,11 @@
   // agents providing the protocol command implementations.
   void RemoveDomain(const std::string& domain);
 
-  // Creates a Runtime.RemoteObject from an engine object.
-  JSONObject CreateRemoteObject(const script::ValueHandleHolder* object);
-
   // Called by the debug agents when an event occurs.
   // Serializes the method and params object to a JSON string and
   // calls |SendEventInternal|.
   void SendEvent(const std::string& method, const JSONObject& params);
 
-  // Runs a JavaScript function with optional JSON parameters, and sends the
-  // event it returns with |SendEvent|.
-  void SendScriptEvent(const std::string& event, const std::string& method,
-                       const std::string& json_params = "");
-
   // Calls |method| in |script_runner_| and creates a response object from
   // the result.
   JSONObject RunScriptCommand(const std::string& method,
diff --git a/src/cobalt/debug/backend/debug_module.cc b/src/cobalt/debug/backend/debug_module.cc
index 040606f..e5611a6 100644
--- a/src/cobalt/debug/backend/debug_module.cc
+++ b/src/cobalt/debug/backend/debug_module.cc
@@ -99,10 +99,6 @@
   script_debugger_agent_.reset(
       new ScriptDebuggerAgent(debug_dispatcher_.get(), script_debugger_.get()));
 
-  if (!script_debugger_agent_->IsSupportedDomain("Runtime")) {
-    runtime_agent_.reset(new RuntimeAgent(debug_dispatcher_.get()));
-  }
-
   console_agent_.reset(new ConsoleAgent(debug_dispatcher_.get(), data.console));
 
   log_agent_.reset(new LogAgent(debug_dispatcher_.get()));
@@ -110,6 +106,8 @@
   dom_agent_.reset(
       new DOMAgent(debug_dispatcher_.get(), dom_render_layer.Pass()));
 
+  css_agent_.reset(new CSSAgent(debug_dispatcher_.get()));
+
   page_agent_.reset(new PageAgent(debug_dispatcher_.get(), data.window,
                                   page_render_layer.Pass(),
                                   data.resource_provider));
diff --git a/src/cobalt/debug/backend/debug_module.h b/src/cobalt/debug/backend/debug_module.h
index 3e41612..d9ba0a0 100644
--- a/src/cobalt/debug/backend/debug_module.h
+++ b/src/cobalt/debug/backend/debug_module.h
@@ -20,12 +20,12 @@
 #include "base/message_loop.h"
 #include "base/synchronization/waitable_event.h"
 #include "cobalt/debug/backend/console_agent.h"
+#include "cobalt/debug/backend/css_agent.h"
 #include "cobalt/debug/backend/debug_dispatcher.h"
 #include "cobalt/debug/backend/dom_agent.h"
 #include "cobalt/debug/backend/log_agent.h"
 #include "cobalt/debug/backend/page_agent.h"
 #include "cobalt/debug/backend/render_overlay.h"
-#include "cobalt/debug/backend/runtime_agent.h"
 #include "cobalt/debug/backend/script_debugger_agent.h"
 #include "cobalt/debug/backend/tracing_agent.h"
 #include "cobalt/dom/console.h"
@@ -111,8 +111,8 @@
   scoped_ptr<ConsoleAgent> console_agent_;
   scoped_ptr<LogAgent> log_agent_;
   scoped_ptr<DOMAgent> dom_agent_;
+  scoped_ptr<CSSAgent> css_agent_;
   scoped_ptr<PageAgent> page_agent_;
-  scoped_ptr<RuntimeAgent> runtime_agent_;
   scoped_ptr<ScriptDebuggerAgent> script_debugger_agent_;
   scoped_ptr<TracingAgent> tracing_agent_;
 };
diff --git a/src/cobalt/debug/backend/debug_script_runner.cc b/src/cobalt/debug/backend/debug_script_runner.cc
index f97fab7..c8db27c 100644
--- a/src/cobalt/debug/backend/debug_script_runner.cc
+++ b/src/cobalt/debug/backend/debug_script_runner.cc
@@ -28,14 +28,16 @@
 
 namespace {
 const char kContentDir[] = "cobalt/debug/backend";
-const char kObjectIdentifier[] = "devtoolsBackend";
+const char kObjectIdentifier[] = "debugScriptRunner";
 }  // namespace
 
 DebugScriptRunner::DebugScriptRunner(
     script::GlobalEnvironment* global_environment,
+    script::ScriptDebugger* script_debugger,
     const dom::CspDelegate* csp_delegate,
     const OnEventCallback& on_event_callback)
     : global_environment_(global_environment),
+      script_debugger_(script_debugger),
       csp_delegate_(csp_delegate),
       on_event_callback_(on_event_callback) {
   // Bind this object to the global object so it can persist state and be
@@ -43,21 +45,6 @@
   global_environment_->Bind(kObjectIdentifier, make_scoped_refptr(this));
 }
 
-base::optional<std::string> DebugScriptRunner::CreateRemoteObject(
-    const script::ValueHandleHolder* object, const std::string& params) {
-  // Callback function should have been set by runtime.js.
-  DCHECK(create_remote_object_callback_);
-
-  CreateRemoteObjectCallback::ReturnValue result =
-      create_remote_object_callback_->value().Run(object, params);
-  if (result.exception) {
-    DLOG(WARNING) << "Exception creating remote object.";
-    return base::nullopt;
-  } else {
-    return result.result;
-  }
-}
-
 bool DebugScriptRunner::RunCommand(const std::string& method,
                                    const std::string& json_params,
                                    std::string* json_result) {
@@ -70,34 +57,11 @@
       "    ? '' : %s.%s(%s);",
       kObjectIdentifier, domain.c_str(), kObjectIdentifier, method.c_str(),
       kObjectIdentifier, method.c_str(), json_params.c_str());
-  return EvaluateScript(script, json_result);
+  return EvaluateDebuggerScript(script, json_result) && !json_result->empty();
 }
 
 bool DebugScriptRunner::RunScriptFile(const std::string& filename) {
   std::string result;
-  bool success = EvaluateScriptFile(filename, &result);
-  if (!success) {
-    DLOG(WARNING) << "Failed to run script file " << filename << ": " << result;
-  }
-  return success;
-}
-
-bool DebugScriptRunner::EvaluateScript(const std::string& js_code,
-                                       std::string* result) {
-  DCHECK(global_environment_);
-  DCHECK(result);
-  scoped_refptr<script::SourceCode> source_code =
-      script::SourceCode::CreateSourceCode(js_code, GetInlineSourceLocation());
-
-  ForceEnableEval();
-  bool succeeded = global_environment_->EvaluateScript(source_code, result);
-  SetEvalAllowedFromCsp();
-  return succeeded;
-}
-
-bool DebugScriptRunner::EvaluateScriptFile(const std::string& filename,
-                                           std::string* result) {
-  DCHECK(result);
 
   FilePath file_path;
   PathService::Get(paths::DIR_COBALT_WEB_ROOT, &file_path);
@@ -110,7 +74,20 @@
     return false;
   }
 
-  return EvaluateScript(script, result);
+  if (!EvaluateDebuggerScript(script, nullptr)) {
+    DLOG(ERROR) << "Failed to run script file " << filename << ": " << result;
+    return false;
+  }
+  return true;
+}
+
+bool DebugScriptRunner::EvaluateDebuggerScript(const std::string& script,
+                                               std::string* out_result_utf8) {
+  ForceEnableEval();
+  bool success =
+      script_debugger_->EvaluateDebuggerScript(script, out_result_utf8);
+  SetEvalAllowedFromCsp();
+  return success;
 }
 
 void DebugScriptRunner::SendEvent(const std::string& method,
@@ -118,6 +95,11 @@
   on_event_callback_.Run(method, params);
 }
 
+std::string DebugScriptRunner::CreateRemoteObject(
+    const script::ValueHandleHolder& object, const std::string& group) {
+  return script_debugger_->CreateRemoteObject(object, group);
+}
+
 void DebugScriptRunner::ForceEnableEval() {
   global_environment_->EnableEval();
   global_environment_->SetReportEvalCallback(base::Closure());
@@ -136,20 +118,6 @@
       &dom::CspDelegate::ReportEval, base::Unretained(csp_delegate_)));
 }
 
-const DebugScriptRunner::CreateRemoteObjectCallbackHolder*
-DebugScriptRunner::create_remote_object_callback() {
-  if (create_remote_object_callback_) {
-    return &(create_remote_object_callback_->referenced_value());
-  } else {
-    return NULL;
-  }
-}
-
-void DebugScriptRunner::set_create_remote_object_callback(
-    const CreateRemoteObjectCallbackHolder& callback) {
-  create_remote_object_callback_.emplace(this, callback);
-}
-
 }  // namespace backend
 }  // namespace debug
 }  // namespace cobalt
diff --git a/src/cobalt/debug/backend/debug_script_runner.h b/src/cobalt/debug/backend/debug_script_runner.h
index f783a07..588515b 100644
--- a/src/cobalt/debug/backend/debug_script_runner.h
+++ b/src/cobalt/debug/backend/debug_script_runner.h
@@ -22,6 +22,7 @@
 #include "cobalt/dom/csp_delegate.h"
 #include "cobalt/script/callback_function.h"
 #include "cobalt/script/global_environment.h"
+#include "cobalt/script/script_debugger.h"
 #include "cobalt/script/script_value.h"
 #include "cobalt/script/value_handle.h"
 #include "cobalt/script/wrappable.h"
@@ -46,27 +47,17 @@
                               const base::optional<std::string>& params)>
       OnEventCallback;
 
-  // Callback to create a JavaScript Runtime.RemoteObject from an opaque JS
-  // object.
-  typedef script::CallbackFunction<std::string(const script::ValueHandleHolder*,
-                                               const std::string&)>
-      CreateRemoteObjectCallback;
-  typedef script::ScriptValue<CreateRemoteObjectCallback>
-      CreateRemoteObjectCallbackHolder;
-
   DebugScriptRunner(script::GlobalEnvironment* global_environment,
+                    script::ScriptDebugger* script_debugger,
                     const dom::CspDelegate* csp_delegate,
                     const OnEventCallback& on_event_callback);
 
-  // Creates a Runtime.RemoteObject corresponding to an opaque JS object, by
-  // calling the |create_remote_object_callback_| script function.
-  // https://chromedevtools.github.io/devtools-protocol/1-3/Runtime#type-RemoteObject
-  base::optional<std::string> CreateRemoteObject(
-      const script::ValueHandleHolder* object, const std::string& params);
-
-  // Runs |method| on the JavaScript |runtimeInspector| object, passing in
-  // |json_params| and putting the result in |json_result|.
-  // Returns |true| if execution was successful, |false| otherwise.
+  // Runs |method| on the JavaScript |devtoolsBackend| object, passing in
+  // |json_params|. If |json_result| is non-NULL it receives the result.
+  // Returns |true| if the method was executed; |json_result| is the value
+  // returned by the method.
+  // Returns |false| if the method wasn't executed; if the method isn't defined
+  // |json_result| is empty, otherwise it's an error message.
   bool RunCommand(const std::string& method, const std::string& json_params,
                   std::string* json_result);
 
@@ -74,21 +65,21 @@
   // functionality to the JS object wrapped by this class.
   bool RunScriptFile(const std::string& filename);
 
-  // Non-standard JavaScript extension API.
-  // Called to send an event via the callback specified in the constructor.
+  // IDL: Sends a protocol event to the debugger frontend.
   void SendEvent(const std::string& method,
                  const base::optional<std::string>& params);
 
-  // Get/Set |create_remote_object_callback_|.
-  const CreateRemoteObjectCallbackHolder* create_remote_object_callback();
-  void set_create_remote_object_callback(
-      const CreateRemoteObjectCallbackHolder& callback);
+  // IDL: Returns the RemoteObject JSON representation of the given object for
+  // the debugger frontend.
+  // https://chromedevtools.github.io/devtools-protocol/1-3/Runtime#type-RemoteObject
+  std::string CreateRemoteObject(const script::ValueHandleHolder& object,
+                                 const std::string& group);
 
   DEFINE_WRAPPABLE_TYPE(DebugScriptRunner);
 
  private:
-  bool EvaluateScript(const std::string& js_code, std::string* result);
-  bool EvaluateScriptFile(const std::string& filename, std::string* result);
+  bool EvaluateDebuggerScript(const std::string& script,
+                              std::string* out_result_utf8);
 
   // Ensures the JS eval command is enabled, overriding CSP if necessary.
   void ForceEnableEval();
@@ -98,15 +89,14 @@
   // No ownership.
   script::GlobalEnvironment* global_environment_;
 
+  // Engine-specific debugger implementation.
+  script::ScriptDebugger* script_debugger_;
+
   // Non-owned reference to let this object query whether CSP allows eval.
   const dom::CspDelegate* csp_delegate_;
 
   // Callback to send events.
   OnEventCallback on_event_callback_;
-
-  // Callback to create a Runtime.RemoteObject.
-  base::optional<CreateRemoteObjectCallbackHolder::Reference>
-      create_remote_object_callback_;
 };
 
 }  // namespace backend
diff --git a/src/cobalt/debug/backend/debug_script_runner.idl b/src/cobalt/debug/backend/debug_script_runner.idl
index 74078cc..d43416c 100644
--- a/src/cobalt/debug/backend/debug_script_runner.idl
+++ b/src/cobalt/debug/backend/debug_script_runner.idl
@@ -17,8 +17,5 @@
 ]
 interface DebugScriptRunner {
   void sendEvent(DOMString method, DOMString? params);
-  attribute CreateRemoteObjectCallback createRemoteObjectCallback;
+  DOMString createRemoteObject(any obj, DOMString group);
 };
-
-callback CreateRemoteObjectCallback =
-    DOMString(object obj, DOMString jsonParams);
diff --git a/src/cobalt/debug/backend/dom_agent.cc b/src/cobalt/debug/backend/dom_agent.cc
index c854b92..a9e9267 100644
--- a/src/cobalt/debug/backend/dom_agent.cc
+++ b/src/cobalt/debug/backend/dom_agent.cc
@@ -28,33 +28,31 @@
 namespace backend {
 
 namespace {
-// File to load JavaScript DOM debugging domain implementation from.
-const char kScriptFile[] = "dom.js";
+// Definitions from the set specified here:
+// https://chromedevtools.github.io/devtools-protocol/tot/DOM
+constexpr char kInspectorDomain[] = "DOM";
 
-// Parameter names:
-const char kA[] = "a";
-const char kB[] = "b";
-const char kContentColor[] = "contentColor";
-const char kG[] = "g";
-const char kHighlightConfig[] = "highlightConfig";
-const char kR[] = "r";
+// File to load JavaScript DOM debugging domain implementation from.
+constexpr char kScriptFile[] = "dom_agent.js";
 }  // namespace
 
 DOMAgent::DOMAgent(DebugDispatcher* dispatcher,
                    scoped_ptr<RenderLayer> render_layer)
     : dispatcher_(dispatcher),
       render_layer_(render_layer.Pass()),
-      ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this)) {
+      ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this, kInspectorDomain)) {
   DCHECK(dispatcher_);
 
-  commands_["DOM.disable"] = &DOMAgent::Disable;
-  commands_["DOM.enable"] = &DOMAgent::Enable;
-  commands_["DOM.highlightNode"] = &DOMAgent::HighlightNode;
-  commands_["DOM.hideHighlight"] = &DOMAgent::HideHighlight;
+  commands_["disable"] = &DOMAgent::Disable;
+  commands_["enable"] = &DOMAgent::Enable;
+  commands_["highlightNode"] = &DOMAgent::HighlightNode;
+  commands_["hideHighlight"] = &DOMAgent::HideHighlight;
 
-  dispatcher_->AddDomain("DOM", commands_.Bind());
+  dispatcher_->AddDomain(kInspectorDomain, commands_.Bind());
 }
 
+DOMAgent::~DOMAgent() { dispatcher_->RemoveDomain(kInspectorDomain); }
+
 void DOMAgent::Enable(const Command& command) {
   bool initialized = dispatcher_->RunScriptFile(kScriptFile);
   if (initialized) {
@@ -75,7 +73,7 @@
 void DOMAgent::HighlightNode(const Command& command) {
   // Get the bounding rectangle of the specified node.
   JSONObject json_dom_rect = dispatcher_->RunScriptCommand(
-      "dom.getBoundingClientRect", command.GetParams());
+      "dom._getBoundingClientRect", command.GetParams());
   double x = 0.0;
   double y = 0.0;
   double width = 0.0;
@@ -93,7 +91,7 @@
   JSONObject params = JSONParse(command.GetParams());
   base::DictionaryValue* highlight_config_value = NULL;
   bool got_highlight_config =
-      params->GetDictionary(kHighlightConfig, &highlight_config_value);
+      params->GetDictionary("highlightConfig", &highlight_config_value);
   DCHECK(got_highlight_config);
   DCHECK(highlight_config_value);
 
@@ -119,12 +117,12 @@
   double a = 0.66;
   const base::DictionaryValue* content_color = NULL;
   bool got_content_color =
-      highlight_config_value->GetDictionary(kContentColor, &content_color);
+      highlight_config_value->GetDictionary("contentColor", &content_color);
   if (got_content_color && content_color) {
-    content_color->GetInteger(kR, &r);
-    content_color->GetInteger(kG, &g);
-    content_color->GetInteger(kB, &b);
-    content_color->GetDouble(kA, &a);
+    content_color->GetInteger("r", &r);
+    content_color->GetInteger("g", &g);
+    content_color->GetInteger("b", &b);
+    content_color->GetDouble("a", &a);
   }
   render_tree::ColorRGBA color(r / 255.0f, g / 255.0f, b / 255.0f,
                                static_cast<float>(a));
diff --git a/src/cobalt/debug/backend/dom_agent.h b/src/cobalt/debug/backend/dom_agent.h
index 64cfd79..809bd44 100644
--- a/src/cobalt/debug/backend/dom_agent.h
+++ b/src/cobalt/debug/backend/dom_agent.h
@@ -32,6 +32,7 @@
 class DOMAgent {
  public:
   DOMAgent(DebugDispatcher* dispatcher, scoped_ptr<RenderLayer> render_layer);
+  ~DOMAgent();
 
  private:
   void Enable(const Command& command);
diff --git a/src/cobalt/debug/backend/javascript_debugger_agent.cc b/src/cobalt/debug/backend/javascript_debugger_agent.cc
deleted file mode 100644
index 8e9d164..0000000
--- a/src/cobalt/debug/backend/javascript_debugger_agent.cc
+++ /dev/null
@@ -1,428 +0,0 @@
-// Copyright 2016 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "cobalt/debug/backend/javascript_debugger_agent.h"
-
-#include "base/bind.h"
-#include "base/optional.h"
-#include "base/stringprintf.h"
-#include "base/values.h"
-
-namespace cobalt {
-namespace debug {
-namespace backend {
-
-namespace {
-// Command, parameter and event names as specified by the protocol:
-// https://chromedevtools.github.io/devtools-protocol/1-3/Debugger
-
-// Parameter names.
-const char kCallFrameId[] = "callFrameId";
-const char kCallFrames[] = "callFrames";
-const char kColumnNumber[] = "columnNumber";
-const char kErrorLine[] = "errorLine";
-const char kErrorMessage[] = "errorMessage";
-const char kFunctionName[] = "functionName";
-const char kLineNumber[] = "lineNumber";
-const char kLocationColumnNumber[] = "location.columnNumber";
-const char kLocationLineNumber[] = "location.lineNumber";
-const char kLocationScriptId[] = "location.scriptId";
-const char kObject[] = "object";
-const char kReason[] = "reason";
-const char kScopeChain[] = "scopeChain";
-const char kScriptId[] = "scriptId";
-const char kState[] = "state";
-const char kThis[] = "this";
-const char kType[] = "type";
-const char kUrl[] = "url";
-
-// Parameter values.
-const char kDebugCommand[] = "debugCommand";
-
-// Result parameters.
-const char kBreakpointId[] = "result.breakpointId";
-const char kLocations[] = "result.locations";
-const char kScriptSource[] = "result.scriptSource";
-
-// Events.
-const char kPaused[] = "Debugger.paused";
-const char kResumed[] = "Debugger.resumed";
-const char kScriptFailedToParse[] = "Debugger.scriptFailedToParse";
-const char kScriptParsed[] = "Debugger.scriptParsed";
-
-// Construct a unique breakpoint id from url and source location.
-// Use the same format as Chrome.
-std::string BreakpointId(const std::string url, int line_number,
-                         int column_number) {
-  return base::StringPrintf("%s:%d:%d", url.c_str(), line_number,
-                            column_number);
-}
-
-}  // namespace
-
-JavaScriptDebuggerAgent::JavaScriptDebuggerAgent(DebugDispatcher* dispatcher)
-    : dispatcher_(dispatcher),
-      source_providers_deleter_(&source_providers_),
-      ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this)) {
-  DCHECK(dispatcher_);
-
-  commands_["Debugger.disable"] = &JavaScriptDebuggerAgent::Disable;
-  commands_["Debugger.enable"] = &JavaScriptDebuggerAgent::Enable;
-  commands_["Debugger.getScriptSource"] =
-      &JavaScriptDebuggerAgent::GetScriptSource;
-  commands_["Debugger.pause"] = &JavaScriptDebuggerAgent::Pause;
-  commands_["Debugger.resume"] = &JavaScriptDebuggerAgent::Resume;
-  commands_["Debugger.stepInto"] = &JavaScriptDebuggerAgent::StepInto;
-  commands_["Debugger.setBreakpointByUrl"] =
-      &JavaScriptDebuggerAgent::SetBreakpointByUrl;
-  commands_["Debugger.setPauseOnExceptions"] =
-      &JavaScriptDebuggerAgent::SetPauseOnExceptions;
-  commands_["Debugger.stepOut"] = &JavaScriptDebuggerAgent::StepOut;
-  commands_["Debugger.stepOver"] = &JavaScriptDebuggerAgent::StepOver;
-
-  dispatcher_->AddDomain("Debugger", commands_.Bind());
-}
-
-JavaScriptDebuggerAgent::~JavaScriptDebuggerAgent() {}
-
-void JavaScriptDebuggerAgent::Enable(const Command& command) {
-  DCHECK(dispatcher_->script_debugger());
-  dispatcher_->script_debugger()->Attach();
-  command.SendResponse();
-}
-
-void JavaScriptDebuggerAgent::Disable(const Command& command) {
-  DCHECK(dispatcher_->script_debugger());
-  dispatcher_->script_debugger()->Detach();
-  command.SendResponse();
-}
-
-void JavaScriptDebuggerAgent::GetScriptSource(const Command& command) {
-  JSONObject params = JSONParse(command.GetParams());
-  // Get the scriptId from the parameters.
-  std::string script_id;
-  bool got_script_id = params->GetString(kScriptId, &script_id);
-  if (!got_script_id) {
-    command.SendErrorResponse(Command::kInvalidParams,
-                              "No scriptId specified in parameters.");
-    return;
-  }
-
-  // Find the source provider with a matching scriptId.
-  SourceProviderMap::iterator it = source_providers_.find(script_id);
-  if (it == source_providers_.end()) {
-    command.SendErrorResponse(Command::kInvalidParams,
-                              "No script found with specified scriptId.");
-    return;
-  }
-  script::SourceProvider* source_provider = it->second;
-  DCHECK(source_provider);
-
-  // Build and return the result.
-  JSONObject result(new base::DictionaryValue());
-  result->SetString(kScriptSource, source_provider->GetScriptSource());
-  command.SendResponse(result);
-}
-
-void JavaScriptDebuggerAgent::Pause(const Command& command) {
-  DCHECK(dispatcher_->script_debugger());
-  dispatcher_->script_debugger()->Pause();
-  command.SendResponse();
-}
-
-void JavaScriptDebuggerAgent::Resume(const Command& command) {
-  DCHECK(dispatcher_->script_debugger());
-  dispatcher_->script_debugger()->Resume();
-  dispatcher_->SetPaused(false);
-  command.SendResponse();
-}
-
-void JavaScriptDebuggerAgent::SetBreakpointByUrl(const Command& command) {
-  DCHECK(dispatcher_->script_debugger());
-  JSONObject params = JSONParse(command.GetParams());
-
-  std::string url;
-  bool got_url = params->GetString(kUrl, &url);
-  if (!got_url) {
-    command.SendErrorResponse(Command::kInvalidParams,
-                              "Breakpoint URL must be specified.");
-    return;
-  }
-
-  // TODO: Should also handle setting of breakpoint by urlRegex
-
-  int line_number;
-  bool got_line_number = params->GetInteger(kLineNumber, &line_number);
-  if (!got_line_number) {
-    command.SendErrorResponse(Command::kInvalidParams,
-                              "Line number must be specified.");
-    return;
-  }
-  // If no column number is specified, just default to 0.
-  int column_number = 0;
-  params->GetInteger(kColumnNumber, &column_number);
-
-  // TODO: Should also handle condition and isAntibreakpoint.
-
-  // Create a new logical breakpoint and store it in our map.
-  const std::string breakpoint_id =
-      BreakpointId(url, line_number, column_number);
-  Breakpoint breakpoint(url, line_number, column_number);
-  breakpoints_[breakpoint_id] = breakpoint;
-
-  // Check the logical breakpoint against all currently loaded source providers
-  // and get an array of matching breakpoint locations.
-  std::vector<ScriptLocation> locations;
-  ResolveBreakpoint(breakpoint, &locations);
-
-  // Construct a result object from the logical breakpoint id and resolved
-  // source locations.
-  JSONObject result(new base::DictionaryValue());
-  result->SetString(kBreakpointId, breakpoint_id);
-  JSONList location_objects(new base::ListValue());
-  for (std::vector<ScriptLocation>::const_iterator it = locations.begin();
-       it != locations.end(); ++it) {
-    JSONObject location(new base::DictionaryValue());
-    location->SetString(kScriptId, it->script_id);
-    location->SetInteger(kLineNumber, it->line_number);
-    location->SetInteger(kColumnNumber, it->column_number);
-    location_objects->Append(location.release());
-  }
-  result->Set(kLocations, location_objects.release());
-  command.SendResponse(result);
-}
-
-void JavaScriptDebuggerAgent::SetPauseOnExceptions(const Command& command) {
-  DCHECK(dispatcher_->script_debugger());
-  JSONObject params = JSONParse(command.GetParams());
-
-  std::string state;
-  DCHECK(params->GetString(kState, &state));
-  if (state == "all") {
-    dispatcher_->script_debugger()->SetPauseOnExceptions(
-        script::ScriptDebugger::kAll);
-  } else if (state == "none") {
-    dispatcher_->script_debugger()->SetPauseOnExceptions(
-        script::ScriptDebugger::kNone);
-  } else if (state == "uncaught") {
-    dispatcher_->script_debugger()->SetPauseOnExceptions(
-        script::ScriptDebugger::kUncaught);
-  } else {
-    NOTREACHED();
-  }
-  command.SendResponse();
-}
-
-void JavaScriptDebuggerAgent::StepInto(const Command& command) {
-  DCHECK(dispatcher_->script_debugger());
-  dispatcher_->script_debugger()->StepInto();
-  dispatcher_->SetPaused(false);
-  command.SendResponse();
-}
-
-void JavaScriptDebuggerAgent::StepOut(const Command& command) {
-  DCHECK(dispatcher_->script_debugger());
-  dispatcher_->script_debugger()->StepOut();
-  dispatcher_->SetPaused(false);
-  command.SendResponse();
-}
-
-void JavaScriptDebuggerAgent::StepOver(const Command& command) {
-  DCHECK(dispatcher_->script_debugger());
-  dispatcher_->script_debugger()->StepOver();
-  dispatcher_->SetPaused(false);
-  command.SendResponse();
-}
-
-void JavaScriptDebuggerAgent::OnScriptDebuggerDetach(
-    const std::string& reason) {
-  DLOG(INFO) << "JavaScript debugger detached: " << reason;
-}
-
-void JavaScriptDebuggerAgent::OnScriptDebuggerPause(
-    scoped_ptr<script::CallFrame> call_frame) {
-  // Notify the clients we're about to pause.
-  SendPausedEvent(call_frame.Pass());
-
-  // Tell the debug dispatcher to enter paused state - block this thread.
-  dispatcher_->SetPaused(true);
-
-  // Notify the clients we've resumed.
-  SendResumedEvent();
-}
-
-void JavaScriptDebuggerAgent::OnScriptFailedToParse(
-    scoped_ptr<script::SourceProvider> source_provider) {
-  DCHECK(source_provider);
-  HandleScriptEvent(kScriptFailedToParse, source_provider.Pass());
-}
-
-void JavaScriptDebuggerAgent::OnScriptParsed(
-    scoped_ptr<script::SourceProvider> source_provider) {
-  DCHECK(source_provider);
-  HandleScriptEvent(kScriptParsed, source_provider.Pass());
-}
-
-JSONObject JavaScriptDebuggerAgent::CreateCallFrameData(
-    const scoped_ptr<script::CallFrame>& call_frame) {
-  DCHECK(call_frame);
-
-  // Create the JSON object and add the data for this call frame.
-  JSONObject call_frame_data(new base::DictionaryValue());
-  call_frame_data->SetString(kCallFrameId, call_frame->GetCallFrameId());
-  call_frame_data->SetString(kFunctionName, call_frame->GetFunctionName());
-
-  // Offset the line number according to the start line of the source.
-  const std::string script_id = call_frame->GetScriptId();
-  int line_number = call_frame->GetLineNumber();
-  DCHECK(source_providers_[script_id]);
-  base::optional<int> start_line = source_providers_[script_id]->GetStartLine();
-  line_number -= start_line.value_or(1);
-
-  // Add the location data.
-  call_frame_data->SetString(kLocationScriptId, script_id);
-  call_frame_data->SetInteger(kLocationLineNumber, line_number);
-  base::optional<int> column_number = call_frame->GetColumnNumber();
-  if (column_number) {
-    call_frame_data->SetInteger(kLocationColumnNumber, column_number.value());
-  }
-
-  // Add the scope chain data.
-  JSONList scope_chain_data(CreateScopeChainData(call_frame->GetScopeChain()));
-  call_frame_data->Set(kScopeChain, scope_chain_data.release());
-
-  // Add the "this" object data.
-  const script::ValueHandleHolder* this_object = call_frame->GetThis();
-  if (this_object) {
-    JSONObject this_object_data(dispatcher_->CreateRemoteObject(this_object));
-    call_frame_data->Set(kThis, this_object_data.release());
-  }
-
-  return call_frame_data.Pass();
-}
-
-JSONList JavaScriptDebuggerAgent::CreateCallStackData(
-    scoped_ptr<script::CallFrame> call_frame) {
-  JSONList call_frame_list(new base::ListValue());
-
-  // Consume the scoped CallFrame objects as we iterate over them.
-  while (call_frame) {
-    JSONObject call_frame_data(CreateCallFrameData(call_frame));
-    DCHECK(call_frame_data);
-    call_frame_list->Append(call_frame_data.release());
-    scoped_ptr<script::CallFrame> next_call_frame(call_frame->GetCaller());
-    call_frame = next_call_frame.Pass();
-  }
-
-  return call_frame_list.Pass();
-}
-
-JSONObject JavaScriptDebuggerAgent::CreateScopeData(
-    const scoped_ptr<script::Scope>& scope) {
-  DCHECK(scope);
-  const script::ValueHandleHolder* scope_object = scope->GetObject();
-  JSONObject scope_data(new base::DictionaryValue());
-  scope_data->Set(kObject,
-                  dispatcher_->CreateRemoteObject(scope_object).release());
-  scope_data->SetString(kType, script::Scope::TypeToString(scope->GetType()));
-  return scope_data.Pass();
-}
-
-JSONList JavaScriptDebuggerAgent::CreateScopeChainData(
-    scoped_ptr<script::Scope> scope) {
-  JSONList scope_chain_list(new base::ListValue());
-
-  // Consume the scoped Scope objects as we iterate over them.
-  while (scope) {
-    JSONObject scope_data(CreateScopeData(scope));
-    DCHECK(scope_data);
-    scope_chain_list->Append(scope_data.release());
-    scoped_ptr<script::Scope> next_scope(scope->GetNext());
-    scope = next_scope.Pass();
-  }
-
-  return scope_chain_list.Pass();
-}
-
-void JavaScriptDebuggerAgent::HandleScriptEvent(
-    const std::string& method,
-    scoped_ptr<script::SourceProvider> source_provider) {
-  DCHECK(source_provider);
-
-  // Send the event notification to the debugger clients.
-  JSONObject params(new base::DictionaryValue());
-  params->SetString(kScriptId, source_provider->GetScriptId());
-  params->SetString(kUrl, source_provider->GetUrl());
-  base::optional<int> error_line = source_provider->GetErrorLine();
-  if (error_line) {
-    DCHECK_EQ(method, kScriptFailedToParse);
-    params->SetInteger(kErrorLine, error_line.value());
-  }
-  base::optional<std::string> error_message =
-      source_provider->GetErrorMessage();
-  if (error_message) {
-    DCHECK_EQ(method, kScriptFailedToParse);
-    params->SetString(kErrorMessage, error_message.value());
-  }
-  dispatcher_->SendEvent(method, params);
-
-  // Store the raw pointer to the source provider in the map.
-  // The values in the map will be deleted on destruction by
-  // |source_providers_deleter_|.
-  const std::string script_id = source_provider->GetScriptId();
-  SourceProviderMap::iterator it = source_providers_.find(script_id);
-  if (it != source_providers_.end()) {
-    delete it->second;
-  }
-  source_providers_[script_id] = source_provider.release();
-}
-
-void JavaScriptDebuggerAgent::ResolveBreakpoint(
-    const Breakpoint& breakpoint, std::vector<ScriptLocation>* locations) {
-  for (SourceProviderMap::iterator it = source_providers_.begin();
-       it != source_providers_.end(); ++it) {
-    script::SourceProvider* script = it->second;
-    if (script->GetUrl() == breakpoint.url) {
-      dispatcher_->script_debugger()->SetBreakpoint(
-          script->GetScriptId(),
-          breakpoint.line_number + script->GetStartLine().value_or(1),
-          breakpoint.column_number);
-      locations->push_back(ScriptLocation(script->GetScriptId(),
-                                          breakpoint.line_number,
-                                          breakpoint.column_number));
-    }
-  }
-}
-
-void JavaScriptDebuggerAgent::SendPausedEvent(
-    scoped_ptr<script::CallFrame> call_frame) {
-  std::string event_method = kPaused;
-  JSONObject event_params(new base::DictionaryValue());
-  JSONList call_stack_data(CreateCallStackData(call_frame.Pass()));
-  DCHECK(call_stack_data);
-  event_params->Set(kCallFrames, call_stack_data.release());
-  event_params->SetString(kReason, kDebugCommand);
-  dispatcher_->SendEvent(event_method, event_params);
-}
-
-void JavaScriptDebuggerAgent::SendResumedEvent() {
-  // Send the event to the clients. No parameters.
-  std::string event_method = kResumed;
-  scoped_ptr<base::DictionaryValue> event_params(new base::DictionaryValue());
-  dispatcher_->SendEvent(event_method, event_params);
-}
-
-}  // namespace backend
-}  // namespace debug
-}  // namespace cobalt
diff --git a/src/cobalt/debug/backend/javascript_debugger_agent.h b/src/cobalt/debug/backend/javascript_debugger_agent.h
deleted file mode 100644
index 1da9fe1..0000000
--- a/src/cobalt/debug/backend/javascript_debugger_agent.h
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright 2016 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-#ifndef COBALT_DEBUG_BACKEND_JAVASCRIPT_DEBUGGER_AGENT_H_
-#define COBALT_DEBUG_BACKEND_JAVASCRIPT_DEBUGGER_AGENT_H_
-
-#include <map>
-#include <string>
-#include <vector>
-
-#include "base/callback.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/stl_util.h"
-#include "cobalt/debug/backend/command_map.h"
-#include "cobalt/debug/backend/debug_dispatcher.h"
-#include "cobalt/debug/command.h"
-#include "cobalt/debug/json_object.h"
-#include "cobalt/script/call_frame.h"
-#include "cobalt/script/scope.h"
-#include "cobalt/script/script_debugger.h"
-#include "cobalt/script/source_provider.h"
-
-namespace cobalt {
-namespace debug {
-namespace backend {
-
-class JavaScriptDebuggerAgent {
- public:
-  explicit JavaScriptDebuggerAgent(DebugDispatcher* dispatcher);
-
-  virtual ~JavaScriptDebuggerAgent();
-
-  // Formerly ScriptDebugger::Delegate implementation.
-  void OnScriptDebuggerDetach(const std::string& reason);
-  void OnScriptDebuggerPause(scoped_ptr<script::CallFrame> call_frame);
-  void OnScriptFailedToParse(
-      scoped_ptr<script::SourceProvider> source_provider);
-  void OnScriptParsed(
-      scoped_ptr<script::SourceProvider> source_provider);
-
- private:
-  // Map of SourceProvider pointers, keyed by string ID.
-  typedef std::map<std::string, script::SourceProvider*> SourceProviderMap;
-
-  // Logical breakpoint. A logical breakpoint exists independently of any
-  // particular script, and will be checked each time a new script is parsed.
-  // It can even correspond to multiple physical breakpoints, at least once we
-  // support url regexes.
-  struct Breakpoint {
-    Breakpoint() : line_number(0), column_number(0) {}
-    Breakpoint(const std::string& url, int line_number, int column_number)
-        : url(url), line_number(line_number), column_number(column_number) {}
-    std::string url;
-    int line_number;
-    int column_number;
-    std::string condition;
-  };
-
-  // Script location, corresponding to physical breakpoint, etc.
-  struct ScriptLocation {
-    ScriptLocation(const std::string& script_id, int line_number,
-                   int column_number)
-        : script_id(script_id),
-          line_number(line_number),
-          column_number(column_number) {}
-    std::string script_id;
-    int line_number;
-    int column_number;
-  };
-
-  // Map of logical breakpoints, keyed by a string ID.
-  typedef std::map<std::string, Breakpoint> BreakpointMap;
-
-  void Enable(const Command& command);
-  void Disable(const Command& command);
-
-  // Gets the source of a specified script.
-  void GetScriptSource(const Command& command);
-
-  // Code execution control commands.
-  void Pause(const Command& command);
-  void Resume(const Command& command);
-  void SetBreakpointByUrl(const Command& command);
-  void SetPauseOnExceptions(const Command& command);
-  void StepInto(const Command& command);
-  void StepOut(const Command& command);
-  void StepOver(const Command& command);
-
-  // Creates a JSON object describing a single call frame.
-  JSONObject CreateCallFrameData(
-      const scoped_ptr<script::CallFrame>& call_frame);
-
-  // Creates an array of JSON objects describing the call stack starting from
-  // the specified call frame. Takes ownership of |call_frame|.
-  JSONList CreateCallStackData(scoped_ptr<script::CallFrame> call_frame);
-
-  // Creates a JSON object describing a single scope object.
-  JSONObject CreateScopeData(const scoped_ptr<script::Scope>& scope);
-
-  // Creates an array of JSON objects describing the scope chain starting from
-  // the specified scope object. Takes ownership of |scope|.
-  JSONList CreateScopeChainData(scoped_ptr<script::Scope> scope);
-
-  // Called by |OnScriptFailedToParse| and |OnScriptParsed|.
-  // Stores the source provider in |source_providers_| and dispatches the
-  // specified event notification to the clients.
-  void HandleScriptEvent(const std::string& method,
-                         scoped_ptr<script::SourceProvider> source_provider);
-
-  // Resolves a logical breakpoint into an array of source locations, one for
-  // each matching script.
-  void ResolveBreakpoint(const Breakpoint& breakpoint,
-                         std::vector<ScriptLocation>* locations);
-
-  // Sends a Debugger.paused event to the clients with call stack data.
-  void SendPausedEvent(scoped_ptr<script::CallFrame> call_frame);
-
-  // Sends a Debugger.resumed event to the clients with no parameters.
-  void SendResumedEvent();
-
-  // Helper object to connect to the debug dispatcher, etc.
-  DebugDispatcher* dispatcher_;
-
-  // Map of source providers with scoped deleter to clean up on destruction.
-  SourceProviderMap source_providers_;
-  STLValueDeleter<SourceProviderMap> source_providers_deleter_;
-
-  // Map of logical breakpoints.
-  BreakpointMap breakpoints_;
-
-  // Map of member functions implementing commands.
-  CommandMap<JavaScriptDebuggerAgent> commands_;
-};
-
-}  // namespace backend
-}  // namespace debug
-}  // namespace cobalt
-
-#endif  // COBALT_DEBUG_BACKEND_JAVASCRIPT_DEBUGGER_AGENT_H_
diff --git a/src/cobalt/debug/backend/log_agent.cc b/src/cobalt/debug/backend/log_agent.cc
index 88c7ba5..ff36910 100644
--- a/src/cobalt/debug/backend/log_agent.cc
+++ b/src/cobalt/debug/backend/log_agent.cc
@@ -23,15 +23,7 @@
 namespace {
 // Definitions from the set specified here:
 // https://chromedevtools.github.io/devtools-protocol/1-3/Log
-
-// Parameter fields:
-constexpr char kEntryText[] = "entry.text";
-constexpr char kEntryLevel[] = "entry.level";
-
-// Events:
-// Our custom "Log.browserEntryAdded" event is just like "Log.entryAdded"
-// except it only shows up in the debug console and not in remote devtools.
-constexpr char kBrowserEntryAdded[] = "Log.browserEntryAdded";
+constexpr char kInspectorDomain[] = "Log";
 
 // Error levels:
 constexpr char kInfoLevel[] = "info";
@@ -57,14 +49,14 @@
 
 LogAgent::LogAgent(DebugDispatcher* dispatcher)
     : dispatcher_(dispatcher),
-      ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this)),
+      ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this, kInspectorDomain)),
       enabled_(false) {
   DCHECK(dispatcher_);
 
-  commands_["Log.enable"] = &LogAgent::Enable;
-  commands_["Log.disable"] = &LogAgent::Disable;
+  commands_["enable"] = &LogAgent::Enable;
+  commands_["disable"] = &LogAgent::Disable;
 
-  dispatcher_->AddDomain("Log", commands_.Bind());
+  dispatcher_->AddDomain(kInspectorDomain, commands_.Bind());
 
   // Get log output while still making it available elsewhere.
   log_message_handler_callback_id_ =
@@ -75,6 +67,8 @@
 LogAgent::~LogAgent() {
   base::LogMessageHandler::GetInstance()->RemoveCallback(
       log_message_handler_callback_id_);
+
+  dispatcher_->RemoveDomain(kInspectorDomain);
 }
 
 void LogAgent::Enable(const Command& command) {
@@ -95,11 +89,14 @@
   DCHECK(this);
 
   if (enabled_) {
+    // Our custom "Log.browserEntryAdded" event is just like "Log.entryAdded"
+    // except it only shows up in the debug console and not in remote devtools.
     // TODO: Flesh out the rest of LogEntry properties (source, timestamp)
     JSONObject params(new base::DictionaryValue());
-    params->SetString(kEntryText, str);
-    params->SetString(kEntryLevel, GetLogLevelFromSeverity(severity));
-    dispatcher_->SendEvent(kBrowserEntryAdded, params);
+    params->SetString("entry.text", str);
+    params->SetString("entry.level", GetLogLevelFromSeverity(severity));
+    dispatcher_->SendEvent(std::string(kInspectorDomain) + ".browserEntryAdded",
+                           params);
   }
 
   // Don't suppress the log message.
diff --git a/src/cobalt/debug/backend/page_agent.cc b/src/cobalt/debug/backend/page_agent.cc
index 9ff800e..c04cd55 100644
--- a/src/cobalt/debug/backend/page_agent.cc
+++ b/src/cobalt/debug/backend/page_agent.cc
@@ -31,18 +31,9 @@
 namespace backend {
 
 namespace {
-// Parameter field names:
-const char kFrameId[] = "result.frameTree.frame.id";
-const char kLoaderId[] = "result.frameTree.frame.loaderId";
-const char kMimeType[] = "result.frameTree.frame.mimeType";
-const char kResources[] = "result.frameTree.resources";
-const char kSecurityOrigin[] = "result.frameTree.frame.securityOrigin";
-const char kUrl[] = "result.frameTree.frame.url";
-
-// Constant parameter values:
-const char kFrameIdValue[] = "Cobalt";
-const char kLoaderIdValue[] = "Cobalt";
-const char kMimeTypeValue[] = "text/html";
+// Definitions from the set specified here:
+// https://chromedevtools.github.io/devtools-protocol/tot/Page
+constexpr char kInspectorDomain[] = "Page";
 }  // namespace
 
 PageAgent::PageAgent(DebugDispatcher* dispatcher, dom::Window* window,
@@ -51,33 +42,38 @@
     : window_(window),
       render_layer_(render_layer.Pass()),
       resource_provider_(resource_provider),
-      ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this)) {
-  DCHECK(dispatcher);
+      dispatcher_(dispatcher),
+      ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this, kInspectorDomain)) {
+  DCHECK(dispatcher_);
   DCHECK(window_);
   DCHECK(window_->document());
   DCHECK(render_layer_);
   DCHECK(resource_provider_);
 
-  commands_["Page.disable"] = &PageAgent::Disable;
-  commands_["Page.enable"] = &PageAgent::Enable;
-  commands_["Page.getResourceTree"] = &PageAgent::GetResourceTree;
-  commands_["Page.setOverlayMessage"] = &PageAgent::SetOverlayMessage;
+  commands_["disable"] = &PageAgent::Disable;
+  commands_["enable"] = &PageAgent::Enable;
+  commands_["getResourceTree"] = &PageAgent::GetResourceTree;
+  commands_["setOverlayMessage"] = &PageAgent::SetOverlayMessage;
 
-  dispatcher->AddDomain("Page", commands_.Bind());
+  dispatcher_->AddDomain(kInspectorDomain, commands_.Bind());
 }
 
+PageAgent::~PageAgent() { dispatcher_->RemoveDomain(kInspectorDomain); }
+
 void PageAgent::Disable(const Command& command) { command.SendResponse(); }
 
 void PageAgent::Enable(const Command& command) { command.SendResponse(); }
 
 void PageAgent::GetResourceTree(const Command& command) {
   JSONObject response(new base::DictionaryValue());
-  response->SetString(kFrameId, kFrameIdValue);
-  response->SetString(kLoaderId, kLoaderIdValue);
-  response->SetString(kMimeType, kMimeTypeValue);
-  response->SetString(kSecurityOrigin, window_->document()->url());
-  response->SetString(kUrl, window_->document()->url());
-  response->Set(kResources, new base::ListValue());
+  JSONObject frame(new base::DictionaryValue());
+  frame->SetString("id", "Cobalt");
+  frame->SetString("loaderId", "Cobalt");
+  frame->SetString("mimeType", "text/html");
+  frame->SetString("securityOrigin", window_->document()->url());
+  frame->SetString("url", window_->document()->url());
+  response->Set("result.frameTree.frame", frame.release());
+  response->Set("result.frameTree.resources", new base::ListValue());
   command.SendResponse(response);
 }
 
@@ -96,8 +92,8 @@
     render_tree::ColorRGBA text_color(0.0f, 0.0f, 0.0f, 1.0f);
 
     scoped_refptr<render_tree::Font> font =
-        resource_provider_->GetLocalTypeface("monospace",
-                                             render_tree::FontStyle())
+        resource_provider_
+            ->GetLocalTypeface("monospace", render_tree::FontStyle())
             ->CreateFontWithSize(font_size);
     scoped_refptr<render_tree::GlyphBuffer> glyph_buffer(
         resource_provider_->CreateGlyphBuffer(message, font));
diff --git a/src/cobalt/debug/backend/page_agent.h b/src/cobalt/debug/backend/page_agent.h
index 90ee978..5490451 100644
--- a/src/cobalt/debug/backend/page_agent.h
+++ b/src/cobalt/debug/backend/page_agent.h
@@ -37,6 +37,7 @@
   PageAgent(DebugDispatcher* dispatcher, dom::Window* window,
             scoped_ptr<RenderLayer> render_layer,
             render_tree::ResourceProvider* resource_provider);
+  ~PageAgent();
 
  private:
   void Enable(const Command& command);
@@ -48,6 +49,8 @@
   scoped_ptr<RenderLayer> render_layer_;
   render_tree::ResourceProvider* resource_provider_;
 
+  DebugDispatcher* dispatcher_;
+
   // Map of member functions implementing commands.
   CommandMap<PageAgent> commands_;
 };
diff --git a/src/cobalt/debug/backend/runtime_agent.cc b/src/cobalt/debug/backend/runtime_agent.cc
deleted file mode 100644
index 3816cd0..0000000
--- a/src/cobalt/debug/backend/runtime_agent.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2016 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "cobalt/debug/backend/runtime_agent.h"
-
-#include "base/bind.h"
-
-namespace cobalt {
-namespace debug {
-namespace backend {
-
-namespace {
-// File to load JavaScript runtime implementation from.
-const char kScriptFile[] = "runtime.js";
-
-// Event "methods" (names):
-const char kExecutionContextCreated[] = "Runtime.executionContextCreated";
-}  // namespace
-
-RuntimeAgent::RuntimeAgent(DebugDispatcher* dispatcher)
-    : dispatcher_(dispatcher), ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this)) {
-  DCHECK(dispatcher_);
-  if (!dispatcher_->RunScriptFile(kScriptFile)) {
-    DLOG(WARNING) << "Cannot execute Runtime initialization script.";
-  }
-
-  commands_["Runtime.enable"] = &RuntimeAgent::Enable;
-  commands_["Runtime.disable"] = &RuntimeAgent::Disable;
-  commands_["Runtime.compileScript"] = &RuntimeAgent::CompileScript;
-
-  dispatcher_->AddDomain("Runtime", commands_.Bind());
-}
-
-void RuntimeAgent::CompileScript(const Command& command) {
-  // TODO: Parse the JS without eval-ing it... This is to support:
-  // a) Multi-line input from the devtools console
-  // b) https://developers.google.com/web/tools/chrome-devtools/snippets
-  command.SendResponse();
-}
-
-void RuntimeAgent::Disable(const Command& command) { command.SendResponse(); }
-
-void RuntimeAgent::Enable(const Command& command) {
-  dispatcher_->SendScriptEvent(kExecutionContextCreated,
-                               "runtime.executionContextCreatedEvent");
-  command.SendResponse();
-}
-
-}  // namespace backend
-}  // namespace debug
-}  // namespace cobalt
diff --git a/src/cobalt/debug/backend/runtime_agent.h b/src/cobalt/debug/backend/runtime_agent.h
deleted file mode 100644
index c0b33e3..0000000
--- a/src/cobalt/debug/backend/runtime_agent.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2016 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef COBALT_DEBUG_BACKEND_RUNTIME_AGENT_H_
-#define COBALT_DEBUG_BACKEND_RUNTIME_AGENT_H_
-
-#include <string>
-
-#include "base/memory/scoped_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "cobalt/debug/backend/command_map.h"
-#include "cobalt/debug/backend/debug_dispatcher.h"
-#include "cobalt/debug/backend/debug_script_runner.h"
-#include "cobalt/debug/command.h"
-#include "cobalt/debug/json_object.h"
-
-namespace cobalt {
-namespace debug {
-namespace backend {
-
-class RuntimeAgent {
- public:
-  explicit RuntimeAgent(DebugDispatcher* dispatcher);
-
- private:
-  void CompileScript(const Command& command);
-  void Disable(const Command& command);
-  void Enable(const Command& command);
-
-  DebugDispatcher* dispatcher_;
-
-  // Map of member functions implementing commands.
-  CommandMap<RuntimeAgent> commands_;
-};
-
-}  // namespace backend
-}  // namespace debug
-}  // namespace cobalt
-
-#endif  // COBALT_DEBUG_BACKEND_RUNTIME_AGENT_H_
diff --git a/src/cobalt/debug/backend/script_debugger_agent.cc b/src/cobalt/debug/backend/script_debugger_agent.cc
index 7ad0314..30aea03 100644
--- a/src/cobalt/debug/backend/script_debugger_agent.cc
+++ b/src/cobalt/debug/backend/script_debugger_agent.cc
@@ -51,6 +51,13 @@
   }
 }
 
+ScriptDebuggerAgent::~ScriptDebuggerAgent() {
+  for (auto it = registered_domains_.begin(); it != registered_domains_.end();
+       ++it) {
+    dispatcher_->RemoveDomain(*it);
+  }
+}
+
 bool ScriptDebuggerAgent::RunCommand(const Command& command) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
diff --git a/src/cobalt/debug/backend/script_debugger_agent.h b/src/cobalt/debug/backend/script_debugger_agent.h
index 728084c..6f8376d 100644
--- a/src/cobalt/debug/backend/script_debugger_agent.h
+++ b/src/cobalt/debug/backend/script_debugger_agent.h
@@ -34,6 +34,7 @@
  public:
   ScriptDebuggerAgent(DebugDispatcher* dispatcher,
                       script::ScriptDebugger* script_debugger);
+  ~ScriptDebuggerAgent();
 
   bool IsSupportedDomain(const std::string& domain) {
     return registered_domains_.count(domain) != 0;
diff --git a/src/cobalt/debug/backend/tracing_agent.cc b/src/cobalt/debug/backend/tracing_agent.cc
index 53ffe7e..05b4cf4 100644
--- a/src/cobalt/debug/backend/tracing_agent.cc
+++ b/src/cobalt/debug/backend/tracing_agent.cc
@@ -24,16 +24,12 @@
 namespace backend {
 
 namespace {
+// Definitions from the set specified here:
+// https://chromedevtools.github.io/devtools-protocol/tot/Tracing
+constexpr char kInspectorDomain[] = "Tracing";
+
 // Size in characters of JSON to batch dataCollected events.
 constexpr size_t kDataCollectedSize = 24 * 1024;
-
-// Parameter fields:
-constexpr char kValue[] = "value";
-constexpr char kCategories[] = "categories";
-
-// Events:
-constexpr char kDataCollected[] = "Tracing.dataCollected";
-constexpr char kTracingComplete[] = "Tracing.tracingComplete";
 }  // namespace
 
 TracingAgent::TracingAgent(DebugDispatcher* dispatcher,
@@ -42,15 +38,17 @@
       script_debugger_(script_debugger),
       tracing_started_(false),
       collected_size_(0),
-      ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this)) {
+      ALLOW_THIS_IN_INITIALIZER_LIST(commands_(this, kInspectorDomain)) {
   DCHECK(dispatcher_);
 
-  commands_["Tracing.end"] = &TracingAgent::End;
-  commands_["Tracing.start"] = &TracingAgent::Start;
+  commands_["end"] = &TracingAgent::End;
+  commands_["start"] = &TracingAgent::Start;
 
-  dispatcher_->AddDomain("Tracing", commands_.Bind());
+  dispatcher_->AddDomain(kInspectorDomain, commands_.Bind());
 }
 
+TracingAgent::~TracingAgent() { dispatcher_->RemoveDomain(kInspectorDomain); }
+
 void TracingAgent::End(const Command& command) {
   DCHECK(thread_checker_.CalledOnValidThread());
   if (!tracing_started_) {
@@ -77,7 +75,7 @@
   // Parse comma-separated tracing categories parameter.
   std::vector<std::string> categories;
   std::string category_param;
-  if (params->GetString(kCategories, &category_param)) {
+  if (params->GetString("categories", &category_param)) {
     for (size_t pos = 0, comma; pos < category_param.size(); pos = comma + 1) {
       comma = category_param.find(',', pos);
       if (comma == std::string::npos) comma = category_param.size();
@@ -111,7 +109,8 @@
 
 void TracingAgent::FlushTraceEvents() {
   SendDataCollectedEvent();
-  dispatcher_->SendEvent(kTracingComplete, JSONObject());
+  dispatcher_->SendEvent(std::string(kInspectorDomain) + ".tracingComplete",
+                         JSONObject());
 }
 
 void TracingAgent::SendDataCollectedEvent() {
@@ -119,8 +118,9 @@
     collected_size_ = 0;
     JSONObject params(new base::DictionaryValue());
     // Releasing the list into the value param avoids copying it.
-    params->Set(kValue, collected_events_.release());
-    dispatcher_->SendEvent(kDataCollected, params);
+    params->Set("value", collected_events_.release());
+    dispatcher_->SendEvent(std::string(kInspectorDomain) + ".dataCollected",
+                           params);
   }
 }
 
diff --git a/src/cobalt/debug/backend/tracing_agent.h b/src/cobalt/debug/backend/tracing_agent.h
index bd31a50..47b2d75 100644
--- a/src/cobalt/debug/backend/tracing_agent.h
+++ b/src/cobalt/debug/backend/tracing_agent.h
@@ -32,6 +32,7 @@
  public:
   explicit TracingAgent(DebugDispatcher* dispatcher,
                         script::ScriptDebugger* script_debugger);
+  ~TracingAgent();
 
   // TraceDelegate
   void AppendTraceEvent(const std::string& trace_event_json) override;
diff --git a/src/cobalt/debug/debug.gyp b/src/cobalt/debug/debug.gyp
index ce20f85..ca81de4 100644
--- a/src/cobalt/debug/debug.gyp
+++ b/src/cobalt/debug/debug.gyp
@@ -24,6 +24,8 @@
         'backend/command_map.h',
         'backend/console_agent.cc',
         'backend/console_agent.h',
+        'backend/css_agent.cc',
+        'backend/css_agent.h',
         'backend/debug_dispatcher.cc',
         'backend/debug_dispatcher.h',
         'backend/debug_module.cc',
@@ -32,8 +34,6 @@
         'backend/debug_script_runner.h',
         'backend/dom_agent.cc',
         'backend/dom_agent.h',
-        'backend/javascript_debugger_agent.cc',
-        'backend/javascript_debugger_agent.h',
         'backend/log_agent.cc',
         'backend/log_agent.h',
         'backend/page_agent.cc',
@@ -42,8 +42,6 @@
         'backend/render_layer.h',
         'backend/render_overlay.cc',
         'backend/render_overlay.h',
-        'backend/runtime_agent.cc',
-        'backend/runtime_agent.h',
         'backend/script_debugger_agent.cc',
         'backend/script_debugger_agent.h',
         'backend/tracing_agent.cc',
diff --git a/src/cobalt/debug/remote/debug_web_server.cc b/src/cobalt/debug/remote/debug_web_server.cc
index b1aaf4e..7647307 100644
--- a/src/cobalt/debug/remote/debug_web_server.cc
+++ b/src/cobalt/debug/remote/debug_web_server.cc
@@ -342,10 +342,15 @@
   std::string address;
   int result = GetLocalAddress(&address);
   if (result == net::OK) {
-    DLOG(INFO) << "Debug web server running at: " << address;
+    // clang-format off
+    LOG(INFO) << "\n---------------------------------"
+              << "\n Connect to the web debugger at:"
+              << "\n " << address
+              << "\n---------------------------------";
+    // clang-format on
     local_address_ = address;
   } else {
-    DLOG(WARNING) << "Could not start debug web server";
+    LOG(WARNING) << "Could not start debug web server";
   }
 }
 
diff --git a/src/cobalt/doc/performance_tuning.md b/src/cobalt/doc/performance_tuning.md
index 3a3b85b..82dac2b 100644
--- a/src/cobalt/doc/performance_tuning.md
+++ b/src/cobalt/doc/performance_tuning.md
@@ -202,7 +202,16 @@
 value of `SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER` in your platform's
 `configuration_public.h` file.  Unless your platform is restricted in this
 aspect, you should ensure that `SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER`
-is set to `0`.
+is set to `0`.  If the platform needs a new frame submitted periodically,
+an alternative to setting `SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER` to `1`
+for OpenGL ES platforms is to have `eglGetProcAddress()` return a function
+when queried for `eglGetMinimumFramesPerSecondCOBALT`:
+
+`EGLint eglGetMinimumFramesPerSecondCOBALT(EGLDisplay display)`
+
+Every `cobalt_minimum_frame_time_in_milliseconds`, this function will be queried
+to determine if a new frame should be presented even if the scene has not
+changed.
 
 **Tags:** *configuration_public.h, startup, browse-to-watch, input latency,
            framerate.*
diff --git a/src/cobalt/dom/element.cc b/src/cobalt/dom/element.cc
index 933bd32..b3b6cd7 100644
--- a/src/cobalt/dom/element.cc
+++ b/src/cobalt/dom/element.cc
@@ -16,6 +16,7 @@
 
 #include <algorithm>
 
+#include "base/debug/trace_event.h"
 #include "base/lazy_instance.h"
 #include "base/string_util.h"
 #include "cobalt/base/tokens.h"
@@ -190,6 +191,8 @@
 //   https://www.w3.org/TR/2014/WD-dom-20140710/#dom-element-setattribute
 void Element::SetAttribute(const std::string& name, const std::string& value) {
   TRACK_MEMORY_SCOPE("DOM");
+  TRACE_EVENT2("cobalt::dom", "Element::SetAttribute",
+               "name", name, "value", value);
   Document* document = node_document();
 
   // 1. Not needed by Cobalt.
@@ -268,6 +271,7 @@
 //   https://www.w3.org/TR/2014/WD-dom-20140710/#dom-element-removeattribute
 void Element::RemoveAttribute(const std::string& name) {
   TRACK_MEMORY_SCOPE("DOM");
+  TRACE_EVENT1("cobalt::dom", "Element::RemoveAttribute", "name", name);
   Document* document = node_document();
 
   // 1. If the context object is in the HTML namespace and its node document is
diff --git a/src/cobalt/dom/event_target.h b/src/cobalt/dom/event_target.h
index 9077f32..1adf1a8 100644
--- a/src/cobalt/dom/event_target.h
+++ b/src/cobalt/dom/event_target.h
@@ -273,6 +273,13 @@
     SetAttributeEventListener(base::Tokens::resize(), event_listener);
   }
 
+  const EventListenerScriptValue* onscroll() {
+    return GetAttributeEventListener(base::Tokens::scroll());
+  }
+  void set_onscroll(const EventListenerScriptValue& event_listener) {
+    SetAttributeEventListener(base::Tokens::scroll(), event_listener);
+  }
+
   const EventListenerScriptValue* ongotpointercapture() {
     return GetAttributeEventListener(base::Tokens::gotpointercapture());
   }
diff --git a/src/cobalt/dom/font_face_updater.cc b/src/cobalt/dom/font_face_updater.cc
index a45ba94..ba36c18 100644
--- a/src/cobalt/dom/font_face_updater.cc
+++ b/src/cobalt/dom/font_face_updater.cc
@@ -169,6 +169,7 @@
     case cssom::KeywordValue::kRepeat:
     case cssom::KeywordValue::kReverse:
     case cssom::KeywordValue::kRight:
+    case cssom::KeywordValue::kScroll:
     case cssom::KeywordValue::kSolid:
     case cssom::KeywordValue::kStart:
     case cssom::KeywordValue::kStatic:
diff --git a/src/cobalt/dom/global_event_handlers.idl b/src/cobalt/dom/global_event_handlers.idl
index 5e1ab6a..6a9e7ef 100644
--- a/src/cobalt/dom/global_event_handlers.idl
+++ b/src/cobalt/dom/global_event_handlers.idl
@@ -43,6 +43,7 @@
   attribute EventHandler onplaying;
 
   attribute EventHandler onresize;
+  attribute EventHandler onscroll;
 
   attribute EventHandler ontransitionend;
 
diff --git a/src/cobalt/dom/html_image_element.cc b/src/cobalt/dom/html_image_element.cc
index 5903ffd..3bdf023 100644
--- a/src/cobalt/dom/html_image_element.cc
+++ b/src/cobalt/dom/html_image_element.cc
@@ -156,7 +156,7 @@
 void HTMLImageElement::OnLoadingSuccess() {
   TRACE_EVENT0("cobalt::dom", "HTMLImageElement::OnLoadingSuccess()");
   AllowGarbageCollectionAfterEventIsDispatched(
-      base::Tokens::load(), &prevent_gc_until_load_complete_);
+      base::Tokens::load(), prevent_gc_until_load_complete_.Pass());
   if (node_document()) {
     node_document()->DecreaseLoadingCounterAndMaybeDispatchLoadEvent();
   }
@@ -166,7 +166,7 @@
 void HTMLImageElement::OnLoadingError() {
   TRACE_EVENT0("cobalt::dom", "HTMLImageElement::OnLoadingError()");
   AllowGarbageCollectionAfterEventIsDispatched(
-      base::Tokens::error(), &prevent_gc_until_load_complete_);
+      base::Tokens::error(), prevent_gc_until_load_complete_.Pass());
   if (node_document()) {
     node_document()->DecreaseLoadingCounterAndMaybeDispatchLoadEvent();
   }
@@ -175,29 +175,30 @@
 
 void HTMLImageElement::PreventGarbageCollectionUntilEventIsDispatched(
     base::Token event_name) {
-  DCHECK(!prevent_gc_until_event_dispatch_);
-  prevent_gc_until_event_dispatch_.reset(
-      new script::GlobalEnvironment::ScopedPreventGarbageCollection(
-          html_element_context()->script_runner()->GetGlobalEnvironment(),
-          this));
+  scoped_ptr<script::GlobalEnvironment::ScopedPreventGarbageCollection>
+      prevent_gc_until_event_dispatch(
+          new script::GlobalEnvironment::ScopedPreventGarbageCollection(
+              html_element_context()->script_runner()->GetGlobalEnvironment(),
+              this));
   AllowGarbageCollectionAfterEventIsDispatched(
-      event_name, &prevent_gc_until_event_dispatch_);
+      event_name, prevent_gc_until_event_dispatch.Pass());
 }
 
 void HTMLImageElement::AllowGarbageCollectionAfterEventIsDispatched(
     base::Token event_name,
-    scoped_ptr<script::GlobalEnvironment::ScopedPreventGarbageCollection>*
+    scoped_ptr<script::GlobalEnvironment::ScopedPreventGarbageCollection>
         scoped_prevent_gc) {
   PostToDispatchEventAndRunCallback(
       FROM_HERE, event_name,
       base::Bind(&HTMLImageElement::DestroyScopedPreventGC,
-                 base::AsWeakPtr<HTMLImageElement>(this), scoped_prevent_gc));
+                 base::AsWeakPtr<HTMLImageElement>(this),
+                 base::Passed(&scoped_prevent_gc)));
 }
 
 void HTMLImageElement::DestroyScopedPreventGC(
-    scoped_ptr<script::GlobalEnvironment::ScopedPreventGarbageCollection>*
+    scoped_ptr<script::GlobalEnvironment::ScopedPreventGarbageCollection>
         scoped_prevent_gc) {
-  scoped_prevent_gc->reset();
+  scoped_prevent_gc.reset();
 }
 
 }  // namespace dom
diff --git a/src/cobalt/dom/html_image_element.h b/src/cobalt/dom/html_image_element.h
index 86101a3..531a7d4 100644
--- a/src/cobalt/dom/html_image_element.h
+++ b/src/cobalt/dom/html_image_element.h
@@ -67,19 +67,16 @@
   void PreventGarbageCollectionUntilEventIsDispatched(base::Token event_name);
   void AllowGarbageCollectionAfterEventIsDispatched(
       base::Token event_name,
-      scoped_ptr<script::GlobalEnvironment::ScopedPreventGarbageCollection>*
+      scoped_ptr<script::GlobalEnvironment::ScopedPreventGarbageCollection>
           scoped_prevent_gc);
   void DestroyScopedPreventGC(
-      scoped_ptr<script::GlobalEnvironment::ScopedPreventGarbageCollection>*
+      scoped_ptr<script::GlobalEnvironment::ScopedPreventGarbageCollection>
           scoped_prevent_gc);
 
   scoped_ptr<loader::image::CachedImage::OnLoadedCallbackHandler>
       cached_image_loaded_callback_handler_;
 
   scoped_ptr<script::GlobalEnvironment::ScopedPreventGarbageCollection>
-      prevent_gc_until_event_dispatch_;
-
-  scoped_ptr<script::GlobalEnvironment::ScopedPreventGarbageCollection>
       prevent_gc_until_load_complete_;
 };
 
diff --git a/src/cobalt/dom/mutation_observer.cc b/src/cobalt/dom/mutation_observer.cc
index 1e53507..fc20b3d 100644
--- a/src/cobalt/dom/mutation_observer.cc
+++ b/src/cobalt/dom/mutation_observer.cc
@@ -14,6 +14,7 @@
 
 #include "cobalt/dom/mutation_observer.h"
 
+#include "base/debug/trace_event.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/dom/dom_settings.h"
 #include "cobalt/dom/mutation_observer_task_manager.h"
@@ -131,11 +132,13 @@
 
 void MutationObserver::QueueMutationRecord(
     const scoped_refptr<MutationRecord>& record) {
+  TRACE_EVENT0("cobalt::dom", "MutationObserver::QueueMutationRecord()");
   record_queue_.push_back(record);
   task_manager_->QueueMutationObserverMicrotask();
 }
 
 bool MutationObserver::Notify() {
+  TRACE_EVENT0("cobalt::dom", "MutationObserver::Notify()");
   // https://www.w3.org/TR/dom/#mutationobserver
   // Step 3 of "notify mutation observers" steps:
   //     1. Let queue be a copy of mo's record queue.
diff --git a/src/cobalt/dom/mutation_observer_task_manager.cc b/src/cobalt/dom/mutation_observer_task_manager.cc
index 4e47ef6..3e10631 100644
--- a/src/cobalt/dom/mutation_observer_task_manager.cc
+++ b/src/cobalt/dom/mutation_observer_task_manager.cc
@@ -15,6 +15,7 @@
 #include "cobalt/dom/mutation_observer_task_manager.h"
 
 #include "base/callback.h"
+#include "base/debug/trace_event.h"
 #include "base/message_loop.h"
 #include "cobalt/dom/mutation_observer.h"
 
@@ -25,6 +26,8 @@
     MutationObserver* observer) {
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(observers_.find(observer) == observers_.end());
+  TRACE_EVENT0("cobalt::dom",
+               "MutationObserverTaskManager::OnMutationObserverCreated()");
   observers_.insert(observer);
 }
 
@@ -32,11 +35,16 @@
     MutationObserver* observer) {
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(observers_.find(observer) != observers_.end());
+  TRACE_EVENT0("cobalt::dom",
+               "MutationObserverTaskManager::OnMutationObserverDestroyed()");
   observers_.erase(observer);
 }
 
 void MutationObserverTaskManager::QueueMutationObserverMicrotask() {
   DCHECK(thread_checker_.CalledOnValidThread());
+  TRACE_EVENT0("cobalt::dom",
+               "MutationObserverTaskManager::QueueMutationObserverMicrotask()");
+
   // https://www.w3.org/TR/dom/#queue-a-mutation-observer-compound-microtask
   // To queue a mutation observer compound microtask, run these steps:
   // 1. If mutation observer compound microtask queued flag is set, terminate
@@ -58,6 +66,8 @@
 }
 
 void MutationObserverTaskManager::NotifyMutationObservers() {
+  TRACE_EVENT0("cobalt::dom",
+               "MutationObserverTaskManager::NotifyMutationObservers()");
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(task_posted_);
   DCHECK(MessageLoop::current());
diff --git a/src/cobalt/dom/mutation_reporter.cc b/src/cobalt/dom/mutation_reporter.cc
index e9e1c3a..77f3b07 100644
--- a/src/cobalt/dom/mutation_reporter.cc
+++ b/src/cobalt/dom/mutation_reporter.cc
@@ -14,6 +14,7 @@
 
 #include "cobalt/dom/mutation_reporter.h"
 
+#include "base/debug/trace_event.h"
 #include "base/hash_tables.h"
 #include "cobalt/dom/mutation_observer.h"
 #include "cobalt/dom/mutation_observer_init.h"
@@ -153,6 +154,7 @@
     const scoped_refptr<Node>& target,
     MutationReporter::RegisteredObserverVector* registered_observers,
     MutationRecordBuilder* record_builder) {
+  TRACE_EVENT0("cobalt::dom", "ReportToInterestedObservers()");
   typedef base::hash_set<MutationObserver*> MutationObserverSet;
   MutationObserverSet reported_observers;
   for (size_t i = 0; i < registered_observers->size(); ++i) {
@@ -198,12 +200,15 @@
 void MutationReporter::ReportAttributesMutation(
     const std::string& name,
     const base::optional<std::string>& old_value) const {
+  TRACE_EVENT0("cobalt::dom", "MutationReporter::ReportAttributesMutation()");
   AttributeMutationRecordBuilder record_builder(name, old_value);
   ReportToInterestedObservers(target_, observers_.get(), &record_builder);
 }
 
 void MutationReporter::ReportCharacterDataMutation(
     const std::string& old_value) const {
+  TRACE_EVENT0("cobalt::dom",
+               "MutationReporter::ReportCharacterDataMutation()");
   CharacterDataMutationRecordBuilder record_builder(old_value);
   ReportToInterestedObservers(target_, observers_.get(), &record_builder);
 }
@@ -213,6 +218,7 @@
     const scoped_refptr<dom::NodeList>& removed_nodes,
     const scoped_refptr<dom::Node>& previous_sibling,
     const scoped_refptr<dom::Node>& next_sibling) const {
+  TRACE_EVENT0("cobalt::dom", "MutationReporter::ReportChildListMutation()");
   ChildListMutationRecordBuilder record_builder(added_nodes, removed_nodes,
                                                 previous_sibling, next_sibling);
   ReportToInterestedObservers(target_, observers_.get(), &record_builder);
diff --git a/src/cobalt/dom/node.cc b/src/cobalt/dom/node.cc
index a2fd668..a8d1842 100644
--- a/src/cobalt/dom/node.cc
+++ b/src/cobalt/dom/node.cc
@@ -199,6 +199,7 @@
 //   https://www.w3.org/TR/2015/WD-dom-20150618/#dom-node-clonenode
 scoped_refptr<Node> Node::CloneNode(bool deep) const {
   TRACK_MEMORY_SCOPE("DOM");
+  TRACE_EVENT0("cobalt::dom", "Node::CloneNode()");
   scoped_refptr<Node> new_node = Duplicate();
   DCHECK(new_node);
   if (deep) {
@@ -229,6 +230,7 @@
 scoped_refptr<Node> Node::InsertBefore(
     const scoped_refptr<Node>& new_child,
     const scoped_refptr<Node>& reference_child) {
+  TRACE_EVENT0("cobalt::dom", "Node::InsertBefore()");
   // The insertBefore(node, child) method must return the result of
   // pre-inserting node into the context object before child.
   return PreInsert(new_child, reference_child);
@@ -237,6 +239,7 @@
 // Algorithm for AppendChild:
 //   https://www.w3.org/TR/dom/#dom-node-appendchild
 scoped_refptr<Node> Node::AppendChild(const scoped_refptr<Node>& new_child) {
+  TRACE_EVENT0("cobalt::dom", "Node::AppendChild()");
   // The appendChild(node) method must return the result of appending node to
   // the context object.
   // To append a node to a parent, pre-insert node into parent before null.
@@ -247,6 +250,7 @@
 //   https://www.w3.org/TR/dom/#dom-node-replacechild
 scoped_refptr<Node> Node::ReplaceChild(const scoped_refptr<Node>& node,
                                        const scoped_refptr<Node>& child) {
+  TRACE_EVENT0("cobalt::dom", "Node::ReplaceChild()");
   // The replaceChild(node, child) method must return the result of replacing
   // child with node within the context object.
   // To replace a child with node within a parent, run these steps:
@@ -343,6 +347,7 @@
 // Algorithm for RemoveChild:
 //   https://www.w3.org/TR/dom/#dom-node-removechild
 scoped_refptr<Node> Node::RemoveChild(const scoped_refptr<Node>& node) {
+  TRACE_EVENT0("cobalt::dom", "Node::RemoveChild()");
   // The removeChild(child) method must return the result of pre-removing child
   // from the context object.
   return PreRemove(node);
@@ -424,6 +429,7 @@
 // Algorithm for AdoptIntoDocument:
 //   https://www.w3.org/TR/dom/#concept-node-adopt
 void Node::AdoptIntoDocument(Document* document) {
+  TRACE_EVENT0("cobalt::dom", "Node::AdoptIntoDocument()");
   DCHECK(!IsDocument());
   if (!document) {
     return;
@@ -672,6 +678,7 @@
 //   https://www.w3.org/TR/dom/#concept-node-pre-insert
 scoped_refptr<Node> Node::PreInsert(const scoped_refptr<Node>& node,
                                     const scoped_refptr<Node>& child) {
+  TRACE_EVENT0("cobalt::dom", "Node::PreInsert()");
   // 1. Ensure pre-insertion validity of node into parent before child.
   if (!EnsurePreInsertionValidity(node, child)) {
     return NULL;
@@ -692,6 +699,7 @@
 //   https://www.w3.org/TR/dom/#concept-node-insert
 void Node::Insert(const scoped_refptr<Node>& node,
                   const scoped_refptr<Node>& child, bool suppress_observers) {
+  TRACE_EVENT0("cobalt::dom", "Node::Insert()");
   // 1. 2. Not needed by Cobalt.
   // 3. Let nodes be node's children if node is a DocumentFragment node, and a
   // list containing solely node otherwise.
@@ -762,6 +770,7 @@
 // Algorithm for PreRemove:
 //   https://www.w3.org/TR/dom/#concept-node-pre-remove
 scoped_refptr<Node> Node::PreRemove(const scoped_refptr<Node>& child) {
+  TRACE_EVENT0("cobalt::dom", "Node::PreRemove()");
   // 1. If child's parent is not parent, throw a "NotFoundError" exception.
   if (!child || child->parent_ != this) {
     // TODO: Throw JS NotFoundError.
@@ -779,6 +788,7 @@
 //   https://www.w3.org/TR/dom/#concept-node-remove
 void Node::Remove(const scoped_refptr<Node>& node, bool suppress_observers) {
   DCHECK(node);
+  TRACE_EVENT0("cobalt::dom", "Node::Remove()");
 
   OnMutation();
   node->UpdateGenerationForNodeAndAncestors();
@@ -858,6 +868,7 @@
 // Algorithm for ReplaceAll:
 //   https://www.w3.org/TR/dom/#concept-node-replace-all
 void Node::ReplaceAll(const scoped_refptr<Node>& node) {
+  TRACE_EVENT0("cobalt::dom", "Node::ReplaceAll()");
   // 1. If node is not null, adopt node into parent's node document.
   if (node) {
     node->AdoptIntoDocument(this->node_document());
diff --git a/src/cobalt/dom/on_screen_keyboard.cc b/src/cobalt/dom/on_screen_keyboard.cc
index 767e3b8..1fcd896 100644
--- a/src/cobalt/dom/on_screen_keyboard.cc
+++ b/src/cobalt/dom/on_screen_keyboard.cc
@@ -29,6 +29,7 @@
       script_value_factory_(script_value_factory),
       next_ticket_(0) {
   DCHECK(bridge_) << "OnScreenKeyboardBridge must not be NULL";
+  suggestions_supported_ = bridge_->SuggestionsSupported();
 }
 
 script::Handle<script::Promise<void>> OnScreenKeyboard::Show() {
@@ -91,22 +92,29 @@
     const script::Sequence<std::string>& suggestions) {
   script::Handle<script::Promise<void>> promise =
       script_value_factory_->CreateBasicPromise<void>();
-
 #if SB_API_VERSION >= SB_ON_SCREEN_KEYBOARD_SUGGESTIONS_VERSION
-  int ticket = next_ticket_++;
-  bool is_emplaced =
-      ticket_to_update_suggestions_promise_map_
-          .emplace(ticket, std::unique_ptr<VoidPromiseValue::Reference>(
-                               new VoidPromiseValue::Reference(this, promise)))
-          .second;
-  DCHECK(is_emplaced);
-  bridge_->UpdateSuggestions(suggestions, ticket);
+  if (suggestions_supported_) {
+    int ticket = next_ticket_++;
+    bool is_emplaced =
+        ticket_to_update_suggestions_promise_map_
+            .emplace(ticket,
+                     std::unique_ptr<VoidPromiseValue::Reference>(
+                         new VoidPromiseValue::Reference(this, promise)))
+            .second;
+    DCHECK(is_emplaced);
+    bridge_->UpdateSuggestions(suggestions, ticket);
+  } else {
+    LOG(WARNING)
+        << "Starboard version " << SB_API_VERSION
+        << " does not support on-screen keyboard suggestions on this platform.";
+    promise->Reject();
+  }
 #else
   UNREFERENCED_PARAMETER(suggestions);
-  LOG(WARNING) << "Starboard version " << SB_API_VERSION
-               << " does not support on-screen keyboard suggestions.";
-  promise->Resolve();
-  DispatchEvent(new dom::Event(base::Tokens::suggestionsUpdated()));
+  LOG(WARNING)
+      << "Starboard version " << SB_API_VERSION
+      << " does not support on-screen keyboard suggestions on this platform.";
+  promise->Reject();
 #endif  // SB_API_VERSION >= SB_ON_SCREEN_KEYBOARD_SUGGESTIONS_VERSION
   return promise;
 }
diff --git a/src/cobalt/dom/on_screen_keyboard.h b/src/cobalt/dom/on_screen_keyboard.h
index f7c22d6..57a0487 100644
--- a/src/cobalt/dom/on_screen_keyboard.h
+++ b/src/cobalt/dom/on_screen_keyboard.h
@@ -65,6 +65,11 @@
   std::string data() const { return data_; }
   void set_data(const std::string& data) { data_ = data; }
 
+  bool is_composing() const { return is_composing_; }
+  void set_is_composing(const bool is_composing) {
+    is_composing_ = is_composing;
+  }
+
   const EventListenerScriptValue* onshow() const;
   void set_onshow(const EventListenerScriptValue& event_listener);
 
@@ -83,6 +88,9 @@
   // If the keyboard is shown.
   bool shown() const;
 
+  // If the keyboard has suggestions implemented.
+  bool suggestions_supported() const { return suggestions_supported_; }
+
   // The rectangle of the keyboard in screen pixel coordinates.
   scoped_refptr<DOMRect> bounding_rect() const;
 
@@ -117,11 +125,14 @@
   script::ScriptValueFactory* const script_value_factory_;
 
   std::string data_;
+  bool is_composing_;
 
   int next_ticket_;
 
   bool keep_focus_ = false;
 
+  bool suggestions_supported_;
+
   DISALLOW_COPY_AND_ASSIGN(OnScreenKeyboard);
 };
 }  // namespace dom
diff --git a/src/cobalt/dom/on_screen_keyboard.idl b/src/cobalt/dom/on_screen_keyboard.idl
index e177ec7..ebe632c 100644
--- a/src/cobalt/dom/on_screen_keyboard.idl
+++ b/src/cobalt/dom/on_screen_keyboard.idl
@@ -24,7 +24,9 @@
   Promise<void> blur();
   Promise<void> updateSuggestions(sequence<DOMString> suggestions);
   readonly attribute boolean shown;
-  // TODO: Add hasSuggestionsAbility boolean.
+  // If the keyboard has suggestions implemented. If false, calling
+  // updateSuggestions() will result in the promise being immediately rejected.
+  readonly attribute boolean suggestionsSupported;
 
   // If the keyboard is shown, return bounding rectangle in screen pixel
   // coordinates, otherwise return NULL.
diff --git a/src/cobalt/dom/on_screen_keyboard_bridge.h b/src/cobalt/dom/on_screen_keyboard_bridge.h
index 5f3d7a3..d58c0ad 100644
--- a/src/cobalt/dom/on_screen_keyboard_bridge.h
+++ b/src/cobalt/dom/on_screen_keyboard_bridge.h
@@ -35,6 +35,7 @@
   virtual void UpdateSuggestions(
       const script::Sequence<std::string>& suggestions, int ticket) = 0;
   virtual bool IsShown() const = 0;
+  virtual bool SuggestionsSupported() const = 0;
   virtual scoped_refptr<DOMRect> BoundingRect() const = 0;
   virtual void SetKeepFocus(bool keep_focus) = 0;
   virtual bool IsValidTicket(int ticket) const = 0;
diff --git a/src/cobalt/dom/on_screen_keyboard_test.cc b/src/cobalt/dom/on_screen_keyboard_test.cc
index 207bf55..40af8be 100644
--- a/src/cobalt/dom/on_screen_keyboard_test.cc
+++ b/src/cobalt/dom/on_screen_keyboard_test.cc
@@ -102,6 +102,12 @@
     last_ticket_ = -1;
   }
 
+  bool SuggestionsSupported() const override {
+    // TODO: implement and test this.
+    SB_NOTIMPLEMENTED();
+    return false;
+  }
+
   void UpdateSuggestions(const script::Sequence<std::string>& suggestions,
                          int ticket) override {
     SB_UNREFERENCED_PARAMETER(suggestions);
diff --git a/src/cobalt/dom/pseudo_element.h b/src/cobalt/dom/pseudo_element.h
index 86e4d84..491ca7c 100644
--- a/src/cobalt/dom/pseudo_element.h
+++ b/src/cobalt/dom/pseudo_element.h
@@ -60,7 +60,6 @@
   cssom::RulesWithCascadePrecedence* matching_rules() {
     return &matching_rules_;
   }
-  void ClearMatchingRules() { matching_rules_.clear(); }
 
   bool computed_style_invalid() const { return computed_style_invalid_; }
   void set_computed_style_invalid() { computed_style_invalid_ = true; }
diff --git a/src/cobalt/dom/rule_matching.cc b/src/cobalt/dom/rule_matching.cc
index 86ae85b..32e2c1e 100644
--- a/src/cobalt/dom/rule_matching.cc
+++ b/src/cobalt/dom/rule_matching.cc
@@ -807,7 +807,7 @@
   // they can be compared to the new matching rules after they are generated.
   cssom::RulesWithCascadePrecedence* element_old_matching_rules =
       element_document->scratchpad_html_element_matching_rules();
-  *element_old_matching_rules = *element->matching_rules();
+  *element_old_matching_rules = std::move(*element->matching_rules());
   element->matching_rules()->clear();
 
   for (int type = 0; type < kMaxPseudoElementType; ++type) {
@@ -817,8 +817,9 @@
       cssom::RulesWithCascadePrecedence* old_pseudo_element_matching_rules =
           element_document->scratchpad_pseudo_element_matching_rules(
               PseudoElementType(type));
-      *old_pseudo_element_matching_rules = *pseudo_element->matching_rules();
-      pseudo_element->ClearMatchingRules();
+      *old_pseudo_element_matching_rules =
+          std::move(*pseudo_element->matching_rules());
+      pseudo_element->matching_rules()->clear();
     }
   }
 
diff --git a/src/cobalt/extension/platform_service.h b/src/cobalt/extension/platform_service.h
new file mode 100644
index 0000000..a9d25ef
--- /dev/null
+++ b/src/cobalt/extension/platform_service.h
@@ -0,0 +1,85 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_EXTENSION_PLATFORM_SERVICE_H_
+#define COBALT_EXTENSION_PLATFORM_SERVICE_H_
+
+#include <stdint.h>
+
+#include "starboard/configuration.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct CobaltExtensionPlatformServicePrivate
+    CobaltExtensionPlatformServicePrivate;
+typedef CobaltExtensionPlatformServicePrivate* CobaltExtensionPlatformService;
+
+// Well-defined value for an invalid |Service|.
+#define kCobaltExtensionPlatformServiceInvalid \
+  ((CobaltExtensionPlatformService)0)
+
+#define kCobaltExtensionPlatformServiceName \
+  "dev.cobalt.extension.PlatformService"
+
+// Checks whether a |CobaltExtensionPlatformService| is valid.
+static SB_C_INLINE bool CobaltExtensionPlatformServiceIsValid(
+    CobaltExtensionPlatformService service) {
+  return service != kCobaltExtensionPlatformServiceInvalid;
+}
+
+// When a client receives a message from a service, the service will be passed
+// in as the |context| here, with |data|, which has length |length|.
+typedef void (*ReceiveMessageCallback)(void* context, void* data,
+                                       uint64_t length);
+
+typedef struct CobaltExtensionPlatformServiceApi {
+  // This name will be "dev.cobalt.extensions.PlatformService".
+  const char* kName;
+  uint64_t kVersion;
+
+  // The fields below this point were added in version 1 or later.
+
+  // Return whether the platform has service indicated by |name|.
+  bool (*Has)(const char* name);
+
+  // Open and return a service by name.
+  //
+  // |context|: pointer to context object for callback.
+  // |name|: name of the service.
+  // |receive_callback|: callback to run when Cobalt should receive data.
+  CobaltExtensionPlatformService (*Open)(
+      void* context, const char* name, ReceiveMessageCallback receive_callback);
+
+  // Close the service passed in as |service|.
+  void (*Close)(CobaltExtensionPlatformService service);
+
+  // Send |data| of length |length| to |service|. If there is a synchronous
+  // response, it will be returned via void* and |output_length| will be set
+  // to its length. The returned void* will be owned by the caller, and must
+  // be deallocated via SbMemoryDeallocate() by the caller when appropriate.
+  // If there is no synchronous response, NULL will be returned and
+  // |output_length| will be 0. The |invalid_state| will be set to true if the
+  // service is not currently able to accept data, and otherwise will be set to
+  // false.
+  void* (*Send)(CobaltExtensionPlatformService service, void* data,
+                uint64_t length, uint64_t* output_length, bool* invalid_state);
+} CobaltExtensionPlatformServiceApi;
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // COBALT_EXTENSION_PLATFORM_SERVICE_H_
diff --git a/src/cobalt/h5vcc/h5vcc.gyp b/src/cobalt/h5vcc/h5vcc.gyp
index ea7ae61..54709b1 100644
--- a/src/cobalt/h5vcc/h5vcc.gyp
+++ b/src/cobalt/h5vcc/h5vcc.gyp
@@ -40,6 +40,8 @@
         'h5vcc_deep_link_event_target.cc',
         'h5vcc_deep_link_event_target.h',
         'h5vcc_event_listener_container.h',
+        'h5vcc_platform_service.cc',
+        'h5vcc_platform_service.h',
         'h5vcc_runtime.cc',
         'h5vcc_runtime.h',
         'h5vcc_runtime_event_target.cc',
diff --git a/src/cobalt/h5vcc/h5vcc_platform_service.cc b/src/cobalt/h5vcc/h5vcc_platform_service.cc
new file mode 100644
index 0000000..a63f004
--- /dev/null
+++ b/src/cobalt/h5vcc/h5vcc_platform_service.cc
@@ -0,0 +1,177 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/h5vcc/h5vcc_platform_service.h"
+
+#include "cobalt/base/polymorphic_downcast.h"
+#include "cobalt/dom/dom_settings.h"
+#include "starboard/configuration.h"
+#include "starboard/string.h"
+
+namespace cobalt {
+namespace h5vcc {
+
+// static
+scoped_refptr<H5vccPlatformService> H5vccPlatformService::Open(
+    script::EnvironmentSettings* settings, const std::string service_name,
+    const ReceiveCallbackArg& receive_callback) {
+#if SB_API_VERSION < SB_EXTENSIONS_API_VERSION
+  SB_DLOG(WARNING)
+      << "PlatformService not implemented in this version of Starboard.";
+  return NULL;
+#else   // SB_API_VERSION < SB_EXTENSIONS_API_VERSION
+  DCHECK(settings);
+  dom::DOMSettings* dom_settings =
+      base::polymorphic_downcast<dom::DOMSettings*>(settings);
+  auto* global_environment = dom_settings->global_environment();
+  DCHECK(global_environment);
+
+  ExtPlatformServiceApi* platform_service_api =
+      static_cast<ExtPlatformServiceApi*>(
+          SbSystemGetExtension(kCobaltExtensionPlatformServiceName));
+  if (!platform_service_api) {
+    SB_DLOG(WARNING) << "PlatformService is not implemented on this platform.";
+    return NULL;
+  }
+  scoped_refptr<H5vccPlatformService> service = new H5vccPlatformService(
+      global_environment, platform_service_api, receive_callback);
+  char* service_name_c_str = new char[kMaxNameLength];
+  SbStringCopy(service_name_c_str, service_name.c_str(), kMaxNameLength);
+
+  ExtPlatformService platform_service = platform_service_api->Open(
+      service, service_name_c_str, &H5vccPlatformService::Receive);
+  if (!platform_service) {
+    return NULL;
+  }
+  service->ext_service_ = platform_service;
+  return service;
+#endif  // SB_API_VERSION < SB_EXTENSIONS_API_VERSION
+}
+
+H5vccPlatformService::H5vccPlatformService(
+    script::GlobalEnvironment* environment,
+    ExtPlatformServiceApi* platform_service_api,
+    const ReceiveCallbackArg& receive_callback)
+    : environment_(environment),
+      platform_service_api_(platform_service_api),
+      receive_callback_(this, receive_callback),
+      main_message_loop_(base::MessageLoopProxy::current()),
+      ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
+      ALLOW_THIS_IN_INITIALIZER_LIST(
+          weak_this_(weak_ptr_factory_.GetWeakPtr())) {
+  DCHECK(platform_service_api_);
+  DCHECK(main_message_loop_);
+}
+
+H5vccPlatformService::~H5vccPlatformService() {
+  if (IsOpen()) {
+    LOG(WARNING) << "Closing service due to destruction";
+    Close();
+  }
+}
+
+// static
+bool H5vccPlatformService::Has(const std::string& service_name) {
+#if SB_API_VERSION < SB_EXTENSIONS_API_VERSION
+  DLOG(WARNING)
+      << "PlatformService not implemented in this version of Starboard.";
+  return false;
+#else   // SB_API_VERSION < SB_EXTENSIONS_API_VERSION
+  ExtPlatformServiceApi* platform_service_api =
+      static_cast<ExtPlatformServiceApi*>(
+          SbSystemGetExtension(kCobaltExtensionPlatformServiceName));
+  if (!platform_service_api) {
+    DLOG(WARNING) << "PlatformService is not implemented on this platform.";
+    return false;
+  }
+  return platform_service_api->Has(service_name.c_str());
+#endif  // SB_API_VERSION < SB_EXTENSIONS_API_VERSION
+}
+
+script::Handle<script::ArrayBuffer> H5vccPlatformService::Send(
+    const script::Handle<script::ArrayBuffer>& data,
+    script::ExceptionState* exception_state) {
+  if (!IsOpen()) {
+    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+                             "Closed service should not Send.",
+                             exception_state);
+    return script::ArrayBuffer::New(environment_, 0);
+  }
+  uint64_t output_length = 0;
+  bool invalid_state = 0;
+  void* output_data = platform_service_api_->Send(
+      ext_service_, data->Data(), data->ByteLength(), &output_length,
+      &invalid_state);
+  if (invalid_state) {
+    dom::DOMException::Raise(dom::DOMException::kInvalidStateErr,
+                             "Service unable to accept data currently.",
+                             exception_state);
+    SbMemoryDeallocate(output_data);
+    return script::ArrayBuffer::New(environment_, 0);
+  }
+  auto output_buffer =
+      script::ArrayBuffer::New(environment_, output_data, output_length);
+  // Deallocate |output_data| which has been copied into the ArrayBuffer.
+  SbMemoryDeallocate(output_data);
+  return output_buffer;
+}
+
+// static
+void H5vccPlatformService::Receive(void* context, void* data, uint64_t length) {
+  DCHECK(context) << "Platform should not call Receive with NULL context";
+  static_cast<H5vccPlatformService*>(context)->ReceiveInternal(data, length);
+}
+
+void H5vccPlatformService::ReceiveInternal(void* data, uint64_t length) {
+  // ReceiveInternal may be called by another thread.
+  if (!main_message_loop_->BelongsToCurrentThread()) {
+    main_message_loop_->PostTask(
+        FROM_HERE, base::Bind(&H5vccPlatformService::ReceiveInternal,
+                              weak_this_, data, length));
+    return;
+  }
+  DCHECK(main_message_loop_->BelongsToCurrentThread());
+  if (!IsOpen()) {
+    LOG(ERROR) << "Closed service cannot Receive.";
+    return;
+  }
+  script::Handle<script::ArrayBuffer> data_array_buffer;
+  if (length) {
+    data_array_buffer = script::ArrayBuffer::New(environment_, data, length);
+  } else {
+    data_array_buffer = script::ArrayBuffer::New(environment_, 0);
+  }
+
+  const scoped_refptr<H5vccPlatformService>& service(this);
+  receive_callback_.value().Run(service, data_array_buffer);
+}
+
+void H5vccPlatformService::Close() {
+  DCHECK(main_message_loop_->BelongsToCurrentThread());
+
+  if (!IsOpen()) {
+    LOG(ERROR) << "Cannot close service that is not open.";
+    return;
+  }
+
+  platform_service_api_->Close(ext_service_);
+  ext_service_ = kCobaltExtensionPlatformServiceInvalid;
+}
+
+bool H5vccPlatformService::IsOpen() {
+  return CobaltExtensionPlatformServiceIsValid(ext_service_);
+}
+
+}  // namespace h5vcc
+}  // namespace cobalt
diff --git a/src/cobalt/h5vcc/h5vcc_platform_service.h b/src/cobalt/h5vcc/h5vcc_platform_service.h
new file mode 100644
index 0000000..1c25386
--- /dev/null
+++ b/src/cobalt/h5vcc/h5vcc_platform_service.h
@@ -0,0 +1,90 @@
+// Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_H5VCC_H5VCC_PLATFORM_SERVICE_H_
+#define COBALT_H5VCC_H5VCC_PLATFORM_SERVICE_H_
+
+#include <map>
+#include <string>
+
+#include "base/message_loop_proxy.h"
+#include "base/optional.h"
+#include "cobalt/dom/dom_exception.h"
+#include "cobalt/extension/platform_service.h"
+#include "cobalt/script/array_buffer.h"
+#include "cobalt/script/callback_function.h"
+#include "cobalt/script/global_environment.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace h5vcc {
+
+class H5vccPlatformService : public script::Wrappable {
+ public:
+  using ExtPlatformService = CobaltExtensionPlatformService;
+  using ExtPlatformServiceApi = CobaltExtensionPlatformServiceApi;
+
+  using ReceiveCallback = script::CallbackFunction<void(
+      const scoped_refptr<H5vccPlatformService>&,
+      const script::Handle<script::ArrayBuffer>&)>;
+  using ReceiveCallbackArg = script::ScriptValue<ReceiveCallback>;
+  using ReceiveCallbackReference = ReceiveCallbackArg::Reference;
+
+  H5vccPlatformService(script::GlobalEnvironment* environment,
+                       ExtPlatformServiceApi* platform_service_api,
+                       const ReceiveCallbackArg& receive_callback);
+
+  ~H5vccPlatformService();
+
+  static bool Has(const std::string& service_name);
+
+  static scoped_refptr<H5vccPlatformService> Open(
+      script::EnvironmentSettings* settings, const std::string service_name,
+      const ReceiveCallbackArg& receive_callback);
+
+  script::Handle<script::ArrayBuffer> Send(
+      const script::Handle<script::ArrayBuffer>& data,
+      script::ExceptionState* exception_state);
+
+  void Close();
+
+  DEFINE_WRAPPABLE_TYPE(H5vccPlatformService);
+
+ private:
+  static constexpr size_t kMaxNameLength = 1024;
+
+  void ReceiveInternal(void* data, uint64_t length);
+
+  static void Receive(void* context, void* data, uint64_t length);
+
+  bool IsOpen();
+
+  script::GlobalEnvironment* environment_;
+  ExtPlatformServiceApi* platform_service_api_;
+  const ReceiveCallbackReference receive_callback_;
+  // The main message loop.
+  scoped_refptr<base::MessageLoopProxy> const main_message_loop_;
+
+  base::WeakPtrFactory<H5vccPlatformService> weak_ptr_factory_;
+  // We construct a WeakPtr upon H5vccPlatformService's construction in order to
+  // associate the WeakPtr with the constructing thread.
+  base::WeakPtr<H5vccPlatformService> weak_this_;
+
+  ExtPlatformService ext_service_ = kCobaltExtensionPlatformServiceInvalid;
+};
+
+}  // namespace h5vcc
+}  // namespace cobalt
+
+#endif  // COBALT_H5VCC_H5VCC_PLATFORM_SERVICE_H_
diff --git a/src/cobalt/h5vcc/h5vcc_platform_service.idl b/src/cobalt/h5vcc/h5vcc_platform_service.idl
new file mode 100644
index 0000000..c9a2a61
--- /dev/null
+++ b/src/cobalt/h5vcc/h5vcc_platform_service.idl
@@ -0,0 +1,46 @@
+// 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.
+
+[
+  ConstructorCallWith=EnvironmentSettings
+]
+
+interface H5vccPlatformService {
+  // Returns true if the given service exists on the platform, false otherwise.
+  static boolean has(DOMString service_name);
+
+  // Opens and returns a service with the specified name.
+  // Returns null if no service of the specified name exists.
+  // Data may be received via the |receive_callback| the moment after this
+  // function returns non-null.
+  [CallWith=EnvironmentSettings] static H5vccPlatformService? open(DOMString service_name, ReceiveCallback receive_callback);
+
+  // Sends the specified data to the service. The service
+  // may optionally synchronously return data. For asynchronous
+  // responses the service should utilize the |receive_callback|.
+  // If the service has been closed, then kInvalidStateErr is raised.
+  // If the service is open but not currently able to accept data, then
+  // kInvalidStateErr is raised.
+  [RaisesException] ArrayBuffer? send(ArrayBuffer data);
+
+  // Closes the specified service.  After calling Close(), it is
+  // guaranteed that |receive_callback| will no longer be called, and
+  // subsequent calls to send() are considered errors.
+  void close();
+};
+
+// When a service is opened a callback must be registered to enable push
+// communications from the service.  A service may use the receive callback
+// to asynchronously respond to a previous Send() call.
+callback ReceiveCallback = void (H5vccPlatformService service, ArrayBuffer data);
diff --git a/src/cobalt/input/input_device_manager_desktop.cc b/src/cobalt/input/input_device_manager_desktop.cc
index 6d62739..5f824cf 100644
--- a/src/cobalt/input/input_device_manager_desktop.cc
+++ b/src/cobalt/input/input_device_manager_desktop.cc
@@ -310,9 +310,7 @@
   dom::InputEventInit input_event;
   UpdateEventInit(event, &input_event);
   input_event.set_data(event->input_text());
-  // We do not handle composition sessions currently, so isComposing should
-  // always be false.
-  input_event.set_is_composing(false);
+  input_event.set_is_composing(event->is_composing());
   input_event_callback_.Run(type, input_event);
 }
 #endif  // SB_HAS(ON_SCREEN_KEYBOARD)
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc
index 98e346a..bd974be 100644
--- a/src/cobalt/layout/box.cc
+++ b/src/cobalt/layout/box.cc
@@ -95,6 +95,12 @@
 
 Box::~Box() { layout_stat_tracker_->OnBoxDestroyed(); }
 
+bool Box::IsOverflowHidden() const {
+  return computed_style()->overflow() == cssom::KeywordValue::GetAuto() ||
+         computed_style()->overflow() == cssom::KeywordValue::GetHidden() ||
+         computed_style()->overflow() == cssom::KeywordValue::GetScroll();
+}
+
 bool Box::IsPositioned() const {
   return computed_style()->position() != cssom::KeywordValue::GetStatic();
 }
@@ -602,8 +608,7 @@
                               &border_node_builder, &animate_node_builder);
   }
 
-  const bool overflow_hidden =
-      computed_style()->overflow() == cssom::KeywordValue::GetHidden();
+  const bool overflow_hidden = IsOverflowHidden();
 
   bool overflow_hidden_needs_to_be_applied = overflow_hidden;
 
@@ -1614,7 +1619,7 @@
     const scoped_refptr<render_tree::Node>& content_node,
     AnimateNode::Builder* /* animate_node_builder */,
     const math::Vector2dF& border_node_offset) {
-  DCHECK_EQ(computed_style()->overflow(), cssom::KeywordValue::GetHidden());
+  DCHECK(IsOverflowHidden());
 
   // The "overflow" property specifies whether a box is clipped to its padding
   // edge.  Use a render_tree viewport filter to implement it.
diff --git a/src/cobalt/layout/box.h b/src/cobalt/layout/box.h
index 38c8db7..c0869ec 100644
--- a/src/cobalt/layout/box.h
+++ b/src/cobalt/layout/box.h
@@ -191,6 +191,11 @@
   // Do not confuse with the formatting context that the element may establish.
   virtual Level GetLevel() const = 0;
 
+  // Returns true if "overflow" should be treated as hidden. This is true for
+  // overflow "auto", "hidden", and "scroll".
+  //   https://www.w3.org/TR/CSS21/visufx.html#overflow
+  bool IsOverflowHidden() const;
+
   // Returns true if the box is positioned (e.g. position is non-static or
   // transform is not None).  Intuitively, this is true if the element does
   // not follow standard layout flow rules for determining its position.
diff --git a/src/cobalt/layout/box_generator.cc b/src/cobalt/layout/box_generator.cc
index 090990f..d7c9c10 100644
--- a/src/cobalt/layout/box_generator.cc
+++ b/src/cobalt/layout/box_generator.cc
@@ -288,6 +288,7 @@
     case cssom::KeywordValue::kReverse:
     case cssom::KeywordValue::kRight:
     case cssom::KeywordValue::kSansSerif:
+    case cssom::KeywordValue::kScroll:
     case cssom::KeywordValue::kSerif:
     case cssom::KeywordValue::kSolid:
     case cssom::KeywordValue::kStart:
@@ -601,6 +602,7 @@
     case cssom::KeywordValue::kReverse:
     case cssom::KeywordValue::kRight:
     case cssom::KeywordValue::kSansSerif:
+    case cssom::KeywordValue::kScroll:
     case cssom::KeywordValue::kSerif:
     case cssom::KeywordValue::kSolid:
     case cssom::KeywordValue::kStart:
@@ -750,6 +752,7 @@
       case cssom::KeywordValue::kReverse:
       case cssom::KeywordValue::kRight:
       case cssom::KeywordValue::kSansSerif:
+      case cssom::KeywordValue::kScroll:
       case cssom::KeywordValue::kSerif:
       case cssom::KeywordValue::kSolid:
       case cssom::KeywordValue::kStart:
diff --git a/src/cobalt/layout/container_box.cc b/src/cobalt/layout/container_box.cc
index c70c290..ec9eb39 100644
--- a/src/cobalt/layout/container_box.cc
+++ b/src/cobalt/layout/container_box.cc
@@ -152,8 +152,7 @@
   //   2. Stacking context children contained within this overflow hidden
   //      container are potentially moving to the split sibling overflow hidden
   //      container.
-  if (HasStackingContextChildren() ||
-      computed_style()->overflow() == cssom::KeywordValue::GetHidden()) {
+  if (HasStackingContextChildren() || IsOverflowHidden()) {
     // Walk up the tree until the nearest stacking context is found. If this box
     // is a stacking context, then it will be used.
     ContainerBox* nearest_stacking_context = this;
@@ -574,8 +573,7 @@
   OverflowHiddenInfo& overflow_hidden_info = overflow_hidden_stack_.back();
 
   ContainerBox* containing_block = overflow_hidden_info.containing_block;
-  DCHECK_EQ(containing_block->computed_style()->overflow(),
-            cssom::KeywordValue::GetHidden());
+  DCHECK(containing_block->IsOverflowHidden());
 
   // Determine the offset from the child container to this containing block's
   // border box.
@@ -762,8 +760,7 @@
 
     bool has_absolute_position =
         computed_style()->position() == cssom::KeywordValue::GetAbsolute();
-    bool has_overflow_hidden =
-        computed_style()->overflow() == cssom::KeywordValue::GetHidden();
+    bool has_overflow_hidden = IsOverflowHidden();
 
     stacking_context_container_box_stack->push_back(
         StackingContextContainerBoxInfo(
diff --git a/src/cobalt/layout/used_style.cc b/src/cobalt/layout/used_style.cc
index 3061b03..f83f789 100644
--- a/src/cobalt/layout/used_style.cc
+++ b/src/cobalt/layout/used_style.cc
@@ -282,6 +282,7 @@
     case cssom::KeywordValue::kReverse:
     case cssom::KeywordValue::kRight:
     case cssom::KeywordValue::kSansSerif:
+    case cssom::KeywordValue::kScroll:
     case cssom::KeywordValue::kSerif:
     case cssom::KeywordValue::kSolid:
     case cssom::KeywordValue::kStart:
@@ -372,6 +373,7 @@
     case cssom::KeywordValue::kRepeat:
     case cssom::KeywordValue::kReverse:
     case cssom::KeywordValue::kRight:
+    case cssom::KeywordValue::kScroll:
     case cssom::KeywordValue::kSolid:
     case cssom::KeywordValue::kStart:
     case cssom::KeywordValue::kStatic:
@@ -1182,6 +1184,7 @@
     case cssom::KeywordValue::kReverse:
     case cssom::KeywordValue::kRight:
     case cssom::KeywordValue::kSansSerif:
+    case cssom::KeywordValue::kScroll:
     case cssom::KeywordValue::kSerif:
     case cssom::KeywordValue::kSolid:
     case cssom::KeywordValue::kStart:
@@ -1367,6 +1370,7 @@
       case cssom::KeywordValue::kReverse:
       case cssom::KeywordValue::kRight:
       case cssom::KeywordValue::kSansSerif:
+      case cssom::KeywordValue::kScroll:
       case cssom::KeywordValue::kSerif:
       case cssom::KeywordValue::kSolid:
       case cssom::KeywordValue::kStart:
@@ -1439,6 +1443,7 @@
       case cssom::KeywordValue::kReverse:
       case cssom::KeywordValue::kRight:
       case cssom::KeywordValue::kSansSerif:
+      case cssom::KeywordValue::kScroll:
       case cssom::KeywordValue::kSerif:
       case cssom::KeywordValue::kSolid:
       case cssom::KeywordValue::kStart:
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-div-with-position-absolute-children-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-div-with-position-absolute-children-expected.png
new file mode 100644
index 0000000..2297154
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-div-with-position-absolute-children-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-div-with-position-absolute-children.html b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-div-with-position-absolute-children.html
new file mode 100644
index 0000000..f63bda4
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-div-with-position-absolute-children.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<!--
+ | Overflow auto div with position absolute children.
+ | Only partial of text in the first 'red' class displayed and a green rectangle
+ | box is also displayed if this test passed.
+ | Results should be similar to overflow hidden.
+ |   https://www.w3.org/TR/CSS21/visufx.html
+ -->
+<head>
+  <style>
+    body {
+      font-family: Roboto;
+      font-size: 16px;
+    }
+
+    .red {
+      background-color: #FF0000;
+    }
+
+    .overflow {
+      width: 200px;
+      height: 20px;
+      overflow: auto;
+    }
+
+    .positioned {
+      position: absolute;
+      left: 150px;
+      top: 100px;
+      width: 100px;
+      height: 100px;
+      background-color: #00CC00;
+    }
+  </style>
+</head>
+<body>
+  <div class="overflow">
+    <div class="red"> Demonstrate overflow auto property value.</div>
+    <div class='red'> This line is invisible. </div>
+    <div class='positioned'></div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-expected.png
new file mode 100644
index 0000000..d99bc8b
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-from-non-positioned-containing-block-should-affect-relative-positioned-child-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-from-non-positioned-containing-block-should-affect-relative-positioned-child-expected.png
new file mode 100644
index 0000000..472eacb
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-from-non-positioned-containing-block-should-affect-relative-positioned-child-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-from-non-positioned-containing-block-should-affect-relative-positioned-child.html b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-from-non-positioned-containing-block-should-affect-relative-positioned-child.html
new file mode 100644
index 0000000..ea4d7ec
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-from-non-positioned-containing-block-should-affect-relative-positioned-child.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<!--
+ | "overflow" property affects the clipping of all of the element's content
+ | except any descendant elements whose containing block is the viewport or
+ | an ancestor of the element.
+ | Results should be similar to overflow hidden.
+ |   https://www.w3.org/TR/CSS21/visufx.html#overflow
+ -->
+<html>
+<head>
+  <style>
+    .outer {
+      background-color: rgb(63, 81, 181);
+      overflow: auto;
+      width: 100px;
+      height: 100px;
+      margin-bottom: 100px;
+    }
+    .inner {
+      position: relative;
+      background-color: #2196f3;
+      width: 100px;
+      height: 100px;
+      left: 50px;
+      top: 50px;
+    }
+  </style>
+</head>
+<body>
+  <div class="outer">
+    <div class="inner"></div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-transform-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-transform-expected.png
new file mode 100644
index 0000000..9f3c1de
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-transform-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-transform.html b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-transform.html
new file mode 100644
index 0000000..c255c51
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto-transform.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<!--
+ | Apply transform to overflow auto.
+ | Results should be similar to overflow hidden.
+ |   https://www.w3.org/TR/CSS21/visufx.html
+ -->
+<head>
+  <style>
+    body {
+      font-family: Roboto;
+      font-size: 16px;
+    }
+
+    .transform {
+      width: 550px;
+      height: 20px;
+      overflow: auto;
+      background-color: rgb(0, 128, 128);
+      transform: translateX(50px) translateY(50px) rotate(7deg);
+    }
+  </style>
+</head>
+<body>
+  <div class="transform">
+    <div> Apply transform to overflow auto. </div>
+    <div> This line is invisible. </div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto.html b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto.html
new file mode 100644
index 0000000..cc2a3f2
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-auto.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<!--
+ | Demonstrate overflow auto.
+ | Results should be similar to overflow hidden.
+ |   https://www.w3.org/TR/CSS21/visufx.html
+ -->
+<head>
+  <style>
+    body {
+      font-family: Roboto;
+      font-size: 16px;
+    }
+
+    .red {
+      background-color: #FF0000;
+    }
+
+    .overflow {
+      width: 200px;
+      height: 20px;
+      overflow: auto;
+    }
+  </style>
+</head>
+<body>
+  <div class="overflow">
+    <div class="red"> Demonstrate overflow auto property value.</div>
+    <div class='red'> This line is invisible. </div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-expected.png
new file mode 100644
index 0000000..38e0f50
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-should-affect-descendants-with-z-index-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-should-affect-descendants-with-z-index-expected.png
new file mode 100644
index 0000000..8db4457
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-should-affect-descendants-with-z-index-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-should-affect-descendants-with-z-index.html b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-should-affect-descendants-with-z-index.html
new file mode 100644
index 0000000..2632d36
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-should-affect-descendants-with-z-index.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<!--
+ | "overflow" property affects the clipping of all of the element's content
+ | except any descendant elements whose containing block is the viewport or
+ | an ancestor of the element.
+ | Results should be similar to overflow hidden.
+ |   https://www.w3.org/TR/CSS21/visufx.html#overflow
+ -->
+<html>
+<head>
+  <style>
+    .outer-common {
+      background-color: rgba(63, 81, 181, 0.5);
+      width: 100px;
+      height: 100px;
+    }
+    .outer-1 {
+      overflow: scroll;
+      margin-bottom: 30px;
+    }
+    .outer-2 {
+      position: absolute;
+      overflow: scroll;
+    }
+    .outer-3 {
+      position: absolute;
+      left: 140px;
+      top: 0px;
+    }
+    .outer-4 {
+      position: absolute;
+      overflow: scroll;
+      left: 140px;
+    }
+    .inner-common {
+      position: absolute;
+      z-index: -1;
+      width: 50px;
+      height: 50px;
+    }
+    .inner-1 {
+      background-color: #7BAAF7;
+      left: 25px;
+      top: 25px;
+    }
+    .inner-2-container {
+      position: absolute;
+      overflow: scroll;
+      width: 25px;
+      height: 50px;
+      left: 25px;
+      top: 75px;
+    }
+    .inner-2 {
+      background-color: #3367D6;
+    }
+    .inner-3 {
+      background-color: #57BB8A;
+      left: 75px;
+      top: 25px;
+    }
+    .inner-4 {
+      background-color: #0B8043;
+      left: 75px;
+      top: 75px;
+    }
+  </style>
+</head>
+<body>
+  <div class="outer-common outer-1">
+    <div class="inner-common inner-1"></div>
+    <div class="inner-2-container">
+      <div class="inner-common inner-2"></div>
+    </div>
+    <div class="inner-common inner-3"></div>
+    <div class="inner-common inner-4"></div>
+  </div>
+  <div class="outer-common outer-2">
+    <div class="inner-common inner-1"></div>
+    <div class="inner-2-container">
+      <div class="inner-common inner-2"></div>
+    </div>
+    <div class="inner-common inner-3"></div>
+    <div class="inner-common inner-4"></div>
+  </div>
+  <div class="outer-common outer-3">
+    <div class="inner-common inner-1"></div>
+    <div class="inner-2-container">
+      <div class="inner-common inner-2"></div>
+    </div>
+    <div class="inner-common inner-3"></div>
+    <div class="inner-common inner-4"></div>
+  </div>
+  <div class="outer-common outer-4">
+    <div class="inner-common inner-1"></div>
+    <div class="inner-2-container">
+      <div class="inner-common inner-2"></div>
+    </div>
+    <div class="inner-common inner-3"></div>
+    <div class="inner-common inner-4"></div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-transform-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-transform-expected.png
new file mode 100644
index 0000000..9f3c1de
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-transform-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-transform.html b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-transform.html
new file mode 100644
index 0000000..47f195d
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll-transform.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<!--
+ | Apply transform to overflow scroll.
+ | Results should be similar to overflow hidden.
+ |   https://www.w3.org/TR/CSS21/visufx.html
+ -->
+<head>
+  <style>
+    body {
+      font-family: Roboto;
+      font-size: 16px;
+    }
+
+    .transform {
+      width: 550px;
+      height: 20px;
+      overflow: scroll;
+      background-color: rgb(0, 128, 128);
+      transform: translateX(50px) translateY(50px) rotate(7deg);
+    }
+  </style>
+</head>
+<body>
+  <div class="transform">
+    <div> Apply transform to overflow scroll. </div>
+    <div> This line is invisible. </div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll.html b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll.html
new file mode 100644
index 0000000..f990293
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-1-overflow-scroll.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<!--
+ | Demonstrate overflow scroll.
+ | Results should be similar to overflow hidden.
+ |   https://www.w3.org/TR/CSS21/visufx.html
+ -->
+<head>
+  <style>
+    body {
+      font-family: Roboto;
+      font-size: 16px;
+    }
+
+    .red {
+      background-color: #FF0000;
+    }
+
+    .overflow {
+      width: 200px;
+      height: 20px;
+      overflow: scroll;
+    }
+  </style>
+</head>
+<body>
+  <div class="overflow">
+    <div class="red"> Demonstrate overflow scroll property value.</div>
+    <div class='red'> This line is invisible. </div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-absolutely-positioned-children-of-overflow-scroll-expected.png b/src/cobalt/layout_tests/testdata/css-2-1/11-1-absolutely-positioned-children-of-overflow-scroll-expected.png
new file mode 100644
index 0000000..c341a50
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-absolutely-positioned-children-of-overflow-scroll-expected.png
Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/11-1-absolutely-positioned-children-of-overflow-scroll.html b/src/cobalt/layout_tests/testdata/css-2-1/11-1-absolutely-positioned-children-of-overflow-scroll.html
new file mode 100644
index 0000000..2bc9e11
--- /dev/null
+++ b/src/cobalt/layout_tests/testdata/css-2-1/11-1-absolutely-positioned-children-of-overflow-scroll.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<!--
+ | Absolutely positioned children of overflow: scroll.
+ | Results should be similar to overflow hidden.
+ |   https://www.w3.org/TR/CSS21/visufx.html
+ -->
+<head>
+  <style>
+    body {
+      font-family: Roboto;
+      font-size: 16px;
+    }
+
+    .overflow {
+      overflow: scroll;
+      width: 8em;
+      height: 8em;
+    }
+
+    .positioned {
+      color: #FFFFFF;
+      background: #00CC00;
+      position: absolute;
+      top: 5em;
+      left: 5em;
+      width: 5em;
+      height: 5em;
+    }
+  </style>
+</head>
+<body>
+  <div class="overflow">
+    <div class="positioned">PASS</div>
+  </div>
+</body>
+</html>
diff --git a/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt b/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt
index 05cf6f7..b9c7340 100644
--- a/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt
+++ b/src/cobalt/layout_tests/testdata/css-2-1/layout_tests.txt
@@ -65,6 +65,10 @@
 10-8-margin-box-should-be-used-for-vertical-align-with-most-boxes
 10-8-vertical-align-top-and-bottom-affect-height-and-baseline
 11-1-1-overflow-from-non-positioned-containing-block-should-affect-relative-positioned-child
+11-1-1-overflow-auto
+11-1-1-overflow-auto-div-with-position-absolute-children
+11-1-1-overflow-auto-from-non-positioned-containing-block-should-affect-relative-positioned-child
+11-1-1-overflow-auto-transform
 11-1-1-overflow-hidden
 11-1-1-overflow-hidden-and-opacity
 11-1-1-overflow-hidden-and-position-absolute-div-with-position-absolute-children
@@ -75,12 +79,16 @@
 11-1-1-overflow-hidden-masks-padding-box-only
 11-1-1-overflow-hidden-transform
 11-1-1-overflow-hidden-translate
+11-1-1-overflow-scroll
+11-1-1-overflow-scroll-should-affect-descendants-with-z-index
+11-1-1-overflow-scroll-transform
 11-1-1-overflow-should-affect-descendants-with-z-index
 11-1-1-overflow-should-not-affect-descendants-contained-in-another-block
 11-1-1-overflow-visible
 11-1-absolutely-positioned-children-of-overflow-hidden
 11-1-absolutely-positioned-children-of-overflow-hidden-to-left
 11-1-absolutely-positioned-children-of-overflow-hidden-to-right
+11-1-absolutely-positioned-children-of-overflow-scroll
 11-2-visibility-hidden-renders-generated-box-invisible
 11-2-visibility-hidden-should-be-overridable
 11-2-visibility-hidden-still-affects-layout
diff --git a/src/cobalt/loader/image/image_decoder.cc b/src/cobalt/loader/image/image_decoder.cc
index af1c67a..43a0b8f 100644
--- a/src/cobalt/loader/image/image_decoder.cc
+++ b/src/cobalt/loader/image/image_decoder.cc
@@ -253,7 +253,7 @@
 }
 
 namespace {
-#if SB_HAS(GRAPHICS)
+#if SB_HAS(GRAPHICS) && !SB_IS(EVERGREEN)
 const char* GetMimeTypeFromImageType(ImageDecoder::ImageType image_type) {
   switch (image_type) {
     case ImageDecoder::kImageTypeJPEG:
@@ -312,7 +312,7 @@
   }
   return scoped_ptr<ImageDataDecoder>();
 }
-#endif  // SB_HAS(GRAPHICS)
+#endif  // SB_HAS(GRAPHICS) && !SB_IS(EVERGREEN)
 
 scoped_ptr<ImageDataDecoder> CreateImageDecoderFromImageType(
     ImageDecoder::ImageType image_type,
@@ -322,34 +322,8 @@
     return make_scoped_ptr<ImageDataDecoder>(
         new StubImageDecoder(resource_provider));
   } else if (image_type == ImageDecoder::kImageTypeJPEG) {
-#if SB_HAS(GLES2) && defined(COBALT_FORCE_DIRECT_GLES_RASTERIZER)
-    // Many image formats can produce native output in multi plane images in YUV
-    // 420.  Allow these images to be decoded into multi plane image not only
-    // reduces the space to store the decoded image to 37.5%, but also improves
-    // decoding performance by not converting the output from YUV to RGBA.
-    bool allow_image_decoding_to_multi_plane = true;
-#else   // SB_HAS(GLES2) && defined(COBALT_FORCE_DIRECT_GLES_RASTERIZER)
-    // Decoding to single plane by default because blitter platforms usually
-    // don't have the ability to perform hardware accelerated YUV-formatted
-    // image blitting.
-    // This also applies to skia based "hardware" rasterizers as the rendering
-    // of multi plane images in such cases are not optimized, but this may be
-    // improved in future.
-    bool allow_image_decoding_to_multi_plane = false;
-#endif  // SB_HAS(GLES2) && defined(COBALT_FORCE_DIRECT_GLES_RASTERIZER)
-    auto command_line = CommandLine::ForCurrentProcess();
-    if (command_line->HasSwitch(switches::kAllowImageDecodingToMultiPlane)) {
-      std::string value = command_line->GetSwitchValueASCII(
-          switches::kAllowImageDecodingToMultiPlane);
-      if (value == "true") {
-        allow_image_decoding_to_multi_plane = true;
-      } else {
-        DCHECK_EQ(value, "false");
-        allow_image_decoding_to_multi_plane = false;
-      }
-    }
     return make_scoped_ptr<ImageDataDecoder>(new JPEGImageDecoder(
-        resource_provider, allow_image_decoding_to_multi_plane));
+        resource_provider, ImageDecoder::AllowDecodingToMultiPlane()));
   } else if (image_type == ImageDecoder::kImageTypePNG) {
     return make_scoped_ptr<ImageDataDecoder>(
         new PNGImageDecoder(resource_provider));
@@ -384,10 +358,11 @@
     image_type_ = DetermineImageType(signature_cache_.data);
   }
 
-#if SB_HAS(GRAPHICS)
+// TODO: Remove the EVERGREEN check once the EGL wiring is ready.
+#if SB_HAS(GRAPHICS) && !SB_IS(EVERGREEN)
   decoder_ =
       MaybeCreateStarboardDecoder(mime_type_, image_type_, resource_provider_);
-#endif  // SB_HAS(GRAPHICS)
+#endif  // SB_HAS(GRAPHICS) && !SB_IS(EVERGREEN)
 
   if (!decoder_) {
     decoder_ = CreateImageDecoderFromImageType(image_type_, resource_provider_);
@@ -404,6 +379,41 @@
 // static
 void ImageDecoder::UseStubImageDecoder() { s_use_stub_image_decoder = true; }
 
+// static
+bool ImageDecoder::AllowDecodingToMultiPlane() {
+#if SB_HAS(GLES2) && defined(COBALT_FORCE_DIRECT_GLES_RASTERIZER)
+  // Many image formats can produce native output in multi plane images in YUV
+  // 420.  Allow these images to be decoded into multi plane image not only
+  // reduces the space to store the decoded image to 37.5%, but also improves
+  // decoding performance by not converting the output from YUV to RGBA.
+  bool allow_image_decoding_to_multi_plane = true;
+#else   // SB_HAS(GLES2) && defined(COBALT_FORCE_DIRECT_GLES_RASTERIZER)
+  // Decoding to single plane by default because blitter platforms usually
+  // don't have the ability to perform hardware accelerated YUV-formatted
+  // image blitting.
+  // This also applies to skia based "hardware" rasterizers as the rendering
+  // of multi plane images in such cases are not optimized, but this may be
+  // improved in future.
+  bool allow_image_decoding_to_multi_plane = false;
+#endif  // SB_HAS(GLES2) && defined(COBALT_FORCE_DIRECT_GLES_RASTERIZER)
+
+#if !defined(COBALT_BUILD_TYPE_GOLD)
+  auto command_line = CommandLine::ForCurrentProcess();
+  if (command_line->HasSwitch(switches::kAllowImageDecodingToMultiPlane)) {
+    std::string value = command_line->GetSwitchValueASCII(
+        switches::kAllowImageDecodingToMultiPlane);
+    if (value == "true") {
+      allow_image_decoding_to_multi_plane = true;
+    } else {
+      DCHECK_EQ(value, "false");
+      allow_image_decoding_to_multi_plane = false;
+    }
+  }
+#endif  // !defined(COBALT_BUILD_TYPE_GOLD)
+
+  return allow_image_decoding_to_multi_plane;
+}
+
 }  // namespace image
 }  // namespace loader
 }  // namespace cobalt
diff --git a/src/cobalt/loader/image/image_decoder.h b/src/cobalt/loader/image/image_decoder.h
index ec4d155..74d457e 100644
--- a/src/cobalt/loader/image/image_decoder.h
+++ b/src/cobalt/loader/image/image_decoder.h
@@ -74,6 +74,9 @@
   // Call this function to use the StubImageDecoder which produces a small image
   // without decoding.
   static void UseStubImageDecoder();
+  // Returns true if the platform allows decoding and storing decoded images
+  // into multi-plane.
+  static bool AllowDecodingToMultiPlane();
 
  private:
   enum State {
diff --git a/src/cobalt/loader/loader.cc b/src/cobalt/loader/loader.cc
index 2e4b8ed..1846d10 100644
--- a/src/cobalt/loader/loader.cc
+++ b/src/cobalt/loader/loader.cc
@@ -86,6 +86,9 @@
   DCHECK(!decoder_creator_.is_null());
   DCHECK(!on_load_complete_.is_null());
 
+  decoder_ = decoder_creator_.Run(
+      base::Bind(&Loader::LoadComplete, base::Unretained(this)));
+
   if (!is_suspended_) {
     Start();
   }
@@ -146,8 +149,6 @@
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(!is_suspended_);
 
-  decoder_ = decoder_creator_.Run(
-      base::Bind(&Loader::LoadComplete, base::Unretained(this)));
   fetcher_to_decoder_adaptor_.reset(new FetcherToDecoderAdapter(
       decoder_.get(),
       base::Bind(&Loader::LoadComplete, base::Unretained(this))));
diff --git a/src/cobalt/media/base/interleaved_sinc_resampler.cc b/src/cobalt/media/base/interleaved_sinc_resampler.cc
index 6741704..730ff54 100644
--- a/src/cobalt/media/base/interleaved_sinc_resampler.cc
+++ b/src/cobalt/media/base/interleaved_sinc_resampler.cc
@@ -1,6 +1,20 @@
 // Copyright (c) 2015 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 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.
 //
 // Input buffer layout, dividing the total buffer into regions (r0_ - r5_):
 //
@@ -31,13 +45,15 @@
 // Note: we're glossing over how the sub-sample handling works with
 // |virtual_source_idx_|, etc.
 
-#include "media/base/interleaved_sinc_resampler.h"
+#include "cobalt/media/base/interleaved_sinc_resampler.h"
 
 #include <algorithm>
 #include <cmath>
 
 #include "base/logging.h"
+#include "starboard/memory.h"
 
+namespace cobalt {
 namespace media {
 
 namespace {
@@ -65,6 +81,30 @@
 
 }  // namespace
 
+class InterleavedSincResampler::Buffer
+    : public base::RefCountedThreadSafe<Buffer> {
+ public:
+  Buffer() : data_(NULL), frames_(0) {}
+  Buffer(const float* data, int frames, int channel_count) : frames_(frames) {
+    data_.reset(new float[frames * channel_count]);
+    SbMemoryCopy(data_.get(), data, frames * channel_count * sizeof(float));
+  }
+  Buffer(scoped_array<float> data, int frames)
+      : data_(data.Pass()), frames_(frames) {}
+
+  const float* GetData() const { return data_.get(); }
+
+  int GetNumFrames() const { return frames_; }
+
+  bool IsEndOfStream() const { return GetData() == NULL; }
+
+ private:
+  scoped_array<float> data_;
+  int frames_;
+
+  DISALLOW_COPY_AND_ASSIGN(Buffer);
+};
+
 InterleavedSincResampler::InterleavedSincResampler(double io_sample_rate_ratio,
                                                    int channel_count)
     : io_sample_rate_ratio_(io_sample_rate_ratio),
@@ -108,13 +148,15 @@
   DCHECK_EQ(r5_ + kBlockSize * channel_count_,
             r1_ + kBufferSize * channel_count_);
 
-  memset(kernel_storage_.get(), 0,
-         sizeof(*kernel_storage_.get()) * kKernelStorageSize);
-  memset(input_buffer_.get(), 0, frame_size_in_bytes_ * kBufferSize);
+  SbMemorySet(kernel_storage_.get(), 0,
+              sizeof(*kernel_storage_.get()) * kKernelStorageSize);
+  SbMemorySet(input_buffer_.get(), 0, frame_size_in_bytes_ * kBufferSize);
 
   InitializeKernel();
 }
 
+InterleavedSincResampler::~InterleavedSincResampler() {}
+
 void InterleavedSincResampler::InitializeKernel() {
   // Blackman window parameters.
   static const double kAlpha = 0.16;
@@ -131,8 +173,8 @@
   // windowing it the transition from pass to stop does not happen right away.
   // So we should adjust the low pass filter cutoff slightly downward to avoid
   // some aliasing at the very high-end.
-  // TODO(crogers): this value is empirical and to be more exact should vary
-  // depending on kKernelSize.
+  // TODO: this value is empirical and to be more exact should vary depending
+  // on kKernelSize.
   sinc_scale_factor *= 0.9;
 
   // Generates a set of windowed sinc() kernels.
@@ -158,28 +200,44 @@
   }
 }
 
-void InterleavedSincResampler::QueueBuffer(
-    const scoped_refptr<::media::Buffer>& buffer) {
-  DCHECK(buffer);
+void InterleavedSincResampler::QueueBuffer(const float* data, int frames) {
   DCHECK(CanQueueBuffer());
 
   if (!pending_buffers_.empty() && pending_buffers_.back()->IsEndOfStream()) {
-    DCHECK(buffer->IsEndOfStream());
+    DCHECK(!data);
     return;
   }
 
-  if (!buffer->IsEndOfStream()) {
-    frames_queued_ += buffer->GetDataSize() / frame_size_in_bytes_;
+  if (!data) {
+    pending_buffers_.push(new Buffer);
+    return;
+  } else {
+    frames_queued_ += frames;
+    pending_buffers_.push(new Buffer(data, frames, channel_count_));
   }
-
-  pending_buffers_.push(buffer);
 }
 
-bool InterleavedSincResampler::Resample(float* destination, int frames) {
-  if (!HasEnoughData(frames)) {
-    return false;
+void InterleavedSincResampler::QueueBuffer(scoped_array<float> data,
+                                           int frames) {
+  DCHECK(CanQueueBuffer());
+
+  if (!pending_buffers_.empty() && pending_buffers_.back()->IsEndOfStream()) {
+    DCHECK(!data.get());
+    return;
   }
 
+  if (!data.get()) {
+    pending_buffers_.push(new Buffer);
+    return;
+  } else {
+    frames_queued_ += frames;
+    pending_buffers_.push(new Buffer(data.Pass(), frames));
+  }
+}
+
+void InterleavedSincResampler::Resample(float* destination, int frames) {
+  DCHECK(HasEnoughData(frames));
+
   int remaining_frames = frames;
 
   // Step (1) -- Prime the input buffer at the start of the input stream.
@@ -219,7 +277,7 @@
 
       if (!--remaining_frames) {
         frames_resampled_ += frames;
-        return true;
+        return;
       }
     }
 
@@ -228,8 +286,8 @@
 
     // Step (3) Copy r3_ to r1_ and r4_ to r2_.
     // This wraps the last input frames back to the start of the buffer.
-    memcpy(r1_, r3_, frame_size_in_bytes_ * (kKernelSize / 2));
-    memcpy(r2_, r4_, frame_size_in_bytes_ * (kKernelSize / 2));
+    SbMemoryCopy(r1_, r3_, frame_size_in_bytes_ * (kKernelSize / 2));
+    SbMemoryCopy(r2_, r4_, frame_size_in_bytes_ * (kKernelSize / 2));
 
     // Step (4)
     // Refresh the buffer with more input.
@@ -237,13 +295,12 @@
   }
 
   NOTREACHED();
-  return false;
 }
 
 void InterleavedSincResampler::Flush() {
   virtual_source_idx_ = 0;
   buffer_primed_ = false;
-  memset(input_buffer_.get(), 0, frame_size_in_bytes_ * kBufferSize);
+  SbMemorySet(input_buffer_.get(), 0, frame_size_in_bytes_ * kBufferSize);
   while (!pending_buffers_.empty()) {
     pending_buffers_.pop();
   }
@@ -287,15 +344,15 @@
     scoped_refptr<Buffer> buffer = pending_buffers_.front();
     if (buffer->IsEndOfStream()) {
       // Zero fill the buffer after EOS has reached.
-      memset(destination, 0, frame_size_in_bytes_ * frames);
+      SbMemorySet(destination, 0, frame_size_in_bytes_ * frames);
       return;
     }
     // Copy the data over.
-    int frames_in_buffer = buffer->GetDataSize() / frame_size_in_bytes_;
+    int frames_in_buffer = buffer->GetNumFrames();
     int frames_to_copy = std::min(frames_in_buffer - offset_in_frames_, frames);
-    const uint8* source = buffer->GetData();
-    source += frame_size_in_bytes_ * offset_in_frames_;
-    memcpy(destination, source, frame_size_in_bytes_ * frames_to_copy);
+    const float* source = buffer->GetData();
+    source += offset_in_frames_ * channel_count_;
+    SbMemoryCopy(destination, source, frame_size_in_bytes_ * frames_to_copy);
     offset_in_frames_ += frames_to_copy;
     // Pop the first buffer if all its content has been read.
     if (offset_in_frames_ == frames_in_buffer) {
@@ -312,8 +369,7 @@
 }
 
 float InterleavedSincResampler::Convolve(const float* input_ptr,
-                                         const float* k1,
-                                         const float* k2,
+                                         const float* k1, const float* k2,
                                          double kernel_interpolation_factor) {
   float sum1 = 0;
   float sum2 = 0;
@@ -333,3 +389,4 @@
 }
 
 }  // namespace media
+}  // namespace cobalt
diff --git a/src/cobalt/media/base/interleaved_sinc_resampler.h b/src/cobalt/media/base/interleaved_sinc_resampler.h
index edfdada..a1eff8b 100644
--- a/src/cobalt/media/base/interleaved_sinc_resampler.h
+++ b/src/cobalt/media/base/interleaved_sinc_resampler.h
@@ -2,37 +2,53 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef MEDIA_BASE_INTERLEAVED_SINC_RESAMPLER_H_
-#define MEDIA_BASE_INTERLEAVED_SINC_RESAMPLER_H_
+// Modifications Copyright 2019 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COBALT_MEDIA_BASE_INTERLEAVED_SINC_RESAMPLER_H_
+#define COBALT_MEDIA_BASE_INTERLEAVED_SINC_RESAMPLER_H_
 
 #include <queue>
 
 #include "base/memory/aligned_memory.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_ptr.h"
-#include "media/base/buffers.h"
-#include "media/base/media_export.h"
+#include "cobalt/export.h"
 
+namespace cobalt {
 namespace media {
 
 // InterleavedSincResampler is a high-quality interleaved multi-channel sample
-//-rate converter operating on samples in float. It uses the same algorithm as
+// -rate converter operating on samples in float. It uses the same algorithm as
 // SincResampler. Unlike SincResampler, it works in push mode instead of pull
 // mode.
-class MEDIA_EXPORT InterleavedSincResampler {
+class COBALT_EXPORT InterleavedSincResampler {
  public:
   // |io_sample_rate_ratio| is the ratio of input / output sample rates.
   // |channel_count| is the number of channels in the interleaved audio stream.
   InterleavedSincResampler(double io_sample_rate_ratio, int channel_count);
+  ~InterleavedSincResampler();
 
   // Append a buffer to the queue. The samples in the buffer has to be floats.
-  void QueueBuffer(const scoped_refptr<Buffer>& buffer);
+  void QueueBuffer(const float* data, int frames);
+  void QueueBuffer(scoped_array<float> data, int frames);
 
   // Resample |frames| of data from enqueued buffers.  Return false if no sample
   // is read.  Return true if all requested samples have been written into
   // |destination|.  It will never do a partial read.  After the stream reaches
   // the end, the function will fill the rest of buffer with 0.
-  bool Resample(float* destination, int frames);
+  void Resample(float* destination, int frames);
 
   // Flush all buffered data and reset internal indices.
   void Flush();
@@ -43,14 +59,17 @@
   // Returning true when we start to return zero filled data because of EOS.
   bool ReachedEOS() const;
 
- private:
-  void InitializeKernel();
+  // Return true if the enqueued buffers have enough data for us to resample the
+  // requested number of frames.
   bool HasEnoughData(int frames_to_resample) const;
+
+ private:
+  class Buffer;
+
+  void InitializeKernel();
   void Read(float* destination, int frames);
 
-  float Convolve(const float* input_ptr,
-                 const float* k1,
-                 const float* k2,
+  float Convolve(const float* input_ptr, const float* k1, const float* k2,
                  double kernel_interpolation_factor);
 
   // The ratio of input / output sample rates.
@@ -78,7 +97,7 @@
   scoped_ptr_malloc<float, base::ScopedPtrAlignedFree> input_buffer_;
 
   // A queue of buffers to be resampled.
-  std::queue<scoped_refptr<Buffer> > pending_buffers_;
+  std::queue<scoped_refptr<Buffer>> pending_buffers_;
 
   // The current offset to read when reading from the first pending buffer.
   int offset_in_frames_;
@@ -100,5 +119,6 @@
 };
 
 }  // namespace media
+}  // namespace cobalt
 
-#endif  // MEDIA_BASE_INTERLEAVED_SINC_RESAMPLER_H_
+#endif  // COBALT_MEDIA_BASE_INTERLEAVED_SINC_RESAMPLER_H_
diff --git a/src/cobalt/media/base/starboard_player.cc b/src/cobalt/media/base/starboard_player.cc
index 817e47c..7d0d7d9 100644
--- a/src/cobalt/media/base/starboard_player.cc
+++ b/src/cobalt/media/base/starboard_player.cc
@@ -288,43 +288,7 @@
   DCHECK(video_frames_decoded || video_frames_dropped || media_time);
 
   base::AutoLock auto_lock(lock_);
-  if (state_ == kSuspended) {
-    if (video_frames_decoded) {
-      *video_frames_decoded = cached_video_frames_decoded_;
-    }
-    if (video_frames_dropped) {
-      *video_frames_dropped = cached_video_frames_dropped_;
-    }
-    if (media_time) {
-      *media_time = preroll_timestamp_;
-    }
-    return;
-  }
-
-  DCHECK(SbPlayerIsValid(player_));
-
-#if SB_API_VERSION < 10
-  SbPlayerInfo info;
-  SbPlayerGetInfo(player_, &info);
-  if (media_time) {
-    *media_time = base::TimeDelta::FromMicroseconds(
-        SB_MEDIA_TIME_TO_SB_TIME(info.current_media_pts));
-  }
-#else   // SB_API_VERSION < 10
-  SbPlayerInfo2 info;
-  SbPlayerGetInfo2(player_, &info);
-
-  if (media_time) {
-    *media_time =
-        base::TimeDelta::FromMicroseconds(info.current_media_timestamp);
-  }
-#endif  // SB_API_VERSION < 10
-  if (video_frames_decoded) {
-    *video_frames_decoded = info.total_video_frames;
-  }
-  if (video_frames_dropped) {
-    *video_frames_dropped = info.dropped_video_frames;
-  }
+  GetInfo_Locked(video_frames_decoded, video_frames_dropped, media_time);
 }
 
 #if SB_HAS(PLAYER_WITH_URL)
@@ -416,9 +380,8 @@
   SbPlayerSetPlaybackRate(player_, 0.0);
 
   base::AutoLock auto_lock(lock_);
-
-  this->GetInfo(&cached_video_frames_decoded_, &cached_video_frames_dropped_,
-                &preroll_timestamp_);
+  GetInfo_Locked(&cached_video_frames_decoded_, &cached_video_frames_dropped_,
+                 &preroll_timestamp_);
 
   state_ = kSuspended;
 
@@ -656,6 +619,49 @@
   return output_mode_;
 }
 
+void StarboardPlayer::GetInfo_Locked(uint32* video_frames_decoded,
+                                     uint32* video_frames_dropped,
+                                     base::TimeDelta* media_time) {
+  lock_.AssertAcquired();
+  if (state_ == kSuspended) {
+    if (video_frames_decoded) {
+      *video_frames_decoded = cached_video_frames_decoded_;
+    }
+    if (video_frames_dropped) {
+      *video_frames_dropped = cached_video_frames_dropped_;
+    }
+    if (media_time) {
+      *media_time = preroll_timestamp_;
+    }
+    return;
+  }
+
+  DCHECK(SbPlayerIsValid(player_));
+
+#if SB_API_VERSION < 10
+  SbPlayerInfo info;
+  SbPlayerGetInfo(player_, &info);
+  if (media_time) {
+    *media_time = base::TimeDelta::FromMicroseconds(
+        SB_MEDIA_TIME_TO_SB_TIME(info.current_media_pts));
+  }
+#else   // SB_API_VERSION < 10
+  SbPlayerInfo2 info;
+  SbPlayerGetInfo2(player_, &info);
+
+  if (media_time) {
+    *media_time =
+        base::TimeDelta::FromMicroseconds(info.current_media_timestamp);
+  }
+#endif  // SB_API_VERSION < 10
+  if (video_frames_decoded) {
+    *video_frames_decoded = info.total_video_frames;
+  }
+  if (video_frames_dropped) {
+    *video_frames_dropped = info.dropped_video_frames;
+  }
+}
+
 void StarboardPlayer::UpdateBounds_Locked() {
   lock_.AssertAcquired();
   DCHECK(SbPlayerIsValid(player_));
diff --git a/src/cobalt/media/base/starboard_player.h b/src/cobalt/media/base/starboard_player.h
index 8607b1f..f66f91a 100644
--- a/src/cobalt/media/base/starboard_player.h
+++ b/src/cobalt/media/base/starboard_player.h
@@ -169,6 +169,9 @@
   void WriteBufferInternal(DemuxerStream::Type type,
                            const scoped_refptr<DecoderBuffer>& buffer);
 
+  void GetInfo_Locked(uint32* video_frames_decoded,
+                      uint32* video_frames_dropped,
+                      base::TimeDelta* media_time);
   void UpdateBounds_Locked();
 
   void ClearDecoderBufferCache();
diff --git a/src/cobalt/media/base/starboard_utils.cc b/src/cobalt/media/base/starboard_utils.cc
index b9e7a01..6e52345 100644
--- a/src/cobalt/media/base/starboard_utils.cc
+++ b/src/cobalt/media/base/starboard_utils.cc
@@ -27,36 +27,56 @@
 namespace media {
 
 SbMediaAudioCodec MediaAudioCodecToSbMediaAudioCodec(AudioCodec codec) {
-  if (codec == kCodecAAC) {
-    return kSbMediaAudioCodecAac;
+  switch (codec) {
+    case kCodecAAC:
+      return kSbMediaAudioCodecAac;
 #if SB_HAS(AC3_AUDIO)
-  } else if (codec == kCodecAC3) {
-    return kSbMediaAudioCodecAc3;
+    case kCodecAC3:
+      return kSbMediaAudioCodecAc3;
+    case kCodecEAC3:
+      return kSbMediaAudioCodecEac3;
 #endif  // SB_HAS(AC3_AUDIO)
-  } else if (codec == kCodecVorbis) {
-    return kSbMediaAudioCodecVorbis;
-  } else if (codec == kCodecOpus) {
-    return kSbMediaAudioCodecOpus;
+    case kCodecVorbis:
+      return kSbMediaAudioCodecVorbis;
+    case kCodecOpus:
+      return kSbMediaAudioCodecOpus;
+    default:
+      // Cobalt only supports a subset of audio codecs defined by Chromium.
+      DLOG(ERROR) << "Unsupported audio codec "
+                  << cobalt::media::GetCodecName(codec);
+      return kSbMediaAudioCodecNone;
   }
-  DLOG(ERROR) << "Unsupported audio codec " << codec;
+  NOTREACHED();
   return kSbMediaAudioCodecNone;
 }
 
 SbMediaVideoCodec MediaVideoCodecToSbMediaVideoCodec(VideoCodec codec) {
-  if (codec == kCodecH264) {
-    return kSbMediaVideoCodecH264;
-  } else if (codec == kCodecVC1) {
-    return kSbMediaVideoCodecVc1;
-  } else if (codec == kCodecMPEG2) {
-    return kSbMediaVideoCodecMpeg2;
-  } else if (codec == kCodecTheora) {
-    return kSbMediaVideoCodecTheora;
-  } else if (codec == kCodecVP8) {
-    return kSbMediaVideoCodecVp8;
-  } else if (codec == kCodecVP9) {
-    return kSbMediaVideoCodecVp9;
+  switch (codec) {
+    case kCodecH264:
+      return kSbMediaVideoCodecH264;
+    case kCodecVC1:
+      return kSbMediaVideoCodecVc1;
+    case kCodecMPEG2:
+      return kSbMediaVideoCodecMpeg2;
+    case kCodecTheora:
+      return kSbMediaVideoCodecTheora;
+    case kCodecVP8:
+      return kSbMediaVideoCodecVp8;
+    case kCodecVP9:
+      return kSbMediaVideoCodecVp9;
+    case kCodecAV1:
+#if SB_API_VERSION >= SB_HAS_AV1_VERSION
+      return kSbMediaVideoCodecAv1;
+#else  // SB_API_VERSION >= SB_HAS_AV1_VERSION
+      return kSbMediaVideoCodecVp10;
+#endif  // SB_API_VERSION >= SB_HAS_AV1_VERSION
+    default:
+      // Cobalt only supports a subset of video codecs defined by Chromium.
+      DLOG(ERROR) << "Unsupported video codec "
+                  << cobalt::media::GetCodecName(codec);
+      return kSbMediaVideoCodecNone;
   }
-  DLOG(ERROR) << "Unsupported video codec " << codec;
+  NOTREACHED();
   return kSbMediaVideoCodecNone;
 }
 
@@ -73,7 +93,7 @@
   audio_header.block_alignment = 4;
   audio_header.bits_per_sample = audio_decoder_config.bits_per_channel();
 
-#if SB_API_VERSION >= 6
+#if SB_HAS(AUDIO_SPECIFIC_CONFIG_AS_POINTER)
   audio_header.audio_specific_config_size =
       static_cast<uint16_t>(audio_decoder_config.extra_data().size());
   if (audio_header.audio_specific_config_size == 0) {
@@ -81,7 +101,7 @@
   } else {
     audio_header.audio_specific_config = &audio_decoder_config.extra_data()[0];
   }
-#else   // SB_API_VERSION >= 6
+#else   // SB_HAS(AUDIO_SPECIFIC_CONFIG_AS_POINTER)
   audio_header.audio_specific_config_size = static_cast<uint16_t>(
       std::min(audio_decoder_config.extra_data().size(),
                sizeof(audio_header.audio_specific_config)));
@@ -90,7 +110,7 @@
                  &audio_decoder_config.extra_data()[0],
                  audio_header.audio_specific_config_size);
   }
-#endif  // SB_API_VERSION >= 6
+#endif  // SB_HAS(AUDIO_SPECIFIC_CONFIG_AS_POINTER)
 
   return audio_header;
 }
diff --git a/src/cobalt/media/base/video_codecs.cc b/src/cobalt/media/base/video_codecs.cc
index a82e041..a18283a 100644
--- a/src/cobalt/media/base/video_codecs.cc
+++ b/src/cobalt/media/base/video_codecs.cc
@@ -11,6 +11,7 @@
 #include "base/string_split.h"
 #include "base/string_util.h"
 #include "starboard/memory.h"
+#include "starboard/string.h"
 
 namespace cobalt {
 namespace media {
@@ -24,6 +25,8 @@
       return "h264";
     case kCodecHEVC:
       return "hevc";
+    case kCodecDolbyVision:
+      return "dolbyvision";
     case kCodecVC1:
       return "vc1";
     case kCodecMPEG2:
@@ -36,6 +39,8 @@
       return "vp8";
     case kCodecVP9:
       return "vp9";
+    case kCodecAV1:
+      return "av1";
   }
   NOTREACHED();
   return "";
@@ -83,11 +88,228 @@
       return "vp9 profile2";
     case VP9PROFILE_PROFILE3:
       return "vp9 profile3";
+    case DOLBYVISION_PROFILE0:
+      return "dolby vision profile 0";
+    case DOLBYVISION_PROFILE4:
+      return "dolby vision profile 4";
+    case DOLBYVISION_PROFILE5:
+      return "dolby vision profile 5";
+    case DOLBYVISION_PROFILE7:
+      return "dolby vision profile 7";
+    case THEORAPROFILE_ANY:
+      return "theora";
+    case AV1PROFILE_PROFILE_MAIN:
+      return "av1 profile main";
+    case AV1PROFILE_PROFILE_HIGH:
+      return "av1 profile high";
+    case AV1PROFILE_PROFILE_PRO:
+      return "av1 profile pro";
   }
   NOTREACHED();
   return "";
 }
 
+bool ParseAv1CodecId(const std::string& codec_id, VideoCodecProfile* profile,
+                     uint8_t* level_idc, gfx::ColorSpace* color_space) {
+  // The codecs parameter string for the AOM AV1 codec is as follows:
+  // See https://aomediacodec.github.io/av1-isobmff/#codecsparam.
+  //
+  // <sample entry4CC>.<profile>.<level><tier>.<bitDepth>.<monochrome>.
+  // <chromaSubsampling>.<colorPrimaries>.<transferCharacteristics>.
+  // <matrixCoefficients>.<videoFullRangeFlag>
+
+  // TODO: Replace the following code using
+  //         std::vector<std::string> fields = base::SplitString(
+  //             codec_id, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+  //       once Chromium base rebase is finished.
+  if (SbStringFindCharacter(codec_id.c_str(), ' ') != nullptr) {
+    return false;
+  }
+  if (SbStringFindString(codec_id.c_str(), "..") != nullptr) {
+    return false;
+  }
+  std::vector<std::string> fields;
+  base::SplitString(codec_id, '.', &fields);
+
+  // The parameters sample entry 4CC, profile, level, tier, and bitDepth are all
+  // mandatory fields. If any of these fields are empty, or not within their
+  // allowed range, the processing device SHOULD treat it as an error.
+  if (fields.size() < 4 || fields.size() > 10) {
+    DVLOG(3) << __func__ << " Invalid number of fields (" << fields.size()
+             << ")";
+    return false;
+  }
+
+  // All the other fields (including their leading '.') are optional, mutually
+  // inclusive (all or none) fields. If not specified then the values listed in
+  // the table below are assumed.
+  //
+  // mono_chrome              0
+  // chromaSubsampling        112 (4:2:0 colocated with luma (0,0))
+  // colorPrimaries           1 (ITU-R BT.709)
+  // transferCharacteristics  1 (ITU-R BT.709)
+  // matrixCoefficients       1 (ITU-R BT.709)
+  // videoFullRangeFlag       0 (studio swing representation)
+  *color_space = gfx::ColorSpace::CreateREC709();
+
+  if (fields[0] != "av01") {
+    DVLOG(3) << __func__ << " Invalid AV1 4CC (" << fields[0] << ")";
+    return false;
+  }
+
+  // The level parameter value SHALL equal the first level value indicated by
+  // seq_level_idx in the Sequence Header. The tier parameter value SHALL be
+  // equal to M when the first seq_tier value in the Sequence Header is equal to
+  // 0, and H when it is equal to 1.
+  if (fields[2].size() != 3 || (fields[2][2] != 'M' && fields[2][2] != 'H')) {
+    DVLOG(3) << __func__ << " Invalid level+tier (" << fields[2] << ")";
+    return false;
+  }
+
+  // Since tier has been validated, strip the trailing tier indicator to allow
+  // int conversion below.
+  fields[2].resize(2);
+
+  // Fill with dummy values to ensure parallel indices with fields.
+  std::vector<int> values(fields.size(), 0);
+  for (size_t i = 1; i < fields.size(); ++i) {
+    if (fields[i].empty()) {
+      DVLOG(3) << __func__ << " Invalid empty field (position:" << i << ")";
+      return false;
+    }
+
+    if (!base::StringToInt(fields[i], &values[i]) || values[i] < 0) {
+      DVLOG(3) << __func__ << " Invalid field value (" << values[i] << ")";
+      return false;
+    }
+  }
+
+  // The profile parameter value, represented by a single digit decimal, SHALL
+  // equal the value of seq_profile in the Sequence Header.
+  const int profile_idc = fields[1].size() == 1 ? values[1] : -1;
+  switch (profile_idc) {
+    case 0:
+      *profile = AV1PROFILE_PROFILE_MAIN;
+      break;
+    case 1:
+      *profile = AV1PROFILE_PROFILE_HIGH;
+      break;
+    case 2:
+      *profile = AV1PROFILE_PROFILE_PRO;
+      break;
+    default:
+      DVLOG(3) << __func__ << " Invalid profile (" << fields[1] << ")";
+      return false;
+  }
+
+  // The level parameter value SHALL equal the first level value indicated by
+  // seq_level_idx in the Sequence Header. Note: We validate that this field has
+  // the required leading zeros above.
+  *level_idc = values[2];
+  if (*level_idc > 31) {
+    DVLOG(3) << __func__ << " Invalid level (" << *level_idc << ")";
+    return false;
+  }
+
+  // The bitDepth parameter value SHALL equal the value of BitDepth variable as
+  // defined in [AV1] derived from the Sequence Header. Leading zeros required.
+  const int bit_depth = values[3];
+  if (fields[3].size() != 2 ||
+      (bit_depth != 8 && bit_depth != 10 && bit_depth != 12)) {
+    DVLOG(3) << __func__ << " Invalid bit-depth (" << fields[3] << ")";
+    return false;
+  }
+
+  if (values.size() <= 4) return true;
+
+  // The monochrome parameter value, represented by a single digit decimal,
+  // SHALL equal the value of mono_chrome in the Sequence Header.
+  const int monochrome = values[4];
+  if (fields[4].size() != 1 || monochrome > 1) {
+    DVLOG(3) << __func__ << " Invalid monochrome (" << fields[4] << ")";
+    return false;
+  }
+
+  if (values.size() <= 5) return true;
+
+  // The chromaSubsampling parameter value, represented by a three-digit
+  // decimal, SHALL have its first digit equal to subsampling_x and its second
+  // digit equal to subsampling_y. If both subsampling_x and subsampling_y are
+  // set to 1, then the third digit SHALL be equal to chroma_sample_position,
+  // otherwise it SHALL be set to 0.
+  if (fields[5].size() != 3) {
+    DVLOG(3) << __func__ << " Invalid chroma subsampling (" << fields[5] << ")";
+    return false;
+  }
+
+  const char subsampling_x = fields[5][0];
+  const char subsampling_y = fields[5][1];
+  const char chroma_sample_position = fields[5][2];
+  if ((subsampling_x < '0' || subsampling_x > '1') ||
+      (subsampling_y < '0' || subsampling_y > '1') ||
+      (chroma_sample_position < '0' || chroma_sample_position > '3')) {
+    DVLOG(3) << __func__ << " Invalid chroma subsampling (" << fields[5] << ")";
+    return false;
+  }
+
+  if (((subsampling_x == '0' || subsampling_y == '0') &&
+       chroma_sample_position != '0')) {
+    DVLOG(3) << __func__ << " Invalid chroma subsampling (" << fields[5] << ")";
+    return false;
+  }
+
+  if (values.size() <= 6) return true;
+
+  // The colorPrimaries, transferCharacteristics, matrixCoefficients and
+  // videoFullRangeFlag parameter values SHALL equal the value of matching
+  // fields in the Sequence Header, if color_description_present_flag is set to
+  // 1, otherwise they SHOULD not be set, defaulting to the values below. The
+  // videoFullRangeFlag is represented by a single digit.
+  auto primaries = gfx::ColorSpace::PrimaryIDFromInt(values[6]);
+  if (fields[6].size() != 2 ||
+      primaries == gfx::ColorSpace::kPrimaryIdReserved0) {
+    DVLOG(3) << __func__ << " Invalid color primaries (" << fields[6] << ")";
+    return false;
+  }
+
+  if (values.size() <= 7) return true;
+
+  auto transfer = gfx::ColorSpace::TransferIDFromInt(values[7]);
+  if (fields[7].size() != 2 ||
+      transfer == gfx::ColorSpace::kTransferIdReserved0) {
+    DVLOG(3) << __func__ << " Invalid transfer function (" << fields[7] << ")";
+    return false;
+  }
+
+  if (values.size() <= 8) return true;
+
+  auto matrix = gfx::ColorSpace::MatrixIDFromInt(values[8]);
+  if (fields[8].size() != 2 || matrix == gfx::ColorSpace::kMatrixIdUnknown) {
+    // TODO: AV1 allows a few matrices we don't support yet.
+    //       https://crbug.com/854290
+    if (values[8] == 12 || values[8] == 13 || values[8] == 14) {
+      DVLOG(3) << __func__ << " Unsupported matrix coefficients (" << fields[8]
+               << ")";
+    } else {
+      DVLOG(3) << __func__ << " Invalid matrix coefficients (" << fields[8]
+               << ")";
+    }
+    return false;
+  }
+
+  if (values.size() <= 9) return true;
+
+  const int video_full_range_flag = values[9];
+  if (fields[9].size() != 1 || video_full_range_flag > 1) {
+    DVLOG(3) << __func__ << " Invalid full range flag (" << fields[9] << ")";
+    return false;
+  }
+  auto range = video_full_range_flag == 1 ? gfx::ColorSpace::kRangeIdFull
+                                          : gfx::ColorSpace::kRangeIdLimited;
+  *color_space = gfx::ColorSpace(primaries, transfer, matrix, range);
+  return true;
+}
+
 bool ParseAVCCodecId(const std::string& codec_id, VideoCodecProfile* profile,
                      uint8_t* level_idc) {
   // Make sure we have avc1.xxxxxx or avc3.xxxxxx , where xxxxxx are hex digits
@@ -294,6 +516,9 @@
       codec_id == "vp9.2") {
     return kCodecVP9;
   }
+  gfx::ColorSpace color_space;
+  if (ParseAv1CodecId(codec_id, &profile, &level, &color_space))
+    return kCodecAV1;
   if (codec_id == "theora") return kCodecTheora;
   if (ParseHEVCCodecId(codec_id, &profile, &level)) return kCodecHEVC;
 
diff --git a/src/cobalt/media/base/video_codecs.h b/src/cobalt/media/base/video_codecs.h
index 085ac15..f941d83 100644
--- a/src/cobalt/media/base/video_codecs.h
+++ b/src/cobalt/media/base/video_codecs.h
@@ -6,6 +6,8 @@
 #define COBALT_MEDIA_BASE_VIDEO_CODECS_H_
 
 #include <string>
+
+#include "cobalt/media/base/color_space.h"
 #include "cobalt/media/base/media_export.h"
 #include "starboard/types.h"
 
@@ -25,12 +27,14 @@
   kCodecVP8,
   kCodecVP9,
   kCodecHEVC,
+  kCodecDolbyVision,
+  kCodecAV1,
   // DO NOT ADD RANDOM VIDEO CODECS!
   //
   // The only acceptable time to add a new codec is if there is production code
   // that uses said codec in the same CL.
 
-  kVideoCodecMax = kCodecHEVC  // Must equal the last "real" codec above.
+  kVideoCodecMax = kCodecAV1  // Must equal the last "real" codec above.
 };
 
 // Video codec profiles. Keep in sync with mojo::VideoCodecProfile (see
@@ -72,12 +76,31 @@
   HEVCPROFILE_MAIN10 = 17,
   HEVCPROFILE_MAIN_STILL_PICTURE = 18,
   HEVCPROFILE_MAX = HEVCPROFILE_MAIN_STILL_PICTURE,
-  VIDEO_CODEC_PROFILE_MAX = HEVCPROFILE_MAX,
+  DOLBYVISION_MIN = 19,
+  DOLBYVISION_PROFILE0 = DOLBYVISION_MIN,
+  DOLBYVISION_PROFILE4 = 20,
+  DOLBYVISION_PROFILE5 = 21,
+  DOLBYVISION_PROFILE7 = 22,
+  DOLBYVISION_MAX = DOLBYVISION_PROFILE7,
+  THEORAPROFILE_MIN = 23,
+  THEORAPROFILE_ANY = THEORAPROFILE_MIN,
+  THEORAPROFILE_MAX = THEORAPROFILE_ANY,
+  AV1PROFILE_MIN = 24,
+  AV1PROFILE_PROFILE_MAIN = AV1PROFILE_MIN,
+  AV1PROFILE_PROFILE_HIGH = 25,
+  AV1PROFILE_PROFILE_PRO = 26,
+  AV1PROFILE_MAX = AV1PROFILE_PROFILE_PRO,
+  VIDEO_CODEC_PROFILE_MAX = AV1PROFILE_PROFILE_PRO,
 };
 
 std::string MEDIA_EXPORT GetCodecName(VideoCodec codec);
 std::string MEDIA_EXPORT GetProfileName(VideoCodecProfile profile);
 
+MEDIA_EXPORT bool ParseAv1CodecId(const std::string& codec_id,
+                                  VideoCodecProfile* profile,
+                                  uint8_t* level_idc,
+                                  gfx::ColorSpace* color_space);
+
 // Handle parsing AVC/H.264 codec ids as outlined in RFC 6381 and ISO-14496-10.
 MEDIA_EXPORT bool ParseAVCCodecId(const std::string& codec_id,
                                   VideoCodecProfile* profile,
diff --git a/src/cobalt/media/base/video_decoder_config.cc b/src/cobalt/media/base/video_decoder_config.cc
index 69a5446..d399d36 100644
--- a/src/cobalt/media/base/video_decoder_config.cc
+++ b/src/cobalt/media/base/video_decoder_config.cc
@@ -39,6 +39,17 @@
     case VP9PROFILE_PROFILE2:
     case VP9PROFILE_PROFILE3:
       return kCodecVP9;
+    case DOLBYVISION_PROFILE0:
+    case DOLBYVISION_PROFILE4:
+    case DOLBYVISION_PROFILE5:
+    case DOLBYVISION_PROFILE7:
+      return kCodecDolbyVision;
+    case THEORAPROFILE_ANY:
+      return kCodecTheora;
+    case AV1PROFILE_PROFILE_MAIN:
+    case AV1PROFILE_PROFILE_HIGH:
+    case AV1PROFILE_PROFILE_PRO:
+      return kCodecAV1;
   }
   NOTREACHED();
   return kUnknownVideoCodec;
diff --git a/src/cobalt/media/filters/stream_parser_factory.cc b/src/cobalt/media/filters/stream_parser_factory.cc
index 79defc7..d0e98ab 100644
--- a/src/cobalt/media/filters/stream_parser_factory.cc
+++ b/src/cobalt/media/filters/stream_parser_factory.cc
@@ -49,7 +49,8 @@
     HISTOGRAM_OPUS,
     HISTOGRAM_HEVC,
     HISTOGRAM_AC3,
-    HISTOGRAM_MAX = HISTOGRAM_AC3  // Must be equal to largest logged entry.
+    HISTOGRAM_AV1,
+    HISTOGRAM_MAX = HISTOGRAM_AV1  // Must be equal to largest logged entry.
   };
 
   const char* pattern;
@@ -77,9 +78,13 @@
                                            CodecInfo::HISTOGRAM_VORBIS};
 static const CodecInfo kOpusCodecInfo = {"opus", CodecInfo::AUDIO, NULL,
                                          CodecInfo::HISTOGRAM_OPUS};
+// Note: Validation of the codec string is handled by the caller.
+static const CodecInfo kAV1CodecInfo = {"av01.*", CodecInfo::VIDEO, nullptr,
+                                        CodecInfo::HISTOGRAM_AV1};
 
 static const CodecInfo* kVideoWebMCodecs[] = {
-    &kVP8CodecInfo, &kVP9CodecInfo, &kVorbisCodecInfo, &kOpusCodecInfo, NULL};
+    &kVP8CodecInfo,  &kVP9CodecInfo, &kVorbisCodecInfo,
+    &kOpusCodecInfo, &kAV1CodecInfo, NULL};
 
 static const CodecInfo* kAudioWebMCodecs[] = {&kVorbisCodecInfo,
                                               &kOpusCodecInfo, NULL};
@@ -169,10 +174,9 @@
                                           CodecInfo::HISTOGRAM_EAC3};
 
 static const CodecInfo* kVideoMP4Codecs[] = {
-    &kH264AVC1CodecInfo,   &kH264AVC3CodecInfo,
-    &kHEVCHEV1CodecInfo,   &kHEVCHVC1CodecInfo,
-    &kMPEG4VP09CodecInfo,  &kMPEG4AACCodecInfo,
-    &kMPEG2AACLCCodecInfo, NULL};
+    &kH264AVC1CodecInfo,   &kH264AVC3CodecInfo,  &kHEVCHEV1CodecInfo,
+    &kHEVCHVC1CodecInfo,   &kMPEG4VP09CodecInfo, &kMPEG4AACCodecInfo,
+    &kMPEG2AACLCCodecInfo, &kAV1CodecInfo,       NULL};
 
 static const CodecInfo* kAudioMP4Codecs[] = {
     &kMPEG4AACCodecInfo, &kMPEG2AACLCCodecInfo, &kAC3CodecInfo1,
diff --git a/src/cobalt/media/formats/mp4/box_definitions.cc b/src/cobalt/media/formats/mp4/box_definitions.cc
index af66813..b39c5c6 100644
--- a/src/cobalt/media/formats/mp4/box_definitions.cc
+++ b/src/cobalt/media/formats/mp4/box_definitions.cc
@@ -544,6 +544,73 @@
   return true;
 }
 
+AV1CodecConfigurationRecord::AV1CodecConfigurationRecord()
+    : profile(VIDEO_CODEC_PROFILE_UNKNOWN) {}
+
+AV1CodecConfigurationRecord::~AV1CodecConfigurationRecord() = default;
+
+FourCC AV1CodecConfigurationRecord::BoxType() const { return FOURCC_AV1C; }
+
+// Parse the AV1CodecConfigurationRecord, which has the following format:
+// unsigned int (1) marker = 1;
+// unsigned int (7) version = 1;
+// unsigned int (3) seq_profile;
+// unsigned int (5) seq_level_idx_0;
+// unsigned int (1) seq_tier_0;
+// unsigned int (1) high_bitdepth;
+// unsigned int (1) twelve_bit;
+// unsigned int (1) monochrome;
+// unsigned int (1) chroma_subsampling_x;
+// unsigned int (1) chroma_subsampling_y;
+// unsigned int (2) chroma_sample_position;
+// unsigned int (3) reserved = 0;
+//
+// unsigned int (1) initial_presentation_delay_present;
+// if (initial_presentation_delay_present) {
+//   unsigned int (4) initial_presentation_delay_minus_one;
+// } else {
+//   unsigned int (4) reserved = 0;
+// }
+//
+// unsigned int (8)[] configOBUs;
+bool AV1CodecConfigurationRecord::Parse(BoxReader* reader) {
+  uint8_t av1c_byte = 0;
+  RCHECK(reader->Read1(&av1c_byte));
+  const uint8_t av1c_marker = av1c_byte >> 7;
+  if (!av1c_marker) {
+    MEDIA_LOG(ERROR, reader->media_log()) << "Unsupported av1C: marker unset.";
+    return false;
+  }
+
+  const uint8_t av1c_version = av1c_byte & 0b01111111;
+  if (av1c_version != 1) {
+    MEDIA_LOG(ERROR, reader->media_log())
+        << "Unsupported av1C: unexpected version number: " << av1c_version;
+    return false;
+  }
+
+  RCHECK(reader->Read1(&av1c_byte));
+  const uint8_t seq_profile = av1c_byte >> 5;
+  switch (seq_profile) {
+    case 0:
+      profile = AV1PROFILE_PROFILE_MAIN;
+      break;
+    case 1:
+      profile = AV1PROFILE_PROFILE_HIGH;
+      break;
+    case 2:
+      profile = AV1PROFILE_PROFILE_PRO;
+      break;
+    default:
+      MEDIA_LOG(ERROR, reader->media_log())
+          << "Unsupported av1C: unknown profile 0x" << std::hex << seq_profile;
+      return false;
+  }
+
+  // The remaining fields are ignored since we don't care about them yet.
+  return true;
+}
+
 PixelAspectRatioBox::PixelAspectRatioBox() : h_spacing(1), v_spacing(1) {}
 PixelAspectRatioBox::~PixelAspectRatioBox() {}
 FourCC PixelAspectRatioBox::BoxType() const { return FOURCC_PASP; }
@@ -614,6 +681,15 @@
           make_scoped_refptr(new HEVCBitstreamConverter(hevcConfig.Pass()));
       break;
     }
+    case FOURCC_AV01: {
+      DVLOG(2) << __func__ << " reading AV1 configuration.";
+      AV1CodecConfigurationRecord av1_config;
+      RCHECK(reader->ReadChild(&av1_config));
+      frame_bitstream_converter = nullptr;
+      video_codec = kCodecAV1;
+      video_codec_profile = av1_config.profile;
+      break;
+    }
     default:
       // Unknown/unsupported format
       MEDIA_LOG(ERROR, reader->media_log()) << __FUNCTION__
@@ -634,6 +710,8 @@
     case FOURCC_HEV1:
     case FOURCC_HVC1:
       return true;
+    case FOURCC_AV01:
+      return true;
     default:
       return false;
   }
diff --git a/src/cobalt/media/formats/mp4/box_definitions.h b/src/cobalt/media/formats/mp4/box_definitions.h
index b3152f1..90415d9 100644
--- a/src/cobalt/media/formats/mp4/box_definitions.h
+++ b/src/cobalt/media/formats/mp4/box_definitions.h
@@ -226,6 +226,12 @@
   VideoCodecProfile profile;
 };
 
+struct MEDIA_EXPORT AV1CodecConfigurationRecord : Box {
+  DECLARE_BOX_METHODS(AV1CodecConfigurationRecord);
+
+  VideoCodecProfile profile;
+};
+
 struct MEDIA_EXPORT PixelAspectRatioBox : Box {
   DECLARE_BOX_METHODS(PixelAspectRatioBox);
 
diff --git a/src/cobalt/media/formats/mp4/fourccs.h b/src/cobalt/media/formats/mp4/fourccs.h
index 1648ff6..e79b003 100644
--- a/src/cobalt/media/formats/mp4/fourccs.h
+++ b/src/cobalt/media/formats/mp4/fourccs.h
@@ -15,6 +15,8 @@
   FOURCC_NULL = 0,
   FOURCC_AC3 = 0x61632d33,   // "ac-3"
   FOURCC_EAC3 = 0x65632d33,  // "ec-3"
+  FOURCC_AV01 = 0x61763031,  // "av01"
+  FOURCC_AV1C = 0x61763143,  // "av1C"
   FOURCC_AVC1 = 0x61766331,
   FOURCC_AVC3 = 0x61766333,
   FOURCC_AVCC = 0x61766343,
diff --git a/src/cobalt/media/media2.gyp b/src/cobalt/media/media2.gyp
index 289ef3a..c39ace8 100644
--- a/src/cobalt/media/media2.gyp
+++ b/src/cobalt/media/media2.gyp
@@ -67,6 +67,8 @@
         'base/encryption_scheme.h',
         'base/hdr_metadata.cc',
         'base/hdr_metadata.h',
+        'base/interleaved_sinc_resampler.cc',
+        'base/interleaved_sinc_resampler.h',
         'base/media_log.cc',
         'base/media_log.h',
         'base/media_track.cc',
diff --git a/src/cobalt/media/sandbox/web_media_player_helper.cc b/src/cobalt/media/sandbox/web_media_player_helper.cc
index 1308b4d..dacf627 100644
--- a/src/cobalt/media/sandbox/web_media_player_helper.cc
+++ b/src/cobalt/media/sandbox/web_media_player_helper.cc
@@ -69,11 +69,7 @@
     : client_(new WebMediaPlayerClientStub(open_cb)),
       player_(media_module->CreateWebMediaPlayer(client_)) {
   player_->SetRate(1.0);
-// TODO: Investigate a better way to exclude this when SB_HAS(PLAYER_WITH_URL)
-//       is enabled.
-#if !SB_HAS(PLAYER_WITH_URL)
   player_->LoadMediaSource();
-#endif  // !SB_HAS(PLAYER_WITH_URL)
   player_->Play();
 }
 
@@ -87,11 +83,7 @@
       base::MessageLoopProxy::current(), video_url, csp::SecurityCallback(),
       fetcher_factory->network_module(), loader::kNoCORSMode,
       loader::Origin()));
-// TODO: Investigate a better way to exclude this when SB_HAS(PLAYER_WITH_URL)
-//       is enabled.
-#if !SB_HAS(PLAYER_WITH_URL)
   player_->LoadProgressive(video_url, data_source.Pass());
-#endif  // !SB_HAS(PLAYER_WITH_URL)
   player_->Play();
 }
 
diff --git a/src/cobalt/media/sandbox/web_media_player_sandbox.cc b/src/cobalt/media/sandbox/web_media_player_sandbox.cc
index 716bb84..25f3250 100644
--- a/src/cobalt/media/sandbox/web_media_player_sandbox.cc
+++ b/src/cobalt/media/sandbox/web_media_player_sandbox.cc
@@ -328,7 +328,7 @@
   void AppendData(const std::string& id, ScopedFile* file, int64* offset) {
     const float kLowWaterMarkInSeconds = 5.f;
     const int64 kMaxBytesToAppend = 1024 * 1024;
-    char buffer[kMaxBytesToAppend];
+    std::vector<uint8_t> buffer(kMaxBytesToAppend);
 
     while (*offset < file->GetSize()) {
       Ranges<TimeDelta> ranges = chunk_demuxer_->GetBufferedRanges(id);
@@ -339,14 +339,14 @@
       }
       int64 bytes_to_append =
           std::min(kMaxBytesToAppend, file->GetSize() - *offset);
-      file->Read(buffer, bytes_to_append);
+      file->Read(reinterpret_cast<char*>(buffer.data()), bytes_to_append);
 #if defined(COBALT_MEDIA_SOURCE_2016)
       base::TimeDelta timestamp_offset;
-      chunk_demuxer_->AppendData(id, reinterpret_cast<uint8*>(buffer),
+      chunk_demuxer_->AppendData(id, buffer.data(),
                                  bytes_to_append, base::TimeDelta(),
                                  media::kInfiniteDuration, &timestamp_offset);
 #else   //  defined(COBALT_MEDIA_SOURCE_2016)
-      chunk_demuxer_->AppendData(id, reinterpret_cast<uint8*>(buffer),
+      chunk_demuxer_->AppendData(id, buffer.data(),
                                  bytes_to_append);
 #endif  //  defined(COBALT_MEDIA_SOURCE_2016)
 
diff --git a/src/cobalt/network/network_event.h b/src/cobalt/network/network_event.h
deleted file mode 100644
index 489568e..0000000
--- a/src/cobalt/network/network_event.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2015 The Cobalt Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef COBALT_NETWORK_NETWORK_EVENT_H_
-#define COBALT_NETWORK_NETWORK_EVENT_H_
-
-#include "base/compiler_specific.h"
-#include "cobalt/base/event.h"
-
-namespace cobalt {
-namespace network {
-
-class NetworkEvent : public base::Event {
- public:
-  enum Type {
-    kConnection,
-    kDisconnection,
-  };
-
-  explicit NetworkEvent(Type type) : type_(type) {}
-  Type type() const { return type_; }
-
-  BASE_EVENT_SUBCLASS(NetworkEvent);
-
- private:
-  Type type_;
-};
-
-}  // namespace network
-}  // namespace cobalt
-
-#endif  // COBALT_NETWORK_NETWORK_EVENT_H_
diff --git a/src/cobalt/network/network_module.cc b/src/cobalt/network/network_module.cc
index f57346f..4a1ac9c 100644
--- a/src/cobalt/network/network_module.cc
+++ b/src/cobalt/network/network_module.cc
@@ -132,7 +132,7 @@
   // in an increase in unresponsiveness and input latency on single-core
   // devices.  Keeping it at normal so that it doesn't take precedence over
   // user interaction processing.
-  thread_options.priority = base::kThreadPriority_Normal;
+  thread_options.priority = base::kThreadPriority_Low;
   thread_->StartWithOptions(thread_options);
 
   base::WaitableEvent creation_event(true, false);
diff --git a/src/cobalt/renderer/backend/blitter/graphics_context.h b/src/cobalt/renderer/backend/blitter/graphics_context.h
index d9ea3a7..19fccc1 100644
--- a/src/cobalt/renderer/backend/blitter/graphics_context.h
+++ b/src/cobalt/renderer/backend/blitter/graphics_context.h
@@ -38,6 +38,8 @@
       const scoped_refptr<RenderTarget>& render_target) override;
   void Finish() override;
 
+  float GetMinimumFramesPerSecond() override { return 0.0f; }
+
   SbBlitterContext GetSbBlitterContext() const { return context_; }
   SbBlitterDevice GetSbBlitterDevice() const { return device_; }
 
diff --git a/src/cobalt/renderer/backend/egl/graphics_context.cc b/src/cobalt/renderer/backend/egl/graphics_context.cc
index 42c019a..23d4d86 100644
--- a/src/cobalt/renderer/backend/egl/graphics_context.cc
+++ b/src/cobalt/renderer/backend/egl/graphics_context.cc
@@ -81,6 +81,10 @@
     ANNOTATE_SCOPED_MEMORY_LEAK;
     ComputeReadPixelsNeedVerticalFlip();
   }
+
+  get_minimum_frames_per_second_ =
+      reinterpret_cast<decltype(get_minimum_frames_per_second_)>(
+          eglGetProcAddress("eglGetMinimumFramesPerSecondCOBALT"));
 }
 
 GraphicsSystemEGL* GraphicsContextEGL::system_egl() {
@@ -485,6 +489,14 @@
   GL_CALL(glFinish());
 }
 
+float GraphicsContextEGL::GetMinimumFramesPerSecond() {
+  if (get_minimum_frames_per_second_) {
+    return get_minimum_frames_per_second_(display_);
+  } else {
+    return 0;
+  }
+}
+
 void GraphicsContextEGL::Blit(GLuint texture, int x, int y, int width,
                               int height) {
   // Render a texture to the specified output rectangle on the render target.
diff --git a/src/cobalt/renderer/backend/egl/graphics_context.h b/src/cobalt/renderer/backend/egl/graphics_context.h
index 2edd297..eecfbfc 100644
--- a/src/cobalt/renderer/backend/egl/graphics_context.h
+++ b/src/cobalt/renderer/backend/egl/graphics_context.h
@@ -65,6 +65,8 @@
 
   void Finish() override;
 
+  float GetMinimumFramesPerSecond() override;
+
   // Helper class to allow one to create a RAII object that will acquire the
   // current context upon construction and release it upon destruction.
   class ScopedMakeCurrent {
@@ -172,6 +174,9 @@
   GLuint blit_program_;
   GLuint blit_vertex_buffer_;
 
+  // Custom EGL extension to allow platforms to request a minimum frame rate.
+  EGLint (*get_minimum_frames_per_second_)(EGLDisplay display) = nullptr;
+
   // Lazily evaluate whether we need to do a vertical flip when calling
   // glReadPixels(), and cache the result here when that question is answered.
   base::optional<bool> read_pixels_needs_vertical_flip_;
diff --git a/src/cobalt/renderer/backend/egl/texture_data_cpu.cc b/src/cobalt/renderer/backend/egl/texture_data_cpu.cc
index b74313f..1f578ff 100644
--- a/src/cobalt/renderer/backend/egl/texture_data_cpu.cc
+++ b/src/cobalt/renderer/backend/egl/texture_data_cpu.cc
@@ -59,16 +59,13 @@
   }
 
   GLint original_texture_alignment;
-  glGetIntegerv(GL_UNPACK_ALIGNMENT, &original_texture_alignment);
-  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+  GL_CALL(glGetIntegerv(GL_UNPACK_ALIGNMENT, &original_texture_alignment));
+  GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
 
   // Copy pixel data over from the user provided source data into the OpenGL
   // driver to be used as a texture from now on.
   glTexImage2D(GL_TEXTURE_2D, 0, format, size.width(), size.height(), 0, format,
                GL_UNSIGNED_BYTE, data);
-
-  glPixelStorei(GL_UNPACK_ALIGNMENT, original_texture_alignment);
-
   if (glGetError() != GL_NO_ERROR) {
     LOG(ERROR) << "Error calling glTexImage2D(size = (" << size.width() << ", "
                << size.height() << "))";
@@ -77,6 +74,8 @@
     texture_handle = 0;
   }
 
+  GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, original_texture_alignment));
+
   GL_CALL(glBindTexture(GL_TEXTURE_2D, 0));
 
   return texture_handle;
diff --git a/src/cobalt/renderer/backend/graphics_context.h b/src/cobalt/renderer/backend/graphics_context.h
index 6830232..bfcfd62 100644
--- a/src/cobalt/renderer/backend/graphics_context.h
+++ b/src/cobalt/renderer/backend/graphics_context.h
@@ -74,6 +74,14 @@
   // Waits until all drawing is finished.
   virtual void Finish() = 0;
 
+  // Get the minimum number of frames that should be rendered per second. This
+  // value can be dynamic and must be queried periodically. This can be used
+  // to force the rasterizer to present a new frame even if nothing has changed
+  // visually. Due to the imprecision of thread scheduling, it may be necessary
+  // to specify a higher minimum fps to ensure frames aren't skipped when the
+  // throttling logic is executed a little too early.
+  virtual float GetMinimumFramesPerSecond() = 0;
+
  private:
   GraphicsSystem* system_;
 };
diff --git a/src/cobalt/renderer/backend/graphics_context_stub.h b/src/cobalt/renderer/backend/graphics_context_stub.h
index 90e9363..8023cbb 100644
--- a/src/cobalt/renderer/backend/graphics_context_stub.h
+++ b/src/cobalt/renderer/backend/graphics_context_stub.h
@@ -45,6 +45,8 @@
   }
 
   void Finish() override {}
+
+  float GetMinimumFramesPerSecond() override { return 0.0f; }
 };
 
 }  // namespace backend
diff --git a/src/cobalt/renderer/pipeline.cc b/src/cobalt/renderer/pipeline.cc
index dbcfd3a..542e813 100644
--- a/src/cobalt/renderer/pipeline.cc
+++ b/src/cobalt/renderer/pipeline.cc
@@ -228,7 +228,7 @@
 
   Submission submission = Submission(animate_node);
   // Rasterize this submission into the newly created target.
-  RasterizeSubmissionToRenderTarget(submission, offscreen_target);
+  RasterizeSubmissionToRenderTarget(submission, offscreen_target, true);
 
   // Load the texture's pixel data into a CPU memory buffer and return it.
   complete.Run(graphics_context_->DownloadPixelDataAsRGBA(offscreen_target),
@@ -309,12 +309,24 @@
   bool is_new_render_tree = submission.render_tree != last_render_tree_;
   bool has_render_tree_changed =
       !last_animations_expired_ || is_new_render_tree;
+  bool force_rasterize = submit_even_if_render_tree_is_unchanged_ ||
+      fps_overlay_update_pending_;
+
+  float minimum_fps = graphics_context_ ?
+      graphics_context_->GetMinimumFramesPerSecond() : 0;
+  if (minimum_fps > 0) {
+    base::TimeDelta max_time_between_rasterize =
+        base::TimeDelta::FromSecondsD(1.0 / minimum_fps);
+    if (start_rasterize_time - last_rasterize_time_ >
+        max_time_between_rasterize) {
+      force_rasterize = true;
+    }
+  }
 
   // If our render tree hasn't changed from the one that was previously
   // rendered and it's okay on this system to not flip the display buffer
   // frequently, then we can just not do anything here.
-  if (fps_overlay_update_pending_ || submit_even_if_render_tree_is_unchanged_ ||
-      has_render_tree_changed) {
+  if (force_rasterize || has_render_tree_changed) {
     // Check whether the animations in the render tree that is being rasterized
     // are active.
     render_tree::animations::AnimateNode* animate_node =
@@ -322,8 +334,11 @@
             submission.render_tree.get());
 
     // Rasterize the last submitted render tree.
-    bool did_rasterize =
-        RasterizeSubmissionToRenderTarget(submission, render_target_);
+    bool did_rasterize = RasterizeSubmissionToRenderTarget(
+        submission, render_target_, force_rasterize);
+    if (did_rasterize) {
+      last_rasterize_time_ = start_rasterize_time;
+    }
 
     bool animations_expired = animate_node->expiry() <= submission.time_offset;
     bool stat_tracked_animations_expired =
@@ -412,7 +427,8 @@
 
 bool Pipeline::RasterizeSubmissionToRenderTarget(
     const Submission& submission,
-    const scoped_refptr<backend::RenderTarget>& render_target) {
+    const scoped_refptr<backend::RenderTarget>& render_target,
+    bool force_rasterize) {
   TRACE_EVENT0("cobalt::renderer",
                "Pipeline::RasterizeSubmissionToRenderTarget()");
 
@@ -442,9 +458,7 @@
   render_tree::animations::AnimateNode::AnimateResults results =
       animate_node->Apply(submission.time_offset);
 
-  if (results.animated == last_animated_render_tree_ &&
-      !submit_even_if_render_tree_is_unchanged_ &&
-      !fps_overlay_update_pending_) {
+  if (results.animated == last_animated_render_tree_ && !force_rasterize) {
     return false;
   }
   last_animated_render_tree_ = results.animated;
diff --git a/src/cobalt/renderer/pipeline.h b/src/cobalt/renderer/pipeline.h
index 0e9b9ae..6000b15 100644
--- a/src/cobalt/renderer/pipeline.h
+++ b/src/cobalt/renderer/pipeline.h
@@ -132,7 +132,8 @@
   // Returns true only if a rasterization actually took place.
   bool RasterizeSubmissionToRenderTarget(
       const Submission& render_tree_submission,
-      const scoped_refptr<backend::RenderTarget>& render_target);
+      const scoped_refptr<backend::RenderTarget>& render_target,
+      bool force_rasterize);
 
   // Updates the rasterizer timer stats according to the |start_time| and
   // |end_time| of the most recent rasterize call.
@@ -311,6 +312,12 @@
   // a discontinuity in animations and reset our submission queue, possibly
   // with new configuration parameters specified in the new |TimelineInfo|.
   Submission::TimelineInfo current_timeline_info_;
+
+  // This timestamp represents the last time the pipeline rasterized a
+  // render tree to render_target_. This is different from last_render_time_
+  // which is specific to the current submission and is reset whenever a new
+  // render tree is submitted.
+  base::TimeTicks last_rasterize_time_;
 };
 
 }  // namespace renderer
diff --git a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
index ab91478..219e1c1 100644
--- a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
+++ b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
@@ -48,7 +48,7 @@
 
 namespace {
 void ConvertContentRegionToScaleTranslateVector(
-    const math::Rect* content_region, const math::Size& texture_size,
+    const math::RectF* content_region, const math::Size& texture_size,
     float* out_vec4) {
   if (!content_region) {
     // If no content region is provided, use the identity matrix.
diff --git a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
index da8a96f..65d8852 100644
--- a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
+++ b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.h
@@ -68,7 +68,7 @@
 
     struct Texture {
       const backend::TextureEGL* texture;
-      math::Rect content_region;
+      math::RectF content_region;
     };
 
     // Returns the number of valid textures in this image, based on its format.
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_image.cc b/src/cobalt/renderer/rasterizer/skia/hardware_image.cc
index 54f6643..81554bd 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_image.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_image.cc
@@ -329,7 +329,8 @@
     scoped_ptr<backend::TextureEGL> texture,
     render_tree::AlphaFormat alpha_format,
     backend::GraphicsContextEGL* cobalt_context, GrContext* gr_context,
-    scoped_ptr<math::Rect> content_region, MessageLoop* rasterizer_message_loop,
+    scoped_ptr<math::RectF> content_region,
+    MessageLoop* rasterizer_message_loop,
     base::optional<AlternateRgbaFormat> alternate_rgba_format)
     : is_opaque_(alpha_format == render_tree::kAlphaFormatOpaque),
       content_region_(content_region.Pass()),
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_image.h b/src/cobalt/renderer/rasterizer/skia/hardware_image.h
index 790b162..0fdead1 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_image.h
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_image.h
@@ -106,7 +106,7 @@
       scoped_ptr<backend::TextureEGL> texture,
       render_tree::AlphaFormat alpha_format,
       backend::GraphicsContextEGL* cobalt_context, GrContext* gr_context,
-      scoped_ptr<math::Rect> content_region,
+      scoped_ptr<math::RectF> content_region,
       MessageLoop* rasterizer_message_loop,
       base::optional<AlternateRgbaFormat> alternate_rgba_format);
   HardwareFrontendImage(
@@ -127,7 +127,7 @@
 
   const backend::TextureEGL* GetTextureEGL() const override;
 
-  const math::Rect* GetContentRegion() const override {
+  const math::RectF* GetContentRegion() const override {
     return content_region_.get();
   }
 
@@ -154,7 +154,7 @@
   // An optional rectangle, in pixel coordinates (with the top-left as the
   // origin) that indicates where in this image the valid content is contained.
   // Usually this is only set from platform-specific SbDecodeTargets.
-  scoped_ptr<math::Rect> content_region_;
+  scoped_ptr<math::RectF> content_region_;
 
   // In some cases where HardwareFrontendImage wraps a RGBA texture, the texture
   // actually contains pixel data in a non-RGBA format, like UYVY for example.
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_mesh.cc b/src/cobalt/renderer/rasterizer/skia/hardware_mesh.cc
index 08deceb..3d39d9f 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_mesh.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_mesh.cc
@@ -29,9 +29,12 @@
   if (vertices_) {
     return static_cast<uint32>(vertices_->size() * sizeof(vertices_->front()) +
                                sizeof(draw_mode_));
+  } else if (vbo_) {
+    return static_cast<uint32>(vbo_->GetVertexCount() * 5 * sizeof(float) +
+                               sizeof(draw_mode_));
+  } else {
+    return 0;
   }
-  return static_cast<uint32>(vbo_->GetVertexCount() * 5 * sizeof(float) +
-                             sizeof(draw_mode_));
 }
 
 const VertexBufferObject* HardwareMesh::GetVBO() const {
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
index 65ce1c2..b4fb2b7 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
@@ -237,16 +237,16 @@
 // Accommodate for the fact that for some image formats, like UYVY, our texture
 // pixel width is actually half the size specified because there are two Y
 // values in each pixel.
-math::Rect AdjustContentRegionForImageType(
+math::RectF AdjustContentRegionForImageType(
     const base::optional<AlternateRgbaFormat>& alternate_rgba_format,
-    const math::Rect& content_region) {
+    const math::RectF& content_region) {
   if (!alternate_rgba_format) {
     return content_region;
   }
 
   switch (*alternate_rgba_format) {
     case AlternateRgbaFormat_UYVY: {
-      math::Rect adjusted_content_region = content_region;
+      math::RectF adjusted_content_region = content_region;
       adjusted_content_region.set_width(content_region.width() / 2);
       return adjusted_content_region;
     } break;
@@ -262,19 +262,19 @@
 // This function will adjust the content region rectangle to match only the
 // left eye's video region, since we are ultimately presenting to a monoscopic
 // display.
-math::Rect AdjustContentRegionForStereoMode(render_tree::StereoMode stereo_mode,
-                                            const math::Rect& content_region) {
+math::RectF AdjustContentRegionForStereoMode(
+    render_tree::StereoMode stereo_mode, const math::RectF& content_region) {
   switch (stereo_mode) {
     case render_tree::kLeftRight: {
       // Use the left half (left eye) of the video only.
-      math::Rect adjusted_content_region(content_region);
+      math::RectF adjusted_content_region(content_region);
       adjusted_content_region.set_width(content_region.width() / 2);
       return adjusted_content_region;
     }
 
     case render_tree::kTopBottom: {
       // Use the top half (left eye) of the video only.
-      math::Rect adjusted_content_region(content_region);
+      math::RectF adjusted_content_region(content_region);
       adjusted_content_region.set_height(content_region.height() / 2);
       return adjusted_content_region;
     }
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
index 2633959..fb3f351 100644
--- a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
+++ b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
@@ -321,11 +321,11 @@
         info.is_opaque ? render_tree::kAlphaFormatOpaque
                        : render_tree::kAlphaFormatUnpremultiplied;
 
-    scoped_ptr<math::Rect> content_region;
+    scoped_ptr<math::RectF> content_region;
     if (plane.content_region.left != 0 || plane.content_region.top != 0 ||
         plane.content_region.right != plane.width ||
         plane.content_region.bottom != plane.height) {
-      content_region.reset(new math::Rect(
+      content_region.reset(new math::RectF(
           plane.content_region.left, plane.content_region.top,
           plane.content_region.right - plane.content_region.left,
           plane.content_region.bottom - plane.content_region.top));
diff --git a/src/cobalt/renderer/rasterizer/skia/image.h b/src/cobalt/renderer/rasterizer/skia/image.h
index 0c414a2..55ab890 100644
--- a/src/cobalt/renderer/rasterizer/skia/image.h
+++ b/src/cobalt/renderer/rasterizer/skia/image.h
@@ -84,7 +84,7 @@
 
   // If not-null, indicates a rectangle within the image in which the valid
   // pixel data is to be found.
-  virtual const math::Rect* GetContentRegion() const { return NULL; }
+  virtual const math::RectF* GetContentRegion() const { return NULL; }
 };
 
 // A multi-plane image is one where different channels may have different planes
diff --git a/src/cobalt/script/script_debugger.h b/src/cobalt/script/script_debugger.h
index 0d749a6..e4869ca 100644
--- a/src/cobalt/script/script_debugger.h
+++ b/src/cobalt/script/script_debugger.h
@@ -94,27 +94,30 @@
   virtual void Attach() = 0;
   virtual void Detach() = 0;
 
+  // Evaluate JavaScript code that is part of the debugger implementation, such
+  // that it does not get reported as debuggable source. Returns true on
+  // success, false if there is an exception. If out_result_utf8 is non-NULL, it
+  // will be set to hold the result of the script evaluation if the script
+  // succeeds, or an exception message if it fails.
+  virtual bool EvaluateDebuggerScript(const std::string& js_code,
+                                      std::string* out_result_utf8) = 0;
+
   // For engines like V8 that directly handle protocol commands.
   virtual bool CanDispatchProtocolMethod(const std::string& method) = 0;
   virtual void DispatchProtocolMessage(const std::string& message) = 0;
 
+  // Creates a JSON representation of an object.
+  // https://chromedevtools.github.io/devtools-protocol/1-3/Runtime#type-RemoteObject
+  virtual std::string CreateRemoteObject(const ValueHandleHolder& object,
+                                         const std::string& group) = 0;
+
   // For performance tracing of JavaScript methods.
   virtual void StartTracing(const std::vector<std::string>& categories,
                             TraceDelegate* trace_delegate) = 0;
   virtual void StopTracing() = 0;
 
-  // Code execution control. Implementations may use
-  // |Delegate::OnScriptDebuggerPause| to have the delegate handle the
-  // actual blocking of the thread in an engine-independent way.
-  virtual void Pause() = 0;
-  virtual void Resume() = 0;
-  virtual void SetBreakpoint(const std::string& script_id, int line_number,
-                             int column_number) = 0;
   virtual PauseOnExceptionsState SetPauseOnExceptions(
       PauseOnExceptionsState state) = 0;  // Returns the previous state.
-  virtual void StepInto() = 0;
-  virtual void StepOut() = 0;
-  virtual void StepOver() = 0;
 
  protected:
   virtual ~ScriptDebugger() {}
diff --git a/src/cobalt/script/shared/stub_script_debugger.cc b/src/cobalt/script/shared/stub_script_debugger.cc
index dfc539f..67f1a43 100644
--- a/src/cobalt/script/shared/stub_script_debugger.cc
+++ b/src/cobalt/script/shared/stub_script_debugger.cc
@@ -30,6 +30,11 @@
   void Attach() override { NOTIMPLEMENTED(); }
   void Detach() override { NOTIMPLEMENTED(); }
 
+  bool EvaluateDebuggerScript(const std::string& js_code,
+                              std::string* out_result_utf8) override {
+    return false;
+  }
+
   bool CanDispatchProtocolMethod(const std::string& method) override {
     NOTIMPLEMENTED();
     return false;
@@ -38,26 +43,23 @@
     NOTIMPLEMENTED();
   }
 
+  std::string CreateRemoteObject(const ValueHandleHolder& object,
+                                 const std::string& group) override {
+    NOTIMPLEMENTED();
+    return "{}";
+  }
+
   void StartTracing(const std::vector<std::string>& categories,
                     TraceDelegate* trace_delegate) {
     NOTIMPLEMENTED();
   }
   void StopTracing() override { NOTIMPLEMENTED(); }
 
-  void Pause() override { NOTIMPLEMENTED(); }
-  void Resume() override { NOTIMPLEMENTED(); }
-  void SetBreakpoint(const std::string& script_id, int line_number,
-                     int column_number) override {
-    NOTIMPLEMENTED();
-  }
   PauseOnExceptionsState SetPauseOnExceptions(
       PauseOnExceptionsState state) override {
     NOTIMPLEMENTED();
     return kNone;
   }
-  void StepInto() override { NOTIMPLEMENTED(); }
-  void StepOut() override { NOTIMPLEMENTED(); }
-  void StepOver() override { NOTIMPLEMENTED(); }
 };
 
 // Static factory method declared in public interface.
diff --git a/src/cobalt/script/v8c/v8c_global_environment.cc b/src/cobalt/script/v8c/v8c_global_environment.cc
index 0648fc0..9a54821 100644
--- a/src/cobalt/script/v8c/v8c_global_environment.cc
+++ b/src/cobalt/script/v8c/v8c_global_environment.cc
@@ -198,10 +198,18 @@
   std::vector<StackFrame> result;
   for (int i = 0; i < stack_trace->GetFrameCount(); i++) {
     v8::Local<v8::StackFrame> stack_frame = stack_trace->GetFrame(i);
-    result.emplace_back(
-        stack_frame->GetLineNumber(), stack_frame->GetColumn(),
-        *v8::String::Utf8Value(isolate_, stack_frame->GetFunctionName()),
-        *v8::String::Utf8Value(isolate_, stack_frame->GetScriptName()));
+    v8::String::Utf8Value function_name(isolate_,
+                                        stack_frame->GetFunctionName());
+    v8::String::Utf8Value script_name(isolate_, stack_frame->GetScriptName());
+    std::string function_name_str, script_name_str;
+    if (*function_name) {
+      function_name_str = std::string(*function_name);
+    }
+    if (*script_name) {
+      script_name_str = std::string(*script_name);
+    }
+    result.emplace_back(stack_frame->GetLineNumber(), stack_frame->GetColumn(),
+                        function_name_str, script_name_str);
   }
 
   return result;
diff --git a/src/cobalt/script/v8c/v8c_script_debugger.cc b/src/cobalt/script/v8c/v8c_script_debugger.cc
index 7991c1f..8a24b4d 100644
--- a/src/cobalt/script/v8c/v8c_script_debugger.cc
+++ b/src/cobalt/script/v8c/v8c_script_debugger.cc
@@ -17,10 +17,13 @@
 #include <sstream>
 #include <string>
 
+#include "base/debug/trace_event.h"
 #include "base/logging.h"
 #include "cobalt/base/polymorphic_downcast.h"
 #include "cobalt/script/v8c/conversion_helpers.h"
 #include "cobalt/script/v8c/v8c_tracing_controller.h"
+#include "include/inspector/Runtime.h"  // generated
+#include "nb/memory_scope.h"
 #include "v8/include/libplatform/v8-tracing.h"
 #include "v8/include/v8-inspector.h"
 
@@ -37,6 +40,28 @@
       IsolateFellowship::GetInstance()->platform->GetTracingController());
 }
 
+// Inspired by |CopyCharsUnsigned| in v8/src/utils.h
+std::string FromStringView(const v8_inspector::StringView& string_view) {
+  std::string string;
+  if (string_view.is8Bit()) {
+    string.assign(reinterpret_cast<const char*>(string_view.characters8()),
+                  string_view.length());
+  } else {
+    string.reserve(string_view.length());
+    const uint16_t* chars =
+        reinterpret_cast<const uint16_t*>(string_view.characters16());
+    for (int i = 0; i < string_view.length(); i++) {
+      string += chars[i];
+    }
+  }
+  return string;
+}
+
+v8_inspector::StringView ToStringView(const std::string& string) {
+  return v8_inspector::StringView(
+      reinterpret_cast<const uint8_t*>(string.c_str()), string.length());
+}
+
 }  // namespace
 
 V8cScriptDebugger::V8cScriptDebugger(
@@ -67,6 +92,48 @@
   inspector_->contextDestroyed(context);
 }
 
+bool V8cScriptDebugger::EvaluateDebuggerScript(const std::string& js_code,
+                                               std::string* out_result_utf8) {
+  TRACK_MEMORY_SCOPE("Javascript");
+  TRACE_EVENT0("cobalt::script", "V8cScriptDebugger::EvaluateDebuggerScript()");
+
+  v8::Isolate* isolate = global_environment_->isolate();
+  EntryScope entry_scope(isolate);
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
+  v8::TryCatch try_catch(isolate);
+  v8::MicrotasksScope microtasksScope(isolate,
+                                      v8::MicrotasksScope::kDoNotRunMicrotasks);
+
+  v8::Local<v8::String> source;
+  if (!v8::String::NewFromUtf8(isolate, js_code.c_str(),
+                               v8::NewStringType::kNormal, js_code.length())
+           .ToLocal(&source)) {
+    LOG(ERROR) << "Failed to convert source code to V8 UTF-8 string.";
+    return false;
+  }
+
+  v8::Local<v8::Value> result;
+  if (!inspector_->compileAndRunInternalScript(context, source)
+           .ToLocal(&result)) {
+    v8::String::Utf8Value exception(try_catch.Exception());
+    std::string string(*exception, exception.length());
+    if (string.empty()) string.assign("Unknown error");
+    LOG(ERROR) << "Debugger script error: " << string;
+    if (out_result_utf8) {
+      out_result_utf8->assign(std::move(string));
+    }
+    return false;
+  }
+
+  if (out_result_utf8) {
+    V8cExceptionState exception_state(isolate);
+    FromJSValue(isolate, result, kNoConversionFlags, &exception_state,
+                out_result_utf8);
+  }
+
+  return true;
+}
+
 ScriptDebugger::PauseOnExceptionsState V8cScriptDebugger::SetPauseOnExceptions(
     ScriptDebugger::PauseOnExceptionsState state) {
   DCHECK(inspector_session_);
@@ -88,6 +155,22 @@
       reinterpret_cast<const uint8_t*>(message.c_str()), message.length()));
 }
 
+std::string V8cScriptDebugger::CreateRemoteObject(
+    const ValueHandleHolder& object, const std::string& group) {
+  const V8cValueHandleHolder* v8_value_handle_holder =
+      base::polymorphic_downcast<const V8cValueHandleHolder*>(&object);
+
+  v8::Isolate* isolate = v8_value_handle_holder->isolate();
+  EntryScope entry_scope(isolate);
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
+  v8::Local<v8::Value> v8_value = v8_value_handle_holder->v8_value();
+
+  std::unique_ptr<v8_inspector::protocol::Runtime::API::RemoteObject>
+      remote_object = inspector_session_->wrapObject(
+          context, v8_value, ToStringView(group), false /*generatePreview*/);
+  return FromStringView(remote_object->toJSONString()->string());
+}
+
 void V8cScriptDebugger::StartTracing(const std::vector<std::string>& categories,
                                      TraceDelegate* trace_delegate) {
   V8cTracingController* tracing_controller = GetTracingController();
@@ -181,24 +264,6 @@
   }
 }
 
-// Inspired by |CopyCharsUnsigned| in v8/src/utils.h
-std::string V8cScriptDebugger::FromStringView(
-    const v8_inspector::StringView& string_view) {
-  std::string string;
-  if (string_view.is8Bit()) {
-    string.assign(reinterpret_cast<const char*>(string_view.characters8()),
-                  string_view.length());
-  } else {
-    string.reserve(string_view.length());
-    const uint16_t* chars =
-        reinterpret_cast<const uint16_t*>(string_view.characters16());
-    for (int i = 0; i < string_view.length(); i++) {
-      string += chars[i];
-    }
-  }
-  return string;
-}
-
 }  // namespace v8c
 
 // Static factory method declared in public interface.
diff --git a/src/cobalt/script/v8c/v8c_script_debugger.h b/src/cobalt/script/v8c/v8c_script_debugger.h
index 5097a48..bb32765 100644
--- a/src/cobalt/script/v8c/v8c_script_debugger.h
+++ b/src/cobalt/script/v8c/v8c_script_debugger.h
@@ -38,24 +38,21 @@
   void Attach() override { attached_ = true; }
   void Detach() override { attached_ = false; }
 
+  bool EvaluateDebuggerScript(const std::string& js_code,
+                              std::string* out_result_utf8) override;
+
   bool CanDispatchProtocolMethod(const std::string& method) override;
   void DispatchProtocolMessage(const std::string& message) override;
 
+  std::string CreateRemoteObject(const ValueHandleHolder& object,
+                                 const std::string& group) override;
+
   void StartTracing(const std::vector<std::string>& categories,
                     TraceDelegate* trace_delegate) override;
   void StopTracing() override;
 
-  void Pause() override { NOTIMPLEMENTED(); }
-  void Resume() override { NOTIMPLEMENTED(); }
-  void SetBreakpoint(const std::string& script_id, int line_number,
-                     int column_number) override {
-    NOTIMPLEMENTED();
-  }
   PauseOnExceptionsState SetPauseOnExceptions(
       PauseOnExceptionsState state) override;
-  void StepInto() override { NOTIMPLEMENTED(); }
-  void StepOut() override { NOTIMPLEMENTED(); }
-  void StepOver() override { NOTIMPLEMENTED(); }
 
   // v8_inspector::V8InspectorClient implementation.
   void runMessageLoopOnPause(int contextGroupId) override;
@@ -78,8 +75,6 @@
   void flushProtocolNotifications() override {}
 
  private:
-  std::string FromStringView(const v8_inspector::StringView& string_view);
-
   V8cGlobalEnvironment* global_environment_;
   Delegate* delegate_;
   std::unique_ptr<v8_inspector::V8Inspector> inspector_;
diff --git a/src/cobalt/site/docs/development/setup-android.md b/src/cobalt/site/docs/development/setup-android.md
new file mode 100644
index 0000000..46d71ab
--- /dev/null
+++ b/src/cobalt/site/docs/development/setup-android.md
@@ -0,0 +1,255 @@
+---
+layout: doc
+title: "Set up your environment - Android"
+---
+
+These instructions explain how to set up Cobalt for your workstation and Android
+device. The package being built here is referred to as CoAT (Cobalt on Android TV).
+
+## Preliminary Setup
+
+1.  Download 'depot_tools', which is used to build the Cobalt code. An easy
+    option is to put them in `~/depot_tools`. Clone the tools, by running the
+    following command:
+
+    ```
+    cd ~/
+    git clone https://cobalt.googlesource.com/depot_tools
+    ```
+
+1.  Add your 'depot_tools' directory to the end of your PATH variable. We
+    recommend adding something like this to your `.bashrc` or `.profile` file:
+
+    ```
+    PATH=${PATH}:/path/to/depot_tools
+    ```
+
+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
+    with 'android-arm' or 'android-arm64' to target those architectures.) The
+    SDK and NDK will be downloaded and installed into a `starboard-toolchains`
+    directory as needed. If prompted, read and accept the license agreement to
+    proceed forward.
+
+    **Note:** If you have trouble building with an error referencing the
+    `debug.keystore` you may need to set one up on your system:
+
+    ```
+    keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000
+    ```
+
+## Setup your device
+
+Configure your device to be in developer mode:
+
+1.  From `Settings`, in the `Device` row, select `About`.
+1.  Scroll down to and click on `Build` several times until a toast appears with
+    the message, "You are now a developer."
+1.  In the newly added "developer options" settings menu, make sure USB
+    debugging is enabled.
+
+## Setup your workstation environment
+
+For manually installing Android Studio and the SDK.
+
+**Note:** Instructions moving forward are assuming a Linux environment.
+
+1.  Complete the Preliminary Setup above.
+1.  Launch Android Studio.
+1.  Android Studio may still prompt to install an SDK if this is the very first
+    time you've run it.
+Go ahead and click 'yes' to open the SDK manager to install the following:
+    *   Edit "Android SDK location" and set the path to:
+    `$HOME/starboard-toolchains/AndroidSdk`
+      *  NOTE: We use the same SDK for the IDE and the gyp/ninja build. This
+         directory will already exist since you've already run gyp_cobalt for an
+         android target, so you'll see a warning in the setup wizard that an SDK
+         was detected, which is okay.
+          * The path may also be `$HOME/cobalt-toolchains/AndroidSdk` if you
+            previously had an older environment configured on your machine -
+            this is okay.
+        *  Select both `Android SDK` and `Android SDK Platform` (whatever
+           current version is presented should be fine)
+    *   On the `SDK Platforms` tab select:
+        *  Android API 28 (or whatever was already installed by default)
+    *   On the "SDK Tools" tab select
+(most of these should already be installed since you already ran gyp):
+        1.  Android SDK Build-Tools (e.g. 28.0.3)
+        1.  CMake
+        1.  LLDB
+        1.  Android Emulator
+        1.  Android SDK Platform-Tools
+        1.  Android SDK Tools
+        1.  NDK
+        1.  Support Repository > Android Support Repository
+        1.  Support Repository > Google Repository
+1.  At the welcome screen, choose to open an existing Android Studio project,
+    and choose the project in your Cobalt repo at `starboard/android/apk` (just
+    select the directory). This is "coat" (Cobalt On Android TV).
+    *   NOTE: Do not let it update the 'Android Gradle Plugin' if it prompts for
+        that, use the 'Don't remind me again' button. If you inadvertently let
+        it upgrade, you can just revert the change to `build.gradle` in your git
+        repo.
+1.  You may see a popup "Unregistered VCS roots detected" indicating that it has
+    detected the cobalt git repo. If you want to use git integration in
+    Android Studio, you can add the roots, or if not then choose to ignore them.
+1.  If you didn't already get prompted to install the SDK, do it now by going to
+    Tools -> SDK Manager (or
+    <img src="/images/android-sdk-manager-icon.png" style="display:inline;"></img>
+    ) on the toolbar) and making the same choices as shown in step 4.
+1.  Make a new virtual device (= emulator) via
+    Tools -> AVD Manager (or
+    <img src="/images/android-avd-manager-icon.png" style="display: inline;"></img>
+    on the toolbar).
+      *   Category: TV -> Android TV (720p)
+      *   System image: Pie (API 28) x86 (you'll have to download the image)
+          (The code should work on any API 21+, but there's a bug in the
+          emulator preventing API 21 from working, but it does work on API 21
+          hardware)
+      *   You may be prompted to install some Intel virtualization drivers
+          and/or change some BIOS settings to enable it, which will require you
+          to reboot your machine.
+1.  Run this AVD device. You can keep it running. Remember to restart it if your
+    machine reboots. (Or you can start it when prompted for the target device if
+    launching the app from Android Studio.)
+
+## Basic Build, Install, and Run (command-line based)
+
+1.  Complete the Preliminary Setup above
+1.  Generate the cobalt.apk by building the "cobalt_deploy" target
+
+    ```
+    ninja -C out/android-x86_gold cobalt_deploy
+    ```
+
+    Output can be found in the corresponding `out/android-x86_gold` directory.
+
+    **Note:** If you have trouble building with an error referencing the
+    `debug.keystore` you may need to set one up on your system:
+
+    ```
+    keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000
+    ```
+
+1.  Install the resulting APK into your test device with adb:
+
+    ```
+    adb install out/android-x86_gold/cobalt.apk
+    ```
+
+1.  Start the application with:
+
+    ```
+    adb shell am start dev.cobalt.coat/dev.cobalt.app.MainActivity
+    ```
+
+1.  For command line parameters use the `--esa` flag to specify the "args" array
+    as comma-separated values (with characters backslash-escaped as needed to
+    make it through both the shell on your workstation and the shell on the
+    device), e.g.:
+
+    ```
+    adb shell am start --esa args --flag_arg --value_arg=something dev.cobalt.coat
+    ```
+
+1.  To monitor log output, watch logcat in another shell with a filter for
+    starboard messages:
+
+    ```
+    adb logcat -s starboard:*
+    ```
+
+1.  To kill any existing running application process (even if it's no longer the
+    active app) use:
+
+    ```
+    adb shell am force-stop dev.cobalt.coat
+    ```
+
+## Building/running/debugging (Android Studio IDE)
+
+1.  Manually run `cobalt/build/gyp_cobalt android-x86` in a shell. (You should
+    do this after each time you sync your repo)
+1.  From the initial setup above, you should have opened the Android Studio
+    project checked in at `starboard/android/apk`.
+1.  In the sidebar on the left, you should see `app` appear as bolded top-level
+    item.  If you don't see this, restart Android Studio.
+1.  If you didn't install your Cobalt depot_tools in the standard location
+    (`$HOME/depot_tools`), then make `starboard/android/apk/app/.cobaltrc` and
+    set the `DEPOT_TOOLS` envvar to the path to where you installed them (or
+    make a symlink in the standard location to where you have them).
+1.  To run the app and attach the debugger: Run -> Debug 'app' (or
+    <img src="/images/android-debug-icon.png" style="display: inline;"></img>
+    in the toolbar)
+1.  If it's taking awhile, it's probably the ninja build. You can see that it is
+    still processing by looking for a rotating spinner labeled "Gradle Build
+    Running" on the bottom bar.
+
+    **Note:** If you have trouble building with an error referencing the
+    `debug.keystore` you may need to set one up on your system:
+
+    ```
+    keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000
+    ```
+
+1.  To add command line parameters add `--esa` to specify the "args" array as
+    comma-separated values (with characters like '&' backslash-escaped to make
+    it through the launch command) under:
+
+    Run -> Edit Configurations… -> "app" -> General -> Launch Options -> Launch Flags
+
+    e.g. To run with a different URL: `--esa args --url=<DIFFERENT_URL>`
+1.  To monitor log output, see the `Logcat` on the bottom-left of the IDE. You
+    can enter "starboard" in the search bubble to filter the output.
+1.  To kill the app go to Run -> Stop, or click the red square stop button
+    either on the top toolbar, or in the debugger on the bottom-left of the IDE.
+1.  To set breakpoints in native code, just open the relevant source file with
+    File -> Open… (sorry, files outside the apk project can't appear in the
+    Project panel of the IDE) and click in the gutter of the relevant line.
+    (Once you have one C++ file open, the path breadcrumbs at the top are useful
+    to open other nearby files.)
+
+
+## Running Tests
+
+The test target itself (e.g. nplb) just builds an .so file (e.g. libnplb.so). To
+run that on a device, it needs to be packaged into an APK, which is done by the
+associated "deploy" target (e.g. nplb_deploy). The Starboard test runner does
+all this for you, so just use that to build and run tests. For example, to
+build and run "devel" NPLB on an ARM64 device, from the top 'src' directory:
+
+```
+starboard/tools/testing/test_runner.py -p android-arm64 -c devel -b -r -t nplb
+```
+
+If you want to debug a test, you can run it from Android Studio. Edit
+`build.gradle` in the 'app' module (not to the one in the top 'apk' module) to
+change `DEFAULT_COBALT_TARGET` to be the name of the test you want to debug
+instead of 'cobalt'. Then you can set breakpoints, etc. in the test the same as
+when debugging Cobalt.
+
+## Removing the Cobalt Android Environment
+
+1.  Unset ANDROID_HOME and or ANDROID_NDK_HOME in your shell and in .bashrc
+1.  Delete the SDK:
+
+    ```
+    rm -rf ~/starboard-toolchains/AndroidSdk
+    ```
+
+1.  Delete NDK toolchains:
+
+    ```
+    rm -rf  ~/starboard-toolchains/android*
+    ```
+
+1.  Delete cached Android files:
+
+    ```
+    rm -rf ~/.android
+    ```
+
+    **NOTE:** Removing this directory will remove all signing keys even for
+    different projects, so only delete this if you truly want to remove the
+    entire Cobalt and Android Studio environment.
diff --git a/src/cobalt/site/docs/development/setup-linux.md b/src/cobalt/site/docs/development/setup-linux.md
index 7320c00..ed5aff6 100644
--- a/src/cobalt/site/docs/development/setup-linux.md
+++ b/src/cobalt/site/docs/development/setup-linux.md
@@ -31,11 +31,11 @@
 
     ```
     $ sudo apt-get install bison build-essential coreutils git gperf \
-           libasound2-dev libavformat-dev libavresample-dev \
+           libaom-dev libasound2-dev libavformat-dev libavresample-dev \
            libdirectfb-dev libdirectfb-extra libpulse-dev \
-           libgl1-mesa-dev libgles2-mesa-dev libx11-dev \
+           libgl1-mesa-dev libgles2-mesa-dev libvpx-dev libx11-dev \
            libxcomposite-dev libxcomposite1 libxrender-dev libxrender1 \
-           libxpm-dev m4 ruby tar xserver-xephyr xz-utils yasm
+           libxpm-dev m4 python ruby tar xserver-xephyr xz-utils yasm
     ```
 
 1.  Install the latest version of the standard C++ header files (`libstdc++`).
diff --git a/src/cobalt/site/docs/overview.md b/src/cobalt/site/docs/overview.md
index db3f038..ecfae9e 100644
--- a/src/cobalt/site/docs/overview.md
+++ b/src/cobalt/site/docs/overview.md
@@ -43,8 +43,8 @@
     *   Cobalt precompiles a set of shaders that are sufficient to express all
         graphical effects, thereby accommodating platforms that cannot compile
         shaders at runtime.
-    *   Cobalt requires a compliant C++03 compiler, allowing it to reach
-        platforms with legacy toolchains.
+    *   Cobalt requires a compliant C++11 compiler, allowing it to reach
+        platforms with toolchains that don't support the newest C++17 features.
 
 *   **Small footprint**
     *   Cobalt is optimized for memory. Its surface cache never exceeds a
@@ -69,20 +69,20 @@
 
 ### Porters
 
-Porters should begin with the [porting guide](/cobalt/starboard/porting.html),
+Porters should begin with the [porting guide](/starboard/porting.html),
 which explains how to use Starboard, Cobalt's porting layer, to customize the
 platform-specific functionality that Cobalt uses. There are several reference
 documents to help porters customize configuration files and to implement
 module-specific functionality. The [Testing with
-NPLB](/cobalt/starboard/testing.html) document provides an overview of
+NPLB](/starboard/testing.html) document provides an overview of
 Starboard's compliance test suite.
 
 ### Developers
 
 Developers can follow the setup instructions for
-[Linux](/cobalt/development/setup-linux.html) or
-[RasPi](/cobalt/development/setup-raspi.html) to set up their Cobalt development
+[Linux](/development/setup-linux.html) or
+[RasPi](/development/setup-raspi.html) to set up their Cobalt development
 environment, fetch a copy of the Cobalt code repository, and build a Cobalt
-binary. The [Cobalt support](/cobalt/development/reference/supported-features.html)
+binary. The [Cobalt support](/development/reference/supported-features.html)
 guide lists the HTML elements, CSS properties, CSS selectors, and JavaScript Web
 APIs that developers can use in their Cobalt applications.
diff --git a/src/cobalt/site/images/android-avd-manager-icon.png b/src/cobalt/site/images/android-avd-manager-icon.png
new file mode 100644
index 0000000..e74c038
--- /dev/null
+++ b/src/cobalt/site/images/android-avd-manager-icon.png
Binary files differ
diff --git a/src/cobalt/site/images/android-debug-icon.png b/src/cobalt/site/images/android-debug-icon.png
new file mode 100644
index 0000000..02e7b6f
--- /dev/null
+++ b/src/cobalt/site/images/android-debug-icon.png
Binary files differ
diff --git a/src/cobalt/site/images/android-sdk-manager-icon.png b/src/cobalt/site/images/android-sdk-manager-icon.png
new file mode 100644
index 0000000..4ccaddb
--- /dev/null
+++ b/src/cobalt/site/images/android-sdk-manager-icon.png
Binary files differ
diff --git a/src/cobalt/speech/google_speech_service.cc b/src/cobalt/speech/google_speech_service.cc
index 94939d2..c0141b4 100644
--- a/src/cobalt/speech/google_speech_service.cc
+++ b/src/cobalt/speech/google_speech_service.cc
@@ -253,6 +253,19 @@
   // no-op.
 }
 
+// static
+base::optional<std::string> GoogleSpeechService::GetSpeechAPIKey() {
+  const int kSpeechApiKeyLength = 100;
+  char buffer[kSpeechApiKeyLength] = {0};
+  bool result = SbSystemGetProperty(kSbSystemPropertySpeechApiKey, buffer,
+                                    SB_ARRAY_SIZE_INT(buffer));
+  if (result) {
+    return std::string(buffer);
+  } else {
+    return base::nullopt;
+  }
+}
+
 void GoogleSpeechService::StartInternal(const SpeechRecognitionConfig& config,
                                         int sample_rate) {
   DCHECK_EQ(thread_.message_loop(), MessageLoop::current());
@@ -288,17 +301,10 @@
   up_url = AppendQueryParameter(up_url, "pair", pair);
   up_url = AppendQueryParameter(up_url, "output", "pb");
 
-  const char* speech_api_key = "";
-#if defined(OS_STARBOARD)
-  const int kSpeechApiKeyLength = 100;
-  char buffer[kSpeechApiKeyLength] = {0};
-  bool result = SbSystemGetProperty(kSbSystemPropertySpeechApiKey, buffer,
-                                    SB_ARRAY_SIZE_INT(buffer));
-  SB_DCHECK(result);
-  speech_api_key = result ? buffer : "";
-#endif  // defined(OS_STARBOARD)
+  base::optional<std::string> api_key = GetSpeechAPIKey();
+  SB_DCHECK(api_key);
 
-  up_url = AppendQueryParameter(up_url, "key", speech_api_key);
+  up_url = AppendQueryParameter(up_url, "key", api_key.value_or(""));
 
   // Language is required. If no language is specified, use the system language.
   if (!config.lang.empty()) {
diff --git a/src/cobalt/speech/google_speech_service.h b/src/cobalt/speech/google_speech_service.h
index d3fefc5..5e9d313 100644
--- a/src/cobalt/speech/google_speech_service.h
+++ b/src/cobalt/speech/google_speech_service.h
@@ -78,6 +78,8 @@
   void OnURLFetchUploadProgress(const net::URLFetcher* /*source*/,
                                 int64 /*current*/, int64 /*total*/) override {}
 
+  static base::optional<std::string> GetSpeechAPIKey();
+
  private:
   void StartInternal(const SpeechRecognitionConfig& config, int sample_rate);
   void StopInternal();
diff --git a/src/cobalt/speech/speech_recognition_manager.cc b/src/cobalt/speech/speech_recognition_manager.cc
index 6ee4ccb..8abe28c 100644
--- a/src/cobalt/speech/speech_recognition_manager.cc
+++ b/src/cobalt/speech/speech_recognition_manager.cc
@@ -17,6 +17,7 @@
 #include "base/bind.h"
 #include "cobalt/dom/dom_exception.h"
 #include "cobalt/speech/speech_configuration.h"
+#include "cobalt/speech/speech_recognition_error.h"
 #if defined(SB_USE_SB_SPEECH_RECOGNIZER)
 #include "cobalt/speech/starboard_speech_recognizer.h"
 #else
@@ -40,10 +41,12 @@
   recognizer_.reset(new StarboardSpeechRecognizer(base::Bind(
       &SpeechRecognitionManager::OnEventAvailable, base::Unretained(this))));
 #else
-  recognizer_.reset(new CobaltSpeechRecognizer(
-      network_module, microphone_options,
-      base::Bind(&SpeechRecognitionManager::OnEventAvailable,
-                 base::Unretained(this))));
+  if (GoogleSpeechService::GetSpeechAPIKey()) {
+    recognizer_.reset(new CobaltSpeechRecognizer(
+        network_module, microphone_options,
+        base::Bind(&SpeechRecognitionManager::OnEventAvailable,
+                   base::Unretained(this))));
+  }
 #endif  // defined(SB_USE_SB_SPEECH_RECOGNIZER)
 }
 
@@ -61,6 +64,15 @@
     return;
   }
 
+  // If no recognizer is available on this platform, immediately generate a
+  // "no-speech" error.
+  //   https://w3c.github.io/speech-api/speechapi.html#speechreco-events
+  if (!recognizer_) {
+    OnEventAvailable(
+        new SpeechRecognitionError(kSpeechRecognitionErrorCodeNoSpeech, ""));
+    return;
+  }
+
   recognizer_->Start(config);
   state_ = kStarted;
 }
diff --git a/src/cobalt/system_window/input_event.h b/src/cobalt/system_window/input_event.h
index 9490343..e38b312 100644
--- a/src/cobalt/system_window/input_event.h
+++ b/src/cobalt/system_window/input_event.h
@@ -59,18 +59,18 @@
     kForwardButton = 1 << 8,
   };
 
-  InputEvent(SbTimeMonotonic timestamp, Type type, int device_id,
-             int key_code, uint32 modifiers, bool is_repeat,
+  InputEvent(SbTimeMonotonic timestamp, Type type, int device_id, int key_code,
+             uint32 modifiers, bool is_repeat,
              const math::PointF& position = math::PointF(),
              const math::PointF& delta = math::PointF()
 #if SB_API_VERSION >= 6
-             ,
+                 ,
              float pressure = 0, const math::PointF& size = math::PointF(),
              const math::PointF& tilt = math::PointF()
 #endif
 #if SB_HAS(ON_SCREEN_KEYBOARD)
-             ,
-             const std::string& input_text = ""
+                 ,
+             const std::string& input_text = "", bool is_composing = false
 #endif  // SB_HAS(ON_SCREEN_KEYBOARD)
              )
       : timestamp_(timestamp),
@@ -89,7 +89,8 @@
 #endif
 #if SB_HAS(ON_SCREEN_KEYBOARD)
         ,
-        input_text_(input_text)
+        input_text_(input_text),
+        is_composing_(is_composing)
 #endif  // SB_HAS(ON_SCREEN_KEYBOARD)
   {
   }
@@ -111,6 +112,7 @@
 #endif
 #if SB_HAS(ON_SCREEN_KEYBOARD)
   const std::string& input_text() const { return input_text_; }
+  bool is_composing() const { return is_composing_; }
 #endif  // SB_HAS(ON_SCREEN_KEYBOARD)
 
   BASE_EVENT_SUBCLASS(InputEvent);
@@ -131,6 +133,7 @@
 #endif
 #if SB_HAS(ON_SCREEN_KEYBOARD)
   std::string input_text_;
+  bool is_composing_;
 #endif  // SB_HAS(ON_SCREEN_KEYBOARD)
 };
 
diff --git a/src/cobalt/system_window/system_window.cc b/src/cobalt/system_window/system_window.cc
index c4b6a24..76accb9 100644
--- a/src/cobalt/system_window/system_window.cc
+++ b/src/cobalt/system_window/system_window.cc
@@ -159,13 +159,13 @@
 
 #if SB_HAS(ON_SCREEN_KEYBOARD)
   scoped_ptr<InputEvent> input_event(
-      new InputEvent(timestamp, type, data.device_id,
-                     key_code, modifiers, is_repeat,
-                     math::PointF(data.position.x, data.position.y),
+      new InputEvent(timestamp, type, data.device_id, key_code, modifiers,
+                     is_repeat, math::PointF(data.position.x, data.position.y),
                      math::PointF(data.delta.x, data.delta.y), pressure,
                      math::PointF(data.size.x, data.size.y),
                      math::PointF(data.tilt.x, data.tilt.y),
-                     data.input_text ? data.input_text : ""));
+                     data.input_text ? data.input_text : "",
+                     data.is_composing ? data.is_composing : false));
 #else   // SB_HAS(ON_SCREEN_KEYBOARD)
   scoped_ptr<InputEvent> input_event(
       new InputEvent(timestamp, type, data.device_id,
diff --git a/src/cobalt/version.h b/src/cobalt/version.h
index 55a78f4..9a3f6f8 100644
--- a/src/cobalt/version.h
+++ b/src/cobalt/version.h
@@ -35,6 +35,6 @@
 //                  release is cut.
 //.
 
-#define COBALT_VERSION "19.master.0"
+#define COBALT_VERSION "19.android.1"
 
 #endif  // COBALT_VERSION_H_
diff --git a/src/cobalt/web_animations/animation.cc b/src/cobalt/web_animations/animation.cc
index f640b04..8a8cf04 100644
--- a/src/cobalt/web_animations/animation.cc
+++ b/src/cobalt/web_animations/animation.cc
@@ -12,8 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "base/logging.h"
 #include "cobalt/web_animations/animation.h"
+
+#include "base/logging.h"
+#include "cobalt/web_animations/animation_set.h"
 #include "cobalt/web_animations/keyframe_effect_read_only.h"
 
 namespace cobalt {
@@ -22,6 +24,9 @@
 Animation::Animation(const scoped_refptr<AnimationEffectReadOnly>& effect,
                      const scoped_refptr<AnimationTimeline>& timeline)
     : effect_(effect) {
+  // Register with the keyframe effect's target so that this Animation can
+  // be referenced from the target, as per the web animations specification:
+  //   https://www.w3.org/TR/web-animations-1/#dom-animatable-getanimations
   const KeyframeEffectReadOnly* keyframe_effect =
       base::polymorphic_downcast<const KeyframeEffectReadOnly*>(effect.get());
   if (keyframe_effect) {
@@ -31,10 +36,14 @@
 }
 
 void Animation::set_timeline(const scoped_refptr<AnimationTimeline>& timeline) {
+  // Deregister from the timeline we were previously registered to.
   if (timeline_) {
     timeline_->Deregister(this);
   }
 
+  // Register with the timeline so that this Animation can be referenced from
+  // that timeline, as per the web animations specifications:
+  //   https://www.w3.org/TR/web-animations-1/#dom-document-getanimations
   if (timeline) {
     timeline->Register(this);
   }
@@ -131,6 +140,14 @@
   if (keyframe_effect) {
     keyframe_effect->target()->Deregister(this);
   }
+
+  // Make a copy of the animation set so that our loop iteration is unaffected
+  // by the animation removals as we make them.
+  std::set<scoped_refptr<AnimationSet>> contained_in_animation_sets =
+      contained_in_animation_sets_;
+  for (const auto& animation_set : contained_in_animation_sets) {
+    animation_set->RemoveAnimation(this);
+  }
 }
 
 void Animation::UpdatePendingTasks() {
@@ -195,6 +212,15 @@
   return event_handler.Pass();
 }
 
+void Animation::OnAddedToAnimationSet(const scoped_refptr<AnimationSet>& set) {
+  contained_in_animation_sets_.insert(set);
+}
+
+void Animation::OnRemovedFromAnimationSet(
+    const scoped_refptr<AnimationSet>& set) {
+  contained_in_animation_sets_.erase(set);
+}
+
 void Animation::RemoveEventHandler(EventHandler* handler) {
   // Called when the EventHandler object is destructed, this "deregisters"
   // the event handler from the Animation's event handler set.
diff --git a/src/cobalt/web_animations/animation.h b/src/cobalt/web_animations/animation.h
index 310719e..9cdb5b2 100644
--- a/src/cobalt/web_animations/animation.h
+++ b/src/cobalt/web_animations/animation.h
@@ -29,6 +29,8 @@
 namespace cobalt {
 namespace web_animations {
 
+class AnimationSet;
+
 // Animations are represented in the Web Animations API by the Animation
 // interface.
 //   https://www.w3.org/TR/2015/WD-web-animations-1-20150707/#the-animation-interface
@@ -169,8 +171,21 @@
   // Called when the animation's effect enters its after phase.
   void OnEnterAfterPhase();
 
+  // It is possible for an Animation to be inserted into a AnimationSet, such
+  // that is not discoverable by Web APIs, so we track all our references from
+  // all AnimationSets so that we can remove ourselves from them when the
+  // animation is ready to destroy itself.
+  void OnAddedToAnimationSet(const scoped_refptr<AnimationSet>& set);
+
+  // Called by AnimationSet when removed from an AnimationSet.
+  void OnRemovedFromAnimationSet(const scoped_refptr<AnimationSet>& set);
+
   scoped_refptr<AnimationEffectReadOnly> effect_;
   scoped_refptr<AnimationTimeline> timeline_;
+
+  // A list of animation sets that contain this animation.
+  std::set<scoped_refptr<AnimationSet>> contained_in_animation_sets_;
+
   Data data_;
 
   // A list of event handlers that are interested in receiving callbacks when
@@ -187,6 +202,9 @@
 
   friend class EventHandler;
 
+  // So that we can track which AnimationSets this animation is added to.
+  friend class AnimationSet;
+
   DISALLOW_COPY_AND_ASSIGN(Animation);
 };
 
diff --git a/src/cobalt/web_animations/animation_set.cc b/src/cobalt/web_animations/animation_set.cc
index bfae755..bd5dcd1 100644
--- a/src/cobalt/web_animations/animation_set.cc
+++ b/src/cobalt/web_animations/animation_set.cc
@@ -23,10 +23,12 @@
 
 void AnimationSet::AddAnimation(Animation* animation) {
   animations_.insert(animation);
+  animation->OnAddedToAnimationSet(this);
 }
 
 void AnimationSet::RemoveAnimation(Animation* animation) {
   animations_.erase(animation);
+  animation->OnRemovedFromAnimationSet(this);
 }
 
 bool AnimationSet::IsPropertyAnimated(cssom::PropertyKey property_name) const {
diff --git a/src/cobalt/web_animations/animation_set.h b/src/cobalt/web_animations/animation_set.h
index 16fdfdf..1311425 100644
--- a/src/cobalt/web_animations/animation_set.h
+++ b/src/cobalt/web_animations/animation_set.h
@@ -33,6 +33,9 @@
 // acquiring the set of all properties affected.
 class AnimationSet : public base::RefCounted<AnimationSet> {
  public:
+  // We use raw pointers here to avoid cyclic references; AnimationSets
+  // are referenced by Animations themselves and it's expected that the
+  // Animation destructor will deregister all sets it's contained within.
   typedef std::set<Animation*> InternalSet;
 
   AnimationSet();
diff --git a/src/cobalt/xhr/xml_http_request.cc b/src/cobalt/xhr/xml_http_request.cc
index 8900fee..64797ac 100644
--- a/src/cobalt/xhr/xml_http_request.cc
+++ b/src/cobalt/xhr/xml_http_request.cc
@@ -709,7 +709,6 @@
   UNREFERENCED_PARAMETER(source);
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK_NE(state_, kDone);
-  ChangeState(kLoading);
 
   // Preserve the response body only for regular XHR requests. Fetch requests
   // process the response in pieces, so do not need to keep the whole response.
@@ -718,6 +717,9 @@
                           download_data->size());
   }
 
+  // Signal to JavaScript that new data is now available.
+  ChangeState(kLoading);
+
   if (fetch_callback_) {
     script::Handle<script::Uint8Array> data =
         script::Uint8Array::New(settings_->global_environment(),
diff --git a/src/crypto/crypto.gyp b/src/crypto/crypto.gyp
index a6d89e5..3705097 100644
--- a/src/crypto/crypto.gyp
+++ b/src/crypto/crypto.gyp
@@ -10,8 +10,8 @@
       'dependencies': [
         '../base/base.gyp:base',
         '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
-        '../third_party/openssl/openssl.gyp:openssl',
         '<(DEPTH)/starboard/starboard.gyp:starboard',
+        '<(DEPTH)/third_party/boringssl/boringssl.gyp:crypto',
       ],
       'defines': [
         'CRYPTO_IMPLEMENTATION',
diff --git a/src/crypto/ec_signature_creator_unittest.cc b/src/crypto/ec_signature_creator_unittest.cc
index df73bec..9489e3a 100644
--- a/src/crypto/ec_signature_creator_unittest.cc
+++ b/src/crypto/ec_signature_creator_unittest.cc
@@ -53,17 +53,9 @@
   std::vector<uint8> public_key_info;
   ASSERT_TRUE(key_original->ExportPublicKey(&public_key_info));
 
-  // This is the algorithm ID for SHA-256 with EC encryption.
-  const uint8 kECDSAWithSHA256AlgorithmID[] = {
-    0x30, 0x0c,
-      0x06, 0x08,
-        0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02,
-      0x05, 0x00
-  };
   crypto::SignatureVerifier verifier;
   ASSERT_TRUE(verifier.VerifyInit(
-      kECDSAWithSHA256AlgorithmID, sizeof(kECDSAWithSHA256AlgorithmID),
-      &signature[0], signature.size(),
+      crypto::SignatureVerifier::ECDSA_SHA256, &signature[0], signature.size(),
       &public_key_info.front(), public_key_info.size()));
 
   verifier.VerifyUpdate(reinterpret_cast<const uint8*>(data.c_str()),
diff --git a/src/crypto/rsa_private_key_openssl.cc b/src/crypto/rsa_private_key_openssl.cc
index 64a627e..0061db1 100644
--- a/src/crypto/rsa_private_key_openssl.cc
+++ b/src/crypto/rsa_private_key_openssl.cc
@@ -126,11 +126,11 @@
 
 RSAPrivateKey* RSAPrivateKey::Copy() const {
   scoped_ptr<RSAPrivateKey> copy(new RSAPrivateKey());
-  RSA* rsa = EVP_PKEY_get1_RSA(key_);
+  bssl::UniquePtr<RSA> rsa(EVP_PKEY_get1_RSA(key_));
   if (!rsa)
     return NULL;
   copy->key_ = EVP_PKEY_new();
-  if (!EVP_PKEY_set1_RSA(copy->key_, rsa))
+  if (!EVP_PKEY_set1_RSA(copy->key_, rsa.get()))
     return NULL;
   return copy.release();
 }
diff --git a/src/crypto/signature_creator_unittest.cc b/src/crypto/signature_creator_unittest.cc
index 2d69223..881de91 100644
--- a/src/crypto/signature_creator_unittest.cc
+++ b/src/crypto/signature_creator_unittest.cc
@@ -36,17 +36,10 @@
   std::vector<uint8> public_key_info;
   ASSERT_TRUE(key_original->ExportPublicKey(&public_key_info));
 
-  // This is the algorithm ID for SHA-1 with RSA encryption.
-  // TODO(aa): Factor this out into some shared location.
-  const uint8 kSHA1WithRSAAlgorithmID[] = {
-    0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
-    0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00
-  };
   crypto::SignatureVerifier verifier;
   ASSERT_TRUE(verifier.VerifyInit(
-      kSHA1WithRSAAlgorithmID, sizeof(kSHA1WithRSAAlgorithmID),
-      &signature.front(), signature.size(),
-      &public_key_info.front(), public_key_info.size()));
+      crypto::SignatureVerifier::RSA_PKCS1_SHA1, &signature.front(),
+      signature.size(), &public_key_info.front(), public_key_info.size()));
 
   verifier.VerifyUpdate(reinterpret_cast<const uint8*>(data.c_str()),
                         data.size());
diff --git a/src/crypto/signature_verifier.h b/src/crypto/signature_verifier.h
index 505ed0c..e181330 100644
--- a/src/crypto/signature_verifier.h
+++ b/src/crypto/signature_verifier.h
@@ -25,21 +25,17 @@
   SignatureVerifier();
   ~SignatureVerifier();
 
+  enum SignatureAlgorithm {
+    RSA_PKCS1_SHA1,
+    RSA_PKCS1_SHA256,
+    ECDSA_SHA256,
+  };
+
   // Streaming interface:
 
   // Initiates a signature verification operation.  This should be followed
   // by one or more VerifyUpdate calls and a VerifyFinal call.
-  //
-  // The signature algorithm is specified as a DER encoded ASN.1
-  // AlgorithmIdentifier structure:
-  //   AlgorithmIdentifier  ::=  SEQUENCE  {
-  //       algorithm               OBJECT IDENTIFIER,
-  //       parameters              ANY DEFINED BY algorithm OPTIONAL  }
-  //
-  // The signature is encoded according to the signature algorithm, but it
-  // must not be further encoded in an ASN.1 BIT STRING.
-  // Note: An RSA signatures is actually a big integer.  It must be in the
-  // big-endian byte order.
+  // The signature is encoded according to the signature algorithm.
   //
   // The public key is specified as a DER encoded ASN.1 SubjectPublicKeyInfo
   // structure, which contains not only the public key but also its type
@@ -47,8 +43,7 @@
   //   SubjectPublicKeyInfo  ::=  SEQUENCE  {
   //       algorithm            AlgorithmIdentifier,
   //       subjectPublicKey     BIT STRING  }
-  bool VerifyInit(const uint8* signature_algorithm,
-                  int signature_algorithm_len,
+  bool VerifyInit(SignatureAlgorithm signature_algorithm,
                   const uint8* signature,
                   int signature_len,
                   const uint8* public_key_info,
diff --git a/src/crypto/signature_verifier_openssl.cc b/src/crypto/signature_verifier_openssl.cc
index cab45db..112c4e3 100644
--- a/src/crypto/signature_verifier_openssl.cc
+++ b/src/crypto/signature_verifier_openssl.cc
@@ -29,8 +29,7 @@
   Reset();
 }
 
-bool SignatureVerifier::VerifyInit(const uint8* signature_algorithm,
-                                   int signature_algorithm_len,
+bool SignatureVerifier::VerifyInit(SignatureAlgorithm signature_algorithm,
                                    const uint8* signature,
                                    int signature_len,
                                    const uint8* public_key_info,
@@ -39,12 +38,18 @@
   verify_context_ = new VerifyContext;
   OpenSSLErrStackTracer err_tracer(FROM_HERE);
 
-  ScopedOpenSSL<X509_ALGOR, X509_ALGOR_free> algorithm(
-      d2i_X509_ALGOR(NULL, &signature_algorithm, signature_algorithm_len));
-  if (!algorithm.get())
-    return false;
-
-  const EVP_MD* digest = EVP_get_digestbyobj(algorithm.get()->algorithm);
+  const EVP_MD* digest = nullptr;
+  switch (signature_algorithm) {
+    case RSA_PKCS1_SHA1:
+      digest = EVP_sha1();
+      break;
+    case RSA_PKCS1_SHA256:
+      digest = EVP_sha256();
+      break;
+    case ECDSA_SHA256:
+      digest = EVP_sha256();
+      break;
+  }
   DCHECK(digest);
 
   signature_.assign(signature, signature + signature_len);
diff --git a/src/crypto/signature_verifier_unittest.cc b/src/crypto/signature_verifier_unittest.cc
index 360afa9..a694a1f 100644
--- a/src/crypto/signature_verifier_unittest.cc
+++ b/src/crypto/signature_verifier_unittest.cc
@@ -111,19 +111,6 @@
     0x74, 0x2e, 0x6f, 0x72, 0x67
   };
 
-  // The signature algorithm is specified as the following ASN.1 structure:
-  //    AlgorithmIdentifier  ::=  SEQUENCE  {
-  //        algorithm               OBJECT IDENTIFIER,
-  //        parameters              ANY DEFINED BY algorithm OPTIONAL  }
-  //
-  const uint8 signature_algorithm[15] = {
-    0x30, 0x0d,  // a SEQUENCE of length 13 (0xd)
-      0x06, 0x09,  // an OBJECT IDENTIFIER of length 9
-        // 1.2.840.113549.1.1.5 - sha1WithRSAEncryption
-        0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05,
-      0x05, 0x00,  // a NULL of length 0
-  };
-
   // RSA signature, a big integer in the big-endian byte order.
   const uint8 signature[256] = {
     0x1e, 0x6a, 0xe7, 0xe0, 0x4f, 0xe7, 0x4d, 0xd0, 0x69, 0x7c, 0xf8, 0x8f,
@@ -205,10 +192,9 @@
 
   // Test 1: feed all of the data to the verifier at once (a single
   // VerifyUpdate call).
-  ok = verifier.VerifyInit(signature_algorithm,
-                           sizeof(signature_algorithm),
-                           signature, sizeof(signature),
-                           public_key_info, sizeof(public_key_info));
+  ok = verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1, signature,
+                           sizeof(signature), public_key_info,
+                           sizeof(public_key_info));
   EXPECT_TRUE(ok);
   verifier.VerifyUpdate(tbs_certificate, sizeof(tbs_certificate));
   ok = verifier.VerifyFinal();
@@ -216,10 +202,9 @@
 
   // Test 2: feed the data to the verifier in three parts (three VerifyUpdate
   // calls).
-  ok = verifier.VerifyInit(signature_algorithm,
-                           sizeof(signature_algorithm),
-                           signature, sizeof(signature),
-                           public_key_info, sizeof(public_key_info));
+  ok = verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1, signature,
+                           sizeof(signature), public_key_info,
+                           sizeof(public_key_info));
   EXPECT_TRUE(ok);
   verifier.VerifyUpdate(tbs_certificate,       256);
   verifier.VerifyUpdate(tbs_certificate + 256, 256);
@@ -231,10 +216,9 @@
   uint8 bad_tbs_certificate[sizeof(tbs_certificate)];
   memcpy(bad_tbs_certificate, tbs_certificate, sizeof(tbs_certificate));
   bad_tbs_certificate[10] += 1;  // Corrupt one byte of the data.
-  ok = verifier.VerifyInit(signature_algorithm,
-                           sizeof(signature_algorithm),
-                           signature, sizeof(signature),
-                           public_key_info, sizeof(public_key_info));
+  ok = verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1, signature,
+                           sizeof(signature), public_key_info,
+                           sizeof(public_key_info));
   EXPECT_TRUE(ok);
   verifier.VerifyUpdate(bad_tbs_certificate, sizeof(bad_tbs_certificate));
   ok = verifier.VerifyFinal();
@@ -244,8 +228,7 @@
   uint8 bad_signature[sizeof(signature)];
   memcpy(bad_signature, signature, sizeof(signature));
   bad_signature[10] += 1;  // Corrupt one byte of the signature.
-  ok = verifier.VerifyInit(signature_algorithm,
-                           sizeof(signature_algorithm),
+  ok = verifier.VerifyInit(crypto::SignatureVerifier::RSA_PKCS1_SHA1,
                            bad_signature, sizeof(bad_signature),
                            public_key_info, sizeof(public_key_info));
 
diff --git a/src/net/base/openssl_memory_private_key_store.cc b/src/net/base/openssl_memory_private_key_store.cc
index 92716f2..4811572 100644
--- a/src/net/base/openssl_memory_private_key_store.cc
+++ b/src/net/base/openssl_memory_private_key_store.cc
@@ -4,10 +4,10 @@
 
 // Defines an in-memory private key store, primarily used for testing.
 
-#include <openssl/evp.h>
-
 #include "net/base/openssl_private_key_store.h"
 
+#include <openssl/evp.h>
+
 #include "base/logging.h"
 #include "base/memory/singleton.h"
 #include "base/synchronization/lock.h"
@@ -34,7 +34,12 @@
   }
 
   virtual bool StorePrivateKey(const GURL& url, EVP_PKEY* pkey) {
+#if defined(OPENSSL_IS_BORINGSSL)
+    EVP_PKEY_up_ref(pkey);
+#else
     CRYPTO_add(&pkey->references, 1, CRYPTO_LOCK_EVP_PKEY);
+#endif  // defined(OPENSSL_IS_BORINGSSL)
+
     base::AutoLock lock(lock_);
     keys_.push_back(pkey);
     return true;
diff --git a/src/net/base/x509_certificate_openssl.cc b/src/net/base/x509_certificate_openssl.cc
index c0dedbe..07e518b 100644
--- a/src/net/base/x509_certificate_openssl.cc
+++ b/src/net/base/x509_certificate_openssl.cc
@@ -250,12 +250,18 @@
 X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle(
     OSCertHandle cert_handle) {
   DCHECK(cert_handle);
+
+#if defined(OPENSSL_IS_BORINGSSL)
+  X509_up_ref(cert_handle);
+#else
   // Using X509_dup causes the entire certificate to be reparsed. This
   // conversion, besides being non-trivial, drops any associated
   // application-specific data set by X509_set_ex_data. Using CRYPTO_add
   // just bumps up the ref-count for the cert, without causing any allocations
   // or deallocations.
   CRYPTO_add(&cert_handle->references, 1, CRYPTO_LOCK_X509);
+#endif  // defined(OPENSSL_IS_BORINGSSL)
+
   return cert_handle;
 }
 
diff --git a/src/net/base/x509_util_openssl.cc b/src/net/base/x509_util_openssl.cc
index 142bf77..fea5ebb 100644
--- a/src/net/base/x509_util_openssl.cc
+++ b/src/net/base/x509_util_openssl.cc
@@ -2,14 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "net/base/x509_util.h"
 #include "net/base/x509_util_openssl.h"
 
+#include <openssl/mem.h>
+
 #include <algorithm>
 
 #include "base/logging.h"
 #include "base/string_piece.h"
 #include "net/base/x509_cert_types.h"
+#include "net/base/x509_util.h"
 
 namespace net {
 
diff --git a/src/net/http/des.cc b/src/net/http/des.cc
index 79240cf..8e8dfc6 100644
--- a/src/net/http/des.cc
+++ b/src/net/http/des.cc
@@ -94,9 +94,9 @@
 
   DES_key_schedule ks;
   DES_set_key_unchecked(
-      reinterpret_cast<const_DES_cblock*>(const_cast<uint8*>(key)), &ks);
+      reinterpret_cast<const DES_cblock*>(const_cast<uint8*>(key)), &ks);
 
-  DES_ecb_encrypt(reinterpret_cast<const_DES_cblock*>(const_cast<uint8*>(src)),
+  DES_ecb_encrypt(reinterpret_cast<const DES_cblock*>(const_cast<uint8*>(src)),
                   reinterpret_cast<DES_cblock*>(hash), &ks, DES_ENCRYPT);
 }
 
diff --git a/src/net/net.gyp b/src/net/net.gyp
index 2e103ba..edb77ad 100644
--- a/src/net/net.gyp
+++ b/src/net/net.gyp
@@ -37,9 +37,9 @@
         '<(DEPTH)/googleurl/googleurl.gyp:googleurl',
         '<(DEPTH)/nb/nb.gyp:nb',
         '<(DEPTH)/starboard/starboard.gyp:starboard',
+        '<(DEPTH)/third_party/boringssl/boringssl.gyp:crypto',
         '<(DEPTH)/third_party/icu/icu.gyp:icui18n',
         '<(DEPTH)/third_party/icu/icu.gyp:icuuc',
-        '<(DEPTH)/third_party/openssl/openssl.gyp:openssl',
         '<(DEPTH)/third_party/zlib/zlib.gyp:zlib',
       ],
       'export_dependent_settings': [
@@ -820,8 +820,8 @@
         '<(DEPTH)/googleurl/googleurl.gyp:googleurl',
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
+        '<(DEPTH)/third_party/boringssl/boringssl.gyp:crypto',
         '<(DEPTH)/third_party/zlib/zlib.gyp:zlib',
-        '<(DEPTH)/third_party/openssl/openssl.gyp:openssl',
         'http_server',  # This is needed by dial_http_server in net
         'net',
         'net_copy_test_data',
@@ -1125,7 +1125,7 @@
         '<(DEPTH)/base/base.gyp:base',
         '<(DEPTH)/base/base.gyp:test_support_base',
         '<(DEPTH)/testing/gtest.gyp:gtest',
-        '<(DEPTH)/third_party/openssl/openssl.gyp:openssl',
+        '<(DEPTH)/third_party/boringssl/boringssl.gyp:crypto',
         'net',
       ],
       'export_dependent_settings': [
diff --git a/src/net/socket/ssl_client_socket_openssl.cc b/src/net/socket/ssl_client_socket_openssl.cc
index bf532ad..b2ce005 100644
--- a/src/net/socket/ssl_client_socket_openssl.cc
+++ b/src/net/socket/ssl_client_socket_openssl.cc
@@ -7,15 +7,16 @@
 
 #include "net/socket/ssl_client_socket_openssl.h"
 
-#include <openssl/ssl.h>
 #include <openssl/err.h>
 #include <openssl/opensslv.h>
+#include <openssl/ssl.h>
 
 #include "base/bind.h"
 #include "base/debug/trace_event.h"
 #include "base/memory/singleton.h"
 #include "base/metrics/histogram.h"
 #include "base/synchronization/lock.h"
+#include "base/time.h"
 #include "crypto/openssl_util.h"
 #include "net/base/cert_verifier.h"
 #include "net/base/net_errors.h"
@@ -33,9 +34,11 @@
 
 // Enable this to see logging for state machine state transitions.
 #if 0
-#define GotoState(s) do { DVLOG(2) << (void *)this << " " << __FUNCTION__ << \
-                           " jump to state " << s; \
-                           next_handshake_state_ = s; } while (0)
+#define GotoState(s)                                                          \
+  do {                                                                        \
+    DVLOG(2) << (void*)this << " " << __FUNCTION__ << " jump to state " << s; \
+    next_handshake_state_ = s;                                                \
+  } while (0)
 #else
 #define GotoState(s) next_handshake_state_ = s
 #endif
@@ -47,6 +50,9 @@
 #else
 const size_t kSessionCacheMaxEntires = 1024;
 #endif  // if defined(COBALT)
+#if defined(OPENSSL_IS_BORINGSSL)
+size_t expiration_check_count = 256;
+#endif  // defined(OPENSSL_IS_BORINGSSL)
 
 // If a client doesn't have a list of protocols that it supports, but
 // the server supports NPN, choosing "http/1.1" is the best answer.
@@ -54,19 +60,19 @@
 
 #if OPENSSL_VERSION_NUMBER < 0x1000103fL
 // This method doesn't seem to have made it into the OpenSSL headers.
-unsigned long SSL_CIPHER_get_id(const SSL_CIPHER* cipher) { return cipher->id; }
+unsigned long SSL_CIPHER_get_id(const SSL_CIPHER* cipher) {
+  return cipher->id;
+}
 #endif
 
 // Used for encoding the |connection_status| field of an SSLInfo object.
-int EncodeSSLConnectionStatus(int cipher_suite,
-                              int compression,
-                              int version) {
-  return ((cipher_suite & SSL_CONNECTION_CIPHERSUITE_MASK) <<
-          SSL_CONNECTION_CIPHERSUITE_SHIFT) |
-         ((compression & SSL_CONNECTION_COMPRESSION_MASK) <<
-          SSL_CONNECTION_COMPRESSION_SHIFT) |
-         ((version & SSL_CONNECTION_VERSION_MASK) <<
-          SSL_CONNECTION_VERSION_SHIFT);
+int EncodeSSLConnectionStatus(int cipher_suite, int compression, int version) {
+  return ((cipher_suite & SSL_CONNECTION_CIPHERSUITE_MASK)
+          << SSL_CONNECTION_CIPHERSUITE_SHIFT) |
+         ((compression & SSL_CONNECTION_COMPRESSION_MASK)
+          << SSL_CONNECTION_COMPRESSION_SHIFT) |
+         ((version & SSL_CONNECTION_VERSION_MASK)
+          << SSL_CONNECTION_VERSION_SHIFT);
 }
 
 // Returns the net SSL version number (see ssl_connection_status_flags.h) for
@@ -87,7 +93,98 @@
       return SSL_CONNECTION_VERSION_UNKNOWN;
   }
 }
+#if defined(OPENSSL_IS_BORINGSSL)
+int MapOpenSSLErrorSSL() {
+  // Walk down the error stack to find the SSLerr generated reason.
+  unsigned long error_code;
+  do {
+    error_code = ERR_get_error();
+    if (error_code == 0) {
+      return ERR_SSL_PROTOCOL_ERROR;
+    }
+  } while (ERR_GET_LIB(error_code) != ERR_LIB_SSL);
 
+  DLOG(ERROR) << "OpenSSL SSL error, reason: " << ERR_GET_REASON(error_code)
+              << ", name: " << ERR_error_string(error_code, NULL);
+  switch (ERR_GET_REASON(error_code)) {
+    case SSL_R_READ_TIMEOUT_EXPIRED:
+      return ERR_TIMED_OUT;
+    case SSL_R_BAD_SSL_FILETYPE:
+      return ERR_INVALID_ARGUMENT;
+    case SSL_R_UNKNOWN_CERTIFICATE_TYPE:
+    case SSL_R_UNKNOWN_CIPHER_TYPE:
+    case SSL_R_UNKNOWN_KEY_EXCHANGE_TYPE:
+    case SSL_R_INVALID_OUTER_RECORD_TYPE:
+    case SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS:
+    case SSL_R_UNKNOWN_SSL_VERSION:
+      return ERR_NOT_IMPLEMENTED;
+    case SSL_R_INVALID_ALPN_PROTOCOL:
+    case SSL_R_NO_CIPHER_MATCH:
+    case SSL_R_NO_SHARED_CIPHER:
+    case SSL_R_TLSV1_ALERT_INSUFFICIENT_SECURITY:
+    case SSL_R_TLSV1_ALERT_PROTOCOL_VERSION:
+    case SSL_R_UNSUPPORTED_PROTOCOL:
+      return ERR_SSL_VERSION_OR_CIPHER_MISMATCH;
+    case SSL_R_SSLV3_ALERT_BAD_CERTIFICATE:
+    case SSL_R_SSLV3_ALERT_UNSUPPORTED_CERTIFICATE:
+    case SSL_R_SSLV3_ALERT_CERTIFICATE_REVOKED:
+    case SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED:
+    case SSL_R_SSLV3_ALERT_CERTIFICATE_UNKNOWN:
+    case SSL_R_TLSV1_ALERT_ACCESS_DENIED:
+    case SSL_R_TLSV1_ALERT_UNKNOWN_CA:
+      return ERR_BAD_SSL_CLIENT_AUTH_CERT;
+    case SSL_R_BAD_ECC_CERT:
+    case SSL_R_SSLV3_ALERT_DECOMPRESSION_FAILURE:
+      return ERR_SSL_DECOMPRESSION_FAILURE_ALERT;
+    case SSL_R_SSLV3_ALERT_BAD_RECORD_MAC:
+      return ERR_SSL_BAD_RECORD_MAC_ALERT;
+    case SSL_R_UNSAFE_LEGACY_RENEGOTIATION_DISABLED:
+      return ERR_SSL_UNSAFE_NEGOTIATION;
+    case SSL_R_RENEGOTIATION_EMS_MISMATCH:
+      return ERR_SSL_WEAK_SERVER_EPHEMERAL_DH_KEY;
+    // SSL_R_UNKNOWN_PROTOCOL is reported if premature application data is
+    // received (see http://crbug.com/42538), and also if all the protocol
+    // versions supported by the server were disabled in this socket instance.
+    // Mapped to ERR_SSL_PROTOCOL_ERROR for compatibility with other SSL sockets
+    // in the former scenario.
+    case SSL_R_UNKNOWN_PROTOCOL:
+    case SSL_R_SSL_HANDSHAKE_FAILURE:
+    case SSL_R_DECRYPTION_FAILED:
+    case SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC:
+    case SSL_R_DH_PUBLIC_VALUE_LENGTH_IS_WRONG:
+    case SSL_R_DIGEST_CHECK_FAILED:
+    case SSL_R_ENCRYPTED_LENGTH_TOO_LONG:
+    case SSL_R_ERROR_IN_RECEIVED_CIPHER_LIST:
+    case SSL_R_EXCESSIVE_MESSAGE_SIZE:
+    case SSL_R_EXTRA_DATA_IN_MESSAGE:
+    case SSL_R_HANDSHAKE_FAILURE_ON_CLIENT_HELLO:
+    case SSL_R_INVALID_COMMAND:
+    case SSL_R_WRONG_VERSION_ON_EARLY_DATA:
+    case SSL_R_INVALID_TICKET_KEYS_LENGTH:
+    case SSL_R_SRTP_UNKNOWN_PROTECTION_PROFILE:
+    case SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE:
+    // TODO(joth): SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE may be returned from the
+    // server after receiving ClientHello if there's no common supported cipher.
+    // Ideally we'd map that specific case to ERR_SSL_VERSION_OR_CIPHER_MISMATCH
+    // to match the NSS implementation. See also http://goo.gl/oMtZW
+    case SSL_R_SSLV3_ALERT_HANDSHAKE_FAILURE:
+    case SSL_R_SSLV3_ALERT_NO_CERTIFICATE:
+    case SSL_R_SSLV3_ALERT_ILLEGAL_PARAMETER:
+    case SSL_R_TLSV1_ALERT_DECODE_ERROR:
+    case SSL_R_TLSV1_ALERT_DECRYPTION_FAILED:
+    case SSL_R_TLSV1_ALERT_DECRYPT_ERROR:
+    case SSL_R_TLSV1_ALERT_EXPORT_RESTRICTION:
+    case SSL_R_TLSV1_ALERT_INTERNAL_ERROR:
+    case SSL_R_TLSV1_ALERT_NO_RENEGOTIATION:
+    case SSL_R_TLSV1_ALERT_RECORD_OVERFLOW:
+    case SSL_R_TLSV1_ALERT_USER_CANCELLED:
+      return ERR_SSL_PROTOCOL_ERROR;
+    default:
+      LOG(WARNING) << "Unmapped error reason: " << ERR_GET_REASON(error_code);
+      return ERR_FAILED;
+  }
+}
+#else   // defined(OPENSSL_IS_BORINGSSL)
 int MapOpenSSLErrorSSL() {
   // Walk down the error stack to find the SSLerr generated reason.
   unsigned long error_code;
@@ -183,6 +280,7 @@
       return ERR_FAILED;
   }
 }
+#endif  // defined(OPENSSL_IS_BORINGSSL)
 
 // Converts an OpenSSL error code into a net error code, walking the OpenSSL
 // error stack if needed. Note that |tracer| is not currently used in the
@@ -207,7 +305,7 @@
 
 // We do certificate verification after handshake, so we disable the default
 // by registering a no-op verify function.
-int NoOpVerifyCallback(X509_STORE_CTX*, void *) {
+int NoOpVerifyCallback(X509_STORE_CTX*, void*) {
   DVLOG(3) << "skipping cert verify";
   return 1;
 }
@@ -219,6 +317,10 @@
  public:
   SSLSessionCache() {}
 
+#if defined(OPENSSL_IS_BORINGSSL)
+  ~SSLSessionCache() { Flush(); }
+#endif  // defined(OPENSSL_IS_BORINGSSL)
+
   void OnSessionAdded(const HostPortPair& host_and_port,
                       const std::string& shard,
                       SSL_SESSION* session) {
@@ -237,8 +339,8 @@
       session_map_.erase(session_to_free.get());
       res.first->second = session;
     }
-    DVLOG(2) << "Adding session " << session << " => "
-             << cache_key << ", new entry = " << res.second;
+    DVLOG(2) << "Adding session " << session << " => " << cache_key
+             << ", new entry = " << res.second;
     DCHECK(host_port_map_[cache_key] == session);
     session_map_[session] = res.first;
     DCHECK_EQ(host_port_map_.size(), session_map_.size());
@@ -262,11 +364,19 @@
     DCHECK_EQ(host_port_map_.size(), session_map_.size());
   }
 
-  // Looks up the host:port in the cache, and if a session is found it is added
-  // to |ssl|, returning true on success.
-  bool SetSSLSession(SSL* ssl, const HostPortPair& host_and_port,
+  // Looks up the host:port in the cache, and if a session is found it is
+  // added to |ssl|, returning true on success.
+  bool SetSSLSession(SSL* ssl,
+                     const HostPortPair& host_and_port,
                      const std::string& shard) {
     base::AutoLock lock(lock_);
+#if defined(OPENSSL_IS_BORINGSSL)
+    lookups_since_flush_++;
+    if (lookups_since_flush_ >= expiration_check_count) {
+      FlushExpiredSessions();
+    }
+#endif  // defined(OPENSSL_IS_BORINGSSL)
+
     const std::string cache_key = GetCacheKey(host_and_port, shard);
     HostPortMap::iterator it = host_port_map_.find(cache_key);
     if (it == host_port_map_.end())
@@ -275,11 +385,19 @@
     SSL_SESSION* session = it->second;
     DCHECK(session);
     DCHECK(session_map_[session] == it);
-    // Ideally we'd release |lock_| before calling into OpenSSL here, however
-    // that opens a small risk |session| will go out of scope before it is used.
-    // Alternatively we would take a temporary local refcount on |session|,
-    // except OpenSSL does not provide a public API for adding a ref (c.f.
-    // SSL_SESSION_free which decrements the ref).
+#if defined(OPENSSL_IS_BORINGSSL)
+    if (IsExpired(session, base::Time::Now().ToTimeT())) {
+      SSL_SESSION_free(session);
+      session_map_.erase(session);
+      host_port_map_.erase(it);
+      return false;
+    }
+#endif  // defined(OPENSSL_IS_BORINGSSL)
+    // Ideally we'd release |lock_| before calling into OpenSSL here,
+    // however that opens a small risk |session| will go out of scope before
+    // it is used. Alternatively we would take a temporary local refcount on
+    // |session|, except OpenSSL does not provide a public API for adding a
+    // ref (c.f. SSL_SESSION_free which decrements the ref).
     return SSL_set_session(ssl, session) == 1;
   }
 
@@ -299,14 +417,40 @@
                                  const std::string& shard) {
     return host_and_port.ToString() + "/" + shard;
   }
+#if defined(OPENSSL_IS_BORINGSSL)
+  static bool IsExpired(SSL_SESSION* session, time_t now) {
+    if (now < 0)
+      return true;
+    uint64_t now_u64 = static_cast<uint64_t>(now);
+    return now_u64 < SSL_SESSION_get_time(session) ||
+           now_u64 >=
+               SSL_SESSION_get_time(session) + SSL_SESSION_get_timeout(session);
+  }
 
+  void FlushExpiredSessions() {
+    time_t now = base::Time::Now().ToTimeT();
+    SessionMap::iterator iter = session_map_.begin();
+    while (iter != session_map_.end()) {
+      if (IsExpired(iter->first, now)) {
+        SSL_SESSION_free(iter->first);
+        host_port_map_.erase(iter->second);
+        iter = session_map_.erase(iter);
+      } else {
+        ++iter;
+      }
+    }
+    lookups_since_flush_ = 0;
+  }
+#endif  // defined(OPENSSL_IS_BORINGSSL)
   // A pair of maps to allow bi-directional lookups between host:port and an
   // associated session.
   typedef std::map<std::string, SSL_SESSION*> HostPortMap;
   typedef std::map<SSL_SESSION*, HostPortMap::iterator> SessionMap;
   HostPortMap host_port_map_;
   SessionMap session_map_;
-
+#if defined(OPENSSL_IS_BORINGSSL)
+  size_t lookups_since_flush_ = 0;
+#endif  // defined(OPENSSL_IS_BORINGSSL)
   // Protects access to both the above maps.
   base::Lock lock_;
 
@@ -340,9 +484,18 @@
     DCHECK_NE(ssl_socket_data_index_, -1);
     ssl_ctx_.reset(SSL_CTX_new(SSLv23_client_method()));
     SSL_CTX_set_cert_verify_callback(ssl_ctx_.get(), NoOpVerifyCallback, NULL);
+#if defined(OPENSSL_IS_BORINGSSL)
+    // OpenSSL internal session cache is useless.
+    // https://boringssl.googlesource.com/boringssl.git/+/1269ddd3773f50dfc2c3c9e23a2249ded1415ba3
+    SSL_CTX_set_session_cache_mode(
+        ssl_ctx_.get(), SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
+#else   // defined(OPENSSL_IS_BORINGSSL)
     SSL_CTX_set_session_cache_mode(ssl_ctx_.get(), SSL_SESS_CACHE_CLIENT);
-    SSL_CTX_sess_set_new_cb(ssl_ctx_.get(), NewSessionCallbackStatic);
+    // RemoveSessionCallbackStatic will not be called in boringssl.
+    // https://chromium.googlesource.com/chromium/src/+/dafe4e53058ed802fabc151e67e75ffded76fd18
     SSL_CTX_sess_set_remove_cb(ssl_ctx_.get(), RemoveSessionCallbackStatic);
+#endif  // defined(OPENSSL_IS_BORINGSSL)
+    SSL_CTX_sess_set_new_cb(ssl_ctx_.get(), NewSessionCallbackStatic);
     SSL_CTX_set_timeout(ssl_ctx_.get(), kSessionCacheTimeoutSeconds);
     SSL_CTX_sess_set_cache_size(ssl_ctx_.get(), kSessionCacheMaxEntires);
     SSL_CTX_set_client_cert_cb(ssl_ctx_.get(), ClientCertCallback);
@@ -362,8 +515,7 @@
   int NewSessionCallback(SSL* ssl, SSL_SESSION* session) {
     SSLClientSocketOpenSSL* socket = GetClientSocketFromSSL(ssl);
     session_cache_.OnSessionAdded(socket->host_and_port(),
-                                  socket->ssl_session_cache_shard(),
-                                  session);
+                                  socket->ssl_session_cache_shard(), session);
     return 1;  // 1 => We took ownership of |session|.
   }
 
@@ -383,9 +535,11 @@
   }
 
   static int SelectNextProtoCallback(SSL* ssl,
-                                     unsigned char** out, unsigned char* outlen,
+                                     unsigned char** out,
+                                     unsigned char* outlen,
                                      const unsigned char* in,
-                                     unsigned int inlen, void* arg) {
+                                     unsigned int inlen,
+                                     void* arg) {
     SSLClientSocketOpenSSL* socket = GetInstance()->GetClientSocketFromSSL(ssl);
     return socket->SelectNextProtoCallback(out, outlen, in, inlen);
   }
@@ -443,8 +597,7 @@
       trying_cached_session_(false),
       next_handshake_state_(STATE_NONE),
       npn_status_(kNextProtoUnsupported),
-      net_log_(transport_socket->socket()->NetLog()) {
-}
+      net_log_(transport_socket->socket()->NetLog()) {}
 
 SSLClientSocketOpenSSL::~SSLClientSocketOpenSSL() {
   Disconnect();
@@ -464,9 +617,8 @@
   if (!SSL_set_tlsext_host_name(ssl_, host_and_port_.host().c_str()))
     return false;
 
-  trying_cached_session_ =
-      context->session_cache()->SetSSLSession(ssl_, host_and_port_,
-                                              ssl_session_cache_shard_);
+  trying_cached_session_ = context->session_cache()->SetSSLSession(
+      ssl_, host_and_port_, ssl_session_cache_shard_);
 
   BIO* ssl_bio = NULL;
   // 0 => use default buffer sizes.
@@ -498,6 +650,11 @@
        ssl_config_.version_max >= SSL_PROTOCOL_VERSION_TLS1_2);
   options.ConfigureFlag(SSL_OP_NO_TLSv1_2, !tls1_2_enabled);
 #endif
+#if defined(OPENSSL_IS_BORINGSSL)
+  // The old OpenSSL we used does not support TLS 1.3. Disable TLS v1.3.
+  // Avoid session to be single use, which is enabled in TLS 1.3.
+  options.ConfigureFlag(SSL_OP_NO_TLSv1_3, true);
+#endif  // defined(OPENSSL_IS_BORINGSSL)
 
 #if defined(SSL_OP_NO_COMPRESSION)
   options.ConfigureFlag(SSL_OP_NO_COMPRESSION, true);
@@ -543,23 +700,25 @@
     bool disable = SSL_CIPHER_get_bits(cipher, NULL) < 80;
     if (!disable) {
       disable = std::find(ssl_config_.disabled_cipher_suites.begin(),
-                          ssl_config_.disabled_cipher_suites.end(), id) !=
-                    ssl_config_.disabled_cipher_suites.end();
+                          ssl_config_.disabled_cipher_suites.end(),
+                          id) != ssl_config_.disabled_cipher_suites.end();
     }
     if (disable) {
-       const char* name = SSL_CIPHER_get_name(cipher);
-       DVLOG(3) << "Found cipher to remove: '" << name << "', ID: " << id
-                << " strength: " << SSL_CIPHER_get_bits(cipher, NULL);
-       command.append(":!");
-       command.append(name);
-     }
+      const char* name = SSL_CIPHER_get_name(cipher);
+      DVLOG(3) << "Found cipher to remove: '" << name << "', ID: " << id
+               << " strength: " << SSL_CIPHER_get_bits(cipher, NULL);
+      command.append(":!");
+      command.append(name);
+    }
   }
   int rv = SSL_set_cipher_list(ssl_, command.c_str());
   // If this fails (rv = 0) it means there are no ciphers enabled on this SSL.
   // This will almost certainly result in the socket failing to complete the
   // handshake at which point the appropriate error is bubbled up to the client.
-  LOG_IF(WARNING, rv != 1) << "SSL_set_cipher_list('" << command << "') "
-                              "returned " << rv;
+  LOG_IF(WARNING, rv != 1) << "SSL_set_cipher_list('" << command
+                           << "') "
+                              "returned "
+                           << rv;
   return true;
 }
 
@@ -578,9 +737,9 @@
 
   // Second pass: a client certificate should have been selected.
   if (ssl_config_.client_cert) {
-    EVP_PKEY* privkey = OpenSSLPrivateKeyStore::GetInstance()->FetchPrivateKey(
-        X509_PUBKEY_get(X509_get_X509_PUBKEY(
-            ssl_config_.client_cert->os_cert_handle())));
+    EVP_PKEY* privkey =
+        OpenSSLPrivateKeyStore::GetInstance()->FetchPrivateKey(X509_PUBKEY_get(
+            X509_get_X509_PUBKEY(ssl_config_.client_cert->os_cert_handle())));
     if (privkey) {
       // TODO(joth): (copied from NSS) We should wait for server certificate
       // verification before sending our credentials. See http://crbug.com/13934
@@ -607,8 +766,7 @@
   ssl_info->cert_status = server_cert_verify_result_.cert_status;
   ssl_info->is_issued_by_known_root =
       server_cert_verify_result_.is_issued_by_known_root;
-  ssl_info->public_key_hashes =
-    server_cert_verify_result_.public_key_hashes;
+  ssl_info->public_key_hashes = server_cert_verify_result_.public_key_hashes;
   ssl_info->client_cert_sent =
       ssl_config_.send_client_cert && ssl_config_.client_cert;
   ssl_info->channel_id_sent = WasChannelIDSent();
@@ -616,12 +774,18 @@
   const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl_);
   CHECK(cipher);
   ssl_info->security_bits = SSL_CIPHER_get_bits(cipher, NULL);
+  int compression_type = 0;
+
+// BoringSSL implementation of SSL_get_current_compression returns NULL
+#if !defined(OPENSSL_IS_BORINGSSL)
   const COMP_METHOD* compression = SSL_get_current_compression(ssl_);
+  if (compression) {
+    compression_type = compression->type;
+  }
+#endif  // defined(OPENSSL_IS_BORINGSSL)
 
   ssl_info->connection_status = EncodeSSLConnectionStatus(
-      SSL_CIPHER_get_id(cipher),
-      compression ? compression->type : 0,
-      GetNetSSLVersion(ssl_));
+      SSL_CIPHER_get_id(cipher), compression_type, GetNetSSLVersion(ssl_));
 
   bool peer_supports_renego_ext = !!SSL_get_secure_renegotiation_support(ssl_);
   if (!peer_supports_renego_ext)
@@ -633,11 +797,11 @@
     ssl_info->connection_status |= SSL_CONNECTION_VERSION_FALLBACK;
 
   DVLOG(3) << "Encoded connection status: cipher suite = "
-      << SSLConnectionStatusToCipherSuite(ssl_info->connection_status)
-      << " compression = "
-      << SSLConnectionStatusToCompression(ssl_info->connection_status)
-      << " version = "
-      << SSLConnectionStatusToVersion(ssl_info->connection_status);
+           << SSLConnectionStatusToCipherSuite(ssl_info->connection_status)
+           << " compression = "
+           << SSLConnectionStatusToCompression(ssl_info->connection_status)
+           << " version = "
+           << SSLConnectionStatusToVersion(ssl_info->connection_status);
   return true;
 }
 
@@ -649,21 +813,20 @@
 
 int SSLClientSocketOpenSSL::ExportKeyingMaterial(
     const base::StringPiece& label,
-    bool has_context, const base::StringPiece& context,
-    unsigned char* out, unsigned int outlen) {
+    bool has_context,
+    const base::StringPiece& context,
+    unsigned char* out,
+    unsigned int outlen) {
   crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
   int rv = SSL_export_keying_material(
-      ssl_, out, outlen, const_cast<char*>(label.data()),
-      label.size(),
+      ssl_, out, outlen, const_cast<char*>(label.data()), label.size(),
       reinterpret_cast<unsigned char*>(const_cast<char*>(context.data())),
-      context.length(),
-      context.length() > 0);
+      context.length(), context.length() > 0);
 
   if (rv != 1) {
     int ssl_error = SSL_get_error(ssl_, rv);
     LOG(ERROR) << "Failed to export keying material;"
-               << " returned " << rv
-               << ", SSL error code " << ssl_error;
+               << " returned " << rv << ", SSL error code " << ssl_error;
     return MapOpenSSLError(ssl_error, err_tracer);
   }
   return OK;
@@ -674,14 +837,15 @@
 }
 
 SSLClientSocket::NextProtoStatus SSLClientSocketOpenSSL::GetNextProto(
-    std::string* proto, std::string* server_protos) {
+    std::string* proto,
+    std::string* server_protos) {
   *proto = npn_proto_;
   *server_protos = server_protos_;
   return npn_status_;
 }
 
-ServerBoundCertService*
-SSLClientSocketOpenSSL::GetServerBoundCertService() const {
+ServerBoundCertService* SSLClientSocketOpenSSL::GetServerBoundCertService()
+    const {
   return NULL;
 }
 
@@ -757,10 +921,10 @@
   user_connect_callback_.Reset();
   user_read_callback_.Reset();
   user_write_callback_.Reset();
-  user_read_buf_         = NULL;
-  user_read_buf_len_     = 0;
-  user_write_buf_        = NULL;
-  user_write_buf_len_    = 0;
+  user_read_buf_ = NULL;
+  user_read_buf_len_ = 0;
+  user_write_buf_ = NULL;
+  user_write_buf_len_ = 0;
 
   server_cert_verify_result_.Reset();
   completed_handshake_ = false;
@@ -787,7 +951,7 @@
       case STATE_VERIFY_CERT:
         DCHECK(rv == OK);
         rv = DoVerifyCert(rv);
-       break;
+        break;
       case STATE_VERIFY_CERT_COMPLETE:
         rv = DoVerifyCertComplete(rv);
         break;
@@ -838,10 +1002,9 @@
     // SSL handshake is completed.  Let's verify the certificate.
     const bool got_cert = !!UpdateServerCert();
     DCHECK(got_cert);
-    net_log_.AddEvent(
-        NetLog::TYPE_SSL_CERTIFICATES_RECEIVED,
-        base::Bind(&NetLogX509CertificateCallback,
-                   base::Unretained(server_cert_.get())));
+    net_log_.AddEvent(NetLog::TYPE_SSL_CERTIFICATES_RECEIVED,
+                      base::Bind(&NetLogX509CertificateCallback,
+                                 base::Unretained(server_cert_.get())));
     GotoState(STATE_VERIFY_CERT);
   } else {
     int ssl_error = SSL_get_error(ssl_, rv);
@@ -851,12 +1014,10 @@
     if (net_error == ERR_IO_PENDING) {
       GotoState(STATE_HANDSHAKE);
     } else {
-      LOG(ERROR) << "handshake failed; returned " << rv
-                 << ", SSL error code " << ssl_error
-                 << ", net_error " << net_error;
-      net_log_.AddEvent(
-          NetLog::TYPE_SSL_HANDSHAKE_ERROR,
-          CreateNetLogSSLErrorCallback(net_error, ssl_error));
+      LOG(ERROR) << "handshake failed; returned " << rv << ", SSL error code "
+                 << ssl_error << ", net_error " << net_error;
+      net_log_.AddEvent(NetLog::TYPE_SSL_HANDSHAKE_ERROR,
+                        CreateNetLogSSLErrorCallback(net_error, ssl_error));
     }
   }
   return net_error;
@@ -884,11 +1045,10 @@
 
   // For each protocol in server preference order, see if we support it.
   for (unsigned int i = 0; i < inlen; i += in[i] + 1) {
-    for (std::vector<std::string>::const_iterator
-             j = ssl_config_.next_protos.begin();
+    for (std::vector<std::string>::const_iterator j =
+             ssl_config_.next_protos.begin();
          j != ssl_config_.next_protos.end(); ++j) {
-      if (in[i] == j->size() &&
-          memcmp(&in[i + 1], j->data(), in[i]) == 0) {
+      if (in[i] == j->size() && memcmp(&in[i + 1], j->data(), in[i]) == 0) {
         // We found a match.
         *out = const_cast<unsigned char*>(in) + i + 1;
         *outlen = in[i];
@@ -902,8 +1062,8 @@
 
   // If we didn't find a protocol, we select the first one from our list.
   if (npn_status_ == kNextProtoNoOverlap) {
-    *out = reinterpret_cast<uint8*>(const_cast<char*>(
-        ssl_config_.next_protos[0].data()));
+    *out = reinterpret_cast<uint8*>(
+        const_cast<char*>(ssl_config_.next_protos[0].data()));
     *outlen = ssl_config_.next_protos[0].size();
   }
 
@@ -936,8 +1096,7 @@
     flags |= CertVerifier::VERIFY_CERT_IO_ENABLED;
   verifier_.reset(new SingleRequestCertVerifier(cert_verifier_));
   return verifier_->Verify(
-      server_cert_, host_and_port_.host(), flags,
-      NULL /* no CRL set */,
+      server_cert_, host_and_port_.host(), flags, NULL /* no CRL set */,
       &server_cert_verify_result_,
       base::Bind(&SSLClientSocketOpenSSL::OnHandshakeIOComplete,
                  base::Unretained(this)),
@@ -951,8 +1110,8 @@
     // TODO(joth): Work out if we need to remember the intermediate CA certs
     // when the server sends them to us, and do so here.
   } else {
-    DVLOG(1) << "DoVerifyCertComplete error " << ErrorToString(result)
-             << " (" << result << ")";
+    DVLOG(1) << "DoVerifyCertComplete error " << ErrorToString(result) << " ("
+             << result << ")";
   }
 
   completed_handshake_ = true;
@@ -1018,10 +1177,9 @@
   }
 
   int rv = transport_->socket()->Write(
-        send_buffer_,
-        send_buffer_->BytesRemaining(),
-        base::Bind(&SSLClientSocketOpenSSL::BufferSendComplete,
-                   base::Unretained(this)));
+      send_buffer_, send_buffer_->BytesRemaining(),
+      base::Bind(&SSLClientSocketOpenSSL::BufferSendComplete,
+                 base::Unretained(this)));
   if (rv == ERR_IO_PENDING) {
     transport_send_busy_ = true;
   } else {
@@ -1139,28 +1297,26 @@
   int rv_write = ERR_IO_PENDING;
   bool network_moved;
   do {
-      if (user_read_buf_)
-          rv_read = DoPayloadRead();
-      if (user_write_buf_)
-          rv_write = DoPayloadWrite();
-      network_moved = DoTransportIO();
-  } while (rv_read == ERR_IO_PENDING &&
-           rv_write == ERR_IO_PENDING &&
-           (user_read_buf_ || user_write_buf_) &&
-           network_moved);
+    if (user_read_buf_)
+      rv_read = DoPayloadRead();
+    if (user_write_buf_)
+      rv_write = DoPayloadWrite();
+    network_moved = DoTransportIO();
+  } while (rv_read == ERR_IO_PENDING && rv_write == ERR_IO_PENDING &&
+           (user_read_buf_ || user_write_buf_) && network_moved);
 
   // Performing the Read callback may cause |this| to be deleted. If this
   // happens, the Write callback should not be invoked. Guard against this by
   // holding a WeakPtr to |this| and ensuring it's still valid.
   base::WeakPtr<SSLClientSocketOpenSSL> guard(weak_factory_.GetWeakPtr());
   if (user_read_buf_ && rv_read != ERR_IO_PENDING)
-      DoReadCallback(rv_read);
+    DoReadCallback(rv_read);
 
   if (!guard.get())
     return;
 
   if (user_write_buf_ && rv_write != ERR_IO_PENDING)
-      DoWriteCallback(rv_write);
+    DoWriteCallback(rv_write);
 }
 
 void SSLClientSocketOpenSSL::OnRecvComplete(int result) {
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java
new file mode 100644
index 0000000..591f63c
--- /dev/null
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java
@@ -0,0 +1,68 @@
+// 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.
+
+package dev.cobalt.coat;
+
+import dev.cobalt.util.UsedByNative;
+
+/** Abstract class that provides an interface for Cobalt to interact with a platform service. */
+public abstract class CobaltService {
+  /** Interface that returns an object that extends CobaltService. */
+  public interface Factory {
+    /** Create the service. */
+    public CobaltService createCobaltService(long nativeService);
+
+    /** Get the name of the service. */
+    public String getServiceName();
+  }
+  // Lifecycle
+  /** Prepare service for start or resume. */
+  public abstract void beforeStartOrResume();
+
+  /** Prepare service for suspend. */
+  public abstract void beforeSuspend();
+
+  /** Prepare service for stop. */
+  public abstract void afterStopped();
+
+  // Service API
+  /** Response to client from calls to receiveFromClient(). */
+  @SuppressWarnings("unused")
+  @UsedByNative
+  public static class ResponseToClient {
+    /** Indicate if the service was unable to receive data because it is in an invalid state. */
+    @SuppressWarnings("unused")
+    @UsedByNative
+    public boolean invalidState;
+    /** The synchronous response data from the service. */
+    @SuppressWarnings("unused")
+    @UsedByNative
+    public byte[] data;
+  }
+
+  /** Receive data from client of the service. */
+  @SuppressWarnings("unused")
+  @UsedByNative
+  public abstract ResponseToClient receiveFromClient(byte[] data);
+
+  /** Close the service. */
+  public abstract void close();
+
+  /** Send data from the service to the client. */
+  protected void sendToClient(long nativeService, byte[] data) {
+    nativeSendToClient(nativeService, data);
+  }
+
+  private native void nativeSendToClient(long nativeService, byte[] data);
+}
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltSystemConfigChangeReceiver.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltSystemConfigChangeReceiver.java
new file mode 100644
index 0000000..5207655
--- /dev/null
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/CobaltSystemConfigChangeReceiver.java
@@ -0,0 +1,48 @@
+// 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.
+
+package dev.cobalt.coat;
+
+import static dev.cobalt.util.Log.TAG;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import dev.cobalt.util.Log;
+
+// Helper class to receive and handle broadcast notifications of system locale
+// changes.
+final class CobaltSystemConfigChangeReceiver extends BroadcastReceiver {
+  private boolean isForeground;
+  private final Runnable stopRequester;
+
+  CobaltSystemConfigChangeReceiver(Context appContext, Runnable stopRequester) {
+    this.isForeground = true;
+    this.stopRequester = stopRequester;
+    appContext.registerReceiver(this, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+  }
+
+  @Override
+  public void onReceive(Context context, Intent intent) {
+    if ((!Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) || isForeground) return;
+
+    Log.w(TAG, "System locale settings have changed.");
+    stopRequester.run();
+  }
+
+  public void setForeground(final boolean isForeground) {
+    this.isForeground = isForeground;
+  }
+}
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/KeyboardEditor.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/KeyboardEditor.java
index be5aa0f..b28030f 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/KeyboardEditor.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/KeyboardEditor.java
@@ -20,6 +20,7 @@
 import android.text.Selection;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
@@ -33,6 +34,8 @@
   private final Context context;
   private Editable editable;
   private KeyboardInputConnection inputConnection;
+  private boolean keepFocus;
+  private boolean keyboardShowing;
 
   public KeyboardEditor(Context context) {
     this(context, null);
@@ -41,6 +44,8 @@
   public KeyboardEditor(Context context, AttributeSet attrs) {
     super(context, attrs);
     this.context = context;
+    this.keepFocus = false;
+    this.keyboardShowing = false;
     setFocusable(true);
   }
 
@@ -60,6 +65,23 @@
     return inputConnection;
   }
 
+  /** Update the keepFocus boolean. */
+  public void updateKeepFocus(boolean keepFocus) {
+    this.keepFocus = keepFocus;
+  }
+
+  /** If the on-screen keyboard is showing. */
+  public boolean isKeyboardShowing() {
+    return this.keyboardShowing;
+  }
+
+  /** Hide the on-screen keyboard if keepFocus is set to false. */
+  public void search() {
+    if (!this.keepFocus) {
+      hideKeyboard();
+    }
+  }
+
   /** Show the on-screen keyboard. */
   public void showKeyboard() {
     final Activity activity = (Activity) context;
@@ -74,7 +96,10 @@
 
             InputMethodManager imm =
                 (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
-            imm.showSoftInput(view, 0);
+            boolean success = imm.showSoftInput(view, 0);
+            if (success) {
+              view.updateKeyboardShowing(true);
+            }
           }
         });
   }
@@ -93,7 +118,10 @@
 
             InputMethodManager imm =
                 (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
-            imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+            boolean success = imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+            if (success) {
+              view.updateKeyboardShowing(false);
+            }
           }
         });
   }
@@ -104,4 +132,15 @@
         (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
     imm.updateSelection(view, selStart, selEnd, compStart, compEnd);
   }
+
+  /** Update the custom list of completions shown within the on-screen keyboard. */
+  public void updateCustomCompletions(CompletionInfo[] completions) {
+    InputMethodManager imm =
+        (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+    imm.displayCompletions(this, completions);
+  }
+
+  private void updateKeyboardShowing(boolean keyboardShowing) {
+    this.keyboardShowing = keyboardShowing;
+  }
 }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/KeyboardInputConnection.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/KeyboardInputConnection.java
index 99dc95a..54a5a46 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/KeyboardInputConnection.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/KeyboardInputConnection.java
@@ -69,8 +69,6 @@
 
     updateEditingState();
     Editable editable = getEditable();
-    // TODO: Implement composition events for composing text.
-    nativeSendText(editable.toString());
     return result;
   }
 
@@ -104,7 +102,14 @@
     int composingEnd = BaseInputConnection.getComposingSpanEnd(editable);
     keyboardEditor.updateSelection(
         keyboardEditor, selectionStart, selectionEnd, composingStart, composingEnd);
-    nativeSendText(editable.toString());
+
+    if (composingStart != -1) {
+      // Send the composing text as an input event with isComposing set to true.
+      nativeSendText(editable.toString().substring(composingStart, composingEnd), true);
+    } else {
+      // Send the committed text as an input event with isComposing set to false.
+      nativeSendText(editable.toString(), false);
+    }
   }
 
   /** Send text to the search bar and set the new cursor position. */
@@ -136,7 +141,7 @@
   public boolean sendKeyEvent(KeyEvent event) {
     if (event.getAction() == KeyEvent.ACTION_DOWN) {
       if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
-        keyboardEditor.hideKeyboard();
+        keyboardEditor.search();
       } else if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
         Editable editable = getEditable();
         int selStart = Selection.getSelectionStart(editable);
@@ -164,11 +169,10 @@
   @Override
   public boolean performEditorAction(int editorAction) {
     if (editorAction == EditorInfo.IME_ACTION_SEARCH) {
-      // TODO: Implement keep focus where the keyboard is only hidden if there are search results.
-      keyboardEditor.hideKeyboard();
+      keyboardEditor.search();
     }
     return true;
   }
 
-  public static native void nativeSendText(CharSequence text);
+  public static native void nativeSendText(CharSequence text, boolean isComposing);
 }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/PlatformError.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/PlatformError.java
index 4aa82e7..db0cae7 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/PlatformError.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/coat/PlatformError.java
@@ -26,13 +26,10 @@
 import android.support.annotation.IntDef;
 import dev.cobalt.util.Holder;
 import dev.cobalt.util.Log;
-import dev.cobalt.util.UsedByNative;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-/**
- * Shows an ErrorDialog to inform the user of a Starboard platform error.
- */
+/** Shows an ErrorDialog to inform the user of a Starboard platform error. */
 public class PlatformError
     implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
 
@@ -71,18 +68,19 @@
 
   /** Display the error. */
   public void raise() {
-    uiThreadHandler.post(new Runnable() {
-      @Override
-      public void run() {
-        showDialogOnUiThread();
-      }
-    });
+    uiThreadHandler.post(
+        new Runnable() {
+          @Override
+          public void run() {
+            showDialogOnUiThread();
+          }
+        });
   }
 
   private void showDialogOnUiThread() {
     Activity activity = activityHolder.get();
     if (activity == null) {
-      onCleared(CANCELLED, data);
+      sendResponse(CANCELLED, data);
       return;
     }
     ErrorDialog.Builder dialogBuilder = new ErrorDialog.Builder(activity);
@@ -97,28 +95,10 @@
         Log.e(TAG, "Unknown platform error " + errorType);
         return;
     }
-    dialog = dialogBuilder
-        .setButtonClickListener(this)
-        .setOnDismissListener(this)
-        .create();
+    dialog = dialogBuilder.setButtonClickListener(this).setOnDismissListener(this).create();
     dialog.show();
   }
 
-  /** Programmatically dismiss the error. */
-  @SuppressWarnings("unused")
-  @UsedByNative
-  public void clear() {
-    uiThreadHandler.post(
-        new Runnable() {
-          @Override
-          public void run() {
-            if (dialog != null) {
-              dialog.dismiss();
-            }
-          }
-        });
-  }
-
   @Override
   public void onClick(DialogInterface dialogInterface, int whichButton) {
     if (errorType == CONNECTION_ERROR) {
@@ -141,13 +121,13 @@
   @Override
   public void onDismiss(DialogInterface dialogInterface) {
     dialog = null;
-    onCleared(response, data);
+    sendResponse(response, data);
   }
 
   /** Informs Starboard when the error is dismissed. */
-  protected void onCleared(@PlatformError.Response int response, long data) {
-    nativeOnCleared(response, data);
+  protected void sendResponse(@PlatformError.Response int response, long data) {
+    nativeSendResponse(response, data);
   }
 
-  private native void nativeOnCleared(@PlatformError.Response int response, long data);
+  private native void nativeSendResponse(@PlatformError.Response int response, long data);
 }
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 6e7cb4b..bee6e6d 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
@@ -45,10 +45,6 @@
 import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
 import java.lang.reflect.Method;
-import java.net.InterfaceAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Locale;
 
@@ -62,6 +58,7 @@
     StarboardBridge getStarboardBridge();
   }
 
+  private CobaltSystemConfigChangeReceiver sysConfigChangeReceiver;
   private CobaltTextToSpeechHelper ttsHelper;
   private UserAuthorizer userAuthorizer;
   private FeedbackService feedbackService;
@@ -91,6 +88,9 @@
 
   private volatile boolean starboardStopped = false;
 
+  private final HashMap<String, CobaltService.Factory> cobaltServiceFactories = new HashMap<>();
+  private final HashMap<String, CobaltService> cobaltServices = new HashMap<>();
+
   public StarboardBridge(
       Context appContext,
       Holder<Activity> activityHolder,
@@ -107,6 +107,7 @@
     this.activityHolder = activityHolder;
     this.args = args;
     this.startDeepLink = startDeepLink;
+    this.sysConfigChangeReceiver = new CobaltSystemConfigChangeReceiver(appContext, stopRequester);
     this.ttsHelper = new CobaltTextToSpeechHelper(appContext, stopRequester);
     this.userAuthorizer = userAuthorizer;
     this.feedbackService = feedbackService;
@@ -123,12 +124,14 @@
   protected void onActivityStart(Activity activity, KeyboardEditor keyboardEditor) {
     activityHolder.set(activity);
     this.keyboardEditor = keyboardEditor;
+    sysConfigChangeReceiver.setForeground(true);
   }
 
   protected void onActivityStop(Activity activity) {
     if (activityHolder.get() == activity) {
       activityHolder.set(null);
     }
+    sysConfigChangeReceiver.setForeground(false);
   }
 
   protected void onActivityDestroy(Activity activity) {
@@ -149,6 +152,9 @@
     // whatever the web app wants to do with them as part of its start/resume logic.
     cobaltMediaSession.resume();
     feedbackService.connect();
+    for (CobaltService service : cobaltServices.values()) {
+      service.beforeStartOrResume();
+    }
   }
 
   @SuppressWarnings("unused")
@@ -160,6 +166,9 @@
     // can take their time suspending after that.
     cobaltMediaSession.suspend();
     feedbackService.disconnect();
+    for (CobaltService service : cobaltServices.values()) {
+      service.beforeSuspend();
+    }
   }
 
   @SuppressWarnings("unused")
@@ -168,6 +177,9 @@
     starboardStopped = true;
     ttsHelper.shutdown();
     userAuthorizer.shutdown();
+    for (CobaltService service : cobaltServices.values()) {
+      service.afterStopped();
+    }
     Activity activity = activityHolder.get();
     if (activity != null) {
       // Wait until the activity is destroyed to exit.
@@ -213,10 +225,9 @@
 
   @SuppressWarnings("unused")
   @UsedByNative
-  PlatformError raisePlatformError(@PlatformError.ErrorType int errorType, long data) {
+  void raisePlatformError(@PlatformError.ErrorType int errorType, long data) {
     PlatformError error = new PlatformError(activityHolder, errorType, data);
     error.raise();
-    return error;
   }
 
   /** Returns true if the native code is compiled for release (i.e. 'gold' build). */
@@ -270,48 +281,6 @@
     return appContext.getCacheDir().getAbsolutePath();
   }
 
-  /**
-   * Returns non-loopback network interface address, or null if none.
-   *
-   * <p>An IPv4 address will have only a 4 byte array, while an IPv6 address will have a 16 byte
-   * array.
-   *
-   * <p>A Java function to help implement Starboard's SbSocketGetLocalInterfaceAddress.
-   *
-   * <p>Required for platforms older than 24. Since 24, bionic includes getifaddrs() which can be
-   * used by the C layer directly.
-   */
-  @SuppressWarnings("unused")
-  @UsedByNative
-  byte[] getLocalInterfaceAddress() {
-    try {
-      Enumeration<NetworkInterface> it = NetworkInterface.getNetworkInterfaces();
-
-      while (it.hasMoreElements()) {
-        NetworkInterface ni = it.nextElement();
-        if (ni.isLoopback()) {
-          continue;
-        }
-        if (!ni.isUp()) {
-          continue;
-        }
-        if (ni.isPointToPoint()) {
-          continue;
-        }
-
-        for (InterfaceAddress ia : ni.getInterfaceAddresses()) {
-          // Just return the first address.
-          return ia.getAddress().getAddress();
-        }
-      }
-    } catch (SocketException ex) {
-      // TODO should we have a logging story that strips logs for production?
-      Log.w(TAG, "sbSocketGetLocalInterfaceAddress exception", ex);
-      return null;
-    }
-    return null;
-  }
-
   @SuppressWarnings("unused")
   @UsedByNative
   CobaltTextToSpeechHelper getTextToSpeechHelper() {
@@ -554,4 +523,40 @@
     }
     return false;
   }
+
+  public void registerCobaltService(CobaltService.Factory factory) {
+    cobaltServiceFactories.put(factory.getServiceName(), factory);
+  }
+
+  @SuppressWarnings("unused")
+  @UsedByNative
+  boolean hasCobaltService(String serviceName) {
+    return cobaltServiceFactories.get(serviceName) != null;
+  }
+
+  @SuppressWarnings("unused")
+  @UsedByNative
+  CobaltService openCobaltService(long nativeService, String serviceName) {
+    if (cobaltServices.get(serviceName) != null) {
+      // Attempting to re-open an already open service fails.
+      Log.e(TAG, String.format("Cannot open already open service %s", serviceName));
+      return null;
+    }
+    final CobaltService.Factory factory = cobaltServiceFactories.get(serviceName);
+    if (factory == null) {
+      Log.e(TAG, String.format("Cannot open unregistered service %s", serviceName));
+      return null;
+    }
+    CobaltService service = factory.createCobaltService(nativeService);
+    if (service != null) {
+      cobaltServices.put(serviceName, service);
+    }
+    return service;
+  }
+
+  @SuppressWarnings("unused")
+  @UsedByNative
+  void closeCobaltService(String serviceName) {
+    cobaltServices.remove(serviceName);
+  }
 }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
index 54d00ac..5f59062 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -26,7 +26,9 @@
 import dev.cobalt.util.UsedByNative;
 import java.nio.ByteBuffer;
 
-/** A wrapper of the android AudioTrack class. */
+// A wrapper of the android AudioTrack class.
+// Android AudioTrack would not start playing until the buffer is fully
+// filled once.
 @UsedByNative
 public class AudioTrackBridge {
   private AudioTrack audioTrack;
@@ -70,13 +72,25 @@
     }
     while (audioTrackBufferSize > 0) {
       try {
-        audioTrack =
-            new AudioTrack(
-                attributes,
-                format,
-                audioTrackBufferSize,
-                AudioTrack.MODE_STREAM,
-                AudioManager.AUDIO_SESSION_ID_GENERATE);
+        if (Build.VERSION.SDK_INT >= 26) {
+          audioTrack =
+              new AudioTrack.Builder()
+                  .setAudioAttributes(attributes)
+                  .setAudioFormat(format)
+                  .setBufferSizeInBytes(audioTrackBufferSize)
+                  .setTransferMode(AudioTrack.MODE_STREAM)
+                  .setSessionId(AudioManager.AUDIO_SESSION_ID_GENERATE)
+                  .setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
+                  .build();
+        } else {
+          audioTrack =
+              new AudioTrack(
+                  attributes,
+                  format,
+                  audioTrackBufferSize,
+                  AudioTrack.MODE_STREAM,
+                  AudioManager.AUDIO_SESSION_ID_GENERATE);
+        }
       } catch (Exception e) {
         audioTrack = null;
       }
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 5fd0a8b..3be35ef 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
@@ -71,7 +71,9 @@
   private static final int BITRATE_ADJUSTMENT_FPS = 30;
   private static final int MAXIMUM_INITIAL_FPS = 30;
 
+  private long mNativeMediaCodecBridge;
   private MediaCodec mMediaCodec;
+  private MediaCodec.Callback mCallback;
   private boolean mFlushed;
   private long mLastPresentationTimeUs;
   private final String mMime;
@@ -329,6 +331,7 @@
   }
 
   private MediaCodecBridge(
+      long nativeMediaCodecBridge,
       MediaCodec mediaCodec,
       String mime,
       boolean adaptivePlaybackSupported,
@@ -336,17 +339,73 @@
     if (mediaCodec == null) {
       throw new IllegalArgumentException();
     }
+    mNativeMediaCodecBridge = nativeMediaCodecBridge;
     mMediaCodec = mediaCodec;
     mMime = mime; // TODO: Delete the unused mMime field
     mLastPresentationTimeUs = 0;
     mFlushed = true;
     mAdaptivePlaybackSupported = adaptivePlaybackSupported;
     mBitrateAdjustmentType = bitrateAdjustmentType;
+    mCallback =
+        new MediaCodec.Callback() {
+          @Override
+          public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+            synchronized (this) {
+              if (mNativeMediaCodecBridge == 0) {
+                return;
+              }
+              nativeOnMediaCodecError(
+                  mNativeMediaCodecBridge,
+                  e.isRecoverable(),
+                  e.isTransient(),
+                  e.getDiagnosticInfo());
+            }
+          }
+
+          @Override
+          public void onInputBufferAvailable(MediaCodec codec, int index) {
+            synchronized (this) {
+              if (mNativeMediaCodecBridge == 0) {
+                return;
+              }
+              nativeOnMediaCodecInputBufferAvailable(mNativeMediaCodecBridge, index);
+            }
+          }
+
+          @Override
+          public void onOutputBufferAvailable(
+              MediaCodec codec, int index, MediaCodec.BufferInfo info) {
+            synchronized (this) {
+              if (mNativeMediaCodecBridge == 0) {
+                return;
+              }
+              nativeOnMediaCodecOutputBufferAvailable(
+                  mNativeMediaCodecBridge,
+                  index,
+                  info.flags,
+                  info.offset,
+                  info.presentationTimeUs,
+                  info.size);
+            }
+          }
+
+          @Override
+          public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+            synchronized (this) {
+              if (mNativeMediaCodecBridge == 0) {
+                return;
+              }
+              nativeOnMediaCodecOutputFormatChanged(mNativeMediaCodecBridge);
+            }
+          }
+        };
+    mMediaCodec.setCallback(mCallback);
   }
 
   @SuppressWarnings("unused")
   @UsedByNative
   public static MediaCodecBridge createAudioMediaCodecBridge(
+      long nativeMediaCodecBridge,
       String mime,
       boolean isSecure,
       boolean requireSoftwareCodec,
@@ -370,7 +429,8 @@
       return null;
     }
     MediaCodecBridge bridge =
-        new MediaCodecBridge(mediaCodec, mime, true, BitrateAdjustmentTypes.NO_ADJUSTMENT);
+        new MediaCodecBridge(
+            nativeMediaCodecBridge, mediaCodec, mime, true, BitrateAdjustmentTypes.NO_ADJUSTMENT);
 
     MediaFormat mediaFormat = createAudioFormat(mime, sampleRate, channelCount);
     setFrameHasADTSHeader(mediaFormat);
@@ -391,6 +451,7 @@
   @SuppressWarnings("unused")
   @UsedByNative
   public static MediaCodecBridge createVideoMediaCodecBridge(
+      long nativeMediaCodecBridge,
       String mime,
       boolean isSecure,
       boolean requireSoftwareCodec,
@@ -425,7 +486,8 @@
       return null;
     }
     MediaCodecBridge bridge =
-        new MediaCodecBridge(mediaCodec, mime, true, BitrateAdjustmentTypes.NO_ADJUSTMENT);
+        new MediaCodecBridge(
+            nativeMediaCodecBridge, mediaCodec, mime, true, BitrateAdjustmentTypes.NO_ADJUSTMENT);
     MediaFormat mediaFormat =
         createVideoDecoderFormat(mime, width, height, findVideoDecoderResult.videoCapabilities);
 
@@ -522,6 +584,9 @@
   @SuppressWarnings("unused")
   @UsedByNative
   private void stop() {
+    synchronized (mCallback) {
+      mNativeMediaCodecBridge = 0;
+    }
     try {
       mMediaCodec.stop();
     } catch (IllegalStateException e) {
@@ -950,4 +1015,23 @@
         return AudioFormat.CHANNEL_OUT_DEFAULT;
     }
   }
+
+  private native void nativeOnMediaCodecError(
+      long nativeMediaCodecBridge,
+      boolean isRecoverable,
+      boolean isTransient,
+      String diagnosticInfo);
+
+  private native void nativeOnMediaCodecInputBufferAvailable(
+      long nativeMediaCodecBridge, int bufferIndex);
+
+  private native void nativeOnMediaCodecOutputBufferAvailable(
+      long nativeMediaCodecBridge,
+      int bufferIndex,
+      int flags,
+      int offset,
+      long presentationTimeUs,
+      int size);
+
+  private native void nativeOnMediaCodecOutputFormatChanged(long nativeMediaCodecBridge);
 }
diff --git a/src/starboard/android/shared/application_android.cc b/src/starboard/android/shared/application_android.cc
index 8e8b2dc..a51dddd 100644
--- a/src/starboard/android/shared/application_android.cc
+++ b/src/starboard/android/shared/application_android.cc
@@ -404,8 +404,10 @@
   jobject j_keyboard_editor = env->CallStarboardObjectMethodOrAbort(
       "getKeyboardEditor", "()Ldev/cobalt/coat/KeyboardEditor;");
   env->CallVoidMethodOrAbort(j_keyboard_editor, "showKeyboard", "()V");
-  // TODO: Fire kSbEventTypeWindowSizeChange and
-  // kSbEventTypeOnScreenKeyboardShown if necessary.
+  int* data = new int;
+  *data = ticket;
+  Inject(new Event(kSbEventTypeOnScreenKeyboardShown, data,
+                   &DeleteDestructor<int>));
   return;
 }
 
@@ -415,8 +417,39 @@
   jobject j_keyboard_editor = env->CallStarboardObjectMethodOrAbort(
       "getKeyboardEditor", "()Ldev/cobalt/coat/KeyboardEditor;");
   env->CallVoidMethodOrAbort(j_keyboard_editor, "hideKeyboard", "()V");
-  // TODO: Fire kSbEventTypeWindowSizeChange and
-  // kSbEventTypeOnScreenKeyboardHidden if necessary.
+  int* data = new int;
+  *data = ticket;
+  Inject(new Event(kSbEventTypeOnScreenKeyboardHidden, data,
+                   &DeleteDestructor<int>));
+  return;
+}
+
+void ApplicationAndroid::SbWindowUpdateOnScreenKeyboardSuggestions(
+    SbWindow window,
+    const std::vector<std::string>& suggestions,
+    int ticket) {
+  JniEnvExt* env = JniEnvExt::Get();
+  jobjectArray completions = env->NewObjectArray(
+      suggestions.size(),
+      env->FindClass("android/view/inputmethod/CompletionInfo"), 0);
+  jstring str;
+  jobject j_completion_info;
+  for (size_t i = 0; i < suggestions.size(); i++) {
+    str = env->NewStringUTF(suggestions[i].c_str());
+    j_completion_info =
+        env->NewObjectOrAbort("android/view/inputmethod/CompletionInfo",
+                              "(JILjava/lang/CharSequence;)V", i, i, str);
+    env->SetObjectArrayElement(completions, i, j_completion_info);
+  }
+  jobject j_keyboard_editor = env->CallStarboardObjectMethodOrAbort(
+      "getKeyboardEditor", "()Ldev/cobalt/coat/KeyboardEditor;");
+  env->CallVoidMethodOrAbort(j_keyboard_editor, "updateCustomCompletions",
+                             "([Landroid/view/inputmethod/CompletionInfo;)V",
+                             completions);
+  int* data = new int;
+  *data = ticket;
+  Inject(new Event(kSbEventTypeOnScreenKeyboardSuggestionsUpdated, data,
+                   &DeleteDestructor<int>));
   return;
 }
 
@@ -424,10 +457,12 @@
 Java_dev_cobalt_coat_KeyboardInputConnection_nativeSendText(
     JniEnvExt* env,
     jobject unused_clazz,
-    jstring text) {
+    jstring text,
+    jboolean is_composing) {
   if (text) {
     std::string utf_str = env->GetStringStandardUTFOrAbort(text);
-    ApplicationAndroid::Get()->SbWindowSendInputEvent(utf_str.c_str());
+    ApplicationAndroid::Get()->SbWindowSendInputEvent(utf_str.c_str(),
+                                                      is_composing);
   }
 }
 
@@ -439,7 +474,8 @@
   ApplicationAndroid::DeleteDestructor<SbInputData>(ptr);
 }
 
-void ApplicationAndroid::SbWindowSendInputEvent(const char* input_text) {
+void ApplicationAndroid::SbWindowSendInputEvent(const char* input_text,
+                                                bool is_composing) {
   char* text = SbStringDuplicate(input_text);
   SbInputData* data = new SbInputData();
   SbMemorySet(data, 0, sizeof(*data));
@@ -447,6 +483,7 @@
   data->type = kSbInputEventTypeInput;
   data->device_type = kSbInputDeviceTypeOnScreenKeyboard;
   data->input_text = text;
+  data->is_composing = is_composing;
   Inject(new Event(kSbEventTypeInput, data, &DeleteSbInputDataWithText));
   return;
 }
diff --git a/src/starboard/android/shared/application_android.h b/src/starboard/android/shared/application_android.h
index 3631e03..ad7acb7 100644
--- a/src/starboard/android/shared/application_android.h
+++ b/src/starboard/android/shared/application_android.h
@@ -17,6 +17,8 @@
 
 #include <android/looper.h>
 #include <android/native_window.h>
+#include <string>
+#include <vector>
 
 #include "starboard/android/shared/input_events_generator.h"
 #include "starboard/android/shared/jni_env_ext.h"
@@ -78,7 +80,11 @@
                                     const char* input_text,
                                     int ticket);
   void SbWindowHideOnScreenKeyboard(SbWindow window, int ticket);
-  void SbWindowSendInputEvent(const char* input_text);
+  void SbWindowUpdateOnScreenKeyboardSuggestions(
+      SbWindow window,
+      const std::vector<std::string>& suggestions,
+      int ticket);
+  void SbWindowSendInputEvent(const char* input_text, bool is_composing);
 
  protected:
   // --- Application overrides ---
diff --git a/src/starboard/android/shared/audio_decoder.cc b/src/starboard/android/shared/audio_decoder.cc
index 5bd0905..e025b34 100644
--- a/src/starboard/android/shared/audio_decoder.cc
+++ b/src/starboard/android/shared/audio_decoder.cc
@@ -222,6 +222,8 @@
         dequeue_output_result.presentation_time_microseconds, size);
 
     SbMemoryCopy(decoded_audio->buffer(), data, size);
+    media_codec_bridge->ReleaseOutputBuffer(dequeue_output_result.index, false);
+
     {
       starboard::ScopedLock lock(decoded_audios_mutex_);
       decoded_audios_.push(decoded_audio);
@@ -229,9 +231,9 @@
                           << decoded_audios_.front()->timestamp();
     }
     Schedule(output_cb_);
+  } else {
+    media_codec_bridge->ReleaseOutputBuffer(dequeue_output_result.index, false);
   }
-
-  media_codec_bridge->ReleaseOutputBuffer(dequeue_output_result.index, false);
 }
 
 void AudioDecoder::RefreshOutputFormat(MediaCodecBridge* media_codec_bridge) {
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.cc b/src/starboard/android/shared/audio_track_audio_sink_type.cc
index 43cf47d..f60a668 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -218,33 +218,37 @@
 
 void AudioTrackAudioSink::AudioThreadFunc() {
   JniEnvExt* env = JniEnvExt::Get();
-  env->CallVoidMethodOrAbort(j_audio_track_bridge_, "play", "()V");
-
-  bool was_playing = true;
+  bool was_playing = false;
 
   while (!quit_) {
-    ScopedLocalJavaRef<jobject> j_audio_timestamp(
-        env->CallObjectMethodOrAbort(j_audio_track_bridge_, "getAudioTimestamp",
-                                     "()Landroid/media/AudioTimestamp;"));
+    int playback_head_position = 0;
+    SbTime frames_consumed_at = 0;
 
-    int playback_head_position =
-        env->GetLongFieldOrAbort(j_audio_timestamp.Get(), "framePosition", "J");
-    SbTime frames_consumed_at =
-        env->GetLongFieldOrAbort(j_audio_timestamp.Get(), "nanoTime", "J") /
-        1000;
+    if (was_playing) {
+      ScopedLocalJavaRef<jobject> j_audio_timestamp(
+          env->CallObjectMethodOrAbort(j_audio_track_bridge_,
+                                       "getAudioTimestamp",
+                                       "()Landroid/media/AudioTimestamp;"));
+      playback_head_position = env->GetLongFieldOrAbort(j_audio_timestamp.Get(),
+                                                        "framePosition", "J");
+      frames_consumed_at =
+          env->GetLongFieldOrAbort(j_audio_timestamp.Get(), "nanoTime", "J") /
+          1000;
 
-    SB_DCHECK(playback_head_position >= last_playback_head_position_);
+      SB_DCHECK(playback_head_position >= last_playback_head_position_);
 
-    playback_head_position =
-        std::max(playback_head_position, last_playback_head_position_);
-    int frames_consumed = playback_head_position - last_playback_head_position_;
-    last_playback_head_position_ = playback_head_position;
-    frames_consumed = std::min(frames_consumed, written_frames_);
+      playback_head_position =
+          std::max(playback_head_position, last_playback_head_position_);
+      int frames_consumed =
+          playback_head_position - last_playback_head_position_;
+      last_playback_head_position_ = playback_head_position;
+      frames_consumed = std::min(frames_consumed, written_frames_);
 
-    if (frames_consumed != 0) {
-      SB_DCHECK(frames_consumed >= 0);
-      consume_frame_func_(frames_consumed, frames_consumed_at, context_);
-      written_frames_ -= frames_consumed;
+      if (frames_consumed != 0) {
+        SB_DCHECK(frames_consumed >= 0);
+        consume_frame_func_(frames_consumed, frames_consumed_at, context_);
+        written_frames_ -= frames_consumed;
+      }
     }
 
     int frames_in_buffer;
@@ -253,7 +257,6 @@
     bool is_eos_reached;
     update_source_status_func_(&frames_in_buffer, &offset_in_frames,
                                &is_playing, &is_eos_reached, context_);
-
     {
       ScopedLock lock(mutex_);
       if (playback_rate_ == 0.0) {
@@ -290,6 +293,7 @@
     if (expected_written_frames == 0) {
       // It is possible that all the frames in buffer are written to the
       // soundcard, but those are not being consumed.
+      SbThreadSleep(10 * kSbTimeMillisecond);
       continue;
     }
     SB_DCHECK(expected_written_frames > 0);
diff --git a/src/starboard/android/shared/bionic/README.md b/src/starboard/android/shared/bionic/README.md
new file mode 100644
index 0000000..f54cfe7
--- /dev/null
+++ b/src/starboard/android/shared/bionic/README.md
@@ -0,0 +1,11 @@
+This is a partial fork of the Android bionic C library, taken from:
+
+https://android.googlesource.com/platform/bionic/+/5e62b34c0d6fa545b487b9b64fb4a04a0589bc13/libc
+
+
+The purpose is to build into the Android Starboard port necesary parts of bionic
+that are not available on all versions of Android that we support, including:
+
+* `ifaddrs` (and its dependencies) - Used by `SbSocketGetInterfaceAddress`, but
+  not available until API 24.
+
diff --git a/src/starboard/android/shared/bionic/bionic_netlink.cpp b/src/starboard/android/shared/bionic/bionic_netlink.cpp
new file mode 100644
index 0000000..f2449dc
--- /dev/null
+++ b/src/starboard/android/shared/bionic/bionic_netlink.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "bionic_netlink.h"
+
+#include <errno.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "private/ErrnoRestorer.h"
+
+NetlinkConnection::NetlinkConnection() {
+  fd_ = -1;
+
+  // The kernel keeps packets under 8KiB (NLMSG_GOODSIZE),
+  // but that's a bit too large to go on the stack.
+  size_ = 8192;
+  data_ = new char[size_];
+}
+
+NetlinkConnection::~NetlinkConnection() {
+  ErrnoRestorer errno_restorer;
+  if (fd_ != -1) close(fd_);
+  delete[] data_;
+}
+
+bool NetlinkConnection::SendRequest(int type) {
+  // Rather than force all callers to check for the unlikely event of being
+  // unable to allocate 8KiB, check here.
+  if (data_ == nullptr) return false;
+
+  // Did we open a netlink socket yet?
+  if (fd_ == -1) {
+    fd_ = socket(PF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
+    if (fd_ == -1) return false;
+  }
+
+  // Construct and send the message.
+  struct NetlinkMessage {
+    nlmsghdr hdr;
+    rtgenmsg msg;
+  } request;
+  memset(&request, 0, sizeof(request));
+  request.hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
+  request.hdr.nlmsg_type = type;
+  request.hdr.nlmsg_len = sizeof(request);
+  request.msg.rtgen_family = AF_UNSPEC; // All families.
+  return (TEMP_FAILURE_RETRY(send(fd_, &request, sizeof(request), 0)) == sizeof(request));
+}
+
+bool NetlinkConnection::ReadResponses(void callback(void*, nlmsghdr*), void* context) {
+  // Read through all the responses, handing interesting ones to the callback.
+  ssize_t bytes_read;
+  while ((bytes_read = TEMP_FAILURE_RETRY(recv(fd_, data_, size_, 0))) > 0) {
+    nlmsghdr* hdr = reinterpret_cast<nlmsghdr*>(data_);
+    for (; NLMSG_OK(hdr, static_cast<size_t>(bytes_read)); hdr = NLMSG_NEXT(hdr, bytes_read)) {
+      if (hdr->nlmsg_type == NLMSG_DONE) return true;
+      if (hdr->nlmsg_type == NLMSG_ERROR) {
+        nlmsgerr* err = reinterpret_cast<nlmsgerr*>(NLMSG_DATA(hdr));
+        errno = (hdr->nlmsg_len >= NLMSG_LENGTH(sizeof(nlmsgerr))) ? -err->error : EIO;
+        return false;
+      }
+      callback(context, hdr);
+    }
+  }
+
+  // We only get here if recv fails before we see a NLMSG_DONE.
+  return false;
+}
diff --git a/src/starboard/android/shared/bionic/bionic_netlink.h b/src/starboard/android/shared/bionic/bionic_netlink.h
new file mode 100644
index 0000000..a21200e
--- /dev/null
+++ b/src/starboard/android/shared/bionic/bionic_netlink.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+struct nlmsghdr;
+
+class NetlinkConnection {
+ public:
+  NetlinkConnection();
+  ~NetlinkConnection();
+
+  bool SendRequest(int type);
+  bool ReadResponses(void callback(void*, nlmsghdr*), void* context);
+
+ private:
+  int fd_;
+  char* data_;
+  size_t size_;
+};
diff --git a/src/starboard/android/shared/bionic/ifaddrs.cpp b/src/starboard/android/shared/bionic/ifaddrs.cpp
new file mode 100644
index 0000000..f5b080c
--- /dev/null
+++ b/src/starboard/android/shared/bionic/ifaddrs.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <ifaddrs.h>
+
+#include <errno.h>
+#include <linux/if_packet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "private/ErrnoRestorer.h"
+
+#include "bionic_netlink.h"
+
+// The public ifaddrs struct is full of pointers. Rather than track several
+// different allocations, we use a maximally-sized structure with the public
+// part at offset 0, and pointers into its hidden tail.
+struct ifaddrs_storage {
+  // Must come first, so that `ifaddrs_storage` is-a `ifaddrs`.
+  ifaddrs ifa;
+
+  // The interface index, so we can match RTM_NEWADDR messages with
+  // earlier RTM_NEWLINK messages (to copy the interface flags).
+  int interface_index;
+
+  // Storage for the pointers in `ifa`.
+  sockaddr_storage addr;
+  sockaddr_storage netmask;
+  sockaddr_storage ifa_ifu;
+  char name[IFNAMSIZ + 1];
+
+  explicit ifaddrs_storage(ifaddrs** list) {
+    memset(this, 0, sizeof(*this));
+
+    // push_front onto `list`.
+    ifa.ifa_next = *list;
+    *list = reinterpret_cast<ifaddrs*>(this);
+  }
+
+  void SetAddress(int family, const void* data, size_t byteCount) {
+    // The kernel currently uses the order IFA_ADDRESS, IFA_LOCAL, IFA_BROADCAST
+    // in inet_fill_ifaddr, but let's not assume that will always be true...
+    if (ifa.ifa_addr == nullptr) {
+      // This is an IFA_ADDRESS and haven't seen an IFA_LOCAL yet, so assume this is the
+      // local address. SetLocalAddress will fix things if we later see an IFA_LOCAL.
+      ifa.ifa_addr = CopyAddress(family, data, byteCount, &addr);
+    } else {
+      // We already saw an IFA_LOCAL, which implies this is a destination address.
+      ifa.ifa_dstaddr = CopyAddress(family, data, byteCount, &ifa_ifu);
+    }
+  }
+
+  void SetBroadcastAddress(int family, const void* data, size_t byteCount) {
+    // ifa_broadaddr and ifa_dstaddr overlap in a union. Unfortunately, it's possible
+    // to have an interface with both. Keeping the last thing the kernel gives us seems
+    // to be glibc 2.19's behavior too, so our choice is being source compatible with
+    // badly-written code that assumes ifa_broadaddr and ifa_dstaddr are interchangeable
+    // or supporting interfaces with both addresses configured. My assumption is that
+    // bad code is more common than weird network interfaces...
+    ifa.ifa_broadaddr = CopyAddress(family, data, byteCount, &ifa_ifu);
+  }
+
+  void SetLocalAddress(int family, const void* data, size_t byteCount) {
+    // The kernel source says "for point-to-point IFA_ADDRESS is DESTINATION address,
+    // local address is supplied in IFA_LOCAL attribute".
+    //   -- http://lxr.free-electrons.com/source/include/uapi/linux/if_addr.h#L17
+
+    // So copy any existing IFA_ADDRESS into ifa_dstaddr...
+    if (ifa.ifa_addr != nullptr) {
+      ifa.ifa_dstaddr = reinterpret_cast<sockaddr*>(memcpy(&ifa_ifu, &addr, sizeof(addr)));
+    }
+    // ...and then put this IFA_LOCAL into ifa_addr.
+    ifa.ifa_addr = CopyAddress(family, data, byteCount, &addr);
+  }
+
+  // Netlink gives us the prefix length as a bit count. We need to turn
+  // that into a BSD-compatible netmask represented by a sockaddr*.
+  void SetNetmask(int family, size_t prefix_length) {
+    // ...and work out the netmask from the prefix length.
+    netmask.ss_family = family;
+    uint8_t* dst = SockaddrBytes(family, &netmask);
+    memset(dst, 0xff, prefix_length / 8);
+    if ((prefix_length % 8) != 0) {
+      dst[prefix_length/8] = (0xff << (8 - (prefix_length % 8)));
+    }
+    ifa.ifa_netmask = reinterpret_cast<sockaddr*>(&netmask);
+  }
+
+  void SetPacketAttributes(int ifindex, unsigned short hatype, unsigned char halen) {
+    sockaddr_ll* sll = reinterpret_cast<sockaddr_ll*>(&addr);
+    sll->sll_ifindex = ifindex;
+    sll->sll_hatype = hatype;
+    sll->sll_halen = halen;
+  }
+
+ private:
+  sockaddr* CopyAddress(int family, const void* data, size_t byteCount, sockaddr_storage* ss) {
+    // Netlink gives us the address family in the header, and the
+    // sockaddr_in or sockaddr_in6 bytes as the payload. We need to
+    // stitch the two bits together into the sockaddr that's part of
+    // our portable interface.
+    ss->ss_family = family;
+    memcpy(SockaddrBytes(family, ss), data, byteCount);
+
+    // For IPv6 we might also have to set the scope id.
+    if (family == AF_INET6 && (IN6_IS_ADDR_LINKLOCAL(data) || IN6_IS_ADDR_MC_LINKLOCAL(data))) {
+      reinterpret_cast<sockaddr_in6*>(ss)->sin6_scope_id = interface_index;
+    }
+
+    return reinterpret_cast<sockaddr*>(ss);
+  }
+
+  // Returns a pointer to the first byte in the address data (which is
+  // stored in network byte order).
+  uint8_t* SockaddrBytes(int family, sockaddr_storage* ss) {
+    if (family == AF_INET) {
+      sockaddr_in* ss4 = reinterpret_cast<sockaddr_in*>(ss);
+      return reinterpret_cast<uint8_t*>(&ss4->sin_addr);
+    } else if (family == AF_INET6) {
+      sockaddr_in6* ss6 = reinterpret_cast<sockaddr_in6*>(ss);
+      return reinterpret_cast<uint8_t*>(&ss6->sin6_addr);
+    } else if (family == AF_PACKET) {
+      sockaddr_ll* sll = reinterpret_cast<sockaddr_ll*>(ss);
+      return reinterpret_cast<uint8_t*>(&sll->sll_addr);
+    }
+    return nullptr;
+  }
+};
+
+static void __getifaddrs_callback(void* context, nlmsghdr* hdr) {
+  ifaddrs** out = reinterpret_cast<ifaddrs**>(context);
+
+  if (hdr->nlmsg_type == RTM_NEWLINK) {
+    ifinfomsg* ifi = reinterpret_cast<ifinfomsg*>(NLMSG_DATA(hdr));
+
+    // Create a new ifaddr entry, and set the interface index and flags.
+    ifaddrs_storage* new_addr = new ifaddrs_storage(out);
+    new_addr->interface_index = ifi->ifi_index;
+    new_addr->ifa.ifa_flags = ifi->ifi_flags;
+
+    // Go through the various bits of information and find the name.
+    rtattr* rta = IFLA_RTA(ifi);
+    size_t rta_len = IFLA_PAYLOAD(hdr);
+    while (RTA_OK(rta, rta_len)) {
+      if (rta->rta_type == IFLA_ADDRESS) {
+          if (RTA_PAYLOAD(rta) < sizeof(new_addr->addr)) {
+            new_addr->SetAddress(AF_PACKET, RTA_DATA(rta), RTA_PAYLOAD(rta));
+            new_addr->SetPacketAttributes(ifi->ifi_index, ifi->ifi_type, RTA_PAYLOAD(rta));
+          }
+      } else if (rta->rta_type == IFLA_BROADCAST) {
+          if (RTA_PAYLOAD(rta) < sizeof(new_addr->ifa_ifu)) {
+            new_addr->SetBroadcastAddress(AF_PACKET, RTA_DATA(rta), RTA_PAYLOAD(rta));
+            new_addr->SetPacketAttributes(ifi->ifi_index, ifi->ifi_type, RTA_PAYLOAD(rta));
+          }
+      } else if (rta->rta_type == IFLA_IFNAME) {
+          if (RTA_PAYLOAD(rta) < sizeof(new_addr->name)) {
+            memcpy(new_addr->name, RTA_DATA(rta), RTA_PAYLOAD(rta));
+            new_addr->ifa.ifa_name = new_addr->name;
+          }
+      }
+      rta = RTA_NEXT(rta, rta_len);
+    }
+  } else if (hdr->nlmsg_type == RTM_NEWADDR) {
+    ifaddrmsg* msg = reinterpret_cast<ifaddrmsg*>(NLMSG_DATA(hdr));
+
+    // We should already know about this from an RTM_NEWLINK message.
+    const ifaddrs_storage* addr = reinterpret_cast<const ifaddrs_storage*>(*out);
+    while (addr != nullptr && addr->interface_index != static_cast<int>(msg->ifa_index)) {
+      addr = reinterpret_cast<const ifaddrs_storage*>(addr->ifa.ifa_next);
+    }
+    // If this is an unknown interface, ignore whatever we're being told about it.
+    if (addr == nullptr) return;
+
+    // Create a new ifaddr entry and copy what we already know.
+    ifaddrs_storage* new_addr = new ifaddrs_storage(out);
+    // We can just copy the name rather than look for IFA_LABEL.
+    strcpy(new_addr->name, addr->name);
+    new_addr->ifa.ifa_name = new_addr->name;
+    new_addr->ifa.ifa_flags = addr->ifa.ifa_flags;
+    new_addr->interface_index = addr->interface_index;
+
+    // Go through the various bits of information and find the address
+    // and any broadcast/destination address.
+    rtattr* rta = IFA_RTA(msg);
+    size_t rta_len = IFA_PAYLOAD(hdr);
+    while (RTA_OK(rta, rta_len)) {
+      if (rta->rta_type == IFA_ADDRESS) {
+        if (msg->ifa_family == AF_INET || msg->ifa_family == AF_INET6) {
+          new_addr->SetAddress(msg->ifa_family, RTA_DATA(rta), RTA_PAYLOAD(rta));
+          new_addr->SetNetmask(msg->ifa_family, msg->ifa_prefixlen);
+        }
+      } else if (rta->rta_type == IFA_BROADCAST) {
+        if (msg->ifa_family == AF_INET) {
+          new_addr->SetBroadcastAddress(msg->ifa_family, RTA_DATA(rta), RTA_PAYLOAD(rta));
+        }
+      } else if (rta->rta_type == IFA_LOCAL) {
+        if (msg->ifa_family == AF_INET || msg->ifa_family == AF_INET6) {
+          new_addr->SetLocalAddress(msg->ifa_family, RTA_DATA(rta), RTA_PAYLOAD(rta));
+        }
+      }
+      rta = RTA_NEXT(rta, rta_len);
+    }
+  }
+}
+
+int getifaddrs(ifaddrs** out) {
+  // We construct the result directly into `out`, so terminate the list.
+  *out = nullptr;
+
+  // Open the netlink socket and ask for all the links and addresses.
+  NetlinkConnection nc;
+  bool okay = nc.SendRequest(RTM_GETLINK) && nc.ReadResponses(__getifaddrs_callback, out) &&
+              nc.SendRequest(RTM_GETADDR) && nc.ReadResponses(__getifaddrs_callback, out);
+  if (!okay) {
+    freeifaddrs(*out);
+    // Ensure that callers crash if they forget to check for success.
+    *out = nullptr;
+    return -1;
+  }
+
+  return 0;
+}
+
+void freeifaddrs(ifaddrs* list) {
+  while (list != nullptr) {
+    ifaddrs* current = list;
+    list = list->ifa_next;
+    free(current);
+  }
+}
diff --git a/src/starboard/android/shared/bionic/ifaddrs.h b/src/starboard/android/shared/bionic/ifaddrs.h
new file mode 100644
index 0000000..9eaabbd
--- /dev/null
+++ b/src/starboard/android/shared/bionic/ifaddrs.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#pragma once
+
+/**
+ * @file ifaddrs.h
+ * @brief Access to network interface addresses.
+ */
+
+#include <sys/cdefs.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+__BEGIN_DECLS
+
+/**
+ * Returned by getifaddrs() and freed by freeifaddrs().
+ */
+struct ifaddrs {
+  /** Pointer to the next element in the linked list. */
+  struct ifaddrs* ifa_next;
+
+  /** Interface name. */
+  char* ifa_name;
+  /** Interface flags (like `SIOCGIFFLAGS`). */
+  unsigned int ifa_flags;
+  /** Interface address. */
+  struct sockaddr* ifa_addr;
+  /** Interface netmask. */
+  struct sockaddr* ifa_netmask;
+
+  union {
+    /** Interface broadcast address (if IFF_BROADCAST is set). */
+    struct sockaddr* ifu_broadaddr;
+    /** Interface destination address (if IFF_POINTOPOINT is set). */
+    struct sockaddr* ifu_dstaddr;
+  } ifa_ifu;
+
+  /** Unused. */
+  void* ifa_data;
+};
+
+/** Synonym for `ifa_ifu.ifu_broadaddr` in `struct ifaddrs`. */
+#define ifa_broadaddr ifa_ifu.ifu_broadaddr
+/** Synonym for `ifa_ifu.ifu_dstaddr` in `struct ifaddrs`. */
+#define ifa_dstaddr ifa_ifu.ifu_dstaddr
+
+/**
+ * [getifaddrs(3)](http://man7.org/linux/man-pages/man3/getifaddrs.3.html) creates a linked list
+ * of `struct ifaddrs`. The list must be freed by freeifaddrs().
+ *
+ * Returns 0 and stores the list in `*__list_ptr` on success,
+ * and returns -1 and sets `errno` on failure.
+ *
+ * Available since API level 24.
+ */
+int getifaddrs(struct ifaddrs** __list_ptr) __INTRODUCED_IN(24);
+
+/**
+ * [freeifaddrs(3)](http://man7.org/linux/man-pages/man3/freeifaddrs.3.html) frees a linked list
+ * of `struct ifaddrs` returned by getifaddrs().
+ *
+ * Available since API level 24.
+ */
+void freeifaddrs(struct ifaddrs* __ptr) __INTRODUCED_IN(24);
+
+__END_DECLS
diff --git a/src/starboard/android/shared/bionic/net_if.cpp b/src/starboard/android/shared/bionic/net_if.cpp
new file mode 100644
index 0000000..db9c9ea
--- /dev/null
+++ b/src/starboard/android/shared/bionic/net_if.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <net/if.h>
+
+#include <errno.h>
+#include <ifaddrs.h>
+#include <linux/if_packet.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/sockios.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "private/ErrnoRestorer.h"
+
+#include "bionic_netlink.h"
+
+char* if_indextoname(unsigned ifindex, char* ifname) {
+  int s = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, 0);
+  if (s == -1) return nullptr;
+
+  struct ifreq ifr;
+  memset(&ifr, 0, sizeof(ifr));
+  ifr.ifr_ifindex = ifindex;
+
+  int rc = ioctl(s, SIOCGIFNAME, &ifr);
+  ErrnoRestorer errno_restorer;
+  close(s);
+  return (rc == -1) ? nullptr : strncpy(ifname, ifr.ifr_name, IFNAMSIZ);
+}
+
+unsigned if_nametoindex(const char* ifname) {
+  int s = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, 0);
+  if (s == -1) return 0;
+
+  struct ifreq ifr;
+  memset(&ifr, 0, sizeof(ifr));
+  strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+  ifr.ifr_name[IFNAMSIZ - 1] = 0;
+
+  int rc = ioctl(s, SIOCGIFINDEX, &ifr);
+  ErrnoRestorer errno_restorer;
+  close(s);
+  return (rc == -1) ? 0 : ifr.ifr_ifindex;
+}
+
+struct if_list {
+  if_list* next;
+  struct if_nameindex data;
+
+  explicit if_list(if_list** list) {
+    // push_front onto `list`.
+    next = *list;
+    *list = this;
+  }
+
+  static void Free(if_list* list, bool names_too) {
+    while (list) {
+      if_list* it = list;
+      list = it->next;
+      if (names_too) free(it->data.if_name);
+      free(it);
+    }
+  }
+};
+
+static void __if_nameindex_callback(void* context, nlmsghdr* hdr) {
+  if_list** list = reinterpret_cast<if_list**>(context);
+  if (hdr->nlmsg_type == RTM_NEWLINK) {
+    ifinfomsg* ifi = reinterpret_cast<ifinfomsg*>(NLMSG_DATA(hdr));
+
+    // Create a new entry and set the interface index.
+    if_list* new_link = new if_list(list);
+    new_link->data.if_index = ifi->ifi_index;
+
+    // Go through the various bits of information and find the name.
+    rtattr* rta = IFLA_RTA(ifi);
+    size_t rta_len = IFLA_PAYLOAD(hdr);
+    while (RTA_OK(rta, rta_len)) {
+      if (rta->rta_type == IFLA_IFNAME) {
+        new_link->data.if_name = strndup(reinterpret_cast<char*>(RTA_DATA(rta)), RTA_PAYLOAD(rta));
+      }
+      rta = RTA_NEXT(rta, rta_len);
+    }
+  }
+}
+
+struct if_nameindex* if_nameindex() {
+  if_list* list = nullptr;
+
+  // Open the netlink socket and ask for all the links;
+  NetlinkConnection nc;
+  bool okay = nc.SendRequest(RTM_GETLINK) && nc.ReadResponses(__if_nameindex_callback, &list);
+  if (!okay) {
+    if_list::Free(list, true);
+    return nullptr;
+  }
+
+  // Count the interfaces.
+  size_t interface_count = 0;
+  for (if_list* it = list; it != nullptr; it = it->next) {
+    ++interface_count;
+  }
+
+  // Build the array POSIX requires us to return.
+  struct if_nameindex* result = new struct if_nameindex[interface_count + 1];
+  if (result) {
+    struct if_nameindex* out = result;
+    for (if_list* it = list; it != nullptr; it = it->next) {
+      out->if_index = it->data.if_index;
+      out->if_name = it->data.if_name;
+      ++out;
+    }
+    out->if_index = 0;
+    out->if_name = nullptr;
+  }
+
+  // Free temporary storage.
+  if_list::Free(list, false);
+
+  return result;
+}
+
+void if_freenameindex(struct if_nameindex* array) {
+  if (array == nullptr) return;
+
+  struct if_nameindex* ptr = array;
+  while (ptr->if_index != 0 || ptr->if_name != nullptr) {
+    free(ptr->if_name);
+    ++ptr;
+  }
+
+  delete[] array;
+}
diff --git a/src/starboard/android/shared/bionic/private/ErrnoRestorer.h b/src/starboard/android/shared/bionic/private/ErrnoRestorer.h
new file mode 100644
index 0000000..52e115a
--- /dev/null
+++ b/src/starboard/android/shared/bionic/private/ErrnoRestorer.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <errno.h>
+
+#include "bionic_macros.h"
+
+class ErrnoRestorer {
+ public:
+  explicit ErrnoRestorer() : saved_errno_(errno) {
+  }
+
+  ~ErrnoRestorer() {
+    errno = saved_errno_;
+  }
+
+  void override(int new_errno) {
+    saved_errno_ = new_errno;
+  }
+
+ private:
+  int saved_errno_;
+
+  BIONIC_DISALLOW_COPY_AND_ASSIGN(ErrnoRestorer);
+};
diff --git a/src/starboard/android/shared/bionic/private/bionic_macros.h b/src/starboard/android/shared/bionic/private/bionic_macros.h
new file mode 100644
index 00